aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/cfgdump.lua128
-rwxr-xr-xtools/ejabberd2prosody.lua15
-rw-r--r--tools/form2table.lua48
-rw-r--r--tools/generate_format_spec.lua52
-rw-r--r--tools/jabberd14sql2prosody.lua4
-rw-r--r--tools/linedebug.lua18
-rw-r--r--tools/make_repo.lua44
-rw-r--r--tools/migration/Makefile12
-rw-r--r--tools/migration/migrator.cfg.lua32
-rw-r--r--tools/migration/migrator/mtools.lua58
-rw-r--r--tools/migration/migrator/prosody_files.lua144
-rw-r--r--tools/migration/migrator/prosody_sql.lua190
-rw-r--r--tools/migration/prosody-migrator.lua244
-rw-r--r--tools/modtrace.lua159
-rwxr-xr-xtools/tb2err21
-rwxr-xr-xtools/xep227toprosody.lua269
16 files changed, 709 insertions, 729 deletions
diff --git a/tools/cfgdump.lua b/tools/cfgdump.lua
new file mode 100755
index 00000000..8c331a58
--- /dev/null
+++ b/tools/cfgdump.lua
@@ -0,0 +1,128 @@
+#!/usr/bin/env lua
+
+-- cfgdump.lua prosody.cfg.lua [[host] option]
+
+local s_format, print = string.format, print;
+local printf = function(fmt, ...) return print(s_format(fmt, ...)); end
+local it = require "util.iterators";
+local function sort_anything(a, b)
+ local typeof_a, typeof_b = type(a), type(b);
+ if typeof_a ~= typeof_b then return typeof_a < typeof_b end
+ return a < b -- should work for everything in a config file
+end
+local serialization = require "util.serialization";
+local serialize = serialization.new and serialization.new({
+ unquoted = true, table_iterator = function(t) return it.sorted_pairs(t, sort_anything); end,
+}) or serialization.serialize;
+local configmanager = require"core.configmanager";
+local startup = require "util.startup";
+
+startup.set_function_metatable();
+local config_filename, onlyhost, onlyoption = ...;
+
+local ok, _, err = configmanager.load(config_filename or "./prosody.cfg.lua", "lua");
+assert(ok, err);
+
+if onlyhost then
+ if not onlyoption then
+ onlyhost, onlyoption = "*", onlyhost;
+ end
+ if onlyhost ~= "*" then
+ local component_module = configmanager.get(onlyhost, "component_module");
+
+ if component_module == "component" then
+ printf("Component %q", onlyhost);
+ elseif component_module then
+ printf("Component %q %q", onlyhost, component_module);
+ else
+ printf("VirtualHost %q", onlyhost);
+ end
+ end
+ printf("%s = %s", onlyoption or "?", serialize(configmanager.get(onlyhost, onlyoption)));
+ return;
+end
+
+local config = configmanager.getconfig();
+
+
+for host, hostcfg in it.sorted_pairs(config) do
+ local fixed = {};
+ for option, value in it.sorted_pairs(hostcfg) do
+ fixed[option] = value;
+ if option:match("ports?$") or option:match("interfaces?$") then
+ if option:match("s$") then
+ if type(value) ~= "table" then
+ fixed[option] = { value };
+ end
+ else
+ if type(value) == "table" and #value > 1 then
+ fixed[option] = nil;
+ fixed[option.."s"] = value;
+ end
+ end
+ end
+ end
+ config[host] = fixed;
+end
+
+local globals = config["*"]; config["*"] = nil;
+
+local function printsection(section)
+ local out, n = {}, 1;
+ for k,v in it.sorted_pairs(section) do
+ out[n], n = s_format("%s = %s", k, serialize(v)), n + 1;
+ end
+ table.sort(out);
+ print(table.concat(out, "\n"));
+end
+
+print("-------------- Prosody Exported Configuration File -------------");
+print();
+print("------------------------ Global section ------------------------");
+print();
+printsection(globals);
+print();
+
+local has_components = nil;
+
+print("------------------------ Virtual hosts -------------------------");
+
+for host, hostcfg in it.sorted_pairs(config) do
+ setmetatable(hostcfg, nil);
+ hostcfg.defined = nil;
+
+ if hostcfg.component_module == nil then
+ print();
+ printf("VirtualHost %q", host);
+ printsection(hostcfg);
+ else
+ has_components = true
+ end
+end
+
+print();
+
+if has_components then
+print("------------------------- Components ---------------------------");
+
+ for host, hostcfg in it.sorted_pairs(config) do
+ local component_module = hostcfg.component_module;
+ hostcfg.component_module = nil;
+
+ if component_module then
+ print();
+ if component_module == "component" then
+ printf("Component %q", host);
+ else
+ printf("Component %q %q", host, component_module);
+ hostcfg.component_module = nil;
+ hostcfg.load_global_modules = nil;
+ end
+ printsection(hostcfg);
+ end
+ end
+end
+
+print()
+print("------------------------- End of File --------------------------");
+
diff --git a/tools/ejabberd2prosody.lua b/tools/ejabberd2prosody.lua
index 46a48f57..e3caefd5 100755
--- a/tools/ejabberd2prosody.lua
+++ b/tools/ejabberd2prosody.lua
@@ -85,7 +85,13 @@ function password(node, host, password)
data.stored_key = hex(unb64(password[2]));
data.server_key = hex(unb64(password[3]));
data.salt = unb64(password[4]);
- data.iteration_count = password[5];
+ if type(password[6]) == "number" then
+ assert(password[5] == "sha", "unexpected passwd entry hash: "..tostring(password[5]));
+ data.iteration_count = password[6];
+ else
+ assert(type(password[5]) == "number", "unexpected passwd entry in source data");
+ data.iteration_count = password[5];
+ end
end
local ret, err = dm.store(node, host, "accounts", data);
print("["..(err or "success").."] accounts: "..node.."@"..host);
@@ -181,13 +187,16 @@ function muc_room(node, host, properties)
for _,aff in ipairs(properties.affiliations) do
store._affiliations[build_jid(aff[1])] = aff[2][1] or aff[2];
end
- store._data.subject = properties.subject;
+ -- destructre ejabberd's subject datum (e.g. [{text,<<>>,<<"my room subject">>}] )
+ store._data.subject = properties.subject[1][3];
if properties.subject_author then
store._data.subject_from = store.jid .. "/" .. properties.subject_author;
end
store._data.name = properties.title;
store._data.description = properties.description;
- store._data.password = properties.password;
+ if properties.password_protected ~= false and properties.password ~= "" then
+ store._data.password = properties.password;
+ end
store._data.moderated = (properties.moderated == "true") or nil;
store._data.members_only = (properties.members_only == "true") or nil;
store._data.persistent = (properties.persistent == "true") or nil;
diff --git a/tools/form2table.lua b/tools/form2table.lua
new file mode 100644
index 00000000..49a6972b
--- /dev/null
+++ b/tools/form2table.lua
@@ -0,0 +1,48 @@
+-- Read an XML dataform and spit out a serialized Lua table of it
+
+local function from_stanza(stanza)
+ local layout = {
+ title = stanza:get_child_text("title");
+ instructions = stanza:get_child_text("instructions");
+ };
+ for tag in stanza:childtags("field") do
+ local field = {
+ name = tag.attr.var;
+ type = tag.attr.type;
+ label = tag.attr.label;
+ desc = tag:get_child_text("desc");
+ required = tag:get_child("required") and true or nil;
+ value = tag:get_child_text("value");
+ options = nil;
+ };
+
+ if field.type == "list-single" or field.type == "list-multi" then
+ local options = {};
+ for option in tag:childtags("option") do
+ options[#options+1] = { label = option.attr.label, value = option:get_child_text("value") };
+ end
+ field.options = options;
+ end
+
+ if field.type == "jid-multi" or field.type == "list-multi" or field.type == "text-multi" then
+ local values = {};
+ for value in tag:childtags("value") do
+ values[#values+1] = value:get_text();
+ end
+ if field.type == "text-multi" then
+ values = table.concat(values, "\n");
+ end
+ field.value = values;
+ end
+
+ if field.type == "boolean" then
+ field.value = field.value == "true" or field.value == "1";
+ end
+
+ layout[#layout+1] = field;
+
+ end
+ return layout;
+end
+
+print("dataforms.new " .. require "util.serialization".serialize(from_stanza(require "util.xml".parse(io.read("*a"))), { unquoted = true }))
diff --git a/tools/generate_format_spec.lua b/tools/generate_format_spec.lua
new file mode 100644
index 00000000..359986f7
--- /dev/null
+++ b/tools/generate_format_spec.lua
@@ -0,0 +1,52 @@
+local format = require"util.format".format;
+local dump = require"util.serialization".new("oneline")
+local types = {
+ "nil";
+ "boolean";
+ "number";
+ "string";
+ "function";
+ -- "userdata";
+ "thread";
+ "table";
+};
+local example_values = {
+ ["nil"] = { n = 1; nil };
+ ["boolean"] = { true; false };
+ ["number"] = { 97; -12345; 1.5; 73786976294838206464; math.huge; 2147483647 };
+ ["string"] = { "hello"; "foo \1\2\3 bar"; "nödåtgärd"; string.sub("nödåtgärd", 1, -4) };
+ ["function"] = { function() end };
+ -- ["userdata"] = {};
+ ["thread"] = { coroutine.create(function() end) };
+ ["table"] = { {}, setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}) };
+};
+local example_strings = setmetatable({
+ ["nil"] = { "nil" };
+ ["function"] = { "function() end" };
+ ["number"] = { "97"; "-12345"; "1.5"; "73786976294838206464"; "math.huge"; "2147483647" };
+ ["thread"] = { "coroutine.create(function() end)" };
+ ["table"] = { "{ }", "setmetatable({},{__tostring=function ()return \"foo \\1\\2\\3 bar\"end})" }
+}, { __index = function() return {} end });
+for _, lua_type in ipairs(types) do
+ print(string.format("\t\tdescribe(\"%s\", function ()", lua_type));
+ local examples = example_values[lua_type];
+ for fmt in ("cdiouxXaAeEfgGqs"):gmatch(".") do
+ print(string.format("\t\t\tdescribe(\"to %%%s\", function ()", fmt));
+ print("\t\t\t\tit(\"works\", function ()");
+ for i = 1, examples.n or #examples do
+ local example = examples[i];
+ if not tostring(example):match("%w+: 0[xX]%x+") then
+ print(string.format("\t\t\t\t\tassert.equal(%q, format(%q, %s))", format("%" .. fmt, example), "%" .. fmt,
+ example_strings[lua_type][i] or dump(example)));
+ else
+ print(string.format("\t\t\t\t\tassert.matches(\"[%s: 0[xX]%%x+]\", format(%q, %s))", lua_type, "%" .. fmt,
+ example_strings[lua_type][i] or dump(example)));
+ end
+ end
+ print("\t\t\t\tend);");
+ print("\t\t\tend);");
+ print()
+ end
+ print("\t\tend);");
+ print()
+end
diff --git a/tools/jabberd14sql2prosody.lua b/tools/jabberd14sql2prosody.lua
index e43dc296..d1d66dc1 100644
--- a/tools/jabberd14sql2prosody.lua
+++ b/tools/jabberd14sql2prosody.lua
@@ -468,7 +468,7 @@ function store_vcard(username, host, stanza)
end
function store_roster(username, host, roster_items)
- -- fetch current roster-table for username@host if he already has one
+ -- fetch current roster-table for username@host if they already have one
local roster = dm.load(username, host, "roster") or {};
-- merge imported roster-items with loaded roster
for item_tag in roster_items:childtags() do
@@ -508,7 +508,7 @@ 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
+ -- fetch current roster-table for username@host if they already have one
local roster = dm.load(username, host, "roster") or {};
local item = roster[from_bare];
diff --git a/tools/linedebug.lua b/tools/linedebug.lua
new file mode 100644
index 00000000..96214cc8
--- /dev/null
+++ b/tools/linedebug.lua
@@ -0,0 +1,18 @@
+local data = {}
+local getinfo = debug.getinfo;
+local function linehook(ev, li)
+ local S = getinfo(2, "S");
+ if S and S.source and S.source:match"^@" then
+ local file = S.source:sub(2);
+ local lines = data[file];
+ if not lines then
+ lines = {};
+ data[file] = lines;
+ for line in io.lines(file) do
+ lines[#lines+1] = line;
+ end
+ end
+ io.stderr:write(ev, " ", file, " ", li, " ", lines[li], "\n");
+ end
+end
+debug.sethook(linehook, "l");
diff --git a/tools/make_repo.lua b/tools/make_repo.lua
new file mode 100644
index 00000000..5b5e6234
--- /dev/null
+++ b/tools/make_repo.lua
@@ -0,0 +1,44 @@
+print("Getting all the available modules")
+if os.execute '[ -e "./downloaded_modules" ]' then
+ os.execute("rm -rf downloaded_modules")
+end
+os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules")
+local i, popen = 0, io.popen
+local flag = "mod_"
+if os.execute '[ -e "./repository" ]' then
+ os.execute("mkdir repository")
+end
+local pfile = popen('ls -a "downloaded_modules"')
+for filename in pfile:lines() do
+ i = i + 1
+ if filename:sub(1, #flag) == flag then
+ local file = io.open("repository/"..filename.."-scm-1.rockspec", "w")
+ file:write('package = "'..filename..'"', '\n')
+ file:write('version = "scm-1"', '\n')
+ file:write('source = {', '\n')
+ file:write('\turl = "hg+https://hg.prosody.im/prosody-modules",', '\n')
+ file:write('\tdir = "prosody-modules"', '\n')
+ file:write('}', '\n')
+ file:write('description = {', '\n')
+ file:write('\thomepage = "https://prosody.im/",', '\n')
+ file:write('\tlicense = "MIT"', '\n')
+ file:write('}', '\n')
+ file:write('dependencies = {', '\n')
+ file:write('\t"lua >= 5.1"', '\n')
+ file:write('}', '\n')
+ file:write('build = {', '\n')
+ file:write('\ttype = "builtin",', '\n')
+ file:write('\tmodules = {', '\n')
+ file:write('\t\t["'..filename..'.'..filename..'"] = "'..filename..'/'..filename..'.lua"', '\n')
+ file:write('\t}', '\n')
+ file:write('}', '\n')
+ file:close()
+ end
+end
+pfile:close()
+os.execute("cd repository/ && luarocks-admin make_manifest ./ && chmod -R 644 ./*")
+print("")
+print("Done!. Modules' sources are locally available at ./downloaded_modules")
+print("Repository is available at ./repository")
+print("The repository contains all of prosody modules' respective rockspecs, as well as manifest files and an html Index")
+print("You can now either point your server to this folder, or copy its contents to another configured folder.")
diff --git a/tools/migration/Makefile b/tools/migration/Makefile
index 713831d2..fce0f7d9 100644
--- a/tools/migration/Makefile
+++ b/tools/migration/Makefile
@@ -12,16 +12,12 @@ INSTALLEDCONFIG = $(SYSCONFDIR)
INSTALLEDMODULES = $(LIBDIR)/prosody/modules
INSTALLEDDATA = $(DATADIR)
-SOURCE_FILES = migrator/*.lua
-
-all: prosody-migrator.install migrator.cfg.lua.install prosody-migrator.lua $(SOURCE_FILES)
+all: prosody-migrator.install migrator.cfg.lua.install prosody-migrator.lua
install: prosody-migrator.install migrator.cfg.lua.install
- install -d $(BIN) $(CONFIG) $(SOURCE) $(SOURCE)/migrator
+ install -d $(BIN) $(CONFIG) $(SOURCE)
install -d $(MAN)/man1
- install -d $(SOURCE)/migrator
install -m755 ./prosody-migrator.install $(BIN)/prosody-migrator
- install -m644 $(SOURCE_FILES) $(SOURCE)/migrator
test -e $(CONFIG)/migrator.cfg.lua || install -m644 migrator.cfg.lua.install $(CONFIG)/migrator.cfg.lua
clean:
@@ -31,7 +27,9 @@ clean:
prosody-migrator.install: prosody-migrator.lua
sed "1s/\blua\b/$(RUNWITH)/; \
s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \
- s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|;" \
+ s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|; \
+ s|^CFG_DATADIR=.*;$$|CFG_DATADIR='$(INSTALLEDDATA)';|; \
+ s|^CFG_PLUGINDIR=.*;$$|CFG_PLUGINDIR='$(INSTALLEDMODULES)/';|;" \
< prosody-migrator.lua > prosody-migrator.install
migrator.cfg.lua.install: migrator.cfg.lua
diff --git a/tools/migration/migrator.cfg.lua b/tools/migration/migrator.cfg.lua
index fa37f2a3..b81389b3 100644
--- a/tools/migration/migrator.cfg.lua
+++ b/tools/migration/migrator.cfg.lua
@@ -1,12 +1,36 @@
local data_path = "../../data";
+local vhost = {
+ "accounts",
+ "account_details",
+ "roster",
+ "vcard",
+ "private",
+ "blocklist",
+ "privacy",
+ "archive-archive",
+ "offline-archive",
+ "pubsub_nodes-pubsub",
+ "pep-pubsub",
+}
+local muc = {
+ "persistent",
+ "config",
+ "state",
+ "muc_log-archive",
+};
+
input {
- type = "prosody_files";
+ hosts = {
+ ["example.com"] = vhost;
+ ["conference.example.com"] = muc;
+ };
+ type = "internal";
path = data_path;
}
output {
- type = "prosody_sql";
+ type = "sql";
driver = "SQLite3";
database = data_path.."/prosody.sqlite";
}
@@ -14,11 +38,11 @@ output {
--[[
input {
- type = "prosody_files";
+ type = "internal";
path = data_path;
}
output {
- type = "prosody_sql";
+ type = "sql";
driver = "SQLite3";
database = data_path.."/prosody.sqlite";
}
diff --git a/tools/migration/migrator/mtools.lua b/tools/migration/migrator/mtools.lua
deleted file mode 100644
index cfbfcce8..00000000
--- a/tools/migration/migrator/mtools.lua
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-local print = print;
-local t_insert = table.insert;
-local t_sort = table.sort;
-
-
-local function sorted(params)
-
- local reader = params.reader; -- iterator to get items from
- local sorter = params.sorter; -- sorting function
- local filter = params.filter; -- filter function
-
- local cache = {};
- for item in reader do
- if filter then item = filter(item); end
- if item then t_insert(cache, item); end
- end
- if sorter then
- t_sort(cache, sorter);
- end
- local i = 0;
- return function()
- i = i + 1;
- return cache[i];
- end;
-
-end
-
-local function merged(reader, merger)
-
- local item1 = reader();
- local merged = { item1 };
- return function()
- while true do
- if not item1 then return nil; end
- local item2 = reader();
- if not item2 then item1 = nil; return merged; end
- if merger(item1, item2) then
- --print("merged")
- item1 = item2;
- t_insert(merged, item1);
- else
- --print("unmerged", merged)
- item1 = item2;
- local tmp = merged;
- merged = { item1 };
- return tmp;
- end
- end
- end;
-
-end
-
-return {
- sorted = sorted;
- merged = merged;
-}
diff --git a/tools/migration/migrator/prosody_files.lua b/tools/migration/migrator/prosody_files.lua
deleted file mode 100644
index 4de09273..00000000
--- a/tools/migration/migrator/prosody_files.lua
+++ /dev/null
@@ -1,144 +0,0 @@
-
-local print = print;
-local assert = assert;
-local setmetatable = setmetatable;
-local tonumber = tonumber;
-local char = string.char;
-local coroutine = coroutine;
-local lfs = require "lfs";
-local loadfile = loadfile;
-local pcall = pcall;
-local mtools = require "migrator.mtools";
-local next = next;
-local pairs = pairs;
-local json = require "util.json";
-local os_getenv = os.getenv;
-local error = error;
-
-prosody = {};
-local dm = require "util.datamanager"
-
-
-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 encode, decode; do
- local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end });
- decode = function (s) return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes)); end
- encode = function (s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end
-end
-local function decode_dir(x)
- if x:gsub("%%%x%x", ""):gsub("[a-zA-Z0-9]", "") == "" then
- return decode(x);
- end
-end
-local function decode_file(x)
- if x:match(".%.dat$") and x:gsub("%.dat$", ""):gsub("%%%x%x", ""):gsub("[a-zA-Z0-9]", "") == "" then
- return decode(x:gsub("%.dat$", ""));
- end
-end
-local function prosody_dir(path, ondir, onfile, ...)
- for x in lfs.dir(path) do
- local xpath = path.."/"..x;
- if decode_dir(x) and is_dir(xpath) then
- ondir(xpath, x, ...);
- elseif decode_file(x) and is_file(xpath) then
- onfile(xpath, x, ...);
- end
- end
-end
-
-local function handle_root_file(path, name)
- --print("root file: ", decode_file(name))
- coroutine.yield { user = nil, host = nil, store = decode_file(name) };
-end
-local function handle_host_file(path, name, host)
- --print("host file: ", decode_dir(host).."/"..decode_file(name))
- coroutine.yield { user = nil, host = decode_dir(host), store = decode_file(name) };
-end
-local function handle_store_file(path, name, store, host)
- --print("store file: ", decode_file(name).."@"..decode_dir(host).."/"..decode_dir(store))
- coroutine.yield { user = decode_file(name), host = decode_dir(host), store = decode_dir(store) };
-end
-local function handle_host_store(path, name, host)
- prosody_dir(path, function() end, handle_store_file, name, host);
-end
-local function handle_host_dir(path, name)
- prosody_dir(path, handle_host_store, handle_host_file, name);
-end
-local function handle_root_dir(path)
- prosody_dir(path, handle_host_dir, handle_root_file);
-end
-
-local function decode_user(item)
- local userdata = {
- user = item[1].user;
- host = item[1].host;
- stores = {};
- };
- for i=1,#item do -- loop over stores
- local result = {};
- local store = item[i];
- userdata.stores[store.store] = store.data;
- store.user = nil; store.host = nil; store.store = nil;
- end
- return userdata;
-end
-
-local function reader(input)
- local path = clean_path(assert(input.path, "no input.path specified"));
- assert(is_dir(path), "input.path is not a directory");
- local iter = coroutine.wrap(function()handle_root_dir(path);end);
- -- get per-user stores, sorted
- local iter = mtools.sorted {
- reader = function()
- local x = iter();
- while x do
- dm.set_data_path(path);
- local err;
- x.data, err = dm.load(x.user, x.host, x.store);
- if x.data == nil and err then
- local p = dm.getpath(x.user, x.host, x.store);
- print(("Error loading data at path %s for %s@%s (%s store): %s")
- :format(p, x.user or "<nil>", x.host or "<nil>", x.store or "<nil>", err or "<nil>"));
- else
- return x;
- end
- x = iter();
- end
- end;
- sorter = function(a, b)
- local a_host, a_user, a_store = a.host or "", a.user or "", a.store or "";
- local b_host, b_user, b_store = b.host or "", b.user or "", b.store or "";
- return a_host > b_host or (a_host==b_host and a_user > b_user) or (a_host==b_host and a_user==b_user and a_store > b_store);
- end;
- };
- -- merge stores to get users
- iter = mtools.merged(iter, function(a, b)
- return (a.host == b.host and a.user == b.user);
- end);
-
- return function()
- local x = iter();
- return x and decode_user(x);
- end
-end
-
-local function writer(output)
- local path = clean_path(assert(output.path, "no output.path specified"));
- assert(is_dir(path), "output.path is not a directory");
- return function(item)
- if not item then return; end -- end of input
- dm.set_data_path(path);
- for store, data in pairs(item.stores) do
- assert(dm.store(item.user, item.host, store, data));
- end
- end
-end
-
-return {
- reader = reader;
- writer = writer;
-}
diff --git a/tools/migration/migrator/prosody_sql.lua b/tools/migration/migrator/prosody_sql.lua
deleted file mode 100644
index 6df2b025..00000000
--- a/tools/migration/migrator/prosody_sql.lua
+++ /dev/null
@@ -1,190 +0,0 @@
-
-local assert = assert;
-local have_DBI = pcall(require,"DBI");
-local print = print;
-local type = type;
-local next = next;
-local pairs = pairs;
-local t_sort = table.sort;
-local json = require "util.json";
-local mtools = require "migrator.mtools";
-local tostring = tostring;
-local tonumber = tonumber;
-
-if not have_DBI then
- error("LuaDBI (required for SQL support) was not found, please see https://prosody.im/doc/depends#luadbi", 0);
-end
-
-local sql = require "util.sql";
-
-local function create_table(engine, name) -- luacheck: ignore 431/engine
- local Table, Column, Index = sql.Table, sql.Column, sql.Index;
-
- local ProsodyTable = Table {
- name= name or "prosody";
- Column { name="host", type="TEXT", nullable=false };
- Column { name="user", type="TEXT", nullable=false };
- Column { name="store", type="TEXT", nullable=false };
- Column { name="key", type="TEXT", nullable=false };
- Column { name="type", type="TEXT", nullable=false };
- Column { name="value", type="MEDIUMTEXT", nullable=false };
- Index { name="prosody_index", "host", "user", "store", "key" };
- };
- engine:transaction(function()
- ProsodyTable:create(engine);
- end);
-
-end
-
-local function serialize(value)
- local t = type(value);
- if t == "string" or t == "boolean" or t == "number" then
- return t, tostring(value);
- elseif t == "table" then
- local value,err = json.encode(value);
- if value then return "json", value; end
- return nil, err;
- end
- return nil, "Unhandled value type: "..t;
-end
-local function deserialize(t, value)
- if t == "string" then return value;
- elseif t == "boolean" then
- if value == "true" then return true;
- elseif value == "false" then return false; end
- elseif t == "number" then return tonumber(value);
- elseif t == "json" then
- return json.decode(value);
- end
-end
-
-local function decode_user(item)
- local userdata = {
- user = item[1][1].user;
- host = item[1][1].host;
- stores = {};
- };
- for i=1,#item do -- loop over stores
- local result = {};
- local store = item[i];
- for i=1,#store do -- loop over store data
- local row = store[i];
- local k = row.key;
- local v = deserialize(row.type, row.value);
- if k and v then
- if k ~= "" then result[k] = v; elseif type(v) == "table" then
- for a,b in pairs(v) do
- result[a] = b;
- end
- end
- end
- userdata.stores[store[1].store] = result;
- end
- end
- return userdata;
-end
-
-local function needs_upgrade(engine, params)
- if params.driver == "MySQL" then
- local success = engine:transaction(function()
- local result = engine:execute("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'");
- assert(result:rowcount() == 0);
-
- -- COMPAT w/pre-0.10: Upgrade table to UTF-8 if not already
- local check_encoding_query = [[
- SELECT "COLUMN_NAME","COLUMN_TYPE","TABLE_NAME"
- FROM "information_schema"."columns"
- WHERE "TABLE_NAME" LIKE 'prosody%%' AND ( "CHARACTER_SET_NAME"!='%s' OR "COLLATION_NAME"!='%s_bin' );
- ]];
- check_encoding_query = check_encoding_query:format(engine.charset, engine.charset);
- local result = engine:execute(check_encoding_query);
- assert(result:rowcount() == 0)
- end);
- if not success then
- -- Upgrade required
- return true;
- end
- end
- return false;
-end
-
-local function reader(input)
- local engine = assert(sql:create_engine(input, function (engine) -- luacheck: ignore 431/engine
- if needs_upgrade(engine, input) then
- error("Old database format detected. Please run: prosodyctl mod_storage_sql upgrade");
- end
- end));
- local keys = {"host", "user", "store", "key", "type", "value"};
- assert(engine:connect());
- local f,s,val = assert(engine:select("SELECT \"host\", \"user\", \"store\", \"key\", \"type\", \"value\" FROM \"prosody\";"));
- -- get SQL rows, sorted
- local iter = mtools.sorted {
- reader = function() val = f(s, val); return val; end;
- filter = function(x)
- for i=1,#keys do
- x[ keys[i] ] = x[i];
- end
- if x.host == "" then x.host = nil; end
- if x.user == "" then x.user = nil; end
- if x.store == "" then x.store = nil; end
- return x;
- end;
- sorter = function(a, b)
- local a_host, a_user, a_store = a.host or "", a.user or "", a.store or "";
- local b_host, b_user, b_store = b.host or "", b.user or "", b.store or "";
- return a_host > b_host or (a_host==b_host and a_user > b_user) or (a_host==b_host and a_user==b_user and a_store > b_store);
- end;
- };
- -- merge rows to get stores
- iter = mtools.merged(iter, function(a, b)
- return (a.host == b.host and a.user == b.user and a.store == b.store);
- end);
- -- merge stores to get users
- iter = mtools.merged(iter, function(a, b)
- return (a[1].host == b[1].host and a[1].user == b[1].user);
- end);
- return function()
- local x = iter();
- return x and decode_user(x);
- end;
-end
-
-local function writer(output, iter)
- local engine = assert(sql:create_engine(output, function (engine) -- luacheck: ignore 431/engine
- if needs_upgrade(engine, output) then
- error("Old database format detected. Please run: prosodyctl mod_storage_sql upgrade");
- end
- create_table(engine);
- end));
- assert(engine:connect());
- assert(engine:delete("DELETE FROM \"prosody\""));
- local insert_sql = "INSERT INTO \"prosody\" (\"host\",\"user\",\"store\",\"key\",\"type\",\"value\") VALUES (?,?,?,?,?,?)";
-
- return function(item)
- if not item then assert(engine.conn:commit()) return end -- end of input
- local host = item.host or "";
- local user = item.user or "";
- for store, data in pairs(item.stores) do
- -- TODO transactions
- local extradata = {};
- for key, value in pairs(data) do
- if type(key) == "string" and key ~= "" then
- local t, value = assert(serialize(value));
- local ok, err = assert(engine:insert(insert_sql, host, user, store, key, t, value));
- else
- extradata[key] = value;
- end
- end
- if next(extradata) ~= nil then
- local t, extradata = assert(serialize(extradata));
- local ok, err = assert(engine:insert(insert_sql, host, user, store, "", t, extradata));
- end
- end
- end;
-end
-
-
-return {
- reader = reader;
- writer = writer;
-}
diff --git a/tools/migration/prosody-migrator.lua b/tools/migration/prosody-migrator.lua
index 1219d891..c51dde15 100644
--- a/tools/migration/prosody-migrator.lua
+++ b/tools/migration/prosody-migrator.lua
@@ -1,42 +1,84 @@
#!/usr/bin/env lua
-CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
-CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
+CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR");
+CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR");
+CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR");
+CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR");
--- Substitute ~ with path to home directory in paths
-if CFG_CONFIGDIR then
- CFG_CONFIGDIR = CFG_CONFIGDIR:gsub("^~", os.getenv("HOME"));
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+local function is_relative(path)
+ local path_sep = package.config:sub(1,1);
+ return ((path_sep == "/" and path:sub(1,1) ~= "/")
+ or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\")))
end
+-- Tell Lua where to find our libraries
if CFG_SOURCEDIR then
- CFG_SOURCEDIR = CFG_SOURCEDIR:gsub("^~", os.getenv("HOME"));
+ local function filter_relative_paths(path)
+ if is_relative(path) then return ""; end
+ end
+ local function sanitise_paths(paths)
+ return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";"));
+ end
+ package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path);
+ package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath);
+end
+
+-- Substitute ~ with path to home directory in data path
+if CFG_DATADIR then
+ if os.getenv("HOME") then
+ CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
+ end
end
local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua";
--- Command-line parsing
-local options = {};
-local i = 1;
-while arg[i] do
- if arg[i]:sub(1,2) == "--" then
- local opt, val = arg[i]:match("([%w-]+)=?(.*)");
- if opt then
- options[(opt:sub(3):gsub("%-", "_"))] = #val > 0 and val or true;
- end
- table.remove(arg, i);
- else
- i = i + 1;
- end
+local function usage()
+ print("Usage: " .. arg[0] .. " [OPTIONS] FROM_STORE TO_STORE");
+ print(" --config FILE Specify config file")
+ print(" --keep-going Keep going in case of errors");
+ print(" -v, --verbose Incease log-level");
+ print("");
+ print("If no stores are specified, 'input' and 'output' are used.");
end
-if CFG_SOURCEDIR then
- package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
- package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
-else
- package.path = "../../?.lua;"..package.path
- package.cpath = "../../?.so;"..package.cpath
+local startup = require "util.startup";
+do
+ startup.parse_args({
+ short_params = { v = "verbose", h = "help", ["?"] = "help" };
+ value_params = { config = true };
+ });
+ startup.init_global_state();
+ prosody.process_type = "migrator";
+ if prosody.opts.help then
+ usage();
+ os.exit(0);
+ end
+ startup.force_console_logging();
+ startup.init_logging();
+ startup.init_gc();
+ startup.init_errors();
+ startup.setup_plugindir();
+ startup.setup_plugin_install_path();
+ startup.setup_datadir();
+ startup.chdir();
+ startup.read_version();
+ startup.switch_user();
+ startup.check_dependencies();
+ startup.log_startup_warnings();
+ prosody.config_loaded = true;
+ startup.load_libraries();
+ startup.init_http_client();
+ prosody.core_post_stanza = function ()
+ -- silence assert in core.moduleapi
+ error("Attempt to send stanzas from inside migrator.", 0);
+ end
end
+-- Command-line parsing
+local options = prosody.opts;
+
local envloadfile = require "util.envload".envloadfile;
local config_file = options.config or default_config;
@@ -69,28 +111,17 @@ if not config[to_store] then
print("Error: Output store '"..to_store.."' not found in the config file.");
end
-function load_store_handler(name)
- local store_type = config[name].type;
- if not store_type then
- print("Error: "..name.." store type not specified in the config file");
- return false;
- else
- local ok, err = pcall(require, "migrator."..store_type);
- if not ok then
- print(("Error: Failed to initialize '%s' store:\n\t%s")
- :format(name, err));
- return false;
- end
+for store, conf in pairs(config) do -- COMPAT
+ if conf.type == "prosody_files" then
+ conf.type = "internal";
+ elseif conf.type == "prosody_sql" then
+ conf.type = "sql";
end
- return true;
end
-have_err = have_err or not(load_store_handler(from_store, "input") and load_store_handler(to_store, "output"));
-
if have_err then
print("");
- print("Usage: "..arg[0].." FROM_STORE TO_STORE");
- print("If no stores are specified, 'input' and 'output' are used.");
+ usage();
print("");
print("The available stores in your migrator config are:");
print("");
@@ -101,17 +132,126 @@ if have_err then
os.exit(1);
end
-local itype = config[from_store].type;
-local otype = config[to_store].type;
-local reader = require("migrator."..itype).reader(config[from_store]);
-local writer = require("migrator."..otype).writer(config[to_store]);
+local async = require "util.async";
+local server = require "net.server";
+local watchers = {
+ error = function (_, err)
+ error(err);
+ end;
+ waiting = function ()
+ server.loop();
+ end;
+};
-local json = require "util.json";
+local cm = require "core.configmanager";
+local hm = require "core.hostmanager";
+local sm = require "core.storagemanager";
+local um = require "core.usermanager";
-io.stderr:write("Migrating...\n");
-for x in reader do
- --print(json.encode(x))
- writer(x);
+local function users(store, host)
+ if store.users then
+ log("debug", "Using store user iterator")
+ return store:users();
+ else
+ log("debug", "Using usermanagre user iterator")
+ return um.users(host);
+ end
+end
+
+local function prepare_config(host, conf)
+ if conf.type == "internal" then
+ sm.olddm.set_data_path(conf.path or prosody.paths.data);
+ elseif conf.type == "sql" then
+ cm.set(host, "sql", conf);
+ end
+end
+
+local function get_driver(host, conf)
+ prepare_config(host, conf);
+ return assert(sm.load_driver(host, conf.type));
+end
+
+local migrate_once = {
+ keyval = function(origin, destination, user)
+ local data, err = origin:get(user);
+ assert(not err, err);
+ assert(destination:set(user, data));
+ end;
+ archive = function(origin, destination, user)
+ local iter, err = origin:find(user);
+ assert(iter, err);
+ for id, item, when, with in iter do
+ assert(destination:append(user, id, item, when, with));
+ end
+ end;
+}
+migrate_once.pubsub = function(origin, destination, user, prefix, input_driver, output_driver)
+ if not user and prefix == "pubsub_" then return end
+ local data, err = origin:get(user);
+ assert(not err, err);
+ if not data then return end
+ assert(destination:set(user, data));
+ if prefix == "pubsub_" then user = nil end
+ for node in pairs(data) do
+ local pep_origin = assert(input_driver:open(prefix .. node, "archive"));
+ local pep_destination = assert(output_driver:open(prefix .. node, "archive"));
+ migrate_once.archive(pep_origin, pep_destination, user);
+ end
end
-writer(nil); -- close
+
+if options["keep-going"] then
+ local xpcall = require "util.xpcall".xpcall;
+ for t, f in pairs(migrate_once) do
+ migrate_once[t] = function (origin, destination, user, ...)
+ local function log_err(err)
+ if user then
+ log("error", "Error migrating data for user %q: %s", user, err);
+ else
+ log("error", "Error migrating data for host: %s", err);
+ end
+ log("debug", "%s", debug.traceback(nil, 2));
+ end
+ xpcall(f, log_err, origin, destination, user, ...);
+ end
+ end
+end
+
+local migration_runner = async.runner(function (job)
+ for host, stores in pairs(job.input.hosts) do
+ prosody.hosts[host] = startup.make_host(host);
+ sm.initialize_host(host);
+ um.initialize_host(host);
+
+ local input_driver = get_driver(host, job.input);
+
+ local output_driver = get_driver(host, job.output);
+
+ for _, store in ipairs(stores) do
+ local p, typ = store:match("()%-(%w+)$");
+ if typ then store = store:sub(1, p-1); else typ = "keyval"; end
+ log("info", "Migrating host %s store %s (%s)", host, store, typ);
+
+ local migrate = assert(migrate_once[typ], "Unknown store type: "..typ);
+
+ local prefix = store .. "_";
+ if typ == "pubsub" then typ = "keyval"; end
+ if store == "pubsub_nodes" then prefix = "pubsub_"; end
+
+ local origin = assert(input_driver:open(store, typ));
+ local destination = assert(output_driver:open(store, typ));
+
+ migrate(origin, destination, nil, prefix, input_driver, output_driver); -- host data
+
+ for user in users(origin, host) do
+ log("info", "Migrating user %s@%s store %s (%s)", user, host, store, typ);
+ migrate(origin, destination, user, prefix, input_driver, output_driver);
+ end
+ end
+ end
+end, watchers);
+
+io.stderr:write("Migrating...\n");
+
+migration_runner:run({ input = config[from_store], output = config[to_store] });
+
io.stderr:write("Done!\n");
diff --git a/tools/modtrace.lua b/tools/modtrace.lua
new file mode 100644
index 00000000..45fa9f6a
--- /dev/null
+++ b/tools/modtrace.lua
@@ -0,0 +1,159 @@
+-- Trace module calls and method calls on created objects
+--
+-- Very rough and for debugging purposes only. It makes many
+-- assumptions and there are many ways it could fail.
+--
+-- Example use:
+--
+-- local dbuffer = require "tools.modtrace".trace("util.dbuffer");
+--
+
+local t_pack = require "util.table".pack;
+local serialize = require "util.serialization".serialize;
+local unpack = table.unpack or unpack; --luacheck: ignore 113
+local set = require "util.set";
+
+local serialize_cfg = {
+ preset = "oneline";
+ freeze = true;
+ fatal = false;
+ fallback = function (v) return "<"..tostring(v)..">" end;
+};
+
+local function stringify_value(v)
+ if type(v) == "string" and #v > 20 then
+ return ("<string(%d)>"):format(#v);
+ elseif type(v) == "function" then
+ return tostring(v);
+ end
+ return serialize(v, serialize_cfg);
+end
+
+local function stringify_params(...)
+ local n = select("#", ...);
+ local r = {};
+ for i = 1, n do
+ table.insert(r, stringify_value((select(i, ...))));
+ end
+ return table.concat(r, ", ");
+end
+
+local function stringify_result(ret)
+ local r = {};
+ for i = 1, ret.n do
+ table.insert(r, stringify_value(ret[i]));
+ end
+ return table.concat(r, ", ");
+end
+
+local function stringify_call(method_name, ...)
+ return ("%s(%s)"):format(method_name, stringify_params(...));
+end
+
+local function wrap_method(original_obj, original_method, method_name)
+ method_name = ("<%s>:%s"):format(getmetatable(original_obj).__name or "object", method_name);
+ return function (new_obj_self, ...)
+ local opts = new_obj_self._modtrace_opts;
+ local f = opts.output or io.stderr;
+ f:write(stringify_call(method_name, ...));
+ local ret = t_pack(original_method(original_obj, ...));
+ if ret.n > 0 then
+ f:write(" = ", stringify_result(ret), "\n");
+ else
+ f:write("\n");
+ end
+ return unpack(ret, 1, ret.n);
+ end;
+end
+
+local function wrap_function(original_function, function_name, opts)
+ local f = opts.output or io.stderr;
+ return function (...)
+ f:write(stringify_call(function_name, ...));
+ local ret = t_pack(original_function(...));
+ if ret.n > 0 then
+ f:write(" = ", stringify_result(ret), "\n");
+ else
+ f:write("\n");
+ end
+ return unpack(ret, 1, ret.n);
+ end;
+end
+
+local function wrap_metamethod(name, method)
+ if name == "__index" then
+ return function (new_obj, k)
+ local original_method;
+ if type(method) == "table" then
+ original_method = new_obj._modtrace_original_obj[k];
+ else
+ original_method = method(new_obj._modtrace_original_obj, k);
+ end
+ if original_method == nil then
+ return nil;
+ end
+ return wrap_method(new_obj._modtrace_original_obj, original_method, k);
+ end;
+ end
+ return function (new_obj, ...)
+ return method(new_obj._modtrace_original_obj, ...);
+ end;
+end
+
+local function wrap_mt(original_mt)
+ local new_mt = {};
+ for k, v in pairs(original_mt) do
+ new_mt[k] = wrap_metamethod(k, v);
+ end
+ return new_mt;
+end
+
+local function wrap_obj(original_obj, opts)
+ local new_mt = wrap_mt(getmetatable(original_obj));
+ return setmetatable({_modtrace_original_obj = original_obj, _modtrace_opts = opts}, new_mt);
+end
+
+local function wrap_new(original_new, function_name, opts)
+ local f = opts.output or io.stderr;
+ return function (...)
+ f:write(stringify_call(function_name, ...));
+ local ret = t_pack(original_new(...));
+ local obj = ret[1];
+
+ if ret.n == 1 and type(ret[1]) == "table" then
+ f:write(" = <", getmetatable(ret[1]).__name or "object", ">", "\n");
+ elseif ret.n > 0 then
+ f:write(" = ", stringify_result(ret), "\n");
+ else
+ f:write("\n");
+ end
+
+ if obj then
+ ret[1] = wrap_obj(obj, opts);
+ end
+ return unpack(ret, 1, ret.n);
+ end;
+end
+
+local function trace(module, opts)
+ if type(module) == "string" then
+ module = require(module);
+ end
+ opts = opts or {};
+ local new_methods = set.new(opts.new_methods or {"new"});
+ local fake_module = setmetatable({}, {
+ __index = function (_, k)
+ if new_methods:contains(k) then
+ return wrap_new(module[k], k, opts);
+ else
+ return wrap_function(module[k], k, opts);
+ end
+ end;
+ });
+ return fake_module;
+end
+
+return {
+ wrap = trace;
+ trace = trace;
+}
diff --git a/tools/tb2err b/tools/tb2err
new file mode 100755
index 00000000..7b676813
--- /dev/null
+++ b/tools/tb2err
@@ -0,0 +1,21 @@
+#!/usr/bin/env lua-any
+-- Lua-Versions: 5.3 5.2 5.1
+-- traceback to errors.err for vim -q
+local path_sep = package.config:sub(1,1);
+for line in io.lines() do
+ local src, err = line:match("%s*(%S+)(:%d+: .*)")
+ if src then
+ src = src:gsub("\\", path_sep);
+ local cut = src:match("/()core/")
+ or src:match("/()net/")
+ or src:match("/()util/")
+ or src:match("/()modules/")
+ or src:match("/()plugins/")
+ or src:match("/()prosody[ctl]*$")
+ if cut then
+ src = src:sub(cut);
+ end
+ src = src:gsub("^modules/", "plugins/")
+ io.write(src, err, "\n");
+ end
+end
diff --git a/tools/xep227toprosody.lua b/tools/xep227toprosody.lua
deleted file mode 100755
index 521851e9..00000000
--- a/tools/xep227toprosody.lua
+++ /dev/null
@@ -1,269 +0,0 @@
-#!/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
-
-local my_name = arg[0];
-if my_name:match("[/\\]") then
- package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
- package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
-end
-
--- 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 xmppstream = require "util.xmppstream";
-local new_xmpp_handlers = xmppstream.new_sax_handlers;
-local dm = require "util.datamanager"
-dm.set_data_path("data");
-
-local ns_separator = xmppstream.ns_separator;
-local ns_pattern = xmppstream.ns_pattern;
-
-local xmlns_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("item") do
- -- jid for this roster-item
- local item_jid = item_tag.attr.jid
- -- validate item stanzas
- if (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("group") do
- local group_name = group_tag:get_text();
- if (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 ipairs(private_items.tags) 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("message", "jabber:client") 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 = xmlns_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 = new_xmpp_handlers({ 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(parser, elementname, attributes);
- elseif (curr_ns == xmlns_xep227) and (name == "host") then
- curr_host = attributes["jid"]; -- start of host element
- print("Begin parsing host "..curr_host);
- elseif (curr_ns ~= xmlns_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 == xmlns_xep227) and (name == "host") then
- print("End parsing host "..curr_host);
- curr_host = "" -- end of host element
- else
- -- forward to xmlhandlers
- user_handlers.EndElement(parser, elementname);
- end
- elseif (curr_ns ~= xmlns_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(parser, 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();