aboutsummaryrefslogtreecommitdiff
path: root/src/mod_announce.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_announce.erl')
-rw-r--r--src/mod_announce.erl443
1 files changed, 436 insertions, 7 deletions
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index 2426ff371..8cabf748a 100644
--- a/src/mod_announce.erl
+++ b/src/mod_announce.erl
@@ -15,10 +15,16 @@
init/0,
stop/1,
announce/3,
- send_motd/1]).
+ send_motd/1,
+ disco_identity/5,
+ disco_features/5,
+ disco_items/5,
+ announce_commands/4,
+ announce_items/4]).
-include("ejabberd.hrl").
-include("jlib.hrl").
+-include("adhoc.hrl").
-record(motd, {server, packet}).
-record(motd_users, {us, dummy = []}).
@@ -33,6 +39,11 @@ start(Host, _Opts) ->
update_tables(),
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),
@@ -66,6 +77,11 @@ 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(sm_register_connection_hook, Host,
@@ -74,6 +90,7 @@ stop(Host) ->
exit(whereis(Proc), stop),
{wait, Proc}.
+% Announcing via messages to a custom resource
announce(From, To, Packet) ->
case To of
#jid{luser = "", lresource = Res} ->
@@ -105,12 +122,422 @@ announce(From, To, Packet) ->
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) ->
+ case Node of
+ "announce/all" ->
+ ?INFO_COMMAND(Lang, Node);
+ "announce/all-hosts/online" ->
+ ?INFO_COMMAND(Lang, Node);
+ "announce/online" ->
+ ?INFO_COMMAND(Lang, Node);
+ "announce/motd" ->
+ ?INFO_COMMAND(Lang, Node);
+ "announce/motd/delete" ->
+ ?INFO_COMMAND(Lang, Node);
+ "announce/motd/update" ->
+ ?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,
+ "announce/all-hosts/online", _Lang) ->
+ case gen_mod:is_loaded(LServer, mod_adhoc) of
+ false ->
+ Acc;
+ _ ->
+ Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Allow = acl:match_rule(global, Access, From),
+ ?INFO_RESULT(Allow, [?NS_COMMANDS])
+ 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),
+ case Node of
+ "announce/all" ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ "announce/online" ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ "announce/motd" ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ "announce/motd/delete" ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ "announce/motd/update" ->
+ ?INFO_RESULT(Allow, [?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,
+ "announce/all-hosts/online", _Lang) ->
+ case gen_mod:is_loaded(LServer, mod_adhoc) of
+ false ->
+ Acc;
+ _ ->
+ Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Allow = acl:match_rule(global, Access, From),
+ ?ITEMS_RESULT(Allow, [])
+ 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),
+ case Node of
+ "announce/all" ->
+ ?ITEMS_RESULT(Allow, []);
+ "announce/online" ->
+ ?ITEMS_RESULT(Allow, []);
+ "announce/motd" ->
+ ?ITEMS_RESULT(Allow, []);
+ "announce/motd/delete" ->
+ ?ITEMS_RESULT(Allow, []);
+ "announce/motd/update" ->
+ ?ITEMS_RESULT(Allow, []);
+ _ ->
+ 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, "announce/all"),
+ ?NODE_TO_ITEM(Lang, Server, "announce/online"),
+ ?NODE_TO_ITEM(Lang, Server, "announce/motd"),
+ ?NODE_TO_ITEM(Lang, Server, "announce/motd/delete"),
+ ?NODE_TO_ITEM(Lang, Server, "announce/motd/update")];
+ 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, "announce/all-hosts/online")];
+ deny ->
+ []
+ end,
+ case {Nodes1, Nodes2} of
+ {[], []} ->
+ Acc;
+ _ ->
+ Items = case Acc of
+ {result, I} -> I;
+ _ -> []
+ end,
+ {result, Items ++ Nodes1 ++ Nodes2}
+ end.
+
+%-------------------------------------------------------------------------
+
+-define(COMMANDS_RESULT(Allow, From, To, Request),
+ case Allow of
+ deny ->
+ {error, ?ERR_FORBIDDEN};
+ allow ->
+ announce_commands(From, To, Request)
+ end).
+
+announce_commands(_Acc, From, To,
+ #adhoc_request{
+ node = "announce/all-hosts/online"} = Request) ->
+ Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Allow = acl:match_rule(global, Access, From),
+ ?COMMANDS_RESULT(Allow, From, To, Request);
+
+announce_commands(Acc, From, #jid{lserver = LServer} = To,
+ #adhoc_request{node = Node} = Request) ->
+ Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
+ Allow = acl:match_rule(LServer, Access, From),
+ case Node of
+ "announce/all" ->
+ ?COMMANDS_RESULT(Allow, From, To, Request);
+ "announce/online" ->
+ ?COMMANDS_RESULT(Allow, From, To, Request);
+ "announce/motd" ->
+ ?COMMANDS_RESULT(Allow, From, To, Request);
+ "announce/motd/delete" ->
+ ?COMMANDS_RESULT(Allow, From, To, Request);
+ "announce/motd/update" ->
+ ?COMMANDS_RESULT(Allow, From, To, Request);
+ _ ->
+ Acc
+ 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
+ adhoc:produce_response(
+ Request,
+ #adhoc_response{status = executing,
+ elements = [generate_adhoc_form(Lang, Node)]});
+ 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.
+
+generate_adhoc_form(Lang, Node) ->
+ {xmlelement, "x",
+ [{"xmlns", ?NS_XDATA},
+ {"type", "form"}],
+ [{xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}]
+ ++
+ if Node == "announce/motd/delete" ->
+ [{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")}],
+ []},
+ {xmlelement, "field",
+ [{"var", "body"},
+ {"type", "text-multi"},
+ {"label", translate:translate(Lang, "Message body")}],
+ []}]
+ 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
+ {"announce/motd/delete", _} ->
+ if Confirm ->
+ Proc ! {announce_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
+ {"announce/all", _} ->
+ Proc ! {announce_all, From, To, Packet},
+ adhoc:produce_response(Response);
+ {"announce/online", _} ->
+ Proc ! {announce_online, From, To, Packet},
+ adhoc:produce_response(Response);
+ {"announce/all-hosts/online", _} ->
+ Proc ! {announce_all_hosts_online, From, To, Packet},
+ adhoc:produce_response(Response);
+ {"announce/motd", _} ->
+ Proc ! {announce_motd, From, To, Packet},
+ adhoc:produce_response(Response);
+ {"announce/motd/update", _} ->
+ Proc ! {announce_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, "announce/all") ->
+ translate:translate(Lang, "Send announcement to all users");
+get_title(Lang, "announce/online") ->
+ translate:translate(Lang, "Send announcement to all online users");
+get_title(Lang, "announce/all-hosts/online") ->
+ translate:translate(Lang, "Send announcement to all online users on all hosts");
+get_title(Lang, "announce/motd") ->
+ translate:translate(Lang, "Set message of the day and send to online users");
+get_title(Lang, "announce/motd/update") ->
+ translate:translate(Lang, "Update message of the day (don't send)");
+get_title(Lang, "announce/motd/delete") ->
+ translate:translate(Lang, "Delete message of the day").
+
+%-------------------------------------------------------------------------
+
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_NOT_ALLOWED),
+ Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
Local = jlib:make_jid("", To#jid.server, ""),
@@ -126,7 +553,7 @@ announce_online(From, To, Packet) ->
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_NOT_ALLOWED),
+ Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:get_vh_session_list(Host),
@@ -138,7 +565,7 @@ 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_NOT_ALLOWED),
+ Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
@@ -159,7 +586,7 @@ announce_motd(From, To, Packet) ->
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_NOT_ALLOWED),
+ Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(To#jid.lserver, Packet),
@@ -179,7 +606,7 @@ announce_motd_update(From, To, Packet) ->
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_NOT_ALLOWED),
+ Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(Host, Packet)
@@ -197,7 +624,7 @@ announce_motd_delete(From, To, Packet) ->
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_NOT_ALLOWED),
+ Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_delete(Host)
@@ -237,6 +664,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
ok
end.
+%-------------------------------------------------------------------------
update_tables() ->
update_motd_table(),
@@ -326,3 +754,4 @@ update_motd_users_table() ->
?INFO_MSG("Recreating motd_users table", []),
mnesia:transform_table(motd_users, ignore, Fields)
end.
+