See the video:
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:
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:
- smtpd_client_restrictions
- smtpd_helo_restrictions
- smtpd_sender_restriction
- smtpd_relay_restriction
- smtpd_recipient_restriction
- smtpd_data_restriction
- smtpd_end_of_data_restriction
- 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:
SMTP “Bible” (RFC 5321)
Postfix Site Links:
- Postfix – Home
- Postfix – SMTP relay and access control
- Postfix – Configuration Parameters
- Postfix – Debugging
SMTP Reply / Status / Error Codes:
Jim Seymour’s suggestions/examples for Postfix anti-UCE configuration.
Akadia docs:
Great blog , top video,
really really useful, you not only have a knowledge but also a good style to teach , many thanks
Konstantin
Thank you so much for sharing a powerful and effective method.
It really helped me a lot.
– OPENLitePanel Team
Thank you for this article. It has helped my with my postfix configuration immensely. I think the “warn_if_reject” directive caught my attention along with the order of processing. I did a lot of research almost line by line in your tutorial and was/am very impressed. Your video and transcript are invaluable.
Rip
Awesome!