Have Postfix relay e-mail to GMail

This post documents how I did set up Postfix 2.6 to relay all of its e-mail to GMail.

I used different sources to assemble what is described next. Worth mentioning are Getting Postfix to work on Ubuntu with Gmail, Gmail on Home Linux Box using Postfix and Fetchmail, Postfix Gmail SMTP Relay and finally Postfix TLS Support.

No client-side certificate, please

Some Web sites out there seem to insist on creating client-side certificates for Postfix when dealing with mail relaying to GMal. That is incorrect. Client-side certificates are not required when relaying mail to GMail. At the moment, GMail does only support user and password authentication, so trying to supply client-side certificates during the authentication phase might likely confuse the GMail SMTP servers and/or create problems.

Postfix main.cf main configuration file

The following configuration directives have to be added to a Postfix’s pristine main.cf configuration file. I added them at the end of the file:

# The e-mail sent will use this hostname as the e-mail origin.
myhostname = my.dynamicdns.domain.name
myorigin = $myhostname

# Relay all e-mail via GMail.
relayhost = [smtp.gmail.com]:587

# SASL authentication
smtp_sasl_auth_enable=yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous

# TLS
smtp_tls_eccert_file =
smtp_tls_eckey_file =
smtp_tls_security_level = may # http://www.postfix.org/TLS_README.html#client_tls_may
smtp_tls_CAfile = /etc/postfix/cacert.pem # Trusted root CAs
smtpd_tls_received_header = yes
tls_random_source = dev:/dev/urandom
smtpd_tls_security_level = may # http://www.postfix.org/TLS_README.html#client_tls_may

Store authentication credentials

GMail MSA/SMTP servers require the sending user to authenticate using their standard GMail user name and password. This authentication data must be stored properly secured in the file /etc/postfix/sasl_passwd:

gmail-smtp.l.google.com user.name@gmail.com:password
smtp.gmail.com user.name@gmail.com:password

Make sure the file is properly secured so that only the root user can dump its contents:

# chown root:root /etc/postfix/sasl_passwd
# chmod 600 /etc/postfix/sasl_passwrd

Postfix requires the conversion of the plain-text file to a hashed table format. This is achieved by running:

# postmap /etc/postfix/sasl_passwd

This will create a filed named /etc/postfix/sasl_passwd.db.

Populate the list of trusted CA certificates

This is required because, by default, Postfix does not trust any CA out there. cacert.pem is just Postfix’s trusted CA root certificate store. Other software components, like Web browsers, use different stores, but this file is essentially equivalent to those.

GMail SSL/TLS certificates are signed by Thawte. Therefore, in order to get Postfix to be able to authenticate the GMail SMTP server, it is necessary to store Thawte’s root CA certificates somewhere. Else, when Postfix tries to foward a message to smtp.gmail.com the following errors are logged:

May 10 15:40:07 postfix postfix/smtp[10677]: certificate verification failed
  for smtp.gmail.com[72.14.221.111]:587: untrusted issuer
  /C=ZA
  /ST=Western Cape
  /L=Cape Town
  /O=Thawte Consulting cc
  /OU=Certification Services Division
  /CN=Thawte Premium Server CA
  /emailAddress=premium-server@thawte.com
May 10 15:40:07 postfix postfix/smtp[10677]: warning: SASL authentication failure:
  No worthy mechs found

From a Ubuntu Linux box that had the ssl-cert package installed, I copied the certificates that correspond to Thawte’s CA to the Postfix machine. There, it’s just a matter of concatenating the multiple .pem files into just one file that Postfix will use: /etc/postfix/cacert.pem.

In order to generate cacert.pem from the individual Thawte certificates:

# cat {
  Thawte_Personal_Basic_CA,
  Thawte_Personal_Freemail_CA,
  Thawte_Personal_Premium_CA,
  Thawte_Premium_Server_CA,
  Thawte_Server_CA,
  Thawte_Time_Stamping_CA
}.pem /etc/postfix/cacert.pem

Reload Postfix configuration

For example, by sending the SIGHUP signal to Postfix’s master process:

# pkill -1 master
# tail /var/log/maillog
May 10 15:58:42 postfix postfix/master[6921]: reload
  -- version 2.6-20090125, configuration /etc/postfix

Test

You can test by connecting port 25 of your Postfix machine or, as in my case, use the mail command:

# mail user.name@gmail.com
Subject: Hola
Este es un mensaje de prueba.
.

Postfix should log some messages to /var/log/maillog that should be equivalent to the following ones:

May 10 15:58:52 postfix postfix/pickup[32213]: 1234567890: uid=0 from=<root>
May 10 15:58:52 postfix postfix/cleanup[12716]: 1234567890:
  message id=<20090510135852.1234567890@my.dynamicdns.domain.name>
May 10 15:58:52 postfix postfix/qmgr[8604]: 1234567890:
  from=<root@my.dynamicdns.domain.name>, size=323, nrcpt=1 (queue active)
May 10 15:58:54 postfix postfix/smtp[32243]: 1234567890:
  to=<user.name@gmail.com>,
  relay=smtp.gmail.com[72.14.221.111]:587,
  delay=3.4,
  delays=1.1/0.21/0.76/1.3,
  dsn=2.0.0,
  status=sent (250 2.0.0 OK 1241963934 l12sm1383617fgb.4)
May 10 15:58:54 postfix postfix/qmgr[8604]: 1234567890: removed

Enterprise mail routing with Postfix and LDAP

A LDAP directory offers a single, logically centralized, hierarchical store to keep data, like information about users, groups, mailboxes, services, etc. There are a number of applications out there than can leverage this LDAP store. For example, PAM can authenticate by checking against an LDAP directory, Cyrus-IMAP can check whether a certain user has a IMAP/POP mailbox available, and Postfix can contact an LDAP directory in order to route an incoming mail to its corresponding mail host.

LDAP is flexible enough to describe abstract concepts, like persons, by using object classes. Each object class is built upon attributes. An attribute is the minimal unit of information and tells about a property, such as the name of a person, its postal address or e-mail address. The minimal storage unit is an LDAP entry, however, which is an instance of one ore several object classes. The collection of attributes and object classes is called schema, while the collection of LDAP entries is called Directory Information Tree (DIT).

There are several RFC documents out there describing most of the standard LDAP object classes for the LDAPv3 protocol. There are other object classes which are either propietary or non-standard. For example, Fedora Directory Server defines an object class named mailRecipient. This legacy object class was used by the Netscape Messaging Server 4 to define a mailbox and is defined as:

objectclasses: ( 2.16.840.1.113730.3.2.3
NAME 'mailRecipient'
DESC '' SUP top AUXILIARY
MUST ( objectClass )
MAY ( cn $ mail $ mailAlternateAddress $
mailHost $ mailRoutingAddress $
mailAccessDomain $ mailAutoReplyMode $
mailAutoReplyText $ mailDeliveryOption $
mailForwardingAddress $
mailMessageStore $ mailProgramDeliveryInfo $
mailQuota $ multiLineDescription $ uid $
userPassword )
X-ORIGIN 'Netscape Messaging Server 4.x' )

This object class is particularly simple and interesting since it defines a few concepts we can use when routing mail, like the idea of multiple mail aliases for a single mailbox.

Describing our purpouses

Take this scenario: we have a real user, John Smith which owns a mailbox which is stored at host mail1.internal. The mailbox for John Smith is john.smith@mail1.internal, since it’s stored in host mail1.internal. Let’s say this real user has the following e-mail addresses assigned to him (assigned to its mailbox):

  • sample.user@example.com
  • sample_user@example.com
  • sampleuser@example.com
  • sample@example.com
  • john.smith@example.com

These are the e-mail addresses. The last e-mail address is, in fact, the real e-mail address that anyone could expect John Smith to have. The other e-mail addresses are in fact aliases.

Whenever a Postfix MTA in the enterprise receives a mail for any of these e-mail addresses, we want it to be delivered to a mailbox named john.smith in host mail1.internal, that is, delivered to john.smith@mail1.internal, and so, john.smith@mail1.internal is the real address where messages sent to John Smith, using any of the previous e-mail addresses, must be routed to. Thus, john.smith@mail1.internal is what we will call the mail routing address.

We will configure Postfix to use an LDAP-based virtual alias map. Each time a mail is sent to Postfix, it will perform an LDAP query for that e-mail address and will try to guess its mail routing address. If one is found, Postfix will deliver that mail to it:

  • If the domain part (the address at the right of the @ sign) of the mail routing address equals the FQDN of the Postfix host:

    Postfix will try to deliver the message locally, usually using an MDA, like procmail or cyrus-imapd/deliver (Cyrus-IMAP local delivery agent used to deliver messages to a IMAP mailbox).

    For example, let be john.smith@mail1.internal the mail routing address and mail1.internal Postfix’s hostname FQDN. In this case, Postfix will deliver the message locally using a MDA.

  • If the domain part (the address at the right of the @ sign) of the mail routing address does not equal the FQDN of the Postfix host:

    Postfix will trigger the message routing process again in order to deliver it to its final destination, checking querying the LDAP directory again if necessary.

To store mail routing addresses and it’s corresponding mail aliases, we will use the mailRecipient object class. The multi-valued mail attribute will hold all mail aliases and mailRoutingAddress will the real, final destination for any of them.

A sample entry, exemplifying the John Smith user we described before, in LDIF syntax is:

dn: uid=john.smith,ou=People, dc=example, dc=com
givenName: John
sn: Smith
mail: sample.user@example.com
mail: sample_user@example.com
mail: sampleuser@example.com
mail: sample@example.com
mail: john.smith@example.com
objectClass: top
objectClass: mailRecipient
uid: john.smith
mailRoutingAddress: john.smith@mail1.internal

Note the absence of the posixAccount object class. This means John Smith is not a regular UNIX user and thus, this mailbox cannot be used to log in through PAM, for example. Note the absence of the inetOrgPerson object class too, which could mean this mailbox is not assigned to a real user.

Configuring Postfix

The first thing that is required is creating a configuration file with details about how the LDAP directory server should be queried and contacted. The name of this file is not relevant, but I decided to name it /etc/postfix/ldap-aliases.cf:

# cat /etc/postfix/ldap-aliases.cf
bind = no
version = 3
timeout = 20

## set the size_limit to 1 since we only
## want to find one email address match
size_limit = 1
expansion_limit = 0

start_tls = no
#tls_require_cert = no

server_host = ldap://ldap1.internal/ ldap://ldap2.internal/
search_base = dc=example,dc=com
scope = sub
query_filter = (&(objectclass=mailRecipient)(mail=%s))
result_attribute = mailRoutingAddress
special_result_filter = %s@%d

The configuration options are described in detail in the Postfix ldap_table(5) manual page. A brief description lies hereafter:

  • bind = no tells Postfix to perform an unauthenticated (anonymous) BIND against the LDAP directory.
  • size_limit = 1 tells Postfix to request one, and only one, LDAP entry matching the query_filter, starting the search operation at the LDAP entry whose DN is specified by search_base, and using a scope search scope.

    The search scope is one of: sub, one, base.

  • server_host defines one or several LDAP hosts, trying them in order should the first one fail.
  • query_filter defines the LDAP search that Postfix will use in order to retrieve the mail routing address given a mail address.

    In the previous configuration file, the LDAP search filter will look for entries belonging to the mailRecipient object class whose mail attribute matches the recipient mail address of the incoming message.

    If one is found (at most only one entry will be retrieved since size_limit = 1), the mailRoutingAddress attribute, defined to by result_attribute, points to the final destination for the message, that is the mail routing address.

Finally, we will add the virtual_alias_maps directive to Postfix’s /etc/postfix/main.cf configuration file:

# tail -1 /etc/postfix/main.cf
virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf

For example, when receiving a message for sample.user@example.com, Postfix will first look to see if this mail address is an alias or a real address. The LDAP query can be tested by running:

$ ldapsearch -x -b"dc=example,dc=coml" 
"(&(objectclass=mailRecipient)(mail=sample.user@example.com))" 
mailRoutingAddress
# extended LDIF
#
# LDAPv3
# base  with scope sub
# filter: (&(objectclass=mailRecipient)(mail=sample.user@example.com))
# requesting: mailRoutingAddress
#

dn: uid=john.smith,ou=People, dc=example, dc=com
mailRoutingAddress: john.smith@mail1.internal

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Thus, Postfix will route any message to sample.user@example.com to a mailbox named john.smith at mailhost mail1.internal.

Postfix and Cyrus-IMAP interactions on x86_64

The file /etc/postfix/master.cf of postfix-2.1.5-4.2.RHEL4.x86_64 contains a hard-coded path to Cyrus-IMAP LMTP deliver agent, /usr/lib/cyrus-imapd/deliver. However, this causes problems on x86_64 systems since Cyrus-IMAP LMTP deliver agent path is /usr/lib64/cyrus-imapd/deliver.

Fixing this problem is easy: just edit /etc/postfix/master.cf and replace /usr/lib/cyrus-imapd/deliver with /usr/lib64/cyrus-imapd/deliver.

I have filled in a bug report against Red Hat’s Enterprise Linux 4:

https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=192601

Block sender address spoofing with SMPT AUTH

In order to evade mail-relaying, unathorized users try to spoof the e-mail sender address in mail messages. One trick they use consists in making Postfix think the sender belongs to a mail domain in $mydestination (that the message comes from a domain belonging to the list of domains that Postfix considers itself the final destination for).

For example:

mydomain = example.com
myorigin = $mydomain
mydestination =
    $mydomain,
    mail.$mydomain,
    www.$mydomain,
    ftp.$mydomain,
    localhost

The unauthorized user could try to pose as user@example.com in order to send messages to other users in the example.com mail domain, or to other users in other mail domains.

One way to stop this from happening is by using Authenticated SMTP (so Postfix can track who the sender is) and by establishing a relationship between (authenticated) users and message addresses. For example, we can link user user1 to message addresses user1@example.com and user2@example.com. So, when user1 authenticates against Postfix, he is allowed to send messages as either user1@example.com or user2@example.com, but no other address from the example.com mail domain.

This can be achieved by listing the option reject_sender_login_mismatch inside smtpd_recipient_restrictions. For example:

smtpd_recipient_restrictions =
    permit_sasl_authenticated,
    permit_mynetworks,
    reject_invalid_hostname,
    reject_non_fqdn_sender,
    reject_non_fqdn_recipient,
    reject_unknown_sender_domain,
    reject_unknown_address,
    reject_non_fqdn_hostname,
    reject_unauth_destination,
    reject_sender_login_mismatch

reject_sender_login_mismatch works in cooperation with smtpd_sender_login_maps, which defines the linking between (authenticated) users and mail addresses. This mapping is defined using a table, which can be stored in a plain-text file and then converted to a hash table, a mysql table, etc. For example:

smtpd_sender_login_maps =
    hash:/etc/postfix/smtpd_sender_login_maps

The table is stored as a hash map, whose plain-text representation is stored in file /etc/postfix/smtpd_sender_login_maps. This plain-text file is then converted to a hash map by using the postmap command:

# cd /etc/postfix
# postmap smtpd_sender_login_maps

Thus, smtpd_sender_login_maps specifies ownership of MAIL FROM addresses, as used by the reject_sender_login_mismatch sender address restriction.

Each line of the table specifies a sender address and the (authenticated) user login name that owns that address. The table has the following syntax and search order:

  1. user@domain owner

    This form has the highest precedence. A user who successfully authenticates against Postfix as owner can send messages as user@domain.

  2. user owner

    This matches user@site when site is equal to $myorigin, when site is listed in $mydestination, or when site is listed in $inet_interfaces.

  3. @domain owner

    This matches every address in the specified domain, and has the lowest precedence.

For example, a file /etc/postfix/smtpd_sender_login_maps with these entries:

user1@example.com user1
user2@example.com user1
user3@example.com user3
@example.com user4

Has the following meaning:

  • user1 owns both user1@example.com and user2@example.com mail addresses.
  • user3 owns user3@example.com
  • user4 owns any other address from the @example.com mail domain, except user1@example.com, user2@example.com and user3@example.com.

Thus, user1 can send messages as user1@example.com or user2@example.com, but he is not allowed to pose as sender user3@example.com or any other address.

NOTE: More information about Postfix UCE (Unsolicited Commercial E-mails) can be found here.