Comparison

plugins/mod_blocklist.lua @ 6977:450db0b83fe9

Merge 0.10->trunk
author Kim Alvefur <zash@zash.se>
date Sun, 06 Dec 2015 02:43:01 +0100
parent 6976:4688ff9d4f2b
child 7079:f094683ae6eb
comparison
equal deleted inserted replaced
6966:3e3a83be7e14 6977:450db0b83fe9
1 -- Prosody IM 1 -- Prosody IM
2 -- Copyright (C) 2009-2010 Matthew Wild 2 -- Copyright (C) 2009-2010 Matthew Wild
3 -- Copyright (C) 2009-2010 Waqas Hussain 3 -- Copyright (C) 2009-2010 Waqas Hussain
4 -- Copyright (C) 2014 Kim Alvefur 4 -- Copyright (C) 2014-2015 Kim Alvefur
5 -- 5 --
6 -- This project is MIT/X11 licensed. Please see the 6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information. 7 -- COPYING file in the source package for more information.
8 -- 8 --
9 -- This module implements XEP-0191: Blocking Command 9 -- This module implements XEP-0191: Blocking Command
10 -- 10 --
11 11
12 local user_exists = require"core.usermanager".user_exists; 12 local user_exists = require"core.usermanager".user_exists;
13 local is_contact_subscribed = require"core.rostermanager".is_contact_subscribed; 13 local rostermanager = require"core.rostermanager";
14 local is_contact_subscribed = rostermanager.is_contact_subscribed;
15 local is_contact_pending_in = rostermanager.is_contact_pending_in;
16 local load_roster = rostermanager.load_roster;
17 local save_roster = rostermanager.save_roster;
14 local st = require"util.stanza"; 18 local st = require"util.stanza";
15 local st_error_reply = st.error_reply; 19 local st_error_reply = st.error_reply;
16 local jid_prep = require"util.jid".prep; 20 local jid_prep = require"util.jid".prep;
17 local jid_split = require"util.jid".split; 21 local jid_split = require"util.jid".split;
18 22
19 local storage = module:open_store(); 23 local storage = module:open_store();
20 local sessions = prosody.hosts[module.host].sessions; 24 local sessions = prosody.hosts[module.host].sessions;
21 25
22 -- Cache of blocklists by username may randomly expire at any time 26 -- First level cache of blocklists by username.
27 -- Weak table so may randomly expire at any time.
23 local cache = setmetatable({}, { __mode = "v" }); 28 local cache = setmetatable({}, { __mode = "v" });
24 29
25 -- Second level of caching, keeps a fixed number of items, 30 -- Second level of caching, keeps a fixed number of items, also anchors
26 -- also anchors items in the above cache 31 -- items in the above cache.
32 --
33 -- The size of this affects how often we will need to load a blocklist from
34 -- disk, which we want to avoid during routing. On the other hand, we don't
35 -- want to use too much memory either, so this can be tuned by advanced
36 -- users. TODO use science to figure out a better default, 64 is just a guess.
27 local cache_size = module:get_option_number("blocklist_cache_size", 64); 37 local cache_size = module:get_option_number("blocklist_cache_size", 64);
28 local cache2 = require"util.cache".new(cache_size); 38 local cache2 = require"util.cache".new(cache_size);
29 39
30 local null_blocklist = {}; 40 local null_blocklist = {};
31 41
108 -- Add or remove some jid(s) from the blocklist 118 -- Add or remove some jid(s) from the blocklist
109 -- We want this to be atomic and not do a partial update 119 -- We want this to be atomic and not do a partial update
110 local function edit_blocklist(event) 120 local function edit_blocklist(event)
111 local origin, stanza = event.origin, event.stanza; 121 local origin, stanza = event.origin, event.stanza;
112 local username = origin.username; 122 local username = origin.username;
113 local action = stanza.tags[1]; 123 local action = stanza.tags[1]; -- "block" or "unblock"
114 local new = {}; 124 local is_blocking = action.name == "block" or nil; -- nil if unblocking
125 local new = {}; -- JIDs to block depending or unblock on action
126
127 -- XEP-0191 sayeth:
128 -- > When the user blocks communications with the contact, the user's
129 -- > server MUST send unavailable presence information to the contact (but
130 -- > only if the contact is allowed to receive presence notifications [...]
131 -- So contacts we need to do that for are added to the set below.
132 local send_unavailable = is_blocking and {};
133
134 -- Because blocking someone currently also blocks the ability to reject
135 -- subscription requests, we'll preemptively reject such
136 local remove_pending = is_blocking and {};
115 137
116 for item in action:childtags("item") do 138 for item in action:childtags("item") do
117 local jid = jid_prep(item.attr.jid); 139 local jid = jid_prep(item.attr.jid);
118 if not jid then 140 if not jid then
119 origin.send(st_error_reply(stanza, "modify", "jid-malformed")); 141 origin.send(st_error_reply(stanza, "modify", "jid-malformed"));
120 return true; 142 return true;
121 end 143 end
122 item.attr.jid = jid; -- echo back prepped 144 item.attr.jid = jid; -- echo back prepped
123 new[jid] = is_contact_subscribed(username, module.host, jid) or false; 145 new[jid] = true;
124 end 146 if is_blocking then
125 147 if is_contact_subscribed(username, module.host, jid) then
126 local mode = action.name == "block" or nil; 148 send_unavailable[jid] = true;
127 149 elseif is_contact_pending_in(username, module.host, jid) then
128 if mode and not next(new) then 150 remove_pending[jid] = true;
151 end
152 end
153 end
154
155 if is_blocking and not next(new) then
129 -- <block/> element does not contain at least one <item/> child element 156 -- <block/> element does not contain at least one <item/> child element
130 origin.send(st_error_reply(stanza, "modify", "bad-request")); 157 origin.send(st_error_reply(stanza, "modify", "bad-request"));
131 return true; 158 return true;
132 end 159 end
133 160
134 local blocklist = get_blocklist(username); 161 local blocklist = get_blocklist(username);
135 162
136 local new_blocklist = {}; 163 local new_blocklist = {};
137 164
138 if mode or next(new) then 165 if is_blocking or next(new) then
139 for jid in pairs(blocklist) do 166 for jid in pairs(blocklist) do
140 new_blocklist[jid] = true; 167 new_blocklist[jid] = true;
141 end 168 end
142 for jid in pairs(new) do 169 for jid in pairs(new) do
143 new_blocklist[jid] = mode; 170 new_blocklist[jid] = is_blocking;
144 end 171 end
145 -- else empty the blocklist 172 -- else empty the blocklist
146 end 173 end
147 new_blocklist[false] = "not empty"; -- In order to avoid doing the migration thing twice 174 new_blocklist[false] = "not empty"; -- In order to avoid doing the migration thing twice
148 175
152 else 179 else
153 origin.send(st_error_reply(stanza, "wait", "internal-server-error", err)); 180 origin.send(st_error_reply(stanza, "wait", "internal-server-error", err));
154 return true; 181 return true;
155 end 182 end
156 183
157 if mode then 184 if is_blocking then
158 for jid, in_roster in pairs(new) do 185 for jid in pairs(send_unavailable) do
159 if not blocklist[jid] and in_roster and sessions[username] then 186 if not blocklist[jid] then
160 for _, session in pairs(sessions[username].sessions) do 187 for _, session in pairs(sessions[username].sessions) do
161 if session.presence then 188 if session.presence then
162 module:send(st.presence({ type = "unavailable", to = jid, from = session.full_jid })); 189 module:send(st.presence({ type = "unavailable", to = jid, from = session.full_jid }));
163 end 190 end
164 end 191 end
165 end 192 end
166 end 193 end
167 end 194
168 if sessions[username] then 195 if next(remove_pending) then
169 local blocklist_push = st.iq({ type = "set", id = "blocklist-push" }) 196 local roster = load_roster(username, module.host);
170 :add_child(action); -- I am lazy 197 for jid in pairs(remove_pending) do
171 198 roster[false].pending[jid] = nil;
172 for _, session in pairs(sessions[username].sessions) do
173 if session.interested_blocklist then
174 blocklist_push.attr.to = session.full_jid;
175 session.send(blocklist_push);
176 end 199 end
200 save_roster(username, module.host, roster);
201 -- Not much we can do about save failing here
202 end
203 end
204
205 local blocklist_push = st.iq({ type = "set", id = "blocklist-push" })
206 :add_child(action); -- I am lazy
207
208 for _, session in pairs(sessions[username].sessions) do
209 if session.interested_blocklist then
210 blocklist_push.attr.to = session.full_jid;
211 session.send(blocklist_push);
177 end 212 end
178 end 213 end
179 214
180 return true; 215 return true;
181 end 216 end
184 module:hook("iq-set/self/urn:xmpp:blocking:unblock", edit_blocklist); 219 module:hook("iq-set/self/urn:xmpp:blocking:unblock", edit_blocklist);
185 220
186 -- Cache invalidation, solved! 221 -- Cache invalidation, solved!
187 module:hook_global("user-deleted", function (event) 222 module:hook_global("user-deleted", function (event)
188 if event.host == module.host then 223 if event.host == module.host then
224 cache:set(event.username, nil);
189 cache[event.username] = nil; 225 cache[event.username] = nil;
190 end 226 end
191 end); 227 end);
192 228
193 -- Buggy clients 229 -- Buggy clients
274 310
275 module:hook("pre-message/bare", bounce_outgoing, prio_out); 311 module:hook("pre-message/bare", bounce_outgoing, prio_out);
276 module:hook("pre-message/full", bounce_outgoing, prio_out); 312 module:hook("pre-message/full", bounce_outgoing, prio_out);
277 module:hook("pre-message/host", bounce_outgoing, prio_out); 313 module:hook("pre-message/host", bounce_outgoing, prio_out);
278 314
315 -- Note: MUST bounce these, but we don't because this would produce
316 -- lots of error replies due to server-generated presence.
317 -- FIXME some day, likely needing changes to mod_presence
279 module:hook("pre-presence/bare", drop_outgoing, prio_out); 318 module:hook("pre-presence/bare", drop_outgoing, prio_out);
280 module:hook("pre-presence/full", drop_outgoing, prio_out); 319 module:hook("pre-presence/full", drop_outgoing, prio_out);
281 module:hook("pre-presence/host", drop_outgoing, prio_out); 320 module:hook("pre-presence/host", drop_outgoing, prio_out);
282 321
283 module:hook("pre-iq/bare", bounce_outgoing, prio_out); 322 module:hook("pre-iq/bare", bounce_outgoing, prio_out);