]> granicus.if.org Git - ejabberd/commitdiff
ODBC support for mod_announce
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Mon, 2 Apr 2012 04:49:13 +0000 (14:49 +1000)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Mon, 2 Apr 2012 04:49:13 +0000 (14:49 +1000)
doc/guide.tex
src/ejd2odbc.erl
src/mod_announce_odbc.erl [new file with mode: 0644]
src/odbc/mysql.sql
src/odbc/pg.sql

index 4c701e4a0a0dc20bb83cd499ce9a9124d4670116..4ae4ba03346ecf5c189c2e7f2fa9ca8e388c4328 100644 (file)
@@ -66,6 +66,7 @@
 \newcommand{\module}[1]{\texttt{#1}}
 \newcommand{\modadhoc}{\module{mod\_adhoc}}
 \newcommand{\modannounce}{\module{mod\_announce}}
+\newcommand{\modannounceodbc}{\module{mod\_announce\_odbc}}
 \newcommand{\modblocking}{\module{mod\_blocking}}
 \newcommand{\modcaps}{\module{mod\_caps}}
 \newcommand{\modconfigure}{\module{mod\_configure}}
@@ -2579,6 +2580,8 @@ The following table lists all modules included in \ejabberd{}.
     \hline
     \hline \modadhoc{} & Ad-Hoc Commands (\xepref{0050}) &  \\
     \hline \ahrefloc{modannounce}{\modannounce{}} & Manage announcements & recommends \modadhoc{} \\
+    \hline \ahrefloc{modannounce}{\modannounceodbc{}} & Manage announcements & recommends \modadhoc{} \\
+    & & supported DB (*) \\
     \hline \modblocking{} & Simple Communications Blocking (\xepref{0191}) & \modprivacy{} \\
     \hline \modcaps{} &  Entity Capabilities (\xepref{0115}) & \\
     \hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\
@@ -2654,6 +2657,7 @@ database for the following data:
 \item User rules for blocking communications: Use \term{mod\_privacy\_odbc} instead of \term{mod\_privacy}.
 \item Pub-Sub nodes, items and subscriptions: Use \term{mod\_pubsub\_odbc} instead of \term{mod\_pubsub}.
 \item Multi-user chats: Use \term{mod\_muc\_odbc} instead of \term{mod\_muc}.
+\item Manage announcements: Use \term{mod\_announce\_odbc} instead of \term{mod\_announce}.
 \end{itemize}
 
 You can find more
index a1f3a1eedbce05b7baaa8c53c87473d2c2d18430..4f09cd7b283f394c8de31083b15d0c449fd600dd 100644 (file)
@@ -36,6 +36,8 @@
         export_vcard_search/2,
         export_private_storage/2,
          export_privacy/2,
+         export_motd/2,
+         export_motd_users/2,
          export_muc_room/2,
          export_muc_registered/2]).
 
@@ -64,6 +66,8 @@
 -record(private_storage, {usns, xml}).
 -record(muc_room, {name_host, opts}).
 -record(muc_registered, {us_host, nick}).
+-record(motd, {server, packet}).
+-record(motd_users, {us, dummy = []}).
 
 -define(MAX_RECORDS_PER_TRANSACTION, 1000).
 
@@ -355,6 +359,31 @@ export_privacy(Server, Output) ->
               []
       end).
 
+export_motd(Server, Output) ->
+    export_common(
+      Server, motd, Output,
+      fun(Host, #motd{server = LServer, packet = El})
+            when LServer == Host ->
+              ["delete from motd where username='';"
+               "insert into motd(username, xml) values ('', '",
+               ejabberd_odbc:escape(xml:element_to_binary(El)), "');"];
+         (_Host, _R) ->
+              []
+      end).
+
+export_motd_users(Server, Output) ->
+    export_common(
+      Server, motd_users, Output,
+      fun(Host, #motd_users{us = {LUser, LServer}})
+            when LServer == Host, LUser /= "" ->
+              Username = ejabberd_odbc:escape(LUser),
+              ["delete from motd where username='", Username, "';"
+               "insert into motd(username, xml) values ('",
+               Username, "', '');"];
+         (_Host, _R) ->
+              []
+      end).
+
 %%%----------------------------------------------------------------------
 %%% Internal functions
 %%%----------------------------------------------------------------------
diff --git a/src/mod_announce_odbc.erl b/src/mod_announce_odbc.erl
new file mode 100644 (file)
index 0000000..7e1e9dc
--- /dev/null
@@ -0,0 +1,907 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_announce_odbc.erl
+%%% Author  : Alexey Shchepin <alexey@process-one.net>
+%%% Purpose : Manage announce messages
+%%% Created : 11 Aug 2003 by Alexey Shchepin <alexey@process-one.net>
+%%%
+%%%
+%%% 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_odbc).
+-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("ejabberd.hrl").
+-include("jlib.hrl").
+-include("adhoc.hrl").
+
+-define(PROCNAME, ejabberd_announce).
+
+-define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
+tokenize(Node) -> string:tokens(Node, "/#").
+
+start(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),
+    ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
+    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,
+                      ?MODULE, send_motd, 50),
+    register(gen_mod:get_module_proc(Host, ?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) ->
+    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}.
+
+%% Announcing via messages to a custom resource
+announce(From, To, Packet) ->
+    case To of
+       #jid{luser = "", lresource = Res} ->
+           {xmlelement, Name, _Attrs, _Els} = Packet,
+           Proc = gen_mod:get_module_proc(To#jid.lserver, ?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),
+        [{xmlelement, "identity",
+         [{"category", "automation"},
+          {"type", "command-node"},
+          {"name", get_title(Lang, Node)}], []}]).
+
+disco_identity(Acc, _From, _To, Node, Lang) ->
+    LNode = tokenize(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, ?ERR_FORBIDDEN};
+           allow ->
+               {result, Feats}
+       end).
+
+disco_features(Acc, From, #jid{lserver = LServer} = _To,
+              "announce", _Lang) ->
+    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, ?ERR_FORBIDDEN};
+               _ ->
+                   {result, []}
+           end
+    end;
+
+disco_features(Acc, From, #jid{lserver = LServer} = _To,
+              Node, _Lang) ->
+    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
+               ?NS_ADMIN ++ "#announce" ->
+                   ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#announce-all" ->
+                   ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#set-motd" ->
+                   ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#edit-motd" ->
+                   ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#delete-motd" ->
+                   ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#announce-allhosts" ->
+                   ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#announce-all-allhosts" ->
+                   ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#set-motd-allhosts" ->
+                   ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#edit-motd-allhosts" ->
+                   ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+               ?NS_ADMIN ++ "#delete-motd-allhosts" ->
+                   ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+               _ ->
+                   Acc
+           end
+    end.
+
+%%-------------------------------------------------------------------------
+
+-define(NODE_TO_ITEM(Lang, Server, Node),
+       {xmlelement, "item",
+        [{"jid", Server},
+         {"node", Node},
+         {"name", get_title(Lang, Node)}],
+        []}).
+
+-define(ITEMS_RESULT(Allow, Items),
+       case Allow of
+           deny ->
+               {error, ?ERR_FORBIDDEN};
+           allow ->
+               {result, Items}
+       end).
+
+disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To,
+           "", Lang) ->
+    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, #jid{lserver = LServer} = To, "announce", Lang) ->
+    case gen_mod:is_loaded(LServer, mod_adhoc) of
+       false ->
+           Acc;
+       _ ->
+           announce_items(Acc, From, To, Lang)
+    end;
+
+disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
+    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
+               ?NS_ADMIN ++ "#announce" ->
+                   ?ITEMS_RESULT(Allow, []);
+               ?NS_ADMIN ++ "#announce-all" ->
+                   ?ITEMS_RESULT(Allow, []);
+               ?NS_ADMIN ++ "#set-motd" ->
+                   ?ITEMS_RESULT(Allow, []);
+               ?NS_ADMIN ++ "#edit-motd" ->
+                   ?ITEMS_RESULT(Allow, []);
+               ?NS_ADMIN ++ "#delete-motd" ->
+                   ?ITEMS_RESULT(Allow, []);
+               ?NS_ADMIN ++ "#announce-allhosts" ->
+                   ?ITEMS_RESULT(AllowGlobal, []);
+               ?NS_ADMIN ++ "#announce-all-allhosts" ->
+                   ?ITEMS_RESULT(AllowGlobal, []);
+               ?NS_ADMIN ++ "#set-motd-allhosts" ->
+                   ?ITEMS_RESULT(AllowGlobal, []);
+               ?NS_ADMIN ++ "#edit-motd-allhosts" ->
+                   ?ITEMS_RESULT(AllowGlobal, []);
+               ?NS_ADMIN ++ "#delete-motd-allhosts" ->
+                   ?ITEMS_RESULT(AllowGlobal, []);
+               _ ->
+                   Acc
+           end
+    end.
+
+%%-------------------------------------------------------------------------
+
+announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) ->
+    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, ?NS_ADMIN ++ "#announce"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd")];
+                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, ?NS_ADMIN ++ "#announce-allhosts"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all-allhosts"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd-allhosts"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd-allhosts"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd-allhosts")];
+                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, ?ERR_FORBIDDEN};
+       allow ->
+           announce_commands(From, To, Request)
+    end.
+
+
+announce_commands(Acc, From, #jid{lserver = LServer} = To,
+                 #adhoc_request{ node = Node} = Request) ->
+    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 <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" ->
+           %% User cancels request
+           adhoc:produce_response(Request, 
+                                  #adhoc_response{status = canceled});
+       XData == false, ActionIsExecute ->
+           %% 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, ?ERR_BAD_REQUEST};
+               Fields ->
+                   handle_adhoc_form(From, To, Request, Fields)
+           end;
+       true ->
+           {error, ?ERR_BAD_REQUEST}
+    end.
+
+-define(VVALUE(Val),
+       {xmlelement, "value", [], [{xmlcdata, Val}]}).
+-define(TVFIELD(Type, Var, Val),
+       {xmlelement, "field", [{"type", Type},
+                              {"var", Var}],
+        vvaluel(Val)}).
+-define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)).
+
+vvaluel(Val) ->
+    case Val of
+        "" -> [];
+        _ -> [?VVALUE(Val)]
+    end.
+
+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,
+    {xmlelement, "x",
+     [{"xmlns", ?NS_XDATA},
+      {"type", "form"}],
+     [?HFIELD(),
+      {xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}]
+     ++
+     if (LNode == ?NS_ADMINL("delete-motd"))
+       or (LNode == ?NS_ADMINL("delete-motd-allhosts")) ->
+            [{xmlelement, "field",
+              [{"var", "confirm"},
+               {"type", "boolean"},
+               {"label", translate:translate(Lang, "Really delete message of the day?")}],
+              [{xmlelement, "value",
+                [],
+                [{xmlcdata, "true"}]}]}];
+       true ->
+            [{xmlelement, "field", 
+              [{"var", "subject"},
+               {"type", "text-single"},
+               {"label", translate:translate(Lang, "Subject")}],
+              vvaluel(OldSubject)},
+             {xmlelement, "field",
+              [{"var", "body"},
+               {"type", "text-multi"},
+               {"label", translate:translate(Lang, "Message body")}],
+              vvaluel(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, #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
+             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 = {xmlelement, "message", [{"type", "normal"}], 
+             if Subject /= [] ->
+                     [{xmlelement, "subject", [], 
+                       [{xmlcdata, Subject}]}];
+                true ->
+                     []
+             end ++
+             if Body /= [] ->
+                     [{xmlelement, "body", [],
+                       [{xmlcdata, Body}]}];
+                true ->
+                     []
+             end},
+
+    Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
+    case {Node, Body} of
+       {?NS_ADMIN ++ "#delete-motd", _} ->
+           if  Confirm ->
+                   Proc ! {announce_motd_delete, From, To, Packet},
+                   adhoc:produce_response(Response);
+               true ->
+                   adhoc:produce_response(Response)
+           end;
+       {?NS_ADMIN ++ "#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, ?ERRT_NOT_ACCEPTABLE(
+                      Lang,
+                      "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 <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);
+       {?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);
+       {?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);
+       {?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);
+       _ ->
+           %% This can't happen, as we haven't registered any other
+           %% command nodes.
+           {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+get_title(Lang, "announce") ->
+    translate:translate(Lang, "Announcements");
+get_title(Lang, ?NS_ADMIN ++ "#announce-all") ->
+    translate:translate(Lang, "Send announcement to all users");
+get_title(Lang, ?NS_ADMIN ++ "#announce-all-allhosts") ->
+    translate:translate(Lang, "Send announcement to all users on all hosts");
+get_title(Lang, ?NS_ADMIN ++ "#announce") ->
+    translate:translate(Lang, "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");
+get_title(Lang, ?NS_ADMIN ++ "#set-motd") ->
+    translate:translate(Lang, "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");
+get_title(Lang, ?NS_ADMIN ++ "#edit-motd") ->
+    translate:translate(Lang, "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)");
+get_title(Lang, ?NS_ADMIN ++ "#delete-motd") ->
+    translate:translate(Lang, "Delete message of the day");
+get_title(Lang, ?NS_ADMIN ++ "#delete-motd-allhosts") ->
+    translate:translate(Lang, "Delete message of the day on all hosts").
+
+%%-------------------------------------------------------------------------
+
+announce_all(From, To, Packet) ->
+    Host = To#jid.lserver,
+    Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+    case acl:match_rule(Host, Access, From) of
+       deny ->
+           Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+           ejabberd_router:route(To, From, Err);
+       allow ->
+           Local = jlib:make_jid("", To#jid.server, ""),
+           lists:foreach(
+             fun({User, Server}) ->
+                     Dest = jlib:make_jid(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 = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+           ejabberd_router:route(To, From, Err);
+       allow ->
+           Local = jlib:make_jid("", To#jid.server, ""),
+           lists:foreach(
+             fun({User, Server}) ->
+                     Dest = jlib:make_jid(User, Server, ""),
+                     ejabberd_router:route(Local, Dest, Packet)
+             end, ejabberd_auth:dirty_get_registered_users())
+    end.
+
+announce_online(From, To, Packet) ->
+    Host = To#jid.lserver,
+    Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+    case acl:match_rule(Host, Access, From) of
+       deny ->
+           Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+           ejabberd_router:route(To, From, Err);
+       allow ->
+           announce_online1(ejabberd_sm:get_vh_session_list(Host),
+                            To#jid.server,
+                            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 = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+           ejabberd_router:route(To, From, Err);
+       allow ->
+           announce_online1(ejabberd_sm:dirty_get_sessions_list(),
+                            To#jid.server,
+                            Packet)
+    end.
+
+announce_online1(Sessions, Server, Packet) ->
+    Local = jlib:make_jid("", Server, ""),
+    lists:foreach(
+      fun({U, S, R}) ->
+             Dest = jlib:make_jid(U, S, R),
+             ejabberd_router:route(Local, Dest, Packet)
+      end, Sessions).
+
+announce_motd(From, To, Packet) ->
+    Host = To#jid.lserver,
+    Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+    case acl:match_rule(Host, Access, From) of
+       deny ->
+           Err = jlib:make_error_reply(Packet, ?ERR_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 = jlib:make_error_reply(Packet, ?ERR_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(Host),
+    announce_online1(Sessions, Host, Packet),
+    F = fun() ->
+                lists:foreach(
+                  fun({U, _S, _R}) ->
+                          Username = ejabberd_odbc:escape(U),
+                          update_t("motd",
+                                   ["username", "xml"],
+                                   [Username, ""],
+                                   ["username='", Username, "'"])
+                  end, Sessions)
+        end,
+    LServer = jlib:nameprep(Host),
+    ejabberd_odbc:sql_transaction(LServer, F).
+
+announce_motd_update(From, To, Packet) ->
+    Host = To#jid.lserver,
+    Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+    case acl:match_rule(Host, Access, From) of
+       deny ->
+           Err = jlib:make_error_reply(Packet, ?ERR_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 = jlib:make_error_reply(Packet, ?ERR_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),
+    XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
+    F = fun() ->
+                update_t("motd",
+                         ["username", "xml"],
+                         ["", XML],
+                         ["username=''"])
+        end,
+    ejabberd_odbc:sql_transaction(LServer, F).
+
+announce_motd_delete(From, To, Packet) ->
+    Host = To#jid.lserver,
+    Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+    case acl:match_rule(Host, Access, From) of
+       deny ->
+           Err = jlib:make_error_reply(Packet, ?ERR_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 = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+           ejabberd_router:route(To, From, Err);
+       allow ->
+           Hosts = ?MYHOSTS,
+           [announce_motd_delete(Host) || Host <- Hosts]
+    end.
+
+announce_motd_delete(LServer) ->
+    F = fun() ->
+                ejabberd_odbc:sql_query_t(["delete from motd;"])
+        end,
+    ejabberd_odbc:sql_transaction(LServer, F).
+
+send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= "" ->
+    case catch ejabberd_odbc:sql_query(
+                 LServer, ["select xml from motd where username='';"]) of
+        {selected, ["xml"], [{XML}]} ->
+            case xml_stream:parse_element(XML) of
+                {error, _} ->
+                    ok;
+                Packet ->
+                    Username = ejabberd_odbc:escape(LUser),
+                    case catch ejabberd_odbc:sql_query(
+                                 LServer,
+                                 ["select username from motd "
+                                  "where username='", Username, "';"]) of
+                        {selected, ["username"], []} ->
+                            Local = jlib:make_jid("", LServer, ""),
+                            ejabberd_router:route(Local, JID, Packet),
+                            F = fun() ->
+                                        update_t(
+                                          ["motd"],
+                                          ["username", "xml"],
+                                          [Username, ""],
+                                          ["username='", Username, "'"])
+                                end,
+                            ejabberd_odbc:sql_transaction(LServer, F);
+                        _ ->
+                            ok
+                    end
+            end;
+        _ ->
+            ok
+    end;
+send_motd(_) ->
+    ok.
+
+get_stored_motd(LServer) ->
+    case catch ejabberd_odbc:sql_query(
+                 LServer, ["select xml from motd where username='';"]) of
+        {selected, ["xml"], [{XML}]} ->
+            case xml_stream:parse_element(XML) of
+                {error, _} ->
+                    {"", ""};
+                Packet ->
+                    {xml:get_subtag_cdata(Packet, "subject"),
+                     xml:get_subtag_cdata(Packet, "body")}
+            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 /= [] ->
+                     [{xmlelement, "subject", [], [{xmlcdata, SubjectS}]}];
+                true ->
+                     []
+             end,
+    BodyEls = if BodyS /= [] ->
+                     [{xmlelement, "body", [], [{xmlcdata, BodyS}]}];
+                true ->
+                     []
+             end,
+    Packet = {xmlelement, "message", [{"type", "normal"}], SubjectEls ++ BodyEls},
+    Sessions = ejabberd_sm:dirty_get_sessions_list(),
+    Local = jlib:make_jid("", Host, ""),
+    lists:foreach(
+      fun({U, S, R}) ->
+             Dest = jlib:make_jid(U, S, R),
+             ejabberd_router:route(Local, Dest, Packet)
+      end, Sessions).
+
+%% Almost a copy of string:join/2.
+%% We use this version because string:join/2 is relatively
+%% new function (introduced in R12B-0).
+join([], _Sep) ->
+    [];
+join([H|T], Sep) ->
+    [H, [[Sep, X] || X <- T]].
+
+%% Safe atomic update.
+update_t(Table, Fields, Vals, Where) ->
+    UPairs = lists:zipwith(fun(A, B) -> A ++ "='" ++ B ++ "'" end,
+                           Fields, Vals),
+    case ejabberd_odbc:sql_query_t(
+           ["update ", Table, " set ",
+            join(UPairs, ", "),
+            " where ", Where, ";"]) of
+        {updated, 1} ->
+            ok;
+        _ ->
+            ejabberd_odbc:sql_query_t(
+              ["insert into ", Table, "(", join(Fields, ", "),
+               ") values ('", join(Vals, "', '"), "');"])
+    end.
index d67ca6c883be8154b61d68864d498cf826859c1d..104b4448144579d9702b176a0b57cb9021e2de00 100644 (file)
@@ -238,3 +238,9 @@ CREATE TABLE muc_registered (
 
 CREATE INDEX i_muc_registered_nick USING BTREE ON muc_registered(nick(75));
 CREATE UNIQUE INDEX i_muc_registered_jid_host USING BTREE ON muc_registered(jid(75), host(75));
+
+CREATE TABLE motd (
+    username varchar(250) PRIMARY KEY,
+    xml text,
+    created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+) CHARACTER SET utf8;
index aae472d6b13f29a0145533b9198e62ebba63423c..f28cf8af2cee517404e445393ed39732577473da 100644 (file)
@@ -238,3 +238,9 @@ CREATE TABLE muc_registered (
 
 CREATE INDEX i_muc_registered_nick ON muc_registered USING btree (nick);
 CREATE UNIQUE INDEX i_muc_registered_jid_host ON muc_registered USING btree (jid, host);
+
+CREATE TABLE motd (
+    username text PRIMARY KEY,
+    xml text,
+    created_at TIMESTAMP NOT NULL DEFAULT now()
+);