Notice: This documentation was written for CentOS 5. It may not be accurate for CentOS 6 or subsequent releases.
If you have followed the basic postfix guide you will have a simple postfix email server up and running that is capable of sending and receiving email. However, you will probably have noticed that your postfix email server is receiving all email, unrestricted, including spam. Spam, otherwise known as Unsolicited Bulk Email (UBE) or Unsolicited Commercial Email (UCE) is estimated to constitute in excess of 80% of all email sent over the Internet and as such represents a major challenge to any email postmaster. This guide will examine some of the restrictions available in postfix for combating spam and is designed to be modular in nature such that it may be implemented in addition to the basic postfix guide should the reader so desire.
2. Postfix and Spam
The Internet is largely built on a policy of trust and UBE abuses that trust to allow the delivery of large amounts of spam. When placing any form of restrictions on email we are deviating from the policy of trust and as such also risk restricting or blocking legitimate email. Hence we should be conservative in the restrictions we apply and should test and monitor them thoroughly. Your users will thank you for limiting the amount of spam they see but they will not thank you for blocking legitimate email.
We are going to use 3 different postfix restriction classes to examine the information provided to our email server by any connecting client:
Luckily for us, many spammers don't bother to closely follow RFC guidelines and we can use these restriction classes to identify obvious spam on that basis and reject it before it enters our email server. Rejecting obvious spam before it enters our email server has the benefits of saving on bandwidth as well as reducing further processing overheads for our email server.
Restrictions work by processing a number of rules that determines what action postfix will take for that message. Possible outcomes are OK, REJECT or DUNNO. Restrictions are processed or evaluated in the order in which they are listed. If a rule returns a value of REJECT then the message is immediately rejected without further processing. If a rule returns a value of OK, then further processing of that class of restrictions is stopped and the next class of restrictions are evaluated until the message has either successfully passed all restriction classes or is subsequently rejected by a later rule. So, for example, a message may receive a value of OK from an smtpd_sender_restriction but may still be rejected by a subsequent smtpd_recipient_restriction rule. Therefore, we need to give careful consideration to the order of our restriction rules. If the message is not explicitly rejected by any rule then the default policy is to accept the message.
To better understand the different classes of restrictions and how they apply, lets take a look at the smtp process in more detail as we telnet into our email server and send a simple test message to ourselves:
telnet 192.168.0.2 25 # Comments Trying 192.168.0.2... Connected to 192.168.0.2 (192.168.0.2). Escape character is '^]'. 220 mail.example.com ESMTP Postfix # <-smtp_client_restrictions HELO mail.example.com # <-smtp_helo_restrictions 250 mail.example.com # MAIL FROM:<email@example.com> # <-smtp_sender_restrictions 250 2.1.0 Ok # RCPT TO:<firstname.lastname@example.org> # <-smtp_recipient_restrictions 250 2.1.5 Ok # DATA # <-smtp_data_restrictions 354 End data with <CR><LF>.<CR><LF> # To:<email@example.com> # <-header_checks From:<firstname.lastname@example.org> # Subject:SMTP Test # This is a test message # <-body_checks . # 250 2.0.0 Ok: queued as 301AE20034 QUIT 221 2.0.0 Bye Connection closed by foreign host.
One of the main advantages of smtpd helo, sender and recipient restrictions are that they are evaluated and acted upon before the body of the email is ever sent/received so potential spam may be rejected early in the smtp process without consuming additional bandwidth and/or processing resources. It's probably best that we see some restrictions in action to better understand what each does. All restrictions are added to the postfix configuration file located at /etc/postfix/main.cf
3. Helo Restrictions
When a client system first connects to our email server, it is required to identity itself using the smtp HELO command. Many spammers either try to either skip this step altogether, will send impossibly invalid information, or will deliberately try to obfuscate their identity to gain access. Either way, we can safely reject connections from clients that are severely mis-configured or that are deliberately obfuscating their identity.
# /etc/postfix/main.cf # HELO restrictions: smtpd_delay_reject = yes smtpd_helo_required = yes smtpd_helo_restrictions = permit_mynetworks, reject_non_fqdn_helo_hostname, reject_invalid_helo_hostname, permit
Note: Postfix 2.2 (CentOS 4) uses reject_non_fqdn_hostname and reject_invalid_hostname, respectively.
The first line (smtpd_delay_reject) allows the smtp conversation to continue until the point of actually receiving the message before it is rejected, and is useful because it allows full sender and recipient information to be logged. It is also a requirement for helo_restrictions. The second line (smtpd_helo_required) rejects email from any system that fails to identify itself.
Our smtpd_helo_restrictions begin at line 3. permit_mynetworks tells postfix to accept connections from trusted machines defined in mynetworks so email from our own trusted clients won't undergo further helo restriction checks. reject_non_fqdn_helo_hostname will reject connections if the hostname supplied with the HELO command is not a fully qualified domain name as required by the RFC guidelines. It should be perfectly safe to reject connection attempts from clients that make no effort to correctly identify themselves. Likewise, reject_invalid_helo_hostname will reject connection attempts when the HELO hostname syntax is invalid. Finally we permit messages to proceed to the next stage of filtering.
One further option that is occasionally specified is reject_unknown_helo_hostname which will reject messages if the hostname supplied with the helo command doesn't have either a valid DNS A or MX record. However, this setting should be used with caution as it will reject email from legitimate systems with temporary DNS problems and can lead to false positives.
4. Sender Restrictions
The next step is to filter out invalid senders with some sender restrictions:
# /etc/postfix/main.cf # Sender restrictions: smtpd_sender_restrictions = permit_mynetworks, reject_non_fqdn_sender, reject_unknown_sender_domain, permit
Again, firstly we allow email from senders on our own network (permit_mynetworks). The next two lines reject messages if the senders email address is malformed or nonexistent as there’s no real reason to accept mail from them. reject_non_fqdn_sender will reject email when the MAIL FROM address is not in fully-qualified domain form as required by the RFC. reject_unknown_sender_domain will reject email when the MAIL FROM address has neither a DNS A nor MX record, or when it has a malformed MX record such as a record with a zero-length MX hostname. The response code for reject_unknown_sender_domain is 450 (try again later) in case of a temporary DNS error. Further, since the MAIL FROM address is the address that bounce notifications must be sent to, it makes no sense to receive mail from an unknown domain. The final line allows every other message to move on to the next phase of filtering.
5. Recipient Restrictions
By this time, it’s clear that the client machine isn’t completely mis-configured and that the sender stands at least a chance of being legitimate. The final step is to see that the client has permission to send to the given recipient and to apply some external non-postfix checks to the small number of messages that make it this far.
# /etc/postfix/main.cf # Recipient restrictions: smtpd_recipient_restrictions = reject_unauth_pipelining, reject_non_fqdn_recipient, reject_unknown_recipient_domain, permit_mynetworks, reject_unauth_destination, check_sender_access hash:/etc/postfix/sender_access, reject_rbl_client zen.spamhaus.org, reject_rbl_client bl.spamcop.net, check_policy_service unix:postgrey/socket, permit
Postfix supports a technique known as pipelining that speeds up bulk deliveries of email by sending multiple smtp commands at once. The protocol requires that clients first check that the server supports pipelining. Many spammers send a series of commands without waiting for authorization, in order to deliver their messages as quickly as possible. reject_unauth_pipelining stops mail from bulk mail software that improperly uses pipelining in order to speed up deliveries.
The next few lines should be somewhat familiar by now and reject mail to domains that don’t exist or can’t exist. Firstly we reject email when the RCPT TO address is not in fully-qualified domain form (reject_non_fqdn_recipient) as required by RFC and then reject email when our email server is not final destination for the recipient address, when the RCPT TO address has no DNS A or MX record, or when it has a malformed MX record. The response code for reject_unknown_recipient_domain is 450 (try again later) in case of a temporary DNS error. Again, permit_mynetworks tells postfix to accept connections from local users on our network.
The next line, reject_unauth_destination, is critically important as it tells postfix not to accept messages with recipients at domains that are not hosted locally or that we serve as a backup server for. Without this line, our server would be an open relay.
The next setting, check_sender_access, allows us to implement whitelisting and blacklisting of full or partial email addresses and domains as specified in the MAIL FROM field against a specified access table with a target of either OK or REJECT. First we must create our lookup table, which in this case is the plain text file /etc/postfix/sender_access:
# /etc/postfix/sender_access # # Black/Whitelist for senders matching the 'MAIL FROM' field. Examples... # email@example.com OK firstname.lastname@example.org REJECT marketing@ REJECT theboss@ OK deals.marketing.com REJECT somedomain.com OK
In the example above we can see that we can black or white list mail from fully qualified email addresses, usernames at any domain, or domains and partial domain names contained within the MAIL FROM field. Note that these are examples only and generally you should not whitelist your own domain as spammers often spoof the sender address to be that of your own domain. Once you have created your sender_access file you must run the postmap command against it to create the indexed version that postfix will use (you must also repeat this whenever you change the file):
That is the last of our internal postfix checks. The remaining checks either consult external sources or helper applications.
We next perform some real-time blacklist (RBL) checks. RBL's, also known as DNS Black Lists (DNSBL) are 3rd party services that maintain a list of IP addresses known to be involved in some form of illicit activity such as the distribution of spam or other undesirable content such as viruses. The reject_rbl_client line causes postfix to perform a check against the listed database and will reject the message if it is from a known bad source. Because such checks involve a DNS lookup they are relatively "expensive" in terms of resources and thus are best placed towards the end of our checks so that as much spam as possible has already been eliminated to minimize the number that occur. Likewise, it also makes sense to place the most effective lists first, again minimizing the number of lookups that are needed to reject a (spam) message.
There are many DNSBL's available, how many and which you choose to use is up to you, but you should monitor your logs on a regular basis to review their effectiveness (hint: install the postfix-pflogsumm package and use the pflogsumm command to analyze your postfix logs). We should also recognize that using DNSBL's starts to introduce the possibility of false positives as we are relying on the policy of the list maintainer for adding rogue addresses. Some are highly effective, some block virtually nothing, and others will block as much genuine mail as they do spam. For further information and real time statistics on DNSBL's the reader is referred to: http://stats.dnsbl.com/
The final check, check_policy_service, is used to call an add on application or script, in this case postgrey bound to a unix socket. Postgrey can be highly effective when it comes to eliminating spam and there is a separate guide dedicated purely to the installation of postgrey on a postfix email server. The reader is strongly encouraged to read that guide next.
In this guide we have seen how we can use postfix restrictions to reject mail from obviously mis-configured clients or those who otherwise have no business sending email to our server. The restrictions implemented are deliberately conservative with the intention of not causing false positives and should work well in any postfix installation. Postfix restrictions, particularly rejecting helo hostnames in non fully qualified domain name form, can block as much as 30% of spam and using 1 or 2 good DNSBLs can block 90% or more of the remainder. Combined with further filtering techniques such as greylisting, it should be possible to filter out the vast majority of spam (~99%) before it enters the email server thus saving on bandwidth and processing resources. This guide is designed to compliment the basic postfix guide.
This article was inspired by and closely based on the excellent guide by Kirk Strauser published under the "Attribution-Sharealike" Creative Commons License 2.5 at: http://www.freesoftwaremagazine.com/articles/focus_spam_postfix