Software /
code /
prosody
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); |