25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-24 17:29:28 +01:00

ODBC support for mod_announce

This commit is contained in:
Evgeniy Khramtsov 2012-04-02 14:49:13 +10:00
parent 866085c918
commit b43444f674
5 changed files with 952 additions and 0 deletions

View 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

View 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
%%%----------------------------------------------------------------------

907
src/mod_announce_odbc.erl Normal file
View File

@ -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.

View 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;

View 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()
);