Email Server With Postfix Dovecot and MailScanner (Part 2 - Postfix and Dovecot)

Postfix is the mail transfer agent (MTA) that routes and delivers email. Dovecot is the IMAP and POP3 server. The two works together make a perfect email server.

Postfix

Postfix is installed and running after default CentOS 7 installation. It is just a basic SMTP server lintening on local interface (127.0.0.1) and serve for existing local user only.

Database for virtual domain and user

We will install PostfixAdmin which use MaridDB as back-end to handle virtual domain and user accounts. When Postfix and Dovecot want authenticate users they will query the MariaDB database.

Login MariaDB as root and create a database postfix. Add user postfix and grant accesses to it.

mysql -uroot -p

Create a database

CREATE DATABASE postfix;
CREATE USER 'postfix'@'localhost' IDENTIFIED BY 'PApassword';
GRANT ALL PRIVILEGES ON postfix.* TO 'postfix'@'localhost';
FLUSH PRIVILEGES;
QUIT;

Postfix Admin

Postfix Admin is a web based interface used to manage mailboxes, virtual domains and aliases

Get the Postfix Admin site ready:

cd /usr/local/src
wget http://downloads.sourceforge.net/project/postfixadmin/postfixadmin/postfixadmin-2.93/postfixadmin-2.93.tar.gz
tar zvxf postfixadmin-2.93.tar.gz
rm -rf /var/www/html/postfixadmin
mv postfixadmin-2.93 /var/www/html/postfixadmin
chown -R nginx:nginx /var/www/html/postfixadmin

Edit /var/www/html/postfixadmin/config.inc.php
With:

<?php
$CONF['configured'] = true;

$CONF['database_type'] = 'mysqli';
$CONF['database_user'] = 'postfix';
$CONF['database_password'] = 'PApassword';
$CONF['database_name'] = 'postfix';
?>

I also need to manual create a session dir:

mkdir /var/lib/php/session/
chown -R nginx:nginx /var/lib/php/session/

Postfix Admin web setup

Now go to https://postfixadmin.mydomain.com/setup.php to continue the setup in the web interface.

Type in “Setup Password” and generate password hash.
Copy this line back to /var/www/html/postfixadmin/config.inc.php, at line 30:

$CONF['setup_password'] = '82020bc067e2a6deaa3fba3632529114:0834f649bf09ee930910ab1b353e3f0722b77ee8';

Then create the superadmin account.

Now use the superadmin account to login at https://postfixadmin.mydomain.com/login.php. This is the admin login.

Once we create user email account, the user may login to manage their own account at https://postfixadmin.mydomain.com/users/login.php. Note the different address.

Bug Fix:
After login, click on “Fetch email” I got error “Invalid query: FUNCTION postfix.FROM_BASE64 does not exist”
To fix it, edit /var/www/html/postfixadmin/model/PFAHandler.php at line 572:

$base64_decode = "###KEY###";

Create 5 database query configration files:

  1. /etc/postfix/mysql-virtual_domains_maps.cf

    hosts = localhost
    user = postfix
    password = PApassword
    dbname = postfix
    query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1'
  2. /etc/postfix/mysql-relay_domains_maps.cf

    hosts = localhost
    user = postfix
    password = PApassword
    dbname = postfix
    query = SELECT domain FROM domain WHERE domain='%s' and backupmx = '1'
  3. /etc/postfix/mysql-virtual_mailbox_maps.cf

    hosts = localhost
    user = postfix
    password = PApassword
    dbname = postfix
    query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
  4. /etc/postfix/mysql-virtual_mailbox_limit_maps.cf

    hosts = localhost
    user = postfix
    password = PApassword
    dbname = postfix
    query = SELECT quota FROM mailbox WHERE username='%s' and active = '1'
  5. /etc/postfix/mysql-virtual_alias_maps.cf

    hosts = localhost
    user = postfix
    password = PApassword
    dbname = postfix
    query = SELECT goto FROM alias WHERE address='%s' AND active = '1'

Change permission to protect this files:

chgrp postfix mysql-*.cf
chmod 640 /etc/postfix/mysql-*.cf

Make Postfix use these 5 files to qurey database, add the following lines to /etc/postfix/main.cf

relay_domains = proxy:mysql:/etc/postfix/mysql-relay_domains_maps.cf
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual_alias_maps.cf,regexp:/etc/postfix/virtual_regexp
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql-virtual_domains_maps.cf
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf
virtual_mailbox_limit_maps = proxy:mysql:/etc/postfix/mysql-virtual_mailbox_limit_maps.cf
proxy_read_maps = $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps $sender_bcc_maps $recipient_bcc_maps $smtp_generic_maps $lmtp_generic_maps $alias_maps $virtual_mailbox_limit_maps

Create virtual_regexp file

touch /etc/postfix/virtual_regexp

To enable postfix listen on all interface, edit /etc/postfix/main.cf line 113,116:

inet_interfaces = all
#inet_interfaces = localhost

vmail user

Create a local user vmail and set its home dir as maildir:

groupadd -g 5000 vmail
mkdir /home/vmail
chmod 770 /home/vmail/
useradd -r -u 5000 -g vmail -d /home/vmail/ -s /sbin/nologin -c "Virtual Mailbox" vmail
chown vmail:vmail /home/vmail/

Make postfix to use this vmail account, add to /etc/postfix/main.cf

virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
virtual_mailbox_base = /home/vmail

Restart postfix:

systemctl restart postfix

Also open TCP port 25 in iptables.

Now the Postfix SMTP is running. I can test it from another computer:

$ telnet smtp.mydomain.com 25
Trying 11.22.33.44...
Connected to smtp.mydomain.com.
Escape character is '^]'.
220 smtp.mydomain.com ESMTP Postfix

Test virtual domain/mailbox

Setup test email account

Go to Postfix Admin site [https://postfixadmin.mydomain.com], log in as admin user, then create a virtual domain. In my case, I go to “Domain List” –> “New Domain”, add “mydomain.com”.

Next go to “Virtual List”, then “Add Mailbox”, add a newmail box(Username: “gao”). Make sure “Active” and “Send Welcome mail” are checked. Then click on “Add Mailbox”.

Test receiving email

If everything is ok, you should see an email has been sent to gao@mydomain.com in the /var/log/maillog log file:

Apr 20 15:47:13 smtp postfix/smtpd[27293]: connect from localhost[::1]
Apr 20 15:47:14 smtp postfix/smtpd[27293]: 5CC0199812: client=localhost[::1]
Apr 20 15:47:14 smtp postfix/cleanup[27300]: 5CC0199812: message-id=<20160420224714.5CC0199812@smtp.mydomain.com>
Apr 20 15:47:14 smtp postfix/smtpd[27293]: disconnect from localhost[::1]
Apr 20 15:47:14 smtp postfix/qmgr[27051]: 5CC0199812: from=<gao@mydomain.com>, size=485, nrcpt=1 (queue active)
Apr 20 15:47:14 smtp postfix/virtual[27309]: 5CC0199812: to=<gao@mydomain.com>, relay=virtual, delay=0.34, delays=0.11/0.07/0/0.17, dsn=2.0.0, status=sent (delivered to maildir)
Apr 20 15:47:14 smtp postfix/qmgr[27051]: 5CC0199812: removed

Check the maildir I saw the email has been put in the maildir:

# ls -l /home/vmail/mydomain.com/gao/new/
total 4
-rw------- 1 vmail vmail 568 Apr 20 15:47 1461192434.Vfd02I30000081M492053.smtp.mydomain.com

I also tested it by sending an email from my Gmail account and watch the maillog to see the mail arrived.

Test send email out

I still use telnet command from a remote PC to send a test email out:

$ telnet smtp.mydomain.com 25
Trying 11.22.33.44...
Connected to smtp.mydomain.com.
Escape character is '^]'.
220 smtp.mydomain.com ESMTP Postfix
EHLO jade
250-smtp.mydomain.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
MAIL FROM: gao@mydomain.com
250 2.1.0 Ok
RCPT TO: mytestmail@gmail.com
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
Subject: this is a telnet test

Hi,this is just a quick test form telnet.
.
250 2.0.0 Ok: queued as 2528F1A4E3
QUIT
221 2.0.0 Bye
Connection closed by foreign host.

Check the maillog to make sure the mail is send out.

At this stage, sending email to Gmail may fail. This will be fixed later by setup proper SPF and DKIM record in our DNS settings. If you have to test with Gmail then you may add mydomain.com as truseted domain at [https://postmaster.google.com]

SMTP Submission

I will enable SMTP submission on port 587, and use dovecot embebed SASL authentication to verify user identity.

Edit /etc/postfix/master.cf, remove comments in these lines:

submission inet n       -       n       -       -       smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=$mua_client_restrictions
-o smtpd_helo_restrictions=$mua_helo_restrictions
-o smtpd_sender_restrictions=$mua_sender_restrictions
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING

Edit /etc/postfix/main.cf, add lines:

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

smtpd_tls_cert_file = /etc/letsencrypt/live/mydomain.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mydomain.com/privkey.pem
smtpd_tls_security_level = may

#Disable sslv2 ad SSLv3
smtpd_tls_protocols= !SSLv2, !SSLv3
smtpd_tls_mandatory_protocols= !SSLv2, !SSLv3

#set minimum TLS ciphers grade for tls
smtpd_tls_mandatory_ciphers = high

#use server ciphers instead client preference
tls_preempt_cipherlist = yes

#ciphers to exclude
smtpd_tls_mandatory_exclude_ciphers = aNULL, MD5 , DES, ADH, RC4, PSD, SRP, 3DES, eNULL

#disallow plain login
smtpd_tls_auth_only = yes

mua_client_restrictions = permit_sasl_authenticated,reject
mua_helo_restrictions = permit_sasl_authenticated,reject
mua_sender_restrictions = permit_sasl_authenticated,reject

Restart postfix

systemctl restart postfix

Dovecot

I use Dovecot to provide POP amd IMAP services so the user can use an email client(Thunderbird, Outlook.,etc) to retrive emails.

Install dovecot

yum install dovecot dovecot-mysql dovecot-pigeonhole

dovecot main config file

Edit dovecot main config file /etc/dovecot/dovecot.conf

protocols = imap pop3
listen = *
shutdown_clients = yes

Enable and start dovecot:

systemctl enable dovecot
systemctl start dovecot

Check use netstat to see dovecot is listening on port 110,143,993,995

# netstat -ntlp | grep dovecot
tcp 0 0 0.0.0.0:110 0.0.0.0:* LISTEN 7564/dovecot
tcp 0 0 0.0.0.0:143 0.0.0.0:* LISTEN 7564/dovecot
tcp 0 0 0.0.0.0:993 0.0.0.0:* LISTEN 7564/dovecot
tcp 0 0 0.0.0.0:995 0.0.0.0:* LISTEN 7564/dovecot

Dovecot authentication

I configured Dovecot to use database created by PostfixAdmin for user authentication with Let’s Encript’s SSL certificate:

database query configration

Create a file /etc/dovecot/conf.d/dovecot-mysql.conf.ext

driver = mysql
connect = host=localhost dbname=postfix user=postfix password=PApassword
password_query = SELECT username as user, password, concat('/home/vmail/', maildir) as userdb_home, concat('maildir:/home/vmail/', maildir) as userdb_mail, 5000 as userdb_uid, 5000 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
user_query = SELECT concat('/home/vmail/', maildir) as home, concat('maildir:/home/vmail/', maildir) as mail, 5000 AS uid, 5000 AS gid, CONCAT('*:bytes=', quota) as quota_rule FROM mailbox WHERE username = '%u' AND active = '1'

Also we need a file to provide information about accounts quota. Edit /etc/dovecot/conf.d/dovecot-mysql-quota.conf.ext

connect = host=localhost dbname=postfix user=postfix password=PApassword
map {
pattern = priv/quota/storage
table = quota2
username_field = username
value_field = bytes
}
map {
pattern = priv/quota/messages
table = quota2
username_field = username
value_field = messages
}

Set file permission

chmod 640 /etc/dovecot/conf.d/dovecot-mysql*

With above files dovecot will query mariadb info about users idendity and mailbox quotas.

Make dovecot use MariaDB for authentication

Edit /etc/dovecot/conf.d/auth-sql.conf.ext

passdb {
driver = sql
args = /etc/dovecot/conf.d/dovecot-mysql.conf.ext
}
userdb {
driver = sql
args = /etc/dovecot/conf.d/dovecot-mysql.conf.ext
}

Enable dovecot to use sql_auth, edit /etc/dovecot/conf.d/10-auth.conf

disable_plaintext_auth = yes

auth_mechanisms = plain login cram-md5

#!include auth-system.conf.ext
!include auth-sql.conf.ext

Enable SSL/TLS support

Edit /etc/dovecot/conf.d/10-ssl.conf

ssl_cert = </etc/letsencrypt/live/mydomain.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mydomain.com/privkey.pem

# SSL protocols to use
ssl_protocols = !SSLv2 !SSLv3
# SSL ciphers to use
ssl_cipher_list = EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4

# Prefer the server's order of ciphers over client's.
ssl_prefer_server_ciphers = yes

Edit /etc/dovecot/conf.d/10-master.conf

service imap-login {
inet_listener imap {
port = 143
}
inet_listener imaps {
port = 993
ssl = yes
}

service pop3-login {
inet_listener pop3 {
port = 110
}
inet_listener pop3s {
port = 995
ssl = yes
}
}

service auth {

unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = vmail
group = vmail
}

Maildir and mail user

Edit /etc/dovecot/conf.d/10-mail.conf

mail_location = maildir:/home/vmail/%d/%n/:INDEX=/home/vmail/%d/%n/indexes
mail_uid =5000
mail_gid =5000
first_valid_uid = 5000
last_valid_uid = 5000
first_valid_gid = 5000
last_valid_gid = 5000

Test dovecot

Open TCP port 993,995 and 587 on iptables. (I force all clients to use SSL/TLS so I keep port 110 and 143 closed.)

Restart dovecot

systemctl restart dovecot

Test SSL login to IMAPS on port 993 from a remote PC:

#openssl s_client -connect smtp.mydomain.com:993 -quiet
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X1
verify error:num=20:unable to get local issuer certificate
verify return:0
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN AUTH=LOGIN AUTH=CRAM-MD5] Dovecot ready.
a1 LOGIN gao@mydomain.com my_password
a1 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE BINARY MOVE] Logged in
a2 LIST "" "*"
* LIST (\HasNoChildren) "." INBOX
a2 OK List completed.
a3 EXAMINE INBOX
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* OK [PERMANENTFLAGS ()] Read-only mailbox.
* 4 EXISTS
* 4 RECENT
* OK [UNSEEN 1] First unseen.
* OK [UIDVALIDITY 1457462303] UIDs valid
* OK [UIDNEXT 5] Predicted next UID
a3 OK [READ-ONLY] Examine completed (2.716 secs).
a4 LOGOUT
* BYE Logging out
a4 OK Logout completed.

Mailbox quota

I enable imap_quota plugin:
Edit /etc/dovecot/conf.d/10-mail.conf, line 215 should be

mail_plugins = $mail_plugins quota

Edit /etc/dovecot/conf.d/20-imap.conf, line 56 should be

mail_plugins = $mail_plugins imap_quota

Edit /etc/dovecot/conf.d/90-quota.conf, line 68 should look like these

quota = maildir:User quota

Now restart dovecot service

Edit mailbox quota in PostfixAdmin, set to non-zero, then I can see the quota of INBOX.

Test the email server

Now I can create an new email account in Thunderbird to test.

Always use full email address as login name. Here is the setup with IMAP on port 993:

Now I can send out and receive email so the basic mail server is working.


Quick links: