aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/ejabberd2prosody.lua2
-rw-r--r--tools/erlparse.lua72
-rwxr-xr-xtools/xep227toprosody.lua261
3 files changed, 305 insertions, 30 deletions
diff --git a/tools/ejabberd2prosody.lua b/tools/ejabberd2prosody.lua
index d44c929b..a231abd8 100755
--- a/tools/ejabberd2prosody.lua
+++ b/tools/ejabberd2prosody.lua
@@ -17,6 +17,8 @@ end
require "erlparse";
+prosody = {};
+
local serialize = require "util.serialization".serialize;
local st = require "util.stanza";
package.loaded["util.logger"] = {init = function() return function() end; end}
diff --git a/tools/erlparse.lua b/tools/erlparse.lua
index 387ffbe6..dc3a2f94 100644
--- a/tools/erlparse.lua
+++ b/tools/erlparse.lua
@@ -6,16 +6,22 @@
-- COPYING file in the source package for more information.
--
-
+local string_byte, string_char = string.byte, string.char;
+local t_concat, t_insert = table.concat, table.insert;
+local type, tonumber, tostring = type, tonumber, tostring;
local file = nil;
local last = nil;
+local line = 1;
local function read(expected)
local ch;
if last then
ch = last; last = nil;
- else ch = file:read(1); end
- if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil")); end
+ else
+ ch = file:read(1);
+ if ch == "\n" then line = line + 1; end
+ end
+ if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil").." on line "..line); end
return ch;
end
local function pushback(ch)
@@ -27,21 +33,21 @@ local function peek()
return last;
end
-local _A, _a, _Z, _z, _0, _9, __, _at, _space = string.byte("AaZz09@_ ", 1, 9);
+local _A, _a, _Z, _z, _0, _9, __, _at, _space, _minus = string_byte("AaZz09@_ -", 1, 10);
local function isLowerAlpha(ch)
- ch = string.byte(ch) or 0;
+ ch = string_byte(ch) or 0;
return (ch >= _a and ch <= _z);
end
local function isNumeric(ch)
- ch = string.byte(ch) or 0;
- return (ch >= _0 and ch <= _9);
+ ch = string_byte(ch) or 0;
+ return (ch >= _0 and ch <= _9) or ch == _minus;
end
local function isAtom(ch)
- ch = string.byte(ch) or 0;
+ ch = string_byte(ch) or 0;
return (ch >= _A and ch <= _Z) or (ch >= _a and ch <= _z) or (ch >= _0 and ch <= _9) or ch == __ or ch == _at;
end
local function isSpace(ch)
- ch = string.byte(ch) or "x";
+ ch = string_byte(ch) or "x";
return ch <= _space;
end
@@ -49,79 +55,85 @@ local escapes = {["\\b"]="\b", ["\\d"]="\d", ["\\e"]="\e", ["\\f"]="\f", ["\\n"]
local function readString()
read("\""); -- skip quote
local slash = nil;
- local str = "";
+ local str = {};
while true do
local ch = read();
if slash then
slash = slash..ch;
if not escapes[slash] then error("Unknown escape sequence: "..slash); end
- str = str..escapes[slash];
+ str[#str+1] = escapes[slash];
slash = nil;
elseif ch == "\"" then
break;
elseif ch == "\\" then
slash = ch;
else
- str = str..ch;
+ str[#str+1] = ch;
end
end
- return str;
+ return t_concat(str);
end
local function readAtom1()
- local var = read();
+ local var = { read() };
while isAtom(peek()) do
- var = var..read();
+ var[#var+1] = read();
end
- return var;
+ return t_concat(var);
end
local function readAtom2()
- local str = read("'");
+ local str = { read("'") };
local slash = nil;
while true do
local ch = read();
- str = str..ch;
+ str[#str+1] = ch;
if ch == "'" and not slash then break; end
end
- return str;
+ return t_concat(str);
end
local function readNumber()
- local num = read();
+ local num = { read() };
while isNumeric(peek()) do
- num = num..read();
+ num[#num+1] = read();
end
- return tonumber(num);
+ return tonumber(t_concat(num));
end
local readItem = nil;
local function readTuple()
local t = {};
- local s = ""; -- string representation
+ local s = {}; -- string representation
read(); -- read {, or [, or <
while true do
local item = readItem();
if not item then break; end
- if type(item) ~= type(0) or item > 255 then
+ if type(item) ~= "number" or item > 255 then
s = nil;
elseif s then
- s = s..string.char(item);
+ s[#s+1] = string_char(item);
end
- table.insert(t, item);
+ t_insert(t, item);
end
read(); -- read }, or ], or >
- if s and s ~= "" then
- return s
+ if s and #s > 0 then
+ return t_concat(s)
else
return t
end;
end
local function readBinary()
read("<"); -- read <
+ -- Discard PIDs
+ if isNumeric(peek()) then
+ while peek() ~= ">" do read(); end
+ read(">");
+ return {};
+ end
local t = readTuple();
read(">") -- read >
local ch = peek();
- if type(t) == type("") then
+ if type(t) == "string" then
-- binary is a list of integers
return t;
- elseif type(t) == type({}) then
+ elseif type(t) == "table" then
if t[1] then
-- binary contains string
return t[1];
diff --git a/tools/xep227toprosody.lua b/tools/xep227toprosody.lua
new file mode 100755
index 00000000..313b2194
--- /dev/null
+++ b/tools/xep227toprosody.lua
@@ -0,0 +1,261 @@
+#!/usr/bin/env lua
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- Copyright (C) 2010 Stefan Gehn
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+-- FIXME: XEP-0227 supports XInclude but luaexpat does not
+--
+-- XEP-227 elements and their current level of support:
+-- Hosts : supported
+-- Users : supported
+-- Rosters : supported, needs testing
+-- Offline Messages : supported, needs testing
+-- Private XML Storage : supported, needs testing
+-- vCards : supported, needs testing
+-- Privacy Lists: UNSUPPORTED
+-- http://xmpp.org/extensions/xep-0227.html#privacy-lists
+-- mod_privacy uses dm.load(username, host, "privacy"); and stores stanzas 1:1
+-- Incoming Subscription Requests : supported
+
+package.path = package.path..";../?.lua";
+package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager
+
+-- ugly workaround for getting datamanager to work outside of prosody :(
+prosody = { };
+prosody.platform = "unknown";
+if os.getenv("WINDIR") then
+ prosody.platform = "windows";
+elseif package.config:sub(1,1) == "/" then
+ prosody.platform = "posix";
+end
+
+local lxp = require "lxp";
+local st = require "util.stanza";
+local init_xmlhandlers = require "core.xmlhandlers";
+local dm = require "util.datamanager"
+dm.set_data_path("data");
+
+local ns_separator = "\1";
+local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+local ns_xep227 = "http://www.xmpp.org/extensions/xep-0227.html#ns";
+
+-----------------------------------------------------------------------
+
+function store_vcard(username, host, stanza)
+ -- create or update vCard for username@host
+ local ret, err = dm.store(username, host, "vcard", st.preserialize(stanza));
+ print("["..(err or "success").."] stored vCard: "..username.."@"..host);
+end
+
+function store_password(username, host, password)
+ -- create or update account for username@host
+ local ret, err = dm.store(username, host, "accounts", {password = password});
+ print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password);
+end
+
+function store_roster(username, host, roster_items)
+ -- fetch current roster-table for username@host if he already has one
+ local roster = dm.load(username, host, "roster") or {};
+ -- merge imported roster-items with loaded roster
+ for item_tag in roster_items:childtags() do
+ -- jid for this roster-item
+ local item_jid = item_tag.attr.jid
+ -- validate item stanzas
+ if (item_tag.name == "item") and (item_jid ~= "") then
+ -- prepare roster item
+ -- TODO: is the subscription attribute optional?
+ local item = {subscription = item_tag.attr.subscription, groups = {}};
+ -- optional: give roster item a real name
+ if item_tag.attr.name then
+ item.name = item_tag.attr.name;
+ end
+ -- optional: iterate over group stanzas inside item stanza
+ for group_tag in item_tag:childtags() do
+ local group_name = group_tag:get_text();
+ if (group_tag.name == "group") and (group_name ~= "") then
+ item.groups[group_name] = true;
+ else
+ print("[error] invalid group stanza: "..group_tag:pretty_print());
+ end
+ end
+ -- store item in roster
+ roster[item_jid] = item;
+ print("[success] roster entry: " ..username.."@"..host.." - "..item_jid);
+ else
+ print("[error] invalid roster stanza: " ..item_tag:pretty_print());
+ end
+
+ end
+ -- store merged roster-table
+ local ret, err = dm.store(username, host, "roster", roster);
+ print("["..(err or "success").."] stored roster: " ..username.."@"..host);
+end
+
+function store_private(username, host, private_items)
+ local private = dm.load(username, host, "private") or {};
+ for ch in private_items:childtags() do
+ --print("private :"..ch:pretty_print());
+ private[ch.name..":"..ch.attr.xmlns] = st.preserialize(ch);
+ print("[success] private item: " ..username.."@"..host.." - "..ch.name);
+ end
+ local ret, err = dm.store(username, host, "private", private);
+ print("["..(err or "success").."] stored private: " ..username.."@"..host);
+end
+
+function store_offline_messages(username, host, offline_messages)
+ -- TODO: maybe use list_load(), append and list_store() instead
+ -- of constantly reopening the file with list_append()?
+ for ch in offline_messages:childtags() do
+ --print("message :"..ch:pretty_print());
+ local ret, err = dm.list_append(username, host, "offline", st.preserialize(ch));
+ print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..ch.attr.from);
+ end
+end
+
+
+function store_subscription_request(username, host, presence_stanza)
+ local from_bare = presence_stanza.attr.from;
+
+ -- fetch current roster-table for username@host if he already has one
+ local roster = dm.load(username, host, "roster") or {};
+
+ local item = roster[from_bare];
+ if item and (item.subscription == "from" or item.subscription == "both") then
+ return; -- already subscribed, do nothing
+ end
+
+ -- add to table of pending subscriptions
+ if not roster.pending then roster.pending = {}; end
+ roster.pending[from_bare] = true;
+
+ -- store updated roster-table
+ local ret, err = dm.store(username, host, "roster", roster);
+ print("["..(err or "success").."] stored subscription request: " ..username.."@"..host.." - "..from_bare);
+end
+
+-----------------------------------------------------------------------
+
+local curr_host = "";
+local user_name = "";
+
+
+local cb = {
+ stream_tag = "user",
+ stream_ns = ns_xep227,
+};
+function cb.streamopened(session, attr)
+ session.notopen = false;
+ user_name = attr.name;
+ store_password(user_name, curr_host, attr.password);
+end
+function cb.streamclosed(session)
+ session.notopen = true;
+ user_name = "";
+end
+function cb.handlestanza(session, stanza)
+ --print("Parsed stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or ""));
+ if (stanza.name == "vCard") and (stanza.attr.xmlns == "vcard-temp") then
+ store_vcard(user_name, curr_host, stanza);
+ elseif (stanza.name == "query") then
+ if (stanza.attr.xmlns == "jabber:iq:roster") then
+ store_roster(user_name, curr_host, stanza);
+ elseif (stanza.attr.xmlns == "jabber:iq:private") then
+ store_private(user_name, curr_host, stanza);
+ end
+ elseif (stanza.name == "offline-messages") then
+ store_offline_messages(user_name, curr_host, stanza);
+ elseif (stanza.name == "presence") and (stanza.attr.xmlns == "jabber:client") then
+ store_subscription_request(user_name, curr_host, stanza);
+ else
+ print("UNHANDLED stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or ""));
+ end
+end
+
+local user_handlers = init_xmlhandlers({ notopen = true, }, cb);
+
+-----------------------------------------------------------------------
+
+local lxp_handlers = {
+ --count = 0
+};
+
+-- TODO: error handling for invalid opening elements if curr_host is empty
+function lxp_handlers.StartElement(parser, elementname, attributes)
+ local curr_ns, name = elementname:match(ns_pattern);
+ if name == "" then
+ curr_ns, name = "", curr_ns;
+ end
+ --io.write("+ ", string.rep(" ", count), name, " (", curr_ns, ")", "\n")
+ --count = count + 1;
+ if curr_host ~= "" then
+ -- forward to xmlhandlers
+ user_handlers:StartElement(elementname, attributes);
+ elseif (curr_ns == ns_xep227) and (name == "host") then
+ curr_host = attributes["jid"]; -- start of host element
+ print("Begin parsing host "..curr_host);
+ elseif (curr_ns ~= ns_xep227) or (name ~= "server-data") then
+ io.stderr:write("Unhandled XML element: ", name, "\n");
+ os.exit(1);
+ end
+end
+
+-- TODO: error handling for invalid closing elements if host is empty
+function lxp_handlers.EndElement(parser, elementname)
+ local curr_ns, name = elementname:match(ns_pattern);
+ if name == "" then
+ curr_ns, name = "", curr_ns;
+ end
+ --count = count - 1;
+ --io.write("- ", string.rep(" ", count), name, " (", curr_ns, ")", "\n")
+ if curr_host ~= "" then
+ if (curr_ns == ns_xep227) and (name == "host") then
+ print("End parsing host "..curr_host);
+ curr_host = "" -- end of host element
+ else
+ -- forward to xmlhandlers
+ user_handlers:EndElement(elementname);
+ end
+ elseif (curr_ns ~= ns_xep227) or (name ~= "server-data") then
+ io.stderr:write("Unhandled XML element: ", name, "\n");
+ os.exit(1);
+ end
+end
+
+function lxp_handlers.CharacterData(parser, string)
+ if curr_host ~= "" then
+ -- forward to xmlhandlers
+ user_handlers:CharacterData(string);
+ end
+end
+
+-----------------------------------------------------------------------
+
+local arg = ...;
+local help = "/? -? ? /h -h /help -help --help";
+if not arg or help:find(arg, 1, true) then
+ print([[XEP-227 importer for Prosody
+
+ Usage: xep227toprosody.lua filename.xml
+
+]]);
+ os.exit(1);
+end
+
+local file = io.open(arg);
+if not file then
+ io.stderr:write("Could not open file: ", arg, "\n");
+ os.exit(0);
+end
+
+local parser = lxp.new(lxp_handlers, ns_separator);
+for l in file:lines() do
+ parser:parse(l);
+end
+parser:parse();
+parser:close();
+file:close();