Thursday, August 02, 2007

Howto Setup a Mail Server with Virtual Users and Domains

Introduction

This document describes how to setup a Mail Server with Virtual Domains (Aliases and Mailboxes) with Dovecot POP3 and IMAP Server, Postfix MTA with Dovecot LDA transport and Dovecot SASL Authentication, MySQL, Postfix Admin and SquirrelMail. We use CentOS as the base system, so it's easily usable on various Red Hat Enterprice Linux (RHEL) versions and on Fedora, but probably also usable on all other system too.
A few word about the component selection. To avoid religius war we do not explain why we choose CentOS and MySQL. Postfix was a natural choise since not sendmail, supported by many distribution, easy to configure, fast and secure. We use Dovecot since 2002, it's fast, secure and easy to configure. With Dovecot LDA the most resource intensive task, the indexing, is distributed as mail received not as the users open the Inbox. This can be very important with many mailbox. With Dovecot SASL we can avoid Cyrus SASL which is more complicated, more heavy weight and much more complicated to configure. What's more with Dovecot Auth we can use not just the same datasource but the same service for authentication both in the MTA (Postfix), POP3 and IMAP (Dovecot). Postfix Admin is almost the only usable solution to manage multiple domains, assign admins to different domains, manage alias and quotas for domains and for users and multilanguage. The most feature rich and multilanguage PHP based webmail system.
This document is based on the following documents:

Disclaimer

This document assumes that you have some knowledge on server setup all of the required softwares. At least enough to get everything installed. Installing and basic configuration of the software is outside the scope of this document.

Requirements

At the time of writing this document we use the these versions of the required softwares (probably newer version also usable):
  • CentOS 4.4 (or RHEL 4 update 4 or Fedora 6)
  • Apache 2.0.52
  • PHP 5.1.6
  • MySQL 5.0.22
  • Dovecot 1.0rc15
  • Postfix 2.3.5
  • Postfix Admin 2.1.1 or 2.2 or the latest from SVN (2.1.0 has a few bug:-()

How to Build the required packages

In same case the packages shiped with the distribution (including the latest updates) is suitable if not we list how to obtain them and build them, but in case you're too lazy you can download all of these packages from here:-)

PHP 5.1.6

Probably CentOS 4 default 4.3.9 also usable. If you still would like to update to this version you can get this version from centosplus repository with:
yum --enablerepo=centosplus install php-mysql 

MySQL 5.0.22

Probably CentOS 4 default 4.1.20 also usable. If you still would like to update to this version you can get this version from centosplus repository with:
yum --enablerepo=centosplus install mysql-server 

Postfix 2.3.5

May be CentOS 4 default 2.2.10 also usable, but 2.3.x series contains a few extension so it's advice to update. Get the latest source rpm fromFedora Core Developmentand install it (Note: the src.rpm not the i386.rpm) with:
rpm -Uvh postfix-2.3.x-1.src.rpm 
If it still not the latest get the latest source from any of thePostfix Download Siteand put int into rpm/SOURCES/ directory. In postfix.spec modify the version and add MySQL support (%define MYSQL 1) and build it:
rpmbuild -ba postfix.spec

Dovecot 1.0rc15

Get the latest source rpm fromFedora Core Developmentand install it (Note: the src.rpm not the i386.rpm) with:
rpm -Uvh dovecot-1.0-1.rc15.fc7.src.rpm 
In dovecot.spec remove PostgreSQL support (%define build_postgres 0) if you wouldn't like to install postgresql-lib and build it:
rpmbuild -ba dovecot.spec 

Postfix Admin 2.1.1 or 2.2 or the latest from SVN (2.1.0 has a few bug:-()

If 2.1.1 or 2.2 is not released (not now) I suggest to use the lastest from SVN:
svn co https://postfixadmin.svn.sourceforge.net/svnroot/postfixadmin/trunk postfixadmin 

Configuration

First of all we assume you install everything which is required and you do the basic configuration (eg. Apache, PHP). Now lets configure our mail server components.

MySQL

It's not requires any kind of special configuration, but i suggest to use large configuration default file:
cp /usr/share/doc/mysql-server-*/my-large.cnf /etc/my.cnf
and use UTF-8 character encoding as a default for you sever (to avoid later character problems). Add to your /etc/my.cnf :
default-character-set = utf8
default-collation = utf8_general_ci
(you can define password for user like root, but that outside the scope of this document) and start you mysql server:
service mysqld start
Now we can install the database required for Postfix Admin. Before you do this you can change the password in this file or you can change them later:
mysql -u root < /usr/share/doc/postfixadmin-2.*/DATABASE_MYSQL.TXT
Now add assess right to mysql for dovecot we also add a abckup user which backup the whole database (you can change password here too):
mysql
mysql> grant SELECT ON postfix.* to 'dovecot'@'localhost' IDENTIFIED by 'dovecot';
mysql> grant SELECT, RELOAD, LOCK TABLES ON *.* to 'backup'@'localhost' IDENTIFIED by 'dump';
mysql> flush privileges;
mysql> exit
Tha's all for MySQL.

Dovecot

At this point you have to decice a few things.
  • In which directory you would like to store mails? We choose /var/vmail/, and create it
    mkdir -p /var/vmail
    chmod 770 /var/vmail
  • Which UID and GID like to use in this folder (the best choise something different from all other system and user accounts). We choose UID 101 and mail for GID:
    useradd -r -u 101 -g mail -d /var/vmail -s /sbin/nologin -c "Virtual mailbox" vmail
    chown vmail.mail /var/vmail
Now configure Dovecot itself. We list only where we change something in the default config. We useCACert certificates for SSL (that's outside of the scope of this docs), but you can use the default self-signed certs also. Here is our /etc/dovecot.conf everything else is commented out:
ssl_cert_file = /etc/pki/dovecot/certs/myserver.example.com.crt
ssl_key_file = /etc/pki/dovecot/private/myserver.example.com.key
ssl_ca_file = /etc/pki/dovecot/certs/ca-bundle.crt
mail_location = maildir:/var/vmail/%d/%u
mail_extra_groups = mail
first_valid_uid = 101
last_valid_uid = 101
maildir_copy_with_hardlinks = yes
protocol imap {
mail_plugins = quota imap_quota
imap_client_workarounds = outlook-idle delay-newmail
}
protocol pop3 {
mail_plugins = quota
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
}
protocol lda {
postmaster_address = postmaster@example.com
mail_plugins = quota
log_path = /var/log/dovecot-deliver.log
info_log_path = /var/log/dovecot-deliver.log
}
auth default {
mechanisms = plain
passdb sql {
args = /etc/dovecot/sql.conf
}
userdb sql {
args = /etc/dovecot/sql.conf
}
userdb prefetch {
}
user = nobody
socket listen {
master {
path = /var/run/dovecot/auth-master
mode = 0660
user = vmail
group = mail
}
client {
path = /var/spool/postfix/private/auth
mode = 0660
user = postfix
group = mail
}
}
}
dict {
}
plugin {
quota = maildir:storage=10240:messages=1000
acl = vfile:/etc/dovecot/acls
trash = /etc/dovecot/trash.conf
}
}and two more config files /etc/dovecot/sql.conf (don't forget to fix the password here to the one you use in MySQL for Dovecot and use one lines for the query statements), more info here (the second query always use a static maildir and home which can be also used in our case):
driver = mysql
connect = host=localhost dbname=postfix user=dovecot password=dovecot
user_query = SELECT concat('/var/vmail/', maildir) as home, concat('maildir:/var/vmail/', maildir) as mail, 101 AS uid, 12 AS gid, concat('maildir:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
# fast but now so nice:-)
#user_query = SELECT '/var/vmail/%d/%n' as home, 'maildir:/var/vmail/%d/%n' as mail, 101 AS uid, 12 AS gid, concat('dirsize:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
password_query = SELECT username as user, password, concat('/var/vmail/', maildir) as userdb_home, concat('maildir:/var/vmail/', maildir) as userdb_mail, 101 as userdb_uid, 12 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
# fast but now so nice:-)
#password_query = SELECT username as user, password, '/var/vmail/%d/%n' as userdb_home, 'maildir:/var/vmail/%d/%n' as userdb_mail, 101 as userdb_uid, 12 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
and /etc/dovecot/trash.conf (you may want to add Sent too to this file, but we wouldn't like), more info here:
1 Spam
2 Trash
That's all for Dovecot.

Postfix

First add this to the end of your /etc/postfix/master.cf for LDA delivery:
# Dovecot LDA
dovecot unix - n n - - pipe
flags=DRhu user=vmail:mail argv=/usr/libexec/dovecot/deliver -d ${recipient}
then the end of /etc/postfix/main.cf (we collect all of our changes to the end of this file):
# --------------- local settings ------------------
myhostname = myserver.example.com
inet_interfaces = localhost, $myhostname
mynetworks = $config_directory/mynetworks
mydestination = localhost.$mydomain, localhost, $myhostname
relay_domains = proxy:mysql:$config_directory/mysql_relay_domains_maps.cf
relay_clientcerts = hash:$config_directory/relay_clientcerts
address_verify_map = btree:/var/spool/postfix/address_verify
# ---------------------- VIRTUAL DOMAINS START ----------------------
virtual_mailbox_domains = proxy:mysql:$config_directory/mysql_virtual_domains_maps.cf
virtual_mailbox_base = /var/vmail
virtual_mailbox_maps = proxy:mysql:$config_directory/mysql_virtual_mailbox_maps.cf
virtual_alias_maps = proxy:mysql:$config_directory/mysql_virtual_alias_maps.cf
virtual_minimum_uid = 101
virtual_uid_maps = static:101
virtual_gid_maps = static:12
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1
# ---------------------- VIRTUAL DOMAINS END ----------------------
# ---------------------- SASL PART START ----------------------
smtpd_sasl_auth_enable = yes
#smtpd_sasl_local_domain = $myhostname
smtpd_sasl_exceptions_networks = $mynetworks
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_sasl_type = dovecot
# Can be an absolute path, or relative to $queue_directory
smtpd_sasl_path = private/auth
# ---------------------- SASL PART END ----------------------
# ---------------------- TLS PART START ----------------------
smtp_tls_CAfile = /etc/pki/tls/certs/cert.pem
smtp_tls_cert_file = /etc/pki/tls/certs/myserver.example.com.crt
smtp_tls_key_file = /etc/pki/tls/private/myserver.example.com.key
smtp_tls_session_cache_database = btree:/var/spool/postfix/smtp_tls_session_cache
smtp_tls_security_level = may
smtpd_tls_CAfile = /etc/pki/tls/certs/cert.pem
smtpd_tls_cert_file = /etc/pki/tls/certs/myserver.example.com.crt
smtpd_tls_key_file = /etc/pki/tls/private/myserver.example.com.key
smtpd_tls_session_cache_database = btree:/var/spool/postfix/smtpd_tls_session_cache
smtpd_tls_dh1024_param_file = $config_directory/dh_1024.pem
smtpd_tls_dh512_param_file = $config_directory/dh_512.pem
smtpd_tls_security_level = may
smtpd_tls_received_header = yes
smtpd_tls_ask_ccert = yes
smtpd_tls_loglevel = 1
tls_random_source = dev:/dev/urandom
# ---------------------- TLS PART END ----------------------
smtpd_helo_required = yes
disable_vrfy_command = yes
non_fqdn_reject_code = 450
invalid_hostname_reject_code = 450
maps_rbl_reject_code = 450
#unverified_sender_reject_code = 550
#header_checks = pcre:$config_directory/header_checks
#body_checks = pcre:$config_directory/body_checks
smtpd_recipient_restrictions =
permit_mynetworks
permit_sasl_authenticated
permit_tls_clientcerts
reject_unauth_destination
check_recipient_access pcre:$config_directory/recipient_global.allow
reject_invalid_helo_hostname
warn_if_reject reject_non_fqdn_helo_hostname
warn_if_reject reject_unknown_helo_hostname
warn_if_reject reject_unknown_client
reject_non_fqdn_sender
reject_non_fqdn_recipient
reject_unknown_sender_domain
reject_unknown_recipient_domain
warn_if_reject reject_unverified_sender
reject_unverified_recipient
check_helo_access hash:$config_directory/mydomain.deny
check_helo_access cidr:$config_directory/mynetworks.deny
check_helo_access cidr:$config_directory/reserved_ip.deny
check_sender_mx_access cidr:$config_directory/reserved_ip.deny
check_sender_access pcre:$config_directory/sender.deny
check_sender_access hash:$config_directory/sender.verify
check_recipient_access pcre:$config_directory/recipient.deny
check_sender_access pcre:$config_directory/sender_email.allow
check_recipient_access pcre:$config_directory/recipient_email_fast.allow
check_recipient_access pcre:$config_directory/recipient_domain.allow
reject_rbl_client cbl.abuseat.org
reject_rbl_client list.dsbl.org
reject_rbl_client opm.blitzed.org
reject_rbl_client sbl.spamhaus.org
reject_rbl_client bl.spamcop.net
reject_rbl_client dnsbl.sorbs.net=127.0.0.2
reject_rbl_client dnsbl.sorbs.net=127.0.0.3
reject_rbl_client dnsbl.sorbs.net=127.0.0.4
reject_rbl_client dnsbl.sorbs.net=127.0.0.5
reject_rbl_client dnsbl.sorbs.net=127.0.0.7
reject_rbl_client dnsbl.sorbs.net=127.0.0.9
reject_rbl_client dnsbl.sorbs.net=127.0.0.11
reject_rbl_client dnsbl.sorbs.net=127.0.0.12
warn_if_reject reject_rhsbl_sender dsn.rfc-ignorant.org
warn_if_reject reject_rhsbl_sender abuse.rfc-ignorant.org
warn_if_reject reject_rhsbl_sender whois.rfc-ignorant.org
warn_if_reject reject_rhsbl_sender bogusmx.rfc-ignorant.org
warn_if_reject reject_rhsbl_sender postmaster.rfc-ignorant.org
permit
smtpd_data_restrictions =
reject_unauth_pipelining,
reject_multi_recipient_bounce,
permit
This may seems to be a little bit complicated, but most of it is not relevant to you. If you like to start with a minimal set delete all rbl, rhsbl, check, reject and permit lines. The other important files mysql_virtual_alias_maps.cf (don't forget to change the passwords here):
user            = postfix
password = postfix
hosts = localhost
dbname = postfix
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
mysql_virtual_domains_maps.cf:
user            = postfix
password = postfix
hosts = localhost
dbname = postfix
#query = SELECT domain FROM domain WHERE domain='%s'
#optional query to use when relaying for backup MX
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1'
mysql_relay_domains_maps.cf:
user            = postfix
password = postfix
hosts = localhost
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' and backupmx = '1'
mysql_virtual_mailbox_limit_maps.cf:
user            = postfix
password = postfix
hosts = localhost
dbname = postfix
query = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'
mysql_virtual_mailbox_maps.cf:
user            = postfix
password = postfix
hosts = localhost
dbname = postfix
query = SELECT CONCAT(domain,'/',maildir) FROM mailbox WHERE username='%s' AND active = '1'
That's all for Postfix.

Postfix Admin

If you have more virtual domains in you Web Server than uncomment everything in/etc/httpd/conf.d/postfixadmin.confand put it into your VirtualHost in Apache. Edit local settings in/etc/postfixadmin/config.inc.php(it's well documented), here is our setting (comments dropped):
<?php
if (ereg ("config.inc.php", $_SERVER['PHP_SELF']))
{
header ("Location: login.php");
exit;
}
#$CONF['postfix_admin_url'] = 'http://myserver.example.com/postfixadmin/admin';
$CONF['postfix_admin_url'] = '';
$CONF['postfix_admin_path'] = '';
$CONF['default_language'] = 'en';
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'postfixadmin';
$CONF['database_name'] = 'postfix';
$CONF['database_prefix'] = '';
$CONF['database_prefix'] = '';
$CONF['database_tables'] = array (
'admin' => 'admin',
'alias' => 'alias',
'domain' => 'domain',
'domain_admins' => 'domain_admins',
'log' => 'log',
'mailbox' => 'mailbox',
'vacation' => 'vacation'
);
$CONF['admin_email'] = 'postmaster@example.com';
$CONF['smtp_server'] = 'localhost';
$CONF['smtp_port'] = '25';
$CONF['encrypt'] = 'md5';
$CONF['generate_password'] = 'YES';
$CONF['show_password'] = 'NO';
$CONF['page_size'] = '10';
$CONF['default_aliases'] = array (
'abuse' => 'abuse@example.com',
'hostmaster' => 'hostmaster@example.com',
'postmaster' => 'postmaster@example.com',
'webmaster' => 'webmaster@example.com'
);
$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['aliases'] = '10';
$CONF['mailboxes'] = '10';
$CONF['maxquota'] = '10';
$CONF['quota'] = 'YES';
$CONF['quota_multiplier'] = '1024000';
$CONF['transport'] = 'NO';
$CONF['transport_options'] = array (
'virtual', // for virtual accounts
'local', // for system accounts
'relay' // for backup mx
);
$CONF['transport_default'] = 'virtual';
$CONF['vacation'] = 'NO';
$CONF['vacation_domain'] = 'autoreply.example.com';
$CONF['vacation_control'] ='YES';
$CONF['vacation_control_admin'] = 'YES';
$CONF['alias_control'] = 'NO';
$CONF['alias_control_admin'] = 'YES';
$CONF['special_alias_control'] = 'YES';
$CONF['alias_goto_limit'] = '0';
$CONF['backup'] = 'YES';
$CONF['sendmail'] = 'YES';
$CONF['logging'] = 'YES';
$CONF['show_header_text'] = 'NO';
$CONF['header_text'] = ':: Postfix Admin ::';
$CONF['show_footer_text'] = 'YES';
$CONF['footer_text'] = 'Return to myserver.example.com';
$CONF['footer_link'] = 'http://myserver.example.com/postfixadmin/';
$CONF['welcome_text'] = <<<EOM
Hi,
Welcome to your new account.
EOM;
$CONF['emailcheck_resolve_domain']='YES';
$CONF['create_mailbox_subdirs']=array('Spam','Trash');
$CONF['create_mailbox_subdirs_host']='localhost';
?>
Unfortunately the current SVN of Postfix Admin has a few bugs but it's still usable and better the the latest release. Apply this patch to SVN code (thanks to Stefan Rubner):
--- postfixadmin/functions.inc.php.md5  2006-12-27 14:31:35.000000000 +0100
+++ postfixadmin/functions.inc.php 2006-12-27 14:32:35.000000000 +0100
@@ -887,6 +887,10 @@
$password = md5crypt ($pw, $salt);
}
+ if ($CONF['encrypt'] == 'md5')
+ {
+ $password = md5($pw);
+ }
if ($CONF['encrypt'] == 'system')
{
with:
patch -p1 < postfixadmin-md5.patch
That's all!


Sick sense of humor? Visit Yahoo! TV's Comedy with an Edge to see what's on, when.

0 Comments: