aboutsummaryrefslogtreecommitdiffstats
path: root/tools/migration
diff options
context:
space:
mode:
Diffstat (limited to 'tools/migration')
-rw-r--r--tools/migration/migrator/jabberd14.lua194
1 files changed, 194 insertions, 0 deletions
diff --git a/tools/migration/migrator/jabberd14.lua b/tools/migration/migrator/jabberd14.lua
new file mode 100644
index 00000000..47004a52
--- /dev/null
+++ b/tools/migration/migrator/jabberd14.lua
@@ -0,0 +1,194 @@
+
+local lfs = require "lfs";
+local lxp = require "lxp";
+local st = require "util.stanza";
+local os_getenv = os.getenv;
+local io_open = io.open;
+local assert = assert;
+local ipairs = ipairs;
+local coroutine = coroutine;
+local print = print;
+
+module "jabberd14"
+
+local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end
+local function is_file(path) return lfs.attributes(path, "mode") == "file"; end
+local function clean_path(path)
+ return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~");
+end
+
+local parse_xml = (function()
+ local ns_prefixes = {
+ ["http://www.w3.org/XML/1998/namespace"] = "xml";
+ };
+ local ns_separator = "\1";
+ local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+ return function(xml)
+ local handler = {};
+ local stanza = st.stanza("root");
+ function handler:StartElement(tagname, attr)
+ local curr_ns,name = tagname:match(ns_pattern);
+ if name == "" then
+ curr_ns, name = "", curr_ns;
+ end
+ if curr_ns ~= "" then
+ attr.xmlns = curr_ns;
+ end
+ for i=1,#attr do
+ local k = attr[i];
+ attr[i] = nil;
+ local ns, nm = k:match(ns_pattern);
+ if nm ~= "" then
+ ns = ns_prefixes[ns];
+ if ns then
+ attr[ns..":"..nm] = attr[k];
+ attr[k] = nil;
+ end
+ end
+ end
+ stanza:tag(name, attr);
+ end
+ function handler:CharacterData(data)
+ stanza:text(data);
+ end
+ function handler:EndElement(tagname)
+ stanza:up();
+ end
+ local parser = lxp.new(handler, "\1");
+ local ok, err, line, col = parser:parse(xml);
+ if ok then ok, err, line, col = parser:parse(); end
+ --parser:close();
+ if ok then
+ return stanza.tags[1];
+ else
+ return ok, err.." (line "..line..", col "..col..")";
+ end
+ end;
+end)();
+
+local function load_xml(path)
+ if path then
+ local f, err = io_open(path);
+ if not f then return f, err; end
+ local data = f:read("*a");
+ f:close();
+ if data then
+ return parse_xml(data);
+ end
+ end
+end
+
+local function load_spool_file(host, filename, path)
+ local xml = load_xml(path);
+ if not xml then return; end
+
+ local register_element = xml:get_child("query", "jabber:iq:register");
+ local username_element = register_element and register_element:get_child("username", "jabber:iq:register");
+ local password_element = register_element and register_element:get_child("password", "jabber:iq:auth");
+ local username = username_element and username_element:get_text();
+ local password = password_element and password_element:get_text();
+ if not username then
+ print("[warn] Missing /xdb/{jabber:iq:register}register/username> in file "..filename)
+ return;
+ elseif username..".xml" ~= filename then
+ print("[warn] Missing /xdb/{jabber:iq:register}register/username does not match filename "..filename);
+ return;
+ end
+
+ local userdata = {
+ user = username;
+ host = host;
+ stores = {};
+ };
+ local stores = userdata.stores;
+ stores.accounts = { password = password };
+
+ for i=1,#xml.tags do
+ local tag = xml.tags[i];
+ local xname = (tag.attr.xmlns or "")..":"..tag.name;
+ if tag.attr.j_private_flag == "1" and tag.attr.xmlns then
+ -- Private XML
+ stores.private = stores.private or {};
+ tag.attr.j_private_flag = nil;
+ stores.private[tag.attr.xmlns] = st.preserialize(tag);
+ elseif xname == "jabber:iq:auth:password" then
+ if stores.accounts.password ~= tag:get_text() then
+ if password then
+ print("[warn] conflicting passwords")
+ else
+ stores.accounts.password = tag:get_text();
+ end
+ end
+ elseif xname == "jabber:iq:register:query" then
+ -- already processed
+ elseif xname == "jabber:xdb:nslist:foo" then
+ -- ignore
+ elseif xname == "jabber:iq:auth:0k:zerok" then
+ -- ignore
+ elseif xname == "jabber:iq:roster:query" then
+ -- Roster
+ local roster = {};
+ local subscription_types = { from = true, to = true, both = true, none = true };
+ for _,item_element in ipairs(tag.tags) do
+ assert(item_element.name == "item");
+ assert(item_element.attr.jid);
+ assert(subscription_types[item_element.attr.subscription]);
+ assert((item_element.attr.ask or "subscribe") == "subscribe")
+ if item_element.name == "item" then
+ local groups = {};
+ for _,group_element in ipairs(item_element.tags) do
+ assert(group_element.name == "group");
+ groups[group_element:get_text()] = true;
+ end
+ local item = {
+ name = item_element.attr.name;
+ subscription = item_element.attr.subscription;
+ ask = item_element.attr.ask;
+ groups = groups;
+ };
+ roster[item_element.attr.jid] = item;
+ end
+ end
+ stores.roster = roster;
+ elseif xname == "jabber:iq:last:query" then
+ -- Last activity
+ elseif xname == "jabber:x:offline:foo" then
+ -- Offline messages
+ elseif xname == "vcard-temp:vCard" then
+ -- vCards
+ stores.vcard = st.preserialize(tag);
+ else
+ print("[warn] Unknown tag: "..xname);
+ end
+ end
+ return userdata;
+end
+
+local function loop_over_users(path, host, cb)
+ for file in lfs.dir(path) do
+ if file:match("%.xml$") then
+ local user = load_spool_file(host, file, path.."/"..file);
+ if user then cb(user); end
+ end
+ end
+end
+local function loop_over_hosts(path, cb)
+ for host in lfs.dir(path) do
+ if host ~= "." and host ~= ".." and is_dir(path.."/"..host) then
+ loop_over_users(path.."/"..host, host, cb);
+ end
+ end
+end
+
+function reader(input)
+ local path = clean_path(assert(input.path, "no input.path specified"));
+ assert(is_dir(path), "input.path is not a directory");
+
+ if input.host then
+ return coroutine.wrap(function() loop_over_users(input.path, input.host, coroutine.yield) end);
+ else
+ return coroutine.wrap(function() loop_over_hosts(input.path, coroutine.yield) end);
+ end
+end
+
+return _M;