====== Replacing SpamAssassin with Rspamd ====== Reference system: **Debian GNU/Linux 12 Bookworm**. Install the Debian packages **rspamd** and **redis-server** (the latter is installed by dependency). Using the Systemd's tool **systemctl**, we can enable/disable/start/stop the services by referring to **rspamd.service** and **redis-server.service**, eg: systemctl restart rspamd.service The rspamd daemon is listening only on **localhost:11333**, it does not listen on external interfaces. ===== Using the Milter protocol with the self-scan option ===== The **[[wp>Milter|Milter protocol]]** is a //mail filter// protocol used e.g. by the Postfix MTA to filter messages using an external program. The external program return a "//success//" or "//failed//" status to the MTA. FIXME Only //success// or //failed//? How are handled greylisted messages? Default options are configured into **/etc/rspamd/worker-proxy.inc**, the **self-scan** mode is configured by the following snippet: milter = yes; # Enable milter mode timeout = 120s; # Needed for Milter usually upstream "local" { default = yes; hosts = "localhost"; } count = 1; # Do not spawn too many processes of this type max_retries = 5; # How many times master is queried in case of failure discard_on_reject = false; # Discard message instead of rejection quarantine_on_reject = false; # Tell MTA to quarantine rejected messages spam_header = "X-Spam"; # Use the specific spam header reject_message = "Spam message rejected"; # Use custom rejection message FIXME What is the //self-scan// mode? What are the alternatives? Suppose we want to spawn at least 5 processes for scanning messages, we can change only the **count** option by creating a local configuration file **/etc/rspamd/local.d/worker-proxy.inc**, putting a single line in it: count = 5; That option will be merged into the **worker** => **rspamd_proxy** section of the configuration. To check if the file is properly parsed you can execute rspamadm configdump and search the output for the worker => rspamd_proxy definition: worker { rspamd_proxy { count = 5; max_retries = 5; discard_on_reject = false; quarantine_on_reject = false; spam_header = "X-Spam"; milter = true; bind_socket = "localhost:11332"; timeout = 120; reject_message = "Spam message rejected"; upstream { local { hosts = "localhost"; default = true; } } } } ===== The Redis database backend ===== After the installation of the **redis-server** Debian package, the daemon is listening on **127.0.0.1:6379**. Execute the client **redis-cli** to check the connection: 127.0.0.1:6379> PING PONG The default Debian configuration prepares 16 databases, you can select the second one (starting to count from zero) with: 127.0.0.1:6379> SELECT 1 OK 127.0.0.1:6379[1]> The Redis database backend can be used by several Rspamd modules, e.g. //greylist//, //ratelimit//, etc. Place the settings common to all the modules into **/etc/rspamd/local.d/redis.conf**: servers = "127.0.0.1"; Once you configured the use of Redis, check that the depending modules are actually enabled using ''rspamadm configdump -m''. ===== Test the daemon ===== Using the **rspamc** client it is possible to ask the rspamd daemon about a mail message. The result includes the rules triggered by the message and the scores associated to each of them: rspamc < message.txt Results for file: stdin (0.36 seconds) [Metric: default] Action: greylist Spam: false Score: 5.50 / 15.00 Symbol: ASN (0.00)[asn:8677, ipnet:193.201.76.0/24, country:FR] Symbol: CTYPE_MIXED_BOGUS (1.00) Symbol: INVALID_DATE (1.50) Symbol: MIME_GOOD (-0.10)[multipart/mixed, text/plain] Symbol: MIME_TRACE (0.00)[0:+, 1:+] Symbol: PREVIOUSLY_DELIVERED (0.00)[...] Symbol: R_BAD_CTE_7BIT (3.50)[7bit] ... The most importan rows are **Action**, **Spam** and **Score**: here it is the result for a good message: Results for file: stdin (0.228 seconds) [Metric: default] Action: no action Spam: false Score: -0.29 / 15.00 Every message scan is logged into **/var/log/rspamd/rspamd.log**. ===== Configure Postfix to filter with Rspamd ===== The simplest configuration is to add the Rspamd milter to the **smtpd_milters** option in **/etc/postfix/main.cf**. In our example we add it to the existing DKIM filter: # Mails received via SMTP protocol are filtered with: # * opendkim localhost:8891 # * Rspamd localhost:11332 smtpd_milters = inet:localhost:8891, inet:localhost:11332 # If some Milters are broken, accept messages without checks. milter_default_action = accept With the default configuration, a message with **SPAM** score of **15 or above** will be rejected by Postfix (the receiving MTA) and the sender MTA will generate a **sender non-delivery notification**. ===== Rspamd modules ===== To get the list of enabled and disabled modules: rspamadm configdump -m Interesting modules: ^ rbl | Checks a message’s sender IP address against Realtime Blackhole Lists (RBLs), etc. | ^ spf | Checks the proclaimed sender domain’s Sender Policy Framework (SPF) policy. | ^ dkim | Verifies/Adds Domain Keys Identified Mail (DKIM) signatures to validate a mail really comes from the proclaimed domain. | ^ antivirus | Passes the message to external virus scanners such as clamav. | ^ regexp | This module is responsible for checking messages against //regexp// rules; if you create custom regexp rules you need this module enabled. | ^ redis | This is a database backend, required e.g. by the //greylist// or //antivirus// modules. | ^ greylist | Implements greylisting (temporarily refusing messages so a legitimate sender has to retry later) as an action. | ^ multimap | Can be used to add custom symbols or force actions upon match on a list of regexp. | ^ milter_headers | | ^ reputation | | ^ rspamd_update | This modules was responsible for backporting of new rules and score changes (similar to the ''sa-update'' tool for SpamAssassin), but it was disabled in version 1.8.2 (2018). | ===== Customize actions on SPAM score ===== The default configuration provided by Debian is stored into the file **/etc/rspamd/actions.conf**. The greylisting means that the message is rejected with //soft reject action//, this means that a legitimate MTA should retry the message after a while. actions { reject = 15; add_header = 6; greylist = 4; } If you want to customize the score, create a file **/etc/rspamd/local.d/actions.conf** and define the options you want to override (do not declare the //actions// section, just put the options): # Reject when reaching this score. reject = 18.0; # Rewrite the subject when reaching this score. rewrite_subject = 6.0; # Add header "X-Spam: Yes" when reaching this score. add_header = 5.0; # Apply greylisting when reaching this score (will emit "soft reject action"). greylist = 4.0; # Set rewrite subject to this value (%s is replaced by the original subject). subject = "***** SPAM ***** %s" The action **add_header** means adding the header **X-Spam: Yes** to the message. **NOTICE**: Each action must have the correct score order, e.g. //add_header// must have lower score than action //rewrite_subject//. **NOTICE**: Each action is mutually exclusive. This is obvious e.g. for //greylist// and //reject//, but it is also true for //add_header// and //rewrite_subject//. This means that **you cannot have both** the header added and the subject rewritten, only the action with the nearest score is selected. ===== Customize the score ===== Rspamd attaches **symbols** to messages. Each symbol has a **score** (or **weight**), which is a floating point number (negative or positive); per default its value is **1.0**. If several symbols are attacched to a message, their scores are summed up, forming the overall **metric** of the message. If you want to define or override the scores of symbols, create the file **local.d/metrics.conf** and declare the symbols, one by one or in groups: symbol "TEST_SPAM_STRING" { description = "My test Rspamd rule"; score = 6.50; } group "antivirus" { symbol "CLAM_VIRUS" { description = "Virus detected"; score = 20.00; } } **NOTICE**: You must to **reload** the rspamd.service in order to make the new metrics effective. **WARNING**: The keywords **score** and **weight** are synonyms and can be used interchangeably into the symbol definition, but **score** prevails if both are used. ===== Configure the blacklists ===== The **[[https://rspamd.com/doc/modules/rbl.html|RBL module]]** can check each message against the Runtime Black Lists (RBL) typically provided through dedicated DNS zones. Several RBLs are listed into the default configuration file **modules.d/rbl.conf**. You can find the definition of each RBL as a symbol, eventually using two different syntax: FIXME Why two syntax forms? rbl { rbls { ... dnswl { symbol = "RCVD_IN_DNSWL"; ... } "RSPAMD_URIBL" { } ... } ... } Local configuration must go into **local.d/rbl.conf**, here it is an example on how to add a custom RBL: # Map containing additional IPv4/IPv6 addresses/subnets that should # be excluded from checks where exclude_local is true (the default). local_exclude_ip_map = "${LOCAL_CONFDIR}/maps.d/rbl_local_exclude_ip.map"; # Add a custom RBL. rbls { zen_rigacci { # Checks to enable for this RBL. # from: the sending IP that sent the message. checks = ["from"]; # Address used for RBL-testing. rbl = "zen.rigacci.org"; ipv4 = true; ipv6 = true; exclude_local = true; local_exclude_ip_map = "${LOCAL_CONFDIR}/maps.d/zen_rigacci_exclude_ip.map"; # Symbol to yeld. symbol = "ZEN_RIGACCI"; returncodes = { # Apply a specific symbol instead of the generic one. "ZEN_RIGACCI_CODE_1" = "127.0.0.1"; "ZEN_RIGACCI_CODE_2" = "127.0.0.2"; "ZEN_RIGACCI_CODE_3" = "127.0.0.3"; } } } The file pointed by the **local_exclude_ip_map** option can be updated (adding or removing IP addresses or subnets) without the need to reload any service. A custom score can be defined into **local.d/rbl_group.conf**: symbols = { "ZEN_RIGACCI" { weight = 6.2; description = "From address is listed in ZEN Rigacci.Org"; groups = ["zen_rigacci"]; } } symbols = { "ZEN_RIGACCI_CODE_1" { weight = 6.8; description = "From address is listed in ZEN Rigacci.Org, code 1"; groups = ["zen_rigacci"]; } } Several RBLs are enabled per default in the Debian 12 install. if you want to disable some, just add the symbol into the **rbls** list with the option **enabled = false**: rbls { dnswl { symbol = "RCVD_IN_DNSWL"; enabled = false; } } ===== Antivirus scanning ===== The antivirus function is accomplished by the specific **[[https://rspamd.com/doc/modules/antivirus.html|antivirus module]]**. Local configuration should go into the **local.d/antivirus.conf** file; here it is an example to use the ClamAV daemon listening on the TCP port: # local.d/antivirus.conf clamav { # The antivirus engine to use. type = "clamav"; servers = "127.0.0.1:3310"; # If set, force this action if any virus is found (default unset: no action is forced). action = "reject"; message = 'Forbidden: virus found: "${VIRUS}"'; # If `max_size` is set, messages > n bytes in size are not scanned max_size = 20000000; # Symbol to add (add it to metrics if you want non-zero weight). # You can use this if you want to apply default actions based on score. symbol = "CLAM_VIRUS"; # Prefix used for caching in Redis: scanner-specific defaults are used. # If Redis is enabled and multiple scanners of the same type are present, # it is important to set prefix to something unique. prefix = "rs_cl_"; # if "patterns" is specified, virus name will be matched against provided # regexes and the related symbol will be yielded if a match is found. If # no match is found, default symbol is yielded. patterns { # symbol_name = "pattern"; JUST_EICAR = '^Eicar-Signature$'; } } If the **action** option is not set, the action based on the overall SPAM score is taken. In the example above we forced the //reject// action and using the **message** option we will create a specific SMTP 554 5.7.1 message for the virus found case. **NOTICE**: The optional section **patterns**: if one the patterns matches (the ones on the right of the equal sign), the specified symbol is added to the message, instead of the one specified at the module level. A custom score for the symbols can be defined into **local.d/metrics.conf**: group "antivirus" { symbol "CLAM_VIRUS" { description = "Virus detected"; score = 2.92; } symbol "JUST_EICAR" { description = "Only a virus test"; score = -6.00; } } ===== SPF check ===== The **[[https://rspamd.com/doc/modules/spf.html|spf module]]** is enable per default in Debian 12 Bookworm. Local configuration goes into **local.d/spf.conf**, here e.g. we add a whitelist of **IP addresses** that will be exempted from SPF check: # whitelist IPs from checks. whitelist = "${LOCAL_CONFDIR}/maps.d/spf_whitelist.inc"; The symbol that can be attached by the module are into the **spf** group, defined into **scores.d/policies_group.conf**: VIOLATED_DIRECT_SPF(3.50) R_SPF_SOFTFAIL(0.0) R_SPF_FAIL(1.00) R_SPF_NEUTRAL(0.0) R_SPF_ALLOW(-0.2) R_SPF_DNSFAIL(0.0) R_SPF_NA(0.0) R_SPF_PERMFAIL(0.0) The ''VIOLATED_DIRECT_SPF'' is a composite symbol, it combines an SPF (soft) fail and has no Received or no trusted received relays. As you can see from the log below, an SPF fail does not trigger a significative SPAM score using the default metrics: only 0.90/18.00: #1251327(normal) <47b9f4>; task; rspamd_task_write_log: id: , qid: <6A3AD3FF39>, ip: 2a01:4f8:2a36:8743::1, from: , (default: F (no action): [0.90/18.00] [R_SPF_FAIL(1.00){-all;}, MIME_GOOD(-0.10){text/plain;},ARC_NA(0.00){},ASN(0.00){asn:24940, ipnet:2a01:4f8::/32, country:DE;},DMARC_NA(0.00){rigacci.org;}, FROM_EQ_ENVFROM(0.00){},FROM_HAS_DN(0.00){},MIME_TRACE(0.00){0:+;}, RCPT_COUNT_ONE(0.00){1;},RCVD_COUNT_TWO(0.00){2;},RCVD_TLS_LAST(0.00){}, R_DKIM_NA(0.00){},TO_DN_NONE(0.00){},TO_MATCH_ENVRCPT_ALL(0.00){}]), len: 541, time: 143.467ms, dns req: 33, digest: <9aee68ee8076d6dc6887c0d88cb4f4d1>, rcpts: , mime_rcpts: If you want to customize the score, just create a file **local.d/policies_group.conf** containing only the differences from the default file: symbols = { "R_SPF_FAIL" { weight = 4.80; } } **NOTICE**: We used the keyword **//weight//** to replace the same keyword declared into ''scores.d/policies_group.conf''. Alternatively it is possibile to define the keyword **//score//** that will take precedence over //weight//. ===== Greylist ===== Greylisting is provided by the **[[https://rspamd.com/doc/modules/greylisting.html|greylist module]]**, which in turn requires that the Redis database engine is enabled. Once the //redis// module is enabled, the //greylist// module should also be enabled; verify using the ''rspamadm configdump -m'' command. You can test that greylisting is working by sending a message that will trigger a metric that results in greylisting (see e.g. the [[#custom_regexp_rule_with_multimap|multimap]] module to add a custom score to a message based on regexp, default score for greylisting is 4 in Debian 12 Bookworm). This is what will be logged in **mail.log** when the message is temporary rejected: postfix/smtpd[521074]: connect from test.rigacci.org[188.21.172.134] postfix/smtpd[521074]: 0D00641ACD: client=test.rigacci.org[188.21.172.134] postfix/cleanup[521078]: 0D00641ACD: milter-reject: END-OF-MESSAGE from test.rigacci.org[188.21.172.134]: 4.7.1 Try again later; from= to= proto=ESMTP helo= This is greylist log in **rspamd/rspamd.log**, you can search for the keywords **greylisted until**: #1223676(normal) <8003ac>; lua; greylist.lua:430: greylisted until "Fri, 10 Nov 2023 08:39:31 GMT", new record #1223676(normal) <8003ac>; task; rspamd_add_passthrough_result: : set pre-result to 'soft reject' (no score): 'Try again later' from greylist(1) #1223676(normal) <8003ac>; task; rspamd_task_write_log: id: , qid: <4891841B4C>, ip: 2a01:4f8:2c28:8646::1, from: , (default: F (soft reject): [4.28/18.00] [TEST_SPAM_STRING(4.58){},R_SPF_ALLOW(-0.20){+a:mail.rigacci.org;}, MIME_GOOD(-0.10){text/plain;},ARC_NA(0.00){}, ASN(0.00){asn:24940, ipnet:2a01:4f8::/32, country:DE;}, DMARC_NA(0.00){rigacci.org;},FROM_EQ_ENVFROM(0.00){}, FROM_HAS_DN(0.00){},GREYLIST(0.00) {greylisted;Fri, 10 Nov 2023 08:39:31 GMT;new record;}, MID_RHS_MATCH_FROMTLD(0.00){},MIME_TRACE(0.00){0:+;}, RCPT_COUNT_ONE(0.00){1;},RCVD_COUNT_TWO(0.00){2;}, RCVD_TLS_LAST(0.00){},R_DKIM_NA(0.00){},TO_DN_NONE(0.00){}, TO_MATCH_ENVRCPT_ALL(0.00){},URIBL_BLOCKED(0.00) {rigacci.net:url;santorini.rigacci.org:rdns;mail.rigacci.org:helo;}]), len: 530, time: 198.906ms, dns req: 31, digest: , rcpts: , mime_rcpts: , forced: soft reject "Try again later"; score=nan (set by greylist) The sender can retry after the default **timeout = 5min** and should succeed. When the greylisting period expired, the logging will be something like this (keywords **greylisting pass**): #1235394(normal) <7b0b7f>; lua; greylist.lua:404: greylisting pass (body) until Sat, 11 Nov 2023 10:27:37 GMT It is possible to **whitelist** some sender domains, listing them one per line into the local file **local.d/greylist-whitelist-domains.inc** === The Redis database entries === Two hashes will be recorded into the Redis database: the **meta** one is based on the **from:to:ip** triplet, the **data** one is based on the message body. The keys stored into the database have a **key_prefix**, which is "rg" per default in Debian 12 Bookworm. The keys contain the Unix timestamp of the time when greylisting happened. You can use the **redis-cli** tool to view or delete keys in the database: 127.0.0.1:6379> KEYS rg* 1) "rgm9hy6nondu18enp5xfn7e" 2) "rgme18xdtyoa4scg7eeiqmr" 127.0.0.1:6379> GET rgm9hy6nondu18enp5xfn7e "1699610557" 127.0.0.1:6379> DEL rgm9hy6nondu18enp5xfn7e (integer) 1 ===== Custom regexp rule with multimap ===== To add a custom rule using some regular expressions, we will use the **[[https://rspamd.com/doc/modules/multimap.html|multimap]]** module, which is enabled per default in Debian. We can create a file called **/etc/rspamd/local.d/multimap.conf** and write a **symbol** (rule name) in it: TEST_SPAM_STRING { description = "Test SPAM rule"; type = "content"; filter = "body"; regexp = true; map = "${LOCAL_CONFDIR}/maps.d/test_spam.map"; prefilter = true; # Action can be: "accept", "greylist", "add_header", "rewrite_subject" and "reject". # If no action is specified, the generic score-based one will be applied. action = "reject"; # Score is eventually overridden/defined in local.d/metrics.conf. score = 20.0; # Message returned to MTA on reject action. message = "The message contains a knwon SPAM test"; } The **type** //content// means that we are considering the mail content (headers and/or body), alternatively you can consider the //from//, //hostname//, //ip//, attachment //filename//, etc. The **filter** //body// means that we are searching the message body, not the //headers//. If **regexp** is set to //true// it means that the map contains regular expressions (one per line). The **map** in this case is a local file, it can be and URL, etc. If the file is changed, no reload of the daemon is required. Here it is an example of configuration file named **maps.d/test_spam.map**, containing just two regexp: /first test string/gi /second example string/gi If the map file is updated, it will be reloaded automatically. If **prefilter** is true, we have to define the //action//. In case of match the action is executed and no filters will be applied. If an **action** is specified and the message matches, process the message accordingly. In this case the score specified in the rule is summed to the overall score, but it does not contribute in determining the action. ^ accept | Accept the message (no action), regardless the score added by this rule. | ^ add_header | Add a header ''X-Spam: Yes'' to the message, but the message is eccepted for delivery. | ^ rewrite_subject | The message is accepted, but the ''Subject:'' header is modified according to the global //actions// => //subject// setting. | ^ soft reject | The message is rejected with a 451 SMTP status code, meaning a temporary problem. The sender MTA is notified with a temporary failure message and it should retry later. Notice that this action **is not a greylisting**: when the message is retried the same rule applies again. | ^ reject | The message is reject witha 554 SMTP status code. The default message generated by the Postfix MTA is ''554 5.7.1 Matched map: TEST_SPAM_STRING''. The sender MTA should create a //sender non-delivery notification//. | If specified, the **score** is added to the metric of the message; otherwise the value **0.0** is used. If a score for this symbol is defined also into the **local.d/metrics.conf** file, the latter will take precedence. Here it is an example on how to define the score into **local.d/metrics.conf**: symbol "TEST_SPAM_STRING" { description = "My test Rspamd rule"; score = 4.58; } The **message** is eventually used if the mail matches this rule and the action is set to //reject// or //soft reject//. In this case the sender MTA will be notified with this message in reply to end of DATA command. The SMTP message code will be 554 for reject or 451 for soft reject. ===== Whitelist-From using the multimap module ===== It is possibile replicate the **whitelist_from** option found in **SpamAssassin** using the multimap module of **rspamd**. In the **/etc/rspamd/local.d/multimap.conf** we create a symbol called e.g. **WHITELIST_FROM**: WHITELIST_FROM { description = "Whitelist From regex"; type = "from"; regexp = true; map = "${LOCAL_CONFDIR}/maps.d/whitelist_from.map"; prefilter = true; action = "accept"; score = -100; } In the file **/etc/rspamd/maps.d/whitelist_from.map** it is possibile to add one regex per line to mach the **From** header. The header content is cleaned of extra data, e.g. **''%%Niccolo Rigacci %%''** will be pruned to **''%%niccolo@rigacci.org%%''**. Standard regex **meta-characters** can be used, e.g. (see ''man grep'' for a full list): ^ %%^%% | Matches the start of the line. | ^ %%$%% | Matches the end of the line. | ^ %%.%% | Matches a single character. | ^ %%\.%% | Matches a single dot. | ^ %%\b%% | Matches the empty string at the edge of a word. | The regex must be enclosed into a pair of **%%/%%** chars and the standard **flags** can be used: ^ %%i%% | Case insensitive match. | Here are an example to whitelist a single email address and an entire mail domain (NOTICE: if the map is updated, it will be reloaded automatically): /^niccolo@texnet\.it$/i /@domain\.org$/i ===== Customizing the headers ===== Rspamd generally does not add headers to the email messages unless the action selected is //add header//. If you want more control on customizing the headers, you can use the **[[https://rspamd.com/doc/modules/milter_headers.html|milter headers module]]**. Local configuration must be into **local.d/milter_headers.conf**: # Add the X-Spamd-Result header to all the messages. use = ["x-spamd-result", "x-spam-status", "x-spam-level"]; # Implies X-Spamd-Result and add X-Rspamd-Queue-Id, X-Rspamd-Server and X-Rspamd-Action. extended_spam_headers = true; The **use** option is the only one required to activate the module: we should list what //routines// must be applied to the messages. Here some of the routines available: ^ x-spamd-result | Add the header ''X-Spamd-Result'' with details on the total score. | ^ x-spam-status | Add an header like ''X-Spam-Status: No, score=-0.30''. The status becomes //Yes// on reaching at least the //add_header// level. | ^ x-spam-level | Add an header like ''%%X-Spam-Level: ******%%'' if the message scores at least the //add_header// level. | The **extended_spam_headers** option will add some headers to all the messages, regardless the spam score: X-Spamd-Result: default: False [-0.30 / 18.00]; R_SPF_ALLOW(-0.20)[+a:mail.rigacci.org]; MIME_GOOD(-0.10)[text/plain]; FROM_EQ_ENVFROM(0.00)[]; MID_RHS_MATCH_FROMTLD(0.00)[]; ARC_NA(0.00)[]; R_DKIM_NA(0.00)[]; RCVD_TLS_LAST(0.00)[]; ASN(0.00)[asn:24940, ipnet:2a01:4f8::/32, country:DE]; URIBL_BLOCKED(0.00)[mail.rigacci.org:rdns,mail.rigacci.org:helo,rigacci.net:url]; RCPT_COUNT_ONE(0.00)[1]; DMARC_NA(0.00)[rigacci.org]; RCVD_IN_DNSWL_FAIL(0.00)[2a01:4f8:2a36:8743::1:server fail]; MIME_TRACE(0.00)[0:+]; FROM_HAS_DN(0.00)[]; TO_DN_NONE(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; RCVD_COUNT_TWO(0.00)[2] X-Rspamd-Server: mail-test X-Rspamd-Action: no action X-Rspamd-Queue-Id: 08D4341B21 In the following example we used a [[#custom_regexp_rule_with_multimap|multimap]] rule associated to the symbol TEST_SPAM_STRING to reach the //add_header// score: X-Spamd-Result: default: False [5.28 / 18.00]; TEST_SPAM_STRING(5.58)[]; ... X-Rspamd-Server: test-mail X-Rspamd-Action: add header X-Rspamd-Queue-Id: 4C9E742AE5 X-Spam: Yes Changing the multimap rule we reached the //rewrite_subject// score (notice that the ''X-Spam:'' header is not added in this case, despite the //rewrite_subject// is considered greather in SPAM score than //add_header//): X-Spamd-Result: default: False [6.28 / 18.00]; TEST_SPAM_STRING(6.58)[]; ... X-Rspamd-Queue-Id: D87FC41B3A X-Rspamd-Server: test-mail X-Rspamd-Action: rewrite subject With the following example we add a custom **X-Virus** header if a symbol was added, e.g. by the antivirus module. In this case the antivirus module should not apply its own //reject// action, otherwise it is pointless to mangle the headers. # Add the X-Spamd-Result header and others to all the messages. use = ["x-spamd-result", "x-spam-level", "x-spam-status", "x-virus"]; # Implies X-Spamd-Result and add X-Rspamd-Queue-Id, X-Rspamd-Server and X-Rspamd-Action. extended_spam_headers = true; # Special routine to add custon a X-Virus header upon specific symbols. routines { x-virus { header = "X-Virus"; remove = 0; # The following setting is an empty list by default and required to be set. # These are user-defined symbols added by the antivirus module. symbols = ["CLAM_VIRUS", "JUST_EICAR"]; } } ===== Logging ===== Example to enable logging for the //milter// and the //rbl// modules: create the file **/etc/rspamd/local.d/logging.inc** with: debug_modules = ["milter", "rbl"] ===== ClamAV on TCP socket in Debian 12 ===== BEWARE of the **[[https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1042377|Debian bug #1042377]]** which prevents the ClamAV daemon to listen on the TCP socket, even if you run the **dpkg-reconfigure clamav-daemon** procedure on a clean system. The problem is reported into **/var/log/clamav/clamav.log**: TCP: No tcp AF_INET/AF_INET6 SOCK_STREAM socket received from systemd. This means that the Systemd unit must create the socket and pass it to the daemon, so any **TCPSocket** and **TCPAddr** configurationin **/etc/clamav/clamd.conf** is useless. The workaround is contained into the bug report, create a new Systemd unit **/etc/systemd/system/clamav-daemon.socket.d/tcp-socket.conf** and declare the socket into it: [Socket] ListenStream=127.0.0.1:3310 systemctl daemon-reload systemctl restart clamav-daemon.socket systemctl restart clamav-daemon.service ===== Web references ===== * **[[https://rspamd.com/doc/tutorials/writing_rules.html|Writing Rspamd rules]]** * **[[https://blog.mailpace.com/blog/how-to-catch-spam-with-rspamd/|How to catch spam with Rspamd]]** * **[[https://www.0xf8.org/2018/05/an-alternative-introduction-to-rspamd-configuration-introduction/|An alternative introduction to rspamd configuration: Introduction]]**