aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/mod_admin_adhoc.lua1179
-rw-r--r--plugins/mod_admin_telnet.lua14
-rw-r--r--plugins/mod_announce.lua1
-rw-r--r--plugins/mod_auth_anonymous.lua1
-rw-r--r--plugins/mod_auth_internal_hashed.lua23
-rw-r--r--plugins/mod_auth_internal_plain.lua21
-rw-r--r--plugins/mod_c2s.lua21
-rw-r--r--plugins/mod_component.lua2
-rw-r--r--plugins/mod_compression.lua5
-rw-r--r--plugins/mod_dialback.lua7
-rw-r--r--plugins/mod_groups.lua9
-rw-r--r--plugins/mod_http.lua7
-rw-r--r--plugins/mod_http_errors.lua1
-rw-r--r--plugins/mod_iq.lua2
-rw-r--r--plugins/mod_message.lua4
-rw-r--r--plugins/mod_motd.lua1
-rw-r--r--plugins/mod_posix.lua10
-rw-r--r--plugins/mod_presence.lua8
-rw-r--r--plugins/mod_privacy.lua16
-rw-r--r--plugins/mod_private.lua7
-rw-r--r--plugins/mod_proxy65.lua18
-rw-r--r--plugins/mod_pubsub.lua21
-rw-r--r--plugins/mod_register.lua6
-rw-r--r--plugins/mod_roster.lua1
-rw-r--r--plugins/mod_s2s/mod_s2s.lua184
-rw-r--r--plugins/mod_s2s/s2sout.lib.lua20
-rw-r--r--plugins/mod_saslauth.lua11
-rw-r--r--plugins/mod_storage_none.lua23
-rw-r--r--plugins/mod_storage_sql2.lua237
-rw-r--r--plugins/mod_tls.lua5
-rw-r--r--plugins/mod_vcard.lua9
-rw-r--r--plugins/muc/mod_muc.lua22
-rw-r--r--plugins/muc/muc.lib.lua35
-rw-r--r--plugins/sql.lib.lua9
34 files changed, 1135 insertions, 805 deletions
diff --git a/plugins/mod_admin_adhoc.lua b/plugins/mod_admin_adhoc.lua
index f136eb46..31c4bde4 100644
--- a/plugins/mod_admin_adhoc.lua
+++ b/plugins/mod_admin_adhoc.lua
@@ -10,8 +10,9 @@ local prosody = _G.prosody;
local hosts = prosody.hosts;
local t_concat = table.concat;
-local iterators = require "util.iterators";
-local keys, values = iterators.keys, iterators.values;
+local module_host = module:get_host();
+
+local keys = require "util.iterators".keys;
local usermanager_user_exists = require "core.usermanager".user_exists;
local usermanager_create_user = require "core.usermanager".create_user;
local usermanager_delete_user = require "core.usermanager".delete_user;
@@ -19,14 +20,15 @@ local usermanager_get_password = require "core.usermanager".get_password;
local usermanager_set_password = require "core.usermanager".set_password;
local hostmanager_activate = require "core.hostmanager".activate;
local hostmanager_deactivate = require "core.hostmanager".deactivate;
-local is_admin = require "core.usermanager".is_admin;
local rm_load_roster = require "core.rostermanager".load_roster;
-local st, jid, uuid = require "util.stanza", require "util.jid", require "util.uuid";
+local st, jid = require "util.stanza", require "util.jid";
local timer_add_task = require "util.timer".add_task;
local dataforms_new = require "util.dataforms".new;
local array = require "util.array";
local modulemanager = require "modulemanager";
local core_post_stanza = prosody.core_post_stanza;
+local adhoc_simple = require "util.adhoc".new_simple_form;
+local adhoc_initial = require "util.adhoc".new_initial_data_form;
module:depends("adhoc");
local adhoc_new = module:require "adhoc".new;
@@ -39,82 +41,69 @@ local function generate_error_message(errors)
return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
end
-function add_user_command_handler(self, data, state)
- local add_user_layout = dataforms_new{
- title = "Adding a User";
- instructions = "Fill out this form to add a user.";
+-- Adding a new user
+local add_user_layout = dataforms_new{
+ title = "Adding a User";
+ instructions = "Fill out this form to add a user.";
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for the account to be added" };
- { name = "password", type = "text-private", label = "The password for this account" };
- { name = "password-verify", type = "text-private", label = "Retype password" };
- };
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for the account to be added" };
+ { name = "password", type = "text-private", label = "The password for this account" };
+ { name = "password-verify", type = "text-private", label = "Retype password" };
+};
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
- local fields, err = add_user_layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- local username, host, resource = jid.split(fields.accountjid);
- if data.to ~= host then
- return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. data.to}};
- end
- if (fields["password"] == fields["password-verify"]) and username and host then
- if usermanager_user_exists(username, host) then
- return { status = "completed", error = { message = "Account already exists" } };
+local add_user_command_handler = adhoc_simple(add_user_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
+ local username, host, resource = jid.split(fields.accountjid);
+ if module_host ~= host then
+ return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. module_host}};
+ end
+ if (fields["password"] == fields["password-verify"]) and username and host then
+ if usermanager_user_exists(username, host) then
+ return { status = "completed", error = { message = "Account already exists" } };
+ else
+ if usermanager_create_user(username, fields.password, host) then
+ module:log("info", "Created new account %s@%s", username, host);
+ return { status = "completed", info = "Account successfully created" };
else
- if usermanager_create_user(username, fields.password, host) then
- module:log("info", "Created new account %s@%s", username, host);
- return { status = "completed", info = "Account successfully created" };
- else
- return { status = "completed", error = { message = "Failed to write data to disk" } };
- end
+ return { status = "completed", error = { message = "Failed to write data to disk" } };
end
- else
- module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or "<nil>");
- return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } };
end
else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = add_user_layout }, "executing";
+ module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or "<nil>");
+ return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } };
end
-end
+end);
-function change_user_password_command_handler(self, data, state)
- local change_user_password_layout = dataforms_new{
- title = "Changing a User Password";
- instructions = "Fill out this form to change a user's password.";
+-- Changing a user's password
+local change_user_password_layout = dataforms_new{
+ title = "Changing a User Password";
+ instructions = "Fill out this form to change a user's password.";
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" };
- { name = "password", type = "text-private", required = true, label = "The password for this account" };
- };
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" };
+ { name = "password", type = "text-private", required = true, label = "The password for this account" };
+};
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
- local fields, err = change_user_password_layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- local username, host, resource = jid.split(fields.accountjid);
- if data.to ~= host then
- return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. data.to}};
- end
- if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then
- return { status = "completed", info = "Password successfully changed" };
- else
- return { status = "completed", error = { message = "User does not exist" } };
- end
+local change_user_password_command_handler = adhoc_simple(change_user_password_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
+ local username, host, resource = jid.split(fields.accountjid);
+ if module_host ~= host then
+ return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. module_host}};
+ end
+ if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then
+ return { status = "completed", info = "Password successfully changed" };
else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = change_user_password_layout }, "executing";
+ return { status = "completed", error = { message = "User does not exist" } };
end
-end
+end);
-function config_reload_handler(self, data, state)
+-- Reloading the config
+local function config_reload_handler(self, data, state)
local ok, err = prosody.reload_config();
if ok then
return { status = "completed", info = "Configuration reloaded (modules may need to be reloaded for this to have an effect)" };
@@ -123,46 +112,39 @@ function config_reload_handler(self, data, state)
end
end
+-- Deleting a user's account
+local delete_user_layout = dataforms_new{
+ title = "Deleting a User";
+ instructions = "Fill out this form to delete a user.";
-function delete_user_command_handler(self, data, state)
- local delete_user_layout = dataforms_new{
- title = "Deleting a User";
- instructions = "Fill out this form to delete a user.";
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" };
+};
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" };
- };
-
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
- local fields, err = delete_user_layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- local failed = {};
- local succeeded = {};
- for _, aJID in ipairs(fields.accountjids) do
- local username, host, resource = jid.split(aJID);
- if (host == data.to) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then
- module:log("debug", "User %s has been deleted", aJID);
- succeeded[#succeeded+1] = aJID;
- else
- module:log("debug", "Tried to delete non-existant user %s", aJID);
- failed[#failed+1] = aJID;
- end
+local delete_user_command_handler = adhoc_simple(delete_user_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
+ local failed = {};
+ local succeeded = {};
+ for _, aJID in ipairs(fields.accountjids) do
+ local username, host, resource = jid.split(aJID);
+ if (host == module_host) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then
+ module:log("debug", "User %s has been deleted", aJID);
+ succeeded[#succeeded+1] = aJID;
+ else
+ module:log("debug", "Tried to delete non-existant user %s", aJID);
+ failed[#failed+1] = aJID;
end
- return {status = "completed", info = (#succeeded ~= 0 and
- "The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "")..
- (#failed ~= 0 and
- "The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") };
- else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = delete_user_layout }, "executing";
end
-end
-
-function disconnect_user(match_jid)
+ return {status = "completed", info = (#succeeded ~= 0 and
+ "The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "")..
+ (#failed ~= 0 and
+ "The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") };
+end);
+
+-- Ending a user's session
+local function disconnect_user(match_jid)
local node, hostname, givenResource = jid.split(match_jid);
local host = hosts[hostname];
local sessions = host.sessions[node] and host.sessions[node].sessions;
@@ -175,447 +157,382 @@ function disconnect_user(match_jid)
return true;
end
-function end_user_session_handler(self, data, state)
- local end_user_session_layout = dataforms_new{
- title = "Ending a User Session";
- instructions = "Fill out this form to end a user's session.";
+local end_user_session_layout = dataforms_new{
+ title = "Ending a User Session";
+ instructions = "Fill out this form to end a user's session.";
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" };
- };
-
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" };
+};
- local fields, err = end_user_session_layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- local failed = {};
- local succeeded = {};
- for _, aJID in ipairs(fields.accountjids) do
- local username, host, resource = jid.split(aJID);
- if (host == data.to) and usermanager_user_exists(username, host) and disconnect_user(aJID) then
- succeeded[#succeeded+1] = aJID;
- else
- failed[#failed+1] = aJID;
- end
- end
- return {status = "completed", info = (#succeeded ~= 0 and
- "The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "")..
- (#failed ~= 0 and
- "The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") };
- else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = end_user_session_layout }, "executing";
+local end_user_session_handler = adhoc_simple(end_user_session_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
end
-end
-
-function get_user_password_handler(self, data, state)
- local get_user_password_layout = dataforms_new{
- title = "Getting User's Password";
- instructions = "Fill out this form to get a user's password.";
-
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" };
- };
-
- local get_user_password_result_layout = dataforms_new{
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjid", type = "jid-single", label = "JID" };
- { name = "password", type = "text-single", label = "Password" };
- };
-
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
- local fields, err = get_user_password_layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- local user, host, resource = jid.split(fields.accountjid);
- local accountjid = "";
- local password = "";
- if host ~= data.to then
- return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. data.to } };
- elseif usermanager_user_exists(user, host) then
- accountjid = fields.accountjid;
- password = usermanager_get_password(user, host);
+ local failed = {};
+ local succeeded = {};
+ for _, aJID in ipairs(fields.accountjids) do
+ local username, host, resource = jid.split(aJID);
+ if (host == module_host) and usermanager_user_exists(username, host) and disconnect_user(aJID) then
+ succeeded[#succeeded+1] = aJID;
else
- return { status = "completed", error = { message = "User does not exist" } };
+ failed[#failed+1] = aJID;
end
- return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } };
+ end
+ return {status = "completed", info = (#succeeded ~= 0 and
+ "The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "")..
+ (#failed ~= 0 and
+ "The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") };
+end);
+
+-- Getting a user's password
+local get_user_password_layout = dataforms_new{
+ title = "Getting User's Password";
+ instructions = "Fill out this form to get a user's password.";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" };
+};
+
+local get_user_password_result_layout = dataforms_new{
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjid", type = "jid-single", label = "JID" };
+ { name = "password", type = "text-single", label = "Password" };
+};
+
+local get_user_password_handler = adhoc_simple(get_user_password_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
+ local user, host, resource = jid.split(fields.accountjid);
+ local accountjid = "";
+ local password = "";
+ if host ~= module_host then
+ return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. module_host } };
+ elseif usermanager_user_exists(user, host) then
+ accountjid = fields.accountjid;
+ password = usermanager_get_password(user, host);
else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_password_layout }, "executing";
+ return { status = "completed", error = { message = "User does not exist" } };
+ end
+ return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } };
+end);
+
+-- Getting a user's roster
+local get_user_roster_layout = dataforms_new{
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" };
+};
+
+local get_user_roster_result_layout = dataforms_new{
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjid", type = "jid-single", label = "This is the roster for" };
+ { name = "roster", type = "text-multi", label = "Roster XML" };
+};
+
+local get_user_roster_handler = adhoc_simple(get_user_roster_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
end
-end
-
-function get_user_roster_handler(self, data, state)
- local get_user_roster_layout = dataforms_new{
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" };
- };
-
- local get_user_roster_result_layout = dataforms_new{
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjid", type = "jid-single", label = "This is the roster for" };
- { name = "roster", type = "text-multi", label = "Roster XML" };
- };
-
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
-
- local fields, err = get_user_roster_layout:data(data.form);
-
- if err then
- return generate_error_message(err);
- end
- local user, host, resource = jid.split(fields.accountjid);
- if host ~= data.to then
- return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. data.to } };
- elseif not usermanager_user_exists(user, host) then
- return { status = "completed", error = { message = "User does not exist" } };
- end
- local roster = rm_load_roster(user, host);
-
- local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
- for jid in pairs(roster) do
- if jid ~= "pending" and jid then
- query:tag("item", {
- jid = jid,
- subscription = roster[jid].subscription,
- ask = roster[jid].ask,
- name = roster[jid].name,
- });
- for group in pairs(roster[jid].groups) do
- query:tag("group"):text(group):up();
- end
- query:up();
+ local user, host, resource = jid.split(fields.accountjid);
+ if host ~= module_host then
+ return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. module_host } };
+ elseif not usermanager_user_exists(user, host) then
+ return { status = "completed", error = { message = "User does not exist" } };
+ end
+ local roster = rm_load_roster(user, host);
+
+ local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
+ for jid in pairs(roster) do
+ if jid ~= "pending" and jid then
+ query:tag("item", {
+ jid = jid,
+ subscription = roster[jid].subscription,
+ ask = roster[jid].ask,
+ name = roster[jid].name,
+ });
+ for group in pairs(roster[jid].groups) do
+ query:tag("group"):text(group):up();
end
+ query:up();
end
-
- local query_text = tostring(query):gsub("><", ">\n<");
-
- local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result");
- result:add_child(query);
- return { status = "completed", other = result };
- else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_roster_layout }, "executing";
end
-end
-function get_user_stats_handler(self, data, state)
- local get_user_stats_layout = dataforms_new{
- title = "Get User Statistics";
- instructions = "Fill out this form to gather user statistics.";
+ local query_text = tostring(query):gsub("><", ">\n<");
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" };
- };
+ local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result");
+ result:add_child(query);
+ return { status = "completed", other = result };
+end);
- local get_user_stats_result_layout = dataforms_new{
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "ipaddresses", type = "text-multi", label = "IP Addresses" };
- { name = "rostersize", type = "text-single", label = "Roster size" };
- { name = "onlineresources", type = "text-multi", label = "Online Resources" };
- };
+-- Getting user statistics
+local get_user_stats_layout = dataforms_new{
+ title = "Get User Statistics";
+ instructions = "Fill out this form to gather user statistics.";
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
-
- local fields, err = get_user_stats_layout:data(data.form);
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" };
+};
- if err then
- return generate_error_message(err);
- end
+local get_user_stats_result_layout = dataforms_new{
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "ipaddresses", type = "text-multi", label = "IP Addresses" };
+ { name = "rostersize", type = "text-single", label = "Roster size" };
+ { name = "onlineresources", type = "text-multi", label = "Online Resources" };
+};
- local user, host, resource = jid.split(fields.accountjid);
- if host ~= data.to then
- return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. data.to } };
- elseif not usermanager_user_exists(user, host) then
- return { status = "completed", error = { message = "User does not exist" } };
- end
- local roster = rm_load_roster(user, host);
- local rostersize = 0;
- local IPs = "";
- local resources = "";
- for jid in pairs(roster) do
- if jid ~= "pending" and jid then
- rostersize = rostersize + 1;
- end
- end
- for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do
- resources = resources .. "\n" .. resource;
- IPs = IPs .. "\n" .. session.ip;
- end
- return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize),
- onlineresources = resources}} };
- else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_stats_layout }, "executing";
+local get_user_stats_handler = adhoc_simple(get_user_stats_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
end
-end
-function get_online_users_command_handler(self, data, state)
- local get_online_users_layout = dataforms_new{
- title = "Getting List of Online Users";
- instructions = "How many users should be returned at most?";
-
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "max_items", type = "list-single", label = "Maximum number of users",
- value = { "25", "50", "75", "100", "150", "200", "all" } };
- { name = "details", type = "boolean", label = "Show details" };
- };
-
- local get_online_users_result_layout = dataforms_new{
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "onlineuserjids", type = "text-multi", label = "The list of all online users" };
- };
-
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
-
- local fields, err = get_online_users_layout:data(data.form);
-
- if err then
- return generate_error_message(err);
+ local user, host, resource = jid.split(fields.accountjid);
+ if host ~= module_host then
+ return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. module_host } };
+ elseif not usermanager_user_exists(user, host) then
+ return { status = "completed", error = { message = "User does not exist" } };
+ end
+ local roster = rm_load_roster(user, host);
+ local rostersize = 0;
+ local IPs = "";
+ local resources = "";
+ for jid in pairs(roster) do
+ if jid ~= "pending" and jid then
+ rostersize = rostersize + 1;
end
+ end
+ for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do
+ resources = resources .. "\n" .. resource;
+ IPs = IPs .. "\n" .. session.ip;
+ end
+ return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize),
+ onlineresources = resources}} };
+end);
+
+-- Getting a list of online users
+local get_online_users_layout = dataforms_new{
+ title = "Getting List of Online Users";
+ instructions = "How many users should be returned at most?";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "max_items", type = "list-single", label = "Maximum number of users",
+ value = { "25", "50", "75", "100", "150", "200", "all" } };
+ { name = "details", type = "boolean", label = "Show details" };
+};
+
+local get_online_users_result_layout = dataforms_new{
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "onlineuserjids", type = "text-multi", label = "The list of all online users" };
+};
+
+local get_online_users_command_handler = adhoc_simple(get_online_users_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
- local max_items = nil
- if fields.max_items ~= "all" then
- max_items = tonumber(fields.max_items);
- end
- local count = 0;
- local users = {};
- for username, user in pairs(hosts[data.to].sessions or {}) do
- if (max_items ~= nil) and (count >= max_items) then
- break;
- end
- users[#users+1] = username.."@"..data.to;
- count = count + 1;
- if fields.details then
- for resource, session in pairs(user.sessions or {}) do
- local status, priority = "unavailable", tostring(session.priority or "-");
- if session.presence then
- status = session.presence:child_with_name("show");
- if status then
- status = status:get_text() or "[invalid!]";
- else
- status = "available";
- end
+ local max_items = nil
+ if fields.max_items ~= "all" then
+ max_items = tonumber(fields.max_items);
+ end
+ local count = 0;
+ local users = {};
+ for username, user in pairs(hosts[module_host].sessions or {}) do
+ if (max_items ~= nil) and (count >= max_items) then
+ break;
+ end
+ users[#users+1] = username.."@"..module_host;
+ count = count + 1;
+ if fields.details then
+ for resource, session in pairs(user.sessions or {}) do
+ local status, priority = "unavailable", tostring(session.priority or "-");
+ if session.presence then
+ status = session.presence:child_with_name("show");
+ if status then
+ status = status:get_text() or "[invalid!]";
+ else
+ status = "available";
end
- users[#users+1] = " - "..resource..": "..status.."("..priority..")";
end
+ users[#users+1] = " - "..resource..": "..status.."("..priority..")";
end
end
- return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
- else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_online_users_layout }, "executing";
end
-end
+ return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
+end);
-function list_modules_handler(self, data, state)
- local result = dataforms_new {
- title = "List of loaded modules";
+-- Getting a list of loaded modules
+local list_modules_result = dataforms_new {
+ title = "List of loaded modules";
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" };
- { name = "modules", type = "text-multi", label = "The following modules are loaded:" };
- };
-
- local modules = array.collect(keys(hosts[data.to].modules)):sort():concat("\n");
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" };
+ { name = "modules", type = "text-multi", label = "The following modules are loaded:" };
+};
- return { status = "completed", result = { layout = result; values = { modules = modules } } };
+local function list_modules_handler(self, data, state)
+ local modules = array.collect(keys(hosts[module_host].modules)):sort():concat("\n");
+ return { status = "completed", result = { layout = list_modules_result; values = { modules = modules } } };
end
-function load_module_handler(self, data, state)
- local layout = dataforms_new {
- title = "Load module";
- instructions = "Specify the module to be loaded";
+-- Loading a module
+local load_module_layout = dataforms_new {
+ title = "Load module";
+ instructions = "Specify the module to be loaded";
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" };
- { name = "module", type = "text-single", required = true, label = "Module to be loaded:"};
- };
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
- local fields, err = layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- if modulemanager.is_loaded(data.to, fields.module) then
- return { status = "completed", info = "Module already loaded" };
- end
- local ok, err = modulemanager.load(data.to, fields.module);
- if ok then
- return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..data.to..'".' };
- else
- return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..data.to..
- '". Error was: "'..tostring(err or "<unspecified>")..'"' } };
- end
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" };
+ { name = "module", type = "text-single", required = true, label = "Module to be loaded:"};
+};
+
+local load_module_handler = adhoc_simple(load_module_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
+ if modulemanager.is_loaded(module_host, fields.module) then
+ return { status = "completed", info = "Module already loaded" };
+ end
+ local ok, err = modulemanager.load(module_host, fields.module);
+ if ok then
+ return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..module_host..'".' };
else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing";
+ return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..module_host..
+ '". Error was: "'..tostring(err or "<unspecified>")..'"' } };
end
-end
-
-local function globally_load_module_handler(self, data, state)
- local layout = dataforms_new {
- title = "Globally load module";
- instructions = "Specify the module to be loaded on all hosts";
-
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" };
- { name = "module", type = "text-single", required = true, label = "Module to globally load:"};
- };
- if state then
- local ok_list, err_list = {}, {};
+end);
- if data.action == "cancel" then
- return { status = "canceled" };
- end
+-- Globally loading a module
+local globally_load_module_layout = dataforms_new {
+ title = "Globally load module";
+ instructions = "Specify the module to be loaded on all hosts";
- local fields, err = layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" };
+ { name = "module", type = "text-single", required = true, label = "Module to globally load:"};
+};
- local ok, err = modulemanager.load(data.to, fields.module);
- if ok then
- ok_list[#ok_list + 1] = data.to;
- else
- err_list[#err_list + 1] = data.to .. " (Error: " .. tostring(err) .. ")";
- end
+local globally_load_module_handler = adhoc_simple(globally_load_module_layout, function(fields, err)
+ local ok_list, err_list = {}, {};
- -- Is this a global module?
- if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(data.to, fields.module) then
- return { status = "completed", info = 'Global module '..fields.module..' loaded.' };
- end
-
- -- This is either a shared or "normal" module, load it on all other hosts
- for host_name, host in pairs(hosts) do
- if host_name ~= data.to and host.type == "local" then
- local ok, err = modulemanager.load(host_name, fields.module);
- if ok then
- ok_list[#ok_list + 1] = host_name;
- else
- err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
- end
- end
- end
+ if err then
+ return generate_error_message(err);
+ end
- local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "")
- .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
- (#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or "");
- return { status = "completed", info = info };
+ local ok, err = modulemanager.load(module_host, fields.module);
+ if ok then
+ ok_list[#ok_list + 1] = module_host;
else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing";
+ err_list[#err_list + 1] = module_host .. " (Error: " .. tostring(err) .. ")";
end
-end
-function reload_modules_handler(self, data, state)
- local layout = dataforms_new {
- title = "Reload modules";
- instructions = "Select the modules to be reloaded";
+ -- Is this a global module?
+ if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(module_host, fields.module) then
+ return { status = "completed", info = 'Global module '..fields.module..' loaded.' };
+ end
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" };
- { name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"};
- };
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
- local fields, err = layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- local ok_list, err_list = {}, {};
- for _, module in ipairs(fields.modules) do
- local ok, err = modulemanager.reload(data.to, module);
+ -- This is either a shared or "normal" module, load it on all other hosts
+ for host_name, host in pairs(hosts) do
+ if host_name ~= module_host and host.type == "local" then
+ local ok, err = modulemanager.load(host_name, fields.module);
if ok then
- ok_list[#ok_list + 1] = module;
+ ok_list[#ok_list + 1] = host_name;
else
- err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
+ err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
end
end
- local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "")
- .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
- (#err_list > 0 and ("Failed to reload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
- return { status = "completed", info = info };
- else
- local modules = array.collect(keys(hosts[data.to].modules)):sort();
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout; values = { modules = modules } } }, "executing";
end
-end
-local function globally_reload_module_handler(self, data, state)
- local layout = dataforms_new {
- title = "Globally reload module";
- instructions = "Specify the module to reload on all hosts";
-
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" };
- { name = "module", type = "list-single", required = true, label = "Module to globally reload:"};
- };
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
-
- local is_global = false;
- local fields, err = layout:data(data.form);
- if err then
- return generate_error_message(err);
+ local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "")
+ .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
+ (#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or "");
+ return { status = "completed", info = info };
+end);
+
+-- Reloading modules
+local reload_modules_layout = dataforms_new {
+ title = "Reload modules";
+ instructions = "Select the modules to be reloaded";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" };
+ { name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"};
+};
+
+local reload_modules_handler = adhoc_initial(reload_modules_layout, function()
+ return { modules = array.collect(keys(hosts[module_host].modules)):sort() };
+end, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
+ local ok_list, err_list = {}, {};
+ for _, module in ipairs(fields.modules) do
+ local ok, err = modulemanager.reload(module_host, module);
+ if ok then
+ ok_list[#ok_list + 1] = module;
+ else
+ err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
end
+ end
+ local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "")
+ .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
+ (#err_list > 0 and ("Failed to reload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or "");
+ return { status = "completed", info = info };
+end);
+
+-- Globally reloading a module
+local globally_reload_module_layout = dataforms_new {
+ title = "Globally reload module";
+ instructions = "Specify the module to reload on all hosts";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" };
+ { name = "module", type = "list-single", required = true, label = "Module to globally reload:"};
+};
+
+local globally_reload_module_handler = adhoc_initial(globally_reload_module_layout, function()
+ local loaded_modules = array(keys(modulemanager.get_modules("*")));
+ for _, host in pairs(hosts) do
+ loaded_modules:append(array(keys(host.modules)));
+ end
+ loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
+ return { module = loaded_modules };
+end, function(fields, err)
+ local is_global = false;
- if modulemanager.is_loaded("*", fields.module) then
- local ok, err = modulemanager.reload("*", fields.module);
- if not ok then
- return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err };
- end
- is_global = true;
- end
+ if err then
+ return generate_error_message(err);
+ end
- local ok_list, err_list = {}, {};
- for host_name, host in pairs(hosts) do
- if modulemanager.is_loaded(host_name, fields.module) then
- local ok, err = modulemanager.reload(host_name, fields.module);
- if ok then
- ok_list[#ok_list + 1] = host_name;
- else
- err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
- end
- end
+ if modulemanager.is_loaded("*", fields.module) then
+ local ok, err = modulemanager.reload("*", fields.module);
+ if not ok then
+ return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err };
end
+ is_global = true;
+ end
- if #ok_list == 0 and #err_list == 0 then
- if is_global then
- return { status = "completed", info = 'Successfully reloaded global module '..fields.module };
+ local ok_list, err_list = {}, {};
+ for host_name, host in pairs(hosts) do
+ if modulemanager.is_loaded(host_name, fields.module) then
+ local ok, err = modulemanager.reload(host_name, fields.module);
+ if ok then
+ ok_list[#ok_list + 1] = host_name;
else
- return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
+ err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
end
end
+ end
- local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
- .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
- (#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
- return { status = "completed", info = info };
- else
- local loaded_modules = array(keys(modulemanager.get_modules("*")));
- for _, host in pairs(hosts) do
- loaded_modules:append(array(keys(host.modules)));
+ if #ok_list == 0 and #err_list == 0 then
+ if is_global then
+ return { status = "completed", info = 'Successfully reloaded global module '..fields.module };
+ else
+ return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
end
- loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout, values = { module = loaded_modules } } }, "executing";
end
-end
-function send_to_online(message, server)
+ local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
+ .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
+ (#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
+ return { status = "completed", info = info };
+end);
+
+local function send_to_online(message, server)
if server then
sessions = { [server] = hosts[server] };
else
@@ -635,202 +552,170 @@ function send_to_online(message, server)
return c;
end
-function shut_down_service_handler(self, data, state)
- local shut_down_service_layout = dataforms_new{
- title = "Shutting Down the Service";
- instructions = "Fill out this form to shut down the service.";
-
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
- { name = "delay", type = "list-single", label = "Time delay before shutting down",
- value = { {label = "30 seconds", value = "30"},
- {label = "60 seconds", value = "60"},
- {label = "90 seconds", value = "90"},
- {label = "2 minutes", value = "120"},
- {label = "3 minutes", value = "180"},
- {label = "4 minutes", value = "240"},
- {label = "5 minutes", value = "300"},
- };
+-- Shutting down the service
+local shut_down_service_layout = dataforms_new{
+ title = "Shutting Down the Service";
+ instructions = "Fill out this form to shut down the service.";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+ { name = "delay", type = "list-single", label = "Time delay before shutting down",
+ value = { {label = "30 seconds", value = "30"},
+ {label = "60 seconds", value = "60"},
+ {label = "90 seconds", value = "90"},
+ {label = "2 minutes", value = "120"},
+ {label = "3 minutes", value = "180"},
+ {label = "4 minutes", value = "240"},
+ {label = "5 minutes", value = "300"},
};
- { name = "announcement", type = "text-multi", label = "Announcement" };
};
+ { name = "announcement", type = "text-multi", label = "Announcement" };
+};
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
+local shut_down_service_handler = adhoc_simple(shut_down_service_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
- local fields, err = shut_down_service_layout:data(data.form);
+ if fields.announcement and #fields.announcement > 0 then
+ local message = st.message({type = "headline"}, fields.announcement):up()
+ :tag("subject"):text("Server is shutting down");
+ send_to_online(message);
+ end
- if err then
- return generate_error_message(err);
- end
+ timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end);
- if fields.announcement and #fields.announcement > 0 then
- local message = st.message({type = "headline"}, fields.announcement):up()
- :tag("subject"):text("Server is shutting down");
- send_to_online(message);
- end
+ return { status = "completed", info = "Server is about to shut down" };
+end);
- timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end);
+-- Unloading modules
+local unload_modules_layout = dataforms_new {
+ title = "Unload modules";
+ instructions = "Select the modules to be unloaded";
- return { status = "completed", info = "Server is about to shut down" };
- else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = shut_down_service_layout }, "executing";
- end
-end
-
-function unload_modules_handler(self, data, state)
- local layout = dataforms_new {
- title = "Unload modules";
- instructions = "Select the modules to be unloaded";
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" };
+ { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"};
+};
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" };
- { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"};
- };
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
+local unload_modules_handler = adhoc_initial(unload_modules_layout, function()
+ return { modules = array.collect(keys(hosts[module_host].modules)):sort() };
+end, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
+ local ok_list, err_list = {}, {};
+ for _, module in ipairs(fields.modules) do
+ local ok, err = modulemanager.unload(module_host, module);
+ if ok then
+ ok_list[#ok_list + 1] = module;
+ else
+ err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
end
- local fields, err = layout:data(data.form);
- if err then
- return generate_error_message(err);
+ end
+ local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "")
+ .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
+ (#err_list > 0 and ("Failed to unload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or "");
+ return { status = "completed", info = info };
+end);
+
+-- Globally unloading a module
+local globally_unload_module_layout = dataforms_new {
+ title = "Globally unload module";
+ instructions = "Specify a module to unload on all hosts";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" };
+ { name = "module", type = "list-single", required = true, label = "Module to globally unload:"};
+};
+
+local globally_unload_module_handler = adhoc_initial(globally_unload_module_layout, function()
+ local loaded_modules = array(keys(modulemanager.get_modules("*")));
+ for _, host in pairs(hosts) do
+ loaded_modules:append(array(keys(host.modules)));
+ end
+ loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
+ return { module = loaded_modules };
+end, function(fields, err)
+ local is_global = false;
+ if err then
+ return generate_error_message(err);
+ end
+
+ if modulemanager.is_loaded("*", fields.module) then
+ local ok, err = modulemanager.unload("*", fields.module);
+ if not ok then
+ return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err };
end
- local ok_list, err_list = {}, {};
- for _, module in ipairs(fields.modules) do
- local ok, err = modulemanager.unload(data.to, module);
+ is_global = true;
+ end
+
+ local ok_list, err_list = {}, {};
+ for host_name, host in pairs(hosts) do
+ if modulemanager.is_loaded(host_name, fields.module) then
+ local ok, err = modulemanager.unload(host_name, fields.module);
if ok then
- ok_list[#ok_list + 1] = module;
+ ok_list[#ok_list + 1] = host_name;
else
- err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
+ err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
end
end
- local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "")
- .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
- (#err_list > 0 and ("Failed to unload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
- return { status = "completed", info = info };
- else
- local modules = array.collect(keys(hosts[data.to].modules)):sort();
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout; values = { modules = modules } } }, "executing";
end
-end
-
-local function globally_unload_module_handler(self, data, state)
- local layout = dataforms_new {
- title = "Globally unload module";
- instructions = "Specify a module to unload on all hosts";
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" };
- { name = "module", type = "list-single", required = true, label = "Module to globally unload:"};
- };
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
+ if #ok_list == 0 and #err_list == 0 then
+ if is_global then
+ return { status = "completed", info = 'Successfully unloaded global module '..fields.module };
+ else
+ return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
end
+ end
- local is_global = false;
- local fields, err = layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
+ local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
+ .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
+ (#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
+ return { status = "completed", info = info };
+end);
- if modulemanager.is_loaded("*", fields.module) then
- local ok, err = modulemanager.unload("*", fields.module);
- if not ok then
- return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err };
- end
- is_global = true;
- end
+-- Activating a host
+local activate_host_layout = dataforms_new {
+ title = "Activate host";
+ instructions = "";
- local ok_list, err_list = {}, {};
- for host_name, host in pairs(hosts) do
- if modulemanager.is_loaded(host_name, fields.module) then
- local ok, err = modulemanager.unload(host_name, fields.module);
- if ok then
- ok_list[#ok_list + 1] = host_name;
- else
- err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
- end
- end
- end
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
+ { name = "host", type = "text-single", required = true, label = "Host:"};
+};
- if #ok_list == 0 and #err_list == 0 then
- if is_global then
- return { status = "completed", info = 'Successfully unloaded global module '..fields.module };
- else
- return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
- end
- end
+local activate_host_handler = adhoc_simple(activate_host_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
+ end
+ local ok, err = hostmanager_activate(fields.host);
- local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
- .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
- (#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
- return { status = "completed", info = info };
+ if ok then
+ return { status = "completed", info = fields.host .. " activated" };
else
- local loaded_modules = array(keys(modulemanager.get_modules("*")));
- for _, host in pairs(hosts) do
- loaded_modules:append(array(keys(host.modules)));
- end
- loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout, values = { module = loaded_modules } } }, "executing";
+ return { status = "canceled", error = err }
end
-end
+end);
+-- Deactivating a host
+local deactivate_host_layout = dataforms_new {
+ title = "Deactivate host";
+ instructions = "";
-function activate_host_handler(self, data, state)
- local layout = dataforms_new {
- title = "Activate host";
- instructions = "";
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
+ { name = "host", type = "text-single", required = true, label = "Host:"};
+};
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
- { name = "host", type = "text-single", required = true, label = "Host:"};
- };
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
- local fields, err = layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- local ok, err = hostmanager_activate(fields.host);
-
- if ok then
- return { status = "completed", info = fields.host .. " activated" };
- else
- return { status = "canceled", error = err }
- end
- else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing";
+local deactivate_host_handler = adhoc_simple(deactivate_host_layout, function(fields, err)
+ if err then
+ return generate_error_message(err);
end
-end
-
-function deactivate_host_handler(self, data, state)
- local layout = dataforms_new {
- title = "Deactivate host";
- instructions = "";
+ local ok, err = hostmanager_deactivate(fields.host);
- { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
- { name = "host", type = "text-single", required = true, label = "Host:"};
- };
- if state then
- if data.action == "cancel" then
- return { status = "canceled" };
- end
- local fields, err = layout:data(data.form);
- if err then
- return generate_error_message(err);
- end
- local ok, err = hostmanager_deactivate(fields.host);
-
- if ok then
- return { status = "completed", info = fields.host .. " deactivated" };
- else
- return { status = "canceled", error = err }
- end
+ if ok then
+ return { status = "completed", info = fields.host .. " deactivated" };
else
- return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing";
+ return { status = "canceled", error = err }
end
-end
+end);
local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin");
diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua
index e1b90684..2622a5f9 100644
--- a/plugins/mod_admin_telnet.lua
+++ b/plugins/mod_admin_telnet.lua
@@ -903,13 +903,23 @@ local console_room_mt = {
end;
};
-function def_env.muc:room(room_jid)
- local room_name, host = jid_split(room_jid);
+local function check_muc(jid)
+ local room_name, host = jid_split(jid);
if not hosts[host] then
return nil, "No such host: "..host;
elseif not hosts[host].modules.muc then
return nil, "Host '"..host.."' is not a MUC service";
end
+ return room_name, host;
+end
+
+function def_env.muc:create(room_jid)
+ local room, host = check_muc(room_jid);
+ return hosts[host].modules.muc.create_room(room_jid);
+end
+
+function def_env.muc:room(room_jid)
+ local room_name, host = check_muc(room_jid);
local room_obj = hosts[host].modules.muc.rooms[room_jid];
if not room_obj then
return nil, "No such room: "..room_jid;
diff --git a/plugins/mod_announce.lua b/plugins/mod_announce.lua
index 0872bd21..96976d6f 100644
--- a/plugins/mod_announce.lua
+++ b/plugins/mod_announce.lua
@@ -8,6 +8,7 @@
local st, jid = require "util.stanza", require "util.jid";
+local hosts = prosody.hosts;
local is_admin = require "core.usermanager".is_admin;
function send_to_online(message, host)
diff --git a/plugins/mod_auth_anonymous.lua b/plugins/mod_auth_anonymous.lua
index a327f438..c877d532 100644
--- a/plugins/mod_auth_anonymous.lua
+++ b/plugins/mod_auth_anonymous.lua
@@ -8,6 +8,7 @@
local new_sasl = require "util.sasl".new;
local datamanager = require "util.datamanager";
+local hosts = prosody.hosts;
-- define auth provider
local provider = {};
diff --git a/plugins/mod_auth_internal_hashed.lua b/plugins/mod_auth_internal_hashed.lua
index cb6cc8ff..2b041e43 100644
--- a/plugins/mod_auth_internal_hashed.lua
+++ b/plugins/mod_auth_internal_hashed.lua
@@ -7,13 +7,14 @@
-- COPYING file in the source package for more information.
--
-local datamanager = require "util.datamanager";
local log = require "util.logger".init("auth_internal_hashed");
local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
local usermanager = require "core.usermanager";
local generate_uuid = require "util.uuid".generate;
local new_sasl = require "util.sasl".new;
+local accounts = module:open_store("accounts");
+
local to_hex;
do
local function replace_byte_with_hex(byte)
@@ -44,7 +45,7 @@ local provider = {};
log("debug", "initializing internal_hashed authentication provider for host '%s'", host);
function provider.test_password(username, password)
- local credentials = datamanager.load(username, host, "accounts") or {};
+ local credentials = accounts:get(username) or {};
if credentials.password ~= nil and string.len(credentials.password) ~= 0 then
if credentials.password ~= password then
@@ -75,7 +76,7 @@ function provider.test_password(username, password)
end
function provider.set_password(username, password)
- local account = datamanager.load(username, host, "accounts");
+ local account = accounts:get(username);
if account then
account.salt = account.salt or generate_uuid();
account.iteration_count = account.iteration_count or iteration_count;
@@ -87,13 +88,13 @@ function provider.set_password(username, password)
account.server_key = server_key_hex
account.password = nil;
- return datamanager.store(username, host, "accounts", account);
+ return accounts:set(username, account);
end
return nil, "Account not available.";
end
function provider.user_exists(username)
- local account = datamanager.load(username, host, "accounts");
+ local account = accounts:get(username);
if not account then
log("debug", "account not found for username '%s' at host '%s'", username, host);
return nil, "Auth failed. Invalid username";
@@ -102,22 +103,22 @@ function provider.user_exists(username)
end
function provider.users()
- return datamanager.users(host, "accounts");
+ return accounts:users();
end
function provider.create_user(username, password)
if password == nil then
- return datamanager.store(username, host, "accounts", {});
+ return accounts:set(username, {});
end
local salt = generate_uuid();
local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
local stored_key_hex = to_hex(stored_key);
local server_key_hex = to_hex(server_key);
- return datamanager.store(username, host, "accounts", {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count});
+ return accounts:set(username, {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count});
end
function provider.delete_user(username)
- return datamanager.store(username, host, "accounts", nil);
+ return accounts:set(username, nil);
end
function provider.get_sasl_handler()
@@ -126,11 +127,11 @@ function provider.get_sasl_handler()
return usermanager.test_password(username, realm, password), true;
end,
scram_sha_1 = function(sasl, username, realm)
- local credentials = datamanager.load(username, host, "accounts");
+ local credentials = accounts:get(username);
if not credentials then return; end
if credentials.password then
usermanager.set_password(username, credentials.password, host);
- credentials = datamanager.load(username, host, "accounts");
+ credentials = accounts:get(username);
if not credentials then return; end
end
diff --git a/plugins/mod_auth_internal_plain.lua b/plugins/mod_auth_internal_plain.lua
index 178ae5a5..d226fdbe 100644
--- a/plugins/mod_auth_internal_plain.lua
+++ b/plugins/mod_auth_internal_plain.lua
@@ -6,20 +6,21 @@
-- COPYING file in the source package for more information.
--
-local datamanager = require "util.datamanager";
local usermanager = require "core.usermanager";
local new_sasl = require "util.sasl".new;
local log = module._log;
local host = module.host;
+local accounts = module:open_store("accounts");
+
-- define auth provider
local provider = {};
log("debug", "initializing internal_plain authentication provider for host '%s'", host);
function provider.test_password(username, password)
- log("debug", "test password '%s' for user %s at host %s", password, username, host);
- local credentials = datamanager.load(username, host, "accounts") or {};
+ log("debug", "test password for user %s at host %s", username, host);
+ local credentials = accounts:get(username) or {};
if password == credentials.password then
return true;
@@ -30,20 +31,20 @@ end
function provider.get_password(username)
log("debug", "get_password for username '%s' at host '%s'", username, host);
- return (datamanager.load(username, host, "accounts") or {}).password;
+ return (accounts:get(username) or {}).password;
end
function provider.set_password(username, password)
- local account = datamanager.load(username, host, "accounts");
+ local account = accounts:get(username);
if account then
account.password = password;
- return datamanager.store(username, host, "accounts", account);
+ return accounts:set(username, account);
end
return nil, "Account not available.";
end
function provider.user_exists(username)
- local account = datamanager.load(username, host, "accounts");
+ local account = accounts:get(username);
if not account then
log("debug", "account not found for username '%s' at host '%s'", username, host);
return nil, "Auth failed. Invalid username";
@@ -52,15 +53,15 @@ function provider.user_exists(username)
end
function provider.users()
- return datamanager.users(host, "accounts");
+ return accounts:users();
end
function provider.create_user(username, password)
- return datamanager.store(username, host, "accounts", {password = password});
+ return accounts:set(username, {password = password});
end
function provider.delete_user(username)
- return datamanager.store(username, host, "accounts", nil);
+ return accounts:set(username, nil);
end
function provider.get_sasl_handler()
diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
index 89d678ca..efef8763 100644
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -29,6 +29,7 @@ local opt_keepalives = module:get_option_boolean("tcp_keepalives", false);
local sessions = module:shared("sessions");
local core_process_stanza = prosody.core_process_stanza;
+local hosts = prosody.hosts;
local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza };
local listener = {};
@@ -115,7 +116,7 @@ function stream_callbacks.error(session, error, data)
end
end
-local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
+local function handleerr(err) log("error", "Traceback[c2s]: %s", traceback(tostring(err), 2)); end
function stream_callbacks.handlestanza(session, stanza)
stanza = session.filter("stanzas/in", stanza);
if stanza then
@@ -132,25 +133,25 @@ local function session_close(session, reason)
session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
end
if reason then -- nil == no err, initiated by us, false == initiated by client
+ local stream_error = st.stanza("stream:error");
if type(reason) == "string" then -- assume stream error
- log("debug", "Disconnecting client, <stream:error> is: %s", reason);
- session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
+ stream_error:tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' });
elseif type(reason) == "table" then
if reason.condition then
- local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
+ stream_error:tag(reason.condition, stream_xmlns_attr):up();
if reason.text then
- stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
+ stream_error:tag("text", stream_xmlns_attr):text(reason.text):up();
end
if reason.extra then
- stanza:add_child(reason.extra);
+ stream_error:add_child(reason.extra);
end
- log("debug", "Disconnecting client, <stream:error> is: %s", tostring(stanza));
- session.send(stanza);
elseif reason.name then -- a stanza
- log("debug", "Disconnecting client, <stream:error> is: %s", tostring(reason));
- session.send(reason);
+ stream_error = reason;
end
end
+ stream_error = tostring(stream_error);
+ log("debug", "Disconnecting client, <stream:error> is: %s", stream_error);
+ session.send(stream_error);
end
session.send("</stream:stream>");
diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua
index 68d8a5de..871a20e4 100644
--- a/plugins/mod_component.lua
+++ b/plugins/mod_component.lua
@@ -19,7 +19,7 @@ local new_xmpp_stream = require "util.xmppstream".new;
local uuid_gen = require "util.uuid".generate;
local core_process_stanza = prosody.core_process_stanza;
-
+local hosts = prosody.hosts;
local log = module._log;
diff --git a/plugins/mod_compression.lua b/plugins/mod_compression.lua
index 67a88eb9..92856099 100644
--- a/plugins/mod_compression.lua
+++ b/plugins/mod_compression.lua
@@ -141,10 +141,7 @@ module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(ev
-- setup decompression for session.data
setup_decompression(session, inflate_stream);
session:reset_stream();
- local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
- ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
- session.sends2s("<?xml version='1.0'?>");
- session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
+ session:open_stream(session.from_host, session.to_host);
session.compressed = true;
return true;
end
diff --git a/plugins/mod_dialback.lua b/plugins/mod_dialback.lua
index b2f84603..9dcb0ed5 100644
--- a/plugins/mod_dialback.lua
+++ b/plugins/mod_dialback.lua
@@ -7,7 +7,6 @@
--
local hosts = _G.hosts;
-local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
local log = module._log;
@@ -110,7 +109,7 @@ module:hook("stanza/jabber:server:dialback:verify", function(event)
if dialback_verifying and attr.from == origin.to_host then
local valid;
if attr.type == "valid" then
- s2s_make_authenticated(dialback_verifying, attr.from);
+ module:fire_event("s2s-authenticated", { session = dialback_verifying, host = attr.from });
valid = "valid";
else
-- Warn the original connection that is was not verified successfully
@@ -146,7 +145,7 @@ module:hook("stanza/jabber:server:dialback:result", function(event)
return true;
end
if stanza.attr.type == "valid" then
- s2s_make_authenticated(origin, attr.from);
+ module:fire_event("s2s-authenticated", { session = origin, host = attr.from });
else
origin:close("not-authorized", "dialback authentication failed");
end
@@ -170,7 +169,7 @@ module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
end
end, 100);
-module:hook("s2s-authenticate-legacy", function (event)
+module:hook("s2sout-authenticate-legacy", function (event)
module:log("debug", "Initiating dialback...");
initiate_dialback(event.origin);
return true;
diff --git a/plugins/mod_groups.lua b/plugins/mod_groups.lua
index 7a876f1d..f7f632c2 100644
--- a/plugins/mod_groups.lua
+++ b/plugins/mod_groups.lua
@@ -13,7 +13,7 @@ local members;
local groups_file;
local jid, datamanager = require "util.jid", require "util.datamanager";
-local jid_bare, jid_prep = jid.bare, jid.prep;
+local jid_prep = jid.prep;
local module_host = module:get_host();
@@ -80,7 +80,7 @@ function remove_virtual_contacts(username, host, datastore, data)
end
function module.load()
- groups_file = config.get(module:get_host(), "core", "groups_file");
+ groups_file = module:get_option_string("groups_file");
if not groups_file then return; end
module:hook("roster-load", inject_roster_contacts);
@@ -121,3 +121,8 @@ end
function module.unload()
datamanager.remove_callback(remove_virtual_contacts);
end
+
+-- Public for other modules to access
+function group_contains(group_name, jid)
+ return groups[group_name][jid];
+end
diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua
index 018f2ea3..0689634e 100644
--- a/plugins/mod_http.lua
+++ b/plugins/mod_http.lua
@@ -9,6 +9,7 @@
module:set_global();
module:depends("http_errors");
+local portmanager = require "core.portmanager";
local moduleapi = require "core.moduleapi";
local url_parse = require "socket.url".parse;
local url_build = require "socket.url".build;
@@ -38,9 +39,10 @@ local function get_http_event(host, app_path, key)
end
local function get_base_path(host_module, app_name, default_app_path)
- return normalize_path(host_module:get_option("http_paths", {})[app_name] -- Host
+ return (normalize_path(host_module:get_option("http_paths", {})[app_name] -- Host
or module:get_option("http_paths", {})[app_name] -- Global
- or default_app_path); -- Default
+ or default_app_path)) -- Default
+ :gsub("%$(%w+)", { host = module.host });
end
local ports_by_scheme = { http = 80, https = 443, };
@@ -137,6 +139,7 @@ module:provides("net", {
listener = server.listener;
default_port = 5281;
encryption = "ssl";
+ ssl_config = { verify = "none" };
multiplex = {
pattern = "^[A-Z]";
};
diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua
index 828216dd..2568ea80 100644
--- a/plugins/mod_http_errors.lua
+++ b/plugins/mod_http_errors.lua
@@ -2,7 +2,6 @@ module:set_global();
local server = require "net.http.server";
local codes = require "net.http.codes";
-local termcolours = require "util.termcolours";
local show_private = module:get_option_boolean("http_errors_detailed", false);
local always_serve = module:get_option_boolean("http_errors_always_show", true);
diff --git a/plugins/mod_iq.lua b/plugins/mod_iq.lua
index 8044a533..e7901ab4 100644
--- a/plugins/mod_iq.lua
+++ b/plugins/mod_iq.lua
@@ -9,7 +9,7 @@
local st = require "util.stanza";
-local full_sessions = full_sessions;
+local full_sessions = prosody.full_sessions;
if module:get_host_type() == "local" then
module:hook("iq/full", function(data)
diff --git a/plugins/mod_message.lua b/plugins/mod_message.lua
index 0b0ad8e4..e85da613 100644
--- a/plugins/mod_message.lua
+++ b/plugins/mod_message.lua
@@ -7,8 +7,8 @@
--
-local full_sessions = full_sessions;
-local bare_sessions = bare_sessions;
+local full_sessions = prosody.full_sessions;
+local bare_sessions = prosody.bare_sessions;
local st = require "util.stanza";
local jid_bare = require "util.jid".bare;
diff --git a/plugins/mod_motd.lua b/plugins/mod_motd.lua
index fea2cb85..ed78294b 100644
--- a/plugins/mod_motd.lua
+++ b/plugins/mod_motd.lua
@@ -13,7 +13,6 @@ local motd_jid = module:get_option_string("motd_jid", host);
if not motd_text then return; end
-local jid_join = require "util.jid".join;
local st = require "util.stanza";
motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n%s+", "\n"); -- Strip indentation from the config
diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua
index e871e5cf..28fd7f38 100644
--- a/plugins/mod_posix.lua
+++ b/plugins/mod_posix.lua
@@ -7,10 +7,12 @@
--
-local want_pposix_version = "0.3.5";
+local want_pposix_version = "0.3.6";
local pposix = assert(require "util.pposix");
-if pposix._VERSION ~= want_pposix_version then module:log("warn", "Unknown version (%s) of binary pposix module, expected %s", tostring(pposix._VERSION), want_pposix_version); end
+if pposix._VERSION ~= want_pposix_version then
+ module:log("warn", "Unknown version (%s) of binary pposix module, expected %s. Perhaps you need to recompile?", tostring(pposix._VERSION), want_pposix_version);
+end
local signal = select(2, pcall(require, "util.signal"));
if type(signal) == "string" then
@@ -118,9 +120,9 @@ function syslog_sink_maker(config)
local syslog, format = pposix.syslog_log, string.format;
return function (name, level, message, ...)
if ... then
- syslog(level, format(message, ...));
+ syslog(level, name, format(message, ...));
else
- syslog(level, message);
+ syslog(level, name, message);
end
end;
end
diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua
index 23012750..8dac2d35 100644
--- a/plugins/mod_presence.lua
+++ b/plugins/mod_presence.lua
@@ -9,7 +9,7 @@
local log = module._log;
local require = require;
-local pairs, ipairs = pairs, ipairs;
+local pairs = pairs;
local t_concat, t_insert = table.concat, table.insert;
local s_find = string.find;
local tonumber = tonumber;
@@ -19,7 +19,9 @@ local st = require "util.stanza";
local jid_split = require "util.jid".split;
local jid_bare = require "util.jid".bare;
local datetime = require "util.datetime";
-local hosts = hosts;
+local hosts = prosody.hosts;
+local bare_sessions = prosody.bare_sessions;
+local full_sessions = prosody.full_sessions;
local NULL = {};
local rostermanager = require "core.rostermanager";
@@ -344,7 +346,7 @@ module:hook("presence/full", function(data)
end);
module:hook("presence/host", function(data)
-- inbound presence to the host
- local origin, stanza = data.origin, data.stanza;
+ local stanza = data.stanza;
local from_bare = jid_bare(stanza.attr.from);
local t = stanza.attr.type;
diff --git a/plugins/mod_privacy.lua b/plugins/mod_privacy.lua
index 2d696154..31ace9f9 100644
--- a/plugins/mod_privacy.lua
+++ b/plugins/mod_privacy.lua
@@ -9,16 +9,16 @@
module:add_feature("jabber:iq:privacy");
-local prosody = prosody;
local st = require "util.stanza";
-local datamanager = require "util.datamanager";
-local bare_sessions, full_sessions = bare_sessions, full_sessions;
+local bare_sessions, full_sessions = prosody.bare_sessions, prosody.full_sessions;
local util_Jid = require "util.jid";
local jid_bare = util_Jid.bare;
local jid_split, jid_join = util_Jid.split, util_Jid.join;
local load_roster = require "core.rostermanager".load_roster;
local to_number = tonumber;
+local privacy_storage = module:open_store();
+
function isListUsed(origin, name, privacy_lists)
local user = bare_sessions[origin.username.."@"..origin.host];
if user then
@@ -218,7 +218,7 @@ module:hook("iq/bare/jabber:iq:privacy:query", function(data)
if stanza.attr.to == nil then -- only service requests to own bare JID
local query = stanza.tags[1]; -- the query element
local valid = false;
- local privacy_lists = datamanager.load(origin.username, origin.host, "privacy") or { lists = {} };
+ local privacy_lists = privacy_storage:get(origin.username) or { lists = {} };
if privacy_lists.lists[1] then -- Code to migrate from old privacy lists format, remove in 0.8
module:log("info", "Upgrading format of stored privacy lists for %s@%s", origin.username, origin.host);
@@ -273,7 +273,7 @@ module:hook("iq/bare/jabber:iq:privacy:query", function(data)
end
origin.send(st.error_reply(stanza, valid[1], valid[2], valid[3]));
else
- datamanager.store(origin.username, origin.host, "privacy", privacy_lists);
+ privacy_storage:set(origin.username, privacy_lists);
end
return true;
end
@@ -281,7 +281,7 @@ end);
function checkIfNeedToBeBlocked(e, session)
local origin, stanza = e.origin, e.stanza;
- local privacy_lists = datamanager.load(session.username, session.host, "privacy") or {};
+ local privacy_lists = privacy_storage:get(session.username) or {};
local bare_jid = session.username.."@"..session.host;
local to = stanza.attr.to or bare_jid;
local from = stanza.attr.from;
@@ -367,6 +367,10 @@ function checkIfNeedToBeBlocked(e, session)
end
if apply then
if block then
+ -- drop and not bounce groupchat messages, otherwise users will get kicked
+ if stanza.attr.type == "groupchat" then
+ return true;
+ end
module:log("debug", "stanza blocked: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from));
if stanza.name == "message" then
origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
diff --git a/plugins/mod_private.lua b/plugins/mod_private.lua
index f1ebe786..365a997c 100644
--- a/plugins/mod_private.lua
+++ b/plugins/mod_private.lua
@@ -9,8 +9,7 @@
local st = require "util.stanza"
-local jid_split = require "util.jid".split;
-local datamanager = require "util.datamanager"
+local private_storage = module:open_store();
module:add_feature("jabber:iq:private");
@@ -21,7 +20,7 @@ module:hook("iq/self/jabber:iq:private:query", function(event)
if #query.tags == 1 then
local tag = query.tags[1];
local key = tag.name..":"..tag.attr.xmlns;
- local data, err = datamanager.load(origin.username, origin.host, "private");
+ local data, err = private_storage:get(origin.username);
if err then
origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
return true;
@@ -40,7 +39,7 @@ module:hook("iq/self/jabber:iq:private:query", function(event)
data[key] = st.preserialize(tag);
end
-- TODO delete datastore if empty
- if datamanager.store(origin.username, origin.host, "private", data) then
+ if private_storage:set(origin.username, data) then
origin.send(st.reply(stanza));
else
origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua
index d6e41604..1fa42bd8 100644
--- a/plugins/mod_proxy65.lua
+++ b/plugins/mod_proxy65.lua
@@ -95,7 +95,7 @@ function module.add_host(module)
local proxy_port = next(portmanager.get_active_services():search("proxy65", nil)[1] or {});
local proxy_acl = module:get_option("proxy65_acl");
- -- COMPAT w/pre-0.9 where proxy65_port was specified the components section of the config
+ -- COMPAT w/pre-0.9 where proxy65_port was specified in the components section of the config
local legacy_config = module:get_option_number("proxy65_port");
if legacy_config then
module:log("warn", "proxy65_port is deprecated, please put proxy65_ports = { %d } into the global section instead", legacy_config);
@@ -106,16 +106,20 @@ function module.add_host(module)
module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
local origin, stanza = event.origin, event.stanza;
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
- :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
- :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
- return true;
+ if not stanza.tags[1].attr.node then
+ origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
+ :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
+ :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
+ return true;
+ end
end, -1);
module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
local origin, stanza = event.origin, event.stanza;
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
- return true;
+ if not stanza.tags[1].attr.node then
+ origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
+ return true;
+ end
end, -1);
module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
diff --git a/plugins/mod_pubsub.lua b/plugins/mod_pubsub.lua
index fe6c0b0a..22969ab5 100644
--- a/plugins/mod_pubsub.lua
+++ b/plugins/mod_pubsub.lua
@@ -22,6 +22,9 @@ function handle_pubsub_iq(event)
local origin, stanza = event.origin, event.stanza;
local pubsub = stanza.tags[1];
local action = pubsub.tags[1];
+ if not action then
+ return origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+ end
local handler = handlers[stanza.attr.type.."_"..action.name];
if handler then
handler(origin, stanza, action);
@@ -164,16 +167,6 @@ function handlers.set_subscribe(origin, stanza, subscribe)
reply = pubsub_error_reply(stanza, ret);
end
origin.send(reply);
- if ok then
- -- Send all current items
- local ok, items = service:get_items(node, stanza.attr.from);
- if items then
- local jids = { [jid] = options or true };
- for id, item in pairs(items) do
- service.config.broadcaster("items", node, jids, item);
- end
- end
- end
end
function handlers.set_unsubscribe(origin, stanza, unsubscribe)
@@ -197,7 +190,13 @@ function handlers.set_publish(origin, stanza, publish)
return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
end
local item = publish:get_child("item");
- local id = (item and item.attr.id) or uuid_generate();
+ local id = (item and item.attr.id);
+ if not id then
+ id = uuid_generate();
+ if item then
+ item.attr.id = id;
+ end
+ end
local ok, ret = service:publish(node, stanza.attr.from, id, item);
local reply;
if ok then
diff --git a/plugins/mod_register.lua b/plugins/mod_register.lua
index b3abd394..141a4997 100644
--- a/plugins/mod_register.lua
+++ b/plugins/mod_register.lua
@@ -7,9 +7,7 @@
--
-local hosts = _G.hosts;
local st = require "util.stanza";
-local datamanager = require "util.datamanager";
local dataform_new = require "util.dataforms".new;
local usermanager_user_exists = require "core.usermanager".user_exists;
local usermanager_create_user = require "core.usermanager".create_user;
@@ -23,6 +21,8 @@ local compat = module:get_option_boolean("registration_compat", true);
local allow_registration = module:get_option_boolean("allow_registration", false);
local additional_fields = module:get_option("additional_registration_fields", {});
+local account_details = module:open_store("account_details");
+
local field_map = {
username = { name = "username", type = "text-single", label = "Username", required = true };
password = { name = "password", type = "text-private", label = "Password", required = true };
@@ -235,7 +235,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event)
-- TODO unable to write file, file may be locked, etc, what's the correct error?
local error_reply = st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk.");
if usermanager_create_user(username, password, host) then
- if next(data) and not datamanager.store(username, host, "account_details", data) then
+ if next(data) and not account_details:set(username, data) then
usermanager_delete_user(username, host);
session.send(error_reply);
return true;
diff --git a/plugins/mod_roster.lua b/plugins/mod_roster.lua
index 40d95be7..d530bb45 100644
--- a/plugins/mod_roster.lua
+++ b/plugins/mod_roster.lua
@@ -69,7 +69,6 @@ module:hook("iq/self/jabber:iq:roster:query", function(event)
and query.tags[1].attr.jid ~= "pending" then
local item = query.tags[1];
local from_node, from_host = jid_split(stanza.attr.from);
- local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID
local jid = jid_prep(item.attr.jid);
local node, host, resource = jid_split(jid);
if not resource and host then
diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua
index 15c89ced..30ebb706 100644
--- a/plugins/mod_s2s/mod_s2s.lua
+++ b/plugins/mod_s2s/mod_s2s.lua
@@ -15,6 +15,7 @@ local core_process_stanza = prosody.core_process_stanza;
local tostring, type = tostring, type;
local t_insert = table.insert;
local xpcall, traceback = xpcall, debug.traceback;
+local NULL = {};
local add_task = require "util.timer".add_task;
local st = require "util.stanza";
@@ -24,14 +25,19 @@ local new_xmpp_stream = require "util.xmppstream".new;
local s2s_new_incoming = require "core.s2smanager".new_incoming;
local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
local s2s_destroy_session = require "core.s2smanager".destroy_session;
-local s2s_mark_connected = require "core.s2smanager".mark_connected;
local uuid_gen = require "util.uuid".generate;
local cert_verify_identity = require "util.x509".verify_identity;
+local fire_global_event = prosody.events.fire_event;
local s2sout = module:require("s2sout");
local connect_timeout = module:get_option_number("s2s_timeout", 90);
local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5);
+local opt_keepalives = module:get_option_boolean("s2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true));
+local secure_auth = module:get_option_boolean("s2s_secure_auth", false); -- One day...
+local secure_domains, insecure_domains =
+ module:get_option_set("s2s_secure_domains", {})._items, module:get_option_set("s2s_insecure_domains", {})._items;
+local require_encryption = module:get_option_boolean("s2s_require_encryption", secure_auth);
local sessions = module:shared("sessions");
@@ -75,6 +81,10 @@ function route_to_existing_session(event)
log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host);
return false;
end
+ if hosts[to_host] then
+ log("warn", "Attempt to route stanza to a remote %s - a host we do serve?!", from_host);
+ return false;
+ end
local host = hosts[from_host].s2sout[to_host];
if host then
-- We have a connection to this host already
@@ -130,12 +140,86 @@ function module.add_host(module)
module:log("warn", "The 'disallow_s2s' config option is deprecated, please see http://prosody.im/doc/s2s#disabling");
return nil, "This host has disallow_s2s set";
end
- module:hook("route/remote", route_to_existing_session, 200);
- module:hook("route/remote", route_to_new_session, 100);
+ module:hook("route/remote", route_to_existing_session, -1);
+ module:hook("route/remote", route_to_new_session, -10);
+ module:hook("s2s-authenticated", make_authenticated, -1);
+end
+
+-- Stream is authorised, and ready for normal stanzas
+function mark_connected(session)
+ local sendq, send = session.sendq, session.sends2s;
+
+ local from, to = session.from_host, session.to_host;
+
+ session.log("info", "%s s2s connection %s->%s complete", session.direction, from, to);
+
+ local event_data = { session = session };
+ if session.type == "s2sout" then
+ fire_global_event("s2sout-established", event_data);
+ hosts[from].events.fire_event("s2sout-established", event_data);
+ else
+ local host_session = hosts[to];
+ session.send = function(stanza)
+ return host_session.events.fire_event("route/remote", { from_host = to, to_host = from, stanza = stanza });
+ end;
+
+ fire_global_event("s2sin-established", event_data);
+ hosts[to].events.fire_event("s2sin-established", event_data);
+ end
+
+ if session.direction == "outgoing" then
+ if sendq then
+ session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", #sendq, session.to_host);
+ for i, data in ipairs(sendq) do
+ send(data[1]);
+ sendq[i] = nil;
+ end
+ session.sendq = nil;
+ end
+
+ session.ip_hosts = nil;
+ session.srv_hosts = nil;
+ end
+end
+
+function make_authenticated(event)
+ local session, host = event.session, event.host;
+ if not session.secure then
+ if require_encryption or secure_auth or secure_domains[host] then
+ session:close({
+ condition = "policy-violation",
+ text = "Encrypted server-to-server communication is required but was not "
+ ..((session.direction == "outgoing" and "offered") or "used")
+ });
+ end
+ end
+ if hosts[host] then
+ session:close({ condition = "undefined-condition", text = "Attempt to authenticate as a host we serve" });
+ end
+ if session.type == "s2sout_unauthed" then
+ session.type = "s2sout";
+ elseif session.type == "s2sin_unauthed" then
+ session.type = "s2sin";
+ if host then
+ if not session.hosts[host] then session.hosts[host] = {}; end
+ session.hosts[host].authed = true;
+ end
+ elseif session.type == "s2sin" and host then
+ if not session.hosts[host] then session.hosts[host] = {}; end
+ session.hosts[host].authed = true;
+ else
+ return false;
+ end
+ session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host);
+
+ mark_connected(session);
+
+ return true;
end
--- Helper to check that a session peer's certificate is valid
local function check_cert_status(session)
+ local host = session.direction == "outgoing" and session.to_host or session.from_host
local conn = session.conn:socket()
local cert
if conn.getpeercertificate then
@@ -143,11 +227,19 @@ local function check_cert_status(session)
end
if cert then
- local chain_valid, errors = conn:getpeerverification()
+ local chain_valid, errors;
+ if conn.getpeerverification then
+ chain_valid, errors = conn:getpeerverification();
+ elseif conn.getpeerchainvalid then -- COMPAT mw/luasec-hg
+ chain_valid, errors = conn:getpeerchainvalid();
+ errors = (not chain_valid) and { { errors } } or nil;
+ else
+ chain_valid, errors = false, { { "Chain verification not supported by this version of LuaSec" } };
+ end
-- Is there any interest in printing out all/the number of errors here?
if not chain_valid then
(session.log or log)("debug", "certificate chain validation result: invalid");
- for depth, t in ipairs(errors) do
+ for depth, t in ipairs(errors or NULL) do
(session.log or log)("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", "))
end
session.cert_chain_status = "invalid";
@@ -155,8 +247,6 @@ local function check_cert_status(session)
(session.log or log)("debug", "certificate chain validation result: valid");
session.cert_chain_status = "valid";
- local host = session.direction == "incoming" and session.from_host or session.to_host
-
-- We'll go ahead and verify the asserted identity if the
-- connecting server specified one.
if host then
@@ -168,6 +258,7 @@ local function check_cert_status(session)
end
end
end
+ return module:fire_event("s2s-check-certificate", { host = host, session = session, cert = cert });
end
--- XMPP stream event handlers
@@ -246,11 +337,18 @@ function stream_callbacks.streamopened(session, attr)
end
end
- if session.secure and not session.cert_chain_status then check_cert_status(session); end
+ if hosts[from] then
+ session:close({ condition = "undefined-condition", text = "Attempt to connect from a host we serve" });
+ return;
+ end
+
+ if session.secure and not session.cert_chain_status then
+ if check_cert_status(session) == false then
+ return;
+ end
+ end
- send("<?xml version='1.0'?>");
- send(st.stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
- ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=to, to=from, version=(session.version > 0 and "1.0" or nil) }):top_tag());
+ session:open_stream(session.to_host, session.from_host)
if session.version >= 1.0 then
local features = st.stanza("stream:features");
@@ -268,7 +366,11 @@ function stream_callbacks.streamopened(session, attr)
if not attr.id then error("stream response did not give us a streamid!!!"); end
session.streamid = attr.id;
- if session.secure and not session.cert_chain_status then check_cert_status(session); end
+ if session.secure and not session.cert_chain_status then
+ if check_cert_status(session) == false then
+ return;
+ end
+ end
-- Send unauthed buffer
-- (stanzas which are fine to send before dialback)
@@ -287,9 +389,9 @@ function stream_callbacks.streamopened(session, attr)
-- If server is pre-1.0, don't wait for features, just do dialback
if session.version < 1.0 then
if not session.dialback_verifying then
- hosts[session.from_host].events.fire_event("s2s-authenticate-legacy", { origin = session });
+ hosts[session.from_host].events.fire_event("s2sout-authenticate-legacy", { origin = session });
else
- s2s_mark_connected(session);
+ mark_connected(session);
end
end
end
@@ -327,7 +429,7 @@ function stream_callbacks.error(session, error, data)
end
end
-local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
+local function handleerr(err) log("error", "Traceback[s2s]: %s", traceback(tostring(err), 2)); end
function stream_callbacks.handlestanza(session, stanza)
if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
stanza.attr.xmlns = nil;
@@ -342,13 +444,15 @@ local listener = {};
--- Session methods
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
local function session_close(session, reason, remote_reason)
local log = session.log or log;
if session.conn then
if session.notopen then
- session.sends2s("<?xml version='1.0'?>");
- session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
+ if session.direction == "incoming" then
+ session:open_stream(session.to_host, session.from_host);
+ else
+ session:open_stream(session.from_host, session.to_host);
+ end
end
if reason then -- nil == no err, initiated by us, false == initiated by remote
if type(reason) == "string" then -- assume stream error
@@ -395,6 +499,24 @@ local function session_close(session, reason, remote_reason)
end
end
+function session_open_stream(session, from, to)
+ local attr = {
+ ["xmlns:stream"] = 'http://etherx.jabber.org/streams',
+ xmlns = 'jabber:server',
+ version = session.version and (session.version > 0 and "1.0" or nil),
+ ["xml:lang"] = 'en',
+ id = session.streamid,
+ from = from, to = to,
+ }
+ if not from or (hosts[from] and hosts[from].modules.dialback) then
+ attr["xmlns:db"] = 'jabber:server:dialback';
+ end
+
+ session.sends2s("<?xml version='1.0'?>");
+ session.sends2s(st.stanza("stream:stream", attr):top_tag());
+ return true;
+end
+
-- Session initialization logic shared by incoming and outgoing
local function initialize_session(session)
local stream = new_xmpp_stream(session, stream_callbacks);
@@ -406,6 +528,8 @@ local function initialize_session(session)
session.notopen = true;
session.stream:reset();
end
+
+ session.open_stream = session_open_stream;
local filter = session.filter;
function session.data(data)
@@ -440,6 +564,7 @@ local function initialize_session(session)
end
function listener.onconnect(conn)
+ conn:setoption("keepalive", opt_keepalives);
local session = sessions[conn];
if not session then -- New incoming connection
session = s2s_new_incoming(conn);
@@ -506,6 +631,29 @@ function listener.register_outgoing(conn, session)
initialize_session(session);
end
+function check_auth_policy(event)
+ local host, session = event.host, event.session;
+ local must_secure = secure_auth;
+
+ if not must_secure and secure_domains[host] then
+ must_secure = true;
+ elseif must_secure and insecure_domains[host] then
+ must_secure = false;
+ end
+
+ if must_secure and not session.cert_identity_status then
+ module:log("warn", "Forbidding insecure connection to/from %s", host);
+ if session.direction == "incoming" then
+ session:close({ condition = "not-authorized", text = "Your server's certificate is invalid, expired, or not trusted by "..session.to_host });
+ else -- Close outgoing connections without warning
+ session:close(false);
+ end
+ return false;
+ end
+end
+
+module:hook("s2s-check-certificate", check_auth_policy, -1);
+
s2sout.set_listener(listener);
module:hook("server-stopping", function(event)
diff --git a/plugins/mod_s2s/s2sout.lib.lua b/plugins/mod_s2s/s2sout.lib.lua
index 07623968..cb2f8be4 100644
--- a/plugins/mod_s2s/s2sout.lib.lua
+++ b/plugins/mod_s2s/s2sout.lib.lua
@@ -13,7 +13,7 @@ local wrapclient = require "net.server".wrapclient;
local initialize_filters = require "util.filters".initialize;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local new_ip = require "util.ip".new_ip;
-local rfc3484_dest = require "util.rfc3484".destination;
+local rfc6724_dest = require "util.rfc6724".destination;
local socket = require "socket";
local adns = require "net.adns";
local dns = require "net.dns";
@@ -44,15 +44,9 @@ local function compare_srv_priorities(a,b)
return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
end
-local function session_open_stream(session, from, to)
- session.sends2s(st.stanza("stream:stream", {
- xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
- ["xmlns:stream"]='http://etherx.jabber.org/streams',
- from=from, to=to, version='1.0', ["xml:lang"]='en'}):top_tag());
-end
-
function s2sout.initiate_connection(host_session)
initialize_filters(host_session);
+ host_session.version = 1;
host_session.open_stream = session_open_stream;
-- Kick the connection attempting machine into life
@@ -96,7 +90,7 @@ function s2sout.attempt_connection(host_session, err)
host_session.connecting = nil;
if answer and #answer > 0 then
log("debug", "%s has SRV records, handling...", to_host);
- local srv_hosts = {};
+ local srv_hosts = { answer = answer };
host_session.srv_hosts = srv_hosts;
for _, record in ipairs(answer) do
t_insert(srv_hosts, record.srv);
@@ -197,7 +191,7 @@ function s2sout.try_connect(host_session, connect_host, connect_port, err)
if have_other_result then
if #IPs > 0 then
- rfc3484_dest(host_session.ip_hosts, sources);
+ rfc6724_dest(host_session.ip_hosts, sources);
for i = 1, #IPs do
IPs[i] = {ip = IPs[i], port = connect_port};
end
@@ -233,7 +227,7 @@ function s2sout.try_connect(host_session, connect_host, connect_port, err)
if have_other_result then
if #IPs > 0 then
- rfc3484_dest(host_session.ip_hosts, sources);
+ rfc6724_dest(host_session.ip_hosts, sources);
for i = 1, #IPs do
IPs[i] = {ip = IPs[i], port = connect_port};
end
@@ -277,6 +271,10 @@ function s2sout.make_connect(host_session, connect_host, connect_port)
local from_host, to_host = host_session.from_host, host_session.to_host;
+ -- Reset secure flag in case this is another
+ -- connection attempt after a failed STARTTLS
+ host_session.secure = nil;
+
local conn, handler;
if connect_host.proto == "IPv4" then
conn, handler = socket.tcp();
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index f6abd3b8..201cc477 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -11,7 +11,6 @@
local st = require "util.stanza";
local sm_bind_resource = require "core.sessionmanager".bind_resource;
local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
-local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
local base64 = require "util.encodings".base64;
local cert_verify_identity = require "util.x509".verify_identity;
@@ -88,13 +87,9 @@ module:hook_stanza(xmlns_sasl, "success", function (session, stanza)
module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host);
session.external_auth = "succeeded"
session:reset_stream();
+ session:open_stream(session.from_host, session.to_host);
- local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
- ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
- session.sends2s("<?xml version='1.0'?>");
- session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
-
- s2s_make_authenticated(session, session.to_host);
+ module:fire_event("s2s-authenticated", { session = session, host = session.to_host });
return true;
end)
@@ -191,7 +186,7 @@ local function s2s_external_auth(session, stanza)
local domain = text ~= "" and text or session.from_host;
module:log("info", "Accepting SASL EXTERNAL identity from %s", domain);
- s2s_make_authenticated(session, domain);
+ module:fire_event("s2s-authenticated", { session = session, host = domain });
session:reset_stream();
return true
end
diff --git a/plugins/mod_storage_none.lua b/plugins/mod_storage_none.lua
new file mode 100644
index 00000000..8f2d2f56
--- /dev/null
+++ b/plugins/mod_storage_none.lua
@@ -0,0 +1,23 @@
+local driver = {};
+local driver_mt = { __index = driver };
+
+function driver:open(store)
+ return setmetatable({ store = store }, driver_mt);
+end
+function driver:get(user)
+ return {};
+end
+
+function driver:set(user, data)
+ return nil, "Storage disabled";
+end
+
+function driver:stores(username)
+ return { "roster" };
+end
+
+function driver:purge(user)
+ return true;
+end
+
+module:provides("storage", driver);
diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua
new file mode 100644
index 00000000..7d705b0b
--- /dev/null
+++ b/plugins/mod_storage_sql2.lua
@@ -0,0 +1,237 @@
+
+local json = require "util.json";
+local resolve_relative_path = require "core.configmanager".resolve_relative_path;
+
+local mod_sql = module:require("sql");
+local params = module:get_option("sql");
+
+local engine; -- TODO create engine
+
+local function create_table()
+ --[[local Table,Column,Index = mod_sql.Table,mod_sql.Column,mod_sql.Index;
+ local ProsodyTable = Table {
+ name="prosody";
+ Column { name="host", type="TEXT", nullable=false };
+ Column { name="user", type="TEXT", nullable=false };
+ Column { name="store", type="TEXT", nullable=false };
+ Column { name="key", type="TEXT", nullable=false };
+ Column { name="type", type="TEXT", nullable=false };
+ Column { name="value", type="TEXT", nullable=false };
+ Index { name="prosody_index", "host", "user", "store", "key" };
+ };
+ engine:transaction(function()
+ ProsodyTable:create(engine);
+ end);]]
+ if not module:get_option("sql_manage_tables", true) then
+ return;
+ end
+
+ local create_sql = "CREATE TABLE `prosody` (`host` TEXT, `user` TEXT, `store` TEXT, `key` TEXT, `type` TEXT, `value` TEXT);";
+ if params.driver == "PostgreSQL" then
+ create_sql = create_sql:gsub("`", "\"");
+ elseif params.driver == "MySQL" then
+ create_sql = create_sql:gsub("`value` TEXT", "`value` MEDIUMTEXT")
+ :gsub(";$", " CHARACTER SET 'utf8' COLLATE 'utf8_bin';");
+ end
+
+ local index_sql = "CREATE INDEX `prosody_index` ON `prosody` (`host`, `user`, `store`, `key`)";
+ if params.driver == "PostgreSQL" then
+ index_sql = index_sql:gsub("`", "\"");
+ elseif params.driver == "MySQL" then
+ index_sql = index_sql:gsub("`([,)])", "`(20)%1");
+ end
+
+ local success,err = engine:transaction(function()
+ engine:execute(create_sql);
+ engine:execute(index_sql);
+ end);
+ if not success then -- so we failed to create
+ if params.driver == "MySQL" then
+ success,err = engine:transaction(function()
+ local result = engine:execute("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'");
+ if result:rowcount() > 0 then
+ module:log("info", "Upgrading database schema...");
+ engine:execute("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT");
+ module:log("info", "Database table automatically upgraded");
+ end
+ return true;
+ end);
+ if not success then
+ module:log("error", "Failed to check/upgrade database schema (%s), please see "
+ .."http://prosody.im/doc/mysql for help",
+ err or "unknown error");
+ end
+ end
+ end
+end
+local function set_encoding()
+ if params.driver ~= "SQLite3" then
+ local set_names_query = "SET NAMES 'utf8';";
+ if params.driver == "MySQL" then
+ set_names_query = set_names_query:gsub(";$", " COLLATE 'utf8_bin';");
+ end
+ local success,err = engine:transaction(function() return engine:execute(set_names_query); end);
+ if not success then
+ module:log("error", "Failed to set database connection encoding to UTF8: %s", err);
+ return;
+ end
+ if params.driver == "MySQL" then
+ -- COMPAT w/pre-0.9: Upgrade tables to UTF-8 if not already
+ local check_encoding_query = "SELECT `COLUMN_NAME`,`COLUMN_TYPE` FROM `information_schema`.`columns` WHERE `TABLE_NAME`='prosody' AND ( `CHARACTER_SET_NAME`!='utf8' OR `COLLATION_NAME`!='utf8_bin' );";
+ local success,err = engine:transaction(function()
+ local result = engine:execute(check_encoding_query);
+ local n_bad_columns = result:rowcount();
+ if n_bad_columns > 0 then
+ module:log("warn", "Found %d columns in prosody table requiring encoding change, updating now...", n_bad_columns);
+ local fix_column_query1 = "ALTER TABLE `prosody` CHANGE `%s` `%s` BLOB;";
+ local fix_column_query2 = "ALTER TABLE `prosody` CHANGE `%s` `%s` %s CHARACTER SET 'utf8' COLLATE 'utf8_bin';";
+ for row in success:rows() do
+ local column_name, column_type = unpack(row);
+ engine:execute(fix_column_query1:format(column_name, column_name));
+ engine:execute(fix_column_query2:format(column_name, column_name, column_type));
+ end
+ module:log("info", "Database encoding upgrade complete!");
+ end
+ end);
+ local success,err = engine:transaction(function() return engine:execute(check_encoding_query); end);
+ if not success then
+ module:log("error", "Failed to check/upgrade database encoding: %s", err or "unknown error");
+ end
+ end
+ end
+end
+
+do -- process options to get a db connection
+ params = params or { driver = "SQLite3" };
+
+ if params.driver == "SQLite3" then
+ params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
+ end
+
+ assert(params.driver and params.database, "Both the SQL driver and the database need to be specified");
+
+ --local dburi = db2uri(params);
+ engine = mod_sql:create_engine(params);
+
+ -- Encoding mess
+ set_encoding();
+
+ -- Automatically create table, ignore failure (table probably already exists)
+ create_table();
+end
+
+local function serialize(value)
+ local t = type(value);
+ if t == "string" or t == "boolean" or t == "number" then
+ return t, tostring(value);
+ elseif t == "table" then
+ local value,err = json.encode(value);
+ if value then return "json", value; end
+ return nil, err;
+ end
+ return nil, "Unhandled value type: "..t;
+end
+local function deserialize(t, value)
+ if t == "string" then return value;
+ elseif t == "boolean" then
+ if value == "true" then return true;
+ elseif value == "false" then return false; end
+ elseif t == "number" then return tonumber(value);
+ elseif t == "json" then
+ return json.decode(value);
+ end
+end
+
+local host = module.host;
+local user, store;
+
+local function keyval_store_get()
+ local haveany;
+ local result = {};
+ for row in engine:select("SELECT `key`,`type`,`value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user, store) do
+ haveany = true;
+ local k = row[1];
+ local v = deserialize(row[2], row[3]);
+ if k and v then
+ if k ~= "" then result[k] = v; elseif type(v) == "table" then
+ for a,b in pairs(v) do
+ result[a] = b;
+ end
+ end
+ end
+ end
+ if haveany then
+ return result;
+ end
+end
+local function keyval_store_set(data)
+ engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user, store);
+
+ if data and next(data) ~= nil then
+ local extradata = {};
+ for key, value in pairs(data) do
+ if type(key) == "string" and key ~= "" then
+ local t, value = serialize(value);
+ assert(t, value);
+ engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user, store, key, t, value);
+ else
+ extradata[key] = value;
+ end
+ end
+ if next(extradata) ~= nil then
+ local t, extradata = serialize(extradata);
+ assert(t, extradata);
+ engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user, store, "", t, extradata);
+ end
+ end
+ return true;
+end
+
+local keyval_store = {};
+keyval_store.__index = keyval_store;
+function keyval_store:get(username)
+ user,store = username,self.store;
+ return select(2, engine:transaction(keyval_store_get));
+end
+function keyval_store:set(username, data)
+ user,store = username,self.store;
+ return engine:transaction(function()
+ return keyval_store_set(data);
+ end);
+end
+function keyval_store:users()
+ return engine:transaction(function()
+ return engine:select("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store);
+ end);
+end
+
+local driver = {};
+
+function driver:open(store, typ)
+ if not typ then -- default key-value store
+ return setmetatable({ store = store }, keyval_store);
+ end
+ return nil, "unsupported-store";
+end
+
+function driver:stores(username)
+ local sql = "SELECT DISTINCT `store` FROM `prosody` WHERE `host`=? AND `user`" ..
+ (username == true and "!=?" or "=?");
+ if username == true or not username then
+ username = "";
+ end
+ return engine:transaction(function()
+ return engine:select(sql, host, username);
+ end);
+end
+
+function driver:purge(username)
+ return engine:transaction(function()
+ local stmt,err = engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username);
+ return true,err;
+ end);
+end
+
+module:provides("storage", driver);
+
+
diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua
index 707ae8f5..80b56abb 100644
--- a/plugins/mod_tls.lua
+++ b/plugins/mod_tls.lua
@@ -25,6 +25,7 @@ if secure_s2s_only then s2s_feature:tag("required"):up(); end
local global_ssl_ctx = prosody.global_ssl_ctx;
+local hosts = prosody.hosts;
local host = hosts[module.host];
local function can_do_tls(session)
@@ -91,10 +92,10 @@ module:hook_stanza(xmlns_starttls, "proceed", function (session, stanza)
end);
function module.load()
- local ssl_config = config.rawget(module.host, "core", "ssl");
+ local ssl_config = config.rawget(module.host, "ssl");
if not ssl_config then
local base_host = module.host:match("%.(.*)");
- ssl_config = config.get(base_host, "core", "ssl");
+ ssl_config = config.get(base_host, "ssl");
end
host.ssl_ctx = create_context(host.host, "client", ssl_config); -- for outgoing connections
host.ssl_ctx_in = create_context(host.host, "server", ssl_config); -- for incoming connections
diff --git a/plugins/mod_vcard.lua b/plugins/mod_vcard.lua
index d3c27cc0..26b30e3a 100644
--- a/plugins/mod_vcard.lua
+++ b/plugins/mod_vcard.lua
@@ -8,7 +8,8 @@
local st = require "util.stanza"
local jid_split = require "util.jid".split;
-local datamanager = require "util.datamanager"
+
+local vcards = module:open_store();
module:add_feature("vcard-temp");
@@ -19,9 +20,9 @@ local function handle_vcard(event)
local vCard;
if to then
local node, host = jid_split(to);
- vCard = st.deserialize(datamanager.load(node, host, "vcard")); -- load vCard for user or server
+ vCard = st.deserialize(vcards:get(node)); -- load vCard for user or server
else
- vCard = st.deserialize(datamanager.load(session.username, session.host, "vcard"));-- load user's own vCard
+ vCard = st.deserialize(vcards:get(session.username));-- load user's own vCard
end
if vCard then
session.send(st.reply(stanza):add_child(vCard)); -- send vCard!
@@ -30,7 +31,7 @@ local function handle_vcard(event)
end
else
if not to then
- if datamanager.store(session.username, session.host, "vcard", st.preserialize(stanza.tags[1])) then
+ if vcards:set(session.username, st.preserialize(stanza.tags[1])) then
session.send(st.reply(stanza));
else
-- TODO unable to write file, file may be locked, etc, what's the correct error?
diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua
index 0df8b790..7861092c 100644
--- a/plugins/muc/mod_muc.lua
+++ b/plugins/muc/mod_muc.lua
@@ -28,13 +28,14 @@ local jid_split = require "util.jid".split;
local jid_bare = require "util.jid".bare;
local st = require "util.stanza";
local uuid_gen = require "util.uuid".generate;
-local datamanager = require "util.datamanager";
local um_is_admin = require "core.usermanager".is_admin;
-local hosts = hosts;
+local hosts = prosody.hosts;
rooms = {};
local rooms = rooms;
-local persistent_rooms = datamanager.load(nil, muc_host, "persistent") or {};
+local persistent_rooms_storage = module:open_store("persistent");
+local persistent_rooms = persistent_rooms_storage:get() or {};
+local room_configs = module:open_store("config");
-- Configurable options
muclib.set_max_history_length(module:get_option_number("max_history_messages"));
@@ -66,15 +67,15 @@ local function room_save(room, forced)
_data = room._data;
_affiliations = room._affiliations;
};
- datamanager.store(node, muc_host, "config", data);
+ room_configs:set(node, data);
room._data.history = history;
elseif forced then
- datamanager.store(node, muc_host, "config", nil);
+ room_configs:set(node, nil);
if not next(room._occupants) then -- Room empty
rooms[room.jid] = nil;
end
end
- if forced then datamanager.store(nil, muc_host, "persistent", persistent_rooms); end
+ if forced then persistent_rooms_storage:set(nil, persistent_rooms); end
end
function create_room(jid)
@@ -88,7 +89,7 @@ end
local persistent_errors = false;
for jid in pairs(persistent_rooms) do
local node = jid_split(jid);
- local data = datamanager.load(node, muc_host, "config");
+ local data = room_configs:get(node);
if data then
local room = create_room(jid);
room._data = data._data;
@@ -99,7 +100,7 @@ for jid in pairs(persistent_rooms) do
persistent_errors = true;
end
end
-if persistent_errors then datamanager.store(nil, muc_host, "persistent", persistent_rooms); end
+if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end
local host_room = muc_new_room(muc_host);
host_room.route_stanza = room_route_stanza;
@@ -126,9 +127,10 @@ local function handle_to_domain(event)
if type == "error" or type == "result" then return; end
if stanza.name == "iq" and type == "get" then
local xmlns = stanza.tags[1].attr.xmlns;
- if xmlns == "http://jabber.org/protocol/disco#info" then
+ local node = stanza.tags[1].attr.node;
+ if xmlns == "http://jabber.org/protocol/disco#info" and not node then
origin.send(get_disco_info(stanza));
- elseif xmlns == "http://jabber.org/protocol/disco#items" then
+ elseif xmlns == "http://jabber.org/protocol/disco#items" and not node then
origin.send(get_disco_items(stanza));
elseif xmlns == "http://jabber.org/protocol/muc#unique" then
origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index 16a0238d..a5aba3c8 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -88,6 +88,10 @@ local function getText(stanza, path) return getUsingPath(stanza, path, true); en
local room_mt = {};
room_mt.__index = room_mt;
+function room_mt:__tostring()
+ return "MUC room ("..self.jid..")";
+end
+
function room_mt:get_default_role(affiliation)
if affiliation == "owner" or affiliation == "admin" then
return "moderator";
@@ -576,10 +580,9 @@ function room_mt:send_form(origin, stanza)
end
function room_mt:get_form_layout()
- local title = "Configuration for "..self.jid;
- return dataform.new({
- title = title,
- instructions = title,
+ local form = dataform.new({
+ title = "Configuration for "..self.jid,
+ instructions = "Complete and submit this form to configure the room.",
{
name = 'FORM_TYPE',
type = 'hidden',
@@ -649,6 +652,7 @@ function room_mt:get_form_layout()
value = tostring(self:get_historylength())
}
});
+ return module:fire_event("muc-config-form", { room = self, form = form }) or form;
end
local valid_whois = {
@@ -669,6 +673,10 @@ function room_mt:process_form(origin, stanza)
local dirty = false
+ local event = { room = self, fields = fields, changed = dirty };
+ module:fire_event("muc-config-submitted", event);
+ dirty = event.changed or dirty;
+
local name = fields['muc#roomconfig_roomname'];
if name ~= self:get_name() then
self:set_name(name);
@@ -765,13 +773,9 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
local type = stanza.attr.type;
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
if stanza.name == "iq" then
- if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" then
- if stanza.tags[1].attr.node then
- origin.send(st.error_reply(stanza, "cancel", "feature-not-implemented"));
- else
- origin.send(self:get_disco_info(stanza));
- end
- elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" then
+ if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then
+ origin.send(self:get_disco_info(stanza));
+ elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then
origin.send(self:get_disco_items(stanza));
elseif xmlns == "http://jabber.org/protocol/muc#admin" then
local actor = stanza.attr.from;
@@ -896,7 +900,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
origin.send(st.error_reply(stanza, "auth", "forbidden"));
end
else
- self:broadcast_message(stanza, self:get_historylength() > 0);
+ self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body"));
end
stanza.attr.from = from;
end
@@ -987,7 +991,7 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason)
return true;
end
if actor_affiliation ~= "owner" then
- if actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then
+ if affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then
return nil, "cancel", "not-allowed";
end
elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change
@@ -1049,11 +1053,12 @@ function room_mt:get_role(nick)
return session and session.role or nil;
end
function room_mt:can_set_role(actor_jid, occupant_jid, role)
- local actor = self._occupants[self._jid_nick[actor_jid]];
local occupant = self._occupants[occupant_jid];
-
if not occupant or not actor then return nil, "modify", "not-acceptable"; end
+ if actor_jid == true then return true; end
+
+ local actor = self._occupants[self._jid_nick[actor_jid]];
if actor.role == "moderator" then
if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then
if actor.affiliation == "owner" or actor.affiliation == "admin" then
diff --git a/plugins/sql.lib.lua b/plugins/sql.lib.lua
new file mode 100644
index 00000000..005ee45d
--- /dev/null
+++ b/plugins/sql.lib.lua
@@ -0,0 +1,9 @@
+local cache = module:shared("/*/sql.lib/util.sql");
+
+if not cache._M then
+ prosody.unlock_globals();
+ cache._M = require "util.sql";
+ prosody.lock_globals();
+end
+
+return cache._M;