Table of Contents

Overview

Q: What does this doc do?
A: By the end of this doc, you should have a functional SMTP and IMAP system, with e-mail aliases and passwords stored in a database, and actual messages stored within Cyrus rather than in some clunky "universal" home directory, as is prescribed in existing doc. Postfix will handle the SMTP side of things, inclusive of ensuring no messages for invalid addresses are accepted by checking recipients against a mysql database containing emails and their corresponding passwords, as well routing messages to a place the IMAP server knows about. The IMAP server will authenticate users against this same database, and upon successful authentication, pass the messages on to the user's mail reader. This doc tells you how to get Postfix to give the messages to Cyrus, and how to have both Cyrus and Postfix lookup user account information from a database.

Q: Can you explain more about doing $foo or variation $bar?
A: Probably. E-mail docs@whitehathouston.com with a sensible subject (e.g. 'Postfix Guide Suggestions'), and I'll figure out the best way to produce an addendum or clarify an existing section. I peek at analytics results from time to time in order to see why people land on this page. Many of the keywords they use are questions I can answer, but that are unrelated to this guide. So, whatever I can relatively easily answer, I'm happy to - time permitting, naturally, I do have a paying job, so I only update content on this site either when the whim hits me, or every month or two, whichever comes first.

Q: There's already plenty of doc, why bother?
A: I just plain don't like Courier-IMAP, don't like the idea of putting everything into /home/vmail, and find some of the existing doc adds in extraneous nonsense that's completely unnecessary and unrelated, without telling you "hey, this piece isn't *needed* necessarily, it's just *cough*excess bloat*cough* sorry, it's just "making some ease of use assumptions with no regard for security". To see the discussion thread which prompted the writing of this document, which may well be kept up to date more regularly than this document, head over to this thread on the Gentoo forums for the latest changes/discussion.

Q: Can I skip the other docs and just read this?
A: Probably so. While it would be good to have at least a vague familiarity with Postfix, Cyrus, SASL, MySQL, and how the pieces tie together, it's not strictly necessary. By the end of this, I will have explained to you not only *what* to do and how to do it, but *why* it's done this way, and what doing it in such a way accomplishes. Frankly, the other docs are all over the place. They describe one way of doing things, a sub-optimal way of doing things, and don't really explain along the way WHY they're doing something, or what it means. So ultimately you end up with a ton of people with the exact same setup, having blindly followed a configuration guide, and because they don't understand what does what, they don't know how they'd deviate in even the slightest way. And if something breaks along the way, or it doesn't work out of the box? Dear god man, they're marooned, no chance they'll dig themselves out without a fair bit of pain, or fair bit of help.


Getting Started

Before I just have you start hammering away at your keyboard like a drone on autopilot, it is probably useful to explain how each of these pieces tie together, as well give a brief overview of which pieces have which responsibilities. By the end of this section, if I've written this well, you should be saying to yourself "excellent, I know what needs to be done - now, how do I actually install things and configure things to make it actually DO $that?"


Relevant Pieces

Postfix: Postfix will handle the SMTP portion of a message's lifespan. When a user on another system wishes to connect to your server, and send you e-mail, the actual sending is done via the SMTP protocol. The 'smtpd' portion of Postfix will handle accepting incoming mail from a remote host. Likewise, when you go to send an e-mail to someone else on a remote system, the actual transmission of the message over the wire will be done via Postfix, using its 'smtp' process (not to be confused with smtpd process - even though the two both deal with the SMTP protocol, smtpd is for receiving mail inbound, smtp is for sending mail outbound; this is a Postfix-specific designation, not an SMTP protocol specific designation)). In addition to handling the transmission or receipt of an e-mail across the wire, for incoming mail, Postfix also has to, once the message is accepted, process it and put it into a place where it can be read. For some setups, it will simply place the message beneath the user's home directory, in a subdirectory named '.maildir'. For this setup, instead of Postfix storing the message itself, it will pass the message off to Cyrus IMAP via a different protocol, LMTP

Cyrus IMAP: It's all well and good for Postfix to put the e-mail message somewhere, but how is it stored, and how does one fetch the message from storage so they can actually read it? This is where protocols such as POP and IMAP come into play. In this case, we will be using IMAP, and the package of choice for this is Cyrus. When you connect your mail client (e.g. Thunderbird, KMail, Outlook) to a mail system and check your e-mail, it will normally either be done via POP or IMAP (Exchange users: you're likely using MAPI, which is a hideous abomination, albeit a mostly functional one). Think of it as a regular old snail mailbox - your local postman is Postfix, and you are well, you. The LMTP connection is a common place where Postfix and Cyrus can meet up for tea, and Postfix can unload all of grandma's postcards from Zanzibar. Cyrus will take messages sent over this LMTP connection, and store them in its own little database format, in its own little storage directory rather than under a user's home directory. While this may seem like an extra layer of complexity, it actually *simplifies* a number of things, namely your dependency on .maildir, and other solutions where every user's mail is stored under /home/vmail/.maildir only segregated per-account via some level of hideous wizardry. When the e-mail finally finds its home in Cyrus' internal storage database, a user can connect via IMAP, ask Cyrus if it has any mail for them, and if it does the message is transmitted to and shown within the user's mail client. The key thing to remember, above all else, is that IMAP (Cyrus) is for storing, fetching, and reading, whereas SMTP is for sending and receiving.

Database: Traditionally, a database plays absolutely zero part in the actual sending, receiving, storing, reading, or fetching, of an e-mail. It is certainly possible, and often done, for SMTP and IMAP servers to operate completely independent of a database. The reason for its inclusion here, is that it's an easy way for someone to have a single spot on there system that has a list of all valid e-mail addresses and passwords. There are countless other ways you can do this. For example, most big commercial operations would use an LDAP server, and tell their SMTP/IMAP systems to validate users directly against LDAP. Many smaller operations will simply add a new Linux user to the system, and have their login credentials validated against /etc/shadow. With Cyrus, there's yet another option - one we won't be using - of firing up 'cyradm', the Cyrus mailbox administration tool, and adding as well defining permissions for mailboxes. If you feel like learning cyradm (which is poorly documented), you don't need a database at all for validating users on the IMAP side, though, for ESMTP authentication, Postfix still needs to have a list it can check somewhere. Cyrus can realistically just validate users against an sasl database, as can Postfix, but again you're stuck with the task of manually adding/deleting users with an unfamiliar tool. Thus comes in using an RDBMS, such as MySQL or SQLite. Most users, if they're not already familiar, can fairly easily pick up the few basics of adding/removing data from say, MySQL (e.g. 'INSERT', 'UPDATE', 'DELETE', 'SELECT').

So how do Postfix and Cyrus use the database? Postfix will use it in two ways: 1)it can check the recipient of an incoming message against the list of addresses in the database, and if it doesn't exist, reject it. 2)Postfix can use SASL for authentication, which allows a user to provide their login name and password to the server, and if correct, the server will allow them to send outbound mail from wherever they are in the world. SASL can very easily be configured to check login credentials against a MySQL database. Starting off in this guide, I will only provide instructions for MySQL, as I am far more familiar with it than SQLite. However, as this database isn't going to be particularly large nor resource-intensive, MySQL is realistically overkill, and SQLite should be perfectly suitable unless you have thousands upon thousands of users, and you have multiple people updating the database at the same time. Nonetheless, for most MySQL is going to be the most familiar, if nothing else because they may already have it installed, or may have installed it a thousand times over and have the process committed to memory. Of course, if you do happen to already have a working MySQL installation, or you're already intimately familiar with its setup, all the better, you can skip right over the installation section for MySQL.

Configuration Prerequisites

Postfix

-built with the ssl sasl mysql pam USE flags enabled
-the following configuration files changed (detailed later)

Cyrus

-built with the autocreate pam ssl tcpd USE flags enabled
-the following configuration files changed (detailed later)

Database

If you are querying a remote mysql server, you can enabled the minimal USE flag. The machine where the database is to be housed, however, must NOT have mysql built with the minimal USE flag set. So if 'dbserver' is a remote mysql server, and 'mailserver' is your postfix/cyrus server, you must USE="-minimal" emerge dev-db/mysql on dbserver, and you can safely USE="minimal" emerge dev-db/mysql on mailserver. For installations where everything is on the same physical hardware (e.g. an "all in one" install), mysql must NOT be built with the minimal USE flag set.
It should go without saying, but shall be said nonetheless; you must also have permissions to create and modify databases on the server where the database will sit. Postfix and Cyrus, however, need only read permissions on the database itself.

Package Installation

Before we can begin hacking about with configuration files, we have to get the packages built, make sure they're built with support for all of the different features we're going to use, and get them installed. By the end of this (brief) section, you should have all of the pieces we need built correctly, installed, and set to run on startup every time you boot your machine.



Installing Postfix

The actual installation process for Postfix on Gentoo should be relatively easy. We have the benefit of a wonderful package management system that automatically resolves dependencies, and if you set up your build environment correctly, will have all of the dependencies necessary to make this guide work.

Before we begin, however, we should go ahead and set up the environment not only for postfix, but for all of the custom motifications we'll want for the rest of the packages. Namely, we need to set the correct USE flags so that Postfix is built with all of the goodies we need to make this guide work. Now, obviously some of these can be set in make.conf, and thusly applied to your entire box, however going with the lowest common denominator, we'll set these on a per-package basis via package.use.

If you're already making use of package.use, then you should already know how to adjust these steps accordingly. If you aren't, then you can go ahead and key this stuff in verbatim. So let's get start; we need the following USE flags enabled:

You can enable these USE flags for Postfix by doing the following:

mkdir -p /etc/portage/package.use
echo "mail-mta/postfix ssl sasl mysql pam" >> /etc/portage/package.use/postfix

In addition to setting up USE flags for Postfix, we must also make sure the cyrus-sasl package, which we'll later use for querying logins against a mysql database, is built with support for querying a mysql database.

echo "dev-libs/cyrus-sasl mysql" >> /etc/portage/package.use/sasl

Now all that's left is emerging Postfix, and Cyrus-SASL:

emerge -av postfix cyrus-sasl
rc-update add postfix default
rc-update add saslauthd default

With this, all of the binaries, documentation, and example configuration files, should be in place, and Postfix plus saslauthd will start up automatically next time you reboot. We're far from done on the Postfix side of things, we still have to get it configured, but before we do that we'll take care of installing the other packages we need.

Installing Cyrus

Installing Cyrus is a bit trickier, because at the time of this writing, none of the ebuilds available in Portage include the "autocreate" patch, without which all of this is a hell of a lot more difficult as you have to learn how to use cyradm to create mailboxes. Realistically, since Postfix is going to already be doing the job of making sure you don't receive mail for invalid addresses, why should Cyrus bother doing the validation? The autocreate patch means that Cyrus will automatically create a new mailbox for any previously unknown e-mail address it sees. While this may sound a bit sketchy, again, Postfix has already done the recipient validation (or well, it will by the end of this guide), so there's really no reason to worry. In order to make use of the 'autocreate' feature/patch, you will need to install Cyrus using a custom ebuild.

UPDATE: looks like cyrus 2.4.8 has hit portage. I have an updated 2.4.8 ebuild in the ./downloads section of this site, though I highly recommend going with 2.3.16

UPDATE: I've uploaded a 2.4.12 ebuild. Working fine for me in production

Now, if that sounds troubling, fret not - this doc will talk you through getting the custom ebuild, adding it to an overlay, and emerging, step by step.

First, lets set up the directories we need for a local overlay:

mkdir -p /root/overlays/cyrus/net-mail/cyrus-imapd/files
cd /root/overlays/cyrus/net-mail/cyrus-imapd
wget http://whitehathouston.com/downloads/gentoo/ebuilds/cyrus/net-mail/cyrus-imapd/cyrus-imapd-2.3.16.ebuild
wget http://whitehathouston.com/downloads/gentoo/ebuilds/cyrus/net-mail/cyrus-imapd/metadata.xml
cp -r /usr/portage/net-mail/cyrus-imapd/files/* /root/overlays/cyrus/net-mail/cyrus-imapd/files/
ebuild cyrus-imapd-2.3.16.ebuild digest

That sets up the overlay. Now we have to tell portage the overlay exists. Fire up your favorite text editor, edit /etc/make.conf, and add the following line at the very bottom of make.conf, beneath everything else:

PORTDIR_OVERLAY="${PORTDIR_OVERLAY} /root/overlays/cyrus"

That's it! The overlay is set up. Hopefully at some point I can get this committed to Portage so you don't have to go through all of that, but for the time being, I'm maintaining that ebuild myself. At any rate, now we have to do for Cyrus what we did for Postfix, and set up its USE flags correctly. Assuming you created a package.use directory as I suggested for Postfix earlier, you need only type this to set the USE, and emerge Cyrus:

echo "net-mail/cyrus-imapd autocreate pam ssl tcpd" >> /etc/portage/package.use/cyrus
emerge -av =net-mail/cyrus-imapd-2.3.16
rc-update add cyrus default

If the versions change, or this package is masked for whatever reason, ping me on the Gentoo Forums and I'll update the ebuild, as well update the instructions accordingly

Installing MySQL

With Cyrus and Postfix installed, we now need to install some form of database engine, so that later we can create a database, and add to it a list of e-mail aliases and passwords.

Unless you're running one of the desktop profiles, MySQL should already have the correct USE flags set by default. However, just to be sure, let's go ahead and specify those again via package.use, then install the package:

echo "dev-db/mysql -embedded -minimal" >> /etc/portage/package.use/mysql
emerge -av dev-db/mysql

It'll take a while to build on a slower system, so go have yourself a cup of tea. When you get back, we're not quite finished installing MySQL, as we have to run a few more commands to set up its core database, plus tighten down permissions so that you're not wide open.

With your tea in hand, now that the package is finished, we need to run two more commands, and follow the on-screen wizard to completion:

mysql_install_db
mysql_secure_installation
rc-update add mysql default

That should install MySQL's appropriately named default database, 'mysql', as well it will ask you to set a password for anyone who wishes to logon to the machine as root.

Initial Package Configuration

By the end of this section, you should have a functional Postfix installation, accepting mail for a single domain, and sending it to Cyrus IMAP for retrieval via LMTP. For the purposes of this demonstration, we will be configuring mail for my 'whitehathouston.com' domain, on my mail server, named 'renee.whitehathouston.com' after the stunning character from the US TV series "24".



Initial Postfix Setup

With all of the tedious minutiae of installing the necessary packages out of the way, now comes time to actually configure each to do what you'd like for it to do. We'll start with a basic Postfix configuration which will accept mail for 'whitehathouston.com'. If you're wondering how to add more domains, validate recipients, and so forth, I'll get to that later, and post the requisite modifications at that stage. For now, we're just going to get basic pieces put together.

For this initial configuration phase, there are only two files we're going to concern ourselves with:

For starters, go ahead and mv your /etc/postfix/master.cf to /etc/postfix/master.default, and mv your /etc/postfix/main.cf to /etc/postfix/main.default. There are plenty of great comments in both of those files, but I find people tend to get lost if they try to go through them top to bottom - if you wanted to do that, you wouldn't be taking the shortcut of reading this doc!

With those files moved out of the way, first fire up your favorite text editor, and we'll be editing a new file, /etc/postfix/master.cf. You can go ahead and copy/paste the following verbatim; while I would like to explain as much as possible about what does what in each config file, it's just not very interesting for master.cf:

smtp      inet  n       -       n       -       -       smtpd
pickup    fifo  n       -       n       60      1       pickup
cleanup   unix  n       -       n       -       0       cleanup
qmgr      fifo  n       -       n       300     1       qmgr
tlsmgr    unix  -       -       n       1000?   1       tlsmgr
rewrite   unix  -       -       n       -       -       trivial-rewrite
bounce    unix  -       -       n       -       0       bounce
defer     unix  -       -       n       -       0       bounce
trace     unix  -       -       n       -       0       bounce
verify    unix  -       -       n       -       1       verify
flush     unix  n       -       n       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       n       -       -       smtp
relay     unix  -       -       n       -       -       smtp
        -o smtp_fallback_relay=
showq     unix  n       -       n       -       -       showq
error     unix  -       -       n       -       -       error
retry     unix  -       -       n       -       -       error
discard   unix  -       -       n       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       n       -       -       lmtp
anvil     unix  -       -       n       -       1       anvil
scache    unix  -       -       n       -       1       scache

The main pieces to pay attention to above, are the 'smtpd' line, which tells the Postfix smtpd process to use a network socket, whilst the 'lmtp' line tells the Postfix lmtp process to use a Unix socket.

Now, the other relevant configuration file is main.cf. For this one, I'll post a basic example for accepting mail for 'whitehathouston.com', but then attempt to explain, moreso than I did for master.cf, what things do (where it actually makes sense to explain them, mind you). One thing to note with main.cf, what you're doing is not modifying a setting from ground zero; what each of these lines are, are overrides from the default settings. You can see the default settings by typing postconf -d in the shell as root:

queue_directory = /var/spool/postfix
message_size_limit = 102400000
mailbox_size_limit = 1024000000
command_directory = /usr/sbin
daemon_directory = /usr/lib64/postfix #newer postfix versions, change to /usr/libexec/postfix
data_directory = /var/lib/postfix
mail_owner = postfix
default_privs = nobody
myhostname = renee.whitehathouston.com
mydomain = whitehathouston.com
myorigin = $myhostname
mailbox_transport = lmtp:unix:/var/imap/socket/lmtp
inet_interfaces = all	#or 'inet_interfaces = ipv4' if you don't use ipv6. Newer Postfix grips about this...
mydestination = $myhostname, localhost, $mydomain
local_recipient_maps =
unknown_local_recipient_reject_code = 550
mynetworks = 75.148.243.88/29, 127.0.0.0/8
mail_spool_directory = /var/spool/mail
smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
local_destination_concurrency_limit = 2
default_destination_concurrency_limit = 20
debug_peer_level = 2
debugger_command =
         PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
         ddd $daemon_directory/$process_name $process_id & sleep 5
sendmail_path = /usr/sbin/sendmail
newaliases_path = /usr/bin/newaliases
mailq_path = /usr/bin/mailq
setgid_group = postdrop
smtpd_tls_security_level = may
smtpd_tls_cert_file = /etc/ssl/postfix/server.crt
smtpd_tls_key_file = /etc/ssl/postfix/server.key
smtpd_tls_CAfile = /etc/ssl/postfix/root.crt
smtpd_tls_ask_ccert = no
smtpd_tls_loglevel = 1
smtpd_recipient_restrictions =
        permit_mynetworks,
        reject_unauth_destination
biff = no
empty_address_recipient = MAILER-DAEMON
tls_random_source = dev:/dev/urandom
smtp_tls_note_starttls_offer = yes

Now, to explain what a few of those do that you might actually be interested in changing. For any of those I don't mention, you should be able to get all of the information you need on the Postconf Configuration Parameters Page. One other thing to point out, any parameters you set one place in main.cf, can be referenced later in main.cf by appending a dollar sign. For example, if you set 'mydomain=foo.com', and you want to include the string 'foo.com' elsewhere in the configuration file, rather than typing it out, you can simply put $mydomain, as you see in the example above.

That should well cover off the particularly interesting settings. Again, for some of the fancier things we intend to do, I'll cover off later (we will be adding to the above!). One other thing to note, depending on your version of Postfix, the paths for certain things may be slightly different from the example. Namely, for 32bit installations, references to /usr/lib64 (amd64/x86_64) should be changed to simply /usr/lib (x86)

Even though Postfix is set up, we're not yet ready to start it up, namely because the mail won't go anywhere, as Cyrus isn't yet set up, but also because we're going to add extra functionality to it later.

Initial Cyrus Setup

With Postfix more or less set up, now we can begin tackling Cyrus. Fortunately, since we're using the autocreate patch, we don't have to go through the headache of setting up a zillion mailboxes, and can instead edit a small few (two) configuration files with only a handful of settings, and have our basic Postfix/Cyrus system up and running. There are two files involved in this, at least, for this early stage of the game:

For starters, let's mv /etc/cyrus.conf to /etc/cyrus.default, mv /etc/imapd.conf to /etc/imapd.default, then fire up a text editor, create a new file at /etc/cyrus.conf, and paste in the following verbatim:

START {
  # Do not delete this entry!
  recover       cmd="ctl_cyrusdb -r"
}

SERVICES {
  # Add or remove based on preferences.
  imap          cmd="imapd" listen="imap2" prefork=0
  #pop3          cmd="pop3d" listen="pop-3" prefork=0

  # Don't forget to generate the needed keys for SSL or TLS
  # (see doc/html/install-configure.html).
  imaps         cmd="imapd -s" listen="imaps" prefork=0
  #pop3s                cmd="pop3d -s" listen="pop3s" prefork=0
  lmtpunix      cmd="lmtpd" listen="/var/imap/socket/lmtp" prefork=0
}

EVENTS {
  checkpoint    cmd="ctl_cyrusdb -c" period=30
  delprune      cmd="ctl_deliver -E 3" period=1440
  tlsprune      cmd="tls_prune" period=1440
}

In the above, the only thing you might conceivably want to change, is the inclusion of POP support. I've commented out both pop3 and pop3s, because I simply don't care for POP (that's another rant - a DLP beef). You should leave everything else as-is, and for darn sure do not deviate from what I've set for 'lmtpunix', unless you genuinely know what you're doing

Now, for tackling imapd.conf, which is where you actually get to play, and where most of the magic happens. Of course, for this initial configuration section, we're not going to make much magic, but rest assured, magic will be made later! Enough chit chat, fire up your favorite text editor, and paste this into /etc/imapd.conf:

configdirectory:        /var/imap
partition-default:      /var/spool/imap
sievedir:               /var/imap/sieve
admins: root meat
tls_ca_path:            /etc/ssl/certs
tls_cert_file:          /etc/ssl/cyrus/server.crt
tls_key_file:           /etc/ssl/cyrus/server.key
virtdomains: on
hashimapspool:          yes
allowanonymouslogin:    no
allowplaintext:         yes
allowusermoves:         yes
sasl_pwcheck_method:    saslauthd
sasldb_path: /etc/sasl2/sasldb2
sasl_mech_list: LOGIN PLAIN
autocreatequota: -1
createonpost: yes

Now, I will readily admit I don't know what every single one of those settings above does. So, as I'm too lazy to look them up (Cyrus is poorly documented anyway), and there are only certain ones you might want to change, I'll just detail the ones I know about

Now, in theory, you could go ahead and startup Postfix, start up Cyrus, and test this puppy out at this point. However, you won't be able to login to IMAP unless you set up your SASL password database, and since we're not going to use the SASL password database, and want to keep our system clean and pristine, it's probably a good idea to hold off until we've finished up with the rest of the guide.

Extended Configuration

Now that we're successfully shuffling mail to and fro, we need to set up a method for users to login, set up a method for Postfix and Cyrus to know which addresses are valid, and which are bogus, as well demonstrate how to do a few additional things, such as adding multiple domains to be handled by this installation, and setting up a bit of basic content filtering. By the end of this section, you should have a completely configured mail system that is by all accounts "commercial grade", yet relatively easily managed.



Adding Login Database

With the basic pieces set up, we can begin putting in place the framework to allow Postfix/Cyrus to validate logins (and in Postfix's case, recipient addresses) against a database.

Before we can do anything else, we need to create the empty database itself, and setup a different login so that our mail systems aren't connecting to the database as root. For the first time since you started reading this guide, you finally get to start up a daemon and actually leave it running(yay!), albeit not one that has anything directly to do with mail (booooo!). I'm sure you've been itching to start *something* up, so go ahead and:

/etc/init.d/mysql start

Assuming that started up successfully without burning your house down, we will create the empty database via:

mysqladmin create maildb -p

Enter the root password you created with mysql_secure_installation whenever it prompts you, and it will create the database. Now that that's done, we should set up a different user and password that Postfix/Cyrus will use for connecting to our 'maildb' database. Since the password is going to be stored plaintext in a file, we can go ahead and make the password as cryptic as we like, as it's always possible to simply read the configuration file where it's stored if you forget it. I normally use some big long hideous string generated from cat'ing /dev/urandom and base64 encoding it, however even easier, emerge the 'net-misc/whois' package, as it actually includes a tool called 'mkpasswd', which is nothing more than a handy tool to help you create complex passwords. You run that command, key in a few characters, it munges them into a slightly more complex string. Mine generated 'vwwP0q2I5UmM6', so we'll use that in this example. The following assumes your Postfix and Cyrus installations are both on the same physical server as MySQL, and will be connecting via a username of 'maildb' to a database also named 'maildb' via 'localhost' - if you are connecting to a remote mysql server, adjust accordingly:

# mysql -p
<enter your root pass - if you see 'mysql>' you're at the mysql command prompt, whereupon you enter the next line>
mysql>grant all privileges on maildb.* to 'maildb'@'localhost' identified by 'vwwP0q2I5UmM6';
mysql>flush privileges;
mysql>quit;

With that out of the way, all we have to do now is create the database structure.

The database is going to be one of the most simplistic ones you've ever seen. It will have one table, named 'aliases', that has a char(255) column that will hold e-mail addresses, and a char(50) column that will hold the cleartext password. I won't cover off encrypting the password in the DB, as that's a huge extra layer of complexity believe it or not (md5 crypt function is easy enough to do on the mysql end, but getting postfix/cyrus to read this is another matter). We will call the column holding the e-mail addresses 'email', and the column holding the passwords 'plainpass'.

To create the database structure per the above specification, go ahead and log back in to the mysql command-line as root via mysql -p, then enter the following:

mysql>use maildb;
mysql>CREATE TABLE aliases (email CHAR(255) CHARACTER SET utf8 COLLATE utf8_bin UNIQUE, plainpass CHAR(50) CHARACTER SET utf8 COLLATE utf8_bin);
mysql>quit;

That's it. Your database is created. Assuming everything was successful, it should look like so:

mysql> describe aliases;
+-----------+-----------+------+-----+---------+-------+
| Field     | Type      | Null | Key | Default | Extra |
+-----------+-----------+------+-----+---------+-------+
| email     | char(255) | YES  | UNI | NULL    |       |
| plainpass | char(50)  | YES  |     | NULL    |       |
+-----------+-----------+------+-----+---------+-------+
2 rows in set (0.02 sec)

Easy enough yeah? Now all we have to do is start adding pairs of e-mail addresses and passwords to it, which I'll detail in the next little mini-section.

Managing or Editing Logins

Now that we have the database structure created, we can control user login credentials by simply adding an e-mail address, and associated password, to the database. If you're relatively comfortable with basic MySQL syntax, this should be a piece of cake. Even if you aren't, I'm going to provide example commands you can type verbatim and test with.

Why that way? Well, here's a slight nit I have with most of the existing docs - they all seem to try and get a user who doesn't know any better to download phpMyAdmin for this, set up Apache, set up phpMyAdmin, so on and so forth. Seriously? Overkill much? For starters, people might not necessarily want Apache on their mail server. Secondly, phpMyAdmin has a long and well-documented history of non-trivial vulnerabilities. I don't want the integrity of my database to be contingent upon my ability to secure a (clunky) webapp that I'm stuck watching like a hawk so I can update it every time a security patch is released. Now, you can password-protect it via .htaccess/htpasswd, but that's another layer of inconvenience. Again, seriously? We're adding two small strings to a simplistic database. There's no reason to put yourself through that huge bit of extra headache for something that may actually prove to be *less* convenient to use, and take such a horrendously huge hit on your system's overall security (nevermind the resource consumption required by PHP). Just as a general rule, I never put a webapp out in public view without hiding it behind htpasswd/htaccess, it's just plainly a bad practice, and frankly, unless you're doing heaps of dynamic content, there's no reason to tie a website into a database - but that's another rant for another day, I'll hush and get on with the e-mail side of things. Long story short: don't use phpMyAdmin, it's overkill, it's intended for novices who don't like reading directions, or people on shared hosting environments (and if you're on a shared hosting environment, you can't follow this guide now can you!).

If I haven't lost you in that rant, in this next example I'll show you how I would add an e-mail address of 'test@whitehathouston.com', with a password of 'awesomepassword'

Again, login with mysql -p as root, and type:

mysql>use maildb;
mysql>INSERT INTO aliases (email,plainpass) VALUES ('test@whitehathouston.com','awesomepassword');

I should now have an 'aliases' table that looks like:

mysql> select * from aliases;
+--------------------------+-----------------+
| email                    | plainpass       |
+--------------------------+-----------------+
| test@whitehathouston.com | awesomepassword |
+--------------------------+-----------------+

Again, uber complicated. If you're worried you'll forget the syntax, don't, because a)I'm not taking this guide down any time soon, b)you can always either chunk this example in a text file on your local system, or write a script to do it (perl or php would do that just fine - time permitting, I'll post up a perl script on this guide that'll semi-automate things)

For the time being, I've thrown up a simple pair of PHP pages that'll at least let you review, add, and delete entries from this database. Adjust the database credentials and host address accordingly:

http://whitehathouston.com/testcode/

For a simple, pared down interface, snag 'userdel.php.gz' and 'useradd.php.gz'. For the semi "pretty" interface I hack on occasionally, you'll want 'AddUser.php.gz', 'DelUser.php.gz', 'AddDomains.php.gz', 'DelDomains.php.gz', and 'db.inc' (they should all go in the same directory with each other - and make sure your web server denies requests to view db.inc!). If you want to see the latest result of my haphazard PHP hackage, check out Yet Another Postfix Management Interface, which will have the latest day's code (though, the latest day == "whenever I feel like it").

Again, adjust those accordingly. Yes, they contain real passwords and IP's in there. No, I didn't forget, these aren't usernames/passwords/dbnames I use in production. This simply isn't valuable data to anyone. Further to that, if you remove the '.gz' portion of that, I have up a live demo. For your own production uses, you would never, ever, ever want to make this publicly accessible from the outside world. These pages don't ask for any sort of authentication, so you should make sure you password-protect the directory where you place them on your web server, should you choose to do so. I don't do so here, because again, although you're connecting to a live DB, the DB is completely and totally meaningless to me, as is the system it's hosted on. I slipped up on input validation, and the server gets 'owned'? I truly don't care. I have enough other checks in place on my network, the second any nefarious activity is detected, that host will automatically be denied network access. Not a big deal. At any rate, that should get you started if you absolutely can't just manage this from the CLI - if you have any fancy modifications you'd like me to put up, I'm happy to do so, and provide credit where due. I just don't really care about those scripts, because I don't use them, and put about 30 minutes' effort into making them on the crapper.

Moving right along - removing a user from the database is just as easy - actually, easier. All you need to know is the e-mail address you want to delete, not the password (in case that wasn't obvious). To delete my 'test@whitehathouston.com' user, I'd again login with mysql -p as root, and do the following:

mysql>use maildb;
mysql>delete from aliases where email='test@whitehathouston.com';
mysql>quit;

Easy as pie. The good kind of pie. Not only that, but hopefully you get an idea why I say having to go through the headache/nightmare/insecurity of lumping a big bloated mound of phpMyAdmin into the equation is complete overkill, unnecessary, etc. Frankly, you'd be hard-pressed to find an easier way of adding or deleting logins, even with other point-and-click tools, LDAP, or even Exchange System Manager on Windows. Yes, it's two pieces of syntax to remember, but it shouldn't take too terribly long to get the hang of it if you keep these examples handy.

Now, if you find it particularly cumbersome having to do this one by one (and even hitting the up arrow on your keyboard, then modifying the INSERT statement you used before, is too much), it's easy enough to do a bulk import. I'll go ahead and show an example of this as well - essentially, all you need to do is copy that insert statement a handful of times, edit the email and password entries as appropriate, then feed it back into mysql. So, again, fire up your favorite text editor, and let's create a new file (anywhere, doesn't matter, current directory is fine) called 'aliases.sql', and paste in the following:

use maildb;
INSERT INTO aliases (email,plainpass) VALUES ('fred@whitehathouston.com','awesomepassword2');
INSERT INTO aliases (email,plainpass) VALUES ('john@whitehathouston.com','awesomepassword3');
INSERT INTO aliases (email,plainpass) VALUES ('rick@whitehathouston.com','awesomepassword4');
INSERT INTO aliases (email,plainpass) VALUES ('bill@whitehathouston.com','awesomepassword5');
INSERT INTO aliases (email,plainpass) VALUES ('jane@whitehathouston.com','awesomepassword6');

Save the file, then as root (just in your shell, you dont need to login to the mysql command-line first) do:

mysql maildb < aliases.sql -p

Enter your root mysql pass when prompted, and assuming no errors, it should import all of those users. That's not the tidiest MySQL syntax, but it'll get the job done (in case any of you DB snobs want to nitpick).

Deleting, of course, would involve the same sort of thing, only instead of mimicking the INSERT command shown in the first example, you'd take the DELETE command, adjust the contents of the 'email=' bit, and follow the same steps as above. Or do both at the same time, with something like this in your 'aliases.sql' file:

use maildb;
DELETE FROM aliases WHERE email='test@whitehathouston.com';
DELETE FROM aliases WHERE email='othertest@whitehathouston.com';
INSERT INTO aliases (email,plainpass) VALUES ('fred@whitehathouston.com','awesomepassword2');
INSERT INTO aliases (email,plainpass) VALUES ('john@whitehathouston.com','awesomepassword3');
INSERT INTO aliases (email,plainpass) VALUES ('rick@whitehathouston.com','awesomepassword4');
INSERT INTO aliases (email,plainpass) VALUES ('bill@whitehathouston.com','awesomepassword5');
INSERT INTO aliases (email,plainpass) VALUES ('jane@whitehathouston.com','awesomepassword6');

Hopefully those are enough examples to get you by for now - I have other things to do! Namely, I have to explain how to tie Postfix and Cyrus into this database.

Connecting Postfix to Login Database

Now that you've created this (monster?) database containing loads e-mail addresses and their corresponding passwords, having read this far along in this document, you may be asking yourself - why? Either I've forgotten to explain it well (which is possible), or it's been so much text at this point, you've forgotten - understandable, as I too, have forgotten whether or not I mentioned it.

At any rate, just as I can do a simple "SELECT" statement and fetch a value out of this database, so too can Postfix (and Cyrus, covered in the next section). So what does Postfix use this database for, specifically?

Recipient Validation:

So, how do we do that? There are a few new configuration variables we need to set in main.cf. I'll describe them individually here, then provide a complete example later:

Before we can make use of these new main.cf settings, we must first set up a handful of external configuration files that explain to Postfix how to query the database and validate e-mail addresses. Since I'm sure by now your text editor is lonely, go ahead and fire it up, and we'll make a new file, /etc/postfix/validate.cf, that contains the following:

hosts = 127.0.0.1
user = maildb
dbname = maildb
password = vwwP0q2I5UmM6
query = SELECT email from aliases where email='%s'

In the above, '%s' is a special variable. When Postfix receives a message, and goes to do a SQL query to see if the e-mail address is valid, it will replace %s with the recipient's e-mail address. So in the case where my server receives a message for 'test@whitehathouston.com', soon as we set up main.cf to do so, Postfix will connect to the 'maildb' database, using a user of 'maildb' and password of 'vwwP0q2I5UmM6', and perform the query SELECT email from aliases where email='test@whitehathouston.com';. If the result is a match, the recipient is valid. If no result is returned (e.g. "empty set"), the user does not exist. How do we instruct Postfix to do this? Well, that's coming right up!

Now that we've provided a configuration file that tells Postfix how to query a user against our database, we need to actually instruct Postfix when and where to use it. We will do this for both the 'alias_maps' configuration setting, and the 'virtual_mailbox_maps' setting. Adding the following to your main.cf (full example later!) would instruct alias_maps and virtual_mailbox_maps respectively, to query MySQL:

alias_maps = mysql:/etc/postfix/validate.cf
virtual_mailbox_maps = mysql:/etc/postfix/validate.cf

Now, remember earlier when I said any setting you add to main.cf can be referenced later as a $variable? Well, with virtual_mailbox_maps and alias_maps, you can now reference them with $virtual_mailbox_maps and $alias_maps. We are going to do precisely this, by setting the following in main.cf:

local_recipient_maps = $alias_maps, $virtual_mailbox_maps

With those three settings/parameters set up, we're darn near finished with the recipient validation portion of the Postfix setup. All that's left is adding a virtual domain or two, and telling Postfix to use our existing LMTP socket for virtual_transport. Thankfully, I can provide a sensible example; I host e-mail for a friend's domain, 'infectiousbinary.com', so I'll go ahead and add that to my virtual domains (if I needed to add another, and another, and another, it would simply be a comma-separated list):

virtual_mailbox_domains = infectiousbinary.com
And lastly, we tell Postfix to use LMTP for virtual domains, too, not just our $mydestination:
virtual_transport = lmtp:unix:/var/imap/socket/lmtp

That's it! We've now set up Postfix to validate our primary domain(s) via $mydestination, virtual domains via $virtual_mailbox_domains, and to validate recipients via local_recipient_maps inclusion of $alias_maps and $virtual_mailbox_maps. If you didn't need ESMTP authentication or content filtering, at this point you would be completely done with the Postfix side of things! But of course, I'm not lazy, and I'm already running on pure caffeine, so I'll press on and explain how to do ESMTP auth with this database backend as well, plus later on cover a few content filtering basics.

If you'd like to check your work, this is what your main.cf should look like at this stage:

queue_directory = /var/spool/postfix
message_size_limit = 102400000
mailbox_size_limit = 1024000000
command_directory = /usr/sbin
daemon_directory = /usr/lib64/postfix #change to /usr/libexec/postfix on newer versions
data_directory = /var/lib/postfix
mail_owner = postfix
default_privs = nobody
myhostname = renee.whitehathouston.com
mydomain = whitehathouston.com
virtual_mailbox_domains = infectiousbinary.com
myorigin = $myhostname
alias_maps = mysql:/etc/postfix/validate.cf
virtual_mailbox_maps = mysql:/etc/postfix/validate.cf
mailbox_transport = lmtp:unix:/var/imap/socket/lmtp
virtual_transport = lmtp:unix:/var/imap/socket/lmtp
inet_interfaces = all	#or 'inet_interfaces = ipv4' if you don't use ipv6. Newer Postfix grips about this...
mydestination = $myhostname, localhost, $mydomain
local_recipient_maps = $alias_maps, $virtual_mailbox_maps
unknown_local_recipient_reject_code = 550
mynetworks = 75.148.243.88/29, 127.0.0.0/8
mail_spool_directory = /var/spool/mail
smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
local_destination_concurrency_limit = 2
default_destination_concurrency_limit = 20
debug_peer_level = 2
debugger_command =
         PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
         ddd $daemon_directory/$process_name $process_id & sleep 5
sendmail_path = /usr/sbin/sendmail
newaliases_path = /usr/bin/newaliases
mailq_path = /usr/bin/mailq
setgid_group = postdrop
smtpd_tls_security_level = may
smtpd_tls_cert_file = /etc/ssl/postfix/server.crt
smtpd_tls_key_file = /etc/ssl/postfix/server.key
smtpd_tls_CAfile = /etc/ssl/postfix/root.crt
smtpd_tls_ask_ccert = no
smtpd_tls_loglevel = 1
smtpd_recipient_restrictions =
        permit_mynetworks,
        reject_unauth_destination
biff = no
empty_address_recipient = MAILER-DAEMON
tls_random_source = dev:/dev/urandom
smtp_tls_note_starttls_offer = yes
ESMTP Authentication:

As noted at the beginning of this section, this alias database is dual-purpose, and while we've covered off one of its functions, recipient validation, I did mention (didn't I? I'm too young to be senile!) we can use this same database for ESMTP authentication. Yet again, this will involve adding a handful of new settings to main.cf, but also a handful of settings to an external file - in this case, a file containing Postfix's SASL configuration. We'll start by getting that set up, then later put the bits and bobs into main.cf that reference/utilize it. Once again, we mv the default out of the way, in this case mv /etc/sasl2/smtpd.conf /smtpd.bak (yes, at the root - we need to get it completely out of the way), fire up ye olde text editor, and make a new file, /etc/sasl2/smtpd.conf, which is where we're going to put all of the information SASL needs for checking aliases and passwords against 'maildb':

pwcheck_method: auxprop
auxprop_plugin: sql
mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5
sql_engine: mysql
sql_hostnames: 127.0.0.1
sql_user: maildb
sql_passwd: vwwP0q2I5UmM6
sql_database: maildb
sql_select: SELECT plainpass FROM aliases WHERE email = '%u@%r'

This is derived directly from the Postfix SASL documentation, which explains it a bit better than I can even in my "plain english". Long story short, in addition to providing interfaces to PAM, or a basic sasldb, Postfix's SASL implementation has a number of external plugins (included in the default Gentoo build) that can be accessed via the 'auxprop' method, one of which is a 'sql' method that supports mysql, postgres, and a handful of others. Remember how we added saslauthd to the default runlevel? Well, we don't need it. Go ahead and rc-update del saslauthd. You only need saslauthd running if you have the pwcheck_method in smtpd.conf set to 'saslauthd', which in this case we do not. One important thing to point out, where in /etc/postfix/validate.cf, we used the variable '%s' to represent the e-mail address, with SASL you must manually specify '%u@%r', as seen above, since %s is not (currently) available to SASL.

Now that the SQL pieces are set up, all we have to do is tell Postfix to enable ESMTP authentication via SASL, which as you may have guessed involves adding new parameters to main.cf!

Before we start, I'll list off the new variables/settings we're going to add:

These settings should be configured as follows:

smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_authenticated_header = yes
broken_sasl_auth_clients = yes
smtpd_tls_auth_only = yes

Of course, I'm missing "permit_sasl_authenticated", but without including that in its full context, it doesn't look particularly clear where it goes; thusly, I allow you to check your work, and see what our main.cf should look like at this stage:

queue_directory = /var/spool/postfix
message_size_limit = 102400000
mailbox_size_limit = 1024000000
command_directory = /usr/sbin
daemon_directory = /usr/lib64/postfix #change to /usr/libexec/postfix on newer versions
data_directory = /var/lib/postfix
mail_owner = postfix
default_privs = nobody
myhostname = renee.whitehathouston.com
mydomain = whitehathouston.com
virtual_mailbox_domains = infectiousbinary.com
myorigin = $myhostname
alias_maps = mysql:/etc/postfix/validate.cf
virtual_mailbox_maps = mysql:/etc/postfix/validate.cf
mailbox_transport = lmtp:unix:/var/imap/socket/lmtp
virtual_transport = lmtp:unix:/var/imap/socket/lmtp
inet_interfaces = all	#or 'inet_interfaces = ipv4' if you don't use ipv6. Newer Postfix grips about this...
mydestination = $myhostname, localhost, $mydomain
local_recipient_maps = $alias_maps, $virtual_mailbox_maps
unknown_local_recipient_reject_code = 550
mynetworks = 75.148.243.88/29, 127.0.0.0/8
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_authenticated_header = yes
broken_sasl_auth_clients = yes
smtpd_tls_auth_only = yes
mail_spool_directory = /var/spool/mail
smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
local_destination_concurrency_limit = 2
default_destination_concurrency_limit = 20
debug_peer_level = 2
debugger_command =
         PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
         ddd $daemon_directory/$process_name $process_id & sleep 5
sendmail_path = /usr/sbin/sendmail
newaliases_path = /usr/bin/newaliases
mailq_path = /usr/bin/mailq
setgid_group = postdrop
smtpd_tls_security_level = may
smtpd_tls_cert_file = /etc/ssl/postfix/server.crt
smtpd_tls_key_file = /etc/ssl/postfix/server.key
smtpd_tls_CAfile = /etc/ssl/postfix/root.crt
smtpd_tls_ask_ccert = no
smtpd_tls_loglevel = 1
smtpd_recipient_restrictions =
        permit_mynetworks,
        permit_sasl_authenticated,
        reject_unauth_destination
biff = no
empty_address_recipient = MAILER-DAEMON
tls_random_source = dev:/dev/urandom
smtp_tls_note_starttls_offer = yes

Does yours look like mine? Yes? Then congratulations. You're completely finished with the Postfix essentials. There is no further *need* to touch Postfix configuration files, although I will later on detail a few "finishing touches", miscellaneous administrative tasks, and whatnot. But for the time being, take a break; we're done with Postfix, all that's left is getting Cyrus working.

Connecting Cyrus to Login Database

Hopefully by this stage you've taken a break. I haven't, which is probably why I keep talking about you needing to take a break. But no time for that now, we have things to configure! Fortunately, this entails nothing more than a small change to one of Cyrus' configuration files.

As you may well be guessing, it's text editor time again. We're going to edit /etc/imapd.conf, and instead of the old 'saslauthd' pw_check method, we're going to set Cyrus to check logins against our 'maildb' database. To do so, change what you had before, to the following:

configdirectory:        /var/imap
partition-default:      /var/spool/imap
sievedir:               /var/imap/sieve
admins: root meat
tls_ca_path:            /etc/ssl/certs
tls_cert_file:          /etc/ssl/cyrus/server.crt
tls_key_file:           /etc/ssl/cyrus/server.key
virtdomains: on
hashimapspool:          yes
allowanonymouslogin:    no
allowplaintext:         yes
allowusermoves:         yes
autocreatequota: -1
createonpost: yes
sasl_pwcheck_method: auxprop
sasl_auxprop_plugin: sql
sasl_sql_engine: mysql
sasl_mech_list: LOGIN PLAIN CRAM-MD5 DIGEST-MD5
sasl_sql_user: maildb
sasl_sql_passwd: vwwP0q2I5UmM6
sasl_sql_database: maildb
sasl_sql_hostnames: localhost
sasl_sql_select: SELECT plainpass FROM aliases WHERE email = '%u@%r'

You will notice a few things different here:

Surprised as you may be, that's it. You're finished. No, I mean really finished, Postfix is validating recipients against this database, Postfix is doing authenticated SMTP (only over TLS!) using this database, and now Cyrus IMAP is checking users' credentials against this database whenever they logon with their mail client. There's really nothing left that *needs* to be done, though, I have some more trivial things to explain later.

Now, for the long-awaited triumph:

/etc/init.d/postfix start
/etc/init.d/cyrus start

Assuming the services start up, mail should be flowing, routing to and from where it's supposed to be, assuming you've actually, well, added users' logins to this database. And of course, we should be able to check mail now with our favorite IMAP client. You can realistically stop reading here, though there are a few more matters of adding a bit of polish to this doc if you have any attention span left for reading.

Adding Domains

Shortest. Section. Ever.
To add on extra domains to be accepted by Postfix, you need only add them onto virtual_mailbox_domains. Of course, you should also make sure you add any users for these new domains to the 'maildb' database, lest people end up with their e-mail being rejected. You could, too, fetch this from the database as well. You'd need a new table containing one column where each row contains a domain, then tell Postfix to do a mysql lookup similar to how you did with the examples above. Nothing special is required on the Cyrus side - this is precisely why (well, one of the reasons) we opted to go the autocreate route. It makes this exponentially easier.

If you do indeed wish to store virtual mailbox domains in the database, the following should create a table that does what you need (adjust your SQL lookups accordingly!)

create table domainlist (domain CHAR(255) CHARACTER SET utf8 COLLATE utf8_bin UNIQUE NOT NULL);

With the database table to hold domains set up, you would change virtual_mailbox_domains in main.cf to point to a mysql configuration file, just as you did above for looking up e-mail addresses:

virtual_mailbox_domains = mysql:/etc/postfix/virtual_domain_lookup.cf

The virtual_domain_lookup.cf mentioned above would use the '%d' variable which will be replaced with the domain (instead of the %s variable used to lookup users, as %s is replaced with the entire email address)

hosts = 127.0.0.1
user = maildb
dbname = maildb
password = vwwP0q2I5UmM6
query = SELECT domain from domainlist where domain='%d'

Secondary Aliases and Forwarding

So, if you want to automagically forward e-mail destined for one address, to another address? Think along the lines of wanting to redirect 'webmaster@whitehathouston.com' to 'chris@whitehathouston.com'. This is actually fairly straightforward, and I'll happily cover this off here.

However, setting up something like a distribution list is a fair bit more complex. I don't particularly feel like muddying up previous documentation, and especially the database schema, trying to explain how to get this to work for the few of you who aren't content simply giving one or multiple users permission (via cyradm) to manage a non-human alias's mailbox, or setting up mailing lists. With cyradm you can set up mailbox ACL's, and basically say 'give billy@domain.com permission to manage marketing@domain.com', repeating as needed for anyone that needs that access.

But enough of what I won't do, how about what I *will* do. For basic aliasing/forwarding, such as the webmaster example I mentioned above, it's a good bit easier to keep a hash table of redirects than it is trying to tie yet something else into MySQL. The easiest way to do this, is to set up virtual_alias_maps in main.cf, and point it at the hash table of aliases/forwards/redirects (whatever you want to call it) you've defined. In this case, a line like so in main.cf:

virtual_alias_maps = hash:/etc/mail/virtual

Now, fire up that text editor again, and create a new file, /etc/mail/virtual. Its contents should be similar to the following:

@bauer.whitehathouston.com noc@whitehathouston.com
webmaster@whitehathouston.com chris@whitehathouston.com
alerts@whitehathouston.com noc@whitehathouston.com

The layout is as such: when Postfix receives a message for one of the addresses on the left, it will rewrite the recipient address to whatever's on the right. You'll note the example of of '@bauer.whitehathouston.com' - in this context, this simply says to take mail to any user at 'bauer.whitehathouston.com', and redirect it to 'noc@whitehathouston.com', whereas the others only match specific addresses. Again, this is something you could put into mysql if you really wanted to (virtual_alias_maps can point at mysql just as easily as it can a hash table). You can experiment with that if you like, but that's not needed for most SOHO setups.

You will of course have to make sure the aliases in question are already added to the MySQL database that houses your e-mail addresses ('maildb' if you're following this guide!), otherwise it'll never even get to this point. Soon as you have this file set to your liking, and saved, you will need to 'postmap' it, which basically takes the file, converts it into a simplistic binary format that Postfix can read a bit quicker than a flat text file. This is done via typing this in your standard shell/terminal/console as root:

postmap /etc/mail/virtual

Once this is done, instead of restarting postfix, simply key in postfix reload, and it should re-read this alias file.

Content Filtering

For the time being I won't cover this off in *too* much detail. I have two weapons of choice for this:

You may see a handful of docs on the internet that tell you have evil or useless RBL's are. I won't bother qualifying this, but flat-out, the people who speak the ills of RBL's have not the foggiest clue what they're talking about, and generally aren't people that've dealt with commercial content filtering on any large scale. I have. It's what I've been living and breathing for the past decade or better, my bills are paid by the commercial anti-spam vendors I've worked for, and they pay me to be the wizard behind their filters. This has been my job. I'm damn good at it. Plain and simple, RBL's work, they're NOT bad, nobody with a lick of usable knowledge and real-world experience says otherwise. Yes, if you choose an overly aggressive RBL (i.e. SpamCop) to drop messages at the gateway, you're asking for trouble. But if you select the right RBL's, who have both sensible listing AND delisting policy, you can generally with very high confidence reject blacklisted messages. Don't buy the hype, every major commercial anti-spam vendor out there uses an RBL of some form, whether they call it that, a "reputation list", or something else.

So, how does one set this up within Postfix? First and foremost, I don't even let blacklisted hosts so much as send a HELO. I do this by setting:

smtpd_delay_reject = no

within main.cf

This controls the behaviour of Postfix when rejecting a blacklisted host. Next is setting which blacklists you use; I generally prefer to add a new setting, one I've not discussed yet, called 'smtpd_client_restrictions', which goes above smtpd_recipient_restrictions, leaving you with a config that looks like so:

smtpd_delay_reject = no
smtpd_client_restrictions =
        permit_mynetworks
        reject_rbl_client ix.dnsbl.manitu.net
        reject_rbl_client cbl.abuseat.org
        reject_rbl_client b.barracudacentral.org
        reject_rbl_client new.spam.dnsbl.sorbs.net
smtpd_recipient_restrictions =
        permit_mynetworks,
        permit_sasl_authenticated,
        reject_unauth_destination

The four RBL's I use above are top quality. You can safely just copy/paste them into your configuration, though it's never a bad idea to read up on your RBL's, and see if they're seemingly trustworthy. For now, just take my word for it, these should cut out a good 90% or better of your spam volume, right off the bat.

Now, I did mention amavisd-new. I won't go into detail on setting that up, but it effectively boils down to setting up master.cf to spawn an additional postfix listener on port 10025, then telling Postfix to pass requests off to to the amavisd-new listener on port 10024 (with the line below), and amavis either quarantines it, or feeds it back to Postfix on the instance listening on port 10025, whereupon Postfix delivers it:

content_filter = smtp-amavis:[127.0.0.1]:10024

For details on setting up amavisd-new, check out the Gentoo Mail Filter Guide. Two caveats there:

Hope this wasn't too long, and hope someone out there on the interwebz finds this useful! --cach0rr0 (on the forums @ gentoo!)

PS: Usual disclaimers. This isn't official Gentoo doc by any stretch; it's doc about Gentoo, not by nor sanctioned by Gentoo, the project, foundation, or anything of the sort.

[Return to Top]