mod_storage_none: A null-like storage provider that returns all stores as empty,...
[prosody.git] / core / rostermanager.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 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 pairs = pairs;
15 local tostring = tostring;
16
17 local hosts = hosts;
18 local bare_sessions = bare_sessions;
19
20 local datamanager = require "util.datamanager"
21 local st = require "util.stanza";
22
23 module "rostermanager"
24
25 function add_to_roster(session, jid, item)
26         if session.roster then
27                 local old_item = session.roster[jid];
28                 session.roster[jid] = item;
29                 if save_roster(session.username, session.host) then
30                         return true;
31                 else
32                         session.roster[jid] = old_item;
33                         return nil, "wait", "internal-server-error", "Unable to save roster";
34                 end
35         else
36                 return nil, "auth", "not-authorized", "Session's roster not loaded";
37         end
38 end
39
40 function remove_from_roster(session, jid)
41         if session.roster then
42                 local old_item = session.roster[jid];
43                 session.roster[jid] = nil;
44                 if save_roster(session.username, session.host) then
45                         return true;
46                 else
47                         session.roster[jid] = old_item;
48                         return nil, "wait", "internal-server-error", "Unable to save roster";
49                 end
50         else
51                 return nil, "auth", "not-authorized", "Session's roster not loaded";
52         end
53 end
54
55 function roster_push(username, host, jid)
56         local roster = jid and jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
57         if roster then
58                 local item = hosts[host].sessions[username].roster[jid];
59                 local stanza = st.iq({type="set"});
60                 stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1")  });
61                 if item then
62                         stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
63                         for group in pairs(item.groups) do
64                                 stanza:tag("group"):text(group):up();
65                         end
66                 else
67                         stanza:tag("item", {jid = jid, subscription = "remove"});
68                 end
69                 stanza:up(); -- move out from item
70                 stanza:up(); -- move out from stanza
71                 -- stanza ready
72                 for _, session in pairs(hosts[host].sessions[username].sessions) do
73                         if session.interested then
74                                 -- FIXME do we need to set stanza.attr.to?
75                                 session.send(stanza);
76                         end
77                 end
78         end
79 end
80
81 function load_roster(username, host)
82         local jid = username.."@"..host;
83         log("debug", "load_roster: asked for: %s", jid);
84         local user = bare_sessions[jid];
85         local roster;
86         if user then
87                 roster = user.roster;
88                 if roster then return roster; end
89                 log("debug", "load_roster: loading for new user: %s@%s", username, host);
90         else -- Attempt to load roster for non-loaded user
91                 log("debug", "load_roster: loading for offline user: %s@%s", username, host);
92         end
93         local data, err = datamanager.load(username, host, "roster");
94         roster = data or {};
95         if user then user.roster = roster; end
96         if not roster[false] then roster[false] = { broken = err or nil }; end
97         if roster[jid] then
98                 roster[jid] = nil;
99                 log("warn", "roster for %s has a self-contact", jid);
100         end
101         if not err then
102                 hosts[host].events.fire_event("roster-load", username, host, roster);
103         end
104         return roster, err;
105 end
106
107 function save_roster(username, host, roster)
108         log("debug", "save_roster: saving roster for %s@%s", username, host);
109         if not roster then
110                 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
111                 --if not roster then
112                 --      --roster = load_roster(username, host);
113                 --      return true; -- roster unchanged, no reason to save
114                 --end
115         end
116         if roster then
117                 local metadata = roster[false];
118                 if not metadata then
119                         metadata = {};
120                         roster[false] = metadata;
121                 end
122                 if metadata.version ~= true then
123                         metadata.version = (metadata.version or 0) + 1;
124                 end
125                 if roster[false].broken then return nil, "Not saving broken roster" end
126                 return datamanager.store(username, host, "roster", roster);
127         end
128         log("warn", "save_roster: user had no roster to save");
129         return nil;
130 end
131
132 function process_inbound_subscription_approval(username, host, jid)
133         local roster = load_roster(username, host);
134         local item = roster[jid];
135         if item and item.ask then
136                 if item.subscription == "none" then
137                         item.subscription = "to";
138                 else -- subscription == from
139                         item.subscription = "both";
140                 end
141                 item.ask = nil;
142                 return save_roster(username, host, roster);
143         end
144 end
145
146 function process_inbound_subscription_cancellation(username, host, jid)
147         local roster = load_roster(username, host);
148         local item = roster[jid];
149         local changed = nil;
150         if is_contact_pending_out(username, host, jid) then
151                 item.ask = nil;
152                 changed = true;
153         end
154         if item then
155                 if item.subscription == "to" then
156                         item.subscription = "none";
157                         changed = true;
158                 elseif item.subscription == "both" then
159                         item.subscription = "from";
160                         changed = true;
161                 end
162         end
163         if changed then
164                 return save_roster(username, host, roster);
165         end
166 end
167
168 function process_inbound_unsubscribe(username, host, jid)
169         local roster = load_roster(username, host);
170         local item = roster[jid];
171         local changed = nil;
172         if is_contact_pending_in(username, host, jid) then
173                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
174                 changed = true;
175         end
176         if item then
177                 if item.subscription == "from" then
178                         item.subscription = "none";
179                         changed = true;
180                 elseif item.subscription == "both" then
181                         item.subscription = "to";
182                         changed = true;
183                 end
184         end
185         if changed then
186                 return save_roster(username, host, roster);
187         end
188 end
189
190 local function _get_online_roster_subscription(jidA, jidB)
191         local user = bare_sessions[jidA];
192         local item = user and (user.roster[jidB] or { subscription = "none" });
193         return item and item.subscription;
194 end
195 function is_contact_subscribed(username, host, jid)
196         do
197                 local selfjid = username.."@"..host;
198                 local subscription = _get_online_roster_subscription(selfjid, jid);
199                 if subscription then return (subscription == "both" or subscription == "from"); end
200                 local subscription = _get_online_roster_subscription(jid, selfjid);
201                 if subscription then return (subscription == "both" or subscription == "to"); end
202         end
203         local roster, err = load_roster(username, host);
204         local item = roster[jid];
205         return item and (item.subscription == "from" or item.subscription == "both"), err;
206 end
207
208 function is_contact_pending_in(username, host, jid)
209         local roster = load_roster(username, host);
210         return roster.pending and roster.pending[jid];
211 end
212 function set_contact_pending_in(username, host, jid, pending)
213         local roster = load_roster(username, host);
214         local item = roster[jid];
215         if item and (item.subscription == "from" or item.subscription == "both") then
216                 return; -- false
217         end
218         if not roster.pending then roster.pending = {}; end
219         roster.pending[jid] = true;
220         return save_roster(username, host, roster);
221 end
222 function is_contact_pending_out(username, host, jid)
223         local roster = load_roster(username, host);
224         local item = roster[jid];
225         return item and item.ask;
226 end
227 function set_contact_pending_out(username, host, jid) -- subscribe
228         local roster = load_roster(username, host);
229         local item = roster[jid];
230         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
231                 return true;
232         end
233         if not item then
234                 item = {subscription = "none", groups = {}};
235                 roster[jid] = item;
236         end
237         item.ask = "subscribe";
238         log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
239         return save_roster(username, host, roster);
240 end
241 function unsubscribe(username, host, jid)
242         local roster = load_roster(username, host);
243         local item = roster[jid];
244         if not item then return false; end
245         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
246                 return true;
247         end
248         item.ask = nil;
249         if item.subscription == "both" then
250                 item.subscription = "from";
251         elseif item.subscription == "to" then
252                 item.subscription = "none";
253         end
254         return save_roster(username, host, roster);
255 end
256 function subscribed(username, host, jid)
257         if is_contact_pending_in(username, host, jid) then
258                 local roster = load_roster(username, host);
259                 local item = roster[jid];
260                 if not item then -- FIXME should roster item be auto-created?
261                         item = {subscription = "none", groups = {}};
262                         roster[jid] = item;
263                 end
264                 if item.subscription == "none" then
265                         item.subscription = "from";
266                 else -- subscription == to
267                         item.subscription = "both";
268                 end
269                 roster.pending[jid] = nil;
270                 -- TODO maybe remove roster.pending if empty
271                 return save_roster(username, host, roster);
272         end -- TODO else implement optional feature pre-approval (ask = subscribed)
273 end
274 function unsubscribed(username, host, jid)
275         local roster = load_roster(username, host);
276         local item = roster[jid];
277         local pending = is_contact_pending_in(username, host, jid);
278         if pending then
279                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
280         end
281         local subscribed;
282         if item then
283                 if item.subscription == "from" then
284                         item.subscription = "none";
285                         subscribed = true;
286                 elseif item.subscription == "both" then
287                         item.subscription = "to";
288                         subscribed = true;
289                 end
290         end
291         local success = (pending or subscribed) and save_roster(username, host, roster);
292         return success, pending, subscribed;
293 end
294
295 function process_outbound_subscription_request(username, host, jid)
296         local roster = load_roster(username, host);
297         local item = roster[jid];
298         if item and (item.subscription == "none" or item.subscription == "from") then
299                 item.ask = "subscribe";
300                 return save_roster(username, host, roster);
301         end
302 end
303
304 --[[function process_outbound_subscription_approval(username, host, jid)
305         local roster = load_roster(username, host);
306         local item = roster[jid];
307         if item and (item.subscription == "none" or item.subscription == "from" then
308                 item.ask = "subscribe";
309                 return save_roster(username, host, roster);
310         end
311 end]]
312
313
314
315 return _M;