3. Postfix

Postfix is a MTA (Mail Transport Agent) developed by Wietse Venema as an alternative to the widely-used Sendmail program; it attempts to be fast, easy to administer, and secure. The outside has a definite Sendmail-ish flavor, but the inside is completely different. Postfix also comes with excellent documentation and lots of howtos.

Our mail server requirements will be quite simple: it will be final destination solely for its canonical domains and it will only relay mail from systems on the internal network (though we will also consider relaying from untrusted networks by means of SMTP authentication). Canonical domains include the hostname (in our case, "mail.kernel-panic.it") and the IP address (172.16.240.150) of the machine that Postfix runs on, and the parent domain of the hostname ("kernel-panic.it").

Canonical domains are usually implemented with the Postfix local domain address class, which, unfortunately, has one major drawback for me: it requires that each e-mail account have a corresponding Unix account. On the contrary, I prefer:

  1. keeping Unix and e-mail accounts separate and
  2. having all mailboxes well-ordered inside a single directory.

Therefore, we will use Postfix Virtual Domain Hosting, which is normally used for hosting multiple internet domains on the same server, but will also allow us to achieve the above goals.

3.1 Configuration

In this paragraph, we will configure Postfix to work standalone, with no back-end database. Then, in the next chapter, when everything will be working fine, we will hook up Postfix to a MySQL database; this will allow us to centrally store configuration information that both Postfix and Courier-IMAP will need to access.

There are a few packages we need to install:

Note: if you're planning to use SMTP authentication, you will need to compile Postfix from the ports, because there's no pre-compiled package available with both MySQL and SASL support:

# cd /usr/ports/mail/postfix/snapshot
# env FLAVOR="mysql sasl2" make install

The installation will create the /etc/postfix directory, containing all the configuration files. Postfix has several hundred configuration parameters that are controlled via the /etc/postfix/main.cf file, but don't worry: for the vast majority of these parameters, the default value is the best option (see postconf(5) for a detailed list of all the available configuration parameters, their description and default value) and we will only have to override a very small subset of them:

/etc/postfix/main.cf
# Directory containing all the post* commands
command_directory = /usr/local/sbin
# Directory containing all the Postfix daemon programs
daemon_directory = /usr/local/libexec/postfix
# Location of the Postfix queue and root directory of chrooted Postfix daemons
queue_directory = /var/spool/postfix

# Full pathnames of various Postfix commands
sendmail_path = /usr/local/sbin/sendmail
newaliases_path = /usr/local/sbin/newaliases
mailq_path = /usr/local/sbin/mailq

# Directories containing documentation
html_directory = /usr/local/share/doc/postfix/html
manpage_directory = /usr/local/man
readme_directory = /usr/local/share/doc/postfix/readme

# The owner of the Postfix queue and of most Postfix daemon processes
mail_owner = _postfix
# The group for mail submission and queue management commands
setgid_group = _postdrop

# The myhostname parameter specifies the internet hostname of this mail system. It is
# used as default for many other configuration parameters (default = system's FQDN)
myhostname = mail.kernel-panic.it

# The internet domain name of this mail system. Used as default for many other
# configuration parameters (default = $myhostname minus the first component)
mydomain = kernel-panic.it

# The domain name that locally-posted mail appears to come from, and that locally posted
# mail is delivered to. As you can see, a parameter value may refer to other parameters
myorigin = $myhostname

# Network interface addresses that this mail system receives mail on
inet_interfaces = all

# Network interface addresses that this mail system receives mail on by way of a
# proxy or NAT unit
proxy_interfaces = router.kernel-panic.it

# List of domains that this machine considers itself the final destination for.
# Virtual domains must not be specified here
mydestination = $myhostname, localhost.$mydomain, localhost

# List of "trusted" SMTP clients allowed to relay mail through Postfix.
mynetworks = 127.0.0.0/8, 172.16.0.0/24, 172.16.240.0/24

# What destination (sub)domains this system will relay mail to
relay_domains = $mydestination

# The default host to send mail to when no entry is matched in the optional
# transport(5) table. Square brackets turn off MX lookups
relayhost = [smtp.isp.com]

# List of alias databases used by the local delivery agent
alias_maps = hash:/etc/postfix/aliases

# Alias database(s) built with "newaliases" or "sendmail -bi". This is a separate
# configuration parameter, because alias_maps may specify tables that are not
# necessarily all under control by Postfix
alias_database = hash:/etc/postfix/aliases

# SMTP greeting banner
smtpd_banner = $myhostname ESMTP $mail_name

# Postfix is final destination for the specified list of "virtual" domains
virtual_mailbox_domains = kernel-panic.it

# Virtual mailboxes base directory
virtual_mailbox_base = /var/vmail

# Optional lookup tables with all valid addresses in the domains that match
# $virtual_mailbox_domains.
virtual_mailbox_maps = hash:/etc/postfix/vmailbox

# The minimum user ID value accepted by the virtual(8) delivery agent
virtual_minimum_uid = 2000

# User ID that the virtual(8) delivery agent uses while writing to the recipient's mailbox
virtual_uid_maps = static:2000

# Group ID that the virtual(8) delivery agent uses while writing to the recipient's mailbox
virtual_gid_maps = static:2000

# Optional lookup tables that alias specific mail addresses or domains to other local or
# remote address
virtual_alias_maps = hash:/etc/postfix/virtual

Let's take a closer look at some of the above configuration parameters.

One of our initial goals was to avoid having a separate Unix account for each e-mail account. We have achieved this by configuring Postfix to write to the mailboxes using uid 2000 and gid 2000 (see the virtual_uid_maps and virtual_gid_maps parameters above). Now we only have to create a user with this pair of uid and gid:

# useradd -d /var/vmail -g =uid -u 2000 -s /sbin/nologin \
> -c "Virtual Mailboxes Owner" -m vmail

Our second goal was to have all mailboxes grouped together in a single directory; this is achieved by setting the value of the virtual_mailbox_base parameter to the path of that directory (in our configuration, /var/vmail). In matter of fact, this parameter is a prefix that the virtual(8) agent prepends to all pathname results from virtual_mailbox_maps table lookups.

In our configuration, the virtual_mailbox_maps parameter refers to the /etc/postfix/vmailbox file, containing the list of all valid addresses in the virtual domains (virtual_mailbox_domains parameter) and the path to the corresponding mailboxes or maildirs (a mailbox is a single file containing all the emails; a maildir, instead, is a directory, with a particular structure, containing all the emails in separate files):

/etc/postfix/vmailbox
info@kernel-panic.it          kernel-panic.it/info/
d.mazzocchio@kernel-panic.it  kernel-panic.it/d.mazzocchio/
[...]

Please pay attention to the trailing slashes: they tell Postfix that the pathname refers to a maildir instead of a mailbox file, and maildirs are our only option, since Courier-IMAP doesn't support mailbox files.

The virtual_alias_maps parameter allows you to alias specific mail addresses or domains to other local or remote addresses. Its value is the pathname to a file (in our case /etc/postfix/virtual) containing the alias mappings:

/etc/postfix/virtual
root@kernel-panic.it		root@localhost.kernel-panic.it
postmaster@kernel-panic.it	postmaster@localhost.kernel-panic.it
abuse@kernel-panic.it		postmaster@localhost.kernel-panic.it
[...]

Finally, the /etc/postfix/aliases file contains the addresses to which Postfix will redirect mail for local recipients (see aliases(5)). Since many accounts point to root's email address, you should check root email frequently or forward it all to another account. E.g.:

/etc/postfix/aliases
root: d.mazzocchio@kernel-panic.it
MAILER-DAEMON: postmaster
postmaster: root
bin: root
[...]

Now we only have to update Postfix lookup tables:

# /usr/local/sbin/postmap /etc/postfix/vmailbox
# /usr/local/sbin/postmap /etc/postfix/virtual
# /usr/local/sbin/newaliases

replace Sendmail:

# /usr/local/sbin/postfix-enable
old /etc/mailer.conf saved as /etc/mailer.conf.pre-postfix
postfix /etc/mailer.conf enabled

NOTE: do not forget to add sendmail_flags=NO to
      /etc/rc.conf.local to disable sendmail.

NOTE: do not forget to add "-a /var/spool/postfix/dev/log" to
      syslogd_flags in /etc/rc.conf.local and restart syslogd.

NOTE: do not forget to add postfix to pkg_scripts in
      /etc/rc.conf.local to start postfix automatically.

NOTE: do not forget to remove the "sendmail clientmqueue runner"
      from root's crontab.

and follow the above advice, by commenting out the "sendmail clientmqueue runner" in root's crontab:

# sendmail clientmqueue runner
#*/30 * * * * /usr/sbin/sendmail -L sm-msp-queue -Ac -q

and adding the appropriate configuration to /etc/rc.conf.local(8).

/etc/rc.conf.local
# Specify an additional syslogd(8) socket for Postfix
syslogd_flags="-a /var/spool/postfix/dev/log"

# Disable sendmail
sendmail_flags="NO"

# Start postfix on boot
pkg_scripts="postfix"

Now we can change a few permissions and restart the processes (or simply reboot):

# pkill syslogd
# syslogd -a /var/empty/dev/log -a /var/spool/postfix/dev/log
# pkill sendmail
# /etc/rc.d/postfix start
postfix(ok)

and test our hard work!

# telnet mail.kernel-panic.it 25
Trying 172.16.240.150...
Connected to mail.kernel-panic.it.
Escape character is '^]'.
220 mail.kernel-panic.it ESMTP Postfix
HELO somedomain.org
250 mail.kernel-panic.it
mail from: someone@somedomain.org
250 Ok
rcpt to: d.mazzocchio@kernel-panic.it
250 Ok
data
354 End data with <CR><LF>.<CR><LF>
From: someone@somedomain.org
To: d.mazzocchio@kernel-panic.it
Subject: Test mail

It works!
.
250 Ok: queued as 548D7286
quit
221 Bye
Connection closed by foreign host.
# tail /var/log/maillog
Dec 16 15:26:35 mail postfix/smtpd[29212]: connect from ws1.lan.kernel-panic.it[172.16.0.15]
Dec 16 15:26:53 mail postfix/smtpd[29212]: 57076222: client=ws1.lan.kernel-panic.it[172.16.0.15]
Dec 16 15:27:02 mail postfix/cleanup[13428]: 57076222: message-id=<20070210142653.57076222@mail.kernel-panic.it>
Dec 16 15:27:02 mail postfix/qmgr[26776]: 57076222: from=<someone@somedomain.org>, size=392, nrcpt=1 (queue active)
Dec 16 15:27:02 mail postfix/virtual[14381]: 57076222: to=<d.mazzocchio@kernel-panic.it>, relay=virtual, delay=15,
delays=15/0.28/0/0.03, dsn=2.0.0, status=sent (delivered to maildir)
Dec 16 15:27:02 mail postfix/qmgr[26776]: 57076222: removed
Dec 16 15:27:06 mail postfix/smtpd[29212]: disconnect from ws1.lan.kernel-panic.it[172.16.0.15]
# cat /var/vmail/kernel-panic.it/d.mazzocchio/new/1118146014.V3I9448M811660.mail.kernel-panic.it
Return-Path: <someone@somedomain.org>
X-Original-To: d.mazzocchio@kernel-panic.it
Delivered-To: d.mazzocchio@kernel-panic.it
Received: from somedomain.org (ws1.lan.kernel-panic.it [172.16.0.15])
	by mail.kernel-panic.it (Postfix) with SMTP id 57076222
	for <d.mazzocchio@kernel-panic.it> Sat, 16 Dec 2007 15:26:47 +0100 (CET)
From: someone@somedomain.org
To: d.mazzocchio@kernel-panic.it
Subject: Test mail
Message-Id: <20070210142653.57076222@mail.kernel-panic.it>
Date: Sat, 16 Dec 2007 15:26:47 +0100 (CET)

It works!
#