aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/mod_roster.lua
blob: d301dd78d23b4e3e17d104b544b3a209305d245a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
-- Prosody IM
-- Copyright (C) 2008-2009 Matthew Wild
-- Copyright (C) 2008-2009 Waqas Hussain
-- 
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--



local st = require "util.stanza"

local jid_split = require "util.jid".split;
local jid_prep = require "util.jid".prep;
local t_concat = table.concat;
local tostring = tostring;

local rm_remove_from_roster = require "core.rostermanager".remove_from_roster;
local rm_add_to_roster = require "core.rostermanager".add_to_roster;
local rm_roster_push = require "core.rostermanager".roster_push;
local core_post_stanza = core_post_stanza;

module:add_feature("jabber:iq:roster");

local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}):tag("optional"):up();
module:hook("stream-features", function(event)
	local origin, features = event.origin, event.features;
	if origin.username then
		features:add_child(rosterver_stream_feature);
	end
end);

module:add_iq_handler("c2s", "jabber:iq:roster", 
		function (session, stanza)
			if stanza.tags[1].name == "query" then
				if stanza.attr.type == "get" then
					local roster = st.reply(stanza);
					
					local client_ver = tonumber(stanza.tags[1].attr.ver);
					local server_ver = tonumber(session.roster[false].version or 1);
					
					if not (client_ver and server_ver) or client_ver ~= server_ver then
						roster:query("jabber:iq:roster");
						-- Client does not support versioning, or has stale roster
						for jid in pairs(session.roster) do
							if jid ~= "pending" and jid then
								roster:tag("item", {
									jid = jid,
									subscription = session.roster[jid].subscription,
									ask = session.roster[jid].ask,
									name = session.roster[jid].name,
								});
								for group in pairs(session.roster[jid].groups) do
									roster:tag("group"):text(group):up();
								end
								roster:up(); -- move out from item
							end
						end
						roster.tags[1].attr.ver = server_ver;
					end
					session.send(roster);
					session.interested = true; -- resource is interested in roster updates
					return true;
				elseif stanza.attr.type == "set" then
					local query = stanza.tags[1];
					if #query.tags == 1 and query.tags[1].name == "item"
							and query.tags[1].attr.xmlns == "jabber:iq:roster" and query.tags[1].attr.jid 
							-- Protection against overwriting roster.pending, until we move it
							and query.tags[1].attr.jid ~= "pending" then
						local item = query.tags[1];
						local from_node, from_host = jid_split(stanza.attr.from);
						local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID
						local jid = jid_prep(item.attr.jid);
						local node, host, resource = jid_split(jid);
						if not resource and host then
							if jid ~= from_node.."@"..from_host then
								if item.attr.subscription == "remove" then
									local roster = session.roster;
									local r_item = roster[jid];
									if r_item then
										local to_bare = node and (node.."@"..host) or host; -- bare JID
										if r_item.subscription == "both" or r_item.subscription == "from" or (roster.pending and roster.pending[jid]) then
											core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
										end
										if r_item.subscription == "both" or r_item.subscription == "to" or r_item.ask then
											core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
										end
										local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid);
										if success then
											session.send(st.reply(stanza));
											rm_roster_push(from_node, from_host, jid);
										else
											session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
										end
									else
										session.send(st.error_reply(stanza, "modify", "item-not-found"));
									end
								else
									local r_item = {name = item.attr.name, groups = {}};
									if r_item.name == "" then r_item.name = nil; end
									if session.roster[jid] then
										r_item.subscription = session.roster[jid].subscription;
										r_item.ask = session.roster[jid].ask;
									else
										r_item.subscription = "none";
									end
									for _, child in ipairs(item) do	
										if child.name == "group" then
											local text = t_concat(child);
											if text and text ~= "" then
												r_item.groups[text] = true;
											end
										end
									end
									local success, err_type, err_cond, err_msg = rm_add_to_roster(session, jid, r_item);
									if success then
										-- Ok, send success
										session.send(st.reply(stanza));
										-- and push change to all resources
										rm_roster_push(from_node, from_host, jid);
									else
										-- Adding to roster failed
										session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
									end
								end
							else
								-- Trying to add self to roster
								session.send(st.error_reply(stanza, "cancel", "not-allowed"));
							end
						else
							-- Invalid JID added to roster
							session.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME what's the correct error?
						end
					else
						-- Roster set didn't include a single item, or its name wasn't  'item'
						session.send(st.error_reply(stanza, "modify", "bad-request"));
					end
					return true;
				end
			end
		end);