%%%---------------------------------------------------------------------- %%% Purpose: Transform old-style Erlang config to YAML config %%% %%% ejabberd, Copyright (C) 2002-2024 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_old_config). %% API -export([read_file/1]). -include("logger.hrl"). %%%=================================================================== %%% API %%%=================================================================== read_file(File) -> case consult(File) of {ok, Terms1} -> ?INFO_MSG("Converting from old configuration format", []), Terms2 = strings_to_binary(Terms1), Terms3 = transform(Terms2), Terms4 = transform_certfiles(Terms3), Terms5 = transform_host_config(Terms4), {ok, collect_options(Terms5)}; {error, Reason} -> {error, {old_config, File, Reason}} end. %%%=================================================================== %%% Internal functions %%%=================================================================== 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(Opts) -> Opts1 = transform_register(Opts), Opts2 = transform_s2s(Opts1), Opts3 = transform_listeners(Opts2), Opts5 = transform_sql(Opts3), Opts6 = transform_shaper(Opts5), Opts7 = transform_s2s_out(Opts6), Opts8 = transform_acl(Opts7), Opts9 = transform_modules(Opts8), Opts10 = transform_globals(Opts9), collect_options(Opts10). %%%=================================================================== %%% mod_register %%%=================================================================== transform_register(Opts) -> try {value, {modules, ModOpts}, Opts1} = lists:keytake(modules, 1, Opts), {value, {?MODULE, RegOpts}, ModOpts1} = lists:keytake(?MODULE, 1, ModOpts), {value, {ip_access, L}, RegOpts1} = lists:keytake(ip_access, 1, RegOpts), true = is_list(L), ?WARNING_MSG("Old 'ip_access' format detected. " "The old format is still supported " "but it is better to fix your config: " "use access rules instead.", []), ACLs = lists:flatmap( fun({Action, S}) -> ACLName = misc:binary_to_atom( iolist_to_binary( ["ip_", S])), [{Action, ACLName}, {acl, ACLName, {ip, S}}] end, L), Access = {access, mod_register_networks, [{Action, ACLName} || {Action, ACLName} <- ACLs]}, [ACL || {acl, _, _} = ACL <- ACLs] ++ [Access, {modules, [{mod_register, [{ip_access, mod_register_networks}|RegOpts1]} | ModOpts1]}|Opts1] catch error:{badmatch, false} -> Opts end. %%%=================================================================== %%% ejabberd_s2s %%%=================================================================== transform_s2s(Opts) -> lists:foldl(fun transform_s2s/2, [], Opts). transform_s2s({{s2s_host, Host}, Action}, Opts) -> ?WARNING_MSG("Option 's2s_host' is deprecated.", []), ACLName = misc:binary_to_atom( iolist_to_binary(["s2s_access_", Host])), [{acl, ACLName, {server, Host}}, {access, s2s, [{Action, ACLName}]}, {s2s_access, s2s} | Opts]; transform_s2s({s2s_default_policy, Action}, Opts) -> ?WARNING_MSG("Option 's2s_default_policy' is deprecated. " "The option is still supported but it is better to " "fix your config: " "use 's2s_access' with an access rule.", []), [{access, s2s, [{Action, all}]}, {s2s_access, s2s} | Opts]; transform_s2s(Opt, Opts) -> [Opt|Opts]. %%%=================================================================== %%% ejabberd_s2s_out %%%=================================================================== transform_s2s_out(Opts) -> lists:foldl(fun transform_s2s_out/2, [], Opts). transform_s2s_out({outgoing_s2s_options, Families, Timeout}, Opts) -> ?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. " "The option is still supported " "but it is better to fix your config: " "use 'outgoing_s2s_timeout' and " "'outgoing_s2s_families' instead.", []), [{outgoing_s2s_families, Families}, {outgoing_s2s_timeout, Timeout} | Opts]; transform_s2s_out({s2s_dns_options, S2SDNSOpts}, AllOpts) -> ?WARNING_MSG("Option 's2s_dns_options' is deprecated. " "The option is still supported " "but it is better to fix your config: " "use 's2s_dns_timeout' and " "'s2s_dns_retries' instead", []), lists:foldr( fun({timeout, T}, AccOpts) -> [{s2s_dns_timeout, T}|AccOpts]; ({retries, R}, AccOpts) -> [{s2s_dns_retries, R}|AccOpts]; (_, AccOpts) -> AccOpts end, AllOpts, S2SDNSOpts); transform_s2s_out(Opt, Opts) -> [Opt|Opts]. %%%=================================================================== %%% ejabberd_listener %%%=================================================================== transform_listeners(Opts) -> lists:foldl(fun transform_listeners/2, [], Opts). transform_listeners({listen, LOpts}, Opts) -> [{listen, lists:map(fun transform_listener/1, LOpts)} | Opts]; transform_listeners(Opt, Opts) -> [Opt|Opts]. transform_listener({{Port, IP, Transport}, Mod, Opts}) -> IPStr = if is_tuple(IP) -> list_to_binary(inet_parse:ntoa(IP)); true -> IP end, Opts1 = lists:map( fun({ip, IPT}) when is_tuple(IPT) -> {ip, list_to_binary(inet_parse:ntoa(IP))}; (ssl) -> {tls, true}; (A) when is_atom(A) -> {A, true}; (Opt) -> Opt end, Opts), Opts2 = lists:foldl( fun(Opt, Acc) -> transform_listen_option(Mod, Opt, Acc) end, [], Opts1), TransportOpt = if Transport == tcp -> []; true -> [{transport, Transport}] end, IPOpt = if IPStr == <<"0.0.0.0">> -> []; true -> [{ip, IPStr}] end, IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2]; transform_listener({{Port, Transport}, Mod, Opts}) when Transport == tcp orelse Transport == udp -> transform_listener({{Port, all_zero_ip(Opts), Transport}, Mod, Opts}); transform_listener({{Port, IP}, Mod, Opts}) -> transform_listener({{Port, IP, tcp}, Mod, Opts}); transform_listener({Port, Mod, Opts}) -> transform_listener({{Port, all_zero_ip(Opts), tcp}, Mod, Opts}); transform_listener(Opt) -> Opt. transform_listen_option(ejabberd_http, captcha, Opts) -> [{captcha, true}|Opts]; transform_listen_option(ejabberd_http, register, Opts) -> [{register, true}|Opts]; transform_listen_option(ejabberd_http, web_admin, Opts) -> [{web_admin, true}|Opts]; transform_listen_option(ejabberd_http, http_bind, Opts) -> [{http_bind, true}|Opts]; transform_listen_option(ejabberd_http, http_poll, Opts) -> [{http_poll, true}|Opts]; transform_listen_option(ejabberd_http, {request_handlers, Hs}, Opts) -> Hs1 = lists:map( fun({PList, Mod}) when is_list(PList) -> Path = iolist_to_binary([[$/, P] || P <- PList]), {Path, Mod}; (Opt) -> Opt end, Hs), [{request_handlers, Hs1} | Opts]; transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) -> case lists:keyfind(hosts, 1, Opts) of {_, PrevHostOpts} -> NewHostOpts = lists:foldl( fun(H, Acc) -> dict:append_list(H, O, Acc) end, dict:from_list(PrevHostOpts), Hosts), [{hosts, dict:to_list(NewHostOpts)}| lists:keydelete(hosts, 1, Opts)]; _ -> [{hosts, [{H, O} || H <- Hosts]}|Opts] end; transform_listen_option(ejabberd_service, {host, Host, Os}, Opts) -> transform_listen_option(ejabberd_service, {hosts, [Host], Os}, Opts); transform_listen_option(ejabberd_xmlrpc, {access_commands, ACOpts}, Opts) -> NewACOpts = lists:map( fun({AName, ACmds, AOpts}) -> {AName, [{commands, ACmds}, {options, AOpts}]}; (Opt) -> Opt end, ACOpts), [{access_commands, NewACOpts}|Opts]; transform_listen_option(_, Opt, Opts) -> [Opt|Opts]. -spec all_zero_ip([proplists:property()]) -> inet:ip_address(). all_zero_ip(Opts) -> case proplists:get_bool(inet6, Opts) of true -> {0,0,0,0,0,0,0,0}; false -> {0,0,0,0} end. %%%=================================================================== %%% ejabberd_shaper %%%=================================================================== transform_shaper(Opts) -> lists:foldl(fun transform_shaper/2, [], Opts). transform_shaper({shaper, Name, {maxrate, N}}, Opts) -> [{shaper, [{Name, N}]} | Opts]; transform_shaper({shaper, Name, none}, Opts) -> [{shaper, [{Name, none}]} | Opts]; transform_shaper({shaper, List}, Opts) when is_list(List) -> R = lists:map( fun({Name, Args}) when is_list(Args) -> MaxRate = proplists:get_value(rate, Args, 1000), BurstSize = proplists:get_value(burst_size, Args, MaxRate), {Name, MaxRate, BurstSize}; ({Name, Val}) -> {Name, Val, Val} end, List), [{shaper, R} | Opts]; transform_shaper(Opt, Opts) -> [Opt | Opts]. %%%=================================================================== %%% acl %%%=================================================================== transform_acl(Opts) -> Opts1 = lists:foldl(fun transform_acl/2, [], Opts), {ACLOpts, Opts2} = lists:mapfoldl( fun({acl, Os}, Acc) -> {Os, Acc}; (O, Acc) -> {[], [O|Acc]} end, [], Opts1), {AccessOpts, Opts3} = lists:mapfoldl( fun({access, Os}, Acc) -> {Os, Acc}; (O, Acc) -> {[], [O|Acc]} end, [], Opts2), {NewAccessOpts, Opts4} = lists:mapfoldl( fun({access_rules, Os}, Acc) -> {Os, Acc}; (O, Acc) -> {[], [O|Acc]} end, [], Opts3), {ShaperOpts, Opts5} = lists:mapfoldl( fun({shaper_rules, Os}, Acc) -> {Os, Acc}; (O, Acc) -> {[], [O|Acc]} end, [], Opts4), ACLOpts1 = collect_options(lists:flatten(ACLOpts)), AccessOpts1 = case collect_options(lists:flatten(AccessOpts)) of [] -> []; L1 -> [{access, L1}] end, ACLOpts2 = case lists:map( fun({ACLName, Os}) -> {ACLName, collect_options(Os)} end, ACLOpts1) of [] -> []; L2 -> [{acl, L2}] end, NewAccessOpts1 = case lists:map( fun({NAName, Os}) -> {NAName, transform_access_rules_config(Os)} end, lists:flatten(NewAccessOpts)) of [] -> []; L3 -> [{access_rules, L3}] end, ShaperOpts1 = case lists:map( fun({SName, Ss}) -> {SName, transform_access_rules_config(Ss)} end, lists:flatten(ShaperOpts)) of [] -> []; L4 -> [{shaper_rules, L4}] end, ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5. transform_acl({acl, Name, Type}, Opts) -> T = case Type of all -> all; none -> none; {user, U} -> {user, [b(U)]}; {user, U, S} -> {user, [[{b(U), b(S)}]]}; {shared_group, G} -> {shared_group, [b(G)]}; {shared_group, G, H} -> {shared_group, [[{b(G), b(H)}]]}; {user_regexp, UR} -> {user_regexp, [b(UR)]}; {user_regexp, UR, S} -> {user_regexp, [[{b(UR), b(S)}]]}; {node_regexp, UR, SR} -> {node_regexp, [[{b(UR), b(SR)}]]}; {user_glob, UR} -> {user_glob, [b(UR)]}; {user_glob, UR, S} -> {user_glob, [[{b(UR), b(S)}]]}; {node_glob, UR, SR} -> {node_glob, [[{b(UR), b(SR)}]]}; {server, S} -> {server, [b(S)]}; {resource, R} -> {resource, [b(R)]}; {server_regexp, SR} -> {server_regexp, [b(SR)]}; {server_glob, S} -> {server_glob, [b(S)]}; {ip, S} -> {ip, [b(S)]}; {resource_glob, R} -> {resource_glob, [b(R)]}; {resource_regexp, R} -> {resource_regexp, [b(R)]} end, [{acl, [{Name, [T]}]}|Opts]; transform_acl({access, Name, Rules}, Opts) -> NewRules = [{ACL, Action} || {Action, ACL} <- Rules], [{access, [{Name, NewRules}]}|Opts]; transform_acl(Opt, Opts) -> [Opt|Opts]. transform_access_rules_config(Config) when is_list(Config) -> lists:map(fun transform_access_rules_config2/1, lists:flatten(Config)); transform_access_rules_config(Config) -> transform_access_rules_config([Config]). transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) -> {Type, [all]}; transform_access_rules_config2({Type, ACL}) when is_atom(ACL) -> {Type, [{acl, ACL}]}; transform_access_rules_config2({Res, Rules}) when is_list(Rules) -> T = lists:map(fun({Type, Args}) when is_list(Args) -> {Type, hd(lists:flatten(Args))}; (V) -> V end, lists:flatten(Rules)), {Res, T}; transform_access_rules_config2({Res, Rule}) -> {Res, [Rule]}. %%%=================================================================== %%% SQL %%%=================================================================== -define(PGSQL_PORT, 5432). -define(MYSQL_PORT, 3306). transform_sql(Opts) -> lists:foldl(fun transform_sql/2, [], Opts). transform_sql({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) -> [{sql_type, Type}, {sql_server, Server}, {sql_port, Port}, {sql_database, DB}, {sql_username, User}, {sql_password, Pass}|Opts]; transform_sql({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) -> transform_sql({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts); transform_sql({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) -> transform_sql({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts); transform_sql({odbc_server, {sqlite, DB}}, Opts) -> [{sql_type, sqlite}, {sql_database, DB}|Opts]; transform_sql({odbc_pool_size, N}, Opts) -> [{sql_pool_size, N}|Opts]; transform_sql(Opt, Opts) -> [Opt|Opts]. %%%=================================================================== %%% modules %%%=================================================================== transform_modules(Opts) -> lists:foldl(fun transform_modules/2, [], Opts). transform_modules({modules, ModOpts}, Opts) -> [{modules, lists:map( fun({Mod, Opts1}) -> {Mod, transform_module(Mod, Opts1)}; (Other) -> Other end, ModOpts)}|Opts]; transform_modules(Opt, Opts) -> [Opt|Opts]. transform_module(mod_disco, Opts) -> lists:map( fun({server_info, Infos}) -> NewInfos = lists:map( fun({Modules, Name, URLs}) -> [[{modules, Modules}, {name, Name}, {urls, URLs}]]; (Opt) -> Opt end, Infos), {server_info, NewInfos}; (Opt) -> Opt end, Opts); transform_module(mod_muc_log, Opts) -> lists:map( fun({top_link, {S1, S2}}) -> {top_link, [{S1, S2}]}; (Opt) -> Opt end, Opts); transform_module(mod_proxy65, Opts) -> lists:map( fun({ip, IP}) when is_tuple(IP) -> {ip, misc:ip_to_list(IP)}; ({hostname, IP}) when is_tuple(IP) -> {hostname, misc:ip_to_list(IP)}; (Opt) -> Opt end, Opts); transform_module(mod_register, Opts) -> lists:flatmap( fun({welcome_message, {Subj, Body}}) -> [{welcome_message, [{subject, Subj}, {body, Body}]}]; (Opt) -> [Opt] end, Opts); transform_module(_Mod, Opts) -> Opts. %%%=================================================================== %%% Host config %%%=================================================================== transform_host_config(Opts) -> Opts1 = lists:foldl(fun transform_host_config/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(O)} || {H, O} <- HOs]}] end, AHOpts1 = case collect_options(lists:flatten(AHOpts)) of [] -> []; AHOs -> [{append_host_config, [{H, transform(O)} || {H, O} <- AHOs]}] end, HOpts1 ++ AHOpts1 ++ Opts3. transform_host_config({host_config, Host, HOpts}, Opts) -> {AddOpts, HOpts1} = lists:mapfoldl( fun({{add, Opt}, Val}, Os) -> {[{Opt, Val}], Os}; (O, Os) -> {[], [O|Os]} end, [], HOpts), [{append_host_config, [{Host, lists:flatten(AddOpts)}]}, {host_config, [{Host, HOpts1}]}|Opts]; transform_host_config(Opt, Opts) -> [Opt|Opts]. %%%=================================================================== %%% Top-level options %%%=================================================================== transform_globals(Opts) -> lists:foldl(fun transform_globals/2, [], Opts). transform_globals(Opt, Opts) when Opt == override_global; Opt == override_local; Opt == override_acls -> ?WARNING_MSG("Option '~ts' has no effect anymore", [Opt]), Opts; transform_globals({node_start, _}, Opts) -> ?WARNING_MSG("Option 'node_start' has no effect anymore", []), Opts; transform_globals({iqdisc, {queues, N}}, Opts) -> [{iqdisc, N}|Opts]; transform_globals({define_macro, Macro, Val}, Opts) -> [{define_macro, [{Macro, Val}]}|Opts]; transform_globals(Opt, Opts) -> [Opt|Opts]. %%%=================================================================== %%% Certfiles %%%=================================================================== transform_certfiles(Opts) -> lists:foldl(fun transform_certfiles/2, [], Opts). transform_certfiles({domain_certfile, Domain, CertFile}, Opts) -> [{host_config, [{Domain, [{domain_certfile, CertFile}]}]}|Opts]; transform_certfiles(Opt, Opts) -> [Opt|Opts]. %%%=================================================================== %%% Consult file %%%=================================================================== consult(File) -> case file:consult(File) of {ok, Terms} -> include_config_files(Terms); Err -> Err end. include_config_files(Terms) -> include_config_files(Terms, []). include_config_files([], Res) -> {ok, 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) -> case consult(Filename) of {ok, Included_terms} -> 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); Err -> Err end; include_config_files([Term | Terms], Res) -> include_config_files(Terms, Res ++ [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 -> delete_disallowed2(Disallowed, T); _ -> [H|delete_disallowed2(Disallowed, T)] end; delete_disallowed2(_, []) -> []. 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), As. %%%=================================================================== %%% Aux functions %%%=================================================================== 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. b(S) -> iolist_to_binary(S).