Block Email Spam By Checking Header and Body in Postfix/SpamAssassin

In previous articles, we discussed how you can quickly set up your own mail server using iRedMail and  7 effective methods for blocking email spam with Postfix SMTP server. I’m going to share more tips and tricks to block email spam in this article. Specifically, we will see how to check email header and body with Postfix and SpamAssassin (SA) to detect spam. SpamAssassin is a free, open-source, flexible and powerful spam-fighting tool. While the commands in this article are for Ubuntu users, you can easily adapt them for Debian, CentOS, RHEL and OpenSUSE.

Email Header and Body Checks with Postfix SMTP Server

Postfix provides 4 simple content checking parameters.

  • header_checks
  • mime_header_checks
  • nested_header_checks
  • body_checks

Postfix will check all inbound emails when any of the above parameters is being used. Each parameter points to a lookup table containing regular expression patterns and actions. The patterns are compared to strings within email messages (header and body). If Postfix finds a match, the specified action is executed. Header and body checks are done by the Postfix cleanup daemon.

There are mainly two types of regular expressions that can be used by Postfix.

  • regexp: POSIX regular expression
  • PCRE: Perl compatible regular expression

Postfix comes with POSIX regular expression support, but PCRE is way faster. To use PCRE in Postfix, you need to install the postfix-pcre package.

sudo apt install postfix-pcre

Run the following command and you will see pcre is now supported.

postconf -m

Header Checks

To enable header_checks in Postfix, open the main configuration file.

sudo nano /etc/postfix/main.cf

Add the following line at the end of the file.

header_checks = pcre:/etc/postfix/header_checks

Save and close the file. Then you need to create the /etc/postfix/header_checks lookup file.

sudo nano /etc/postfix/header_checks

You can add regular expression checking like below.

/free mortgage quote/     REJECT
/repair your credit/      REJECT

The lefthand key is a regular expression enclosed by two forward slashes. If any of the strings on the leftland appear in any of the headers of an email message (these would most likely show up in the Subject: header), the message is rejected and bounced to the sender. By default regular expression checking is not case-sensitive.

You can also use DISCARD, instead of REJECT.

/free mortgage quote/    DISCARD
/repair your credit/     DISCARD

This will cause Postfix to claim successful delivery and silently discard the message. DISCARD makes it look as if the message was delivered even though it was simply thrown away. DISCARD can also be useful to minimize the backscatter problem. If an innocent user’s email address is used as the sender address, you can claim successful delivery, so that the innocent user does not receive bounce messages.

You can add the following line to reject all messages that include attachments with file extensions that might be dangerous.

/name ?="?.*\.(bat|com|dll|exe|hta|pif|vbs)"?/    DISCARD

Some spammers use blank email address in the From: or To: header, you can add the following checks.

/To:.*<>/           DISCARD
/From:.*<>/         DISCARD

Once you finish editing the header_checks lookup file, you need to build the index file.

sudo postmap /etc/postfix/header_checks

Then restart Postfix for the changes to take effect.

sudo systemctl restart postfix

Body Checks

In addition to header checks, Postfix can check the body of an email message. To enable body_checks in Postfix, open the main configuration file.

sudo nano /etc/postfix/main.cf

Add the following line at the end of the file.

body_checks = pcre:/etc/postfix/body_checks

Save and close the file. Then you need to create the /etc/postfix/body_checks lookup file.

sudo nano /etc/postfix/body_checks

You can add regular expression checking like below.

/free mortgage quote/     REJECT
/repair your credit/      REJECT

You can use DISCARD, instead REJECT.

/free mortgage quote/     DISCARD
/repair your credit/      DISCARD

The patterns indicated by the body_checks parameter are checked against each line of the body of the message. If any of the strings on the leftland appear in the body of an email message, the message is rejected or discarded. Once you finish editing the body_checks lookup file, you need to build the index file.

sudo postmap /etc/postfix/body_checks

Then restart Postfix for the changes to take effect.

sudo systemctl restart postfix

SpamAssassin Content Filter

The built-in content checking in Postfix is very simple. However, there is no way to whitelist individual messages that you might want to receive despite their containing phrases that trigger a rejection and you might not want to reject or discard an email message by checking a single phrase in the email header or body. For more sophisticated analysis, we need to use a separate content filter (such as SpamAssassin) specifically designed to detect spam.

SpamAssassin is a score-based system. It will check email message against a large set of rules. Each rule adds or removes points in the message’s score. If the score is high enough (by default 5.0), the message is considered spam.

Integrate SpamAssassin with Postfix SMTP Server

Note: If you used iRedMail to set up your mail server, then SpamAssassin is already installed alongside Amavis, which can read SpamAssassin rules. You don’t need to follow the instructions in this section.

Run the following command to install SpamAssassin from the default Ubuntu software repository. Spamc is the client for SpamAssassin spam filtering daemon.

sudo apt install spamassassin spamc

During the installation, the debian-spamd user and group will be automatically created. The binary installed by the spamassassin package is called spamd, which will be listening on TCP port 783 on local host.

spamassassin content filter

By default, the spamassassin systemd service is disabled, you can enable auto start at boot time with:

sudo systemctl enable spamassassin

Then start SpamAssassin.

sudo systemctl start spamassassin

Next, we need to configure Postfix so it will pass inbound emails to Spamassassin. Open Postfix master configuration file.

sudo nano /etc/postfix/master.cf

Find the following line:

smtp    inet     n     -    y     -     -     smtpd

And add the following option to it.

  -o content_filter=spamassassin

spamassassin content filter

Then add the following lines to the end of the file in order to enable after-queue content filtering.

spamassassin unix -     n       n       -       -       pipe
     user=debian-spamd argv=/usr/bin/spamc -f -e  
     /usr/sbin/sendmail -oi -f ${sender} ${recipient}

Save and close the file.  Restart Postfix for the changes to take effect.

sudo systemctl restart postfix

Now SpamAssassin is integrated with Postfix SMTP server.

Checking Email Header and Body with SpamAssassin

SpamAssassin ships with many spam detection rules in /usr/share/spamassassin/ directory. Allow me to explain some of the rules.

In the /usr/share/spamassassin/20_head_tests.cf file, you can find the following two lines.

header MISSING_HEADERS       eval:check_for_missing_to_header()
describe MISSING_HEADERS     Missing To: header

The first line tests if the To: header exists in an email message. The second line, which is optional, explains what the first line tests for. The uppercase letters is the name of this test.

The following 3 lines are for testing if there’s a Date: header in the email message.

header __HAS_DATE            exists:Date
meta MISSING_DATE            !__HAS_DATE
describe MISSING_DATE        Missing Date: header

And these 3 lines are for testing if there’s a From: header in the email message.

header __HAS_FROM            exists:From
meta MISSING_FROM            !__HAS_FROM
describe MISSING_FROM        Missing From: header

You might want to use the Cron job shipped with SpamAssassin to automatically update SpamAssassin’s rules on a daily basis. If so, open the /etc/default/spamassassin file and change CRON=0 to CRON=1.

Set Custom Score for Existing Rules

In the 50_scores.cf and 72_scores.cf file, you can see the default scores for various tests. If you think the default score is too low or too high for a certain test, you can set custom score in /etc/spamassassin/local.cf file.

For example, RFC 5322 requires that every email message must have From: and Date: header fields, so I can set a very high score if either of them is missing in an email message by appending the following two lines in local.cf file.

score MISSING_FROM   5.0
score MISSING_DATE   5.0

Although the To: header field is not mandatory in RFC 5322, I prefer to set a high score if it’s missing in an email message because I have never seen a legitimate email missing this header field.

score MISSING_HEADERS 3.0

Some spammers uses two email addresses in the From: header field like below.

From: "[email protected]" <[email protected]>

I think the default score for this kind of email is low, I prefer to set it to 3.0.

score PDS_FROM_2_EMAILS 3.0

And other spammers often ask you to send a read receipt, I set the score to 2.0 for this kind of email.

score FREEMAIL_DISPTO 2.0

There are some spammers use different domain names in the From: and Reply-To: header, I give them a 3.5 score.

score FREEMAIL_FORGED_REPLYTO 3.5

I also have seen some spammers using non-existent domain name in the From: header field. I set a 5.0 score for this type of email.

score DKIM_ADSP_NXDOMAIN 5.0

Last but not least, many spammers spoof the gmail.com domain in the From: header field. I set a 2.5 score this kind of email.

score FORGED_GMAIL_RCVD 2.5

Adding Your Own Rules

You can add custom SpamAssassin rules in /etc/spamassassin/local.cf file.

sudo nano /etc/spamassassin/local.cf

For example, some spammers use the same email address in the From: and To: header, you can add the following lines at the end of the file to add scores to such emails.

header   FROM_SAME_AS_TO   ALL=~/\nFrom: ([^\n]+)\nTo: \1/sm
describe FROM_SAME_AS_TO   From address is the same as To address.
score    FROM_SAME_AS_TO   3.0

If you have configured OpenDMARC on your mail server, you can now add the following lines to add scores to emails that fail DMARC check.

header    CUSTOM_DMARC_FAIL   Authentication-Results =~ /dmarc=fail/
describe  CUSTOM_DMARC_FAIL   This email failed DMARC check
score     CUSTOM_DMARC_FAIL   3.0

The above code tells SpamAssassin to check if the Authentication-Results header contains the string “dmarc=fail”. If found, increase the score by 3.0.

You can tell SpamAssassin to increase the score of an email if a certain phrase is found in the body. For example, many spammers use the recipient’s email address in the first body line like below.

Hi [email protected]
Hello [email protected]
Dear [email protected]

I don’t want to talk with people who doesn’t bother using my name in the first line of email. So I created a rule in SpamAssassin to filter this kind of email.

body      BE_POLITE       /(hi|hello|dear) xiao\@linuxbabe\.com/i
describe  BE_POLITE       This email doesn't use a proper title for the recipient
score     BE_POLITE       5.0

Regular expression in SpamAssassin is case-sensitive by default, you can add the i option at the end to make it case-insensitive.

Whitelist

You can use the whitelist_from parameter to add a particular email address or domain to your Spamassassin whitelist. For example, add the following two lines at the end of local.cf file.

whitelist_from [email protected]
whitelist_from *@canonical.com

Save and close the file. Restart SpamAssassin for the changes to take effect. (If you use Amavis with Spamassasin, you just need to restart amavis: sudo systemctl restart amavis)

sudo systemctl restart spamassassin

A whitelisted sender has a -100 default score. They will still be tested by SpamAssassin rules, but it’s super hard for them to reach a 5.0 score.

Blacklist

To blacklist a sender, use the blacklist_from parameter, which has the same format as whitelist_from.

blacklist_from [email protected]
blacklist_from *@example.org

Move Spam into the Junk Folder

I’m going to show you how to move spam to Junk folder with the Dovecot IMAP server and the sieve plugin. This method requires that inbound emails are delivered to the message store via the Dovecot “deliver” LDA (local delivery agent). If you can find the following text in /var/log/mail.log file, then this requirement is satisfied.

postfix/lmtp

or

delivered via dovecot service

Run the following command install dovecot-sieve from Ubuntu software repository.

sudo apt install dovecot-sieve

This package installs two configuration files under /etc/dovecot/conf.d/ directory: 90-sieve.conf and 90-sieve-extprograms.conf. Open the 15-lda.conf file.

sudo nano /etc/dovecot/conf.d/15-lda.conf

Add the sieve plugin to local delivery agent (LDA).

protocol lda {
    # Space separated list of plugins to load (default is global mail_plugins).
    mail_plugins = $mail_plugins sieve
}

Save and close the file. If you can find the 20-lmtp.conf file under /etc/dovecot/conf.d/ directory, then you should also enable the sieve plugin in that file like below.

protocol lmtp {
      mail_plugins = quota sieve
}

Then open the 90-sieve.conf file.

sudo nano /etc/dovecot/conf.d/90-sieve.conf

Go to line 79 and add the following line, which tells Sieve to always execute the SpamToJunk.sieve script before any user specific scripts.

sieve_before = /var/mail/SpamToJunk.sieve

Save and close the file. Then create the sieve script.

sudo nano /var/mail/SpamToJunk.sieve

Add the following lines, which tells Dovecot to move any email messages with the X-Spam-Flag: YES header into Junk folder.

require "fileinto";

if header :contains "X-Spam-Flag" "YES"
{
   fileinto "Junk";
   stop;
}

Save and close the file. We can compile this script, so it will run faster.

sudo sievec /var/mail/SpamToJunk.sieve

Now there is a binary file saved as /var/mail/SpamToJunk.svbin. Finally, restart dovecot for the changes to take effect.

sudo systemctl restart dovecot

Automatically Clean the Junk Folder and Trash Folder

To delete emails in Junk folder for all users, you can run

sudo doveadm expunge -A mailbox Junk all

To delete emails in Trash folder, run

sudo doveadm expunge -A mailbox Trash all

Then add a cron job to automate the job.

sudo crontab -e

Add the following line to empty Junk and Trash folder weekly.

@weekly doveadm expunge -A mailbox Junk all;doveadm expunge -A mailbox Trash all

Or monthly.

@monthly doveadm expunge -A mailbox Junk all;doveadm expunge -A mailbox Trash all

Set Reject Score

If you would like to reject an email when it gets a very high score such as 7.5, you can tell Spamassassin to reject the email, so it will never be seen by the recipient. In order to set a reject score, you need to integrate SpamAssassin as a milter for Postfix. First, remove the SpamAssassin configurations in /etc/postfix/master.cf file. Then install the spamass-filter packages from Ubuntu default software repository.

sudo apt install spamass-milter

Next, edit /etc/postfix/main.cf file and add the following lines at the end of the file.

# Milter configuration
milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:/spamass/spamass.sock
non_smtpd_milters = $smtpd_milters

If you have configured OpenDKIM and OpenDMARC, then theses lines should look like below. The order matters.

# Milter configuration
milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:/opendkim/opendkim.sock,local:/opendmarc/opendmarc.sock,local:/spamass/spamass.sock
non_smtpd_milters = $smtpd_milters

Save and close the file. Now open the /etc/default/spamass-milter file and find the following line.

#OPTIONS="${OPTIONS} -r 15"

Uncomment this line and change 15 to your preferred reject score such as 7.5.

OPTIONS="${OPTIONS} -r 7.5"

Save and close the file. Restart Postfix and Spamass Milter for the changes to take effect.

sudo systemctl restart postfix spamass-milter

Now if the score of a particular email is over 7.5, Spamassassin would reject it and you would find a message like below in the /var/log/mail.log file, indicating it’s rejected.

postfix/cleanup[8641]: B644B3E8D6: milter-reject: END-OF-MESSAGE  5.7.1 Blocked by SpamAssassin

Wrapping Up

I hope this tutorial helped you use Postifix and SpamAssassin to filter spam. As always, if you found this post useful, then subscribe to our free newsletter to get more tips and tricks. Take care 🙂

Rate this tutorial
[Total: 2 Average: 5]

report this ad

2 Responses to “Block Email Spam By Checking Header and Body in Postfix/SpamAssassin

  • Marcio Merlone
    3 weeks ago

    Very good guide, although it is not wise to block/reject empty addresses, like From: . There is legitimate mail with that.

  • Nina Vise
    1 week ago

    Hey Xiao Guo An,

    very nice guide I just used the Part: Set Reject Score for my Plesk installation of dovecot and postfix, and it is working. Is there a way to change the Message “Blocked by SpamAssassin” to “Blocked by provider in case you are a human contact: xyz” ?

    Thank you very much!

Leave a Comment

  • Comments with links are moderated by admin before published.
  • Your email address will not be published.
  • Use <pre> ... </pre> HTML tag to quote the output from your terminal/console.
  • Please use the community (https://community.linuxbabe.com) for questions unrelated to this article.
  • If my answer helped you, please consider supporting this site. Thanks :)