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