Written for CentOS 6.4, Postfix 2.6.6. * Server is `example.com`. * Mail users map to local accounts (i.e., in `/etc/passwd`). * DNS looks like this: [me@example.com ~]$ dig example.com -t MX +short 10 mail.example.com. * Made sure that reverse DNS is set up properly. ## Overview Things were easier to set up after understanding these things, even cursorily[^1]. ### Mail Transfer - Basic Idea Lots of formal abbreviations! They are, luckily enough, [quite sensible](http://dev.mutt.org/trac/wiki/MailConcept). Here's the basic flow: Sender > Server > Server > ... > Server > Receiver (MUA)    (MTA)    (MTA)          (MTA)     (MUA) You can be a bit more granular:    Sender   >  Server > Server > ... >   Server     | Delivery |  >   Receiver (MUA > MSA)    (MTA)    (MTA)          (MTA > MDA)  | complete |     (MRA > MUA) (MDAs, when local, are also called LDAs.) This separation of purpose is good since you can use [a variety of applications and topologies](http://twiki.org/cgi-bin/view/Wikilearn/EmailServerSketches) at each stage. Lot of possibilities. E.g. Sender > Postfix > Procmail > Clam Anti-Virus > SpamAssassin > Procmail > Fetchmail > Receiver (MUA)    (MTA)     (                       MDA                        )     (MRA)       (MUA) ### Open Relays Where it is not required to (1) authenticate to your server, and/or (2) be in the same network as the server to send email. This is very bad for public-facing mail servers. From a simpler time when there were very few email servers and everybody was nice to each other. ### Mailbox Formats There are [quite a few](http://wiki2.dovecot.org/MailboxFormat), each with its own pros and cons. I personally like [Maildir](http://wiki2.dovecot.org/MailboxFormat/Maildir). ## Installation yum install postfix cyrus-sasl ln -s /usr/sbin/sendmail.postfix /etc/alternatives/mta --force ## Configuration ### The Basics Postfix ships with sane and secure defaults. Here's stuff I changed in `/etc/postfix/main.cf` First set the hostname and domain myhostname = example.com mydomain = $myhostname Mail from this server will come from this domain. myorigin = $mydomain Accept mail on specified interface[^2] and all protocols (IPv4 and IPv6[^3]) inet_interfaces = all inet_protocols = all This server will think itself the final MTA (in the chain above) for these domains: mydestination = $mydomain, localhost The server will only trust itself[^4] mynetworks_style = host Use the Maildir format for message delivery home_mailbox = Maildir/ Change the banner for fun (and no version information) smtpd_banner = $myhostname ESMTP Why, hello there! Now edit `[http://www.postfix.org/master.5.html /etc/postfix/master.cf]` to enable the submission port[^5]. submission inet n       -       n       -       -       smtpd Uncomment any options ("-o"); we'll take care of these in later. ### Testing Restart the postfix service. Then from another computer, orangebox:~$ telnet example.com 25 Trying 96.126.123.32... Connected to example.com. Escape character is '^]'. EHLO example.com 250-example.com 250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250 DSN MAIL FROM: testuser@internet.com 250 2.1.0 Ok RCPT TO: me@example.com 250 2.1.5 Ok DATA 354 End data with . Subject: Hello! How have you been? . 250 2.0.0 Ok: queued as 7602C72C2 QUIT This should: * Work for a valid user MAIL FROM: postmaster@example.com 250 2.1.0 Ok * Not work an invalid user RCPT TO: nonexistent@example.com 550 5.1.1 : Recipient address rejected: User unknown in local recipient table * Deliver a mail to your home folder! The "`Maildir`" folder will be created automagically. ~/Maildir  ├── cur  ├── new  │   └── 1377029606.Vca00I4025fM640219.example.com  └── tmp But this is done insecurely. Let's fix that. ### Doing things securely Generate a self-signed certificate[^6] openssl req -new -x509 \             -newkey rsa:4096 \             -days 3650 \             -nodes \             -out /etc/pki/tls/certs/dovecot.crt \             -keyout /etc/pki/tls/private/dovecot.key chmod o= /etc/pki/tls/private/dovecot.pem Now configure Postfix to use these certificates for TLS smtp_tls_security_level = may smtpd_tls_security_level = may smtpd_tls_cert_file = /etc/pki/tls/certs/postfix.crt smtpd_tls_key_file = /etc/pki/tls/private/postfix.key smtpd_tls_auth_only = yes smtpd_tls_loglevel = 3 smtpd_tls_received_header = yes smtpd_tls_session_cache_timeout = 3600s tls_random_source = dev:/dev/urandom Restart Postfix. As always, see `/var/log/maillog` for any errors. Now test. **Important Stuff** * You have to use the OpenSSL client instead of `telnet` from this point on! * Watch out for non-zero "Verify return codes". Avoid these by providing a full path to the system root certs. This is normally done via `openssl version -d`. On OS X, use "Keychain Access" to export all the stuff under "System Roots" into a single PEM file. * Keep the former telnet commands lowercase! Else, the client will renegotiate every time you type `RCPT TO`. [OpenSSL can waste your time like that](http://archives.neohapsis.com/archives/postfix/2007-01/1334.html)! openssl s_client -starttls smtp \                  -CAfile /path/to/roots.pem \                  -connect example.com:25 ### Some restrictions Stepping throught the telnet output in the previous section, start adding some restrictions to the client connection[^7]: smtpd_client_restrictions = reject_unknown_client_hostname, permit Then the [`HELO`](http://www.postfix.org/postconf.5.html#smtpd_helo_restrictions) command smtpd_helo_required = yes smtpd_helo_restrictions = reject_unknown_helo_hostname, reject_non_fqdn_helo_hostname, reject_invalid_helo_hostname, permit [`MAIL FROM`](http://www.postfix.org/postconf.5.html#smtpd_sender_restrictions) smtpd_sender_login_maps = pcre:/etc/postfix/login_maps.pcre smtpd_sender_restrictions = reject_non_fqdn_sender, reject_sender_login_mismatch, reject_unknown_sender_domain, permit And finally, [`RCPT TO`](http://www.postfix.org/postconf.5.html#smtpd_recipient_restrictions) This will allow you to relay messages (i.e. send email to *other* domains) if you're SASL-authenticated. smtpd_recipient_restrictions = permit_sasl_authenticated, reject_unauth_destination, permit Add this to `/etc/postfix/login_maps.pcre`[^8]. /^(.*)@example.com$/   ${1} Test away! You should see good errors like: 450 4.1.8 : Sender address rejected: Domain not found 450 4.7.1 : Helo command rejected: Host not found 553 5.7.1 : Sender address rejected: not logged in Now add a way in which you an log in to the server remotely to send messages through it. ### SASL Authentication Will use [Cyrus](http://cyrusimap.web.cmu.edu/docs/cyrus-sasl/2.1.23/). Postfix [uses it by default](http://www.postfix.org/SASL_README.html#server_sasl_enable). You can see what other libraries Postfix was compiled with support for as well: [root@example ~]# postconf -a cyrus dovecot Install Cyrus yum install cyrus-sasl You can then see what authentication methods Cyrus supports: [root@example !]# saslauthd -v saslauthd 2.1.23 authentication mechanisms: getpwent kerberos5 pam rimap shadow ldap Install the appropriate package. Since I'm using plain auth, yum install cyrus-sasl-plain Since we're dealing with local accounts, let's tell Cyrus to use `/etc/shadow`. Open `/etc/sysconfig/saslauthd`: SOCKETDIR=/var/run/saslauthd MECH=shadow FLAGS= Start the service service saslauthd start Make sure it starts when you reboot your server chkconfig saslauthd on Test! [root@example !]# testsaslauthd -u testuser -p secretpassword 0: OK "Success." Now tell Postfix to use Cyrus in `/etc/postfix/main.cf` smtpd_sasl_auth_enable = yes smtpd_sasl_type = cyrus Set [some security options](http://www.postfix.org/SASL_README.html#smtpd_sasl_security_options) smtpd_sasl_security_options = noanonymous Restart the Postfix service. Test: [root@toolkit ~]# openssl s_client -starttls smtp -CAfile /path/to/postfix.crt -connect example.com:25 (Certificate, connection info) --- 250 DSN helo example.com** 250 example.com auth plain An8o0tjsHojfDausWtzblk4bnZA 235 2.7.0 Authentication successful Generate the funky MD5 output with your username and password: echo -ne '\000user\000password' | base64 ## Preventing Spam, Bad Email, and DOS Attacks ### Using Blocklists Change `smtpd_recipient_restrictions` to add some blocklists[^9] and other stringent policies: smtpd_recipient_restrictions =   permit_sasl_authenticated,   reject_invalid_hostname,   reject_non_fqdn_sender,   reject_non_fqdn_recipient,   reject_unauth_destination,   reject_rbl_client zen.spamhaus.org,   reject_rbl_client psbl.surriel.com,   reject_rbl_client bl.spamcop.net,   permit Most residential IPs are banned by blocklists, so keep that in mind when testing your setup: 554 5.7.1 Service unavailable; Client host [173.29.77.33] blocked using zen.spamhaus.org; http://www.spamhaus.org/query/bl?ip=173.29.77.33 ### Using SPF [Sender Policy Framework](http://www.openspf.org/Project_Overview) prevents fake sender addresses from your domain. It's a great idea and is something everyone should do[^10]. To empower Postfix with SPF, first install some required packages from EPEL: yum install perl-core perl-Mail-SPF --enablerepo=epel I'm going to try [the Perl implementation of SPF](https://launchpad.net/postfix-policyd-spf-perl/). [^11] Download, extract, move to a good place: tar -xvzf postfix-policyd-spf-perl-2.010.tar.gz cd postfix-policyd-spf-perl-2.010 mv postfix-policyd-spf-perl /usr/local/bin/ Now set up `/etc/postfix/main.cf`. Add to `smtpd_recipient_restrictions` # Other options not shown for brevity smtpd_recipient_restrictions =   check_policy_service unix:private/policy-spf,   policy_time_limit = 3600 # Default is 1000; too short[^12] Then add to `/etc/postfix/master.cf` policy-spf unix  -       n       n       -       0       spawn     user=nobody  argv=/usr/bin/perl /usr/local/bin/postfix-policyd-spf-perl Restart the service. Send yourself an email from another service Gmail, and look for SPF output in `/var/log/maillog` ### Some Limits on Interaction The first setting is the denominator for the "limit"s # Connection limits anvil_rate_time_unit = 120s smtpd_client_connection_rate_limit = 2400 smtpd_client_message_rate_limit = 12000 smtpd_error_sleep_time = 60 ### Prevent Abuse [Greylisting](http://en.wikipedia.org/wiki/Greylisting) is a great approach to fighting spam. The idea is that spammy mail servers do not respect the RFC spec that, if an email couldn't be delivered initially, they are to re-attempt delivery later. [Postgrey](http://postgrey.schweikert.ch/) works well for this. By default, it asks MTAs to attempt redelivery in 5 minutes. yum install postgrey service postgrey start chkconfig postgrey on This will run on a Unix socket. The next step is to get Postfix to use it. Edit `/etc/postfix/main.cf`. # Other options not shown for brevity smtpd_recipient_restrictions =     check_policy_service unix:postgrey/socket I lowered the default wait time to a minute by creating `/etc/sysconfig/postgrey` and adding this: OPTIONS=$OPTIONS" --delay=60" Restart Postfix and Postgrey. You'll see something like this in `maillog` to make sure it's working: postgrey[12582]: action=pass, reason=client whitelist, client_name=mail-qc0-f169.google.com,     client_address=209.85.216.169, sender=foo@bar.com, recipient=test@example.com ## Miscellaneous ### "warning: dict\_nis\_init:" Disable NIS lookups alias_maps = hash:/etc/aliases ### "Relay Access Denied" Usually [something](http://serverfault.com/questions/321864/postfix-relay-access-denied) quite [simple](http://serverfault.com/questions/192354/postfix-sasl-relay-access-denied-when-sending-from-outside-the-network). ## References * [Email agent infrastructure](http://en.wikipedia.org/wiki/E-mail_agent_(infrastructure)) * [How email works](http://en.kioskea.net/contents/116-how-email-works-mta-mda-mua) * [Securing Postfix (Red Hat)](https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Security_Guide/sect-Security_Guide-Server_Security-Securing_Postfix.html) * [Relay and Access Control](http://www.postfix.org/SMTPD_ACCESS_README.html) * [Postfix SASL Howto](http://www.postfix.org/SASL_README.html) * [Cyrus SASL for Systems Administrators](http://www.sendmail.org/~ca/email/cyrus/sysadmin.html) * [Postfix + SPF + CentOS6](http://www.thenoccave.com/2013/05/08/centos-6-postfix-spf-checking/) * [Postfix FAQ](http://www.seaglass.com/postfix/faq.html#chbnc) * [How to Set Up your Own Personal Email Server](http://aurellem.org/free/html/email.html) by Robert McIntyre ## Footnotes [^1]: [The Linode page](https://library.linode.com/mailserver) on mail servers is also a great overview [^2]: Can specify IP address also: inet_interfaces=all inet_interfaces=eth0,eth1 inet_interfaces=38.9.127.1,10.0.1.23 inet_interfaces=mail.tux.com [^3]: Default is IPv4 [^4]: Can trust network classes or subnets and specific IP addresses [^5]: I was a little confused about this but think I understand. Port 25 is the standard SMTP port that used for MTA-to-MTA communication. So if you have a user who is behind an ISP connection that blocks port 25 (for spam or other reasons like bad proxying), they can still send/submit mail to your server, even if it's not the final destination on the message envelope, on port 587. [^6]: Can also use [StartSSL](http://www.startssl.com/) or [CACert](http://www.cacert.org/). [^7]: http://www.postfix.org/postconf.5.html#smtpd_client_restrictions [^8]: From [ServerFault](http://serverfault.com/a/318432). Postfix can use a *lot* more formats for controlled envelopes. See the output of `postconf -m`. For instance, I initally used this file (Specified with `hash:/path/to/file`): # Envelope sender Owner me@example.com me [^9]: Of which there are [a lot available](http://www.dnsbl.info/dnsbl-list.php) [^10]: To get started, [read about the syntax](http://www.openspf.org/SPF_Record_Syntax) or use [a wizard](http://spfwizard.com), then the [validation tool](http://www.kitterman.com/spf/validate.html). [^11]: Tried to make the Python version work but ran into issues with Python3 and the `ipaddr` module. [^12]: http://www.postfix.org/SMTPD_POLICY_README.html#client_config