1 |
slords |
1.1 |
%%%---------------------------------------------------------------------- |
2 |
|
|
%%% File : mod_ctlextra.erl |
3 |
|
|
%%% Author : Badlop <badlop@ono.com> |
4 |
|
|
%%% Purpose : Adds more commands to ejabberd_ctl |
5 |
|
|
%%% Created : 30 Nov 2006 by Badlop <badlop@ono.com> |
6 |
|
|
%%% Id : $Id$ |
7 |
|
|
%%%---------------------------------------------------------------------- |
8 |
|
|
|
9 |
|
|
-module(mod_ctlextra). |
10 |
|
|
-author('badlop@ono.com'). |
11 |
|
|
-vsn('$Revision$ '). |
12 |
|
|
|
13 |
|
|
-behaviour(gen_mod). |
14 |
|
|
|
15 |
|
|
-export([start/2, |
16 |
|
|
stop/1, |
17 |
|
|
ctl_process/2, |
18 |
|
|
ctl_process/3 |
19 |
|
|
]). |
20 |
|
|
|
21 |
|
|
-include("ejabberd.hrl"). |
22 |
|
|
-include("ejabberd_ctl.hrl"). |
23 |
|
|
-include("jlib.hrl"). |
24 |
|
|
-include("mod_roster.hrl"). |
25 |
|
|
|
26 |
|
|
-record(session, {sid, usr, us, priority}). % copied from ejabberd_sm.erl |
27 |
|
|
|
28 |
|
|
start(Host, _Opts) -> |
29 |
|
|
ejabberd_ctl:register_commands([ |
30 |
|
|
{"compile file", "recompile and reload file"}, |
31 |
|
|
{"load-config file", "load config from file"}, |
32 |
|
|
{"remove-node nodename", "remove an ejabberd node from the database"}, |
33 |
|
|
|
34 |
|
|
%% ejabberd_auth |
35 |
|
|
{"delete-older-users days", "delete users that have not logged in the last 'days'"}, |
36 |
|
|
{"set-password user server password", "set password to user@server"}, |
37 |
|
|
|
38 |
|
|
%% ejd2odbc |
39 |
|
|
{"export2odbc server output", "export Mnesia tables on server to files on output directory"}, |
40 |
|
|
|
41 |
|
|
%% mod_offline |
42 |
|
|
{"delete-older-messages days", "delete offline messages older than 'days'"}, |
43 |
|
|
|
44 |
|
|
%% mod_shared_roster |
45 |
|
|
{"srg-create group host name description display", "create the group with options"}, |
46 |
|
|
{"srg-delete group host", "delete the group"}, |
47 |
|
|
{"srg-user-add user server group host", "add user@server to group on host"}, |
48 |
|
|
{"srg-user-del user server group host", "delete user@server from group on host"}, |
49 |
|
|
|
50 |
|
|
%% mod_vcard |
51 |
|
|
{"vcard-get user host data [data2]", "get data from the vCard of the user"}, |
52 |
|
|
{"vcard-set user host data [data2] content", "set data to content on the vCard"}, |
53 |
|
|
|
54 |
|
|
%% mod_announce |
55 |
|
|
%% announce_send_online host message |
56 |
|
|
%% announce_send_all host, message |
57 |
|
|
|
58 |
|
|
%% mod_muc |
59 |
|
|
%% muc-add room opts |
60 |
|
|
%% muc-del room |
61 |
|
|
{"muc-purge days", "destroy rooms with not activity on the last 'days'"}, |
62 |
|
|
{"muc-online-rooms", "list existing rooms"}, |
63 |
|
|
|
64 |
|
|
%% mod_roster |
65 |
|
|
{"add-rosteritem user1 server1 user2 server2 nick group subs", "Add user2@server2 to user1@server1's roster"}, |
66 |
|
|
%%{"", "subs= none, from, to or both"}, |
67 |
|
|
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"}, |
68 |
|
|
%%{"", "will add mike@server.com to peter@localhost roster"}, |
69 |
|
|
{"rem-rosteritem user1 server1 user2 server2", "Remove user2@server2 from user1@server1's roster"}, |
70 |
|
|
{"pushroster file user server", "push template roster in file to user@server"}, |
71 |
|
|
{"pushroster-all file", "push template roster in file to all those users"}, |
72 |
|
|
{"push-alltoall server group", "adds all the users to all the users in Group"}, |
73 |
|
|
|
74 |
|
|
{"status-list status", "list the logged users with status"}, |
75 |
|
|
{"status-num status", "number of logged users with status"}, |
76 |
|
|
|
77 |
|
|
{"stats registeredusers", "number of registered users"}, |
78 |
|
|
{"stats onlineusers", "number of logged users"}, |
79 |
|
|
{"stats uptime-seconds", "uptime of ejabberd node in seconds"}, |
80 |
|
|
|
81 |
|
|
%% misc |
82 |
|
|
{"get-cookie", "get the Erlang cookie of this node"}, |
83 |
|
|
{"killsession user server resource", "kill a user session"} |
84 |
|
|
], ?MODULE, ctl_process), |
85 |
|
|
ejabberd_ctl:register_commands(Host, [ |
86 |
|
|
%% mod_muc |
87 |
|
|
{"muc-purge days", "destroy rooms with not activity on the last 'days'"}, |
88 |
|
|
{"muc-online-rooms", "list existing rooms"}, |
89 |
|
|
|
90 |
|
|
%% mod_last |
91 |
|
|
{"num-active-users days", "number of users active in the last 'days'"}, |
92 |
|
|
{"status-list status", "list the logged users with status"}, |
93 |
|
|
{"status-num status", "number of logged users with status"}, |
94 |
|
|
{"stats registeredusers", "number of registered users"}, |
95 |
|
|
{"stats onlineusers", "number of logged users"} |
96 |
|
|
], ?MODULE, ctl_process), |
97 |
|
|
ok. |
98 |
|
|
|
99 |
|
|
stop(_Host) -> |
100 |
|
|
ok. |
101 |
|
|
|
102 |
|
|
|
103 |
|
|
ctl_process(_Val, ["blo"]) -> |
104 |
|
|
FResources = "eeeaaa aaa", |
105 |
|
|
io:format("~s", [FResources]), |
106 |
|
|
?STATUS_SUCCESS; |
107 |
|
|
|
108 |
|
|
ctl_process(_Val, ["delete-older-messages", Days]) -> |
109 |
|
|
mod_offline:remove_old_messages(list_to_integer(Days)), |
110 |
|
|
?STATUS_SUCCESS; |
111 |
|
|
|
112 |
|
|
ctl_process(_Val, ["delete-older-users", Days]) -> |
113 |
|
|
{removed, N, UR} = delete_older_users(list_to_integer(Days)), |
114 |
|
|
io:format("Deleted ~p users: ~p~n", [N, UR]), |
115 |
|
|
?STATUS_SUCCESS; |
116 |
|
|
|
117 |
|
|
ctl_process(_Val, ["export2odbc", Server, Output]) -> |
118 |
|
|
Tables = [ |
119 |
|
|
{export_last, last}, |
120 |
|
|
{export_offline, offline}, |
121 |
|
|
{export_passwd, passwd}, |
122 |
|
|
{export_private_storage, private_storage}, |
123 |
|
|
{export_roster, roster}, |
124 |
|
|
{export_vcard, vcard}, |
125 |
|
|
{export_vcard_search, vcard_search}], |
126 |
|
|
Export = fun({TableFun, Table}) -> |
127 |
|
|
Filename = filename:join([Output, atom_to_list(Table)++".txt"]), |
128 |
|
|
io:format("Trying to export Mnesia table '~p' on server '~s' to file '~s'~n", [Table, Server, Filename]), |
129 |
|
|
catch ejd2odbc:TableFun(Server, Filename) |
130 |
|
|
end, |
131 |
|
|
lists:foreach(Export, Tables), |
132 |
|
|
?STATUS_SUCCESS; |
133 |
|
|
|
134 |
|
|
ctl_process(_Val, ["set-password", User, Server, Password]) -> |
135 |
|
|
ejabberd_auth:set_password(User, Server, Password), |
136 |
|
|
?STATUS_SUCCESS; |
137 |
|
|
|
138 |
|
|
ctl_process(_Val, ["vcard-get", User, Server, Data]) -> |
139 |
|
|
{ok, Res} = vcard_get(User, Server, [Data]), |
140 |
|
|
io:format("~s~n", [Res]), |
141 |
|
|
?STATUS_SUCCESS; |
142 |
|
|
|
143 |
|
|
ctl_process(_Val, ["vcard-get", User, Server, Data1, Data2]) -> |
144 |
|
|
{ok, Res} = vcard_get(User, Server, [Data1, Data2]), |
145 |
|
|
io:format("~s~n", [Res]), |
146 |
|
|
?STATUS_SUCCESS; |
147 |
|
|
|
148 |
|
|
ctl_process(_Val, ["vcard-set", User, Server, Data1, Content]) -> |
149 |
|
|
{ok, Res} = vcard_set(User, Server, [Data1], Content), |
150 |
|
|
io:format("~s~n", [Res]), |
151 |
|
|
?STATUS_SUCCESS; |
152 |
|
|
|
153 |
|
|
ctl_process(_Val, ["vcard-set", User, Server, Data1, Data2, Content]) -> |
154 |
|
|
{ok, Res} = vcard_set(User, Server, [Data1, Data2], Content), |
155 |
|
|
io:format("~s~n", [Res]), |
156 |
|
|
?STATUS_SUCCESS; |
157 |
|
|
|
158 |
|
|
ctl_process(_Val, ["compile", Module]) -> |
159 |
|
|
compile:file(Module), |
160 |
|
|
?STATUS_SUCCESS; |
161 |
|
|
|
162 |
|
|
ctl_process(_Val, ["remove-node", Node]) -> |
163 |
|
|
mnesia:del_table_copy(schema, list_to_atom(Node)), |
164 |
|
|
?STATUS_SUCCESS; |
165 |
|
|
|
166 |
|
|
ctl_process(_Val, ["srg-create", Group, Host, Name, Description, Display]) -> |
167 |
|
|
Opts = [{name, Name}, {displayed_groups, [Display]}, {description, Description}], |
168 |
|
|
{atomic, ok} = mod_shared_roster:create_group(Host, Group, Opts), |
169 |
|
|
?STATUS_SUCCESS; |
170 |
|
|
|
171 |
|
|
ctl_process(_Val, ["srg-delete", Group, Host]) -> |
172 |
|
|
{atomic, ok} = mod_shared_roster:delete_group(Host, Group), |
173 |
|
|
?STATUS_SUCCESS; |
174 |
|
|
|
175 |
|
|
ctl_process(_Val, ["srg-user-add", User, Server, Group, Host]) -> |
176 |
|
|
{atomic, ok} = mod_shared_roster:add_user_to_group(Host, {User, Server}, Group), |
177 |
|
|
?STATUS_SUCCESS; |
178 |
|
|
|
179 |
|
|
ctl_process(_Val, ["srg-user-del", User, Server, Group, Host]) -> |
180 |
|
|
{atomic, ok} = mod_shared_roster:remove_user_from_group(Host, {User, Server}, Group), |
181 |
|
|
?STATUS_SUCCESS; |
182 |
|
|
|
183 |
|
|
ctl_process(_Val, ["muc-purge", Days]) -> |
184 |
|
|
{purged, Num_total, Num_purged, Names_purged} = muc_purge(list_to_integer(Days)), |
185 |
|
|
io:format("Purged ~p chatrooms from a total of ~p on the server:~n~p~n", [Num_purged, Num_total, Names_purged]), |
186 |
|
|
?STATUS_SUCCESS; |
187 |
|
|
|
188 |
|
|
ctl_process(_Val, ["muc-online-rooms"]) -> |
189 |
|
|
format_print_room(all, ets:tab2list(muc_online_room)), |
190 |
|
|
?STATUS_SUCCESS; |
191 |
|
|
|
192 |
|
|
ctl_process(_Val, ["add-rosteritem", LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subs]) -> |
193 |
|
|
case add_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, list_to_atom(Subs), []) of |
194 |
|
|
{atomic, ok} -> |
195 |
|
|
?STATUS_SUCCESS; |
196 |
|
|
{error, Reason} -> |
197 |
|
|
io:format("Can't add ~p@~p to ~p@~p: ~p~n", |
198 |
|
|
[RemoteUser, RemoteServer, LocalUser, LocalServer, Reason]), |
199 |
|
|
?STATUS_ERROR; |
200 |
|
|
{badrpc, Reason} -> |
201 |
|
|
io:format("Can't add roster item to user ~p: ~p~n", |
202 |
|
|
[LocalUser, Reason]), |
203 |
|
|
?STATUS_BADRPC |
204 |
|
|
end; |
205 |
|
|
|
206 |
|
|
ctl_process(_Val, ["rem-rosteritem", LocalUser, LocalServer, RemoteUser, RemoteServer]) -> |
207 |
|
|
case rem_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer) of |
208 |
|
|
{atomic, ok} -> |
209 |
|
|
?STATUS_SUCCESS; |
210 |
|
|
{error, Reason} -> |
211 |
|
|
io:format("Can't remove ~p@~p to ~p@~p: ~p~n", |
212 |
|
|
[RemoteUser, RemoteServer, LocalUser, LocalServer, Reason]), |
213 |
|
|
?STATUS_ERROR; |
214 |
|
|
{badrpc, Reason} -> |
215 |
|
|
io:format("Can't remove roster item to user ~p: ~p~n", |
216 |
|
|
[LocalUser, Reason]), |
217 |
|
|
?STATUS_BADRPC |
218 |
|
|
end; |
219 |
|
|
|
220 |
|
|
ctl_process(_Val, ["pushroster", File, User, Server]) -> |
221 |
|
|
case pushroster(File, User, Server) of |
222 |
|
|
ok -> |
223 |
|
|
?STATUS_SUCCESS; |
224 |
|
|
{error, Reason} -> |
225 |
|
|
io:format("Can't push roster ~p to ~p@~p: ~p~n", |
226 |
|
|
[File, User, Server, Reason]), |
227 |
|
|
?STATUS_ERROR; |
228 |
|
|
{badrpc, Reason} -> |
229 |
|
|
io:format("Can't push roster ~p: ~p~n", |
230 |
|
|
[File, Reason]), |
231 |
|
|
?STATUS_BADRPC |
232 |
|
|
end; |
233 |
|
|
|
234 |
|
|
ctl_process(_Val, ["pushroster-all", File]) -> |
235 |
|
|
case pushroster_all([File]) of |
236 |
|
|
ok -> |
237 |
|
|
?STATUS_SUCCESS; |
238 |
|
|
{error, Reason} -> |
239 |
|
|
io:format("Can't push roster ~p: ~p~n", |
240 |
|
|
[File, Reason]), |
241 |
|
|
?STATUS_ERROR; |
242 |
|
|
{badrpc, Reason} -> |
243 |
|
|
io:format("Can't push roster ~p: ~p~n", |
244 |
|
|
[File, Reason]), |
245 |
|
|
?STATUS_BADRPC |
246 |
|
|
end; |
247 |
|
|
|
248 |
|
|
ctl_process(_Val, ["push-alltoall", Server, Group]) -> |
249 |
|
|
case push_alltoall(Server, Group) of |
250 |
|
|
ok -> |
251 |
|
|
?STATUS_SUCCESS; |
252 |
|
|
{error, Reason} -> |
253 |
|
|
io:format("Can't push all to all: ~p~n", |
254 |
|
|
[Reason]), |
255 |
|
|
?STATUS_ERROR; |
256 |
|
|
{badrpc, Reason} -> |
257 |
|
|
io:format("Can't push all to all: ~p~n", |
258 |
|
|
[Reason]), |
259 |
|
|
?STATUS_BADRPC |
260 |
|
|
end; |
261 |
|
|
|
262 |
|
|
ctl_process(_Val, ["load-config", Path]) -> |
263 |
|
|
case ejabberd_config:load_file(Path) of |
264 |
|
|
{atomic, ok} -> |
265 |
|
|
?STATUS_SUCCESS; |
266 |
|
|
{error, Reason} -> |
267 |
|
|
io:format("Can't load config file ~p: ~p~n", |
268 |
|
|
[filename:absname(Path), Reason]), |
269 |
|
|
?STATUS_ERROR; |
270 |
|
|
{badrpc, Reason} -> |
271 |
|
|
io:format("Can't load config file ~p: ~p~n", |
272 |
|
|
[filename:absname(Path), Reason]), |
273 |
|
|
?STATUS_BADRPC |
274 |
|
|
end; |
275 |
|
|
|
276 |
|
|
ctl_process(_Val, ["stats", Stat]) -> |
277 |
|
|
Res = case Stat of |
278 |
|
|
"uptime-seconds" -> uptime_seconds(); |
279 |
|
|
"registeredusers" -> mnesia:table_info(passwd, size); |
280 |
|
|
"onlineusers" -> mnesia:table_info(session, size) |
281 |
|
|
end, |
282 |
|
|
io:format("~p~n", [Res]), |
283 |
|
|
?STATUS_SUCCESS; |
284 |
|
|
|
285 |
|
|
ctl_process(_Val, ["status-num", Status_required]) -> |
286 |
|
|
ctl_process(_Val, "all", ["status-num", Status_required]); |
287 |
|
|
|
288 |
|
|
ctl_process(_Val, ["status-list", Status_required]) -> |
289 |
|
|
ctl_process(_Val, "all", ["status-list", Status_required]); |
290 |
|
|
|
291 |
|
|
ctl_process(_Val, ["get-cookie"]) -> |
292 |
|
|
io:format("~s~n", [atom_to_list(erlang:get_cookie())]), |
293 |
|
|
?STATUS_SUCCESS; |
294 |
|
|
|
295 |
|
|
ctl_process(_Val, ["killsession", User, Server, Resource]) -> |
296 |
|
|
ejabberd_router:route( |
297 |
|
|
jlib:make_jid("", "", ""), |
298 |
|
|
jlib:make_jid(User, Server, Resource), |
299 |
|
|
{xmlelement, "broadcast", [], [{exit, "killed"}]}), |
300 |
|
|
?STATUS_SUCCESS; |
301 |
|
|
|
302 |
|
|
ctl_process(Val, _Args) -> |
303 |
|
|
Val. |
304 |
|
|
|
305 |
|
|
|
306 |
|
|
ctl_process(_Val, Host, ["muc-purge", Days]) -> |
307 |
|
|
{purged, Num_total, Num_purged, Names_purged} = muc_purge(Host, list_to_integer(Days)), |
308 |
|
|
io:format("Purged ~p chatrooms from a total of ~p on the host ~p:~n~p~n", [Num_purged, Num_total, Host, Names_purged]), |
309 |
|
|
?STATUS_SUCCESS; |
310 |
|
|
|
311 |
|
|
ctl_process(_Val, ServerHost, ["muc-online-rooms"]) -> |
312 |
|
|
MUCHost = find_host(ServerHost), |
313 |
|
|
format_print_room(MUCHost, ets:tab2list(muc_online_room)), |
314 |
|
|
?STATUS_SUCCESS; |
315 |
|
|
|
316 |
|
|
ctl_process(_Val, Host, ["num-active-users", Days]) -> |
317 |
|
|
Number = num_active_users(Host, list_to_integer(Days)), |
318 |
|
|
io:format("~p~n", [Number]), |
319 |
|
|
?STATUS_SUCCESS; |
320 |
|
|
|
321 |
|
|
ctl_process(_Val, Host, ["stats", Stat]) -> |
322 |
|
|
Res = case Stat of |
323 |
|
|
"registeredusers" -> length(ejabberd_auth:get_vh_registered_users(Host)); |
324 |
|
|
"onlineusers" -> length(ejabberd_sm:get_vh_session_list(Host)) |
325 |
|
|
end, |
326 |
|
|
io:format("~p~n", [Res]), |
327 |
|
|
?STATUS_SUCCESS; |
328 |
|
|
|
329 |
|
|
ctl_process(_Val, Host, ["status-num", Status_required]) -> |
330 |
|
|
Num = length(get_status_list(Host, Status_required)), |
331 |
|
|
io:format("~p~n", [Num]), |
332 |
|
|
?STATUS_SUCCESS; |
333 |
|
|
|
334 |
|
|
ctl_process(_Val, Host, ["status-list", Status_required]) -> |
335 |
|
|
Res = get_status_list(Host, Status_required), |
336 |
|
|
[ io:format("~s@~s ~s ~p \"~s\"~n", [U, S, R, P, St]) || {U, S, R, P, St} <- Res], |
337 |
|
|
?STATUS_SUCCESS; |
338 |
|
|
|
339 |
|
|
ctl_process(Val, _Host, _Args) -> |
340 |
|
|
Val. |
341 |
|
|
|
342 |
|
|
|
343 |
|
|
%%------------- |
344 |
|
|
%% UTILS |
345 |
|
|
%%------------- |
346 |
|
|
|
347 |
|
|
get_status_list(Host, Status_required) -> |
348 |
|
|
%% Get list of all logged users |
349 |
|
|
Sessions = ejabberd_sm:dirty_get_my_sessions_list(), |
350 |
|
|
%% Reformat the list |
351 |
|
|
Sessions2 = [ {Session#session.usr, Session#session.sid, Session#session.priority} || Session <- Sessions], |
352 |
|
|
Fhost = case Host of |
353 |
|
|
"all" -> |
354 |
|
|
%% All hosts are requested, so dont filter at all |
355 |
|
|
fun(_, _) -> true end; |
356 |
|
|
_ -> |
357 |
|
|
%% Filter the list, only Host is interesting |
358 |
|
|
fun(A, B) -> A == B end |
359 |
|
|
end, |
360 |
|
|
Sessions3 = [ {Pid, Server, Priority} || {{_User, Server, _Resource}, {_, Pid}, Priority} <- Sessions2, apply(Fhost, [Server, Host])], |
361 |
|
|
%% For each Pid, get its presence |
362 |
|
|
Sessions4 = [ {ejabberd_c2s:get_presence(Pid), Server, Priority} || {Pid, Server, Priority} <- Sessions3], |
363 |
|
|
%% Filter by status |
364 |
|
|
Fstatus = case Status_required of |
365 |
|
|
"all" -> |
366 |
|
|
fun(_, _) -> true end; |
367 |
|
|
_ -> |
368 |
|
|
fun(A, B) -> A == B end |
369 |
|
|
end, |
370 |
|
|
[{User, Server, Resource, Priority, stringize(Status_text)} |
371 |
|
|
|| {{User, Resource, Status, Status_text}, Server, Priority} <- Sessions4, |
372 |
|
|
apply(Fstatus, [Status, Status_required])]. |
373 |
|
|
|
374 |
|
|
%% Make string more print-friendly |
375 |
|
|
stringize(String) -> |
376 |
|
|
%% Replace newline characters with other code |
377 |
|
|
element(2, regexp:gsub(String, "\n", "\\n")). |
378 |
|
|
|
379 |
|
|
add_rosteritem(LU, LS, RU, RS, Nick, Group, Subscription, Xattrs) -> |
380 |
|
|
subscribe(LU, LS, RU, RS, Nick, Group, Subscription, Xattrs), |
381 |
|
|
route_rosteritem(LU, LS, RU, RS, Nick, Group, Subscription), |
382 |
|
|
{atomic, ok}. |
383 |
|
|
|
384 |
|
|
subscribe(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subscription, Xattrs) -> |
385 |
|
|
R = #roster{usj = {LocalUser,LocalServer,{RemoteUser,RemoteServer,[]}}, |
386 |
|
|
us = {LocalUser,LocalServer}, |
387 |
|
|
jid = {RemoteUser,RemoteServer,[]}, |
388 |
|
|
name = Nick, |
389 |
|
|
subscription = Subscription, % none, to=you see him, from=he sees you, both |
390 |
|
|
ask = none, % out=send request, in=somebody requests you, none |
391 |
|
|
groups = [Group], |
392 |
|
|
askmessage = Xattrs, % example: [{"category","conference"}] |
393 |
|
|
xs = []}, |
394 |
|
|
mnesia:transaction(fun() -> mnesia:write(R) end). |
395 |
|
|
|
396 |
|
|
rem_rosteritem(LU, LS, RU, RS) -> |
397 |
|
|
unsubscribe(LU, LS, RU, RS), |
398 |
|
|
route_rosteritem(LU, LS, RU, RS, "", "", "remove"), |
399 |
|
|
{atomic, ok}. |
400 |
|
|
|
401 |
|
|
unsubscribe(LocalUser, LocalServer, RemoteUser, RemoteServer) -> |
402 |
|
|
Key = {{LocalUser,LocalServer,{RemoteUser,RemoteServer,[]}}, |
403 |
|
|
{LocalUser,LocalServer}}, |
404 |
|
|
mnesia:transaction(fun() -> mnesia:delete(roster, Key, write) end). |
405 |
|
|
|
406 |
|
|
route_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subscription) -> |
407 |
|
|
LJID = jlib:make_jid(LocalUser, LocalServer, ""), |
408 |
|
|
RJID = jlib:make_jid(RemoteUser, RemoteServer, ""), |
409 |
|
|
ToS = jlib:jid_to_string(LJID), |
410 |
|
|
ItemJIDS = jlib:jid_to_string(RJID), |
411 |
|
|
GroupXML = {xmlelement, "group", [], [{xmlcdata, Group}]}, |
412 |
|
|
Item = {xmlelement, "item", |
413 |
|
|
[{"jid", ItemJIDS}, |
414 |
|
|
{"name", Nick}, |
415 |
|
|
{"subscription", Subscription}], |
416 |
|
|
[GroupXML]}, |
417 |
|
|
Query = {xmlelement, "query", [{"xmlns", ?NS_ROSTER}], [Item]}, |
418 |
|
|
Packet = {xmlelement, "iq", [{"type", "set"}, {"to", ToS}], [Query]}, |
419 |
|
|
ejabberd_router:route(LJID, LJID, Packet). |
420 |
|
|
|
421 |
|
|
pushroster(File, User, Server) -> |
422 |
|
|
{ok, [Roster]} = file:consult(File), |
423 |
|
|
subscribe_roster({User, Server, "", User}, Roster). |
424 |
|
|
|
425 |
|
|
pushroster_all(File) -> |
426 |
|
|
{ok, [Roster]} = file:consult(File), |
427 |
|
|
subscribe_all(Roster). |
428 |
|
|
|
429 |
|
|
subscribe_all(Roster) -> |
430 |
|
|
subscribe_all(Roster, Roster). |
431 |
|
|
subscribe_all([], _) -> |
432 |
|
|
ok; |
433 |
|
|
subscribe_all([User1 | Users], Roster) -> |
434 |
|
|
subscribe_roster(User1, Roster), |
435 |
|
|
subscribe_all(Users, Roster). |
436 |
|
|
|
437 |
|
|
subscribe_roster(_, []) -> |
438 |
|
|
ok; |
439 |
|
|
%% Do not subscribe a user to itself |
440 |
|
|
subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) -> |
441 |
|
|
subscribe_roster({Name, Server, Group, Nick}, Roster); |
442 |
|
|
%% Subscribe Name2 to Name1 |
443 |
|
|
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) -> |
444 |
|
|
subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, both, []), |
445 |
|
|
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster). |
446 |
|
|
|
447 |
|
|
push_alltoall(S, G) -> |
448 |
|
|
Users = ejabberd_auth:get_vh_registered_users(S), |
449 |
|
|
Users2 = build_list_users(G, Users, []), |
450 |
|
|
subscribe_all(Users2). |
451 |
|
|
|
452 |
|
|
build_list_users(_Group, [], Res) -> |
453 |
|
|
Res; |
454 |
|
|
build_list_users(Group, [{User, Server}|Users], Res) -> |
455 |
|
|
build_list_users(Group, Users, [{User, Server, Group, User}|Res]). |
456 |
|
|
|
457 |
|
|
vcard_get(User, Server, Data) -> |
458 |
|
|
[{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}), |
459 |
|
|
JID = jlib:make_jid(User, Server, ""), |
460 |
|
|
IQ = #iq{type = get, xmlns = ?NS_VCARD}, |
461 |
|
|
IQr = Module:Function(JID, JID, IQ), |
462 |
|
|
Res = case IQr#iq.sub_el of |
463 |
|
|
[A1] -> |
464 |
|
|
case vcard_get(Data, A1) of |
465 |
|
|
false -> no_value; |
466 |
|
|
Elem -> xml:get_tag_cdata(Elem) |
467 |
|
|
end; |
468 |
|
|
[] -> |
469 |
|
|
no_vcard |
470 |
|
|
end, |
471 |
|
|
{ok, Res}. |
472 |
|
|
|
473 |
|
|
vcard_get([Data1, Data2], A1) -> |
474 |
|
|
case xml:get_subtag(A1, Data1) of |
475 |
|
|
false -> false; |
476 |
|
|
A2 -> vcard_get([Data2], A2) |
477 |
|
|
end; |
478 |
|
|
|
479 |
|
|
vcard_get([Data], A1) -> |
480 |
|
|
xml:get_subtag(A1, Data). |
481 |
|
|
|
482 |
|
|
vcard_set(User, Server, Data, Content) -> |
483 |
|
|
[{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}), |
484 |
|
|
JID = jlib:make_jid(User, Server, ""), |
485 |
|
|
IQ = #iq{type = get, xmlns = ?NS_VCARD}, |
486 |
|
|
IQr = Module:Function(JID, JID, IQ), |
487 |
|
|
|
488 |
|
|
%% Get old vcard |
489 |
|
|
A4 = case IQr#iq.sub_el of |
490 |
|
|
[A1] -> |
491 |
|
|
{_, _, _, A2} = A1, |
492 |
|
|
update_vcard_els(Data, Content, A2); |
493 |
|
|
[] -> |
494 |
|
|
update_vcard_els(Data, Content, []) |
495 |
|
|
end, |
496 |
|
|
|
497 |
|
|
%% Build new vcard |
498 |
|
|
SubEl = {xmlelement, "vCard", [{"xmlns","vcard-temp"}], A4}, |
499 |
|
|
IQ2 = #iq{type=set, sub_el = SubEl}, |
500 |
|
|
|
501 |
|
|
Module:Function(JID, JID, IQ2), |
502 |
|
|
{ok, "done"}. |
503 |
|
|
|
504 |
|
|
update_vcard_els(Data, Content, Els1) -> |
505 |
|
|
Els2 = lists:keysort(2, Els1), |
506 |
|
|
[Data1 | Data2] = Data, |
507 |
|
|
NewEl = case Data2 of |
508 |
|
|
[] -> |
509 |
|
|
{xmlelement, Data1, [], [{xmlcdata,Content}]}; |
510 |
|
|
[D2] -> |
511 |
|
|
OldEl = case lists:keysearch(Data1, 2, Els2) of |
512 |
|
|
{value, A} -> A; |
513 |
|
|
false -> {xmlelement, Data1, [], []} |
514 |
|
|
end, |
515 |
|
|
{xmlelement, _, _, ContentOld1} = OldEl, |
516 |
|
|
Content2 = [{xmlelement, D2, [], [{xmlcdata,Content}]}], |
517 |
|
|
ContentOld2 = lists:keysort(2, ContentOld1), |
518 |
|
|
ContentOld3 = lists:keydelete(D2, 2, ContentOld2), |
519 |
|
|
ContentNew = lists:keymerge(2, Content2, ContentOld3), |
520 |
|
|
{xmlelement, Data1, [], ContentNew} |
521 |
|
|
end, |
522 |
|
|
Els3 = lists:keydelete(Data1, 2, Els2), |
523 |
|
|
lists:keymerge(2, [NewEl], Els3). |
524 |
|
|
|
525 |
|
|
-record(last_activity, {us, timestamp, status}). |
526 |
|
|
|
527 |
|
|
delete_older_users(Days) -> |
528 |
|
|
%% Convert older time |
529 |
|
|
SecOlder = Days*24*60*60, |
530 |
|
|
|
531 |
|
|
%% Get current time |
532 |
|
|
{MegaSecs, Secs, _MicroSecs} = now(), |
533 |
|
|
TimeStamp_now = MegaSecs * 1000000 + Secs, |
534 |
|
|
|
535 |
|
|
%% Get the list of registered users |
536 |
|
|
Users = ejabberd_auth:dirty_get_registered_users(), |
537 |
|
|
|
538 |
|
|
%% For a user, remove if required and answer true |
539 |
|
|
F = fun({LUser, LServer}) -> |
540 |
|
|
%% Check if the user is logged |
541 |
|
|
case ejabberd_sm:get_user_resources(LUser, LServer) of |
542 |
|
|
%% If it isnt |
543 |
|
|
[] -> |
544 |
|
|
%% Look for his last_activity |
545 |
|
|
case mnesia:dirty_read(last_activity, {LUser, LServer}) of |
546 |
|
|
%% If it is |
547 |
|
|
%% existent: |
548 |
|
|
[#last_activity{timestamp = TimeStamp}] -> |
549 |
|
|
%% get his age |
550 |
|
|
Sec = TimeStamp_now - TimeStamp, |
551 |
|
|
%% If he is |
552 |
|
|
if |
553 |
|
|
%% younger than SecOlder: |
554 |
|
|
Sec < SecOlder -> |
555 |
|
|
%% do nothing |
556 |
|
|
false; |
557 |
|
|
%% older: |
558 |
|
|
true -> |
559 |
|
|
%% remove the user |
560 |
|
|
ejabberd_auth:remove_user(LUser, LServer), |
561 |
|
|
true |
562 |
|
|
end; |
563 |
|
|
%% nonexistent: |
564 |
|
|
[] -> |
565 |
|
|
%% remove the user |
566 |
|
|
ejabberd_auth:remove_user(LUser, LServer), |
567 |
|
|
true |
568 |
|
|
end; |
569 |
|
|
%% Else |
570 |
|
|
_ -> |
571 |
|
|
%% do nothing |
572 |
|
|
false |
573 |
|
|
end |
574 |
|
|
end, |
575 |
|
|
%% Apply the function to every user in the list |
576 |
|
|
Users_removed = lists:filter(F, Users), |
577 |
|
|
{removed, length(Users_removed), Users_removed}. |
578 |
|
|
|
579 |
|
|
num_active_users(Host, Days) -> |
580 |
|
|
list_last_activity(Host, true, Days). |
581 |
|
|
|
582 |
|
|
%% Code based on ejabberd/src/web/ejabberd_web_admin.erl |
583 |
|
|
list_last_activity(Host, Integral, Days) -> |
584 |
|
|
{MegaSecs, Secs, _MicroSecs} = now(), |
585 |
|
|
TimeStamp = MegaSecs * 1000000 + Secs, |
586 |
|
|
TS = TimeStamp - Days * 86400, |
587 |
|
|
case catch mnesia:dirty_select( |
588 |
|
|
last_activity, [{{last_activity, {'_', Host}, '$1', '_'}, |
589 |
|
|
[{'>', '$1', TS}], |
590 |
|
|
[{'trunc', {'/', |
591 |
|
|
{'-', TimeStamp, '$1'}, |
592 |
|
|
86400}}]}]) of |
593 |
|
|
{'EXIT', _Reason} -> |
594 |
|
|
[]; |
595 |
|
|
Vals -> |
596 |
|
|
Hist = histogram(Vals, Integral), |
597 |
|
|
if |
598 |
|
|
Hist == [] -> |
599 |
|
|
0; |
600 |
|
|
true -> |
601 |
|
|
Left = if |
602 |
|
|
Days == infinity -> |
603 |
|
|
0; |
604 |
|
|
true -> |
605 |
|
|
Days - length(Hist) |
606 |
|
|
end, |
607 |
|
|
Tail = if |
608 |
|
|
Integral -> |
609 |
|
|
lists:duplicate(Left, lists:last(Hist)); |
610 |
|
|
true -> |
611 |
|
|
lists:duplicate(Left, 0) |
612 |
|
|
end, |
613 |
|
|
lists:nth(Days, Hist ++ Tail) |
614 |
|
|
end |
615 |
|
|
end. |
616 |
|
|
histogram(Values, Integral) -> |
617 |
|
|
histogram(lists:sort(Values), Integral, 0, 0, []). |
618 |
|
|
histogram([H | T], Integral, Current, Count, Hist) when Current == H -> |
619 |
|
|
histogram(T, Integral, Current, Count + 1, Hist); |
620 |
|
|
histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H -> |
621 |
|
|
if |
622 |
|
|
Integral -> |
623 |
|
|
histogram(Values, Integral, Current + 1, Count, [Count | Hist]); |
624 |
|
|
true -> |
625 |
|
|
histogram(Values, Integral, Current + 1, 0, [Count | Hist]) |
626 |
|
|
end; |
627 |
|
|
histogram([], _Integral, _Current, Count, Hist) -> |
628 |
|
|
if |
629 |
|
|
Count > 0 -> |
630 |
|
|
lists:reverse([Count | Hist]); |
631 |
|
|
true -> |
632 |
|
|
lists:reverse(Hist) |
633 |
|
|
end. |
634 |
|
|
|
635 |
|
|
|
636 |
|
|
format_print_room(Host1, Rooms)-> |
637 |
|
|
lists:foreach( |
638 |
|
|
fun({_, {Roomname, Host},_}) -> |
639 |
|
|
case Host1 of |
640 |
|
|
all -> |
641 |
|
|
io:format("~s ~s ~n", [Roomname, Host]); |
642 |
|
|
Host -> |
643 |
|
|
io:format("~s ~s ~n", [Roomname, Host]); |
644 |
|
|
_ -> |
645 |
|
|
ok |
646 |
|
|
end |
647 |
|
|
end, |
648 |
|
|
Rooms). |
649 |
|
|
|
650 |
|
|
|
651 |
|
|
%%---------------------------- |
652 |
|
|
%% Purge MUC |
653 |
|
|
%%---------------------------- |
654 |
|
|
|
655 |
|
|
%% Required for muc_purge |
656 |
|
|
%% Copied from mod_muc/mod_muc_room.erl |
657 |
|
|
-define(DICT, dict). |
658 |
|
|
-record(muc_online_room, {name_host, pid}). |
659 |
|
|
-record(lqueue, {queue, len, max}). |
660 |
|
|
-record(config, {title = "", |
661 |
|
|
allow_change_subj = true, |
662 |
|
|
allow_query_users = true, |
663 |
|
|
allow_private_messages = true, |
664 |
|
|
public = true, |
665 |
|
|
public_list = true, |
666 |
|
|
persistent = false, |
667 |
|
|
moderated = false, % TODO |
668 |
|
|
members_by_default = true, |
669 |
|
|
members_only = false, |
670 |
|
|
allow_user_invites = false, |
671 |
|
|
password_protected = false, |
672 |
|
|
password = "", |
673 |
|
|
anonymous = true, |
674 |
|
|
logging = false |
675 |
|
|
}). |
676 |
|
|
-record(state, {room, |
677 |
|
|
host, |
678 |
|
|
server_host, |
679 |
|
|
access, |
680 |
|
|
jid, |
681 |
|
|
config = #config{}, |
682 |
|
|
users, |
683 |
|
|
affiliations, |
684 |
|
|
history, |
685 |
|
|
subject = "", |
686 |
|
|
subject_author = "", |
687 |
|
|
just_created = false}). |
688 |
|
|
|
689 |
|
|
muc_purge(Days) -> |
690 |
|
|
ServerHost = global, |
691 |
|
|
Host = global, |
692 |
|
|
muc_purge2(ServerHost, Host, Days). |
693 |
|
|
|
694 |
|
|
muc_purge(ServerHost, Days) -> |
695 |
|
|
Host = find_host(ServerHost), |
696 |
|
|
muc_purge2(ServerHost, Host, Days). |
697 |
|
|
|
698 |
|
|
muc_purge2(ServerHost, Host, Last_allowed) -> |
699 |
|
|
Room_names = get_room_names(Host), |
700 |
|
|
|
701 |
|
|
Decide = fun(N) -> decide(N, Last_allowed) end, |
702 |
|
|
Rooms_to_delete = lists:filter(Decide, Room_names), |
703 |
|
|
|
704 |
|
|
Num_rooms = length(Room_names), |
705 |
|
|
Num_rooms_to_delete = length(Rooms_to_delete), |
706 |
|
|
|
707 |
|
|
Rooms_to_delete_full = fill_serverhost(Rooms_to_delete, ServerHost), |
708 |
|
|
|
709 |
|
|
Delete = fun({N, H, SH}) -> |
710 |
|
|
mod_muc:room_destroyed(H, N, SH), |
711 |
|
|
mod_muc:forget_room(H, N) |
712 |
|
|
end, |
713 |
|
|
lists:foreach(Delete, Rooms_to_delete_full), |
714 |
|
|
{purged, Num_rooms, Num_rooms_to_delete, Rooms_to_delete}. |
715 |
|
|
|
716 |
|
|
fill_serverhost(Rooms_to_delete, global) -> |
717 |
|
|
ServerHosts1 = ?MYHOSTS, |
718 |
|
|
ServerHosts2 = [ {ServerHost, find_host(ServerHost)} || ServerHost <- ServerHosts1], |
719 |
|
|
[ {Name, Host, find_serverhost(Host, ServerHosts2)} || {Name, Host} <- Rooms_to_delete]; |
720 |
|
|
fill_serverhost(Rooms_to_delete, ServerHost) -> |
721 |
|
|
[ {Name, Host, ServerHost}|| {Name, Host} <- Rooms_to_delete]. |
722 |
|
|
|
723 |
|
|
find_serverhost(Host, ServerHosts) -> |
724 |
|
|
{value, {ServerHost, Host}} = lists:keysearch(Host, 2, ServerHosts), |
725 |
|
|
ServerHost. |
726 |
|
|
|
727 |
|
|
find_host(ServerHost) -> |
728 |
|
|
gen_mod:get_module_opt(ServerHost, mod_muc, host, "conference." ++ ServerHost). |
729 |
|
|
|
730 |
|
|
decide({Room_name, Host}, Last_allowed) -> |
731 |
|
|
Room_pid = get_room_pid(Room_name, Host), |
732 |
|
|
|
733 |
|
|
C = get_room_config(Room_pid), |
734 |
|
|
Persistent = C#config.persistent, |
735 |
|
|
|
736 |
|
|
S = get_room_state(Room_pid), |
737 |
|
|
Just_created = S#state.just_created, |
738 |
|
|
|
739 |
|
|
Room_users = S#state.users, |
740 |
|
|
Num_users = length(?DICT:to_list(Room_users)), |
741 |
|
|
|
742 |
|
|
History = (S#state.history)#lqueue.queue, |
743 |
|
|
Ts_now = calendar:now_to_universal_time(now()), |
744 |
|
|
Ts_uptime = element(1, erlang:statistics(wall_clock))/1000, |
745 |
|
|
{Have_history, Last} = case queue:is_empty(History) of |
746 |
|
|
true -> |
747 |
|
|
{false, Ts_uptime}; |
748 |
|
|
false -> |
749 |
|
|
Last_message = queue:last(History), |
750 |
|
|
{_, _, _, Ts_last, _} = Last_message, |
751 |
|
|
Ts_diff = |
752 |
|
|
calendar:datetime_to_gregorian_seconds(Ts_now) |
753 |
|
|
- calendar:datetime_to_gregorian_seconds(Ts_last), |
754 |
|
|
{true, Ts_diff} |
755 |
|
|
end, |
756 |
|
|
|
757 |
|
|
case {Persistent, Just_created, Num_users, Have_history, seconds_to_days(Last)} of |
758 |
|
|
{true, false, 0, _, Last_days} |
759 |
|
|
when Last_days > Last_allowed -> |
760 |
|
|
true; |
761 |
|
|
_ -> |
762 |
|
|
false |
763 |
|
|
end. |
764 |
|
|
|
765 |
|
|
seconds_to_days(S) -> |
766 |
|
|
round(S) div 60*60*24. |
767 |
|
|
|
768 |
|
|
uptime_seconds() -> |
769 |
|
|
trunc(element(1, erlang:statistics(wall_clock))/1000). |
770 |
|
|
|
771 |
|
|
get_room_names(Host) -> |
772 |
|
|
Get_room_names = fun(Room_reg, Names) -> |
773 |
|
|
case {Host, Room_reg#muc_online_room.name_host} of |
774 |
|
|
{Host, {Name1, Host}} -> |
775 |
|
|
[{Name1, Host} | Names]; |
776 |
|
|
{global, {Name1, Host1}} -> |
777 |
|
|
[{Name1, Host1} | Names]; |
778 |
|
|
_ -> |
779 |
|
|
Names |
780 |
|
|
end |
781 |
|
|
end, |
782 |
|
|
ets:foldr(Get_room_names, [], muc_online_room). |
783 |
|
|
|
784 |
|
|
get_room_pid(Name, Host) -> |
785 |
|
|
[Room_ets] = ets:lookup(muc_online_room, {Name, Host}), |
786 |
|
|
Room_ets#muc_online_room.pid. |
787 |
|
|
|
788 |
|
|
get_room_config(Room_pid) -> |
789 |
|
|
gen_fsm:sync_send_all_state_event(Room_pid, get_config). |
790 |
|
|
|
791 |
|
|
get_room_state(Room_pid) -> |
792 |
|
|
gen_fsm:sync_send_all_state_event(Room_pid, get_state). |