Filtering out spam with rspamd

You have a perfectly working mail server by now. But before you go live let’s does something about the insane amount of spam. In previous editions of this guide I used and recommended SpamAssassin. However I have found a piece of software that is more versatile, scales better and is still easy to integrate: rspamd. rspamd has a (maybe biased) comparison on their home page.

rspamd is a permanent process that runs on your mail server. It listens to connections from Postfix using the milter (=mail filter) protocol. Every time an email enters ryour system, Postfix will send it to rspamd to have its content checked. rspamd runs a lot of checks on the email and computes a total score. The higher the score – the more likely the email should be considered unsolicited.

Make Postfix use rspamd

Let’s tell Postfix to send all incoming email through rspamd. Run these commands on the shell:

postconf smtpd_milters=inet:127.0.0.1:11332
postconf non_smtpd_milters=inet:127.0.0.1:11332
postconf milter_protocol=6
postconf milter_mail_macros="i {mail_addr} {client_addr} {client_name} {auth_authen}"

Postfix will now connect to rspamd that is listening to TCP port 11332 on localhost (127.0.0.1) and pass the email over that connection. The smtpd_milters setting defines that connection for emails that came into the system from the internet via the SMTP protocol. The non_smtpd_milters setting is optional – it makes Postfix have all emails checked that originate from the system itself. Finally the milter_mail_macros setting defines several variables that rspamd expects for better spam detection. rspamd then runs its checks and tells Postfix whether the email should pass or get rejected.

Testing spam detection

For testing we can use a sample spam email that comes with SpamAssassin. It is called GTUBE (Generic Test for Unsolicited Bulk Email). It contains a certain articial pattern that is recognized as spam by SpamAssassin. Do you know EICAR.COM to test virus scanners? This is the same thing for spam.

I suggest that you download the file on the server:

wget http://spamassassin.apache.org/gtube/gtube.txt

…and send that email to our test user John…

sendmail john@example.org < gtube.txt

Check your /var/log/mail.log. You will find something like this:

Jan 13 09:21:01 mail postfix/cleanup[19945]: 95B7A42212: milter-reject: END-OF-MESSAGE from localhost[127.0.0.1]: 5.7.1 Gtube pattern; from=<root@mail.example.org> to=<john@example.org>
Jan 13 09:21:01 jen postfix/cleanup[19945]: 95B7A42212: to=<john@example.org>, relay=none, delay=0.18, delays=0.18/0/0/0, dsn=5.7.1, status=bounced (Gtube pattern)

The interesting parts are those printed in bold letters. “milter-reject” tells that the milter (rspamd) recommended to Postfix to reject the email. It gave the reason “5.7.1 Gtube pattern”. In mail communication you often find these three digit codes. They are defined in RFC 3463. The first digit is most important:

  • 2 = Success
  • 4 = Transient failure (temporary problem – come back later)
  • 5 = Permanent failure (do not try again in this way)

So 5.7.1 tells us that the result code is a permanent failure in delivery. If you looked up the RFC you would find that the 7 stands for an issue regarding the security policy. So it’s not a technical failure but instead a security-relevant component on the system has rejected the email. That’s what rspamd did. It even told us the reason: “Gtube pattern”. So you see that rspamd knows about the Gtube spam test pattern and reacts as expected.

Accordingly you won’t see this email in John’s inbox. This is a great advantage of using milters by the way. Imagine Postfix receiving a spam email and confirming its reception. What should it do when it finds out that it’s unwanted email? According to the SMTP protocol it must not throw away any emails. Would you create a bounce message telling the sender that you did not accept the email? That would be a bad idea because the pretended sender address is very likely not the real sender. You would send the bounce to an innocent person thus creating so called backscatter and make it even worse. So the right approach is to check the email while the sending server is still connected to your Postfix. This allows Postfix to reject the email with a 5.x.x error code and let the other side figure out what to do.

Score metrics (optional)

rspamd will however not reject all spam email. As it computes a score of the spam propability you can tell it which scores you would accept, flag as spam or make the incoming email get rejected. Take a look at the /etc/rspamd/metrics.conf file. There are tons of scores defined for various conditions. At the beginning of the file you will find:

 actions {
 reject = 15;
 add_header = 6;
 greylist = 4;
 }

These are the default actions. If rspamd computes a score of at least 15 then the email will get rejected just like the Gtube pattern in the previous test. Any score above 6 will add header lines like “X-Spam: Yes” so that your mail software can detect them and maybe file the email to a different folder. Any score above 4 will trigger greylisting which is a mechanism that temporarily rejects the email with a 4.x.x code and waits if the sending server will try again. After a waiting time of a few minutes greylisting will accept the email. The idea is to reject email from systems that do not have a sending queue. Malware like on infected Wind*ws computers used to try sending an email just once which triggered greylisting and successfully rejected the spammer. But even malware programmers have learned and may try again after a few minutes thus circumventing greylisting. Your mileage may vary.

If you like to change these defaults then create a new file in /etc/rspamd/override.d/metrics.conf contaning:

actions {
 reject = 150;
 add_header = 2;
 greylist = 5;
}

This would virtually never reject an email. And it would flag any email with a score of at least 2 as spam. Greylisting would happen at a score above 5. These are not necessarily sane values – they are just meant as an example.

Please take a moment to understand how to change rspamd defaults. You can either create files in /etc/rspamd/local.d/… which will replace entire sections; or create files in /etc/rspamd/override.d/… which will change only small parts of the configuration. There is a helpful page in the rspamd documentation that contains examples. Whatever you do – never change the /etc/rspamd/* files directly because a software update will try to replace them.

Of course restart rspamd after any configuration changes:

service rspamd reload

To check if rspamd has picked up your configuration use this command to see the current configuration:

rspamadm configdump

You may test your configuration using

rspamadm configtest

Alternatively you may check if all required rspamd processes are running…

pgrep -a rspam

22510 rspamd: main process 
22511 rspamd: rspamd_proxy process 
22512 rspamd: controller process 
22513 rspamd: normal process 
22514 rspamd: normal process 
22515 rspamd: hs_helper process

Adding headers

As you may know an email contains of the header and the body. Your users will only see header information like the subject, the sender, the recipient and the date and time the email was sent. But there is way more information like the router the email travelled or extended headers added by the various mail server on the way to the destination. Such extended headers begin with an “X-“. rspamd can add such headers to help you filter out spam. For that purpose create a new configuration override file at /etc/rspamd/override.d/milter_headers.conf with this content:

extended_spam_headers = true;

As documented it will add these headers:

X-Rspamd-Server: mail
Authentication-Results: dmarc=fail reason="No valid SPF, No valid DKIM" …
X-Rspamd-Queue-Id: C22E55A005B3
X-Spamd-Result: default: False [11.55 / 15.00]
 R_PARTS_DIFFER(0.27)[63.4%]
 FROM_NO_DN(0.00)[]
 RCVD_COUNT_ZERO(0.00)[0]
 R_DKIM_NA(0.00)[]
 FUZZY_DENIED(12.00)[1:19305c7fdd:1.00:bin,1:35699594fd:0.91:txt]
 RBL_SENDERSCORE(2.00)[55.181.23.94.bl.score.senderscore.com]
 ARC_NA(0.00)[]
 RCPT_COUNT_ONE(0.00)[1]
 RCVD_TLS_ALL(0.00)[]
 FROM_EQ_ENVFROM(0.00)[]
 R_SPF_SOFTFAIL(0.00)[~all]
 BAYES_HAM(-2.71)[98.75%]
 TO_MATCH_ENVRCPT_ALL(0.00)[]
 MIME_GOOD(-0.10)[multipart/related,multipart/alternative,text/plain]
 MID_RHS_MATCH_FROM(0.00)[]
 ASN(0.00)[asn:16276, ipnet:94.23.0.0/16, country:FR]
 TO_DN_NONE(0.00)[]
 DMARC_POLICY_SOFTFAIL(0.10)[Chronopost.fr : No valid SPF, No valid DKIM,none]
 SUBJECT_ENDS_EXCLAIM(0.00)[]
X-Spam: Yes

Each of the uppercase symbols like FROM_HAS_DN means that a certain detection routing of rspamd was triggered. It does not necessarily mean something bad about the email. For example R_SPF_ALLOW has a negative score that lowers the total score because it is something good about the email. There are a several symbols with a 0.00 score. These do not change the score but show you what rspamd has found. But if you consider certain criteria good or bad then you can define your own scores for them.

The last line here is especially interesting because next on our list is…

Sending spam to the Junk folder

Your users will not realize that their spam emails have an added “X-Spam: Yes” header. Their emails just appear like normal in their inbox. So let’s aid them by moving spam to a seperate Junk folder beneath their inbox automatically. Dovecot has support for Sieve filters which are basically scripts that run automatically whenever an email coming in.

John could log into Roundcube and configure a new Sieve filter for himself that would save any emails to his “Junk” folder if the header line “X-Spam: Yes” was found. This rule would be useful for all your users though so let’s find a general solution.

Note: SpamAssassin as used in previous versions of this guide used a slightly different header. “X-Spam-Flag: YES”. Make sure to change your Sieve filters accordingly.

Dovecot supports global Sieve filters that apply to all users. Edit the file /etc/dovecot/conf.d/90-sieve.conf. Look for the “sieve_after” lines. They are commented out. So add a new line there:

sieve_after = /etc/dovecot/sieve-after

The “sieve after” filters are executed after the user’s filters. John can define his own filter rules. And after that Dovecot will run any filter rules it finds in files in /etc/dovecot/sieve-after. Create that directory:

mkdir /etc/dovecot/sieve-after

And create a new file /etc/dovecot/sieve-after/spam-to-folder.sieve reading:

require ["fileinto","mailbox"];

if header :contains "X-Spam" "Yes" {
 fileinto :create "INBOX.Junk";
 stop;
}

The “require” lines include functionality to move emails into certain folders (fileinto) and to create folders if they don’t exist yet (mailbox). Then if SpamAssassin marked a header as spam it is moved into the INBOX.Junk folder which just appears as “Junk” to the user underneath their inbox.

Dovecot cannot deal with such human-readable files though. So we need to compile it:

sievec /etc/dovecot/sieve-after/spam-to-folder.sieve

That generated a machine-readable file /etc/dovecot/sieve-after/spam-to-folder.svbin.

Restart Dovecot:

service dovecot restart

Now all your users will automatically get spam emails moved to their Junk folder. Nice – isn’t it?

Learning existing spam

If you followed previous ISPmail guides then you will have used SpamAssassin for a while. You may have trained it with ham (good) and spam (unwanted) emails for a while which fueled the built-in Bayes database to increase its detection reliability. The bad news is that you cannot use that training data in rspamd because it uses a different algorithm. There are three ways to get started with rspamd…

(a) No Bayes training

This is not as bad as it sounds. rspamd has way more functionality to determine if an email is ham or spam. I recommend that you enable auto learning. Just create a new file /etc/rspamd/override.d/classifier-bayes.conf and make it contain:

autolearn = true;

This is a very conservative approach though. It learns emails as spam if they are so bad that they get rejected. And it learns emails as ham if they have a negative (=very good) score. The rspamd documentation has further examples how to fine-tune auto learning.

(b) Using pre-built statistics

rspamd provides a sample database with over 3000 learned emails. They are from 2015 and perhaps are not a good start because the nature of spam changes over time.

Stop rspamd:

service rspamd stop

Take their pre-built statistics and put the *sqlite files into your /var/lib/rspamd directory. Fix the ownership of those files by running…

chown _rspamd._rspamd /var/lib/rspamd/*sqlite

And finally start rspamd again…

service rspamd start

Verify that the database is now filled by running…

rspamc stat

At the end of the output you will see something like…

Statfile: BAYES_SPAM type: sqlite3; length: 41.78M; free blocks: 0; total blocks: 596k; free: 0.00%; learned: 1880; users: 1; languages: 3
Statfile: BAYES_HAM type: sqlite3; length: 51.12M; free blocks: 0; total blocks: 684k; free: 0.00%; learned: 1578; users: 1; languages: 3
Total learns: 3458

(c) Re-learn your existing ham and spam

You have been running your mail server for a while? And you have a good amount of ham and spam emails? Then let’s use those to train rspamd. It is important to train both ham and spam emails. The rspamc command will allow you to feed entire directories/folders of emails to the learning process. Fortunately we are using the Maildir format to store emails which rspamd can understand. An example to train spam:

rspamc learn_spam /var/vmail/example.org/john/Maildir/.INBOX.Junk/cur

And this would be an example to train ham:

rspamc learn_ham /var/vmail/example.org/john/Maildir/cur".

Of course the quality of spam detection will depend on how good the source data is. So do not train spam emails from users who randomly put mails into their Junk folder that is not actually spam.

Check the number of emails you learned by running…

rspamc stat

Bayes spam checking will not work before it learned at least 200 spam and ham emails. Teaching rspamd fewer emails or just spam emails will not work. This is defined by the min_learns variable defined in /etc/rspamd/statistic.conf.

In the output you will find lines beginning with “Statfile” like these…

Statfile: BAYES_SPAM type: sqlite3; length: 41.78M; free blocks: 0; total blocks: 0; free: 0.00%; learned: 0; users: 1; languages: 1
Statfile: BAYES_HAM type: sqlite3; length: 51.12M; free blocks: 0; total blocks: 0; free: 0.00%; learned: 0; users: 1; languages: 1
Total learns: 0

This is what you usually start with. The more emails you feed into the training process the better the detection rate will be. Some emails however may not be long enough or be too similar to previously trained emails. So don’t worry if you are training 1000 emails but just get a count of 500 emails here.

Logging

rspamd keeps a verbose log of its actions in /var/log/rspamd/rspamd.log. If a user complains that a certain email got blocked or at least flagged as spam then take a look at this log. You can match the /var/log/mail.log with it by comparing the Postfix queue ID. Those are the 12-digit hexadecimal number like “95CE05A00547“. Those IDs can be found in the rspamd.log, too:

2018-01-14 06:39:45 #10424(normal) <40985d>; task; rspamd_task_write_log: id: <undef>, qid: <95CE05A00547>, ip: 12.13.51.194, from: <…>, (default: F (no action): [3.40/15.00] [MISSING_MID(2.50){},MISSING_DATE(1.00){},MIME_GOOD(-0.10){text/plain;},ARC_NA(0.00){},ASN(0.00){asn:8220, ipnet:212.123.192.0/18, country:GB;},FROM_EQ_ENVFROM(0.00){},FROM_NO_DN(0.00){},RCPT_COUNT_ONE(0.00){1;},RCVD_COUNT_ZERO(0.00){0;},RCVD_TLS_ALL(0.00){},TO_DN_NONE(0.00){},TO_DOM_EQ_FROM_DOM(0.00){},TO_MATCH_ENVRCPT_ALL(0.00){}]), len: 181, time: 16.000ms real, 6.385ms virtual, dns req: 0, digest: <69b289a82827c11f759837c033cd800a>, rcpts: <…>, mime_rcpt: <…>

The web interface

rspamd’s web interface

rspamd comes with a neat bonus feature: a web interface. It allows you to check emails for spam, get statistics and fine-tune scores. It is already installed and enabled by default and expects HTTP requests on port 11334 on the localhost interface. I suggest you add a simple proxy configuration to your already working HTTPS-enabled web mail configuration to get access.

First you need to enable Apache’s module for HTTP proxying:

a2enmod proxy_http

You can either create a new virtual host configuration or just edit the /etc/apache2/sites-available/webmail.example.org-https.conf file. Anywhere within the VirtualHost tags add:

 ProxyPass "/rspamd" "http://localhost:11334"
 ProxyPassReverse "/rspamd" "http://localhost:11334"

This piece of configuration will forward any requests to https://webmail.example.org/rspamd to localhost:11334 and thus give you access to the rspamd web interface.

The interface is password protected. Let’s generate a new access password:

pwgen 15 1

This gives you a password like “eiLi1lueTh9mia4”. You could put that password in an rspamd configuration file. But cleartext passwords in configuration files are not quite elegant. Let’s create a hash of the password:

rspamadm pw
Enter passphrase: …
$2$icoahes75e7g9wxapnrbmqnpuzjoq7z…

Feed it the password that pwgen created for you and you will get a long hashed password. This procedure by the way is also documented on the rspamd pages.

Create a new configuration file /etc/rspamd/local.d/worker-controller.inc and put your hashed password in there:

password = "$2$icoahes75e7g9wxapnrbmqnpuzjoq7z…"

That’s it for the configuration. Finally restart both rspamd and Apache to load your changed configuration:

service rspamd reload
service apache2 restart

If everything went as expected you should now be able to access the rspamd web interface at https://webmail.example.org/rspamd

Anything else?

If you consider using rspamd for a larger environment then please take your time reading the good amount of documentation they provide. rspamd scales very well but requires additional setup like using Redis for faster spam analysis across multiple mail gateways.

15 thoughts on “Filtering out spam with rspamd

  • 2018-01-21 at 23:50
    Permalink

    I think the /etc/rspamd/override/milter_headers.conf should be /etc/rspamd/override.d/milter_headers.conf

    (b) using pre-built statistics doesn’t seem to do anything maybe it is missing some rspamc commands ?

    Reply
  • 2018-01-23 at 21:29
    Permalink

    Hi Christoph,

    rspamd listens also on 0.0.0.0 by default. For security reasons, I like to turn all public listening ports off (normal root server providers might not offer a firewall).

    Hence, I created 2 files in /etc/rspamd/local.d:

    worker-normal.inc
    bind_socket = “127.0.0.1:11333”;

    worder-proxy.inc
    bind_socket = “127.0.0.1:11332”;

    Regards,
    Andy

    Reply
  • 2018-02-02 at 13:51
    Permalink

    Anyone has a running configuration to see the rspamd Web GUI ?
    I am trying to set it up with a nginx server on another machine and I don’t get it to run 🙁

    Reply
  • 2018-02-04 at 06:44
    Permalink

    hi,
    for those who want to extend rspamd to support dkim, this is the following procedures:

    mkdir /var/lib/rspamd/dkim/
    rspamadm dkim_keygen -b 1024 -s mail -k /var/lib/rspamd/dkim/mail.key > /var/lib/rspamd/dkim/mail.txt
    (**I use 1024 as gandi.net seems to support only 1024 bits, feel free to move up to 2048. mail is a selector again feel free to change it **)
    chown -R _rspamd:_rspamd /var/lib/rspamd/dkim
    (** remember to set owner to rspamd **)
    chmod 440 /var/lib/rspamd/dkim/*

    Once the dkim key is set up it do:

    nano -$ /var/lib/rspamd/dkim/mail.txt

    It should look like this:

    mail._domainkey IN TXT ( “v=DKIM1; k=rsa; ”
    “p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/al5HqXUpe+HUazCr6t9lv2VOZLR369PPB4t+dgljZQvgUsIKoYzfS/w9NagS32xZYxi1dtlDWuRfTU/ahHO2MYzE0zHE4lMfwb6VkNCG+pM6bAkCwc5cFvyRygwxAPEiHAtmtU5b0i9LY25Z/ZWgyBxEWZ0Wf+hLjYHvnvMqewPsduUqKVjDOdUqeBb1VAu3WFErOAGVUYfKqFX”
    “+yfz36Alb7/OMAort8A5Vo5t5k0vxTHzkYYg5KB6tLS8jngrNucGjyNL5+k0ijPs3yT7WpTGL3U3SEa8cX8WvOO1fIpWQz4yyZJJ1Mm62+FskSc7BHjdiMHE64Id/UBDDVjxwIDAQAB”
    ) ;

    create a dkim signing.conf:

    /etc/rspamd/local.d/dkim_signing.conf

    and fill it with :

    path = “/var/lib/rspamd/dkim/$selector.key”;
    selector = “mail”;
    ### Enable DKIM signing for alias sender addresses
    allow_username_mismatch = true;

    As rspamd also support arc:

    cp -R /etc/rspamd/local.d/dkim_signing.conf /etc/rspamd/local.d/arc.conf

    I took this from : https://thomas-leister.de/en/mailserver-debian-stretch/

    Reply
    • 2018-02-06 at 03:33
      Permalink

      To configure spf, dkim and dmarc I followed the steps of https://dmarcguide.globalcyberalliance.org

      To configure the dkim_signing module to sign different subdomains with different dkim keys, I modify some steps:

      rspamadm dkim_keygen -k /var/lib/rspamd/dkim/subdomain.example.org.dkim.key -b 2048 -s dkim -d subdomain.example.org

      DNS
      dkim._domainkey.subdomain.example.org. IN TXT “v=DKIM1; k=rsa;” “p=MIIBI…..HtByA” “504pO…..DAQAB”

      chown -R _rspamd._rspamd /var/lib/rspamd/dkim
      chmod 640 /var/lib/rspamd/dkim/subdomain.example.org.dkim.key

      create a dkim signing.conf:
      /etc/rspamd/local.d/dkim_signing.conf

      and fill it with :
      path = “/var/lib/rspamd/dkim/$domain.$selector.key”;
      selector = “dkim”;
      allow_username_mismatch = true;
      use_esld = false;

      and then restart rspamd:
      systemctl restart rspamd

      https://github.com/vstakhov/rspamd/issues/1808

      I accept suggestions, since I have some doubts about whether it is the optimal configuration.
      Regards,
      Marcelo

      Reply
  • 2018-02-08 at 09:12
    Permalink

    If you experience problems with outbound mails from authenticated users (with Postfix 2.11.3 on Debian jessie I got the error “4.7.1 Try again later” when sending outbound mails) try to replace

    milter_mail_macros = “i {mail_addr} {client_addr} {client_name} {auth_authen}”

    with

    milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}

    inside your main.cf 🙂

    Reply
  • 2018-02-09 at 11:12
    Permalink

    First of all, great guide!
    Your last command in den rspamd section “apache restart” should be “apache2ctl restart”

    Reply
    • 2018-02-09 at 11:45
      Permalink

      Oops, thanks, fixed.

      Reply
  • 2018-02-10 at 18:59
    Permalink

    Thanks for the great guide!
    To get the rspamd gui working I needed to add some more lines into my apache config:

    RewriteEngine on
    RewriteRule ^/rspamd$ /rspamd/ [R]
    ProxyPreserveHost On
    ProxyPass /rspamd http://localhost:11334/
    ProxyPassReverse /rspamd http://localhost:11334/

    Reply
  • 2018-02-10 at 21:43
    Permalink

    The web interface is really nice. Is there something similar for spamassassin?

    Reply
  • 2018-02-12 at 21:16
    Permalink

    I suppose it’s a problem of copy/paste from the Jessie version. You write :

    The “require” lines include functionality to move emails into certain folders (fileinto) and to create folders if they don’t exist yet (mailbox). Then if SpamAssassin marked a header as spam it is moved into the INBOX.Junk folder which just appears as “Junk” to the user underneath their inbox.

    I suppose I have to read rspamd instead of SpamAssassin…

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close