mod_proxy65: Fix typo in comment
[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 keys = require "util.iterators".keys;
14 local usermanager_user_exists = require "core.usermanager".user_exists;
15 local usermanager_create_user = require "core.usermanager".create_user;
16 local usermanager_delete_user = require "core.usermanager".delete_user;
17 local usermanager_get_password = require "core.usermanager".get_password;
18 local usermanager_set_password = require "core.usermanager".set_password;
19 local hostmanager_activate = require "core.hostmanager".activate;
20 local hostmanager_deactivate = require "core.hostmanager".deactivate;
21 local rm_load_roster = require "core.rostermanager".load_roster;
22 local st, jid = require "util.stanza", require "util.jid";
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 function get_user_password_handler(self, data, state)
214         local get_user_password_layout = dataforms_new{
215                 title = "Getting User's Password";
216                 instructions = "Fill out this form to get a user's password.";
217
218                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
219                 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" };
220         };
221
222         local get_user_password_result_layout = dataforms_new{
223                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
224                 { name = "accountjid", type = "jid-single", label = "JID" };
225                 { name = "password", type = "text-single", label = "Password" };
226         };
227
228         if state then
229                 if data.action == "cancel" then
230                         return { status = "canceled" };
231                 end
232                 local fields, err = get_user_password_layout:data(data.form);
233                 if err then
234                         return generate_error_message(err);
235                 end
236                 local user, host, resource = jid.split(fields.accountjid);
237                 local accountjid = "";
238                 local password = "";
239                 if host ~= data.to then
240                         return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. data.to } };
241                 elseif usermanager_user_exists(user, host) then
242                         accountjid = fields.accountjid;
243                         password = usermanager_get_password(user, host);
244                 else
245                         return { status = "completed", error = { message = "User does not exist" } };
246                 end
247                 return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } };
248         else
249                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_password_layout }, "executing";
250         end
251 end
252
253 function get_user_roster_handler(self, data, state)
254         local get_user_roster_layout = dataforms_new{
255                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
256                 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" };
257         };
258
259         local get_user_roster_result_layout = dataforms_new{
260                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
261                 { name = "accountjid", type = "jid-single", label = "This is the roster for" };
262                 { name = "roster", type = "text-multi", label = "Roster XML" };
263         };
264
265         if state then
266                 if data.action == "cancel" then
267                         return { status = "canceled" };
268                 end
269
270                 local fields, err = get_user_roster_layout:data(data.form);
271
272                 if err then
273                         return generate_error_message(err);
274                 end
275
276                 local user, host, resource = jid.split(fields.accountjid);
277                 if host ~= data.to then
278                         return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. data.to } };
279                 elseif not usermanager_user_exists(user, host) then
280                         return { status = "completed", error = { message = "User does not exist" } };
281                 end
282                 local roster = rm_load_roster(user, host);
283
284                 local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
285                 for jid in pairs(roster) do
286                         if jid ~= "pending" and jid then
287                                 query:tag("item", {
288                                         jid = jid,
289                                         subscription = roster[jid].subscription,
290                                         ask = roster[jid].ask,
291                                         name = roster[jid].name,
292                                 });
293                                 for group in pairs(roster[jid].groups) do
294                                         query:tag("group"):text(group):up();
295                                 end
296                                 query:up();
297                         end
298                 end
299
300                 local query_text = tostring(query):gsub("><", ">\n<");
301
302                 local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result");
303                 result:add_child(query);
304                 return { status = "completed", other = result };
305         else
306                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_roster_layout }, "executing";
307         end
308 end
309
310 function get_user_stats_handler(self, data, state)
311         local get_user_stats_layout = dataforms_new{
312                 title = "Get User Statistics";
313                 instructions = "Fill out this form to gather user statistics.";
314
315                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
316                 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" };
317         };
318
319         local get_user_stats_result_layout = dataforms_new{
320                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
321                 { name = "ipaddresses", type = "text-multi", label = "IP Addresses" };
322                 { name = "rostersize", type = "text-single", label = "Roster size" };
323                 { name = "onlineresources", type = "text-multi", label = "Online Resources" };
324         };
325
326         if state then
327                 if data.action == "cancel" then
328                         return { status = "canceled" };
329                 end
330
331                 local fields, err = get_user_stats_layout:data(data.form);
332
333                 if err then
334                         return generate_error_message(err);
335                 end
336
337                 local user, host, resource = jid.split(fields.accountjid);
338                 if host ~= data.to then
339                         return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. data.to } };
340                 elseif not usermanager_user_exists(user, host) then
341                         return { status = "completed", error = { message = "User does not exist" } };
342                 end
343                 local roster = rm_load_roster(user, host);
344                 local rostersize = 0;
345                 local IPs = "";
346                 local resources = "";
347                 for jid in pairs(roster) do
348                         if jid ~= "pending" and jid then
349                                 rostersize = rostersize + 1;
350                         end
351                 end
352                 for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do
353                         resources = resources .. "\n" .. resource;
354                         IPs = IPs .. "\n" .. session.ip;
355                 end
356                 return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize),
357                         onlineresources = resources}} };
358         else
359                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_stats_layout }, "executing";
360         end
361 end
362
363 function get_online_users_command_handler(self, data, state)
364         local get_online_users_layout = dataforms_new{
365                 title = "Getting List of Online Users";
366                 instructions = "How many users should be returned at most?";
367
368                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
369                 { name = "max_items", type = "list-single", label = "Maximum number of users",
370                         value = { "25", "50", "75", "100", "150", "200", "all" } };
371                 { name = "details", type = "boolean", label = "Show details" };
372         };
373
374         local get_online_users_result_layout = dataforms_new{
375                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
376                 { name = "onlineuserjids", type = "text-multi", label = "The list of all online users" };
377         };
378
379         if state then
380                 if data.action == "cancel" then
381                         return { status = "canceled" };
382                 end
383
384                 local fields, err = get_online_users_layout:data(data.form);
385
386                 if err then
387                         return generate_error_message(err);
388                 end
389
390                 local max_items = nil
391                 if fields.max_items ~= "all" then
392                         max_items = tonumber(fields.max_items);
393                 end
394                 local count = 0;
395                 local users = {};
396                 for username, user in pairs(hosts[data.to].sessions or {}) do
397                         if (max_items ~= nil) and (count >= max_items) then
398                                 break;
399                         end
400                         users[#users+1] = username.."@"..data.to;
401                         count = count + 1;
402                         if fields.details then
403                                 for resource, session in pairs(user.sessions or {}) do
404                                         local status, priority = "unavailable", tostring(session.priority or "-");
405                                         if session.presence then
406                                                 status = session.presence:child_with_name("show");
407                                                 if status then
408                                                         status = status:get_text() or "[invalid!]";
409                                                 else
410                                                         status = "available";
411                                                 end
412                                         end
413                                         users[#users+1] = " - "..resource..": "..status.."("..priority..")";
414                                 end
415                         end
416                 end
417                 return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
418         else
419                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_online_users_layout }, "executing";
420         end
421 end
422
423 function list_modules_handler(self, data, state)
424         local result = dataforms_new {
425                 title = "List of loaded modules";
426
427                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" };
428                 { name = "modules", type = "text-multi", label = "The following modules are loaded:" };
429         };
430
431         local modules = array.collect(keys(hosts[data.to].modules)):sort():concat("\n");
432
433         return { status = "completed", result = { layout = result; values = { modules = modules } } };
434 end
435
436 function load_module_handler(self, data, state)
437         local layout = dataforms_new {
438                 title = "Load module";
439                 instructions = "Specify the module to be loaded";
440
441                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" };
442                 { name = "module", type = "text-single", required = true, label = "Module to be loaded:"};
443         };
444         if state then
445                 if data.action == "cancel" then
446                         return { status = "canceled" };
447                 end
448                 local fields, err = layout:data(data.form);
449                 if err then
450                         return generate_error_message(err);
451                 end
452                 if modulemanager.is_loaded(data.to, fields.module) then
453                         return { status = "completed", info = "Module already loaded" };
454                 end
455                 local ok, err = modulemanager.load(data.to, fields.module);
456                 if ok then
457                         return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..data.to..'".' };
458                 else
459                         return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..data.to..
460                         '". Error was: "'..tostring(err or "<unspecified>")..'"' } };
461                 end
462         else
463                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing";
464         end
465 end
466
467 local function globally_load_module_handler(self, data, state)
468         local layout = dataforms_new {
469                 title = "Globally load module";
470                 instructions = "Specify the module to be loaded on all hosts";
471
472                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" };
473                 { name = "module", type = "text-single", required = true, label = "Module to globally load:"};
474         };
475         if state then
476                 local ok_list, err_list = {}, {};
477
478                 if data.action == "cancel" then
479                         return { status = "canceled" };
480                 end
481
482                 local fields, err = layout:data(data.form);
483                 if err then
484                         return generate_error_message(err);
485                 end
486
487                 local ok, err = modulemanager.load(data.to, fields.module);
488                 if ok then
489                         ok_list[#ok_list + 1] = data.to;
490                 else
491                         err_list[#err_list + 1] = data.to .. " (Error: " .. tostring(err) .. ")";
492                 end
493
494                 -- Is this a global module?
495                 if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(data.to, fields.module) then
496                         return { status = "completed", info = 'Global module '..fields.module..' loaded.' };
497                 end
498
499                 -- This is either a shared or "normal" module, load it on all other hosts
500                 for host_name, host in pairs(hosts) do
501                         if host_name ~= data.to and host.type == "local" then
502                                 local ok, err = modulemanager.load(host_name, fields.module);
503                                 if ok then
504                                         ok_list[#ok_list + 1] = host_name;
505                                 else
506                                         err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
507                                 end
508                         end
509                 end
510
511                 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "")
512                         .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
513                         (#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or "");
514                 return { status = "completed", info = info };
515         else
516                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing";
517         end
518 end
519
520 function reload_modules_handler(self, data, state)
521         local layout = dataforms_new {
522                 title = "Reload modules";
523                 instructions = "Select the modules to be reloaded";
524
525                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" };
526                 { name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"};
527         };
528         if state then
529                 if data.action == "cancel" then
530                         return { status = "canceled" };
531                 end
532                 local fields, err = layout:data(data.form);
533                 if err then
534                         return generate_error_message(err);
535                 end
536                 local ok_list, err_list = {}, {};
537                 for _, module in ipairs(fields.modules) do
538                         local ok, err = modulemanager.reload(data.to, module);
539                         if ok then
540                                 ok_list[#ok_list + 1] = module;
541                         else
542                                 err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
543                         end
544                 end
545                 local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "")
546                         .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
547                         (#err_list > 0 and ("Failed to reload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
548                 return { status = "completed", info = info };
549         else
550                 local modules = array.collect(keys(hosts[data.to].modules)):sort();
551                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout; values = { modules = modules } } }, "executing";
552         end
553 end
554
555 local function globally_reload_module_handler(self, data, state)
556         local layout = dataforms_new {
557                 title = "Globally reload module";
558                 instructions = "Specify the module to reload on all hosts";
559
560                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" };
561                 { name = "module", type = "list-single", required = true, label = "Module to globally reload:"};
562         };
563         if state then
564                 if data.action == "cancel" then
565                         return { status = "canceled" };
566                 end
567
568                 local is_global = false;
569                 local fields, err = layout:data(data.form);
570                 if err then
571                         return generate_error_message(err);
572                 end
573
574                 if modulemanager.is_loaded("*", fields.module) then
575                         local ok, err = modulemanager.reload("*", fields.module);
576                         if not ok then
577                                 return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err };
578                         end
579                         is_global = true;
580                 end
581
582                 local ok_list, err_list = {}, {};
583                 for host_name, host in pairs(hosts) do
584                         if modulemanager.is_loaded(host_name, fields.module)  then
585                                 local ok, err = modulemanager.reload(host_name, fields.module);
586                                 if ok then
587                                         ok_list[#ok_list + 1] = host_name;
588                                 else
589                                         err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
590                                 end
591                         end
592                 end
593
594                 if #ok_list == 0 and #err_list == 0 then
595                         if is_global then
596                                 return { status = "completed", info = 'Successfully reloaded global module '..fields.module };
597                         else
598                                 return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
599                         end
600                 end
601
602                 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
603                         .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
604                         (#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
605                 return { status = "completed", info = info };
606         else
607                 local loaded_modules = array(keys(modulemanager.get_modules("*")));
608                 for _, host in pairs(hosts) do
609                         loaded_modules:append(array(keys(host.modules)));
610                 end
611                 loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
612                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout, values = { module = loaded_modules } } }, "executing";
613         end
614 end
615
616 function send_to_online(message, server)
617         if server then
618                 sessions = { [server] = hosts[server] };
619         else
620                 sessions = hosts;
621         end
622
623         local c = 0;
624         for domain, session in pairs(sessions) do
625                 for user in pairs(session.sessions or {}) do
626                         c = c + 1;
627                         message.attr.from = domain;
628                         message.attr.to = user.."@"..domain;
629                         core_post_stanza(session, message);
630                 end
631         end
632
633         return c;
634 end
635
636 function shut_down_service_handler(self, data, state)
637         local shut_down_service_layout = dataforms_new{
638                 title = "Shutting Down the Service";
639                 instructions = "Fill out this form to shut down the service.";
640
641                 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
642                 { name = "delay", type = "list-single", label = "Time delay before shutting down",
643                         value = { {label = "30 seconds", value = "30"},
644                                   {label = "60 seconds", value = "60"},
645                                   {label = "90 seconds", value = "90"},
646                                   {label = "2 minutes", value = "120"},
647                                   {label = "3 minutes", value = "180"},
648                                   {label = "4 minutes", value = "240"},
649                                   {label = "5 minutes", value = "300"},
650                         };
651                 };
652                 { name = "announcement", type = "text-multi", label = "Announcement" };
653         };
654
655         if state then
656                 if data.action == "cancel" then
657                         return { status = "canceled" };
658                 end
659
660                 local fields, err = shut_down_service_layout:data(data.form);
661
662                 if err then
663                         return generate_error_message(err);
664                 end
665
666                 if fields.announcement and #fields.announcement > 0 then
667                         local message = st.message({type = "headline"}, fields.announcement):up()
668                                 :tag("subject"):text("Server is shutting down");
669                         send_to_online(message);
670                 end
671
672                 timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end);
673
674                 return { status = "completed", info = "Server is about to shut down" };
675         else
676                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = shut_down_service_layout }, "executing";
677         end
678 end
679
680 function unload_modules_handler(self, data, state)
681         local layout = dataforms_new {
682                 title = "Unload modules";
683                 instructions = "Select the modules to be unloaded";
684
685                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" };
686                 { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"};
687         };
688         if state then
689                 if data.action == "cancel" then
690                         return { status = "canceled" };
691                 end
692                 local fields, err = layout:data(data.form);
693                 if err then
694                         return generate_error_message(err);
695                 end
696                 local ok_list, err_list = {}, {};
697                 for _, module in ipairs(fields.modules) do
698                         local ok, err = modulemanager.unload(data.to, module);
699                         if ok then
700                                 ok_list[#ok_list + 1] = module;
701                         else
702                                 err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
703                         end
704                 end
705                 local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "")
706                         .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
707                         (#err_list > 0 and ("Failed to unload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
708                 return { status = "completed", info = info };
709         else
710                 local modules = array.collect(keys(hosts[data.to].modules)):sort();
711                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout; values = { modules = modules } } }, "executing";
712         end
713 end
714
715 local function globally_unload_module_handler(self, data, state)
716         local layout = dataforms_new {
717                 title = "Globally unload module";
718                 instructions = "Specify a module to unload on all hosts";
719
720                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" };
721                 { name = "module", type = "list-single", required = true, label = "Module to globally unload:"};
722         };
723         if state then
724                 if data.action == "cancel" then
725                         return { status = "canceled" };
726                 end
727
728                 local is_global = false;
729                 local fields, err = layout:data(data.form);
730                 if err then
731                         return generate_error_message(err);
732                 end
733
734                 if modulemanager.is_loaded("*", fields.module) then
735                         local ok, err = modulemanager.unload("*", fields.module);
736                         if not ok then
737                                 return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err };
738                         end
739                         is_global = true;
740                 end
741
742                 local ok_list, err_list = {}, {};
743                 for host_name, host in pairs(hosts) do
744                         if modulemanager.is_loaded(host_name, fields.module)  then
745                                 local ok, err = modulemanager.unload(host_name, fields.module);
746                                 if ok then
747                                         ok_list[#ok_list + 1] = host_name;
748                                 else
749                                         err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
750                                 end
751                         end
752                 end
753
754                 if #ok_list == 0 and #err_list == 0 then
755                         if is_global then
756                                 return { status = "completed", info = 'Successfully unloaded global module '..fields.module };
757                         else
758                                 return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
759                         end
760                 end
761
762                 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
763                         .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
764                         (#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
765                 return { status = "completed", info = info };
766         else
767                 local loaded_modules = array(keys(modulemanager.get_modules("*")));
768                 for _, host in pairs(hosts) do
769                         loaded_modules:append(array(keys(host.modules)));
770                 end
771                 loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
772                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout, values = { module = loaded_modules } } }, "executing";
773         end
774 end
775
776
777 function activate_host_handler(self, data, state)
778         local layout = dataforms_new {
779                 title = "Activate host";
780                 instructions = "";
781
782                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
783                 { name = "host", type = "text-single", required = true, label = "Host:"};
784         };
785         if state then
786                 if data.action == "cancel" then
787                         return { status = "canceled" };
788                 end
789                 local fields, err = layout:data(data.form);
790                 if err then
791                         return generate_error_message(err);
792                 end
793                 local ok, err = hostmanager_activate(fields.host);
794
795                 if ok then
796                         return { status = "completed", info = fields.host .. " activated" };
797                 else
798                         return { status = "canceled", error = err }
799                 end
800         else
801                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing";
802         end
803 end
804
805 function deactivate_host_handler(self, data, state)
806         local layout = dataforms_new {
807                 title = "Deactivate host";
808                 instructions = "";
809
810                 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
811                 { name = "host", type = "text-single", required = true, label = "Host:"};
812         };
813         if state then
814                 if data.action == "cancel" then
815                         return { status = "canceled" };
816                 end
817                 local fields, err = layout:data(data.form);
818                 if err then
819                         return generate_error_message(err);
820                 end
821                 local ok, err = hostmanager_deactivate(fields.host);
822
823                 if ok then
824                         return { status = "completed", info = fields.host .. " deactivated" };
825                 else
826                         return { status = "canceled", error = err }
827                 end
828         else
829                 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing";
830         end
831 end
832
833
834 local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin");
835 local change_user_password_desc = adhoc_new("Change User Password", "http://jabber.org/protocol/admin#change-user-password", change_user_password_command_handler, "admin");
836 local config_reload_desc = adhoc_new("Reload configuration", "http://prosody.im/protocol/config#reload", config_reload_handler, "global_admin");
837 local delete_user_desc = adhoc_new("Delete User", "http://jabber.org/protocol/admin#delete-user", delete_user_command_handler, "admin");
838 local end_user_session_desc = adhoc_new("End User Session", "http://jabber.org/protocol/admin#end-user-session", end_user_session_handler, "admin");
839 local get_user_password_desc = adhoc_new("Get User Password", "http://jabber.org/protocol/admin#get-user-password", get_user_password_handler, "admin");
840 local get_user_roster_desc = adhoc_new("Get User Roster","http://jabber.org/protocol/admin#get-user-roster", get_user_roster_handler, "admin");
841 local get_user_stats_desc = adhoc_new("Get User Statistics","http://jabber.org/protocol/admin#user-stats", get_user_stats_handler, "admin");
842 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");
843 local list_modules_desc = adhoc_new("List loaded modules", "http://prosody.im/protocol/modules#list", list_modules_handler, "admin");
844 local load_module_desc = adhoc_new("Load module", "http://prosody.im/protocol/modules#load", load_module_handler, "admin");
845 local globally_load_module_desc = adhoc_new("Globally load module", "http://prosody.im/protocol/modules#global-load", globally_load_module_handler, "global_admin");
846 local reload_modules_desc = adhoc_new("Reload modules", "http://prosody.im/protocol/modules#reload", reload_modules_handler, "admin");
847 local globally_reload_module_desc = adhoc_new("Globally reload module", "http://prosody.im/protocol/modules#global-reload", globally_reload_module_handler, "global_admin");
848 local shut_down_service_desc = adhoc_new("Shut Down Service", "http://jabber.org/protocol/admin#shutdown", shut_down_service_handler, "global_admin");
849 local unload_modules_desc = adhoc_new("Unload modules", "http://prosody.im/protocol/modules#unload", unload_modules_handler, "admin");
850 local globally_unload_module_desc = adhoc_new("Globally unload module", "http://prosody.im/protocol/modules#global-unload", globally_unload_module_handler, "global_admin");
851 local activate_host_desc = adhoc_new("Activate host", "http://prosody.im/protocol/hosts#activate", activate_host_handler, "global_admin");
852 local deactivate_host_desc = adhoc_new("Deactivate host", "http://prosody.im/protocol/hosts#deactivate", deactivate_host_handler, "global_admin");
853
854 module:provides("adhoc", add_user_desc);
855 module:provides("adhoc", change_user_password_desc);
856 module:provides("adhoc", config_reload_desc);
857 module:provides("adhoc", delete_user_desc);
858 module:provides("adhoc", end_user_session_desc);
859 module:provides("adhoc", get_user_password_desc);
860 module:provides("adhoc", get_user_roster_desc);
861 module:provides("adhoc", get_user_stats_desc);
862 module:provides("adhoc", get_online_users_desc);
863 module:provides("adhoc", list_modules_desc);
864 module:provides("adhoc", load_module_desc);
865 module:provides("adhoc", globally_load_module_desc);
866 module:provides("adhoc", reload_modules_desc);
867 module:provides("adhoc", globally_reload_module_desc);
868 module:provides("adhoc", shut_down_service_desc);
869 module:provides("adhoc", unload_modules_desc);
870 module:provides("adhoc", globally_unload_module_desc);
871 module:provides("adhoc", activate_host_desc);
872 module:provides("adhoc", deactivate_host_desc);