mirror of
https://github.com/bjc/prosody.git
synced 2025-04-03 21:27:38 +03:00
mod_blocklist: XEP-0191 implementation written for speed and independence from mod_privacy
This commit is contained in:
parent
503aba403d
commit
0bdc884676
1 changed files with 273 additions and 0 deletions
273
plugins/mod_blocklist.lua
Normal file
273
plugins/mod_blocklist.lua
Normal file
|
@ -0,0 +1,273 @@
|
|||
-- Prosody IM
|
||||
-- Copyright (C) 2009-2010 Matthew Wild
|
||||
-- Copyright (C) 2009-2010 Waqas Hussain
|
||||
-- Copyright (C) 2014 Kim Alvefur
|
||||
--
|
||||
-- This project is MIT/X11 licensed. Please see the
|
||||
-- COPYING file in the source package for more information.
|
||||
--
|
||||
-- This module implements XEP-0191: Blocking Command
|
||||
--
|
||||
|
||||
local user_exists = require"core.usermanager".user_exists;
|
||||
local is_contact_subscribed = require"core.rostermanager".is_contact_subscribed;
|
||||
local st = require"util.stanza";
|
||||
local st_error_reply = st.error_reply;
|
||||
local jid_prep, jid_split = import("jid", "prep", "split");
|
||||
|
||||
local host = module.host;
|
||||
local storage = module:open_store();
|
||||
local sessions = prosody.hosts[host].sessions;
|
||||
|
||||
-- Cache of blocklists used since module was loaded
|
||||
local cache = {};
|
||||
if module:get_option_boolean("blocklist_weak_cache") then
|
||||
-- Lower memory usage, more IO and latency
|
||||
setmetatable(cache, { __mode = "v" });
|
||||
end
|
||||
|
||||
local null_blocklist = {};
|
||||
|
||||
module:add_feature("urn:xmpp:blocking");
|
||||
|
||||
local function set_blocklist(username, blocklist)
|
||||
local ok, err = storage:set(username, blocklist);
|
||||
if not ok then
|
||||
return ok, err;
|
||||
end
|
||||
-- Successful save, update the cache
|
||||
cache[username] = blocklist;
|
||||
return true;
|
||||
end
|
||||
|
||||
-- Migrates from the old mod_privacy storage
|
||||
local function migrate_privacy_list(username)
|
||||
local migrated_data = { [false] = "not empty" };
|
||||
module:log("info", "Migrating blocklist from mod_privacy storage for user '%s'", username);
|
||||
local legacy_data = module:open_store("privacy"):get(username);
|
||||
if legacy_data and legacy_data.lists and legacy_data.default then
|
||||
legacy_data = legacy_data.lists[legacy_data.default];
|
||||
legacy_data = legacy_data and legacy_data.items;
|
||||
else
|
||||
return migrated_data;
|
||||
end
|
||||
if legacy_data then
|
||||
local item, jid;
|
||||
for i = 1, #legacy_data do
|
||||
item = legacy_data[i];
|
||||
if item.type == "jid" and item.action == "deny" then
|
||||
jid = jid_prep(item.value);
|
||||
if not jid then
|
||||
module:log("warn", "Invalid JID in privacy store for user '%s' not migrated: %s", username, tostring(item.value));
|
||||
else
|
||||
migrated_data[jid] = true;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
set_blocklist(username, migrated_data);
|
||||
return migrated_data;
|
||||
end
|
||||
|
||||
local function get_blocklist(username)
|
||||
local blocklist = cache[username];
|
||||
if not blocklist then
|
||||
if not user_exists(username, host) then
|
||||
return null_blocklist;
|
||||
end
|
||||
blocklist = storage:get(username);
|
||||
if not blocklist then
|
||||
blocklist = migrate_privacy_list(username);
|
||||
end
|
||||
cache[username] = blocklist;
|
||||
end
|
||||
return blocklist;
|
||||
end
|
||||
|
||||
module:hook("iq-get/self/urn:xmpp:blocking:blocklist", function (event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local username = origin.username;
|
||||
local reply = st.reply(stanza):tag("blocklist", { xmlns = "urn:xmpp:blocking" });
|
||||
local blocklist = get_blocklist(username);
|
||||
for jid in pairs(blocklist) do
|
||||
if jid then
|
||||
reply:tag("item", { jid = jid }):up();
|
||||
end
|
||||
end
|
||||
origin.interested_blocklist = true; -- Gets notified about changes
|
||||
return origin.send(reply);
|
||||
end);
|
||||
|
||||
-- Add or remove a bare jid from the blocklist
|
||||
-- We want this to be atomic and not do a partial update
|
||||
local function edit_blocklist(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local username = origin.username;
|
||||
local act = stanza.tags[1];
|
||||
local new = {};
|
||||
|
||||
local jid;
|
||||
for item in act:childtags("item") do
|
||||
jid = jid_prep(item.attr.jid);
|
||||
if not jid then
|
||||
return origin.send(st_error_reply(stanza, "modify", "jid-malformed"));
|
||||
end
|
||||
item.attr.jid = jid; -- echo back prepped
|
||||
new[jid] = is_contact_subscribed(username, host, jid) or false;
|
||||
end
|
||||
|
||||
local mode = act.name == "block" or nil;
|
||||
|
||||
if mode and not next(new) then
|
||||
-- <block/> element does not contain at least one <item/> child element
|
||||
return origin.send(st_error_reply(stanza, "modify", "bad-request"));
|
||||
end
|
||||
|
||||
local blocklist = get_blocklist(username);
|
||||
|
||||
local new_blocklist = {};
|
||||
|
||||
if mode and next(new) then
|
||||
for jid in pairs(blocklist) do
|
||||
new_blocklist[jid] = true;
|
||||
end
|
||||
for jid in pairs(new) do
|
||||
new_blocklist[jid] = mode;
|
||||
end
|
||||
-- else empty the blocklist
|
||||
end
|
||||
new_blocklist[false] = "not empty"; -- In order to avoid doing the migration thing twice
|
||||
|
||||
local ok, err = set_blocklist(username, new_blocklist);
|
||||
if ok then
|
||||
origin.send(st.reply(stanza));
|
||||
else
|
||||
return origin.send(st_error_reply(stanza, "wait", "internal-server-error", err));
|
||||
end
|
||||
|
||||
if mode then
|
||||
for jid, in_roster in pairs(new) do
|
||||
if not blocklist[jid] and in_roster and sessions[username] then
|
||||
for _, session in pairs(sessions[username].sessions) do
|
||||
module:send(st.presence({ type = "unavailable", to = jid, from = session.full_jid }));
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if sessions[username] then
|
||||
local blocklist_push = st.iq({ type = "set", id = "blocklist-push" })
|
||||
:add_child(act); -- I am lazy
|
||||
|
||||
for _, session in pairs(sessions[username].sessions) do
|
||||
if session.interested_blocklist then
|
||||
blocklist_push.attr.to = session.full_jid;
|
||||
session.send(blocklist_push);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true;
|
||||
end
|
||||
|
||||
module:hook("iq-set/self/urn:xmpp:blocking:block", edit_blocklist);
|
||||
module:hook("iq-set/self/urn:xmpp:blocking:unblock", edit_blocklist);
|
||||
|
||||
-- Cache invalidation, solved!
|
||||
module:hook_global("user-deleted", function (event)
|
||||
if event.host == host then
|
||||
cache[event.username] = nil;
|
||||
end
|
||||
end);
|
||||
|
||||
-- Buggy clients
|
||||
module:hook("iq-error/self/blocklist-push", function (event)
|
||||
local type, condition, text = event.stanza:get_error();
|
||||
(event.origin.log or module._log)("warn", "client returned an error in response to notification from mod_%s: %s%s%s", module.name, condition, text and ": " or "", text or "");
|
||||
return true;
|
||||
end);
|
||||
|
||||
local function is_blocked(user, jid)
|
||||
local blocklist = cache[user] or get_blocklist(user);
|
||||
if blocklist[jid] then return true; end
|
||||
local node, host = jid_split(jid);
|
||||
return blocklist[host] or node and blocklist[node..'@'..host];
|
||||
end
|
||||
|
||||
-- Event handlers for bouncing or dropping stanzas
|
||||
local function drop_stanza(event)
|
||||
local stanza = event.stanza;
|
||||
local attr = stanza.attr;
|
||||
local to, from = attr.to, attr.from;
|
||||
to = to and jid_split(to);
|
||||
if to and from then
|
||||
return is_blocked(to, from);
|
||||
end
|
||||
end
|
||||
|
||||
local function bounce_stanza(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
if drop_stanza(event) then
|
||||
return origin.send(st_error_reply(stanza, "cancel", "service-unavailable"));
|
||||
end
|
||||
end
|
||||
|
||||
local function bounce_iq(event)
|
||||
local type = event.stanza.attr.type;
|
||||
if type == "set" or type == "get" then
|
||||
return bounce_stanza(event);
|
||||
end
|
||||
return drop_stanza(event); -- result or error
|
||||
end
|
||||
|
||||
local function bounce_message(event)
|
||||
local type = event.stanza.attr.type;
|
||||
if type == "chat" or not type or type == "normal" then
|
||||
return bounce_stanza(event);
|
||||
end
|
||||
return drop_stanza(event); -- drop headlines, groupchats etc
|
||||
end
|
||||
|
||||
local function drop_outgoing(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local username = origin.username or jid_split(stanza.attr.from);
|
||||
if not username then return end
|
||||
local to = stanza.attr.to;
|
||||
if to then return is_blocked(username, to); end
|
||||
-- nil 'to' means a self event, don't bock those
|
||||
end
|
||||
|
||||
local function bounce_outgoing(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local type = stanza.attr.type;
|
||||
if type == "error" or stanza.name == "iq" and type == "result" then
|
||||
return drop_outgoing(event);
|
||||
end
|
||||
if drop_outgoing(event) then
|
||||
return origin.send(st_error_reply(stanza, "cancel", "not-acceptable", "You have blocked this JID")
|
||||
:tag("blocked", { xmlns = "urn:xmpp:blocking:errors" }));
|
||||
end
|
||||
end
|
||||
|
||||
-- Hook all the events!
|
||||
local prio_in, prio_out = 100, 100;
|
||||
module:hook("presence/bare", drop_stanza, prio_in);
|
||||
module:hook("presence/full", drop_stanza, prio_in);
|
||||
|
||||
module:hook("message/bare", bounce_message, prio_in);
|
||||
module:hook("message/full", bounce_message, prio_in);
|
||||
|
||||
module:hook("iq/bare", bounce_iq, prio_in);
|
||||
module:hook("iq/full", bounce_iq, prio_in);
|
||||
|
||||
module:hook("pre-message/bare", bounce_outgoing, prio_out);
|
||||
module:hook("pre-message/full", bounce_outgoing, prio_out);
|
||||
module:hook("pre-message/host", bounce_outgoing, prio_out);
|
||||
|
||||
module:hook("pre-presence/bare", drop_outgoing, prio_out);
|
||||
module:hook("pre-presence/full", drop_outgoing, prio_out);
|
||||
module:hook("pre-presence/host", drop_outgoing, prio_out);
|
||||
|
||||
module:hook("pre-iq/bare", bounce_outgoing, prio_out);
|
||||
module:hook("pre-iq/full", bounce_outgoing, prio_out);
|
||||
module:hook("pre-iq/host", bounce_outgoing, prio_out);
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue