TT#14103 update upstream modules to 2e1a4740adee

* refresh sipwise patches

Change-Id: Ic486faa84e78b3fdbcca403d4a87108a67a083e8
changes/89/12589/1
Victor Seva 10 years ago
parent 0521a0386c
commit 5b993fc443

@ -1,189 +0,0 @@
From 9e65f1029f84b0336f5096f6e56b27cb117ec568 Mon Sep 17 00:00:00 2001
From: Victor Seva <vseva@sipwise.com>
Date: Mon, 26 Dec 2016 14:42:46 +0100
Subject: [PATCH] TT#7482 mod_man: support mam version 0.5 (urn:xmpp:mam:1)
* keep compability with mam version 0.3 (urn:xmpp:mam:0)
Change-Id: I03dcc49c39b5eb6c38e30d27bcc3b93b8137c770
---
plugins/mod_mam/mamprefsxml.lib.lua | 3 +-
plugins/mod_mam/mod_mam.lua | 80 +++++++++++++++++++++++++++++--------
2 files changed, 64 insertions(+), 19 deletions(-)
diff --git a/plugins/mod_mam/mamprefsxml.lib.lua b/plugins/mod_mam/mamprefsxml.lib.lua
index a0d4624..4b3a50b 100644
--- a/plugins/mod_mam/mamprefsxml.lib.lua
+++ b/plugins/mod_mam/mamprefsxml.lib.lua
@@ -4,7 +4,6 @@
-- This file is MIT/X11 licensed.
local st = require"util.stanza";
-local xmlns_mam = "urn:xmpp:mam:0";
local default_attrs = {
always = true, [true] = "always",
@@ -12,7 +11,7 @@ local default_attrs = {
roster = "roster",
}
-local function tostanza(prefs)
+local function tostanza(prefs, xmlns_mam)
local default = prefs[false];
default = default_attrs[default];
local prefstanza = st.stanza("prefs", { xmlns = xmlns_mam, default = default });
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
index fe69e33..dbdd92a 100644
--- a/plugins/mod_mam/mod_mam.lua
+++ b/plugins/mod_mam/mod_mam.lua
@@ -3,7 +3,8 @@
--
-- This file is MIT/X11 licensed.
-local xmlns_mam = "urn:xmpp:mam:0";
+local xmlns_mam0 = "urn:xmpp:mam:0";
+local xmlns_mam1 = "urn:xmpp:mam:1";
local xmlns_delay = "urn:xmpp:delay";
local xmlns_forward = "urn:xmpp:forward:0";
@@ -54,11 +55,11 @@ end
local cleanup;
-- Handle prefs.
-module:hook("iq/self/"..xmlns_mam..":prefs", function(event)
+local function handle_prefs(event, xmlns_mam)
local origin, stanza = event.origin, event.stanza;
local user = origin.username;
if stanza.attr.type == "get" then
- local prefs = prefs_to_stanza(get_prefs(user));
+ local prefs = prefs_to_stanza(get_prefs(user), xmlns_mam);
local reply = st.reply(stanza):add_child(prefs);
origin.send(reply);
else -- type == "set"
@@ -72,24 +73,47 @@ module:hook("iq/self/"..xmlns_mam..":prefs", function(event)
end
end
return true;
+end
+
+module:hook("iq/self/"..xmlns_mam1..":prefs", function(event)
+ return handle_prefs(event, xmlns_mam1)
+end);
+
+module:hook("iq/self/"..xmlns_mam0..":prefs", function(event)
+ return handle_prefs(event, xmlns_mam0)
end);
-local query_form = dataform {
- { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam; };
+local query_form = {}
+query_form[xmlns_mam1] = dataform {
+ { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam1; };
+ { name = "with"; type = "jid-single"; };
+ { name = "start"; type = "text-single" };
+ { name = "end"; type = "text-single"; };
+};
+query_form[xmlns_mam0] = dataform {
+ { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam0; };
{ name = "with"; type = "jid-single"; };
{ name = "start"; type = "text-single" };
{ name = "end"; type = "text-single"; };
};
-- Serve form
-module:hook("iq-get/self/"..xmlns_mam..":query", function(event)
+local function get_form(event, xmlns_mam)
local origin, stanza = event.origin, event.stanza;
- origin.send(st.reply(stanza):add_child(query_form:form()));
+ origin.send(st.reply(stanza):add_child(query_form[xmlns_mam]:form()));
return true;
+end
+
+module:hook("iq-get/self/"..xmlns_mam1..":query", function(event)
+ return get_form(event, xmlns_mam1);
+end);
+
+module:hook("iq-get/self/"..xmlns_mam0..":query", function(event)
+ return get_form(event, xmlns_mam0);
end);
-- Handle archive queries
-module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
+local function handle_query(event, xmlns_mam)
local origin, stanza = event.origin, event.stanza;
local query = stanza.tags[1];
local qid = query.attr.queryid;
@@ -101,7 +125,7 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
local form = query:get_child("x", "jabber:x:data");
if form then
local err;
- form, err = query_form:data(form);
+ form, err = query_form[xmlns_mam]:data(form);
if err then
origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
return true;
@@ -145,9 +169,13 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
return true;
end
local total = tonumber(err);
+ local msg_reply_attr;
+
+ if xmlns_mam == xmlns_mam0 then
+ origin.send(st.reply(stanza));
+ msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
+ end
- origin.send(st.reply(stanza));
- local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
local results = {};
@@ -192,11 +220,27 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
-- That's all folks!
module:log("debug", "Archive query %s completed", tostring(qid));
- origin.send(st.message(msg_reply_attr)
- :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
- :add_child(rsm.generate {
- first = first, last = last, count = total }));
+ local reply_stanza;
+ if xmlns_mam == xmlns_mam0 then
+ reply_stanza = st.message(msg_reply_attr);
+ else
+ reply_stanza = st.reply(stanza);
+ end
+
+ if form or xmlns_mam == xmlns_mam0 then
+ reply_stanza:tag("fin",{ xmlns = xmlns_mam, queryid = qid, complete = complete })
+ :add_child(rsm.generate { first = first, last = last, count = total })
+ end
+ origin.send(reply_stanza);
return true;
+end
+
+module:hook("iq-set/self/"..xmlns_mam1..":query", function(event)
+ return handle_query(event, xmlns_mam1);
+end);
+
+module:hook("iq-set/self/"..xmlns_mam0..":query", function(event)
+ return handle_query(event, xmlns_mam0);
end);
local function has_in_roster(user, who)
@@ -330,9 +374,11 @@ module:hook("pre-message/full", c2s_message_handler, 2);
module:hook("message/bare", message_handler, 2);
module:hook("message/full", message_handler, 2);
-module:add_feature(xmlns_mam); -- COMPAT with XEP-0313 v 0.1
+module:add_feature(xmlns_mam0); -- COMPAT with XEP-0313 v 0.1
+module:add_feature(xmlns_mam1); -- COMPAT with XEP-0313 v 0.5
module:hook("account-disco-info", function(event)
- (event.reply or event.stanza):tag("feature", {var=xmlns_mam}):up();
+ (event.reply or event.stanza):tag("feature", {var=xmlns_mam0}):up();
+ (event.reply or event.stanza):tag("feature", {var=xmlns_mam1}):up();
end);
--
2.11.0

@ -1,27 +1,28 @@
From 754fb5b63c5d3b4125ec80b8ecfdfdd3676242b3 Mon Sep 17 00:00:00 2001
From 0dd1a7404c27b4a9352f196d52462b7ae6237590 Mon Sep 17 00:00:00 2001
From: Victor Seva <vseva@sipwise.com>
Date: Tue, 3 Jan 2017 17:14:49 +0100
Subject: [PATCH] TT#8297 mam: implement archive to DB
Change-Id: If9644ac66fdd257a430cfb7fa185230c77cd783e
---
plugins/mod_mam/mod_mam.lua | 16 +---
plugins/mod_mam/mod_mam.lua | 17 +---
plugins/mod_mam/sipwise_archive.lib.lua | 165 ++++++++++++++++++++++++++++++++
2 files changed, 166 insertions(+), 15 deletions(-)
2 files changed, 166 insertions(+), 16 deletions(-)
create mode 100644 plugins/mod_mam/sipwise_archive.lib.lua
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
index dbdd92a..d7f4f61 100644
index a4aa014..13e0a0b 100644
--- a/plugins/mod_mam/mod_mam.lua
+++ b/plugins/mod_mam/mod_mam.lua
@@ -37,21 +37,7 @@ if global_default_policy ~= "roster" then
@@ -40,22 +40,7 @@ if global_default_policy ~= "roster" then
global_default_policy = module:get_option_boolean("default_archive_policy", global_default_policy);
end
-local archive_store = "archive2";
-local archive = assert(module:open_store(archive_store, "archive"));
-local archive_store = module:get_option_string("archive_store", "archive2");
-local archive = module:open_store(archive_store, "archive");
-
-if archive.name == "null" or not archive.find then
- -- luacheck: ignore 631
- if not archive.find then
- module:log("debug", "Attempt to open archive storage returned a valid driver but it does not seem to implement the storage API");
- module:log("debug", "mod_%s does not support archiving", archive._provided_by or archive.name and "storage_"..archive.name.."(?)" or "<unknown>");

@ -1,33 +1,38 @@
From 7fc4d61ef370de25c69a0a637c0d0e2624e61dc0 Mon Sep 17 00:00:00 2001
From 9e01a7a10fc1cd8a2d7dc6fe65bdccccf90f8668 Mon Sep 17 00:00:00 2001
From: Victor Seva <vseva@sipwise.com>
Date: Thu, 19 Jan 2017 17:13:12 +0100
Subject: [PATCH] TT#9317 mod_mam: don't store bodyless chat messages
* implement store hints https://prosody.im/issues/issue/750
* implement store hints
https://prosody.im/issues/issue/750
mod_mam doesn't honor forced storage hint.
When a message without a body is received mod_mam always ignores the forced
storage hint, even if <store xmlns='urn:xmpp:hints'/> is present.
This can cause issue with OMEMO, which sends message elements without a body
Change-Id: I590b1fb9bd95afdce6a117778052d11d8102f718
---
plugins/mod_mam/mod_mam.lua | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
plugins/mod_mam/mod_mam.lua | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
index d7f4f61..bb604ed 100644
index 13e0a0b..8d8ef62 100644
--- a/plugins/mod_mam/mod_mam.lua
+++ b/plugins/mod_mam/mod_mam.lua
@@ -267,11 +267,25 @@ local function message_handler(event, c2s)
local orig_from = stanza.attr.from;
local orig_to = stanza.attr.to or orig_from;
-- Stanza without 'to' are treated as if it was to their own bare jid
-
@@ -269,19 +269,30 @@ local function message_handler(event, c2s)
return tag;
end);
- -- We store chat messages or normal messages that have a body
- if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body")) ) then
- log("debug", "Not archiving stanza: %s (type)", stanza:top_tag());
- return;
+ local body = stanza:get_child("body");
+ local force_store = stanza:get_child("store", "urn:xmpp:hints");
+
+ if not force_store then
- end
-
-- or if hints suggest we shouldn't
if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store
+ -- We store chat messages or normal messages that have a body
+ local body = stanza:get_child("body");
+ if not(orig_type == "chat" or (orig_type == "normal" and body)) then
+ log("debug", "Not archiving stanza: %s (type)", stanza:top_tag());
+ return;
@ -36,6 +41,12 @@ index d7f4f61..bb604ed 100644
+ stanza:top_tag());
+ return;
+ end
+
if stanza:get_child("no-permanent-store", "urn:xmpp:hints")
or stanza:get_child("no-store", "urn:xmpp:hints") then -- Hint telling us we should NOT store
log("debug", "Not archiving stanza: %s (hint)", stanza:top_tag());
return;
end
+ else
+ log("debug", "store hint detected");
+ if orig_type == 'error' then
@ -43,8 +54,8 @@ index d7f4f61..bb604ed 100644
+ return;
+ end
end
-- or if hints suggest we shouldn't
if stanza:get_child("no-permanent-storage", "urn:xmpp:hints") -- The XEP needs to decide on "store" or "storage"
-- Check with the users preferences
--
2.11.0

@ -1,5 +1,4 @@
mod_auth_sql.path
0001-TT-7482-mod_man-support-mam-version-0.5-urn-xmpp-mam.patch
0002-TT-8297-mam-implement-archive-to-DB.patch
0003-TT-9303-mod_mam-sipwise_archive.lib-fix-crash-at-fin.patch
0004-TT-9311-mod_mam-sipwise_archive.lib-full-username.patch

@ -69,7 +69,7 @@ Query size limits
This is the largest number of messages that are allowed to be retrieved
in one request *page*. A query that does not fit in one page will
include a reference to the next page, letting clients page through the
result set. Setting large number is not recomended, as Prosody will be
result set. Setting large number is not recommended, as Prosody will be
blocked while processing the request and will not be able to do anything
else.
@ -117,12 +117,13 @@ default_archive_policy = true
Compatibility
=============
------- ---------------
trunk Works
0.10 Works
------- -----------------------
trunk Included with Prosody
0.10 Included with Prosody
0.9 Works
0.8 Does not work
------- ---------------
------- -----------------------
[^1]: Might be changed to "mam" at some point

@ -65,7 +65,6 @@ function archive_store:delete(username, query)
local archive = store[username];
if not archive then return true; end -- no messages, nothing to delete
local start, stop, step = 1, archive[0] or #archive, 1;
local qstart = query.start or -math.huge;
local qend = query["end"] or math.huge;
local qwith = query.with;

@ -2,10 +2,12 @@
-- Copyright (C) 2011-2013 Kim Alvefur
--
-- This file is MIT/X11 licensed.
-- luacheck: ignore 122/prosody
local global_default_policy = module:get_option("default_archive_policy", true);
do
-- luacheck: ignore 211/prefs_format
local prefs_format = {
[false] = "roster",
-- default ::= true | false | "roster"
@ -15,8 +17,9 @@ do
};
end
local sessions = hosts[module.host].sessions;
local prefs = module:open_store("archive2_prefs");
local sessions = prosody.hosts[module.host].sessions;
local archive_store = module:get_option_string("archive_store", "archive");
local prefs = module:open_store(archive_store .. "_prefs");
local function get_prefs(user)
local user_sessions = sessions[user];

@ -4,7 +4,6 @@
-- This file is MIT/X11 licensed.
local st = require"util.stanza";
local xmlns_mam = "urn:xmpp:mam:0";
local default_attrs = {
always = true, [true] = "always",
@ -12,7 +11,7 @@ local default_attrs = {
roster = "roster",
}
local function tostanza(prefs)
local function tostanza(prefs, xmlns_mam)
local default = prefs[false];
default = default_attrs[default];
local prefstanza = st.stanza("prefs", { xmlns = xmlns_mam, default = default });

@ -3,9 +3,12 @@
--
-- This file is MIT/X11 licensed.
local xmlns_mam = "urn:xmpp:mam:0";
local xmlns_mam0 = "urn:xmpp:mam:0";
local xmlns_mam1 = "urn:xmpp:mam:1";
local xmlns_mam2 = "urn:xmpp:mam:2";
local xmlns_delay = "urn:xmpp:delay";
local xmlns_forward = "urn:xmpp:forward:0";
local xmlns_st_id = "urn:xmpp:sid:0";
local um = require "core.usermanager";
local st = require "util.stanza";
@ -16,6 +19,7 @@ local prefs_to_stanza = module:require"mamprefsxml".tostanza;
local prefs_from_stanza = module:require"mamprefsxml".fromstanza;
local jid_bare = require "util.jid".bare;
local jid_split = require "util.jid".split;
local jid_prepped_split = require "util.jid".prepped_split;
local dataform = require "util.dataforms".new;
local host = module.host;
@ -36,10 +40,11 @@ if global_default_policy ~= "roster" then
global_default_policy = module:get_option_boolean("default_archive_policy", global_default_policy);
end
local archive_store = "archive2";
local archive = assert(module:open_store(archive_store, "archive"));
local archive_store = module:get_option_string("archive_store", "archive2");
local archive = module:open_store(archive_store, "archive");
if archive.name == "null" or not archive.find then
-- luacheck: ignore 631
if not archive.find then
module:log("debug", "Attempt to open archive storage returned a valid driver but it does not seem to implement the storage API");
module:log("debug", "mod_%s does not support archiving", archive._provided_by or archive.name and "storage_"..archive.name.."(?)" or "<unknown>");
@ -54,11 +59,12 @@ end
local cleanup;
-- Handle prefs.
module:hook("iq/self/"..xmlns_mam..":prefs", function(event)
local function handle_prefs(event)
local origin, stanza = event.origin, event.stanza;
local xmlns_mam = stanza.tags[1].attr.xmlns;
local user = origin.username;
if stanza.attr.type == "get" then
local prefs = prefs_to_stanza(get_prefs(user));
local prefs = prefs_to_stanza(get_prefs(user), xmlns_mam);
local reply = st.reply(stanza):add_child(prefs);
origin.send(reply);
else -- type == "set"
@ -72,25 +78,36 @@ module:hook("iq/self/"..xmlns_mam..":prefs", function(event)
end
end
return true;
end);
end
module:hook("iq/self/"..xmlns_mam0..":prefs", handle_prefs);
module:hook("iq/self/"..xmlns_mam1..":prefs", handle_prefs);
module:hook("iq/self/"..xmlns_mam2..":prefs", handle_prefs);
local query_form = dataform {
{ name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam; };
{ name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam0; };
{ name = "with"; type = "jid-single"; };
{ name = "start"; type = "text-single" };
{ name = "end"; type = "text-single"; };
};
-- Serve form
module:hook("iq-get/self/"..xmlns_mam..":query", function(event)
local function handle_get_form(event)
local origin, stanza = event.origin, event.stanza;
origin.send(st.reply(stanza):add_child(query_form:form()));
local xmlns_mam = stanza.tags[1].attr.xmlns;
query_form[1].value = xmlns_mam;
origin.send(st.reply(stanza):query(xmlns_mam):add_child(query_form:form()));
return true;
end);
end
module:hook("iq-get/self/"..xmlns_mam0..":query", handle_get_form);
module:hook("iq-get/self/"..xmlns_mam1..":query", handle_get_form);
module:hook("iq-get/self/"..xmlns_mam2..":query", handle_get_form);
-- Handle archive queries
module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
local function handle_mam_query(event)
local origin, stanza = event.origin, event.stanza;
local xmlns_mam = stanza.tags[1].attr.xmlns;
local query = stanza.tags[1];
local qid = query.attr.queryid;
@ -101,6 +118,7 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
local form = query:get_child("x", "jabber:x:data");
if form then
local err;
query_form[1].value = xmlns_mam;
form, err = query_form:data(form);
if err then
origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
@ -129,7 +147,6 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
local before, after = qset and qset.before, qset and qset.after;
if type(before) ~= "string" then before = nil; end
-- Load all the data!
local data, err = archive:find(origin.username, {
start = qstart; ["end"] = qend; -- Time range
@ -146,7 +163,9 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
end
local total = tonumber(err);
origin.send(st.reply(stanza));
if xmlns_mam == xmlns_mam0 then
origin.send(st.reply(stanza));
end
local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
local results = {};
@ -192,12 +211,23 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
-- That's all folks!
module:log("debug", "Archive query %s completed", tostring(qid));
origin.send(st.message(msg_reply_attr)
:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
local fin;
if xmlns_mam == xmlns_mam0 then
fin = st.message(msg_reply_attr);
else
fin = st.reply(stanza);
end
do
fin:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
:add_child(rsm.generate {
first = first, last = last, count = total }));
first = first, last = last, count = total })
end
origin.send(fin);
return true;
end);
end
module:hook("iq-set/self/"..xmlns_mam0..":query", handle_mam_query);
module:hook("iq-set/self/"..xmlns_mam1..":query", handle_mam_query);
module:hook("iq-set/self/"..xmlns_mam2..":query", handle_mam_query);
local function has_in_roster(user, who)
local roster = rm_load_roster(user, host);
@ -238,32 +268,46 @@ local function message_handler(event, c2s)
local orig_to = stanza.attr.to or orig_from;
-- Stanza without 'to' are treated as if it was to their own bare jid
-- Whos storage do we put it in?
local store_user = c2s and origin.username or jid_split(orig_to);
-- And who are they chatting with?
local with = jid_bare(c2s and orig_to or orig_from);
-- Filter out <stanza-id> that claim to be from us
stanza:maptags(function (tag)
if tag.name == "stanza-id" and tag.attr.xmlns == xmlns_st_id then
local by_user, by_host, res = jid_prepped_split(tag.attr.by);
if not res and by_host == module.host and by_user == store_user then
return nil;
end
end
return tag;
end);
-- We store chat messages or normal messages that have a body
if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body")) ) then
log("debug", "Not archiving stanza: %s (type)", stanza:top_tag());
return;
end
-- or if hints suggest we shouldn't
if 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
log("debug", "Not archiving stanza: %s (hint)", stanza:top_tag());
return;
if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store
if stanza:get_child("no-permanent-store", "urn:xmpp:hints")
or stanza:get_child("no-store", "urn:xmpp:hints") then -- Hint telling us we should NOT store
log("debug", "Not archiving stanza: %s (hint)", stanza:top_tag());
return;
end
end
-- Whos storage do we put it in?
local store_user = c2s and origin.username or jid_split(orig_to);
-- And who are they chatting with?
local with = jid_bare(c2s and orig_to or orig_from);
-- Check with the users preferences
if shall_store(store_user, with) then
log("debug", "Archiving stanza: %s", stanza:top_tag());
-- And stash it
local ok, id = archive:append(store_user, nil, stanza, time_now(), with);
local ok = archive:append(store_user, nil, stanza, time_now(), with);
if ok then
local id = ok;
stanza:tag("stanza-id", { xmlns = xmlns_st_id, by = store_user.."@"..host, id = id }):up();
if cleanup then cleanup[store_user] = true; end
module:fire_event("archive-message-added", { origin = origin, stanza = stanza, for_user = store_user, id = id });
end
@ -276,6 +320,18 @@ local function c2s_message_handler(event)
return message_handler(event, true);
end
local function strip_stanza_id(event)
local strip_by = jid_bare(event.origin.full_jid);
event.stanza:maptags(function(tag)
if not ( tag.attr.xmlns == xmlns_st_id and tag.attr.by == strip_by ) then
return tag;
end
end);
end
module:hook("pre-message/bare", strip_stanza_id, 0.01);
module:hook("pre-message/full", strip_stanza_id, 0.01);
local cleanup_after = module:get_option_string("archive_expires_after", "1w");
local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60);
if cleanup_after ~= "never" then
@ -296,14 +352,20 @@ if cleanup_after ~= "never" then
return false;
end
-- Set of known users to do message expiry for
-- Populated either below or when new messages are added
cleanup = {};
-- Iterating over users is not supported by all authentication modules
-- Catch and ignore error if not supported
pcall(function ()
-- If this works, then we schedule cleanup for all known users on startup
for user in um.users(module.host) do
cleanup[user] = true;
end
end);
-- At odd intervals, delete old messages for one user
module:add_timer(math.random(10, 60), function()
local user = next(cleanup);
if user then
@ -330,9 +392,12 @@ module:hook("pre-message/full", c2s_message_handler, 2);
module:hook("message/bare", message_handler, 2);
module:hook("message/full", message_handler, 2);
module:add_feature(xmlns_mam); -- COMPAT with XEP-0313 v 0.1
module:add_feature(xmlns_mam0); -- COMPAT with XEP-0313 v 0.1
module:hook("account-disco-info", function(event)
(event.reply or event.stanza):tag("feature", {var=xmlns_mam}):up();
(event.reply or event.stanza):tag("feature", {var=xmlns_mam0}):up();
(event.reply or event.stanza):tag("feature", {var=xmlns_mam1}):up();
(event.reply or event.stanza):tag("feature", {var=xmlns_mam2}):up();
(event.reply or event.stanza):tag("feature", {var=xmlns_st_id}):up();
end);

@ -39,36 +39,45 @@ These events are: "smacks-ack-delayed", "smacks-hibernation-start" and
"smacks-hibernation-end". See [mod_cloud_notify] for details on how this
events are used there.
Use prosody 0.10+ to have per user limits on allowed sessions in hibernation
state and allowed sessions for which the h-value is kept even after the
hibernation timed out.
These are settable using `smacks_max_hibernated_sessions` and `smacks_max_old_sessions`.
Configuration
=============
Option Default Description
------------------------------ ----------------- -----------------------------------------------------------------------------------------
`smacks_hibernation_time` 300 (5 minutes) The number of seconds a disconnected session should stay alive for (to allow reconnect)
`smacks_enabled_s2s` false Enable Stream Management on server connections? *Experimental*
`smacks_max_unacked_stanzas` 0 How many stanzas to send before requesting acknowledgement
`smacks_max_ack_delay` 60 (1 minute) The number of seconds an ack must be unanswered to trigger an "smacks-ack-delayed" event
Option Default Description
---------------------------------- ----------------- ------------------------------------------------------------------------------------------------------------------
`smacks_hibernation_time` 300 (5 minutes) The number of seconds a disconnected session should stay alive for (to allow reconnect)
`smacks_enabled_s2s` false Enable Stream Management on server connections? *Experimental*
`smacks_max_unacked_stanzas` 0 How many stanzas to send before requesting acknowledgement
`smacks_max_ack_delay` 60 (1 minute) The number of seconds an ack must be unanswered to trigger an "smacks-ack-delayed" event
`smacks_max_hibernated_sessions` 10 The number of allowed sessions in hibernated state (limited per user)
`smacks_max_old_sessions` 10 The number of allowed sessions with timed out hibernation for which the h-value is still kept (limited per user)
Compatibility
=============
----- -----------------------------------
----- -----------------------------------------------------------------------------
0.10 Works
0.9 Works
0.8 Works, use version [7693724881b3]
----- -----------------------------------
0.9 Works, no per user limit of hibernated sessions
0.8 Works, no per user limit of hibernated sessions, use version [7693724881b3]
----- -----------------------------------------------------------------------------
Clients
=======
Clients that support XEP-0198:
Clients that support [XEP-0198]:
- Gajim
- Gajim (Linux, Windows, OS X)
- Conversations (Android)
- ChatSecure (iOS)
- Swift (but not resumption, as of version 2.0 and alphas of 3.0)
- Psi (in an unreleased branch)
- Conversations
- Yaxim
- Yaxim (Android)
- Monal (iOS)
[7693724881b3]: //hg.prosody.im/prosody-modules/raw-file/7693724881b3/mod_smacks/mod_smacks.lua
[mod_smacks_offline]: //modules.prosody.im/mod_smacks_offline

@ -5,13 +5,15 @@
-- Copyright (C) 2012-2015 Kim Alvefur
-- Copyright (C) 2012 Thijs Alkemade
-- Copyright (C) 2014 Florian Zeitz
-- Copyright (C) 2016 Thilo Molitor
-- Copyright (C) 2016-2017 Thilo Molitor
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local st = require "util.stanza";
local dep = require "util.dependencies";
local cache = dep.softreq("util.cache"); -- only available in prosody 0.10+
local uuid_generate = require "util.uuid".generate;
local t_insert, t_remove = table.insert, table.remove;
@ -35,18 +37,74 @@ local s2s_smacks = module:get_option_boolean("smacks_enabled_s2s", false);
local s2s_resend = module:get_option_boolean("smacks_s2s_resend", false);
local max_unacked_stanzas = module:get_option_number("smacks_max_unacked_stanzas", 0);
local delayed_ack_timeout = module:get_option_number("smacks_max_ack_delay", 60);
local max_hibernated_sessions = module:get_option_number("smacks_max_hibernated_sessions", 10);
local max_old_sessions = module:get_option_number("smacks_max_old_sessions", 10);
local core_process_stanza = prosody.core_process_stanza;
local sessionmanager = require"core.sessionmanager";
local c2s_sessions = module:shared("/*/c2s/sessions");
local session_registry = {};
local function init_session_cache(max_entries, evict_callback)
-- old prosody version < 0.10 (no limiting at all!)
if not cache then
local store = {};
return {
get = function(user, key) return store[key]; end;
set = function(user, key, value) store[key] = value; end;
};
end
-- use per user limited cache for prosody >= 0.10
local stores = {};
return {
get = function(user, key)
if not stores[user] then
stores[user] = cache.new(max_entries, evict_callback);
end
return stores[user]:get(key);
end;
set = function(user, key, value)
if not stores[user] then stores[user] = cache.new(max_entries, evict_callback); end
stores[user]:set(key, value);
-- remove empty caches completely
if not stores[user]:count() then stores[user] = nil; end
end;
};
end
local old_session_registry = init_session_cache(max_old_sessions, nil);
local session_registry = init_session_cache(max_hibernated_sessions, function(resumption_token, session)
if session.destroyed then return; end
session.log("warn", "User has too much hibernated sessions, removing oldest session (token: %s)", resumption_token);
-- store old session's h values on force delete
-- save only actual h value and username/host (for security)
old_session_registry.set(session.username, resumption_token, {
h = session.handled_stanza_count,
username = session.username,
host = session.host
});
return true; -- allow session to be removed from full cache to make room for new one
end);
local function stoppable_timer(delay, callback)
local stopped = false;
return {
stop = function () stopped = true end;
module:add_timer(delay, function (t)
if stopped then return; end
return callback(t);
end);
};
end
local function delayed_ack_function(session)
-- fire event only when configured to do so
if delayed_ack_timeout > 0 and session.awaiting_ack and not session.outgoing_stanza_queue == nil then
session.log("debug", "Firing event 'smacks-ack-delayed', queue = %d", #session.outgoing_stanza_queue);
-- fire event only if configured to do so and our session is not hibernated or destroyed
if delayed_ack_timeout > 0 and session.awaiting_ack
and not session.hibernating and not session.destroyed then
session.log("debug", "Firing event 'smacks-ack-delayed', queue = %d",
session.outgoing_stanza_queue and #session.outgoing_stanza_queue or 0);
module:fire_event("smacks-ack-delayed", {origin = session, queue = session.outgoing_stanza_queue});
end
session.delayed_ack_timer = nil;
end
local function can_do_smacks(session, advertise_only)
@ -80,6 +138,38 @@ module:hook("s2s-stream-features",
end
end);
local function request_ack_if_needed(session, force)
local queue = session.outgoing_stanza_queue;
if session.awaiting_ack == nil then
if (#queue > max_unacked_stanzas and session.last_queue_count ~= #queue) or force then
session.log("debug", "Queuing <r> (in a moment)");
session.awaiting_ack = false;
session.awaiting_ack_timer = stoppable_timer(1e-06, function ()
if not session.awaiting_ack then
session.log("debug", "Sending <r> (inside timer, before send)");
(session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }))
session.log("debug", "Sending <r> (inside timer, after send)");
session.awaiting_ack = true;
if not session.delayed_ack_timer then
session.delayed_ack_timer = stoppable_timer(delayed_ack_timeout, function()
delayed_ack_function(session);
end);
end
end
end);
end
-- Trigger "smacks-ack-delayed"-event if we added new (ackable) stanzas to the outgoing queue
-- and there isn't already a timer for this event running.
-- If we wouldn't do this, stanzas added to the queue after the first "smacks-ack-delayed"-event
-- would not trigger this event (again).
if #queue > max_unacked_stanzas and session.awaiting_ack and session.delayed_ack_timer == nil then
session.log("debug", "Calling delayed_ack_function directly (still waiting for ack)");
delayed_ack_function(session);
end
end
session.last_queue_count = #queue;
end
local function outgoing_stanza_filter(stanza, session)
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
@ -97,21 +187,7 @@ local function outgoing_stanza_filter(stanza, session)
session.log("debug", "hibernating, stanza queued");
return nil;
end
if #queue > max_unacked_stanzas and session.awaiting_ack == nil then
session.log("debug", "Queuing <r> (in a moment)");
session.awaiting_ack = false;
session.awaiting_ack_timer = module:add_timer(1e-06, function ()
if not session.awaiting_ack then
session.log("debug", "Sending <r> (before send)");
(session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }))
session.log("debug", "Sending <r> (after send)");
session.awaiting_ack = true;
session.delayed_ack_timer = module:add_timer(delayed_ack_timeout, function()
delayed_ack_function(session);
end);
end
end);
end
request_ack_if_needed(session, false);
end
return stanza;
end
@ -135,9 +211,14 @@ local function wrap_session_out(session, resume)
local session_close = session.close;
function session.close(...)
if session.resumption_token then
session_registry[session.resumption_token] = nil;
session_registry.set(session.username, session.resumption_token, nil);
old_session_registry.set(session.username, session.resumption_token, nil);
session.resumption_token = nil;
end
-- send out last ack as per revision 1.5.2 of XEP-0198
if session.smacks and session.conn then
(session.sends2s or session.send)(st.stanza("a", { xmlns = session.smacks, h = tostring(session.handled_stanza_count) }));
end
return session_close(...);
end
return session;
@ -175,7 +256,7 @@ function handle_enable(session, stanza, xmlns_sm)
local resume = stanza.attr.resume;
if resume == "true" or resume == "1" then
resume_token = uuid_generate();
session_registry[resume_token] = session;
session_registry.set(session.username, resume_token, session);
session.resumption_token = resume_token;
end
(session.sends2s or session.send)(st.stanza("enabled", { xmlns = xmlns_sm, id = resume_token, resume = resume }));
@ -186,7 +267,7 @@ module:hook_stanza(xmlns_sm3, "enable", function (session, stanza) return handle
module:hook_stanza("http://etherx.jabber.org/streams", "features",
function (session, stanza)
module:add_timer(1e-6, function ()
stoppable_timer(1e-6, function ()
if can_do_smacks(session) then
if stanza:get_child("sm", xmlns_sm3) then
session.sends2s(st.stanza("enable", sm3_attr));
@ -236,9 +317,10 @@ function handle_a(origin, stanza)
end
if origin.delayed_ack_timer then
origin.delayed_ack_timer:stop();
origin.delayed_ack_timer = nil;
end
-- Remove handled stanzas from outgoing_stanza_queue
--log("debug", "ACK: h=%s, last=%s", stanza.attr.h or "", origin.last_acknowledged_stanza or "");
-- origin.log("debug", "ACK: h=%s, last=%s", stanza.attr.h or "", origin.last_acknowledged_stanza or "");
local h = tonumber(stanza.attr.h);
if not h then
origin:close{ condition = "invalid-xml"; text = "Missing or invalid 'h' attribute"; };
@ -258,6 +340,7 @@ function handle_a(origin, stanza)
end
origin.log("debug", "#queue = %d", #queue);
origin.last_acknowledged_stanza = origin.last_acknowledged_stanza + handled_stanza_count;
request_ack_if_needed(origin, false)
return true;
end
module:hook_stanza(xmlns_sm2, "a", handle_a);
@ -314,7 +397,13 @@ module:hook("pre-resource-unbind", function (event)
-- otherwise the session resumed and re-hibernated.
and session.hibernating == hibernate_time then
session.log("debug", "Destroying session for hibernating too long");
session_registry[session.resumption_token] = nil;
session_registry.set(session.username, session.resumption_token, nil);
-- save only actual h value and username/host (for security)
old_session_registry.set(session.username, session.resumption_token, {
h = session.handled_stanza_count,
username = session.username,
host = session.host
});
session.resumption_token = nil;
sessionmanager.destroy_session(session);
else
@ -323,7 +412,6 @@ module:hook("pre-resource-unbind", function (event)
end);
return true; -- Postpone destruction for now
end
end
end);
@ -356,12 +444,21 @@ function handle_resume(session, stanza, xmlns_sm)
end
local id = stanza.attr.previd;
local original_session = session_registry[id];
local original_session = session_registry.get(session.username, id);
if not original_session then
session.log("debug", "Tried to resume non-existent session with id %s", id);
session.send(st.stanza("failed", { xmlns = xmlns_sm })
:tag("item-not-found", { xmlns = xmlns_errors })
);
local old_session = old_session_registry.get(session.username, id);
if old_session and session.username == old_session.username
and session.host == old_session.host
and old_session.h then
session.send(st.stanza("failed", { xmlns = xmlns_sm, h = tostring(old_session.h) })
:tag("item-not-found", { xmlns = xmlns_errors })
);
else
session.send(st.stanza("failed", { xmlns = xmlns_sm })
:tag("item-not-found", { xmlns = xmlns_errors })
);
end;
elseif session.username == original_session.username
and session.host == original_session.host then
session.log("debug", "mod_smacks resuming existing session...");
@ -375,6 +472,7 @@ function handle_resume(session, stanza, xmlns_sm)
original_session.ip = session.ip;
original_session.conn = session.conn;
original_session.send = session.send;
original_session.close = session.close;
original_session.filter = session.filter;
original_session.filter.session = original_session;
original_session.filters = session.filters;
@ -387,7 +485,7 @@ function handle_resume(session, stanza, xmlns_sm)
-- Similar for connlisteners
c2s_sessions[session.conn] = original_session;
session.send(st.stanza("resumed", { xmlns = xmlns_sm,
original_session.send(st.stanza("resumed", { xmlns = xmlns_sm,
h = original_session.handled_stanza_count, previd = id }));
-- Fake an <a> with the h of the <resume/> from the client
@ -397,12 +495,17 @@ function handle_resume(session, stanza, xmlns_sm)
-- Ok, we need to re-send any stanzas that the client didn't see
-- ...they are what is now left in the outgoing stanza queue
local queue = original_session.outgoing_stanza_queue;
module:fire_event("smacks-hibernation-end", {origin = session, queue = queue});
session.log("debug", "#queue = %d", #queue);
module:fire_event("smacks-hibernation-end", {origin = session, resumed = original_session, queue = queue});
original_session.log("debug", "#queue = %d", #queue);
for i=1,#queue do
session.send(queue[i]);
original_session.send(queue[i]);
end
session.log("debug", "#queue = %d -- after send", #queue);
original_session.log("debug", "#queue = %d -- after send", #queue);
function session.send(stanza)
session.log("warn", "Tried to send stanza on old session migrated by smacks resume (maybe there is a bug?): %s", tostring(stanza));
return false;
end
request_ack_if_needed(original_session, true);
else
module:log("warn", "Client %s@%s[%s] tried to resume stream for %s@%s[%s]",
session.username or "?", session.host or "?", session.type,
@ -424,6 +527,7 @@ local function handle_read_timeout(event)
end
if session.delayed_ack_timer then
session.delayed_ack_timer:stop();
session.delayed_ack_timer = nil;
end
return false; -- Kick the session
end
@ -431,9 +535,11 @@ local function handle_read_timeout(event)
session.awaiting_ack = false;
(session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }));
session.awaiting_ack = true;
session.delayed_ack_timer = module:add_timer(delayed_ack_timeout, function()
delayed_ack_function(session);
end);
if not session.delayed_ack_timer then
session.delayed_ack_timer = stoppable_timer(delayed_ack_timeout, function()
delayed_ack_function(session);
end);
end
return true;
end
end

@ -1 +1 @@
51cf82d36a8a
2e1a4740adee

Loading…
Cancel
Save