Blob Blame History Raw
From 85b432d0293d867dd5c11ae8e7b5c724b7fad056 Mon Sep 17 00:00:00 2001
From: Peter Lemenkov <lemenkov@gmail.com>
Date: Tue, 16 Feb 2010 16:05:53 +0300
Subject: [PATCH 02/11] Add mod_ctlextra as an ejabberd module

See this link for the details:
http://www.ejabberd.im/mod_ctlextra

Also note that this module will be removed in the nearest future
in favor of mod_admin_extra:

svn export -r 1020 https://svn.process-one.net/ejabberd-modules/mod_ctlextra/trunk/src/mod_ctlextra.erl

Signed-off-by: Peter Lemenkov <lemenkov@gmail.com>
---
 src/ejabberd.app     |    1 +
 src/mod_ctlextra.erl |  895 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 896 insertions(+), 0 deletions(-)
 create mode 100644 src/mod_ctlextra.erl

diff --git a/src/ejabberd.app b/src/ejabberd.app
index c2d282d..6db563c 100644
--- a/src/ejabberd.app
+++ b/src/ejabberd.app
@@ -70,6 +70,7 @@
 	     mod_caps,
 	     mod_configure2,
 	     mod_configure,
+	     mod_ctlextra,
 	     mod_disco,
 	     mod_echo,
 	     mod_http_bind,
diff --git a/src/mod_ctlextra.erl b/src/mod_ctlextra.erl
new file mode 100644
index 0000000..bc61024
--- /dev/null
+++ b/src/mod_ctlextra.erl
@@ -0,0 +1,895 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_ctlextra.erl
+%%% Author  : Badlop <badlop@ono.com>
+%%% Purpose : Adds more commands to ejabberd_ctl
+%%% Created : 30 Nov 2006 by Badlop <badlop@ono.com>
+%%% Id      : $Id: mod_ctlextra.erl 1020 2009-08-30 10:13:34Z badlop $
+%%%----------------------------------------------------------------------
+
+-module(mod_ctlextra).
+-author('badlop@ono.com').
+
+-behaviour(gen_mod).
+
+-export([start/2,
+	 stop/1,
+	 ctl_process/2,
+	 ctl_process/3
+	]).
+
+-include("ejabberd.hrl").
+-include("ejabberd_ctl.hrl").
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+
+%% Copied from ejabberd_sm.erl
+-record(session, {sid, usr, us, priority, info}).
+
+-compile(export_all).
+
+%%-------------
+%% gen_mod
+%%-------------
+
+start(Host, _Opts) ->
+    ejabberd_ctl:register_commands(commands_global(), ?MODULE, ctl_process),
+    ejabberd_ctl:register_commands(Host, commands_host(), ?MODULE, ctl_process).
+
+stop(Host) ->
+    ejabberd_ctl:unregister_commands(commands_global(), ?MODULE, ctl_process),
+    ejabberd_ctl:unregister_commands(Host, commands_host(), ?MODULE, ctl_process).
+
+commands_global() ->
+    [
+     {"compile file", "recompile and reload file"},
+     {"load-config file", "load config from file"},
+     {"remove-node nodename", "remove an ejabberd node from the database"},
+
+     %% ejabberd_auth
+     {"delete-older-users days", "delete users that have not logged in the last 'days'"},
+     {"delete-older-users-vhost host days", "delete users that not logged in last 'days' in 'host'"},
+     {"set-password user server password", "set password to user@server"},
+
+     %% ejd2odbc
+     {"export2odbc server output", "export Mnesia tables on server to files on output directory"},
+
+     %% mod_shared_roster
+     {"srg-create group host name description display", "create the group with options"},
+     {"srg-delete group host", "delete the group"},
+     {"srg-user-add user server group host", "add user@server to group on host"},
+     {"srg-user-del user server group host", "delete user@server from group on host"},
+     {"srg-list-groups host", "list the shared roster groups from host"},
+     {"srg-get-info group host", "get info of a specific group on host"},
+
+     %% mod_vcard
+     {"vcard-get user host data [data2]", "get data from the vCard of the user"},
+     {"vcard-set user host data [data2] content", "set data to content on the vCard"},
+
+     %% mod_announce
+     %% announce_send_online host message
+     %% announce_send_all host, message
+
+     %% mod_roster
+     {"add-rosteritem user1 server1 user2 server2 nick group subs", "Add user2@server2 to user1@server1's roster"},
+     %%{"", "subs= none, from, to or both"},
+     %%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
+     %%{"", "will add mike@server.com to peter@localhost roster"},
+     {"rem-rosteritem user1 server1 user2 server2", "Remove user2@server2 from user1@server1's roster"},
+     {"rosteritem-purge [options]", "Purge all rosteritems that match filtering options"},
+     {"pushroster file user server", "push template roster in file to user@server"},
+     {"pushroster-all file", "push template roster in file to all those users"},
+     {"push-alltoall server group", "adds all the users to all the users in Group"},
+
+     {"status-list status", "list the logged users with status"},
+     {"status-num status", "number of logged users with status"},
+
+     {"stats registeredusers", "number of registered users"},
+     {"stats onlineusers", "number of logged users"},
+     {"stats onlineusersnode", "number of logged users in the ejabberd node"},
+     {"stats uptime-seconds", "uptime of ejabberd node in seconds"},
+
+     %% misc
+     {"get-cookie", "get the Erlang cookie of this node"},
+     {"killsession user server resource", "kill a user session"}
+    ].
+
+commands_host() ->
+    [
+     %% mod_last
+     {"num-active-users days", "number of users active in the last 'days'"},
+     {"status-list status", "list the logged users with status"},
+     {"status-num status", "number of logged users with status"},
+     {"stats registeredusers", "number of registered users"},
+     {"stats onlineusers", "number of logged users"},
+
+     %% misc
+     {"ban-account username [reason]", "ban account: kick sessions and change password"}
+    ].
+
+
+%%-------------
+%% Commands global
+%%-------------
+
+ctl_process(_Val, ["delete-older-users", Days]) ->
+    {removed, N, UR} = delete_older_users(list_to_integer(Days)),
+    io:format("Deleted ~p users: ~p~n", [N, UR]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["delete-older-users-vhost", Host, Days]) ->
+    {removed, N, UR} = delete_older_users_vhost(Host, list_to_integer(Days)),
+    io:format("Deleted ~p users: ~p~n", [N, UR]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["export2odbc", Server, Output]) ->
+    Tables = [
+	      {export_last, last},
+	      {export_offline, offline},
+	      {export_passwd, passwd},
+	      {export_private_storage, private_storage},
+	      {export_roster, roster},
+	      {export_vcard, vcard},
+	      {export_vcard_search, vcard_search}],
+    Export = fun({TableFun, Table}) -> 
+		     Filename = filename:join([Output, atom_to_list(Table)++".txt"]),
+		     io:format("Trying to export Mnesia table '~p' on server '~s' to file '~s'~n", [Table, Server, Filename]),
+		     Res = (catch ejd2odbc:TableFun(Server, Filename)),
+		     io:format("  Result: ~p~n", [Res])
+	     end,
+    lists:foreach(Export, Tables),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["set-password", User, Server, Password]) ->
+    ejabberd_auth:set_password(User, Server, Password),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["vcard-get", User, Server, Data]) ->
+    {ok, Res} = vcard_get(User, Server, [Data]),
+    io:format("~s~n", [Res]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["vcard-get", User, Server, Data1, Data2]) ->
+    {ok, Res} = vcard_get(User, Server, [Data1, Data2]),
+    io:format("~s~n", [Res]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["vcard-set", User, Server, Data1, Content]) ->
+    {ok, Res} = vcard_set(User, Server, [Data1], Content),
+    io:format("~s~n", [Res]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["vcard-set", User, Server, Data1, Data2, Content]) ->
+    {ok, Res} = vcard_set(User, Server, [Data1, Data2], Content),
+    io:format("~s~n", [Res]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["compile", Module]) ->
+    compile:file(Module),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["remove-node", Node]) ->
+    mnesia:del_table_copy(schema, list_to_atom(Node)),
+    ?STATUS_SUCCESS;
+
+%% The Display argument can be several groups separated with ,
+%% Example: ejabberdctl srg-create aa localhost Name Desc Display1,Display2,Display3
+ctl_process(_Val, ["srg-create" | Parameters]) ->
+	[Group, Host, Name, Description, Display] = group_parameters(Parameters, "'"),
+    DisplayList = string:tokens(Display, ","),
+    Opts = [{name, Name}, {displayed_groups, DisplayList}, {description, Description}],
+    {atomic, ok} = mod_shared_roster:create_group(Host, Group, Opts),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["srg-delete", Group, Host]) ->
+    {atomic, ok} = mod_shared_roster:delete_group(Host, Group),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["srg-user-add", User, Server, Group, Host]) ->
+    {atomic, ok} = mod_shared_roster:add_user_to_group(Host, {User, Server}, Group),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["srg-user-del", User, Server, Group, Host]) ->
+    {atomic, ok} = mod_shared_roster:remove_user_from_group(Host, {User, Server}, Group),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["srg-list-groups", Host]) ->
+    lists:foreach(
+      fun(SrgGroup) ->
+	      io:format("~s~n",[SrgGroup])
+      end,
+      lists:sort(mod_shared_roster:list_groups(Host))),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["srg-get-info", Group, Host]) ->
+    Opts = mod_shared_roster:get_group_opts(Host,Group),
+    [io:format("~s: ~p~n", [Title, Value]) || {Title , Value} <- Opts],
+    
+    Members = mod_shared_roster:get_group_explicit_users(Host,Group),
+    Members_string = [ " " ++ jlib:jid_to_string(jlib:make_jid(MUser, MServer, "")) 
+		       || {MUser, MServer} <- Members],
+    io:format("members:~s~n", [Members_string]),
+    
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["add-rosteritem", LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subs]) ->
+    case add_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, list_to_atom(Subs), []) of
+	{atomic, ok} ->
+	    ?STATUS_SUCCESS;
+	{error, Reason} ->
+	    io:format("Can't add ~p@~p to ~p@~p: ~p~n",
+		      [RemoteUser, RemoteServer, LocalUser, LocalServer, Reason]),
+	    ?STATUS_ERROR;
+	{badrpc, Reason} ->
+	    io:format("Can't add roster item to user ~p: ~p~n",
+		      [LocalUser, Reason]),
+	    ?STATUS_BADRPC
+    end;
+
+ctl_process(_Val, ["rem-rosteritem", LocalUser, LocalServer, RemoteUser, RemoteServer]) ->
+    case rem_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer) of
+	{atomic, ok} ->
+	    ?STATUS_SUCCESS;
+	{error, Reason} ->
+	    io:format("Can't remove ~p@~p to ~p@~p: ~p~n",
+		      [RemoteUser, RemoteServer, LocalUser, LocalServer, Reason]),
+	    ?STATUS_ERROR;
+	{badrpc, Reason} ->
+	    io:format("Can't remove roster item to user ~p: ~p~n",
+		      [LocalUser, Reason]),
+	    ?STATUS_BADRPC
+    end;
+
+ctl_process(_Val, ["rosteritem-purge"]) ->
+    io:format("Rosteritems that match all the filtering will be removed.~n"),
+    io:format("Options for filtering:~n"),
+    io:format("~n"),
+    io:format("  -subs none|from|to|both~n"),
+    io:format("       Subscription type. By default all values~n"),
+    io:format("~n"),
+    io:format("  -ask none|out|in~n"),
+    io:format("       Pending subscription. By default all values~n"),
+    io:format("~n"),
+    io:format("  -user JID~n"),
+    io:format("       The JID of the local user.~n"),
+    io:format("       Can use these globs: *, ? and [...].~n"),
+    io:format("       By default it is: * *@*~n"),
+    io:format("~n"),
+    io:format("  -contact JID~n"),
+    io:format("       Similar to 'user', but for the contact JID.~n"),
+    io:format("~n"),
+    io:format("Example:~n"),
+    io:format("  ejabberdctl rosteritem-purge -subs none from to -ask out in -contact *@*icq*~n"),
+    io:format("~n"),
+    ?STATUS_SUCCESS;
+ctl_process(_Val, ["rosteritem-purge" | Options_list]) ->
+    Options_prop_list = lists:foldl(
+			  fun(O, R) ->
+				  case O of
+				      [$- | K] ->
+					  [{K, []} | R];
+				      V ->
+					  [{K, Vs} | RT] = R,
+					  [{K, [V|Vs]} | RT]
+				  end
+			  end,
+			  [],
+			  Options_list),
+    
+    Subs = [list_to_atom(S)
+	    || S <- proplists:get_value("subs", 
+					Options_prop_list, 
+					["none", "from", "to", "both"])],
+    Asks = [list_to_atom(S)
+	    || S <- 
+		   proplists:get_value("ask",
+				       Options_prop_list, 
+				       ["none", "out", "in"])],
+    User = proplists:get_value("user", Options_prop_list, ["*", "*@*"]),
+    Contact = proplists:get_value("contact", Options_prop_list, ["*", "*@*"]),
+    
+    case rosteritem_purge({Subs, Asks, User, Contact}) of
+	{atomic, ok} ->
+	    ?STATUS_SUCCESS;
+	{error, Reason} ->
+	    io:format("Error purging rosteritems: ~p~n",
+		      [Reason]),
+	    ?STATUS_ERROR;
+	{badrpc, Reason} ->
+	    io:format("BadRPC purging rosteritems: ~p~n",
+		      [Reason]),
+	    ?STATUS_BADRPC
+    end;
+
+ctl_process(_Val, ["pushroster", File, User, Server]) ->
+    case pushroster(File, User, Server) of
+	ok ->
+	    ?STATUS_SUCCESS;
+	{error, Reason} ->
+	    io:format("Can't push roster ~p to ~p@~p: ~p~n",
+		      [File, User, Server, Reason]),
+	    ?STATUS_ERROR;
+	{badrpc, Reason} ->
+	    io:format("Can't push roster ~p: ~p~n",
+		      [File, Reason]),
+	    ?STATUS_BADRPC
+    end;
+
+ctl_process(_Val, ["pushroster-all", File]) ->
+    case pushroster_all([File]) of
+	ok ->
+	    ?STATUS_SUCCESS;
+	{error, Reason} ->
+	    io:format("Can't push roster ~p: ~p~n",
+		      [File, Reason]),
+	    ?STATUS_ERROR;
+	{badrpc, Reason} ->
+	    io:format("Can't push roster ~p: ~p~n",
+		      [File, Reason]),
+	    ?STATUS_BADRPC
+    end;
+
+ctl_process(_Val, ["push-alltoall", Server, Group]) ->
+    case push_alltoall(Server, Group) of
+	ok ->
+	    ?STATUS_SUCCESS;
+	{error, Reason} ->
+	    io:format("Can't push all to all: ~p~n",
+		      [Reason]),
+	    ?STATUS_ERROR;
+	{badrpc, Reason} ->
+	    io:format("Can't push all to all: ~p~n",
+		      [Reason]),
+	    ?STATUS_BADRPC
+    end;
+
+ctl_process(_Val, ["load-config", Path]) ->
+    case catch ejabberd_config:load_file(Path) of
+        ok ->
+            ?STATUS_SUCCESS;
+        {'EXIT', Reason} ->
+            io:format("Problem loading config file ~p: ~p~n",
+                      [filename:absname(Path), Reason]),
+	    ?STATUS_ERROR;
+        {badrpc, Reason} ->
+            io:format("Can't load config file ~p: ~p~n",
+                      [filename:absname(Path), Reason]),
+	    ?STATUS_BADRPC
+    end;
+
+ctl_process(_Val, ["stats", Stat]) ->
+    Res = case Stat of
+	      "uptime-seconds" -> uptime_seconds();
+	      "registeredusers" -> length(ejabberd_auth:dirty_get_registered_users());
+	      "onlineusersnode" -> length(ejabberd_sm:dirty_get_my_sessions_list());
+	      "onlineusers" -> length(ejabberd_sm:dirty_get_sessions_list())
+	  end,
+    io:format("~p~n", [Res]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["status-num", Status_required]) ->
+    ctl_process(_Val, "all", ["status-num", Status_required]);
+
+ctl_process(_Val, ["status-list", Status_required]) ->
+    ctl_process(_Val, "all", ["status-list", Status_required]);
+
+ctl_process(_Val, ["get-cookie"]) ->
+    io:format("~s~n", [atom_to_list(erlang:get_cookie())]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, ["killsession", User, Server, Resource | Tail]) ->
+    kick_session(User, Server, Resource, prepare_reason(Tail)),
+    ?STATUS_SUCCESS;
+
+ctl_process(Val, _Args) ->
+    Val.
+
+
+%%-------------
+%% Commands vhost
+%%-------------
+
+ctl_process(_Val, Host, ["num-active-users", Days]) ->
+    Number = num_active_users(Host, list_to_integer(Days)),
+    io:format("~p~n", [Number]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, Host, ["stats", Stat]) ->
+    Res = case Stat of
+	      "registeredusers" -> length(ejabberd_auth:get_vh_registered_users(Host));
+	      "onlineusers" -> length(ejabberd_sm:get_vh_session_list(Host))
+	  end,
+    io:format("~p~n", [Res]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, Host, ["status-num", Status_required]) ->
+    Num = length(get_status_list(Host, Status_required)),
+    io:format("~p~n", [Num]),
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, Host, ["status-list", Status_required]) ->
+    Res = get_status_list(Host, Status_required),
+    [ io:format("~s@~s ~s ~p \"~s\"~n", [U, S, R, P, St]) || {U, S, R, P, St} <- Res],
+    ?STATUS_SUCCESS;
+
+ctl_process(_Val, Host, ["ban-account", User | Tail]) ->
+    ban_account(User, Host, prepare_reason(Tail)),
+    ?STATUS_SUCCESS;
+
+ctl_process(Val, _Host, _Args) ->
+    Val.
+
+
+%%-------------
+%% Utils
+%%-------------
+
+uptime_seconds() ->
+    trunc(element(1, erlang:statistics(wall_clock))/1000).
+
+get_status_list(Host, Status_required) ->
+    %% Get list of all logged users
+    Sessions = ejabberd_sm:dirty_get_my_sessions_list(),
+    %% Reformat the list
+    Sessions2 = [ {Session#session.usr, Session#session.sid, Session#session.priority} || Session <- Sessions],
+    Fhost = case Host of
+		"all" ->
+		    %% All hosts are requested, so dont filter at all
+		    fun(_, _) -> true end;
+		_ ->
+		    %% Filter the list, only Host is interesting
+		    fun(A, B) -> A == B end
+	    end,
+    Sessions3 = [ {Pid, Server, Priority} || {{_User, Server, _Resource}, {_, Pid}, Priority} <- Sessions2, apply(Fhost, [Server, Host])],
+    %% For each Pid, get its presence
+    Sessions4 = [ {ejabberd_c2s:get_presence(Pid), Server, Priority} || {Pid, Server, Priority} <- Sessions3],
+    %% Filter by status
+    Fstatus = case Status_required of
+		  "all" ->
+		      fun(_, _) -> true end;
+		  _ ->
+		      fun(A, B) -> A == B end
+	      end,
+    [{User, Server, Resource, Priority, stringize(Status_text)} 
+     || {{User, Resource, Status, Status_text}, Server, Priority} <- Sessions4, 
+	apply(Fstatus, [Status, Status_required])].
+
+%% Make string more print-friendly
+stringize(String) ->
+    %% Replace newline characters with other code
+    element(2, regexp:gsub(String, "\n", "\\n")).
+
+add_rosteritem(LU, LS, RU, RS, Nick, Group, Subscription, Xattrs) ->
+    subscribe(LU, LS, RU, RS, Nick, Group, Subscription, Xattrs),
+    route_rosteritem(LU, LS, RU, RS, Nick, Group, Subscription),
+    {atomic, ok}.
+
+subscribe(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subscription, Xattrs) ->
+    R = #roster{usj = {LocalUser,LocalServer,{RemoteUser,RemoteServer,[]}},
+		us = {LocalUser,LocalServer},
+		jid = {RemoteUser,RemoteServer,[]},
+		name = Nick,
+		subscription = Subscription, % none, to=you see him, from=he sees you, both
+		ask = none, % out=send request, in=somebody requests you, none
+		groups = [Group],
+		askmessage = Xattrs, % example: [{"category","conference"}]
+		xs = []},
+    mnesia:transaction(fun() -> mnesia:write(R) end).
+
+rem_rosteritem(LU, LS, RU, RS) ->
+    unsubscribe(LU, LS, RU, RS),
+    route_rosteritem(LU, LS, RU, RS, "", "", "remove"),
+    {atomic, ok}.
+
+unsubscribe(LocalUser, LocalServer, RemoteUser, RemoteServer) ->
+    Key = {{LocalUser,LocalServer,{RemoteUser,RemoteServer,[]}},
+	   {LocalUser,LocalServer}},
+    mnesia:transaction(fun() -> mnesia:delete(roster, Key, write) end).
+
+route_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subscription) ->
+    LJID = jlib:make_jid(LocalUser, LocalServer, ""),
+    RJID = jlib:make_jid(RemoteUser, RemoteServer, ""),
+    ToS = jlib:jid_to_string(LJID),
+    ItemJIDS = jlib:jid_to_string(RJID),
+    GroupXML = {xmlelement, "group", [], [{xmlcdata, Group}]},
+    Item = {xmlelement, "item", 
+	    [{"jid", ItemJIDS},
+	     {"name", Nick},
+	     {"subscription", Subscription}], 
+	    [GroupXML]},
+    Query = {xmlelement, "query", [{"xmlns", ?NS_ROSTER}], [Item]},
+    Packet = {xmlelement, "iq", [{"type", "set"}, {"to", ToS}], [Query]},
+    ejabberd_router:route(LJID, LJID, Packet).
+
+
+%%-----------------------------
+%% Ban user
+%%-----------------------------
+
+ban_account(User, Server, Reason) ->
+    kick_sessions(User, Server, Reason),
+    set_random_password(User, Server, Reason).
+
+kick_sessions(User, Server, Reason) ->
+    lists:map(
+      fun(Resource) ->
+	      kick_session(User, Server, Resource, Reason)
+      end,
+      get_resources(User, Server)).
+
+kick_session(User, Server, Resource, Reason) ->
+    ejabberd_router:route(
+      jlib:make_jid("", "", ""),
+      jlib:make_jid(User, Server, Resource),
+      {xmlelement, "broadcast", [], [{exit, Reason}]}).
+
+get_resources(User, Server) ->
+    lists:map(
+      fun(Session) ->
+	      element(3, Session#session.usr)
+      end,
+      get_sessions(User, Server)).
+
+get_sessions(User, Server) ->
+    LUser = jlib:nodeprep(User),
+    LServer = jlib:nameprep(Server),
+    Sessions =  mnesia:dirty_index_read(session, {LUser, LServer}, #session.us),
+    true = is_list(Sessions),
+    Sessions.
+
+set_random_password(User, Server, Reason) ->
+    NewPass = build_random_password(Reason),
+    set_password(User, Server, NewPass).
+
+build_random_password(Reason) ->
+    Date = jlib:timestamp_to_iso(calendar:universal_time()),
+    RandomString = randoms:get_string(),
+    "BANNED_ACCOUNT--" ++ Date ++ "--" ++ RandomString ++ "--" ++ Reason.
+
+set_password(User, Server, Password) ->
+    {atomic, ok} = ejabberd_auth:set_password(User, Server, Password).
+
+prepare_reason([]) ->
+    "Kicked by administrator";
+prepare_reason([Reason]) ->
+    Reason;
+prepare_reason(StringList) ->
+    string:join(StringList, "_").
+
+
+%%-----------------------------
+%% Purge roster items
+%%-----------------------------
+
+rosteritem_purge(Options) ->
+    Num_rosteritems = mnesia:table_info(roster, size),
+    io:format("There are ~p roster items in total.~n", [Num_rosteritems]),
+    Key = mnesia:dirty_first(roster),
+    ok = rip(Key, Options, {0, Num_rosteritems, 0, 0}),
+    {atomic, ok}.
+
+rip('$end_of_table', _Options, Counters) ->
+    print_progress_line(Counters),
+    ok;
+rip(Key, Options, {Pr, NT, NV, ND}) ->
+    Key_next = mnesia:dirty_next(roster, Key),
+    ND2 = case decide_rip(Key, Options) of
+	      true ->
+		  mnesia:dirty_delete(roster, Key),
+		  ND+1;
+	      false ->
+		  ND
+	  end,
+    NV2 = NV+1,
+    Pr2 = print_progress_line({Pr, NT, NV2, ND2}),
+    rip(Key_next, Options, {Pr2, NT, NV2, ND2}).
+
+print_progress_line({Pr, NT, NV, ND}) ->
+    Pr2 = trunc((NV/NT)*100),
+    case Pr == Pr2 of
+	true ->
+	    ok;
+	false ->
+	    io:format("Progress ~p% - visited ~p - deleted ~p~n", [Pr2, NV, ND])
+    end,
+    Pr2.
+
+decide_rip(Key, {Subs, Asks, User, Contact}) ->
+    case catch mnesia:dirty_read(roster, Key) of
+	[RI] ->
+	    lists:member(RI#roster.subscription, Subs)
+		andalso lists:member(RI#roster.ask, Asks)
+		andalso decide_rip_jid(RI#roster.us, User)
+		andalso decide_rip_jid(RI#roster.jid, Contact);
+	_ ->
+	    false
+    end.
+
+%% Returns true if the server of the JID is included in the servers
+decide_rip_jid({UName, UServer, _UResource}, Match_list) ->
+    decide_rip_jid({UName, UServer}, Match_list);
+decide_rip_jid({UName, UServer}, Match_list) ->
+    lists:any(
+      fun(Match_string) ->
+	      MJID = jlib:string_to_jid(Match_string),
+	      MName = MJID#jid.luser,
+	      MServer = MJID#jid.lserver,
+	      Is_server = is_glob_match(UServer, MServer),
+	      case MName of
+		  [] when UName == [] ->
+		      Is_server;
+		  [] ->
+		      false;
+		  _ ->
+		      Is_server
+			  andalso is_glob_match(UName, MName)
+	      end
+      end,
+      Match_list).
+
+%% Copied from ejabberd-2.0.0/src/acl.erl
+is_regexp_match(String, RegExp) ->
+    case regexp:first_match(String, RegExp) of
+	nomatch ->
+	    false;
+	{match, _, _} ->
+	    true;
+	{error, ErrDesc} ->
+	    io:format(
+	      "Wrong regexp ~p in ACL: ~p",
+	      [RegExp, lists:flatten(regexp:format_error(ErrDesc))]),
+	    false
+    end.
+is_glob_match(String, Glob) ->
+    is_regexp_match(String, regexp:sh_to_awk(Glob)).
+
+
+%%-----------------------------
+%% Push Roster from file 
+%%-----------------------------
+
+pushroster(File, User, Server) ->
+    {ok, [Roster]} = file:consult(File),
+    subscribe_roster({User, Server, "", User}, Roster).
+
+pushroster_all(File) ->
+    {ok, [Roster]} = file:consult(File),
+    subscribe_all(Roster).
+
+subscribe_all(Roster) ->
+    subscribe_all(Roster, Roster).
+subscribe_all([], _) ->
+    ok;
+subscribe_all([User1 | Users], Roster) ->
+    subscribe_roster(User1, Roster),
+    subscribe_all(Users, Roster).
+
+subscribe_roster(_, []) ->
+    ok;
+%% Do not subscribe a user to itself
+subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) ->
+    subscribe_roster({Name, Server, Group, Nick}, Roster);
+%% Subscribe Name2 to Name1
+subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
+    subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, both, []),
+    subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
+
+push_alltoall(S, G) ->
+    Users = ejabberd_auth:get_vh_registered_users(S),
+    Users2 = build_list_users(G, Users, []),
+    subscribe_all(Users2).
+
+build_list_users(_Group, [], Res) ->
+    Res;
+build_list_users(Group, [{User, Server}|Users], Res) ->
+    build_list_users(Group, Users, [{User, Server, Group, User}|Res]).
+
+vcard_get(User, Server, Data) ->
+    [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
+    JID = jlib:make_jid(User, Server, ""),
+    IQ = #iq{type = get, xmlns = ?NS_VCARD},
+    IQr = Module:Function(JID, JID, IQ),
+    Res = case IQr#iq.sub_el of
+	      [A1] ->
+		  case vcard_get(Data, A1) of
+		      false -> no_value;
+		      Elem -> xml:get_tag_cdata(Elem)
+		  end;
+	      [] -> 
+		  no_vcard
+	  end,
+    {ok, Res}.
+
+vcard_get([Data1, Data2], A1) ->
+    case xml:get_subtag(A1, Data1) of
+    	false -> false;
+	A2 -> vcard_get([Data2], A2)
+    end;
+
+vcard_get([Data], A1) ->
+    xml:get_subtag(A1, Data).
+
+vcard_set(User, Server, Data, Content) ->
+    [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
+    JID = jlib:make_jid(User, Server, ""),
+    IQ = #iq{type = get, xmlns = ?NS_VCARD},
+    IQr = Module:Function(JID, JID, IQ),
+
+    %% Get old vcard
+    A4 = case IQr#iq.sub_el of
+	     [A1] ->
+		 {_, _, _, A2} = A1,
+		 update_vcard_els(Data, Content, A2);
+	     [] -> 
+		 update_vcard_els(Data, Content, [])
+	 end,
+
+    %% Build new vcard
+    SubEl = {xmlelement, "vCard", [{"xmlns","vcard-temp"}], A4},
+    IQ2 = #iq{type=set, sub_el = SubEl},
+
+    Module:Function(JID, JID, IQ2),
+    {ok, "done"}.
+
+update_vcard_els(Data, Content, Els1) ->
+    Els2 = lists:keysort(2, Els1),
+    [Data1 | Data2] = Data,
+    NewEl = case Data2 of
+		[] ->
+		    {xmlelement, Data1, [], [{xmlcdata,Content}]};
+		[D2] ->
+		    OldEl = case lists:keysearch(Data1, 2, Els2) of
+				{value, A} -> A;
+				false -> {xmlelement, Data1, [], []}
+			    end,
+		    {xmlelement, _, _, ContentOld1} = OldEl,
+		    Content2 = [{xmlelement, D2, [], [{xmlcdata,Content}]}],
+		    ContentOld2 = lists:keysort(2, ContentOld1),
+		    ContentOld3 = lists:keydelete(D2, 2, ContentOld2),
+		    ContentNew = lists:keymerge(2, Content2, ContentOld3),
+		    {xmlelement, Data1, [], ContentNew}
+	    end,
+    Els3 = lists:keydelete(Data1, 2, Els2),
+    lists:keymerge(2, [NewEl], Els3).
+
+-record(last_activity, {us, timestamp, status}).
+
+delete_older_users(Days) ->
+    %% Get the list of registered users
+    Users = ejabberd_auth:dirty_get_registered_users(),
+    delete_older_users(Days, Users).
+
+delete_older_users_vhost(Host, Days) ->
+    %% Get the list of registered users
+    Users = ejabberd_auth:get_vh_registered_users(Host),
+    delete_older_users(Days, Users).
+
+delete_older_users(Days, Users) ->
+    %% Convert older time
+    SecOlder = Days*24*60*60,
+
+    %% Get current time
+    {MegaSecs, Secs, _MicroSecs} = now(),
+    TimeStamp_now = MegaSecs * 1000000 + Secs,
+
+    %% For a user, remove if required and answer true
+    F = fun({LUser, LServer}) ->
+		%% Check if the user is logged
+		case ejabberd_sm:get_user_resources(LUser, LServer) of
+		    %% If it isnt
+		    [] ->
+			%% Look for his last_activity
+			case mnesia:dirty_read(last_activity, {LUser, LServer}) of
+			    %% If it is
+			    %% existent:
+			    [#last_activity{timestamp = TimeStamp}] ->
+				%% get his age
+				Sec = TimeStamp_now - TimeStamp,
+				%% If he is
+				if 
+				    %% younger than SecOlder: 
+				    Sec < SecOlder ->
+					%% do nothing
+					false;
+				    %% older: 
+				    true ->
+					%% remove the user
+					ejabberd_auth:remove_user(LUser, LServer),
+					true
+				end;
+			    %% nonexistent:
+			    [] ->
+				%% remove the user
+				ejabberd_auth:remove_user(LUser, LServer),
+				true
+			end;
+		    %% Else
+		    _ ->
+			%% do nothing
+			false
+		end
+	end,
+    %% Apply the function to every user in the list
+    Users_removed = lists:filter(F, Users),
+    {removed, length(Users_removed), Users_removed}.
+
+num_active_users(Host, Days) ->
+    list_last_activity(Host, true, Days).
+
+%% Code based on ejabberd/src/web/ejabberd_web_admin.erl
+list_last_activity(Host, Integral, Days) ->
+    {MegaSecs, Secs, _MicroSecs} = now(),
+    TimeStamp = MegaSecs * 1000000 + Secs,
+    TS = TimeStamp - Days * 86400,
+    case catch mnesia:dirty_select(
+		 last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
+				  [{'>', '$1', TS}],
+				  [{'trunc', {'/',
+					      {'-', TimeStamp, '$1'},
+					      86400}}]}]) of
+							      {'EXIT', _Reason} ->
+		 [];
+	       Vals ->
+		 Hist = histogram(Vals, Integral),
+		 if
+		     Hist == [] ->
+			 0;
+		     true ->
+			 Left = if
+				    Days == infinity ->
+					0;
+				    true ->
+					Days - length(Hist)
+				end,
+			 Tail = if
+				    Integral ->
+					lists:duplicate(Left, lists:last(Hist));
+				    true ->
+					lists:duplicate(Left, 0)
+				end,
+			 lists:nth(Days, Hist ++ Tail)
+		 end
+	 end.
+histogram(Values, Integral) ->
+    histogram(lists:sort(Values), Integral, 0, 0, []).
+histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
+    histogram(T, Integral, Current, Count + 1, Hist);
+histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H ->
+    if
+	Integral ->
+	    histogram(Values, Integral, Current + 1, Count, [Count | Hist]);
+	true ->
+	    histogram(Values, Integral, Current + 1, 0, [Count | Hist])
+    end;
+histogram([], _Integral, _Current, Count, Hist) ->
+    if
+	Count > 0 ->
+	    lists:reverse([Count | Hist]);
+	true ->
+	    lists:reverse(Hist)
+    end.
+
+group_parameters(Ps, [Char]) ->
+	{none, Grouped_Ps} = lists:foldl(
+		fun(P, {State, Res}) ->
+			case State of
+				none -> 
+					case P of
+						[Char | PTail]->
+							{building, [PTail | Res]};
+						_ ->
+							{none, [P | Res]}
+					end;
+				building -> 
+					[ResHead | ResTail] = Res,
+					case lists:last(P) of
+						Char ->
+							P2 = lists:sublist(P, length(P)-1),
+							{none, [ResHead ++ " " ++ P2 | ResTail]};
+						_ ->
+							{building, [ResHead ++ " " ++ P | ResTail]}
+					end
+			end
+		end,
+		{none, []},
+		Ps),
+	lists:reverse(Grouped_Ps).
-- 
1.7.4.1