Software /
code /
prosody-modules
File
mod_anti_spam/mod_anti_spam.lua @ 6199:fe8222112cf4
mod_conversejs: Serve base app at /
This makes things slightly less awkward for the browser to figure out which
URLs belong to a PWA. The app's "start URL" was previously without the '/' and
therefore was not considered within the scope of the PWA. Now the canonical
app URL will always have a '/'.
Prosody/mod_http should take care of redirecting existing links without the
trailing / to the new URL.
If you have an installation at https://prosody/conversejs then it is now at
https://prosody/conversejs/ (the first URL will now redirect to the second
URL if you use it).
The alternative would be to make the PWA scope include the parent, i.e.
the whole of https://prosody/ in this case. This might get messy if other
PWAs are provided by the same site or Prosody installation, however.
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Tue, 11 Feb 2025 13:18:38 +0000 (5 weeks ago) |
parent | 6192:76ae646563ea |
child | 6211:750d64c47ec6 |
line wrap: on
line source
local ip = require "util.ip"; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local set = require "util.set"; local sha256 = require "util.hashes".sha256; local st = require"util.stanza"; local rm = require "core.rostermanager"; local full_sessions = prosody.full_sessions; local user_exists = require "core.usermanager".user_exists; local new_rtbl_subscription = module:require("rtbl").new_rtbl_subscription; local trie = module:require("trie"); local spam_source_domains = set.new(); local spam_source_ips = trie.new(); local spam_source_jids = set.new(); local default_spam_action = module:get_option("anti_spam_default_action", "bounce"); local custom_spam_actions = module:get_option("anti_spam_actions", {}); local spam_actions = setmetatable({}, { __index = function (t, reason) local action = rawget(custom_spam_actions, reason) or default_spam_action; rawset(t, reason, action); return action; end; }); local count_spam_blocked = module:metric("counter", "anti_spam_blocked", "stanzas", "Stanzas blocked as spam", {"reason"}); local hosts = prosody.hosts; local reason_messages = { default = "Rejected as spam"; ["known-spam-source"] = "Rejected as spam. Your server is listed as a known source of spam. Please contact your server operator."; }; function block_spam(event, reason, action) if not action then action = spam_actions[reason]; end event.spam_reason = reason; event.spam_action = action; if module:fire_event("spam-blocked", event) == false then module:log("debug", "Spam allowed by another module"); return; end count_spam_blocked:with_labels(reason):add(1); if action == "bounce" then module:log("debug", "Bouncing likely spam %s from %s (%s)", event.stanza.name, event.stanza.attr.from, reason); event.origin.send(st.error_reply(event.stanza, "cancel", "policy-violation", reason_messages[reason] or reason_messages.default)); else module:log("debug", "Discarding likely spam %s from %s (%s)", event.stanza.name, event.stanza.attr.from, reason); end return true; end function is_from_stranger(from_jid, event) local stanza = event.stanza; local to_user, to_host, to_resource = jid_split(stanza.attr.to); if not to_user then return false; end local to_session = full_sessions[stanza.attr.to]; if to_session then return false; end if not ( rm.is_contact_subscribed(to_user, to_host, from_jid) or rm.is_user_subscribed(to_user, to_host, from_jid) or rm.is_contact_pending_out(to_user, to_host, from_jid) or rm.is_contact_preapproved(to_user, to_host, from_jid) ) then local from_user, from_host = jid_split(from_jid); -- Allow all messages from your own jid if from_user == to_user and from_host == to_host then return false; -- Pass through end if to_resource and stanza.attr.type == "groupchat" then return false; -- Pass through group chat messages end if rm.is_contact_subscribed(to_user, to_host, from_host) then -- If you have the sending domain in your roster, -- allow through (probably a gateway) return false; end return true; -- Stranger danger end end function is_spammy_server(session) if spam_source_domains:contains(session.from_host) then return true; end local raw_ip = session.ip; local parsed_ip = raw_ip and ip.new_ip(session.ip); -- Not every session has an ip - for example, stanzas sent from a -- local host session if parsed_ip and spam_source_ips:contains_ip(parsed_ip) then return true; end end function is_spammy_sender(sender_jid) return spam_source_jids:contains(sha256(sender_jid, true)); end local spammy_strings = module:get_option_array("anti_spam_block_strings"); local spammy_patterns = module:get_option_array("anti_spam_block_patterns"); function is_spammy_content(stanza) -- Only support message content if stanza.name ~= "message" then return; end if not (spammy_strings or spammy_patterns) then return; end local body = stanza:get_child_text("body"); if not body then return; end if spammy_strings then for _, s in ipairs(spammy_strings) do if body:find(s, 1, true) then return true; end end end if spammy_patterns then for _, s in ipairs(spammy_patterns) do if body:find(s) then return true; end end end end -- Set up RTBLs local anti_spam_services = module:get_option_array("anti_spam_services", {}); for _, rtbl_service_jid in ipairs(anti_spam_services) do new_rtbl_subscription(rtbl_service_jid, "spam_source_domains", { added = function (item) spam_source_domains:add(item); end; removed = function (item) spam_source_domains:remove(item); end; }); new_rtbl_subscription(rtbl_service_jid, "spam_source_ips", { added = function (item) local subnet_ip, subnet_bits = ip.parse_cidr(item); if not subnet_ip then return; end spam_source_ips:add_subnet(subnet_ip, subnet_bits); end; removed = function (item) local subnet_ip, subnet_bits = ip.parse_cidr(item); if not subnet_ip then return; end spam_source_ips:remove_subnet(subnet_ip, subnet_bits); end; }); new_rtbl_subscription(rtbl_service_jid, "spam_source_jids_sha256", { added = function (item) spam_source_jids:add(item); end; removed = function (item) spam_source_jids:remove(item); end; }); end module:hook("message/bare", function (event) local to_user, to_host = jid_split(event.stanza.attr.to); if not hosts[to_host] then module:log("warn", "Skipping filtering of message to unknown host <%s>", to_host); return; end local from_bare = jid_bare(event.stanza.attr.from); if user_exists(to_user, to_host) then if not is_from_stranger(from_bare, event) then return; end end module:log("debug", "Processing message from stranger..."); if is_spammy_server(event.origin) then return block_spam(event, "known-spam-source"); end if is_spammy_sender(from_bare) then return block_spam(event, "known-spam-jid"); end if is_spammy_content(event.stanza) then return block_spam(event, "spam-content"); end module:log("debug", "Allowing message through"); end, 500); module:hook("presence/bare", function (event) if event.stanza.attr.type ~= "subscribe" then return; end local to_user, to_host = jid_split(event.stanza.attr.to); local from_bare = jid_bare(event.stanza.attr.from); if user_exists(to_user, to_host) then if not is_from_stranger(from_bare, event) then return; end end module:log("debug", "Processing subscription request from stranger..."); if is_spammy_server(event.origin) then return block_spam(event, "known-spam-source"); end module:log("debug", "Not from known spam source server"); if is_spammy_sender(jid_bare(event.stanza.attr.from)) then return block_spam(event, "known-spam-jid"); end module:log("debug", "Not from known spam source JID"); module:log("debug", "Allowing subscription request through"); end, 500);