POP3 IMAP SMTP E-mail server

From OptionC

Table of contents

Introduction

(This was reviewed and modified on 2005-12-05, and is going through testing.)

This document was created to show, step-by-step, how we created an e-mail server DomU VM for Xen. In reading through the document you will find that there's almost no reference to Xen or virtualization, as the DomU VM should be configured as a standalone machine would be.

I wanted to create a single-domain e-mail server that would handle both IMAP and POP3 connections (via Courier MTA) and SMTP (via Postfix). Rather than be forced to maintain a database and a set of users on the machine, I chose to use a single database for maintenance. The database of choice is MySQL. Usually this database would be remote for security and segregation purposes, but for this VM it's included as part of the machine (I'll make notes on needed changes for remote database connectivity).

Because it wasn't any extra work, the e-mail server has been set up in such a way as to support multiple domains (although only one is presented). As such it will make use of Postfix's transport and virtual mapping mechanisms. These can be expanded as needed by modifying or adding database entries.


Initial System Setup

I started with a Debian install created via debootstrap, as defined here (http://www.option-c.com/xwiki/Create_a_Debian_VM_with_debootstrap).

After creating an image with all the packages defined in this HOWTO, I had approximately 50 MB of free space (based upon an initial 300 MB disk image), and approximately 50 MB of packages in the cache. If you do not size your images or partitions to match your e-mail needs during the initial configuration, you always have the option of resizing them (using "resize2fs" for file-back VBDs or "lvextend" for logical volumes) or adding another partition to the configuration and mounting it in fstab.

Note: These instructions are performed at the command-line because we do not feel that a secure, optimized Mail Server is the place for a GUI. Webmin might be acceptable, but isn't demonstrated herein. We don't guarantee the security of the installation.

Note: Throughout this document I use the following conventions:

"SERVERNAME"    = local name of the e-mail server
"DOMAIN.COM"    = the name of the e-mail domain you're building
"192.168.99.99" = the local IP address of the e-mail server
"PASSWORD"      = the database password, needed by MySQL, Courier and Postfix


After the system was ready and running (within Xen) I made the following modifications:

# echo SERVERNAME > /etc/hostname
# hostname SERVERNAME
# echo 127.0.0.1 localhost SERVERNAME > /etc/hosts
# echo DOMAIN.COM > /etc/mailname
Note: it is important to not have the localhost.localdomain alias in /etc/hosts, else it will also be needed in the Postfix main.cf configuration file

MySQL

The versions of MySQL used when testing were 4.0.23 and 4.0.24.

How to install and configure MySQL for Postfix and Courier, step-by-step.

Installation

First download and install MySQL:

# apt-get update
# apt-get install mysql-server

The standard install of MySQL is not exactly secure, so change the root password:

# mysql -u root
mysql> use mysql;
mysql> update user set password=password('newpassword')
    -> where user='root';
mysql> flush privileges;
mysql> quit;
 
# mysql -u root -p
Enter password:

Debian installs of MySQL have a test database already created. If you want to remove this, type

mysql> drop database test;

At this point we're ready to have some fun.

Creating the MySQL Database

We are going to create a database that will be used by both Postfix and Courier to retrieve information about the e-mail recipients (user name, password, location of the mail files, and so forth). We're going to be really creative and call this database "maildb".

Inside maildb there will be three tables:

Users     - the table with the user-level information
Virtual   - Defines rewriting of recipient addresses for all local,
            virtual, and remote mail destinations.
Transport - Specifies mapping from e-mail addresses to message 
            delivery transports or relay hosts.

From within MySQL, run the following:

create database maildb;
use maildb;
 
CREATE TABLE transport
(
    domain varchar(128) NOT NULL default "",
    transport varchar(128) NOT NULL default "",
    UNIQUE KEY domain (domain)
);
 
CREATE TABLE virtual
(
    address varchar(255) NOT NULL default "",
    goto varchar(255) NOT NULL default "",
    UNIQUE KEY address (address)
);
 
CREATE TABLE users
(
    id varchar(128) NOT NULL default "",
    address varchar(128) NOT NULL default "",
    crypt varchar(128) NOT NULL default "",
    clear varchar(128) NOT NULL default "",
    name varchar(128) NOT NULL default "",
    uid smallint(5) unsigned NOT NULL default '999',
    gid smallint(5) unsigned NOT NULL default '100',
    home varchar(128) NOT NULL default '/',
    domain varchar(128) NOT NULL default "",
    maildir varchar(255) NOT NULL default "",
    PRIMARY KEY  (id),
    UNIQUE KEY id (id),
    UNIQUE KEY address (address)
);
Note: the uid/gid default values (999/100) are the same ones which will be used in the creation of the virtual user. These must match or else there will be some very-hard-to-trace permission issues.

Next create a MySQL user to be used by Postfix and Courier. Once again, creativity rules the day; our user will be named "mailuser":

mysql> grant all on maildb.* to mailuser@localhost identified by 'PASSWORD';
mysql> flush privileges;

Remember the PASSWORD (you should use your own value, not simply the word "PASSWORD"). You'll need it later for both the Postfix and Courier configuration.

The final step needed is to define the transport for your domain(s). This is accomplished by adding a line (per domain) to the transport table:

mysql> INSERT INTO transport VALUES ( 'DOMAIN.COM', 'virtual' );

"Virtual" is the name of the user/transport mechanism which will be defined in Postfix.

Testing the MySQL Database

To test the database:

# mysql -D maildb -u mailuser -p
Password:
mysql> select * from users;
Empty set (0.00 sec)

If an error occurs, a mistake has been made in following the instructions.

For now, that's it. We'll come back to the database later.

Note: If we wanted to use the database from a remote machine (i.e., the e-mail server were on machine1, the database on machine2), then we would need to grant access to mailuser@machine1. There is a little more to it than that, but accessing a remote database is outside of the scope of this document.
(Also, we have our remote server set up to use SSL - the tips in MySQL_Replication_with_SSL might be helpful if you want to set it up that way.)


Postfix

The version of Postfix used for testing was 2.1.5-9.

How to install and configure Postfix, step-by-step.

Installation

To install Postfix and its needed components:

# apt-get install postfix postfix-mysql postfix-pcre postfix-tls

There may be some Debian configuration screens to work through such as locale configuration. Work through these using sensible choices (I can't define specific ones that will appear due to the original configuration of your machine). Many of these options will be revisited as we edit the configuration files.

If the Postfix configuration is shown, I selected "Internet Site" as the type of configuration. Read the instructions and make your choice. Most of the defaults are acceptable (we'll make further modifications later on).

When asked for the "Mail Name", enter your domain name. Destinations can be the default. Again, these can be changed later on (and will be if virtual hosts are being used).

Configuration

Perform the following steps (when editing, I use VI; substitute your choice of text editor):

# cd /etc/postfix
# vi main.cf

Create or edit the following lines:

myhostname=localhost
local_transport=virtual
transport_maps=mysql:/etc/postfix/transport.cf
virtual_mailbox_maps=mysql:/etc/postfix/mysql_virt.cf
virtual_uid_maps=mysql:/etc/postfix/uids.cf
virtual_gid_maps=mysql:/etc/postfix/gids.cf
virtual_mailbox_base=/
mydestination=$mydomain,$myhostname,$transport_maps
virtual_maps=mysql:/etc/postfix/virtual.cf

Create the following files with the noted contents (replace the word PASSWORD with your MySQL password):

transport.cf:

user=mailuser
password=PASSWORD
dbname=maildb
table=transport
select_field=transport
where_field=domain
hosts=127.0.0.1

mysql_virt.cf:

user=mailuser
password=PASSWORD
dbname=maildb
table=users
select_field=maildir
where_field=address
hosts=127.0.0.1

uids.cf:

user=mailuser
password=PASSWORD
dbname=maildb
table=users
select_field=uid
where_field=address
hosts=127.0.0.1

gids.cf:

user=mailuser
password=PASSWORD
dbname=maildb
table=users
select_field=gid
where_field=address
hosts=127.0.0.1

virtual.cf:

user=mailuser
password=PASSWORD
dbname=maildb
table=virtual
select_field=goto
where_field=address
hosts=127.0.0.1

Note: If the MySQL server is remote, then each of the 5 created files must be modified appropriately. In particular, each should have a "hosts=" line with the appropriate definitions. For example (machine1 = e-mail server, machine2 and machine3 are database servers):

hosts=machine2 machine3
For more information please refer to the Postfix man page on mysql_table (http://www.postfix.org/mysql_table.5.html).

After creating the files, set the permissions so only Postfix can read them -- they do have passwords in, after all.

# chmod 0640 transport.cf mysql_virt.cf uids.cf gids.cf virtual.cf
# chgrp postfix transport.cf mysql_virt.cf uids.cf gids.cf virtual.cf

After that work, it's a good idea to have Postfix validate the configuration files:

# postfix check

If all turns out okay (i.e., no error messages) then reload the configurations:

# postfix reload

We finish up by creating the user that Postfix will use for e-mail, as well as some directories.

# adduser --system --no-create-home --uid 999 --gid 100 --disabled-login virtual
# mkdir /var/spool/postfix/virtual
# chmod 0700 /var/spool/postfix/virtual
# chown virtual /var/spool/postfix/virtual

Useful Postfix Notes

If you're having trouble with Postfix you can turn on debugging. However, if you have many systems hitting your machine simultaneously, the amount of debugging information can be overwhelming.

To set debugging on only when a particular IP address is connected, add the following lines to the main.cf file:

debug_peer_list=192.168.99.10
debug_peer_level=3

The first line (which can be a comma-delimited list of addresses as well as other formats) says to Postfix that it should set the debug logging level if it gets requests from the list of IP addresses. The second line defines what the debug logging level should be. For more information, please refer to the Postfix Documentation (http://www.postfix.org/documentation.html).


Courier MTA

The version of Courier used for testing was 0.47-4.

How to install and configure Courier for POP3 IMAP E-mail server, step-by-step.

Installation

We start the installation of Courier by getting the software packages.

# apt-get install courier-imap courier-imap-ssl courier-pop \
  courier-pop-ssl courier-authmysql

As with Postfix, you might be faced with some configuration screens. Accept the default values as we will be modifying the configuration by hand.

Configuration

Once software installation is complete it's time to configure.

# cd /etc/courier
# vi authmysqlrc

Add or update the following values:

MYSQL_SERVER            localhost
MYSQL_USERNAME          mailuser
MYSQL_PASSWORD          PASSWORD
MYSQL_SOCKET            /var/run/mysqld/mysqld.sock
MYSQL_DATABASE          maildb
MYSQL_USER_TABLE        users
MYSQL_CRYPT_PWFIELD     crypt
MYSQL_CLEAR_PWFIELD     clear
MYSQL_UID_FIELD         uid
MYSQL_GID_FIELD         gid
MYSQL_LOGIN_FIELD       id
MYSQL_HOME_FIELD        home
MYSQL_NAME_FIELD        name
MYSQL_MAILDIR_FIELD     maildir
DEFAULT_DOMAIN          DOMAIN.COM

Note: There must be no trailing whitespace after the variable values.
Note: If the MySQL server is remote, then the MYSQL_SERVER value must be set appropriately.
Note: The last variable, DEFAULT_DOMAIN, will set up the default domain value. If someone attempts to log in simply as "user" then Courier uses "user@DEFAULT_DOMAIN". This is more useful if you are hosting only a single domain.

To continue:

# vi authdaemonrc

Add or update the following lines:

authmodulelist="authmysql"
version="authdaemond.mysql"

Note: There must be no trailing whitespace after the variable values.

Finally, we need to restart all the Courier daemons. We can either do this by hand (one at a time), type the following single line:

# for var in /etc/init.d/courier-*; do $var restart; done

or create a slightly more elaborate script to do it for us.

I chose the latter option, and placed the script in /etc/init.d/courier (don't forget to make it executable). The courier daemon script can be found here.

# chmod 755 /etc/init.d/courier
# . /etc/init.d/courier restart

To test our Courier setup, please go to the Testing Courier section below.


Relay Control

How to set up Relay Control for POP3 IMAP SMTP E-mail server, step-by-step.

If the above instructions have been followed and you've been experimenting, then you may have noticed that if you try to send an e-mail from a remote box, to a remote e-mail address, through your new SMTP server you will get a "Relay access denied" message. This is because we've set the box up to only relay messages from the local host. We can set it up to relay from the local subnet if we want, or even from all IP addresses (which is very bad; spammers love open relays).

Since we are trying to create an Internet e-mail server, we can't always guarantee the IP address our users will have when trying to send e-mails. What we need is a way to limit the SMTP relay connections to those who actually have accounts on our server.

There are many ways to control who can and can not relay e-mail through our SMTP server. The more secure way requires SMTP Authentication with encryption, which we hope to add at some point. An easier way, and one which is used by a high number of SMTP servers, is known as POP Before SMTP. Basically this requires that any machine that wishes to send e-mail through the SMTP server must first identify itself by logging in to either the POP server or IMAP server. The IP address is then stored, temporarily, so that SMTP access is granted. This is the approach we're going to take for now.

POP Before SMTP

With our setup (Debian, Postfix SMTP, Courier POP/IMAP) there are several methods of implementing POP Before SMTP. I've chosen to use the simplest one, and install the pop-before-smtp Debian package (currently version 1.36). This is a Perl solution which has been configured specifically for Debian. The following instructions, then, detail how to implement it on our system.

Install pop-before-smtp.

# apt-get install pop-before-smtp

Edit the /etc/pop-before-smtp/pop-before-smtp.conf file:

- Look for a line like the following:
       # $file_tail{'name'} = '/var/log/mail.log';
  if it exists, uncomment it (remove the #), else create the line 
  before the comment which says "... or we'll try to figure it out for you."
- Find the $pat section.
- Uncomment the Courier-POP3 and Courier-IMAP line(s).

At this point it is a good idea to test the pop-before-smtp installation. To do this, make sure the daemon is stopped and then run it in non-daemon mode:

# /etc/init.d/pop-before-smtp stop
# pop-before-smtp --debug --nowrite --reprocess

Hit Ctrl-C to end the above test. For further testing, please refer to the Testing Pop-Before-SMTP section below.

To finish the configuration of pop-before-smtp, we need to start the daemon:

# /etc/init.d/pop-before-smtp start

Add or edit the following 2 lines in /etc/postfix/main.cf:

check_client_access=hash:/var/lib/pop-before-smtp/hosts
smtpd_recipient_restrictions=permit_mynetworks,reject_non_fqdn_recipient,
        check_client_access hash:/var/lib/pop-before-smtp/hosts,
        reject_unauth_destination

Reload postfix configuration files:

# postfix reload

Update: The latest version of pop-before-smtp was released recently (May 13, 2005). The following notes are how to upgrade. The administrator must be careful when transferring over changes to the configuration and executable files to maintain the Debian-specific options such as the mail.log file name. This is not recommended unless you have some idea as to what you are doing.

The following instructions have not been rigorously tested. Your mileage may vary.

  • Backup your old configuration file and executable:
# cp /etc/pop-before-smtp/pop-before-smtp.conf \ 
  /etc/pop-before-smtp/pop-before-smtp.conf.OLD
# cp /usr/sbin/pop-before-smtp /usr/sbin/pop-before-smtp.OLD
  • Stop the daemon;
# /etc/init.d/pop-before-smtp stop
  • Download the pop-before-smtp-1.37.tar.gz file from popbsmtp.sourceforge.net
  • Unpack the tar file
# tar -zxvf pop-before-smtp-1.37.tar.gz
# cd pop-before-smtp-1.37
  • Copy over the new executable and configuration
# cp pop-before-smtp /usr/sbin
# cp pop-before-smtp-conf.pl /etc/pop-before-smtp/pop-before-smtp.conf
  • Edit the /usr/sbin/pop-before-smtp file. In particular,
find the line:
       my $config_file = '/etc/pop-before-smtp-conf.pl';
change to:
       my $config_file = '/etc/pop-before-smtp/pop-before-smtp.conf';
 
find the line:
       $dbfile = '/etc/postfix/pop-before-smtp';
change to:
       $dbfile = '/var/lib/pop-before-smtp/hosts';
  • Edit the new configuration file by comparing line-by-line with the old. In particular, look out for the following:
find the line:
       #$dbfile = '/etc/postfix/pop-before-smtp';
change to:
       #$dbfile = '/var/lib/pop-before-smtp/hosts';
 
find the line:
       #$file_tail{'name'} = '/var/log/maillog';
change to:
       $file_tail{'name'} = '/var/log/mail.log';
  • Find the $pat and $out_pat patterns for Courier-POP3 and Courier-IMAP; uncomment them.
Note: Because the Debian version uses a tailored pattern, you will need to copy the Courier $pat from the 1.36 configuration file to the new configuration file.
  • To ensure everything is working, run pop-before-smtp as before:
       # pop-before-smtp --debug --nowrite --reprocess
  • If everything seems okay, then Ctrl-C out of the debug version and restart the daemon.
       # /etc/init.d/pop-before-smtp start

Once again, this upgrade has not been rigorously tested; implement it at your own risk.


SMTP Authentication

Keep in mind this is "either or" (I believe) - you don't want to have this method of SMTP Authentication and "Pop before SMTP."

If you've wandered around the internet, trying to figure this out, you've probably found a lot of comments to the effect of "I couldn't figure this out, and I tried this, and now it works." Except that it doesn't. The one that worked for me was the instructions on the second half of this page (http://www.howtoforge.com/virtual_postfix_mysql_quota_courier_p2) - the configuration parts in step 4, and all of step 5. I'll spell it out, as we had some differences.

Required Packages

The Debian packages that are required (you may alread have them) are: postfix-tls, libsasl2, libsasl2-modules, libsasl2-modules-sql, sasl2-bin, libpam-mysql, and openssl.

apt-get install postfix-tls libsasl2 libsasl2-modules \
 libsasl2-modules-sql sasl2-bin libpam-mysql openssl

Postfix Configuration Changes

These lines need to be added to main.cf

# SMTP authentication
smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = yes
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated,
 reject_unauth_destination
smptd_use_tls = yes
smptd_tls_cert_file = /etc/postfix/smtpd.cert
smptd_tls_key_file = /etc/postfix/smtpd.key

Create SSL certificate

Create the requisite SSL certificate (this probably needs to be explained in more detail - you will need to provide various information):

# cd /etc/postfix
# openssl req -new -outform PEM -out smtpd.cert -newkey rsa:2048 \
  -nodes -keyout smtpd.key -keyform PEM -days 365 -x509

Make it so the key is not world readable.

# chmod o= /etc/postfix/smtpd.key

Configure Saslauthd

I believe that most of these changes are because Debian runs Postfix in a chrooted environment...

# mkdir -p /var/spool/postfix/var/run/saslauthd

Edit /etc/default/saslauthd so that it looks like this (" vi/etc/default/saslauthd"):

START=yes
MECHANISMS="pam"
PARAMS="-m /var/spool/postfix/var/run/saslauthd -r"

Create /etc/pam.d/smtp so that it looks like this (you man need to make changes depending on your database setup) - ("vi /etc/pam.d/smtp"):

auth    required   pam_mysql.so user=mailuser passwd=PASSWORD
 host=127.0.0.1 db=maildb table=users usercolumn=id
 passwdcolumn=crypt crypt=1
account sufficient pam_mysql.so user=mailuser passwd=PASSWORD 
 host=127.0.0.1 db=maildb table=users usercolumn=id
 passwdcolumn=crypt crypt=1

Create /etc/postfix/sasl/smtpd.conf so that it looks like this ("vi /etc/postfix/sasl/smtpd.conf"):

pwcheck_method: saslauthd
mech_list: plain login
allow_plaintext: true

Restart postfix and saslauthd:

# /etc/init.d/postfix restart
# postfix check
# /etc/init.d/saslauthd restart

System Testing

System Testing Components for POP3 IMAP SMTP E-mail server, step-by-step.

Testing Postfix/SMTP

To test the Postfix/SMTP installation we first need to create a test user. This can be done one of two ways: Manually, or by using the addmailuser script.

To create a user manually, the procedure is:

  • Enter the database.
# mysql -D maildb -u mailuser -p
Password:
  • Create a test user.
mysql> INSERT INTO users VALUES ( 'test@DOMAIN.COM', 'test@DOMAIN.COM',
    -> ENCRYPT( 'password' ), 'password', 'test name', 999, 100,
    -> '/var/spool/postfix/virtual/DOMAIN.COM/test', 'DOMAIN.COM',
    -> '/var/spool/postfix/virtual/DOMAIN.COM/test/Maildir/' );
mysql> INSERT INTO virtual VALUES ( 'test@DOMAIN.COM', 'test@DOMAIN.COM' );

This will create a test user with the password "password" (unless you change it). You should replace DOMAIN.COM with your domain.

Note: We use the MySQL ENCRYPT() function to create the encrypted version of the password rather than the PASSWORD() function. This is because Courier will perform comparisons against a system-encrypted password, which PASSWORD() does not supply.
  • Set up the transport values for the domain. This only needs to be done once per domain, so if you did it earlier then you can skip this step.
mysql> INSERT INTO transport VALUES ( 'DOMAIN.COM', 'virtual' );

Next we need to create the directories or else Postfix won't be able to deliver the mail it receives. We create the directories using the maildirmake utility which will create QMail structure mail directories.

# mkdir /var/spool/postfix/virtual/DOMAIN.COM
# chown 999 /var/spool/postfix/virtual/DOMAIN.COM
# mkdir /var/spool/postfix/virtual/DOMAIN.COM/test
# maildirmake /var/spool/postfix/virtual/DOMAIN.COM/test/Maildir
# maildirmake -f Trash /var/spool/postfix/virtual/DOMAIN.COM/test/Maildir
# maildirmake -f Drafts /var/spool/postfix/virtual/DOMAIN.COM/test/Maildir
# maildirmake -f Sent /var/spool/postfix/virtual/DOMAIN.COM/test/Maildir
# chown -R 999.100 /var/spool/postfix/virtual/DOMAIN.COM/test
# chmod 0700 /var/spool/postfix/virtual/DOMAIN.COM/test

We are now ready to test Postfix.

# telnet localhost smtp

Here is a sample session (you should enter the lines beginning with ">>")

   Trying 127.0.0.1...
   Connected to localhost.
   Escape character is '^]'.
   220 localhost ESMTP Postfix (Debian/GNU)
>> HELO localhost
   250 localhost
>> MAIL FROM: test@DOMAIN.COM
   250 Ok
>> RCPT TO: test@DOMAIN.COM
   250 Ok
>> DATA
   354 End data with <CR><LF>.<CR><LF>
>> testing...
>> .
   250 Ok: queued as E80291B070
>> QUIT
   221 Bye

If this did not happen then please look in the /var/log/mail.log file to see what's going wrong.

Really daring people can use a remote machine to perform the test. Just make sure you telnet to the right server...

Testing Courier

To test our Courier setup we, once again, use telnet. We'll try two different ways: using POP3 and IMAP. Again, you should enter values designated by ">>"

POP3:

 # telnet localhost pop-3
   Trying 127.0.0.1...
   Connected to localhost.
   Escape character is '^]'.
   +OK Hello there.
>> USER test@DOMAIN.COM
   +OK Password required.
>> PASS password
   +OK logged in.
>> LIST
   +OK POP3 clients that break here, they violate STD53.
   1 425
   2 432
   .
>> RETR 1
   +OK 425 octets follow.
   Return-Path: <test@DOMAIN.COM>
   X-Original-To: test@DOMAIN.COM
   Delivered-To: test@DOMAIN.COM
   Received: from localhost (unknown [192.168.99.99])
           by localhost (Postfix) with SMTP id ADE201B070
           for <test@DOMAIN.COM>; Tue,  7 Jun 2005 20:13:48 -0500 (CDT)
   Message-Id: <20050608011348.ADE201B070@localhost>
   Date: Tue,  7 Jun 2005 20:13:48 -0500 (CDT)
   From: test@DOMAIN.COM
   To: undisclosed-recipients:;
 
   testing...
   .
>> QUIT
   +OK Bye-bye.

IMAP:

   # telnet localhost imap
   Trying 127.0.0.1...
   Connected to localhost.
   Escape character is '^]'.
   * OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT
   THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright
   1998-2004 Double Precision, Inc.  See COPYING for distribution information.
>> a LOGIN test@DOMAIN.COM test
   a OK LOGIN Ok.
>> a SELECT INBOX
   * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
   * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
   * 2 EXISTS
   * 2 RECENT
   * OK [UIDVALIDITY 1118196844] Ok
   * OK [MYRIGHTS "acdilrsw"] ACL
   a OK [READ-WRITE] Ok
>> a FETCH 1 body[1]
   * 1 FETCH (BODY[1] {12}
   testing...
   )
   a OK FETCH completed.
>> a LOGOUT
   * BYE Courier-IMAP server shutting down
   a OK LOGOUT completed
   Connection closed by foreign host.

Testing Pop Before SMTP

To test pop-before-smtp in non-daemon mode, type the following:

# pop-before-smtp --debug --nowrite --reprocess

You should see messages about IPs the script finds. If you don't see any messages, check to see that you've chosen the correct $pat pattern and are looking at the right mail log file (see above).

While running the above command, type the following from another console on the local machine:

   # telnet localhost imap
   Trying 127.0.0.1...
   Connected to localhost.
   Escape character is '^]'.
   * OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT
   THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright
   1998-2004 Double Precision, Inc.  See COPYING for distribution information.
>> a LOGIN test password
   a OK LOGIN Ok.
>> a LOGOUT
   * BYE Courier-IMAP server shutting down
   a OK LOGOUT completed
   Connection closed by foreign host.

Switch back to the original console; you should see a message saying something about ignoring the local network:

Feb 16 05:12:09 ignoring local-net ip=127.0.0.1

Repeat the test from a remote machine (using the mail server's IP address rather than localhost). This time you should see a message about the IP address being added to the database:

Feb 16 05:12:20 found ip=192.168.99.99
Feb 16 05:12:20 added 192.168.99.99 to DB

Hit Ctrl-C in the console running pop-before-smtp to stop the test.


To fully test pop-before-smtp's capabilities, you need to run the same test as before from an IP address that hasn't connected to the e-mail server recently (by default pop-before-smtp keeps IP addresses for 30 minutes).

On the e-mail server, start up pop-before-smtp as a daemon:

# /etc/init.d/pop-before-smtp start

Then do the following from the remote machine:

   $ telnet 192.168.99.99 smtp
   Trying 192.168.99.99...
   Connected to 192.168.99.99.
   Escape character is '^]'.
   220 localhost ESMTP Postfix (Debian/GNU)
>> MAIL FROM: test@DOMAIN.COM 
   250 Ok
>> RCPT TO: some_address@some_remote_domain.com
   554 <some_address@some_remote_domain.com>: Relay access denied
>> QUIT
   221 Bye
   Connection closed by foreign host.

To get pop-before-smtp to update its database, you must login from the remote IP address either through POP3 or IMAP:

   $ telnet 192.168.99.99 pop-3
   Trying 192.168.99.99...
   Connected to 192.168.99.99.
   Escape character is '^]'.
   +OK Hello there.
>> USER test
   +OK Password required.
>> PASS password
   +OK logged in.
>> QUIT
   +OK Bye-bye.
   Connection closed by foreign host.

or:

   $ telnet 192.168.99.99 imap
   Trying 192.168.99.99...
   Connected to 192.168.99.99.
   Escape character is '^]'.
   * OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT
   THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright
   1998-2004 Double Precision, Inc.  See COPYING for distribution information.
>> a LOGIN test password
   a OK LOGIN Ok.
>> a LOGOUT
   * BYE Courier-IMAP server shutting down
   a OK LOGOUT completed
   Connection closed by foreign host.

Now you should be able to perform the SMTP login:

   $ telnet 192.168.99.99 smtp
   Trying 192.168.99.99...
   Connected to 192.168.99.99.
   Escape character is '^]'.
   220 localhost ESMTP Postfix (Debian/GNU)
>> MAIL FROM: test@DOMAIN.COM 
   250 Ok
>> RCPT TO: some_address@some_remote_domain.com
   250 Ok
>> DATA
   354 End data with <CR><LF>.<CR><LF>
>> blah blah
>> .
   250 Ok: queued as 3DCFE103C9
>> QUIT
   221 Bye
   Connection closed by foreign host.

Testing SMTP Authentication

I did this a lot. First...

tail -f /var/log/mail.log

Then go to another terminal window (or machine):

telnet my.mail.server 25

The response should be:

Trying ip.add.re.ss...
Connected to my.mail.server.
Escape character is '^]'.
220 localhost ESMTP Postfix (Debian/GNU)

Type:

ehlo localhost

The response should be:

250-localhost
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH LOGIN PLAIN 
250-AUTH=LOGIN PLAIN
250 8BITMIME

The original documentation says to look for these lines to make sure everything is okay.

250-STARTTLS
250-AUTH

Originally I didn't have "250-STARTTLS" and it didn't work, but now I've made changes, it does work, and I still don't have "250-STARTTLS." Go figure.

I had a variety of error messages, which I shall now try to replicate for your edification...

This one seems fairly straightforward. It means either saslauthd isn't running, or it isn't running in the postfix chroot.

Dec  5 23:13:52 ruber postfix/smtpd[11099]: warning: SASL authentication failure: \
 cannot connect to saslauthd server: Connection refused
Dec  5 23:13:52 ruber postfix/smtpd[11099]: warning: ip.add.re.ss: \
 SASL LOGIN authentication failed 

If you know saslauthd is running "ps aux" should show the processes, then kill it:

/etc/init.d/saslauthd stop

(I would check with "ps aux" again, because I had problems stopping this and often just killed it manually.)

Check /etc/defaults/saslauthd to make sure the following line exists:

PARAMS="-m /var/spool/postfix/var/run/saslauthd -r"

Conclusion

If all of the system tests worked then you have a functioning POP3/IMAP/SMTP E-mail server. Congratulations.

You should now be able to set up your favourite e-mail client and begin sending and receiving e-mails (after, of course, making any DNS changes to your network and/or to the MX record at your registrar).

To add new users you need to add new rows to the USERS and VIRTUAL tables in MySQL, and to create the directories. To make this easier I have created some scripts (see below) to help add and delete users.

Enjoy.


Future Enhancements

Some future enhancements I have in mind are:

 X Add SMTP Authentication (done 2005-12-05)
 Create a script that will perform most/all the setup
 Document the method for adding additional (virtual) domains
 X Add spam filtering (SpamAssassin) (done 2006-02-09)
 X Add virus checking (ClamAV) (done 2006-02-09)

Scripts

Courier Daemon Script

The courier daemon script will allow you to start/stop/restart the several Courier daemons.

#!/bin/sh

case "$1" in
start)
        for var in /etc/init.d/courier-*; do
            $var start;
        done
        ;;
stop)
        for var in /etc/init.d/courier-*; do
            $var stop;
        done
        ;;
restart)
        for var in /etc/init.d/courier-*; do
            $var restart;
        done
        ;;
reload)
        for var in /etc/init.d/courier-*; do
            $var reload;
        done
        ;;
force-reload )
        for var in /etc/init.d/courier-*; do
            $var force-reload;
        done
        ;;
*)
        echo "Usage: $0 {start|stop|restart|reload|force-reload}" >&2
        exit 1
        ;;
esac
exit 0

Add Mail User Script

The addmailuser script makes it easy to add a new mail user. It is quick and dirty, needs a lot of work, but it gets the job done.

# addmailuser
# A very (very) simple script to create an e-mail user.
# Creates the directory structure and the database entry (using
# the /etc/courier file for username/password/database name.
# It's not secure or foolproof, but it's quick and gets the job done.
#
#!/bin/sh

# Including the -X values, we expect/require 6 command-line args.
# Let's make sure we get them.
TOTAL_ARGS=6
Username=""
Domain=""
Password=""

if [ "$#" -ne "$TOTAL_ARGS" ]
then
  echo "Usage $0 -u {Username} -d {Domain} -p {password}"
  exit 1
fi

while getopts "u:d:p:" Option
do
  case $Option in
    u) Username="${OPTARG}";;
    d) Domain=${OPTARG};;
    p) Password=${OPTARG};;
    *) break;;
  esac
done
       
if [ "${#Username}" -eq 0 ]
then
  echo "Usage $0 -u {Username} -d {Domain} -p {password}"
  echo "Username required"
  exit 1
fi

if [ "${#Domain}" -eq 0 ]
then
  echo "Usage $0 -u {Username} -d {Domain} -p {password}"
  echo "Domain required"
  exit 1
fi

if [ "${#Password}" -eq 0 ]
then
  echo "Usage $0 -u {Username} -d {Domain} -p {password}"
  echo "Password required"
  exit 1
fi

# Now we've got what is (we hope) all needed information, let's begin.
# First we create the directories
PARENT_DIR=/var/spool/postfix/virtual/
DOMAIN_DIR=$PARENT_DIR$Domain/
USER_DIR=$DOMAIN_DIR$Username/
MAIL_DIR=$USER_DIR"Maildir/"

mkdir $DOMAIN_DIR 2>/dev/null
chown 999 $DOMAIN_DIR
mkdir $USER_DIR
maildirmake $MAIL_DIR
maildirmake -f Trash $MAIL_DIR
maildirmake -f Drafts $MAIL_DIR
maildirmake -f Sent $MAIL_DIR
chown -R 999.100 $USER_DIR
chmod 0700 $USER_DIR

# Finally we need to create the database entries. To do this we will need to get the
# database name, user, and password from the /etc/courier/authmysqlrc file. These 
# values are stored in the MYSQL_SERVER, MYSQL_USERNAME, MYSQL_PASSWORD entries.
DATAFILE=/etc/courier/authmysqlrc
DB=`grep MYSQL_DATABASE $DATAFILE | grep -v : `
USER=`grep MYSQL_USERNAME $DATAFILE | grep -v : `
PW=`grep MYSQL_PASSWORD $DATAFILE | grep -v : `

# Clean up tabs/whitespace, etc. There are quicker/cleaner ways of doing this, but
# this is a quick 'n' dirty script.
DB=${DB//MYSQL_DATABASE/}
DB=${DB//       /}     # The spaces actually are a single tab.
DB=${DB// /}           # This one's just a space.

USER=${USER//MYSQL_USERNAME/}
USER=${USER//   /}     # The spaces actually are a single tab.
USER=${USER// /}       # This one's just a space.

PW=${PW//MYSQL_PASSWORD/}
PW=${PW//       /}     # The spaces actually are a single tab.
PW=${PW// /}           # This one's just a space.

CMDLINE="-s --database=$DB -u $USER --password=$PW -e"
QRY1="INSERT INTO virtual VALUES( '$Username@$Domain', '$Username@$Domain' )"
QRY2="INSERT INTO users VALUES( '$Username@$Domain', '$Username@$Domain', 
     ENCRYPT( '$Password' ), '$Password', ' ', 999, 100, '$USER_DIR', '$Domain',
     '$MAIL_DIR' )"

mysql $CMDLINE "$QRY1"
if [ $? -ne "0" ]
then
  echo "Error occurred in insert query:"
  echo $CMDLINE \"$QRY1\"
  exit 1
fi

mysql $CMDLINE "$QRY2"
if [ $? -ne "0" ]
then
  echo "Error occurred in insert query:"
  echo $CMDLINE \"$QRY1\"
  exit 1
fi

echo "Okay"

Delete Mail User Script

The delmailuser script makes it easy to delete a mail user. As with the addmailuser script, it is quick and dirty, but it gets the job done.

# delmailuser
# A very (very) simple script to delete an e-mail user.
# Removes the directory structure and the database entry (using
# the /etc/courier file for username/password/database name.
# It's not secure or foolproof, but it's quick and gets the job done.
#
#!/bin/sh

# Including the -X values, we expect/require 4 command-line args.
# Let's make sure we get them.
TOTAL_ARGS=4
Username=""
Domain=""

if [ "$#" -ne "$TOTAL_ARGS" ]
then
  echo "Usage $0 -u {Username} -d {Domain}"
  exit 1
fi

while getopts "u:d:" Option
do
  case $Option in
    u) Username="${OPTARG}";;
    d) Domain=${OPTARG};;
    *) break;;
  esac
done

if [ "${#Username}" -eq 0 ]
then
  echo "Usage $0 -u {Username} -d {Domain} -p {password}"
  echo "Username required"
  exit 1
fi

if [ "${#Domain}" -eq 0 ]
then
  echo "Usage $0 -u {Username} -d {Domain} -p {password}"
  echo "Domain required"
  exit 1
fi

echo "Are you sure you want to delete user $Username? (yes/no) [default: no]"
read ynval

if [ "$ynval" != "yes" ]
then
  echo "Answer was not \"yes\"; aborting."
  exit 0
fi

# Now we've got what is (we hope) all needed information, let's begin.
# First we create the directory variables
PARENT_DIR=/var/spool/postfix/virtual/
DOMAIN_DIR=$PARENT_DIR$Domain/
USER_DIR=$DOMAIN_DIR$Username/

rm -fr $USER_DIR

# Next we delete the database values. We grab needed MySQL info in the
# same way as the addmailuser script.
DATAFILE=/etc/courier/authmysqlrc
DB=`grep MYSQL_DATABASE $DATAFILE | grep -v : `
USER=`grep MYSQL_USERNAME $DATAFILE | grep -v : `
PW=`grep MYSQL_PASSWORD $DATAFILE | grep -v : `

# Clean up tabs/whitespace, etc. There are quicker/cleaner ways of doing this, but
# this is a quick 'n' dirty script.
DB=${DB//MYSQL_DATABASE/}
DB=${DB//       /}     # The spaces actually are a single tab.
DB=${DB// /}           # This one's just a space.

USER=${USER//MYSQL_USERNAME/}
USER=${USER//   /}     # The spaces actually are a single tab.
USER=${USER// /}       # This one's just a space.

PW=${PW//MYSQL_PASSWORD/}
PW=${PW//       /}     # The spaces actually are a single tab.
PW=${PW// /}           # This one's just a space.

CMDLINE="-s --database=$DB -u $USER --password=$PW -e"
QRY1="DELETE FROM virtual where address = '$Username@$Domain'"
QRY2="DELETE FROM users where id = '$Username@$Domain'"

mysql $CMDLINE "$QRY1"
if [ $? -ne "0" ]
then
  echo "Error occurred in delete query:"
  echo $CMDLINE \"$QRY1\"
  exit 1
fi

mysql $CMDLINE "$QRY2"
if [ $? -ne "0" ]
then
  echo "Error occurred in delete query:"
  echo $CMDLINE \"$QRY2\"
  exit 1
fi

echo "Okay"

Recommended Reading

The following references were useful in creating this document: