plugins/mod_admin_adhoc.lua

1
-- Copyright (C) 2009-2010 Florian Zeitz
2
--
3
-- This file is MIT/X11 licensed. Please see the
4
-- COPYING file in the source package for more information.
5
--
6
 
7
local _G = _G;
8
 
9
local prosody = _G.prosody;
10
local hosts = prosody.hosts;
11
local t_concat = table.concat;
12
 
13
require "util.iterators";
14
local usermanager_user_exists = require "core.usermanager".user_exists;
15
local usermanager_create_user = require "core.usermanager".create_user;
16
local usermanager_get_password = require "core.usermanager".get_password;
17
local usermanager_set_password = require "core.usermanager".set_password;
18
local is_admin = require "core.usermanager".is_admin;
19
local rm_load_roster = require "core.rostermanager".load_roster;
20
local st, jid, uuid = require "util.stanza", require "util.jid", require "util.uuid";
21
local timer_add_task = require "util.timer".add_task;
22
local dataforms_new = require "util.dataforms".new;
23
local array = require "util.array";
24
local modulemanager = require "modulemanager";
25
 
26
local adhoc_new = module:require "adhoc".new;
27
 
28
function add_user_command_handler(self, data, state)
29
	local add_user_layout = dataforms_new{
30
		title = "Adding a User";
31
		instructions = "Fill out this form to add a user.";
32
 
33
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
34
		{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for the account to be added" };
35
		{ name = "password", type = "text-private", label = "The password for this account" };
36
		{ name = "password-verify", type = "text-private", label = "Retype password" };
37
	};
38
 
39
	if state then
40
		if data.action == "cancel" then
41
			return { status = "canceled" };
42
		end
43
		local fields = add_user_layout:data(data.form);
44
		if not fields.accountjid then
45
			return { status = "completed", error = { message = "You need to specify a JID." } };
46
		end
47
		local username, host, resource = jid.split(fields.accountjid);
48
		if data.to ~= host then
49
			return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. data.to}};
50
		end
51
		if (fields["password"] == fields["password-verify"]) and username and host then
52
			if usermanager_user_exists(username, host) then
53
				return { status = "completed", error = { message = "Account already exists" } };
54
			else
55
				if usermanager_create_user(username, fields.password, host) then
56
					module:log("info", "Created new account " .. username.."@"..host);
57
					return { status = "completed", info = "Account successfully created" };
58
				else
59
					return { status = "completed", error = { message = "Failed to write data to disk" } };
60
				end
61
			end
62
		else
63
			module:log("debug", (fields.accountjid or "<nil>") .. " " .. (fields.password or "<nil>") .. " "
64
				.. (fields["password-verify"] or "<nil>"));
65
			return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } };
66
		end
67
	else
68
		return { status = "executing", form = add_user_layout }, "executing";
69
	end
70
end
71
 
72
function change_user_password_command_handler(self, data, state)
73
	local change_user_password_layout = dataforms_new{
74
		title = "Changing a User Password";
75
		instructions = "Fill out this form to change a user's password.";
76
 
77
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
78
		{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" };
79
		{ name = "password", type = "text-private", required = true, label = "The password for this account" };
80
	};
81
 
82
	if state then
83
		if data.action == "cancel" then
84
			return { status = "canceled" };
85
		end
86
		local fields = change_user_password_layout:data(data.form);
87
		if not fields.accountjid or fields.accountjid == "" or not fields.password then
88
			return { status = "completed", error = { message = "Please specify username and password" } };
89
		end
90
		local username, host, resource = jid.split(fields.accountjid);
91
		if data.to ~= host then
92
			return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. data.to}};
93
		end
94
		if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then
95
			return { status = "completed", info = "Password successfully changed" };
96
		else
97
			return { status = "completed", error = { message = "User does not exist" } };
98
		end
99
	else
100
		return { status = "executing", form = change_user_password_layout }, "executing";
101
	end
102
end
103
 
104
function delete_user_command_handler(self, data, state)
105
	local delete_user_layout = dataforms_new{
106
		title = "Deleting a User";
107
		instructions = "Fill out this form to delete a user.";
108
 
109
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
110
		{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" };
111
	};
112
 
113
	if state then
114
		if data.action == "cancel" then
115
			return { status = "canceled" };
116
		end
117
		local fields = delete_user_layout:data(data.form);
118
		local failed = {};
119
		local succeeded = {};
120
		for _, aJID in ipairs(fields.accountjids) do
121
			local username, host, resource = jid.split(aJID);
122
			if (host == data.to) and  usermanager_user_exists(username, host) and disconnect_user(aJID) and usermanager_create_user(username, nil, host) then
123
				module:log("debug", "User " .. aJID .. " has been deleted");
124
				succeeded[#succeeded+1] = aJID;
125
			else
126
				module:log("debug", "Tried to delete non-existant user "..aJID);
127
				failed[#failed+1] = aJID;
128
			end
129
		end
130
		return {status = "completed", info = (#succeeded ~= 0 and
131
				"The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "")..
132
				(#failed ~= 0 and
133
				"The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") };
134
	else
135
		return { status = "executing", form = delete_user_layout }, "executing";
136
	end
137
end
138
 
139
function disconnect_user(match_jid)
140
	local node, hostname, givenResource = jid.split(match_jid);
141
	local host = hosts[hostname];
142
	local sessions = host.sessions[node] and host.sessions[node].sessions;
143
	for resource, session in pairs(sessions or {}) do
144
		if not givenResource or (resource == givenResource) then
145
			module:log("debug", "Disconnecting "..node.."@"..hostname.."/"..resource);
146
			session:close();
147
		end
148
	end
149
	return true;
150
end
151
 
152
function end_user_session_handler(self, data, state)
153
	local end_user_session_layout = dataforms_new{
154
		title = "Ending a User Session";
155
		instructions = "Fill out this form to end a user's session.";
156
 
157
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
158
		{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" };
159
	};
160
 
161
	if state then
162
		if data.action == "cancel" then
163
			return { status = "canceled" };
164
		end
165
 
166
		local fields = end_user_session_layout:data(data.form);
167
		local failed = {};
168
		local succeeded = {};
169
		for _, aJID in ipairs(fields.accountjids) do
170
			local username, host, resource = jid.split(aJID);
171
			if (host == data.to) and  usermanager_user_exists(username, host) and disconnect_user(aJID) then
172
				succeeded[#succeeded+1] = aJID;
173
			else
174
				failed[#failed+1] = aJID;
175
			end
176
		end
177
		return {status = "completed", info = (#succeeded ~= 0 and
178
				"The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "")..
179
				(#failed ~= 0 and
180
				"The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") };
181
	else
182
		return { status = "executing", form = end_user_session_layout }, "executing";
183
	end
184
end
185
 
186
local end_user_session_layout = dataforms_new{
187
	title = "Ending a User Session";
188
	instructions = "Fill out this form to end a user's session.";
189
 
190
	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
191
	{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" };
192
};
193
 
194
 
195
function get_user_password_handler(self, data, state)
196
	local get_user_password_layout = dataforms_new{
197
		title = "Getting User's Password";
198
		instructions = "Fill out this form to get a user's password.";
199
 
200
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
201
		{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" };
202
	};
203
 
204
	local get_user_password_result_layout = dataforms_new{
205
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
206
		{ name = "accountjid", type = "jid-single", label = "JID" };
207
		{ name = "password", type = "text-single", label = "Password" };
208
	};
209
 
210
	if state then
211
		if data.action == "cancel" then
212
			return { status = "canceled" };
213
		end
214
		local fields = get_user_password_layout:data(data.form);
215
		if not fields.accountjid then
216
			return { status = "completed", error = { message = "Please specify a JID." } };
217
		end
218
		local user, host, resource = jid.split(fields.accountjid);
219
		local accountjid = "";
220
		local password = "";
221
		if host ~= data.to then
222
			return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. data.to } };
223
		elseif usermanager_user_exists(user, host) then
224
			accountjid = fields.accountjid;
225
			password = usermanager_get_password(user, host);
226
		else
227
			return { status = "completed", error = { message = "User does not exist" } };
228
		end
229
		return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } };
230
	else
231
		return { status = "executing", form = get_user_password_layout }, "executing";
232
	end
233
end
234
 
235
function get_user_roster_handler(self, data, state)
236
	local get_user_roster_layout = dataforms_new{
237
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
238
		{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" };
239
	};
240
 
241
	local get_user_roster_result_layout = dataforms_new{
242
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
243
		{ name = "accountjid", type = "jid-single", label = "This is the roster for" };
244
		{ name = "roster", type = "text-multi", label = "Roster XML" };
245
	};
246
 
247
	if state then
248
		if data.action == "cancel" then
249
			return { status = "canceled" };
250
		end
251
 
252
		local fields = get_user_roster_layout:data(data.form);
253
 
254
		if not fields.accountjid then
255
			return { status = "completed", error = { message = "Please specify a JID" } };
256
		end
257
 
258
		local user, host, resource = jid.split(fields.accountjid);
259
		if host ~= data.to then
260
			return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. data.to } };
261
		elseif not usermanager_user_exists(user, host) then
262
			return { status = "completed", error = { message = "User does not exist" } };
263
		end
264
		local roster = rm_load_roster(user, host);
265
 
266
		local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
267
		for jid in pairs(roster) do
268
			if jid ~= "pending" and jid then
269
				query:tag("item", {
270
					jid = jid,
271
					subscription = roster[jid].subscription,
272
					ask = roster[jid].ask,
273
					name = roster[jid].name,
274
				});
275
				for group in pairs(roster[jid].groups) do
276
					query:tag("group"):text(group):up();
277
				end
278
				query:up();
279
			end
280
		end
281
 
282
		local query_text = query:__tostring(); -- TODO: Use upcoming pretty_print() function
283
		query_text = query_text:gsub("><", ">\n<");
284
 
285
		local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result");
286
		result:add_child(query);
287
		return { status = "completed", other = result };
288
	else
289
		return { status = "executing", form = get_user_roster_layout }, "executing";
290
	end
291
end
292
 
293
function get_user_stats_handler(self, data, state)
294
	local get_user_stats_layout = dataforms_new{
295
		title = "Get User Statistics";
296
		instructions = "Fill out this form to gather user statistics.";
297
 
298
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
299
		{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" };
300
	};
301
 
302
	local get_user_stats_result_layout = dataforms_new{
303
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
304
		{ name = "ipaddresses", type = "text-multi", label = "IP Addresses" };
305
		{ name = "rostersize", type = "text-single", label = "Roster size" };
306
		{ name = "onlineresources", type = "text-multi", label = "Online Resources" };
307
	};
308
 
309
	if state then
310
		if data.action == "cancel" then
311
			return { status = "canceled" };
312
		end
313
 
314
		local fields = get_user_stats_layout:data(data.form);
315
 
316
		if not fields.accountjid then
317
			return { status = "completed", error = { message = "Please specify a JID." } };
318
		end
319
 
320
		local user, host, resource = jid.split(fields.accountjid);
321
		if host ~= data.to then
322
			return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. data.to } };
323
		elseif not usermanager_user_exists(user, host) then
324
			return { status = "completed", error = { message = "User does not exist" } };
325
		end
326
		local roster = rm_load_roster(user, host);
327
		local rostersize = 0;
328
		local IPs = "";
329
		local resources = "";
330
		for jid in pairs(roster) do
331
			if jid ~= "pending" and jid then
332
				rostersize = rostersize + 1;
333
			end
334
		end
335
		for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do
336
			resources = resources .. "\n" .. resource;
337
			IPs = IPs .. "\n" .. session.ip;
338
		end
339
		return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize),
340
			onlineresources = resources}} };
341
	else
342
		return { status = "executing", form = get_user_stats_layout }, "executing";
343
	end
344
end
345
 
346
function get_online_users_command_handler(self, data, state)
347
	local get_online_users_layout = dataforms_new{
348
		title = "Getting List of Online Users";
349
		instructions = "How many users should be returned at most?";
350
 
351
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
352
		{ name = "max_items", type = "list-single", label = "Maximum number of users",
353
			value = { "25", "50", "75", "100", "150", "200", "all" } };
354
		{ name = "details", type = "boolean", label = "Show details" };
355
	};
356
 
357
	local get_online_users_result_layout = dataforms_new{
358
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
359
		{ name = "onlineuserjids", type = "text-multi", label = "The list of all online users" };
360
	};
361
 
362
	if state then
363
		if data.action == "cancel" then
364
			return { status = "canceled" };
365
		end
366
 
367
		local fields = get_online_users_layout:data(data.form);
368
 
369
		local max_items = nil
370
		if fields.max_items ~= "all" then
371
			max_items = tonumber(fields.max_items);
372
		end
373
		local count = 0;
374
		local users = {};
375
		for username, user in pairs(hosts[data.to].sessions or {}) do
376
			if (max_items ~= nil) and (count >= max_items) then
377
				break;
378
			end
379
			users[#users+1] = username.."@"..data.to;
380
			count = count + 1;
381
			if fields.details then
382
				for resource, session in pairs(user.sessions or {}) do
383
					local status, priority = "unavailable", tostring(session.priority or "-");
384
					if session.presence then
385
						status = session.presence:child_with_name("show");
386
						if status then
387
							status = status:get_text() or "[invalid!]";
388
						else
389
							status = "available";
390
						end
391
					end
392
					users[#users+1] = " - "..resource..": "..status.."("..priority..")";
393
				end
394
			end
395
		end
396
		return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
397
	else
398
		return { status = "executing", form = get_online_users_layout }, "executing";
399
	end
400
end
401
 
402
function list_modules_handler(self, data, state)
403
	local result = dataforms_new {
404
		title = "List of loaded modules";
405
 
406
		{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" };
407
		{ name = "modules", type = "text-multi", label = "The following modules are loaded:" };
408
	};
409
 
410
	local modules = array.collect(keys(hosts[data.to].modules)):sort():concat("\n");
411
 
412
	return { status = "completed", result = { layout = result; values = { modules = modules } } };
413
end
414
 
415
function load_module_handler(self, data, state)
416
	local layout = dataforms_new {
417
		title = "Load module";
418
		instructions = "Specify the module to be loaded";
419
 
420
		{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" };
421
		{ name = "module", type = "text-single", required = true, label = "Module to be loaded:"};
422
	};
423
	if state then
424
		if data.action == "cancel" then
425
			return { status = "canceled" };
426
		end
427
		local fields = layout:data(data.form);
428
		if (not fields.module) or (fields.module == "") then
429
			return { status = "completed", error = {
430
				message = "Please specify a module."
431
			} };
432
		end
433
		if modulemanager.is_loaded(data.to, fields.module) then
434
			return { status = "completed", info = "Module already loaded" };
435
		end
436
		local ok, err = modulemanager.load(data.to, fields.module);
437
		if ok then
438
			return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..data.to..'".' };
439
		else
440
			return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..data.to..
441
			'". Error was: "'..tostring(err or "<unspecified>")..'"' } };
442
		end
443
	else
444
		local modules = array.collect(keys(hosts[data.to].modules)):sort();
445
		return { status = "executing", form = layout }, "executing";
446
	end
447
end
448
 
449
function reload_modules_handler(self, data, state)
450
	local layout = dataforms_new {
451
		title = "Reload modules";
452
		instructions = "Select the modules to be reloaded";
453
 
454
		{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" };
455
		{ name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"};
456
	};
457
	if state then
458
		if data.action == "cancel" then
459
			return { status = "canceled" };
460
		end
461
		local fields = layout:data(data.form);
462
		if #fields.modules == 0 then
463
			return { status = "completed", error = {
464
				message = "Please specify a module. (This means your client misbehaved, as this field is required)"
465
			} };
466
		end
467
		local ok_list, err_list = {}, {};
468
		for _, module in ipairs(fields.modules) do
469
			local ok, err = modulemanager.reload(data.to, module);
470
			if ok then
471
				ok_list[#ok_list + 1] = module;
472
			else
473
				err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
474
			end
475
		end
476
		local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "")..
477
			(#err_list > 0 and ("Failed to reload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
478
		return { status = "completed", info = info };
479
	else
480
		local modules = array.collect(keys(hosts[data.to].modules)):sort();
481
		return { status = "executing", form = { layout = layout; values = { modules = modules } } }, "executing";
482
	end
483
end
484
 
485
function send_to_online(message, server)
486
	if server then
487
		sessions = { [server] = hosts[server] };
488
	else
489
		sessions = hosts;
490
	end
491
 
492
	local c = 0;
493
	for domain, session in pairs(sessions) do
494
		for user in pairs(session.sessions or {}) do
495
			c = c + 1;
496
			message.attr.from = domain;
497
			message.attr.to = user.."@"..domain;
498
			core_post_stanza(session, message);
499
		end
500
	end
501
 
502
	return c;
503
end
504
 
505
function shut_down_service_handler(self, data, state)
506
	local shut_down_service_layout = dataforms_new{
507
		title = "Shutting Down the Service";
508
		instructions = "Fill out this form to shut down the service.";
509
 
510
		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
511
		{ name = "delay", type = "list-single", label = "Time delay before shutting down",
512
			value = { {label = "30 seconds", value = "30"},
513
				  {label = "60 seconds", value = "60"},
514
				  {label = "90 seconds", value = "90"},
515
				  {label = "2 minutes", value = "120"},
516
				  {label = "3 minutes", value = "180"},
517
				  {label = "4 minutes", value = "240"},
518
				  {label = "5 minutes", value = "300"},
519
			};
520
		};
521
		{ name = "announcement", type = "text-multi", label = "Announcement" };
522
	};
523
 
524
	if state then
525
		if data.action == "cancel" then
526
			return { status = "canceled" };
527
		end
528
 
529
		local fields = shut_down_service_layout:data(data.form);
530
 
531
		if fields.announcement and #fields.announcement > 0 then
532
			local message = st.message({type = "headline"}, fields.announcement):up()
533
				:tag("subject"):text("Server is shutting down");
534
			send_to_online(message);
535
		end
536
 
537
		timer_add_task(tonumber(fields.delay or "5"), prosody.shutdown);
538
 
539
		return { status = "completed", info = "Server is about to shut down" };
540
	else
541
		return { status = "executing", form = shut_down_service_layout }, "executing";
542
	end
543
 
544
	return true;
545
end
546
 
547
function unload_modules_handler(self, data, state)
548
	local layout = dataforms_new {
549
		title = "Unload modules";
550
		instructions = "Select the modules to be unloaded";
551
 
552
		{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" };
553
		{ name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"};
554
	};
555
	if state then
556
		if data.action == "cancel" then
557
			return { status = "canceled" };
558
		end
559
		local fields = layout:data(data.form);
560
		if #fields.modules == 0 then
561
			return { status = "completed", error = {
562
				message = "Please specify a module. (This means your client misbehaved, as this field is required)"
563
			} };
564
		end
565
		local ok_list, err_list = {}, {};
566
		for _, module in ipairs(fields.modules) do
567
			local ok, err = modulemanager.unload(data.to, module);
568
			if ok then
569
				ok_list[#ok_list + 1] = module;
570
			else
571
				err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
572
			end
573
		end
574
		local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "")..
575
			(#err_list > 0 and ("Failed to unload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
576
		return { status = "completed", info = info };
577
	else
578
		local modules = array.collect(keys(hosts[data.to].modules)):sort();
579
		return { status = "executing", form = { layout = layout; values = { modules = modules } } }, "executing";
580
	end
581
end
582
 
583
local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin");
584
local change_user_password_desc = adhoc_new("Change User Password", "http://jabber.org/protocol/admin#change-user-password", change_user_password_command_handler, "admin");
585
local delete_user_desc = adhoc_new("Delete User", "http://jabber.org/protocol/admin#delete-user", delete_user_command_handler, "admin");
586
local end_user_session_desc = adhoc_new("End User Session", "http://jabber.org/protocol/admin#end-user-session", end_user_session_handler, "admin");
587
local get_user_password_desc = adhoc_new("Get User Password", "http://jabber.org/protocol/admin#get-user-password", get_user_password_handler, "admin");
588
local get_user_roster_desc = adhoc_new("Get User Roster","http://jabber.org/protocol/admin#get-user-roster", get_user_roster_handler, "admin");
589
local get_user_stats_desc = adhoc_new("Get User Statistics","http://jabber.org/protocol/admin#user-stats", get_user_stats_handler, "admin");
590
local get_online_users_desc = adhoc_new("Get List of Online Users", "http://jabber.org/protocol/admin#get-online-users", get_online_users_command_handler, "admin");
591
local list_modules_desc = adhoc_new("List loaded modules", "http://prosody.im/protocol/modules#list", list_modules_handler, "admin");
592
local load_module_desc = adhoc_new("Load module", "http://prosody.im/protocol/modules#load", load_module_handler, "admin");
593
local reload_modules_desc = adhoc_new("Reload modules", "http://prosody.im/protocol/modules#reload", reload_modules_handler, "admin");
594
local shut_down_service_desc = adhoc_new("Shut Down Service", "http://jabber.org/protocol/admin#shutdown", shut_down_service_handler, "admin");
595
local unload_modules_desc = adhoc_new("Unload modules", "http://prosody.im/protocol/modules#unload", unload_modules_handler, "admin");
596
 
597
module:add_item("adhoc", add_user_desc);
598
module:add_item("adhoc", change_user_password_desc);
599
module:add_item("adhoc", delete_user_desc);
600
module:add_item("adhoc", end_user_session_desc);
601
module:add_item("adhoc", get_user_password_desc);
602
module:add_item("adhoc", get_user_roster_desc);
603
module:add_item("adhoc", get_user_stats_desc);
604
module:add_item("adhoc", get_online_users_desc);
605
module:add_item("adhoc", list_modules_desc);
606
module:add_item("adhoc", load_module_desc);
607
module:add_item("adhoc", reload_modules_desc);
608
module:add_item("adhoc", shut_down_service_desc);
609
module:add_item("adhoc", unload_modules_desc);