54c9708372c7d53c77ebb75a9a3f72f8e2b66d06
[prosody.git] / core / rostermanager.lua
1 -- Prosody IM
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         local roster;
86         if hosts[host] and hosts[host].sessions[username] then
87                 roster = hosts[host].sessions[username].roster;
88                 if not roster then
89                         log("debug", "load_roster: loading for new user: "..username.."@"..host);
90                         roster = datamanager.load(username, host, "roster") or {};
91                         if not roster[false] then roster[false] = { }; end
92                         roster[username.."@"..host] = nil;
93                         hosts[host].sessions[username].roster = roster;
94                         hosts[host].events.fire_event("roster-load", username, host, roster);
95                 end
96                 return roster;
97         end
98         
99         -- Attempt to load roster for non-loaded user
100         log("debug", "load_roster: loading for offline user: "..username.."@"..host);
101         roster = datamanager.load(username, host, "roster") or {};
102         roster[username.."@"..host] = nil;
103         hosts[host].events.fire_event("roster-load", username, host, roster);
104         return roster;
105 end
106
107 function save_roster(username, host, roster)
108         log("debug", "save_roster: saving roster for "..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                 if not roster[false] then roster[false] = {}; end
118                 roster[false].version = (roster[false].version or 0) + 1;
119                 return datamanager.store(username, host, "roster", roster);
120         end
121         log("warn", "save_roster: user had no roster to save");
122         return nil;
123 end
124
125 function process_inbound_subscription_approval(username, host, jid)
126         local roster = load_roster(username, host);
127         local item = roster[jid];
128         if item and item.ask then
129                 if item.subscription == "none" then
130                         item.subscription = "to";
131                 else -- subscription == from
132                         item.subscription = "both";
133                 end
134                 item.ask = nil;
135                 return save_roster(username, host, roster);
136         end
137 end
138
139 function process_inbound_subscription_cancellation(username, host, jid)
140         local roster = load_roster(username, host);
141         local item = roster[jid];
142         local changed = nil;
143         if is_contact_pending_out(username, host, jid) then
144                 item.ask = nil;
145                 changed = true;
146         end
147         if item then
148                 if item.subscription == "to" then
149                         item.subscription = "none";
150                         changed = true;
151                 elseif item.subscription == "both" then
152                         item.subscription = "from";
153                         changed = true;
154                 end
155         end
156         if changed then
157                 return save_roster(username, host, roster);
158         end
159 end
160
161 function process_inbound_unsubscribe(username, host, jid)
162         local roster = load_roster(username, host);
163         local item = roster[jid];
164         local changed = nil;
165         if is_contact_pending_in(username, host, jid) then
166                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
167                 changed = true;
168         end
169         if item then
170                 if item.subscription == "from" then
171                         item.subscription = "none";
172                         changed = true;
173                 elseif item.subscription == "both" then
174                         item.subscription = "to";
175                         changed = true;
176                 end
177         end
178         if changed then
179                 return save_roster(username, host, roster);
180         end
181 end
182
183 function is_contact_subscribed(username, host, jid)
184         local roster = load_roster(username, host);
185         local item = roster[jid];
186         return item and (item.subscription == "from" or item.subscription == "both");
187 end
188
189 function is_contact_pending_in(username, host, jid)
190         local roster = load_roster(username, host);
191         return roster.pending and roster.pending[jid];
192 end
193 function set_contact_pending_in(username, host, jid, pending)
194         local roster = load_roster(username, host);
195         local item = roster[jid];
196         if item and (item.subscription == "from" or item.subscription == "both") then
197                 return; -- false
198         end
199         if not roster.pending then roster.pending = {}; end
200         roster.pending[jid] = true;
201         return save_roster(username, host, roster);
202 end
203 function is_contact_pending_out(username, host, jid)
204         local roster = load_roster(username, host);
205         local item = roster[jid];
206         return item and item.ask;
207 end
208 function set_contact_pending_out(username, host, jid) -- subscribe
209         local roster = load_roster(username, host);
210         local item = roster[jid];
211         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
212                 return true;
213         end
214         if not item then
215                 item = {subscription = "none", groups = {}};
216                 roster[jid] = item;
217         end
218         item.ask = "subscribe";
219         log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
220         return save_roster(username, host, roster);
221 end
222 function unsubscribe(username, host, jid)
223         local roster = load_roster(username, host);
224         local item = roster[jid];
225         if not item then return false; end
226         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
227                 return true;
228         end
229         item.ask = nil;
230         if item.subscription == "both" then
231                 item.subscription = "from";
232         elseif item.subscription == "to" then
233                 item.subscription = "none";
234         end
235         return save_roster(username, host, roster);
236 end
237 function subscribed(username, host, jid)
238         if is_contact_pending_in(username, host, jid) then
239                 local roster = load_roster(username, host);
240                 local item = roster[jid];
241                 if not item then -- FIXME should roster item be auto-created?
242                         item = {subscription = "none", groups = {}};
243                         roster[jid] = item;
244                 end
245                 if item.subscription == "none" then
246                         item.subscription = "from";
247                 else -- subscription == to
248                         item.subscription = "both";
249                 end
250                 roster.pending[jid] = nil;
251                 -- TODO maybe remove roster.pending if empty
252                 return save_roster(username, host, roster);
253         end -- TODO else implement optional feature pre-approval (ask = subscribed)
254 end
255 function unsubscribed(username, host, jid)
256         local roster = load_roster(username, host);
257         local item = roster[jid];
258         local pending = is_contact_pending_in(username, host, jid);
259         local changed = nil;
260         if is_contact_pending_in(username, host, jid) then
261                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
262                 changed = true;
263         end
264         if item then
265                 if item.subscription == "from" then
266                         item.subscription = "none";
267                         changed = true;
268                 elseif item.subscription == "both" then
269                         item.subscription = "to";
270                         changed = true;
271                 end
272         end
273         if changed then
274                 return save_roster(username, host, roster);
275         end
276 end
277
278 function process_outbound_subscription_request(username, host, jid)
279         local roster = load_roster(username, host);
280         local item = roster[jid];
281         if item and (item.subscription == "none" or item.subscription == "from") then
282                 item.ask = "subscribe";
283                 return save_roster(username, host, roster);
284         end
285 end
286
287 --[[function process_outbound_subscription_approval(username, host, jid)
288         local roster = load_roster(username, host);
289         local item = roster[jid];
290         if item and (item.subscription == "none" or item.subscription == "from" then
291                 item.ask = "subscribe";
292                 return save_roster(username, host, roster);
293         end
294 end]]
295
296
297
298 return _M;