22 |
22 |
23 local storage = module:open_store(); |
23 local storage = module:open_store(); |
24 local sessions = prosody.hosts[module.host].sessions; |
24 local sessions = prosody.hosts[module.host].sessions; |
25 local full_sessions = prosody.full_sessions; |
25 local full_sessions = prosody.full_sessions; |
26 |
26 |
27 -- First level cache of blocklists by username. |
27 -- Cache of blocklists, keeps a fixed number of items. |
28 -- Weak table so may randomly expire at any time. |
|
29 local cache = setmetatable({}, { __mode = "v" }); |
|
30 |
|
31 -- Second level of caching, keeps a fixed number of items, also anchors |
|
32 -- items in the above cache. |
|
33 -- |
28 -- |
34 -- The size of this affects how often we will need to load a blocklist from |
29 -- The size of this affects how often we will need to load a blocklist from |
35 -- disk, which we want to avoid during routing. On the other hand, we don't |
30 -- disk, which we want to avoid during routing. On the other hand, we don't |
36 -- want to use too much memory either, so this can be tuned by advanced |
31 -- want to use too much memory either, so this can be tuned by advanced |
37 -- users. TODO use science to figure out a better default, 64 is just a guess. |
32 -- users. TODO use science to figure out a better default, 64 is just a guess. |
38 local cache_size = module:get_option_integer("blocklist_cache_size", 64, 1); |
33 local cache_size = module:get_option_integer("blocklist_cache_size", 256, 1); |
39 local cache2 = require"prosody.util.cache".new(cache_size); |
34 local blocklist_cache = require"prosody.util.cache".new(cache_size); |
40 |
35 |
41 local null_blocklist = {}; |
36 local null_blocklist = {}; |
42 |
37 |
43 module:add_feature("urn:xmpp:blocking"); |
38 module:add_feature("urn:xmpp:blocking"); |
44 |
39 |
46 local ok, err = storage:set(username, blocklist); |
41 local ok, err = storage:set(username, blocklist); |
47 if not ok then |
42 if not ok then |
48 return ok, err; |
43 return ok, err; |
49 end |
44 end |
50 -- Successful save, update the cache |
45 -- Successful save, update the cache |
51 cache2:set(username, blocklist); |
46 blocklist_cache:set(username, blocklist); |
52 cache[username] = blocklist; |
|
53 return true; |
47 return true; |
54 end |
48 end |
55 |
49 |
56 -- Migrates from the old mod_privacy storage |
50 -- Migrates from the old mod_privacy storage |
57 -- TODO mod_privacy was removed in 0.10.0, this should be phased out |
51 -- TODO mod_privacy was removed in 0.10.0, this should be phased out |
84 return nil; |
78 return nil; |
85 end |
79 end |
86 end |
80 end |
87 |
81 |
88 local function get_blocklist(username) |
82 local function get_blocklist(username) |
89 local blocklist = cache2:get(username); |
83 local blocklist = blocklist_cache:get(username); |
90 if not blocklist then |
84 if not blocklist then |
91 if not user_exists(username, module.host) then |
85 if not user_exists(username, module.host) then |
92 return null_blocklist; |
86 return null_blocklist; |
93 end |
87 end |
94 blocklist = storage:get(username); |
88 blocklist = storage:get(username); |
96 blocklist = migrate_privacy_list(username); |
90 blocklist = migrate_privacy_list(username); |
97 end |
91 end |
98 if not blocklist then |
92 if not blocklist then |
99 blocklist = { [false] = { created = os.time(); }; }; |
93 blocklist = { [false] = { created = os.time(); }; }; |
100 end |
94 end |
101 cache2:set(username, blocklist); |
95 blocklist_cache:set(username, blocklist); |
102 end |
96 end |
103 cache[username] = blocklist; |
|
104 return blocklist; |
97 return blocklist; |
105 end |
98 end |
106 |
99 |
107 module:hook("iq-get/self/urn:xmpp:blocking:blocklist", function (event) |
100 module:hook("iq-get/self/urn:xmpp:blocking:blocklist", function (event) |
108 local origin, stanza = event.origin, event.stanza; |
101 local origin, stanza = event.origin, event.stanza; |
109 local username = origin.username; |
102 local username = origin.username; |
110 local reply = st.reply(stanza):tag("blocklist", { xmlns = "urn:xmpp:blocking" }); |
103 local reply = st.reply(stanza):tag("blocklist", { xmlns = "urn:xmpp:blocking" }); |
111 local blocklist = cache[username] or get_blocklist(username); |
104 local blocklist = get_blocklist(username); |
112 for jid in pairs(blocklist) do |
105 for jid in pairs(blocklist) do |
113 if jid then |
106 if jid then |
114 reply:tag("item", { jid = jid }):up(); |
107 reply:tag("item", { jid = jid }):up(); |
115 end |
108 end |
116 end |
109 end |
165 -- <block/> element does not contain at least one <item/> child element |
158 -- <block/> element does not contain at least one <item/> child element |
166 origin.send(st_error_reply(stanza, "modify", "bad-request")); |
159 origin.send(st_error_reply(stanza, "modify", "bad-request")); |
167 return true; |
160 return true; |
168 end |
161 end |
169 |
162 |
170 local blocklist = cache[username] or get_blocklist(username); |
163 local blocklist = get_blocklist(username); |
171 |
164 |
172 local new_blocklist = { |
165 local new_blocklist = { |
173 -- We set the [false] key to something as a signal not to migrate privacy lists |
166 -- We set the [false] key to something as a signal not to migrate privacy lists |
174 [false] = blocklist[false] or { created = now; }; |
167 [false] = blocklist[false] or { created = now; }; |
175 }; |
168 }; |
239 module:hook("iq-set/self/urn:xmpp:blocking:unblock", edit_blocklist, -1); |
232 module:hook("iq-set/self/urn:xmpp:blocking:unblock", edit_blocklist, -1); |
240 |
233 |
241 -- Cache invalidation, solved! |
234 -- Cache invalidation, solved! |
242 module:hook_global("user-deleted", function (event) |
235 module:hook_global("user-deleted", function (event) |
243 if event.host == module.host then |
236 if event.host == module.host then |
244 cache2:set(event.username, nil); |
237 blocklist_cache:set(event.username, nil); |
245 cache[event.username] = nil; |
|
246 end |
238 end |
247 end); |
239 end); |
248 |
240 |
249 -- Buggy clients |
241 -- Buggy clients |
250 module:hook("iq-error/self/blocklist-push", function (event) |
242 module:hook("iq-error/self/blocklist-push", function (event) |
255 module.name, condition, text and ": " or "", text or ""); |
247 module.name, condition, text and ": " or "", text or ""); |
256 return true; |
248 return true; |
257 end); |
249 end); |
258 |
250 |
259 local function is_blocked(user, jid) |
251 local function is_blocked(user, jid) |
260 local blocklist = cache[user] or get_blocklist(user); |
252 local blocklist = get_blocklist(user); |
261 if blocklist[jid] then return true; end |
253 if blocklist[jid] then return true; end |
262 local node, host = jid_split(jid); |
254 local node, host = jid_split(jid); |
263 return blocklist[host] or node and blocklist[node..'@'..host]; |
255 return blocklist[host] or node and blocklist[node..'@'..host]; |
264 end |
256 end |
265 |
257 |