Stop Spam With Postfix Email Server

See the video:

How to Stop Spam With Postfix Linux Email Server

Video Summary:

How to stop spam / UCE with the Postfix mail transfer agent (MTA). Actual spam email is analyzed using mail logs and various SMTP restrictions are explored, as well as looking into how to setup client restriction maps to reject or defer. Lastly, different Postfix debug approaches are discussed.

As demonstrated with Postfix v3.1.12 on Debian 9.

Read the blog post:

Today I want to discuss ways to mitigate spam in the Postfix MTA. So here I’m showing some example spam:

This is an actual extortion spam that I received through my email server after the initial setup of Postfix.  This type of spam has become popular in recent years.  So, in this particular case, people do actually get hacked (or more commonly, an online service they used & trusted got hacked), their passwords get discovered and they become victims of this attack. These started showing up after I had a domain for a few years and then later decided to start hosting my own email for this domain. So they were probably sending these for quite some time and they weren’t actually going anywhere. Needless to say, when I decided to start hosting my own email server for this domain, it became my problem.

So let’s take a look at how we can deal with these and other types of spam. Now, in order to fight the spam effectively, you’re gonna have to get intimately familiar with your mail logs. These of course live in the

/var/log/mail.log

and associated archive files. So for example, here I’ve got three log snippets:

spam mail log snippets
spam mail log snippets

The one on top is showing the spam log entry of the spam that I was showing earlier and we can see the spammers populating the envelope “FROM:” with “RecordedYou*” and we see the original “TO:” is holly@brackin.net.  And brackin.net is that new domain that I added. If you saw my previous Postfix video, you know that I’ve set everything up to forward to my personal gmail account for now (that’s blacked out here).  And we can see that the status gets set to sent and the last entry is “removed”.  So, it got sent to my personal email and removed from the queue. The bottom two snippets show two more spam attempts – apparently from the same spammer – that were blocked by Postfix using two different techniques. The first technique was rejection based on a reverse host name lookup failure and the bottom technique is a mailbox is non-existent failure.

So now let’s take a look at how to configure these and other spam blocking techniques.

OK let’s start out with some version awareness.  This is the command to check your Postfix version:

% sudo postconf mail_version
mail_version = 3.1.12
Drop tha Hamma…

Postfix version 2.2 introduced the anvil service. The way anvil works: there’s a time window they call the “anvil rate time unit” ( defaults to 60 seconds ) and it provides a number of per-client metrics that we can use to limit bad actors. So, by clamping down on these parameters we can pretty much mitigate email flood attacks from out of control clients.  Lets take a look at some of these:

smtpd_error_sleep_time = 3s
smtpd_soft_error_limit = 5
smtpd_hard_error_limit = 10

These first three are all tightly related. So, a client can make (in this case) up to 5 errors before getting a 3-second back off delay in responses and then after ten errors, Postfix will just disconnect the client.  The defaults for these three are 1s, 10 and 20 for soft and hard.  Some other useful anvil-related parms:

smtpd_client_connection_count_limit = 5
smtpd_client_connection_rate_limit = 20

These next two anvil-related parameters specify a total allowed client connection count (maximum concurrent connections). I have it at 5 and the default is 50.  Then I have set my connection rate limiter to 20 (the default is 0 which means unlimited).   And that is a rate limit per the anvil time window.

smtpd_client_connection_count_limit was added in Postfix 2.8
smtpd_client_connection_rate_limit was added in Postfix 2.2

SMTP restrictions

So, as I showed, on this server I am using Postfix version 3.1.12.  However most of these restriction settings should work if you have Postfix version 2.3 or higher. If you’re working on a mission-critical server, one cool feature of Postfix is you can place a

warn_if_reject

in front of the restriction that you’re thinking to add, and sort of test it out. And it will show in the logs what would happen if you had fully enabled it and didn’t have warn_if_reject in front of the restriction. So, sort of a nice non-intrusive way to test out proposed restrictions.

If you have clients which will authenticate, be sure to put the

permit_sasl_authenticated

in your restriction list.

OK, last but not least be sure to remember that I’ve set these restrictions up for my particular scenario which is a non-mission-critical, non-busy server (at the moment) and so these are specific to my needs. Everybody will have to customize the restrictions to their own environment and their own needs, but you can think of it maybe as a good starting point or boilerplate restrictions.

First some related parameters that are not part of any of the restriction stages:

smtpd_helo_required = yes 
disable_vrfy_command = yes
unverified_sender_reject_reason = Address verification failed

smtpd_helo_required means the client should introduce their selves with the HELO or EHELO before sending the MAIL command or any other commands that require a HELO negotiation.  The default here is “no”, so as you don’t have any problems with troubled clients that can’t do that but if you don’t have that, I would recommend setting it to “yes.”   OK, next disable_vrfy_command basically, the VRFY command can be used to query if email addresses exist, and this command gets abused sometimes for email and login username harvesting.  So, I definitely recommend setting this to yes unless you have a particular need otherwise.  OK, by default, if you have an unverified sender and you’re rejecting, Postfix we’ll send the verification details for the reason why it failed. Instead of that, you can just have it send some straight text using the unverified_sender_reject_reason and that parameter is available as of Postfix 2.6.

One of the nice things about Postfix is it has this layered approach to restrictions and you can do some very complex restrictions.  Postfix has several restriction stages. These stages are processed in a certain order (this stage here being the relay stage). And within each stage the restrictions are evaluated in the order they’re listed. In order for processing to continue within that stage, each line must evaluate to an OK or DUNNO. Otherwise, that’s the end of the story for that particular stage.   So, stages are done in a pre-set order and the restriction order you set within each stages does matter.

To be clear, the restriction stages are evaluated in the order shown here:

  1. smtpd_client_restrictions
  2. smtpd_helo_restrictions
  3. smtpd_sender_restriction
  4. smtpd_relay_restriction
  5. smtpd_recipient_restriction
  6. smtpd_data_restriction
  7. smtpd_end_of_data_restriction
  8. smtpd_etrn_restrictions

However if you keep the smtpd_delay_reject parameter set to the default of “yes”, then most of the restrictions can be rolled up into the recipient restrictions. But, you still want to have a separate relay restrictions and data restrictions.  Placing the bulk of the restrictions into the smtpd_recipient_restrictions does have some advantages.  It makes the flow less confusing, white-lists are only needed once, you have more ordering control & you get more visibility into the spammers whole modus operandi.  So in practice what this means is that any restrictions that you have in the client, HELO, or sender stages can be moved into the recipient stage and that’s exactly what I’ve done here.

Here is how I set up my restrictions in my main.cf for this server:

smtpd_relay_restrictions = 
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unknown_sender_domain,
    reject_unauth_destination

smtpd_recipient_restrictions =
    permit_mynetworks,
    reject_non_fqdn_helo_hostname,
    reject_non_fqdn_sender,
    reject_non_fqdn_recipient,
    reject_unknown_reverse_client_hostname,
    reject_invalid_helo_hostname,
    reject_unknown_helo_hostname,
    reject_unknown_sender_domain,
    reject_unknown_recipient_domain,
    check_recipient_access hash:/etc/postfix/recipient_access,
    check_helo_access hash:/etc/postfix/helo_checks,
    check_sender_access hash:/etc/postfix/sender_access,
    #reject_rbl_client cbl.abuseat.org,
    #reject_rbl_client sbl.spamhaus.org,
    #reject_rbl_client pbl.spamhaus.org,
    permit

smtpd_data_restrictions =
    reject_unauth_pipelining,
    permit

Lets look at these in detail.

smtpd_relay_restrictions

As of Postfix 2.10, the relay restrictions were added which allow you to set up relay restrictions separate from your other restrictions. The first three are by default. I added:

reject_unauth_destination – which means if the destination domain is not in the relay domain list, it’ll be rejected. You can also do a defer_unauth_destination.   Postfix ships by default to disallow the server as an open relay, and adding reject_unauth_destination just furthers the cause.

smtpd_recipient_restrictions

permit_mynetworks  – a white-list to permit my network, my IP, and any other IPs I put in the mynetworks parameter.

reject_non_fqdn_helo_hostname

reject_non_fqdn_sender

reject_non_fqdn_recipient

For these 3, I’m telling Postfix I want to reject anything that’s not a fully qualified domain name for HELO, sender or recipient, even though in the HELO stage, it’s technically legal / compliant with the RFC to use an IP address enclosed in square brackets.

reject_unknown_reverse_client_hostname – this basically means that the reverse DNS has to map to something. A more strict version of this would be reject_unknown_client_hostname which means the forward and reverse have to match and it has to be the actual IP of the client.

reject_invalid_helo_hostname – That basically means if the client’s HELO host is malformed, it will be rejected. Of course you have to have the smtpd_helo_required field that I showed earlier set to “yes”, otherwise they could just go around this.

reject_unknown_helo_hostname – So if the client’s HELO hostname has no A or MX record, you guessed it, rejected.  Also requires smtpd_helo_required  = “yes”

reject_unknown_sender_domain – so in cases where this Postfix is not the final destination, if the mail FROM domain has no MX or A record or the MX record is malformed, rejected.

reject_unknown_recipient_domain – this works just exactly the same as reject_unknown_sender_domain except it’s for the RCPT TO domain.

reject_unauth_destination – now with 2.10 and higher

check_recipient_access – a hash of recipients that you want to specially address.  In this case, I had some email addresses that were attached to the domain from I guess years ago that we’re still getting email. So I want to make sure that I reject those.  This is the hash file:

holly@brackin.net       550     Mailbox doesn't exist.
anthony@brackin.net     550     Mailbox doesn't exist.
peggy@brackin.net       550     Mailbox doesn't exist.
gute@brackin.net        550     Mailbox doesn't exist.

check_helo_access – a hash here for the HELO checks; basically because HELO can be spoofed. We want to make sure they’re not trying to impersonate our domains, IPs or local hosts.  Here is the hash file I am using:

# HELO'ing as being in our own domain(s)?
grokshop.tv             REJECT You are not in grokshop.tv
brackin.net             REJECT You are not in brackin.net

# HELO'ing with our IP address?
198.58.109.26           REJECT You are not 198.58.109.26

# HELO'ing as "localhost?"
localhost               REJECT You are not me

check_sender_access – I chose to add this hash because Draftkings was sending email to a defunct account. I did try to contact them and do it the nice way, but they wouldn’t listen so I went ahead and set up a special reject just for them.  Here is that hash file:

draftkings.com REJECT
draftkings.zendesk.com REJECT

If you’re still having problems after you’ve tuned all your Postfix restrictions you can filter with a blacklist server using reject_rbl_client. For me, I haven’t needed that so I’ve got them commented out right now.

permit – optional. I put it there just to make it clear that if you get to this point you’re permitted you’re getting through.

smtpd_data_restrictions

reject_unauth_pipelining – What this does is slow down or stop mail from bulk mail senders who use illegal pipelining or basically, speaking out of turn.

 

Use the default error / status codes where possible

Quick note about the status or error codes you’ll notice that except for the check_access hash files, I didn’t specify any codes other than the default and that’s generally considered best practice. Unless you really know what you’re doing, let Postfix use the default codes.

 

Summary & Links

As you can see, Postfix is extremely powerful and flexible in terms of stopping spam in it’s own right; in fact so much so, you may not even need to use a third-party spam software tool like Spam Assassin or some of the other point-based systems. There’s definitely a lot more capability than just what I showed here, so if you’re admin-ing Postfix, I totally recommend spending the time to get to know all of the things that Postfix can do.

If your Postfix email server is getting hit with huge amounts of spam, you may want to look into Postscreen.

If you haven’t already, be sure to check out these other Postfix blog posts:

Postfix Mail Server Install, Configure & Forward to Gmail

Debugging and Tracing In Postfix

SMTP “Bible” (RFC 5321)
Postfix Site Links:
SMTP Reply / Status / Error Codes:

Jim Seymour’s suggestions/examples for Postfix anti-UCE configuration.

Akadia docs:

Dr Wietse Zweitze Venema (original creator of Postfix)

Sharing is caring!

2 thoughts on “Stop Spam With Postfix Email Server”

  1. Great blog , top video,

    really really useful, you not only have a knowledge but also a good style to teach , many thanks

    Konstantin

Leave a Comment

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


Notice: Undefined index: total_count_position in /var/www/wordpress/wp-content/plugins/social-pug/inc/functions-frontend.php on line 46
shares