aboutsummaryrefslogtreecommitdiff
path: root/src/ejabberd_config.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/ejabberd_config.erl')
-rw-r--r--src/ejabberd_config.erl266
1 files changed, 224 insertions, 42 deletions
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 5d1df5056..6f7368b7c 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -5,7 +5,7 @@
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@@ -36,13 +36,16 @@
prepare_opt_val/4, convert_table_to_binary/5,
transform_options/1, collect_options/1,
convert_to_yaml/1, convert_to_yaml/2,
- env_binary_to_list/2]).
+ env_binary_to_list/2, opt_type/1, may_hide_data/1]).
+
+-export([start/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_config.hrl").
-include_lib("kernel/include/file.hrl").
+-callback opt_type(atom()) -> function() | [atom()].
%% @type macro() = {macro_key(), macro_value()}
@@ -51,8 +54,48 @@
%% @type macro_value() = term().
-
start() ->
+ mnesia_init(),
+ Config = get_ejabberd_config_path(),
+ State0 = read_file(Config),
+ State1 = hosts_to_start(State0),
+ State2 = validate_opts(State1),
+ %% This start time is used by mod_last:
+ UnixTime = p1_time_compat:system_time(seconds),
+ SharedKey = case erlang:get_cookie() of
+ nocookie ->
+ p1_sha:sha(randoms:get_string());
+ Cookie ->
+ p1_sha:sha(jlib:atom_to_binary(Cookie))
+ end,
+ State3 = set_option({node_start, global}, UnixTime, State2),
+ State4 = set_option({shared_key, global}, SharedKey, State3),
+ set_opts(State4).
+
+%% When starting ejabberd for testing, we sometimes want to start a
+%% subset of hosts from the one define in the config file.
+%% This function override the host list read from config file by the
+%% one we provide.
+%% Hosts to start are defined in an ejabberd application environment
+%% variable 'hosts' to make it easy to ignore some host in config
+%% file.
+hosts_to_start(State) ->
+ case application:get_env(ejabberd, hosts) of
+ undefined ->
+ %% Start all hosts as defined in config file
+ State;
+ {ok, Hosts} ->
+ set_hosts_in_options(Hosts, State)
+ end.
+
+%% @private
+%% At the moment, these functions are mainly used to setup unit tests.
+-spec(start/2 :: (Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok).
+start(Hosts, Opts) ->
+ mnesia_init(),
+ set_opts(#state{hosts = Hosts, opts = Opts}).
+
+mnesia_init() ->
case catch mnesia:table_info(local_config, storage_type) of
disc_copies ->
mnesia:delete_table(local_config);
@@ -63,21 +106,7 @@ start() ->
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, local_config)}]),
- mnesia:add_table_copy(local_config, node(), ram_copies),
- Config = get_ejabberd_config_path(),
- State = read_file(Config),
- %% This start time is used by mod_last:
- {MegaSecs, Secs, _} = now(),
- UnixTime = MegaSecs*1000000 + Secs,
- SharedKey = case erlang:get_cookie() of
- nocookie ->
- p1_sha:sha(randoms:get_string());
- Cookie ->
- p1_sha:sha(jlib:atom_to_binary(Cookie))
- end,
- State1 = set_option({node_start, global}, UnixTime, State),
- State2 = set_option({shared_key, global}, SharedKey, State1),
- set_opts(State2).
+ mnesia:add_table_copy(local_config, node(), ram_copies).
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
@@ -111,10 +140,11 @@ get_env_config() ->
%% @doc Read the ejabberd configuration file.
%% It also includes additional configuration files and replaces macros.
%% This function will crash if finds some error in the configuration file.
-%% @spec (File::string()) -> #state{}.
+%% @spec (File::string()) -> #state{}
read_file(File) ->
read_file(File, [{replace_macros, true},
- {include_files, true}]).
+ {include_files, true},
+ {include_modules_configs, true}]).
read_file(File, Opts) ->
Terms1 = get_plain_terms_file(File, Opts),
@@ -160,7 +190,7 @@ convert_to_yaml(File, Output) ->
fun({Host, Opts1}) ->
{host_config, [{Host, Opts1}]}
end, HOpts),
- Data = p1_yaml:encode(lists:reverse(NewOpts)),
+ Data = fast_yaml:encode(lists:reverse(NewOpts)),
case Output of
stdout ->
io:format("~s~n", [Data]);
@@ -189,7 +219,6 @@ env_binary_to_list(Application, Parameter) ->
%% Returns a list of plain terms,
%% in which the options 'include_config_file' were parsed
%% and the terms in those files were included.
-%% @spec(string()) -> [term()]
%% @spec(iolist()) -> [term()]
get_plain_terms_file(File) ->
get_plain_terms_file(File, [{include_files, true}]).
@@ -200,7 +229,17 @@ get_plain_terms_file(File1, Opts) ->
File = get_absolute_path(File1),
case consult(File) of
{ok, Terms} ->
- BinTerms = strings_to_binary(Terms),
+ BinTerms1 = strings_to_binary(Terms),
+ ModInc = case proplists:get_bool(include_modules_configs, Opts) of
+ true ->
+ Files = [{filename:rootname(filename:basename(F)), F}
+ || F <- filelib:wildcard(ext_mod:config_dir() ++ "/*.{yml,yaml}")
+ ++ filelib:wildcard(ext_mod:modules_dir() ++ "/*/conf/*.{yml,yaml}")],
+ [proplists:get_value(F,Files) || F <- proplists:get_keys(Files)];
+ _ ->
+ []
+ end,
+ BinTerms = BinTerms1 ++ [{include_config_file, list_to_binary(V)} || V <- ModInc],
case proplists:get_bool(include_files, Opts) of
true ->
include_config_files(BinTerms);
@@ -215,14 +254,14 @@ get_plain_terms_file(File1, Opts) ->
consult(File) ->
case filename:extension(File) of
Ex when (Ex == ".yml") or (Ex == ".yaml") ->
- case p1_yaml:decode_from_file(File, [plain_as_atom]) of
+ case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, []} ->
{ok, []};
{ok, [Document|_]} ->
{ok, parserl(Document)};
{error, Err} ->
Msg1 = "Cannot load " ++ File ++ ": ",
- Msg2 = p1_yaml:format_error(Err),
+ Msg2 = fast_yaml:format_error(Err),
{error, Msg1 ++ Msg2}
end;
_ ->
@@ -266,7 +305,7 @@ search_hosts(Term, State) ->
{host, Host} ->
if
State#state.hosts == [] ->
- add_hosts_to_option([Host], State);
+ set_hosts_in_options([Host], State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
@@ -275,7 +314,7 @@ search_hosts(Term, State) ->
{hosts, Hosts} ->
if
State#state.hosts == [] ->
- add_hosts_to_option(Hosts, State);
+ set_hosts_in_options(Hosts, State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
@@ -285,16 +324,19 @@ search_hosts(Term, State) ->
State
end.
-add_hosts_to_option(Hosts, State) ->
+set_hosts_in_options(Hosts, State) ->
PrepHosts = normalize_hosts(Hosts),
- set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts}).
+ NewOpts = lists:filter(fun({local_config,{hosts,global},_}) -> false;
+ (_) -> true
+ end, State#state.opts),
+ set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts, opts = NewOpts}).
normalize_hosts(Hosts) ->
normalize_hosts(Hosts,[]).
normalize_hosts([], PrepHosts) ->
lists:reverse(PrepHosts);
normalize_hosts([Host|Hosts], PrepHosts) ->
- case jlib:nodeprep(iolist_to_binary(Host)) of
+ case jid:nodeprep(iolist_to_binary(Host)) of
error ->
?ERROR_MSG("Can't load config file: "
"invalid host name [~p]", [Host]),
@@ -358,6 +400,62 @@ exit_or_halt(ExitText) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Support for 'include_config_file'
+get_config_option_key(Name, Val) ->
+ if Name == listen ->
+ case Val of
+ {{Port, IP, Trans}, _Mod, _Opts} ->
+ {Port, IP, Trans};
+ {{Port, Trans}, _Mod, _Opts} when Trans == tcp; Trans == udp ->
+ {Port, {0,0,0,0}, Trans};
+ {{Port, IP}, _Mod, _Opts} ->
+ {Port, IP, tcp};
+ {Port, _Mod, _Opts} ->
+ {Port, {0,0,0,0}, tcp};
+ V when is_list(V) ->
+ lists:foldl(
+ fun({port, Port}, {_, IP, T}) ->
+ {Port, IP, T};
+ ({ip, IP}, {Port, _, T}) ->
+ {Port, IP, T};
+ ({transport, T}, {Port, IP, _}) ->
+ {Port, IP, T};
+ (_, Res) ->
+ Res
+ end, {5222, {0,0,0,0}, tcp}, Val)
+ end;
+ is_tuple(Val) ->
+ element(1, Val);
+ true ->
+ Val
+ end.
+
+maps_to_lists(IMap) ->
+ maps:fold(fun(Name, Map, Res) when Name == host_config orelse Name == append_host_config ->
+ [{Name, [{Host, maps_to_lists(SMap)} || {Host,SMap} <- maps:values(Map)]} | Res];
+ (Name, Map, Res) when is_map(Map) ->
+ [{Name, maps:values(Map)} | Res];
+ (Name, Val, Res) ->
+ [{Name, Val} | Res]
+ end, [], IMap).
+
+merge_configs(Terms, ResMap) ->
+ lists:foldl(fun({Name, Val}, Map) when is_list(Val), Name =/= auth_method ->
+ Old = maps:get(Name, Map, #{}),
+ New = lists:foldl(fun(SVal, OMap) ->
+ NVal = if Name == host_config orelse Name == append_host_config ->
+ {Host, Opts} = SVal,
+ {_, SubMap} = maps:get(Host, OMap, {Host, #{}}),
+ {Host, merge_configs(Opts, SubMap)};
+ true ->
+ SVal
+ end,
+ maps:put(get_config_option_key(Name, SVal), NVal, OMap)
+ end, Old, Val),
+ maps:put(Name, New, Map);
+ ({Name, Val}, Map) ->
+ maps:put(Name, Val, Map)
+ end, ResMap, Terms).
+
%% @doc Include additional configuration files in the list of terms.
%% @spec ([term()]) -> [term()]
include_config_files(Terms) ->
@@ -374,7 +472,10 @@ include_config_files(Terms) ->
fun({File, Opts}) ->
include_config_file(File, Opts)
end, lists:flatten(FileOpts)),
- Terms1 ++ Terms2.
+
+ M1 = merge_configs(transform_terms(Terms1), #{}),
+ M2 = merge_configs(transform_terms(Terms2), M1),
+ maps_to_lists(M2).
transform_include_option({include_config_file, File}) when is_list(File) ->
case is_string(File) of
@@ -452,11 +553,11 @@ split_terms_macros(Terms) ->
lists:foldl(
fun(Term, {TOs, Ms}) ->
case Term of
- {define_macro, Key, Value} ->
+ {define_macro, Key, Value} ->
case is_correct_macro({Key, Value}) of
- true ->
+ true ->
{TOs, Ms++[{Key, Value}]};
- false ->
+ false ->
exit({macro_not_properly_defined, Term})
end;
{define_macro, KeyVals} ->
@@ -686,6 +787,46 @@ get_option(Opt, F, Default) ->
end
end.
+get_modules_with_options() ->
+ {ok, Mods} = application:get_key(ejabberd, modules),
+ ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
+ lists:foldl(
+ fun(Mod, D) ->
+ case catch Mod:opt_type('') of
+ Opts when is_list(Opts) ->
+ lists:foldl(
+ fun(Opt, Acc) ->
+ dict:append(Opt, Mod, Acc)
+ end, D, Opts);
+ {'EXIT', {undef, _}} ->
+ D
+ end
+ end, dict:new(), [?MODULE|ExtMods++Mods]).
+
+validate_opts(#state{opts = Opts} = State) ->
+ ModOpts = get_modules_with_options(),
+ NewOpts = lists:filter(
+ fun(#local_config{key = {Opt, _Host}, value = Val}) ->
+ case dict:find(Opt, ModOpts) of
+ {ok, [Mod|_]} ->
+ VFun = Mod:opt_type(Opt),
+ case catch VFun(Val) of
+ {'EXIT', _} ->
+ ?ERROR_MSG("ignoring option '~s' with "
+ "invalid value: ~p",
+ [Opt, Val]),
+ false;
+ _ ->
+ true
+ end;
+ _ ->
+ ?ERROR_MSG("unknown option '~s' will be likely"
+ " ignored", [Opt]),
+ true
+ end
+ end, Opts),
+ State#state{opts = NewOpts}.
+
-spec get_vh_by_auth_method(atom()) -> [binary()].
%% Return the list of hosts handled by a given module
@@ -739,20 +880,31 @@ replace_module(mod_roster_odbc) -> {mod_roster, odbc};
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
+replace_module(mod_pubsub_odbc) -> {mod_pubsub, odbc};
replace_module(Module) ->
case is_elixir_module(Module) of
true -> expand_elixir_module(Module);
false -> Module
end.
-replace_modules(Modules) -> lists:map( fun({Module, Opts}) -> case
- replace_module(Module) of {NewModule, DBType} ->
- emit_deprecation_warning(Module, NewModule, DBType), NewOpts =
- [{db_type, DBType} | lists:keydelete(db_type, 1, Opts)],
- {NewModule, transform_module_options(Module, NewOpts)}; NewModule
- -> if Module /= NewModule -> emit_deprecation_warning(Module,
- NewModule); true -> ok end, {NewModule,
- transform_module_options(Module, Opts)} end end, Modules).
+replace_modules(Modules) ->
+ lists:map(
+ fun({Module, Opts}) ->
+ case replace_module(Module) of
+ {NewModule, DBType} ->
+ emit_deprecation_warning(Module, NewModule, DBType),
+ NewOpts = [{db_type, DBType} |
+ lists:keydelete(db_type, 1, Opts)],
+ {NewModule, transform_module_options(Module, NewOpts)};
+ NewModule ->
+ if Module /= NewModule ->
+ emit_deprecation_warning(Module, NewModule);
+ true ->
+ ok
+ end,
+ {NewModule, transform_module_options(Module, Opts)}
+ end
+ end, Modules).
%% Elixir module naming
%% ====================
@@ -1062,3 +1214,33 @@ emit_deprecation_warning(Module, NewModule) ->
?WARNING_MSG("Module ~s is deprecated, use ~s instead",
[Module, NewModule])
end.
+
+opt_type(hide_sensitive_log_data) ->
+ fun (H) when is_boolean(H) -> H end;
+opt_type(hosts) ->
+ fun(L) when is_list(L) ->
+ lists:map(
+ fun(H) ->
+ iolist_to_binary(H)
+ end, L)
+ end;
+opt_type(language) ->
+ fun iolist_to_binary/1;
+opt_type(_) ->
+ [hide_sensitive_log_data, hosts, language].
+
+-spec may_hide_data(string()) -> string();
+ (binary()) -> binary().
+
+may_hide_data(Data) ->
+ case ejabberd_config:get_option(
+ hide_sensitive_log_data,
+ fun(false) -> false;
+ (true) -> true
+ end,
+ false) of
+ false ->
+ Data;
+ true ->
+ "hidden_by_ejabberd"
+ end.