Prosody's mod_firewall comes with a ready-made anti-spam ruleset that you can use to tackle incoming spam on your server. The default ruleset is conservative about what it blocks. In other words, if there is a chance that a message may be legitimate, it will allow it by default.
The ruleset can be extended in various ways to override its default behaviour. This document demonstrates how to do this.
Of course, you need mod_firewall enabled for any of this to work. Just add it to modules_enabled:
modules_enabled = {
...
"firewall";
...
}
And to load the base spam-blocking rules that are discussed in this document, add:
firewall_scripts = {
"module:scripts/spam-blocking.pfw";
}
Additional rulesets that you create should be listed here too. For example, if you
create /etc/prosody/firewall
and create a script called 'my-custom-rules.pfw' your
config would look like this:
firewall_scripts = {
"module:scripts/spam-blocking.pfw";
"/etc/prosody/firewall/my-custom-rules.pfw";
}
For what should be put into 'my-custom-rules.pfw' or any other file you create, keep reading!
The base ruleset attempts to categorize incoming stanzas into one of three categories:
- spam
- not spam
- unknown
By default traffic in the "unknown" category is allowed to pass. However it is possible to define custom handling for this traffic and apply further rules.
One example of a useful extension is checking "unknown" traffic against the JabberSPAM blocklist. See the how-to for more info.
The built-in rules expose a number of extension points where you can add additional rules to classify traffic or override default behaviour. All these chains end with the '_custom' suffix. The base ruleset also defines a number of internal chains that do not end with '_custom' - these should not be modified by custom rulesets.
For more details about rule chains, check the mod_firewall documentation.
Apply additional rules to all stanzas before they are checked by the built-in rules. Mainly useful to PASS stanzas that you do not want to be filtered.
Apply additional rules to messages from strangers, aiming to PASS stanzas that are not spam and jump to ::user/spam_reject for stanzas that are considered spam.
Apply additional rules to messages that may be spam, based on message content rules. These may contain more intensive rules, so are executed after all other checks. Rules should jump to the 'user/spam_reject' chain if a message is considered spam.
Apply additional rules to presence that may be spam.
Apply additional rules to subscription requests.
Override default handling of stanzas that weren't explicitly passed or rejected by the anti-spam checks. By default unknown stanzas are allowed through, but you may want to override this default (e.g. jump unconditionally to 'user/spam_reject') or apply further scrutiny to these stanzas (e.g. check against external blocklists).
Override default handling of stanzas that have been recognised as spam (default is to bounce a policy-violation error).
Although we don't necessarily recommend this approach, it serves as a good starter example. Let's see how to reject all "unknown" traffic:
::user/spam_handle_unknown_custom
JUMP CHAIN=user/spam_reject
This inserts a single rule into the 'user/spam_handle_unknown_custom' rule chain. That single rule immediately jumps to the built-in chain called 'user/spam_reject' which is the built-in chain that deals with stanzas that should be rejected as spam.
This snippet will do the same as above, but configurable on a per-account basis:
%LIST protected_accounts: file:/etc/prosody/protected_accounts.txt (missing: ignore)
::user/spam_handle_unknown_custom
CHECK LIST: protected_accounts contains $<@to|bare>
JUMP CHAIN=user/spam_reject
In /etc/prosody/protected_accounts.txt
you would put a simple list of JIDs:
someuser@example.com
other@example.com
Tip: To reverse this (e.g. block unknown stanzas addressed to everyone on
the server, but let them through to listed users) simply change CHECK LIST
to NOT CHECK LIST
(and you should probably also rename 'protected_accounts' to
'unprotected_accounts' to avoid confusion!).
::user/spam_check_message_content
# Reject all messages with the word 'advertising' in the body
INSPECT: body#~=advertising
JUMP CHAIN=user/spam_reject
See the documentation for the INSPECT condition and the documentation of Lua patterns for the regex-like language used for matching.
The default behaviour when rejecting spam is to send an error back to the sender. Although this can be useful, especially to give the sender feedback in case of a mistake, you may want to not reveal that you are using any spam filtering.
This example overrides the default behaviour to discard spam:
::user/spam_reject_custom
LOG=Discarding suspected spam: $(stanza:top_tag())
DROP.
These are just some examples of what is possible with mod_firewall. As you get further into developing your own rulesets, you will want to refer to the mod_firewall documentation.
If you have questions, suggestions or other feedback, or if you want to share your rulesets or suggest some additional examples, join the Prosody community!