Merge 0.10->trunk
[prosody.git] / tools / migration / migrator / jabberd14.lua
1
2 local lfs = require "lfs";
3 local st = require "util.stanza";
4 local parse_xml = require "util.xml".parse;
5 local os_getenv = os.getenv;
6 local io_open = io.open;
7 local assert = assert;
8 local ipairs = ipairs;
9 local coroutine = coroutine;
10 local print = print;
11
12 module "jabberd14"
13
14 local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end
15 local function is_file(path) return lfs.attributes(path, "mode") == "file"; end
16 local function clean_path(path)
17         return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~");
18 end
19
20 local function load_xml(path)
21         local f, err = io_open(path);
22         if not f then return f, err; end
23         local data = f:read("*a");
24         f:close();
25         if not data then return; end
26         return parse_xml(data);
27 end
28
29 local function load_spool_file(host, filename, path)
30         local xml = load_xml(path);
31         if not xml then return; end
32
33         local register_element = xml:get_child("query", "jabber:iq:register");
34         local username_element = register_element and register_element:get_child("username", "jabber:iq:register");
35         local password_element = register_element and register_element:get_child("password", "jabber:iq:auth");
36         local username = username_element and username_element:get_text();
37         local password = password_element and password_element:get_text();
38         if not username then
39                 print("[warn] Missing /xdb/{jabber:iq:register}register/username> in file "..filename)
40                 return;
41         elseif username..".xml" ~= filename then
42                 print("[warn] Missing /xdb/{jabber:iq:register}register/username does not match filename "..filename);
43                 return;
44         end
45
46         local userdata = {
47                 user = username;
48                 host = host;
49                 stores = {};
50         };
51         local stores = userdata.stores;
52         stores.accounts = { password = password };
53
54         for i=1,#xml.tags do
55                 local tag = xml.tags[i];
56                 local xname = (tag.attr.xmlns or "")..":"..tag.name;
57                 if tag.attr.j_private_flag == "1" and tag.attr.xmlns then
58                         -- Private XML
59                         stores.private = stores.private or {};
60                         tag.attr.j_private_flag = nil;
61                         stores.private[tag.attr.xmlns] = st.preserialize(tag);
62                 elseif xname == "jabber:iq:auth:password" then
63                         if stores.accounts.password ~= tag:get_text() then
64                                 if password then
65                                         print("[warn] conflicting passwords")
66                                 else
67                                         stores.accounts.password = tag:get_text();
68                                 end
69                         end
70                 elseif xname == "jabber:iq:register:query" then
71                         -- already processed
72                 elseif xname == "jabber:xdb:nslist:foo" then
73                         -- ignore
74                 elseif xname == "jabber:iq:auth:0k:zerok" then
75                         -- ignore
76                 elseif xname == "jabber:iq:roster:query" then
77                         -- Roster
78                         local roster = {};
79                         local subscription_types = { from = true, to = true, both = true, none = true };
80                         for _,item_element in ipairs(tag.tags) do
81                                 assert(item_element.name == "item");
82                                 assert(item_element.attr.jid);
83                                 assert(subscription_types[item_element.attr.subscription]);
84                                 assert((item_element.attr.ask or "subscribe") == "subscribe")
85                                 if item_element.name == "item" then
86                                         local groups = {};
87                                         for _,group_element in ipairs(item_element.tags) do
88                                                 assert(group_element.name == "group");
89                                                 groups[group_element:get_text()] = true;
90                                         end
91                                         local item = {
92                                                 name = item_element.attr.name;
93                                                 subscription = item_element.attr.subscription;
94                                                 ask = item_element.attr.ask;
95                                                 groups = groups;
96                                         };
97                                         roster[item_element.attr.jid] = item;
98                                 end
99                         end
100                         stores.roster = roster;
101                 elseif xname == "jabber:iq:last:query" then
102                         -- Last activity
103                 elseif xname == "jabber:x:offline:foo" then
104                         -- Offline messages
105                 elseif xname == "vcard-temp:vCard" then
106                         -- vCards
107                         stores.vcard = st.preserialize(tag);
108                 else
109                         print("[warn] Unknown tag: "..xname);
110                 end
111         end
112         return userdata;
113 end
114
115 local function loop_over_users(path, host, cb)
116         for file in lfs.dir(path) do
117                 if file:match("%.xml$") then
118                         local user = load_spool_file(host, file, path.."/"..file);
119                         if user then cb(user); end
120                 end
121         end
122 end
123 local function loop_over_hosts(path, cb)
124         for host in lfs.dir(path) do
125                 if host ~= "." and host ~= ".." and is_dir(path.."/"..host) then
126                         loop_over_users(path.."/"..host, host, cb);
127                 end
128         end
129 end
130
131 function reader(input)
132         local path = clean_path(assert(input.path, "no input.path specified"));
133         assert(is_dir(path), "input.path is not a directory");
134
135         if input.host then
136                 return coroutine.wrap(function() loop_over_users(input.path, input.host, coroutine.yield) end);
137         else
138                 return coroutine.wrap(function() loop_over_hosts(input.path, coroutine.yield) end);
139         end
140 end
141
142 return _M;