Updated as of October 1, 2011.
In this document I am going to share my experience with setting up a viable virtual mail server solution for a Linux system.
I have made countless attempts at trying to setup mail servers in the past, but have never been able to achieve the results that I desired.
Whether it was the incomplete HOWTOs that I was reading or my own lack of knowledge on the subject, I don't know; but after eventually reading through enough
document I was able to conceive a solution that works great (so far on two boxes). Most importantly, however, I will attempt to cover and help you resolve the common problems that myself and others have ran into.
Before asking a question, check this article for some fixes.
By the end of this document you will hopefully achieve what I have:
- A MySQL database which mail users will be primarily authenticated against.
- Postfix MTA will allow SMTP AUTH SASL connections instead of using relay-domains. Authentication will be done against MySQL and PAM.
- Maildrop will deliver mail to the users' home directories using Maildir style mail boxes. Any missing Maildir folders will be created.
- Invoked within Maildrop, SpamAssassin will do its best to flag spam. Then maildrop will deliver it into a separate Maildir.
- Users will retrieve their mail using IMAP. Authentication will be done against MySQL and PAM.
- You will NOT need to use PAM-MySQL.
To start off, you will use the following applications:
Installing Sources
First and foremost, you need to compile the sources of the top applications.
I will not go through installing MySQL since it is outside of the scope of this document.
OpenSSL
Compile and install OpenSSL.
$ tar zxvf openssl-yourversion.tar.gz
$ ./config
$ make
$ make test
$ make install (as root)
Cyrus SASL v2
The following assumes that you have MySQL setup in: /usr/local/mysql.
We will be using the built in MySQL authentication module, instead of having to redirect the authentication to PAM-MySQL. Prior to installing these modules, attempt to remove any pre-installed ones (look in: /usr/lib, /usr/local/lib, /usr/local/lib/sasl2).
$ tar zxvf cyrus-sasl-2.1.22.tar.gz
$ ./configure
--enable-anon
--enable-plain
--enable-login
--enable-sql
--disable-krb4
--disable-otp
--disable-cram
--disable-digest
--with-mysql=/usr/local/mysql/lib
--without-pam
--with-saslauthd
--without-pwcheck
--with-dblib=berkeley
--with-openssl=/usr/local/ssl
--with-plugindir=/usr/local/lib/sasl2
$ make
$ make install (as root)
Check to make sure that the path /usr/local/lib is in /etc/ld.so.conf.
If it is not, append that path to the file and run ldconfig (as root):
$ echo "/usr/local/lib" >> /etc/ld.so.conf
$ ldconfig
Double check to make sure libsql.so is in /usr/local/lib/sasl2.
Postfix
Compiling Postfix is pretty straight forward. Be sure to add the postfix user and group. During the install it will ask for the user and group, use postfix and postdrop. For the configuration directory, specify /etc/postfix.
$ groupadd postdrop -g 1001 (as root)
$ useradd postfix -u 1001 -g 1001 (as root)
$ tar zxvf postfix-2.5.5.tar.gz
$ make -f Makefile.init makefiles
$ make makefiles 'CCARGS=-DHAS_MYSQL -I/usr/local/mysql/include -I/usr/local/mysql/include/mysql -DUSE_CYRUS_SASL -DUSE_SASL_AUTH -I/usr/local/include/sasl -I/usr/local/bdb/include' 'AUXLIBS=-L/usr/local/mysql/lib -lmysqlclient -lz -lm -L/usr/local/lib -lsasl2'
$ make install (as root)
After you finish with the interactive installation, you will need to check and see if Postfix is linked against SASL.
To do this run the following from within the root of Postfix's source:
$ ldd ./bin/postconf
If everything worked well (and I hope it did), one of the lines should read:
libsasl2.so.2 => /usr/local/lib/libsasl2.so.2
Courier Authlib
Authlib is now responsible for all MySQL authentication. Maildrop and imapd
are not longer responsible of it (which actually makes it easier to compile).
Maildrop and imapd need to be aware of authlib! Be sure to specify the user and group when compiling authlib, otherwise maildrop will have no
access to authlib. DO NOT put the uid and gid, it must be the name, authdaemond will work but
courierauthconfig will fail (hence the rest of the apps).
$ export CPPFLAGS="-I/usr/local/mysql/include/mysql"
$ export LDFLAGS="-L/usr/local/mysql/lib"
$ ./configure --prefix=/usr/local/courier --with-mysql-libs=/usr/local/mysql/lib/mysql --with-mysql-includes=/usr/local/mysql/include/mysql/ --with-authmysql --with-authmysql=yes --with-authchangepwdir --with-mailuser=vmail --with-mailgroup=vmail
$ make
$ make install
Once compiled there are only a few files to modify.
In /usr/local/courier/etc/authlib copy the files by removing the ".dist" at the end.
Courier IMAP
As with Postfix, Courier IMAP should be simple to install.
We will use /usr/local/courier as the base directory for the IMAPd and for Maildrop.
This also allows you to use Courier MTA in the future without having to re-do a lot of things.
$ bzip2 -dc courier-imap-4.4.1.tar.bz2 | tar xvf -
$ ./configure --prefix=/usr/local/courier COURIERAUTHCONFIG=/usr/local/courier/bin/courierauthconfig
$ make
$ make install
$ make install-config
Courier Maildrop
Now we go to installing Maildrop, our local delivery agent. First you create the user and group that Maildrop will deliver as. For our application, we will use vmail for both.
$ bzip2 -dc maildrop-2.0.4.20080726.tar.bz2 | tar xvf -
$ groupadd vmail -g 1004 (as root)
$ useradd vmail -u 1004 -g 1004 (as root)
$ tar zxvf maildrop-1.6.3.tar.gz
$ ./configure --prefix=/usr/local/courier --enable-maildrop-uid=1004 --enable-maildrop-gid=1004 --with-authpwd=yes COURIERAUTHCONFIG=/usr/local/courier/bin/courierauthconfig
$ make
$ make install
SpamAssassin
We're almost done installing our sources, just one more to go. Prior to installing SpamAssassin, get all the required Perl modules (check the INSTALL file).
$ tar zxvf Mail-SpamAssassin-2.63.tar.gz
$ perl MakeFile.PL
$ make
$ make install (as root)
Database and Virtual Mail Directory Setup
Database
The database side of this was used from Martin List-Petersen's excellent document, ISP Mailserver Solution Howto that helped me most of the way.
I will just copy the CREATE TABLE syntax that is provided in his HOWTO and explain the use of two main tables
(things such as the default uid and gid have been modified to fit this document).
I expect that you have created the database (in this document we will use "mail" as our database) and have secured it with a user and password.
CRITICAL NOTE: When creating the user for our database, create TWO: one that authenticates from "localhost" and one from "127.0.0.1"
I don't know why it doesn't see it as the same, but on two different boxes (RedHat 9 and Slackware 9) it needed two seperate entries.
CREATE TABLE postfix_alias (
id int(11) unsigned NOT NULL auto_increment,
alias varchar(128) NOT NULL default '',
destination varchar(128) NOT NULL default '',
PRIMARY KEY (id)
) TYPE=MyISAM;
CREATE TABLE postfix_relocated (
id int(11) unsigned NOT NULL auto_increment,
email varchar(128) NOT NULL default '',
destination varchar(128) NOT NULL default '',
PRIMARY KEY (id)
) TYPE=MyISAM;
CREATE TABLE postfix_transport (
id int(11) unsigned NOT NULL auto_increment,
domain varchar(128) NOT NULL default '',
destination varchar(128) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY domain (domain)
) TYPE=MyISAM;
CREATE TABLE postfix_users (
id int(11) unsigned NOT NULL auto_increment,
email varchar(128) NOT NULL default '',
clear varchar(128) NOT NULL default '',
crypt varchar(128) NOT NULL default '',
name tinytext NOT NULL,
uid int(11) unsigned NOT NULL default '1004',
gid int(11) unsigned NOT NULL default '1004',
homedir tinytext NOT NULL,
maildir tinytext NOT NULL,
quota tinytext NOT NULL,
access enum('Y','N') NOT NULL default 'Y',
postfix enum('Y','N') NOT NULL default 'Y',
PRIMARY KEY (id),
UNIQUE KEY email (email)
) TYPE=MyISAM;
CREATE TABLE postfix_virtual (
id int(11) unsigned NOT NULL auto_increment,
email varchar(128) NOT NULL default '',
destination varchar(128) NOT NULL default '',
PRIMARY KEY (id)
) TYPE=MyISAM;
CREATE TABLE postfix_access (
id int(10) unsigned NOT NULL auto_increment,
source varchar(128) NOT NULL default '',
access varchar(128) NOT NULL default '',
type enum('recipient','sender','client') NOT NULL default 'recipient',
PRIMARY KEY (id)
) TYPE=MyISAM
In this document I will only go over the postfix_users and postfix_virtual tables, as Martin List-Petersen does a great job at describing the rest. When we get to testing, I will go into detail on what goes into those tables, for now just leave them empty.
Virtual Mail Directory
All mail for our virtual users will be stored in the following format:
/home/vmail
- domain.tld
-- user1
-- user2
--- Maildir
- domain2.tld
Seems simple enough, right? Go ahead and create only the top-most directory (/home/vmail)
and set it's owner and group to vmail. For security reasons, chmod it 700.
$ mkdir /home/vmail
$ chown vmail.vmail /home/vmail
$ chmod 700 /home/vmail
Next we move onto configuration.
Configuring Installed Packages
The majority of this was found in Martin List-Petersen's document.
Postfix
Most configuration will be done to Postfix. Open up /etc/postfix/master.cf and change the following:
flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
To:
flags=R user=vmail argv=/usr/local/courier/bin/maildrop -d ${recipient}
CRITICAL NOTE: Be sure that the two leading spaces on those lines remain present.
Now open up /etc/postfix/main.cf and configure the following:
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
mydestination = example.com, $transport_maps
local_recipient_maps = $alias_maps $virtual_mailbox_maps unix:passwd.byname
home_mailbox = Maildir/
# Add the following to the bottom
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_maps_rbl
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
broken_sasl_auth_clients = yes
smtpd_recipient_restrictions = permit_mynetworks,
reject_unauth_pipelining,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain,
permit_sasl_authenticated,
reject_rbl_client sbl-xbl.spamhaus.org,
reject_rbl_client bl.spamcop.net,
#reject_rbl_client ips.backscatterer.org,
check_recipient_access mysql:/etc/postfix/mysql-recipient.cf,
reject_unauth_destination
smtpd_sender_restrictions = check_sender_access mysql:/etc/postfix/mysql-sender.cf
smtpd_client_restrictions = check_client_access mysql:/etc/postfix/mysql-client.cf
smtpd_sasl_authenticated_header = yes
smtpd_sasl_path = smtpd
alias_maps = mysql:/etc/postfix/mysql-aliases.cf
relocated_maps = mysql:/etc/postfix/mysql-relocated.cf
transport_maps = mysql:/etc/postfix/mysql-transport.cf
virtual_maps = mysql:/etc/postfix/mysql-virtual.cf
virtual_mailbox_base = /home/vmail
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-maps.cf
virtual_uid_maps = mysql:/etc/postfix/mysql-virtual-uid.cf
virtual_gid_maps = mysql:/etc/postfix/mysql-virtual-gid.cf
maps_rbl_domains =
dev.null.dk,
opm.blitzed.org,
sbl.spamhaus.org
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_helo_restrictions =
reject_invalid_hostname,
permit
Once again, the following is borrowed from Martin List-Petersen's document.
To conserve time writing this document, I will paste the contents of the required files that allow Postfix to communicate with MySQL.
# mysql-aliases.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_alias
select_field = destination
where_field = alias
hosts = 127.0.0.1
# mysql-relocated.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_relocated
select_field = destination
where_field = email
hosts = 127.0.0.1
# mysql-transport.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_transport
select_field = destination
where_field = domain
hosts = 127.0.0.1
# mysql-virtual.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_virtual
select_field = destination
where_field = email
hosts = 127.0.0.1
# mysql-recipient.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_access
select_field = access
where_field = source
additional_conditions = and type = 'recipient'
hosts = 127.0.0.1
# mysql-sender.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_access
select_field = access
where_field = source
additional_conditions = and type = 'sender'
hosts = 127.0.0.1
# mysql-client.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_access
select_field = access
where_field = source
additional_conditions = and type = 'client'
hosts = 127.0.0.1
# mysql-virtual-maps.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_users
select_field = maildir
where_field = email
additional_conditions = and postfix = 'y'
hosts = 127.0.0.1
# mysql-virtual-uid.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_users
select_field = uid
where_field = email
additional_conditions = and postfix = 'y'
hosts = 127.0.0.1
# mysql-virtual-gid.cf
user = mysql-postfix-user
password = mysql-postfix-pass
dbname = mail
table = postfix_users
select_field = gid
where_field = email
additional_conditions = and postfix = 'y'
hosts = 127.0.0.1
Of course you will have to substitue the username and password for the one you created earlier.
Courier IMAP
Open /usr/local/courier/etc/authlib/authdaemonrc (if it doesn't exist, make a copy from authdaemonrc.dist located in the same directory). Change the line that starts with "authmodulelist" to read:
"authmysql authshadow"
Next create a file called authmysqlrc (in the same directory) and put the following in:
MYSQL_USERNAME USRENAME
MYSQL_PASSWORD PASSWORD
MYSQL_PORT 0
MYSQL_OPT 0
MYSQL_DATABASE mail
MYSQL_USER_TABLE postfix_users
MYSQL_LOGIN_FIELD email
MYSQL_CRYPT_PWFIELD crypt
MYSQL_CLEAR_PWFIELD clear
MYSQL_UID_FIELD uid
MYSQL_GID_FIELD gid
MYSQL_HOME_FIELD homedir
MYSQL_MAILDIR_FIELD maildir
MYSQL_WHERE_CLAUSE access='y'
Open /usr/local/courier/etc/imapd and make sure the following lines looks like this:
# This is all on one line
IMAP_CAPABILITY="IMAP4rev1 CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT AUTH=CRAM-MD5 AUTH=CRAM-SHA1 IDLE"
# Seperate line
IMAPDSTART=YES
Open /usr/local/courier/etc/imapd-ssl and make sure the following line looks like this:
IMAPDSSLSTART=YES
Courier Maildrop
While we're still in /usr/local/courier/etc edit or create a file called maildropmysql.config and put in the following:
hostname localhost
port 3306
database mail
dbuser USERNAME
dbpw PASSWORD
dbtable postfix_users
default_uidnumber 1004
default_gidnumber 1004
uid_field email
uidnumber_field uid
gidnumber_field gid
maildir_field maildir
homedirectory_field homedir
quota_field quota
# unused for now, but needs to be a valid field.
mailstatus_field postfix
where_clause AND postfix = 'y'
Now we need to setup Maildrop to deliver our mail. Create (or edit) /etc/maildroprc and put in the following:
NOTE: Take care in bracket placement, Maildrop is very picky in this sense.
if ( $SIZE < 26144 )
{
exception {
xfilter "/usr/bin/spamassassin"
}
}
# if the user in the domain does not exist, create it
# create the domain with parents, then remove Maildir/
# finally create a new maildir
`test -d $HOME/$DEFAULT`
if ( $RETURNCODE == 1 )
{
`mkdir -p $HOME/$DEFAULT`
`rm -rf $HOME/$DEFAULT`
`/usr/local/courier/bin/maildirmake $HOME/$DEFAULT`
`/usr/local/courier/bin/maildirmake $HOME/$MAILDIR/.Spam/`
`/usr/local/courier/bin/maildirmake $HOME/$MAILDIR/.Spam/.New`
}
if (/^X-Spam-Flag: *YES/)
{
exception {
to "$HOME/$DEFAULT.Spam"
}
}
exception {
to "$HOME/$DEFAULT"
}
Cyrus SASL
Since Cyrus will be doing some authenticating, we need to configure it as well. Create the file smtpd.conf in /usr/local/lib/sasl2 and put in the following:
sasl_pwcheck_method: auxprop
sasl_auxprop_plugin: mysql login plain crammd6 digestmd5
mysql_user: USERNAME
mysql_passwd: PASSWORD
mysql_hostnames: localhost
mysql_database: mail
mysql_statement: SELECT clear FROM postfix_users WHERE email = '%u@%r'
mysql_verbose: yes
Finalizing
Adding a user
You are now ready to add a test user to the database, start the mailserver and test the whole system out.
Populate the postfix_transport table with the following:
domain: "test.com"
destination: "maildrop:"
The domain field needs no explination, however, the destination field does. In the destination field Postfix is told what program the message should be carried to.
For instance, if you wanted delivery to be handled by Postfix's internal system, you would put "virtual:" In our example however, we are using maildrop, so we put "maildrop:"
Next add a test user by populating the postfix_users table.
email: "test@test.com"
clear: "mypassword"
homedir: "/home/vmail"
maildir: "test.com/test/Maildir/"
That's all there is to adding a new user. The "email" field stores just that, the user's email
(it must include the TLD). The "clear" field stores the user's password in clear text.
"Homedir" contains the root directory for all virtual mail, if we were using "local:" delivery,
this would be set to the user's home directory. Finally, "maildir" is the Maildir location relative to the homedir.
Starting the Daemons
Open up your system log (/var/log/messages or /var/log/maillog) and monitor it as you start your daemons:
$ /usr/local/sbin/saslauthd -a shadow
$ /usr/local/courier/sbin/authdaemond start
$ /usr/local/courier/libexec/imapd.rc start
$ /usr/sbin/postfix start
If everything went right, the daemons should start without any failures. nmap yourself and see what ports are open, there should be three (smtp, imap and imap-ssl)
Testing the SMTP and IMAP Servers
Everything not starting with a ">" or "$" is a response from the server.
$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.test.com ESMTP Postfix
> EHLO test.com
250-mail.test.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN PLAIN
250-XVERP
250 8BITMIME
If you see something like that, congratulations, you're still in good shape!
Create all directories all the way up to "/home/vmail/test.com/test" after which create a Maildir in that directory. Maildrop will NOT create Maildir's by default (source has to be patched to do so).
Continue the telnet session and type in the following:
> MAIL FROM: test@test.com
250 Ok
> RCPT TO: test@test.com
250 Ok
> DATA
354 End data with <CR><LF>.<CR><LF>
> .
250 Ok: queued as 6CE4223727
Now, if everything is setup right, the mailserver should queue your message. Check inside of /home/vmail/test.com/test/Maildir/new for anything, if you see a file, congratulations, the message was delivered!
When using a mail client (such as Outlook or Mozilla), be sure to use the full email address for the username (ie: "user@domain.com", not just "user"). |