24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-08 21:43:07 +02:00
xmpp.chapril.org-ejabberd/src/ejabberd_config_transformer.erl
Evgeny Khramtsov ffe1c722e0 Deprecate 'route_subdomains' option
This option was introduced to fulfill requirement of RFC3920 10.3,
but in practice it was very inconvenient and many admins were
forced to change its value to 's2s' (i.e. to behaviour that
violates the RFC). Also, it seems like in RFC6120 this requirement
no longer presents.

Those admins who used this option to block s2s with their subdomains
can use 's2s_access' option for the same purpose.
2019-06-26 10:45:58 +03:00

582 lines
19 KiB
Erlang

%%%----------------------------------------------------------------------
%%% ejabberd, Copyright (C) 2002-2019 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_transformer).
%% API
-export([map_reduce/1]).
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
map_reduce(Y) ->
F = fun(Y1) ->
Y2 = (validator())(Y1),
Y3 = transform(Y2),
if Y2 /= Y3 ->
?DEBUG("Transformed configuration:~s~n",
[misc:format_val({yaml, Y3})]);
true ->
ok
end,
Y3
end,
econf:validate(F, Y).
%%%===================================================================
%%% Transformer
%%%===================================================================
transform(Y) ->
{Y1, Acc1} = transform(global, Y, #{}),
{Y2, Acc2} = update(Y1, Acc1),
filter(global, Y2, Acc2).
transform(Host, Y, Acc) ->
filtermapfoldr(
fun({Opt, HostOpts}, Acc1) when (Opt == host_config orelse
Opt == append_host_config)
andalso Host == global ->
case filtermapfoldr(
fun({Host1, Opts}, Acc2) ->
case transform(Host1, Opts, Acc2) of
{[], Acc3} ->
{false, Acc3};
{Opts1, Acc3} ->
{{true, {Host1, Opts1}}, Acc3}
end
end, Acc1, HostOpts) of
{[], Acc4} ->
{false, Acc4};
{HostOpts1, Acc4} ->
{{true, {Opt, HostOpts1}}, Acc4}
end;
({Opt, Val}, Acc1) ->
transform(Host, Opt, Val, Acc1)
end, Acc, Y).
transform(Host, modules, ModOpts, Acc) ->
{ModOpts1, Acc2} =
lists:mapfoldr(
fun({Mod, Opts}, Acc1) ->
Opts1 = transform_module_options(Opts),
transform_module(Host, Mod, Opts1, Acc1)
end, Acc, ModOpts),
{{true, {modules, ModOpts1}}, Acc2};
transform(global, listen, Listeners, Acc) ->
{Listeners1, Acc2} =
lists:mapfoldr(
fun(Opts, Acc1) ->
transform_listener(Opts, Acc1)
end, Acc, Listeners),
{{true, {listen, Listeners1}}, Acc2};
transform(_Host, Opt, CertFile, Acc) when (Opt == domain_certfile) orelse
(Opt == c2s_certfile) orelse
(Opt == s2s_certfile) ->
?WARNING_MSG("Option '~s' is deprecated and was automatically "
"appended to 'certfiles' option. ~s",
[Opt, adjust_hint()]),
CertFiles = maps:get(certfiles, Acc, []),
Acc1 = maps:put(certfiles, CertFiles ++ [CertFile], Acc),
{false, Acc1};
transform(_Host, certfiles, CertFiles1, Acc) ->
CertFiles2 = maps:get(certfiles, Acc, []),
Acc1 = maps:put(certfiles, CertFiles1 ++ CertFiles2, Acc),
{true, Acc1};
transform(Host, s2s_use_starttls, required_trusted, Acc) ->
?WARNING_MSG("The value 'required_trusted' of option "
"'s2s_use_starttls' is deprecated and was "
"automatically replaced with value 'required'. "
"The module 'mod_s2s_dialback' has also "
"been automatically removed from the configuration. ~s",
[adjust_hint()]),
Hosts = maps:get(remove_s2s_dialback, Acc, []),
Acc1 = maps:put(remove_s2s_dialback, [Host|Hosts], Acc),
{{true, {s2s_use_starttls, required}}, Acc1};
transform(_Host, _Opt, _Val, Acc) ->
{true, Acc}.
update(Y, Acc) ->
set_certfiles(Y, Acc).
filter(Host, Y, Acc) ->
lists:filtermap(
fun({Opt, HostOpts}) when (Opt == host_config orelse
Opt == append_host_config)
andalso Host == global ->
HostOpts1 = lists:map(
fun({Host1, Opts1}) ->
{Host1, filter(Host1, Opts1, Acc)}
end, HostOpts),
{true, {Opt, HostOpts1}};
({Opt, Val}) ->
filter(Host, Opt, Val, Acc)
end, Y).
filter(_Host, ca_path, _, _) ->
warn_removed_option(ca_path, ca_file),
false;
filter(_Host, iqdisc, _, _) ->
warn_removed_option(iqdisc),
false;
filter(_Host, access, _, _) ->
warn_removed_option(access, access_rules),
false;
filter(_Host, commands, _, _) ->
warn_removed_option(commands, api_permissions),
false;
filter(_Host, ejabberdctl_access_commands, _, _) ->
warn_removed_option(ejabberdctl_access_commands, api_permissions),
false;
filter(_Host, commands_admin_access, _, _) ->
warn_removed_option(commands_admin_access, api_permissions),
false;
filter(_Host, ldap_group_cache_size, _, _) ->
warn_removed_option(ldap_group_cache_size, cache_size),
false;
filter(_Host, ldap_user_cache_size, _, _) ->
warn_removed_option(ldap_user_cache_size, cache_size),
false;
filter(_Host, ldap_group_cache_validity, _, _) ->
warn_removed_option(ldap_group_cache_validity, cache_life_time),
false;
filter(_Host, ldap_user_cache_validity, _, _) ->
warn_removed_option(ldap_user_cache_validity, cache_life_time),
false;
filter(_Host, ldap_local_filter, _, _) ->
warn_removed_option(ldap_local_filter),
false;
filter(_Host, deref_aliases, Val, _) ->
warn_replaced_option(deref_aliases, ldap_deref_aliases),
{true, {ldap_deref_aliases, Val}};
filter(_Host, default_db, internal, _) ->
{true, {default_db, mnesia}};
filter(_Host, default_db, odbc, _) ->
{true, {default_db, sql}};
filter(_Host, auth_method, Ms, _) ->
Ms1 = lists:map(
fun(internal) -> mnesia;
(odbc) -> sql;
(M) -> M
end, Ms),
{true, {auth_method, Ms1}};
filter(_Host, default_ram_db, internal, _) ->
{true, {default_ram_db, mnesia}};
filter(_Host, default_ram_db, odbc, _) ->
{true, {default_ram_db, sql}};
filter(_Host, extauth_cache, _, _) ->
?WARNING_MSG("Option 'extauth_cache' is deprecated "
"and has no effect, use authentication "
"or global cache configuration options: "
"auth_use_cache, auth_cache_life_time, "
"use_cache, cache_life_time, and so on", []),
false;
filter(_Host, extauth_instances, Val, _) ->
warn_replaced_option(extauth_instances, extauth_pool_size),
{true, {extauth_pool_size, Val}};
filter(_Host, Opt, Val, _) when Opt == outgoing_s2s_timeout;
Opt == s2s_dns_timeout ->
warn_huge_timeout(Opt, Val),
true;
filter(_Host, captcha_host, _, _) ->
warn_deprecated_option(captcha_host, captcha_url),
true;
filter(_Host, route_subdomains, _, _) ->
warn_removed_option(route_subdomains, s2s_access),
false;
filter(Host, modules, ModOpts, State) ->
NoDialbackHosts = maps:get(remove_s2s_dialback, State, []),
ModOpts1 = lists:filter(
fun({mod_s2s_dialback, _}) ->
not lists:member(Host, NoDialbackHosts);
({mod_echo, _}) ->
warn_removed_module(mod_echo),
false;
(_) ->
true
end, ModOpts),
{true, {modules, ModOpts1}};
filter(_, _, _, _) ->
true.
%%%===================================================================
%%% Listener transformers
%%%===================================================================
transform_listener(Opts, Acc) ->
Opts1 = transform_request_handlers(Opts),
Opts2 = remove_inet_options(Opts1),
collect_listener_certfiles(Opts2, Acc).
transform_request_handlers(Opts) ->
case lists:keyfind(module, 1, Opts) of
{_, ejabberd_http} ->
replace_request_handlers(Opts);
{_, ejabberd_xmlrpc} ->
remove_xmlrpc_access_commands(Opts);
_ ->
Opts
end.
replace_request_handlers(Opts) ->
Handlers = proplists:get_value(request_handlers, Opts, []),
Handlers1 =
lists:foldl(
fun({captcha, true}, Acc) ->
Handler = {<<"/captcha">>, ejabberd_captcha},
warn_replaced_handler(captcha, Handler),
[Handler|Acc];
({register, true}, Acc) ->
Handler = {<<"/register">>, mod_register_web},
warn_replaced_handler(register, Handler),
[Handler|Acc];
({web_admin, true}, Acc) ->
Handler = {<<"/admin">>, ejabberd_web_admin},
warn_replaced_handler(web_admin, Handler),
[Handler|Acc];
({http_bind, true}, Acc) ->
Handler = {<<"/bosh">>, mod_bosh},
warn_replaced_handler(http_bind, Handler),
[Handler|Acc];
({xmlrpc, true}, Acc) ->
Handler = {<<"/">>, ejabberd_xmlrpc},
warn_replaced_handler(xmlrpc, Handler),
Acc ++ [Handler];
(_, Acc) ->
Acc
end, Handlers, Opts),
Handlers2 = lists:map(
fun({Path, mod_http_bind}) ->
warn_replaced_module(mod_http_bind, mod_bosh),
{Path, mod_bosh};
(PathMod) ->
PathMod
end, Handlers1),
lists:filtermap(
fun({captcha, _}) -> false;
({register, _}) -> false;
({web_admin, _}) -> false;
({http_bind, _}) -> false;
({xmlrpc, _}) -> false;
({http_poll, _}) ->
?WARNING_MSG("Listening option 'http_poll' is "
"ignored: HTTP Polling support was "
"removed in ejabberd 15.04. ~s",
[adjust_hint()]),
false;
({request_handlers, _}) ->
{true, {request_handlers, Handlers2}};
(_) -> true
end, Opts).
remove_xmlrpc_access_commands(Opts) ->
lists:filter(
fun({access_commands, _}) ->
warn_removed_option(access_commands, api_permissions),
false;
(_) ->
true
end, Opts).
remove_inet_options(Opts) ->
lists:filter(
fun({Opt, _}) when Opt == inet; Opt == inet6 ->
warn_removed_option(Opt, ip),
false;
(_) ->
true
end, Opts).
collect_listener_certfiles(Opts, Acc) ->
Mod = proplists:get_value(module, Opts),
if Mod == ejabberd_http;
Mod == ejabberd_c2s;
Mod == ejabberd_s2s_in ->
case lists:keyfind(certfile, 1, Opts) of
{_, CertFile} ->
?WARNING_MSG("Listening option 'certfile' of module ~s "
"is deprecated and was automatically "
"appended to global 'certfiles' option. ~s",
[Mod, adjust_hint()]),
CertFiles = maps:get(certfiles, Acc, []),
{proplists:delete(certfile, Opts),
maps:put(certfiles, [CertFile|CertFiles], Acc)};
false ->
{Opts, Acc}
end;
true ->
{Opts, Acc}
end.
%%%===================================================================
%%% Module transformers
%%% NOTE: transform_module_options/1 is called before transform_module/4
%%%===================================================================
transform_module_options(Opts) ->
lists:filtermap(
fun({Opt, internal}) when Opt == db_type;
Opt == ram_db_type ->
{true, {Opt, mnesia}};
({Opt, odbc}) when Opt == db_type;
Opt == ram_db_type ->
{true, {Opt, sql}};
({deref_aliases, Val}) ->
warn_replaced_option(deref_aliases, ldap_deref_aliases),
{true, {ldap_deref_aliases, Val}};
({ldap_group_cache_size, _}) ->
warn_removed_option(ldap_group_cache_size, cache_size),
false;
({ldap_user_cache_size, _}) ->
warn_removed_option(ldap_user_cache_size, cache_size),
false;
({ldap_group_cache_validity, _}) ->
warn_removed_option(ldap_group_cache_validity, cache_life_time),
false;
({ldap_user_cache_validity, _}) ->
warn_removed_option(ldap_user_cache_validity, cache_life_time),
false;
({iqdisc, _}) ->
warn_removed_option(iqdisc),
false;
(_) ->
true
end, Opts).
transform_module(Host, mod_http_bind, Opts, Acc) ->
warn_replaced_module(mod_http_bind, mod_bosh),
transform_module(Host, mod_bosh, Opts, Acc);
transform_module(Host, mod_vcard_xupdate_odbc, Opts, Acc) ->
warn_replaced_module(mod_vcard_xupdate_odbc, mod_vcard_xupdate),
transform_module(Host, mod_vcard_xupdate, Opts, Acc);
transform_module(Host, mod_vcard_ldap, Opts, Acc) ->
warn_replaced_module(mod_vcard_ldap, mod_vcard, ldap),
transform_module(Host, mod_vcard, [{db_type, ldap}|Opts], Acc);
transform_module(Host, M, Opts, Acc) when (M == mod_announce_odbc orelse
M == mod_blocking_odbc orelse
M == mod_caps_odbc orelse
M == mod_last_odbc orelse
M == mod_muc_odbc orelse
M == mod_offline_odbc orelse
M == mod_privacy_odbc orelse
M == mod_private_odbc orelse
M == mod_pubsub_odbc orelse
M == mod_roster_odbc orelse
M == mod_shared_roster_odbc orelse
M == mod_vcard_odbc) ->
M1 = strip_odbc_suffix(M),
warn_replaced_module(M, M1, sql),
transform_module(Host, M1, [{db_type, sql}|Opts], Acc);
transform_module(_Host, mod_blocking, Opts, Acc) ->
Opts1 = lists:filter(
fun({db_type, _}) ->
warn_removed_module_option(db_type, mod_blocking),
false;
(_) ->
true
end, Opts),
{{mod_blocking, Opts1}, Acc};
transform_module(_Host, mod_carboncopy, Opts, Acc) ->
Opts1 = lists:filter(
fun({Opt, _}) when Opt == ram_db_type;
Opt == use_cache;
Opt == cache_size;
Opt == cache_missed;
Opt == cache_life_time ->
warn_removed_module_option(Opt, mod_carboncopy),
false;
(_) ->
true
end, Opts),
{{mod_carboncopy, Opts1}, Acc};
transform_module(_Host, mod_http_api, Opts, Acc) ->
Opts1 = lists:filter(
fun({admin_ip_access, _}) ->
warn_removed_option(admin_ip_access, api_permissions),
false;
(_) ->
true
end, Opts),
{{mod_http_api, Opts1}, Acc};
transform_module(_Host, mod_http_upload, Opts, Acc) ->
Opts1 = lists:filter(
fun({service_url, _}) ->
warn_deprecated_option(service_url, external_secret),
true;
(_) ->
true
end, Opts),
{{mod_http_upload, Opts1}, Acc};
transform_module(_Host, mod_pubsub, Opts, Acc) ->
Opts1 = lists:map(
fun({plugins, Plugins}) ->
{plugins,
lists:filter(
fun(Plugin) ->
case lists:member(
Plugin,
[<<"buddy">>, <<"club">>, <<"dag">>,
<<"dispatch">>, <<"hometree">>, <<"mb">>,
<<"mix">>, <<"online">>, <<"private">>,
<<"public">>]) of
true ->
?WARNING_MSG(
"Plugin '~s' of mod_pubsub is not "
"supported anymore and has been "
"automatically removed from 'plugins' "
"option. ~s",
[Plugin, adjust_hint()]),
false;
false ->
true
end
end, Plugins)};
(Opt) ->
Opt
end, Opts),
{{mod_pubsub, Opts1}, Acc};
transform_module(_Host, Mod, Opts, Acc) ->
{{Mod, Opts}, Acc}.
strip_odbc_suffix(M) ->
[_|T] = lists:reverse(string:tokens(atom_to_list(M), "_")),
list_to_atom(string:join(lists:reverse(T), "_")).
%%%===================================================================
%%% Aux
%%%===================================================================
filtermapfoldr(Fun, Init, List) ->
lists:foldr(
fun(X, {Ret, Acc}) ->
case Fun(X, Acc) of
{true, Acc1} -> {[X|Ret], Acc1};
{{true, X1}, Acc1} -> {[X1|Ret], Acc1};
{false, Acc1} -> {Ret, Acc1}
end
end, {[], Init}, List).
set_certfiles(Y, #{certfiles := CertFiles} = Acc) ->
{lists:keystore(certfiles, 1, Y, {certfiles, CertFiles}), Acc};
set_certfiles(Y, Acc) ->
{Y, Acc}.
%%%===================================================================
%%% Warnings
%%%===================================================================
warn_replaced_module(From, To) ->
?WARNING_MSG("Module ~s is deprecated and was automatically "
"replaced by ~s. ~s",
[From, To, adjust_hint()]).
warn_replaced_module(From, To, Type) ->
?WARNING_MSG("Module ~s is deprecated and was automatically "
"replaced by ~s with db_type: ~s. ~s",
[From, To, Type, adjust_hint()]).
warn_removed_module(Mod) ->
?WARNING_MSG("Module ~s is deprecated and was automatically "
"removed from the configuration. ~s", [Mod, adjust_hint()]).
warn_replaced_handler(Opt, {Path, Module}) ->
?WARNING_MSG("Listening option '~s' is deprecated "
"and was automatically replaced by "
"HTTP request handler: \"~s\" -> ~s. ~s",
[Opt, Path, Module, adjust_hint()]).
warn_deprecated_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~s' is deprecated. Use option '~s' instead.",
[OldOpt, NewOpt]).
warn_replaced_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~s' is deprecated and was automatically "
"replaced by '~s'. ~s",
[OldOpt, NewOpt, adjust_hint()]).
warn_removed_option(Opt) ->
?WARNING_MSG("Option '~s' is deprecated and has no effect anymore. "
"Please remove it from the configuration.", [Opt]).
warn_removed_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~s' is deprecated and has no effect anymore. "
"Use option '~s' instead.", [OldOpt, NewOpt]).
warn_removed_module_option(Opt, Mod) ->
?WARNING_MSG("Option '~s' of module ~s is deprecated "
"and has no effect anymore. ~s",
[Opt, Mod, adjust_hint()]).
warn_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
?WARNING_MSG("Value '~B' of option '~s' is too big, "
"are you sure you have set seconds?",
[T, Opt]);
warn_huge_timeout(_, _) ->
ok.
adjust_hint() ->
"Please adjust your configuration file accordingly. "
"Hint: run `ejabberdctl dump-config` command to view current "
"configuration as it is seen by ejabberd.".
%%%===================================================================
%%% Very raw validator: just to make sure we get properly typed terms
%%% Expand it if you need to transform more options, but don't
%%% abuse complex types: simple and composite types are preferred
%%%===================================================================
validator() ->
Validators =
#{s2s_use_starttls => econf:atom(),
certfiles => econf:list(econf:any()),
c2s_certfile => econf:binary(),
s2s_certfile => econf:binary(),
domain_certfile => econf:binary(),
default_db => econf:atom(),
default_ram_db => econf:atom(),
auth_method => econf:list_or_single(econf:atom()),
listen =>
econf:list(
econf:options(
#{captcha => econf:bool(),
register => econf:bool(),
web_admin => econf:bool(),
http_bind => econf:bool(),
http_poll => econf:bool(),
xmlrpc => econf:bool(),
module => econf:atom(),
certfile => econf:binary(),
request_handlers =>
econf:map(econf:binary(), econf:atom()),
'_' => econf:any()},
[])),
modules =>
econf:options(
#{'_' =>
econf:options(
#{db_type => econf:atom(),
plugins => econf:list(econf:binary()),
'_' => econf:any()},
[])},
[]),
'_' => econf:any()},
econf:options(
Validators#{host_config =>
econf:map(econf:binary(),
econf:options(Validators, [])),
append_host_config =>
econf:map(econf:binary(),
econf:options(Validators, []))},
[]).