Section 7.5. Postfix

Wietse Venema's program, Postfix, provides an alternative to Sendmail that is simpler in design, more modular, and easier to configure and administer. Equally important, it's designed with scalability, reliability, and security as fundamental requirements.

The remainder of this chapter brings you up to speed quickly on how to use Postfix as a secure means of exchanging your network's email with Internet hosts. In particular, I'll focus on deploying Postfix on firewalls, in DMZs, and in other settings in which your SMTP server will have contact with untrusted systems.

I won't go into nearly as much depth with Postfix as I just did with Sendmail. The whole point of Postfix is ease of use: you'll have no problem figuring out how to use Postfix given little more than the documentation and example configurations included with Postfix itself.

7.5.1 Postfix Architecture

On the one hand, since Postfix can do most of what Sendmail can, its architecture is arguably as complex or even a little more so than Sendmail's. Postfix consists of a suite of daemons and helper applications, whereas Sendmail is essentially monolithic.

On the other hand, Postfix's modularity actually makes it much simpler in practice. For Mr. Venema and the others who maintain Postfix's code, it's easier to fix a bug in the SMTP daemon if that daemon's code is self-contained and not part of a much larger whole. As for end users, Postfix is administered mainly with the postfix command and a few others (most users only need postqueue and postalias).

Separating functions across different processes is a big factor in Postfix's speed and stability. Another factor is the intelligence with which Postfix handles mail. Rather than processing mail out of one big queue as Sendmail does, Postfix uses four different queues:

Maildrop queue

Mail that is submitted locally on the system is accepted in the Maildrop queue. Here the mail is checked for proper formatting (and fixed if necessary) before being handed to the Incoming queue.

Incoming queue

Mail initially received both from local processes via the Maildrop queue and from external hosts via Postfix's smtpd process is preformatted if necessary and then sent to the Incoming queue. Here it will stay until there's room in the Active queue.

Active queue

Since the Active queue contains messages that Postfix is actively trying to deliver, it has the greatest risk of something going wrong. Accordingly, the Active queue is kept intentionally small, and it accepts messages only if there is space for them.

Deferred queue

Email that cannot be delivered is placed in the deferred queue. This prevents the system from continuously trying to deliver email and keeps the active queue as short as possible to give newer messages priority. This also enhances stability. If your MTA cannot reach a given domain, all the email for that domain is assigned a wait time and placed in the deferred queue so that those messages will not needlessly monopolize system resources.

When a deferred message's wait time has expired, the message is placed in the Active queue again for delivery (as soon as there's room in the Active queue). Each time delivery is attempted and failed, the message's wait time is increased, and it is returned to the Deferred queue.

7.5.2 Getting and Installing Postfix

Current versions of Red Hat, SuSE, and Debian Linux all include Postfix packages; if you use some other distribution, it probably does too. Red Hat 7.1'spowertools directory (which, by the way, is not present on all mirrors) provides postfix-20010202-4.i386.rpm, which isn't the newest version of Postfix but is not known to have any major vulnerabilities. This RPM has been compiled with support for STARTTLS (SSL) and therefore depends on the package openssl. Oddly, Red Hat 7.2 doesn't appear to have a Postfix package, but the one from Red Hat 7.1 may be used.

Under SuSE 7, postfix.rpm can be found in the n2 series. The SuSE RPM also supports TLS and therefore needs openssl, and it also needs the package pcre because it's been compiled with support for Perl Regular Expressions (which are extremely useful in Postfix's map files).

Debian Potato includes a package of Postfix v.19991231pl11-2. Postfix used date stamps rather than version numbers until the 20010228 release, which was dubbed "v.1.0.0." As of this writing, the most current version is 1.1.3. This old version supports neither SMTP AUTH nor TLS; if you need these, you'll either have to compile Postfix from source or upgrade to Debian "Woody" (currently the "testing" release), which has Postfix v1.1.3 in the "main" section and Postfix-TLS (also v1.1.3) in the "non-US" section.

If for whatever reason you can't use a binary package, Postfix's source code is available at http://www.postfix.org. If you wish to compile Postfix with TLS (SSL) support, you'll also need to obtain Lutz Jaenicke's patch, which is available from his web site: http://www.aet.tu-cottbus.de/personen/jaenicke/postfix_tls/. Note that Wietse Venema's reason for not building in TLS support himself is that, according to the Postfix home page, he hasn't yet "figured out a way to avoid adding tens of thousands of lines of code to the SMTP client and server programs." (In other words, this patch adds complexity to a program whose main purpose in life is to be simple and, presumably, more secure.)

7.5.3 Postfix for the Lazy: A Quick-Start Procedure

One of the best things about Postfix is that it can be set up quickly and easily without sacrificing security. Therefore, before we go any further, let's look at a minimal Postfix quick-start procedure. For many users, these are the only steps necessary to configure Postfix on an SMTP gateway:

  1. Install Postfix from a binary package via your local package tool (rpm, dpkg, etc.) or by compiling and installing from source (see "When and How to Compile from Source").

  2. Open /etc/postfix/main.cf with the text editor of your choice, and set the parameter myhostname to the fully qualified name of your host, e.g.:

    myhostname = fearnley.polkatistas.org
  3. Set the parameter myorigin (the stated origin of mail sent from your network) to equal your domain name (enter this line verbatim):

    myorigin = $mydomain
  4. Set the parameter mydestination as follows, assuming this is the email gateway for your entire domain (enter this line verbatim):

    mydestination = $myhostname, localhost.$mydomain, $mydomain

    Save and close main.cf.

  5. Redirect root's mail to an unprivileged account by adding or editing this line in /etc/aliases:

    root: mick

    Add or change other email aliases as you see fit, then save and close aliases.

  6. Execute the command postfix /etc/aliases.

  7. Execute the command postfix start.

In seven brief steps, we just installed, configured, and started SMTP services for our machine and its local name domain. If this machine is a firewall or an SMTP gateway on a firewall's DMZ network, it can now be used by local users to route outbound email, and it can be pointed to by our domain's "MX" DNS record (i.e., it can be advertised to the outside world as a mail server for email addressed to our domain). Pretty good return on the investment of about ten minutes worth of typing, no?

This may be enough to get Postfix working, but it probably isn't enough to secure it fully. Don't stop reading yet!

Succinct though the seven-step method is, it may not be enough to get Postfix to do what needs to be done for your network. Even if it is, it behooves you to dig a little deeper: ignorance nearly always leads to bad security. Let's take a closer look at what we just did and then move on to some Postfix tricks.

7.5.4 Configuring Postfix

Like Sendmail, Postfix uses a .cf text file as its primary configuration file (logically enough, it's called main.cf). However, .cf files in Postfix use a simple parameter=$value syntax. What's more, these files are extremely well commented and use highly descriptive variable names. If your email needs are simple enough, it's possible for you to figure out much of what you need to know by editing main.cf and reading its comments as you go.

You may wonder why, in our little seven-step procedure, so little information needed to be entered in main.cf. The only thing we added to it was our fully qualified domain name. In fact, depending on how your machine is configured, it may not have been necessary to supply even that!

This is because Postfix can use system calls such as gethostname( ) to glean as much information as possible directly from your kernel. Furthermore, once it knows the fully qualified domain name of your host, Postfix is smart enough to know that everything past the first "." is your domain, and it sets the variable mydomain accordingly.

You may need to add additional names to mydestination if your server has more than one FQDN (that is, multiple "A" records in your domain's DNS). For example, if your SMTP gateway doubles as your public FTP server with the "ftp" name associated with it in addition to its normal hostname, your mydestination declaration might look something like this:

mydestination = $myhostname, localhost.$mydomain, ftp.$mydomain, $mydomain

It's important that this line contain any name to which your server can be legitimately referred, and that the entire declaration occupy a single line. If you have a very long list of local host or domain names, it might be easier to specify a file name, e.g.:

mydestination = /path/to/mydests.txt

where /path/to/mydests.txt is the name of a file containing your domain or hostnames, one per line. Dr. Venema suggests not using comments in this file, so as "to avoid surprises."

There were two other interesting things we did in the "quick and dirty" procedure. One was to start Postfix with the command postfix start. Just as BIND uses ndc (or rndc) to control the various processes that comprise BIND, the postfix command can be used to manage Postfix.

The most common invocations of the postfix command are postfix start, postfix stop, and postfix reload. start and stop are obvious; reload causes postfix to reload its configuration files without stopping and restarting. Another handy one is postfix flush, which forces Postfix to attempt to send all queued messages immediately. This is useful after changing a setting that may have been causing problems: in the event that your change worked, all messages delayed by the problem will go out immediately. (They would go out regardless, but not as quickly).

In Step 6, we added a line to /etc/aliases to divert root's email to an unprivileged account. This is healthy paranoia: we don't want to log in as the superuser for mundane activities such as viewing system reports, which are sometimes emailed to root.

Be careful, however: if your unprivileged account uses a .forward file to forward your mail to some other system, you may wind up sending administrative messages in clear text over public bandwidth!

7.5.5 Hiding Internal Email Addresses by Masquerading

To prevent giving out information that serves no legitimate purpose, it's wise to set the parameter masquerade_domains = $mydomain in the main.cf file (remember, the string $mydomain refers to a variable and will be substituted with the domain name you specified as part of the variable myhostname). This will strip internal hostnames from the FQDSs in From: addresses of outbound messages.

If you wish to make an exception for mail sent by root, you can set the parameter masquerade_exceptions = root. This is probably a good idea, especially if you have one or more processes that send host-specific warnings or other messages as root. For example, if you configure a log watcher like Swatch, described in Chapter 10, to send you email whenever the filesystem starts to fill up, that email will be more useful if you know which host sent it!

In general, however, you will want most outbound mail to be masqueraded with domain names rather than hostnames.

7.5.6 Running Postfix in a chroot Jail

One of the niftier things you can do to secure Postfix is to run selected parts of it chrooted (see Chapter 6 for more information on the chroot technique). This usually requires you to create copies of things needed by the chrooted process. For example, if the process looks for /etc/mydaemon.conf on startup but is chrooted to /var/mydaemon, the process will actually look for mydaemon.conf in /var/mydaemon/etc/mydaemon.conf.

Happily, the preparations required to chroot Postfix are explained for a variety of architectures, including Linux, in the examples/chroot-setup subdirectory of the Postfix source code. If you install Postfix from a binary package, the package may have an installation script to make these preparations for you automatically after installing Postfix. In SuSE, for example, the Postfix RPM package runs a script that creates a complete directory tree for chrooted Postfix processes to use (etc, usr, lib, and so forth). This directory tree then resides in /var/spool/postfix (the default Postfix home directory and therefore the logical place to chroot its processes to), with the appropriate ownerships and permissions preset.

If your binary distribution doesn't do this for you, simply download the current Postfix source code from http://www.postfix.org and extract the examples/chroot-setup directory to obtain the chroot script LINUX2. If your Postfix home directory isn't /var/spool/postfix, set (and export) the environment variable POSTFIX_DIR to the correct path before running the chroot script, e.g.:

bash-# export POSTFIX_DIR=/var/postfix

bash-# ./LINUX2

If you install a SuSE RPM, you should immediately change your working directory to /var/spool/postfix and make sure that the directories bin (if present), etc, lib, and usr are owned by root:root and not by postfix:postdrop.

As of this writing, SuSE's Postfix postinstallation scripts use the command chown -R postfix /var/spool/postfix/*, which according to Matthias Andree's Bugtraq posting of 12/04/2001 is problematic for two reasons. First, it gives Postfix's chrooted processes inappropriate control over its local copies of configuration files and system libraries; second, it can create a race condition.

After provisioning Postfix's chroot jail, you'll need to edit /etc/postfix/master.cf to toggle the Postfix-daemons you wish to run chrooted (i.e., by putting a "y" in the "chroot" column of each daemon to be chrooted). Do not, however, do this for entries that use the commands pipe, local, or virtual (i.e., entries with pipe, local, or virtual in the "command" column): generally, you can't chroot processes that deliver mail on the server itself. Some binary-package distributions (such as SuSE's) automatically toggle the appropriate daemons to chroot during Postfix installation.

Example 7-20 shows part of a master.cf file.

Example 7-20. A master.cf file
# ==========================================================================

# service type  private unpriv  chroot  wakeup  maxproc command + args

#               (yes)   (yes)   (yes)   (never) (50)

# ==========================================================================

smtp      inet  n       -       y       -       -       smtpd

pickup    unix  n       n       y       60      1       pickup

cleanup   unix  -       -       y       -       0       cleanup

qmgr      unix  n       -       y       300     1       qmgr

#qmgr     fifo  n       -       n       300     1       nqmgr

tlsmgr    fifo  -       -       n       300     1       tlsmgr

rewrite   unix  -       -       y       -       -       trivial-rewrite

bounce    unix  -       -       y       -       0       bounce

defer     unix  -       -       y       -       0       bounce

flush     unix  -       -       n       1000?   0       flush

smtp      unix  -       -       y       -       -       smtp

showq     unix  n       -       y       -       -       showq

error     unix  -       -       y       -       -       error

local     unix  -       n       n       -       -       local

lmtp      unix  -       -       y       -       -       lmtp

procmail  unix  -       n       n       -       -       pipe

    flags=R user=cyrus argv=/usr/bin/procmail -t -m

    USER=${user} EXT=${extension} /etc/procmailrc

After configuring the chroot jail and editing master.cf, all you need to do is start Postfix the way you normally would: postfix start.

7.5.7 Postfix Aliases, Revealed

You probably don't want your users connecting to and storing mail on a publicly accessible server. The greater the separation between public servers and private servers, the better. (Don't forget, POP3 passwords are transmitted in clear text by default.)

As alluded to in the quick and dirty procedure, aliases are also useful for mapping email addresses for users who don't actually have accounts on the SMTP gateway. This practice has two main benefits: first, most users prefer meaningful email names and short host-domain names, e.g., john.smith@acme.com rather than jsmith023@mail77.midwest.acme.com.

Still another use of aliases is the maintenance of mailing lists. If an alias points to a comma-separated list of addresses rather than a single address, mail sent to that alias will be copied and sent to all specified addresses, i.e., to the mailing list.

The addresses that comprise a mailing list can also be stored in a separate file (each address on its own line). To specify an entry in aliases whose target is the name of such a file, be sure to use the :include: tag as shown in the second-to-last line of Example 7-21. Without this tag, Postfix will append mail to the file specified rather than sending mail to the recipients listed therein. (This is a feature, not a bug; it's useful sometimes to write certain types of messages to a text file rather than to a mailbox.)

Example 7-21. Excerpt from /etc/aliases
postmaster:       root

mailer-daemon:    root

hostmaster:       root

root:             bdewinter

mailguys:         bdewinter,mick.bauer

mick.bauer:       mbauer@biscuit.stpaul.dogpeople.org

clients:          :include:/etc/postfix/clientlist.txt

spam-reports:     /home/bdewinter/spambucket.txt

One caveat: if an alias points to a different mail server, that server must belong to a domain for which the SMTP gateway is configured to relay mail (i.e., either that server's FQDN or its domain must be listed in the relay_domains declaration in main.cf).

Don't forget to run postalias /etc/aliases any time you edit aliases. postalias converts the alias file into a database file that can be searched repeatedly and rapidly each time a destination address is parsed; neither Postfix nor Sendmail directly use the text version of aliases.

7.5.8 Keeping out Unsolicited Commercial Email (UCE)

Postfix offers protection against UCE via several settings in main.cf. Some caution is in order, however: there's a fine line between spam and legitimate dissemination, and it's entirely possible that even modest UCE controls will cause some legitimate (i.e., desired) mail to be dropped.

Having said that, for most sites this is an acceptable risk (avoidable, too, through end-user education), and we recommend that at a minimum you set the following in main.cf (for a complete list of anti-UCE parameters and their exact syntax, see /etc/postfix/sample-smtpd.cf):

smtpd_recipient_limit

Indicates how many recipients the SMTP server will accept per message delivery, i.e., how many SMTP RCPT TO commands may be sent by an SMTP client in a single delivery. Normally, this should not exceed 250 or so. (Anyone who needs to send one message to this many users should be sending it to an email list server such as majordomo, not to individual recipients.)

smtpd_recipient_restrictions

Instructs Postfix to check each message's recipient address against one or more criteria. One of the easiest to maintain is the access database. This file lists domains, hosts, networks, and users who are allowed to receive mail from your server. To enable it:

  1. Set check_recipient_access = hash:/etc/postfix/access

  2. Specify a relaying policy with smtp_recipient_restrictions, e.g.:

    smtpd_recipient restrictions =
    
           permit_mynetworks
    
           hash:/etc/postfix/access
    
           reject_unauth_destination
  3. Create /etc/postfix/access (do a man 5 access for format/syntax)

  4. Run postmap hash:/etc/postfix/access to convert the file into a database. Repeat Step 4 each time you edit /etc/postfix/access.

smtpd_client_restrictions

Use this parameter to block mail from specific senders or originating domains. Senders to block may be named both specifically, via an external mapfile such as the access database, and generally, via values such as the following:

reject_maps_rbl

Enables use of the Real Time Blackhole List described in Section 7.6.2 of this chapter; this requires maps_rbl_domains to be set

reject_unknown_client

Rejects mail from clients whose hostname can't be determined

See the file /etc/postfix/sample-smtpd.cf for a full list of valid smtpd_client_restrictions settings.

maps_rbl_domains

Specifies one or more Blackhole database providers, e.g. blackholes.mail-abuse.org.