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