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