mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-30 16:36:29 +01:00
789 lines
25 KiB
Erlang
789 lines
25 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-2010 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., 59 Temple Place, Suite 330, Boston, MA
|
|
%%% 02111-1307 USA
|
|
%%%
|
|
%%%----------------------------------------------------------------------
|
|
|
|
-module(ejabberd_config).
|
|
-author('alexey@process-one.net').
|
|
|
|
-export([start/0, load_file/1, get_host_option/2,
|
|
add_global_option/2, add_local_option/2,
|
|
mne_add_local_option/2, mne_del_local_option/1,
|
|
del_global_option/1, del_local_option/1,
|
|
get_global_option/1, get_local_option/1]).
|
|
|
|
-export([for_host/1
|
|
,configure_host/2
|
|
,delete_host/1
|
|
]).
|
|
|
|
-export([search/1]).
|
|
|
|
-export([get_vh_by_auth_method/1]).
|
|
-export([is_file_readable/1]).
|
|
|
|
-include("ejabberd.hrl").
|
|
-include("ejabberd_config.hrl").
|
|
-include_lib("kernel/include/file.hrl").
|
|
-include_lib("stdlib/include/ms_transform.hrl").
|
|
|
|
-record(state, {opts = [],
|
|
hosts = [],
|
|
override_local = false,
|
|
override_global = false,
|
|
override_acls = false}).
|
|
|
|
%% @type macro() = {macro_key(), macro_value()}
|
|
|
|
%% @type macro_key() = atom().
|
|
%% The atom must have all characters in uppercase.
|
|
|
|
%% @type macro_value() = term().
|
|
|
|
|
|
start() ->
|
|
mnesia:create_table(config,
|
|
[{disc_copies, [node()]},
|
|
{attributes, record_info(fields, config)}]),
|
|
mnesia:add_table_copy(config, node(), ram_copies),
|
|
mnesia:create_table(local_config,
|
|
[{disc_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(),
|
|
load_file(Config),
|
|
%% This start time is used by mod_last:
|
|
add_local_option(node_start, now()),
|
|
ok.
|
|
|
|
%% @doc Get the filename of the ejabberd configuration file.
|
|
%% The filename can be specified with: erl -config "/path/to/ejabberd.cfg".
|
|
%% It can also be specified with the environtment variable EJABBERD_CONFIG_PATH.
|
|
%% If not specified, the default value 'ejabberd.cfg' is assumed.
|
|
%% @spec () -> string()
|
|
get_ejabberd_config_path() ->
|
|
case application:get_env(config) of
|
|
{ok, Path} -> Path;
|
|
undefined ->
|
|
case os:getenv("EJABBERD_CONFIG_PATH") of
|
|
false ->
|
|
?CONFIG_PATH;
|
|
Path ->
|
|
Path
|
|
end
|
|
end.
|
|
|
|
%% @doc Load 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()) -> ok
|
|
load_file(File) ->
|
|
Terms = get_plain_terms_file(File),
|
|
State = lists:foldl(fun search_hosts/2, #state{}, Terms),
|
|
Terms_macros = replace_macros(Terms),
|
|
Res = lists:foldl(fun process_term/2, State, Terms_macros),
|
|
set_opts(Res).
|
|
|
|
%% @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(string()) -> [term()]
|
|
get_plain_terms_file(File1) ->
|
|
File = get_absolute_path(File1),
|
|
case file:consult(File) of
|
|
{ok, Terms} ->
|
|
include_config_files(Terms);
|
|
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
|
ExitText = describe_config_problem(File, Reason, LineNumber),
|
|
?ERROR_MSG(ExitText, []),
|
|
exit_or_halt(ExitText);
|
|
{error, Reason} ->
|
|
ExitText = describe_config_problem(File, Reason),
|
|
?ERROR_MSG(ExitText, []),
|
|
exit_or_halt(ExitText)
|
|
end.
|
|
|
|
%% @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 ->
|
|
Config_path = get_ejabberd_config_path(),
|
|
Config_dir = filename:dirname(Config_path),
|
|
filename:absname_join(Config_dir, File)
|
|
end.
|
|
|
|
|
|
search_hosts(Term, State) ->
|
|
case Term of
|
|
{host, Host} ->
|
|
if
|
|
State#state.hosts == [] ->
|
|
add_hosts_to_option([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 == [] ->
|
|
add_hosts_to_option(Hosts, State);
|
|
true ->
|
|
?ERROR_MSG("Can't load config file: "
|
|
"too many hosts definitions", []),
|
|
exit("too many hosts definitions")
|
|
end;
|
|
_ ->
|
|
State
|
|
end.
|
|
|
|
add_hosts_to_option(Hosts, State) ->
|
|
PrepHosts1 = normalize_hosts(Hosts),
|
|
PrepHosts = ensure_localhost_is_first(PrepHosts1),
|
|
mnesia:transaction(
|
|
fun() ->
|
|
lists:foreach(
|
|
fun(H) ->
|
|
mnesia:write(#local_config{key = {H, host},
|
|
value = []})
|
|
end, PrepHosts)
|
|
end),
|
|
add_option(hosts, PrepHosts, State#state{hosts = PrepHosts}).
|
|
|
|
normalize_hosts(Hosts) ->
|
|
normalize_hosts(Hosts,[]).
|
|
normalize_hosts([], PrepHosts) ->
|
|
lists:reverse(PrepHosts);
|
|
normalize_hosts([Host|Hosts], PrepHosts) ->
|
|
try
|
|
PrepHost = exmpp_stringprep:nodeprep(Host),
|
|
normalize_hosts(Hosts, [PrepHost|PrepHosts])
|
|
catch
|
|
_ ->
|
|
?ERROR_MSG("Can't load config file: "
|
|
"invalid host name [~p]", [Host]),
|
|
exit("invalid hostname")
|
|
end.
|
|
|
|
%% @spec (Hosts::[string()]) -> [Localhost::string() | [string()]]
|
|
%% @doc Return the list where the first is surely "localhost".
|
|
ensure_localhost_is_first(Hosts) ->
|
|
case lists:all(fun is_list/1, Hosts) of
|
|
true ->
|
|
ensure_localhost_is_first1(Hosts);
|
|
false ->
|
|
?ERROR_MSG("This list of hosts is bad formed:~n~p", [Hosts]),
|
|
ensure_localhost_is_first1([])
|
|
end.
|
|
|
|
ensure_localhost_is_first1(["localhost" | _] = Hosts) ->
|
|
Hosts;
|
|
ensure_localhost_is_first1(Hosts) ->
|
|
case lists:member("localhost", Hosts) of
|
|
true ->
|
|
["localhost" | lists:delete("localhost", Hosts)];
|
|
false ->
|
|
?INFO_MSG("ejabberd added the default virtual host \"localhost\""
|
|
" to the list of hosts.", []),
|
|
["localhost" | Hosts]
|
|
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
|
|
[] ->
|
|
timer:sleep(1000),
|
|
halt(ExitText);
|
|
[_] ->
|
|
exit(ExitText)
|
|
end.
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
%%% Support for 'include_config_file'
|
|
|
|
%% @doc Include additional configuration files in the list of terms.
|
|
%% @spec ([term()]) -> [term()]
|
|
include_config_files(Terms) ->
|
|
include_config_files(Terms, []).
|
|
|
|
include_config_files([], Res) ->
|
|
Res;
|
|
include_config_files([{include_config_file, Filename} | Terms], Res) ->
|
|
include_config_files([{include_config_file, Filename, []} | Terms], Res);
|
|
include_config_files([{include_config_file, Filename, Options} | Terms], Res) ->
|
|
Included_terms = get_plain_terms_file(Filename),
|
|
Disallow = proplists:get_value(disallow, Options, []),
|
|
Included_terms2 = delete_disallowed(Disallow, Included_terms),
|
|
Allow_only = proplists:get_value(allow_only, Options, all),
|
|
Included_terms3 = keep_only_allowed(Allow_only, Included_terms2),
|
|
include_config_files(Terms, Res ++ Included_terms3);
|
|
include_config_files([Term | Terms], Res) ->
|
|
include_config_files(Terms, Res ++ [Term]).
|
|
|
|
%% @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_atom(Key) and is_all_uppercase(Key) of
|
|
true ->
|
|
{TOs, Ms++[{Key, Value}]};
|
|
false ->
|
|
exit({macro_not_properly_defined, Term})
|
|
end;
|
|
Term ->
|
|
{TOs ++ [Term], Ms}
|
|
end
|
|
end,
|
|
{[], []},
|
|
Terms).
|
|
|
|
%% @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(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
|
|
override_global ->
|
|
State#state{override_global = true};
|
|
override_local ->
|
|
State#state{override_local = true};
|
|
override_acls ->
|
|
State#state{override_acls = true};
|
|
{acl, _ACLName, _ACLData} ->
|
|
process_host_term(Term, global, State);
|
|
{access, _RuleName, _Rules} ->
|
|
process_host_term(Term, global, State);
|
|
{shaper, _Name, _Data} ->
|
|
%%lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
|
|
%% State, State#state.hosts);
|
|
process_host_term(Term, global, State);
|
|
{host, _Host} ->
|
|
State;
|
|
{hosts, _Hosts} ->
|
|
State;
|
|
{host_config, Host, Terms} ->
|
|
lists:foldl(fun(T, S) -> process_host_term(T, Host, S) end,
|
|
State, Terms);
|
|
{clusterid, ClusterID} ->
|
|
add_option(clusterid, ClusterID, State);
|
|
{listen, Listeners} ->
|
|
Listeners2 =
|
|
lists:map(
|
|
fun({PortIP, Module, Opts}) ->
|
|
{Port, IPT, _, _, Proto, OptsClean} =
|
|
ejabberd_listener:parse_listener_portip(PortIP, Opts),
|
|
{{Port, IPT, Proto}, Module, OptsClean}
|
|
end,
|
|
Listeners),
|
|
add_option(listen, Listeners2, State);
|
|
{language, Val} ->
|
|
add_option(language, Val, State);
|
|
{outgoing_s2s_port, Port} ->
|
|
add_option(outgoing_s2s_port, Port, State);
|
|
{outgoing_s2s_options, Methods, Timeout} ->
|
|
add_option(outgoing_s2s_options, {Methods, Timeout}, State);
|
|
{outgoing_s2s_local_address, Addr} ->
|
|
add_option(outgoing_s2s_local_address, Addr, State);
|
|
{s2s_dns_options, PropList} ->
|
|
add_option(s2s_dns_options, PropList, State);
|
|
{s2s_use_starttls, Port} ->
|
|
add_option(s2s_use_starttls, Port, State);
|
|
{s2s_certfile, CertFile} ->
|
|
case ejabberd_config:is_file_readable(CertFile) of
|
|
true -> add_option(s2s_certfile, CertFile, State);
|
|
false ->
|
|
ErrorText = "There is a problem in the configuration: "
|
|
"the specified file is not readable: ",
|
|
throw({error, ErrorText ++ CertFile})
|
|
end;
|
|
{domain_certfile, Domain, CertFile} ->
|
|
case ejabberd_config:is_file_readable(CertFile) of
|
|
true -> add_option({domain_certfile, Domain}, CertFile, State);
|
|
false ->
|
|
ErrorText = "There is a problem in the configuration: "
|
|
"the specified file is not readable: ",
|
|
throw({error, ErrorText ++ CertFile})
|
|
end;
|
|
{node_type, NodeType} ->
|
|
add_option(node_type, NodeType, State);
|
|
{cluster_nodes, Nodes} ->
|
|
add_option(cluster_nodes, Nodes, State);
|
|
{domain_balancing, Domain, Balancing} ->
|
|
add_option({domain_balancing, Domain}, Balancing, State);
|
|
{domain_balancing_component_number, Domain, N} ->
|
|
add_option({domain_balancing_component_number, Domain}, N, State);
|
|
{watchdog_admins, Admins} ->
|
|
add_option(watchdog_admins, Admins, State);
|
|
{watchdog_large_heap, LH} ->
|
|
add_option(watchdog_large_heap, LH, State);
|
|
{registration_timeout, Timeout} ->
|
|
add_option(registration_timeout, Timeout, State);
|
|
{ejabberdctl_access_commands, ACs} ->
|
|
add_option(ejabberdctl_access_commands, ACs, State);
|
|
{captcha_cmd, Cmd} ->
|
|
add_option(captcha_cmd, Cmd, State);
|
|
{captcha_host, Host} ->
|
|
add_option(captcha_host, Host, State);
|
|
{loglevel, Loglevel} ->
|
|
ejabberd_loglevel:set(Loglevel),
|
|
State;
|
|
{max_fsm_queue, N} ->
|
|
add_option(max_fsm_queue, N, State);
|
|
{_Opt, _Val} ->
|
|
process_host_term(Term, global, State)
|
|
end.
|
|
|
|
process_host_term(Term, Host, State) ->
|
|
case Term of
|
|
{acl, ACLName, ACLData} ->
|
|
State#state{opts =
|
|
[acl:to_record(Host, ACLName, ACLData) | State#state.opts]};
|
|
{access, RuleName, Rules} ->
|
|
State#state{opts = [#config{key = {access, RuleName, Host},
|
|
value = Rules} |
|
|
State#state.opts]};
|
|
{shaper, Name, Data} ->
|
|
State#state{opts = [#config{key = {shaper, Name, Host},
|
|
value = Data} |
|
|
State#state.opts]};
|
|
{host, Host} ->
|
|
State;
|
|
{hosts, _Hosts} ->
|
|
State;
|
|
{odbc_server, ODBC_server} ->
|
|
add_option({odbc_server, Host}, ODBC_server, State);
|
|
{auth_method, Methods} ->
|
|
{Methods2, StorageOption} = replace_storage_auth(Host, Methods),
|
|
State2 = case StorageOption of
|
|
{auth_storage, Storage} ->
|
|
add_option({auth_storage, Host}, Storage, State);
|
|
undefined ->
|
|
State
|
|
end,
|
|
add_option({auth_method, Host}, Methods2, State2);
|
|
{Opt, Val} ->
|
|
add_option({Opt, Host}, Val, State)
|
|
end.
|
|
|
|
add_option(Opt, Val, State) ->
|
|
Table = case Opt of
|
|
hosts ->
|
|
config;
|
|
language ->
|
|
config;
|
|
_ ->
|
|
local_config
|
|
end,
|
|
case Table of
|
|
config ->
|
|
State#state{opts = [#config{key = Opt, value = Val} |
|
|
State#state.opts]};
|
|
local_config ->
|
|
case Opt of
|
|
{{add, OptName}, Host} ->
|
|
State#state{opts = compact({OptName, Host}, Val,
|
|
State#state.opts, [])};
|
|
_ ->
|
|
State#state{opts = [#local_config{key = Opt, value = Val} |
|
|
State#state.opts]}
|
|
end
|
|
end.
|
|
|
|
compact({OptName, Host} = Opt, Val, [], Os) ->
|
|
?WARNING_MSG("The option '~p' is defined for the host ~p using host_config "
|
|
"before the global '~p' option. This host_config option may get overwritten.", [OptName, Host, OptName]),
|
|
[#local_config{key = Opt, value = Val}] ++ Os;
|
|
%% Traverse the list of the options already parsed
|
|
compact(Opt, Val, [O | Os1], Os2) ->
|
|
case catch O#local_config.key of
|
|
%% If the key of a local_config matches the Opt that wants to be added
|
|
Opt ->
|
|
%% Then prepend the new value to the list of old values
|
|
Os2 ++ [#local_config{key = Opt,
|
|
value = Val++O#local_config.value}
|
|
] ++ Os1;
|
|
_ ->
|
|
compact(Opt, Val, Os1, Os2++[O])
|
|
end.
|
|
|
|
|
|
set_opts(State) ->
|
|
Opts = lists:reverse(State#state.opts),
|
|
F = fun() ->
|
|
if
|
|
State#state.override_global ->
|
|
Ksg = mnesia:all_keys(config),
|
|
lists:foreach(fun(K) ->
|
|
mnesia:delete({config, K})
|
|
end, Ksg);
|
|
true ->
|
|
ok
|
|
end,
|
|
if
|
|
State#state.override_local ->
|
|
Ksl = mnesia:all_keys(local_config),
|
|
lists:foreach(fun(K) ->
|
|
mnesia:delete({local_config, K})
|
|
end, Ksl);
|
|
true ->
|
|
ok
|
|
end,
|
|
if
|
|
State#state.override_acls ->
|
|
Ksa = mnesia:all_keys(acl),
|
|
lists:foreach(fun(K) ->
|
|
mnesia:delete({acl, K})
|
|
end, Ksa);
|
|
true ->
|
|
ok
|
|
end,
|
|
lists:foreach(fun(R) ->
|
|
mnesia:write(R)
|
|
end, Opts)
|
|
end,
|
|
case mnesia:transaction(F) of
|
|
{atomic, _} -> ok;
|
|
{aborted,{no_exists,Table}} ->
|
|
MnesiaDirectory = mnesia:system_info(directory),
|
|
?ERROR_MSG("Error reading Mnesia database spool files:~n"
|
|
"The Mnesia database couldn't read the spool file for the table '~p'.~n"
|
|
"ejabberd needs read and write access in the directory:~n ~s~n"
|
|
"Maybe the problem is a change in the computer hostname,~n"
|
|
"or a change in the Erlang node name, which is currently:~n ~p~n"
|
|
"Check the ejabberd guide for details about changing the~n"
|
|
"computer hostname or Erlang node name.~n",
|
|
[Table, MnesiaDirectory, node()]),
|
|
exit("Error reading Mnesia database")
|
|
end.
|
|
|
|
|
|
add_global_option(Opt, Val) ->
|
|
mnesia:transaction(fun() ->
|
|
mnesia:write(#config{key = Opt,
|
|
value = Val})
|
|
end).
|
|
|
|
add_local_option(Opt, Val) ->
|
|
mnesia:transaction(fun mne_add_local_option/2, [Opt, Val]).
|
|
|
|
mne_add_local_option(Opt, Val) ->
|
|
mnesia:write(#local_config{key = Opt,
|
|
value = Val}).
|
|
|
|
del_global_option(Opt) ->
|
|
mnesia:transaction(fun() ->
|
|
mnesia:delete({config, Opt})
|
|
end).
|
|
|
|
del_local_option(Opt) ->
|
|
mnesia:transaction(fun() ->
|
|
mnesia:delete({local_config, Opt})
|
|
end).
|
|
|
|
|
|
get_global_option({Opt1, Host} = Opt) when is_list(Host) ->
|
|
case ets:lookup(config, Opt) of
|
|
[#config{value = Val}] ->
|
|
Val;
|
|
_ ->
|
|
get_global_option({Opt1, global})
|
|
end;
|
|
get_global_option(Opt) ->
|
|
case ets:lookup(config, Opt) of
|
|
[#config{value = Val}] when Opt == hosts ->
|
|
ensure_localhost_is_first(Val);
|
|
[#config{value = Val}] ->
|
|
Val;
|
|
_ ->
|
|
undefined
|
|
end.
|
|
|
|
get_local_option({Opt1, Host} = Opt) when is_list(Host) ->
|
|
case ets:lookup(local_config, Opt) of
|
|
[#local_config{value = Val}] ->
|
|
Val;
|
|
_ ->
|
|
get_local_option({Opt1, global})
|
|
end;
|
|
get_local_option(Opt) ->
|
|
case ets:lookup(local_config, Opt) of
|
|
[#local_config{value = Val}] ->
|
|
Val;
|
|
_ ->
|
|
undefined
|
|
end.
|
|
|
|
get_host_option(Host, Option) ->
|
|
case ets:lookup(local_config, {Option, Host}) of
|
|
[#local_config{value=V}] -> V;
|
|
_ -> undefined
|
|
end.
|
|
|
|
mne_del_local_option({_OptName, Host} = Opt) when is_list(Host) ->
|
|
mnesia:delete({local_config, Opt});
|
|
mne_del_local_option({Host, host} = Opt) when is_list(Host) ->
|
|
mnesia:delete({local_config, Opt}).
|
|
|
|
%% Return the list of hosts handled by a given module
|
|
get_vh_by_auth_method(AuthMethod) ->
|
|
mnesia:dirty_select(local_config,
|
|
[{#local_config{key = {auth_method, '$1'},
|
|
value=AuthMethod},[],['$1']}]).
|
|
|
|
%% @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.
|
|
|
|
search(Pattern) ->
|
|
{atomic, Res} = mnesia:transaction(fun mnesia:select/2, [local_config, Pattern]),
|
|
Res.
|
|
|
|
for_host(Host) ->
|
|
mnesia:read({local_config, {Host, host}})
|
|
++ mnesia:select(local_config,
|
|
ets:fun2ms(fun (#local_config{key={_, H}})
|
|
when H =:= Host ->
|
|
object()
|
|
end))
|
|
++ acl:for_host(Host).
|
|
|
|
delete_host(Host) ->
|
|
mnesia_delete_objects(for_host(Host)),
|
|
ok.
|
|
|
|
configure_host(Host, Config) ->
|
|
HostExistenceTerm = {{Host, host}, []},
|
|
Records = host_terms_to_records(Host, [HostExistenceTerm | Config]),
|
|
mnesia_write_objects(Records),
|
|
ok.
|
|
|
|
host_terms_to_records(Host, Terms) ->
|
|
lists:foldl(fun (Term, Acc) ->
|
|
host_term_to_record(Term, Host, Acc)
|
|
end, [], Terms).
|
|
|
|
host_term_to_record({acl, ACLName, ACLData}, Host, Acc) ->
|
|
[acl:to_record(Host, ACLName, ACLData) | Acc];
|
|
host_term_to_record({access, RuleName, Rules}, Host, Acc) ->
|
|
[#config{key={access, RuleName, Host}, value=Rules} | Acc];
|
|
host_term_to_record({shaper, Name, Data}, Host, Acc) ->
|
|
[#config{key={shaper, Name, Host}, value=Data} | Acc];
|
|
host_term_to_record({host, _}, _Host, Acc) -> Acc;
|
|
host_term_to_record({hosts, _}, _Host, Acc) -> Acc;
|
|
host_term_to_record({{Host, host}, []}, Host, Acc) ->
|
|
[#local_config{key={Host, host}, value=[]} | Acc];
|
|
host_term_to_record({Opt, Val}, Host, Acc) when is_atom(Opt) ->
|
|
[#local_config{key={Opt, Host}, value=Val} | Acc].
|
|
|
|
|
|
mnesia_delete_objects(List) when is_list(List) ->
|
|
true = lists:all(fun (I) ->
|
|
ok =:= mnesia:delete_object(I)
|
|
end, List).
|
|
mnesia_write_objects(List) when is_list(List) ->
|
|
true = lists:all(fun (I) ->
|
|
ok =:= mnesia:write(I)
|
|
end, List).
|
|
|
|
%% Replace internal and odbc auth_methods with storage.
|
|
%% Only one storage type can be used, either internal or odbc.
|
|
replace_storage_auth(Host, Val) when not is_list(Val) ->
|
|
replace_storage_auth(Host, [Val]);
|
|
replace_storage_auth(Host, Val) ->
|
|
replace_storage_auth(Host, Val, [], undefined).
|
|
|
|
replace_storage_auth(_Host, [], Val2, Storage) ->
|
|
{lists:reverse(Val2), Storage};
|
|
|
|
replace_storage_auth(Host, [internal = Val | ValT], Val2, undefined) ->
|
|
Storage = {auth_storage, mnesia},
|
|
?WARNING_MSG("The auth method '~p' is deprecated.~nReplace it with 'storage'"
|
|
" and also add this option: ~n~p.", [Val, Storage]),
|
|
replace_storage_auth(Host, ValT, [storage | Val2], Storage);
|
|
|
|
replace_storage_auth(Host, [odbc = Val | ValT], Val2, undefined) ->
|
|
Storage = {auth_storage, odbc},
|
|
?WARNING_MSG("The auth method '~p' is deprecated.~nReplace it with 'storage'"
|
|
" and also add this option: ~n~p.", [Val, Storage]),
|
|
replace_storage_auth(Host, ValT, [storage | Val2], Storage);
|
|
|
|
replace_storage_auth(Host, [Val | ValT], Val2, Storage)
|
|
when (Val /= internal) and (Val /= odbc) ->
|
|
replace_storage_auth(Host, ValT, [Val | Val2], Storage);
|
|
|
|
replace_storage_auth(Host, [Val | _ValT], _Val2, Storage) ->
|
|
?CRITICAL_MSG("The auth method '~p' conflicts with~n~p in the host \"~p\"."
|
|
"~nOnly one of them can be used in each host.",
|
|
[Val, Storage, Host]),
|
|
throw({unacceptable_auth_conflict, Host, Val, Storage}).
|