plugins/mod_admin_adhoc.lua
changeset 5514 1091d7c3b4d2
parent 5371 706206e191e8
child 5721 579c51cbc12c
equal deleted inserted replaced
5513:755f705f126a 5514:1091d7c3b4d2
     7 local _G = _G;
     7 local _G = _G;
     8 
     8 
     9 local prosody = _G.prosody;
     9 local prosody = _G.prosody;
    10 local hosts = prosody.hosts;
    10 local hosts = prosody.hosts;
    11 local t_concat = table.concat;
    11 local t_concat = table.concat;
       
    12 
       
    13 local module_host = module:get_host();
    12 
    14 
    13 local keys = require "util.iterators".keys;
    15 local keys = require "util.iterators".keys;
    14 local usermanager_user_exists = require "core.usermanager".user_exists;
    16 local usermanager_user_exists = require "core.usermanager".user_exists;
    15 local usermanager_create_user = require "core.usermanager".create_user;
    17 local usermanager_create_user = require "core.usermanager".create_user;
    16 local usermanager_delete_user = require "core.usermanager".delete_user;
    18 local usermanager_delete_user = require "core.usermanager".delete_user;
    23 local timer_add_task = require "util.timer".add_task;
    25 local timer_add_task = require "util.timer".add_task;
    24 local dataforms_new = require "util.dataforms".new;
    26 local dataforms_new = require "util.dataforms".new;
    25 local array = require "util.array";
    27 local array = require "util.array";
    26 local modulemanager = require "modulemanager";
    28 local modulemanager = require "modulemanager";
    27 local core_post_stanza = prosody.core_post_stanza;
    29 local core_post_stanza = prosody.core_post_stanza;
       
    30 local adhoc_simple = require "util.adhoc".new_simple_form;
       
    31 local adhoc_initial = require "util.adhoc".new_initial_data_form;
    28 
    32 
    29 module:depends("adhoc");
    33 module:depends("adhoc");
    30 local adhoc_new = module:require "adhoc".new;
    34 local adhoc_new = module:require "adhoc".new;
    31 
    35 
    32 local function generate_error_message(errors)
    36 local function generate_error_message(errors)
    35 		errmsg[#errmsg + 1] = name .. ": " .. err;
    39 		errmsg[#errmsg + 1] = name .. ": " .. err;
    36 	end
    40 	end
    37 	return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
    41 	return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
    38 end
    42 end
    39 
    43 
    40 function add_user_command_handler(self, data, state)
    44 -- Adding a new user
    41 	local add_user_layout = dataforms_new{
    45 local add_user_layout = dataforms_new{
    42 		title = "Adding a User";
    46 	title = "Adding a User";
    43 		instructions = "Fill out this form to add a user.";
    47 	instructions = "Fill out this form to add a user.";
    44 
    48 
    45 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
    49 	{ 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" };
    50 	{ 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" };
    51 	{ name = "password", type = "text-private", label = "The password for this account" };
    48 		{ name = "password-verify", type = "text-private", label = "Retype password" };
    52 	{ name = "password-verify", type = "text-private", label = "Retype password" };
    49 	};
    53 };
    50 
    54 
    51 	if state then
    55 local add_user_command_handler = adhoc_simple(add_user_layout, function(fields, err)
    52 		if data.action == "cancel" then
    56 	if err then
    53 			return { status = "canceled" };
    57 		return generate_error_message(err);
    54 		end
    58 	end
    55 		local fields, err = add_user_layout:data(data.form);
    59 	local username, host, resource = jid.split(fields.accountjid);
    56 		if err then
    60 	if module_host ~= host then
    57 			return generate_error_message(err);
    61 		return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. module_host}};
    58 		end
    62 	end
    59 		local username, host, resource = jid.split(fields.accountjid);
    63 	if (fields["password"] == fields["password-verify"]) and username and host then
    60 		if data.to ~= host then
    64 		if usermanager_user_exists(username, host) then
    61 			return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. data.to}};
    65 			return { status = "completed", error = { message = "Account already exists" } };
    62 		end
    66 		else
    63 		if (fields["password"] == fields["password-verify"]) and username and host then
    67 			if usermanager_create_user(username, fields.password, host) then
    64 			if usermanager_user_exists(username, host) then
    68 				module:log("info", "Created new account %s@%s", username, host);
    65 				return { status = "completed", error = { message = "Account already exists" } };
    69 				return { status = "completed", info = "Account successfully created" };
    66 			else
    70 			else
    67 				if usermanager_create_user(username, fields.password, host) then
    71 				return { status = "completed", error = { message = "Failed to write data to disk" } };
    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
    72 			end
    74 		else
    73 		end
    75 			module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or "<nil>");
    74 	else
    76 			return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } };
    75 		module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or "<nil>");
    77 		end
    76 		return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } };
    78 	else
    77 	end
    79 		return { status = "executing", actions = {"next", "complete", default = "complete"}, form = add_user_layout }, "executing";
    78 end);
    80 	end
    79 
    81 end
    80 -- Changing a user's password
    82 
    81 local change_user_password_layout = dataforms_new{
    83 function change_user_password_command_handler(self, data, state)
    82 	title = "Changing a User Password";
    84 	local change_user_password_layout = dataforms_new{
    83 	instructions = "Fill out this form to change a user's password.";
    85 		title = "Changing a User Password";
    84 
    86 		instructions = "Fill out this form to change a user's password.";
    85 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
    87 
    86 	{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" };
    88 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
    87 	{ name = "password", type = "text-private", required = true, label = "The password for this account" };
    89 		{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" };
    88 };
    90 		{ name = "password", type = "text-private", required = true, label = "The password for this account" };
    89 
    91 	};
    90 local change_user_password_command_handler = adhoc_simple(change_user_password_layout, function(fields, err)
    92 
    91 	if err then
    93 	if state then
    92 		return generate_error_message(err);
    94 		if data.action == "cancel" then
    93 	end
    95 			return { status = "canceled" };
    94 	local username, host, resource = jid.split(fields.accountjid);
    96 		end
    95 	if module_host ~= host then
    97 		local fields, err = change_user_password_layout:data(data.form);
    96 		return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. module_host}};
    98 		if err then
    97 	end
    99 			return generate_error_message(err);
    98 	if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then
   100 		end
    99 		return { status = "completed", info = "Password successfully changed" };
   101 		local username, host, resource = jid.split(fields.accountjid);
   100 	else
   102 		if data.to ~= host then
   101 		return { status = "completed", error = { message = "User does not exist" } };
   103 			return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. data.to}};
   102 	end
   104 		end
   103 end);
   105 		if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then
   104 
   106 			return { status = "completed", info = "Password successfully changed" };
   105 -- Reloading the config
   107 		else
   106 local function config_reload_handler(self, data, state)
   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();
   107 	local ok, err = prosody.reload_config();
   117 	if ok then
   108 	if ok then
   118 		return { status = "completed", info = "Configuration reloaded (modules may need to be reloaded for this to have an effect)" };
   109 		return { status = "completed", info = "Configuration reloaded (modules may need to be reloaded for this to have an effect)" };
   119 	else
   110 	else
   120 		return { status = "completed", error = { message = "Failed to reload config: " .. tostring(err) } };
   111 		return { status = "completed", error = { message = "Failed to reload config: " .. tostring(err) } };
   121 	end
   112 	end
   122 end
   113 end
   123 
   114 
   124 
   115 -- Deleting a user's account
   125 function delete_user_command_handler(self, data, state)
   116 local delete_user_layout = dataforms_new{
   126 	local delete_user_layout = dataforms_new{
   117 	title = "Deleting a User";
   127 		title = "Deleting a User";
   118 	instructions = "Fill out this form to delete a user.";
   128 		instructions = "Fill out this form to delete a user.";
   119 
   129 
   120 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   130 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   121 	{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" };
   131 		{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" };
   122 };
   132 	};
   123 
   133 
   124 local delete_user_command_handler = adhoc_simple(delete_user_layout, function(fields, err)
   134 	if state then
   125 	if err then
   135 		if data.action == "cancel" then
   126 		return generate_error_message(err);
   136 			return { status = "canceled" };
   127 	end
   137 		end
   128 	local failed = {};
   138 		local fields, err = delete_user_layout:data(data.form);
   129 	local succeeded = {};
   139 		if err then
   130 	for _, aJID in ipairs(fields.accountjids) do
   140 			return generate_error_message(err);
   131 		local username, host, resource = jid.split(aJID);
   141 		end
   132 		if (host == module_host) and  usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then
   142 		local failed = {};
   133 			module:log("debug", "User %s has been deleted", aJID);
   143 		local succeeded = {};
   134 			succeeded[#succeeded+1] = aJID;
   144 		for _, aJID in ipairs(fields.accountjids) do
   135 		else
   145 			local username, host, resource = jid.split(aJID);
   136 			module:log("debug", "Tried to delete non-existant user %s", aJID);
   146 			if (host == data.to) and  usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then
   137 			failed[#failed+1] = aJID;
   147 				module:log("debug", "User %s has been deleted", aJID);
   138 		end
   148 				succeeded[#succeeded+1] = aJID;
   139 	end
   149 			else
   140 	return {status = "completed", info = (#succeeded ~= 0 and
   150 				module:log("debug", "Tried to delete non-existant user %s", aJID);
   141 			"The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "")..
   151 				failed[#failed+1] = aJID;
   142 			(#failed ~= 0 and
   152 			end
   143 			"The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") };
   153 		end
   144 end);
   154 		return {status = "completed", info = (#succeeded ~= 0 and
   145 
   155 				"The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "")..
   146 -- Ending a user's session
   156 				(#failed ~= 0 and
   147 local function disconnect_user(match_jid)
   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);
   148 	local node, hostname, givenResource = jid.split(match_jid);
   165 	local host = hosts[hostname];
   149 	local host = hosts[hostname];
   166 	local sessions = host.sessions[node] and host.sessions[node].sessions;
   150 	local sessions = host.sessions[node] and host.sessions[node].sessions;
   167 	for resource, session in pairs(sessions or {}) do
   151 	for resource, session in pairs(sessions or {}) do
   168 		if not givenResource or (resource == givenResource) then
   152 		if not givenResource or (resource == givenResource) then
   171 		end
   155 		end
   172 	end
   156 	end
   173 	return true;
   157 	return true;
   174 end
   158 end
   175 
   159 
   176 function end_user_session_handler(self, data, state)
   160 local end_user_session_layout = dataforms_new{
   177 	local end_user_session_layout = dataforms_new{
   161 	title = "Ending a User Session";
   178 		title = "Ending a User Session";
   162 	instructions = "Fill out this form to end a user's session.";
   179 		instructions = "Fill out this form to end a user's session.";
   163 
   180 
   164 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   181 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   165 	{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" };
   182 		{ name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" };
   166 };
   183 	};
   167 
   184 
   168 local end_user_session_handler = adhoc_simple(end_user_session_layout, function(fields, err)
   185 	if state then
   169 	if err then
   186 		if data.action == "cancel" then
   170 		return generate_error_message(err);
   187 			return { status = "canceled" };
   171 	end
   188 		end
   172 	local failed = {};
   189 
   173 	local succeeded = {};
   190 		local fields, err = end_user_session_layout:data(data.form);
   174 	for _, aJID in ipairs(fields.accountjids) do
   191 		if err then
   175 		local username, host, resource = jid.split(aJID);
   192 			return generate_error_message(err);
   176 		if (host == module_host) and  usermanager_user_exists(username, host) and disconnect_user(aJID) then
   193 		end
   177 			succeeded[#succeeded+1] = aJID;
   194 		local failed = {};
   178 		else
   195 		local succeeded = {};
   179 			failed[#failed+1] = aJID;
   196 		for _, aJID in ipairs(fields.accountjids) do
   180 		end
   197 			local username, host, resource = jid.split(aJID);
   181 	end
   198 			if (host == data.to) and  usermanager_user_exists(username, host) and disconnect_user(aJID) then
   182 	return {status = "completed", info = (#succeeded ~= 0 and
   199 				succeeded[#succeeded+1] = aJID;
   183 		"The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "")..
       
   184 		(#failed ~= 0 and
       
   185 		"The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") };
       
   186 end);
       
   187 
       
   188 -- Getting a user's password
       
   189 local get_user_password_layout = dataforms_new{
       
   190 	title = "Getting User's Password";
       
   191 	instructions = "Fill out this form to get a user's password.";
       
   192 
       
   193 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
   194 	{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" };
       
   195 };
       
   196 
       
   197 local get_user_password_result_layout = dataforms_new{
       
   198 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
   199 	{ name = "accountjid", type = "jid-single", label = "JID" };
       
   200 	{ name = "password", type = "text-single", label = "Password" };
       
   201 };
       
   202 
       
   203 local get_user_password_handler = adhoc_simple(get_user_password_layout, function(fields, err)
       
   204 	if err then
       
   205 		return generate_error_message(err);
       
   206 	end
       
   207 	local user, host, resource = jid.split(fields.accountjid);
       
   208 	local accountjid = "";
       
   209 	local password = "";
       
   210 	if host ~= module_host then
       
   211 		return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. module_host } };
       
   212 	elseif usermanager_user_exists(user, host) then
       
   213 		accountjid = fields.accountjid;
       
   214 		password = usermanager_get_password(user, host);
       
   215 	else
       
   216 		return { status = "completed", error = { message = "User does not exist" } };
       
   217 	end
       
   218 	return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } };
       
   219 end);
       
   220 
       
   221 -- Getting a user's roster
       
   222 local get_user_roster_layout = dataforms_new{
       
   223 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
   224 	{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" };
       
   225 };
       
   226 
       
   227 local get_user_roster_result_layout = dataforms_new{
       
   228 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
   229 	{ name = "accountjid", type = "jid-single", label = "This is the roster for" };
       
   230 	{ name = "roster", type = "text-multi", label = "Roster XML" };
       
   231 };
       
   232 
       
   233 local get_user_roster_handler = adhoc_simple(get_user_roster_layout, function(fields, err)
       
   234 	if err then
       
   235 		return generate_error_message(err);
       
   236 	end
       
   237 
       
   238 	local user, host, resource = jid.split(fields.accountjid);
       
   239 	if host ~= module_host then
       
   240 		return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. module_host } };
       
   241 	elseif not usermanager_user_exists(user, host) then
       
   242 		return { status = "completed", error = { message = "User does not exist" } };
       
   243 	end
       
   244 	local roster = rm_load_roster(user, host);
       
   245 
       
   246 	local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
       
   247 	for jid in pairs(roster) do
       
   248 		if jid ~= "pending" and jid then
       
   249 			query:tag("item", {
       
   250 				jid = jid,
       
   251 				subscription = roster[jid].subscription,
       
   252 				ask = roster[jid].ask,
       
   253 				name = roster[jid].name,
       
   254 			});
       
   255 			for group in pairs(roster[jid].groups) do
       
   256 				query:tag("group"):text(group):up();
       
   257 			end
       
   258 			query:up();
       
   259 		end
       
   260 	end
       
   261 
       
   262 	local query_text = tostring(query):gsub("><", ">\n<");
       
   263 
       
   264 	local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result");
       
   265 	result:add_child(query);
       
   266 	return { status = "completed", other = result };
       
   267 end);
       
   268 
       
   269 -- Getting user statistics
       
   270 local get_user_stats_layout = dataforms_new{
       
   271 	title = "Get User Statistics";
       
   272 	instructions = "Fill out this form to gather user statistics.";
       
   273 
       
   274 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
   275 	{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" };
       
   276 };
       
   277 
       
   278 local get_user_stats_result_layout = dataforms_new{
       
   279 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
   280 	{ name = "ipaddresses", type = "text-multi", label = "IP Addresses" };
       
   281 	{ name = "rostersize", type = "text-single", label = "Roster size" };
       
   282 	{ name = "onlineresources", type = "text-multi", label = "Online Resources" };
       
   283 };
       
   284 
       
   285 local get_user_stats_handler = adhoc_simple(get_user_stats_layout, function(fields, err)
       
   286 	if err then
       
   287 		return generate_error_message(err);
       
   288 	end
       
   289 
       
   290 	local user, host, resource = jid.split(fields.accountjid);
       
   291 	if host ~= module_host then
       
   292 		return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. module_host } };
       
   293 	elseif not usermanager_user_exists(user, host) then
       
   294 		return { status = "completed", error = { message = "User does not exist" } };
       
   295 	end
       
   296 	local roster = rm_load_roster(user, host);
       
   297 	local rostersize = 0;
       
   298 	local IPs = "";
       
   299 	local resources = "";
       
   300 	for jid in pairs(roster) do
       
   301 		if jid ~= "pending" and jid then
       
   302 			rostersize = rostersize + 1;
       
   303 		end
       
   304 	end
       
   305 	for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do
       
   306 		resources = resources .. "\n" .. resource;
       
   307 		IPs = IPs .. "\n" .. session.ip;
       
   308 	end
       
   309 	return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize),
       
   310 		onlineresources = resources}} };
       
   311 end);
       
   312 
       
   313 -- Getting a list of online users
       
   314 local get_online_users_layout = dataforms_new{
       
   315 	title = "Getting List of Online Users";
       
   316 	instructions = "How many users should be returned at most?";
       
   317 
       
   318 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
   319 	{ name = "max_items", type = "list-single", label = "Maximum number of users",
       
   320 		value = { "25", "50", "75", "100", "150", "200", "all" } };
       
   321 	{ name = "details", type = "boolean", label = "Show details" };
       
   322 };
       
   323 
       
   324 local get_online_users_result_layout = dataforms_new{
       
   325 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
       
   326 	{ name = "onlineuserjids", type = "text-multi", label = "The list of all online users" };
       
   327 };
       
   328 
       
   329 local get_online_users_command_handler = adhoc_simple(get_online_users_layout, function(fields, err)
       
   330 	if err then
       
   331 		return generate_error_message(err);
       
   332 	end
       
   333 
       
   334 	local max_items = nil
       
   335 	if fields.max_items ~= "all" then
       
   336 		max_items = tonumber(fields.max_items);
       
   337 	end
       
   338 	local count = 0;
       
   339 	local users = {};
       
   340 	for username, user in pairs(hosts[module_host].sessions or {}) do
       
   341 		if (max_items ~= nil) and (count >= max_items) then
       
   342 			break;
       
   343 		end
       
   344 		users[#users+1] = username.."@"..module_host;
       
   345 		count = count + 1;
       
   346 		if fields.details then
       
   347 			for resource, session in pairs(user.sessions or {}) do
       
   348 				local status, priority = "unavailable", tostring(session.priority or "-");
       
   349 				if session.presence then
       
   350 					status = session.presence:child_with_name("show");
       
   351 					if status then
       
   352 						status = status:get_text() or "[invalid!]";
       
   353 					else
       
   354 						status = "available";
       
   355 					end
       
   356 				end
       
   357 				users[#users+1] = " - "..resource..": "..status.."("..priority..")";
       
   358 			end
       
   359 		end
       
   360 	end
       
   361 	return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
       
   362 end);
       
   363 
       
   364 -- Getting a list of loaded modules
       
   365 local list_modules_result = dataforms_new {
       
   366 	title = "List of loaded modules";
       
   367 
       
   368 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" };
       
   369 	{ name = "modules", type = "text-multi", label = "The following modules are loaded:" };
       
   370 };
       
   371 
       
   372 local function list_modules_handler(self, data, state)
       
   373 	local modules = array.collect(keys(hosts[module_host].modules)):sort():concat("\n");
       
   374 	return { status = "completed", result = { layout = list_modules_result; values = { modules = modules } } };
       
   375 end
       
   376 
       
   377 -- Loading a module
       
   378 local load_module_layout = dataforms_new {
       
   379 	title = "Load module";
       
   380 	instructions = "Specify the module to be loaded";
       
   381 
       
   382 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" };
       
   383 	{ name = "module", type = "text-single", required = true, label = "Module to be loaded:"};
       
   384 };
       
   385 
       
   386 local load_module_handler = adhoc_simple(load_module_layout, function(fields, err)
       
   387 	if err then
       
   388 		return generate_error_message(err);
       
   389 	end
       
   390 	if modulemanager.is_loaded(module_host, fields.module) then
       
   391 		return { status = "completed", info = "Module already loaded" };
       
   392 	end
       
   393 	local ok, err = modulemanager.load(module_host, fields.module);
       
   394 	if ok then
       
   395 		return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..module_host..'".' };
       
   396 	else
       
   397 		return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..module_host..
       
   398 		'". Error was: "'..tostring(err or "<unspecified>")..'"' } };
       
   399 	end
       
   400 end);
       
   401 
       
   402 -- Globally loading a module
       
   403 local globally_load_module_layout = dataforms_new {
       
   404 	title = "Globally load module";
       
   405 	instructions = "Specify the module to be loaded on all hosts";
       
   406 
       
   407 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" };
       
   408 	{ name = "module", type = "text-single", required = true, label = "Module to globally load:"};
       
   409 };
       
   410 
       
   411 local globally_load_module_handler = adhoc_simple(globally_load_module_layout, function(fields, err)
       
   412 	local ok_list, err_list = {}, {};
       
   413 
       
   414 	if err then
       
   415 		return generate_error_message(err);
       
   416 	end
       
   417 
       
   418 	local ok, err = modulemanager.load(module_host, fields.module);
       
   419 	if ok then
       
   420 		ok_list[#ok_list + 1] = module_host;
       
   421 	else
       
   422 		err_list[#err_list + 1] = module_host .. " (Error: " .. tostring(err) .. ")";
       
   423 	end
       
   424 
       
   425 	-- Is this a global module?
       
   426 	if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(module_host, fields.module) then
       
   427 		return { status = "completed", info = 'Global module '..fields.module..' loaded.' };
       
   428 	end
       
   429 
       
   430 	-- This is either a shared or "normal" module, load it on all other hosts
       
   431 	for host_name, host in pairs(hosts) do
       
   432 		if host_name ~= module_host and host.type == "local" then
       
   433 			local ok, err = modulemanager.load(host_name, fields.module);
       
   434 			if ok then
       
   435 				ok_list[#ok_list + 1] = host_name;
   200 			else
   436 			else
   201 				failed[#failed+1] = aJID;
   437 				err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
   202 			end
   438 			end
   203 		end
   439 		end
   204 		return {status = "completed", info = (#succeeded ~= 0 and
   440 	end
   205 				"The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "")..
   441 
   206 				(#failed ~= 0 and
   442 	local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "")
   207 				"The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") };
   443 		.. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
   208 	else
   444 		(#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or "");
   209 		return { status = "executing", actions = {"next", "complete", default = "complete"}, form = end_user_session_layout }, "executing";
   445 	return { status = "completed", info = info };
   210 	end
   446 end);
   211 end
   447 
   212 
   448 -- Reloading modules
   213 function get_user_password_handler(self, data, state)
   449 local reload_modules_layout = dataforms_new {
   214 	local get_user_password_layout = dataforms_new{
   450 	title = "Reload modules";
   215 		title = "Getting User's Password";
   451 	instructions = "Select the modules to be reloaded";
   216 		instructions = "Fill out this form to get a user's password.";
   452 
   217 
   453 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" };
   218 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   454 	{ name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"};
   219 		{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" };
   455 };
   220 	};
   456 
   221 
   457 local reload_modules_handler = adhoc_initial(reload_modules_layout, function()
   222 	local get_user_password_result_layout = dataforms_new{
   458 	return { modules = array.collect(keys(hosts[module_host].modules)):sort() };
   223 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   459 end, function(fields, err)
   224 		{ name = "accountjid", type = "jid-single", label = "JID" };
   460 	if err then
   225 		{ name = "password", type = "text-single", label = "Password" };
   461 		return generate_error_message(err);
   226 	};
   462 	end
   227 
   463 	local ok_list, err_list = {}, {};
   228 	if state then
   464 	for _, module in ipairs(fields.modules) do
   229 		if data.action == "cancel" then
   465 		local ok, err = modulemanager.reload(module_host, module);
   230 			return { status = "canceled" };
   466 		if ok then
   231 		end
   467 			ok_list[#ok_list + 1] = module;
   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
   468 		else
   245 			return { status = "completed", error = { message = "User does not exist" } };
   469 			err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
   246 		end
   470 		end
   247 		return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } };
   471 	end
   248 	else
   472 	local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "")
   249 		return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_password_layout }, "executing";
   473 		.. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
   250 	end
   474 		(#err_list > 0 and ("Failed to reload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or "");
   251 end
   475 	return { status = "completed", info = info };
   252 
   476 end);
   253 function get_user_roster_handler(self, data, state)
   477 
   254 	local get_user_roster_layout = dataforms_new{
   478 -- Globally reloading a module
   255 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   479 local globally_reload_module_layout = dataforms_new {
   256 		{ name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" };
   480 	title = "Globally reload module";
   257 	};
   481 	instructions = "Specify the module to reload on all hosts";
   258 
   482 
   259 	local get_user_roster_result_layout = dataforms_new{
   483 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" };
   260 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   484 	{ name = "module", type = "list-single", required = true, label = "Module to globally reload:"};
   261 		{ name = "accountjid", type = "jid-single", label = "This is the roster for" };
   485 };
   262 		{ name = "roster", type = "text-multi", label = "Roster XML" };
   486 
   263 	};
   487 local globally_reload_module_handler = adhoc_initial(globally_reload_module_layout, function()
   264 
   488 	local loaded_modules = array(keys(modulemanager.get_modules("*")));
   265 	if state then
   489 	for _, host in pairs(hosts) do
   266 		if data.action == "cancel" then
   490 		loaded_modules:append(array(keys(host.modules)));
   267 			return { status = "canceled" };
   491 	end
   268 		end
   492 	loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
   269 
   493 	return { module = loaded_modules };
   270 		local fields, err = get_user_roster_layout:data(data.form);
   494 end, function(fields, err)
   271 
   495 	local is_global = false;
   272 		if err then
   496 
   273 			return generate_error_message(err);
   497 	if err then
   274 		end
   498 		return generate_error_message(err);
   275 
   499 	end
   276 		local user, host, resource = jid.split(fields.accountjid);
   500 
   277 		if host ~= data.to then
   501 	if modulemanager.is_loaded("*", fields.module) then
   278 			return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. data.to } };
   502 		local ok, err = modulemanager.reload("*", fields.module);
   279 		elseif not usermanager_user_exists(user, host) then
   503 		if not ok then
   280 			return { status = "completed", error = { message = "User does not exist" } };
   504 			return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err };
   281 		end
   505 		end
   282 		local roster = rm_load_roster(user, host);
   506 		is_global = true;
   283 
   507 	end
   284 		local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
   508 
   285 		for jid in pairs(roster) do
   509 	local ok_list, err_list = {}, {};
   286 			if jid ~= "pending" and jid then
   510 	for host_name, host in pairs(hosts) do
   287 				query:tag("item", {
   511 		if modulemanager.is_loaded(host_name, fields.module)  then
   288 					jid = jid,
   512 			local ok, err = modulemanager.reload(host_name, fields.module);
   289 					subscription = roster[jid].subscription,
   513 			if ok then
   290 					ask = roster[jid].ask,
   514 				ok_list[#ok_list + 1] = host_name;
   291 					name = roster[jid].name,
   515 			else
   292 				});
   516 				err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
   293 				for group in pairs(roster[jid].groups) do
       
   294 					query:tag("group"):text(group):up();
       
   295 				end
       
   296 				query:up();
       
   297 			end
   517 			end
   298 		end
   518 		end
   299 
   519 	end
   300 		local query_text = tostring(query):gsub("><", ">\n<");
   520 
   301 
   521 	if #ok_list == 0 and #err_list == 0 then
   302 		local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result");
   522 		if is_global then
   303 		result:add_child(query);
   523 			return { status = "completed", info = 'Successfully reloaded global module '..fields.module };
   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
   524 		else
   459 			return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..data.to..
   525 			return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
   460 			'". Error was: "'..tostring(err or "<unspecified>")..'"' } };
   526 		end
   461 		end
   527 	end
   462 	else
   528 
   463 		return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing";
   529 	local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
   464 	end
   530 		.. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
   465 end
   531 		(#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
   466 
   532 	return { status = "completed", info = info };
   467 local function globally_load_module_handler(self, data, state)
   533 end);
   468 	local layout = dataforms_new {
   534 
   469 		title = "Globally load module";
   535 local function send_to_online(message, server)
   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
   536 	if server then
   618 		sessions = { [server] = hosts[server] };
   537 		sessions = { [server] = hosts[server] };
   619 	else
   538 	else
   620 		sessions = hosts;
   539 		sessions = hosts;
   621 	end
   540 	end
   631 	end
   550 	end
   632 
   551 
   633 	return c;
   552 	return c;
   634 end
   553 end
   635 
   554 
   636 function shut_down_service_handler(self, data, state)
   555 -- Shutting down the service
   637 	local shut_down_service_layout = dataforms_new{
   556 local shut_down_service_layout = dataforms_new{
   638 		title = "Shutting Down the Service";
   557 	title = "Shutting Down the Service";
   639 		instructions = "Fill out this form to shut down the service.";
   558 	instructions = "Fill out this form to shut down the service.";
   640 
   559 
   641 		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   560 	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
   642 		{ name = "delay", type = "list-single", label = "Time delay before shutting down",
   561 	{ name = "delay", type = "list-single", label = "Time delay before shutting down",
   643 			value = { {label = "30 seconds", value = "30"},
   562 		value = { {label = "30 seconds", value = "30"},
   644 				  {label = "60 seconds", value = "60"},
   563 			  {label = "60 seconds", value = "60"},
   645 				  {label = "90 seconds", value = "90"},
   564 			  {label = "90 seconds", value = "90"},
   646 				  {label = "2 minutes", value = "120"},
   565 			  {label = "2 minutes", value = "120"},
   647 				  {label = "3 minutes", value = "180"},
   566 			  {label = "3 minutes", value = "180"},
   648 				  {label = "4 minutes", value = "240"},
   567 			  {label = "4 minutes", value = "240"},
   649 				  {label = "5 minutes", value = "300"},
   568 			  {label = "5 minutes", value = "300"},
   650 			};
       
   651 		};
   569 		};
   652 		{ name = "announcement", type = "text-multi", label = "Announcement" };
       
   653 	};
   570 	};
   654 
   571 	{ name = "announcement", type = "text-multi", label = "Announcement" };
   655 	if state then
   572 };
   656 		if data.action == "cancel" then
   573 
   657 			return { status = "canceled" };
   574 local shut_down_service_handler = adhoc_simple(shut_down_service_layout, function(fields, err)
   658 		end
   575 	if err then
   659 
   576 		return generate_error_message(err);
   660 		local fields, err = shut_down_service_layout:data(data.form);
   577 	end
   661 
   578 
   662 		if err then
   579 	if fields.announcement and #fields.announcement > 0 then
   663 			return generate_error_message(err);
   580 		local message = st.message({type = "headline"}, fields.announcement):up()
   664 		end
   581 			:tag("subject"):text("Server is shutting down");
   665 
   582 		send_to_online(message);
   666 		if fields.announcement and #fields.announcement > 0 then
   583 	end
   667 			local message = st.message({type = "headline"}, fields.announcement):up()
   584 
   668 				:tag("subject"):text("Server is shutting down");
   585 	timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end);
   669 			send_to_online(message);
   586 
   670 		end
   587 	return { status = "completed", info = "Server is about to shut down" };
   671 
   588 end);
   672 		timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end);
   589 
   673 
   590 -- Unloading modules
   674 		return { status = "completed", info = "Server is about to shut down" };
   591 local unload_modules_layout = dataforms_new {
   675 	else
   592 	title = "Unload modules";
   676 		return { status = "executing", actions = {"next", "complete", default = "complete"}, form = shut_down_service_layout }, "executing";
   593 	instructions = "Select the modules to be unloaded";
   677 	end
   594 
   678 end
   595 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" };
   679 
   596 	{ name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"};
   680 function unload_modules_handler(self, data, state)
   597 };
   681 	local layout = dataforms_new {
   598 
   682 		title = "Unload modules";
   599 local unload_modules_handler = adhoc_initial(unload_modules_layout, function()
   683 		instructions = "Select the modules to be unloaded";
   600 	return { modules = array.collect(keys(hosts[module_host].modules)):sort() };
   684 
   601 end, function(fields, err)
   685 		{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" };
   602 	if err then
   686 		{ name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"};
   603 		return generate_error_message(err);
   687 	};
   604 	end
   688 	if state then
   605 	local ok_list, err_list = {}, {};
   689 		if data.action == "cancel" then
   606 	for _, module in ipairs(fields.modules) do
   690 			return { status = "canceled" };
   607 		local ok, err = modulemanager.unload(module_host, module);
   691 		end
   608 		if ok then
   692 		local fields, err = layout:data(data.form);
   609 			ok_list[#ok_list + 1] = module;
   693 		if err then
   610 		else
   694 			return generate_error_message(err);
   611 			err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
   695 		end
   612 		end
   696 		local ok_list, err_list = {}, {};
   613 	end
   697 		for _, module in ipairs(fields.modules) do
   614 	local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "")
   698 			local ok, err = modulemanager.unload(data.to, module);
   615 		.. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
       
   616 		(#err_list > 0 and ("Failed to unload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or "");
       
   617 	return { status = "completed", info = info };
       
   618 end);
       
   619 
       
   620 -- Globally unloading a module
       
   621 local globally_unload_module_layout = dataforms_new {
       
   622 	title = "Globally unload module";
       
   623 	instructions = "Specify a module to unload on all hosts";
       
   624 
       
   625 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" };
       
   626 	{ name = "module", type = "list-single", required = true, label = "Module to globally unload:"};
       
   627 };
       
   628 
       
   629 local globally_unload_module_handler = adhoc_initial(globally_unload_module_layout, function()
       
   630 	local loaded_modules = array(keys(modulemanager.get_modules("*")));
       
   631 	for _, host in pairs(hosts) do
       
   632 		loaded_modules:append(array(keys(host.modules)));
       
   633 	end
       
   634 	loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
       
   635 	return { module = loaded_modules };
       
   636 end, function(fields, err)
       
   637 	local is_global = false;
       
   638 	if err then
       
   639 		return generate_error_message(err);
       
   640 	end
       
   641 
       
   642 	if modulemanager.is_loaded("*", fields.module) then
       
   643 		local ok, err = modulemanager.unload("*", fields.module);
       
   644 		if not ok then
       
   645 			return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err };
       
   646 		end
       
   647 		is_global = true;
       
   648 	end
       
   649 
       
   650 	local ok_list, err_list = {}, {};
       
   651 	for host_name, host in pairs(hosts) do
       
   652 		if modulemanager.is_loaded(host_name, fields.module)  then
       
   653 			local ok, err = modulemanager.unload(host_name, fields.module);
   699 			if ok then
   654 			if ok then
   700 				ok_list[#ok_list + 1] = module;
   655 				ok_list[#ok_list + 1] = host_name;
   701 			else
   656 			else
   702 				err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
   657 				err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
   703 			end
   658 			end
   704 		end
   659 		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 "")
   660 	end
   706 			.. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
   661 
   707 			(#err_list > 0 and ("Failed to unload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
   662 	if #ok_list == 0 and #err_list == 0 then
   708 		return { status = "completed", info = info };
   663 		if is_global then
   709 	else
   664 			return { status = "completed", info = 'Successfully unloaded global module '..fields.module };
   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
   665 		else
   798 			return { status = "canceled", error = err }
   666 			return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
   799 		end
   667 		end
   800 	else
   668 	end
   801 		return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing";
   669 
   802 	end
   670 	local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
   803 end
   671 		.. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
   804 
   672 		(#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
   805 function deactivate_host_handler(self, data, state)
   673 	return { status = "completed", info = info };
   806 	local layout = dataforms_new {
   674 end);
   807 		title = "Deactivate host";
   675 
   808 		instructions = "";
   676 -- Activating a host
   809 
   677 local activate_host_layout = dataforms_new {
   810 		{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
   678 	title = "Activate host";
   811 		{ name = "host", type = "text-single", required = true, label = "Host:"};
   679 	instructions = "";
   812 	};
   680 
   813 	if state then
   681 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
   814 		if data.action == "cancel" then
   682 	{ name = "host", type = "text-single", required = true, label = "Host:"};
   815 			return { status = "canceled" };
   683 };
   816 		end
   684 
   817 		local fields, err = layout:data(data.form);
   685 local activate_host_handler = adhoc_simple(activate_host_layout, function(fields, err)
   818 		if err then
   686 	if err then
   819 			return generate_error_message(err);
   687 		return generate_error_message(err);
   820 		end
   688 	end
   821 		local ok, err = hostmanager_deactivate(fields.host);
   689 	local ok, err = hostmanager_activate(fields.host);
   822 
   690 
   823 		if ok then
   691 	if ok then
   824 			return { status = "completed", info = fields.host .. " deactivated" };
   692 		return { status = "completed", info = fields.host .. " activated" };
   825 		else
   693 	else
   826 			return { status = "canceled", error = err }
   694 		return { status = "canceled", error = err }
   827 		end
   695 	end
   828 	else
   696 end);
   829 		return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing";
   697 
   830 	end
   698 -- Deactivating a host
   831 end
   699 local deactivate_host_layout = dataforms_new {
       
   700 	title = "Deactivate host";
       
   701 	instructions = "";
       
   702 
       
   703 	{ name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
       
   704 	{ name = "host", type = "text-single", required = true, label = "Host:"};
       
   705 };
       
   706 
       
   707 local deactivate_host_handler = adhoc_simple(deactivate_host_layout, function(fields, err)
       
   708 	if err then
       
   709 		return generate_error_message(err);
       
   710 	end
       
   711 	local ok, err = hostmanager_deactivate(fields.host);
       
   712 
       
   713 	if ok then
       
   714 		return { status = "completed", info = fields.host .. " deactivated" };
       
   715 	else
       
   716 		return { status = "canceled", error = err }
       
   717 	end
       
   718 end);
   832 
   719 
   833 
   720 
   834 local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin");
   721 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");
   722 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");
   723 local config_reload_desc = adhoc_new("Reload configuration", "http://prosody.im/protocol/config#reload", config_reload_handler, "global_admin");