aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/mod_offline.lua56
-rw-r--r--plugins/storage/ejabberdstore.lib.lua190
-rw-r--r--plugins/storage/mod_storage.lua83
-rw-r--r--plugins/storage/sqlbasic.lib.lua97
-rw-r--r--plugins/storage/xep227store.lib.lua168
-rw-r--r--plugins/storage/xmlparse.lib.lua56
-rwxr-xr-xprosody3
7 files changed, 650 insertions, 3 deletions
diff --git a/plugins/mod_offline.lua b/plugins/mod_offline.lua
new file mode 100644
index 00000000..24aef9ed
--- /dev/null
+++ b/plugins/mod_offline.lua
@@ -0,0 +1,56 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+local datamanager = require "util.datamanager";
+local st = require "util.stanza";
+local datetime = require "util.datetime";
+local ipairs = ipairs;
+local jid_split = require "util.jid".split;
+
+module:add_feature("msgoffline");
+
+module:hook("message/offline/store", function(event)
+ local origin, stanza = event.origin, event.stanza;
+ local to = stanza.attr.to;
+ local node, host;
+ if to then
+ node, host = jid_split(to)
+ else
+ node, host = origin.username, origin.host;
+ end
+
+ stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();
+ local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));
+ stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+
+ return true;
+end);
+
+module:hook("message/offline/broadcast", function(event)
+ local origin = event.origin;
+ local node, host = origin.username, origin.host;
+
+ local data = datamanager.list_load(node, host, "offline");
+ if not data then return true; end
+ for _, stanza in ipairs(data) do
+ stanza = st.deserialize(stanza);
+ 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;
+ origin.send(stanza);
+ end
+ return true;
+end);
+
+module:hook("message/offline/delete", function(event)
+ local origin = event.origin;
+ local node, host = origin.username, origin.host;
+
+ return datamanager.list_store(node, host, "offline", nil);
+end);
diff --git a/plugins/storage/ejabberdstore.lib.lua b/plugins/storage/ejabberdstore.lib.lua
new file mode 100644
index 00000000..7e8592a8
--- /dev/null
+++ b/plugins/storage/ejabberdstore.lib.lua
@@ -0,0 +1,190 @@
+
+local handlers = {};
+
+handlers.accounts = {
+ get = function(self, user)
+ local select = self:query("select password from users where username=?", user);
+ local row = select and select:fetch();
+ if row then return { password = row[1] }; end
+ end;
+ set = function(self, user, data)
+ if data and data.password then
+ return self:modify("update users set password=? where username=?", data.password, user)
+ or self:modify("insert into users (username, password) values (?, ?)", user, data.password);
+ else
+ return self:modify("delete from users where username=?", user);
+ end
+ end;
+};
+handlers.vcard = {
+ get = function(self, user)
+ local select = self:query("select vcard from vcard where username=?", user);
+ local row = select and select:fetch();
+ if row then return parse_xml(row[1]); end
+ end;
+ set = function(self, user, data)
+ if data then
+ data = unparse_xml(data);
+ return self:modify("update vcard set vcard=? where username=?", data, user)
+ or self:modify("insert into vcard (username, vcard) values (?, ?)", user, data);
+ else
+ return self:modify("delete from vcard where username=?", user);
+ end
+ end;
+};
+handlers.private = {
+ get = function(self, user)
+ local select = self:query("select namespace,data from private_storage where username=?", user);
+ if select then
+ local data = {};
+ for row in select:rows() do
+ data[row[1]] = parse_xml(row[2]);
+ end
+ return data;
+ end
+ end;
+ set = function(self, user, data)
+ if data then
+ self:modify("delete from private_storage where username=?", user);
+ for namespace,text in pairs(data) do
+ self:modify("insert into private_storage (username, namespace, data) values (?, ?, ?)", user, namespace, unparse_xml(text));
+ end
+ return true;
+ else
+ return self:modify("delete from private_storage where username=?", user);
+ end
+ end;
+ -- TODO map_set, map_get
+};
+local subscription_map = { N = "none", B = "both", F = "from", T = "to" };
+local subscription_map_reverse = { none = "N", both = "B", from = "F", to = "T" };
+handlers.roster = {
+ get = function(self, user)
+ local select = self:query("select jid,nick,subscription,ask,server,subscribe,type from rosterusers where username=?", user);
+ if select then
+ local roster = { pending = {} };
+ for row in select:rows() do
+ local jid,nick,subscription,ask,server,subscribe,typ = unpack(row);
+ local item = { groups = {} };
+ if nick == "" then nick = nil; end
+ item.nick = nick;
+ item.subscription = subscription_map[subscription];
+ if ask == "N" then ask = nil;
+ elseif ask == "O" then ask = "subscribe"
+ elseif ask == "I" then roster.pending[jid] = true; ask = nil;
+ elseif ask == "B" then roster.pending[jid] = true; ask = "subscribe";
+ else module:log("debug", "bad roster_item.ask: %s", ask); ask = nil; end
+ item.ask = ask;
+ roster[jid] = item;
+ end
+
+ select = self:query("select jid,grp from rostergroups where username=?", user);
+ if select then
+ for row in select:rows() do
+ local jid,grp = unpack(rows);
+ if roster[jid] then roster[jid].groups[grp] = true; end
+ end
+ end
+ select = self:query("select version from roster_version where username=?", user);
+ local row = select and select:fetch();
+ if row then
+ roster[false] = { version = row[1]; };
+ end
+ return roster;
+ end
+ end;
+ set = function(self, user, data)
+ if data and next(data) ~= nil then
+ self:modify("delete from rosterusers where username=?", user);
+ self:modify("delete from rostergroups where username=?", user);
+ self:modify("delete from roster_version where username=?", user);
+ local done = {};
+ local pending = data.pending or {};
+ for jid,item in pairs(data) do
+ if jid and jid ~= "pending" then
+ local subscription = subscription_map_reverse[item.subscription];
+ local ask;
+ if pending[jid] then
+ if item.ask then ask = "B"; else ask = "I"; end
+ else
+ if item.ask then ask = "O"; else ask = "N"; end
+ end
+ local r = self:modify("insert into rosterusers (username,jid,nick,subscription,ask,askmessage,server,subscribe) values (?, ?, ?, ?, ?, '', '', '')", user, jid, item.nick or "", subscription, ask);
+ if not r then module:log("debug", "--- :( %s", tostring(r)); end
+ done[jid] = true;
+ for group in pairs(item.groups) do
+ self:modify("insert into rostergroups (username,jid,grp) values (?, ?, ?)", user, jid, group);
+ end
+ end
+ end
+ for jid in pairs(pending) do
+ if not done[jid] then
+ self:modify("insert into rosterusers (username,jid,nick,subscription,ask,askmessage,server,subscribe) values (?, ?, ?, ?, ?. ''. ''. '')", user, jid, "", "N", "I");
+ end
+ end
+ local version = data[false] and data[false].version;
+ if version then
+ self:modify("insert into roster_version (username,version) values (?, ?)", user, version);
+ end
+ return true;
+ else
+ self:modify("delete from rosterusers where username=?", user);
+ self:modify("delete from rostergroups where username=?", user);
+ self:modify("delete from roster_version where username=?", user);
+ end
+ end;
+};
+
+-----------------------------
+local driver = {};
+driver.__index = driver;
+
+function driver:prepare(sql)
+ module:log("debug", "query: %s", sql);
+ local err;
+ if not self.sqlcache then self.sqlcache = {}; end
+ local r = self.sqlcache[sql];
+ if r then return r; end
+ r, err = self.database:prepare(sql);
+ if not r then error("Unable to prepare SQL statement: "..err); end
+ self.sqlcache[sql] = r;
+ return r;
+end
+
+function driver:query(sql, ...)
+ local stmt = self:prepare(sql);
+ if stmt:execute(...) then return stmt; end
+end
+function driver:modify(sql, ...)
+ local stmt = self:query(sql, ...);
+ if stmt and stmt:affected() > 0 then return stmt; end
+end
+
+function driver:open(host, datastore, typ)
+ local cache_key = host.." "..datastore;
+ if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end
+ local instance = setmetatable({}, self);
+ instance.host = host;
+ instance.datastore = datastore;
+ local handler = handlers[datastore];
+ if not handler then return nil; end
+ for key,val in pairs(handler) do
+ instance[key] = val;
+ end
+ if instance.init then instance:init(); end
+ self.ds_cache[cache_key] = instance;
+ return instance;
+end
+
+-----------------------------
+local _M = {};
+
+function _M.new(dbtype, dbname, ...)
+ local instance = setmetatable({}, driver);
+ instance.__index = instance;
+ instance.database = get_database(dbtype, dbname, ...);
+ instance.ds_cache = {};
+ return instance;
+end
+
+return _M;
diff --git a/plugins/storage/mod_storage.lua b/plugins/storage/mod_storage.lua
new file mode 100644
index 00000000..e22de82c
--- /dev/null
+++ b/plugins/storage/mod_storage.lua
@@ -0,0 +1,83 @@
+
+module:set_global();
+
+local cache = { data = {} };
+function cache:get(key) return self.data[key]; end
+function cache:set(key, val) self.data[key] = val; return val; end
+
+local DBI = require "DBI";
+function get_database(driver, db, ...)
+ local uri = "dbi:"..driver..":"..db;
+ return cache:get(uri) or cache:set(uri, (function(...)
+ module:log("debug", "Opening database: %s", uri);
+ prosody.unlock_globals();
+ local dbh = assert(DBI.Connect(...));
+ prosody.lock_globals();
+ dbh:autocommit(true)
+ return dbh;
+ end)(driver, db, ...));
+end
+
+local st = require "util.stanza";
+local _parse_xml = module:require("xmlparse");
+parse_xml_real = _parse_xml;
+function parse_xml(str)
+ local s = _parse_xml(str);
+ if s and not s.gsub then
+ return st.preserialize(s);
+ end
+end
+function unparse_xml(s)
+ return tostring(st.deserialize(s));
+end
+
+local drivers = {};
+
+--local driver = module:require("sqlbasic").new("SQLite3", "hello.sqlite");
+local option_datastore = module:get_option("datastore");
+local option_datastore_params = module:get_option("datastore_params") or {};
+if option_datastore then
+ local driver = module:require(option_datastore).new(unpack(option_datastore_params));
+ table.insert(drivers, driver);
+end
+
+local datamanager = require "util.datamanager";
+local olddm = {};
+local dm = {};
+for key,val in pairs(datamanager) do olddm[key] = val; end
+
+do -- driver based on old datamanager
+ local dmd = {};
+ dmd.__index = dmd;
+ function dmd:open(host, datastore)
+ return setmetatable({ host = host, datastore = datastore }, dmd);
+ end
+ function dmd:get(user) return olddm.load(user, self.host, self.datastore); end
+ function dmd:set(user, data) return olddm.store(user, self.host, self.datastore, data); end
+ table.insert(drivers, dmd);
+end
+
+local function open(...)
+ for _,driver in pairs(drivers) do
+ local ds = driver:open(...);
+ if ds then return ds; end
+ end
+end
+
+local _data_path;
+--function dm.set_data_path(path) _data_path = path; end
+--function dm.add_callback(...) end
+--function dm.remove_callback(...) end
+--function dm.getpath(...) end
+function dm.load(username, host, datastore)
+ local x = open(host, datastore);
+ return x:get(username);
+end
+function dm.store(username, host, datastore, data)
+ return open(host, datastore):set(username, data);
+end
+--function dm.list_append(...) return driver:list_append(...); end
+--function dm.list_store(...) return driver:list_store(...); end
+--function dm.list_load(...) return driver:list_load(...); end
+
+for key,val in pairs(dm) do datamanager[key] = val; end
diff --git a/plugins/storage/sqlbasic.lib.lua b/plugins/storage/sqlbasic.lib.lua
new file mode 100644
index 00000000..f1202287
--- /dev/null
+++ b/plugins/storage/sqlbasic.lib.lua
@@ -0,0 +1,97 @@
+
+-- Basic SQL driver
+-- This driver stores data as simple key-values
+
+local ser = require "util.serialization".serialize;
+local deser = function(data)
+ module:log("debug", "deser: %s", tostring(data));
+ if not data then return nil; end
+ local f = loadstring("return "..data);
+ if not f then return nil; end
+ setfenv(f, {});
+ local s, d = pcall(f);
+ if not s then return nil; end
+ return d;
+end;
+
+local driver = {};
+driver.__index = driver;
+
+driver.item_table = "item";
+driver.list_table = "list";
+
+function driver:prepare(sql)
+ module:log("debug", "query: %s", sql);
+ local err;
+ if not self.sqlcache then self.sqlcache = {}; end
+ local r = self.sqlcache[sql];
+ if r then return r; end
+ r, err = self.connection:prepare(sql);
+ if not r then error("Unable to prepare SQL statement: "..err); end
+ self.sqlcache[sql] = r;
+ return r;
+end
+
+function driver:load(username, host, datastore)
+ local select = self:prepare("select data from "..self.item_table.." where username=? and host=? and datastore=?");
+ select:execute(username, host, datastore);
+ local row = select:fetch();
+ return row and deser(row[1]) or nil;
+end
+
+function driver:store(username, host, datastore, data)
+ if not data or next(data) == nil then
+ local delete = self:prepare("delete from "..self.item_table.." where username=? and host=? and datastore=?");
+ delete:execute(username, host, datastore);
+ return true;
+ else
+ local d = self:load(username, host, datastore);
+ if d then -- update
+ local update = self:prepare("update "..self.item_table.." set data=? where username=? and host=? and datastore=?");
+ return update:execute(ser(data), username, host, datastore);
+ else -- insert
+ local insert = self:prepare("insert into "..self.item_table.." values (?, ?, ?, ?)");
+ return insert:execute(username, host, datastore, ser(data));
+ end
+ end
+end
+
+function driver:list_append(username, host, datastore, data)
+ if not data then return; end
+ local insert = self:prepare("insert into "..self.list_table.." values (?, ?, ?, ?)");
+ return insert:execute(username, host, datastore, ser(data));
+end
+
+function driver:list_store(username, host, datastore, data)
+ -- remove existing data
+ local delete = self:prepare("delete from "..self.list_table.." where username=? and host=? and datastore=?");
+ delete:execute(username, host, datastore);
+ if data and next(data) ~= nil then
+ -- add data
+ for _, d in ipairs(data) do
+ self:list_append(username, host, datastore, ser(d));
+ end
+ end
+ return true;
+end
+
+function driver:list_load(username, host, datastore)
+ local select = self:prepare("select data from "..self.list_table.." where username=? and host=? and datastore=?");
+ select:execute(username, host, datastore);
+ local r = {};
+ for row in select:rows() do
+ table.insert(r, deser(row[1]));
+ end
+ return r;
+end
+
+local _M = {};
+function _M.new(dbtype, dbname, ...)
+ local d = {};
+ setmetatable(d, driver);
+ local dbh = get_database(dbtype, dbname, ...);
+ --d:set_connection(dbh);
+ d.connection = dbh;
+ return d;
+end
+return _M;
diff --git a/plugins/storage/xep227store.lib.lua b/plugins/storage/xep227store.lib.lua
new file mode 100644
index 00000000..5ef8df54
--- /dev/null
+++ b/plugins/storage/xep227store.lib.lua
@@ -0,0 +1,168 @@
+
+local st = require "util.stanza";
+
+local function getXml(user, host)
+ local jid = user.."@"..host;
+ local path = "data/"..jid..".xml";
+ local f = io.open(path);
+ if not f then return; end
+ local s = f:read("*a");
+ return parse_xml_real(s);
+end
+local function setXml(user, host, xml)
+ local jid = user.."@"..host;
+ local path = "data/"..jid..".xml";
+ if xml then
+ local f = io.open(path, "w");
+ if not f then return; end
+ local s = tostring(xml);
+ f:write(s);
+ f:close();
+ return true;
+ else
+ return os.remove(path);
+ end
+end
+local function getUserElement(xml)
+ if xml and xml.name == "server-data" then
+ local host = xml.tags[1];
+ if host and host.name == "host" then
+ local user = host.tags[1];
+ if user and user.name == "user" then
+ return user;
+ end
+ end
+ end
+end
+local function createOuterXml(user, host)
+ return st.stanza("server-data", {xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'})
+ :tag("host", {jid=host})
+ :tag("user", {name = user});
+end
+local function removeFromArray(array, value)
+ for i,item in ipairs(array) do
+ if item == value then
+ table.remove(array, i);
+ return;
+ end
+ end
+end
+local function removeStanzaChild(s, child)
+ removeFromArray(s.tags, child);
+ removeFromArray(s, child);
+end
+
+local handlers = {};
+
+handlers.accounts = {
+ get = function(self, user)
+ local user = getUserElement(getXml(user, self.host));
+ if user and user.attr.password then
+ return { password = user.attr.password };
+ end
+ end;
+ set = function(self, user, data)
+ if data and data.password then
+ local xml = getXml(user, self.host);
+ if not xml then xml = createOuterXml(user, self.host); end
+ local usere = getUserElement(xml);
+ usere.attr.password = data.password;
+ return setXml(user, self.host, xml);
+ else
+ return setXml(user, self.host, nil);
+ end
+ end;
+};
+handlers.vcard = {
+ get = function(self, user)
+ local user = getUserElement(getXml(user, self.host));
+ if user then
+ local vcard = user:get_child("vCard", 'vcard-temp');
+ if vcard then
+ return st.preserialize(vcard);
+ end
+ end
+ end;
+ set = function(self, user, data)
+ local xml = getXml(user, self.host);
+ local usere = xml and getUserElement(xml);
+ if usere then
+ local vcard = usere:get_child("vCard", 'vcard-temp');
+ if vcard then
+ removeStanzaChild(usere, vcard);
+ elseif not data then
+ return true;
+ end
+ if data then
+ vcard = st.deserialize(data);
+ usere:add_child(vcard);
+ end
+ return setXml(user, self.host, xml);
+ end
+ return true;
+ end;
+};
+handlers.private = {
+ get = function(self, user)
+ local user = getUserElement(getXml(user, self.host));
+ if user then
+ local private = user:get_child("query", "jabber:iq:private");
+ if private then
+ local r = {};
+ for _, tag in ipairs(private.tags) do
+ r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag);
+ end
+ return r;
+ end
+ end
+ end;
+ set = function(self, user, data)
+ local xml = getXml(user, self.host);
+ local usere = xml and getUserElement(xml);
+ if usere then
+ local private = usere:get_child("query", 'jabber:iq:private');
+ if private then removeStanzaChild(usere, private); end
+ if data and next(data) ~= nil then
+ private = st.stanza("query", {xmlns='jabber:iq:private'});
+ for _,tag in pairs(data) do
+ private:add_child(st.deserialize(tag));
+ end
+ usere:add_child(private);
+ end
+ return setXml(user, self.host, xml);
+ end
+ return true;
+ end;
+};
+
+-----------------------------
+local driver = {};
+driver.__index = driver;
+
+function driver:open(host, datastore, typ)
+ local cache_key = host.." "..datastore;
+ if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end
+ local instance = setmetatable({}, self);
+ instance.host = host;
+ instance.datastore = datastore;
+ local handler = handlers[datastore];
+ if not handler then return nil; end
+ for key,val in pairs(handler) do
+ instance[key] = val;
+ end
+ if instance.init then instance:init(); end
+ self.ds_cache[cache_key] = instance;
+ return instance;
+end
+
+-----------------------------
+local _M = {};
+
+function _M.new()
+ local instance = setmetatable({}, driver);
+ instance.__index = instance;
+ instance.ds_cache = {};
+ return instance;
+end
+
+return _M;
diff --git a/plugins/storage/xmlparse.lib.lua b/plugins/storage/xmlparse.lib.lua
new file mode 100644
index 00000000..91063995
--- /dev/null
+++ b/plugins/storage/xmlparse.lib.lua
@@ -0,0 +1,56 @@
+
+local st = require "util.stanza";
+
+-- XML parser
+local parse_xml = (function()
+ local entity_map = setmetatable({
+ ["amp"] = "&";
+ ["gt"] = ">";
+ ["lt"] = "<";
+ ["apos"] = "'";
+ ["quot"] = "\"";
+ }, {__index = function(_, s)
+ if s:sub(1,1) == "#" then
+ if s:sub(2,2) == "x" then
+ return string.char(tonumber(s:sub(3), 16));
+ else
+ return string.char(tonumber(s:sub(2)));
+ end
+ end
+ end
+ });
+ local function xml_unescape(str)
+ return (str:gsub("&(.-);", entity_map));
+ end
+ local function parse_tag(s)
+ local name,sattr=(s):gmatch("([^%s]+)(.*)")();
+ local attr = {};
+ for a,b in (sattr):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr[a] = xml_unescape(b); end
+ return name, attr;
+ end
+ return function(xml)
+ local stanza = st.stanza("root");
+ local regexp = "<([^>]*)>([^<]*)";
+ for elem, text in xml:gmatch(regexp) do
+ if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions
+ elseif elem:sub(1,1) == "/" then -- end tag
+ elem = elem:sub(2);
+ stanza:up(); -- TODO check for start-end tag name match
+ elseif elem:sub(-1,-1) == "/" then -- empty tag
+ elem = elem:sub(1,-2);
+ local name,attr = parse_tag(elem);
+ stanza:tag(name, attr):up();
+ else -- start tag
+ local name,attr = parse_tag(elem);
+ stanza:tag(name, attr);
+ end
+ if #text ~= 0 then -- text
+ stanza:text(xml_unescape(text));
+ end
+ end
+ return stanza.tags[1];
+ end
+end)();
+-- end of XML parser
+
+return parse_xml;
diff --git a/prosody b/prosody
index b13369a9..4b016e50 100755
--- a/prosody
+++ b/prosody
@@ -22,9 +22,6 @@ if CFG_SOURCEDIR then
package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
end
-package.path = package.path..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.lua";
-package.cpath = package.cpath..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.so";
-
-- Substitute ~ with path to home directory in data path
if CFG_DATADIR then
if os.getenv("HOME") then