--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_adhoc_blacklist/mod_adhoc_blacklist.lua Mon Aug 10 21:13:31 2015 +0200
@@ -0,0 +1,90 @@
+-- mod_adhoc_blacklist
+--
+-- http://xmpp.org/extensions/xep-0133.html#edit-blacklist
+--
+-- Copyright (C) 2015 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+--
+
+module:depends("adhoc");
+local adhoc = module:require "adhoc";
+local st = require"util.stanza";
+local set = require"util.set";
+local dataform = require"util.dataforms";
+local adhoc_inital_data = require "util.adhoc".new_initial_data_form;
+
+local blocklist_form = dataform.new {
+ title = "Editing the Blacklist";
+ instructions = "Fill out this form to edit the list of entities with whom communications are disallowed.";
+ {
+ type = "hidden";
+ name = "FORM_TYPE";
+ value = "http://jabber.org/protocol/admin";
+ };
+ {
+ type = "jid-multi";
+ name = "blacklistjids";
+ label = "The blacklist";
+ };
+}
+
+local blocklists = module:open_store("blocklist");
+
+local blocklist_handler = adhoc_inital_data(blocklist_form, function ()
+ local blacklistjids = {};
+ local blacklist = blocklists:get();
+ if blacklist then
+ for jid in pairs(blacklist) do
+ table.insert(blacklistjids, jid);
+ end
+ end
+ return { blacklistjids = blacklistjids };
+end, function(fields, form_err)
+ if form_err then
+ return { status = "completed", error = { message = "Problem in submitted form" } };
+ end
+ local blacklistjids = set.new(fields.blacklistjids);
+ local ok, err = blocklists:set(nil, blacklistjids._items);
+ if ok then
+ return { status = "completed", info = "Blacklist updated" };
+ else
+ return { status = "completed", error = { message = "Error saving blacklist: "..err } };
+ end
+end);
+
+module:add_item("adhoc", adhoc.new("Edit Blacklist", "http://jabber.org/protocol/admin#edit-blacklist", blocklist_handler, "admin"));
+
+local function is_blocked(host)
+ local blacklistjids = blocklists:get();
+ return blacklistjids and blacklistjids[host];
+end
+
+module:hook("route/remote", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ if is_blocked(event.to_host) then
+ if origin and stanza then
+ origin.send(st.error_reply(stanza, "cancel", "not-allowed", "Communication with this domain is not allowed"));
+ return true;
+ end
+ return false;
+ end
+end, 1000);
+
+
+module:hook("s2s-stream-features", function (event)
+ local session = event.origin;
+ if is_blocked(session.from_host) then
+ session:close("policy-violation");
+ return false;
+ end
+end, 1000);
+
+module:hook("stanza/http://etherx.jabber.org/streams:features", function (event)
+ local session = event.origin;
+ if is_blocked(session.to_host) then
+ session:close("policy-violation");
+ return true;
+ end
+end, 1000);
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_admin_blocklist/mod_admin_blocklist.lua Mon Aug 10 21:13:31 2015 +0200
@@ -0,0 +1,59 @@
+-- mod_admin_blocklist
+--
+-- If a local admin has blocked a domain, don't allow s2s to that domain
+--
+-- Copyright (C) 2015 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+--
+
+module:depends("blocklist");
+
+local st = require"util.stanza";
+local jid_split = require"util.jid".split;
+
+local admins = module:get_option_inherited_set("admins", {}) /
+ function (admin) -- Filter out non-local admins
+ local user, host = jid_split(admin);
+ if host == module.host then return user; end
+ end
+
+local blocklists = module:open_store("blocklist");
+
+local function is_blocked(host)
+ for admin in admins do
+ local blocklist = blocklists:get(admin);
+ if blocklist and blocklist[host] then
+ return true;
+ end
+ end
+end
+
+module:hook("route/remote", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ if is_blocked(event.to_host) then
+ if origin and stanza then
+ origin.send(st.error_reply(stanza, "cancel", "not-allowed", "Communication with this domain is not allowed"));
+ return true;
+ end
+ return false;
+ end
+end, 1000);
+
+
+module:hook("s2s-stream-features", function (event)
+ local session = event.origin;
+ if is_blocked(session.from_host) then
+ session:close("policy-violation");
+ return false;
+ end
+end, 1000);
+
+module:hook("stanza/http://etherx.jabber.org/streams:features", function (event)
+ local session = event.origin;
+ if is_blocked(session.to_host) then
+ session:close("policy-violation");
+ return true;
+ end
+end, 1000);
+
--- a/mod_auth_http_async/mod_auth_http_async.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_auth_http_async/mod_auth_http_async.lua Mon Aug 10 21:13:31 2015 +0200
@@ -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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_benchmark_storage/mod_benchmark_storage.lua Mon Aug 10 21:13:31 2015 +0200
@@ -0,0 +1,119 @@
+-- mod_benchmark_storage
+-- Copyright (C) 2015 Kim Alvefur
+--
+-- Prime numbers are pretty cool
+
+local gettime = require"socket".gettime;
+
+local sm = require"core.storagemanager";
+local um = require"core.usermanager";
+local mm = require"core.modulemanager";
+
+local test_data, test_users;
+
+function module.command(arg)
+ local test_driver = arg[1];
+ if not test_driver then
+ return print("Usage: prosodyctl mod_"..module.name.." <storage driver>");
+ end
+
+ sm.initialize_host("localhost");
+ um.initialize_host("localhost");
+
+ local start_time = gettime();
+ local storage = assert(sm.load_driver("localhost", test_driver));
+ storage = assert(storage:open("benchmark"));
+ -- for i = 1, 23 do
+ -- storage:set(test_users[i], test_data);
+ -- end
+ local floor, sin, random, pi = math.floor, math.sin, math.random, math.pi;
+ for i = 1, 10079 do
+ if i % 11 == 1 then
+ storage:set(test_users[i%23+1], test_data[3-floor(sin(random()*pi)*3)]);
+ else
+ storage:get(test_users[i%23+1]);
+ end
+ if i % 151 == 0 then
+ -- Give indication of progress
+ io.write("*");
+ io.flush();
+ end
+ end
+ -- Cleanup
+ for i = 1, 23 do
+ storage:set(test_users[i], nil);
+ end
+ mm.unload("localhost", "storage_"..test_driver);
+ local time_taken = gettime() - start_time;
+ io.write("\27[0G\27[K"); -- Clear current line
+ io.flush();
+ print(("Took %fs with mod_storage_%s"):format(time_taken, test_driver));
+end
+
+-- 23 usernames
+test_users = {
+ "tritonymph"; "ankylotomy"; "tron"; "barbaric"; "twiddler";
+ "spiritful"; "unmollifiably"; "suggestion"; "presubsistence";
+ "unneeded"; "taxemic"; "teloteropathic"; "nonbending"; "mev";
+ "septifragally"; "clame"; "obsolescent"; "unconceivable";
+ "foolishly"; "conjunctur"; "precirculation"; "bethump"; "vermivorous";
+};
+
+test_data = {
+ { some_data = "tiny data" };
+ --
+ { [false] = { version = 1; pending = {}; }; -- Medium data
+ ["user@example.com"] = { subscription = "both"; groups = {};
+ name = "My Best Friend"; }; };
+ --
+ { attr = { xmlns = "vcard-temp"; }; name = "vCard"; -- The largest data
+ { attr = { xmlns = "vcard-temp"; }; name = "NICKNAME"; "Buster"; };
+ { attr = { xmlns = "vcard-temp"; }; name = "PHOTO";
+ { attr = { xmlns = "vcard-temp"; }; name = "TYPE"; "image/jpeg"; };
+ { attr = { xmlns = "vcard-temp"; }; name = "BINVAL"; [[
+ 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
+ FsQtGrr65p97R/X/ANxCn6r/2Q==]]; }; }; };
+};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_compact_resource/mod_compact_resource.lua Mon Aug 10 21:13:31 2015 +0200
@@ -0,0 +1,12 @@
+
+local base64_encode = require"util.encodings".base64.encode;
+local random_bytes = require"util.random".bytes;
+
+local b64url = { ["+"] = "-", ["/"] = "_", ["="] = "" };
+local function random_resource()
+ return base64_encode(random_bytes(8)):gsub("[+/=]", b64url);
+end
+
+module:hook("pre-resource-bind", function (event)
+ event.resource = random_resource();
+end);
--- a/mod_filter_chatstates/mod_filter_chatstates.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_filter_chatstates/mod_filter_chatstates.lua Mon Aug 10 21:13:31 2015 +0200
@@ -1,9 +1,6 @@
local filters = require "util.filters";
local st = require "util.stanza";
-local dummy_stanza_mt = setmetatable({ __tostring = function () return ""; end }, { __index = st.stanza_mt });
-local dummy_stanza = setmetatable(st.stanza(), dummy_stanza_mt);
-
module:depends("csi");
local function filter_chatstates(stanza)
@@ -11,11 +8,11 @@
stanza = st.clone(stanza);
stanza:maptags(function (tag)
if tag.attr.xmlns ~= "http://jabber.org/protocol/chatstates" then
- return tag;
+ return tag
end
end);
if #stanza.tags == 0 then
- return dummy_stanza;
+ return nil;
end
end
return stanza;
--- a/mod_http_muc_log/http_muc_log.html Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_http_muc_log/http_muc_log.html Mon Aug 10 21:13:31 2015 +0200
@@ -44,7 +44,7 @@
<header>
<h1 title="xmpp:{jid?}">{title}</h1>
<nav>{links#
-<a class="{rel?}" href="{href}" rel="{rel?}">{text}</a>}
+<a class="{item.rel?}" href="{item.href}" rel="{item.rel?}">{item.text}</a>}
</nav>
</header>
<hr>
@@ -52,33 +52,33 @@
<nav>
<dl class="room-list">
{rooms#
-<dt class="name"><a href="{href}">{name}</a></dt>
-<dd class="description">{description?}</dd>}
+<dt class="name"><a href="{item.href}">{item.name}</a></dt>
+<dd class="description">{item.description?}</dd>}
</dl>
{years#
-<h2 id="{year}">{year}</h2>
-{months#
-<table id="{month}-{year}">
-<caption>{month}</caption>
-<tr><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th><th>Sun</th></tr>{weeks#
-<tr>{days#<td>{href&<a href="{href}">}{day? }{href&</a>}</td>}</tr>}
+<h2 id="{item.year}">{item.year}</h2>
+{item.months#
+<table id="{item.month}-{item.year}">
+<caption>{item.month}</caption>
+<tr><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th><th>Sun</th></tr>{item.weeks#
+<tr>{item.days#<td>{item.href&<a href="{item.href}">}{item.day? }{item.href&</a>}</td>}</tr>}
</table>
}
}
</nav>
<ol class="chat-logs">{lines#
-<li class="{st_name} {st_type?}" id="{key}">
-<a class="time" href="#{key}"><time datetime="{datetime}">{time}</time></a>
-<b class="nick">{nick}</b>
-<em class="verb">{verb?}</em>
-<q class="body">{body?}</q>
+<li class="{item.st_name} {item.st_type?}" id="{item.key}">
+<a class="time" href="#{item.key}"><time datetime="{item.datetime}">{item.time}</time></a>
+<b class="nick">{item.nick}</b>
+<em class="verb">{item.verb?}</em>
+<q class="body">{item.body?}</q>
</li>}
</ol>
</div>
<hr>
<footer>
<nav>{links#
-<a class="{rel?}" href="{href}" rel="{rel?}">{text}</a>}
+<a class="{item.rel?}" href="{item.href}" rel="{item.rel?}">{item.text}</a>}
</nav>
<br>
<div class="powered-by">Prosody</div>
--- a/mod_http_muc_log/mod_http_muc_log.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_http_muc_log/mod_http_muc_log.lua Mon Aug 10 21:13:31 2015 +0200
@@ -1,4 +1,3 @@
-local st = require "util.stanza";
local mt = require"util.multitable";
local datetime = require"util.datetime";
local jid_split = require"util.jid".split;
@@ -7,9 +6,8 @@
local it = require"util.iterators";
local gettime = require"socket".gettime;
local url = require"socket.url";
-local xml_escape = st.xml_escape;
-local t_concat = table.concat;
local os_time, os_date = os.time, os.date;
+local render = require"util.interpolation".new("%b{}", require"util.stanza".xml_escape);
local archive = module:open_store("muc_log", "archive");
@@ -33,45 +31,6 @@
module:depends"http";
-local function render(template, values)
- -- This function takes a string template and a table of values.
- -- Sequences like {name} in the template string are substituted
- -- with values from the table, optionally depending on a modifier
- -- symbol.
- --
- -- Variants are:
- -- {name} is substituted for values["name"] and is XML escaped
- -- {name? sub-template } renders a sub-template if values["name"] is false-ish
- -- {name& sub-template } renders a sub-template if values["name"] is true-ish
- -- {name# sub-template } renders a sub-template using an array of values
- -- {name!} is substituted *without* XML escaping
- return (template:gsub("%b{}", function (block)
- local name, opt, e = block:sub(2, -2):match("^([%a_][%w_]*)(%p?)()");
- local value = values[name];
- if opt == '#' then
- if not value or not value[1] then return ""; end
- local out, subtpl = {}, block:sub(e+1, -2);
- for i=1, #value do
- out[i] = render(subtpl, value[i]);
- end
- return t_concat(out);
- elseif opt == '&' then
- if not value then return ""; end
- return render(block:sub(e+1, -2), values);
- elseif opt == '?' and not value then
- return render(block:sub(e+1, -2), values);
- elseif value ~= nil then
- if type(value) ~= "string" then
- value = tostring(value);
- end
- if opt ~= '!' then
- return xml_escape(value);
- end
- return value;
- end
- end));
-end
-
local template;
do
local template_file = module:get_option_string(module.name .. "_template", module.name .. ".html");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_upload/mod_http_upload.lua Mon Aug 10 21:13:31 2015 +0200
@@ -0,0 +1,110 @@
+-- mod_http_upload
+--
+-- Copyright (C) 2015 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+--
+-- Implementation of HTTP Upload file transfer mechanism used by Conversations
+--
+
+-- imports
+local st = require"util.stanza";
+local lfs = require"lfs";
+local join_path = require"util.paths".join;
+local uuid = require"util.uuid".generate;
+
+-- depends
+module:depends("http");
+
+-- namespace
+local xmlns_http_upload = "eu:siacs:conversations:http:upload";
+
+module:add_feature(xmlns_http_upload);
+
+-- state
+local pending_slots = module:shared("upload_slots");
+
+local storage_path = join_path(prosody.paths.data, module.name);
+lfs.mkdir(storage_path);
+
+-- hooks
+module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
+ local stanza, origin = event.stanza, event.origin;
+ -- local clients only
+ if origin.type ~= "c2s" then
+ origin.send(st.error_reply(stanza, "cancel", "not-authorized"));
+ return true;
+ end
+ -- validate
+ local filename = stanza.tags[1]:get_child_text("filename");
+ if not filename or filename:find("/") then
+ origin.send(st.error_reply(stanza, "modify", "bad-request"));
+ return true;
+ end
+ local reply = st.reply(stanza);
+ reply:tag("slot", { xmlns = xmlns_http_upload });
+ local random = uuid();
+ pending_slots[random.."/"..filename] = origin.full_jid;
+ local url = module:http_url() .. "/" .. random .. "/" .. filename;
+ reply:tag("get"):text(url):up();
+ reply:tag("put"):text(url):up();
+ origin.send(reply);
+ return true;
+end);
+
+-- http service
+local function upload_data(event, path)
+ if not pending_slots[path] then
+ return 401;
+ end
+ local random, filename = path:match("^([^/]+)/([^/]+)$");
+ if not random then
+ return 400;
+ end
+ local dirname = join_path(storage_path, random);
+ if not lfs.mkdir(dirname) then
+ module:log("error", "Could not create directory %s for upload", dirname);
+ return 500;
+ end
+ local full_filename = join_path(dirname, filename);
+ local fh, ferr = io.open(full_filename, "w");
+ if not fh then
+ module:log("error", "Could not open file %s for upload: %s", full_filename, ferr);
+ return 500;
+ end
+ local ok, err = fh:write(event.request.body);
+ if not ok then
+ module:log("error", "Could not write to file %s for upload: %s", full_filename, err);
+ os.remove(full_filename);
+ return 500;
+ end
+ ok, err = fh:close();
+ if not ok then
+ module:log("error", "Could not write to file %s for upload: %s", full_filename, err);
+ os.remove(full_filename);
+ return 500;
+ end
+ module:log("info", "File uploaded by %s to slot %s", pending_slots[path], random);
+ pending_slots[path] = nil;
+ return 200;
+end
+
+local serve_uploaded_files = module:depends("http_files").serve(storage_path);
+
+local function size_only(request, data)
+ request.headers.content_size = #data;
+ return 200;
+end
+
+local function serve_head(event, path)
+ event.send = size_only;
+ return serve_uploaded_files(event, path);
+end
+
+module:provides("http", {
+ route = {
+ ["GET /*"] = serve_uploaded_files;
+ ["HEAD /*"] = serve_head;
+ ["PUT /*"] = upload_data;
+ };
+});
--- a/mod_list_inactive/mod_list_inactive.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_list_inactive/mod_list_inactive.lua Mon Aug 10 21:13:31 2015 +0200
@@ -19,6 +19,15 @@
}
function module.command(arg)
+ if #arg < 2 then
+ print("usage: prosodyctl mod_list_inactive example.net time [format]");
+ print("time is a number followed by 'day', 'week', 'month' or 'year'");
+ print("formats are:");
+ for name, fmt in pairs(output_formats) do
+ print(name, fmt:format("user@example.com", "last action"))
+ end
+ return;
+ end
local items = {};
local host = arg[1];
assert(hosts[host], "Host "..tostring(host).." does not exist");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_log_rate/mod_log_rate.lua Mon Aug 10 21:13:31 2015 +0200
@@ -0,0 +1,17 @@
+module:set_global();
+
+local measure = require"core.statsmanager".measure;
+
+local function sink_maker(config)
+ local levels = {
+ debug = measure("rate", "log.debug");
+ info = measure("rate", "log.info");
+ warn = measure("rate", "log.warn");
+ error = measure("rate", "log.error");
+ };
+ return function (_, level)
+ return levels[level]();
+ end
+end
+
+require"core.loggingmanager".register_sink_type("measure", sink_maker);
--- a/mod_mam/mod_mam.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_mam/mod_mam.lua Mon Aug 10 21:13:31 2015 +0200
@@ -41,7 +41,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
@@ -230,7 +230,9 @@
-- or that don't have a <body/>
or not stanza:get_child("body")
-- or if hints suggest we shouldn't
+ or stanza:get_child("no-permanent-storage", "urn:xmpp:hints") -- The XEP needs to decide on "store" or "storage"
or stanza:get_child("no-permanent-store", "urn:xmpp:hints")
+ or stanza:get_child("no-storage", "urn:xmpp:hints")
or stanza:get_child("no-store", "urn:xmpp:hints") then
module:log("debug", "Not archiving stanza: %s (content)", stanza:top_tag());
return;
@@ -247,6 +249,9 @@
-- And stash it
local ok, id = archive:append(store_user, nil, time_now(), with, stanza);
+ if ok then
+ module:fire_event("archive-message-added", { origin = origin, stanza = stanza, for_user = store_user, id = id });
+ end
else
module:log("debug", "Not archiving stanza: %s (prefs)", stanza:top_tag());
end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_mamsub/mod_mamsub.lua Mon Aug 10 21:13:31 2015 +0200
@@ -0,0 +1,64 @@
+-- MAM Subscriptions prototype
+-- Copyright (C) 2015 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+
+local mt = require"util.multitable";
+local st = require"util.stanza";
+
+local xmlns_mamsub = "http://prosody.im/protocol/mamsub";
+
+module:add_feature(xmlns_mamsub);
+
+local host_sessions = prosody.hosts[module.host].sessions;
+
+local weak = { __mode = "k" };
+
+module:hook("iq-set/self/"..xmlns_mamsub..":subscribe", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ if origin.mamsub ~= nil then
+ origin.send(st.error_reply(stanza, "modify", "conflict"));
+ return true;
+ end
+ origin.mamsub = xmlns_mamsub;
+ local mamsub_sessions = host_sessions[origin.username].mamsub_sessions;
+ if not mamsub_sessions then
+ mamsub_sessions = setmetatable({}, weak);
+ host_sessions[origin.username].mamsub_sessions = mamsub_sessions;
+ end
+ mamsub_sessions[origin] = true;
+ origin.send(st.reply(stanza));
+ return true;
+end);
+
+module:hook("iq-set/self/"..xmlns_mamsub..":unsubscribe", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ if origin.mamsub ~= xmlns_mamsub then
+ origin.send(st.error_reply(stanza, "modify", "conflict"));
+ return true;
+ end
+ origin.mamsub = nil;
+ local mamsub_sessions = host_sessions[origin.username].mamsub_sessions;
+ if mamsub_sessions then
+ mamsub_sessions[origin] = nil;
+ end
+ origin.send(st.reply(stanza));
+ return true;
+end);
+
+module:hook("archive-message-added", function (event)
+ local user_session = host_sessions[event.for_user];
+ local mamsub_sessions = user_session and user_session.mamsub_sessions;
+ if not mamsub_sessions then return end;
+
+ local for_broadcast = st.message():tag("mamsub", { xmlns = xmlns_mamsub })
+ :tag("forwarded", { xmlns = "urn:xmpp:forward:0" })
+ :add_child(event.stanza);
+
+ for session in pairs(mamsub_sessions) do
+ if session.mamsub == xmlns_mamsub then
+ for_broadcast.attr.to = session.full_jid;
+ session.send(for_broadcast);
+ end
+ end
+end);
--- a/mod_muc_limits/mod_muc_limits.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_muc_limits/mod_muc_limits.lua Mon Aug 10 21:13:31 2015 +0200
@@ -1,8 +1,8 @@
-local rooms = module:shared "muc/rooms";
+local mod_muc = module:depends"muc";
+local rooms = rawget(mod_muc, "rooms"); -- Old MUC API
if not rooms then
- module:log("error", "This module only works on MUC components!");
- return;
+ rooms = module:shared"muc/rooms"; -- New MUC API
end
local jid_split, jid_bare = require "util.jid".split, require "util.jid".bare;
--- a/mod_profile/mod_profile.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_profile/mod_profile.lua Mon Aug 10 21:13:31 2015 +0200
@@ -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,18 +87,25 @@
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
+ if err then
+ origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
+ return true;
+ end
data = legacy_storage:get(username);
data = data and st.deserialize(data);
if data then
- return origin.send(st.reply(stanza):add_child(data));
+ origin.send(st.reply(stanza):add_child(data));
+ return true;
end
end
if not data then
- return origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
+ return true;
end
- return origin.send(st.reply(stanza):add_child(vcard.to_xep54(data)));
+ origin.send(st.reply(stanza):add_child(vcard.to_xep54(data)));
+ return true;
end
local function handle_set(event)
@@ -107,20 +115,23 @@
local to = stanza.attr.to;
if to then
if not is_admin(jid_bare(stanza.attr.from), module.host) then
- return origin.send(st.error_reply(stanza, "auth", "forbidden"));
+ origin.send(st.error_reply(stanza, "auth", "forbidden"));
+ return true;
end
username = jid_split(to);
end
local ok, err = storage:set(username, data);
if not ok then
- return origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
+ origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
+ return true;
end
if pep_plus and username then
update_pep(username, data);
end
- return origin.send(st.reply(stanza));
+ origin.send(st.reply(stanza));
+ return true;
end
module:hook("iq-get/bare/vcard-temp:vCard", handle_get);
@@ -185,9 +196,11 @@
local username = jid_split(stanza.attr.to) or origin.username;
local data = storage:get(username);
if not data then
- return origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
+ return true;
end
- return origin.send(st.reply(stanza):add_child(vcard.to_vcard4(data)));
+ origin.send(st.reply(stanza):add_child(vcard.to_vcard4(data)));
+ return true;
end);
if vcard.from_vcard4 then
@@ -195,14 +208,17 @@
local origin, stanza = event.origin, event.stanza;
local ok, err = storage:set(origin.username, vcard.from_vcard4(stanza.tags[1]));
if not ok then
- return origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
+ origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
+ return true;
end
- return origin.send(st.reply(stanza));
+ origin.send(st.reply(stanza));
+ return true;
end);
else
module:hook("iq-set/self/urn:ietf:params:xml:ns:vcard-4.0:vcard", function(event)
local origin, stanza = event.origin, event.stanza;
- return origin.send(st.error_reply(stanza, "cancel", "feature-not-implemented"));
+ origin.send(st.error_reply(stanza, "cancel", "feature-not-implemented"));
+ return true;
end);
end
end
--- a/mod_s2s_auth_dane/mod_s2s_auth_dane.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_s2s_auth_dane/mod_s2s_auth_dane.lua Mon Aug 10 21:13:31 2015 +0200
@@ -14,6 +14,8 @@
-- No encryption offered
-- Different hostname before and after STARTTLS - mod_s2s should complain
-- Interaction with Dialback
+--
+-- luacheck: ignore module
module:set_global();
@@ -294,7 +296,10 @@
log("info", "DANE validated ok for %s using %s", host, tlsa:getUsage());
if use == 2 then -- DANE-TA
session.cert_identity_status = "valid";
- session.cert_chain_status = "valid";
+ if cert_verify_identity(host, "xmpp-server", cert) then
+ session.cert_chain_status = "valid";
+ -- else -- TODO Check against SRV target?
+ end
-- for usage 0, PKIX-CA, identity and chain has to be valid already
end
match_found = true;
--- a/mod_smacks/mod_smacks.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_smacks/mod_smacks.lua Mon Aug 10 21:13:31 2015 +0200
@@ -298,6 +298,18 @@
end
end);
+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);
+
function handle_resume(session, stanza, xmlns_sm)
if session.full_jid then
session.log("warn", "Tried to resume after resource binding");
@@ -365,3 +377,18 @@
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;
+ if session.smacks then
+ if session.awaiting_ack then
+ return false; -- Kick the session
+ end
+ (session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }));
+ session.awaiting_ack = true;
+ return true;
+ end
+end
+
+module:hook("s2s-read-timeout", handle_read_timeout);
+module:hook("c2s-read-timeout", handle_read_timeout);
--- a/mod_smacks_offline/mod_smacks_offline.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_smacks_offline/mod_smacks_offline.lua Mon Aug 10 21:13:31 2015 +0200
@@ -21,9 +21,11 @@
local host_sessions = prosody.hosts[module.host].sessions;
mod_smacks.handle_unacked_stanzas = function (session)
- local sessions = host_sessions[session.username].sessions;
- if next(sessions) == session.resource and next(sessions, session.resource) == nil then
- store_unacked_stanzas(session)
+ if session.username then
+ local sessions = host_sessions[session.username].sessions;
+ if next(sessions) == session.resource and next(sessions, session.resource) == nil then
+ store_unacked_stanzas(session)
+ end
end
return handle_unacked_stanzas(session);
end
--- a/mod_storage_gdbm/mod_storage_gdbm.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_storage_gdbm/mod_storage_gdbm.lua Mon Aug 10 21:13:31 2015 +0200
@@ -73,7 +73,11 @@
archive.get = keyval.get;
archive.set = keyval.set;
-function archive:append(username, key, when, with, value)
+function archive:append(username, key, value, when, with)
+ if type(when) ~= "number" then
+ when, with, value = value, when, with;
+ end
+
key = key or uuid();
local meta = self:get(username);
if not meta then
@@ -148,7 +152,7 @@
db = assert(gdbm.open(db_path, "c"));
cache[db_path] = db;
end
- return setmetatable({ _db = db; _path = db_path; store = store, typ = type }, driver_mt);
+ return setmetatable({ _db = db; _path = db_path; store = store, type = typ }, driver_mt);
end
function purge(_, user)
@@ -166,6 +170,7 @@
function module.unload()
for db_path, db in pairs(cache) do
module:log("debug", "Closing db at %q", db_path);
+ gdbm.reorganize(db);
gdbm.sync(db);
gdbm.close(db);
end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_lmdb/mod_storage_lmdb.lua Mon Aug 10 21:13:31 2015 +0200
@@ -0,0 +1,86 @@
+-- mod_storage_lmdb
+-- Copyright (C) 2015 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+--
+-- Depends on lightningdbm
+-- https://github.com/shmul/lightningdbm
+--
+-- luacheck: globals prosody open
+
+local lmdb = require"lightningmdb";
+local lfs = require"lfs";
+local path = require"util.paths";
+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);
+
+local env = lmdb.env_create();
+assert(env:set_maxdbs(module:get_option_number("lmdb_maxdbs", 20)));
+local env_flags = 0;
+for i, flag in ipairs(module:get_option_array("lmdb_flags", {})) do
+ env_flags = env_flags + assert(lmdb["MDB_"..flag:upper()], "No such flag "..flag);
+end
+env:open(base_path, env_flags, tonumber("640", 8));
+
+local keyval = {};
+local keyval_mt = { __index = keyval, flags = lmdb.MDB_CREATE };
+
+function keyval:set(user, value)
+ local t = self.env:txn_begin(nil, 0);
+ if type(value) == "table" and next(value) == nil then
+ value = nil;
+ end
+ if value ~= nil then
+ value = serialize(value);
+ end
+ local ok, err;
+ if value ~= nil then
+ ok, err = t:put(self.db, user, value, 0);
+ else
+ ok, err = t:del(self.db, user, value);
+ end
+ if not ok then
+ t:abort();
+ return nil, err;
+ end
+ return t:commit();
+end
+
+function keyval:get(user)
+ local t = self.env:txn_begin(nil, 0);
+ local data, err = t:get(self.db, user, 0);
+ if not data then
+ t:abort();
+ return nil, err;
+ end
+ t:commit();
+ return deserialize(data);
+end
+
+local drivers = {
+ keyval = keyval_mt;
+}
+
+function open(_, store, typ)
+ typ = typ or "keyval";
+ local driver_mt = drivers[typ];
+ if not driver_mt then
+ return nil, "unsupported-store";
+ end
+ local t = env:txn_begin(nil, 0);
+ local db = t:dbi_open(store.."_"..typ, driver_mt.flags);
+ assert(t:commit());
+
+ return setmetatable({ env = env, store = store, type = typ, db = db }, driver_mt);
+end
+
+function module.unload()
+ env:sync(1);
+ env:close();
+end
+
+module:provides("storage");
--- a/mod_storage_memory/mod_storage_memory.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_storage_memory/mod_storage_memory.lua Mon Aug 10 21:13:31 2015 +0200
@@ -42,7 +42,10 @@
local archive_store = {};
archive_store.__index = archive_store;
-function archive_store:append(username, key, when, with, value)
+function archive_store:append(username, key, value, when, with)
+ if type(when) ~= "number" then
+ when, with, value = value, when, with;
+ end
local a = self.store[username];
if not a then
a = {};
@@ -116,7 +119,7 @@
i = old[i];
t = i.when;
if not(qstart >= t and qend <= t and (not qwith or i.with == qwith)) then
- self:append(username, i.key, t, i.with, i.value);
+ self:append(username, i.key, i.value, t, i.with);
end
end
if #new == 0 then
--- a/mod_storage_muc_log/mod_storage_muc_log.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_storage_muc_log/mod_storage_muc_log.lua Mon Aug 10 21:13:31 2015 +0200
@@ -48,7 +48,10 @@
return with and tag.name .. "<" .. with or tag.name;
end
-function driver:append(node, key, when, with, stanza)
+function driver:append(node, key, stanza, when, with)
+ if type(when) ~= "number" then
+ when, with, stanza = stanza, when, with;
+ end
local today = os_date(datef, when);
local now = os_date(timef, when);
local data = data_load(node, host, datastore .. "/" .. today) or {};
--- a/mod_storage_xmlarchive/mod_storage_xmlarchive.lua Fri Jul 31 18:46:27 2015 +0200
+++ b/mod_storage_xmlarchive/mod_storage_xmlarchive.lua Mon Aug 10 21:13:31 2015 +0200
@@ -1,3 +1,10 @@
+-- mod_storage_xmlarchive
+-- Copyright (C) 2015 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+--
+-- luacheck: ignore unused self
+
local dm = require "core.storagemanager".olddm;
local hmac_sha256 = require"util.hashes".hmac_sha256;
local st = require"util.stanza";
@@ -12,9 +19,9 @@
if not ok then
return ok, msg;
end
- f:seek("set", offset);
- return true;
+ return f:seek("set", offset);
end;
+
pcall(function()
local pposix = require "util.pposix";
fallocate = pposix.fallocate or fallocate;
@@ -23,32 +30,37 @@
local archive = {};
local archive_mt = { __index = archive };
-function archive:append(username, _, when, with, data)
+function archive:append(username, _, data, when, with)
+ if type(when) ~= "number" then
+ when, with, data = data, when, with;
+ end
if getmetatable(data) ~= st.stanza_mt then
+ module:log("error", "Attempt to store non-stanza object, traceback: %s", debug.traceback());
return nil, "unsupported-datatype";
end
+
username = username or "@";
data = tostring(data) .. "\n";
+
local day = dt.date(when);
local filename = dm.getpath(username.."@"..day, module.host, self.store, "xml", true);
+
local ok, err;
local f = io.open(filename, "r+");
if not f then
- f, err = io.open(filename, "w");
- if not f then return nil, err; end
+ f, err = io.open(filename, "w"); if not f then return nil, err; end
ok, err = dm.list_append(username, module.host, self.store, day);
if not ok then return nil, err; end
end
- local offset = f and f:seek("end");
- ok, err = fallocate(f, offset, #data);
- if not ok then return nil, err; end
- f:seek("set", offset);
- ok, err = f:write(data);
- if not ok then return nil, err; end
- ok, err = f:close();
- if not ok then return nil, err; end
+
+ local offset = f:seek("end"); -- Seek to the end and collect current file length
+ -- then make sure there is enough free space for what we're about to add
+ ok, err = fallocate(f, offset, #data); if not ok then return nil, err; end
+ ok, err = f:write(data); if not ok then return nil, err; end
+ ok, err = f:close(); if not ok then return nil, err; end
+
local id = day .. "-" .. hmac_sha256(username.."@"..day.."+"..offset, data, true):sub(-16);
- ok, err = dm.list_append(username.."@"..day, module.host, self.store, { id = id, when = when, with = with, offset = offset, length = #data });
+ ok, err = dm.list_append(username.."@"..day, module.host, self.store, { id = id, when = dt.datetime(when), with = with, offset = offset, length = #data });
if not ok then return nil, err; end
return id;
end
@@ -69,7 +81,13 @@
local stream = new_stream(stream_sess, { handlestanza = cb, stream_ns = "jabber:client"});
local dates = dm.list_load(username, module.host, self.store) or empty;
stream:feed(st.stanza("stream", { xmlns = "jabber:client" }):top_tag());
- stream_sess.notopen = nil;
+ local function reset_stream()
+ stream:reset();
+ stream_sess.notopen = true;
+ stream:feed(st.stanza("stream", { xmlns = "jabber:client" }):top_tag());
+ stream_sess.notopen = nil;
+ end
+ reset_stream();
local limit = query.limit;
local start_day, step, last_day = 1, 1, #dates;
@@ -100,10 +118,11 @@
return function ()
if limit and count >= limit then xmlfile:close() return; end
+ local filename;
for d = start_day, last_day, step do
if d ~= start_day or not items then
- module:log("debug", "Load items for %s", dates[d]);
+ module:log("debug", "Loading items from %s", dates[d]);
start_day = d;
items = dm.list_load(username .. "@" .. dates[d], module.host, self.store) or empty;
if not rev then
@@ -112,24 +131,33 @@
first_item, last_item = #items, 1;
end
local ferr;
- xmlfile, ferr = io.open(dm.getpath(username .. "@" .. dates[d], module.host, self.store, "xml"));
+ filename = dm.getpath(username .. "@" .. dates[d], module.host, self.store, "xml");
+ xmlfile, ferr = io.open(filename);
if not xmlfile then
module:log("error", "Error: %s", ferr);
return;
end
end
+ local q_with, q_start, q_end = query.with, query.start, query["end"];
for i = first_item, last_item, step do
- module:log("debug", "data[%q][%d]", dates[d], i);
local item = items[i];
+ local i_when, i_with = item.when, item.with;
+ if type(i_when) == "string" then
+ i_when = dt.parse(i_when);
+ end
+ if type(i_when) ~= "number" then
+ module:log("warn", "data[%q][%d].when is invalid", dates[d], i);
+ break;
+ end
if not item then
- module:log("debug", "data[%q][%d] is nil", dates[d], i);
+ module:log("warn", "data[%q][%d] is nil", dates[d], i);
break;
end
if xmlfile and in_range
- and (not query.with or item.with == query.with)
- and (not query.start or item.when >= query.start)
- and (not query["end"] or item.when <= query["end"]) then
+ and (not q_with or i_with == q_with)
+ and (not q_start or i_when >= q_start)
+ and (not q_end or i_when <= q_end) then
count = count + 1;
first_item = i + step;
@@ -137,12 +165,13 @@
local data = xmlfile:read(item.length);
local ok, err = stream:feed(data);
if not ok then
- module:log("warn", "Parse error: %s", err);
+ module:log("warn", "Parse error in %s at %d+%d: %s", filename, item.offset, item.length, err);
+ reset_stream();
end
if result then
local stanza = result;
result = nil;
- return item.id, stanza, item.when, item.with;
+ return item.id, stanza, i_when, i_with;
end
end
if (rev and item.id == query.after) or