diff options
Diffstat (limited to 'src/mod_announce.erl')
-rw-r--r-- | src/mod_announce.erl | 961 |
1 files changed, 473 insertions, 488 deletions
diff --git a/src/mod_announce.erl b/src/mod_announce.erl index d74c46bf9..013594af8 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -5,7 +5,7 @@ %%% Created : 11 Aug 2003 by Alexey Shchepin <alexey@process-one.net> %%% %%% -%%% ejabberd, Copyright (C) 2002-2016 ProcessOne +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -29,39 +29,84 @@ -module(mod_announce). -author('alexey@process-one.net'). +-behaviour(gen_server). -behaviour(gen_mod). --export([start/2, init/0, stop/1, export/1, import/1, - import/3, announce/3, send_motd/1, disco_identity/5, +-export([start/2, stop/1, reload/3, export/1, import_info/0, + import_start/2, import/5, announce/1, send_motd/1, disco_identity/5, disco_features/5, disco_items/5, depends/2, send_announcement_to_all/3, announce_commands/4, - announce_items/4, mod_opt_type/1]). + announce_items/4, mod_opt_type/1, mod_options/1, clean_cache/1]). +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, terminate/2, code_change/3]). +-export([announce_all/1, + announce_all_hosts_all/1, + announce_online/1, + announce_all_hosts_online/1, + announce_motd/1, + announce_all_hosts_motd/1, + announce_motd_update/1, + announce_all_hosts_motd_update/1, + announce_motd_delete/1, + announce_all_hosts_motd_delete/1]). --include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). --include("adhoc.hrl"). +-include("xmpp.hrl"). -include("mod_announce.hrl"). +-include("translate.hrl"). -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #motd{} | #motd_users{}) -> ok | pass. --callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}. --callback set_motd(binary(), xmlel()) -> {atomic, any()}. --callback delete_motd(binary()) -> {atomic, any()}. --callback get_motd(binary()) -> {ok, xmlel()} | error. --callback is_motd_user(binary(), binary()) -> boolean(). --callback set_motd_user(binary(), binary()) -> {atomic, any()}. +-callback import(binary(), binary(), [binary()]) -> ok. +-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> ok | {error, any()}. +-callback set_motd(binary(), xmlel()) -> ok | {error, any()}. +-callback delete_motd(binary()) -> ok | {error, any()}. +-callback get_motd(binary()) -> {ok, xmlel()} | error | {error, any()}. +-callback is_motd_user(binary(), binary()) -> {ok, boolean()} | {error, any()}. +-callback set_motd_user(binary(), binary()) -> ok | {error, any()}. +-callback use_cache(binary()) -> boolean(). +-callback cache_nodes(binary()) -> [node()]. --define(PROCNAME, ejabberd_announce). +-optional_callbacks([use_cache/1, cache_nodes/1]). + +-record(state, {host :: binary()}). -define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>, <<"admin">>, <<Sub>>]). +-define(MOTD_CACHE, motd_cache). tokenize(Node) -> str:tokens(Node, <<"/#">>). +%%==================================================================== +%% gen_mod callbacks +%%==================================================================== start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + gen_mod:start_child(?MODULE, Host, Opts). + +stop(Host) -> + gen_mod:stop_child(?MODULE, Host). + +reload(Host, NewOpts, OldOpts) -> + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), + if NewMod /= OldMod -> + NewMod:init(Host, NewOpts); + true -> + ok + end, + init_cache(NewMod, Host, NewOpts). + +depends(_Host, _Opts) -> + [{mod_adhoc, hard}]. + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== +init([Host|_]) -> + process_flag(trap_exit, true), + Opts = gen_mod:get_module_opts(Host, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), + init_cache(Mod, Host, Opts), ejabberd_hooks:add(local_send_to_resource_hook, Host, ?MODULE, announce, 50), ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50), @@ -69,114 +114,98 @@ start(Host, Opts) -> ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 50), ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, announce_items, 50), ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, announce_commands, 50), - ejabberd_hooks:add(user_available_hook, Host, + ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE, send_motd, 50), - register(gen_mod:get_module_proc(Host, ?PROCNAME), - proc_lib:spawn(?MODULE, init, [])). + {ok, #state{host = Host}}. + +handle_call(Request, From, State) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), + {noreply, State}. + +handle_cast({F, #message{from = From, to = To} = Pkt}, State) when is_atom(F) -> + LServer = To#jid.lserver, + Host = case F of + announce_all -> LServer; + announce_all_hosts_all -> global; + announce_online -> LServer; + announce_all_hosts_online -> global; + announce_motd -> LServer; + announce_all_hosts_motd -> global; + announce_motd_update -> LServer; + announce_all_hosts_motd_update -> global; + announce_motd_delete -> LServer; + announce_all_hosts_motd_delete -> global + end, + Access = get_access(Host), + case acl:match_rule(Host, Access, From) of + deny -> + route_forbidden_error(Pkt); + allow -> + ?MODULE:F(Pkt) + end, + {noreply, State}; +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), + {noreply, State}. -depends(_Host, _Opts) -> - [{mod_adhoc, hard}]. -init() -> - loop(). - -loop() -> - receive - {announce_all, From, To, Packet} -> - announce_all(From, To, Packet), - loop(); - {announce_all_hosts_all, From, To, Packet} -> - announce_all_hosts_all(From, To, Packet), - loop(); - {announce_online, From, To, Packet} -> - announce_online(From, To, Packet), - loop(); - {announce_all_hosts_online, From, To, Packet} -> - announce_all_hosts_online(From, To, Packet), - loop(); - {announce_motd, From, To, Packet} -> - announce_motd(From, To, Packet), - loop(); - {announce_all_hosts_motd, From, To, Packet} -> - announce_all_hosts_motd(From, To, Packet), - loop(); - {announce_motd_update, From, To, Packet} -> - announce_motd_update(From, To, Packet), - loop(); - {announce_all_hosts_motd_update, From, To, Packet} -> - announce_all_hosts_motd_update(From, To, Packet), - loop(); - {announce_motd_delete, From, To, Packet} -> - announce_motd_delete(From, To, Packet), - loop(); - {announce_all_hosts_motd_delete, From, To, Packet} -> - announce_all_hosts_motd_delete(From, To, Packet), - loop(); - _ -> - loop() - end. +handle_info(Info, State) -> + ?WARNING_MSG("Unexpected info: ~p", [Info]), + {noreply, State}. -stop(Host) -> +terminate(_Reason, #state{host = Host}) -> ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, announce_commands, 50), ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, announce_items, 50), ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 50), ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50), ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 50), - ejabberd_hooks:delete(local_send_to_resource_hook, Host, - ?MODULE, announce, 50), - ejabberd_hooks:delete(user_available_hook, Host, - ?MODULE, send_motd, 50), - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - exit(whereis(Proc), stop), - {wait, Proc}. + ejabberd_hooks:delete(local_send_to_resource_hook, Host, ?MODULE, announce, 50), + ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE, send_motd, 50). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. %% Announcing via messages to a custom resource -announce(From, #jid{luser = <<>>} = To, #xmlel{name = <<"message">>} = Packet) -> - Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME), - case To#jid.lresource of - <<"announce/all">> -> - Proc ! {announce_all, From, To, Packet}, - stop; - <<"announce/all-hosts/all">> -> - Proc ! {announce_all_hosts_all, From, To, Packet}, - stop; - <<"announce/online">> -> - Proc ! {announce_online, From, To, Packet}, - stop; - <<"announce/all-hosts/online">> -> - Proc ! {announce_all_hosts_online, From, To, Packet}, - stop; - <<"announce/motd">> -> - Proc ! {announce_motd, From, To, Packet}, - stop; - <<"announce/all-hosts/motd">> -> - Proc ! {announce_all_hosts_motd, From, To, Packet}, - stop; - <<"announce/motd/update">> -> - Proc ! {announce_motd_update, From, To, Packet}, - stop; - <<"announce/all-hosts/motd/update">> -> - Proc ! {announce_all_hosts_motd_update, From, To, Packet}, - stop; - <<"announce/motd/delete">> -> - Proc ! {announce_motd_delete, From, To, Packet}, - stop; - <<"announce/all-hosts/motd/delete">> -> - Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, - stop; - _ -> - ok +-spec announce(stanza()) -> ok | stop. +announce(#message{to = #jid{luser = <<>>} = To} = Packet) -> + Proc = gen_mod:get_module_proc(To#jid.lserver, ?MODULE), + Res = case To#jid.lresource of + <<"announce/all">> -> + gen_server:cast(Proc, {announce_all, Packet}); + <<"announce/all-hosts/all">> -> + gen_server:cast(Proc, {announce_all_hosts_all, Packet}); + <<"announce/online">> -> + gen_server:cast(Proc, {announce_online, Packet}); + <<"announce/all-hosts/online">> -> + gen_server:cast(Proc, {announce_all_hosts_online, Packet}); + <<"announce/motd">> -> + gen_server:cast(Proc, {announce_motd, Packet}); + <<"announce/all-hosts/motd">> -> + gen_server:cast(Proc, {announce_all_hosts_motd, Packet}); + <<"announce/motd/update">> -> + gen_server:cast(Proc, {announce_motd_update, Packet}); + <<"announce/all-hosts/motd/update">> -> + gen_server:cast(Proc, {announce_all_hosts_motd_update, Packet}); + <<"announce/motd/delete">> -> + gen_server:cast(Proc, {announce_motd_delete, Packet}); + <<"announce/all-hosts/motd/delete">> -> + gen_server:cast(Proc, {announce_all_hosts_motd_delete, Packet}); + _ -> + undefined + end, + case Res of + ok -> stop; + _ -> ok end; -announce(_From, _To, _Packet) -> +announce(_Packet) -> ok. %%------------------------------------------------------------------------- %% Announcing via ad-hoc commands -define(INFO_COMMAND(Lang, Node), - [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}, - {<<"name">>, get_title(Lang, Node)}]}]). + [#identity{category = <<"automation">>, + type = <<"command-node">>, + name = get_title(Lang, Node)}]). disco_identity(Acc, _From, _To, Node, Lang) -> LNode = tokenize(Node), @@ -210,7 +239,7 @@ disco_identity(Acc, _From, _To, Node, Lang) -> -define(INFO_RESULT(Allow, Feats, Lang), case Allow of deny -> - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> {result, Feats} end). @@ -225,8 +254,8 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) - case {acl:match_rule(LServer, Access1, From), acl:match_rule(global, Access2, From)} of {deny, deny} -> - Txt = <<"Denied by ACL">>, - {error, ?ERRT_FORBIDDEN(Lang, Txt)}; + Txt = ?T("Access denied by service policy"), + {error, xmpp:err_forbidden(Txt, Lang)}; _ -> {result, []} end @@ -269,26 +298,19 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) -> %%------------------------------------------------------------------------- -define(NODE_TO_ITEM(Lang, Server, Node), -( - #xmlel{ - name = <<"item">>, - attrs = [ - {<<"jid">>, Server}, - {<<"node">>, Node}, - {<<"name">>, get_title(Lang, Node)} - ] - } -)). + #disco_item{jid = jid:make(Server), + node = Node, + name = get_title(Lang, Node)}). -define(ITEMS_RESULT(Allow, Items, Lang), case Allow of deny -> - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> {result, Items} end). -disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<>>, Lang) -> +disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<"">>, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; @@ -353,7 +375,10 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) -> end. %%------------------------------------------------------------------------- - +-spec announce_items(empty | {error, stanza_error()} | {result, [disco_item()]}, + jid(), jid(), binary()) -> {error, stanza_error()} | + {result, [disco_item()]} | + empty. announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) -> Access1 = get_access(LServer), Nodes1 = case acl:match_rule(LServer, Access1, From) of @@ -393,15 +418,16 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) commands_result(Allow, From, To, Request) -> case Allow of deny -> - Lang = Request#adhoc_request.lang, - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + Lang = Request#adhoc_command.lang, + {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> announce_commands(From, To, Request) end. - +-spec announce_commands(empty | adhoc_command(), jid(), jid(), adhoc_command()) -> + adhoc_command() | {error, stanza_error()}. announce_commands(Acc, From, #jid{lserver = LServer} = To, - #adhoc_request{ node = Node} = Request) -> + #adhoc_command{node = Node} = Request) -> LNode = tokenize(Node), F = fun() -> Access = get_access(global), @@ -440,112 +466,72 @@ announce_commands(Acc, From, #jid{lserver = LServer} = To, %%------------------------------------------------------------------------- announce_commands(From, To, - #adhoc_request{lang = Lang, + #adhoc_command{lang = Lang, node = Node, - action = Action, - xdata = XData} = Request) -> - %% If the "action" attribute is not present, it is - %% understood as "execute". If there was no <actions/> - %% element in the first response (which there isn't in our - %% case), "execute" and "complete" are equivalent. - ActionIsExecute = lists:member(Action, [<<>>, <<"execute">>, <<"complete">>]), - if Action == <<"cancel">> -> + sid = SID, + xdata = XData, + action = Action} = Request) -> + if Action == cancel -> %% User cancels request - adhoc:produce_response(Request, #adhoc_response{status = canceled}); - XData == false, ActionIsExecute -> + #adhoc_command{status = canceled, lang = Lang, node = Node, + sid = SID}; + XData == undefined andalso Action == execute -> %% User requests form - Elements = generate_adhoc_form(Lang, Node, To#jid.lserver), - adhoc:produce_response(Request, - #adhoc_response{status = executing,elements = [Elements]}); - XData /= false, ActionIsExecute -> - %% User returns form. - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)}; - Fields -> - handle_adhoc_form(From, To, Request, Fields) + Form = generate_adhoc_form(Lang, Node, To#jid.lserver), + xmpp_util:make_adhoc_response( + #adhoc_command{status = executing, lang = Lang, node = Node, + sid = SID, xdata = Form}); + XData /= undefined andalso (Action == execute orelse Action == complete) -> + case handle_adhoc_form(From, To, Request) of + ok -> + #adhoc_command{lang = Lang, node = Node, sid = SID, + status = completed}; + {error, _} = Err -> + Err end; true -> - Txt = <<"Incorrect action or data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + Txt = ?T("Unexpected action"), + {error, xmpp:err_bad_request(Txt, Lang)} end. --define(VVALUE(Val), -( - #xmlel{ - name = <<"value">>, - children = [{xmlcdata, Val}] - } -)). - -define(TVFIELD(Type, Var, Val), -( - #xmlel{ - name = <<"field">>, - attrs = [{<<"type">>, Type}, {<<"var">>, Var}], - children = vvaluel(Val) - } -)). - --define(HFIELD(), ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, ?NS_ADMIN)). + #xdata_field{type = Type, var = Var, values = vvaluel(Val)}). vvaluel(Val) -> case Val of <<>> -> []; - _ -> [?VVALUE(Val)] + _ -> [Val] end. generate_adhoc_form(Lang, Node, ServerHost) -> LNode = tokenize(Node), - {OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd")) + {OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd")) or (LNode == ?NS_ADMINL("edit-motd-allhosts")) -> get_stored_motd(ServerHost); - true -> + true -> {<<>>, <<>>} end, - #xmlel{ - name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = [ - ?HFIELD(), - #xmlel{name = <<"title">>, children = [{xmlcdata, get_title(Lang, Node)}]} - ] - ++ - if (LNode == ?NS_ADMINL("delete-motd")) - or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> - [#xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"confirm">>}, - {<<"type">>, <<"boolean">>}, - {<<"label">>, - translate:translate(Lang, <<"Really delete message of the day?">>)} - ], - children = [ - #xmlel{name = <<"value">>, children = [{xmlcdata, <<"true">>}]} - ] - } - ]; - true -> - [#xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"subject">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, translate:translate(Lang, <<"Subject">>)}], - children = vvaluel(OldSubject) - }, - #xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"body">>}, - {<<"type">>, <<"text-multi">>}, - {<<"label">>, translate:translate(Lang, <<"Message body">>)}], - children = vvaluel(OldBody) - } - ] - - end}. + Fs = if (LNode == ?NS_ADMINL("delete-motd")) + or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> + [#xdata_field{type = boolean, + var = <<"confirm">>, + label = translate:translate( + Lang, ?T("Really delete message of the day?")), + values = [<<"true">>]}]; + true -> + [#xdata_field{type = 'text-single', + var = <<"subject">>, + label = translate:translate(Lang, ?T("Subject")), + values = vvaluel(OldSubject)}, + #xdata_field{type = 'text-multi', + var = <<"body">>, + label = translate:translate(Lang, ?T("Message body")), + values = vvaluel(OldBody)}] + end, + #xdata{type = form, + title = get_title(Lang, Node), + fields = [#xdata_field{type = hidden, var = <<"FORM_TYPE">>, + values = [?NS_ADMIN]}|Fs]}. join_lines([]) -> <<>>; @@ -558,227 +544,130 @@ join_lines([], Acc) -> iolist_to_binary(lists:reverse(tl(Acc))). handle_adhoc_form(From, #jid{lserver = LServer} = To, - #adhoc_request{lang = Lang, - node = Node, - sessionid = SessionID}, - Fields) -> - Confirm = case lists:keysearch(<<"confirm">>, 1, Fields) of - {value, {<<"confirm">>, [<<"true">>]}} -> - true; - {value, {<<"confirm">>, [<<"1">>]}} -> - true; - _ -> - false + #adhoc_command{lang = Lang, node = Node, + xdata = XData}) -> + Confirm = case xmpp_util:get_xdata_values(<<"confirm">>, XData) of + [<<"true">>] -> true; + [<<"1">>] -> true; + _ -> false end, - Subject = case lists:keysearch(<<"subject">>, 1, Fields) of - {value, {<<"subject">>, SubjectLines}} -> - %% There really shouldn't be more than one - %% subject line, but can we stop them? - join_lines(SubjectLines); - _ -> - <<>> - end, - Body = case lists:keysearch(<<"body">>, 1, Fields) of - {value, {<<"body">>, BodyLines}} -> - join_lines(BodyLines); - _ -> - <<>> - end, - Response = #adhoc_response{lang = Lang, - node = Node, - sessionid = SessionID, - status = completed}, - Packet = #xmlel{ - name = <<"message">>, - attrs = [{<<"type">>, <<"headline">>}], - children = if Subject /= <<>> -> - [#xmlel{name = <<"subject">>, children = [{xmlcdata, Subject}]}]; - true -> - [] - end - ++ - if Body /= <<>> -> - [#xmlel{name = <<"body">>, children = [{xmlcdata, Body}]}]; - true -> - [] - end - }, - Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), + Subject = join_lines(xmpp_util:get_xdata_values(<<"subject">>, XData)), + Body = join_lines(xmpp_util:get_xdata_values(<<"body">>, XData)), + Packet = #message{from = From, + to = To, + type = headline, + body = xmpp:mk_text(Body), + subject = xmpp:mk_text(Subject)}, + Proc = gen_mod:get_module_proc(LServer, ?MODULE), case {Node, Body} of {?NS_ADMIN_DELETE_MOTD, _} -> if Confirm -> - Proc ! {announce_motd_delete, From, To, Packet}, - adhoc:produce_response(Response); + gen_server:cast(Proc, {announce_motd_delete, Packet}); true -> - adhoc:produce_response(Response) + ok end; {?NS_ADMIN_DELETE_MOTD_ALLHOSTS, _} -> if Confirm -> - Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, - adhoc:produce_response(Response); + gen_server:cast(Proc, {announce_all_hosts_motd_delete, Packet}); true -> - adhoc:produce_response(Response) + ok end; {_, <<>>} -> %% An announce message with no body is definitely an operator error. %% Throw an error and give him/her a chance to send message again. - {error, ?ERRT_NOT_ACCEPTABLE(Lang, - <<"No body provided for announce message">>)}; - %% Now send the packet to ?PROCNAME. + {error, xmpp:err_not_acceptable( + ?T("No body provided for announce message"), Lang)}; + %% Now send the packet to ?MODULE. %% We don't use direct announce_* functions because it %% leads to large delay in response and <iq/> queries processing {?NS_ADMIN_ANNOUNCE, _} -> - Proc ! {announce_online, From, To, Packet}, - adhoc:produce_response(Response); - {?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} -> - Proc ! {announce_all_hosts_online, From, To, Packet}, - adhoc:produce_response(Response); + gen_server:cast(Proc, {announce_online, Packet}); + {?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} -> + gen_server:cast(Proc, {announce_all_hosts_online, Packet}); {?NS_ADMIN_ANNOUNCE_ALL, _} -> - Proc ! {announce_all, From, To, Packet}, - adhoc:produce_response(Response); - {?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} -> - Proc ! {announce_all_hosts_all, From, To, Packet}, - adhoc:produce_response(Response); + gen_server:cast(Proc, {announce_all, Packet}); + {?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} -> + gen_server:cast(Proc, {announce_all_hosts_all, Packet}); {?NS_ADMIN_SET_MOTD, _} -> - Proc ! {announce_motd, From, To, Packet}, - adhoc:produce_response(Response); - {?NS_ADMIN_SET_MOTD_ALLHOSTS, _} -> - Proc ! {announce_all_hosts_motd, From, To, Packet}, - adhoc:produce_response(Response); + gen_server:cast(Proc, {announce_motd, Packet}); + {?NS_ADMIN_SET_MOTD_ALLHOSTS, _} -> + gen_server:cast(Proc, {announce_all_hosts_motd, Packet}); {?NS_ADMIN_EDIT_MOTD, _} -> - Proc ! {announce_motd_update, From, To, Packet}, - adhoc:produce_response(Response); - {?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} -> - Proc ! {announce_all_hosts_motd_update, From, To, Packet}, - adhoc:produce_response(Response); - _ -> + gen_server:cast(Proc, {announce_motd_update, Packet}); + {?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} -> + gen_server:cast(Proc, {announce_all_hosts_motd_update, Packet}); + Junk -> %% This can't happen, as we haven't registered any other %% command nodes. - {error, ?ERR_INTERNAL_SERVER_ERROR} + ?ERROR_MSG("Unexpected node/body = ~p", [Junk]), + {error, xmpp:err_internal_server_error()} end. get_title(Lang, <<"announce">>) -> - translate:translate(Lang, <<"Announcements">>); + translate:translate(Lang, ?T("Announcements")); get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL) -> - translate:translate(Lang, <<"Send announcement to all users">>); + translate:translate(Lang, ?T("Send announcement to all users")); get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS) -> - translate:translate(Lang, <<"Send announcement to all users on all hosts">>); + translate:translate(Lang, ?T("Send announcement to all users on all hosts")); get_title(Lang, ?NS_ADMIN_ANNOUNCE) -> - translate:translate(Lang, <<"Send announcement to all online users">>); + translate:translate(Lang, ?T("Send announcement to all online users")); get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALLHOSTS) -> - translate:translate(Lang, <<"Send announcement to all online users on all hosts">>); + translate:translate(Lang, ?T("Send announcement to all online users on all hosts")); get_title(Lang, ?NS_ADMIN_SET_MOTD) -> - translate:translate(Lang, <<"Set message of the day and send to online users">>); + translate:translate(Lang, ?T("Set message of the day and send to online users")); get_title(Lang, ?NS_ADMIN_SET_MOTD_ALLHOSTS) -> - translate:translate(Lang, <<"Set message of the day on all hosts and send to online users">>); + translate:translate(Lang, ?T("Set message of the day on all hosts and send to online users")); get_title(Lang, ?NS_ADMIN_EDIT_MOTD) -> - translate:translate(Lang, <<"Update message of the day (don't send)">>); + translate:translate(Lang, ?T("Update message of the day (don't send)")); get_title(Lang, ?NS_ADMIN_EDIT_MOTD_ALLHOSTS) -> - translate:translate(Lang, <<"Update message of the day on all hosts (don't send)">>); + translate:translate(Lang, ?T("Update message of the day on all hosts (don't send)")); get_title(Lang, ?NS_ADMIN_DELETE_MOTD) -> - translate:translate(Lang, <<"Delete message of the day">>); + translate:translate(Lang, ?T("Delete message of the day")); get_title(Lang, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS) -> - translate:translate(Lang, <<"Delete message of the day on all hosts">>). + translate:translate(Lang, ?T("Delete message of the day on all hosts")). %%------------------------------------------------------------------------- -announce_all(From, To, Packet) -> - Host = To#jid.lserver, - Access = get_access(Host), - case acl:match_rule(Host, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - Local = jid:make(<<>>, To#jid.server, <<>>), - lists:foreach( - fun({User, Server}) -> - Dest = jid:make(User, Server, <<>>), - ejabberd_router:route(Local, Dest, add_store_hint(Packet)) - end, ejabberd_auth:get_vh_registered_users(Host)) - end. - -announce_all_hosts_all(From, To, Packet) -> - Access = get_access(global), - case acl:match_rule(global, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - Local = jid:make(<<>>, To#jid.server, <<>>), - lists:foreach( - fun({User, Server}) -> - Dest = jid:make(User, Server, <<>>), - ejabberd_router:route(Local, Dest, add_store_hint(Packet)) - end, ejabberd_auth:dirty_get_registered_users()) - end. +announce_all(#message{to = To} = Packet) -> + Local = jid:make(To#jid.server), + lists:foreach( + fun({User, Server}) -> + Dest = jid:make(User, Server), + ejabberd_router:route( + xmpp:set_from_to(add_store_hint(Packet), Local, Dest)) + end, ejabberd_auth:get_users(To#jid.lserver)). + +announce_all_hosts_all(#message{to = To} = Packet) -> + Local = jid:make(To#jid.server), + lists:foreach( + fun({User, Server}) -> + Dest = jid:make(User, Server), + ejabberd_router:route( + xmpp:set_from_to(add_store_hint(Packet), Local, Dest)) + end, ejabberd_auth:get_users()). -announce_online(From, To, Packet) -> - Host = To#jid.lserver, - Access = get_access(Host), - case acl:match_rule(Host, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - announce_online1(ejabberd_sm:get_vh_session_list(Host), - To#jid.server, - Packet) - end. +announce_online(#message{to = To} = Packet) -> + announce_online1(ejabberd_sm:get_vh_session_list(To#jid.lserver), + To#jid.server, Packet). -announce_all_hosts_online(From, To, Packet) -> - Access = get_access(global), - case acl:match_rule(global, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - announce_online1(ejabberd_sm:dirty_get_sessions_list(), - To#jid.server, - Packet) - end. +announce_all_hosts_online(#message{to = To} = Packet) -> + announce_online1(ejabberd_sm:dirty_get_sessions_list(), + To#jid.server, Packet). announce_online1(Sessions, Server, Packet) -> - Local = jid:make(<<>>, Server, <<>>), + Local = jid:make(Server), lists:foreach( fun({U, S, R}) -> Dest = jid:make(U, S, R), - ejabberd_router:route(Local, Dest, Packet) + ejabberd_router:route(xmpp:set_from_to(Packet, Local, Dest)) end, Sessions). -announce_motd(From, To, Packet) -> - Host = To#jid.lserver, - Access = get_access(Host), - case acl:match_rule(Host, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - announce_motd(Host, Packet) - end. +announce_motd(#message{to = To} = Packet) -> + announce_motd(To#jid.lserver, Packet). -announce_all_hosts_motd(From, To, Packet) -> - Access = get_access(global), - case acl:match_rule(global, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - Hosts = ?MYHOSTS, - [announce_motd(Host, Packet) || Host <- Hosts] - end. +announce_all_hosts_motd(Packet) -> + Hosts = ejabberd_option:hosts(), + [announce_motd(Host, Packet) || Host <- Hosts]. announce_motd(Host, Packet) -> LServer = jid:nameprep(Host), @@ -788,147 +677,243 @@ announce_motd(Host, Packet) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:set_motd_users(LServer, Sessions). -announce_motd_update(From, To, Packet) -> - Host = To#jid.lserver, - Access = get_access(Host), - case acl:match_rule(Host, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - announce_motd_update(Host, Packet) - end. +announce_motd_update(#message{to = To} = Packet) -> + announce_motd_update(To#jid.lserver, Packet). -announce_all_hosts_motd_update(From, To, Packet) -> - Access = get_access(global), - case acl:match_rule(global, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - Hosts = ?MYHOSTS, - [announce_motd_update(Host, Packet) || Host <- Hosts] - end. +announce_all_hosts_motd_update(Packet) -> + Hosts = ejabberd_option:hosts(), + [announce_motd_update(Host, Packet) || Host <- Hosts]. announce_motd_update(LServer, Packet) -> - announce_motd_delete(LServer), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:set_motd(LServer, Packet). + delete_motd(Mod, LServer), + set_motd(Mod, LServer, xmpp:encode(Packet)). -announce_motd_delete(From, To, Packet) -> - Host = To#jid.lserver, - Access = get_access(Host), - case acl:match_rule(Host, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - announce_motd_delete(Host) +announce_motd_delete(#message{to = To}) -> + LServer = To#jid.lserver, + Mod = gen_mod:db_mod(LServer, ?MODULE), + delete_motd(Mod, LServer). + +announce_all_hosts_motd_delete(_Packet) -> + lists:foreach( + fun(Host) -> + Mod = gen_mod:db_mod(Host, ?MODULE), + delete_motd(Mod, Host) + end, ejabberd_option:hosts()). + +-spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}. +send_motd({_, #{pres_last := _}} = Acc) -> + %% This is just a presence update, nothing to do + Acc; +send_motd({#presence{type = available}, + #{jid := #jid{luser = LUser, lserver = LServer} = JID}} = Acc) + when LUser /= <<>> -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + case get_motd(Mod, LServer) of + {ok, Packet} -> + CodecOpts = ejabberd_config:codec_options(), + try xmpp:decode(Packet, ?NS_CLIENT, CodecOpts) of + Msg -> + case is_motd_user(Mod, LUser, LServer) of + false -> + Local = jid:make(LServer), + ejabberd_router:route( + xmpp:set_from_to(Msg, Local, JID)), + set_motd_user(Mod, LUser, LServer); + true -> + ok + end + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("Failed to decode motd packet ~p: ~ts", + [Packet, xmpp:format_error(Why)]) + end; + _ -> + ok + end, + Acc; +send_motd(Acc) -> + Acc. + +-spec get_motd(module(), binary()) -> {ok, xmlel()} | error | {error, any()}. +get_motd(Mod, LServer) -> + case use_cache(Mod, LServer) of + true -> + ets_cache:lookup( + ?MOTD_CACHE, {<<"">>, LServer}, + fun() -> Mod:get_motd(LServer) end); + false -> + Mod:get_motd(LServer) end. -announce_all_hosts_motd_delete(From, To, Packet) -> - Access = get_access(global), - case acl:match_rule(global, Access, From) of - deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); - allow -> - Hosts = ?MYHOSTS, - [announce_motd_delete(Host) || Host <- Hosts] +-spec set_motd(module(), binary(), xmlel()) -> any(). +set_motd(Mod, LServer, XML) -> + case use_cache(Mod, LServer) of + true -> + ets_cache:update( + ?MOTD_CACHE, {<<"">>, LServer}, {ok, XML}, + fun() -> Mod:set_motd(LServer, XML) end, + cache_nodes(Mod, LServer)); + false -> + Mod:set_motd(LServer, XML) end. -announce_motd_delete(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:delete_motd(LServer). +-spec is_motd_user(module(), binary(), binary()) -> boolean(). +is_motd_user(Mod, LUser, LServer) -> + Res = case use_cache(Mod, LServer) of + true -> + ets_cache:lookup( + ?MOTD_CACHE, {LUser, LServer}, + fun() -> Mod:is_motd_user(LUser, LServer) end); + false -> + Mod:is_motd_user(LUser, LServer) + end, + case Res of + {ok, Bool} -> Bool; + _ -> false + end. -send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - case Mod:get_motd(LServer) of - {ok, Packet} -> - case Mod:is_motd_user(LUser, LServer) of - false -> - Local = jid:make(<<>>, LServer, <<>>), - ejabberd_router:route(Local, JID, Packet), - Mod:set_motd_user(LUser, LServer); +-spec set_motd_user(module(), binary(), binary()) -> any(). +set_motd_user(Mod, LUser, LServer) -> + case use_cache(Mod, LServer) of + true -> + ets_cache:update( + ?MOTD_CACHE, {LUser, LServer}, {ok, true}, + fun() -> Mod:set_motd_user(LUser, LServer) end, + cache_nodes(Mod, LServer)); + false -> + Mod:set_motd_user(LUser, LServer) + end. + +-spec delete_motd(module(), binary()) -> ok | {error, any()}. +delete_motd(Mod, LServer) -> + case Mod:delete_motd(LServer) of + ok -> + case use_cache(Mod, LServer) of true -> + ejabberd_cluster:eval_everywhere( + ?MODULE, clean_cache, [LServer]); + false -> ok end; - error -> - ok - end; -send_motd(_) -> - ok. + Err -> + Err + end. get_stored_motd(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - case Mod:get_motd(LServer) of + case get_motd(Mod, LServer) of {ok, Packet} -> - {fxml:get_subtag_cdata(Packet, <<"subject">>), - fxml:get_subtag_cdata(Packet, <<"body">>)}; - error -> + CodecOpts = ejabberd_config:codec_options(), + try xmpp:decode(Packet, ?NS_CLIENT, CodecOpts) of + #message{body = Body, subject = Subject} -> + {xmpp:get_text(Subject), xmpp:get_text(Body)} + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("Failed to decode motd packet ~p: ~ts", + [Packet, xmpp:format_error(Why)]) + end; + _ -> {<<>>, <<>>} end. %% This function is similar to others, but doesn't perform any ACL verification send_announcement_to_all(Host, SubjectS, BodyS) -> - SubjectEls = if SubjectS /= <<>> -> - [#xmlel{name = <<"subject">>, children = [{xmlcdata, SubjectS}]}]; - true -> - [] - end, - BodyEls = if BodyS /= <<>> -> - [#xmlel{name = <<"body">>, children = [{xmlcdata, BodyS}]}]; - true -> - [] - end, - Packet = #xmlel{ - name = <<"message">>, - attrs = [{<<"type">>, <<"headline">>}], - children = SubjectEls ++ BodyEls - }, + Packet = #message{type = headline, + body = xmpp:mk_text(BodyS), + subject = xmpp:mk_text(SubjectS)}, Sessions = ejabberd_sm:dirty_get_sessions_list(), - Local = jid:make(<<>>, Host, <<>>), + Local = jid:make(Host), lists:foreach( fun({U, S, R}) -> Dest = jid:make(U, S, R), - ejabberd_router:route(Local, Dest, add_store_hint(Packet)) + ejabberd_router:route( + xmpp:set_from_to(add_store_hint(Packet), Local, Dest)) end, Sessions). -spec get_access(global | binary()) -> atom(). get_access(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, access, - fun(A) -> A end, - none). - --spec add_store_hint(xmlel()) -> xmlel(). + mod_announce_opt:access(Host). +-spec add_store_hint(stanza()) -> stanza(). add_store_hint(El) -> - Hint = #xmlel{name = <<"store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]}, - fxml:append_subtags(El, [Hint]). + xmpp:set_subtag(El, #hint{type = store}). + +-spec route_forbidden_error(stanza()) -> ok. +route_forbidden_error(Packet) -> + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_forbidden(?T("Access denied by service policy"), Lang), + ejabberd_router:route_error(Packet, Err). + +-spec init_cache(module(), binary(), gen_mod:opts()) -> ok. +init_cache(Mod, Host, Opts) -> + case use_cache(Mod, Host) of + true -> + CacheOpts = cache_opts(Opts), + ets_cache:new(?MOTD_CACHE, CacheOpts); + false -> + ets_cache:delete(?MOTD_CACHE) + end. + +-spec cache_opts(gen_mod:opts()) -> [proplists:property()]. +cache_opts(Opts) -> + MaxSize = mod_announce_opt:cache_size(Opts), + CacheMissed = mod_announce_opt:cache_missed(Opts), + LifeTime = mod_announce_opt:cache_life_time(Opts), + [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. + +-spec use_cache(module(), binary()) -> boolean(). +use_cache(Mod, Host) -> + case erlang:function_exported(Mod, use_cache, 1) of + true -> Mod:use_cache(Host); + false -> mod_announce_opt:use_cache(Host) + end. + +-spec cache_nodes(module(), binary()) -> [node()]. +cache_nodes(Mod, Host) -> + case erlang:function_exported(Mod, cache_nodes, 1) of + true -> Mod:cache_nodes(Host); + false -> ejabberd_cluster:get_nodes() + end. + +-spec clean_cache(binary()) -> non_neg_integer(). +clean_cache(LServer) -> + ets_cache:filter( + ?MOTD_CACHE, + fun({_, S}, _) -> S /= LServer end). %%------------------------------------------------------------------------- export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). +import_info() -> + [{<<"motd">>, 3}]. + +import_start(LServer, DBType) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:init(LServer, []). -import(LServer, DBType, LA) -> +import(LServer, {sql, _}, DBType, Tab, List) -> Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, LA). + Mod:import(LServer, Tab, List). mod_opt_type(access) -> - fun acl:access_rules_validator/1; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(_) -> [access, db_type]. + econf:acl(); +mod_opt_type(db_type) -> + econf:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). + +mod_options(Host) -> + [{access, none}, + {db_type, ejabberd_config:default_db(Host, ?MODULE)}, + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. |