tools/ejabberd2prosody: Fixed private storage export
[prosody.git] / core / rostermanager.lua
1 -- Prosody IM v0.4
2 -- Copyright (C) 2008-2009 Matthew Wild
3 -- Copyright (C) 2008-2009 Waqas Hussain
4 -- 
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9
10
11
12 local log = require "util.logger".init("rostermanager");
13
14 local setmetatable = setmetatable;
15 local format = string.format;
16 local loadfile, setfenv, pcall = loadfile, setfenv, pcall;
17 local pairs, ipairs = pairs, ipairs;
18 local tostring = tostring;
19
20 local hosts = hosts;
21
22 local datamanager = require "util.datamanager"
23 local st = require "util.stanza";
24
25 module "rostermanager"
26
27 function add_to_roster(session, jid, item)
28         if session.roster then
29                 local old_item = session.roster[jid];
30                 session.roster[jid] = item;
31                 if save_roster(session.username, session.host) then
32                         return true;
33                 else
34                         session.roster[jid] = old_item;
35                         return nil, "wait", "internal-server-error", "Unable to save roster";
36                 end
37         else
38                 return nil, "auth", "not-authorized", "Session's roster not loaded";
39         end
40 end
41
42 function remove_from_roster(session, jid)
43         if session.roster then
44                 local old_item = session.roster[jid];
45                 session.roster[jid] = nil;
46                 if save_roster(session.username, session.host) then
47                         return true;
48                 else
49                         session.roster[jid] = old_item;
50                         return nil, "wait", "internal-server-error", "Unable to save roster";
51                 end
52         else
53                 return nil, "auth", "not-authorized", "Session's roster not loaded";
54         end
55 end
56
57 function roster_push(username, host, jid)
58         local roster = jid and jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
59         if roster then
60                 local item = hosts[host].sessions[username].roster[jid];
61                 local stanza = st.iq({type="set"});
62                 stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1")  });
63                 if item then
64                         stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
65                         for group in pairs(item.groups) do
66                                 stanza:tag("group"):text(group):up();
67                         end
68                 else
69                         stanza:tag("item", {jid = jid, subscription = "remove"});
70                 end
71                 stanza:up(); -- move out from item
72                 stanza:up(); -- move out from stanza
73                 -- stanza ready
74                 for _, session in pairs(hosts[host].sessions[username].sessions) do
75                         if session.interested then
76                                 -- FIXME do we need to set stanza.attr.to?
77                                 session.send(stanza);
78                         end
79                 end
80         end
81 end
82
83 function load_roster(username, host)
84         log("debug", "load_roster: asked for: "..username.."@"..host);
85         if hosts[host] and hosts[host].sessions[username] then
86                 local roster = hosts[host].sessions[username].roster;
87                 if not roster then
88                         log("debug", "load_roster: loading for new user: "..username.."@"..host);
89                         roster = datamanager.load(username, host, "roster") or {};
90                         if not roster[false] then roster[false] = { }; end
91                         hosts[host].sessions[username].roster = roster;
92                 end
93                 return roster;
94         end
95         -- Attempt to load roster for non-loaded user
96         log("debug", "load_roster: loading for offline user: "..username.."@"..host);
97         return datamanager.load(username, host, "roster") or {};
98 end
99
100 function save_roster(username, host)
101         log("debug", "save_roster: saving roster for "..username.."@"..host);
102         if hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster then
103                 local roster = hosts[host].sessions[username].roster;
104                 roster[false].version = (roster[false].version or 1) + 1;
105                 return datamanager.store(username, host, "roster", hosts[host].sessions[username].roster);
106         end
107         log("warn", "save_roster: user had no roster to save");
108         return nil;
109 end
110
111 function process_inbound_subscription_approval(username, host, jid)
112         local roster = load_roster(username, host);
113         local item = roster[jid];
114         if item and item.ask then
115                 if item.subscription == "none" then
116                         item.subscription = "to";
117                 else -- subscription == from
118                         item.subscription = "both";
119                 end
120                 item.ask = nil;
121                 return datamanager.store(username, host, "roster", roster);
122         end
123 end
124
125 function process_inbound_subscription_cancellation(username, host, jid)
126         local roster = load_roster(username, host);
127         local item = roster[jid];
128         local changed = nil;
129         if is_contact_pending_out(username, host, jid) then
130                 item.ask = nil;
131                 changed = true;
132         end
133         if item then
134                 if item.subscription == "to" then
135                         item.subscription = "none";
136                         changed = true;
137                 elseif item.subscription == "both" then
138                         item.subscription = "from";
139                         changed = true;
140                 end
141         end
142         if changed then
143                 return datamanager.store(username, host, "roster", roster);
144         end
145 end
146
147 function process_inbound_unsubscribe(username, host, jid)
148         local roster = load_roster(username, host);
149         local item = roster[jid];
150         local changed = nil;
151         if is_contact_pending_in(username, host, jid) then
152                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
153                 changed = true;
154         end
155         if item then
156                 if item.subscription == "from" then
157                         item.subscription = "none";
158                         changed = true;
159                 elseif item.subscription == "both" then
160                         item.subscription = "to";
161                         changed = true;
162                 end
163         end
164         if changed then
165                 return datamanager.store(username, host, "roster", roster);
166         end
167 end
168
169 function is_contact_subscribed(username, host, jid)
170         local roster = load_roster(username, host);
171         local item = roster[jid];
172         return item and (item.subscription == "from" or item.subscription == "both");
173 end
174
175 function is_contact_pending_in(username, host, jid)
176         local roster = load_roster(username, host);
177         return roster.pending and roster.pending[jid];
178 end
179 function set_contact_pending_in(username, host, jid, pending)
180         local roster = load_roster(username, host);
181         local item = roster[jid];
182         if item and (item.subscription == "from" or item.subscription == "both") then
183                 return; -- false
184         end
185         if not roster.pending then roster.pending = {}; end
186         roster.pending[jid] = true;
187         return datamanager.store(username, host, "roster", roster);
188 end
189 function is_contact_pending_out(username, host, jid)
190         local roster = load_roster(username, host);
191         local item = roster[jid];
192         return item and item.ask;
193 end
194 function set_contact_pending_out(username, host, jid) -- subscribe
195         local roster = load_roster(username, host);
196         local item = roster[jid];
197         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
198                 return true;
199         end
200         if not item then
201                 item = {subscription = "none", groups = {}};
202                 roster[jid] = item;
203         end
204         item.ask = "subscribe";
205         log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
206         return datamanager.store(username, host, "roster", roster);
207 end
208 function unsubscribe(username, host, jid)
209         local roster = load_roster(username, host);
210         local item = roster[jid];
211         if not item then return false; end
212         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
213                 return true;
214         end
215         item.ask = nil;
216         if item.subscription == "both" then
217                 item.subscription = "from";
218         elseif item.subscription == "to" then
219                 item.subscription = "none";
220         end
221         return datamanager.store(username, host, "roster", roster);
222 end
223 function subscribed(username, host, jid)
224         if is_contact_pending_in(username, host, jid) then
225                 local roster = load_roster(username, host);
226                 local item = roster[jid];
227                 if not item then -- FIXME should roster item be auto-created?
228                         item = {subscription = "none", groups = {}};
229                         roster[jid] = item;
230                 end
231                 if item.subscription == "none" then
232                         item.subscription = "from";
233                 else -- subscription == to
234                         item.subscription = "both";
235                 end
236                 roster.pending[jid] = nil;
237                 -- TODO maybe remove roster.pending if empty
238                 return datamanager.store(username, host, "roster", roster);
239         end -- TODO else implement optional feature pre-approval (ask = subscribed)
240 end
241 function unsubscribed(username, host, jid)
242         local roster = load_roster(username, host);
243         local item = roster[jid];
244         local pending = is_contact_pending_in(username, host, jid);
245         local changed = nil;
246         if is_contact_pending_in(username, host, jid) then
247                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
248                 changed = true;
249         end
250         if item then
251                 if item.subscription == "from" then
252                         item.subscription = "none";
253                         changed = true;
254                 elseif item.subscription == "both" then
255                         item.subscription = "to";
256                         changed = true;
257                 end
258         end
259         if changed then
260                 return datamanager.store(username, host, "roster", roster);
261         end
262 end
263
264 function process_outbound_subscription_request(username, host, jid)
265         local roster = load_roster(username, host);
266         local item = roster[jid];
267         if item and (item.subscription == "none" or item.subscription == "from") then
268                 item.ask = "subscribe";
269                 return datamanager.store(username, host, "roster", roster);
270         end
271 end
272
273 --[[function process_outbound_subscription_approval(username, host, jid)
274         local roster = load_roster(username, host);
275         local item = roster[jid];
276         if item and (item.subscription == "none" or item.subscription == "from" then
277                 item.ask = "subscribe";
278                 return datamanager.store(username, host, "roster", roster);
279         end
280 end]]
281
282
283
284 return _M;