From 83191198a15690e3caed559f1dacf69461d4f761 Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Sun, 4 Sep 2005 01:58:47 +0000 Subject: [PATCH] * src/mod_disco.erl: Functions register_sm_feature and register_sm_node replaced with hooks (thanks to Sergei Golovan) * src/mod_vcard.erl: * src/mod_vcard_ldap.erl: * src/mod_disco.erl: Now mod_disco doesn't depend on mod_configure (thanks to Sergei Golovan) * src/mod_configure.erl: Likewise SVN Revision: 408 --- ChangeLog | 11 + src/mod_configure.erl | 444 +++++++++++++++++++++++++-- src/mod_disco.erl | 665 ++++++++++++----------------------------- src/mod_vcard.erl | 27 +- src/mod_vcard_ldap.erl | 28 +- 5 files changed, 663 insertions(+), 512 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0c88d7aa2..0579f2e29 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2005-09-04 Alexey Shchepin + + * src/mod_disco.erl: Functions register_sm_feature and + register_sm_node replaced with hooks (thanks to Sergei Golovan) + * src/mod_vcard.erl: + * src/mod_vcard_ldap.erl: + + * src/mod_disco.erl: Now mod_disco doesn't depend on mod_configure + (thanks to Sergei Golovan) + * src/mod_configure.erl: Likewise + 2005-08-29 Alexey Shchepin * src/ejd2odbc.erl: Converter from mnesia to ODBC diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 435636884..078686747 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -14,6 +14,11 @@ -export([start/2, stop/1, + get_local_identity/5, + get_local_features/5, + get_local_items/5, + get_sm_features/5, + get_sm_items/5, process_local_iq/3, process_sm_iq/3]). @@ -27,14 +32,399 @@ start(Host, Opts) -> ?MODULE, process_local_iq, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_EJABBERD_CONFIG, ?MODULE, process_sm_iq, IQDisc), + ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_items, 50), + ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 50), + ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 50), + ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 50), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), ok. stop(Host) -> + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), + ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50), + ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 50), + ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 50), + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_items, 50), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_EJABBERD_CONFIG), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_EJABBERD_CONFIG). -process_local_iq(From, To, #iq{id = ID, type = Type, xmlns = XMLNS, +-define(EMPTY_INFO_RESULT, {result, Feats}). + +get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; + +get_sm_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> + case {acl:match_rule(LServer, configure, From), Node} of + {allow, []} -> + case Acc of + {result, Features} -> + {result, [?NS_EJABBERD_CONFIG | Features]}; + empty -> + {result, [?NS_EJABBERD_CONFIG]} + end; + _ -> + Acc + end. + +get_local_identity(Acc, _From, _To, Node, _Lang) -> + case Node of + ["running nodes", ENode] -> + [{xmlelement, "identity", + [{"category", "ejabberd"}, + {"type", "node"}, + {"name", ENode}], []}]; + _ -> + Acc + end. + +get_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; + +get_local_features(Acc, _From, _To, [], _Lang) -> + Acc; + +get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> + Feats = case Acc of + {result, Features} -> Features; + empty -> [] + end, + case {acl:match_rule(LServer, configure, From), Node} of + {deny, _} -> + {error, ?ERR_NOT_ALLOWED}; + {allow, ["config"]} -> + ?EMPTY_INFO_RESULT; + {allow, ["online users"]} -> + ?EMPTY_INFO_RESULT; + {allow, ["all users"]} -> + ?EMPTY_INFO_RESULT; + {allow, ["all users", [$@ | _]]} -> + ?EMPTY_INFO_RESULT; + {allow, ["outgoing s2s" | _]} -> + ?EMPTY_INFO_RESULT; + {allow, ["running nodes"]} -> + ?EMPTY_INFO_RESULT; + {allow, ["stopped nodes"]} -> + ?EMPTY_INFO_RESULT; + {allow, ["running nodes", _ENode]} -> + {result, [?NS_STATS | Feats]}; + {allow, ["running nodes", _ENode, "DB"]} -> + {result, [?NS_EJABBERD_CONFIG | Feats]}; + {allow, ["running nodes", _ENode, "modules"]} -> + ?EMPTY_INFO_RESULT; + {allow, ["running nodes", _ENode, "modules", _]} -> + {result, [?NS_EJABBERD_CONFIG | Feats]}; + {allow, ["running nodes", _ENode, "backup"]} -> + ?EMPTY_INFO_RESULT; + {allow, ["running nodes", _ENode, "backup", _]} -> + {result, [?NS_EJABBERD_CONFIG | Feats]}; + {allow, ["running nodes", _ENode, "import"]} -> + ?EMPTY_INFO_RESULT; + {allow, ["running nodes", _ENode, "import", _]} -> + {result, [?NS_EJABBERD_CONFIG | Feats]}; + {allow, ["config", _]} -> + {result, [?NS_EJABBERD_CONFIG | Feats]}; + _ -> + Acc + end. + +get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; + +get_sm_items(Acc, From, + #jid{user = User, server = Server, lserver = LServer} = _To, + Node, _Lang) -> + case {acl:match_rule(LServer, configure, From), Node} of + {allow, []} -> + Items = case Acc of + {result, Its} -> + Its; + empty -> + [] + end, + {result, Items ++ get_user_resources(User, Server)}; + _ -> + Acc + end. + +get_user_resources(User, Server) -> + Rs = ejabberd_sm:get_user_resources(User, Server), + lists:map(fun(R) -> + {xmlelement, "item", + [{"jid", User ++ "@" ++ Server ++ "/" ++ R}, + {"name", User}], []} + end, lists:sort(Rs)). + +get_local_items({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; + +get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) -> + Items = case Acc of + {result, Its} -> + Its; + empty -> + [] + end, + case acl:match_rule(LServer, configure, From) of + deny when Node /= [] -> + {error, ?ERR_NOT_ALLOWED}; + deny -> + {result, Items}; + _ -> + case get_local_items(To#jid.lserver, Node, + jlib:jid_to_string(To), Lang) of + {result, Res} -> + {result, Items ++ Res}; + {error, Error} -> + {error, Error} + end + end. + +-define(NODE(Name, Node), + {xmlelement, "item", + [{"jid", Server}, + {"name", translate:translate(Lang, Name)}, + {"node", Node}], []}). + +get_local_items(_Host, [], Server, Lang) -> + {result, + [?NODE("Configuration", "config"), + ?NODE("Online Users", "online users"), + ?NODE("All Users", "all users"), + ?NODE("Outgoing S2S connections", "outgoing s2s"), + ?NODE("Running Nodes", "running nodes"), + ?NODE("Stopped Nodes", "stopped nodes") + ]}; + +get_local_items(_Host, ["config"], Server, Lang) -> + {result, + [?NODE("Host Name", "config/hostname"), + ?NODE("Access Control Lists", "config/acls"), + ?NODE("Access Rules", "config/access") + % Too expensive on big hosts + %?NODE("Remove Users", "config/remusers") + ]}; + +get_local_items(_Host, ["config", _], _Server, _Lang) -> + {result, []}; + +get_local_items(Host, ["online users"], _Server, _Lang) -> + {result, get_online_vh_users(Host)}; + +get_local_items(Host, ["all users"], _Server, _Lang) -> + {result, get_all_vh_users(Host)}; + +get_local_items(_Host, ["all users", [$@ | Diap]], _Server, _Lang) -> + case catch ejabberd_auth:dirty_get_registered_users() of + {'EXIT', _Reason} -> + ?ERR_INTERNAL_SERVER_ERROR; + Users -> + SUsers = lists:sort([{S, U} || {U, S} <- Users]), + case catch begin + {ok, [S1, S2]} = regexp:split(Diap, "-"), + N1 = list_to_integer(S1), + N2 = list_to_integer(S2), + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), + lists:map(fun({S, U}) -> + {xmlelement, "item", + [{"jid", U ++ "@" ++ S}, + {"name", U ++ "@" ++ S}], []} + end, Sub) + end of + {'EXIT', _Reason} -> + ?ERR_NOT_ACCEPTABLE; + Res -> + {result, Res} + end + end; + +get_local_items(Host, ["outgoing s2s"], _Server, Lang) -> + {result, get_outgoing_s2s(Host, Lang)}; + +get_local_items(Host, ["outgoing s2s", To], _Server, Lang) -> + {result, get_outgoing_s2s(Host, Lang, To)}; + +get_local_items(_Host, ["running nodes"], _Server, Lang) -> + {result, get_running_nodes(Lang)}; + +get_local_items(_Host, ["stopped nodes"], _Server, Lang) -> + {result, get_stopped_nodes(Lang)}; + +get_local_items(_Host, ["running nodes", ENode], Server, Lang) -> + {result, + [?NODE("DB", "running nodes/" ++ ENode ++ "/DB"), + ?NODE("Modules", "running nodes/" ++ ENode ++ "/modules"), + ?NODE("Backup Management", "running nodes/" ++ ENode ++ "/backup"), + ?NODE("Import users from jabberd1.4 spool files", + "running nodes/" ++ ENode ++ "/import") + ]}; + +get_local_items(_Host, ["running nodes", _ENode, "DB"], _Server, _Lang) -> + {result, []}; + +get_local_items(_Host, ["running nodes", ENode, "modules"], Server, Lang) -> + {result, + [?NODE("Start Modules", "running nodes/" ++ ENode ++ "/modules/start"), + ?NODE("Stop Modules", "running nodes/" ++ ENode ++ "/modules/stop") + ]}; + +get_local_items(_Host, ["running nodes", _ENode, "modules", _], _Server, _Lang) -> + {result, []}; + +get_local_items(_Host, ["running nodes", ENode, "backup"], Server, Lang) -> + {result, + [?NODE("Backup", "running nodes/" ++ ENode ++ "/backup/backup"), + ?NODE("Restore", "running nodes/" ++ ENode ++ "/backup/restore"), + ?NODE("Dump to Text File", + "running nodes/" ++ ENode ++ "/backup/textfile") + ]}; + +get_local_items(_Host, ["running nodes", _ENode, "backup", _], _Server, _Lang) -> + {result, []}; + +get_local_items(_Host, ["running nodes", ENode, "import"], Server, Lang) -> + {result, + [?NODE("Import File", "running nodes/" ++ ENode ++ "/import/file"), + ?NODE("Import Directory", "running nodes/" ++ ENode ++ "/import/dir") + ]}; + +get_local_items(_Host, ["running nodes", _ENode, "import", _], _Server, _Lang) -> + {result, []}; + +get_local_items(_Host, _, _Server, _Lang) -> + {error, ?ERR_ITEM_NOT_FOUND}. + + +get_online_vh_users(Host) -> + case catch ejabberd_sm:get_vh_session_list(Host) of + {'EXIT', _Reason} -> + []; + USRs -> + SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]), + lists:map(fun({S, U, R}) -> + {xmlelement, "item", + [{"jid", U ++ "@" ++ S ++ "/" ++ R}, + {"name", U ++ "@" ++ S}], []} + end, SURs) + end. + +get_all_vh_users(Host) -> + case catch ejabberd_auth:get_vh_registered_users(Host) of + {'EXIT', _Reason} -> + []; + Users -> + SUsers = lists:sort([{S, U} || {U, S} <- Users]), + case length(SUsers) of + N when N =< 100 -> + lists:map(fun({S, U}) -> + {xmlelement, "item", + [{"jid", U ++ "@" ++ S}, + {"name", U ++ "@" ++ S}], []} + end, SUsers); + N -> + NParts = trunc(math:sqrt(N * 0.618)) + 1, + M = trunc(N / NParts) + 1, + lists:map(fun(K) -> + L = K + M - 1, + Node = + "@" ++ integer_to_list(K) ++ + "-" ++ integer_to_list(L), + {FS, FU} = lists:nth(K, SUsers), + {LS, LU} = + if L < N -> lists:nth(L, SUsers); + true -> lists:last(SUsers) + end, + Name = + FU ++ "@" ++ FS ++ + " -- " ++ + LU ++ "@" ++ LS, + {xmlelement, "item", + [{"jid", Host}, + {"node", "all users/" ++ Node}, + {"name", Name}], []} + end, lists:seq(1, N, M)) + end + end. + +get_outgoing_s2s(Host, Lang) -> + case catch ejabberd_s2s:dirty_get_connections() of + {'EXIT', _Reason} -> + []; + Connections -> + DotHost = "." ++ Host, + TConns = [TH || {FH, TH} <- Connections, + Host == FH orelse lists:suffix(DotHost, FH)], + lists:map( + fun(T) -> + {xmlelement, "item", + [{"jid", Host}, + {"node", "outgoing s2s/" ++ T}, + {"name", + lists:flatten( + io_lib:format( + translate:translate(Lang, "To ~s"), [T]))}], + []} + end, lists:usort(TConns)) + end. + +get_outgoing_s2s(Host, Lang, To) -> + case catch ejabberd_s2s:dirty_get_connections() of + {'EXIT', _Reason} -> + []; + Connections -> + lists:map( + fun({F, _T}) -> + {xmlelement, "item", + [{"jid", Host}, + {"node", "outgoing s2s/" ++ To ++ "/" ++ F}, + {"name", + lists:flatten( + io_lib:format( + translate:translate(Lang, "From ~s"), [F]))}], + []} + end, lists:keysort(1, lists:filter(fun(E) -> + element(2, E) == To + end, Connections))) + end. + + +get_running_nodes(_Lang) -> + case catch mnesia:system_info(running_db_nodes) of + {'EXIT', _Reason} -> + []; + DBNodes -> + lists:map( + fun(N) -> + S = atom_to_list(N), + {xmlelement, "item", + [{"jid", ?MYNAME}, + {"node", "running nodes/" ++ S}, + {"name", S}], + []} + end, lists:sort(DBNodes)) + end. + +get_stopped_nodes(_Lang) -> + case catch (lists:usort(mnesia:system_info(db_nodes) ++ + mnesia:system_info(extra_db_nodes)) -- + mnesia:system_info(running_db_nodes)) of + {'EXIT', _Reason} -> + []; + DBNodes -> + lists:map( + fun(N) -> + S = atom_to_list(N), + {xmlelement, "item", + [{"jid", ?MYNAME}, + {"node", "stopped nodes/" ++ S}, + {"name", S}], + []} + end, lists:sort(DBNodes)) + end. + + + +process_local_iq(From, To, #iq{type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ) -> case acl:match_rule(To#jid.lserver, configure, From) of deny -> @@ -46,7 +436,7 @@ process_local_iq(From, To, #iq{id = ID, type = Type, xmlns = XMLNS, case XDataEl of false -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; - {xmlelement, _Name, Attrs, SubEls} -> + {xmlelement, _Name, Attrs, _SubEls} -> case xml:get_attr_s("type", Attrs) of "cancel" -> IQ#iq{type = result, @@ -394,7 +784,7 @@ get_form(["config", "remusers"], Lang) -> translate:translate( Lang, "Choose users to remove")}]}] ++ case catch ejabberd_auth:dirty_get_registered_users() of - {'EXIT', Reason} -> + {'EXIT', _Reason} -> []; Users -> lists:map(fun(U) -> @@ -403,12 +793,12 @@ get_form(["config", "remusers"], Lang) -> end }]}; -get_form(_, Lang) -> +get_form(_, _Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. -set_form(["running nodes", ENode, "DB"], Lang, XData) -> +set_form(["running nodes", ENode, "DB"], _Lang, XData) -> case search_running_node(ENode) of false -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -442,7 +832,7 @@ set_form(["running nodes", ENode, "DB"], Lang, XData) -> {result, []} end; -set_form(["running nodes", ENode, "modules", "stop"], Lang, XData) -> +set_form(["running nodes", ENode, "modules", "stop"], _Lang, XData) -> case search_running_node(ENode) of false -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -460,7 +850,7 @@ set_form(["running nodes", ENode, "modules", "stop"], Lang, XData) -> {result, []} end; -set_form(["running nodes", ENode, "modules", "start"], Lang, XData) -> +set_form(["running nodes", ENode, "modules", "start"], _Lang, XData) -> case search_running_node(ENode) of false -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -496,7 +886,7 @@ set_form(["running nodes", ENode, "modules", "start"], Lang, XData) -> end; -set_form(["running nodes", ENode, "backup", "backup"], Lang, XData) -> +set_form(["running nodes", ENode, "backup", "backup"], _Lang, XData) -> case search_running_node(ENode) of false -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -506,9 +896,9 @@ set_form(["running nodes", ENode, "backup", "backup"], Lang, XData) -> {error, ?ERR_BAD_REQUEST}; {value, {_, [String]}} -> case rpc:call(Node, mnesia, backup, [String]) of - {badrpc, Reason} -> + {badrpc, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, Reason} -> + {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; _ -> {result, []} @@ -519,7 +909,7 @@ set_form(["running nodes", ENode, "backup", "backup"], Lang, XData) -> end; -set_form(["running nodes", ENode, "backup", "restore"], Lang, XData) -> +set_form(["running nodes", ENode, "backup", "restore"], _Lang, XData) -> case search_running_node(ENode) of false -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -530,9 +920,9 @@ set_form(["running nodes", ENode, "backup", "restore"], Lang, XData) -> {value, {_, [String]}} -> case rpc:call(Node, mnesia, restore, [String, [{default_op, keep_tables}]]) of - {badrpc, Reason} -> + {badrpc, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, Reason} -> + {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; _ -> {result, []} @@ -543,7 +933,7 @@ set_form(["running nodes", ENode, "backup", "restore"], Lang, XData) -> end; -set_form(["running nodes", ENode, "backup", "textfile"], Lang, XData) -> +set_form(["running nodes", ENode, "backup", "textfile"], _Lang, XData) -> case search_running_node(ENode) of false -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -553,9 +943,9 @@ set_form(["running nodes", ENode, "backup", "textfile"], Lang, XData) -> {error, ?ERR_BAD_REQUEST}; {value, {_, [String]}} -> case rpc:call(Node, mnesia, dump_to_textfile, [String]) of - {badrpc, Reason} -> + {badrpc, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, Reason} -> + {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR}; _ -> {result, []} @@ -566,7 +956,7 @@ set_form(["running nodes", ENode, "backup", "textfile"], Lang, XData) -> end; -set_form(["running nodes", ENode, "import", "file"], Lang, XData) -> +set_form(["running nodes", ENode, "import", "file"], _Lang, XData) -> case search_running_node(ENode) of false -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -583,7 +973,7 @@ set_form(["running nodes", ENode, "import", "file"], Lang, XData) -> end; -set_form(["running nodes", ENode, "import", "dir"], Lang, XData) -> +set_form(["running nodes", ENode, "import", "dir"], _Lang, XData) -> case search_running_node(ENode) of false -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -600,7 +990,7 @@ set_form(["running nodes", ENode, "import", "dir"], Lang, XData) -> end; -set_form(["config", "hostname"], Lang, XData) -> +set_form(["config", "hostname"], _Lang, XData) -> case lists:keysearch("hostname", 1, XData) of false -> {error, ?ERR_BAD_REQUEST}; @@ -613,7 +1003,7 @@ set_form(["config", "hostname"], Lang, XData) -> {error, ?ERR_BAD_REQUEST} end; -set_form(["config", "acls"], Lang, XData) -> +set_form(["config", "acls"], _Lang, XData) -> case lists:keysearch("acls", 1, XData) of {value, {_, Strings}} -> String = lists:foldl(fun(S, Res) -> @@ -639,7 +1029,7 @@ set_form(["config", "acls"], Lang, XData) -> {error, ?ERR_BAD_REQUEST} end; -set_form(["config", "access"], Lang, XData) -> +set_form(["config", "access"], _Lang, XData) -> SetAccess = fun(Rs) -> mnesia:transaction( @@ -685,7 +1075,7 @@ set_form(["config", "access"], Lang, XData) -> {error, ?ERR_BAD_REQUEST} end; -set_form(["config", "remusers"], Lang, XData) -> +set_form(["config", "remusers"], _Lang, XData) -> lists:foreach( fun({Var, Vals}) -> case Vals of @@ -697,7 +1087,7 @@ set_form(["config", "remusers"], Lang, XData) -> end, XData), {result, []}; -set_form(_, Lang, XData) -> +set_form(_, _Lang, _XData) -> {error, ?ERR_SERVICE_UNAVAILABLE}. @@ -730,7 +1120,7 @@ process_sm_iq(From, To, case XDataEl of false -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; - {xmlelement, _Name, Attrs, SubEls} -> + {xmlelement, _Name, Attrs, _SubEls} -> case xml:get_attr_s("type", Attrs) of "cancel" -> IQ#iq{type = result, @@ -815,11 +1205,11 @@ get_sm_form(User, Server, [], Lang) -> % [{xmlelement, "value", [], [{xmlcdata, ?MYNAME}]}]} ]}]}; -get_sm_form(_User, _Server, _Node, Lang) -> +get_sm_form(_User, _Server, _Node, _Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. -set_sm_form(User, Server, [], Lang, XData) -> +set_sm_form(User, Server, [], _Lang, XData) -> case lists:keysearch("action", 1, XData) of {value, {_, ["edit"]}} -> case lists:keysearch("password", 1, XData) of @@ -835,7 +1225,7 @@ set_sm_form(User, Server, [], Lang, XData) -> _ -> {error, ?ERR_BAD_REQUEST} end; -set_sm_form(_User, _Server, _Node, Lang, XData) -> +set_sm_form(_User, _Server, _Node, _Lang, _XData) -> {error, ?ERR_SERVICE_UNAVAILABLE}. find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 511b72ea4..b25f79029 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -16,26 +16,22 @@ stop/1, process_local_iq_items/3, process_local_iq_info/3, + get_local_identity/5, + get_local_features/5, + get_local_services/5, process_sm_iq_items/3, process_sm_iq_info/3, + get_sm_identity/5, + get_sm_features/5, + get_sm_items/5, register_feature/2, unregister_feature/2, register_extra_domain/2, - unregister_extra_domain/2, - register_sm_feature/2, - unregister_sm_feature/2, - register_sm_node/4, - unregister_sm_node/1]). + unregister_extra_domain/2]). -include("ejabberd.hrl"). -include("jlib.hrl"). --define(EMPTY_INFO_RESULT, - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_INFO}, - {"node", SNode}], []}]}). - start(Host, Opts) -> ejabberd_local:refresh_iq_handlers(), @@ -60,9 +56,21 @@ start(Host, Opts) -> ExtraDomains), catch ets:new(disco_sm_features, [named_table, ordered_set, public]), catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]), + ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_services, 100), + ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 100), + ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 100), + ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 100), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 100), + ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100), ok. stop(Host) -> + ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100), + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 100), + ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 100), + ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 100), + ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 100), + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_services, 100), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS), @@ -97,123 +105,86 @@ process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = Node = string:tokens(SNode, "/"), Host = To#jid.lserver, - case acl:match_rule(Host, configure, From) of - deny when Node /= [] -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; - deny -> + case ejabberd_hooks:run_fold(disco_local_items, + Host, + empty, + [From, To, Node, Lang]) of + {result, Items} -> + ANode = case Node of + [] -> []; + _ -> [{"node", SNode}] + end, IQ#iq{type = result, sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS}], - get_services_only(Host) + [{"xmlns", ?NS_DISCO_ITEMS} | ANode], + Items }]}; - _ -> - case get_local_items(Host, Node, - jlib:jid_to_string(To), Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS}, - {"node", SNode}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} end end. -process_local_iq_info(From, To, #iq{type = Type, xmlns = XMLNS, +process_local_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> case Type of set -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> - LServer = To#jid.lserver, + Host = To#jid.lserver, SNode = xml:get_tag_attr_s("node", SubEl), Node = string:tokens(SNode, "/"), - case {acl:match_rule(LServer, configure, From), Node} of - {_, []} -> - Features = lists:map( - fun feature_to_xml/1, - ets:select(disco_features, - [{{{'$1', LServer}}, - [], - ['$1']}])), + Identity = ejabberd_hooks:run_fold(disco_local_identity, + Host, + [], + [From, To, Node, Lang]), + case ejabberd_hooks:run_fold(disco_local_features, + Host, + empty, + [From, To, Node, Lang]) of + {result, Features} -> + ANode = case Node of + [] -> []; + _ -> [{"node", SNode}] + end, IQ#iq{type = result, - sub_el = [{xmlelement, - "query", - [{"xmlns", ?NS_DISCO_INFO}], - [{xmlelement, "identity", - [{"category", "server"}, - {"type", "im"}, - {"name", "ejabberd"}], []}] ++ - Features + sub_el = [{xmlelement, "query", + [{"xmlns", ?NS_DISCO_INFO} | ANode], + Identity ++ + lists:map(fun feature_to_xml/1, Features) }]}; - {deny, _} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; - {allow, ["config"]} -> ?EMPTY_INFO_RESULT; - {allow, ["online users"]} -> ?EMPTY_INFO_RESULT; - {allow, ["all users"]} -> ?EMPTY_INFO_RESULT; - {allow, ["all users", [$@ | _]]} -> ?EMPTY_INFO_RESULT; - {allow, ["outgoing s2s" | _]} -> ?EMPTY_INFO_RESULT; - {allow, ["running nodes"]} -> ?EMPTY_INFO_RESULT; - {allow, ["stopped nodes"]} -> ?EMPTY_INFO_RESULT; - {allow, ["running nodes", ENode]} -> - IQ#iq{type = result, - sub_el = [{xmlelement, - "query", - [{"xmlns", XMLNS}, - {"node", SNode}], - [{xmlelement, "identity", - [{"category", "ejabberd"}, - {"type", "node"}, - {"name", ENode}], []}, - feature_to_xml(?NS_STATS) - ] - }]}; - {allow, ["running nodes", ENode, "DB"]} -> - IQ#iq{type = result, - sub_el = [{xmlelement, - "query", - [{"xmlns", XMLNS}, - {"node", SNode}], - [feature_to_xml(?NS_EJABBERD_CONFIG)]}]}; - {allow, ["running nodes", ENode, "modules"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["running nodes", ENode, "modules", _]} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}, - {"node", SNode}], - [feature_to_xml(?NS_EJABBERD_CONFIG)]}]}; - {allow, ["running nodes", ENode, "backup"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["running nodes", ENode, "backup", _]} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}, - {"node", SNode}], - [feature_to_xml(?NS_EJABBERD_CONFIG)]}]}; - {allow, ["running nodes", ENode, "import"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["running nodes", ENode, "import", _]} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}, - {"node", SNode}], - [feature_to_xml(?NS_EJABBERD_CONFIG)]}]}; - {allow, ["config", _]} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}, - {"node", SNode}], - [feature_to_xml(?NS_EJABBERD_CONFIG)]}]}; - _ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]} + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} end end. +get_local_identity(_Acc, _From, _To, [], _Lang) -> + [{xmlelement, "identity", + [{"category", "server"}, + {"type", "im"}, + {"name", "ejabberd"}], []}]; + +get_local_identity(Acc, _From, _To, _Node, _Lang) -> + Acc. + +get_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; + +get_local_features(Acc, _From, _To, [], _Lang) -> + Feats = case Acc of + {result, Features} -> Features; + empty -> [] + end, + {result, ets:tab2list(disco_features) ++ Feats}; + +get_local_features(Acc, _From, _To, _Node, _Lang) -> + case Acc of + {result, _Features} -> + Acc; + empty -> + {error, ?ERR_ITEM_NOT_FOUND} + end. + feature_to_xml({{Feature, _Host}}) -> feature_to_xml(Feature); @@ -225,140 +196,27 @@ domain_to_xml({Domain}) -> domain_to_xml(Domain) -> {xmlelement, "item", [{"jid", Domain}], []}. --define(NODE(Name, Node), - {xmlelement, "item", - [{"jid", Server}, - {"name", translate:translate(Lang, Name)}, - {"node", Node}], []}). +get_local_services({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; - -get_services_only(Host) -> - lists:map(fun domain_to_xml/1, - get_vh_services(Host)) ++ - lists:map(fun domain_to_xml/1, - ets:select(disco_extra_domains, - [{{{'$1', Host}}, - [], - ['$1']}])). - -get_local_items(Host, [], Server, Lang) -> - Domains = - lists:map(fun domain_to_xml/1, - get_vh_services(Host)) ++ - lists:map(fun domain_to_xml/1, - ets:select(disco_extra_domains, - [{{{'$1', Host}}, - [], - ['$1']}])), +get_local_services(Acc, _From, To, [], _Lang) -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + Host = To#jid.lserver, {result, - Domains ++ - [?NODE("Configuration", "config"), - ?NODE("Online Users", "online users"), - ?NODE("All Users", "all users"), - ?NODE("Outgoing S2S connections", "outgoing s2s"), - ?NODE("Running Nodes", "running nodes"), - ?NODE("Stopped Nodes", "stopped nodes") - ]}; - -get_local_items(Host, ["config"], Server, Lang) -> - {result, - [?NODE("Host Name", "config/hostname"), - ?NODE("Access Control Lists", "config/acls"), - ?NODE("Access Rules", "config/access") - % Too expensive on big hosts - %?NODE("Remove Users", "config/remusers") - ]}; - -get_local_items(Host, ["config", _], Server, Lang) -> - {result, []}; - -get_local_items(Host, ["online users"], Server, Lang) -> - {result, get_online_vh_users(Host)}; - -get_local_items(Host, ["all users"], Server, Lang) -> - {result, get_all_vh_users(Host)}; - -get_local_items(Host, ["all users", [$@ | Diap]], Server, Lang) -> - case catch ejabberd_auth:dirty_get_registered_users() of - {'EXIT', Reason} -> - ?ERR_INTERNAL_SERVER_ERROR; - Users -> - SUsers = lists:sort([{S, U} || {U, S} <- Users]), - case catch begin - {ok, [S1, S2]} = regexp:split(Diap, "-"), - N1 = list_to_integer(S1), - N2 = list_to_integer(S2), - Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), - lists:map(fun({S, U}) -> - {xmlelement, "item", - [{"jid", U ++ "@" ++ S}, - {"name", U ++ "@" ++ S}], []} - end, Sub) - end of - {'EXIT', Reason} -> - ?ERR_NOT_ACCEPTABLE; - Res -> - {result, Res} - end - end; - -get_local_items(Host, ["outgoing s2s"], Server, Lang) -> - {result, get_outgoing_s2s(Host, Lang)}; - -get_local_items(Host, ["outgoing s2s", To], Server, Lang) -> - {result, get_outgoing_s2s(Host, Lang, To)}; - -get_local_items(Host, ["running nodes"], Server, Lang) -> - {result, get_running_nodes(Lang)}; - -get_local_items(Host, ["stopped nodes"], Server, Lang) -> - {result, get_stopped_nodes(Lang)}; - -get_local_items(Host, ["running nodes", ENode], Server, Lang) -> - {result, - [?NODE("DB", "running nodes/" ++ ENode ++ "/DB"), - ?NODE("Modules", "running nodes/" ++ ENode ++ "/modules"), - ?NODE("Backup Management", "running nodes/" ++ ENode ++ "/backup"), - ?NODE("Import users from jabberd1.4 spool files", - "running nodes/" ++ ENode ++ "/import") - ]}; - -get_local_items(Host, ["running nodes", ENode, "DB"], Server, Lang) -> - {result, []}; - -get_local_items(Host, ["running nodes", ENode, "modules"], Server, Lang) -> - {result, - [?NODE("Start Modules", "running nodes/" ++ ENode ++ "/modules/start"), - ?NODE("Stop Modules", "running nodes/" ++ ENode ++ "/modules/stop") - ]}; - -get_local_items(Host, ["running nodes", ENode, "modules", _], Server, Lang) -> - {result, []}; - -get_local_items(Host, ["running nodes", ENode, "backup"], Server, Lang) -> - {result, - [?NODE("Backup", "running nodes/" ++ ENode ++ "/backup/backup"), - ?NODE("Restore", "running nodes/" ++ ENode ++ "/backup/restore"), - ?NODE("Dump to Text File", - "running nodes/" ++ ENode ++ "/backup/textfile") - ]}; - -get_local_items(Host, ["running nodes", ENode, "backup", _], Server, Lang) -> - {result, []}; - -get_local_items(Host, ["running nodes", ENode, "import"], Server, Lang) -> - {result, - [?NODE("Import File", "running nodes/" ++ ENode ++ "/import/file"), - ?NODE("Import Directory", "running nodes/" ++ ENode ++ "/import/dir") - ]}; - -get_local_items(Host, ["running nodes", ENode, "import", _], Server, Lang) -> - {result, []}; - -get_local_items(_Host, _, _, _) -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. + lists:usort( + lists:map(fun domain_to_xml/1, + get_vh_services(Host) ++ + ets:tab2list(disco_extra_domains)) + ) ++ Items}; +get_local_services({result, _} = Acc, _From, _To, _Node, _Lang) -> + Acc; +get_local_services(empty, _From, _To, _Node, _Lang) -> + {error, ?ERR_ITEM_NOT_FOUND}. get_vh_services(Host) -> Hosts = lists:sort(fun(H1, H2) -> length(H1) >= length(H2) end, ?MYHOSTS), @@ -374,253 +232,120 @@ get_vh_services(Host) -> end end, ejabberd_router:dirty_get_all_routes()). -get_online_vh_users(Host) -> - case catch ejabberd_sm:get_vh_session_list(Host) of - {'EXIT', Reason} -> - []; - USRs -> - SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]), - lists:map(fun({S, U, R}) -> - {xmlelement, "item", - [{"jid", U ++ "@" ++ S ++ "/" ++ R}, - {"name", U ++ "@" ++ S}], []} - end, SURs) - end. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -get_all_vh_users(Host) -> - case catch ejabberd_auth:get_vh_registered_users(Host) of - {'EXIT', Reason} -> - []; - Users -> - SUsers = lists:sort([{S, U} || {U, S} <- Users]), - case length(SUsers) of - N when N =< 100 -> - lists:map(fun({S, U}) -> - {xmlelement, "item", - [{"jid", U ++ "@" ++ S}, - {"name", U ++ "@" ++ S}], []} - end, SUsers); - N -> - NParts = trunc(math:sqrt(N * 0.618)) + 1, - M = trunc(N / NParts) + 1, - lists:map(fun(K) -> - L = K + M - 1, - Node = - "@" ++ integer_to_list(K) ++ - "-" ++ integer_to_list(L), - {FS, FU} = lists:nth(K, SUsers), - {LS, LU} = - if L < N -> lists:nth(L, SUsers); - true -> lists:last(SUsers) - end, - Name = - FU ++ "@" ++ FS ++ - " -- " ++ - LU ++ "@" ++ LS, - {xmlelement, "item", - [{"jid", Host}, - {"node", "all users/" ++ Node}, - {"name", Name}], []} - end, lists:seq(1, N, M)) +process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> + case Type of + set -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + get -> + Host = To#jid.lserver, + SNode = xml:get_tag_attr_s("node", SubEl), + Node = string:tokens(SNode, "/"), + case ejabberd_hooks:run_fold(disco_sm_items, + Host, + empty, + [From, To, Node, Lang]) of + {result, Items} -> + ANode = case Node of + [] -> []; + _ -> [{"node", SNode}] + end, + IQ#iq{type = result, + sub_el = [{xmlelement, "query", + [{"xmlns", ?NS_DISCO_ITEMS} | ANode], + Items + }]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} end end. -get_outgoing_s2s(Host, Lang) -> - case catch ejabberd_s2s:dirty_get_connections() of - {'EXIT', Reason} -> - []; - Connections -> - DotHost = "." ++ Host, - TConns = [TH || {FH, TH} <- Connections, - Host == FH orelse lists:suffix(DotHost, FH)], - lists:map( - fun(T) -> - {xmlelement, "item", - [{"jid", Host}, - {"node", "outgoing s2s/" ++ T}, - {"name", - lists:flatten( - io_lib:format( - translate:translate(Lang, "To ~s"), [T]))}], - []} - end, lists:usort(TConns)) - end. +get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; -get_outgoing_s2s(Host, Lang, To) -> - case catch ejabberd_s2s:dirty_get_connections() of - {'EXIT', Reason} -> - []; - Connections -> - lists:map( - fun({F, T}) -> - {xmlelement, "item", - [{"jid", Host}, - {"node", "outgoing s2s/" ++ To ++ "/" ++ F}, - {"name", - lists:flatten( - io_lib:format( - translate:translate(Lang, "From ~s"), [F]))}], - []} - end, lists:keysort(1, lists:filter(fun(E) -> - element(2, E) == To - end, Connections))) - end. +get_sm_items(Acc, + #jid{luser = LFrom, lserver = LSFrom} = _From, + #jid{user = User, server = Server, luser = LTo, lserver = LSTo} = _To, + [], _Lang) -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + Items1 = case {LFrom, LSFrom} of + {LTo, LSTo} -> get_user_resources(User, Server); + _ -> [] + end, + {result, Items ++ Items1}; + +get_sm_items({result, _} = Acc, _From, _To, _Node, _Lang) -> + Acc; - -get_running_nodes(Lang) -> - case catch mnesia:system_info(running_db_nodes) of - {'EXIT', Reason} -> - []; - DBNodes -> - lists:map( - fun(N) -> - S = atom_to_list(N), - {xmlelement, "item", - [{"jid", ?MYNAME}, - {"node", "running nodes/" ++ S}, - {"name", S}], - []} - end, lists:sort(DBNodes)) - end. - -get_stopped_nodes(Lang) -> - case catch (lists:usort(mnesia:system_info(db_nodes) ++ - mnesia:system_info(extra_db_nodes)) -- - mnesia:system_info(running_db_nodes)) of - {'EXIT', Reason} -> - []; - DBNodes -> - lists:map( - fun(N) -> - S = atom_to_list(N), - {xmlelement, "item", - [{"jid", ?MYNAME}, - {"node", "stopped nodes/" ++ S}, - {"name", S}], - []} - end, lists:sort(DBNodes)) - end. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -register_sm_feature(Host, Feature) -> - catch ets:new(disco_sm_features, [named_table, ordered_set, public]), - ets:insert(disco_sm_features, {{Feature, Host}}). - -unregister_sm_feature(Host, Feature) -> - catch ets:new(disco_sm_features, [named_table, ordered_set, public]), - ets:delete(disco_sm_features, {Feature, Host}). - -register_sm_node(Node, Name, Module, Function) -> - catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]), - ets:insert(disco_sm_nodes, {Node, Name, Module, Function}). - -unregister_sm_node(Node) -> - catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]), - ets:delete(disco_sm_nodes, Node). - -process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - #jid{user = User, luser = LTo} = To, - #jid{luser = LFrom, lserver = LServer} = From, - Self = (LTo == LFrom) andalso (LServer == ?MYNAME), - Node = xml:get_tag_attr_s("node", SubEl), - case {acl:match_rule(To#jid.lserver, configure, From), Type, Self, Node} of - {_, set, _, _} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; - {_, get, true, []} -> - Nodes = lists:map(fun({Nod, Name, _, _}) -> - node_to_xml(User, - Nod, - translate:translate(Lang, Name)) - end, ets:tab2list(disco_sm_nodes)), - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS}], - get_user_resources(User) ++ Nodes}]}; - {allow, get, _, []} -> - Nodes = lists:map(fun({Nod, Name, _, _}) -> - node_to_xml(User, - Nod, - translate:translate(Lang, Name)) - end, ets:tab2list(disco_sm_nodes)), - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS}], - get_user_resources(User) ++ Nodes}]}; - {A, get, S, _} when (A == allow) or (S == true) -> - case ets:lookup(disco_sm_nodes, Node) of - [] -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; - [{Node, _Name, Module, Function}] -> - case Module:Function(From, To, IQ) of - {error, Err} -> - IQ#iq{type = error, sub_el = [SubEl, Err]}; - {result, Res} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS}, - {"node", Node}], - Res}]} - end - end; - {_, get, _, _} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}; +get_sm_items(empty, From, To, _Node, _Lang) -> + #jid{luser = LFrom, lserver = LSFrom} = From, + #jid{luser = LTo, lserver = LSTo} = To, + case {LFrom, LSFrom} of + {LTo, LSTo} -> + {error, ?ERR_ITEM_NOT_FOUND}; _ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]} + {error, ?ERR_NOT_ALLOWED} end. - -process_sm_iq_info(From, To, #iq{type = Type, xmlns = XMLNS, - sub_el = SubEl} = IQ) -> - #jid{luser = LTo} = To, - #jid{luser = LFrom, lserver = LServer} = From, - Self = (LTo == LFrom) andalso (LServer == ?MYNAME), - Node = xml:get_tag_attr_s("node", SubEl), - case {acl:match_rule(To#jid.lserver, configure, From), Type, Self, Node} of - {_, set, _, _} -> +process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> + case Type of + set -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; - {allow, get, _, []} -> - Features = lists:map(fun feature_to_xml/1, - ets:tab2list(disco_sm_features)), - IQ#iq{type = result, - sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}], - [feature_to_xml(?NS_EJABBERD_CONFIG)] ++ - Features}]}; - {_, get, _, []} -> - Features = lists:map(fun feature_to_xml/1, - ets:tab2list(disco_sm_features)), - IQ#iq{type = result, - sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}], - Features}]}; - {A, get, S, _} when (A == allow) or (S == true) -> - case ets:lookup(disco_sm_nodes, Node) of - [] -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; - _ -> - IQ#iq{type = result, sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}, - {"node", Node}], []}]} - end; - {_, get, _, _} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}; - _ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]} + get -> + Host = To#jid.lserver, + SNode = xml:get_tag_attr_s("node", SubEl), + Node = string:tokens(SNode, "/"), + Identity = ejabberd_hooks:run_fold(disco_sm_identity, + Host, + [], + [From, To, Node, Lang]), + case ejabberd_hooks:run_fold(disco_sm_features, + Host, + empty, + [From, To, Node, Lang]) of + {result, Features} -> + ANode = case Node of + [] -> []; + _ -> [{"node", SNode}] + end, + IQ#iq{type = result, + sub_el = [{xmlelement, "query", + [{"xmlns", ?NS_DISCO_INFO} | ANode], + Identity ++ + lists:map(fun feature_to_xml/1, Features) + }]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end end. +get_sm_identity(Acc, _From, _To, _Node, _Lang) -> + Acc. + +get_sm_features(empty, From, To, _Node, _Lang) -> + #jid{luser = LFrom, lserver = LSFrom} = From, + #jid{luser = LTo, lserver = LSTo} = To, + case {LFrom, LSFrom} of + {LTo, LSTo} -> + {error, ?ERR_ITEM_NOT_FOUND}; + _ -> + {error, ?ERR_NOT_ALLOWED} + end; + +get_sm_features(Acc, _From, _To, _Node, _Lang) -> + Acc. -get_user_resources(User) -> - Rs = ejabberd_sm:get_user_resources(User, 'TODO'), + +get_user_resources(User, Server) -> + Rs = ejabberd_sm:get_user_resources(User, Server), lists:map(fun(R) -> {xmlelement, "item", - [{"jid", User ++ "@" ++ ?MYNAME ++ "/" ++ R}, + [{"jid", User ++ "@" ++ Server ++ "/" ++ R}, {"name", User}], []} end, lists:sort(Rs)). -node_to_xml(User, Node, Name) -> - {xmlelement, "item", [{"jid", User ++ "@" ++ ?MYNAME}, - {"node", Node}, - {"name", Name}], []}. - diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 137ace03e..120a1bef7 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -13,6 +13,7 @@ -behaviour(gen_mod). -export([start/2, init/3, stop/1, + get_sm_features/5, process_local_iq/3, process_sm_iq/3, reindex_vcards/0, @@ -69,7 +70,7 @@ start(Host, Opts) -> ?MODULE, process_local_iq, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc), - catch mod_disco:register_sm_feature(Host, ?NS_VCARD), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), MyHost = gen_mod:get_opt(host, Opts, "vjud." ++ Host), Search = gen_mod:get_opt(search, Opts, true), register(gen_mod:get_module_proc(Host, ?PROCNAME), @@ -107,11 +108,27 @@ stop(Host) -> ?MODULE, remove_user, 50), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD), - catch mod_disco:unregister_sm_feature(Host, ?NS_VCARD), + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc ! stop, {wait, Proc}. +get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; + +get_sm_features(Acc, _From, _To, Node, _Lang) -> + case Node of + [] -> + case Acc of + {result, Features} -> + {result, [?NS_VCARD | Features]}; + empty -> + {result, [?NS_VCARD]} + end; + _ -> + Acc + end. + process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> case Type of set -> @@ -140,7 +157,7 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) -> case Type of set -> - #jid{user = User, lserver = LServer, luser = LUser} = From, + #jid{user = User, lserver = LServer} = From, case lists:member(LServer, ?MYHOSTS) of true -> set_vcard(User, LServer, SubEl), @@ -327,7 +344,7 @@ do_route(ServerHost, From, To, Packet) -> From, jlib:iq_to_xml(ResIQ)) end; - #iq{type = Type, xmlns = ?NS_DISCO_INFO, sub_el = SubEl} -> + #iq{type = Type, xmlns = ?NS_DISCO_INFO} -> case Type of set -> Err = jlib:make_error_reply( @@ -355,7 +372,7 @@ do_route(ServerHost, From, To, Packet) -> From, jlib:iq_to_xml(ResIQ)) end; - #iq{type = Type, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} -> + #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} -> case Type of set -> Err = jlib:make_error_reply( diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index c2bbeb298..c4c7bbd78 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -13,6 +13,7 @@ -behaviour(gen_mod). -export([start/2, init/3, stop/1, + get_sm_features/5, process_local_iq/3, process_sm_iq/3, remove_user/1]). @@ -29,6 +30,7 @@ start(Host, Opts) -> ?MODULE, process_local_iq, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}), RootDN = ejabberd_config:get_local_option({ldap_rootdn, Host}), Password = ejabberd_config:get_local_option({ldap_password, Host}), @@ -67,10 +69,26 @@ loop(Host, ServerHost) -> stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD), + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc ! stop, {wait, Proc}. +get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; +get_sm_features(Acc, _From, _To, Node, _Lang) -> + case Node of + [] -> + case Acc of + {result, Features} -> + {result, [?NS_VCARD | Features]}; + empty -> + {result, [?NS_VCARD]} + end; + _ -> + Acc + end. + process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> case Type of set -> @@ -180,16 +198,6 @@ ldap_attributes_to_vcard(Attributes,From,To) -> {xmlelement,"ORG",[],FOElts}]) }]. -is_self_request(From,To) -> - #jid{luser = RUser, lserver = RServer } = From, - #jid{luser = LUser} = To, - case RServer == ?MYNAME of - true -> - LUser == RUser; - _ -> - false - end. - process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) -> case Type of set ->