WARNING: THIS HOWTO IS INCONSISTENT AND THERE ARE AMBIGUITIES - CAREFUL!!!
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 words about the component selection. To avoid a religous war, we have not explained why we chose CentOS and MySQL. Postfix was a natural choice since it's not sendmail, many distributions support it, it's easy to configure and fast and secure. We have used Dovecot since 2002. With Dovecot LDA, the most resource intensive task (the indexing) is distributed as mail is received, not as the users open the Inbox. This can be very important in a situation with many mailboxes. 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:
Virtual Users and Domains with Courier-IMAP and MySQL (primary source)
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 these versions of the required software (probably newer versions 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:-()
SquirrelMail 1.4.6
How to Build the required packages
In some cases the packages shipped with the distribution (including the latest updates) are 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 is usable also. If you still would like to update to this version you can get it from centosplus repository with:
yum --enablerepo=centosplus install mysql-server
Postfix 2.3.5
Maybe CentOS 4 default 2.2.10 is usable too, 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 this is not the latest, you can 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
Warning: Do not use version 1.0.7 available in yum (Dovecot.x86_64 0:1.0.7-7.el5). This version contains a bug (file_dotlock_open() failed with file .. Operation not permitted) that does not create directories for new accounts.
Get the latest source rpm fromFedora Core Developmentand install it (Note: the src.rpm not the i386.rpm) with:
Test that postfix is compiled to support mysql and dovecot
hostname ~ # postconf -m | grep mysql mysql hostname ~ # postconf -a | grep dovecot dovecot
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
Make sure that dovecot has been compiled with database support hostname ~ # dovecot --build-options | grep mysql SQL drivers: mysql postgresql sqlite
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 latest from SVN:
svn co https://postfixadmin.svn.sourceforge.net/svnroot/postfixadmin/trunk postfixadmin
If you receive this error when you try to start postfixadmin:
Warning: Unknown: failed to open stream: Permission denied in Unknown on line 0 Warning: Unknown: Failed opening '/var/www/postfixadmin/index.php' for inclusion (include_path='.:/usr/share/pear') in Unknown on line
It may be necessary to execute following 3 commands:
chown -R root:root /var/www/postfixadmin/
find /var/www/postfixadmin/ -type f -exec chmod u=rw,go=r {} \;find /var/www/postfixadmin/ -type d -exec chmod u=rwx,go=rx {} \;
Configuration
First of all we assume you install everything which is required and you do the basic configuration (e.g. Apache, PHP). Now lets configure our mail server components.
MySQL
It does not require any kind of special configuration, but I suggest using the 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 server (to avoid later character problems). Add to your /etc/my.cnf :
[mysqld] default-character-set = utf8 default-collation = utf8_general_ci
If this is a new installation it will need to be initalized by running
mysql_install_db --user=mysql
Start your mysql server:
service mysqld start
Optionally you can secure the installation by running the wizard application
mysql_secure_installation
Now we can install the database required for Postfix Admin.
mysql -u root -p
If you created a password for root use it now otherwise just press enter.
mysql> CREATE DATABASE postfix; mysql> CREATE USER postfix@localhost IDENTIFIED BY 'your_password'; mysql> GRANT ALL PRIVILEGES ON postfix.* TO postfix; mysql> exit
Now add access rights to mysql for dovecot. We also add a "backup" user to 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
That's all for MySQL.
Dovecot
At this point you have to decide 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
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 {
# Having "login" also as a mechanism make sure outlook can use the auth smtpd as well
# http://wiki.dovecot.org/Authentication/Mechanisms
mechanisms = plain login
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), more info here (the second query always use a static maildir and home which can be also used in our case) When copy & paste this text make sure you use one line for the query statements :
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'
# Just in case you are using postfix the delimiter char "+", the above query will probably fail for the username '%n' or '%u' and result in a "5.5.1 user unknown" error
#in this case, you will probalby want to use a separate user and domain part, whilst searching only for the destination user part (user_query only):
# SELECT ... WHERE username = substring_index('%n','+',1) AND userrealm = '%d'
# OR: use "-d ${user}@${domain} -n -m ${extension}" (w/o quotes) for parameters for deliver in postfix/master.cf (instead of "-d ${recipient}")
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
You may also wish to configure the master ACL in /etc/dovecot/acls.
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 at the end of this file):
# --------------- local settings ------------------
myhostname = myserver.example.com
inet_interfaces = localhost, $myhostname
mynetworks = $config_directory/mynetworks
mydestination = localhost.$mydomain, localhost, $myhostname
#uncomment if you need relay_domains... do not list domains in both relay and virtual
#relay_domains = proxy:mysql:$config_directory/mysql_relay_domains_maps.cf
# ---------------------- 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_mailbox_limit_maps = proxy:mysql:/etc/postfix/mysql_virtual_mailbox_limit_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
#Postfix 2.5 or greater must use:
#smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache
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
#Postfix 2.5 or greater must use:
#smtpd_tls_session_cache_database = btree:$data_directory/smtpd_tls_session_cache
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
#warning: the restrictions reject_unknown_(sender|recipient)_domain
#will trigger if your DNS becomes unavailable
smtpd_recipient_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_unauth_destination
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
reject_rbl_client zen.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,
permitThis 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.
Comment from a user: I found out, that I had to add the line, local_transport=virtual; without this line, the mail was not delivered to the created virtual users.
Comment from another user: it would be very nice, if anybody could resolve this ambiguity - THANKS!
The other important files /etc/postfix/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'
/etc/postfix/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'
/etc/postfix/mysql_relay_domains_maps.cf:
user = postfix password = postfix hosts = localhost dbname = postfix query = SELECT domain FROM domain WHERE domain='%s' and backupmx = '1'
/etc/postfix/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'
/etc/postfix/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'
Comment from a user: I found out that the query string in mysql_virtual_mailbox_maps.cf did not work for me together with the query string from sql.conf for dovecot. That is because the domain is added for postfix, but for dovecot it is not. Therefore I changed the query string in mysql_virtual_mailbox_maps.cf to query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
Comment from another user: it would be very nice, if anybody could resolve this ambiguity - THANKS!
/etc/postfix/mynetworks:
# This specifies the list of subnets that Postfix considers as # "trusted" SMTP clients that have more privileges than "strangers". # # In particular, "trusted" SMTP clients are allowed to relay mail # through Postfix. # 192.168.0.0/16 Moja Mreza 127.0.0.0/8 172.16.0.0/12 Moji Linkovi
That's all for Postfix.
Postfix Admin
Warning: currently postfixadmin fux up with mysql, if you use utf-8 tables - so be careful and use latin1 encoding. Authors of postfixadmin: meanwhile please go and study the last posting of Jason Urrich here: http://bugs.mysql.com/bug.php?id=4541
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';
?>WARNING: the original author did not want to tell us which version of postfixadmin he is talking about here - I recommend not to path the current version!
Unfortunately the current SVN of Postfix Admin has a few bugs but it's still usable and better than 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!
SquirrelMail
File <$config_directory/mynetworks> is not in the postfix installation. But it is referenced in this (great) howto, resulting in errors in /var/log/maillog. Can you add it to the howto please?
Comment from user: 2 things: - Instead of creating the vmail user's home dir in /var/ it is better to do that in /home. -> Also note that the logpath /var/log/dovecot-deliver.log is most likely not writable by dovecot since /var/log is by default only writeable by root. - Also I would rather choose a more memorable and not to be confused with other user's GUI and UID for the vmail user, since this id is referenced throughout Something like 5000 is fine.
Testing
if you are facing issues increase the logging http://wiki.dovecot.org/Logging
Psotfix:
Test the postfix config
postfix check
Test the users storage path
postmap -q test1@mydomain.com mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf
test the SMTP port is open, you type lines marked as you>
you> telnet localhost 25 Trying 127.0.0.1... Connected to localhost.localdomain (127.0.0.1). Escape character is '^]'. 220 mail.acme.local ESMTP Postfix you> ehlo host 250-mail.acme.local 250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250 DSN you> mail from: johndoe 250 2.1.0 Ok you> rcpt to: johndoe 250 2.1.5 Ok you> data 354 End data with <CR><LF>.<CR><LF> you> test you> . 250 2.0.0 Ok: queued as 9729067C17 you> quit 221 2.0.0 Bye Connection closed by foreign host.
