mod_storage_gdbm: Use require directly instead of util.import (which is not available in prosodyctl, breaks adduser etc)
--- a/mod_auth_ccert/mod_auth_ccert.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_ccert/mod_auth_ccert.lua Sun Jan 25 13:04:02 2015 +0100
@@ -60,7 +60,7 @@
function get_sasl_handler(session)
return new_sasl(module.host, {
external = session.secure and function(authz)
- if not session.secure then
+ if not session.secure or not session.conn:ssl() then
-- getpeercertificate() on a TCP connection would be bad, abort!
(session.log or log)("error", "How did you manage to select EXTERNAL without TLS?");
return nil, false;
--- a/mod_auth_dovecot/auth_dovecot/sasl_dovecot.lib.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_dovecot/auth_dovecot/sasl_dovecot.lib.lua Sun Jan 25 13:04:02 2015 +0100
@@ -243,6 +243,12 @@
end
end
+ -- If dovecot username formatting is on the username will be on the
+ -- original_user field
+ if data.original_user then
+ data.user = data.original_user
+ end
+
if data.user then
local handle_domain = self.config.handle_domain;
local validate_domain = self.config.validate_domain;
--- a/mod_auth_external/mod_auth_external.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_external/mod_auth_external.lua Sun Jan 25 13:04:02 2015 +0100
@@ -99,6 +99,7 @@
end
local response, err = send_query(query);
+ module:log("debug", "input: %q, output %q", query, response);
if not response then
log("warn", "Error while waiting for result from auth process: %s", err or "unknown error");
elseif (script_type == "ejabberd" and response == "\0\2\0\0") or
--- a/mod_auth_ha1/mod_auth_ha1.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_ha1/mod_auth_ha1.lua Sun Jan 25 13:04:02 2015 +0100
@@ -11,11 +11,13 @@
local nodeprep = require "util.encodings".stringprep.nodeprep;
local nameprep = require "util.encodings".stringprep.nameprep;
local md5 = require "util.hashes".md5;
+local from_hex = require"util.hex".from;
local host = module.host;
local auth_filename = module:get_option_string("auth_ha1_file", "auth.txt");
local auth_data = {};
+local hash_len = #md5("", true);
function reload_auth_data()
local f, err = io.open(auth_filename);
@@ -38,10 +40,12 @@
module:log("error", "Invalid username on line %d of auth file, skipping", line_number);
elseif not realm then
module:log("error", "Invalid hostname/realm on line %d of auth file, skipping", line_number);
+ elseif #hash ~= hash_len then
+ module:log("error", "Hash of wrong length on line %d of auth file, skipping", line_number);
elseif state ~= "authorized" then
not_authorized_count = not_authorized_count + 1;
elseif realm == host then
- auth_data[username] = hash;
+ auth_data[username] = from_hex(hash);
imported_count = imported_count + 1;
end
end
@@ -62,7 +66,7 @@
function provider.test_password(username, password)
module:log("debug", "test password for user %s at host %s, %s", username, host, password);
- local test_hash = md5(username..":"..host..":"..password, true);
+ local test_hash = md5(username..":"..host..":"..password);
if test_hash == auth_data[username] then
return true;
@@ -95,7 +99,10 @@
return new_sasl(host, {
plain_test = function(sasl, username, password, realm)
return usermanager.test_password(username, realm, password), true;
- end
+ end;
+ ["digest-md5"] = function(sasl, username, realm, realm_again, charset)
+ return auth_data[username], true;
+ end;
});
end
--- a/mod_auth_http_async/mod_auth_http_async.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_http_async/mod_auth_http_async.lua Sun Jan 25 13:04:02 2015 +0100
@@ -7,7 +7,6 @@
-- COPYING file in the source package for more information.
--
-local usermanager = require "core.usermanager";
local new_sasl = require "util.sasl".new;
local base64 = require "util.encodings".base64.encode;
local waiter =require "util.async".waiter;
@@ -66,7 +65,7 @@
function provider.get_sasl_handler()
return new_sasl(host, {
plain_test = function(sasl, username, password, realm)
- return usermanager.test_password(username, realm, password), true;
+ return provider.test_password(username, realm, password), true;
end
});
end
--- a/mod_auth_imap/auth_imap/mod_auth_imap.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_imap/auth_imap/mod_auth_imap.lua Sun Jan 25 13:04:02 2015 +0100
@@ -11,6 +11,7 @@
local imap_service_realm = module:get_option_string("imap_auth_realm", module:get_option("sasl_realm"));
local imap_service_name = module:get_option_string("imap_auth_service_name");
local append_host = module:get_option_boolean("auth_append_host");
+local strip_host = module:get_option_boolean("auth_strip_host");
local verify_certificate = module:get_option_boolean("auth_imap_verify_certificate", true);
local ssl_params = module:get_option("auth_imap_ssl", {
@@ -28,7 +29,7 @@
imap_service_realm or realm,
imap_service_name or "xmpp",
imap_host, imap_port,
- ssl_params, append_host
+ ssl_params, append_host, strip_host
);
end
--- a/mod_auth_imap/auth_imap/sasl_imap.lib.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_imap/auth_imap/sasl_imap.lib.lua Sun Jan 25 13:04:02 2015 +0100
@@ -112,7 +112,7 @@
end
-- create a new SASL object which can be used to authenticate clients
-function _M.new(realm, service_name, host, port, ssl_params, append_host)
+function _M.new(realm, service_name, host, port, ssl_params, append_host, strip_host)
log("debug", "new(%q, %q, %q, %d)", realm or "", service_name or "", host or "", port or 0);
local sasl_i = {
realm = realm;
@@ -121,13 +121,14 @@
_port = port;
_ssl_params = ssl_params;
_append_host = append_host;
+ _strip_host = strip_host;
};
local conn, mechs = connect(host, port, ssl_params);
if not conn then
return nil, "Socket connection failure";
end
- if append_host then
+ if append_host or strip_host then
mechs = { PLAIN = mechs.PLAIN };
end
sasl_i.conn, sasl_i.mechs = conn, mechs;
@@ -141,7 +142,7 @@
self.conn = nil;
end
log("debug", "method:clean_clone()");
- return _M.new(self.realm, self.service_name, self._host, self._port, self._ssl_params, self._append_host)
+ return _M.new(self.realm, self.service_name, self._host, self._port, self._ssl_params, self._append_host, self._strip_host)
end
-- get a list of possible SASL mechanisms to use
@@ -177,8 +178,12 @@
function method:process(message)
local username = mitm[self.selected](message);
if username then self.username = username; end
- if self._append_host and self.selected == "PLAIN" then
- message = message:gsub("^([^%z]*%z[^%z]+)(%z[^%z]+)$", "%1@"..self.realm.."%2");
+ if self.selected == "PLAIN" then
+ if self._append_host then
+ message = message:gsub("^%Z*(%z[^%z@]+)@?%Z*(%z%Z+)$", "%1@"..self.realm.."%2");
+ elseif self._strip_host then
+ message = message:gsub("^(%Z*%z[^@%z]+)@%Z+", "%1");
+ end
end
log("debug", "method:process(%d bytes): %q", #message, message:gsub("%z", "."));
local ok, err = self.conn:send(b64(message).."\n");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_auth_internal_ng/mod_auth_internal_ng.lua Sun Jan 25 13:04:02 2015 +0100
@@ -0,0 +1,147 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2014 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local new_sasl = require "util.sasl".new;
+local random_bytes = require"util.random".bytes;
+local rfc5803 = require"util.rfc5803";
+local scram = require "util.sasl.scram";
+local hex = require"util.hex";
+
+local mode = module:get_option_string("store_credentials", "hashed");
+local default_i = module:get_option_number("default_iteration_count", 2^12);
+
+local accounts = module:open_store("accounts");
+
+local function hashify(account)
+ local i = default_i;
+ local password = account.password;
+
+ if account.hashedPassword then
+ if not password then return end -- Already hashed
+ local _, old_i = rfc5803.unpack(account.hashedPassword);
+ if old_i > i then i = old_i; end
+ end
+ local salt = random_bytes(16);
+ local ok, stored_key, server_key = scram.getAuthenticationDatabaseSHA1(password, salt, i);
+ if not ok then return nil, stored_key; end
+ account.hashedPassword = rfc5803.pack("SHA-1", i, salt, stored_key, server_key);
+ account.password = nil;
+ account.iteration_count, account.salt = nil, nil;
+ account.stored_key, account.server_key = nil, nil;
+ return account;
+end
+
+local function get_scram_hash(account)
+ if account.hashedPassword then
+ return rfc5803.unpack(account.hashedPassword);
+ elseif account.stored_key then
+ return "SHA-1", account.iteration_count, account.salt,
+ hex.from(account.stored_key), hex.from(account.server_key);
+ end
+end
+
+function user_exists(username)
+ local account, err = accounts:get(username);
+ if not account then return account, err; end
+ return next(account) ~= nil;
+end
+
+function create_user(username, password)
+ local account = { password = password };
+ if mode == "hashed" then
+ hashify(account);
+ end
+ return accounts:set(username, account);
+end
+
+function delete_user(username)
+ return accounts:set(username, nil);
+end
+
+function test_password(username, password)
+ local account, err = accounts:get(username);
+ if not account then return account, err; end
+ if account.password then
+ return password == account.password;
+ end
+ local hash, i, salt, our_stored_key, our_server_key = get_scram_hash(account);
+ local ok, stored_key, server_key = scram.getAuthenticationDatabaseSHA1(password, salt, i);
+ if not ok then return ok, stored_key; end
+ ok = hash == "SHA-1" and stored_key == our_stored_key and server_key == our_server_key;
+ if ok and mode == "unhash" then
+ account.password, account.hashedPassword = password;
+ accounts:set(username, account);
+ end
+ return ok;
+end
+
+local sasl_profile = {};
+
+function get_sasl_handler()
+ return new_sasl(module.host, sasl_profile);
+end
+
+function set_password(username, password)
+ local account, err = accounts:get(username);
+ if not account then account = {}; end
+ account.password = password;
+ if mode == "hashed" then
+ account, err = hashify(account);
+ end
+ if not account then return account, err; end
+ return accounts:set(username, account);
+end
+
+if mode == "plain" then
+ function get_password(username)
+ local account, err = accounts:get(username);
+ if not account then return nil, err; end
+ return account.password;
+ end
+
+ function sasl_profile:plain(username)
+ return get_password(username), true;
+ end
+elseif mode == "unhash" then
+ function sasl_profile:plain_test(username, password)
+ local ok = test_password(username, password);
+ if ok then
+ set_password(username, password);
+ end
+ return ok, true;
+ end
+elseif mode == "hashed" then
+ function sasl_profile:plain_test(username, password)
+ return test_password(username, password), true;
+ end
+
+ function sasl_profile:scram_sha_1(username)
+ local account, err = accounts:get(username);
+ if not account then return account, err; end
+ if not account.hashedPassword and hashify(account) then
+ accounts:set(username, account);
+ end
+ local hash, i, salt, stored_key, server_key = get_scram_hash(account);
+ if hash == "SHA-1" then
+ return stored_key, server_key, i, salt, true;
+ end
+ end
+else
+ module:log("error", "Unsupported store_credentials mode %q", mode);
+ return;
+end
+
+if accounts.users then
+ function users()
+ return accounts:users();
+ end
+end
+
+module:provides "auth";
+
--- a/mod_auth_ldap/mod_auth_ldap.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_ldap/mod_auth_ldap.lua Sun Jan 25 13:04:02 2015 +0100
@@ -16,12 +16,42 @@
local host = ldap_filter_escape(module:get_option_string("realm", module.host));
-- Initiate connection
-local ld = assert(lualdap.open_simple(ldap_server, ldap_rootdn, ldap_password, ldap_tls));
-module.unload = function() ld:close(); end
+local ld = nil;
+module.unload = function() if ld then pcall(ld, ld.close); end end
+
+function ldap_search_once(args)
+ if ld == nil then
+ local err;
+ ld, err = lualdap.open_simple(ldap_server, ldap_rootdn, ldap_password, ldap_tls);
+ if not ld then return nil, err, "reconnect"; end
+ end
+
+ local success, iterator, invariant, initial = pcall(ld.search, ld, args);
+ if not success then ld = nil; return nil, iterator, "search"; end
+
+ local success, dn, attr = pcall(iterator, invariant, initial);
+ if not success then ld = nil; return success, dn, "iter"; end
+
+ return dn, attr, "return";
+end
+
+function ldap_search(args, retry_count)
+ local dn, attr, where;
+ for i=1,1+retry_count do
+ dn, attr, where = ldap_search_once(args);
+ if dn or not(attr) then break; end -- nothing or something found
+ module:log("warn", "LDAP: %s %s (in %s)", tostring(dn), tostring(attr), where);
+ -- otherwise retry
+ end
+ if not dn and attr then
+ module:log("error", "LDAP: %s", tostring(attr));
+ end
+ return dn, attr;
+end
local function get_user(username)
module:log("debug", "get_user(%q)", username);
- for dn, attr in ld:search({
+ return ldap_search({
base = ldap_base;
scope = ldap_scope;
sizelimit = 1;
@@ -29,7 +59,7 @@
user = ldap_filter_escape(username);
host = host;
});
- }) do return dn, attr; end
+ }, 2);
end
local provider = {};
@@ -39,7 +69,9 @@
end
function provider.user_exists(username)
- return not not get_user(username);
+ local dn, attr = get_user(username);
+ if not dn and attr then return nil, attr; end
+ return not not dn;
end
function provider.set_password(username, password)
@@ -76,8 +108,8 @@
end
function provider.test_password(username, password)
- local dn = get_user(username);
- if not dn then return end
+ local dn, attr = get_user(username);
+ if not dn then return nil, attr; end
return test_password(dn, password)
end
@@ -88,6 +120,30 @@
end
});
end
+elseif ldap_mode == "directbind" then
+ local function test_password(userdn, password)
+ return not not lualdap.open_simple(ldap_server, userdn, password, ldap_tls);
+ end
+
+ function provider.user_exists(username)
+ return true;
+ end
+
+ function provider.test_password(username, password)
+ local dn = ldap_filter:gsub("%$(%a+)", {
+ user = ldap_filter_escape(username);
+ host = host;
+ });
+ return test_password(dn, password);
+ end
+
+ function provider.get_sasl_handler()
+ return new_sasl(module.host, {
+ plain_test = function(sasl, username, password)
+ return provider.test_password(username, password), true;
+ end
+ });
+ end
else
module:log("error", "Unsupported ldap_mode %s", tostring(ldap_mode));
end
--- a/mod_auth_pam/mod_auth_pam.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_pam/mod_auth_pam.lua Sun Jan 25 13:04:02 2015 +0100
@@ -4,14 +4,21 @@
-- Requires https://github.com/devurandom/lua-pam
-- and LuaPosix
-local posix = require "posix";
+local have_posix, posix = pcall(require, "posix");
local pam = require "pam";
local new_sasl = require "util.sasl".new;
-function user_exists(username)
- return not not posix.getpasswd(username);
+if have_posix then
+ function user_exists(username)
+ return not not posix.getpasswd(username);
+ end
+else
+ function user_exists()
+ return true;
+ end
end
+
function test_password(username, password)
local h, err = pam.start("xmpp", username, {
function (t)
@@ -21,15 +28,14 @@
end
});
if h and h:authenticate() and h:endx(pam.SUCCESS) then
- return user_exists(username), true;
+ return user_exists(username);
end
- return nil, true;
end
function get_sasl_handler()
return new_sasl(module.host, {
plain_test = function(sasl, ...)
- return test_password(...)
+ return test_password(...), true;
end
});
end
--- a/mod_auth_sql/mod_auth_sql.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_auth_sql/mod_auth_sql.lua Sun Jan 25 13:04:02 2015 +0100
@@ -79,6 +79,13 @@
end
end
+local function set_password(username, password)
+ return getsql("UPDATE `authreg` SET `password` = ? WHERE `username`=? AND `realm`=?", username, module.host);
+end
+
+local function create_user(username, password)
+ return getsql("INSERT INTO `authreg` (`username`, `realm`, `password`) VALUES (?, ?, ?)", username, module.host, password);
+end
provider = {};
@@ -89,13 +96,13 @@
return get_password(username);
end
function provider.set_password(username, password)
- return nil, "Setting password is not supported.";
+ return set_password(username, password);
end
function provider.user_exists(username)
return get_password(username) and true;
end
function provider.create_user(username, password)
- return nil, "Account creation/modification not supported.";
+ return create_user(username, password);
end
function provider.get_sasl_handler()
local profile = {
--- a/mod_bidi/mod_bidi.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_bidi/mod_bidi.lua Sun Jan 25 13:04:02 2015 +0100
@@ -14,7 +14,10 @@
local xmlns_bidi = "urn:xmpp:bidi";
local secure_only = module:get_option_boolean("secure_bidi_only", true);
local disable_bidi_for = module:get_option_set("no_bidi_with", { });
-local bidi_sessions = module:shared"sessions-cache";
+local bidi_sessions = module:shared"sessions";
+-- if not getmetatable(bidi_sessions) then
+ -- setmetatable(bidi_sessions, { __mode = "kv" });
+-- end
local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
local function handlestanza(session, stanza)
@@ -30,7 +33,7 @@
local function new_bidi(origin)
if origin.type == "s2sin" then -- then we create an "outgoing" bidirectional session
local conflicting_session = hosts[origin.to_host].s2sout[origin.from_host]
- if conflicting_session then
+ if false and conflicting_session then
conflicting_session.log("info", "We already have an outgoing connection to %s, closing it...", origin.from_host);
conflicting_session:close{ condition = "conflict", text = "Replaced by bidirectional stream" }
end
--- a/mod_block_strangers/mod_block_strangers.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_block_strangers/mod_block_strangers.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,20 +1,58 @@
+module:depends("adhoc");
local jid_split = require "util.jid".split;
local jid_bare = require "util.jid".bare;
local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+local adhoc_new = module:require "adhoc".new;
+local bare_sessions = bare_sessions
-function check_subscribed(event)
+local storage = module:open_store();
+
+local function filter_stanza(event)
local stanza = event.stanza;
local to_user, to_host, to_resource = jid_split(stanza.attr.to);
- local from_jid = jid_bare(stanza.attr.from);
- if to_user and not is_contact_subscribed(to_user, to_host, from_jid) then
- if to_resource and stanza.attr.type == "groupchat" then
- return nil; -- Pass through
+ if bare_sessions[to_user.."@"..to_host].block_strangers then
+ local from_jid = jid_bare(stanza.attr.from);
+ if to_user and not is_contact_subscribed(to_user, to_host, from_jid) then
+ if to_resource and stanza.attr.type == "groupchat" then
+ return nil; -- Pass through
+ end
+ return true; -- Drop stanza
end
- return true; -- Drop stanza
end
end
-module:hook("message/bare", check_subscribed, 200);
-module:hook("message/full", check_subscribed, 200);
-module:hook("iq/full", check_subscribed, 200);
+local function load_blocking_status(event)
+ local origin = event.origin;
+ local jid = jid_bare(origin.full_jid);
+ bare_sessions[jid].block_strangers = not not storage:get(username);
+end
+
+module:hook("message/bare", filter_stanza, 200);
+module:hook("message/full", filter_stanza, 200);
+module:hook("iq/full", filter_stanza, 200);
+
+module:hook("presence/bare", load_blocking_status);
+
+
+-- Expose blocking/unblocking through ad-hoc commands.
+
+local function toggle_global_blocking(self, data)
+ local username, host = jid_split(data.from);
+ local enabling = (self.node == "mod_block_strangers#enable");
+ local noun = enabling and "blocked" or "unblocked";
+ storage:set(username, enabling and {enabled=true} or {});
+ bare_sessions[username.."@"..host].block_strangers = enabling;
+ return { info = "Strangers successfully "..noun, status = "completed" };
+end
+
+local function get_blocking_status(self, data)
+ local jid = jid_bare(data.from);
+ local enabled = bare_sessions[jid].block_strangers;
+ local noun = enabled and "blocked" or "unblocked";
+ return { info = "Strangers are currently "..noun, status = "completed" };
+end
+
+module:add_item("adhoc", adhoc_new("Block strangers", "mod_block_strangers#enable", toggle_global_blocking));
+module:add_item("adhoc", adhoc_new("Unblock strangers", "mod_block_strangers#disable", toggle_global_blocking));
+module:add_item("adhoc", adhoc_new("Get strangers block status", "mod_block_strangers#status", get_blocking_status));
--- a/mod_candy/mod_candy.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_candy/mod_candy.lua Sun Jan 25 13:04:02 2015 +0100
@@ -23,10 +23,6 @@
}));
end;
["GET /*"] = serve(module:get_directory().."/www_files");
- GET = function(event)
- event.response.headers.location = event.request.path.."/";
- return 301;
- end;
}
});
--- a/mod_candy/www_files/index.html Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_candy/www_files/index.html Sun Jan 25 13:04:02 2015 +0100
@@ -13,7 +13,7 @@
<script type="text/javascript">
$(document).ready(function() {
Candy.init(Prosody.bosh_path, {
- core: { debug: false },
+ core: { debug: false, autojoin: [ "test@muc.carcharodon.zash.se" ] },
view: { resources: 'res/' }
});
--- a/mod_carbons/mod_carbons.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_carbons/mod_carbons.lua Sun Jan 25 13:04:02 2015 +0100
@@ -16,6 +16,7 @@
local state = stanza.tags[1].attr.mode or stanza.tags[1].name;
module:log("debug", "%s %sd carbons", origin.full_jid, state);
origin.want_carbons = state == "enable" and stanza.tags[1].attr.xmlns;
+ module:fire_event("session-state", { session = origin, property = "want_carbons" });
return origin.send(st.reply(stanza));
end
module:hook("iq-set/self/"..xmlns_carbons..":disable", toggle_carbons);
@@ -62,17 +63,7 @@
return -- No use in sending carbons to an offline user
end
- if stanza:get_child("private", xmlns_carbons) then
- if not c2s then
- stanza:maptags(function(tag)
- if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then
- return tag;
- end
- end);
- end
- module:log("debug", "Message tagged private, ignoring");
- return
- elseif stanza:get_child("no-copy", "urn:xmpp:hints") then
+ if stanza:get_child("no-copy", "urn:xmpp:hints") then
module:log("debug", "Message has no-copy hint, ignoring");
return
elseif stanza:get_child("x", "http://jabber.org/protocol/muc#user") then
--- a/mod_compat_muc_admin/mod_compat_muc_admin.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_compat_muc_admin/mod_compat_muc_admin.lua Sun Jan 25 13:04:02 2015 +0100
@@ -44,7 +44,7 @@
local tag = stanza;
for _, name in ipairs(path) do
if type(tag) ~= 'table' then return; end
- tag = tag:child_with_name(name);
+ tag = tag:get_child(name);
end
if tag and getText then tag = table.concat(tag); end
return tag;
--- a/mod_default_bookmarks/mod_default_bookmarks.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_default_bookmarks/mod_default_bookmarks.lua Sun Jan 25 13:04:02 2015 +0100
@@ -13,37 +13,34 @@
local dm_load = require "util.datamanager".load
local jid_split = require "util.jid".split
-module:hook("iq/self/jabber:iq:private:query", function(event)
+local private_bookmarks_ns = "storage:storage:bookmarks";
+
+local bookmarks = module:get_option("default_bookmarks");
+
+module:hook("iq-get/self/jabber:iq:private:query", function(event)
local origin, stanza = event.origin, event.stanza;
- local typ = stanza.attr.type;
local from = stanza.attr.from;
- local query = stanza.tags[1];
- if #query.tags == 1 and typ == "get" then
- local tag = query.tags[1];
- local key = tag.name..":"..tag.attr.xmlns;
- if key == "storage:storage:bookmarks" then
- local data, err = dm_load(origin.username, origin.host, "private");
- if not(data and data[key]) then
- local bookmarks = module:get_option("default_bookmarks");
- if bookmarks and #bookmarks > 0 then
- local reply = st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"})
- :tag("storage", { xmlns = "storage:bookmarks" });
- local nick = jid_split(from);
- for i=1,#bookmarks do
- local bookmark = bookmarks[i];
- if type(bookmark) ~= "table" then -- assume it's only a jid
- bookmark = { jid = bookmark, name = jid_split(bookmark) };
- end
- reply:tag("conference", {
- jid = bookmark.jid,
- name = bookmark.name,
- autojoin = "1",
- }):tag("nick"):text(nick):up():up();
- end
- origin.send(reply);
- return true;
- end
- end
+ if not stanza.tags[1]:get_child("storage", "storage:bookmarks") then return end
+ local data, err = dm_load(origin.username, origin.host, "private");
+ if data and data[private_bookmarks_ns] then return end
+
+ local reply = st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"})
+ :tag("storage", { xmlns = "storage:bookmarks" });
+
+ local nick = jid_split(from);
+
+ local bookmark;
+ for i=1,#bookmarks do
+ bookmark = bookmarks[i];
+ if type(bookmark) ~= "table" then -- assume it's only a jid
+ bookmark = { jid = bookmark, name = jid_split(bookmark) };
end
+ reply:tag("conference", {
+ jid = bookmark.jid,
+ name = bookmark.name,
+ autojoin = "1",
+ }):tag("nick"):text(nick):up():up();
end
+ origin.send(reply);
+ return true;
end, 1);
--- a/mod_discoitems/mod_discoitems.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_discoitems/mod_discoitems.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,25 +1,25 @@
--- mod_discoitems.lua
---
--- In the config, you can add:
---
--- disco_items = {
--- {"proxy.eu.jabber.org", "Jabber.org SOCKS5 service"};
--- {"conference.jabber.org", "The Jabber.org MUC"};
--- };
---
-
-local st = require "util.stanza";
-
-local result_query = st.stanza("query", {xmlns="http://jabber.org/protocol/disco#items"});
-for _, item in ipairs(module:get_option("disco_items") or {}) do
- result_query:tag("item", {jid=item[1], name=item[2]}):up();
-end
-
-module:hook('iq/host/http://jabber.org/protocol/disco#items:query', function(event)
- local stanza = event.stanza;
- local query = stanza.tags[1];
- if stanza.attr.type == 'get' and not query.attr.node then
- event.origin.send(st.reply(stanza):add_child(result_query));
- return true;
- end
-end, 100);
+-- mod_discoitems.lua
+--
+-- In the config, you can add:
+--
+-- disco_items = {
+-- {"proxy.eu.jabber.org", "Jabber.org SOCKS5 service"};
+-- {"conference.jabber.org", "The Jabber.org MUC"};
+-- };
+--
+
+local st = require "util.stanza";
+
+local result_query = st.stanza("query", {xmlns="http://jabber.org/protocol/disco#items"});
+for _, item in ipairs(module:get_option("disco_items") or {}) do
+ result_query:tag("item", {jid=item[1], name=item[2]}):up();
+end
+
+module:hook('iq/host/http://jabber.org/protocol/disco#items:query', function(event)
+ local stanza = event.stanza;
+ local query = stanza.tags[1];
+ if stanza.attr.type == 'get' and not query.attr.node then
+ event.origin.send(st.reply(stanza):add_child(result_query));
+ return true;
+ end
+end, 100);
--- a/mod_extdisco/mod_extdisco.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_extdisco/mod_extdisco.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,20 +1,28 @@
+local moduleapi = require"core.moduleapi";
local st = require "util.stanza";
-
-local services = module:get_option("external_services");
+local array = require"util.array";
local xmlns_extdisco = "urn:xmpp:extdisco:1";
module:add_feature(xmlns_extdisco);
+function moduleapi:add_external_service(info)
+ return self:add_item("external_service", info);
+end
+
module:hook("iq-get/host/"..xmlns_extdisco..":services", function (event)
local origin, stanza = event.origin, event.stanza;
local service = stanza:get_child("service", xmlns_extdisco);
local service_type = service and service.attr.type;
- local reply = st.reply(stanza);
- for host, service_info in pairs(services) do
+ local reply = st.reply(stanza):tag("services", {xmlns = xmlns_extdisco});
+ local services = array():append(module:get_host_items("external_services"));
+ local event = { services = services, origin = origin, type = service_type };
+ module:fire_event("external-service-discovery", event);
+ for i = 1, #services do
+ local service_info = services[i];
if not(service_type) or service_info.type == service_type then
reply:tag("service", {
- host = host;
+ host = service_info.host;
port = service_info.port;
transport = service_info.transport;
type = service_info.type;
@@ -27,6 +35,20 @@
return true;
end);
+function module.load()
+ local services = module:get_option("external_services");
+ for host, service_info in pairs(services) do
+ module:add_external_service {
+ host = host,
+ port = service_info.port;
+ transport = service_info.transport;
+ type = service_info.type;
+ username = service_info.username;
+ password = service_info.password;
+ }
+ end
+end
+
module:hook("iq-get/host/"..xmlns_extdisco..":credentials", function (event)
local origin, stanza = event.origin, event.stanza;
local credentials = stanza:get_child("credentials", xmlns_extdisco);
@@ -35,7 +57,7 @@
origin.send(st.error_reply(stanza, "cancel", "bad-request", "No host specified"));
return true;
end
- local service_info = services[host];
+ local service_info = module:fire_event("external-services-credentials") or services[host];
if not service_info then
origin.send(st.error_reply(stanza, "cancel", "item-not-found", "No such service known"));
return true;
@@ -50,3 +72,4 @@
origin.send(reply);
return true;
end);
+
--- a/mod_firewall/mod_firewall.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_firewall/mod_firewall.lua Sun Jan 25 13:04:02 2015 +0100
@@ -27,6 +27,10 @@
type = "event"; "route/remote";
priority = 0.1;
};
+ send_remote = { -- FIXME name
+ type = "filter"; "s2sout";
+ priority = 0.1;
+ };
};
local function idsafe(name)
@@ -372,9 +376,13 @@
module:log("error", "Compilation error for %s: %s", script, err);
else
local chain_definition = chains[chain];
- if chain_definition and chain_definition.type == "event" then
- for _, event_name in ipairs(chain_definition) do
- module:hook(event_name, handler, chain_definition.priority);
+ if chain_definition then
+ if chain_definition.type == "event" then
+ for _, event_name in ipairs(chain_definition) do
+ module:hook(event_name, handler, chain_definition.priority);
+ end
+ elseif chain_definition.type == "filter" then
+ -- TODO
end
elseif not chain:match("^user/") then
module:log("warn", "Unknown chain %q", chain);
--- a/mod_http_altconnect/mod_http_altconnect.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_http_altconnect/mod_http_altconnect.lua Sun Jan 25 13:04:02 2015 +0100
@@ -6,16 +6,20 @@
local json = require"util.json";
local st = require"util.stanza";
local array = require"util.array";
+local it = require"util.iterators";
local host_modules = hosts[module.host].modules;
local function get_supported()
- local uris = array();
- if host_modules["bosh"] then
- uris:push({ rel = "urn:xmpp:alt-connections:xbosh", href = module:http_url("bosh", "/http-bind") });
- end
- if host_modules["websocket"] then
- uris:push({ rel = "urn:xmpp:alt-connections:websocket", href = module:http_url("websocket", "xmpp-websocket"):gsub("^http", "ws") });
+ local uris = array(it.values(module:get_host_items("alt-conn-method")));
+ if #uris == 0 then
+ -- COMPAT for with before item array was added
+ if host_modules["bosh"] then
+ uris:push({ rel = "urn:xmpp:alt-connections:xbosh", href = module:http_url("bosh", "/http-bind") });
+ end
+ if host_modules["websocket"] then
+ uris:push({ rel = "urn:xmpp:alt-connections:websocket", href = module:http_url("websocket", "xmpp-websocket"):gsub("^http", "ws") });
+ end
end
return uris;
end
@@ -50,6 +54,16 @@
end
end;
+function module.load()
+ local methods = get_supported()
+ if #methods > 0 then
+ module:log("info", "To advertise alternative XMPP connection methods via dns, add these TXT records:");
+ for i, method in ipairs(methods) do
+ module:log("info", "_xmppconnect IN TXT \"%s=%s\"", method.rel:gsub("^urn:xmpp:alt%-connections:", "_xmpp-client-"), method.href);
+ end
+ end
+end
+
module:provides("http", {
default_path = "/.well-known";
route = {
--- a/mod_http_dir_listing/http_dir_listing/mod_http_dir_listing.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_http_dir_listing/http_dir_listing/mod_http_dir_listing.lua Sun Jan 25 13:04:02 2015 +0100
@@ -58,6 +58,7 @@
end
module:hook_object_event(server, "directory-index", function (event)
+ module:log("debug", "generate directory index for %s (%s)", event.path, event.full_path);
local ok, data = pcall(generate_directory_index, event.path, event.full_path);
if ok then return data end
module:log("warn", data);
--- a/mod_http_dir_listing/http_dir_listing/resources/style.css Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_http_dir_listing/http_dir_listing/resources/style.css Sun Jan 25 13:04:02 2015 +0100
@@ -5,6 +5,12 @@
a:link:hover,a:visited:hover{color:#3465a4;}
.filelist{background-color:white;padding:1em;list-style-position:inside;-moz-column-width:20em;-webkit-column-width:20em;-ms-column-width:20em;column-width:20em;}
.file{list-style-image:url(text-x-generic.png);}
+.file.image{list-style-image:url(image-x-generic.png);}
+.file.video{list-style-image:url(video-x-generic.png);}
+.file.audio{list-style-image:url(audio-x-generic.png);}
+.file.vcf{list-style-image:url(x-office-address-book.png);}
+.file.text.html{list-style-image:url(text-html.png);}
+.file.application{list-style-image:url(application-x-executable.png);}
.directory{list-style-image:url(folder.png);}
.parent{list-style-image:url(user-home.png);}
footer{margin-top:1ex;font-size:smaller;color:#babdb6;}
--- a/mod_http_dir_listing/http_dir_listing/resources/template.html Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_http_dir_listing/http_dir_listing/resources/template.html Sun Jan 25 13:04:02 2015 +0100
@@ -6,9 +6,9 @@
</head>
<body>
<h1>Index of {path}</h1>
-
- {filelist}
-
+ <article>
+ {filelist}
+ </article>
<footer>{footer}</footer>
</body>
</html>
--- a/mod_http_index/mod_http_index.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_http_index/mod_http_index.lua Sun Jan 25 13:04:02 2015 +0100
@@ -45,12 +45,6 @@
.content{background-color:white;padding:1em;list-style-position:inside;}
nav{font-size:large;margin:1ex 1ex;clear:both;line-height:1.5em;}
nav a{padding: 1ex;text-decoration:none;}
-nav a[rel="up"]{font-size:smaller;}
-nav a[rel="prev"]{float:left;}
-nav a[rel="next"]{float:right;}
-nav a[rel="next::after"]{content:" →";}
-nav a[rel="prev::before"]{content:"← ";}
-nav a:empty::after,nav a:empty::before{content:""}
@media screen and (min-width: 460px) {
nav{font-size:x-large;margin:1ex 1em;}
}
@@ -60,12 +54,7 @@
li{list-style:none;}
hr{visibility:hidden;clear:both;}
br{clear:both;}
-li time{float:right;font-size:small;opacity:0.2;}
li:hover time{opacity:1;}
-.room-list .description{font-size:smaller;}
-q.body::before,q.body::after{content:"";}
-.presence .verb{font-style:normal;color:#30c030;}
-.presence.unavailable .verb{color:#c03030;}
</style>
</head>
<body>
--- a/mod_ipcheck/mod_ipcheck.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_ipcheck/mod_ipcheck.lua Sun Jan 25 13:04:02 2015 +0100
@@ -6,41 +6,37 @@
module:add_feature("urn:xmpp:sic:0");
-module:hook("iq/bare/urn:xmpp:sic:0:ip", function(event)
+module:hook("iq-get/bare/urn:xmpp:sic:0:ip", function(event)
local origin, stanza = event.origin, event.stanza;
- if stanza.attr.type == "get" then
- if stanza.attr.to then
- origin.send(st.error_reply(stanza, "auth", "forbidden", "You can only ask about your own IP address"));
- elseif origin.ip then
- origin.send(st.reply(stanza):tag("ip", {xmlns='urn:xmpp:sic:0'}):text(origin.ip));
- else
- -- IP addresses should normally be available, but in case they are not
- origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "IP address for this session is not available"));
- end
- return true;
+ if stanza.attr.to then
+ origin.send(st.error_reply(stanza, "auth", "forbidden", "You can only ask about your own IP address"));
+ elseif origin.ip then
+ origin.send(st.reply(stanza):tag("ip", {xmlns='urn:xmpp:sic:0'}):text(origin.ip));
+ else
+ -- IP addresses should normally be available, but in case they are not
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "IP address for this session is not available"));
end
+ return true;
end);
module:add_feature("urn:xmpp:sic:1");
-module:hook("iq/bare/urn:xmpp:sic:1:address", function(event)
+module:hook("iq-get/bare/urn:xmpp:sic:1:address", function(event)
local origin, stanza = event.origin, event.stanza;
- if stanza.attr.type == "get" then
- if stanza.attr.to then
- origin.send(st.error_reply(stanza, "auth", "forbidden", "You can only ask about your own IP address"));
- elseif origin.ip then
- local reply = st.reply(stanza):tag("address", {xmlns='urn:xmpp:sic:0'})
- :tag("ip"):text(origin.ip):up()
- if origin.conn and origin.conn.port then -- server_event
- reply:tag("port"):text(tostring(origin.conn:port()))
- elseif origin.conn and origin.conn.clientport then -- server_select
- reply:tag("port"):text(tostring(origin.conn:clientport()))
- end
- origin.send(reply);
- else
- -- IP addresses should normally be available, but in case they are not
- origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "IP address for this session is not available"));
+ if stanza.attr.to then
+ origin.send(st.error_reply(stanza, "auth", "forbidden", "You can only ask about your own IP address"));
+ elseif origin.ip then
+ local reply = st.reply(stanza):tag("address", {xmlns='urn:xmpp:sic:0'})
+ :tag("ip"):text(origin.ip):up()
+ if origin.conn and origin.conn.port then -- server_event
+ reply:tag("port"):text(tostring(origin.conn:port()))
+ elseif origin.conn and origin.conn.clientport then -- server_select
+ reply:tag("port"):text(tostring(origin.conn:clientport()))
end
- return true;
+ origin.send(reply);
+ else
+ -- IP addresses should normally be available, but in case they are not
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "IP address for this session is not available"));
end
+ return true;
end);
--- a/mod_limit_auth/mod_limit_auth.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_limit_auth/mod_limit_auth.lua Sun Jan 25 13:04:02 2015 +0100
@@ -7,7 +7,7 @@
local max = math.max(module:get_option_number(module.name.."_max", 5), 1);
local tarpit_delay = module:get_option_number(module.name.."_tarpit_delay", nil);
-if tarpit_delay then
+if tarpit_delay and tarpit_delay > 0 then
local waiter = require "util.async".waiter;
local delay = tarpit_delay;
function tarpit_delay()
@@ -46,4 +46,12 @@
get_throttle(event.session.ip):poll(1);
end);
--- TODO remove old throttles after some time
+module:add_timer(144, function (t)
+ t = t - 86400;
+ for ip, throttle in pairs(throttles) do
+ if throttle.t < t then
+ throttles[ip] = nil;
+ end
+ end
+ return 144;
+end);
--- a/mod_mam/mod_mam.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_mam/mod_mam.lua Sun Jan 25 13:04:02 2015 +0100
@@ -3,7 +3,7 @@
--
-- This file is MIT/X11 licensed.
-local xmlns_mam = "urn:xmpp:mam:0";
+local xmlns_mam = "urn:xmpp:mam:0"; -- Version 0.3
local xmlns_delay = "urn:xmpp:delay";
local xmlns_forward = "urn:xmpp:forward:0";
@@ -35,6 +35,8 @@
global_default_policy = module:get_option_boolean("default_archive_policy", global_default_policy);
end
+local measure_query_time = module:measure("query", "times");
+
local archive_store = "archive2";
local archive = module:open_store(archive_store, "archive");
if not archive or archive.name == "null" then
@@ -115,6 +117,7 @@
local before, after = qset and qset.before, qset and qset.after;
if type(before) ~= "string" then before = nil; end
+ local query_completed = measure_query_time();
-- Load all the data!
local data, err = archive:find(origin.username, {
@@ -131,7 +134,7 @@
end
local count = err;
- origin.send(st.reply(stanza))
+ origin.send(st.reply(stanza)); -- Remove in next MAM version
local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
-- Wrap it in stuff and deliver
@@ -156,11 +159,18 @@
-- That's all folks!
module:log("debug", "Archive query %s completed", tostring(qid));
+ query_completed();
+
if reverse then first, last = last, first; end
return origin.send(st.message(msg_reply_attr)
:tag("fin", { xmlns = xmlns_mam, queryid = qid })
:add_child(rsm.generate {
first = first, last = last, count = count }));
+ --[[ Next MAM version
+ return origin.send(st.reply(stanza)
+ :query(xmlns_mam):add_child(rsm.generate {
+ first = first, last = last, count = count }));
+ --]]
end);
local function has_in_roster(user, who)
@@ -240,3 +250,101 @@
module:add_feature(xmlns_mam);
+
+module:depends"adhoc";
+local dataforms_new = require "util.dataforms".new;
+local jid_split = require "util.jid".split;
+local t_insert = table.insert;
+local prefs = module:require"mod_mam/mamprefs";
+local set_prefs, get_prefs = prefs.set, prefs.get;
+
+local mam_prefs_form = dataforms_new{
+ title = "Archive preferences";
+ --instructions = "";
+ {
+ name = "default",
+ label = "Default storage policy",
+ type = "list-single",
+ value = {
+ { value = "always", label = "Always", default = global_default_policy == true },
+ { value = "never", label = "Never", default = global_default_policy == false },
+ { value = "roster", label = "Roster", default = global_default_policy == "roster" },
+ },
+ };
+ {
+ name = "always",
+ label = "Always store messages to/from",
+ type = "jid-multi"
+ };
+ {
+ name = "never",
+ label = "Never store messages to/from",
+ type = "jid-multi"
+ };
+};
+
+local host = module.host;
+
+local default_attrs = {
+ always = true, [true] = "always",
+ never = false, [false] = "never",
+ roster = "roster",
+}
+
+local function mam_prefs_handler(self, data, state)
+ local username = jid_split(data.from);
+ if data.action == "cancel" then
+ return { status = "canceled" };
+ end
+
+ if state == nil then
+ local prefs = get_prefs(username);
+ local values = {
+ default = {
+ { value = "always", label = "Always" };
+ { value = "never", label = "Never" };
+ { value = "roster", label = "Roster" };
+ };
+ always = {};
+ never = {};
+ };
+
+ for jid, p in pairs(prefs) do
+ if jid then
+ t_insert(values[p and "always" or "never"], jid);
+
+ elseif p == true then -- Yes, this is ugly. FIXME later.
+ values.default[1].default = true;
+ elseif p == false then
+ values.default[2].default = true;
+ elseif p == "roster" then
+ values.default[3].default = true;
+ end
+ end
+ return { status = "executing", actions = { "complete" }, form = { layout = mam_prefs_form, values = values } }, true;
+ else
+ local fields = mam_prefs_form:data(data.form);
+
+ local default, always, never = fields.default, fields.always, fields.never;
+ local prefs = {};
+ if default then
+ prefs[false] = default_attrs[default];
+ end
+ if always then
+ for i=1,#always do
+ prefs[always[i]] = true;
+ end
+ end
+ if never then
+ for i=1,#never do
+ prefs[never[i]] = false;
+ end
+ end
+
+ set_prefs(username, prefs);
+
+ return { status = "completed" }
+ end
+end
+
+module:provides("adhoc", module:require"adhoc".new("Archive settings", "urn:xmpp:mam#configure", mam_prefs_handler, "local_user"));
--- a/mod_mam_muc/mod_mam_muc.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_mam_muc/mod_mam_muc.lua Sun Jan 25 13:04:02 2015 +0100
@@ -50,7 +50,7 @@
module:log("error", "Could not open archive storage");
return
elseif not archive.find then
- module:log("error", "mod_%s does not support archiving, switch to mod_storage_sql2", archive._provided_by);
+ module:log("error", "mod_%s does not support archiving", archive._provided_by);
return
end
--- a/mod_manifesto/mod_manifesto.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_manifesto/mod_manifesto.lua Sun Jan 25 13:04:02 2015 +0100
@@ -57,14 +57,18 @@
module:hook("resource-bind", function (event)
local session = event.session;
+ module:log("debug", "mod_%s sees that %s logged in", module.name, session.username);
local now = time();
local last_notify = notified[session.username] or 0;
if last_notify > ( now - 86400 * 7 ) then
+ module:log("debug", "Already notified %s", session.username);
return
end
+ module:log("debug", "Waiting 15 seconds");
timer.add_task(15, function ()
+ module:log("debug", "15 seconds later... session.type is %q", session.type);
if session.type ~= "c2s" then return end -- user quit already
local bad_contacts, bad_hosts = {}, {};
for contact_jid, item in pairs(session.roster or {}) do
@@ -96,6 +100,7 @@
end
end
end
+ module:log("debug", "%s has %d bad contacts", session.username, #bad_contacts);
if #bad_contacts > 0 then
local vars = {
HOST = host;
@@ -103,6 +108,7 @@
SERVICES = " "..table.concat(bad_hosts, "\n ");
CONTACTVIA = contact_method, CONTACT = contact;
};
+ module:log("debug", "Sending notification to %s", session.username);
session.send(st.message({ type = "headline", from = host }):tag("body"):text(message:gsub("$(%w+)", vars)));
notified[session.username] = now;
end
@@ -159,7 +165,7 @@
config_set(host, "s2s_require_encryption", true);
for _, session in pairs(s2s_sessions) do
- if not session.secure then
+ if session.type == "s2sin" or session.type == "s2sout" and not session.secure then
(session.close or s2s_destroy_session)(session);
end
end
--- a/mod_message_logging/mod_message_logging.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_message_logging/mod_message_logging.lua Sun Jan 25 13:04:02 2015 +0100
@@ -52,7 +52,7 @@
local function write_to_log(log_jid, jid, prefix, body)
if not body then return; end
- local f = open_files[log_jid];
+ local f = open_files[jid_split(jid).."\0"..log_jid];
if not f then return; end
body = body:gsub("\n", "\n "); -- Indent newlines
f:write(os.date("%H:%M:%S "), prefix or "", prefix and ": " or "", jid, ": ", body, "\n");
--- a/mod_motd_sequential/mod_motd_sequential.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_motd_sequential/mod_motd_sequential.lua Sun Jan 25 13:04:02 2015 +0100
@@ -7,33 +7,30 @@
-- COPYING file in the source package for more information.
--
-local host = module:get_host();
-local motd_jid = module:get_option("motd_jid") or host;
-local datamanager = require "util.datamanager";
-local ipairs = ipairs;
-local motd_sequential_messages = module:get_option("motd_sequential_messages") or {};
-local motd_messagesets = {};
-local max = 1;
-for i, message in ipairs(motd_sequential_messages) do
- motd_messagesets[i] = message;
- max = i;
-end
+local st = require "util.stanza";
+local jid_prep = require"util.jid".prep;
-local st = require "util.stanza";
+local motd_messages = module:get_option_array("motd_sequential_messages", {});
+local motd_jid = jid_prep(module:get_option_string("motd_jid", module.host));
+
+if #motd_messages == 0 then return; end
+if not motd_jid then module:log("error", "Invalid jid: %s", module:get_option_string("motd_jid", module.host)); return; end
+
+local seen = module:open_store("motd_sequential_seen");
-module:hook("resource-bind",
- function (event)
- local session = event.session;
- local alreadyseen_list = datamanager.load(session.username, session.host, "motd_sequential_seen") or { max = 0 };
- local alreadyseen = alreadyseen_list["max"] + 1;
- local mod_stanza;
- for i = alreadyseen, max do
- motd_stanza =
- st.message({ to = session.username..'@'..session.host, from = motd_jid })
- :tag("body"):text(motd_messagesets[i]);
- core_route_stanza(hosts[host], motd_stanza);
- module:log("debug", "MOTD send to user %s@%s", session.username, session.host);
- end
- alreadyseen_list["max"] = max;
- datamanager.store(session.username, session.host, "motd_sequential_seen", alreadyseen_list);
+module:hook("presence/bare", function (event)
+ local session, stanza = event.origin, event.stanza;
+ if session.username and not session.presence
+ and not stanza.attr.type and not stanza.attr.to then
+ local alreadyseen_list = seen:get(session.username) or { max = 0 };
+ local alreadyseen = alreadyseen_list.max + 1;
+ for i = alreadyseen, #motd_messages do
+ if motd_messages[i] then
+ session.send(st.message({ to = session.full_jid, from = motd_jid }, motd_messages[i]));
+ module:log("debug", "MOTD send to user %s@%s", session.username, session.host);
+ end
+ end
+ alreadyseen_list.max = max;
+ seen:set(session.username, alreadyseen_list);
+ end
end);
--- a/mod_onions/mod_onions.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_onions/mod_onions.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,15 +1,17 @@
-local wrapclient = require "net.server".wrapclient;
+local addclient = require "net.server".addclient;
local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
local initialize_filters = require "util.filters".initialize;
local st = require "util.stanza";
+local log = module._log;
+local core_process_stanza = prosody.core_process_stanza;
+local hosts = prosody.hosts;
local portmanager = require "core.portmanager";
local softreq = require "util.dependencies".softreq;
-local bit;
-pcall(function() bit = require"bit"; end);
-bit = bit or softreq"bit32"
+local socket = require"socket";
+local bit = softreq"bit" or softreq"bit32"
if not bit then module:log("error", "No bit module found. Either LuaJIT 2, lua-bitop or Lua 5.2 is required"); end
local band = bit.band;
@@ -59,14 +61,11 @@
end
-- this means the server tells us to connect on an IPv4 address
- local ip1 = byte(data, 5);
- local ip2 = byte(data, 6);
- local ip3 = byte(data, 7);
- local ip4 = byte(data, 8);
+ local ip = ("%d.%d.%d.%d"):format(byte(data, 5, 8));
local port = band(byte(data, 9), lshift(byte(data, 10), 8));
- module:log("debug", "Should connect to: "..ip1.."."..ip2.."."..ip3.."."..ip4..":"..port);
+ module:log("debug", "Should connect to: %s:%d", ip, port);
- if not (ip1 == 0 and ip2 == 0 and ip3 == 0 and ip4 == 0 and port == 0) then
+ if not (ip == "0.0.0.0" and port == 0) then
module:log("debug", "The SOCKS5 proxy tells us to connect to a different IP, don't know how. :(");
session:close(false);
return;
@@ -94,7 +93,7 @@
if t then
t = filter("bytes/out", tostring(t));
if t then
- return conn:write(tostring(t));
+ return w(conn, t);
end
end
end
@@ -138,7 +137,7 @@
module:log("debug", "Sending connect message.");
-- version 5, connect, (reserved), type: domainname, (length, hostname), port
- conn:write(c(5) .. c(1) .. c(0) .. c(3) .. c(#session.socks5_to) .. session.socks5_to);
+ conn:write("\5\1\0\3" .. c(#session.socks5_to) .. session.socks5_to);
conn:write(c(rshift(session.socks5_port, 8)) .. c(band(session.socks5_port, 0xff)));
session.socks5_handler = socks5_connect_sent;
@@ -148,7 +147,7 @@
module:log("debug", "Connected to SOCKS5 proxy, sending SOCKS5 handshake.");
-- Socks version 5, 1 method, no auth
- conn:write(c(5) .. c(1) .. c(0));
+ conn:write("\5\1\0");
sessions[conn].socks5_handler = socks5_handshake_sent;
end
@@ -176,19 +175,13 @@
local function connect_socks5(host_session, connect_host, connect_port)
- local conn, handler = socket.tcp();
-
module:log("debug", "Connecting to " .. connect_host .. ":" .. connect_port);
-- this is not necessarily the same as .to_host (it can be that this is a SRV record)
host_session.socks5_to = connect_host;
host_session.socks5_port = connect_port;
- conn:settimeout(0);
-
- local success, err = conn:connect(proxy_ip, proxy_port);
-
- conn = wrapclient(conn, connect_host, connect_port, socks5listener, "*a");
+ local conn, err = addclient( proxy_ip, proxy_port, socks5listener, '*a', nil );
socks5listener.register_outgoing(conn, host_session);
@@ -229,7 +222,7 @@
if not event.to_host:find(".onion(.?)$") then
if forbid_else then
- module:log("debug", event.to_host .. " is not an onion. Blocking it.");
+ module:log("debug", event.to_host .. " is not an onion. Blocking it.");
return false;
elseif not torify_all then
return;
@@ -256,4 +249,4 @@
module:log("debug", "Onions ready and loaded");
-hosts[module.host].events.add_handler("route/remote", route_to_onion, 200);
+module:hook("route/remote", route_to_onion, 200);
--- a/mod_profile/mod_profile.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_profile/mod_profile.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,7 +1,8 @@
-- mod_profile
local st = require"util.stanza";
-local jid_split, jid_bare = import("util.jid", "split", "bare");
+local jid_split = require"util.jid".split;
+local jid_bare = require"util.jid".bare;
local is_admin = require"core.usermanager".is_admin;
local vcard = require"util.vcard";
local base64 = require"util.encodings".base64;
@@ -86,7 +87,7 @@
local username = origin.username;
local to = stanza.attr.to;
if to then username = jid_split(to); end
- local data = storage:get(username);
+ local data, err = storage:get(username);
if not data then
data = legacy_storage:get(username);
data = data and st.deserialize(data);
--- a/mod_rawdebug/mod_rawdebug.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_rawdebug/mod_rawdebug.lua Sun Jan 25 13:04:02 2015 +0100
@@ -19,14 +19,15 @@
end
end
-function rawdebug:enable(sessionid)
+function rawdebug:enable(sessionid, typ)
local session = full_sessions[sessionid];
+ typ = typ or "stanzas";
if not session then
return nil, "No such session";
end
local f = {
- ["stanzas/in"] = new_logger(session.log or log, "RECV");
- ["stanzas/out"] = new_logger(session.log or log, "SEND");
+ [typ .. "/in"] = new_logger(session.log or log, "RECV");
+ [typ .. "/out"] = new_logger(session.log or log, "SEND");
};
for type, callback in pairs(f) do
filters.add_filter(session, type, callback)
--- a/mod_register_redirect/mod_register_redirect.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_register_redirect/mod_register_redirect.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,88 +1,13 @@
--- (C) 2010-2011 Marco Cirillo (LW.Org)
--- (C) 2011 Kim Alvefur
---
--- Registration Redirect module for Prosody
---
--- Redirects IP addresses not in the whitelist to a web page or another method to complete the registration.
-
-local st = require "util.stanza"
-local cman = configmanager
+local st = require"util.stanza";
-local ip_wl = module:get_option_set("registration_whitelist", { "127.0.0.1" })
-local url = module:get_option_string("registration_url", nil)
-local inst_text = module:get_option_string("registration_text", nil)
-local oob = module:get_option_boolean("registration_oob", true)
-local admins_g = cman.get("*", "core", "admins")
-local admins_l = cman.get(module:get_host(), "core", "admins")
-local no_wl = module:get_option_boolean("no_registration_whitelist", false)
-
-if type(admins_g) ~= "table" then admins_g = nil end
-if type(admins_l) ~= "table" then admins_l = nil end
-
-function reg_redirect(event)
- local stanza, origin = event.stanza, event.origin
-
- if not no_wl and ip_wl:contains(origin.ip) then return; end
+local oob_url = module:get_option_string(module.name .. "_url", "http://www.example.com");
+local instructions = module:get_option_string(module.name .. "_instructions", "To register, please visit "..oob_url);
- -- perform checks to set default responses and sanity checks.
- if not inst_text then
- if url and oob then
- if url:match("^%w+[:].*$") then
- if url:match("^(%w+)[:].*$") == "http" or url:match("^(%w+)[:].*$") == "https" then
- inst_text = "Please visit "..url.." to register an account on this server."
- elseif url:match("^(%w+)[:].*$") == "mailto" then
- inst_text = "Please send an e-mail at "..url:match("^%w+[:](.*)$").." to register an account on this server."
- elseif url:match("^(%w+)[:].*$") == "xmpp" then
- inst_text = "Please contact "..module:get_host().."'s server administrator via xmpp to register an account on this server at: "..url:match("^%w+[:](.*)$")
- else
- module:log("error", "This module supports only http/https, mailto or xmpp as URL formats.")
- module:log("error", "If you want to use personalized instructions without an Out-Of-Band method,")
- module:log("error", "specify: register_oob = false; -- in your configuration along your banner string (register_text).")
- return origin.send(st.error_reply(stanza, "wait", "internal-server-error")) -- bouncing request.
- end
- else
- module:log("error", "Please check your configuration, the URL you specified is invalid")
- return origin.send(st.error_reply(stanza, "wait", "internal-server-error")) -- bouncing request.
- end
- else
- if admins_l then
- local ajid; for _,v in ipairs(admins_l) do ajid = v ; break end
- inst_text = "Please contact "..module:get_host().."'s server administrator via xmpp to register an account on this server at: "..ajid
- else
- if admins_g then
- local ajid; for _,v in ipairs(admins_g) do ajid = v ; break end
- inst_text = "Please contact "..module:get_host().."'s server administrator via xmpp to register an account on this server at: "..ajid
- else
- module:log("error", "Please be sure to, _at the very least_, configure one server administrator either global or hostwise...")
- module:log("error", "if you want to use this module, or read it's configuration wiki at: http://code.google.com/p/prosody-modules/wiki/mod_register_redirect")
- return origin.send(st.error_reply(stanza, "wait", "internal-server-error")) -- bouncing request.
- end
- end
- end
- elseif inst_text and url and oob then
- if not url:match("^%w+[:].*$") then
- module:log("error", "Please check your configuration, the URL specified is not valid.")
- return origin.send(st.error_reply(stanza, "wait", "internal-server-error")) -- bouncing request.
- end
- end
+module:hook("stanza/iq/jabber:iq:register:query", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ origin.send(st.reply(stanza):query("jabber:iq:register")
+ :tag("instructions"):text(instructions):up()
+ :tag("x", { xmlns="jabber:x:oob" }):tag("url"):text(oob_url));
+ return true;
+end, 10);
- -- Prepare replies.
- local reply = st.reply(event.stanza)
- if oob then
- reply:query("jabber:iq:register")
- :tag("instructions"):text(inst_text):up()
- :tag("x", {xmlns = "jabber:x:oob"})
- :tag("url"):text(url);
- else
- reply:query("jabber:iq:register")
- :tag("instructions"):text(inst_text):up()
- end
-
- if stanza.attr.type == "get" then
- return origin.send(reply)
- else
- return origin.send(st.error_reply(stanza, "cancel", "not-authorized"))
- end
-end
-
-module:hook("stanza/iq/jabber:iq:register:query", reg_redirect, 10)
--- a/mod_register_web/mod_register_web.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_register_web/mod_register_web.lua Sun Jan 25 13:04:02 2015 +0100
@@ -101,8 +101,9 @@
end
function generate_page(event, display_options)
- local request = event.request;
+ local request, response = event.request, event.response;
+ response.headers.content_type = "text/html; charset=utf-8";
return render(register_tpl, {
path = request.path; hostname = module.host;
notice = display_options and display_options.register_error or "";
@@ -154,6 +155,7 @@
function generate_register_response(event, form, ok, err)
local message;
+ event.response.headers.content_type = "text/html; charset=utf-8";
if ok then
return generate_success(event, form);
else
--- a/mod_s2s_auth_dane/mod_s2s_auth_dane.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_s2s_auth_dane/mod_s2s_auth_dane.lua Sun Jan 25 13:04:02 2015 +0100
@@ -89,7 +89,12 @@
t_insert(dane, record);
end
end
- if n == 0 and cb then return cb(a,b,c,e); end
+ if n == 0 and cb then
+ if #dane == 0 then
+ host_session.dane = false;
+ end
+ return cb(a,b,c,e);
+ end
end, ("_%d._tcp.%s."):format(record.srv.port, record.srv.target), "TLSA");
end
end, "_xmpp-server._tcp."..name..".", "SRV");
@@ -116,12 +121,12 @@
local host_session = event.origin;
if host_session.type == "s2sout" or host_session.type == "s2sin" or host_session.dane ~= nil then return end -- Already authenticated
local function resume()
- host_session.log("debug", "DANE lookup completed, resuming connection");
- host_session.conn:resume()
+ host_session.log("debug", "DANE lookup completed");
+ host_session.unlock("dane");
end
if dane_lookup(host_session, resume) then
- host_session.log("debug", "Pausing connection until DANE lookup is completed");
- host_session.conn:pause()
+ host_session.log("debug", "Locking session until DANE lookup is completed");
+ host_session.lock("dane");
end
end
@@ -134,7 +139,7 @@
module:hook("s2s-authenticated", function(event)
local session = event.session;
- if session.dane and not session.secure then
+ if session.dane and next(session.dane) ~= nil and not session.secure then
-- TLSA record but no TLS, not ok.
-- TODO Optional?
-- Bogus replies should trigger this path
--- a/mod_s2s_auth_fingerprint/mod_s2s_auth_fingerprint.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_s2s_auth_fingerprint/mod_s2s_auth_fingerprint.lua Sun Jan 25 13:04:02 2015 +0100
@@ -8,11 +8,11 @@
local fingerprints = {};
local function hashprep(h)
- return tostring(h):gsub(":",""):lower();
+ return tostring(h):gsub("%X+",""):lower();
end
local function hashfmt(h)
- return h:gsub("..",":%0"):sub(2):upper();
+ return h:gsub("..","%0:", #h/2-1):upper();
end
for host, set in pairs(module:get_option("s2s_trusted_fingerprints", {})) do
@@ -29,19 +29,21 @@
module:hook("s2s-check-certificate", function(event)
local session, host, cert = event.session, event.host, event.cert;
+ local log = session.log or module._log;
local host_fingerprints = fingerprints[host];
if host_fingerprints then
local digest = cert and cert:digest(digest_algo);
if host_fingerprints[digest] then
- module:log("info", "'%s' matched %s fingerprint %s", host, digest_algo:upper(), hashfmt(digest));
+ log("info", "'%s' matched %s fingerprint %s", host, digest_algo:upper(), hashfmt(digest));
session.cert_chain_status = "valid";
session.cert_identity_status = "valid";
return true;
else
- module:log("warn", "'%s' has unknown %s fingerprint %s", host, digest_algo:upper(), hashfmt(digest));
+ log("warn", "'%s' has unknown %s fingerprint %s", host, digest_algo:upper(), hashfmt(digest));
session.cert_chain_status = "invalid";
session.cert_identity_status = "invalid";
+ -- return false;
end
end
-end);
+end, 0);
--- a/mod_s2s_auth_monkeysphere/mod_s2s_auth_monkeysphere.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_s2s_auth_monkeysphere/mod_s2s_auth_monkeysphere.lua Sun Jan 25 13:04:02 2015 +0100
@@ -19,7 +19,7 @@
type = "peer";
};
context = "https";
- -- context = "xmpp"; -- Monkeysphere needs to be extended to understand this
+ -- context = "xmpp-server"; -- Monkeysphere needs to be extended to understand this
pkc = {
type = "x509pem";
data = cert:pem();
--- a/mod_s2s_keysize_policy/mod_s2s_keysize_policy.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_s2s_keysize_policy/mod_s2s_keysize_policy.lua Sun Jan 25 13:04:02 2015 +0100
@@ -26,9 +26,9 @@
if cert and cert.pubkey then
local _, key_type, key_size = cert:pubkey();
if key_size < ( weak_key_size[key_type] or 0 ) then
- local issued = parse_x509_datetime(cert:notbefore());
- if issued > weak_key_cutoff then
- session.log("error", "%s has a %s-bit %s key issued after 31 December 2013, invalidating trust!", host, key_size, key_type);
+ local expires = parse_x509_datetime(cert:notafter());
+ if expires > weak_key_cutoff then
+ session.log("error", "%s has a %s-bit %s key valid after 31 December 2013, invalidating trust!", host, key_size, key_type);
session.cert_chain_status = "invalid";
session.cert_identity_status = "invalid";
else
--- a/mod_s2s_log_certs/mod_s2s_log_certs.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_s2s_log_certs/mod_s2s_log_certs.lua Sun Jan 25 13:04:02 2015 +0100
@@ -24,6 +24,7 @@
digest_algo:upper(),
digest:upper():gsub("..",":%0"):sub(2));
+ -- TODO Include CA info
if do_store then
local seen_certs = dm_load(remote_host, local_host, "s2s_certs") or {};
--- a/mod_smacks/mod_smacks.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_smacks/mod_smacks.lua Sun Jan 25 13:04:02 2015 +0100
@@ -24,7 +24,7 @@
local sessionmanager = require"core.sessionmanager";
local c2s_sessions = module:shared("/*/c2s/sessions");
-local session_registry = {};
+local session_registry = module:shared("sessions");
local function can_do_smacks(session, advertise_only)
if session.smacks then return false, "unexpected-request", "Stream management is already enabled"; end
@@ -58,7 +58,7 @@
end);
local function outgoing_stanza_filter(stanza, session)
- local is_stanza = stanza.attr and not stanza.attr.xmlns;
+ local is_stanza = stanza.attr and not stanza.attr.xmlns and not stanza.name:find":";
if is_stanza and not stanza._cached then -- Stanza in default stream namespace
local queue = session.outgoing_stanza_queue;
local cached_stanza = st.clone(stanza);
@@ -151,22 +151,20 @@
module:hook_stanza(xmlns_sm3, "enable", function (session, stanza) return handle_enable(session, stanza, xmlns_sm3); end, 100);
module:hook_stanza("http://etherx.jabber.org/streams", "features",
- function (session, stanza)
- module:add_timer(0, function ()
- if can_do_smacks(session) then
- if stanza:get_child("sm", xmlns_sm3) then
- session.sends2s(st.stanza("enable", sm3_attr));
- session.smacks = xmlns_sm3;
- elseif stanza:get_child("sm", xmlns_sm2) then
- session.sends2s(st.stanza("enable", sm2_attr));
- session.smacks = xmlns_sm2;
- else
- return;
- end
- wrap_session_out(session, false);
- end
- end);
- end);
+function (session, stanza)
+ if can_do_smacks(session) then
+ if stanza:get_child("sm", xmlns_sm3) then
+ session.sends2s(st.stanza("enable", sm3_attr));
+ session.smacks = xmlns_sm3;
+ elseif stanza:get_child("sm", xmlns_sm2) then
+ session.sends2s(st.stanza("enable", sm2_attr));
+ session.smacks = xmlns_sm2;
+ else
+ return;
+ end
+ wrap_session_out(session, false);
+ end
+end);
function handle_enabled(session, stanza, xmlns_sm)
module:log("debug", "Enabling stream management");
@@ -233,12 +231,10 @@
local error_attr = { type = "cancel" };
if #queue > 0 then
session.outgoing_stanza_queue = {};
+ local reply;
for i=1,#queue do
- local reply = st.reply(queue[i]);
- if reply.attr.to ~= session.full_jid then
- reply.attr.type = "error";
- reply:tag("error", error_attr)
- :tag("recipient-unavailable", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"});
+ if queue[i].attr.from ~= session.full_jid then
+ queue[i], reply = nil, st.error_reply(queue[i], "recipient-unavailable");
core_process_stanza(session, reply);
end
end
@@ -266,7 +262,7 @@
-- (for example, the client may have bound a new resource and
-- started a new smacks session, or not be using smacks)
local curr_session = full_sessions[session.full_jid];
- if false and session.destroyed then
+ if session.destroyed then
session.log("debug", "The session has already been destroyed");
elseif curr_session and curr_session.resumption_token == resumption_token
-- Check the hibernate time still matches what we think it is,
@@ -351,3 +347,26 @@
end
module:hook_stanza(xmlns_sm2, "resume", function (session, stanza) return handle_resume(session, stanza, xmlns_sm2); end);
module:hook_stanza(xmlns_sm3, "resume", function (session, stanza) return handle_resume(session, stanza, xmlns_sm3); end);
+
+local function handle_read_timeout(event)
+ local session = event.session;
+ local xmlns_sm = session.smacks;
+ if xmlns_sm then
+ session.awaiting_ack = true;
+ return (session.sends2s or session.send)(st.stanza("r", { xmlns = xmlns_sm }));
+ end
+end
+
+module:hook("s2s-read-timeout", handle_read_timeout, 10);
+module:hook("c2s-read-timeout", handle_read_timeout, 10);
+
+local function handle_s2s_destroyed(event)
+ local session = event.session;
+ local queue = session.outgoing_stanza_queue;
+ if queue and #queue > 0 then
+ session.log("warn", "Destroying session with %d unacked stanzas", #queue);
+ handle_unacked_stanzas(session);
+ end
+end;
+module:hook("s2sout-destroyed", handle_s2s_destroyed);
+module:hook("s2sin-destroyed", handle_s2s_destroyed);
--- a/mod_srvinjection/mod_srvinjection.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_srvinjection/mod_srvinjection.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,3 +1,4 @@
+local s = require"util.serialization".new"oneline".serialize;
module:set_global();
@@ -25,8 +26,12 @@
local original_lookup = adns.lookup;
function adns.lookup(handler, qname, qtype, qclass)
+ module:log("debug", "adns.lookup(%s, %s, %s)", s(qname), s(qtype), s(qclass));
if qtype == "SRV" then
local host = qname:match("^_xmpp%-server%._tcp%.(.*)%.$");
+ module:log("debug", "qname:match(...) → %s", s(host));
+ local mapping = map[host] or map["*"];
+ module:log("debug", "map[%s] → %s", s(host), s(mapping));
local mapping = map[host] or map["*"];
if mapping then
handler(mapping);
--- a/mod_storage_gdbm/mod_storage_gdbm.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_storage_gdbm/mod_storage_gdbm.lua Sun Jan 25 13:04:02 2015 +0100
@@ -9,7 +9,9 @@
local gdbm = require"gdbm";
local path = require"util.paths";
local lfs = require"lfs";
-local serialize, deserialize = import("util.serialization", "serialize", "deserialize");
+local serialization = require"util.serialization";
+local serialize = serialization.serialize;
+local deserialize = serialization.deserialize;
local base_path = path.resolve_relative_path(prosody.paths.data, module.host);
lfs.mkdir(base_path);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_internal_ng/json.lib.lua Sun Jan 25 13:04:02 2015 +0100
@@ -0,0 +1,2 @@
+local json = require"util.json";
+return json;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_internal_ng/lua.lib.lua Sun Jan 25 13:04:02 2015 +0100
@@ -0,0 +1,13 @@
+
+
+if serialization.new then
+ local settings = module:get_option(module.name .. "_lua", "compact");
+ serialization = serialization.new(settings);
+ serialize = serialization.serialize;
+end
+
+return {
+ encode = serialize;
+ decode = deserialize;
+};
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_internal_ng/mod_storage_internal_ng.lua Sun Jan 25 13:04:02 2015 +0100
@@ -0,0 +1,713 @@
+-- Copyright (C) 2014-2015 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+
+local tonumber = tonumber;
+local tostring = tostring;
+local type = type;
+local io_open = io.open;
+local os_remove = os.remove;
+local os_rename = os.rename;
+
+local noop = function () end
+local id = function (x) return x end
+
+local lfs = require"lfs";
+local st = require"util.stanza";
+local xml = require"util.xml";
+local sha256 = require"util.hashes".sha256;
+local datetime = require"util.datetime";
+local path_join = require"util.paths".join;
+local http = require"util.http";
+
+local encode, decode = import("util.serialization", "serialize", "deserialize");
+local decode_file = encoder.decode_file;
+
+local log = module._log;
+local base_path = prosody.paths.data;
+
+local suffix = ".dat";
+
+-- No path escaping, assume no forbidden chars in names
+local function getpath(...)
+ return path_join(base_path, module.host, ...);
+end
+
+local did_mkdir_already = {};
+local function mkdir(path)
+ if not did_mkdir_already[path] then
+ did_mkdir_already[path] = true;
+ return lfs.mkdir(path);
+ end
+end
+
+function module.load()
+ mkdir(getpath()); -- Create base dir for host
+end
+
+-- Cleanup
+local function cleanup(scratch, f, msg)
+ if f then f:close(); end
+ os_remove(scratch);
+ return nil, msg;
+end
+
+local function atomic_store(filename, data)
+ local scratch = filename.."~";
+ local f, ok, msg;
+ f, msg = io_open(scratch, "wb");
+ if not f then return cleanup(scratch, f, msg) end
+
+ ok, msg = f:write(data);
+ if not ok then return cleanup(scratch, f, msg) end
+
+ ok, msg = f:close();
+ if not ok then return cleanup(scratch, f, msg) end
+
+ return os_rename(scratch, filename);
+end
+
+local function read_fh(fh, max_length)
+ return decode(fh:read(max_length or "*a"));
+end
+
+local function read_file(path, max_length)
+ local f, err = io_open(path, "rb");
+ if not f then
+ return f, err;
+ end
+
+ local data = read_fh(fh, max_length);
+ f:close();
+ return data;
+end
+
+local function log_error(path, store, err, user)
+ local mode = lfs.attributes(path, "mode");
+ if not mode then
+ log("debug", "Assuming empty %s storage ('%s') for user: %s", store, err, user or "nil");
+ return nil;
+ else -- file exists, but can't be read
+ -- TODO more detailed error checking and logging?
+ log("error", "Failed to load %s storage ('%s') for user: %s", store, err, user or "nil");
+ return nil, "Error reading storage";
+ end
+end
+
+local function h(s)
+ return sha256(s, true):sub(1, 2);
+end
+local function new_keyval_store(store)
+ local driver = { store = store, typ = "keyval" };
+
+ function driver:get(user)
+ user = user or "@";
+ local path = getpath(user, store) .. suffix;
+ local data, err = read_file(path);
+ if not data then return log_error(path, store, err, user or "<nil>") end
+ return data;
+ end
+ function driver:set(user, data)
+ user = user or "@";
+ mkdir(getpath(user));
+ local path = getpath(user, store) .. suffix;
+ if data == nil then
+ if lfs.attributes(path, "mode") == "file" then
+ return os_remove(path);
+ end
+ return true;
+ end
+ data = encode(data);
+ return atomic_store(path, data);
+ end
+ function driver:users()
+ local next, state = lfs.dir(getpath());
+ return function (state)
+ for node in next, state do
+ if lfs.attributes(getpath(node, store) .. suffix, "mode") == "file" then
+ return node;
+ end
+ end
+ end, state;
+ end
+ return driver;
+end
+
+local function new_map_store(store, as_keyval)
+ local driver = { store = store, typ = "map" };
+
+ -- FIXME Keys may be encoded to values longer than the file system supports
+
+ function driver:get(user, key)
+ assert(key ~= nil, "map key can't be nil");
+ user = user or "@";
+ key = http.urlencode(encode(key));
+ local path = getpath(user, store, key) .. suffix;
+ local data, err = read_file(path);
+ if not data then return log_error(path, store, err, user or "<nil>") end
+ return data;
+ end
+
+ function driver:set(user, key, data)
+ assert(key ~= nil, "map key can't be nil");
+ user = user or "@";
+ key = http.urlencode(encode(key));
+ mkdir(getpath(user));
+ mkdir(getpath(user, store));
+ local path = getpath(user, store, key) .. suffix;
+ if data == nil then
+ return os_remove(path);
+ end
+ data = encode(data);
+ return atomic_store(path, data);
+ end
+
+ function driver:keys(user)
+ user = user or "@";
+ local ok, iter, state, var = pcall(lfs.dir, getpath(user, store));
+ if not ok then
+ log("warn", iter);
+ return noop;
+ end
+ return function (state, var)
+ local item;
+ repeat
+ item = iter(state, var);
+ if item == nil then return end
+ until lfs.attributes(getpath(user,store, item), "mode") == "file" and item:sub(-#suffix) == suffix;
+ return decode(http.urldecode(item:sub(1, -#suffix-1)));
+ end, state, var;
+ end
+
+ function driver:pairs(user)
+ local iter, state, var = self:keys(user);
+ return function (state, var)
+ local key = iter(state, var);
+ if key == nil then return end
+ return key, self:get(user, key);
+ end, state, var;
+ end
+
+ function driver:empty(user)
+ for key in self:keys(user) do
+ self:set(user, key, nil);
+ end
+ return true;
+ end
+
+ function driver:collect(user)
+ local data = {};
+ for k,v in self:pairs(user) do
+ data[k] = v;
+ end
+ return data;
+ end
+
+ if as_keyval then
+ local keyval_driver = new_keyval_store(store);
+
+ function keyval_driver:get(user)
+ return driver:collect(user);
+ end
+
+ function keyval_driver:set(user, data)
+ driver:empty(user);
+ if data ~= nil then
+ for k, v in pairs(data) do
+ driver:set(user, k, v);
+ end
+ end
+ end
+
+ return keyval_driver;
+ end
+
+ return driver;
+end
+
+-- date utilities
+local function tomorrow(date)
+ return datetime.date(datetime.parse(date .. "T12:00:00Z") + 86400);
+end
+
+local function yesterday(date)
+ return datetime.date(datetime.parse(date .. "T12:00:00Z") - 86400);
+end
+
+local function new_archive_store(store)
+ local driver = { store = store, typ = "archive" };
+ local fallocate = require"util.pposix".fallocate or function (f, o, l)
+ local ok, err = f:write(string.rep(" ", l));
+ if not ok then return ok, err end
+ return f:seek("set", o);
+ end
+
+ local index_storage = new_map_store(store);
+ local _get = index_storage.get;
+ function index_storage:get(user, key)
+ local ok, err = _get(self, user, key);
+ if not ok then
+ log("debug", tostring(err));
+ end
+ return ok, err;
+ end
+
+ local datelists = setmetatable({}, { __mode = 'v'; });
+
+ local function get_datelist(user, with)
+ local key = with and (user..'\0'..with) or user;
+
+ local list = datelists[key];
+ if list then return list end
+
+ if with then
+ local datemap = index_storage:get(user, with);
+ if datemap then
+ list = {};
+ local i = 1;
+ for date in pairs(datemap) do
+ list[i], i = date, i+1;
+ end
+ end
+ end
+
+ if not list then
+ list = {};
+ local ok, iter, state, var = pcall(lfs.dir, getpath(user, store));
+ if not ok then return list; end
+
+ local i = 1;
+ local filename, mode, date;
+ for item in iter, state, var do
+ filename = getpath(user, store, item);
+ mode = lfs.attributes(filename, "mode");
+ if mode == "file" and item:match("^%d%d%d%d%-%d%d%-%d%d") and item:sub(-#suffix) == suffix then
+ list[i], i = item:sub(1,10), i+1;
+ end
+ end
+ end
+
+ table.sort(list);
+ for i = 1, #list do
+ list[ list[i] ] = i;
+ end
+ datelists[key] = list;
+ return list;
+ end
+
+ function driver:append(user, key, when, with, value)
+ user = user or "@";
+ local ok;
+ local date = datetime.date(when);
+ mkdir(getpath(user));
+ mkdir(getpath(user, store));
+ local filename = getpath(user, store, date) .. suffix;
+ local f, msg = io_open(filename, "r+b");
+ if not f then
+ f, msg = io_open(filename, "wb");
+ end
+ if not f then
+ return f, msg;
+ end
+ local mt = getmetatable(value);
+ local typ = type(value);
+ --[[
+ if mt.__freeze then
+ value, typ = mt.__freeze(value);
+ else--]]
+ if mt == st.stanza_mt then
+ value, typ = st.preserialize(value), "stanza";
+ -- if false then
+ -- value, typ = tostring(value), "xml";
+ -- else
+ -- end
+ end
+ local pos = f:seek("end");
+ key = ("%sp%x"):format(date, pos);
+ key = key .. "r".. sha256(user..key, true):sub(1, 8);
+ local metadata = encode({ key, datetime.datetime(when), with, typ });
+ local data = encode(value);
+ local meta_len, data_len = #metadata, #data;
+ local meta_ls, data_ls = ("%d"):format(meta_len), ("%d"):format(data_len);
+ local total_ls = ("%d"):format(meta_len + #meta_ls + data_len + #data_ls + 4); -- two newlines?
+ -- ok, msg = fallocate(f, pos, data_len + #len_str * 2 + 3);
+ -- if not ok then return ok, msg; end
+ ok, msg = f:write(meta_ls, "\n", metadata, "\n", data_ls, "\n", data, "\n", total_ls, "\n");
+ if not ok then return ok, msg; end
+ ok, msg = f:close();
+ if not ok then return ok, msg; end
+ datelists[user] = nil;
+ if with then datelists[user .. "\0" .. with] = nil end
+ local index = index_storage:get(user, with) or {};
+ if not index[date] then
+ index[date] = true;
+ index_storage:set(user, with, index);
+ end
+ return key;
+ end
+
+ local unfreeze = {
+ xml = require"util.xml".parse;
+ stanza = st.deserialize;
+ }
+
+ local function valid_key(user, key)
+ if not key then return nil, "no-key"; end
+ if sha256(user..key:match("^(.-)r"), 1):sub(1, 8) ~= key:match("r(.*)$") then
+ log("warn", "invalid-key-signature");
+ -- return nil, "invalid-key-signature";
+ end
+ local when = datetime.parse(key:sub(1, 10).."T00:00:00Z");
+ if not when then return nil, "invalid-key-date"; end
+ local pos = key:match("^p(%x+)r", 11);
+ pos = tonumber(pos, 16);
+ if not pos then return nil, "invalid-key-offset"; end
+ return when, pos;
+ end
+
+ function driver:get(user, key)
+ local date, seek = valid_key(user, key);
+ if not date then return nil, seek; end
+ date = datetime.date(date);
+ local filename = getpath(user, store, date) .. suffix;
+ local fh, err = io_open(filename, "rb");
+ if not fh then
+ return nil, err;
+ end
+ fh:seek("set", seek);
+ local len = fh:read("*l");
+ local meta = fh:read(tonumber(len));
+ meta = decode(meta);
+ if not meta or meta[1] ~= key then
+ fh:close();
+ return nil, "not found";
+ end
+ len = fh:read("*l");
+ local data = fh:read(tonumber(len));
+ fh:close();
+ data = decode(data);
+ local when = meta[2];
+ if type(when) == "string" then
+ when = datetime.parse(when);
+ end
+ return (unfreeze[ meta[4] ] or id)(data), when, meta[3];
+ end
+
+ local NULL = {};
+
+ -- The ones in math doesn't support strings
+ local function max(a, b) return a > b and a or b; end
+ local function min(a, b) return a < b and a or b; end
+
+ function driver:_update_index(user, query)
+ index_storage:empty();
+ local with_index = {};
+ local iter, err = self:find(user, query);
+ if not iter then
+ log("error", tostring(err));
+ return nil, err;
+ end
+ local date = datetime.date;
+ for key, message, when, with in iter do
+ log("debug", "Item %s with %s", key, with);
+ if with then
+ local with_dates = with_index[with];
+ if not with_dates then
+ with_dates = {};
+ with_index[with] = with_dates;
+ end
+ when = date(when);
+ with_dates[when] = true;
+ end
+ end
+ for key, value in pairs(with_index) do
+ log("debug", "Write index %s", key);
+ index_storage:set(user, key, value);
+ end
+ end
+
+ local function find_sol(fh) -- Find Start of Line
+ local pos;
+ repeat
+ pos = fh:seek("cur", -2);
+ until not pos or pos == 0 or fh:read(1) == "\n";
+ return pos;
+ end
+
+ function driver:find(user, query)
+ query = query or NULL;
+ user = user or "@";
+ local datelist = get_datelist(user, query.with);
+ if not datelist[1] then return noop, 0 end
+
+ local start = query.start or 0;
+ local ending = query["end"] or 0x7fffffff;
+ local start_date = max(datelist[1], datetime.date(start));
+ local end_date = min(datelist[#datelist], datetime.date(ending));
+
+ local seek_once = query.reverse and query.before or query.after;
+ local date = query.reverse and end_date or start_date;
+ if seek_once then
+ local t;
+ t, seek_once = valid_key(user, seek_once);
+ if not t then return nil, seek_once; end
+ date = datetime.date(t);
+ end
+ local limit, results = query.limit, 0;
+ local log = function () end
+
+ if query.reverse then
+ -- log("debug", "find first date");
+ if seek_once == 0 then
+ date = yesterday(date);
+ seek_once = nil;
+ end
+ while not datelist[date] and date >= start_date do
+ date = yesterday(date);
+ end
+ -- log("debug", "datelist[%q] = %s", date, tostring(datelist[date]));
+ -- log("debug", "%s <= %s → %s", tostring(date), tostring(start_date), tostring(date <= start_date));
+ -- log("warn", "reverse");
+ return coroutine.wrap(function ()
+ local filename, fh, err, len, pos, meta, data, when;
+ -- log("debug", "enter outer loop");
+ -- log("debug", "date = %s", tostring(date));
+ while date and datelist[date] and date >= start_date do
+ -- log("debug", "iterate outer loop");
+
+ filename = getpath(user, store, date) .. suffix;
+ fh, err = io_open(filename, "rb");
+ if not fh then
+ log("warn", err);
+ else
+ if seek_once then
+ pos = fh:seek("set", seek_once);
+ seek_once = nil;
+ else
+ fh:seek("end");
+ end
+ pos = find_sol(fh);
+ if pos and pos > 1 then
+ len = fh:read("*l");
+ pos = fh:seek("cur", -tonumber(len)-1);
+ pos = find_sol(fh);
+ end
+ if pos < 2 then pos = fh:seek("set", 0); end
+ len = fh:read("*l");
+ log("debug", "enter inner loop");
+ while len do
+ meta = fh:read(tonumber(len));
+ fh:seek("cur", 1);
+ meta = meta and decode(meta);
+ if not meta then break; end
+ len = fh:read("*l");
+ -- log("debug", "{ %q, %d, %q, %q, <%s> }", data[1], data[2], data[3], data[4], type(data[5]));
+ when = meta[2];
+ if type(when) == "string" then
+ when = datetime.parse(when);
+ end
+ if when >= start and when <= ending and (not query.with or meta[3] == query.with) then
+ data = fh:read(tonumber(len));
+ -- log("debug", "data is %s:%s", type(data), tostring(data));
+ data = decode(data);
+ coroutine.yield(meta[1], (unfreeze[ meta[4] ] or id)(data), when, meta[3]);
+ results = results + 1;
+ if limit and results >= limit then
+ -- log("debug", "results => limit");
+ return;
+ end
+ end
+ if not pos or pos == 0 then
+ -- We just read the first entry, proceed to the last in the previous file
+ break
+ end
+ pos = fh:seek("set", pos); -- Skip to top of the entry we just parsed
+ pos = find_sol(fh); -- Find previous newline
+ len = fh:read("*l"); -- Read the trailing length (meta + data)
+ pos = len and fh:seek("cur", -tonumber(len) - #len);
+ pos = find_sol(fh);
+ if not pos or pos == 1 then fh:seek("set", 0); pos = nil; end
+ len = fh:read("*l");
+ end
+ end
+ fh = fh:close();
+ -- log("debug", "out of inner loop");
+ date = datelist[ ( datelist[date] or 0 ) - 1];
+ -- log("debug", "next date is %s", date or "EOF");
+ end
+ -- log("debug", "out of outer loop");
+ end);
+
+ else
+
+ -- log("debug", "find start date");
+ while not datelist[date] and date <= end_date do
+ date = tomorrow(date);
+ -- log("debug", "datelist[%q]", date);
+ end
+
+ return coroutine.wrap(function ()
+ local filename, fh, err, len, pos, meta, data, when;
+ -- log("debug", "enter outer loop");
+ -- log("debug", "date = %s", tostring(date));
+ while date and datelist[date] and date <= end_date do
+
+ filename = getpath(user, store, date) .. suffix;
+ -- log("debug", "filename = %q", filename);
+ fh, err = io_open(filename, "rb");
+ if not fh then
+ log("warn", err);
+ else
+ if seek_once then
+ fh:seek("set", seek_once);
+ len = fh:read("*l");
+ fh:seek("cur", tonumber(len) + 1);
+ len = fh:read("*l");
+ fh:seek("cur", tonumber(len) + 1);
+ fh:read("*l");
+ seek_once = nil;
+ end
+ -- pos = fh:seek();
+ len = fh:read("*l");
+ -- log("debug", "enter inner loop");
+ while tonumber(len) do
+ -- log("debug", "len is %s:%s", type(len), tostring(len));
+ meta = fh:read(tonumber(len));
+ fh:seek("cur", 1);
+ -- log("debug", "meta is %s:%s", type(meta), tostring(meta));
+ meta = meta and decode(meta);
+ if not meta then
+ -- log("debug", "no metadata?");
+ break;
+ end
+ len = fh:read("*l");
+ -- log("debug", "{ %q, %s, %q, %q } + #%d", meta[1], meta[2], meta[3], meta[4], len);
+ when = meta[2];
+ if type(when) == "string" then
+ when = datetime.parse(when);
+ end
+ -- log("debug", "when = %s", tostring(when))
+ -- log("debug", "start = %s", tostring(start))
+ -- log("debug", "ending = %s", tostring(ending))
+ if when >= start and when <= ending and (not query.with or meta[3] == query.with) and not seek_once then
+ -- log("debug", "len is %s:%s", type(len), tostring(len));
+ data = fh:read(tonumber(len));
+ fh:seek("cur", 1); fh:read("*l"); -- Go past the trailing length line
+ -- log("debug", "data is %s:%s", type(data), tostring(data));
+ data = decode(data);
+ coroutine.yield(meta[1], (unfreeze[ meta[4] ] or id)(data), when, meta[3]);
+ results = results + 1;
+ if limit and results >= limit then
+ -- log("debug", "results => limit");
+ return;
+ end
+ else
+ fh:seek("cur", tonumber(len)+1);
+ -- log("debug", "%q", fh:read"*l");
+ end
+ if seek_once then seek_once = nil; end
+ len = fh:read"*l";
+ -- log("debug", "len = %s", tostring(len));
+ end
+ end
+
+ date = datelist[ ( datelist[date] or -1 ) + 1];
+ -- log("debug", "next date is %s", date or "EOF");
+ end
+ -- log("debug", "out of outer loop");
+ end);
+ end
+ end
+
+ function driver:delete(user, query)
+ user = user or "@";
+ if query and query.with then
+ return nil, "not-implemented"; -- More complicated
+ end
+ query = query or NULL;
+ local dstart, dend = "0000-00-00", "9999-99-99";
+ if query["start"] then
+ dstart = datetime.date(query["start"]);
+ end
+ if query["end"] then
+ dend = datetime.date(query["end"]);
+ end
+
+ for i, date in ipairs(get_datelist(user)) do
+ if date > dstart and date < dend then
+ os_remove(getpath(user, store, date) .. suffix);
+ end
+ end
+
+ -- The expensive part
+ local dates, changed;
+ for key in index_storage:keys(user) do
+ changed = false;
+ dates = index_storage:get(user, key);
+ for date in pairs(dates) do
+ if date > dstart and date < dend then
+ dates[date] = nil;
+ changed = true;
+ end
+ end
+ if changed then
+ if next(dates) == nil then dates = nil; end
+ index_storage:set(user, key, dates);
+ end
+ end
+ -- driver:_update_index(user)
+ end
+ return driver;
+end
+
+local drivers = {
+ keyval = new_keyval_store;
+ -- map = new_map_store;
+ archive = new_archive_store;
+};
+
+local pseudomaps = {
+ private = module:get_host_type() == "local", -- Private XML storage
+ persistent = module:get_host_type() == "component", -- Room persistence
+};
+
+function open(_, store, typ)
+ typ = typ or "keyval";
+ -- log("debug", "open(%q, %q, %q)", module.name, store, typ);
+ if pseudomaps[store] then
+ return new_map_store(store, typ == "keyval");
+ end
+ if not drivers[typ] then
+ return nil, "unsupported-store";
+ end
+ return drivers[typ](store);
+end
+
+local function rm(path)
+ did_mkdir_already[path] = nil;
+ os_remove(path);
+end
+
+function purge(_, user)
+ for item in lfs.dir(getpath(user)) do
+ local filename = getpath(user, item);
+ local mode = lfs.attributes(filename, "mode");
+ if mode == "file" and item:sub(-#suffix) == suffix then
+ rm(filename);
+ elseif mode == "directory" and item ~= "." and item ~= ".." then
+ -- map or archive
+ for mapitem in lfs.dir(getpath(user, item)) do
+ local filename = getpath(user, item, mapitem);
+ local mode = lfs.attributes(filename, "mode");
+ if mode == "file" and mapitem:sub(-#suffix) == suffix then
+ rm(filename);
+ end
+ end
+ rm(filename);
+ end
+ end
+ return true;
+end
+
+module:provides("storage");
+
--- a/mod_storage_memory/mod_storage_memory.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_storage_memory/mod_storage_memory.lua Sun Jan 25 13:04:02 2015 +0100
@@ -19,8 +19,104 @@
return true;
end
+local map_store = {};
+map_store.__index = map_store;
+
+function map_store:get(username, key)
+ local userstore = self.store[username];
+ if type(userstore) == "table" then
+ return userstore[key];
+ end
+end
+
+function map_store:set(username, key, data)
+ local userstore = self.store[username];
+ if userstore == nil then
+ userstore = {};
+ self.store[username] = userstore;
+ end
+ userstore[key] = data;
+ return true;
+end
+
+local archive_store = {};
+archive_store.__index = archive_store;
+
+function archive_store:append(username, key, when, with, value)
+ local a = self.store[username];
+ if not a then
+ a = {};
+ self.store[username] = a;
+ end
+ local i = #a+1;
+ local v = { key = key, when = when, with = with, value = value };
+ if not key then
+ key = tostring(a):match"%x+$"..tostring(v):match"%x+$";
+ v.key = key;
+ end
+ a[i] = v;
+ a[key] = i;
+ return true;
+end
+
+function archive_store:find(username, query)
+ local a = self.store[username] or {};
+ local start, stop, step = 1, #a, 1;
+ if query then
+ if query.reverse then
+ start, stop, step = stop, start, -1;
+ if query.before then
+ start = a[query.before];
+ end
+ elseif query.after then
+ start = a[query.after];
+ end
+ end
+ if not start then return nil, "invalid-key";
+ local iter = coroutine.wrap(function (a, start, stop, step, when_start, when_end, match_with)
+ local item, when, with;
+ for i = start, stop, step do
+ item = a[i];
+ when, with = item.when, item.with;
+ if when >= when_start and when_end >= when and (not match_with or match_with == with) then
+ coroutine.yield(item.key, item.value, when, with);
+ end
+ end
+ end);
+ iter(a, start, stop, step, query and query.start or 0, query and query["end"] or math.huge, query and query.with);
+ return iter;
+end
+
+function archive_store:delete(username, query)
+ if not query then
+ self.store[username] = nil;
+ return true;
+ end
+ local old = self.store[username];
+ if not old then return true; end
+ local qstart = query.start or 0;
+ local qend = query["end"] or math.huge;
+ local with = query.with;
+ local new = {};
+ self.store[username] = new;
+ local t;
+ for i = 1, #old do
+ i = old[i];
+ t = i.when;
+ if not(qstart >= t and qend <= t and (not with or i.with == with)) then
+ self:append(username, i.key, t, i.with, i.value);
+ end
+ end
+ if #new == 0 then
+ self.store[username] = nil;
+ end
+ return true;
+end
+
local stores = {
keyval = keyval_store;
+ map = map_store;
+ archive = nil;
}
local driver = {};
--- a/mod_storage_mongodb/mod_storage_mongodb.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_storage_mongodb/mod_storage_mongodb.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,5 +1,8 @@
local next = next;
local setmetatable = setmetatable;
+local set = require"util.set";
+local it = require"util.iterators";
+local array = require"util.array";
local params = assert ( module:get_option("mongodb") , "mongodb configuration not found" );
@@ -46,9 +49,79 @@
end;
end
+local roster_store = {};
+roster_store.__index = roster_store;
+
+function roster_store:get(username)
+ local host = module.host or "_global";
+ local store = self.store;
+
+ -- The database name can't have a period in it (hence it can't be a host/ip)
+ local namespace = params.dbname .. "." .. host;
+ local v = { _id = { store = store ; username = username } };
+
+ local cursor , err = conn:query ( namespace , v );
+ if not cursor then return nil , err end;
+
+ local r , err = cursor:next ( );
+ if not r then return nil , err end;
+ local roster = {
+ [false] = {
+ version = r.version;
+ pending = setmetatable(set.new( r.pending )._items, nil);
+ };
+ };
+ local items = r.items;
+ for i = 1, #items do
+ local item = items[i];
+ roster[item.jid] = {
+ subscription = item.subscription;
+ groups = set.new( item.groups )._items;
+ ask = item.ask;
+ name = item.name;
+ }
+ end
+ return roster;
+end
+
+function roster_store:set(username, data)
+ local host = module.host or "_global";
+ local store = self.store;
+
+ -- The database name can't have a period in it (hence it can't be a host/ip)
+ local namespace = params.dbname .. "." .. host;
+ local v = { _id = { store = store ; username = username } };
+
+ if data == nil or next(data) == nil then -- delete data
+ return conn:remove ( namespace , v );
+ end
+
+ for k,v in pairs(data[false]) do
+ v[k]=v;
+ end
+ v.pending = array(it.keys(v.pending or data.pending));
+
+ local items = {}
+ for jid, item in pairs(data) do
+ if jid and jid ~= "pending" then
+ table.insert(items, {
+ jid = jid;
+ subscription = item.subscription;
+ groups = array(it.keys( item.groups ));
+ name = item.name;
+ ask = item.ask;
+ });
+ end
+ end
+ v.items = items;
+
+ return conn:insert ( namespace , v );
+end
+
local driver = {};
function driver:open(store, typ)
+ typ = typ or "keyval";
if not conn then
conn = assert ( mongo.Connection.New ( true ) );
assert ( conn:connect ( params.server ) );
@@ -57,8 +130,12 @@
end
end
- if not typ then -- default key-value store
+ if typ == "keyval" then -- default key-value store
+ if store == "roster" then
+ return setmetatable({ store = store }, roster_store);
+ end
return setmetatable({ store = store }, keyval_store);
+ -- TODO archives?
end;
return nil, "unsupported-store";
end
--- a/mod_storage_muc_log/mod_storage_muc_log.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_storage_muc_log/mod_storage_muc_log.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,3 +1,6 @@
+-- Copyright (C) 2014 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
local datamanager = require"core.storagemanager".olddm;
local xml_parse = require"util.xml".parse;
@@ -147,6 +150,25 @@
end);
end
+function driver:delete(node, query)
+ local start_date = query and query.start and os_date(datef, query.start) or "000000";
+ local end_date = query and query["end"] and os_date(datef, query["end"]) or "999999";
+
+ local path = datamanager.getpath(node, host, datastore):match("(.*)/");
+ local ok, iter, state, var = pcall(lfs.dir, path);
+ if not ok then
+ module:log("warn", iter);
+ return nil, iter;
+ end
+
+ for dir in iter, state, var do
+ if dir > start_date and dir < end_date then
+ data_store(node, host, datastore .. "/" .. dir, nil);
+ end
+ end
+ return true;
+end
+
function open(_, store, typ)
if typ ~= "archive" then
return nil, "unsupported-store";
@@ -154,4 +176,6 @@
return setmetatable({ store = store, type = typ }, driver_mt);
end
+purge = driver.delete;
+
module:provides "storage";
--- a/mod_storage_multi/mod_storage_multi.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_storage_multi/mod_storage_multi.lua Sun Jan 25 13:04:02 2015 +0100
@@ -1,7 +1,6 @@
-- mod_storage_multi
local storagemanager = require"core.storagemanager";
-local backends = module:get_option_array(module.name); -- TODO better name?
-- TODO migrate data "upwards"
@@ -17,7 +16,7 @@
local backends = self.backends;
local data, err;
for i = 1, #backends do
- module:log("debug", "%s:%s:get(%q)", tostring(backends[i].get), backends[i]._store, username);
+ module:log("debug", "%s:%s:get(%q)", tostring(backends[i].get), tostring(backends[i]._store), username or "");
data, err = backends[i]:get(username);
if err then
module:log("error", tostring(err));
@@ -48,7 +47,7 @@
all = all and ok; -- All successful
end
if policy == "all" then
- return all, err
+ return all, err;
elseif policy == "majority" then
return oks > (#backends/2), err;
end
@@ -64,9 +63,10 @@
function driver:open(store, typ)
local store_mt = stores[typ or "keyval"];
+ local backends = module:get_option(module.name .. "_backends_".. store) or module:get_option(module.name .. "_backends");
if store_mt then
local my_backends = {};
- local driver, opened
+ local driver, opened;
for i = 1, #backends do
driver = storagemanager.load_driver(module.host, backends[i]);
opened = driver:open(store, typ);
@@ -74,6 +74,9 @@
my_backends[i]._store = store;
end
return setmetatable({ backends = my_backends }, store_mt);
+ elseif backends then
+ module:log("warn", "Unsupported store type %s, passing through to mod_storage_%s", typ, backends[1]);
+ return storagemanager.load_driver(module.host, backends[1]):open(store, typ);
end
return nil, "unsupported-store";
end
--- a/mod_strict_https/mod_strict_https.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_strict_https/mod_strict_https.lua Sun Jan 25 13:04:02 2015 +0100
@@ -20,15 +20,17 @@
end
_old_fire_event = http_server._events.fire_event;
- function http_server._events.fire_event(event, payload)
- local request = payload.request;
- local host = event:match("^[A-Z]+ ([^/]+)");
- local module = modules[host];
- if module and not request.secure then
- payload.response.headers.location = module:http_url(request.path);
- return 301;
+ if module:get_option_boolean("hsts_redirect", true) then
+ function http_server._events.fire_event(event, payload)
+ local request = payload.request;
+ local host = event:match("^[A-Z]+ ([^/]+)");
+ local module = modules[host];
+ if module and not request.secure then
+ payload.response.headers.location = module:http_url(request.path);
+ return 301;
+ end
+ return _old_fire_event(event, payload);
end
- return _old_fire_event(event, payload);
end
end
function module.unload()
--- a/mod_throttle_presence/mod_throttle_presence.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_throttle_presence/mod_throttle_presence.lua Sun Jan 25 13:04:02 2015 +0100
@@ -11,17 +11,17 @@
local buffer = session.presence_buffer;
local from = stanza.attr.from;
if stanza.name ~= "presence" or (stanza.attr.type and stanza.attr.type ~= "unavailable") then
- local cached_presence = buffer[stanza.attr.from];
+ local cached_presence = buffer[from];
if cached_presence then
module:log("debug", "Important stanza for %s from %s, flushing presence", session.full_jid, from);
stanza._flush = true;
cached_presence._flush = true;
session.send(cached_presence);
- buffer[stanza.attr.from] = nil;
+ buffer[from] = nil;
end
else
- module:log("debug", "Buffering presence stanza from %s to %s", stanza.attr.from, session.full_jid);
- buffer[stanza.attr.from] = st.clone(stanza);
+ module:log("debug", "Buffering presence stanza from %s to %s", from, session.full_jid);
+ buffer[from] = st.clone(stanza);
return nil; -- Drop this stanza (we've stored it for later)
end
return stanza;
--- a/mod_vjud/vcard.lib.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_vjud/vcard.lib.lua Sun Jan 25 13:04:02 2015 +0100
@@ -6,17 +6,21 @@
-- TODO
-- Fix folding.
+-- vcard4
local st = require "util.stanza";
+local json = require"util.json";
local t_insert, t_concat = table.insert, table.concat;
local type = type;
local next, pairs, ipairs = next, pairs, ipairs;
local from_text, to_text, from_xep54, to_xep54;
+local from_json, to_json;
local line_sep = "\n";
local vCard_dtd; -- See end of file
+local vCard4_dtd;
local function fold_line()
error "Not implemented" --TODO
@@ -142,7 +146,7 @@
vCards[#vCards+1] = c;
elseif name == "END" and value == "VCARD" then
c = nil;
- elseif vCard_dtd[name] then
+ elseif c and vCard_dtd[name] then
local dtd = vCard_dtd[name];
local p = { name = name };
c[#c+1]=p;
@@ -319,6 +323,108 @@
end
end
+local json_typs = { string = "text" }
+local function json_type(val)
+ local typ = type(val);
+ if typ == "number" then
+ if val % 1 == val then
+ return "integer";
+ else
+ return "float";
+ end
+ end
+ return json_typs[typ] or json.null;
+end
+
+local function vcard_to_json(vCard)
+ local r = {"vcard"};
+ for i = 1,#vCard do
+ local p = {};
+ local ri = { vCard[i].name:lower(), p, json_type(vCard[i][1]), unpack(vCard[i]) };
+ for k,v in pairs(vCard[i]) do
+ if tostring(k) == k and k ~= "name" then
+ p[k] = v;
+ end
+ end
+ r[1+i] = ri;
+ end
+ return r;
+end
+
+function to_json(vCards)
+ if vCards[1] and vCards[1].name then
+ return json.encode(vcard_to_json(vCards));
+ else
+ local t = {};
+ for i=1,#vCards do
+ t[i]=vcard_to_json(vCards[i]);
+ end
+ return json.encode(t);
+ end
+end
+
+local vcard4 = { }
+
+function vcard4:text(node, params, value)
+ self:tag(node:lower())
+ -- FIXME params
+ if type(value) == "string" then
+ self:tag("text"):text(value):up()
+ elseif vcard4[node] then
+ vcard4[node](value);
+ end
+ self:up();
+end
+
+function vcard4.N(value)
+ for i, k in ipairs(vCard_dtd.N.values) do
+ value:tag(k):text(value[i]):up();
+ end
+end
+
+local xmlns_vcard4 = "urn:ietf:params:xml:ns:vcard-4.0"
+
+local function item_to_vcard4(item)
+ local typ = item.name:lower();
+ local t = st.stanza(typ, { xmlns = xmlns_vcard4 });
+
+ local prop_def = vCard4_dtd[typ];
+ if prop_def == "text" then
+ t:tag("text"):text(item[1]):up();
+ elseif type(prop_def) == "table" then
+ if prop_def.values then
+ for i, v in ipairs(prop_def.values) do
+ t:tag(v:lower()):text(item[i] or ""):up();
+ end
+ else
+ t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"})
+ end
+ else
+ t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"})
+ end
+ return t;
+end
+
+local function vcard_to_vcard4xml(vCard)
+ local t = st.stanza("vcard", { xmlns = xmlns_vcard4 });
+ for i=1,#vCard do
+ t:add_child(item_to_vcard4(vCard[i]));
+ end
+ return t;
+end
+
+local function vcards_to_vcard4xml(vCards)
+ if not vCards[1] or vCards[1].name then
+ return vcard_to_vcard4xml(vCards)
+ else
+ local t = st.stanza("vcards", { xmlns = xmlns_vcard4 });
+ for i=1,#vCards do
+ t:add_child(vcard_to_vcard4xml(vCards[i]));
+ end
+ return t;
+ end
+end
+
-- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd
vCard_dtd = {
VERSION = "text", --MUST be 3.0, so parsing is redundant
@@ -445,6 +551,63 @@
vCard_dtd.LOGO = vCard_dtd.PHOTO;
vCard_dtd.SOUND = vCard_dtd.PHOTO;
+vCard4_dtd = {
+ source = "uri",
+ kind = "text",
+ xml = "text",
+ fn = "text",
+ n = {
+ values = {
+ "family",
+ "given",
+ "middle",
+ "prefix",
+ "suffix",
+ },
+ },
+ nickname = "text",
+ photo = "uri",
+ bday = "date-and-or-time",
+ anniversary = "date-and-or-time",
+ gender = "text",
+ adr = {
+ values = {
+ "pobox",
+ "ext",
+ "street",
+ "locality",
+ "region",
+ "code",
+ "country",
+ }
+ },
+ tel = "text",
+ email = "text",
+ impp = "uri",
+ lang = "language-tag",
+ tz = "text",
+ geo = "uri",
+ title = "text",
+ role = "text",
+ logo = "uri",
+ org = "text",
+ member = "uri",
+ related = "uri",
+ categories = "text",
+ note = "text",
+ prodid = "text",
+ rev = "timestamp",
+ sound = "uri",
+ uid = "uri",
+ clientpidmap = "number, uuid",
+ url = "uri",
+ version = "text",
+ key = "uri",
+ fburl = "uri",
+ caladruri = "uri",
+ caluri = "uri",
+};
+
return {
from_text = from_text;
to_text = to_text;
@@ -452,6 +615,10 @@
from_xep54 = from_xep54;
to_xep54 = to_xep54;
+ to_json = to_json;
+
+ to_vcard4 = vcards_to_vcard4xml;
+
-- COMPAT:
lua_to_text = to_text;
lua_to_xep54 = to_xep54;
--- a/mod_webpresence/mod_webpresence.lua Tue Jan 20 11:02:14 2015 +0000
+++ b/mod_webpresence/mod_webpresence.lua Sun Jan 25 13:04:02 2015 +0100
@@ -32,8 +32,8 @@
if user_sessions then
status = user_sessions.top_resources[1];
if status and status.presence then
- message = status.presence:child_with_name("status");
- status = status.presence:child_with_name("show");
+ message = status.presence:get_child("status");
+ status = status.presence:get_child("show");
if not status then
status = "online";
else