]> granicus.if.org Git - ejabberd/commitdiff
* src/mod_register.erl: update ejabberd commands to support XEP-0133 (initial patch...
authorMickaël Rémond <mickael.remond@process-one.net>
Tue, 4 Sep 2007 07:55:41 +0000 (07:55 +0000)
committerMickaël Rémond <mickael.remond@process-one.net>
Tue, 4 Sep 2007 07:55:41 +0000 (07:55 +0000)
* src/mod_configure.erl: Likewise.
* src/mod_announce.erl: Likewise.
* src/jlib.hrl: Likewise.
* src/ejabberd.cfg.example: Likewise.
* doc/guide.tex: Likewise.

SVN Revision: 918

doc/guide.html
doc/guide.tex
src/ejabberd.cfg.example
src/jlib.hrl
src/mod_announce.erl
src/mod_configure.erl
src/mod_register.erl

index d49cfc51983f7a68a4f6e69f4fd5fcb97dfc6623..6b6a2f3870cce9f2bc6fd9fb97c074c9063df63c 100644 (file)
@@ -2034,6 +2034,11 @@ Register a new account on the server.
 rules to restrict registration. If a rule returns `deny' on the requested
 user name, registration for that user name is dennied. (there are no
 restrictions by default).
+</DD><DT CLASS="dt-description"><B><TT>welcome_message</TT></B></DT><DD CLASS="dd-description"> Set a welcome message that
+is sent to each newly registered account. The first string is the subject, and 
+the second string is the message body.
+</DD><DT CLASS="dt-description"><B><TT>registration_watchers</TT></B></DT><DD CLASS="dd-description"> This option defines a 
+list of JIDs which will be notified each time a new account is registered.
 </DD><DT CLASS="dt-description"><B><TT>iqdisc</TT></B></DT><DD CLASS="dd-description"> This specifies 
 the processing discipline for In-Band Registration (<TT>jabber:iq:register</TT>) IQ queries (see section&#XA0;<A HREF="#modiqdiscoption">3.3.2</A>).
 </DD></DL><P>Examples:
@@ -2066,6 +2071,16 @@ example all In-Band Registration functionality is disabled:
 %    {mod_register, [{access, register}]},
     ...
    ]}.
+</PRE></LI><LI CLASS="li-itemize">Define the welcome message and three registration watchers:
+<PRE CLASS="verbatim">  {modules,
+   [
+    ...
+     {mod_register, [
+        {welcome_message, {"Welcome!", "Welcome to this Jabber server. For information about Jabber visit http://www.jabber.org"}},
+        {registration_watchers, ["admin1@example.org", "admin2@example.org", "boss@example.net"]}
+     ]},
+    ...
+   ]}.
 </PRE></LI></UL><!--TOC subsection <TT>mod_roster</TT>-->
 <H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc45">3.3.16</A>&#XA0;&#XA0;<TT>mod_roster</TT></H3><!--SEC END --><P>
 <A NAME="modroster"></A>
index d04bc570d3f2f392c9268d896dde0e772fa308d7..d59db3b077c52ec526bbd7e9436b99430d2794c2 100644 (file)
@@ -2490,6 +2490,11 @@ Options:
   rules to restrict registration. If a rule returns `deny' on the requested
   user name, registration for that user name is dennied. (there are no
   restrictions by default).
+\titem{welcome\_message} \ind{options!welcomem}Set a welcome message that
+  is sent to each newly registered account. The first string is the subject, and 
+  the second string is the message body.
+\titem{registration\_watchers} \ind{options!rwatchers}This option defines a 
+  list of JIDs which will be notified each time a new account is registered.
 \iqdiscitem{In-Band Registration (\ns{jabber:iq:register})}
 \end{description}
 
@@ -2527,6 +2532,18 @@ Examples:
     ...
    ]}.
 \end{verbatim}
+\item Define the welcome message and three registration watchers:
+  \begin{verbatim}
+  {modules,
+   [
+    ...
+     {mod_register, [
+        {welcome_message, {"Welcome!", "Welcome to this Jabber server. For information about Jabber visit http://www.jabber.org"}},
+        {registration_watchers, ["admin1@example.org", "admin2@example.org", "boss@example.net"]}
+     ]},
+    ...
+   ]}.
+\end{verbatim}
 \end{itemize}
 
 \subsection{\modroster{}}
index 009643e0ef2f75ef96e5af6de7022ad591e96d4f..f8609ca199c528c94802fe904b9a8df9b09eedbe 100644 (file)
 % in-band registration
 {access, register, [{allow, all}]}.
 
-% After successful registration user will get message with following subject
-% and body:
-{welcome_message,
- {"Welcome!",
-  "Welcome to Jabber Service.  "
-  "For information about Jabber visit http://jabber.org"}}.
-% Replace them with 'none' if you don't want to send such message:
-%{welcome_message, none}.
-
-% List of people who will get notifications about registered users
-%{registration_watchers, ["admin1@localhost",
-%                         "admin2@localhost"]}.
-
 % Debug: 
 % watchdog admins receive live notifications on ejabberd process consuming too
 % much memory
 % Used modules:
 {modules,
  [
-  {mod_register,   [{access, register}]},
+  {mod_register,   [
+       %% After successful registration user will get message with following subject and body:
+       %{welcome_message, {"Welcome!", "Welcome to this Jabber server."}},
+       %% List of people who will get notifications when users register
+       %{registration_watchers, ["admin1@example.org", "admin2@example.org"]},
+       {access, register}
+  ]},
   {mod_roster,     []},
   {mod_privacy,    []},
   {mod_adhoc,      []},
index a77cb07f823486bca437ca3c9421ba9908b7777f..8034698eb36f44e83261e2ca13e435b8f5b19f6d 100644 (file)
@@ -35,6 +35,7 @@
 -define(NS_PUBSUB_NMI,   "http://jabber.org/protocol/pubsub#node-meta-info").
 -define(NS_COMMANDS,     "http://jabber.org/protocol/commands").
 -define(NS_BYTESTREAMS,  "http://jabber.org/protocol/bytestreams").
+-define(NS_ADMIN,        "http://jabber.org/protocol/admin").
 
 -define(NS_EJABBERD_CONFIG, "ejabberd:config").
 
index deccafcb045baf48166999b590f340e5413bbb2c..8dbf469c98eb8961c814d8204272d1d623992bc8 100644 (file)
@@ -6,6 +6,8 @@
 %%% Id      : $Id$
 %%%----------------------------------------------------------------------
 
+%%% Implements a small subset of XEP-0133: Service Administration Version 1.1 (2005-08-19)
+
 -module(mod_announce).
 -author('alexey@sevcom.net').
 
@@ -31,6 +33,9 @@
 
 -define(PROCNAME, ejabberd_announce).
 
+-define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
+tokenize(Node) -> string:tokens(Node, "/#").
+
 start(Host, _Opts) ->
     mnesia:create_table(motd, [{disc_copies, [node()]},
                               {attributes, record_info(fields, motd)}]),
@@ -102,7 +107,7 @@ stop(Host) ->
     exit(whereis(Proc), stop),
     {wait, Proc}.
 
-% Announcing via messages to a custom resource
+%% Announcing via messages to a custom resource
 announce(From, To, Packet) ->
     case To of
        #jid{luser = "", lresource = Res} ->
@@ -146,8 +151,8 @@ announce(From, To, Packet) ->
            ok
     end.
 
-%-------------------------------------------------------------------------
-% Announcing via ad-hoc commands
+%%-------------------------------------------------------------------------
+%% Announcing via ad-hoc commands
 -define(INFO_COMMAND(Lang, Node),
         [{xmlelement, "identity",
          [{"category", "automation"},
@@ -155,40 +160,41 @@ announce(From, To, Packet) ->
           {"name", get_title(Lang, Node)}], []}]).
 
 disco_identity(Acc, _From, _To, Node, Lang) ->
-    case Node of
-       "announce/all" ->
+    LNode = tokenize(Node),
+    case LNode of
+       ?NS_ADMINL("announce-all") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/all-hosts/all" ->
+       ?NS_ADMINL("announce-all-allhosts") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/online" ->
+       ?NS_ADMINL("announce") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/all-hosts/online" ->
+       ?NS_ADMINL("announce-online-allhosts") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/motd" ->
+       ?NS_ADMINL("set-motd") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/all-hosts/motd" ->
+       ?NS_ADMINL("motd-allhosts") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/motd/delete" ->
+       ?NS_ADMINL("delete-motd") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/all-hosts/motd/delete" ->
+       ?NS_ADMINL("delete-motd-allhosts") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/motd/update" ->
+       ?NS_ADMINL("edit-motd") ->
            ?INFO_COMMAND(Lang, Node);
-       "announce/all-hosts/motd/update" ->
+       ?NS_ADMINL("edit-motd-allhosts") ->
            ?INFO_COMMAND(Lang, Node);
        _ ->
            Acc
     end.
 
-%-------------------------------------------------------------------------
+%%-------------------------------------------------------------------------
 
 -define(INFO_RESULT(Allow, Feats),
-    case Allow of
-       deny ->
-           {error, ?ERR_FORBIDDEN};
-       allow ->
-           {result, Feats}
-    end).
+       case Allow of
+           deny ->
+               {error, ?ERR_FORBIDDEN};
+           allow ->
+               {result, Feats}
+       end).
 
 disco_features(Acc, From, #jid{lserver = LServer} = _To,
               "announce", _Lang) ->
@@ -209,11 +215,11 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
 
 disco_features(Acc, From, #jid{lserver = LServer} = _To,
               Node, _Lang)
-              when (Node == "announce/all-hosts/online")
-              or (Node == "announce/all-hosts/all")
-              or (Node == "announce/all-hosts/motd")
-              or (Node == "announce/all-hosts/motd/update")
-              or (Node == "announce/all-hosts/motd/delete") ->
+  when (Node == [?NS_ADMIN | "#announce-online-allhosts"])
+or (Node == [?NS_ADMIN | "#announce-all-allhosts"])
+or (Node == [?NS_ADMIN | "#motd-allhosts"])
+or (Node == [?NS_ADMIN | "#edit-motd-allhosts"])
+or (Node == [?NS_ADMIN | "#delete-motd-allhosts"]) ->
     case gen_mod:is_loaded(LServer, mod_adhoc) of
        false ->
            Acc;
@@ -232,22 +238,22 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
            Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
            Allow = acl:match_rule(LServer, Access, From),
            case Node of
-               "announce/all" ->
+               ?NS_ADMIN ++ "#announce-all" ->
                    ?INFO_RESULT(Allow, [?NS_COMMANDS]);
-               "announce/online" ->
+               ?NS_ADMIN ++ "#announce" ->
                    ?INFO_RESULT(Allow, [?NS_COMMANDS]);
-               "announce/motd" ->
+               ?NS_ADMIN ++ "#set-motd" ->
                    ?INFO_RESULT(Allow, [?NS_COMMANDS]);
-               "announce/motd/delete" ->
+               ?NS_ADMIN ++ "#delete-motd" ->
                    ?INFO_RESULT(Allow, [?NS_COMMANDS]);
-               "announce/motd/update" ->
+               ?NS_ADMIN ++ "#edit-motd" ->
                    ?INFO_RESULT(Allow, [?NS_COMMANDS]);
                _ ->
                    Acc
            end
     end.
 
-%-------------------------------------------------------------------------
+%%-------------------------------------------------------------------------
 
 -define(NODE_TO_ITEM(Lang, Server, Node),
        {xmlelement, "item",
@@ -257,12 +263,12 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
         []}).
 
 -define(ITEMS_RESULT(Allow, Items),
-    case Allow of
-       deny ->
-           {error, ?ERR_FORBIDDEN};
-       allow ->
-           {result, Items}
-    end).
+       case Allow of
+           deny ->
+               {error, ?ERR_FORBIDDEN};
+           allow ->
+               {result, Items}
+       end).
 
 disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To,
            "", Lang) ->
@@ -295,11 +301,11 @@ disco_items(Acc, From, #jid{lserver = LServer} = To, "announce", Lang) ->
     end;
 
 disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang)
-              when (Node == "announce/all-hosts/online")
-              or (Node == "announce/all-hosts/all")
-              or (Node == "announce/all-hosts/motd")
-              or (Node == "announce/all-hosts/motd/update")
-              or (Node == "announce/all-hosts/motd/delete") ->
+  when (Node == [?NS_ADMIN | "#announce-online-allhosts"])
+or (Node == [?NS_ADMIN | "#announce-all-allhosts"])
+or (Node == [?NS_ADMIN | "#motd-allhosts"])
+or (Node == [?NS_ADMIN | "#edit-motd-allhosts"])
+or (Node == [?NS_ADMIN | "#delete-motd-allhosts"]) ->
     case gen_mod:is_loaded(LServer, mod_adhoc) of
        false ->
            Acc;
@@ -317,43 +323,43 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
            Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
            Allow = acl:match_rule(LServer, Access, From),
            case Node of
-               "announce/all" ->
+               ?NS_ADMIN ++ "#announce-all" ->
                    ?ITEMS_RESULT(Allow, []);
-               "announce/online" ->
+               ?NS_ADMIN ++ "#announce" ->
                    ?ITEMS_RESULT(Allow, []);
-               "announce/motd" ->
+               ?NS_ADMIN ++ "#set-motd" ->
                    ?ITEMS_RESULT(Allow, []);
-               "announce/motd/delete" ->
+               ?NS_ADMIN ++ "#delete-motd" ->
                    ?ITEMS_RESULT(Allow, []);
-               "announce/motd/update" ->
+               ?NS_ADMIN ++ "#edit-motd" ->
                    ?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")];
+                    [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-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, "announce/all-hosts/all"),
-                     ?NODE_TO_ITEM(Lang, Server, "announce/all-hosts/online"),
-                     ?NODE_TO_ITEM(Lang, Server, "announce/all-hosts/motd"),
-                     ?NODE_TO_ITEM(Lang, Server, "announce/all-hosts/motd/update"),
-                     ?NODE_TO_ITEM(Lang, Server, "announce/all-hosts/motd/delete")];
+                    [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all-allhosts"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-online-allhosts"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#motd-allhosts"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd-allhosts"),
+                     ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd-allhosts")];
                 deny ->
                     []
             end,
@@ -368,48 +374,55 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang)
            {result, Items ++ Nodes1 ++ Nodes2}
     end.
 
-%-------------------------------------------------------------------------
+%%-------------------------------------------------------------------------
 
--define(COMMANDS_RESULT(Allow, From, To, Request),
+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 = Node} = Request)
-              when (Node == "announce/all-hosts/online")
-              or (Node == "announce/all-hosts/all")
-              or (Node == "announce/all-hosts/motd")
-              or (Node == "announce/all-hosts/motd/update")
-              or (Node == "announce/all-hosts/motd/delete") ->
-    Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
-    Allow = acl:match_rule(global, Access, From),
-    ?COMMANDS_RESULT(Allow, From, To, Request);
+    end.
+
 
 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
+                 #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-online-allhosts") -> F();
+           ?NS_ADMINL("announce-all-allhosts") -> F();
+           ?NS_ADMINL("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-all") ->
+                       commands_result(Allow, From, To, Request);
+                   ?NS_ADMINL("announce") ->
+                       commands_result(Allow, From, To, Request);
+                   ?NS_ADMINL("set-motd") ->
+                       commands_result(Allow, From, To, Request);
+                   ?NS_ADMINL("delete-motd") ->
+                       commands_result(Allow, From, To, Request);
+                   ?NS_ADMINL("edit-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,
@@ -428,10 +441,11 @@ announce_commands(From, To,
                                   #adhoc_response{status = canceled});
        XData == false, ActionIsExecute ->
            %% User requests form
+           Elements = generate_adhoc_form(Lang, Node),
            adhoc:produce_response(
              Request,
              #adhoc_response{status = executing,
-                             elements = [generate_adhoc_form(Lang, Node)]});
+                             elements = [Elements]});
        XData /= false, ActionIsExecute ->
            %% User returns form.
            case jlib:parse_xdata_submit(XData) of
@@ -444,33 +458,41 @@ announce_commands(From, To,
            {error, ?ERR_BAD_REQUEST}
     end.
 
+-define(TVFIELD(Type, Var, Val),
+       {xmlelement, "field", [{"type", Type},
+                              {"var", Var}],
+        [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+-define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)).
+
 generate_adhoc_form(Lang, Node) ->
+    LNode = tokenize(Node),
     {xmlelement, "x",
      [{"xmlns", ?NS_XDATA},
       {"type", "form"}],
-     [{xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}]
+     [?HFIELD(),
+      {xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}]
      ++
-      if (Node == "announce/motd/delete")
-         or (Node == "announce/all-hosts/motd/delete") ->
-             [{xmlelement, "field",
+     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")}],
-               []},
-              {xmlelement, "field",
-               [{"var", "body"},
-                {"type", "text-multi"},
-                {"label", translate:translate(Lang, "Message body")}],
-               []}]
-      end}.
+                [],
+                [{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([]) ->
     [];
@@ -529,51 +551,51 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To,
 
     Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
     case {Node, Body} of
-       {"announce/motd/delete", _} ->
+       {?NS_ADMIN ++ "#delete-motd", _} ->
            if  Confirm ->
                    Proc ! {announce_motd_delete, From, To, Packet},
                    adhoc:produce_response(Response);
-              true ->
+               true ->
                    adhoc:produce_response(Response)
            end;
-       {"announce/all-hosts/motd/delete", _} ->
+       {?NS_ADMIN ++ "#delete-motd-allhosts", _} ->
            if  Confirm ->
                    Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
                    adhoc:produce_response(Response);
-              true ->
+               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")};
+                      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", _} ->
+       {?NS_ADMIN ++ "#announce-all", _} ->
            Proc ! {announce_all, From, To, Packet},
            adhoc:produce_response(Response);
-       {"announce/all-hosts/all", _} ->            
+       {?NS_ADMIN ++ "#announce-all-allhosts", _} ->       
            Proc ! {announce_all_hosts_all, From, To, Packet},
            adhoc:produce_response(Response);
-       {"announce/online", _} ->
+       {?NS_ADMIN ++ "#announce", _} ->
            Proc ! {announce_online, From, To, Packet},
            adhoc:produce_response(Response);
-       {"announce/all-hosts/online", _} ->         
+       {?NS_ADMIN ++ "#announce-online-allhosts", _} ->            
            Proc ! {announce_all_hosts_online, From, To, Packet},
            adhoc:produce_response(Response);
-       {"announce/motd", _} ->
+       {?NS_ADMIN ++ "#set-motd", _} ->
            Proc ! {announce_motd, From, To, Packet},
            adhoc:produce_response(Response);
-       {"announce/all-hosts/motd", _} ->           
+       {?NS_ADMIN ++ "#motd-allhosts", _} ->       
            Proc ! {announce_all_hosts_motd, From, To, Packet},
            adhoc:produce_response(Response);
-       {"announce/motd/update", _} ->
+       {?NS_ADMIN ++ "#edit-motd", _} ->
            Proc ! {announce_motd_update, From, To, Packet},
            adhoc:produce_response(Response);
-       {"announce/all-hosts/motd/update", _} ->            
+       {?NS_ADMIN ++ "#edit-motd-allhosts", _} ->          
            Proc ! {announce_all_hosts_motd_update, From, To, Packet},
            adhoc:produce_response(Response);
        _ ->
@@ -584,28 +606,28 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To,
 
 get_title(Lang, "announce") ->
     translate:translate(Lang, "Announcements");
-get_title(Lang, "announce/all") ->
+get_title(Lang, ?NS_ADMIN ++ "#announce-all") ->
     translate:translate(Lang, "Send announcement to all users");
-get_title(Lang, "announce/all-hosts/all") ->
+get_title(Lang, ?NS_ADMIN ++ "#announce-all-allhosts") ->
     translate:translate(Lang, "Send announcement to all users on all hosts");
-get_title(Lang, "announce/online") ->
+get_title(Lang, ?NS_ADMIN ++ "#announce") ->
     translate:translate(Lang, "Send announcement to all online users");
-get_title(Lang, "announce/all-hosts/online") ->
+get_title(Lang, ?NS_ADMIN ++ "#announce-online-allhosts") ->
     translate:translate(Lang, "Send announcement to all online users on all hosts");
-get_title(Lang, "announce/motd") ->
+get_title(Lang, ?NS_ADMIN ++ "#set-motd") ->
     translate:translate(Lang, "Set message of the day and send to online users");
-get_title(Lang, "announce/all-hosts/motd") ->
+get_title(Lang, ?NS_ADMIN ++ "#motd-allhosts") ->
     translate:translate(Lang, "Set message of the day on all hosts and send to online users");
-get_title(Lang, "announce/motd/update") ->
+get_title(Lang, ?NS_ADMIN ++ "#edit-motd") ->
     translate:translate(Lang, "Update message of the day (don't send)");
-get_title(Lang, "announce/all-hosts/motd/update") ->
+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, "announce/motd/delete") ->
+get_title(Lang, ?NS_ADMIN ++ "#delete-motd") ->
     translate:translate(Lang, "Delete message of the day");
-get_title(Lang, "announce/all-hosts/motd/delete") ->
+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,
@@ -617,10 +639,10 @@ announce_all(From, To, Packet) ->
        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))
+             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) ->
@@ -632,10 +654,10 @@ announce_all_hosts_all(From, To, Packet) ->
        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())
+             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) ->
@@ -679,7 +701,7 @@ announce_motd(From, To, Packet) ->
            Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
            ejabberd_router:route(To, From, Err);
        allow ->
-               announce_motd(Host, Packet)
+           announce_motd(Host, Packet)
     end.
 
 announce_all_hosts_motd(From, To, Packet) ->
@@ -790,7 +812,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
            ok
     end.
 
-%-------------------------------------------------------------------------
+%%-------------------------------------------------------------------------
 
 update_tables() ->
     update_motd_table(),
index e93b658ee72e607f1010342afbb3f99a7c3e22da..a09cea3c894cf239e12f689c44d79bcd1d3599a4 100644 (file)
@@ -6,6 +6,8 @@
 %%% Id      : $Id$
 %%%----------------------------------------------------------------------
 
+%%% Implements most of XEP-0133: Service Administration Version 1.1 (2005-08-19)
+
 -module(mod_configure).
 -author('alexey@sevcom.net').
 -vsn('$Revision$ ').
 -include("jlib.hrl").
 -include("adhoc.hrl").
 
+-define(T(Lang, Text), translate:translate(Lang, Text)).
+
+%% Copied from ejabberd_sm.erl
+-record(session, {sid, usr, us, priority, info}).
 
 start(Host, _Opts) ->
     ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_items, 50),
@@ -63,11 +69,27 @@ stop(Host) ->
        [{xmlelement, "identity",
          [{"category", Category},
           {"type", Type},
-          {"name", translate:translate(Lang, Name)}], []}]).
+          {"name", ?T(Lang, Name)}], []}]).
 
 -define(INFO_COMMAND(Name, Lang),
        ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
 
+-define(NODEJID(To, Name, Node),
+       {xmlelement, "item",
+        [{"jid", jlib:jid_to_string(To)},
+         {"name", ?T(Lang, Name)},
+         {"node", Node}], []}).
+
+-define(NODE(Name, Node),
+       {xmlelement, "item",
+        [{"jid", Server},
+         {"name", ?T(Lang, Name)},
+         {"node", Node}], []}).
+
+-define(NS_ADMINX(Sub), ?NS_ADMIN++"#"++Sub).
+-define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
+tokenize(Node) -> string:tokens(Node, "/#").
+
 get_sm_identity(Acc, _From, _To, Node, Lang) ->
     case Node of
        "config" ->
@@ -77,7 +99,7 @@ get_sm_identity(Acc, _From, _To, Node, Lang) ->
     end.
 
 get_local_identity(Acc, _From, _To, Node, Lang) ->
-    LNode = string:tokens(Node, "/"),
+    LNode = tokenize(Node),
     case LNode of
        ["running nodes", ENode] ->
            ?INFO_IDENTITY("ejabberd", "node", ENode, Lang);
@@ -97,6 +119,28 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
            ?INFO_COMMAND("Import File", Lang);
        ["running nodes", _ENode, "import", "dir"] ->
            ?INFO_COMMAND("Import Directory", Lang);
+       ["running nodes", _ENode, "restart"] ->
+           ?INFO_COMMAND("Restart Service", Lang);
+       ["running nodes", _ENode, "shutdown"] ->
+           ?INFO_COMMAND("Shut Down Service", Lang);
+       ?NS_ADMINL("add-user") ->
+           ?INFO_COMMAND("Add User", Lang);
+       ?NS_ADMINL("delete-user") ->
+           ?INFO_COMMAND("Delete User", Lang);
+       ?NS_ADMINL("end-user-session") ->
+           ?INFO_COMMAND("End User Session", Lang);
+       ?NS_ADMINL("get-user-password") ->
+           ?INFO_COMMAND("Get User Password", Lang);
+       ?NS_ADMINL("change-user-password") ->
+           ?INFO_COMMAND("Change User Password", Lang);
+       ?NS_ADMINL("get-user-lastlogin") ->
+           ?INFO_COMMAND("Get User Last Login Time", Lang);
+       ?NS_ADMINL("user-stats") ->
+           ?INFO_COMMAND("Get User Statistics", Lang);
+       ?NS_ADMINL("get-registered-users-num") ->
+           ?INFO_COMMAND("Get Number of Registered Users", Lang);
+       ?NS_ADMINL("get-online-users-num") ->
+           ?INFO_COMMAND("Get Number of Online Users", Lang);
        ["config", "hostname"] ->
            ?INFO_COMMAND("Host Name", Lang);
        ["config", "acls"] ->
@@ -110,12 +154,12 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
 %%%-----------------------------------------------------------------------
 
 -define(INFO_RESULT(Allow, Feats),
-    case Allow of
-       deny ->
-           {error, ?ERR_FORBIDDEN};
-       allow ->
-           {result, Feats}
-    end).
+       case Allow of
+           deny ->
+               {error, ?ERR_FORBIDDEN};
+           allow ->
+               {result, Feats}
+       end).
 
 get_sm_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
     case gen_mod:is_loaded(LServer, mod_adhoc) of
@@ -136,11 +180,13 @@ get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
        false ->
            Acc;
        _ ->
-           LNode = string:tokens(Node, "/"),
+           LNode = tokenize(Node),
            Allow = acl:match_rule(LServer, configure, From),
            case LNode of
                ["config"] ->
                    ?INFO_RESULT(Allow, []);
+               ["user"] ->
+                   ?INFO_RESULT(Allow, []);
                ["online users"] ->
                    ?INFO_RESULT(Allow, []);
                ["all users"] ->
@@ -169,8 +215,14 @@ get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
                    ?INFO_RESULT(Allow, []);
                ["running nodes", _ENode, "import", _] ->
                    ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+               ["running nodes", _ENode, "restart"] ->
+                   ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+               ["running nodes", _ENode, "shutdown"] ->
+                   ?INFO_RESULT(Allow, [?NS_COMMANDS]);
                ["config", _] ->
                    ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+               ["http:" | _] ->
+                   ?INFO_RESULT(Allow, [?NS_COMMANDS]);
                _ ->
                    Acc
            end
@@ -187,7 +239,7 @@ adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, Lang) ->
                    end,
            Nodes = [{xmlelement, "item",
                      [{"jid", jlib:jid_to_string(To)},
-                      {"name", translate:translate(Lang, "Configuration")},
+                      {"name", ?T(Lang, "Configuration")},
                       {"node", "config"}], []}],
            {result, Items ++ Nodes};
        _ ->
@@ -209,10 +261,8 @@ get_sm_items(Acc, From,
                    end,
            case {acl:match_rule(LServer, configure, From), Node} of
                {allow, ""} ->
-                   Nodes = [{xmlelement, "item",
-                             [{"jid", jlib:jid_to_string(To)},
-                              {"name", translate:translate(Lang, "Configuration")},
-                              {"node", "config"}], []}],
+                   Nodes = [?NODEJID(To, "Configuration", "config"),
+                            ?NODEJID(To, "User Management", "user")],
                    {result, Items ++ Nodes ++ get_user_resources(User, Server)};
                {allow, "config"} ->
                    {result, []};
@@ -245,14 +295,14 @@ adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
            Nodes = recursively_get_local_items(LServer, "", Server, Lang),
            Nodes1 = lists:filter(
                       fun(N) ->
-                               Nd = xml:get_tag_attr_s("node", N),
-                               F = get_local_features([], From, To, Nd, Lang),
-                               case F of
-                                   {result, [?NS_COMMANDS]} ->
-                                       true;
-                                   _ ->
-                                       false
-                               end
+                              Nd = xml:get_tag_attr_s("node", N),
+                              F = get_local_features([], From, To, Nd, Lang),
+                              case F of
+                                  {result, [?NS_COMMANDS]} ->
+                                      true;
+                                  _ ->
+                                      false
+                              end
                       end, Nodes),
            {result, Items ++ Nodes1};
        _ ->
@@ -266,7 +316,7 @@ recursively_get_local_items(_LServer, "all users", _Server, _Lang) ->
     [];
 
 recursively_get_local_items(LServer, Node, Server, Lang) ->
-    LNode = string:tokens(Node, "/"),
+    LNode = tokenize(Node),
     Items = case get_local_items(LServer, LNode, Server, Lang) of
                {result, Res} ->
                    Res;
@@ -274,34 +324,34 @@ recursively_get_local_items(LServer, Node, Server, Lang) ->
                    []
            end,
     Nodes = lists:flatten(
-      lists:map(
-       fun(N) ->
-               S = xml:get_tag_attr_s("jid", N),
-               Nd = xml:get_tag_attr_s("node", N),
-               if (S /= Server) or (Nd == "") ->
-                   [];
-               true ->
-                   [N, recursively_get_local_items(
-                         LServer, Nd, Server, Lang)]
-               end
-       end, Items)),
+             lists:map(
+               fun(N) ->
+                       S = xml:get_tag_attr_s("jid", N),
+                       Nd = xml:get_tag_attr_s("node", N),
+                       if (S /= Server) or (Nd == "") ->
+                               [];
+                          true ->
+                               [N, recursively_get_local_items(
+                                     LServer, Nd, Server, Lang)]
+                       end
+               end, Items)),
     Nodes.
 
 %%%-----------------------------------------------------------------------
 
 -define(ITEMS_RESULT(Allow, LNode, Fallback),
-    case Allow of
-       deny ->
-           Fallback;
-       allow ->
-           case get_local_items(LServer, LNode,
-                                jlib:jid_to_string(To), Lang) of
-               {result, Res} ->
-                   {result, Res};
-               {error, Error} ->
-                   {error, Error}
-           end
-    end).
+       case Allow of
+           deny ->
+               Fallback;
+           allow ->
+               case get_local_items(LServer, LNode,
+                                    jlib:jid_to_string(To), Lang) of
+                   {result, Res} ->
+                       {result, Res};
+                   {error, Error} ->
+                       {error, Error}
+               end
+       end).
 
 get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
     case gen_mod:is_loaded(LServer, mod_adhoc) of
@@ -332,11 +382,13 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
        false ->
            Acc;
        _ ->
-           LNode = string:tokens(Node, "/"),
+           LNode = tokenize(Node),
            Allow = acl:match_rule(LServer, configure, From),
            case LNode of
                ["config"] ->
                    ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+               ["user"] ->
+                   ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
                ["online users"] ->
                    ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
                ["all users"] ->
@@ -365,8 +417,14 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
                    ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
                ["running nodes", _ENode, "import", _] ->
                    ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+               ["running nodes", _ENode, "restart"] ->
+                   ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+               ["running nodes", _ENode, "shutdown"] ->
+                   ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
                ["config", _] ->
                    ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+               ?NS_ADMINL(_) ->
+                   ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
                _ ->
                    Acc
            end
@@ -374,15 +432,10 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
 
 %%%-----------------------------------------------------------------------
 
--define(NODE(Name, Node),
-       {xmlelement, "item",
-        [{"jid", Server},
-         {"name", translate:translate(Lang, Name)},
-         {"node", Node}], []}).
-
 get_local_items(_Host, [], Server, Lang) ->
     {result,
      [?NODE("Configuration",            "config"),
+      ?NODE("User Management",          "user"),
       ?NODE("Online Users",             "online users"),
       ?NODE("All Users",                "all users"),
       ?NODE("Outgoing s2s Connections", "outgoing s2s"),
@@ -395,13 +448,27 @@ get_local_items(_Host, ["config"], Server, Lang) ->
      [?NODE("Host Name",            "config/hostname"),
       ?NODE("Access Control Lists", "config/acls"),
       ?NODE("Access Rules",         "config/access")
-      % Too expensive on big hosts
-      %?NODE("Remove Users",         "config/remusers")
      ]};
 
 get_local_items(_Host, ["config", _], _Server, _Lang) ->
     {result, []};
 
+get_local_items(_Host, ["user"], Server, Lang) ->
+    {result,
+     [?NODE("Add User",            ?NS_ADMINX("add-user")),
+      ?NODE("Delete User",         ?NS_ADMINX("delete-user")),
+      ?NODE("End User Session",    ?NS_ADMINX("end-user-session")),
+      ?NODE("Get User Password",   ?NS_ADMINX("get-user-password")),
+      ?NODE("Change User Password",?NS_ADMINX("change-user-password")),
+      ?NODE("Get User Last Login Time",   ?NS_ADMINX("get-user-lastlogin")),
+      ?NODE("Get User Statistics",   ?NS_ADMINX("user-stats")),
+      ?NODE("Get Number of Registered Users",?NS_ADMINX("get-registered-users-num")),
+      ?NODE("Get Number of Online Users",?NS_ADMINX("get-online-users-num"))
+     ]};
+
+get_local_items(_Host, ["http:" | _], _Server, _Lang) ->
+    {result, []};
+
 get_local_items(Host, ["online users"], _Server, _Lang) ->
     {result, get_online_vh_users(Host)};
 
@@ -425,11 +492,11 @@ get_local_items(Host, ["all users", [$@ | Diap]], _Server, _Lang) ->
                                               {"name", U ++ "@" ++ S}], []}
                                     end, Sub)
                       end of
-               {'EXIT', _Reason} ->
-                   ?ERR_NOT_ACCEPTABLE;
-               Res ->
-                   {result, Res}
-           end
+                              {'EXIT', _Reason} ->
+                        ?ERR_NOT_ACCEPTABLE;
+                      Res ->
+                        {result, Res}
+                end
     end;
 
 get_local_items(Host, ["outgoing s2s"], _Server, Lang) ->
@@ -450,7 +517,9 @@ get_local_items(_Host, ["running nodes", ENode], Server, Lang) ->
       ?NODE("Modules", "running nodes/" ++ ENode ++ "/modules"),
       ?NODE("Backup Management", "running nodes/" ++ ENode ++ "/backup"),
       ?NODE("Import Users From jabberd 1.4 Spool Files",
-           "running nodes/" ++ ENode ++ "/import")
+           "running nodes/" ++ ENode ++ "/import"),
+      ?NODE("Restart Service", "running nodes/" ++ ENode ++ "/restart"),
+      ?NODE("Shut Down Service", "running nodes/" ++ ENode ++ "/shutdown")
      ]};
 
 get_local_items(_Host, ["running nodes", _ENode, "DB"], _Server, _Lang) ->
@@ -485,6 +554,12 @@ get_local_items(_Host, ["running nodes", ENode, "import"], Server, Lang) ->
 get_local_items(_Host, ["running nodes", _ENode, "import", _], _Server, _Lang) ->
     {result, []};
 
+get_local_items(_Host, ["running nodes", _ENode, "restart"], _Server, _Lang) ->
+    {result, []};
+
+get_local_items(_Host, ["running nodes", _ENode, "shutdown"], _Server, _Lang) ->
+    {result, []};
+
 get_local_items(_Host, _, _Server, _Lang) ->
     {error, ?ERR_ITEM_NOT_FOUND}.
 
@@ -556,7 +631,7 @@ get_outgoing_s2s(Host, Lang) ->
                        {"name",
                         lists:flatten(
                           io_lib:format(
-                            translate:translate(Lang, "To ~s"), [T]))}],
+                            ?T(Lang, "To ~s"), [T]))}],
                       []}
              end, lists:usort(TConns))
     end.
@@ -574,7 +649,7 @@ get_outgoing_s2s(Host, Lang, To) ->
                        {"name",
                         lists:flatten(
                           io_lib:format(
-                            translate:translate(Lang, "From ~s"), [F]))}],
+                            ?T(Lang, "From ~s"), [F]))}],
                       []}
              end, lists:keysort(1, lists:filter(fun(E) ->
                                                         element(2, E) == To
@@ -616,19 +691,19 @@ get_stopped_nodes(_Lang) ->
              end, lists:sort(DBNodes))
     end.
 
-%-------------------------------------------------------------------------
+%%-------------------------------------------------------------------------
 
 -define(COMMANDS_RESULT(Allow, From, To, Request),
-    case Allow of
-       deny ->
-           {error, ?ERR_FORBIDDEN};
-       allow ->
-           adhoc_local_commands(From, To, Request)
-    end).
+       case Allow of
+           deny ->
+               {error, ?ERR_FORBIDDEN};
+           allow ->
+               adhoc_local_commands(From, To, Request)
+       end).
 
 adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
                     #adhoc_request{node = Node} = Request) ->
-    LNode = string:tokens(Node, "/"),
+    LNode = tokenize(Node),
     Allow = acl:match_rule(LServer, configure, From),
     case LNode of
        ["running nodes", _ENode, "DB"] ->
@@ -639,19 +714,25 @@ adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
            ?COMMANDS_RESULT(Allow, From, To, Request);
        ["running nodes", _ENode, "import", _] ->
            ?COMMANDS_RESULT(Allow, From, To, Request);
+       ["running nodes", _ENode, "restart"] ->
+           ?COMMANDS_RESULT(Allow, From, To, Request);
+       ["running nodes", _ENode, "shutdown"] ->
+           ?COMMANDS_RESULT(Allow, From, To, Request);
        ["config", _] ->
            ?COMMANDS_RESULT(Allow, From, To, Request);
+       ?NS_ADMINL(_) ->
+           ?COMMANDS_RESULT(Allow, From, To, Request);
        _ ->
            Acc
     end.
 
-adhoc_local_commands(_From, #jid{lserver = LServer} = _To,
+adhoc_local_commands(From, #jid{lserver = LServer} = _To,
                     #adhoc_request{lang = Lang,
                                    node = Node,
                                    sessionid = SessionID,
                                    action = Action,
                                    xdata = XData} = Request) ->
-    LNode = string:tokens(Node, "/"),
+    LNode = tokenize(Node),
     %% 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
@@ -671,6 +752,11 @@ adhoc_local_commands(_From, #jid{lserver = LServer} = _To,
                      Request,
                      #adhoc_response{status = executing,
                                      elements = Form});
+               {result, Status, Form} ->
+                   adhoc:produce_response(
+                     Request,
+                     #adhoc_response{status = Status,
+                                     elements = Form});
                {error, Error} ->
                    {error, Error}
            end;
@@ -680,13 +766,16 @@ adhoc_local_commands(_From, #jid{lserver = LServer} = _To,
                invalid ->
                    {error, ?ERR_BAD_REQUEST};
                Fields ->
-                   case set_form(LServer, LNode, Lang, Fields) of
-                       {result, _Res} ->
+                   case catch set_form(From, LServer, LNode, Lang, Fields) of
+                       {result, Res} ->
                            adhoc:produce_response(
                              #adhoc_response{lang = Lang,
                                              node = Node,
                                              sessionid = SessionID,
+                                             elements = Res,
                                              status = completed});
+                       {'EXIT', _} ->
+                           {error, ?ERR_BAD_REQUEST};
                        {error, Error} ->
                            {error, Error}
                    end
@@ -696,35 +785,47 @@ adhoc_local_commands(_From, #jid{lserver = LServer} = _To,
     end.
 
 
+-define(TVFIELD(Type, Var, Val),
+       {xmlelement, "field", [{"type", Type},
+                              {"var", Var}],
+        [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+-define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)).
+
 -define(TLFIELD(Type, Label, Var),
        {xmlelement, "field", [{"type", Type},
-                              {"label", translate:translate(Lang, Label)},
+                              {"label", ?T(Lang, Label)},
                               {"var", Var}], []}).
 
 -define(XFIELD(Type, Label, Var, Val),
        {xmlelement, "field", [{"type", Type},
-                              {"label", translate:translate(Lang, Label)},
+                              {"label", ?T(Lang, Label)},
                               {"var", Var}],
         [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
 
+-define(XMFIELD(Type, Label, Var, Vals),
+       {xmlelement, "field", [{"type", Type},
+                              {"label", ?T(Lang, Label)},
+                              {"var", Var}],
+        [{xmlelement, "value", [], [{xmlcdata,Val}]} || Val <- Vals]}).
+
 -define(TABLEFIELD(Table, Val),
        {xmlelement, "field", [{"type", "list-single"},
                               {"label", atom_to_list(Table)},
                               {"var", atom_to_list(Table)}],
         [{xmlelement, "value", [], [{xmlcdata, atom_to_list(Val)}]},
          {xmlelement, "option", [{"label",
-                                  translate:translate(Lang, "RAM copy")}],
+                                  ?T(Lang, "RAM copy")}],
           [{xmlelement, "value", [], [{xmlcdata, "ram_copies"}]}]},
          {xmlelement, "option", [{"label",
-                                  translate:translate(Lang,
-                                                      "RAM and disc copy")}],
+                                  ?T(Lang,
+                                     "RAM and disc copy")}],
           [{xmlelement, "value", [], [{xmlcdata, "disc_copies"}]}]},
          {xmlelement, "option", [{"label",
-                                  translate:translate(Lang,
-                                                      "Disc only copy")}],
+                                  ?T(Lang,
+                                     "Disc only copy")}],
           [{xmlelement, "value", [], [{xmlcdata, "disc_only_copies"}]}]},
          {xmlelement, "option", [{"label",
-                                  translate:translate(Lang, "Remote copy")}],
+                                  ?T(Lang, "Remote copy")}],
           [{xmlelement, "value", [], [{xmlcdata, "unknown"}]}]}
         ]}).
 
@@ -741,15 +842,16 @@ get_form(_Host, ["running nodes", ENode, "DB"], Lang) ->
                Tables ->
                    STables = lists:sort(Tables),
                    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-                              [{xmlelement, "title", [],
+                              [?HFIELD(),
+                               {xmlelement, "title", [],
                                 [{xmlcdata,
-                                  translate:translate(
-                                    Lang, "Database Tables Configuration at ") ++
-                                    ENode}]},
+                                  ?T(
+                                     Lang, "Database Tables Configuration at ") ++
+                                  ENode}]},
                                {xmlelement, "instructions", [],
                                 [{xmlcdata,
-                                  translate:translate(
-                                    Lang, "Choose storage type of tables")}]} |
+                                  ?T(
+                                     Lang, "Choose storage type of tables")}]} |
                                lists:map(
                                  fun(Table) ->
                                          case rpc:call(Node,
@@ -762,7 +864,7 @@ get_form(_Host, ["running nodes", ENode, "DB"], Lang) ->
                                                  ?TABLEFIELD(Table, Type)
                                          end
                                  end, STables)
-                            ]}]}
+                              ]}]}
            end
     end;
 
@@ -777,196 +879,252 @@ get_form(Host, ["running nodes", ENode, "modules", "stop"], Lang) ->
                Modules ->
                    SModules = lists:sort(Modules),
                    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-                              [{xmlelement, "title", [],
+                              [?HFIELD(),
+                               {xmlelement, "title", [],
                                 [{xmlcdata,
-                                  translate:translate(
-                                    Lang, "Stop Modules at ") ++ ENode}]},
+                                  ?T(
+                                     Lang, "Stop Modules at ") ++ ENode}]},
                                {xmlelement, "instructions", [],
                                 [{xmlcdata,
-                                  translate:translate(
-                                    Lang, "Choose modules to stop")}]} |
+                                  ?T(
+                                     Lang, "Choose modules to stop")}]} |
                                lists:map(fun(M) ->
                                                  S = atom_to_list(M),
                                                  ?XFIELD("boolean", S, S, "0")
                                          end, SModules)
-                            ]}]}
+                              ]}]}
            end
     end;
 
 get_form(_Host, ["running nodes", ENode, "modules", "start"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Start Modules at ") ++ ENode}]},
+                  ?T(
+                     Lang, "Start Modules at ") ++ ENode}]},
                {xmlelement, "instructions", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Enter list of {Module, [Options]}")}]},
-               {xmlelement, "field", [{"type", "text-multi"},
-                                      {"label",
-                                       translate:translate(
-                                         Lang, "List of modules to start")},
-                                      {"var", "modules"}],
-                [{xmlelement, "value", [], [{xmlcdata, "[]."}]}]
-               }
-            ]}]};
+                  ?T(
+                     Lang, "Enter list of {Module, [Options]}")}]},
+               ?XFIELD("text-multi", "List of modules to start", "modules", "[].")
+              ]}]};
 
 get_form(_Host, ["running nodes", ENode, "backup", "backup"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Backup to File at ") ++ ENode}]},
+                  ?T(
+                     Lang, "Backup to File at ") ++ ENode}]},
                {xmlelement, "instructions", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Enter path to backup file")}]},
-               {xmlelement, "field", [{"type", "text-single"},
-                                      {"label",
-                                       translate:translate(
-                                         Lang, "Path to File")},
-                                      {"var", "path"}],
-                [{xmlelement, "value", [], [{xmlcdata, ""}]}]
-               }
-            ]}]};
+                  ?T(
+                     Lang, "Enter path to backup file")}]},
+               ?XFIELD("text-single", "Path to File", "path", "")
+              ]}]};
 
 get_form(_Host, ["running nodes", ENode, "backup", "restore"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Restore Backup from File at ") ++ ENode}]},
+                  ?T(
+                     Lang, "Restore Backup from File at ") ++ ENode}]},
                {xmlelement, "instructions", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Enter path to backup file")}]},
-               {xmlelement, "field", [{"type", "text-single"},
-                                      {"label",
-                                       translate:translate(
-                                         Lang, "Path to File")},
-                                      {"var", "path"}],
-                [{xmlelement, "value", [], [{xmlcdata, ""}]}]
-               }
-            ]}]};
+                  ?T(
+                     Lang, "Enter path to backup file")}]},
+               ?XFIELD("text-single", "Path to File", "path", "")
+              ]}]};
 
 get_form(_Host, ["running nodes", ENode, "backup", "textfile"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Dump Backup to Text File at ") ++ ENode}]},
+                  ?T(
+                     Lang, "Dump Backup to Text File at ") ++ ENode}]},
                {xmlelement, "instructions", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Enter path to text file")}]},
-               {xmlelement, "field", [{"type", "text-single"},
-                                      {"label",
-                                       translate:translate(
-                                         Lang, "Path to File")},
-                                      {"var", "path"}],
-                [{xmlelement, "value", [], [{xmlcdata, ""}]}]
-               }
-            ]}]};
+                  ?T(
+                     Lang, "Enter path to text file")}]},
+               ?XFIELD("text-single", "Path to File", "path", "")
+              ]}]};
 
 get_form(_Host, ["running nodes", ENode, "import", "file"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Import User from File at ") ++ ENode}]},
+                  ?T(
+                     Lang, "Import User from File at ") ++ ENode}]},
                {xmlelement, "instructions", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Enter path to jabberd1.4 spool file")}]},
-               {xmlelement, "field", [{"type", "text-single"},
-                                      {"label",
-                                       translate:translate(
-                                         Lang, "Path to File")},
-                                      {"var", "path"}],
-                [{xmlelement, "value", [], [{xmlcdata, ""}]}]
-               }
-            ]}]};
+                  ?T(
+                     Lang, "Enter path to jabberd1.4 spool file")}]},
+               ?XFIELD("text-single", "Path to File", "path", "")
+              ]}]};
 
 get_form(_Host, ["running nodes", ENode, "import", "dir"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Import Users from Dir at ") ++ ENode}]},
+                  ?T(
+                     Lang, "Import Users from Dir at ") ++ ENode}]},
                {xmlelement, "instructions", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Enter path to jabberd1.4 spool dir")}]},
-               {xmlelement, "field", [{"type", "text-single"},
-                                      {"label",
-                                       translate:translate(
-                                         Lang, "Path to Dir")},
-                                      {"var", "path"}],
-                [{xmlelement, "value", [], [{xmlcdata, ""}]}]
-               }
-            ]}]};
+                  ?T(
+                     Lang, "Enter path to jabberd1.4 spool dir")}]},
+               ?XFIELD("text-single", "Path to Dir", "path", "")
+              ]}]};
+
+get_form(_Host, ["running nodes", _ENode, "restart"], Lang) ->
+    Make_option = 
+       fun(LabelNum, LabelUnit, Value)->
+               {xmlelement, "option", 
+                [{"label", LabelNum ++ ?T(Lang, LabelUnit)}],
+                [{xmlelement, "value", [], [{xmlcdata, Value}]}]}
+       end,
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "Restart Service")}]},
+               {xmlelement, "field", 
+                [{"type", "list-single"},
+                 {"label", ?T(Lang, "Time delay")},
+                 {"var", "delay"}],
+                [Make_option("", "immediately", "1"),
+                 Make_option("15 ", "seconds", "15"),
+                 Make_option("30 ", "seconds", "30"),
+                 Make_option("60 ", "seconds", "60"),
+                 Make_option("90 ", "seconds", "90"),
+                 Make_option("2 ", "minutes", "120"),
+                 Make_option("3 ", "minutes", "180"),
+                 Make_option("4 ", "minutes", "240"),
+                 Make_option("5 ", "minutes", "300"),
+                 Make_option("10 ", "minutes", "600"),
+                 Make_option("15 ", "minutes", "900"),
+                 Make_option("30 ", "minutes", "1800"),
+                 {xmlelement, "required", [], []}
+                ]},
+               {xmlelement, "field", 
+                [{"type", "fixed"},
+                 {"label", ?T(Lang, "Send announcement to all online users on all hosts")}],
+                []},
+               {xmlelement, "field", 
+                [{"var", "subject"},
+                 {"type", "text-single"},
+                 {"label", ?T(Lang, "Subject")}],
+                []},
+               {xmlelement, "field",
+                [{"var", "announcement"},
+                 {"type", "text-multi"},
+                 {"label", ?T(Lang, "Message body")}],
+                []}
+              ]}]};
+
+get_form(_Host, ["running nodes", _ENode, "shutdown"], Lang) ->
+    Make_option = 
+       fun(LabelNum, LabelUnit, Value)->
+               {xmlelement, "option", 
+                [{"label", LabelNum ++ ?T(Lang, LabelUnit)}],
+                [{xmlelement, "value", [], [{xmlcdata, Value}]}]}
+       end,
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "Shut Down Service")}]},
+               {xmlelement, "field", 
+                [{"type", "list-single"},
+                 {"label", ?T(Lang, "Time delay")},
+                 {"var", "delay"}],
+                [Make_option("", "immediately", "1"),
+                 Make_option("15 ", "seconds", "15"),
+                 Make_option("30 ", "seconds", "30"),
+                 Make_option("60 ", "seconds", "60"),
+                 Make_option("90 ", "seconds", "90"),
+                 Make_option("2 ", "minutes", "120"),
+                 Make_option("3 ", "minutes", "180"),
+                 Make_option("4 ", "minutes", "240"),
+                 Make_option("5 ", "minutes", "300"),
+                 Make_option("10 ", "minutes", "600"),
+                 Make_option("15 ", "minutes", "900"),
+                 Make_option("30 ", "minutes", "1800"),
+                 {xmlelement, "required", [], []}
+                ]},
+               {xmlelement, "field", 
+                [{"type", "fixed"},
+                 {"label", ?T(Lang, "Send announcement to all online users on all hosts")}],
+                []},
+               {xmlelement, "field", 
+                [{"var", "subject"},
+                 {"type", "text-single"},
+                 {"label", ?T(Lang, "Subject")}],
+                []},
+               {xmlelement, "field",
+                [{"var", "announcement"},
+                 {"type", "text-multi"},
+                 {"label", ?T(Lang, "Message body")}],
+                []}
+              ]}]};
 
 get_form(_Host, ["config", "hostname"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Hostname Configuration")}]},
+                  ?T(
+                     Lang, "Hostname Configuration")}]},
                {xmlelement, "instructions", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Choose host name")}]},
+                  ?T(
+                     Lang, "Choose host name")}]},
                {xmlelement, "field", [{"type", "text-single"},
                                       {"label",
-                                       translate:translate(Lang,
-                                                           "Host name")},
+                                       ?T(Lang,
+                                          "Host name")},
                                       {"var", "hostname"}],
                 [{xmlelement, "value", [], [{xmlcdata, ?MYNAME}]}]}
-            ]}]};
+              ]}]};
 
 get_form(_Host, ["config", "acls"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Access Control List Configuration")}]},
-               %{xmlelement, "instructions", [],
-               % [{xmlcdata,
-               %   translate:translate(
-               %     Lang, "")}]},
+                  ?T(
+                     Lang, "Access Control List Configuration")}]},
                {xmlelement, "field", [{"type", "text-multi"},
                                       {"label",
-                                       translate:translate(
-                                         Lang, "Access control lists")},
+                                       ?T(
+                                          Lang, "Access control lists")},
                                       {"var", "acls"}],
                 lists:map(fun(S) ->
-                              {xmlelement, "value", [], [{xmlcdata, S}]}
+                                  {xmlelement, "value", [], [{xmlcdata, S}]}
                           end,
                           string:tokens(
                             lists:flatten(io_lib:format("~p.",
                                                         [ets:tab2list(acl)])),
                             "\n"))
                }
-            ]}]};
+              ]}]};
 
 get_form(_Host, ["config", "access"], Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Access Configuration")}]},
-               %{xmlelement, "instructions", [],
-               % [{xmlcdata,
-               %   translate:translate(
-               %     Lang, "")}]},
+                  ?T(
+                     Lang, "Access Configuration")}]},
                {xmlelement, "field", [{"type", "text-multi"},
                                       {"label",
-                                       translate:translate(
-                                         Lang, "Access rules")},
+                                       ?T(
+                                          Lang, "Access rules")},
                                       {"var", "access"}],
                 lists:map(fun(S) ->
-                              {xmlelement, "value", [], [{xmlcdata, S}]}
+                                  {xmlelement, "value", [], [{xmlcdata, S}]}
                           end,
                           string:tokens(
                             lists:flatten(
@@ -979,41 +1137,148 @@ get_form(_Host, ["config", "access"], Lang) ->
                                 ])),
                             "\n"))
                }
-            ]}]};
+              ]}]};
 
-get_form(Host, ["config", "remusers"], Lang) ->
+get_form(_Host, ?NS_ADMINL("add-user"), Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
-                [{xmlcdata,
-                  translate:translate(
-                    Lang, "Remove Users")}]},
-               {xmlelement, "instructions", [],
-                [{xmlcdata,
-                  translate:translate(
-                    Lang, "Choose users to remove")}]}] ++
-      case catch ejabberd_auth:get_vh_registered_users(Host) of
-         {'EXIT', _Reason} ->
-             [];
-         Users ->
-             lists:map(fun(U) ->
-                           ?XFIELD("boolean", U, U, "0")
-                       end, lists:sort(Users))
-      end
-    }]};
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "Add User")}]},
+               {xmlelement, "field", 
+                [{"type", "jid-single"},
+                 {"label", ?T(Lang, "Jabber ID")},
+                 {"var", "accountjid"}],
+                [{xmlelement, "required", [], []}]},
+               {xmlelement, "field", 
+                [{"type", "text-private"},
+                 {"label", ?T(Lang, "Password")},
+                 {"var", "password"}],
+                [{xmlelement, "required", [], []}]},
+               {xmlelement, "field", 
+                [{"type", "text-private"},
+                 {"label", ?T(Lang, "Password Verification")},
+                 {"var", "password-verify"}],
+                [{xmlelement, "required", [], []}]}
+              ]}]};
+
+get_form(_Host, ?NS_ADMINL("delete-user"), Lang) ->
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "Delete User")}]},
+               {xmlelement, "field", 
+                [{"type", "jid-multi"},
+                 {"label", ?T(Lang, "Jabber ID")},
+                 {"var", "accountjids"}],
+                [{xmlelement, "required", [], []}]}
+              ]}]};
+
+get_form(_Host, ?NS_ADMINL("end-user-session"), Lang) ->
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "End User Session")}]},
+               {xmlelement, "field", 
+                [{"type", "jid-single"},
+                 {"label", ?T(Lang, "Jabber ID")},
+                 {"var", "accountjid"}],
+                [{xmlelement, "required", [], []}]}
+              ]}]};
+
+get_form(_Host, ?NS_ADMINL("get-user-password"), Lang) ->
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "Get User Password")}]},
+               {xmlelement, "field", 
+                [{"type", "jid-single"},
+                 {"label", ?T(Lang, "Jabber ID")},
+                 {"var", "accountjid"}],
+                [{xmlelement, "required", [], []}]}
+              ]}]};
+
+get_form(_Host, ?NS_ADMINL("change-user-password"), Lang) ->
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "Get User Password")}]},
+               {xmlelement, "field", 
+                [{"type", "jid-single"},
+                 {"label", ?T(Lang, "Jabber ID")},
+                 {"var", "accountjid"}],
+                [{xmlelement, "required", [], []}]},
+               {xmlelement, "field", 
+                [{"type", "text-private"},
+                 {"label", ?T(Lang, "Password")},
+                 {"var", "password"}],
+                [{xmlelement, "required", [], []}]}
+              ]}]};
+
+get_form(_Host, ?NS_ADMINL("get-user-lastlogin"), Lang) ->
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "Get User Last Login Time")}]},
+               {xmlelement, "field", 
+                [{"type", "jid-single"},
+                 {"label", ?T(Lang, "Jabber ID")},
+                 {"var", "accountjid"}],
+                [{xmlelement, "required", [], []}]}
+              ]}]};
+
+get_form(_Host, ?NS_ADMINL("user-stats"), Lang) ->
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               {xmlelement, "title", [],
+                [{xmlcdata, ?T(Lang, "Get User Statistics")}]},
+               {xmlelement, "field", 
+                [{"type", "jid-single"},
+                 {"label", ?T(Lang, "Jabber ID")},
+                 {"var", "accountjid"}],
+                [{xmlelement, "required", [], []}]}
+              ]}]};
+
+get_form(Host, ?NS_ADMINL("get-registered-users-num"), Lang) ->
+    [Num] = io_lib:format("~p", [ejabberd_auth:get_vh_registered_users_number(Host)]),
+    {result, completed,
+     [{xmlelement, "x", 
+       [{"xmlns", ?NS_XDATA}],
+       [?HFIELD(),
+       {xmlelement, 
+        "field", 
+        [{"type", "jid-single"},
+         {"label", ?T(Lang, "Number of registered users")},
+         {"var", "registeredusersnum"}],
+        [{xmlelement, "value", [], [{xmlcdata, Num}]}]
+       }]}]};
+
+get_form(Host, ?NS_ADMINL("get-online-users-num"), Lang) ->
+    Num = io_lib:format("~p", [length(ejabberd_sm:get_vh_session_list(Host))]),
+    {result, completed,
+     [{xmlelement, "x", 
+       [{"xmlns", ?NS_XDATA}],
+       [?HFIELD(),
+       {xmlelement, 
+        "field", 
+        [{"type", "jid-single"},
+         {"label", ?T(Lang, "Number of online users")},
+         {"var", "onlineusersnum"}],
+        [{xmlelement, "value", [], [{xmlcdata, Num}]}]
+       }]}]};
 
 get_form(_Host, _, _Lang) ->
     {error, ?ERR_SERVICE_UNAVAILABLE}.
 
 
 
-set_form(_Host, ["running nodes", ENode, "DB"], _Lang, XData) ->
+set_form(_From, _Host, ["running nodes", ENode, "DB"], _Lang, XData) ->
     case search_running_node(ENode) of
        false ->
            {error, ?ERR_ITEM_NOT_FOUND};
        Node ->
            lists:foreach(
              fun({SVar, SVals}) ->
-                     % We believe that this is allowed only for good people
+                       %% We believe that this is allowed only for good people
                      Table = list_to_atom(SVar),
                      Type = case SVals of
                                 ["unknown"] -> unknown;
@@ -1040,7 +1305,7 @@ set_form(_Host, ["running nodes", ENode, "DB"], _Lang, XData) ->
            {result, []}
     end;
 
-set_form(Host, ["running nodes", ENode, "modules", "stop"], _Lang, XData) ->
+set_form(_From, Host, ["running nodes", ENode, "modules", "stop"], _Lang, XData) ->
     case search_running_node(ENode) of
        false ->
            {error, ?ERR_ITEM_NOT_FOUND};
@@ -1058,7 +1323,7 @@ set_form(Host, ["running nodes", ENode, "modules", "stop"], _Lang, XData) ->
            {result, []}
     end;
 
-set_form(Host, ["running nodes", ENode, "modules", "start"], _Lang, XData) ->
+set_form(_From, Host, ["running nodes", ENode, "modules", "start"], _Lang, XData) ->
     case search_running_node(ENode) of
        false ->
            {error, ?ERR_ITEM_NOT_FOUND};
@@ -1094,7 +1359,7 @@ set_form(Host, ["running nodes", ENode, "modules", "start"], _Lang, XData) ->
     end;
 
 
-set_form(_Host, ["running nodes", ENode, "backup", "backup"], _Lang, XData) ->
+set_form(_From, _Host, ["running nodes", ENode, "backup", "backup"], _Lang, XData) ->
     case search_running_node(ENode) of
        false ->
            {error, ?ERR_ITEM_NOT_FOUND};
@@ -1110,14 +1375,14 @@ set_form(_Host, ["running nodes", ENode, "backup", "backup"], _Lang, XData) ->
                            {error, ?ERR_INTERNAL_SERVER_ERROR};
                        _ ->
                            {result, []}
-                       end;
+                   end;
                _ ->
                    {error, ?ERR_BAD_REQUEST}
            end
     end;
 
 
-set_form(_Host, ["running nodes", ENode, "backup", "restore"], _Lang, XData) ->
+set_form(_From, _Host, ["running nodes", ENode, "backup", "restore"], _Lang, XData) ->
     case search_running_node(ENode) of
        false ->
            {error, ?ERR_ITEM_NOT_FOUND};
@@ -1133,14 +1398,14 @@ set_form(_Host, ["running nodes", ENode, "backup", "restore"], _Lang, XData) ->
                            {error, ?ERR_INTERNAL_SERVER_ERROR};
                        _ ->
                            {result, []}
-                       end;
+                   end;
                _ ->
                    {error, ?ERR_BAD_REQUEST}
            end
     end;
 
 
-set_form(_Host, ["running nodes", ENode, "backup", "textfile"], _Lang, XData) ->
+set_form(_From, _Host, ["running nodes", ENode, "backup", "textfile"], _Lang, XData) ->
     case search_running_node(ENode) of
        false ->
            {error, ?ERR_ITEM_NOT_FOUND};
@@ -1156,14 +1421,14 @@ set_form(_Host, ["running nodes", ENode, "backup", "textfile"], _Lang, XData) ->
                            {error, ?ERR_INTERNAL_SERVER_ERROR};
                        _ ->
                            {result, []}
-                       end;
+                   end;
                _ ->
                    {error, ?ERR_BAD_REQUEST}
            end
     end;
 
 
-set_form(_Host, ["running nodes", ENode, "import", "file"], _Lang, XData) ->
+set_form(_From, _Host, ["running nodes", ENode, "import", "file"], _Lang, XData) ->
     case search_running_node(ENode) of
        false ->
            {error, ?ERR_ITEM_NOT_FOUND};
@@ -1180,7 +1445,7 @@ set_form(_Host, ["running nodes", ENode, "import", "file"], _Lang, XData) ->
     end;
 
 
-set_form(_Host, ["running nodes", ENode, "import", "dir"], _Lang, XData) ->
+set_form(_From, _Host, ["running nodes", ENode, "import", "dir"], _Lang, XData) ->
     case search_running_node(ENode) of
        false ->
            {error, ?ERR_ITEM_NOT_FOUND};
@@ -1196,8 +1461,13 @@ set_form(_Host, ["running nodes", ENode, "import", "dir"], _Lang, XData) ->
            end
     end;
 
+set_form(From, Host, ["running nodes", ENode, "restart"], _Lang, XData) ->
+    stop_node(From, Host, ENode, restart, XData);
+
+set_form(From, Host, ["running nodes", ENode, "shutdown"], _Lang, XData) ->
+    stop_node(From, Host, ENode, stop, XData);
 
-set_form(_Host, ["config", "hostname"], _Lang, XData) ->
+set_form(_From, _Host, ["config", "hostname"], _Lang, XData) ->
     case lists:keysearch("hostname", 1, XData) of
        false ->
            {error, ?ERR_BAD_REQUEST};
@@ -1210,7 +1480,7 @@ set_form(_Host, ["config", "hostname"], _Lang, XData) ->
            {error, ?ERR_BAD_REQUEST}
     end;
 
-set_form(Host, ["config", "acls"], _Lang, XData) ->
+set_form(_From, Host, ["config", "acls"], _Lang, XData) ->
     case lists:keysearch("acls", 1, XData) of
        {value, {_, Strings}} ->
            String = lists:foldl(fun(S, Res) ->
@@ -1236,7 +1506,7 @@ set_form(Host, ["config", "acls"], _Lang, XData) ->
            {error, ?ERR_BAD_REQUEST}
     end;
 
-set_form(_Host, ["config", "access"], _Lang, XData) ->
+set_form(_From, _Host, ["config", "access"], _Lang, XData) ->
     SetAccess =
        fun(Rs) ->
                mnesia:transaction(
@@ -1268,8 +1538,7 @@ set_form(_Host, ["config", "access"], _Lang, XData) ->
                            case SetAccess(Rs) of
                                {atomic, _} ->
                                    {result, []};
-                               E ->
-                                   io:format("A: ~p~n", [E]),
+                               _ ->
                                    {error, ?ERR_BAD_REQUEST}
                            end;
                        _ ->
@@ -1282,21 +1551,144 @@ set_form(_Host, ["config", "access"], _Lang, XData) ->
            {error, ?ERR_BAD_REQUEST}
     end;
 
-set_form(Host, ["config", "remusers"], _Lang, XData) ->
-    lists:foreach(
-      fun({Var, Vals}) ->
-             case Vals of
-                 ["1"] ->
-                     catch ejabberd_auth:remove_user(Var, Host);
-                 _ ->
-                     ok
-             end
-      end, XData),
+set_form(_From, _Host, ?NS_ADMINL("add-user"), _Lang, XData) ->
+    AccountString = get_value("accountjid", XData),
+    Password = get_value("password", XData),
+    Password = get_value("password-verify", XData),
+    AccountJID = jlib:string_to_jid(AccountString),
+    User = AccountJID#jid.luser,
+    Server = AccountJID#jid.lserver,
+    true = lists:member(Server, ?MYHOSTS),
+    ejabberd_auth:try_register(User, Server, Password),
     {result, []};
 
-set_form(_Host, _, _Lang, _XData) ->
+set_form(_From, _Host, ?NS_ADMINL("delete-user"), _Lang, XData) ->
+    AccountStringList = get_values("accountjids", XData),
+    [_|_] = AccountStringList,
+    ASL2 = lists:map(
+            fun(AccountString) ->
+                    JID = jlib:string_to_jid(AccountString),
+                    [_|_] = JID#jid.luser,
+                    User = JID#jid.luser, 
+                    Server = JID#jid.lserver, 
+                    true = ejabberd_auth:is_user_exists(User, Server),
+                    {User, Server}
+            end,
+            AccountStringList),
+    [ejabberd_auth:remove_user(User, Server) || {User, Server} <- ASL2],
+    {result, []};
+
+set_form(_From, _Host, ?NS_ADMINL("end-user-session"), _Lang, XData) ->
+    AccountString = get_value("accountjid", XData),
+    JID = jlib:string_to_jid(AccountString),
+    [_|_] = JID#jid.luser,
+    LUser = JID#jid.luser, 
+    LServer = JID#jid.lserver, 
+    %% Code copied from ejabberd_sm.erl
+    case JID#jid.lresource of
+       [] -> 
+           SIDs = mnesia:dirty_select(session,
+                                      [{#session{sid = '$1', usr = {LUser, LServer, '_'}, _ = '_'}, [], ['$1']}]),
+           [Pid ! replaced || {_, Pid} <- SIDs];
+       R -> 
+           [{_, Pid}] = mnesia:dirty_select(session,
+                                            [{#session{sid = '$1', usr = {LUser, LServer, R}, _ = '_'}, [], ['$1']}]),
+           Pid ! replaced
+    end, 
+    {result, []};
+
+set_form(_From, _Host, ?NS_ADMINL("get-user-password"), Lang, XData) ->
+    AccountString = get_value("accountjid", XData),
+    JID = jlib:string_to_jid(AccountString),
+    [_|_] = JID#jid.luser,
+    User = JID#jid.luser, 
+    Server = JID#jid.lserver, 
+    Password = ejabberd_auth:get_password(User, Server),
+    true = is_list(Password),
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString),
+               ?XFIELD("text-single", "Password", "password", Password)
+              ]}]};
+
+set_form(_From, _Host, ?NS_ADMINL("change-user-password"), _Lang, XData) ->
+    AccountString = get_value("accountjid", XData),
+    Password = get_value("password", XData),
+    JID = jlib:string_to_jid(AccountString),
+    [_|_] = JID#jid.luser,
+    User = JID#jid.luser, 
+    Server = JID#jid.lserver, 
+    true = ejabberd_auth:is_user_exists(User, Server),
+    ejabberd_auth:set_password(User, Server, Password),
+    {result, []};
+
+set_form(_From, _Host, ?NS_ADMINL("get-user-lastlogin"), Lang, XData) ->
+    AccountString = get_value("accountjid", XData),
+    JID = jlib:string_to_jid(AccountString),
+    [_|_] = JID#jid.luser,
+    User = JID#jid.luser, 
+    Server = JID#jid.lserver, 
+
+    %% Code copied from web/ejabberd_web_admin.erl
+    %% TODO: Update time format to XEP-0202: Entity Time
+    FLast =
+       case ejabberd_sm:get_user_resources(User, Server) of
+           [] ->
+               US = {User, Server},
+               case mnesia:dirty_read({last_activity, US}) of
+                   [] ->
+                       ?T(Lang, "Never");
+                   [E] ->
+                       Shift = element(3, E),
+                       TimeStamp = {Shift div 1000000,
+                                    Shift rem 1000000,
+                                    0},
+                       {{Year, Month, Day}, {Hour, Minute, Second}} =
+                           calendar:now_to_local_time(TimeStamp),
+                       lists:flatten(
+                         io_lib:format(
+                           "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+                           [Year, Month, Day, Hour, Minute, Second]))
+               end;
+           _ ->
+               ?T(Lang, "Online")
+       end,
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
+              [?HFIELD(),
+               ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString),
+               ?XFIELD("text-single", "Last login", "lastlogin", FLast)
+              ]}]};
+
+set_form(_From, _Host, ?NS_ADMINL("user-stats"), Lang, XData) ->
+    AccountString = get_value("accountjid", XData),
+    JID = jlib:string_to_jid(AccountString),
+    [_|_] = JID#jid.luser,
+    User = JID#jid.luser, 
+    Server = JID#jid.lserver, 
+
+    Resources = ejabberd_sm:get_user_resources(User, Server),
+    IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource) || Resource <- Resources],
+    IPs = [inet_parse:ntoa(IP)++":"++integer_to_list(Port) || {IP, Port} <- IPs1],
+
+    Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
+    Rostersize = integer_to_list(erlang:length(Items)),
+
+    {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
+              [?HFIELD(),
+               ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString),
+               ?XFIELD("text-single", "Roster size", "rostersize", Rostersize),
+               ?XMFIELD("text-multi", "IP addresses", "ipaddresses", IPs),
+               ?XMFIELD("text-multi", "Resources", "onlineresources", Resources)
+              ]}]};
+
+set_form(_From, _Host, _, _Lang, _XData) ->
     {error, ?ERR_SERVICE_UNAVAILABLE}.
 
+get_value(Field, XData) -> 
+    hd(get_values(Field, XData)).
+get_values(Field, XData) -> 
+    {value, {_, ValueList}} = lists:keysearch(Field, 1, XData),
+    ValueList.
 
 
 search_running_node(SNode) ->
@@ -1312,6 +1704,42 @@ search_running_node(SNode, [Node | Nodes]) ->
            search_running_node(SNode, Nodes)
     end.
 
+stop_node(From, Host, ENode, Action, XData) ->
+    Delay = list_to_integer(get_value("delay", XData)),
+    Subject = case get_value("subject", XData) of
+                 [] -> [];
+                 S -> [{xmlelement, "field", [{"var","subject"}],
+                        [{xmlelement,"value",[],[{xmlcdata,S}]}]}]
+             end,
+    Announcement = case get_values("announcement", XData) of
+                      [] -> [];
+                      As -> [{xmlelement, "field", [{"var","body"}],
+                              [{xmlelement,"value",[],[{xmlcdata,Line}]} || Line <- As] }]
+                  end,
+    case Subject ++ Announcement of
+       [] -> ok;
+       SubEls ->
+           Request = #adhoc_request{
+             lang = "es",
+             node = ?NS_ADMINX("announce-online-allhosts"),
+             sessionid = "2007-08-17T15:28:40.517806Z",
+             action = "complete",
+             xdata = {xmlelement, "x",
+                      [{"xmlns","jabber:x:data"},{"type","submit"}],
+                      SubEls},
+             others= [{xmlelement, "x",
+                       [{"xmlns","jabber:x:data"},{"type","submit"}],
+                       SubEls}]
+            },
+           To = jlib:make_jid("", Host, ""),
+           mod_announce:announce_commands(empty, From, To, Request)
+    end,
+    Time = timer:seconds(Delay),
+    Node = list_to_atom(ENode),
+    {ok, _} = timer:apply_after(Time, rpc, call, [Node, init, Action, []]),
+    {result, []}.
+
+
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 adhoc_sm_commands(_Acc, From,
@@ -1364,25 +1792,26 @@ adhoc_sm_commands(Acc, _From, _To, _Request) ->
 
 get_sm_form(User, Server, "config", Lang) ->
     {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-              [{xmlelement, "title", [],
+              [?HFIELD(),
+               {xmlelement, "title", [],
                 [{xmlcdata,
-                  translate:translate(
-                    Lang, "Administration of ") ++ User}]},
+                  ?T(
+                     Lang, "Administration of ") ++ User}]},
                {xmlelement, "field",
                 [{"type", "list-single"},
-                 {"label", translate:translate(Lang, "Action on user")},
+                 {"label", ?T(Lang, "Action on user")},
                  {"var", "action"}],
                 [{xmlelement, "value", [], [{xmlcdata, "edit"}]},
                  {xmlelement, "option",
-                  [{"label", translate:translate(Lang, "Edit Properties")}],
+                  [{"label", ?T(Lang, "Edit Properties")}],
                   [{xmlelement, "value", [], [{xmlcdata, "edit"}]}]},
                  {xmlelement, "option",
-                  [{"label", translate:translate(Lang, "Remove User")}],
+                  [{"label", ?T(Lang, "Remove User")}],
                   [{xmlelement, "value", [], [{xmlcdata, "remove"}]}]}
                 ]},
                ?XFIELD("text-private", "Password", "password",
                        ejabberd_auth:get_password_s(User, Server))
-            ]}]};
+              ]}]};
 
 get_sm_form(_User, _Server, _Node, _Lang) ->
     {error, ?ERR_SERVICE_UNAVAILABLE}.
@@ -1415,3 +1844,5 @@ set_sm_form(User, Server, "config",
 set_sm_form(_User, _Server, _Node, _Request, _Fields) ->
     {error, ?ERR_SERVICE_UNAVAILABLE}.
 
+
+
index 51cd61702c9663ee57ec13d7ce968be9e0ee2025..242fd7440ca7f714f07af3731bde29cc18cbaedc 100644 (file)
@@ -192,7 +192,7 @@ try_register(User, Server, Password) ->
 
 send_welcome_message(JID) ->
     Host = JID#jid.lserver,
-    case ejabberd_config:get_local_option({welcome_message, Host}) of
+    case gen_mod:get_module_opt(Host, ?MODULE, welcome_message, {"", ""}) of
        {"", ""} ->
            ok;
        {Subj, Body} ->
@@ -208,7 +208,7 @@ send_welcome_message(JID) ->
 
 send_registration_notifications(UJID) ->
     Host = UJID#jid.lserver,
-    case ejabberd_config:get_local_option({registration_watchers, Host}) of
+    case gen_mod:get_module_opt(Host, ?MODULE, registration_watchers, []) of
        [] -> ok;
        JIDs when is_list(JIDs) ->
            Body = lists:flatten(
@@ -231,4 +231,3 @@ send_registration_notifications(UJID) ->
        _ ->
            ok
     end.
-