postfix config-o-rama

postfix config-o-rama

I spent a lot of today finally setting up e-mail for my domain, phekda.org. My goals were:

  • Set up an SMTP SUBMIT server (running on port 587), so that I can send mail from @phekda.org addresses from anywhere.
  • Require mail to be submitted over TLS.
  • Authenticate the client by requiring that the client presents a certificate issued by my private certificate authority (CA). Since I’m only going to issue certificates to people/machines I trust, possession of a certificate is implicit authentication.

TinyCA

I used TinyCA2 to set up my own personal CA. It’s really easy to use. I created a CA for phekda.org. I also created a “bad” CA for testing that my postfix box would only accept certificates issued for phekda.org.

Here’s what I generated in total:

  • CA cert for phekda.org
  • Server cert for mail.phekda.org, signed by CA phekda.org
  • Client cert for my desktop machine, signed by CA phekda.org
  • CA cert for bad.ca
  • Client cert for my desktop machine, signed by CA bad.ca (for testing)

Tip: You can generate password-free keys with TinyCA2. To do this you create the key as normal, specifying the password. When you export the key into a PEM file, you can choose to export without the password.

Server-side postfix configuration

My server is running postfix 2.3.8 on Debian 4.0. The server-side config was split into two halves: general TLS configuration in main.cf, and the config to turn on an SMTP daemon on port 587 with TLS enabled.

Here’s the config I added to main.cf:

smtpd_tls_req_ccert = yes
smtpd_tls_session_cache_database = sdbm:/etc/postfix/smtpd_scache

smtpd_tls_CAfile = /etc/postfix/CAcert.pem
smtpd_tls_cert_file = /etc/postfix/server-cert.pem
smtpd_tls_key_file = /etc/postfix/server-key.pem

# Log TLS info, in logs and headers.
smtpd_tls_loglevel = 2
smtpd_tls_receivedheader = yes

Note that these entries in main.cf don’t actually enable TLS. smtpd_tls_req_ccert requires SMTP clients to use STARTTLS, when TLS is enabled. The smtpd_tls_*file entries set up everything that’s needed on the server-side for TLS encryption. I turned on the last couple of options for debugging purposes.

Here’s the line I added to master.cf, split over multiple lines for clarity. You won’t need the backslashes, when you recombine them into one line.

587       inet  n       -       n       -       -       smtpd \
-o smtpd_enforce_tls=yes \
-o smtpd_tls_req_ccert=yes \
-o smtpd_recipient_restrictions= \
  permit_mynetworks, \
  permit_tls_all_clientcerts, \
  reject_unauth_destination

smtpd_recipient_restrictions allows clients with authenticated certificates to relay, in addition to local users. Although I’m not sure why a local user would relay through port 587.

Server-side testing

I tested this using OpenSSL’s s_client, to set up a client SMTP session using the client certificates I generated with TinyCA2. You fire up openssl s_client with appropriate options, then enter SMTP commands as normal, e.g.:

ehlo fred
mail from:<me@my.domain.example>
rcpt to:<someone@somewhere.else.example>
data
Subject: just a test

.

You need to go all the way, to check that the message can actually be delivered.

  • Connection should be accepted, because the client is using a certificate issued by the CA for phekda.org:

    openssl s_client -connect mail.phekda.org:587 -starttls smtp \
    -CAfile phekda.org-cacert.pem \
    -key katrina.phekda.gotadsl.co.uk-key.pem \
    -cert katrina.phekda.gotadsl.co.uk-cert.pem
    
  • Connection should not be accepted, because the client is using a certificate not issued by the CA for phekda.org:

    openssl s_client -connect mail.phekda.org:587 -starttls smtp \
    -CAfile phekda.org-cacert.pem \
    -key mail.bad.ca-key.pem \
    -cert mail.bad.ca-cert.pem
    

And a slight variation:

openssl s_client -connect mail.phekda.org:587 -starttls smtp \
  -CAfile bad.ca-cacert.pem \
  -key mail.bad.ca-key.pem \
  -cert mail.bad.ca-cert.pem

Tip: One thing to beware of is that OpenSSL will do a TLS renegotiation if you use “RCPT TO”, so use “rcpt to” instead.

Client-side postfix configuration

I have several e-mail accounts. I want to keep sending from my old domain @phekda.gotadsl.co.uk, but I also want to be able to send from @phekda.org. These messages would be sent via the same postfix server running on my desktop machine.

Before making the changes, all my mail was smart-hosted through my ISP’s mail server – i.e.: all my mail went through my ISP’s mail server. Afterwards, my @phekda.org was routed over TLS to mail.phekda.org on port 587, and the rest of the mail was smart-hosted.

To achieve what I wanted, I set up sender-based routing (SBR). Normally mail is routed by recipient address – SBR overrides the recipient-based routing. Configuring sender-based routing was the hardest part to achieve, because postfix’s documentation of SBR and its [sender_dependent_relayhost_maps](http://www.postfix.org/postconf.5.html#sender_dependent_relayhost_maps) configuration format is a little, uh, brief. Fortunately the postfix source code is readable, and I figured it out from that.

My desktop box is running postfix 2.4.3 on Fedora 7. The client-side postfix config is split into three parts: routing and TLS configuration in main.cf; sender-based routing (SBR) map file, sender_dependent_relayhost; TLS policy map file, smtp_tls_policy.

Firstly, here’s the configuration in main.cf:

# Smart-host via Nildram...
relayhost = [smtp.gotadsl.co.uk]

# ...except for certain senders, who we relay through other boxes.
sender_dependent_relayhost_maps = hash:/etc/postfix/sender_dependent_relayhost

# TLS configuration for sending mail to phekda.org
smtp_tls_CAfile = /etc/postfix/CAcert.pem
smtp_tls_cert_file = /etc/postfix/client-cert.pem
smtp_tls_key_file = /etc/postfix/client-key.pem

smtp_tls_loglevel = 1

smtp_tls_policy_maps = hash:/etc/postfix/smtp_tls_policy

Here is /etc/postfix/sender_dependent_relayhost_maps:

#
# Regenerate using:
#   postmap hash:sender_dependent_relayhost < sender_dependent_relayhost
#

# phekda.org sender should be submitted to mail.phekda.org.
@phekda.org	[mail.phekda.org]:587

It wasn’t clear how I could configure all phekda.org subdomains to be routed in the same way. It looks like I would have to specify them all manually. Any domains not configured in this file are routed using the normal mechanisms, which in this case ends up being the smarthost specified by relayhost.

Here is /etc/postfix/smtp_tls_policy:

#
# Regenerate using:
#   postmap hash:smtp_tls_policy < smtp_tls_policy
#

phekda.org	secure
[mail.phekda.org]:587	secure

These configuration files need building into .db files before postfix can use them – this is done using postmap. I wrote a simple Makefile to automate that.

Client-side testing

I tested sending to my gmail account using @phekda.gotadsl.co.uk and @phekda.org addresses. I did this using plain ol’ telnet. From the postfix log in /var/log/maillog, I could see where the messages were being routed to. E.g.:

Sep  1 20:30:38 katrina postfix/smtp[27281]: 3225BD: to=<richdawe@gmail.com>,
relay=smtp.gotadsl.co.uk[195.112.4.54]:25, delay=8.6, delays=8.2/0.19/0.11/0.09,
dsn=2.0.0, status=sent (250 Ok: queued as 3501A2BAE63)

Sep  1 20:51:31 katrina postfix/smtp[27374]: 9E258D: to=<richdawe@gmail.com>,
relay=mail.phekda.org[80.68.89.241]:587, delay=25, delays=24/0.13/1.1/0.15,
dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 0E501803B)

It was easy to see when the config was broken.