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