%%%---------------------------------------------------------------------- %%% File : mod_announce.erl %%% Author : Alexey Shchepin %%% Purpose : Manage announce messages %%% Created : 11 Aug 2003 by Alexey Shchepin %%% %%% %%% ejabberd, Copyright (C) 2002-2012 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as %%% published by the Free Software Foundation; either version 2 of the %%% License, or (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA %%% 02111-1307 USA %%% %%%---------------------------------------------------------------------- %%% Implements a small subset of XEP-0133: Service Administration %%% Version 1.1 (2005-08-19) -module(mod_announce). -author('alexey@process-one.net'). -behaviour(gen_mod). -export([start/2, init/0, stop/1, announce/3, send_motd/1, disco_identity/5, disco_features/5, disco_items/5, send_announcement_to_all/3, announce_commands/4, announce_items/4]). -include_lib("exmpp/include/exmpp.hrl"). -include("ejabberd.hrl"). -include("adhoc.hrl"). -record(motd, {server, packet}). -record(motd_users, {us, dummy = []}). -define(PROCNAME, ejabberd_announce). -define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]). tokenize(Node) -> string:tokens(Node, "/#"). start(Host, Opts) when is_list(Host) -> start(list_to_binary(Host), Opts); start(HostB, _Opts) -> mnesia:create_table(motd, [{disc_copies, [node()]}, {attributes, record_info(fields, motd)}]), mnesia:create_table(motd_users, [{disc_copies, [node()]}, {attributes, record_info(fields, motd_users)}]), update_tables(), ejabberd_hooks:add(local_send_to_resource_hook, HostB, ?MODULE, announce, 50), ejabberd_hooks:add(disco_local_identity, HostB, ?MODULE, disco_identity, 50), ejabberd_hooks:add(disco_local_features, HostB, ?MODULE, disco_features, 50), ejabberd_hooks:add(disco_local_items, HostB, ?MODULE, disco_items, 50), ejabberd_hooks:add(adhoc_local_items, HostB, ?MODULE, announce_items, 50), ejabberd_hooks:add(adhoc_local_commands, HostB, ?MODULE, announce_commands, 50), ejabberd_hooks:add(user_available_hook, HostB, ?MODULE, send_motd, 50), register(gen_mod:get_module_proc(HostB, ?PROCNAME), proc_lib:spawn(?MODULE, init, [])). 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. stop(Host) -> HostB = list_to_binary(Host), ejabberd_hooks:delete(adhoc_local_commands, HostB, ?MODULE, announce_commands, 50), ejabberd_hooks:delete(adhoc_local_items, HostB, ?MODULE, announce_items, 50), ejabberd_hooks:delete(disco_local_identity, HostB, ?MODULE, disco_identity, 50), ejabberd_hooks:delete(disco_local_features, HostB, ?MODULE, disco_features, 50), ejabberd_hooks:delete(disco_local_items, HostB, ?MODULE, disco_items, 50), ejabberd_hooks:delete(local_send_to_resource_hook, HostB, ?MODULE, announce, 50), ejabberd_hooks:delete(user_available_hook, HostB, ?MODULE, send_motd, 50), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), exit(whereis(Proc), stop), {wait, Proc}. %% Announcing via messages to a custom resource announce(From, To, Packet) -> case {exmpp_jid:prep_node(To), exmpp_jid:prep_resource(To)} of {undefined, Res} -> Name = Packet#xmlel.name, Proc = gen_mod:get_module_proc_existing(exmpp_jid:prep_domain_as_list(To), ?PROCNAME), case {Res, Name} of {<<"announce/all">>, 'message'} -> Proc ! {announce_all, From, To, Packet}, stop; {<<"announce/all-hosts/all">>, 'message'} -> Proc ! {announce_all_hosts_all, From, To, Packet}, stop; {<<"announce/online">>, 'message'} -> Proc ! {announce_online, From, To, Packet}, stop; {<<"announce/all-hosts/online">>, 'message'} -> Proc ! {announce_all_hosts_online, From, To, Packet}, stop; {<<"announce/motd">>, 'message'} -> Proc ! {announce_motd, From, To, Packet}, stop; {<<"announce/all-hosts/motd">>, 'message'} -> Proc ! {announce_all_hosts_motd, From, To, Packet}, stop; {<<"announce/motd/update">>, 'message'} -> Proc ! {announce_motd_update, From, To, Packet}, stop; {<<"announce/all-hosts/motd/update">>, 'message'} -> Proc ! {announce_all_hosts_motd_update, From, To, Packet}, stop; {<<"announce/motd/delete">>, 'message'} -> Proc ! {announce_motd_delete, From, To, Packet}, stop; {<<"announce/all-hosts/motd/delete">>, 'message'} -> Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, stop; _ -> ok end; _ -> ok end. %%------------------------------------------------------------------------- %% Announcing via ad-hoc commands -define(INFO_COMMAND(Lang, Node), [#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', attrs = [?XMLATTR(<<"category">>, <<"automation">>), ?XMLATTR(<<"type">>, <<"command-node">>), ?XMLATTR(<<"name">>, get_title(Lang, Node))]}]). disco_identity(Acc, _From, _To, Node, Lang) -> LNode = tokenize(binary_to_list(Node)), case LNode of ?NS_ADMINL("announce") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("announce-allhosts") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("announce-all") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("announce-all-allhosts") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("set-motd") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("set-motd-allhosts") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("edit-motd") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("edit-motd-allhosts") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("delete-motd") -> ?INFO_COMMAND(Lang, Node); ?NS_ADMINL("delete-motd-allhosts") -> ?INFO_COMMAND(Lang, Node); _ -> Acc end. %%------------------------------------------------------------------------- -define(INFO_RESULT(Allow, Feats), case Allow of deny -> {error, 'forbidden'}; allow -> {result, Feats} end). disco_features(Acc, From, To, <<"announce">>, _Lang) -> LServer = exmpp_jid:prep_domain_as_list(To), case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; _ -> Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), case {acl:match_rule(LServer, Access1, From), acl:match_rule(global, Access2, From)} of {deny, deny} -> {error, 'forbidden'}; _ -> {result, []} end end; disco_features(Acc, From, To, Node, _Lang) -> LServer = exmpp_jid:prep_domain_as_list(To), case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; _ -> Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), Allow = acl:match_rule(LServer, Access, From), AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none), AllowGlobal = acl:match_rule(global, AccessGlobal, From), case Node of <> -> ?INFO_RESULT(Allow, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(Allow, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(Allow, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(Allow, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(Allow, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]); <> -> ?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]); _ -> Acc end end. %%------------------------------------------------------------------------- -define(NODE_TO_ITEM(Lang, Server, Node), #xmlel{ns = ?NS_DISCO_ITEMS, name = 'item', attrs = [?XMLATTR(<<"jid">>, Server), ?XMLATTR(<<"node">>, Node), ?XMLATTR(<<"name">>, get_title(Lang, Node))]}). -define(ITEMS_RESULT(Allow, Items), case Allow of deny -> {error, 'forbidden'}; allow -> {result, Items} end). disco_items(Acc, From, To, <<>>, Lang) -> LServer = exmpp_jid:prep_domain_as_list(To), Server = exmpp_jid:domain(To), case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; _ -> Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), case {acl:match_rule(LServer, Access1, From), acl:match_rule(global, Access2, From)} of {deny, deny} -> Acc; _ -> Items = case Acc of {result, I} -> I; _ -> [] end, Nodes = [?NODE_TO_ITEM(Lang, Server, <<"announce">>)], {result, Items ++ Nodes} end end; disco_items(Acc, From, To, <<"announce">>, Lang) -> LServer = exmpp_jid:prep_domain_as_list(To), case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; _ -> announce_items(Acc, From, To, Lang) end; disco_items(Acc, From, To, Node, _Lang) -> LServer = exmpp_jid:prep_domain_as_list(To), case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; _ -> Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), Allow = acl:match_rule(LServer, Access, From), AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none), AllowGlobal = acl:match_rule(global, AccessGlobal, From), case Node of <> -> ?ITEMS_RESULT(Allow, []); <> -> ?ITEMS_RESULT(Allow, []); <> -> ?ITEMS_RESULT(Allow, []); <> -> ?ITEMS_RESULT(Allow, []); <> -> ?ITEMS_RESULT(Allow, []); <> -> ?ITEMS_RESULT(AllowGlobal, []); <> -> ?ITEMS_RESULT(AllowGlobal, []); <> -> ?ITEMS_RESULT(AllowGlobal, []); <> -> ?ITEMS_RESULT(AllowGlobal, []); <> -> ?ITEMS_RESULT(AllowGlobal, []); _ -> Acc end end. %%------------------------------------------------------------------------- announce_items(Acc, From, To, Lang) -> LServer = exmpp_jid:prep_domain_as_list(To), Server = exmpp_jid:domain(To), Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), Nodes1 = case acl:match_rule(LServer, Access1, From) of allow -> [?NODE_TO_ITEM(Lang, Server, <>), ?NODE_TO_ITEM(Lang, Server, <>), ?NODE_TO_ITEM(Lang, Server, <>), ?NODE_TO_ITEM(Lang, Server, <>), ?NODE_TO_ITEM(Lang, Server, <>)]; deny -> [] end, Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), Nodes2 = case acl:match_rule(global, Access2, From) of allow -> [?NODE_TO_ITEM(Lang, Server, <>), ?NODE_TO_ITEM(Lang, Server, <>), ?NODE_TO_ITEM(Lang, Server, <>), ?NODE_TO_ITEM(Lang, Server, <>), ?NODE_TO_ITEM(Lang, Server, <>)]; deny -> [] end, case {Nodes1, Nodes2} of {[], []} -> Acc; _ -> Items = case Acc of {result, I} -> I; _ -> [] end, {result, Items ++ Nodes1 ++ Nodes2} end. %%------------------------------------------------------------------------- commands_result(Allow, From, To, Request) -> case Allow of deny -> {error, 'forbidden'}; allow -> announce_commands(From, To, Request) end. announce_commands(Acc, From, To, #adhoc_request{ node = Node} = Request) -> LServer = exmpp_jid:prep_domain_as_list(To), LNode = tokenize(Node), F = fun() -> Access = gen_mod:get_module_opt(global, ?MODULE, access, none), Allow = acl:match_rule(global, Access, From), commands_result(Allow, From, To, Request) end, R = case LNode of ?NS_ADMINL("announce-allhosts") -> F(); ?NS_ADMINL("announce-all-allhosts") -> F(); ?NS_ADMINL("set-motd-allhosts") -> F(); ?NS_ADMINL("edit-motd-allhosts") -> F(); ?NS_ADMINL("delete-motd-allhosts") -> F(); _ -> Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), Allow = acl:match_rule(LServer, Access, From), case LNode of ?NS_ADMINL("announce") -> commands_result(Allow, From, To, Request); ?NS_ADMINL("announce-all") -> commands_result(Allow, From, To, Request); ?NS_ADMINL("set-motd") -> commands_result(Allow, From, To, Request); ?NS_ADMINL("edit-motd") -> commands_result(Allow, From, To, Request); ?NS_ADMINL("delete-motd") -> commands_result(Allow, From, To, Request); _ -> unknown end end, case R of unknown -> Acc; _ -> {stop, R} end. %%------------------------------------------------------------------------- announce_commands(From, To, #adhoc_request{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 %% 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" -> %% User cancels request adhoc:produce_response(Request, #adhoc_response{status = canceled}); XData == false, ActionIsExecute -> %% User requests form Elements = generate_adhoc_form(Lang, Node, exmpp_jid:prep_domain_as_list(To)), 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, 'bad-request'}; Fields -> handle_adhoc_form(From, To, Request, Fields) end; true -> {error, 'bad-request'} end. -define(VVALUE(Val), #xmlel{ns = ?NS_DATA_FORMS, name = 'value', children = [#xmlcdata{cdata = Val}]}). -define(VVALUEL(Val), case Val of <<>> -> []; _ -> [?VVALUE(Val)] end). -define(TVFIELD(Type, Var, Val), #xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs = [?XMLATTR(<<"type">>, Type), ?XMLATTR(<<"var">>, Var)], children = ?VVALUEL(Val)}). -define(HFIELD(), ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, list_to_binary(?NS_ADMIN_s))). generate_adhoc_form(Lang, Node, ServerHost) -> LNode = tokenize(Node), {OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd")) or (LNode == ?NS_ADMINL("edit-motd-allhosts")) -> get_stored_motd(ServerHost); true -> {[], []} end, #xmlel{ns = ?NS_DATA_FORMS, name = 'x', attrs = [?XMLATTR(<<"type">>, <<"form">>)], children = [?HFIELD(), #xmlel{ns = ?NS_DATA_FORMS, name = 'title', children = [#xmlcdata{cdata = list_to_binary(get_title(Lang, Node))}]}] ++ if (LNode == ?NS_ADMINL("delete-motd")) or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> [#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs = [?XMLATTR(<<"var">>, <<"confirm">>), ?XMLATTR(<<"type">>, <<"boolean">>), ?XMLATTR(<<"label">>, translate:translate(Lang, "Really delete message of the day?"))], children = [#xmlel{ns = ?NS_DATA_FORMS, name = 'value', children = [#xmlcdata{cdata = <<"true">>}]}]}]; true -> [#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs = [?XMLATTR(<<"var">>, <<"subject">>), ?XMLATTR(<<"type">>, <<"text-single">>), ?XMLATTR(<<"label">>, translate:translate(Lang, "Subject"))], children = ?VVALUEL(list_to_binary(OldSubject))}, #xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs = [?XMLATTR(<<"var">>, <<"body">>), ?XMLATTR(<<"type">>, <<"text-multi">>), ?XMLATTR(<<"label">>, translate:translate(Lang, "Message body"))], children = ?VVALUEL(list_to_binary(OldBody))}] end}. join_lines([]) -> []; join_lines(Lines) -> join_lines(Lines, []). join_lines([Line|Lines], Acc) -> join_lines(Lines, ["\n",Line|Acc]); join_lines([], Acc) -> %% Remove last newline lists:flatten(lists:reverse(tl(Acc))). handle_adhoc_form(From, To, #adhoc_request{lang = Lang, node = Node, sessionid = SessionID}, Fields) -> LServerB = exmpp_jid:prep_domain(To), Confirm = case lists:keysearch("confirm", 1, Fields) of {value, {"confirm", ["true"]}} -> true; {value, {"confirm", ["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{ns = ?NS_JABBER_CLIENT, name = 'message', attrs = [?XMLATTR(<<"type">>, <<"normal">>)], children = if Subject /= [] -> [#xmlel{ns = ?NS_JABBER_CLIENT, name = 'subject', children = [#xmlcdata{cdata = list_to_binary(Subject)}]}]; true -> [] end ++ if Body /= [] -> [#xmlel{ns = ?NS_JABBER_CLIENT, name = 'body', children = [#xmlcdata{cdata = list_to_binary(Body)}]}]; true -> [] end}, Proc = gen_mod:get_module_proc_existing(LServerB, ?PROCNAME), case {Node, Body} of {?NS_ADMIN_s ++ "#delete-motd", _} -> if Confirm -> Proc ! {announce_motd_delete, From, To, Packet}, adhoc:produce_response(Response); true -> adhoc:produce_response(Response) end; {?NS_ADMIN_s ++ "#delete-motd-allhosts", _} -> if Confirm -> Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, adhoc:produce_response(Response); true -> adhoc:produce_response(Response) 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, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-acceptable', {"en", "No body provided for announce message"})}; %% Now send the packet to ?PROCNAME. %% We don't use direct announce_* functions because it %% leads to large delay in response and queries processing {?NS_ADMIN_s ++ "#announce", _} -> Proc ! {announce_online, From, To, Packet}, adhoc:produce_response(Response); {?NS_ADMIN_s ++ "#announce-allhosts", _} -> Proc ! {announce_all_hosts_online, From, To, Packet}, adhoc:produce_response(Response); {?NS_ADMIN_s ++ "#announce-all", _} -> Proc ! {announce_all, From, To, Packet}, adhoc:produce_response(Response); {?NS_ADMIN_s ++ "#announce-all-allhosts", _} -> Proc ! {announce_all_hosts_all, From, To, Packet}, adhoc:produce_response(Response); {?NS_ADMIN_s ++ "#set-motd", _} -> Proc ! {announce_motd, From, To, Packet}, adhoc:produce_response(Response); {?NS_ADMIN_s ++ "#set-motd-allhosts", _} -> Proc ! {announce_all_hosts_motd, From, To, Packet}, adhoc:produce_response(Response); {?NS_ADMIN_s ++ "#edit-motd", _} -> Proc ! {announce_motd_update, From, To, Packet}, adhoc:produce_response(Response); {?NS_ADMIN_s ++ "#edit-motd-allhosts", _} -> Proc ! {announce_all_hosts_motd_update, From, To, Packet}, adhoc:produce_response(Response); _ -> %% This can't happen, as we haven't registered any other %% command nodes. {error, 'internal-server-error'} end. get_title(Lang, Node) when is_list(Node) -> get_title(Lang, list_to_binary(Node)); get_title(Lang, <<"announce">>) -> translate:translate(Lang, "Announcements"); get_title(Lang, <>) -> translate:translate(Lang, "Send announcement to all users"); get_title(Lang, <>) -> translate:translate(Lang, "Send announcement to all users on all hosts"); get_title(Lang, <>) -> translate:translate(Lang, "Send announcement to all online users"); get_title(Lang, <>) -> translate:translate(Lang, "Send announcement to all online users on all hosts"); get_title(Lang, <>) -> translate:translate(Lang, "Set message of the day and send to online users"); get_title(Lang, <>) -> translate:translate(Lang, "Set message of the day on all hosts and send to online users"); get_title(Lang, <>) -> translate:translate(Lang, "Update message of the day (don't send)"); get_title(Lang, <>) -> translate:translate(Lang, "Update message of the day on all hosts (don't send)"); get_title(Lang, <>) -> translate:translate(Lang, "Delete message of the day"); get_title(Lang, <>) -> translate:translate(Lang, "Delete message of the day on all hosts"). %%------------------------------------------------------------------------- announce_all(From, To, Packet) -> Host = exmpp_jid:prep_domain_as_list(To), Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> Local = exmpp_jid:make(exmpp_jid:domain(To)), lists:foreach( fun({User, Server}) -> Dest = exmpp_jid:make(User, Server), ejabberd_router:route(Local, Dest, Packet) end, ejabberd_auth:get_vh_registered_users(Host)) end. announce_all_hosts_all(From, To, Packet) -> Access = gen_mod:get_module_opt(global, ?MODULE, access, none), case acl:match_rule(global, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> Local = exmpp_jid:make(exmpp_jid:domain(To)), lists:foreach( fun({User, Server}) -> Dest = exmpp_jid:make(User, Server), ejabberd_router:route(Local, Dest, Packet) end, ejabberd_auth:dirty_get_registered_users()) end. announce_online(From, To, Packet) -> Host = exmpp_jid:prep_domain_as_list(To), Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> announce_online1(ejabberd_sm:get_vh_session_list(exmpp_jid:prep_domain(To)), exmpp_jid:domain_as_list(To), Packet) end. announce_all_hosts_online(From, To, Packet) -> Access = gen_mod:get_module_opt(global, ?MODULE, access, none), case acl:match_rule(global, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> announce_online1(ejabberd_sm:dirty_get_sessions_list(), exmpp_jid:domain_as_list(To), Packet) end. announce_online1(Sessions, Server, Packet) -> Local = exmpp_jid:make(Server), lists:foreach( fun({U, S, R}) -> Dest = exmpp_jid:make(U, S, R), ejabberd_router:route(Local, Dest, Packet) end, Sessions). announce_motd(From, To, Packet) -> Host = exmpp_jid:prep_domain_as_list(To), Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> announce_motd(Host, Packet) end. announce_all_hosts_motd(From, To, Packet) -> Access = gen_mod:get_module_opt(global, ?MODULE, access, none), case acl:match_rule(global, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> Hosts = ?MYHOSTS, [announce_motd(Host, Packet) || Host <- Hosts] end. announce_motd(Host, Packet) -> announce_motd_update(Host, Packet), Sessions = ejabberd_sm:get_vh_session_list(list_to_binary(Host)), announce_online1(Sessions, Host, Packet), F = fun() -> lists:foreach( fun({U, S, _R}) -> mnesia:write(#motd_users{us = {U, S}}) end, Sessions) end, mnesia:transaction(F). announce_motd_update(From, To, Packet) -> Host = exmpp_jid:prep_domain_as_list(To), Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> announce_motd_update(Host, Packet) end. announce_all_hosts_motd_update(From, To, Packet) -> Access = gen_mod:get_module_opt(global, ?MODULE, access, none), case acl:match_rule(global, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> Hosts = ?MYHOSTS, [announce_motd_update(Host, Packet) || Host <- Hosts] end. announce_motd_update(LServer, Packet) -> announce_motd_delete(LServer), F = fun() -> mnesia:write(#motd{server = LServer, packet = Packet}) end, mnesia:transaction(F). announce_motd_delete(From, To, Packet) -> Host = exmpp_jid:prep_domain_as_list(To), Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> announce_motd_delete(Host) end. announce_all_hosts_motd_delete(From, To, Packet) -> Access = gen_mod:get_module_opt(global, ?MODULE, access, none), case acl:match_rule(global, Access, From) of deny -> Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'), ejabberd_router:route(To, From, Err); allow -> Hosts = ?MYHOSTS, [announce_motd_delete(Host) || Host <- Hosts] end. announce_motd_delete(LServer) -> F = fun() -> mnesia:delete({motd, LServer}), mnesia:write_lock_table(motd_users), Users = mnesia:select( motd_users, [{#motd_users{us = '$1', _ = '_'}, [{'==', {element, 2, '$1'}, LServer}], ['$1']}]), lists:foreach(fun(US) -> mnesia:delete({motd_users, US}) end, Users) end, mnesia:transaction(F). send_motd(JID) -> LServer = exmpp_jid:prep_domain_as_list(JID), LUser = exmpp_jid:prep_node_as_list(JID), case catch mnesia:dirty_read({motd, LServer}) of [#motd{packet = Packet}] -> US = {LUser, LServer}, case catch mnesia:dirty_read({motd_users, US}) of [#motd_users{}] -> ok; _ -> Local = exmpp_jid:make(exmpp_jid:prep_domain(JID)), ejabberd_router:route(Local, JID, Packet), F = fun() -> mnesia:write(#motd_users{us = US}) end, mnesia:transaction(F) end; _ -> ok end. get_stored_motd(LServer) -> case catch mnesia:dirty_read({motd, LServer}) of [#motd{packet = Packet}] -> {exmpp_xml:get_cdata_as_list(exmpp_xml:get_element(Packet, 'subject')), exmpp_xml:get_cdata_as_list(exmpp_xml:get_element(Packet, 'body'))}; _ -> {"", ""} 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{ns = ?NS_JABBER_CLIENT, name = 'subject', children = [#xmlcdata{cdata = list_to_binary(SubjectS)}]}]; true -> [] end, BodyEls = if BodyS /= [] -> [#xmlel{ns = ?NS_JABBER_CLIENT, name = 'body', children = [#xmlcdata{cdata = list_to_binary(BodyS)}]}]; true -> [] end, Packet = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', attrs = [?XMLATTR(<<"type">>, <<"normal">>)], children = SubjectEls ++ BodyEls}, Sessions = ejabberd_sm:dirty_get_sessions_list(), Local = exmpp_jid:make(Host), lists:foreach( fun({U, S, R}) -> Dest = exmpp_jid:make(U, S, R), ejabberd_router:route(Local, Dest, Packet) end, Sessions). %%------------------------------------------------------------------------- update_tables() -> update_motd_table(), update_motd_users_table(). update_motd_table() -> Fields = record_info(fields, motd), case mnesia:table_info(motd, attributes) of Fields -> ok; [id, packet] -> ?INFO_MSG("Converting motd table from " "{id, packet} format", []), Host = ?MYNAME, {atomic, ok} = mnesia:create_table( mod_announce_tmp_table, [{disc_only_copies, [node()]}, {type, bag}, {local_content, true}, {record_name, motd}, {attributes, record_info(fields, motd)}]), mnesia:transform_table(motd, ignore, Fields), F1 = fun() -> mnesia:write_lock_table(mod_announce_tmp_table), mnesia:foldl( fun(#motd{server = _} = R, _) -> mnesia:dirty_write( mod_announce_tmp_table, R#motd{server = Host}) end, ok, motd) end, mnesia:transaction(F1), mnesia:clear_table(motd), F2 = fun() -> mnesia:write_lock_table(motd), mnesia:foldl( fun(R, _) -> mnesia:dirty_write(R) end, ok, mod_announce_tmp_table) end, mnesia:transaction(F2), mnesia:delete_table(mod_announce_tmp_table); _ -> ?INFO_MSG("Recreating motd table", []), mnesia:transform_table(motd, ignore, Fields) end. update_motd_users_table() -> Fields = record_info(fields, motd_users), case mnesia:table_info(motd_users, attributes) of Fields -> ok; [luser, dummy] -> ?INFO_MSG("Converting motd_users table from " "{luser, dummy} format", []), Host = ?MYNAME, {atomic, ok} = mnesia:create_table( mod_announce_tmp_table, [{disc_only_copies, [node()]}, {type, bag}, {local_content, true}, {record_name, motd_users}, {attributes, record_info(fields, motd_users)}]), mnesia:transform_table(motd_users, ignore, Fields), F1 = fun() -> mnesia:write_lock_table(mod_announce_tmp_table), mnesia:foldl( fun(#motd_users{us = U} = R, _) -> mnesia:dirty_write( mod_announce_tmp_table, R#motd_users{us = {U, Host}}) end, ok, motd_users) end, mnesia:transaction(F1), mnesia:clear_table(motd_users), F2 = fun() -> mnesia:write_lock_table(motd_users), mnesia:foldl( fun(R, _) -> mnesia:dirty_write(R) end, ok, mod_announce_tmp_table) end, mnesia:transaction(F2), mnesia:delete_table(mod_announce_tmp_table); _ -> ?INFO_MSG("Recreating motd_users table", []), mnesia:transform_table(motd_users, ignore, Fields) end.