24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-14 22:00:16 +02:00
xmpp.chapril.org-ejabberd/src/ejabberd_config.erl
Evgeniy Khramtsov 35a076c251 Stop ejabberd initialization on invalid/unknown options
Since now, ejabberd doesn't ignore unknown options and doesn't
allow to have options with malformed values. The rationale for
this is to avoid unexpected behaviour during runtime, i.e. to
conform to "fail early" approach. Note that it's safe to reload
a configuration with potentialy invalid and/or unknown options:
this will not halt ejabberd, but will only prevent the configuration
from loading.

***NOTE FOR PACKAGE BUILDERS***
This new behaviour should be documented in the upgrade notes.
2018-05-09 11:44:24 +03:00

1498 lines
49 KiB
Erlang

%%%----------------------------------------------------------------------
%%% File : ejabberd_config.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Load config file
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_config).
-author('alexey@process-one.net').
-export([start/0, load_file/1, reload_file/0, read_file/1,
get_option/1, get_option/2, add_option/2, has_option/1,
get_version/0, get_myhosts/0, get_mylang/0, get_lang/1,
get_ejabberd_config_path/0, is_using_elixir_config/0,
prepare_opt_val/4, transform_options/1, collect_options/1,
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
env_binary_to_list/2, opt_type/1, may_hide_data/1,
is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1,
default_db/1, default_db/2, default_ram_db/1, default_ram_db/2,
default_queue_type/1, queue_dir/0, fsm_limit_opts/1,
use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1,
codec_options/1, get_plain_terms_file/2, negotiation_timeout/0]).
-export([start/2]).
%% The following functions are deprecated.
-export([add_global_option/2, add_local_option/2,
get_global_option/2, get_local_option/2,
get_global_option/3, get_local_option/3,
get_option/3]).
-export([is_file_readable/1]).
-deprecated([{add_global_option, 2}, {add_local_option, 2},
{get_global_option, 2}, {get_local_option, 2},
{get_global_option, 3}, {get_local_option, 3},
{get_option, 3}, {is_file_readable, 1}]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_config.hrl").
-include_lib("kernel/include/file.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-callback opt_type(atom()) -> function() | [atom()].
-type bad_option() :: invalid_option | unknown_option.
-spec start() -> ok | {error, bad_option()}.
start() ->
ConfigFile = get_ejabberd_config_path(),
?INFO_MSG("Loading configuration from ~s", [ConfigFile]),
catch ets:new(ejabberd_options,
[named_table, public, {read_concurrency, true}]),
catch ets:new(ejabberd_db_modules,
[named_table, public, {read_concurrency, true}]),
case load_file(ConfigFile) of
{ok, State1} ->
UnixTime = p1_time_compat:system_time(seconds),
SharedKey = case erlang:get_cookie() of
nocookie ->
str:sha(randoms:get_string());
Cookie ->
str:sha(misc:atom_to_binary(Cookie))
end,
State2 = set_option({node_start, global}, UnixTime, State1),
State3 = set_option({shared_key, global}, SharedKey, State2),
set_opts(State3),
ok;
{error, _} = Err ->
?ERROR_MSG("Failed to load configuration file ~s", [ConfigFile]),
Err
end.
%% 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(Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok.
start(Hosts, Opts) ->
catch ets:new(ejabberd_options,
[named_table, public, {read_concurrency, true}]),
catch ets:new(ejabberd_db_modules,
[named_table, public, {read_concurrency, true}]),
UnixTime = p1_time_compat:system_time(seconds),
SharedKey = case erlang:get_cookie() of
nocookie ->
str:sha(randoms:get_string());
Cookie ->
str:sha(misc:atom_to_binary(Cookie))
end,
State1 = #state{opts = Opts},
State2 = set_option({node_start, global}, UnixTime, State1),
State3 = set_option({shared_key, global}, SharedKey, State2),
set_opts(set_hosts_in_options(Hosts, State3)),
ok.
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
%% It can also be specified with the environtment variable EJABBERD_CONFIG_PATH.
%% If not specified, the default value 'ejabberd.yml' is assumed.
%% @spec () -> string()
get_ejabberd_config_path() ->
case get_env_config() of
{ok, Path} -> Path;
undefined ->
case os:getenv("EJABBERD_CONFIG_PATH") of
false ->
?CONFIG_PATH;
Path ->
Path
end
end.
-spec get_env_config() -> {ok, string()} | undefined.
get_env_config() ->
%% First case: the filename can be specified with: erl -config "/path/to/ejabberd.yml".
case application:get_env(ejabberd, config) of
R = {ok, _Path} -> R;
undefined ->
%% Second case for embbeding ejabberd in another app, for example for Elixir:
%% config :ejabberd,
%% file: "config/ejabberd.yml"
application:get_env(ejabberd, file)
end.
%% @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{}
read_file(File) ->
read_file(File, [{replace_macros, true},
{include_files, true},
{include_modules_configs, true}]).
read_file(File, Opts) ->
Terms1 = case is_elixir_enabled() of
true ->
case 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(File) of
true ->
'Elixir.Ejabberd.Config':init(File),
'Elixir.Ejabberd.Config':get_ejabberd_opts();
false ->
get_plain_terms_file(File, Opts)
end;
false ->
get_plain_terms_file(File, Opts)
end,
Terms_macros = case proplists:get_bool(replace_macros, Opts) of
true -> replace_macros(Terms1);
false -> Terms1
end,
Terms = transform_terms(Terms_macros),
State = lists:foldl(fun search_hosts/2, #state{}, Terms),
{Head, Tail} = lists:partition(
fun({host_config, _}) -> false;
({append_host_config, _}) -> false;
(_) -> true
end, Terms),
State1 = lists:foldl(fun process_term/2, State, Head ++ Tail),
State1#state{opts = compact(State1#state.opts)}.
-spec load_file(string()) -> {ok, #state{}} | {error, bad_option()}.
load_file(File) ->
State0 = read_file(File),
State1 = hosts_to_start(State0),
AllMods = get_modules(),
init_module_db_table(AllMods),
ModOpts = get_modules_with_options(AllMods),
validate_opts(State1, ModOpts).
-spec reload_file() -> ok | {error, bad_option()}.
reload_file() ->
Config = get_ejabberd_config_path(),
OldHosts = get_myhosts(),
case load_file(Config) of
{error, _} = Err ->
Err;
{ok, State} ->
set_opts(State),
NewHosts = get_myhosts(),
lists:foreach(
fun(Host) ->
ejabberd_hooks:run(host_up, [Host])
end, NewHosts -- OldHosts),
lists:foreach(
fun(Host) ->
ejabberd_hooks:run(host_down, [Host])
end, OldHosts -- NewHosts),
ejabberd_hooks:run(config_reloaded, [])
end.
-spec convert_to_yaml(file:filename()) -> ok | {error, any()}.
convert_to_yaml(File) ->
convert_to_yaml(File, stdout).
-spec convert_to_yaml(file:filename(),
stdout | file:filename()) -> ok | {error, any()}.
convert_to_yaml(File, Output) ->
State = read_file(File, [{include_files, false}]),
Opts = [{K, V} || #local_config{key = K, value = V} <- State#state.opts],
{GOpts, HOpts} = split_by_hosts(Opts),
NewOpts = GOpts ++ lists:map(
fun({Host, Opts1}) ->
{host_config, [{Host, Opts1}]}
end, HOpts),
Data = fast_yaml:encode(lists:reverse(NewOpts)),
case Output of
stdout ->
io:format("~s~n", [Data]);
FileName ->
file:write_file(FileName, Data)
end.
%% Some Erlang apps expects env parameters to be list and not binary.
%% For example, Mnesia is not able to start if mnesia dir is passed as a binary.
%% However, binary is most common on Elixir, so it is easy to make a setup mistake.
-spec env_binary_to_list(atom(), atom()) -> {ok, any()}|undefined.
env_binary_to_list(Application, Parameter) ->
%% Application need to be loaded to allow setting parameters
application:load(Application),
case application:get_env(Application, Parameter) of
{ok, Val} when is_binary(Val) ->
BVal = binary_to_list(Val),
application:set_env(Application, Parameter, BVal),
{ok, BVal};
Other ->
Other
end.
%% @doc Read an ejabberd configuration file and return the terms.
%% Input is an absolute or relative path to an ejabberd config file.
%% Returns a list of plain terms,
%% in which the options 'include_config_file' were parsed
%% and the terms in those files were included.
%% @spec(iolist()) -> [term()]
get_plain_terms_file(File, Opts) when is_binary(File) ->
get_plain_terms_file(binary_to_list(File), Opts);
get_plain_terms_file(File1, Opts) ->
File = get_absolute_path(File1),
DontStopOnError = lists:member(dont_halt_on_error, Opts),
case consult(File) of
{ok, 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);
false ->
BinTerms
end;
{error, enoent, Reason} ->
case DontStopOnError of
true ->
?WARNING_MSG(Reason, []),
[];
_ ->
?ERROR_MSG(Reason, []),
exit_or_halt(Reason)
end;
{error, Reason} ->
?ERROR_MSG(Reason, []),
case DontStopOnError of
true -> [];
_ -> exit_or_halt(Reason)
end
end.
consult(File) ->
case filename:extension(File) of
Ex when (Ex == ".yml") or (Ex == ".yaml") ->
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 = fast_yaml:format_error(Err),
case Err of
enoent ->
{error, enoent, Msg1 ++ Msg2};
_ ->
{error, Msg1 ++ Msg2}
end
end;
_ ->
case file:consult(File) of
{ok, Terms} ->
{ok, Terms};
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
{error, describe_config_problem(File, Reason, LineNumber)};
{error, Reason} ->
case Reason of
enoent ->
{error, enoent, describe_config_problem(File, Reason)};
_ ->
{error, describe_config_problem(File, Reason)}
end
end
end.
parserl(<<"> ", Term/binary>>) ->
{ok, A2, _} = erl_scan:string(binary_to_list(Term)),
{ok, A3} = erl_parse:parse_term(A2),
A3;
parserl({A, B}) ->
{parserl(A), parserl(B)};
parserl([El|Tail]) ->
[parserl(El) | parserl(Tail)];
parserl(Other) ->
Other.
%% @doc Convert configuration filename to absolute path.
%% Input is an absolute or relative path to an ejabberd configuration file.
%% And returns an absolute path to the configuration file.
%% @spec (string()) -> string()
get_absolute_path(File) ->
case filename:pathtype(File) of
absolute ->
File;
relative ->
{ok, Dir} = file:get_cwd(),
filename:absname_join(Dir, File);
volumerelative ->
filename:absname(File)
end.
search_hosts(Term, State) ->
case Term of
{host, Host} ->
if
State#state.hosts == [] ->
set_hosts_in_options([Host], State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
exit("too many hosts definitions")
end;
{hosts, Hosts} ->
if
State#state.hosts == [] ->
set_hosts_in_options(Hosts, State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
exit("too many hosts definitions")
end;
_ ->
State
end.
set_hosts_in_options(Hosts, State) ->
PrepHosts = normalize_hosts(Hosts),
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 jid:nodeprep(iolist_to_binary(Host)) of
error ->
?ERROR_MSG("Can't load config file: "
"invalid host name [~p]", [Host]),
exit("invalid hostname");
PrepHost ->
normalize_hosts(Hosts, [PrepHost|PrepHosts])
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Errors reading the config file
describe_config_problem(Filename, Reason) ->
Text1 = lists:flatten("Problem loading ejabberd config file " ++ Filename),
Text2 = lists:flatten(" : " ++ file:format_error(Reason)),
ExitText = Text1 ++ Text2,
ExitText.
describe_config_problem(Filename, Reason, LineNumber) ->
Text1 = lists:flatten("Problem loading ejabberd config file " ++ Filename),
Text2 = lists:flatten(" approximately in the line "
++ file:format_error(Reason)),
ExitText = Text1 ++ Text2,
Lines = get_config_lines(Filename, LineNumber, 10, 3),
?ERROR_MSG("The following lines from your configuration file might be"
" relevant to the error: ~n~s", [Lines]),
ExitText.
get_config_lines(Filename, TargetNumber, PreContext, PostContext) ->
{ok, Fd} = file:open(Filename, [read]),
LNumbers = lists:seq(TargetNumber-PreContext, TargetNumber+PostContext),
NextL = io:get_line(Fd, no_prompt),
R = get_config_lines2(Fd, NextL, 1, LNumbers, []),
file:close(Fd),
R.
get_config_lines2(_Fd, eof, _CurrLine, _LNumbers, R) ->
lists:reverse(R);
get_config_lines2(_Fd, _NewLine, _CurrLine, [], R) ->
lists:reverse(R);
get_config_lines2(Fd, Data, CurrLine, [NextWanted | LNumbers], R) when is_list(Data) ->
NextL = io:get_line(Fd, no_prompt),
if
CurrLine >= NextWanted ->
Line2 = [integer_to_list(CurrLine), ": " | Data],
get_config_lines2(Fd, NextL, CurrLine+1, LNumbers, [Line2 | R]);
true ->
get_config_lines2(Fd, NextL, CurrLine+1, [NextWanted | LNumbers], R)
end.
%% If ejabberd isn't yet running in this node, then halt the node
exit_or_halt(ExitText) ->
case [Vsn || {ejabberd, _Desc, Vsn} <- application:which_applications()] of
[] ->
ejabberd:halt();
[_] ->
exit(ExitText)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% 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) ->
{FileOpts, Terms1} =
lists:mapfoldl(
fun({include_config_file, _} = T, Ts) ->
{[transform_include_option(T)], Ts};
({include_config_file, _, _} = T, Ts) ->
{[transform_include_option(T)], Ts};
(T, Ts) ->
{[], [T|Ts]}
end, [], Terms),
Terms2 = lists:flatmap(
fun({File, Opts}) ->
include_config_file(File, Opts)
end, lists:flatten(FileOpts)),
M1 = merge_configs(Terms1, #{}),
M2 = merge_configs(Terms2, M1),
maps_to_lists(M2).
transform_include_option({include_config_file, File}) when is_list(File) ->
case is_string(File) of
true -> {File, []};
false -> File
end;
transform_include_option({include_config_file, Filename}) ->
{Filename, []};
transform_include_option({include_config_file, Filename, Options}) ->
{Filename, Options}.
include_config_file(Filename, Options) ->
Included_terms = get_plain_terms_file(Filename, [{include_files, true}, dont_halt_on_error]),
Disallow = proplists:get_value(disallow, Options, []),
Included_terms2 = delete_disallowed(Disallow, Included_terms),
Allow_only = proplists:get_value(allow_only, Options, all),
keep_only_allowed(Allow_only, Included_terms2).
%% @doc Filter from the list of terms the disallowed.
%% Returns a sublist of Terms without the ones which first element is
%% included in Disallowed.
%% @spec (Disallowed::[atom()], Terms::[term()]) -> [term()]
delete_disallowed(Disallowed, Terms) ->
lists:foldl(
fun(Dis, Ldis) ->
delete_disallowed2(Dis, Ldis)
end,
Terms,
Disallowed).
delete_disallowed2(Disallowed, [H|T]) ->
case element(1, H) of
Disallowed ->
?WARNING_MSG("The option '~p' is disallowed, "
"and will not be accepted", [Disallowed]),
delete_disallowed2(Disallowed, T);
_ ->
[H|delete_disallowed2(Disallowed, T)]
end;
delete_disallowed2(_, []) ->
[].
%% @doc Keep from the list only the allowed terms.
%% Returns a sublist of Terms with only the ones which first element is
%% included in Allowed.
%% @spec (Allowed::[atom()], Terms::[term()]) -> [term()]
keep_only_allowed(all, Terms) ->
Terms;
keep_only_allowed(Allowed, Terms) ->
{As, NAs} = lists:partition(
fun(Term) ->
lists:member(element(1, Term), Allowed)
end,
Terms),
[?WARNING_MSG("This option is not allowed, "
"and will not be accepted:~n~p", [NA])
|| NA <- NAs],
As.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Support for Macro
%% @doc Replace the macros with their defined values.
%% @spec (Terms::[term()]) -> [term()]
replace_macros(Terms) ->
{TermsOthers, Macros} = split_terms_macros(Terms),
replace(TermsOthers, Macros).
%% @doc Split Terms into normal terms and macro definitions.
%% @spec (Terms) -> {Terms, Macros}
%% Terms = [term()]
%% Macros = [macro()]
split_terms_macros(Terms) ->
lists:foldl(
fun(Term, {TOs, Ms}) ->
case Term of
{define_macro, Key, Value} ->
case is_correct_macro({Key, Value}) of
true ->
{TOs, Ms++[{Key, Value}]};
false ->
exit({macro_not_properly_defined, Term})
end;
{define_macro, KeyVals} ->
case lists:all(fun is_correct_macro/1, KeyVals) of
true ->
{TOs, Ms ++ KeyVals};
false ->
exit({macros_not_properly_defined, Term})
end;
Term ->
{TOs ++ [Term], Ms}
end
end,
{[], []},
Terms).
is_correct_macro({Key, _Val}) ->
is_atom(Key) and is_all_uppercase(Key);
is_correct_macro(_) ->
false.
%% @doc Recursively replace in Terms macro usages with the defined value.
%% @spec (Terms, Macros) -> Terms
%% Terms = [term()]
%% Macros = [macro()]
replace([], _) ->
[];
replace([Term|Terms], Macros) ->
[replace_term(Term, Macros) | replace(Terms, Macros)];
replace(Term, Macros) ->
replace_term(Term, Macros).
replace_term(Key, Macros) when is_atom(Key) ->
case is_all_uppercase(Key) of
true ->
case proplists:get_value(Key, Macros) of
undefined -> exit({undefined_macro, Key});
Value -> Value
end;
false ->
Key
end;
replace_term({use_macro, Key, Value}, Macros) ->
proplists:get_value(Key, Macros, Value);
replace_term(Term, Macros) when is_list(Term) ->
replace(Term, Macros);
replace_term(Term, Macros) when is_tuple(Term) ->
List = tuple_to_list(Term),
List2 = replace(List, Macros),
list_to_tuple(List2);
replace_term(Term, _) ->
Term.
is_all_uppercase(Atom) ->
String = erlang:atom_to_list(Atom),
lists:all(fun(C) when C >= $a, C =< $z -> false;
(_) -> true
end, String).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Process terms
process_term(Term, State) ->
case Term of
{host_config, HostTerms} ->
lists:foldl(
fun({Host, Terms}, AccState) ->
lists:foldl(fun(T, S) ->
process_host_term(T, Host, S, set)
end, AccState, Terms)
end, State, HostTerms);
{append_host_config, HostTerms} ->
lists:foldl(
fun({Host, Terms}, AccState) ->
lists:foldl(fun(T, S) ->
process_host_term(T, Host, S, append)
end, AccState, Terms)
end, State, HostTerms);
_ ->
process_host_term(Term, global, State, set)
end.
process_host_term(Term, Host, State, Action) ->
case Term of
{modules, Modules} when Action == set ->
set_option({modules, Host}, replace_modules(Modules), State);
{modules, Modules} when Action == append ->
append_option({modules, Host}, replace_modules(Modules), State);
{host, _} ->
State;
{hosts, _} ->
State;
{Opt, Val} when Action == set ->
set_option({rename_option(Opt), Host}, change_val(Opt, Val), State);
{Opt, Val} when Action == append ->
append_option({rename_option(Opt), Host}, change_val(Opt, Val), State);
Opt ->
?WARNING_MSG("Ignore invalid (outdated?) option ~p", [Opt]),
State
end.
rename_option(Option) when is_atom(Option) ->
case atom_to_list(Option) of
"odbc_" ++ T ->
NewOption = list_to_atom("sql_" ++ T),
?WARNING_MSG("Option '~s' is obsoleted, use '~s' instead",
[Option, NewOption]),
NewOption;
_ ->
Option
end;
rename_option(Option) ->
Option.
change_val(auth_method, Val) ->
prepare_opt_val(auth_method, Val,
fun(V) ->
L = if is_list(V) -> V;
true -> [V]
end,
lists:map(
fun(odbc) -> sql;
(internal) -> mnesia;
(A) when is_atom(A) -> A
end, L)
end, [mnesia]);
change_val(_Opt, Val) ->
Val.
set_option(Opt, Val, State) ->
State#state{opts = [#local_config{key = Opt, value = Val} |
State#state.opts]}.
append_option({Opt, Host}, Val, State) ->
GlobalVals = lists:flatmap(
fun(#local_config{key = {O, global}, value = V})
when O == Opt ->
if is_list(V) -> V;
true -> [V]
end;
(_) ->
[]
end, State#state.opts),
NewVal = if is_list(Val) -> Val ++ GlobalVals;
true -> [Val|GlobalVals]
end,
set_option({Opt, Host}, NewVal, State).
set_opts(State) ->
Opts = State#state.opts,
ets:insert(
ejabberd_options,
lists:map(
fun(#local_config{key = Key, value = Val}) ->
{Key, Val}
end, Opts)),
set_log_level().
set_log_level() ->
Level = get_option(loglevel, 4),
ejabberd_logger:set(Level).
add_global_option(Opt, Val) ->
add_option(Opt, Val).
add_local_option(Opt, Val) ->
add_option(Opt, Val).
add_option(Opt, Val) when is_atom(Opt) ->
add_option({Opt, global}, Val);
add_option({Opt, Host}, Val) ->
ets:insert(ejabberd_options, {{Opt, Host}, Val}),
ok.
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
prepare_opt_val(Opt, Val, F, Default) ->
Call = case F of
{Mod, Fun} ->
fun() -> Mod:Fun(Val) end;
_ ->
fun() -> F(Val) end
end,
try Call() of
Res ->
Res
catch {replace_with, NewRes} ->
NewRes;
{invalid_syntax, Error} ->
?WARNING_MSG("incorrect value '~s' of option '~s', "
"using '~s' as fallback: ~s",
[format_term(Val),
format_term(Opt),
format_term(Default),
Error]),
Default;
_:_ ->
?WARNING_MSG("incorrect value '~s' of option '~s', "
"using '~s' as fallback",
[format_term(Val),
format_term(Opt),
format_term(Default)]),
Default
end.
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
-spec get_global_option(any(), check_fun()) -> any().
get_global_option(Opt, _) ->
get_option(Opt, undefined).
-spec get_global_option(any(), check_fun(), any()) -> any().
get_global_option(Opt, _, Default) ->
get_option(Opt, Default).
-spec get_local_option(any(), check_fun()) -> any().
get_local_option(Opt, _) ->
get_option(Opt, undefined).
-spec get_local_option(any(), check_fun(), any()) -> any().
get_local_option(Opt, _, Default) ->
get_option(Opt, Default).
-spec get_option(any()) -> any().
get_option(Opt) ->
get_option(Opt, undefined).
-spec get_option(any(), check_fun(), any()) -> any().
get_option(Opt, _, Default) ->
get_option(Opt, Default).
-spec get_option(any(), check_fun() | any()) -> any().
get_option(Opt, F) when is_function(F) ->
get_option(Opt, undefined);
get_option(Opt, Default) when is_atom(Opt) ->
get_option({Opt, global}, Default);
get_option(Opt, Default) ->
{Key, Host} = case Opt of
{O, global} when is_atom(O) -> Opt;
{O, H} when is_atom(O), is_binary(H) -> Opt;
_ ->
?WARNING_MSG("Option ~p has invalid (outdated?) "
"format. This is likely a bug", [Opt]),
{undefined, global}
end,
try ets:lookup_element(ejabberd_options, {Key, Host}, 2)
catch _:badarg when Host /= global ->
try ets:lookup_element(ejabberd_options, {Key, global}, 2)
catch _:badarg -> Default
end;
_:badarg ->
Default
end.
-spec has_option(atom() | {atom(), global | binary()}) -> any().
has_option(Opt) ->
get_option(Opt) /= undefined.
init_module_db_table(Modules) ->
%% Dirty hack for mod_pubsub
ets:insert(ejabberd_db_modules, {{mod_pubsub, mnesia}, true}),
ets:insert(ejabberd_db_modules, {{mod_pubsub, sql}, true}),
lists:foreach(
fun(M) ->
case re:split(atom_to_list(M), "_", [{return, list}]) of
[_] ->
ok;
Parts ->
[H|T] = lists:reverse(Parts),
Suffix = list_to_atom(H),
BareMod = list_to_atom(string:join(lists:reverse(T), "_")),
case is_behaviour(BareMod, M) of
true ->
ets:insert(ejabberd_db_modules,
{{BareMod, Suffix}, true});
false ->
ok
end
end
end, Modules).
is_behaviour(Behav, Mod) ->
try Mod:module_info(attributes) of
[] ->
%% Stripped module?
true;
Attrs ->
lists:any(
fun({behaviour, L}) -> lists:member(Behav, L);
({behavior, L}) -> lists:member(Behav, L);
(_) -> false
end, Attrs)
catch _:_ ->
true
end.
-spec v_db(module(), atom()) -> atom().
v_db(Mod, internal) -> v_db(Mod, mnesia);
v_db(Mod, odbc) -> v_db(Mod, sql);
v_db(Mod, Type) ->
case ets:member(ejabberd_db_modules, {Mod, Type}) of
true -> Type;
false -> erlang:error(badarg)
end.
-spec v_dbs(module()) -> [atom()].
v_dbs(Mod) ->
ets:select(
ejabberd_db_modules,
ets:fun2ms(
fun({{M, Type}, _}) when M == Mod ->
Type
end)).
-spec v_dbs_mods(module()) -> [module()].
v_dbs_mods(Mod) ->
lists:map(fun(M) ->
binary_to_atom(<<(atom_to_binary(Mod, utf8))/binary, "_",
(atom_to_binary(M, utf8))/binary>>, utf8)
end, v_dbs(Mod)).
-spec default_db(module()) -> atom().
default_db(Module) ->
default_db(global, Module).
-spec default_db(binary() | global, module()) -> atom().
default_db(Host, Module) ->
default_db(default_db, Host, Module).
-spec default_ram_db(module()) -> atom().
default_ram_db(Module) ->
default_ram_db(global, Module).
-spec default_ram_db(binary() | global, module()) -> atom().
default_ram_db(Host, Module) ->
default_db(default_ram_db, Host, Module).
-spec default_db(default_db | default_ram_db, binary() | global, module()) -> atom().
default_db(Opt, Host, Module) ->
case get_option({Opt, Host}) of
undefined ->
mnesia;
DBType ->
try
v_db(Module, DBType)
catch error:badarg ->
?WARNING_MSG("Module '~s' doesn't support database '~s' "
"defined in option '~s', using "
"'mnesia' as fallback", [Module, DBType, Opt]),
mnesia
end
end.
get_modules() ->
{ok, Mods} = application:get_key(ejabberd, modules),
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
case application:get_env(ejabberd, external_beams) of
{ok, Path} ->
case lists:member(Path, code:get_path()) of
true -> ok;
false -> code:add_patha(Path)
end,
Beams = filelib:wildcard(filename:join(Path, "*\.beam")),
CustMods = [list_to_atom(filename:rootname(filename:basename(Beam)))
|| Beam <- Beams],
CustMods ++ ExtMods ++ Mods;
_ ->
ExtMods ++ Mods
end.
get_modules_with_options(Modules) ->
lists:foldl(
fun(Mod, D) ->
case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of
true ->
try Mod:opt_type('') of
Opts when is_list(Opts) ->
lists:foldl(
fun(Opt, Acc) ->
dict:append(Opt, Mod, Acc)
end, D, Opts)
catch _:undef ->
D
end;
false ->
D
end
end, dict:new(), Modules).
-spec validate_opts(#state{}, dict:dict()) -> {ok, #state{}} | {error, bad_option()}.
validate_opts(#state{opts = Opts} = State, ModOpts) ->
try
NewOpts = lists:map(
fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
case dict:find(Opt, ModOpts) of
{ok, [Mod|_]} ->
VFun = Mod:opt_type(Opt),
try VFun(Val) of
NewVal ->
In#local_config{value = NewVal}
catch {invalid_syntax, Error} ->
?ERROR_MSG("Invalid value '~p' for "
"option '~s': ~s",
[Val, Opt, Error]),
erlang:error(invalid_option);
_:_ ->
?ERROR_MSG("Invalid value '~p' for "
"option '~s'",
[Val, Opt]),
erlang:error(invalid_option)
end;
_ ->
?ERROR_MSG("Unknown option '~s'", [Opt]),
erlang:error(unknown_option)
end
end, Opts),
{ok, State#state{opts = NewOpts}}
catch _:invalid_option ->
{error, invalid_option};
_:unknown_option ->
{error, unknown_option}
end.
%% @spec (Path::string()) -> true | false
is_file_readable(Path) ->
case file:read_file_info(Path) of
{ok, FileInfo} ->
case {FileInfo#file_info.type, FileInfo#file_info.access} of
{regular, read} -> true;
{regular, read_write} -> true;
_ -> false
end;
{error, _Reason} ->
false
end.
get_version() ->
case application:get_key(ejabberd, vsn) of
undefined -> "";
{ok, Vsn} -> list_to_binary(Vsn)
end.
-spec get_myhosts() -> [binary()].
get_myhosts() ->
get_option(hosts).
-spec get_mylang() -> binary().
get_mylang() ->
get_lang(global).
-spec get_lang(global | binary()) -> binary().
get_lang(Host) ->
get_option({language, Host}, <<"en">>).
replace_module(mod_announce_odbc) -> {mod_announce, sql};
replace_module(mod_blocking_odbc) -> {mod_blocking, sql};
replace_module(mod_caps_odbc) -> {mod_caps, sql};
replace_module(mod_irc_odbc) -> {mod_irc, sql};
replace_module(mod_last_odbc) -> {mod_last, sql};
replace_module(mod_muc_odbc) -> {mod_muc, sql};
replace_module(mod_offline_odbc) -> {mod_offline, sql};
replace_module(mod_privacy_odbc) -> {mod_privacy, sql};
replace_module(mod_private_odbc) -> {mod_private, sql};
replace_module(mod_roster_odbc) -> {mod_roster, sql};
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
replace_module(mod_vcard_ldap) -> {mod_vcard, ldap};
replace_module(mod_vcard_xupdate_odbc) -> mod_vcard_xupdate;
replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
replace_module(mod_http_bind) -> mod_bosh;
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).
%% Elixir module naming
%% ====================
-ifdef(ELIXIR_ENABLED).
is_elixir_enabled() ->
true.
-else.
is_elixir_enabled() ->
false.
-endif.
is_using_elixir_config() ->
case is_elixir_enabled() of
true ->
Config = get_ejabberd_config_path(),
'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config);
false ->
false
end.
%% If module name start with uppercase letter, this is an Elixir module:
is_elixir_module(Module) ->
case atom_to_list(Module) of
[H|_] when H >= 65, H =< 90 -> true;
_ ->false
end.
%% We assume we know this is an elixir module
expand_elixir_module(Module) ->
case atom_to_list(Module) of
%% Module name already specified as an Elixir from Erlang module name
"Elixir." ++ _ -> Module;
%% if start with uppercase letter, this is an Elixir module: Append 'Elixir.' to module name.
ModuleString ->
list_to_atom("Elixir." ++ ModuleString)
end.
strings_to_binary([]) ->
[];
strings_to_binary(L) when is_list(L) ->
case is_string(L) of
true ->
list_to_binary(L);
false ->
strings_to_binary1(L)
end;
strings_to_binary({A, B, C, D}) when
is_integer(A), is_integer(B), is_integer(C), is_integer(D) ->
{A, B, C ,D};
strings_to_binary(T) when is_tuple(T) ->
list_to_tuple(strings_to_binary1(tuple_to_list(T)));
strings_to_binary(X) ->
X.
strings_to_binary1([El|L]) ->
[strings_to_binary(El)|strings_to_binary1(L)];
strings_to_binary1([]) ->
[];
strings_to_binary1(T) ->
T.
is_string([C|T]) when (C >= 0) and (C =< 255) ->
is_string(T);
is_string([]) ->
true;
is_string(_) ->
false.
binary_to_strings(B) when is_binary(B) ->
binary_to_list(B);
binary_to_strings([H|T]) ->
[binary_to_strings(H)|binary_to_strings(T)];
binary_to_strings(T) when is_tuple(T) ->
list_to_tuple(binary_to_strings(tuple_to_list(T)));
binary_to_strings(T) ->
T.
format_term(Bin) when is_binary(Bin) ->
io_lib:format("\"~s\"", [Bin]);
format_term(S) when is_list(S), S /= [] ->
case lists:all(fun(C) -> (C>=0) and (C=<255) end, S) of
true ->
io_lib:format("\"~s\"", [S]);
false ->
io_lib:format("~p", [binary_to_strings(S)])
end;
format_term(T) ->
io_lib:format("~p", [binary_to_strings(T)]).
transform_terms(Terms) ->
%% We could check all ejabberd beams, but this
%% slows down start-up procedure :(
Mods = [mod_register,
ejabberd_s2s,
ejabberd_listener,
ejabberd_sql_sup,
shaper,
ejabberd_s2s_out,
acl,
ejabberd_config],
collect_options(transform_terms(Mods, Terms)).
transform_terms([Mod|Mods], Terms) ->
case catch Mod:transform_options(Terms) of
{'EXIT', _} = Err ->
?ERROR_MSG("Failed to transform terms by ~p: ~p", [Mod, Err]),
transform_terms(Mods, Terms);
NewTerms ->
transform_terms(Mods, NewTerms)
end;
transform_terms([], NewTerms) ->
NewTerms.
transform_module_options(Module, Opts) ->
Opts1 = gen_iq_handler:transform_module_options(Opts),
try
Module:transform_module_options(Opts1)
catch error:undef ->
Opts1
end.
compact(Cfg) ->
Opts = [{K, V} || #local_config{key = K, value = V} <- Cfg],
{GOpts, HOpts} = split_by_hosts(Opts),
[#local_config{key = {O, global}, value = V} || {O, V} <- GOpts] ++
lists:flatmap(
fun({Host, OptVal}) ->
case lists:member(OptVal, GOpts) of
true ->
[];
false ->
[#local_config{key = {Opt, Host}, value = Val}
|| {Opt, Val} <- OptVal]
end
end, lists:flatten(HOpts)).
split_by_hosts(Opts) ->
Opts1 = orddict:to_list(
lists:foldl(
fun({{Opt, Host}, Val}, D) ->
orddict:append(Host, {Opt, Val}, D)
end, orddict:new(), Opts)),
case lists:keytake(global, 1, Opts1) of
{value, {global, GlobalOpts}, HostOpts} ->
{GlobalOpts, HostOpts};
_ ->
{[], Opts1}
end.
collect_options(Opts) ->
{D, InvalidOpts} =
lists:foldl(
fun({K, V}, {D, Os}) when is_list(V) ->
{orddict:append_list(K, V, D), Os};
({K, V}, {D, Os}) ->
{orddict:store(K, V, D), Os};
(Opt, {D, Os}) ->
{D, [Opt|Os]}
end, {orddict:new(), []}, Opts),
InvalidOpts ++ orddict:to_list(D).
transform_options(Opts) ->
Opts1 = lists:foldl(fun transform_options/2, [], Opts),
{HOpts, Opts2} = lists:mapfoldl(
fun({host_config, O}, Os) ->
{[O], Os};
(O, Os) ->
{[], [O|Os]}
end, [], Opts1),
{AHOpts, Opts3} = lists:mapfoldl(
fun({append_host_config, O}, Os) ->
{[O], Os};
(O, Os) ->
{[], [O|Os]}
end, [], Opts2),
HOpts1 = case collect_options(lists:flatten(HOpts)) of
[] ->
[];
HOs ->
[{host_config,
[{H, transform_terms(O)} || {H, O} <- HOs]}]
end,
AHOpts1 = case collect_options(lists:flatten(AHOpts)) of
[] ->
[];
AHOs ->
[{append_host_config,
[{H, transform_terms(O)} || {H, O} <- AHOs]}]
end,
HOpts1 ++ AHOpts1 ++ Opts3.
transform_options({domain_certfile, Domain, CertFile}, Opts) ->
?WARNING_MSG("Option 'domain_certfile' now should be defined "
"per virtual host or globally. The old format is "
"still supported but it is better to fix your config", []),
[{host_config, [{Domain, [{domain_certfile, CertFile}]}]}|Opts];
transform_options(Opt, Opts) when Opt == override_global;
Opt == override_local;
Opt == override_acls ->
?WARNING_MSG("Ignoring '~s' option which has no effect anymore", [Opt]),
Opts;
transform_options({node_start, {_, _, _} = Now}, Opts) ->
?WARNING_MSG("Old 'node_start' format detected. This is still supported "
"but it is better to fix your config.", []),
[{node_start, now_to_seconds(Now)}|Opts];
transform_options({host_config, Host, HOpts}, Opts) ->
{AddOpts, HOpts1} =
lists:mapfoldl(
fun({{add, Opt}, Val}, Os) ->
?WARNING_MSG("Option 'add' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 'append_host_config' instead.", []),
{[{Opt, Val}], Os};
(O, Os) ->
{[], [O|Os]}
end, [], HOpts),
[{append_host_config, [{Host, lists:flatten(AddOpts)}]},
{host_config, [{Host, HOpts1}]}|Opts];
transform_options({define_macro, Macro, Val}, Opts) ->
[{define_macro, [{Macro, Val}]}|Opts];
transform_options({include_config_file, _} = Opt, Opts) ->
[{include_config_file, [transform_include_option(Opt)]} | Opts];
transform_options({include_config_file, _, _} = Opt, Opts) ->
[{include_config_file, [transform_include_option(Opt)]} | Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
emit_deprecation_warning(Module, NewModule, DBType) ->
?WARNING_MSG("Module ~s is deprecated, use ~s with 'db_type: ~s'"
" instead", [Module, NewModule, DBType]).
emit_deprecation_warning(Module, NewModule) ->
case is_elixir_module(NewModule) of
%% Do not emit deprecation warning for Elixir
true -> ok;
false ->
?WARNING_MSG("Module ~s is deprecated, use ~s instead",
[Module, NewModule])
end.
-spec now_to_seconds(erlang:timestamp()) -> non_neg_integer().
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
-spec opt_type(hide_sensitive_log_data) -> fun((boolean()) -> boolean());
(hosts) -> fun(([binary()]) -> [binary()]);
(language) -> fun((binary()) -> binary());
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
(default_db) -> fun((atom()) -> atom());
(default_ram_db) -> fun((atom()) -> atom());
(loglevel) -> fun((0..5) -> 0..5);
(queue_dir) -> fun((binary()) -> binary());
(queue_type) -> fun((ram | file) -> ram | file);
(use_cache) -> fun((boolean()) -> boolean());
(cache_size) -> fun((timeout()) -> timeout());
(cache_missed) -> fun((boolean()) -> boolean());
(cache_life_time) -> fun((timeout()) -> timeout());
(shared_key) -> fun((binary()) -> binary());
(node_start) -> fun((non_neg_integer()) -> non_neg_integer());
(atom()) -> [atom()].
opt_type(hide_sensitive_log_data) ->
fun (H) when is_boolean(H) -> H end;
opt_type(hosts) ->
fun(L) ->
[iolist_to_binary(H) || H <- L]
end;
opt_type(language) ->
fun iolist_to_binary/1;
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(default_db) ->
fun(T) when is_atom(T) -> T end;
opt_type(default_ram_db) ->
fun(T) when is_atom(T) -> T end;
opt_type(loglevel) ->
fun (P) when P >= 0, P =< 5 -> P end;
opt_type(queue_dir) ->
fun iolist_to_binary/1;
opt_type(queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(use_cache) ->
fun(B) when is_boolean(B) -> B end;
opt_type(cache_size) ->
fun(I) when is_integer(I), I>0 -> I;
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(cache_missed) ->
fun(B) when is_boolean(B) -> B end;
opt_type(cache_life_time) ->
fun(I) when is_integer(I), I>0 -> I;
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(negotiation_timeout) ->
fun(T) when T > 0 -> T end;
opt_type(shared_key) ->
fun iolist_to_binary/1;
opt_type(node_start) ->
fun(I) when is_integer(I), I>=0 -> I end;
opt_type(validate_stream) ->
fun(B) when is_boolean(B) -> B end;
opt_type(_) ->
[hide_sensitive_log_data, hosts, language, max_fsm_queue,
default_db, default_ram_db, queue_type, queue_dir, loglevel,
use_cache, cache_size, cache_missed, cache_life_time,
shared_key, node_start, validate_stream, negotiation_timeout].
-spec may_hide_data(any()) -> any().
may_hide_data(Data) ->
case get_option(hide_sensitive_log_data, false) of
false ->
Data;
true ->
"hidden_by_ejabberd"
end.
-spec fsm_limit_opts([proplists:property()]) -> [{max_queue, pos_integer()}].
fsm_limit_opts(Opts) ->
case lists:keyfind(max_fsm_queue, 1, Opts) of
{_, I} when is_integer(I), I>0 ->
[{max_queue, I}];
false ->
case get_option(max_fsm_queue) of
undefined -> [];
N -> [{max_queue, N}]
end
end.
-spec queue_dir() -> binary() | undefined.
queue_dir() ->
get_option(queue_dir).
-spec default_queue_type(binary()) -> ram | file.
default_queue_type(Host) ->
get_option({queue_type, Host}, ram).
-spec use_cache(binary() | global) -> boolean().
use_cache(Host) ->
get_option({use_cache, Host}, true).
-spec cache_size(binary() | global) -> pos_integer() | infinity.
cache_size(Host) ->
get_option({cache_size, Host}, 1000).
-spec cache_missed(binary() | global) -> boolean().
cache_missed(Host) ->
get_option({cache_missed, Host}, true).
-spec cache_life_time(binary() | global) -> pos_integer() | infinity.
%% NOTE: the integer value returned is in *seconds*
cache_life_time(Host) ->
get_option({cache_life_time, Host}, 3600).
-spec codec_options(binary() | global) -> [xmpp:decode_option()].
codec_options(Host) ->
case get_option({validate_stream, Host}, false) of
true -> [];
false -> [ignore_els]
end.
-spec negotiation_timeout() -> pos_integer().
negotiation_timeout() ->
timer:seconds(get_option(negotiation_timeout, 30)).