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