Let’s do Postfix slowly and properly – Part 10: Restricting access

When I see what people are trying to do to my poor server, I want to reach for some kind of cyberweapon. That guy, zap. That IP, zip. That is the kind of thinking that leads to blacklists and fail2ban which notes bad behaviour and adds IPs to firewall lists.

There is another line of thinking more accepting of the sea of bad behaviour. There are too may dark clouds to zap them all. And it’s not really about them or “getting” them. Let them rain and rage and eventually move on. I cannot calm the storm so instead I will build a boat that will weather it. I think this is a more viable route to go down (sail? I think this metaphor has run/sailed its course, that’s for sure).

In earlier posts I have introduced various smtpd_relay_restrictions. Some are simple necessities to avoid being an open relay, and in fact can be requirements for Postfix to run at all. Others offer more advanced ways to combat spammers and evildoers, using things like PTR and SPF records. This post will look at further Postfix-native options for protecting myself against attacks and maybe even obviating the need for a spam filter altogether. Wikipedia has a more complete overview of the various spam blocking techniques on offer.

Simple rules for simple spammers

I think this type of solutions is better than the ban hammer/blacklist approach. Why? Well, take this guy:

postfix    | Aug 19 08:47:07 brokkr postfix/smtpd[886]: initializing the server-side TLS engine
postfix    | Aug 19 08:47:07 brokkr postfix/smtpd[886]: connect from unknown[212.70.149.4]
postfix    | Aug 19 08:47:10 brokkr postfix/smtpd[886]: disconnect from unknown[212.70.149.4] ehlo=1 auth=0/1 rset=1 quit=1 commands=3/4
postfix    | Aug 19 08:50:29 brokkr postfix/smtpd[889]: initializing the server-side TLS engine
postfix    | Aug 19 08:50:29 brokkr postfix/smtpd[889]: connect from unknown[212.70.149.4]
postfix    | Aug 19 08:50:33 brokkr postfix/smtpd[889]: disconnect from unknown[212.70.149.4] ehlo=1 auth=0/1 rset=1 quit=1 commands=3/4
postfix    | Aug 19 08:53:53 brokkr postfix/smtpd[892]: initializing the server-side TLS engine
postfix    | Aug 19 08:53:53 brokkr postfix/smtpd[892]: connect from unknown[212.70.149.4]
postfix    | Aug 19 08:53:56 brokkr postfix/smtpd[892]: disconnect from unknown[212.70.149.4] ehlo=1 auth=0/1 rset=1 quit=1 commands=3/4

As can be seen the client connects once every 3 minutes 21 seconds, presumably to fly under the radar of common connection rate limits. It EHLOs and then immediately tries to authenticate, presumably to relay spam. This is the error message it gets again and again, over and over:

> unknown[212.70.149.4]: 503 5.5.1 Error: authentication not enabled

“>” means that the log message is part of the SMTP dialogue and going in the direction from server to client. “Authentication [is] not enabled” on port 25. You want to send out, you use port 587 where authentication is enabled. The SMTP daemon on port 25 is configured to only accept mail for my own domain. That means there is no reason to leave authentication on as an option for that port. So I disabled it. This script, however, is clearly not programmed for that, so it just keeps trying regardless.

(I did wonder for a while, why the client in question was not being rejected outright because of missing PTR records but the smtpd_relay_restrictions that require PTR records only kick in on the “RCPT TO” step. This client never gets to that stage. In fact, even if I were to use the same requirement in smtpd_client_restrictions that apply to the initial contact stage, not the later “RCPT TO” stage, Postfix would still go through the motions of the initial SMTP dialogue. The reason for this is that some clients misbehave if they are kicked too early in the SMTP session, so the client restriction is delayed.)

The main takeaway from this is that what I’m doing right now… works. The client isn’t allowed to do anything, neither a recipient nor a sender be, to misquote Polonius. It’s simple but it works because this attacker is not particularly sophisticated. Probably because there’s more economy in targeting the easy targets, the low-hanging fruit, rather than try to deal with those who take precautions.

Keep It Simple + Less is More

Basically, my advice boils down to, starting simple with rules and only resort to blocklists and adaptive banning if you need to, i.e. spam is still coming through or your server is struggling under the load.

The other pillar of my approach will be “less is more”. I see a lot of guides suggest using everything but the kitchen sink, piling restriction on restriction. That makes for difficult debugging or identifying false positives – or just reading logs and understanding what actually causes the correct positives. But it follows logically from the blacklist approach where more is not just more, it’s a necessity, as the last example showed.

One luxury problem with the approach described above is that with the small amount of spammers that find my server, it is difficult to justify all that many restrictions. Separate submission from incoming mail, require checks on PTR and SPF and refuse unatuhorized destinations. This will pretty much exclude botnets and 99% of the basic scripted hacks that I see.

Fortunately I can see other tricks and lies in the logs, that are worth adressing in and for themselves. This article is not going to try to cover everything that can be done with Postfix and the selection is arbitrary: Essentially, it is techniques that I understand and can see the application of, based on experience and examples from my logs. It should be more than sufficient, combined with the basic methods listed above and especially when combined with a dedicated spam filtering service.

HELO restrictions

Let’s start with the BS that spammers do in the HELO/EHLO phase. Here’s what RFC 5321 (ESMTP) says is permitted here:

The argument field contains the fully-qualified domain name of the SMTP client if one is available. In situations in which the SMTP client system does not have a meaningful domain name (e.g., when its address is dynamically allocated and no reverse mapping record is available), the client SHOULD send an address literal (see section 4.1.3), optionally followed by information that will help to identify the client system.

https://tools.ietf.org/html/rfc5321

So basically either a FQDN or an IP address. You would think then that most illegitimate senders without domains would just present an IP address, right? Well, no:

Aug 20 16:57:11 brokkr postfix/smtpd[214]: > unknown[212.70.149.4]: 220 brokkr.net ESMTP Postfix (Ubuntu)               
Aug 20 16:57:12 brokkr postfix/smtpd[214]: < unknown[212.70.149.4]: EHLO User    

“User”? What is that? The next step up is this one that actually passes PTR and DNS checks but doesn’t use it’s FQDN for some reason. Maybe just bad configuration?

Aug 20 21:18:48 brokkr postfix/smtpd[378]: > hwsrv-764926.hostwindsdns.com[142.11.245.48]: 220 brokkr.net ESMTP Postfix (Ubuntu
Aug 20 21:18:48 brokkr postfix/smtpd[378]: < hwsrv-764926.hostwindsdns.com[142.11.245.48]: EHLO hwc-hwp-5956300

Somebody has to have read the RFC, right? Yes. This guy:

Aug 21 05:06:07 brokkr postfix/smtpd[692]: > unknown[113.100.226.13]: 220 brokkr.net ESMTP Postfix (Ubuntu)                    
Aug 21 05:06:08 brokkr postfix/smtpd[692]: < unknown[113.100.226.13]: EHLO mx.yandex.net

mx.yandex.net even sounds about right for an MTA. Sadly, it has no MX or DNS records, so maybe not.

I also found examples of clients sending an empty HELO hostname which just causes an error message. The final example is… clever-ish? Maybe?

Aug 21 11:50:03 brokkr postfix/smtpd[959]: > unknown[185.234.219.11]: 220 brokkr.net ESMTP Postfix (Ubuntu)
Aug 21 11:50:03 brokkr postfix/smtpd[959]: < unknown[185.234.219.11]: EHLO games.brokkr.net

games.brokkr.net is legitimate subdomain with a DNS record. Of course, I don’t use it for email, just a blog, so there are no MX records. And there is no special treatment for subdomains, but maybe that is a thing that some people do and others then take advantage of? Beats me.

I don’t really understand why these sender opt not to do HELO properly but the fact is that they don’t and (most) legitimate senders do. So I can tell Postfix to enforce the RFC:

smtpd_helo_required = yes
smtpd_helo_restrictions =
    reject_non_fqdn_helo_hostname

The required line is obviously there so as not to reward those who simply skip saying HELO (and with that, the restrictions as well). The restrictions themselves only list one at the moment: rejecting not fully qualified domain names as HELO hostnames. Despite the name the restriction does allow for IP addresses same as the RFC.

This would actually turn down all but the last two of my examples (all of whom were sent packing at a later stage for other reasons but we’ll ignore that for now). There are two kinds of restrictions for HELO: Those that require lists (of domains, addresses, etc.) and those that are just general rules. I am lazy and lists need maintenance so let’s look at the first ones first.

Despite some recommendations, I will skip reject_invalid_helo_hostname because I have been unable to find any detailed information about what constitutes an invalid (or “malformed”) hostname. Also I suspect that most of the invalid hostnames would also be caught by the restriction I just implemented. reject_unknown_helo_hostname is tricky because of the wording “the HELO or EHLO hostname has no DNS A or MX record” Are both or only one required? It is unclear. Linuxbabe rephrases this as the unequivocal “neither DNS A nor MX records” which would make sense but I don’t know where they get that from. A sensible solution comes from ServerFault user dsmk80: Use it but change it to a warning in the logs instead of a reject code:

warn_if_reject reject_unknown_helo_hostname

And eventually the meaning should become clearer. You can obviously have domain names with DNS A records but not MX but I should think you could technically have things the other way around with just a CNAME alias, or an IPv6 AAAA record, right?

If “unknown” is a requirement for a DNS A and an MX record, both the “yandex” and the impersonator from examples above fail. If it’s only one or the othe other, the impersonator is still allowed.

This leaves the lists. Grokshop.tv has an excellent demonstration of how to use these that also takes specific aim at impersonators. My only issue is that in all of the cases listed, the pretender would be caught by other rules. “EHLO localhost”? Not a FQDN. “EHLO brokkr.net” Sure, you can pass, but try using my domain in your From field and my SPF records will call you a liar. “EHLO anothermailserver.thatisnot.mine”? The more lies you tell and the more inconsistencies in your story – e.g. you’re calling from one IP and HELO’ing from another – the greater the risk that my spam filter will penalise you.

All that is true enough but I will admit that the impersonation stuff makes me uncomfortable. So the first exception to the general-rules-over-lists is this: Don’t pretend you’re me, cos you’re not, I’m telling on you. This is implemented by adding the restriction check_helo_access type:table. As with things like virtual_mailbox_maps, I’m going to use a simple hash or LHS/RHS table, i.e. a text file that lists the variable on the left hand side and the response/value on the right hand side. As always with these text file tables, remember to point postmap at them to create the .db version, otherwise they won’t work.

# Adapted from https://grokshop.tv/stop-spam-with-postfix-email-server/

# My domains
brokkr.net             REJECT You are not brokkr.net
games.brokkr.net       REJECT You are not games.brokkr.net
projects.brokkr.net    REJECT You are not projects.brokkr.net

# My IP address
167.86.120.123         REJECT You are not 167.86.120.123

It is worth pointing out again, that in all the cases I found all the spammers I would catch with this were already being caught by more basic rules. However, my data are only 3 days of logs and logically, sombody could have everything in order but slip up on HELO. The three lines I started with seem like a reasonable and cheap inclusion.

Spammers rarely cooperate so right now, all spam connections are disconnected not before HELO but before Postfix evaluates HELO so I can see whether it works or not. Fortunately, I can force Postfix to evalutate the HELO restrictions immediately to see what happens by setting smtpd_delay_reject to no. This is only for testing and should not be used in production. Here’s the “User” host:

Aug 23 11:28:00 brokkr postfix/smtpd[210]: > unknown[212.70.149.4]: 220 brokkr.net ESMTP Postfix (Ubuntu)
Aug 23 11:28:01 brokkr postfix/smtpd[210]: < unknown[212.70.149.4]: EHLO User
Aug 23 11:28:01 brokkr postfix/smtpd[210]: >>> START Helo command RESTRICTIONS <<<
Aug 23 11:28:01 brokkr postfix/smtpd[210]: generic_checks: name=reject_non_fqdn_helo_hostname
Aug 23 11:28:01 brokkr postfix/smtpd[210]: reject_non_fqdn_hostname: User
Aug 23 11:28:01 brokkr postfix/smtpd[210]: NOQUEUE: reject: EHLO from unknown[212.70.149.4]: 504 5.5.2 <User>: Helo command rejected: need fully-qualified hostname; proto=SMTP helo=<User>
Aug 23 11:28:01 brokkr postfix/smtpd[210]: generic_checks: name=reject_non_fqdn_helo_hostname status=2
Aug 23 11:28:01 brokkr postfix/smtpd[210]: >>> END Helo command RESTRICTIONS <<<
Aug 23 11:28:01 brokkr postfix/smtpd[210]: > unknown[212.70.149.4]: 504 5.5.2 <User>: Helo command rejected: need fully-qualified hostname
Aug 23 11:28:01 brokkr postfix/smtpd[210]: < unknown[212.70.149.4]: QUIT
Aug 23 11:28:01 brokkr postfix/smtpd[210]: > unknown[212.70.149.4]: 221 2.0.0 Bye

The FQDN check works, that’s nice. What about my tables of banned names?

Aug 23 11:31:16 brokkr postfix/smtpd[210]: > unknown[185.234.218.84]: 220 brokkr.net ESMTP Postfix (Ubuntu)
Aug 23 11:31:16 brokkr postfix/smtpd[210]: < unknown[185.234.218.84]: EHLO games.brokkr.net
Aug 23 11:31:16 brokkr postfix/smtpd[210]: >>> START Helo command RESTRICTIONS <<<
Aug 23 11:31:16 brokkr postfix/smtpd[210]: generic_checks: name=reject_non_fqdn_helo_hostname
Aug 23 11:31:16 brokkr postfix/smtpd[210]: reject_non_fqdn_hostname: games.brokkr.net
Aug 23 11:31:16 brokkr postfix/smtpd[210]: generic_checks: name=reject_non_fqdn_helo_hostname status=0
Aug 23 11:31:16 brokkr postfix/smtpd[210]: generic_checks: name=check_helo_access
Aug 23 11:31:16 brokkr postfix/smtpd[210]: check_domain_access: games.brokkr.net
Aug 23 11:31:16 brokkr postfix/smtpd[210]: maps_find: hash:/volumes/mail/postfix/helo_access_maps: hash:/volumes/mail/postfix/helo_access_maps(0,lock|fold_fix|utf8_request): games.brokkr.net = REJECT You are not games.brokkr.net
Aug 23 11:31:16 brokkr postfix/smtpd[210]: check_table_result: hash:/volumes/mail/postfix/helo_access_maps REJECT You are not games.brokkr.net games.brokkr.net
Aug 23 11:31:16 brokkr postfix/smtpd[210]: NOQUEUE: reject: EHLO from unknown[185.234.218.84]: 554 5.7.1 <games.brokkr.net>: Helo command rejected: You are not games.brokkr.net; proto=SMTP helo=<games.brokkr.net>
Aug 23 11:31:16 brokkr postfix/smtpd[210]: generic_checks: name=check_helo_access status=2
Aug 23 11:31:16 brokkr postfix/smtpd[210]: >>> END Helo command RESTRICTIONS <<<
Aug 23 11:31:16 brokkr postfix/smtpd[210]: > unknown[185.234.218.84]: 554 5.7.1 <games.brokkr.net>: Helo command rejected: You are not games.brokkr.net

I cannot find a reply to that 5.7.1 (“Delivery not authorized, message refused”) which seems to prove the Postfix documentation’s point that early rejection can be problematic as presumably the connection isn’t getting neatly closed. But hey, the table clearly works. Impostor exposed.

Client and sender restrictions

As I wrote in my first post about relay restrictions, there are a number of other rules that act on various stages of the SMTP protocol and on the information presented at those stages. I have played around with client restrictions for instance, however, the main draw was the idea of an earlier rejection. Similar to slamming down the phone on robocalls and sales people. As mentioned above, however, Postfix has good reasons for not actually doing that, so simply put we have to hear them out even when we know they’re fake. To get to the point: smtpd_client_restrictions isn’t any faster than smtpd_relay_restrictions and it I cannot see many more options relating to the client/connection that would allow me to distinguish friend from foe.

smtpd_sender_restrictions act on the information in the MAIL FROM step, i.e. the sender. This introduces the idea of asking the server of the MAIL FROM domain is there really is such an address, i.e. address verification. Seeing as address verification in itself can look like the work of spammers, probing and checking, I don’t think it’s worth it. An increased risk of being seen as a spammer is not worth a tiny increase in spam identification.

Another, less problematic, option is to check if the MAIL FROM domain even exists and has basic records, i.e. reject_unknown_sender_domain. This is very similar to the reject_unknown_helo_hostname option above, only for the MAIL FROM domain, not the HELO hostname. My initial thought was that this check was already being done by my SPF policy service but on second thought, the SPF service seems to come to the same result for non-existant domains as for domains without an SPF policy, which is to say that they aren’t rejected. Unlike the helo option, here there is no uncertainty about the requirement:

Reject the request when Postfix is not final destination for the sender address, and the MAIL FROM domain has 1) no DNS MX and no DNS A record, or 2) a malformed MX record such as a record with a zero-length MX hostname

http://www.postfix.org/postconf.5.html#reject_sender_login_mismatch

I think it goes without saying what it would make of this example:

Aug 21 10:36:28 brokkr postfix/smtpd[904]: < unknown[114.237.188.194]: MAIL FROM: <qchxzdmowup@bdmpaaacgwuq.com>

This is a genuine attempt from my logs which stranded on some other rule later down the line. When I “reenact” it using telnet, with the sender restrictions in place, here’s what happens:

Aug 23 11:50:29 brokkr postfix/smtpd[217]: >>> START Sender address RESTRICTIONS <<<
Aug 23 11:50:29 brokkr postfix/smtpd[217]: generic_checks: name=reject_unknown_sender_domain
Aug 23 11:50:29 brokkr postfix/smtpd[217]: reject_unknown_address: qchxzdmowup@bdmpaaacgwuq.com
Aug 23 11:50:29 brokkr postfix/smtpd[217]: ctable_locate: leave existing entry key ?qchxzdmowup@bdmpaaacgwuq.com
Aug 23 11:50:29 brokkr postfix/smtpd[217]: reject_unknown_mailhost: bdmpaaacgwuq.com
Aug 23 11:50:29 brokkr postfix/smtpd[217]: lookup bdmpaaacgwuq.com type MX flags 
Aug 23 11:50:29 brokkr postfix/smtpd[217]: dns_query: bdmpaaacgwuq.com (MX): Host not found
Aug 23 11:50:29 brokkr postfix/smtpd[217]: lookup bdmpaaacgwuq.com type A flags 
Aug 23 11:50:29 brokkr postfix/smtpd[217]: dns_query: bdmpaaacgwuq.com (A): Host not found
Aug 23 11:50:29 brokkr postfix/smtpd[217]: lookup bdmpaaacgwuq.com type AAAA flags 
Aug 23 11:50:29 brokkr postfix/smtpd[217]: dns_query: bdmpaaacgwuq.com (AAAA): Host not found
Aug 23 11:50:29 brokkr postfix/smtpd[217]: NOQUEUE: reject: MAIL from unknown[94.147.81.233]: 450 4.1.8 <qchxzdmowup@bdmpaaacgwuq.com>: Sender address rejected: Domain not found; from=<qchxzdmowup@bdmpaaacgwuq.com> proto=ESMTP helo=<bdmpaaacgwuq.com>
Aug 23 11:50:29 brokkr postfix/smtpd[217]: generic_checks: name=reject_unknown_sender_domain status=2
Aug 23 11:50:29 brokkr postfix/smtpd[217]: >>> END Sender address RESTRICTIONS <<<
Aug 23 11:50:29 brokkr postfix/smtpd[217]: > unknown[94.147.81.233]: 450 4.1.8 <qchxzdmowup@bdmpaaacgwuq.com>: Sender address rejected: Domain not found

As we can see it checks for both IPv4 and IPv6 addresses, as well as MX records. Unsurprisingly there is no such thing as bdmpaaacgwuq.com and the client is told as much with a code 4.1.8: “Sender address rejected: Domain not found”.

I do also see a lot of MAIL FROM fictitious accounts on my own server and some that are obviously scraped from blog posts (Hi, Bob and Alice!) Similar to helo restrictions I could make a check_sender_access table and try banning them. However, for incoming mail the attempts get rejected by SPF policy (you’re not allowed to send mail for my domain) and for outgoing (the majority of the attempts) there is defer_unauth_destination. So it’s an open question if it’s worth it. Here’s an example:

Aug 20 21:20:08 brokkr postfix/smtpd[381]: < hwsrv-764926.hostwindsdns.com[142.11.245.48]: MAIL FROM:<user@brokkr.net>
...
Aug 20 21:20:09 brokkr postfix/smtpd[381]: < hwsrv-764926.hostwindsdns.com[142.11.245.48]: RCPT TO:<adalram1029@gmail.com>
...
Aug 20 21:20:09 brokkr postfix/smtpd[381]: NOQUEUE: reject: RCPT from hwsrv-764926.hostwindsdns.com[142.11.245.48]: 454 4.7.1 <adalram1029@gmail.com>: Relay access denied; ...

Anvil

In my far from complete survey, I have identified two Postfix services that attempt to deal with server load caused by spammers and evildoers, postscreen and anvil. “Anvil” conjures up smithing images, presumably to suggest a server being “hammered” and “postscreen”suggest screening SMTP clients, although the screening happens before and not after/post the client get’s to deliver mail. Sorry, it’s getting late.

Of the two, Anvil seems of more interest to small scale mailers. Unless your SMTP server is crumbling under the load, I don’t think the added complexity of screening is worth it. Anvil on the other hand just sits on the sideline, collecting statistics about connections that are easy to use in a few handy rules. Yes, in principle this is not that different from the fail2ban approach, except not operating on actual firewall rules, more on internal Postfix lines.

For me an important distinction is that this is neither my first nor last line of defense. The defense is the rules already in place. Anvil is more of a gentle roadblock. It’s telling an attacker: I know what you’re doing and I could allow you to continue and still feel perfectly safe. I’m just going to slow you down to make sure you’re not being too much of a nuissance. As the documentation says “The purpose of this feature is to limit abuse.” Limit, not abolish.

One big advantage of Anvil is that it is on by default, i.e. there is already a service set to run by most default master.cf setups. The options broadly consist of “How many X is one client allowed to do in Y amount of time before we say no more?” and covers connections, TLS sessions started, AUTH attempts, messages and message recipients. Y is set by the variable anvil_rate_time_unit and is 60 seconds by default which. I think that means that the count is reset to zero every 60 seconds and you get a new X attempts within the timeline before you retrigger the limit. Also by default there are no limitations on any of the activities listed, so just changing anvil_rate_time_unit by itself will do nothing.

Remember 212.70.149.4, the one who asks for AUTH every 3 minutes 21 seconds? Say I wanted to target him I could add the following to my main.cf:

anvil_rate_time_unit = 60m
smtpd_client_auth_rate_limit = 3

Then the exchange changes from the 503 error (“authentication not enabled”) to this:

postfix    | Aug 19 15:32:00 brokkr postfix/smtpd[365]: warning: AUTH command rate limit exceeded: 14 from unknown[212.70.149.4] for service smtp
postfix    | Aug 19 15:32:00 brokkr postfix/smtpd[365]: > unknown[212.70.149.4]: 450 4.7.1 Error: too many AUTH commands from 212.70.149.4
postfix    | Aug 19 15:32:01 brokkr postfix/smtpd[365]: < unknown[212.70.149.4]: QUIT
postfix    | Aug 19 15:32:01 brokkr postfix/smtpd[365]: > unknown[212.70.149.4]: 221 2.0.0 Bye

While it is gratifying to call the client out on it’s wrongdoing I haven’t really achieved anything much. Instead of telling the client that what it’s doing is impossible, I am telling it that it’s doing it too frequently. However, this will also limit the number of permissible AUTH errors where authentication is allowed, i.e. on port 587.

There might also be other cases where the unwanted behaviour doesn’t result in an outright error, e.g. attempted delivery to a non-existant email address, DOS like behavior etc., and you need it to. Why might producing an outright error be desirable? Because Postfix’s slowdown mechanisms trigger on error count.

“Tuning”

It looks like someone is hoping for an open relay:

Aug 13 07:46:55 brokkr postfix/smtpd[315]: lost connection after RCPT from hwsrv-752331.hostwindsdns.com[142.11.205.237]
Aug 13 07:46:55 brokkr postfix/smtpd[315]: disconnect from hwsrv-752331.hostwindsdns.com[142.11.205.237] ehlo=1 mail=1 rcpt=0/1 commands=2/3
Aug 13 07:46:55 brokkr postfix/smtpd[315]: connect from hwsrv-752331.hostwindsdns.com[142.11.205.237]
Aug 13 07:46:56 brokkr postfix/smtpd[315]: NOQUEUE: reject: RCPT from hwsrv-752331.hostwindsdns.com[142.11.205.237]: 454 4.7.1 <adalram1029@gmail.com>: Relay access denied; from=<evengage@brokkr.net> to=<adalram10
29@gmail.com> proto=ESMTP helo=<hwc-hwp-5880780>
Aug 13 07:46:56 brokkr postfix/smtpd[315]: lost connection after RCPT from hwsrv-752331.hostwindsdns.com[142.11.205.237]
Aug 13 07:46:56 brokkr postfix/smtpd[315]: disconnect from hwsrv-752331.hostwindsdns.com[142.11.205.237] ehlo=1 mail=1 rcpt=0/1 commands=2/3
Aug 13 07:46:56 brokkr postfix/smtpd[315]: connect from hwsrv-752331.hostwindsdns.com[142.11.205.237]
Aug 13 07:46:56 brokkr postfix/smtpd[315]: NOQUEUE: reject: RCPT from hwsrv-752331.hostwindsdns.com[142.11.205.237]: 454 4.7.1 <adalram1029@gmail.com>: Relay access denied; from=<user@brokkr.net> to=<adalram1029@g
mail.com> proto=ESMTP helo=<hwc-hwp-5880780>
Aug 13 07:46:57 brokkr postfix/smtpd[315]: lost connection after RCPT from hwsrv-752331.hostwindsdns.com[142.11.205.237]
Aug 13 07:46:57 brokkr postfix/smtpd[315]: disconnect from hwsrv-752331.hostwindsdns.com[142.11.205.237] ehlo=1 mail=1 rcpt=0/1 commands=2/3
Aug 13 07:46:57 brokkr postfix/smtpd[315]: connect from hwsrv-752331.hostwindsdns.com[142.11.205.237]
Aug 13 07:46:57 brokkr postfix/smtpd[315]: NOQUEUE: reject: RCPT from hwsrv-752331.hostwindsdns.com[142.11.205.237]: 454 4.7.1 <adalram1029@gmail.com>: Relay access denied; from=<info@brokkr.net> to=<adalram1029@g
mail.com> proto=ESMTP helo=<hwc-hwp-5880780>

The message is not for me but purporting to come from my domain. This is easily defeated by defer_unauth_destination, i.e. if it’s not for me, I won’t relay it. But there’s nothing saying you cannot boneheadedly make repeated, futile attempts without listening to the response.

Unlike 3m21s guy these attempts come fast one after the other so I want to slow them down. As alluded to above Postfix’s smtpd service maintains a per client error count that is kept for the entirety of the session, i.e. until I restart Postfix. Anvil can add to this error count but once every anvil_rate_time_unit it stops counting client behaviour as errors until x_rate_limit is reached again. The smtpd service, however, never forgets, at least until restart.

Once the count reaches smtpd_soft_error_limit, a delay is introduced in the response. The documentation and various blog posts make it unclear if the delay is static at smtpd_error_sleep_time for each error or whether it get’s incremented by smtpd_error_sleep_time with each error above smtpd_soft_error_limit. In either case, once the error count reaches smtpd_hard_error_limit no more connections to the client will be accepted for the duration of the session.

# Anti-nuissance measures
# http://www.postfix.org/TUNING_README.html#slowdown
smtpd_soft_error_limit = 5
smtpd_error_sleep_time = 10s
smtpd_hard_error_limit = 15

Final notes

This was written over the course of a day in order to finish the series, so apologies if it’s a bit incoherent in places. There is also a fair amount of uncertainty in this post that I hope to clarify over time. For now, hopefully, it can serve as a call to pick restrictions carefully, rather than willy-nilly, to test the effects and to peruse the logs for unintended consequences. Comment if you need clarification and I’ll do the best I can.

This will end my Postfix series for now with what should be a respectable selfhosted email setup. Given that I can email the big boys without being getting blocked and that I rarely see spam anymore, I feel like I can rest on these laurels for a little while. I am sure that the Googles and the Microsofts of this world will only keep tightening up and make the life of the selfhoster harder. So at some point I will reopen it to take on DKIM, DMARC and whatever other measures will be in place by then. However, if anybody has made it this far, please feel free to comment or email me if you need help.

Finally, I want to say that it’s easy to get arrogant and complacent – as I’m sure I have demonstrated plentifully in this post – when you see the many low effort attempts at taking advantage of your server. However, it seems logical that the many bad bad actors out there (as in “bad at what they do and wih bad intentions”) just make it easier for the good bad ones to go unnoticed until it’s too late. Keep watching the logs!

Leave a Reply