aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/storage
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/storage')
-rw-r--r--plugins/storage/ejabberd_init.lib.lua252
-rw-r--r--plugins/storage/mod_ejabberd.lua231
2 files changed, 483 insertions, 0 deletions
diff --git a/plugins/storage/ejabberd_init.lib.lua b/plugins/storage/ejabberd_init.lib.lua
new file mode 100644
index 00000000..91f8563b
--- /dev/null
+++ b/plugins/storage/ejabberd_init.lib.lua
@@ -0,0 +1,252 @@
+
+local t_concat = table.concat;
+local t_insert = table.insert;
+local pairs = pairs;
+local DBI = require "DBI";
+
+local sqlite = true;
+local q = {};
+
+local function set(key, val)
+-- t_insert(q, "SET "..key.."="..val..";\n")
+end
+local function create_table(name, fields)
+ t_insert(q, "CREATE TABLE ".."IF NOT EXISTS "..name.." (\n");
+ for _, field in pairs(fields) do
+ t_insert(q, "\t");
+ field = t_concat(field, " ");
+ if sqlite then
+ if field:lower():match("^primary key *%(") then field = field:gsub("%(%d+%)", ""); end
+ end
+ t_insert(q, field);
+ if _ ~= #fields then t_insert(q, ",\n"); end
+ t_insert(q, "\n");
+ end
+ if sqlite then
+ t_insert(q, ");\n");
+ else
+ t_insert(q, ") CHARACTER SET utf8;\n");
+ end
+end
+local function create_index(name, index)
+ --t_insert(q, "CREATE INDEX "..name.." ON "..index..";\n");
+end
+local function create_unique_index(name, index)
+ --t_insert(q, "CREATE UNIQUE INDEX "..name.." ON "..index..";\n");
+end
+local function insert(target, value)
+ t_insert(q, "INSERT INTO "..target.."\nVALUES "..value..";\n");
+end
+local function foreign_key(name, fkey, fname, fcol)
+ t_insert(q, "ALTER TABLE `"..name.."` ADD FOREIGN KEY (`"..fkey.."`) REFERENCES `"..fname.."` (`"..fcol.."`) ON DELETE CASCADE;\n");
+end
+
+function build_query()
+ q = {};
+ set('table_type', 'InnoDB');
+ create_table('hosts', {
+ {'clusterid','integer','NOT','NULL'};
+ {'host','varchar(250)','NOT','NULL','PRIMARY','KEY'};
+ {'config','text','NOT','NULL'};
+ });
+ insert("hosts (clusterid, host, config)", "(1, 'localhost', '')");
+ create_table('users', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'password','text','NOT','NULL'};
+ {'created_at','timestamp','NOT','NULL','DEFAULT','CURRENT_TIMESTAMP'};
+ {'PRIMARY','KEY','(host, username)'};
+ });
+ create_table('last', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'seconds','text','NOT','NULL'};
+ {'state','text','NOT','NULL'};
+ {'PRIMARY','KEY','(host, username)'};
+ });
+ create_table('rosterusers', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'jid','varchar(250)','NOT','NULL'};
+ {'nick','text','NOT','NULL'};
+ {'subscription','character(1)','NOT','NULL'};
+ {'ask','character(1)','NOT','NULL'};
+ {'askmessage','text','NOT','NULL'};
+ {'server','character(1)','NOT','NULL'};
+ {'subscribe','text','NOT','NULL'};
+ {'type','text'};
+ {'created_at','timestamp','NOT','NULL','DEFAULT','CURRENT_TIMESTAMP'};
+ {'PRIMARY','KEY','(host(75), username(75), jid(75))'};
+ });
+ create_index('i_rosteru_username', 'rosterusers(username)');
+ create_index('i_rosteru_jid', 'rosterusers(jid)');
+ create_table('rostergroups', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'jid','varchar(250)','NOT','NULL'};
+ {'grp','text','NOT','NULL'};
+ {'PRIMARY','KEY','(host(75), username(75), jid(75))'};
+ });
+ --[[create_table('spool', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'xml','text','NOT','NULL'};
+ {'seq','BIGINT','UNSIGNED','NOT','NULL','AUTO_INCREMENT','UNIQUE'};
+ {'created_at','timestamp','NOT','NULL','DEFAULT','CURRENT_TIMESTAMP'};
+ {'PRIMARY','KEY','(host, username, seq)'};
+ });]]
+ create_table('vcard', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'vcard','text','NOT','NULL'};
+ {'created_at','timestamp','NOT','NULL','DEFAULT','CURRENT_TIMESTAMP'};
+ {'PRIMARY','KEY','(host, username)'};
+ });
+ create_table('vcard_search', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'lusername','varchar(250)','NOT','NULL'};
+ {'fn','text','NOT','NULL'};
+ {'lfn','varchar(250)','NOT','NULL'};
+ {'family','text','NOT','NULL'};
+ {'lfamily','varchar(250)','NOT','NULL'};
+ {'given','text','NOT','NULL'};
+ {'lgiven','varchar(250)','NOT','NULL'};
+ {'middle','text','NOT','NULL'};
+ {'lmiddle','varchar(250)','NOT','NULL'};
+ {'nickname','text','NOT','NULL'};
+ {'lnickname','varchar(250)','NOT','NULL'};
+ {'bday','text','NOT','NULL'};
+ {'lbday','varchar(250)','NOT','NULL'};
+ {'ctry','text','NOT','NULL'};
+ {'lctry','varchar(250)','NOT','NULL'};
+ {'locality','text','NOT','NULL'};
+ {'llocality','varchar(250)','NOT','NULL'};
+ {'email','text','NOT','NULL'};
+ {'lemail','varchar(250)','NOT','NULL'};
+ {'orgname','text','NOT','NULL'};
+ {'lorgname','varchar(250)','NOT','NULL'};
+ {'orgunit','text','NOT','NULL'};
+ {'lorgunit','varchar(250)','NOT','NULL'};
+ {'PRIMARY','KEY','(host, lusername)'};
+ });
+ create_index('i_vcard_search_lfn ', 'vcard_search(lfn)');
+ create_index('i_vcard_search_lfamily ', 'vcard_search(lfamily)');
+ create_index('i_vcard_search_lgiven ', 'vcard_search(lgiven)');
+ create_index('i_vcard_search_lmiddle ', 'vcard_search(lmiddle)');
+ create_index('i_vcard_search_lnickname', 'vcard_search(lnickname)');
+ create_index('i_vcard_search_lbday ', 'vcard_search(lbday)');
+ create_index('i_vcard_search_lctry ', 'vcard_search(lctry)');
+ create_index('i_vcard_search_llocality', 'vcard_search(llocality)');
+ create_index('i_vcard_search_lemail ', 'vcard_search(lemail)');
+ create_index('i_vcard_search_lorgname ', 'vcard_search(lorgname)');
+ create_index('i_vcard_search_lorgunit ', 'vcard_search(lorgunit)');
+ create_table('privacy_default_list', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)'};
+ {'name','varchar(250)','NOT','NULL'};
+ {'PRIMARY','KEY','(host, username)'};
+ });
+ --[[create_table('privacy_list', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'name','varchar(250)','NOT','NULL'};
+ {'id','BIGINT','UNSIGNED','NOT','NULL','AUTO_INCREMENT','UNIQUE'};
+ {'created_at','timestamp','NOT','NULL','DEFAULT','CURRENT_TIMESTAMP'};
+ {'PRIMARY','KEY','(host, username, name)'};
+ });]]
+ create_table('privacy_list_data', {
+ {'id','bigint'};
+ {'t','character(1)','NOT','NULL'};
+ {'value','text','NOT','NULL'};
+ {'action','character(1)','NOT','NULL'};
+ {'ord','NUMERIC','NOT','NULL'};
+ {'match_all','boolean','NOT','NULL'};
+ {'match_iq','boolean','NOT','NULL'};
+ {'match_message','boolean','NOT','NULL'};
+ {'match_presence_in','boolean','NOT','NULL'};
+ {'match_presence_out','boolean','NOT','NULL'};
+ });
+ create_table('private_storage', {
+ {'host','varchar(250)','NOT','NULL'};
+ {'username','varchar(250)','NOT','NULL'};
+ {'namespace','varchar(250)','NOT','NULL'};
+ {'data','text','NOT','NULL'};
+ {'created_at','timestamp','NOT','NULL','DEFAULT','CURRENT_TIMESTAMP'};
+ {'PRIMARY','KEY','(host(75), username(75), namespace(75))'};
+ });
+ create_index('i_private_storage_username USING BTREE', 'private_storage(username)');
+ create_table('roster_version', {
+ {'username','varchar(250)','PRIMARY','KEY'};
+ {'version','text','NOT','NULL'};
+ });
+ --[[create_table('pubsub_node', {
+ {'host','text'};
+ {'node','text'};
+ {'parent','text'};
+ {'type','text'};
+ {'nodeid','bigint','auto_increment','primary','key'};
+ });
+ create_index('i_pubsub_node_parent', 'pubsub_node(parent(120))');
+ create_unique_index('i_pubsub_node_tuple', 'pubsub_node(host(20), node(120))');
+ create_table('pubsub_node_option', {
+ {'nodeid','bigint'};
+ {'name','text'};
+ {'val','text'};
+ });
+ create_index('i_pubsub_node_option_nodeid', 'pubsub_node_option(nodeid)');
+ foreign_key('pubsub_node_option', 'nodeid', 'pubsub_node', 'nodeid');
+ create_table('pubsub_node_owner', {
+ {'nodeid','bigint'};
+ {'owner','text'};
+ });
+ create_index('i_pubsub_node_owner_nodeid', 'pubsub_node_owner(nodeid)');
+ foreign_key('pubsub_node_owner', 'nodeid', 'pubsub_node', 'nodeid');
+ create_table('pubsub_state', {
+ {'nodeid','bigint'};
+ {'jid','text'};
+ {'affiliation','character(1)'};
+ {'subscriptions','text'};
+ {'stateid','bigint','auto_increment','primary','key'};
+ });
+ create_index('i_pubsub_state_jid', 'pubsub_state(jid(60))');
+ create_unique_index('i_pubsub_state_tuple', 'pubsub_state(nodeid, jid(60))');
+ foreign_key('pubsub_state', 'nodeid', 'pubsub_node', 'nodeid');
+ create_table('pubsub_item', {
+ {'nodeid','bigint'};
+ {'itemid','text'};
+ {'publisher','text'};
+ {'creation','text'};
+ {'modification','text'};
+ {'payload','text'};
+ });
+ create_index('i_pubsub_item_itemid', 'pubsub_item(itemid(36))');
+ create_unique_index('i_pubsub_item_tuple', 'pubsub_item(nodeid, itemid(36))');
+ foreign_key('pubsub_item', 'nodeid', 'pubsub_node', 'nodeid');
+ create_table('pubsub_subscription_opt', {
+ {'subid','text'};
+ {'opt_name','varchar(32)'};
+ {'opt_value','text'};
+ });
+ create_unique_index('i_pubsub_subscription_opt', 'pubsub_subscription_opt(subid(32), opt_name(32))');]]
+ return t_concat(q);
+end
+
+local function init(dbh)
+ local q = build_query();
+ for statement in q:gmatch("[^;]*;") do
+ statement = statement:gsub("\n", ""):gsub("\t", " ");
+ if sqlite then
+ statement = statement:gsub("AUTO_INCREMENT", "AUTOINCREMENT");
+ statement = statement:gsub("auto_increment", "autoincrement");
+ end
+ local result, err = DBI.Do(dbh, statement);
+ if not result then
+ print("X", result, err);
+ print("Y", statement);
+ end
+ end
+end
+
+local _M = { init = init };
+return _M;
diff --git a/plugins/storage/mod_ejabberd.lua b/plugins/storage/mod_ejabberd.lua
new file mode 100644
index 00000000..92caecbc
--- /dev/null
+++ b/plugins/storage/mod_ejabberd.lua
@@ -0,0 +1,231 @@
+
+local setmetatable = setmetatable;
+local error = error;
+local unpack = unpack;
+local module = module;
+local tostring = tostring;
+local pairs, next = pairs, next;
+local prosody = prosody;
+local assert = assert;
+local require = require;
+
+-- connect to db
+local DBI = require "DBI";
+local option_datastore_params = module:get_option("datastore_params") or error("Missing option: datastore_params");
+local database;
+do
+ local driver, db = unpack(option_datastore_params);
+ module:log("debug", "Opening database: %s", "dbi:"..driver..":"..db);
+ prosody.unlock_globals();
+ local dbh, err = DBI.Connect(unpack(option_datastore_params));
+ prosody.lock_globals();
+ assert(dbh, err);
+ dbh:autocommit(true)
+ database = dbh;
+end
+
+-- initialize db
+local ejabberd_init = module:require("ejabberd_init");
+ejabberd_init.init(database);
+
+local st = require "util.stanza";
+local _parse_xml = module:require("xmlparse");
+local parse_xml_real = _parse_xml;
+local function parse_xml(str)
+ local s = _parse_xml(str);
+ if s and not s.gsub then
+ return st.preserialize(s);
+ end
+end
+local function unparse_xml(s)
+ return tostring(st.deserialize(s));
+end
+
+
+local handlers = {};
+
+handlers.accounts = {
+ get = function(self, user)
+ local select = self:query("select password from users where username=? and host=?", user, self.host);
+ 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=? and host=?", data.password, user, self.host)
+ or self:modify("insert into users (username, host, password) values (?, ?, ?)", user, self.host, data.password);
+ else
+ return self:modify("delete from users where username=? and host=?", user, self.host);
+ end
+ end;
+};
+handlers.vcard = {
+ get = function(self, user)
+ local select = self:query("select vcard from vcard where username=? and host=?", user, self.host);
+ 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=? and host=?", data, user, self.host)
+ or self:modify("insert into vcard (username, host, vcard) values (?, ?, ?)", user, self.host, data);
+ else
+ return self:modify("delete from vcard where username=? and host=?", user, self.host);
+ end
+ end;
+};
+handlers.private = {
+ get = function(self, user)
+ local select = self:query("select namespace,data from private_storage where username=? and host=?", user, self.host);
+ 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=? and host=?", user, self.host);
+ for namespace,text in pairs(data) do
+ self:modify("insert into private_storage (username, host, namespace, data) values (?, ?, ?, ?)", user, self.host, namespace, unparse_xml(text));
+ end
+ return true;
+ else
+ return self:modify("delete from private_storage where username=? and host=?", user, self.host);
+ 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(row);
+ 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 = 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,err = self:prepare(sql);
+ if not stmt then
+ module:log("error", "Failed to prepare SQL [[%s]], error: %s", sql, err);
+ return nil, err;
+ end
+ local ok, err = stmt:execute(...);
+ if not ok then
+ module:log("error", "Failed to execute SQL [[%s]], error: %s", sql, err);
+ return nil, err;
+ end
+ return stmt;
+end
+function driver:modify(sql, ...)
+ local stmt, err = self:query(sql, ...);
+ if stmt and stmt:affected() > 0 then return stmt; end
+ return nil, err;
+end
+
+function driver:open(datastore, typ)
+ local instance = setmetatable({}, self);
+ instance.host = module.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
+ return instance;
+end
+
+-----------------------------
+
+module:add_item("data-driver", driver);