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