aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/offlinemanager.lua32
-rw-r--r--core/stanza_router.lua27
-rw-r--r--util/datamanager.lua69
-rw-r--r--util/datetime.lua28
-rw-r--r--util/stanza.lua3
5 files changed, 150 insertions, 9 deletions
diff --git a/core/offlinemanager.lua b/core/offlinemanager.lua
new file mode 100644
index 00000000..283de5e3
--- /dev/null
+++ b/core/offlinemanager.lua
@@ -0,0 +1,32 @@
+
+local datamanager = require "util.datamanager";
+local st = require "util.stanza";
+local datetime = require "util.datetime";
+local ipairs = ipairs;
+
+module "offlinemanager"
+
+function store(node, host, stanza)
+ stanza.attr.stamp = datetime.datetime();
+ stanza.attr.stamp_legacy = datetime.legacy();
+ return datamanager.list_append(node, host, "offline", st.preserialize(stanza));
+end
+
+function load(node, host)
+ local data = datamanager.list_load(node, host, "offline");
+ if not data then return; end
+ for k, v in ipairs(data) do
+ stanza = st.deserialize(v);
+ stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203
+ stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)
+ stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+ data[k] = stanza;
+ end
+ return data;
+end
+
+function deleteAll(node, host)
+ return datamanager.list_store(node, host, "offline", nil);
+end
+
+return _M;
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index 9ae98f1c..1793f547 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -13,6 +13,7 @@ local user_exists = require "core.usermanager".user_exists;
local rostermanager = require "core.rostermanager";
local sessionmanager = require "core.sessionmanager";
+local offlinemanager = require "core.offlinemanager";
local s2s_verify_dialback = require "core.s2smanager".verify_dialback;
local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
@@ -149,6 +150,10 @@ function core_handle_stanza(origin, stanza)
core_route_stanza(origin, request);
end
end
+ for _, msg in ipairs(offlinemanager.load(node, host) or {}) do
+ origin.send(msg); -- FIXME do we need to modify to/from in any way?
+ end
+ offlinemanager.deleteAll(node, host);
end
origin.priority = 0;
if stanza.attr.type == "unavailable" then
@@ -168,11 +173,16 @@ function core_handle_stanza(origin, stanza)
end
stanza.attr.to = nil; -- reset it
else
- -- TODO error, bad type
+ log("warn", "Unhandled c2s presence: %s", tostring(stanza));
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error?
end
+ else
+ log("warn", "Unhandled c2s stanza: %s", tostring(stanza));
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error?
end -- TODO handle other stanzas
else
- log("warn", "Unhandled origin: %s", origin.type); -- FIXME reply with error
+ log("warn", "Unhandled origin: %s", origin.type);
+ origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error?
end
end
@@ -328,8 +338,14 @@ function core_route_stanza(origin, stanza)
t_insert(recipients, session);
end
end
+ local count = 0;
for _, session in pairs(recipients) do
session.send(stanza);
+ count = count + 1;
+ end
+ if count == 0 then
+ offlinemanager.store(node, host, stanza);
+ -- TODO deal with storage errors
end
else
-- TODO send IQ error
@@ -349,7 +365,12 @@ function core_route_stanza(origin, stanza)
-- TODO send unavailable presence or unsubscribed
end
elseif stanza.name == "message" then
- -- TODO send message error, or store offline messages
+ if stanza.attr.type == "chat" or stanza.attr.type == "normal" or not stanza.attr.type then
+ offlinemanager.store(node, host, stanza);
+ -- FIXME don't store messages with only chat state notifications
+ end
+ -- TODO allow configuration of offline storage
+ -- TODO send error if not storing offline
elseif stanza.name == "iq" then
-- TODO send IQ error
end
diff --git a/util/datamanager.lua b/util/datamanager.lua
index aad370d1..0f00da1b 100644
--- a/util/datamanager.lua
+++ b/util/datamanager.lua
@@ -1,6 +1,6 @@
local format = string.format;
local setmetatable, type = setmetatable, type;
-local pairs = pairs;
+local pairs, ipairs = pairs, ipairs;
local char = string.char;
local loadfile, setfenv, pcall = loadfile, setfenv, pcall;
local log = log;
@@ -9,6 +9,7 @@ local os_remove = os.remove;
local tostring = tostring;
local error = error;
local next = next;
+local t_insert = table.insert;
local indent = function(f, i)
for n = 1, i do
@@ -69,13 +70,14 @@ end
------- API -------------
-function getpath(username, host, datastore)
+function getpath(username, host, datastore, ext)
+ ext = ext or "dat";
if username then
- return format("data/%s/%s/%s.dat", encode(host), datastore, encode(username));
+ return format("data/%s/%s/%s.%s", encode(host), datastore, encode(username), ext);
elseif host then
- return format("data/%s/%s.dat", encode(host), datastore);
+ return format("data/%s/%s.%s", encode(host), datastore, ext);
else
- return format("data/%s.dat", datastore);
+ return format("data/%s.%s", datastore, ext);
end
end
@@ -115,4 +117,59 @@ function store(username, host, datastore, data)
return true;
end
-return _M; \ No newline at end of file
+function list_append(username, host, datastore, data)
+ if not data then return; end
+ -- save the datastore
+ local f, msg = io_open(getpath(username, host, datastore, "list"), "a+");
+ if not f then
+ log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
+ return;
+ end
+ f:write("item(");
+ simplesave(f, data, 1);
+ f:write(");\n");
+ f:close();
+ return true;
+end
+
+function list_store(username, host, datastore, data)
+ if not data then
+ data = {};
+ end
+ -- save the datastore
+ local f, msg = io_open(getpath(username, host, datastore, "list"), "w+");
+ if not f then
+ log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
+ return;
+ end
+ for _, d in ipairs(data) do
+ f:write("item(");
+ simplesave(f, d, 1);
+ f:write(");\n");
+ end
+ f:close();
+ if not next(data) then -- try to delete empty datastore
+ os_remove(getpath(username, host, datastore, "list"));
+ end
+ -- we write data even when we are deleting because lua doesn't have a
+ -- platform independent way of checking for non-exisitng files
+ return true;
+end
+
+function list_load(username, host, datastore)
+ local data, ret = loadfile(getpath(username, host, datastore, "list"));
+ if not data then
+ log("warn", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
+ return nil;
+ end
+ local items = {};
+ setfenv(data, {item = function(i) t_insert(items, i); end});
+ local success, ret = pcall(data);
+ if not success then
+ log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
+ return nil;
+ end
+ return items;
+end
+
+return _M;
diff --git a/util/datetime.lua b/util/datetime.lua
new file mode 100644
index 00000000..077cbb67
--- /dev/null
+++ b/util/datetime.lua
@@ -0,0 +1,28 @@
+-- XEP-0082: XMPP Date and Time Profiles
+
+local os_date = os.date;
+local error = error;
+
+module "datetime"
+
+function date()
+ return os_date("!%Y-%m-%d");
+end
+
+function datetime()
+ return os_date("!%Y-%m-%dT%H:%M:%SZ");
+end
+
+function time()
+ return os_date("!%H:%M:%S");
+end
+
+function legacy()
+ return os_date("!%Y%m%dT%H:%M:%S");
+end
+
+function parse(s)
+ error("datetime.parse: Not implemented"); -- TODO
+end
+
+return _M;
diff --git a/util/stanza.lua b/util/stanza.lua
index 5339b91e..cfa33c5b 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -157,6 +157,9 @@ function deserialize(stanza)
end
stanza.tags = tags;
end
+ if not stanza.last_add then
+ stanza.last_add = {};
+ end
end
return stanza;