diff --git a/big_tests/dynamic_domains.spec b/big_tests/dynamic_domains.spec index 90b58f1181d..609d2786756 100644 --- a/big_tests/dynamic_domains.spec +++ b/big_tests/dynamic_domains.spec @@ -60,6 +60,9 @@ {suites, "tests", sm_SUITE}. +{suites, "tests", vcard_SUITE}. +{suites, "tests", vcard_simple_SUITE}. + {config, ["dynamic_domains.config", "test.config"]}. {logdir, "ct_report"}. diff --git a/big_tests/tests/cluster_commands_SUITE.erl b/big_tests/tests/cluster_commands_SUITE.erl index 73ca1cdc423..530fe7d1fef 100644 --- a/big_tests/tests/cluster_commands_SUITE.erl +++ b/big_tests/tests/cluster_commands_SUITE.erl @@ -459,7 +459,7 @@ run_interactive(Cmd, Response, Timeout) -> Port = erlang:open_port({spawn, Cmd}, [exit_status]), %% respond to interactive question (yes/no) Port ! {self(), {command, Response}}, - ejabberdctl_helper:loop(Cmd, Port, [], Timeout). + ejabberdctl_helper:loop(Cmd, [], Port, [], Timeout). nodes_clustered(#{node := Node1Name} = Node1, #{node := Node2Name} = Node2, ShouldBe) -> DbNodes1 = distributed_helper:rpc(Node1, mnesia, system_info, [db_nodes]), diff --git a/big_tests/tests/ejabberdctl_helper.erl b/big_tests/tests/ejabberdctl_helper.erl index 076971d21c5..f967fbb85ec 100644 --- a/big_tests/tests/ejabberdctl_helper.erl +++ b/big_tests/tests/ejabberdctl_helper.erl @@ -44,12 +44,19 @@ run(Cmd, Args, Opts) -> run(Cmd, Args, Opts, Timeout) -> Port = erlang:open_port({spawn_executable, Cmd}, [exit_status, {args, Args} | Opts]), - loop(Cmd, Port, [], Timeout). + loop(Cmd, Args, Port, [], Timeout). -loop(Cmd, Port, Data, Timeout) -> +loop(Cmd, Args, Port, Data, Timeout) -> receive - {Port, {data, NewData}} -> loop(Cmd, Port, Data++NewData, Timeout); - {Port, {exit_status, ExitStatus}} -> {Data, ExitStatus} + {Port, {data, NewData}} -> loop(Cmd, Args, Port, Data++NewData, Timeout); + {Port, {exit_status, ExitStatus}} -> + log_command(Cmd, Args, Data, ExitStatus), + {Data, ExitStatus} after Timeout -> erlang:error(#{reason => timeout, command => Cmd}) end. + +log_command(Cmd, Args, Data, ExitStatus) -> + Pattern = lists:flatten(lists:duplicate(length(Args), " ~s")), + ct:log("Execute ~s " ++ Pattern ++ "~nResult ~p~nExitStatus ~p", + [Cmd] ++ Args ++ [Data, ExitStatus]). diff --git a/big_tests/tests/mongoose_helper.erl b/big_tests/tests/mongoose_helper.erl index 05a5de74e09..22b38a90c09 100644 --- a/big_tests/tests/mongoose_helper.erl +++ b/big_tests/tests/mongoose_helper.erl @@ -45,6 +45,8 @@ -export([restart_listener_with_opts/3]). -export([should_minio_be_running/1]). -export([new_mongoose_acc/1]). +-export([print_debug_info_for_module/1]). +-export([get_vcard_config/1]). -import(distributed_helper, [mim/0, rpc/4]). @@ -509,3 +511,27 @@ should_minio_be_running(Config) -> lists:member(minio, DBs) end. +%% It is useful to debug dynamic IQ handler registration +print_debug_info_for_module(Module) -> + ModConfig = rpc(mim(), gen_mod, hosts_and_opts_with_module, [mod_vcard]), + IqConfig = rpc(mim(), ets, tab2list, [sm_iqtable]), + ct:pal("hosts_and_opts=~p~n iq_handlers=~p~n", + [ModConfig, IqConfig]). + +get_vcard_config(Config) -> + Preset = proplists:get_value(preset, Config, undefined), + Backend = preset_to_vcard_backend(Preset), + [{backend, Backend}, {host, {prefix, <<"vjud.">>}}] + ++ vcard_backend_specific_options(Backend). + +vcard_backend_specific_options(ldap) -> + [{ldap_base, "ou=Users,dc=esl,dc=com"}, + {ldap_filter, "(objectClass=inetOrgPerson)"}]; +vcard_backend_specific_options(_) -> + []. + +preset_to_vcard_backend("ldap_mnesia") -> ldap; +preset_to_vcard_backend("riak_mnesia") -> riak; +preset_to_vcard_backend("internal_mnesia") -> mnesia; +preset_to_vcard_backend("elasticsearch_and_cassandra_mnesia") -> mnesia; +preset_to_vcard_backend(_) -> rdbms. diff --git a/big_tests/tests/vcard_SUITE.erl b/big_tests/tests/vcard_SUITE.erl index 3426cae67b1..14cdb57de56 100644 --- a/big_tests/tests/vcard_SUITE.erl +++ b/big_tests/tests/vcard_SUITE.erl @@ -124,16 +124,18 @@ suite() -> init_per_suite(Config) -> NewConfig = escalus:init_per_suite(Config), NewConfig1 = vcard_config(NewConfig), - NewConfig2 = stop_running_vcard_mod(NewConfig1), + NewConfig2 = prepare_vcard_module(NewConfig1), AliceAndBob = escalus_users:get_users([alice, bob]), - Domain = domain(), + Spec = escalus_users:get_userspec(Config, alice), + Addr = proplists:get_value(host, Spec, <<"localhost">>), + SecDomain = secondary_domain(), BisUsers = [{aliceb, [{username, <<"aliceb">>}, - {server, <>}, - {host, Domain}, + {server, SecDomain}, + {host, Addr}, {password, <<"makota">>}]}, {bobb, [{username, <<"bobb">>}, - {server, <>}, - {host, Domain}, + {server, SecDomain}, + {host, Addr}, {password, <<"makrolika">>}]}], NewUsers = AliceAndBob ++ BisUsers, escalus:create_users([{escalus_users, NewUsers} | NewConfig2], NewUsers). @@ -146,7 +148,7 @@ vcard_config(Config) -> ] ++ Config. end_per_suite(Config) -> - start_running_vcard_mod(Config), + restore_vcard_module(Config), Who = ?config(escalus_users, Config), NewConfig = escalus:delete_users(Config, Who), escalus:end_per_suite(NewConfig). @@ -160,7 +162,7 @@ init_per_group(Group, Config) when Group == rw; Group == params_limited_infinity Config end; init_per_group(ldap_only, Config) -> - VCardConfig = ?config(mod_vcard, Config), + VCardConfig = ?config(mod_vcard_opts, Config), case proplists:get_value(backend, VCardConfig) of ldap -> Config1 = restart_and_prepare_vcard(ldap_only, Config), @@ -528,10 +530,11 @@ search_rsm_pages(Config) -> <<"2">> = RSMCount2 end, ItemTups2 = search_result_item_tuples(Res2), + SecDomain = secondary_domain(), ExpectedItemTups = get_search_results( Config, - [<<"bobb@localhost.bis">>, - <<"aliceb@localhost.bis">>]), + [<<"bobb@", SecDomain/binary>>, + <<"aliceb@", SecDomain/binary>>]), case vcard_update:is_vcard_ldap() of true -> ignore; @@ -606,10 +609,11 @@ search_rsm_forward(Config) -> end, RSMLast2 = get_rsm_last(Res2), ItemTups2 = search_result_item_tuples(Res2), + SecDomain = secondary_domain(), ExpectedItemTups = get_search_results( Config, - [<<"bobb@localhost.bis">>, - <<"aliceb@localhost.bis">>]), + [<<"bobb@", SecDomain/binary>>, + <<"aliceb@", SecDomain/binary>>]), case vcard_update:is_vcard_ldap() of true -> ignore; @@ -714,10 +718,11 @@ search_rsm_backward(Config) -> end, RSMFirst2 = get_rsm_first(Res2), ItemTups2 = search_result_item_tuples(Res2), + SecDomain = secondary_domain(), ExpectedItemTups = get_search_results( Config, - [<<"bobb@localhost.bis">>, - <<"aliceb@localhost.bis">>]), + [<<"bobb@", SecDomain/binary>>, + <<"aliceb@", SecDomain/binary>>]), case vcard_update:is_vcard_ldap() of true -> ignore; @@ -907,7 +912,6 @@ search_some_limited(Config) -> fun(Client) -> Domain = ct:get_config({hosts, mim, secondary_domain}), DirJID = <<"directory.", Domain/binary>>, - Server = escalus_client:server(Client), Fields = [{get_last_name_search_field(), <<"Doe">>}], Res = escalus:send_and_wait(Client, escalus_stanza:search_iq(DirJID, @@ -916,7 +920,8 @@ search_some_limited(Config) -> ItemTups = search_result_item_tuples(Res), %% exactly one result returned and its JID domain is correct [{SomeJID, _JIDsFields}] = ItemTups, - {_Start, _Length} = binary:match(SomeJID, <<"@", Server/binary>>) + true = lists:member(SomeJID, [<<"aliceb@", Domain/binary>>, + <<"bobb@", Domain/binary>>]) end). @@ -960,8 +965,12 @@ search_not_allowed(Config) -> Res = escalus:send_and_wait(Client, escalus_stanza:search_iq(DirJID, escalus_stanza:search_fields(Fields))), - escalus:assert(is_error, [<<"cancel">>, - <<"service-unavailable">>], Res) + escalus:assert(fun(Packet) -> + escalus_pred:is_error(<<"cancel">>, <<"service-unavailable">>, Packet) + orelse + %% A case for dynamic domains + escalus_pred:is_error(<<"cancel">>, <<"remote-server-not-found">>, Packet) + end, [], Res) end). %% disco#items to no.search.domain doesn't say vjud.no.search.domain exists @@ -1000,11 +1009,11 @@ prepare_vcards(Config) -> prepare_vcard(ModVcardBackend, JID, Fields) end end, AllVCards), - timer:sleep(timer:seconds(3)), %give some time to Yokozuna to index vcards + wait_for_riak(), Config. get_backend(Config) -> - case lists:keyfind(backend, 1, ?config(mod_vcard, Config)) of + case lists:keyfind(backend, 1, ?config(mod_vcard_opts, Config)) of {backend, Backend} -> Backend; _ -> @@ -1110,7 +1119,17 @@ get_jid_record(JID) -> {jid, User, Server, <<"">>, User, Server, <<"">>}. vcard_rpc(JID, Stanza) -> - rpc(mim(), ejabberd_sm, route, [JID, JID, Stanza]). + Res = rpc(mim(), ejabberd_router, route, [JID, JID, Stanza]), + case Res of + #{stanza := #{type := <<"set">>}} -> + ok; + _ -> + %% Something is wrong with the IQ handler + %% Let's print enough info + ct:pal("jid=~p~n stanza=~p~n result=~p~n", [JID, Stanza, Res]), + mongoose_helper:print_debug_info_for_module(mod_vcard), + ct:fail(vcard_rpc_failed) + end. restart_vcard_mod(Config, ro_limited) -> restart_mod(params_limited(Config)); @@ -1123,36 +1142,51 @@ restart_vcard_mod(Config, ldap_only) -> restart_vcard_mod(Config, _GN) -> restart_mod(params_all(Config)). -start_running_vcard_mod(Config) -> - Domain = ct:get_config({hosts, mim, domain}), - OriginalVcardConfig = ?config(mod_vcard, Config), - dynamic_modules:start(Domain, mod_vcard, OriginalVcardConfig). -stop_running_vcard_mod(Config) -> - Domain = ct:get_config({hosts, mim, domain}), - CurrentConfigs = rpc(mim(), gen_mod, loaded_modules_with_opts, [Domain]), - {mod_vcard, CurrentVcardConfig} = lists:keyfind(mod_vcard, 1, CurrentConfigs), - dynamic_modules:stop(Domain, mod_vcard), - [{mod_vcard, CurrentVcardConfig} | Config]. +restart_mod(Params) -> + [restart_mod(HostType, Params) || HostType <- host_types()], + ok. + +restart_mod(HostType, Params) -> + dynamic_modules:stop(HostType, mod_vcard), + {ok, _Pid} = dynamic_modules:start(HostType, mod_vcard, Params). + +prepare_vcard_module(Config) -> + %% Keep the old config, so we can undo our changes, once finished testing + Config1 = dynamic_modules:save_modules_for_host_types(host_types(), Config), + %% Get a list of options, we can use as a prototype to start new modules + [{mod_vcard_opts, mongoose_helper:get_vcard_config(Config)} | Config1]. + +restore_vcard_module(Config) -> + dynamic_modules:restore_modules(Config). + +host_types() -> + HostType = ct:get_config({hosts, mim, host_type}), + SecHostType = ct:get_config({hosts, mim, secondary_host_type}), + lists:usort([HostType, SecHostType]). stop_vcard_mod(_Config) -> - Domain = ct:get_config({hosts, mim, domain}), - dynamic_modules:stop(Domain, mod_vcard). + HostType = ct:get_config({hosts, mim, host_type}), + SecHostType = ct:get_config({hosts, mim, secondary_host_type}), + dynamic_modules:stop(HostType, mod_vcard), + dynamic_modules:stop(SecHostType, mod_vcard). params_all(Config) -> - add_backend_param([], ?config(mod_vcard, Config)). + add_backend_param([], ?config(mod_vcard_opts, Config)). params_limited(Config) -> add_backend_param([{matches, 1}, {host, subhost_pattern("directory.@HOST@")}], - ?config(mod_vcard, Config)). + ?config(mod_vcard_opts, Config)). params_limited_infinity(Config) -> add_backend_param([{matches, infinity}, {host, subhost_pattern("directory.@HOST@")}], - ?config(mod_vcard, Config)). + ?config(mod_vcard_opts, Config)). params_no(Config) -> - add_backend_param([{search, false}], ?config(mod_vcard, Config)). + add_backend_param([{search, false}, + {host, subhost_pattern("vjud.@HOST@")}], + ?config(mod_vcard_opts, Config)). params_ldap_only(Config) -> Reported = [{<<"Full Name">>, <<"FN">>}, @@ -1170,7 +1204,7 @@ params_ldap_only(Config) -> add_backend_param([{ldap_search_operator, 'or'}, {ldap_binary_search_fields, [<<"PHOTO">>]}, {ldap_search_reported, Reported}], - ?config(mod_vcard, Config)). + ?config(mod_vcard_opts, Config)). add_backend_param(Opts, CurrentVCardConfig) -> F = fun({Key, _} = Item, Cfg) -> @@ -1178,15 +1212,6 @@ add_backend_param(Opts, CurrentVCardConfig) -> end, lists:foldl(F, CurrentVCardConfig, Opts). - -restart_mod(Params) -> - Domain = ct:get_config({hosts, mim, domain}), - SecDomain = ct:get_config({hosts, mim, secondary_domain}), - dynamic_modules:stop(Domain, mod_vcard), - dynamic_modules:stop(SecDomain, mod_vcard), - {ok, _Pid} = dynamic_modules:start(Domain, mod_vcard, Params), - {ok, _Pid2} = dynamic_modules:start(SecDomain, mod_vcard, Params). - %%---------------------- %% xmlel shortcuts stanza_get_vcard_field(Stanza, FieldName) -> @@ -1330,6 +1355,7 @@ search_result_item_tuples(Stanza) -> get_all_vcards() -> Domain = domain(), + SecDomain = secondary_domain(), [{<<"alice@", Domain/binary>>, [{<<"NICKNAME">>, <<"alice">>}, {<<"FN">>, <<"Wonderland, Alice">>}, @@ -1368,20 +1394,20 @@ get_all_vcards() -> 208, 178, 208, 187, 209, 143, 208, 181, 209, 130, 209, 129, 209, 143, 32, 208, 178, 209, 139>>}]} ] ++ maybe_add_jabberd_id(<<"bob@", Domain/binary>>)}, - {<<"bobb@", Domain/binary, ".bis">>, + {<<"bobb@", SecDomain/binary>>, [{<<"NICKNAME">>, <<"bobb">>}, {<<"FN">>, <<"Doe, Bob">>}, {<<"N">>, [{<<"FAMILY">>, <<"Doe">>}, {<<"GIVEN">>, <<"Bob">>}]} - ] ++ maybe_add_jabberd_id(<<"bobb@", Domain/binary, ".bis">>)}, - {<<"aliceb@", Domain/binary, ".bis">>, + ] ++ maybe_add_jabberd_id(<<"bobb@", SecDomain/binary>>)}, + {<<"aliceb@", SecDomain/binary>>, [{<<"NICKNAME">>, <<"aliceb">>}, {<<"FN">>, <<"Doe, Alice">>}, {<<"N">>, [{<<"FAMILY">>, <<"Doe">>}, {<<"GIVEN">>, <<"Alice">>}]} - ] ++ maybe_add_jabberd_id(<<"aliceb@", Domain/binary, ".bis">>)} + ] ++ maybe_add_jabberd_id(<<"aliceb@", SecDomain/binary>>)} ]. maybe_add_ctry() -> @@ -1405,18 +1431,26 @@ get_server_vcards() -> [{domain(), [{<<"FN">>, <<"MongooseIM">>}, {<<"DESC">>, <<"MongooseIM XMPP Server\nCopyright (c) Erlang Solutions Ltd.">>}]}, - {<<"vjud.localhost">>, + {<<"vjud.", (domain())/binary>>, [{<<"FN">>, <<"MongooseIM/mod_vcard">>}, {<<"DESC">>, <<"MongooseIM vCard module\nCopyright (c) Erlang Solutions Ltd.">>}]}]. get_user_vcard(JID, Config) -> - {JID, ClientVCardTups} = lists:keyfind(JID, 1, ?config(all_vcards, Config)), - ClientVCardTups. + case lists:keyfind(JID, 1, ?config(all_vcards, Config)) of + {JID, ClientVCardTups} -> + ClientVCardTups; + _ -> + ct:fail({get_user_vcard_failed, JID}) + end. get_server_vcard(ServerJid, Config) -> - {ServerJid, VCard} = lists:keyfind(ServerJid, 1, ?config(server_vcards, Config)), - VCard. + case lists:keyfind(ServerJid, 1, ?config(server_vcards, Config)) of + {ServerJid, VCard} -> + VCard; + _ -> + ct:fail({get_server_vcard_failed, ServerJid}) + end. get_search_results(Config, Users) -> [{User, get_search_result(get_user_vcard(User, Config))} || User <- Users]. @@ -1517,3 +1551,15 @@ get_utf8_city() -> domain() -> ct:get_config({hosts, mim, domain}). + +secondary_domain() -> + ct:get_config({hosts, mim, secondary_domain}). + +wait_for_riak() -> + HostType = ct:get_config({hosts, mim, host_type}), + case mam_helper:is_riak_enabled(HostType) of + true -> + timer:sleep(timer:seconds(3)); %give some time to Yokozuna to index vcards + false -> + ok + end. diff --git a/big_tests/tests/vcard_simple_SUITE.erl b/big_tests/tests/vcard_simple_SUITE.erl index d42cd4094b8..97651bba769 100644 --- a/big_tests/tests/vcard_simple_SUITE.erl +++ b/big_tests/tests/vcard_simple_SUITE.erl @@ -70,23 +70,13 @@ suite() -> %%-------------------------------------------------------------------- init_per_suite(Config) -> - NewConfig0 = escalus:init_per_suite(Config), - NewConfig = case is_vcard_ldap() of - true -> - configure_ldap_vcards(NewConfig0); - _ -> - NewConfig0 - end, - escalus:create_users(NewConfig, escalus:get_users([alice, bob])). + Config1 = prepare_vcard_module(escalus:init_per_suite(Config)), + configure_mod_vcard(Config1), + escalus:create_users(Config1, escalus:get_users([alice, bob])). end_per_suite(Config) -> NewConfig = escalus:delete_users(Config, escalus:get_users([alice, bob])), - case is_vcard_ldap() of - true -> - restore_ldap_vcards_config(Config); - _ -> - ok - end, + restore_vcard_module(NewConfig), escalus:end_per_suite(NewConfig). init_per_group(_GN, Config) -> @@ -451,22 +441,37 @@ get_FN(Config) -> <<"Old Name">> end. -configure_ldap_vcards(Config) -> - Domain = ct:get_config({hosts, mim, domain}), - CurrentConfigs = rpc(mim(), gen_mod, loaded_modules_with_opts, [Domain]), - {mod_vcard, CurrentVcardConfig} = lists:keyfind(mod_vcard, 1, CurrentConfigs), - dynamic_modules:stop(Domain, mod_vcard), - Cfg = [{backend,ldap}, {host, subhost_pattern("vjud.@HOST@")}, - {ldap_uids, [{<<"uid">>}]}, %% equivalent to {<<"uid">>, <<"%u">>} - {ldap_filter,"(objectClass=inetOrgPerson)"}, - {ldap_base,"ou=Users,dc=esl,dc=com"}, - {ldap_search_fields, [{"Full Name","cn"},{"User","uid"}]}, - {ldap_vcard_map,[{"FN","%s",["cn"]}]}], - dynamic_modules:start(Domain, mod_vcard, Cfg), - [{mod_vcard, CurrentVcardConfig} | Config]. - -restore_ldap_vcards_config(Config) -> - OriginalConfig = ?config(mod_vcard, Config), - Domain = ct:get_config({hosts, mim, domain}), - dynamic_modules:stop(Domain, mod_vcard), - dynamic_modules:start(Domain, mod_vcard, OriginalConfig). +configure_mod_vcard(Config) -> + HostType = ct:get_config({hosts, mim, host_type}), + case is_vcard_ldap() of + true -> + ensure_started(HostType, ldap_opts()); + _ -> + ensure_started(HostType, ?config(mod_vcard_opts, Config)) + end. + +ldap_opts() -> + [{backend,ldap}, {host, subhost_pattern("vjud.@HOST@")}, + {ldap_uids, [{<<"uid">>}]}, %% equivalent to {<<"uid">>, <<"%u">>} + {ldap_filter,"(objectClass=inetOrgPerson)"}, + {ldap_base,"ou=Users,dc=esl,dc=com"}, + {ldap_search_fields, [{"Full Name","cn"},{"User","uid"}]}, + {ldap_vcard_map,[{"FN","%s",["cn"]}]}]. + +ensure_started(HostType, Opts) -> + dynamic_modules:stop(HostType, mod_vcard), + dynamic_modules:start(HostType, mod_vcard, Opts). + +prepare_vcard_module(Config) -> + %% Keep the old config, so we can undo our changes, once finished testing + Config1 = dynamic_modules:save_modules_for_host_types(host_types(), Config), + %% Get a list of options, we can use as a prototype to start new modules + [{mod_vcard_opts, mongoose_helper:get_vcard_config(Config)} | Config1]. + +host_types() -> + HostType = ct:get_config({hosts, mim, host_type}), + SecHostType = ct:get_config({hosts, mim, secondary_host_type}), + lists:usort([HostType, SecHostType]). + +restore_vcard_module(Config) -> + dynamic_modules:restore_modules(Config). diff --git a/src/admin_extra/service_admin_extra_vcard.erl b/src/admin_extra/service_admin_extra_vcard.erl index 26c4f8ec796..1a8c6c3ed91 100644 --- a/src/admin_extra/service_admin_extra_vcard.erl +++ b/src/admin_extra/service_admin_extra_vcard.erl @@ -183,12 +183,15 @@ get_module_resource(Server) -> get_vcard_content(#jid{lserver = LServer} = NoResJID, Data) -> JID = jid:replace_resource(NoResJID, list_to_binary(get_module_resource(LServer))), IQ = #iq{type = get, xmlns = ?NS_VCARD, sub_el = []}, + {ok, HostType} = mongoose_domain_api:get_domain_host_type(LServer), Acc = mongoose_acc:new(#{ location => ?LOCATION, from_jid => JID, to_jid => JID, lserver => JID#jid.lserver, + host_type => HostType, element => jlib:iq_to_xml(IQ) }), - {_, IQr} = mod_vcard:process_sm_iq(JID, JID, Acc, IQ), + Extra = #{}, + {_, IQr} = mod_vcard:process_sm_iq(Acc, JID, JID, IQ, Extra), case IQr#iq.sub_el of [#xmlel{} = A1] -> case get_vcard(Data, A1) of @@ -215,12 +218,16 @@ set_vcard_content(JID, D, SomeContent) when is_binary(SomeContent) -> set_vcard_content(JID, D, [SomeContent]); set_vcard_content(JID, Data, ContentList) -> IQ = #iq{type = get, xmlns = ?NS_VCARD, sub_el = []}, + {ok, HostType} = mongoose_domain_api:get_domain_host_type(JID#jid.lserver), Acc = mongoose_acc:new(#{ location => ?LOCATION, from_jid => JID, to_jid => JID, lserver => JID#jid.lserver, + host_type => HostType, element => jlib:iq_to_xml(IQ) }), - {Acc1, IQr} = mod_vcard:process_sm_iq(JID, JID, Acc, IQ), + + Extra = #{}, + {Acc1, IQr} = mod_vcard:process_sm_iq(Acc, JID, JID, IQ, Extra), %% Get old vcard A4 = case IQr#iq.sub_el of @@ -234,7 +241,8 @@ set_vcard_content(JID, Data, ContentList) -> %% Build new vcard SubEl = #xmlel{name = <<"vCard">>, attrs = [{<<"xmlns">>, <<"vcard-temp">>}], children = A4}, IQ2 = #iq{type = set, sub_el = SubEl}, - mod_vcard:process_sm_iq(JID, JID, Acc1, IQ2), + Extra = #{}, + mod_vcard:process_sm_iq(Acc1, JID, JID, IQ2, Extra), {ok, ""}. -spec update_vcard_els(Data :: [binary(), ...], diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index a4bf59e88e0..3e5b9038c79 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -426,11 +426,19 @@ format_arg2(Arg, Parse)-> %%----------------------------- %% Format result %%----------------------------- + +format_error(Error) -> + try + io_lib:format("\"~ts\"", [Error]) + catch _ -> + io_lib:format("~p", [Error]) + end. + -spec format_result(In :: tuple() | atom() | integer() | string() | binary(), {_, 'atom'|'integer'|'string'|'binary'} ) -> string() | {string(), _}. -format_result({error, ErrorAtom}, _) -> - {io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)}; +format_result({error, Error}, _) -> + {io_lib:format("Error: ~ts", [format_error(Error)]), make_status(error)}; format_result(Atom, {_Name, atom}) -> io_lib:format("~p", [Atom]); format_result(Int, {_Name, integer}) -> diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 1c0550e52de..9d076e380dc 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -139,6 +139,9 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). +%% You MUST NOT call this function from the big tests. +%% In 99% you should call ejabberd_router:route/3 instead. +%% This function would fail for the first routed IQ. -spec route(From, To, Packet) -> Acc when From :: jid:jid(), To :: jid:jid(), @@ -986,7 +989,7 @@ process_iq(#iq{xmlns = XMLNS} = IQ, From, To, Acc, Packet) -> [{_, IQHandler}] -> gen_iq_component:handle(IQHandler, Acc, From, To, IQ); [] -> - E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Unknown xmlns">>), + E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Unknown xmlns=", XMLNS/binary, " for host=", Host/binary>>), {Acc1, Err} = jlib:make_error_reply(Acc, Packet, E), ejabberd_router:route(To, From, Acc1, Err) end; diff --git a/src/gen_mod.erl b/src/gen_mod.erl index efb0a555de7..455d185bf75 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -63,6 +63,7 @@ set_module_opt/4, set_module_opts/3, get_module_opts/2, + get_loaded_module_opts/2, get_opt_subhost/3, get_module_opt_subhost/3, @@ -71,6 +72,7 @@ loaded_modules_with_opts/0, loaded_modules_with_opts/1, hosts_with_module/1, + hosts_and_opts_with_module/1, get_module_proc/2, is_loaded/2, get_deps/3]). @@ -78,7 +80,8 @@ -export([is_app_running/1]). % we have to mock it in some tests -ignore_xref([behaviour_info/1, loaded_modules_with_opts/0, - loaded_modules_with_opts/1, set_module_opts/3]). + loaded_modules_with_opts/1, set_module_opts/3, + hosts_and_opts_with_module/1]). -include("mongoose.hrl"). @@ -366,6 +369,13 @@ get_module_opts(HostType, Module) -> [#ejabberd_module{opts = Opts} | _] -> Opts end. +get_loaded_module_opts(HostType, Module) -> + OptsList = ets:lookup(ejabberd_modules, {Module, HostType}), + case OptsList of + [] -> error({module_not_loaded, HostType, Module}); + [#ejabberd_module{opts = Opts} | _] -> Opts + end. + %% @doc use this function only on init stage %% Non-atomic! You have been warned. -spec set_module_opt(host_type(), module(), _Opt, _Value) -> boolean(). @@ -450,6 +460,13 @@ hosts_with_module(Module) -> [{#ejabberd_module{_ = '_', module_host_type = {Module, '$1'}}, [], ['$1']}]). +-spec hosts_and_opts_with_module(module()) -> [{host_type(), module_opts()}]. +hosts_and_opts_with_module(Module) -> + ets:select(ejabberd_modules, + [{#ejabberd_module{_ = '_', module_host_type = {Module, '$1'}, + opts = '$2'}, + [], [{{'$1', '$2'}}]}]). + -spec set_module_opts_mnesia(host_type(), module(), [any()]) -> {'aborted', _} | {'atomic', _}. set_module_opts_mnesia(HostType, Module, Opts0) -> diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 9a62d543ff4..89439986ce3 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -45,7 +45,8 @@ -include("mongoose_config_spec.hrl"). %% gen_mod handlers --export([start/2, stop/1]). +-export([start/2, stop/1, + supported_features/0]). %% config_spec -export([config_spec/0, @@ -65,10 +66,10 @@ -export([process_packet/5]). %% Hook handlers --export([process_local_iq/4, - process_sm_iq/4, +-export([process_local_iq/5, + process_sm_iq/5, remove_user/3, - set_vcard/3]). + set_vcard/4]). -export([start_link/2]). -export([default_search_fields/0]). @@ -86,60 +87,67 @@ -define(MOD_VCARD_BACKEND, mod_vcard_backend). -ignore_xref([ {?MOD_VCARD_BACKEND, backend, 0}, - {?MOD_VCARD_BACKEND, search_fields, 1}, - {?MOD_VCARD_BACKEND, get_vcard, 2}, + {?MOD_VCARD_BACKEND, search_fields, 2}, + {?MOD_VCARD_BACKEND, get_vcard, 3}, {?MOD_VCARD_BACKEND, init, 2}, - {?MOD_VCARD_BACKEND, remove_user, 2}, - {?MOD_VCARD_BACKEND, search, 2}, - {?MOD_VCARD_BACKEND, search_reported_fields, 2}, + {?MOD_VCARD_BACKEND, remove_user, 3}, + {?MOD_VCARD_BACKEND, search, 3}, + {?MOD_VCARD_BACKEND, search_reported_fields, 3}, {?MOD_VCARD_BACKEND, tear_down, 1}, - {?MOD_VCARD_BACKEND, set_vcard, 4}, + {?MOD_VCARD_BACKEND, set_vcard, 5}, behaviour_info/1, config_change/4, get_personal_data/3, process_local_iq/4, - process_packet/5, remove_user/3, set_vcard/3, set_vcard/3, start_link/2 + process_packet/5, remove_user/3, set_vcard/4, start_link/2 ]). -define(PROCNAME, ejabberd_mod_vcard). -record(state, {search :: boolean(), - host :: binary()}). + host_type :: mongooseim:host_type()}). -type error() :: error | {error, any()}. --callback init(Host, Opts) -> ok when - Host :: binary(), - Opts :: list(). +-callback init(HostType, Opts) -> ok when + HostType :: mongooseim:host_type(), + Opts :: gen_mod:module_opts(). --callback remove_user(LUser, LServer) -> any() when - LUser :: binary(), - LServer :: binary(). +-callback remove_user(HostType, LUser, LServer) -> any() when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(). --callback set_vcard(User, VHost, VCard, VCardSearch) -> +-callback set_vcard(HostType, LUser, LServer, VCard, VCardSearch) -> ok | {error, Reason :: term()} when - User :: binary(), - VHost :: binary(), + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(), VCard :: term(), VCardSearch :: term(). --callback get_vcard(LUser, LServer) -> +-callback get_vcard(HostType, LUser, LServer) -> {ok, Vcard :: term()} | {error, Reason :: term()} when - LUser :: binary(), - LServer :: binary(). + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(). --callback search(VHost, Data) -> +-callback search(HostType, LServer, Data) -> Res :: term() when - VHost :: binary(), + HostType :: mongooseim:host_type(), + LServer :: jid:lserver(), Data :: term(). --callback search_fields(VHost) -> +-callback search_fields(HostType, LServer) -> Res :: list() when - VHost :: binary(). + HostType :: mongooseim:host_type(), + LServer :: jid:lserver(). --callback search_reported_fields(VHost, Lang) -> +-callback search_reported_fields(HostType, LServer, Lang) -> Res :: term() when - VHost :: jid:lserver(), + HostType :: mongooseim:host_type(), + LServer :: jid:lserver(), Lang :: binary(). --callback tear_down(jid:lserver()) -> ok. +-callback tear_down(HostType) -> ok when + HostType :: mongooseim:host_type(). -optional_callbacks([tear_down/1]). @@ -148,10 +156,10 @@ %%-------------------------------------------------------------------- -spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) -> gdpr:personal_data(). -get_personal_data(Acc, _HostType, #jid{luser = LUser, lserver = LServer}) -> +get_personal_data(Acc, HostType, #jid{luser = LUser, lserver = LServer}) -> Jid = jid:to_binary({LUser, LServer}), Schema = ["jid", "vcard"], - Entries = case mod_vcard_backend:get_vcard(LUser, LServer) of + Entries = case mod_vcard_backend:get_vcard(HostType, LUser, LServer) of {ok, Record} -> SerializedRecords = exml:to_binary(Record), [{Jid, SerializedRecords}]; @@ -174,9 +182,9 @@ default_search_fields() -> {<<"Organization Name">>, <<"orgname">>}, {<<"Organization Unit">>, <<"orgunit">>}]. --spec get_results_limit(jid:lserver()) -> non_neg_integer() | infinity. -get_results_limit(LServer) -> - case gen_mod:get_module_opt(LServer, mod_vcard, matches, ?JUD_MATCHES) of +-spec get_results_limit(mongooseim:host_type()) -> non_neg_integer() | infinity. +get_results_limit(HostType) -> + case gen_mod:get_module_opt(HostType, mod_vcard, matches, ?JUD_MATCHES) of infinity -> infinity; Val when is_integer(Val) and (Val > 0) -> @@ -195,25 +203,80 @@ default_host() -> %% gen_mod callbacks %%-------------------------------------------------------------------- -start(VHost, Opts) -> +start(HostType, Opts) -> gen_mod:start_backend_module(?MODULE, Opts, [set_vcard, get_vcard, search]), - Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), - ChildSpec = {Proc, {?MODULE, start_link, [VHost, Opts]}, + mod_vcard_backend:init(HostType, Opts), + start_hooks(HostType), + start_iq_handlers(HostType, Opts), + Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), + ChildSpec = {Proc, {?MODULE, start_link, [HostType, Opts]}, transient, 1000, worker, [?MODULE]}, ejabberd_sup:start_child(ChildSpec). -stop(VHost) -> - Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), +-spec stop(mongooseim:host_type()) -> ok. +stop(HostType) -> + Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), + stop_hooks(HostType), + stop_iq_handlers(HostType), + stop_backend(HostType), + gen_server:call(Proc, stop), + ejabberd_sup:stop_child(Proc), + ok. + +supported_features() -> [dynamic_domains]. + +start_hooks(HostType) -> + ejabberd_hooks:add(hooks(HostType)). + +stop_hooks(HostType) -> + ejabberd_hooks:delete(hooks(HostType)). + +hooks(HostType) -> + [{Hook, HostType, ?MODULE, Function, Priority} + || {Hook, Function, Priority} <- hooks2()]. + +hooks2() -> + [{remove_user, remove_user, 50}, + {anonymous_purge_hook, remove_user, 50}, + {host_config_update, config_change, 50}, + {set_vcard, set_vcard, 50}, + {get_personal_data, get_personal_data, 50}]. + +start_iq_handlers(HostType, Opts) -> + IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), + gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_VCARD, ejabberd_sm, + fun ?MODULE:process_sm_iq/5, #{}, IQDisc), + gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_VCARD, ejabberd_local, + fun ?MODULE:process_local_iq/5, #{}, IQDisc). + +stop_iq_handlers(HostType) -> + gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_VCARD, ejabberd_local), + gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_VCARD, ejabberd_sm). + +stop_backend(HostType) -> try - mod_vcard_backend:tear_down(VHost) + mod_vcard_backend:tear_down(HostType) catch error:undef -> %% This is expected for other backends than ldap ok - end, + end. - gen_server:call(Proc, stop), - ejabberd_sup:stop_child(Proc). +%% Domain registration +maybe_register_search(false, _HostType, _Opts) -> + ok; +maybe_register_search(true, HostType, Opts) -> + SubdomainPattern = gen_mod:get_opt(host, Opts, default_host()), + PacketHandler = mongoose_packet_handler:new(?MODULE, #{pid => self()}), + %% Always register, even if search functionality is disabled. + %% So, we can send 503 error, instead of 404 error. + mongoose_domain_api:register_subdomain(HostType, SubdomainPattern, PacketHandler). + +maybe_unregister_search(false, _HostType) -> + ok; +maybe_unregister_search(true, HostType) -> + SubdomainPattern = gen_mod:get_module_opt(HostType, ?MODULE, host, default_host()), + mongoose_domain_api:unregister_subdomain(HostType, SubdomainPattern). %%-------------------------------------------------------------------- %% config_spec @@ -310,7 +373,7 @@ process_search_reported_spec(KVs) -> {SF, VF}. %%-------------------------------------------------------------------- -%% mongoose_packet_handler callbacks +%% mongoose_packet_handler callbacks for search %%-------------------------------------------------------------------- -spec process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(), @@ -318,58 +381,28 @@ process_search_reported_spec(KVs) -> process_packet(Acc, From, To, Packet, #{pid := Pid}) -> Pid ! {route, From, To, Acc, Packet}. +handle_route(From, To, Acc, HostType) -> + {IQ, Acc1} = mongoose_iq:info(Acc), + LServer = directory_jid_to_server_host(To), + try do_route(HostType, LServer, From, To, Acc1, IQ) + catch + Class:Reason:Stacktrace -> + ?LOG_ERROR(#{what => vcard_route_failed, acc => Acc, + class => Class, reason => Reason, stacktrace => Stacktrace}) + end. + %%-------------------------------------------------------------------- -%% gen_server callbacks +%% gen_server callbacks for search %%-------------------------------------------------------------------- -start_link(VHost, Opts) -> - Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []). +start_link(HostType, Opts) -> + Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), + gen_server:start_link({local, Proc}, ?MODULE, [HostType, Opts], []). -init([VHost, Opts]) -> +init([HostType, Opts]) -> process_flag(trap_exit, true), - mod_vcard_backend:init(VHost, Opts), - [ ejabberd_hooks:add(Hook, VHost, M, F, Prio) - || {Hook, M, F, Prio} <- hook_handlers() ], - IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - gen_iq_handler:add_iq_handler(ejabberd_sm, VHost, ?NS_VCARD, - ?MODULE, process_sm_iq, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, VHost, ?NS_VCARD, - ?MODULE, process_local_iq, IQDisc), - DirectoryHost = gen_mod:get_opt(host, Opts, default_host()), - Search = gen_mod:get_opt(search, Opts, true), - case Search of - true -> - %% TODO: Conversion of this module is not done, it doesn't support dynamic - %% domains yet. Only subdomain registration is done properly. - PacketHandler = mongoose_packet_handler:new(?MODULE, #{pid => self()}), - mongoose_domain_api:register_subdomain(VHost, DirectoryHost, PacketHandler); - _ -> - ok - end, - {ok, #state{host = VHost, search = Search}}. - -terminate(_Reason, State) -> - VHost = State#state.host, - case State#state.search of - true -> - DirectoryHost = gen_mod:get_module_opt(VHost, ?MODULE, host, default_host()), - mongoose_domain_api:unregister_subdomain(VHost, DirectoryHost); - _ -> - ok - end, - [ ejabberd_hooks:delete(Hook, VHost, M, F, Prio) - || {Hook, M, F, Prio} <- hook_handlers() ], - gen_iq_handler:remove_iq_handler(ejabberd_local, VHost, ?NS_VCARD), - gen_iq_handler:remove_iq_handler(ejabberd_sm, VHost, ?NS_VCARD). - -hook_handlers() -> - %% Hook, Module, Function, Priority - [{remove_user, ?MODULE, remove_user, 50}, - {anonymous_purge_hook, ?MODULE, remove_user, 50}, - {host_config_update, ?MODULE, config_change, 50}, - {set_vcard, ?MODULE, set_vcard, 50}, - {get_personal_data, ?MODULE, get_personal_data, 50}]. + maybe_register_search(Search, HostType, Opts), + {ok, #state{host_type = HostType, search = Search}}. handle_call(get_state, _From, State) -> {reply, {ok, State}, State}; @@ -378,14 +411,8 @@ handle_call(stop, _From, State) -> handle_call(_Request, _From, State) -> {reply, bad_request, State}. -handle_info({route, From, To, Acc, _El}, State) -> - {IQ, Acc1} = mongoose_iq:info(Acc), - try do_route(State#state.host, From, To, Acc1, IQ) - catch - Class:Reason:Stacktrace -> - ?LOG_ERROR(#{what => vcard_route_failed, acc => Acc, - class => Class, reason => Reason, stacktrace => Stacktrace}) - end, +handle_info({route, From, To, Acc, _El}, State = #state{host_type = HostType}) -> + handle_route(From, To, Acc, HostType), {noreply, State}; handle_info(_, State) -> {noreply, State}. @@ -396,12 +423,15 @@ handle_cast(_Request, State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. +terminate(_Reason, #state{host_type = HostType, search = Search}) -> + maybe_unregister_search(Search, HostType). + %%-------------------------------------------------------------------- %% Hook handlers %%-------------------------------------------------------------------- -process_local_iq(_From, _To, Acc, #iq{type = set, sub_el = SubEl} = IQ) -> +process_local_iq(Acc, _From, _To, IQ = #iq{type = set, sub_el = SubEl}, _Extra) -> {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}}; -process_local_iq(_From, _To, Acc, #iq{type = get} = IQ) -> +process_local_iq(Acc, _From, _To, IQ = #iq{type = get}, _Extra) -> DescCData = #xmlcdata{content = [<<"MongooseIM XMPP Server">>, <<"\nCopyright (c) Erlang Solutions Ltd.">>]}, {Acc, IQ#iq{type = result, @@ -414,17 +444,31 @@ process_local_iq(_From, _To, Acc, #iq{type = get} = IQ) -> children = [DescCData]} ]}]}}. -process_sm_iq(From, To, Acc, #iq{type = set, sub_el = VCARD} = IQ) -> +-spec process_sm_iq(Acc :: mongoose_acc:t(), + From :: jid:jid(), + To :: jid:jid(), + IQ :: jlib:iq(), + Extra :: map()) -> + {stop, mongoose_acc:t()} | {mongoose_acc:t(), jlib:iq()}. +process_sm_iq(Acc, From, To, IQ = #iq{type = set, sub_el = VCARD}, _Extra) -> + HostType = mongoose_acc:host_type(Acc), + process_sm_iq_set(HostType, From, To, Acc, IQ, VCARD); +process_sm_iq(Acc, From, To, IQ = #iq{type = get, sub_el = VCARD}, _Extra) -> + HostType = mongoose_acc:host_type(Acc), + process_sm_iq_get(HostType, From, To, Acc, IQ, VCARD). + +process_sm_iq_set(HostType, From, To, Acc, IQ, VCARD) -> #jid{user = FromUser, lserver = FromVHost} = From, #jid{user = ToUser, lserver = ToVHost, resource = ToResource} = To, - Res = case lists:member(FromVHost, ?MYHOSTS) of - true when FromUser == ToUser, FromVHost == ToVHost, ToResource == <<>>; - ToUser == <<>>, ToVHost == <<>> -> - try unsafe_set_vcard(From, VCARD) of + Local = ((FromUser == ToUser) andalso (FromVHost == ToVHost) andalso (ToResource == <<>>)) + orelse ((ToUser == <<>>) andalso (ToVHost == <<>>)), + Res = case Local of + true -> + try unsafe_set_vcard(HostType, From, VCARD) of ok -> IQ#iq{type = result, sub_el = []}; - {error, {invalid_input, Field}} -> - ?LOG_WARNING(#{what => vcard_sm_iq_set_failed, + {error, {invalid_input, {Field, Value}}} -> + ?LOG_WARNING(#{what => vcard_sm_iq_set_failed, value => Value, reason => invalid_input, field => Field, acc => Acc}), Text = io_lib:format("Invalid input for vcard field ~s", [Field]), ReasonEl = mongoose_xmpp_errors:bad_request(<<"en">>, erlang:iolist_to_binary(Text)), @@ -440,12 +484,15 @@ process_sm_iq(From, To, Acc, #iq{type = set, sub_el = VCARD} = IQ) -> vcard_error(IQ, mongoose_xmpp_errors:internal_server_error()) end; _ -> + ?LOG_WARNING(#{what => vcard_sm_iq_get_failed, + reason => not_allowed, acc => Acc}), vcard_error(IQ, mongoose_xmpp_errors:not_allowed()) end, - {Acc, Res}; -process_sm_iq(_, To, Acc, #iq{type = get, sub_el = SubEl} = IQ) -> + {Acc, Res}. + +process_sm_iq_get(HostType, _From, To, Acc, IQ, SubEl) -> #jid{luser = LUser, lserver = LServer} = To, - Res = try mod_vcard_backend:get_vcard(LUser, LServer) of + Res = try mod_vcard_backend:get_vcard(HostType, LUser, LServer) of {ok, VCARD} -> IQ#iq{type = result, sub_el = VCARD}; {error, Reason} -> @@ -457,26 +504,27 @@ process_sm_iq(_, To, Acc, #iq{type = get, sub_el = SubEl} = IQ) -> end, {Acc, Res}. -unsafe_set_vcard(From, VCARD) -> +unsafe_set_vcard(HostType, From, VCARD) -> #jid{user = FromUser, lserver = FromVHost} = From, case parse_vcard(FromUser, FromVHost, VCARD) of {ok, VcardSearch} -> - mod_vcard_backend:set_vcard(FromUser, FromVHost, VCARD, VcardSearch); + mod_vcard_backend:set_vcard(HostType, FromUser, FromVHost, VCARD, VcardSearch); {error, Reason} -> {error, Reason} end. --spec set_vcard(HandlerAcc, From, VCARD) -> Result when +-spec set_vcard(HandlerAcc, HostType, From, VCARD) -> Result when HandlerAcc :: ok | error(), + HostType :: mongooseim:host_type(), From ::jid:jid(), VCARD :: exml:element(), Result :: ok | error(). -set_vcard(ok, _From, _VCARD) -> +set_vcard(ok, _HostType, _From, _VCARD) -> ?LOG_DEBUG(#{what => hook_call_already_handled}), ok; -set_vcard({error, no_handler_defined}, From, VCARD) -> - try unsafe_set_vcard(From, VCARD) of +set_vcard({error, no_handler_defined}, HostType, From, VCARD) -> + try unsafe_set_vcard(HostType, From, VCARD) of ok -> ok; {error, Reason} -> ?LOG_ERROR(#{what => unsafe_set_vcard_failed, reason => Reason}), @@ -486,13 +534,14 @@ set_vcard({error, no_handler_defined}, From, VCARD) -> reason => R, stacktrace => S}), {error, {E, R}} end; -set_vcard({error, _} = E, _From, _VCARD) -> E. +set_vcard({error, _} = E, _HostType, _From, _VCARD) -> E. %% #rh remove_user(Acc, User, Server) -> + HostType = mongoose_acc:host_type(Acc), LUser = jid:nodeprep(User), LServer = jid:nodeprep(Server), - mod_vcard_backend:remove_user(LUser, LServer), + mod_vcard_backend:remove_user(HostType, LUser, LServer), Acc. %% react to "global" config change @@ -514,93 +563,96 @@ config_change(Acc, _, _, _) -> %% ------------------------------------------------------------------ %% Internal %% ------------------------------------------------------------------ -do_route(_VHost, From, #jid{user = User, - resource =Resource} = To, Acc, _IQ) +do_route(_HostType, _LServer, From, + #jid{user = User, resource = Resource} = To, Acc, _IQ) when (User /= <<"">>) or (Resource /= <<"">>) -> {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:service_unavailable()), ejabberd_router:route(To, From, Acc1, Err); -do_route(VHost, From, To, Acc, #iq{type = set, - xmlns = ?NS_SEARCH, - lang = Lang, - sub_el = SubEl} = IQ) -> - - XDataEl = find_xdata_el(SubEl), - RSMIn = jlib:rsm_decode(IQ), - case XDataEl of - false -> - {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:bad_request()), - ejabberd_router:route(To, From, Acc1, Err); - _ -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:bad_request()), - ejabberd_router:route(To, From, Acc1, Err); - _ -> - {SearchResult, RSMOutEls} = search_result(Lang, To, VHost, XData, RSMIn), - ResIQ = IQ#iq{ - type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_SEARCH}], - children = [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"result">>}], - children = SearchResult} - ] ++ RSMOutEls} - ]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)) - end - end; -do_route(VHost, From, To, _Acc, #iq{type = get, - xmlns = ?NS_SEARCH, - lang = Lang} = IQ) -> - ResIQ = - IQ#iq{type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_SEARCH}], - children = ?FORM(To, mod_vcard_backend:search_fields(VHost), Lang) - }]}, +do_route(HostType, LServer, From, To, Acc, + #iq{type = set, xmlns = ?NS_SEARCH, lang = Lang, sub_el = SubEl} = IQ) -> + route_search_iq_set(HostType, LServer, From, To, Acc, Lang, SubEl, IQ); +do_route(HostType, LServer, From, To, _Acc, + #iq{type = get, xmlns = ?NS_SEARCH, lang = Lang} = IQ) -> + Form = ?FORM(To, mod_vcard_backend:search_fields(HostType, LServer), Lang), + ResIQ = make_search_form_result_iq(IQ, Form), ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); -do_route(_VHost, From, To, Acc, #iq{type = set, - xmlns = ?NS_DISCO_INFO}) -> +do_route(_HostType, _LServer, From, To, Acc, + #iq{type = set, xmlns = ?NS_DISCO_INFO}) -> {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:not_allowed()), ejabberd_router:route(To, From, Acc1, Err); -do_route(VHost, From, To, _Acc, #iq{type = get, - xmlns = ?NS_DISCO_INFO, - lang = Lang} = IQ) -> +do_route(HostType, _LServer, From, To, _Acc, + #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ) -> IdentityXML = mongoose_disco:identities_to_xml([identity(Lang)]), FeatureXML = mongoose_disco:features_to_xml(features()), - InfoXML = mongoose_disco:get_info(VHost, ?MODULE, <<>>, <<>>), + InfoXML = mongoose_disco:get_info(HostType, ?MODULE, <<>>, <<>>), ResIQ = IQ#iq{type = result, sub_el = [#xmlel{name = <<"query">>, attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}], children = IdentityXML ++ FeatureXML ++ InfoXML}]}, ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); -do_route(_VHost, From, To, Acc, #iq{type=set, - xmlns = ?NS_DISCO_ITEMS}) -> +do_route(_HostType, _LServer, From, To, Acc, + #iq{type = set, xmlns = ?NS_DISCO_ITEMS}) -> {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:not_allowed()), ejabberd_router:route(To, From, Acc1, Err); -do_route(_VHost, From, To, _Acc, #iq{ type = get, - xmlns = ?NS_DISCO_ITEMS} = IQ) -> +do_route(_HostType, _LServer, From, To, _Acc, + #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ) -> ResIQ = IQ#iq{type = result, sub_el = [#xmlel{name = <<"query">>, attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}]}]}, ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); -do_route(_VHost, From, To, _Acc, #iq{ type = get, - xmlns = ?NS_VCARD, - lang = Lang} = IQ) -> +do_route(_HostType, _LServer, From, To, _Acc, + #iq{type = get, xmlns = ?NS_VCARD} = IQ) -> ResIQ = IQ#iq{type = result, sub_el = [#xmlel{name = <<"vCard">>, attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = iq_get_vcard(Lang)}]}, + children = iq_get_vcard()}]}, ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); -do_route(_VHost, From, To, Acc, _IQ) -> +do_route(_HostType, _LServer, From, To, Acc, _IQ) -> {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:service_unavailable()), ejabberd_router:route(To, From, Acc1, Err). -iq_get_vcard(_Lang) -> +make_search_form_result_iq(IQ, Form) -> + IQ#iq{type = result, + sub_el = [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_SEARCH}], + children = Form + }]}. + +route_search_iq_set(HostType, LServer, From, To, Acc, Lang, SubEl, IQ) -> + XDataEl = find_xdata_el(SubEl), + RSMIn = jlib:rsm_decode(IQ), + case XDataEl of + false -> + {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:bad_request()), + ejabberd_router:route(To, From, Acc1, Err); + _ -> + XData = jlib:parse_xdata_submit(XDataEl), + case XData of + invalid -> + {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:bad_request()), + ejabberd_router:route(To, From, Acc1, Err); + _ -> + {SearchResult, RSMOutEls} = search_result(HostType, LServer, Lang, To, XData, RSMIn), + ResIQ = make_search_result_iq(IQ, SearchResult, RSMOutEls), + ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)) + end + end. + +make_search_result_iq(IQ, SearchResult, RSMOutEls) -> + IQ#iq{ + type = result, + sub_el = [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_SEARCH}], + children = [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}, + {<<"type">>, <<"result">>}], + children = SearchResult} + ] ++ RSMOutEls} + ]}. + +iq_get_vcard() -> [#xmlel{name = <<"FN">>, children = [#xmlcdata{content = <<"MongooseIM/mod_vcard">>}]}, #xmlel{name = <<"URL">>, children = [#xmlcdata{content = ?MONGOOSE_URI}]}, @@ -630,12 +682,12 @@ identity(Lang) -> type => <<"user">>, name => translate:translate(Lang, <<"vCard User Search">>)}. -search_result(Lang, JID, VHost, Data, RSMIn) -> +search_result(HostType, LServer, Lang, JID, Data, RSMIn) -> Text = translate:translate(Lang, <<"Search Results for ">>), TitleEl = #xmlel{name = <<"title">>, children = [#xmlcdata{content = [Text, jid:to_binary(JID)]}]}, - ReportedFields = mod_vcard_backend:search_reported_fields(VHost, Lang), - Results1 = mod_vcard_backend:search(VHost, Data), + ReportedFields = mod_vcard_backend:search_reported_fields(HostType, LServer, Lang), + Results1 = mod_vcard_backend:search(HostType, LServer, Data), Results2 = lists:filtermap( fun(Result) -> case search_result_get_jid(Result) of @@ -650,12 +702,8 @@ search_result(Lang, JID, VHost, Data, RSMIn) -> Results3 = lists:sort(Results2), {Results4, RSMOutEls} = apply_rsm_to_search_results(Results3, RSMIn, none), - Results5 = [Result - || {_, Result} <- Results4], - - {[TitleEl, ReportedFields - | Results5], - RSMOutEls}. + Results5 = [Result || {_, Result} <- Results4], + {[TitleEl, ReportedFields | Results5], RSMOutEls}. %% No RSM input, create empty apply_rsm_to_search_results(Results, none, RSMOut) -> @@ -811,14 +859,14 @@ parse_vcard(User, VHost, VCARD) -> orgunit = OrgUnit, lorgunit = LOrgUnit }} catch - throw:{invalid_input, FieldName} -> - {error, {invalid_input, FieldName}} + throw:{invalid_input, Info} -> + {error, {invalid_input, Info}} end. prepare_index(FieldName, Value) -> case jid:str_tolower(Value) of error -> - throw({invalid_input, FieldName}); + throw({invalid_input, {FieldName, Value}}); LValue -> LValue end. @@ -853,3 +901,11 @@ config_metrics(Host) -> vcard_error(IQ = #iq{sub_el = VCARD}, ReasonEl) -> IQ#iq{type = error, sub_el = [VCARD, ReasonEl]}. + +directory_jid_to_server_host(#jid{lserver = DirHost}) -> + case mongoose_domain_api:get_subdomain_info(DirHost) of + {ok, #{parent_domain := ServerHost}} when is_binary(ServerHost) -> + ServerHost; + Other -> + error({dir_jid_to_server_host_failed, DirHost, Other}) + end. diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index b592b29b149..447a1a9d5ed 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -29,26 +29,17 @@ -module(mod_vcard_ldap). -author('alexey@process-one.net'). --behaviour(gen_server). -behaviour(mod_vcard). -%% gen_server callbacks. --export([init/1, handle_info/2, handle_call/3, - handle_cast/2, code_change/3, terminate/2]). - --export([start_link/2]). - %% mod_vcards callbacks -export([init/2, tear_down/1, - remove_user/2, - get_vcard/2, - set_vcard/4, - search/2, - search_fields/1, - search_reported_fields/2]). - --ignore_xref([start_link/2]). + remove_user/3, + get_vcard/3, + set_vcard/5, + search/3, + search_fields/2, + search_reported_fields/3]). -include("mongoose.hrl"). -include("eldap.hrl"). @@ -130,23 +121,19 @@ %% mod_vcards callbacks %%-------------------------------------------------------------------- -init(VHost, Options) -> - start_link(VHost, Options), +init(_HostType, _Opts) -> ok. -tear_down(LServer) -> - Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), - gen_server:call(Proc, stop), +tear_down(_HostType) -> ok. -remove_user(_LUser, _LServer) -> +remove_user(_HostType, _LUser, _LServer) -> %% no need to handle this - in ldap %% removing user = delete all user info ok. -get_vcard(LUser, LServer) -> - Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), - {ok, State} = gen_server:call(Proc, get_state), +get_vcard(HostType, LUser, LServer) -> + State = get_state(HostType, LServer), LServer = State#state.serverhost, JID = jid:make(LUser, LServer, <<>>), case ejabberd_auth:does_user_exist(JID) of @@ -163,22 +150,19 @@ get_vcard(LUser, LServer) -> {ok, []} end. -set_vcard(_User, _VHost, _VCard, _VCardSearch) -> +set_vcard(_HostType, _User, _LServer, _VCard, _VCardSearch) -> {error, mongoose_xmpp_errors:not_allowed()}. -search(LServer, Data) -> - Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), - {ok, State} = gen_server:call(Proc, get_state), +search(HostType, LServer, Data) -> + State = get_state(HostType, LServer), search_internal(State, Data). -search_fields(Host) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - {ok, State} = gen_server:call(Proc, get_state), +search_fields(HostType, LServer) -> + State = get_state(HostType, LServer), State#state.search_fields. -search_reported_fields(Host, Lang) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - {ok, State} = gen_server:call(Proc, get_state), +search_reported_fields(HostType, LServer, Lang) -> + State = get_state(HostType, LServer), SearchReported = State#state.search_reported, #xmlel{name = <<"reported">>, attrs = [], children = @@ -191,38 +175,6 @@ search_reported_fields(Host, Lang) -> end, SearchReported)}. - -%%-------------------------------------------------------------------- -%% gen server callbacks -%%-------------------------------------------------------------------- - -start_link(Host, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, - [Host, Opts], []). - -init([Host, Opts]) -> - process_flag(trap_exit, true), - State = parse_options(Host, Opts), - {ok, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -handle_call(get_state, _From, State) -> - {reply, {ok, State}, State}; -handle_call(stop, _From, State) -> - {stop, normal, ok, State}; -handle_call(_Request, _From, State) -> - {reply, bad_request, State}. - -handle_cast(_Request, State) -> {noreply, State}. - -code_change(_OldVsn, State, _Extra) -> {ok, State}. - -terminate(_Reason, _State) -> - ok. - %%-------------------------------------------------------------------- %% Internal %%-------------------------------------------------------------------- @@ -451,8 +403,9 @@ process_pattern(Str, {User, Domain}, AttrValues) -> [{<<"%u">>, User}, {<<"%d">>, Domain}] ++ [{<<"%s">>, V, 1} || V <- AttrValues]). -parse_options(Host, Opts) -> - MyHost = gen_mod:get_opt_subhost(Host, Opts, mod_vcard:default_host()), +get_state(HostType, LServer) -> + Opts = gen_mod:get_loaded_module_opts(HostType, mod_vcard), + MyHost = gen_mod:get_opt_subhost(LServer, Opts, mod_vcard:default_host()), Matches = eldap_utils:get_mod_opt(matches, Opts, fun(infinity) -> infinity; (I) when is_integer(I), I>=0 -> I @@ -461,7 +414,7 @@ parse_options(Host, Opts) -> fun(A) when is_atom(A) -> A end, default), Base = eldap_utils:get_base(Opts), DerefAliases = eldap_utils:get_deref_aliases(Opts), - UIDs = eldap_utils:get_uids(Host, Opts), + UIDs = eldap_utils:get_uids(LServer, Opts), UserFilter = eldap_utils:get_user_filter(UIDs, Opts), {ok, SearchFilter} = eldap_filter:parse(eldap_utils:get_search_filter(UserFilter)), @@ -509,9 +462,9 @@ parse_options(Host, Opts) -> SearchOperatorFun, 'and'), BinaryFields = eldap_utils:get_mod_opt(ldap_binary_search_fields, Opts, fun(X) -> X end, []), - #state{serverhost = Host, + #state{serverhost = LServer, myhost = MyHost, - eldap_id = {Host, EldapID}, + eldap_id = {HostType, EldapID}, base = Base, deref = DerefAliases, uids = UIDs, vcard_map = VCardMap, diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl index 699aaecca35..6d36ac6b2a3 100644 --- a/src/mod_vcard_mnesia.erl +++ b/src/mod_vcard_mnesia.erl @@ -4,22 +4,22 @@ %% mod_vcards callbacks -export([init/2, - remove_user/2, - get_vcard/2, - set_vcard/4, - search/2, - search_fields/1, - search_reported_fields/2]). + remove_user/3, + get_vcard/3, + set_vcard/5, + search/3, + search_fields/2, + search_reported_fields/3]). -include("mongoose.hrl"). -include("jlib.hrl"). -include("mod_vcard.hrl"). -init(_VHost, _Options) -> +init(_HostType, _Options) -> prepare_db(), ok. -remove_user(LUser, LServer) -> +remove_user(_HostType, LUser, LServer) -> US = {LUser, LServer}, F = fun() -> mnesia:delete({vcard, US}), @@ -27,7 +27,7 @@ remove_user(LUser, LServer) -> end, mnesia:transaction(F). -get_vcard(LUser, LServer) -> +get_vcard(_HostType, LUser, LServer) -> US = {LUser, LServer}, F = fun() -> mnesia:read({vcard, US}) @@ -46,34 +46,34 @@ get_vcard(LUser, LServer) -> {error, mongoose_xmpp_errors:internal_server_error()} end. -set_vcard(User, VHost, VCard, VCardSearch) -> +set_vcard(HostType, User, LServer, VCard, VCardSearch) -> LUser = jid:nodeprep(User), VCardSearch2 = stringify_search_fields(VCardSearch), F = fun() -> - mnesia:write(#vcard{us ={LUser, VHost}, vcard = VCard}), + mnesia:write(#vcard{us ={LUser, LServer}, vcard = VCard}), mnesia:write(VCardSearch2) end, {atomic, _} = mnesia:transaction(F), - mongoose_hooks:vcard_set(VHost, LUser, VCard), + mongoose_hooks:vcard_set(HostType, LServer, LUser, VCard), ok. -search(VHost, Data) -> - MatchHead = make_matchhead(VHost, Data), - R = do_search(VHost, MatchHead), +search(_HostType, LServer, Data) -> + MatchHead = make_matchhead(LServer, Data), + R = do_search(LServer, MatchHead), lists:map(fun record_to_item/1, R). do_search(_, #vcard_search{_ = '_'}) -> []; -do_search(VHost, MatchHeadIn) -> - MatchHead = MatchHeadIn#vcard_search{us = {'_', VHost}}, +do_search(LServer, MatchHeadIn) -> + MatchHead = MatchHeadIn#vcard_search{us = {'_', LServer}}, case catch mnesia:dirty_select(vcard_search, [{MatchHead, [], ['$_']}]) of {'EXIT', Reason} -> - ?LOG_ERROR(#{what => vcard_search_failed, server => VHost, + ?LOG_ERROR(#{what => vcard_search_failed, server => LServer, reason => Reason}), []; Rs -> - case mod_vcard:get_results_limit(VHost) of + case mod_vcard:get_results_limit(LServer) of infinity -> Rs; Val -> @@ -81,10 +81,10 @@ do_search(VHost, MatchHeadIn) -> end end. -search_fields(_VHost) -> +search_fields(_HostType, _LServer) -> mod_vcard:default_search_fields(). -search_reported_fields(_VHost, Lang) -> +search_reported_fields(_HostType, _LServer, Lang) -> mod_vcard:get_default_reported_fields(Lang). %%-------------------------------------------------------------------- @@ -121,19 +121,19 @@ set_indexes() -> mnesia:add_table_index(vcard_search, lorgname), mnesia:add_table_index(vcard_search, lorgunit). -make_matchhead(VHost, Data) -> +make_matchhead(LServer, Data) -> GlobMatch = #vcard_search{_ = '_'}, - Match = filter_fields(Data, GlobMatch, VHost), + Match = filter_fields(Data, GlobMatch, LServer), Match. -filter_fields([], Match, _VHost) -> +filter_fields([], Match, _LServer) -> Match; -filter_fields([{SVar, [Val]} | Ds], Match, VHost) +filter_fields([{SVar, [Val]} | Ds], Match, LServer) when is_binary(Val) and (Val /= <<"">>) -> LVal = jid:str_tolower(Val), NewMatch = case SVar of - <<"user">> -> Match#vcard_search{luser = make_val(LVal)}; + <<"user">> -> Match#vcard_search{luser = make_val(LVal)}; <<"fn">> -> Match#vcard_search{lfn = make_val(LVal)}; <<"last">> -> Match#vcard_search{lfamily = make_val(LVal)}; <<"first">> -> Match#vcard_search{lgiven = make_val(LVal)}; @@ -147,9 +147,9 @@ filter_fields([{SVar, [Val]} | Ds], Match, VHost) <<"orgunit">> -> Match#vcard_search{lorgunit = make_val(LVal)}; _ -> Match end, - filter_fields(Ds, NewMatch, VHost); -filter_fields([_ | Ds], Match, VHost) -> - filter_fields(Ds, Match, VHost). + filter_fields(Ds, NewMatch, LServer); +filter_fields([_ | Ds], Match, LServer) -> + filter_fields(Ds, Match, LServer). %% Fulltext search is mnesia is something that is really-really wrong stringify_search_fields(#vcard_search{} = S) -> diff --git a/src/mod_vcard_rdbms.erl b/src/mod_vcard_rdbms.erl index 950bcfde0be..19f550dff3c 100644 --- a/src/mod_vcard_rdbms.erl +++ b/src/mod_vcard_rdbms.erl @@ -29,12 +29,12 @@ %% mod_vcards callbacks -export([init/2, - remove_user/2, - get_vcard/2, - set_vcard/4, - search/2, - search_fields/1, - search_reported_fields/2]). + remove_user/3, + get_vcard/3, + set_vcard/5, + search/3, + search_fields/2, + search_reported_fields/3]). -include("mongoose.hrl"). -include("jlib.hrl"). @@ -46,7 +46,7 @@ -type sql_value() :: binary(). -type sql_filter() :: {filter_type(), sql_column(), sql_value()}. -init(VHost, _Options) -> +init(HostType, _Options) -> mongoose_rdbms:prepare(vcard_remove, vcard, [username, server], <<"DELETE FROM vcard WHERE username=? AND server=?">>), mongoose_rdbms:prepare(vcard_search_remove, vcard_search, [lusername, server], @@ -54,28 +54,29 @@ init(VHost, _Options) -> mongoose_rdbms:prepare(vcard_select, vcard, [username, server], <<"SELECT vcard FROM vcard WHERE username=? AND server=?">>), - rdbms_queries:prepare_upsert(VHost, vcard_upsert, vcard, + rdbms_queries:prepare_upsert(HostType, vcard_upsert, vcard, [<<"username">>, <<"server">>, <<"vcard">>], [<<"vcard">>], [<<"username">>, <<"server">>]), SearchColumns = search_columns(), - rdbms_queries:prepare_upsert(VHost, vcard_search_upsert, vcard_search, + rdbms_queries:prepare_upsert(HostType, vcard_search_upsert, vcard_search, [<<"lusername">>, <<"server">>|SearchColumns], SearchColumns, [<<"lusername">>, <<"server">>]), ok. %% Remove user callback -remove_user(LUser, LServer) -> - mongoose_rdbms:sql_transaction(LServer, fun() -> remove_user_t(LUser, LServer) end). +remove_user(HostType, LUser, LServer) -> + F = fun() -> remove_user_t(HostType, LUser, LServer) end, + mongoose_rdbms:sql_transaction(HostType, F). -remove_user_t(LUser, LServer) -> - mongoose_rdbms:execute(LServer, vcard_remove, [LUser, LServer]), - mongoose_rdbms:execute(LServer, vcard_search_remove, [LUser, LServer]). +remove_user_t(HostType, LUser, LServer) -> + mongoose_rdbms:execute(HostType, vcard_remove, [LUser, LServer]), + mongoose_rdbms:execute(HostType, vcard_search_remove, [LUser, LServer]). %% Get a single vCard callback -get_vcard(LUser, LServer) -> - Res = mongoose_rdbms:execute(LServer, vcard_select, [LUser, LServer]), +get_vcard(HostType, LUser, LServer) -> + Res = mongoose_rdbms:execute(HostType, vcard_select, [LUser, LServer]), case Res of {selected, [{SVCARD}]} -> case exml:parse(SVCARD) of @@ -93,17 +94,17 @@ get_vcard(LUser, LServer) -> end. %% Set a vCard callback -set_vcard(User, LServer, VCard, Search) -> +set_vcard(HostType, User, LServer, VCard, Search) -> LUser = jid:nodeprep(User), SearchArgs = assert_binaries(search_args(User, Search)), XML = exml:to_binary(VCard), F = fun() -> - update_vcard_t(LUser, LServer, XML), - update_vcard_search_t(LUser, LServer, SearchArgs), + update_vcard_t(HostType, LUser, LServer, XML), + update_vcard_search_t(HostType, LUser, LServer, SearchArgs), ok end, - Result = handle_result(rdbms_queries:sql_transaction(LServer, F)), - log_upsert_result(LServer, LUser, VCard, XML, Result), + Result = handle_result(rdbms_queries:sql_transaction(HostType, F)), + log_upsert_result(HostType, LServer, LUser, VCard, XML, Result), Result. %% Do not pass unicode strings as a list of bytes into MySQL driver. @@ -118,44 +119,45 @@ assert_binaries(Bins) -> error(#{what => assert_binaries_failed, binaries => Bins}) end. -log_upsert_result(LServer, LUser, VCard, _XML, ok) -> - mongoose_hooks:vcard_set(LServer, LUser, VCard); -log_upsert_result(LServer, LUser, _VCard, XML, {error, Reason}) -> +log_upsert_result(HostType, LServer, LUser, VCard, _XML, ok) -> + mongoose_hooks:vcard_set(HostType, LServer, LUser, VCard); +log_upsert_result(HostType, LServer, LUser, _VCard, XML, {error, Reason}) -> ?LOG_WARNING(#{what => vcard_update_failed, reason => Reason, - user => LUser, host => LServer, vcard_xml => XML}). + host_type => HostType, + user => LUser, server => LServer, vcard_xml => XML}). handle_result({atomic, ok}) -> ok; handle_result({aborted, Reason}) -> {error, {aborted, Reason}}; handle_result({error, Reason}) -> {error, Reason}. -update_vcard_t(LUser, LServer, XML) -> +update_vcard_t(HostType, LUser, LServer, XML) -> InsertParams = [LUser, LServer, XML], UpdateParams = [XML], UniqueKeyValues = [LUser, LServer], - rdbms_queries:execute_upsert(LServer, vcard_upsert, InsertParams, UpdateParams, UniqueKeyValues). + rdbms_queries:execute_upsert(HostType, vcard_upsert, InsertParams, UpdateParams, UniqueKeyValues). -update_vcard_search_t(LUser, LServer, SearchArgs) -> +update_vcard_search_t(HostType, LUser, LServer, SearchArgs) -> InsertParams = [LUser, LServer|SearchArgs], UpdateParams = SearchArgs, UniqueKeyValues = [LUser, LServer], - rdbms_queries:execute_upsert(LServer, vcard_search_upsert, InsertParams, UpdateParams, UniqueKeyValues). + rdbms_queries:execute_upsert(HostType, vcard_search_upsert, InsertParams, UpdateParams, UniqueKeyValues). %% Search vCards fields callback -search_fields(_VHost) -> +search_fields(_HostType, _VHost) -> mod_vcard:default_search_fields(). %% Search vCards reported fields callback -search_reported_fields(_VHost, Lang) -> +search_reported_fields(_HostType, _VHost, Lang) -> mod_vcard:get_default_reported_fields(Lang). %% Search vCards callback -search(LServer, Data) -> +search(HostType, LServer, Data) -> Filters = make_filters(LServer, Data), case Filters of [] -> []; _ -> - Limit = mod_vcard:get_results_limit(LServer), + Limit = mod_vcard:get_results_limit(HostType), LimitType = limit_type(Limit), StmtName = filters_to_statement_name(Filters, LimitType), case mongoose_rdbms:prepared(StmtName) of @@ -168,7 +170,7 @@ search(LServer, Data) -> ok end, Args = filters_to_args(Filters, LimitType, Limit), - try mongoose_rdbms:execute(LServer, StmtName, Args) of + try mongoose_rdbms:execute(HostType, StmtName, Args) of {selected, Rs} when is_list(Rs) -> record_to_items(Rs); Error -> diff --git a/src/mod_vcard_riak.erl b/src/mod_vcard_riak.erl index bb2c29dd650..d4124d4f3bd 100644 --- a/src/mod_vcard_riak.erl +++ b/src/mod_vcard_riak.erl @@ -18,39 +18,33 @@ %% API -export([init/2, - remove_user/2, - set_vcard/4, - get_vcard/2, - search/2, - search_fields/1, - search_reported_fields/2]). + remove_user/3, + set_vcard/5, + get_vcard/3, + search/3, + search_fields/2, + search_reported_fields/3]). -include("mongoose.hrl"). -include("jlib.hrl"). -include("mod_vcard.hrl"). -include_lib("riakc/include/riakc.hrl"). --spec init(jid:lserver(), list()) -> ok. -init(_Host, _Opts) -> +init(_HostType, _Opts) -> ok. --spec remove_user(jid:luser(), jid:lserver()) -> ok. -remove_user(LUser, LServer) -> - mongoose_riak:delete(bucket_type(LServer), LUser, [{dw, 2}]). +remove_user(HostType, LUser, LServer) -> + mongoose_riak:delete(bucket_type(HostType, LServer), LUser, [{dw, 2}]). --spec set_vcard(jid:user(), jid:lserver(), exml:item(), term()) -> - ok | {error, term()}. -set_vcard(User, VHost, VCard, _VCardSearch) -> - BucketType = bucket_type(VHost), +set_vcard(HostType, User, LServer, VCard, _VCardSearch) -> + BucketType = bucket_type(HostType, LServer), VCardEncoded = exml:to_binary(VCard), LUser = jid:nodeprep(User), Obj = riakc_obj:new(BucketType, LUser, VCardEncoded, "application/xml"), mongoose_riak:put(Obj). --spec get_vcard(jid:luser(), jid:lserver()) -> - {ok, term()} | {error, term()}. -get_vcard(LUser, LServer) -> - BucketType = bucket_type(LServer), +get_vcard(HostType, LUser, LServer) -> + BucketType = bucket_type(HostType, LServer), case mongoose_riak:get(BucketType, LUser) of {ok, Obj} -> XMLBin = riakc_obj:get_value(Obj), @@ -68,32 +62,30 @@ get_vcard(LUser, LServer) -> Other end. --spec search(jid:lserver(), list()) -> list(). -search(VHost, Data) -> +search(HostType, LServer, Data) -> YZQuery = make_yz_query(Data, []), - do_search(YZQuery, VHost). + do_search(YZQuery, HostType, LServer). -do_search([], _) -> +do_search([], _HostType, _) -> []; -do_search(YZQueryIn, VHost) -> - {_BucketType, BucketName} = bucket_type(VHost), +do_search(YZQueryIn, HostType, LServer) -> + {_BucketType, BucketName} = bucket_type(HostType, LServer), YZQuery = [<<"_yz_rb:", BucketName/binary>> | YZQueryIn], - Limit = mod_vcard:get_results_limit(VHost), + Limit = mod_vcard:get_results_limit(HostType), YZQueryBin = mongoose_bin:join(YZQuery, <<" AND ">>), - case mongoose_riak:search(yz_vcard_index(VHost), YZQueryBin, [{rows, Limit}]) of + case mongoose_riak:search(yz_vcard_index(HostType), YZQueryBin, [{rows, Limit}]) of {ok, #search_results{docs=R, num_found = _N}} -> - lists:map(fun({_Index, Props}) -> doc2item(VHost, Props) end, R); + lists:map(fun({_Index, Props}) -> doc2item(HostType, LServer, Props) end, R); Err -> - ?LOG_ERROR(#{what => vcard_search_failed, index => yz_vcard_index(VHost), + ?LOG_ERROR(#{what => vcard_search_failed, index => yz_vcard_index(HostType), riak_query => YZQueryBin, reason => Err}), [] end. --spec search_fields(jid:lserver()) -> list(). -search_fields(_VHost) -> +search_fields(_HostType, _LServer) -> mod_vcard:default_search_fields(). -search_reported_fields(_VHost, Lang) -> +search_reported_fields(_HostType, _LServer, Lang) -> mod_vcard:get_default_reported_fields(Lang). make_yz_query([], Acc) -> Acc; @@ -123,8 +115,8 @@ make_val(Val) -> [$", LVal, $"] end. -doc2item(VHost, Props) -> - Vals = lists:map(pa:bind(fun extract_field/2, Props), search_fields(VHost)), +doc2item(HostType, LServer, Props) -> + Vals = lists:map(pa:bind(fun extract_field/2, Props), search_fields(HostType, LServer)), #xmlel{name = <<"item">>, children = Vals}. @@ -143,8 +135,8 @@ extract_field(Props, {_, Field}) -> ?FIELD(Field, V). -bucket_type(Host) -> - {gen_mod:get_module_opt(Host, mod_vcard, bucket_type, <<"vcard">>), <<"vcard_", Host/binary>>}. +bucket_type(HostType, LServer) -> + {gen_mod:get_module_opt(HostType, mod_vcard, bucket_type, <<"vcard">>), <<"vcard_", LServer/binary>>}. -yz_vcard_index(Host) -> - gen_mod:get_module_opt(Host, mod_vcard, search_index, <<"vcard">>). +yz_vcard_index(HostType) -> + gen_mod:get_module_opt(HostType, mod_vcard, search_index, <<"vcard">>). diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index 691772ed30f..b3ae6d840c6 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -40,7 +40,7 @@ user_receive_packet/6, user_sent_keep_alive/2, user_send_packet/4, - vcard_set/3, + vcard_set/4, xmpp_send_element/3, xmpp_stanza_dropped/4]). @@ -388,14 +388,14 @@ session_cleanup(Server, Acc, User, Resource, SID) -> [User, Server, Resource, SID]). %%% @doc The `set_vcard' hook is called when the caller wants to set the VCard. --spec set_vcard(LServer, User, VCard) -> Result when - LServer :: jid:lserver(), - User :: jid:jid(), +-spec set_vcard(HostType, UserJID, VCard) -> Result when + HostType :: mongooseim:host_type(), + UserJID :: jid:jid(), VCard :: exml:element(), Result :: ok | {error, any()}. -set_vcard(LServer, User, VCard) -> - run_hook_for_host_type(set_vcard, LServer, {error, no_handler_defined}, - [User, VCard]). +set_vcard(HostType, UserJID, VCard) -> + run_hook_for_host_type(set_vcard, HostType, {error, no_handler_defined}, + [HostType, UserJID, VCard]). -spec unacknowledged_message(HostType, Acc, JID) -> Result when HostType :: binary(), @@ -489,13 +489,14 @@ user_send_packet(Acc, From, To, Packet) -> %%% @doc The `vcard_set' hook is called to inform that the vcard %%% has been set in mod_vcard backend. --spec vcard_set(Server, LUser, VCard) -> Result when +-spec vcard_set(HostType, Server, LUser, VCard) -> Result when + HostType :: mongooseim:host_type(), Server :: jid:server(), LUser :: jid:luser(), VCard :: exml:element(), Result :: any(). -vcard_set(Server, LUser, VCard) -> - run_hook_for_host_type(vcard_set, Server, ok, [LUser, Server, VCard]). +vcard_set(HostType, Server, LUser, VCard) -> + run_hook_for_host_type(vcard_set, HostType, ok, [HostType, LUser, Server, VCard]). -spec xmpp_send_element(HostType, Acc, El) -> Result when HostType :: binary(), diff --git a/src/wpool/mongoose_wpool.erl b/src/wpool/mongoose_wpool.erl index b9e8962d5d5..4555b570a17 100644 --- a/src/wpool/mongoose_wpool.erl +++ b/src/wpool/mongoose_wpool.erl @@ -74,8 +74,9 @@ Tag :: tag(), WpoolOpts :: pool_opts(), ConnOpts :: conn_opts()}. --type worker_result() :: {ok, pid()} | {error, pool_not_started}. --type pool_record_result() :: {ok, #mongoose_wpool{}} | {error, pool_not_started}. +-type pool_error() :: {pool_not_started, term()}. +-type worker_result() :: {ok, pid()} | {error, pool_error()}. +-type pool_record_result() :: {ok, #mongoose_wpool{}} | {error, pool_error()}. -type start_result() :: {ok, pid()} | {error, term()}. -type stop_result() :: ok | term(). @@ -300,7 +301,7 @@ cast(PoolType, HostType, Tag, HashKey, Request) -> get_pool_settings(PoolType, HostType, Tag) -> case get_pool(PoolType, HostType, Tag) of {ok, PoolRec} -> PoolRec; - {error, pool_not_started} -> undefined + {error, {pool_not_started, _}} -> undefined end. -spec get_pools() -> [pool_name()]. @@ -373,6 +374,6 @@ get_unique_types(Pools) -> get_pool(PoolType, HostType, Tag) -> case ets:lookup(?MODULE, {PoolType, HostType, Tag}) of [] when is_binary(HostType) -> get_pool(PoolType, global, Tag); - [] -> {error, pool_not_started}; + [] -> {error, {pool_not_started, {PoolType, HostType, Tag}}}; [Pool] -> {ok, Pool} end.