Virtual Mail Hosting on Centos7
Using
Postfix MariaDB Dovecot PostfixAdmin Amavisd-new Spamassassin and Clamav




Virtual Mail hosting provides the option of offering mail services to multiple domains on one server.

You will get the following features:


This guide was developed from lessons I learned from Campworld Centos6 Virtual Mail Hosting.  Plus lots of discussions and near flame wars on lists for many of the packages used here.

Further, this guide strives to only modify package config files, rather than replacing them.  This is a nod to those package authors on knowing what works best in their packages and easier adoption of new features.

Enjoy!

NOTE: The services set up here should be run on a system with at least 1GB of memory with 2GB really being the lower limit.  Additionally, though it will run on a duo-core ARMv7 system, a quad core would be better with AMAVIS restricted to 2 core.  AMAVIS and CLAMAV can really tie up the system.


Building the Base Server

Follow the instructions at: Centos7 for armv7 SOC

to build your base Centos7 armv7 server.  Be sure to add the EPEL repo as many of the mailserver packages are there.

Installing the Mail packages

This guide needs the following packages installed:

yum install httpd mod_ssl php mariadb-server php-mysql telnet
yum install dovecot dovecot-mysql dovecot-pigeonhole spamassassin
yum install perl-MIME-EncWords perl-MIME-Charset php-imap
yum install perl-Email-Valid perl-Test-Pod
yum install perl-Mail-Sender imapsync offlineimap
yum install amavisd-new clamav clamav-devel perl-Convert-BinHex
yum install clamav-server clamav-update clamav-server-systemd roundcubemail
yum install clamav-scanner php-mcrypt php-enchant aspell-en

For armv7hl, there are dependency problems for perl-Log-Log4perl.  As of May 4, 2017, the following worked.

mkdir /root/rpms
cd /root/rpms
wget https://armv7.dev.centos.org/repodir/c7-pass-1/rrdtool/1.4.8-8.el7/armv7hl/rrdtool-1.4.8-8.el7.armv7hl.rpm
wget https://armv7.dev.centos.org/repodir/c7-pass-1/rrdtool/1.4.8-8.el7/armv7hl/rrdtool-perl-1.4.8-8.el7.armv7hl.rpm
wget https://armv7.dev.centos.org/repodir/c7-pass-1/pango/1.34.1-5.el7/armv7hl/pango-1.34.1-5.el7.armv7hl.rpm
wget https://armv7.dev.centos.org/repodir/c7-pass-1/harfbuzz/0.9.20-4.el7/armv7hl/harfbuzz-0.9.20-4.el7.armv7hl.rpm
wget https://armv7.dev.centos.org/repodir/c7-pass-1/graphite2/1.2.2-5.el7/armv7hl/graphite2-1.2.2-5.el7.armv7hl.rpm
yum install perl-Log-Log4perl ./*

There is no rpm package for postfixadmin.  Get the most recent version at:
http://sourceforge.net/projects/postfixadmin/
Let's assume that the current version you downloaded is: 3.0.2.  Place it in /usr/share.

cd /usr/share
wget https://downloads.sourceforge.net/project/postfixadmin/postfixadmin/postfixadmin-3.0.2/postfixadmin-3.0.2.tar.gz

Then:

tar -xzvf postfixadmin-3.0.2.tar.gz
mv postfixadmin-3.0.2/ postfixadmin
rm -f postfixadmin-3.0.2*
chown -R root:root postfixadmin
chmod 700 postfixadmin/templates_c/
chown apache:apache postfixadmin/templates_c/



Configuring the Mail packages

Throughout this guide, there are values unique to an installation that have to be provided.  For the most part, these can be handled by first setting some environment variables that will be used in cat and sed commands.  Or you can manually alter the variables.  Special characters (\, $, and / tested) MUST be proceeded by a \.  Spaces should work with quotes around value entered (but not really tested).

To set date_timezone value.  See

http://php.net/date.timezone

for valid timezones (use the same timezone name you used for timedatectl).  Set the variables by altering these commands.

Long passphrases may be more secure for the passwords, and not cause problems with the commands, than the use of special characters or spaces.

your_domain_tld=
your_host_only=
your_host_tld=$your_host_only.$your_domain_tld
date_timezone=
your_ipv4_address=
admin_email=
Mysql_Root_Password=
Postfix_Database_Password=
Roundcube_Database_Password=

Three TLS certificates are created in this guide.  All should have the following values, but any MAY be left blank.  If any include a space, inclose that value in quotes.  Special characters can be included, preceeded by \.

countryName=
stateOrProvinceName=
localityName=
organizationName=
organizationalUnitName=
emailAddress=postmaster@$your_domain_tld

Now there is a lot to configure from all these packages and perhaps you are testing from a temporary IP address that is not in DNS.  For this you need to add to your /etc/hosts file:

bind 'set disable-completion on'
cat <<EOF>>/etc/hosts || exit 1
$your_ipv4_address	$your_host_tld
EOF
bind 'set disable-completion off'

Next we have a lot of firewall rules to add:

firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-service=smtp
firewall-cmd --permanent --add-service=imaps
firewall-cmd --permanent --add-service=pop3s
firewall-cmd --permanent --add-port=587/tcp
firewall-cmd --permanent --add-port=143/tcp
firewall-cmd --permanent --add-port=110/tcp
firewall-cmd --permanent --add-port=4190/tcp
firewall-cmd --reload
firewall-cmd --list-all

Port 587 is used by client smtp to send mail to your mailserver.  Port 143 is used by IMAP.  Port 110 is for POP3.  Port 4190 is used by manageseive.  Amavis uses ports 10024 & 10025 but only to localhost so no firewall rule is needed there.

Now edit /etc/php.ini

sed -i -e "s';date.timezone ='date.timezone = $date_timezone'w /dev/stdout" /etc/php.ini

Now create the mail store directory.  Put it in the /home directory to make backups and other item easy.

mkdir /home/vmail
chmod 770 /home/vmail
useradd -r -u 101 -g mail -d /home/vmail -s /sbin/nologin -c "Virtual mailbox" vmail
chown vmail:mail /home/vmail
chcon -Rv --type=mail_home_rw_t /home/vmail/


Setting up MariaDB

Next start MariaDB and secure it.  You can manually run mysql_secure_installation, or use the following to auto answer the prompts.

systemctl enable mariadb
systemctl start mariadb
mysql_secure_installation <<EOF || exit 1

y
$Mysql_Root_Password
$Mysql_Root_Password
y
y
y
y
EOF

Now setup the mysql database for postfixadmin. The following creates the database and user. The setup URL will create the rest.

mysql -u root -p$Mysql_Root_Password <<EOF || exit 1
CREATE DATABASE postfix;
CREATE USER postfix@localhost IDENTIFIED BY "$Postfix_Database_Password";
GRANT ALL PRIVILEGES ON postfix.* TO postfix@localhost;
EOF

Next is the Roundcube database.

mysql -u root -p$Mysql_Root_Password <<EOF || exit 1
CREATE DATABASE roundcubemail;
CREATE USER roundcube@localhost IDENTIFIED BY "$Roundcube_Database_Password";
GRANT ALL PRIVILEGES ON roundcubemail.* TO roundcube@localhost;
FLUSH PRIVILEGES;
EOF

Then the tables.

mysql -u root -p$Mysql_Root_Password roundcubemail < /usr/share/roundcubemail/SQL/mysql.initial.sql


Setting up Apache

This guide uses virtual hosting for the webmail service (Roundcubemail).  Virtual hosting requires a DNS entry for webmail.your domain as well as your host.  Once virtual hosting is configured, the first virtual host is the default host.  Additionally, once a virtual host is set to use TLS, this also becomes the default behavior.

This conf file will load first and force a polite behavior.


bind 'set disable-completion on'
cat <<EOF>/etc/httpd/conf.d/00-init.conf || exit 1
ServerAdmin $admin_email
ServerName $your_host_tld
<VirtualHost *:80>
	<Directory "/var/www/html">
		Options Indexes FollowSymLinks
		AllowOverride None
		Require all granted
	</Directory>
</VirtualHost>
<VirtualHost *:443>
	SSLEngine On
	SSLCertificateFile /etc/pki/tls/certs/$your_host_tld.crt
	SSLCertificateKeyFile /etc/pki/tls/private/$your_host_tld.key
	<Directory "/var/www/html">
		Options Indexes FollowSymLinks
		AllowOverride None
		Require all granted
	</Directory>
</VirtualHost>
EOF
bind 'set disable-completion off'

The default server access can be restricted with:

sed -i -e "s/all granted/ip 192.168.0.0\/16/gw /dev/stdout" /etc/httpd/conf.d/00-init.conf

Next is postfixadmin's Apache conf file. 

bind 'set disable-completion on'
cat <<EOF>/etc/httpd/conf.d/postfixadmin.conf || exit 1
alias /mailadmin /usr/share/postfixadmin
<Directory "/usr/share/postfixadmin">
	AllowOverride AuthConfig
	Require all granted
</Directory>
EOF
bind 'set disable-completion off'

Postfixadmin is a Security Risk; Postfixadmin access can be restricted to your IP addresses.  Finer control can be added into a /usr/share/postfixadmin/.htaccess file.

sed -i -e "s/all granted/ip 192.168.0.0\/16/gw /dev/stdout" /etc/httpd/conf.d/postfixadmin.conf

Postfixadmin access is via 'http://your_host_tld/mailadmin'.  You don't want the whole world accessing this link.

Roundcubemail httpd configuration is next.  This guide uses virtual hosting.  The conf can be edited to use your_host_tld/webmail.

Further, this guide forces users to https.  This is enhanced over the default roundcubemail.conf.  Also it secures the cookie used by Roundcubemail.  This is an important security deficiency in the default configuration.

cp -b /etc/httpd/conf.d/roundcubemail.conf /etc/httpd/conf.d/roundcubemail.conf.save
bind 'set disable-completion on'
cat <<EOF>/etc/httpd/conf.d/roundcubemail.conf || exit 1
<VirtualHost *:80>

#	Alias /roundcubemail /usr/share/roundcubemail
#	Alias /webmail /usr/share/roundcubemail

	ServerName webmail.$your_domain_tld
	ServerAlias webmail

	Redirect permanent / https://webmail.$your_domain_tld/
	ExpiresDefault "access plus 1 years"
	php_admin_flag session.cookie_secure "1"

</VirtualHost>

<VirtualHost *:443>

# Round Cube Webmail is a browser-based multilingual IMAP client
#

#	Alias /roundcubemail /usr/share/roundcubemail
#	Alias /webmail /usr/share/roundcubemail

	ServerName webmail.$your_domain_tld
	ServerAlias webmail

	SSLEngine On
	SSLCertificateFile /etc/pki/tls/certs/webmail.$your_domain_tld.crt
	SSLCertificateKeyFile /etc/pki/tls/private/webmail.$your_domain_tld.key

	DocumentRoot /usr/share/roundcubemail

<Directory /usr/share/roundcubemail/>
	Require ip 192.168.0.0/16
# You can enlarge permissions once configured
#	Require all granted
	php_admin_flag session.cookie_secure "1"
</Directory>

</VirtualHost>

# Define who can access the installer
# keep this secured once configured

	Alias /roundcubemail /usr/share/roundcubemail

<Directory /usr/share/roundcubemail/installer/>
# You may want to restrict the installer to a single IP address
	Require ip 192.168.0.0/16
</Directory>

# Those directories should not be viewed by Web clients.
<Directory /usr/share/roundcubemail/bin/>
	Require all denied
</Directory>
<Directory /usr/share/roundcubemail/plugins/enigma/home/>
	Require all denied
</Directory>
EOF
bind 'set disable-completion off'

Last step for setting up Apache is creating the SSL certificates.  Three certificates are needed.  One for webmail.$your_domain_tld, another for $your_host_tld, and the third for localhost.  Follow how you setup the roundcubemail.conf and how your users will access your server.  The following commands will create the RSA 2048/SHA256 certificates with a 10 years life.

These are self-signed certificates.  You can purchase your certificates from a recognized CA.  You can find instructions on how to create your own CA and issue these certificates from it.

restore_mask=$(umask -p)
umask 077
cd /etc/pki/tls
commonName=$your_host_tld
openssl req -new -outform PEM -out certs/$commonName.crt -newkey rsa:2048 -nodes -keyout private/$commonName.key -keyform PEM -days 3650 -x509 -extensions v3_req -subj "/countryName=$countryName/stateOrProvinceName=$stateOrProvinceName/localityName=$localityName/organizationName=$organizationName/organizationalUnitName=$organizationalUnitName/commonName=$commonName/emailAddress=$emailAddress"
chmod 640 private/$commonName.key
openssl req -new -outform PEM -out certs/localhost.crt -newkey rsa:2048 -nodes -keyout private/localhost.key -keyform PEM -days 3650 -x509 -extensions v3_req -subj "/countryName=$countryName/stateOrProvinceName=$stateOrProvinceName/localityName=$localityName/organizationName=$organizationName/organizationalUnitName=$organizationalUnitName/commonName=$commonName/emailAddress=$emailAddress"
chmod 640 private/localhost.key
commonName=webmail.$your_domain_tld
openssl req -new -outform PEM -out certs/$commonName.crt -newkey rsa:2048 -nodes -keyout private/$commonName.key -keyform PEM -days 3650 -x509 -extensions v3_req -subj "/countryName=$countryName/stateOrProvinceName=$stateOrProvinceName/localityName=$localityName/organizationName=$organizationName/organizationalUnitName=$organizationalUnitName/commonName=$commonName/emailAddress=$emailAddress"
chmod 640 private/$commonName.key
$restore_mask

You can review the certificate content with:

openssl x509 -in certs/$commonName.crt -text -nameopt multiline -noout|more

If you get any of the certificates 'wrong' httpd will not start, and trouble-shooting can be a challenge. Check out /etc/httpd/logs/ssl_error_log for messages hinting at what is wrong with your certificates.

It is now time to enable and start Apache.

systemctl enable httpd
systemctl start httpd


Configuring Postfix

Now on to configure the actual packages starting with Postfix.

Postfix is a real task to configure.  It will be easier in versions of Postfix beyond 2.10 that is supplied in Centos 7.  The following modifies the existing config files, rather than replacing them.

This is a 'best effort' from reviewing a number of sources. 

The place to start is with main.cf.  Note that message_size_limit limits a message to ~20Meg.  Change this as needed.

cp /etc/postfix/main.cf /etc/postfix/main.cf.save
# postfix config file

# uncomment for debugging if needed
#postconf -e 'soft_bounce=yes'

# postfix main
postconf -e 'delay_warning_time = 4'

# network settings
postconf -e 'inet_interfaces = all'
postconf -e "mydomain = $your_domain_tld"
postconf -e "myhostname = $your_host_tld"
postconf -e 'mynetworks = $config_directory/mynetworks'
postconf -e 'relay_domains = proxy:mysql:/etc/postfix/mysql-relay_domains_maps.cf'

# mail delivery
postconf -e 'recipient_delimiter = +'
postconf -e 'content_filter = amavis:[127.0.0.1]:10024'

# mappings
postconf -e 'alias_maps = hash:/etc/aliases'
postconf -e 'transport_maps = hash:/etc/postfix/transport'

# virtual setup
postconf -e 'virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual_alias_maps.cf, regexp:/etc/postfix/virtual_regexp'
postconf -e 'virtual_mailbox_base = /home/vmail'
postconf -e 'virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql-virtual_domains_maps.cf'
postconf -e 'virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf'
postconf -e 'virtual_minimum_uid = 101'
postconf -e 'virtual_uid_maps = static:101'
postconf -e 'virtual_gid_maps = static:12'
postconf -e 'virtual_transport = dovecot'
postconf -e 'dovecot_destination_recipient_limit = 1'

# authentication
postconf -e 'smtpd_sasl_auth_enable = yes'
# postconf -e 'smtpd_sasl_security_options = noanonymous'
postconf -e 'smtpd_sasl_local_domain = $mydomain'
postconf -e 'broken_sasl_auth_clients = yes'
postconf -e 'smtpd_sasl_type = dovecot'
postconf -e 'smtpd_sasl_path = private/auth'

# tls config
postconf -e 'smtp_use_tls = yes'
postconf -e 'smtp_tls_exclude_ciphers = RC4, aNULL'
postconf -e 'smtp_tls_loglevel = 1'
postconf -e 'smtp_tls_note_starttls_offer = yes'
postconf -e 'smtp_tls_protocols = !SSLv2, !SSLv3'
postconf -e 'smtp_tls_security_level = may'
postconf -e 'smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache'
postconf -e 'smtpd_tls_cert_file = /etc/pki/tls/certs/$myhostname.crt'
postconf -e 'smtpd_tls_exclude_ciphers = RC4, aNULL'
postconf -e 'smtpd_tls_key_file = /etc/pki/tls/private/$myhostname.key'
postconf -e 'smtpd_tls_loglevel = 1'
postconf -e 'smtpd_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA'
postconf -e 'smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3'
postconf -e 'smtpd_tls_protocols = !SSLv2, !SSLv3'
postconf -e 'smtpd_tls_received_header = yes'
postconf -e 'smtpd_tls_security_level = may'
postconf -e 'smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache'
postconf -e 'smtpd_use_tls = yes'

# rules restrictions
postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_non_fqdn_sender, reject_non_fqdn_recipient, reject_unknown_recipient_domain'
postconf -e 'smtpd_sender_login_maps = mysql:/etc/postfix/mysql-virtual_alias_maps.cf'

# others
postconf -e 'smtpd_helo_required = yes'
postconf -e 'disable_vrfy_command = yes'
postconf -e 'message_size_limit = 20480000'
postconf -e 'smtpd_data_restrictions = reject_unauth_pipelining'
postconf -e 'smtpd_banner = $myhostname ESMTP'

master.cf is next.  I have worked out how to just append needed changes to the end.

cp /etc/postfix/master.cf /etc/postfix/master.cf.save

bind 'set disable-completion on'
cat <<\EOF>>/etc/postfix/master.cf || exit 1
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtpd     pass  -       -       n       -       -       smtpd
submission inet n       -       n       -       -       smtpd
	-o smtpd_recipient_restrictions=
pickup    unix  n       -       n       60      1       pickup
	-o content_filter=
relay     unix  -       -       n       -       -       smtp
	-o fallback_relay=
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
#
# spam/virus section
#
amavis unix	-	-	y	-	2	lmtp
	-o lmtp_data_done_timeout=1200
	-o lmtp_send_xforward_command=yes
	-o disable_dns_lookups=yes
	-o max_use=20
127.0.0.1:10025 inet n	-	n	-	-	smtpd
	-o content_filter=
	-o smtpd_delay_reject=no
	-o smtpd_client_restrictions=permit_mynetworks,reject
	-o smtpd_helo_restrictions=
	-o smtpd_sender_restrictions=
	-o smtpd_recipient_restrictions=permit_mynetworks,reject
	-o smtpd_data_restrictions=reject_unauth_pipelining
	-o smtpd_end_of_data_restrictions=
	-o smtpd_restriction_classes=
	-o mynetworks=127.0.0.0/8
	-o smtpd_error_sleep_time=0
	-o smtpd_soft_error_limit=1001
	-o smtpd_hard_error_limit=1000
	-o smtpd_client_connection_count_limit=0
	-o smtpd_client_connection_rate_limit=0
	-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
	-o local_header_rewrite_clients=
	-o smtpd_milters=
	-o local_recipient_maps=
	-o relay_recipient_maps=
#
# Dovecot LDA
dovecot	unix	-	n	n	-	-	pipe
  flags=DRhu user=vmail:mail argv=/usr/libexec/dovecot/deliver -d ${recipient}
#
# Vacation mail
vacation unix	-	n	n	-	-	pipe
  flags=Rq user=vacation argv=/var/spool/vacation/vacation.pl -f ${sender} -- ${recipient}
EOF
bind 'set disable-completion off'

And if you have to support Outlook TLS connections, then

bind 'set disable-completion on'
cat <<EOF>>/etc/postfix/master.cf || exit 1
# Enable only if you have to support Outlook TLS connections
smtps	inet	n	-	n	-	-	smtpd
#
EOF
bind 'set disable-completion off'

Next a number of files in /etc/postfix
mynetworks
mysql-virtual_alias_maps.cf
mysql-virtual_domains_maps.cf
mysql-relay_domains_maps.cf
mysql-virtual_mailbox_maps.cf
virtual_regexp

cat <<EOF>/etc/postfix/mynetworks || exit 1
# 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.
#
# Be sure to add your public ip address block if needed.
#
192.168.0.0/16
10.0.0.0/8
127.0.0.0/8
EOF

cat <<EOF>/etc/postfix/mysql-virtual_alias_maps.cf || exit 1
hosts = localhost
user = postfix
password = $Postfix_Database_Password
dbname = postfix
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
EOF

cat <<EOF>/etc/postfix/mysql-virtual_domains_maps.cf || exit 1
hosts = localhost
user = postfix
password = $Postfix_Database_Password
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1'
EOF

cat <<EOF>/etc/postfix/mysql-relay_domains_maps.cf || exit 1
hosts = localhost
user = postfix
password = $Postfix_Database_Password
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' and backupmx = '1'
EOF

cat <<EOF>/etc/postfix/mysql-virtual_mailbox_maps.cf || exit 1
hosts = localhost
user = postfix
password = $Postfix_Database_Password
dbname = postfix
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
EOF

touch /etc/postfix/virtual_regexp

Edit /etc/postfix/mynetworks as needed,

The last step for postfix is the Vacation Email Functionality

useradd -r -d /var/spool/vacation -s /sbin/nologin -c "Virtual vacation" vacation
mkdir /var/spool/vacation
chmod 770 /var/spool/vacation
cp /usr/share/postfixadmin/VIRTUAL_VACATION/vacation.pl /var/spool/vacation/
echo "autoreply."$your_domain_tld" vacation:" > /etc/postfix/transport
postmap /etc/postfix/transport
chown -R vacation:vacation /var/spool/vacation
echo "127.0.0.1    autoreply."$your_domain_tld >> /etc/hosts
mkdir /etc/postfixadmin

And finally create /etc/postfixadmin/vacation.conf

cat <<\EOF>/etc/postfixadmin/vacation.conf || exit 1
# ========== begin configuration ==========
$db_type = 'mysql';
$db_username = 'user';
$db_password = 'postfixsqlpassword';
$db_name = 'postfix';
$vacation_domain = 'autoreply.change-this-to-your.domain.tld';
EOF
sed -i -e "s/postfixsqlpassword/$Postfix_Database_Password/w /dev/stdout" /etc/postfixadmin/vacation.conf
sed -i -e "s/change-this-to-your.domain.tld/$your_domain_tld/w /dev/stdout" /etc/postfixadmin/vacation.conf

Finally restart postfix

systemctl restart postfix


Configuring Dovecot

Dovecot is next.  There are a number of Dovecot conf files to edit.  Instead, this guide uses the local.conf file which is loaded after the default conf files.  By using the default files and local.conf, these modifications should work in future versions of Dovecot.  The files altered by the local.conf are:

dovecot.conf
10-auth.conf
auth-sql.conf.ext
10-mail.conf
10-master.conf
10-ssl.conf
15-lda.conf
20-imap.conf
20-lmtp.conf
20-managesieve.conf
20-pop3.conf
90-acl.conf
90-quota.conf
90-sieve.conf

bind 'set disable-completion on'
cat <<EOF>/etc/dovecot/local.conf || exit 1
#	Developed on Dovecot 2.2.10


#	dovecot.conf
protocols = imap pop3 lmtp sieve
dict {
	sqlquota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
}

#	10-auth.conf
!include conf.d/auth-sql.conf.ext

#	auth-sql.conf.ext
userdb {
	driver = prefetch
}

#	10-mail.conf
mail_location = maildir:/home/vmail/%d/%n
first_valid_uid = 101
first_valid_gid = 12

#	10-master.conf
service auth {
	unix_listener auth-userdb {
		mode = 0666
		user = vmail
		group = mail
	}
	unix_listener /var/spool/postfix/private/auth {
		mode = 0666
		user = postfix
		group = postfix
	}
}
service dict {
	unix_listener dict {
		mode = 0666
		user = vmail
		group = mail
	}
}

#	10-ssl.conf
ssl_cert = </etc/pki/tls/certs/$your_host_tld.crt
ssl_key = </etc/pki/tls/private/$your_host_tld.key

#	15-lda.conf
postmaster_address = postmaster@$your_domain_tld
protocol lda {
	mail_plugins = quota sieve
}
lda_mailbox_autocreate = yes
lda_mailbox_autosubscribe = yes

#	20-imap.conf
imap_client_workarounds = delay-newmail
protocol imap {
	mail_plugins = quota imap_quota trash
}

#	20-lmtp.conf
lmtp_save_to_detail_mailbox = yes
protocol lmtp {
	mail_plugins = sieve
}

#	20-managesieve.conf
service managesieve-login {
	inet_listener sieve {
		port = 4190
	}
	service_count = 1
	process_min_avail = 0
	vsz_limit = 64M
}

#	20-pop3.conf
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
protocol pop3 {
	mail_plugins = quota
}

#	90-acl.conf
plugin {
	acl = vfile:/etc/dovecot/global-acls:cache_secs=300
}

#	90-quota.conf
plugin {
	quota = dict:user::proxy::sqlquota
	trash = /etc/dovecot/dovecot-trash.conf.ext
}

#	90-sieve.conf
plugin {
	sieve_before = /home/sieve/globalfilter.sieve
}
EOF
bind 'set disable-completion off'

Next the following files are added into /etc/dovecot
dovecot-trash.conf.ext
dovecot-sql.conf.ext
dovecot-dict-sql.conf.ext
Note that the user_query limits an imap mailbox to 30,000 messages.  Change this as needed.

mv /etc/dovecot/dovecot-trash.conf.ext /etc/dovecot/dovecot-trash.conf.ext.save
mv /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/dovecot-sql.conf.ext.save
mv /etc/dovecot/dovecot-dict-sql.conf.ext /etc/dovecot/dovecot-dict-sql.conf.ext.save

bind 'set disable-completion on'
cat <<EOF>/etc/dovecot/dovecot-trash.conf.ext || exit 1
# Spam mailbox is emptied before Trash
1 Spam
# Trash mailbox is emptied before Sent
# 2 Trash
# If both Sent and "Sent Messages" mailboxes exist, the next oldest message
# to be deleted is looked up from both of the mailboxes.
# 3 Sent
# 3 Sent Messages
EOF

cat <<EOF>/etc/dovecot/dovecot-sql.conf.ext || exit 1
driver = mysql
connect = host=/var/lib/mysql/mysql.sock dbname=postfix user=postfix password=$Postfix_Database_Password
default_pass_scheme = MD5-CRYPT
# following should all be on one line.
password_query = SELECT username as user, password, concat('/home/vmail/', maildir) as userdb_home, concat('maildir:/home/vmail/', maildir) as userdb_mail, 101 as userdb_uid, 12 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
# following should all be on one line
user_query = SELECT concat('/home/vmail/', maildir) as home, concat('maildir:/home/vmail/', maildir) as mail, 101 AS uid, 12 AS gid, CONCAT('*:messages=30000:bytes=', quota) as quota_rule FROM mailbox WHERE username = '%u' AND active = '1'
EOF

cat <<EOF>/etc/dovecot/dovecot-dict-sql.conf.ext || exit 1
connect = host=/var/lib/mysql/mysql.sock dbname=postfix user=postfix password=$Postfix_Database_Password
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
}
EOF
bind 'set disable-completion off'

Lastly create the sieve filter for SPAM filtering

bind 'set disable-completion on'
mkdir /home/sieve
cat <<EOF>/home/sieve/globalfilter.sieve || exit 1
require "fileinto";
if anyof
	(
		header :contains "X-Spam-Flag" "YES",
		header :contains "subject" "***SPAM***"
	)
{
	fileinto "Spam";
}
EOF
bind 'set disable-completion off'

sievec /home/sieve/globalfilter.sieve
chown -R vmail:mail /home/sieve
chcon -Rv --type=mail_home_rw_t /home/sieve/

It is now time to enable and start Dovecot.

systemctl enable dovecot
systemctl start dovecot


Configuring Amavis and Clamav

These two packages work together, along with Spamassassin, to provide anti-spam and anti-virus protection.  Their configuration is simple.

With some guidance from https://ismailyenigul.wordpress.com/2015/01/05/install-clamav-on-centos-7/
And more from http://forums.sentora.org/showthread.php?tid=1132
We can get clamav working with the following:

cp /etc/clamd.d/scan.conf /etc/clamd.d/scan.conf.save
cp /etc/freshclam.conf /etc/freshclam.conf.save
sed -i -e "s/^Example/#Example/w /dev/stdout" /etc/freshclam.conf
sed -i -e "s/^Example/#Example/w /dev/stdout" /etc/clamd.d/scan.conf
sed -i -e "s/^#LocalSocket /LocalSocket /w /dev/stdout" /etc/clamd.d/scan.conf
sed -i -e "s/^FRESHCLAM_DELAY/#FRESHCLAM_DELAY/w /dev/stdout" /etc/sysconfig/freshclam
setsebool -P antivirus_can_scan_system 1
setsebool -P clamd_use_jit 1
freshclam

A simple test of clamav is

clamdscan -c /etc/clamd.d/scan.conf /etc/hosts

Next edit /etc/amavisd/amavisd.conf.

cp /etc/amavisd/amavisd.conf /etc/amavisd/amavisd.conf.save
cat <<\EOF>>/etc/amavisd/amavisd.conf || exit 1
$mydomain = 'your_domain_tld';
$myhostname = 'your_host_tld'; # must be a fully-qualified domain name!
$log_level = 1; # set the log level to one
$sa_tag_level_deflt = -99; # I want to see the headers so change to -99
$sa_tag2_level_deflt = 5.0; # start with 5
$sa_kill_level_deflt = 9;
$sa_dsn_cutoff_level = 9;
$sa_quarantine_cutoff_level = 50;
$notify_method = 'smtp:[127.0.0.1]:10025';
$forward_method = 'smtp:[127.0.0.1]:10025';
$final_banned_destiny = D_DISCARD;
$final_spam_destiny = D_PASS;
@mynetworks = qw( 127.0.0.0/8 [::1] [FE80::]/10 [FEC0::]/10 [2607:f4b8:3::]/48
                  10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 50.253.254.0/28);
@lookup_sql_dsn =
   ( ['DBI:mysql:database=postfix;host=localhost;mysql_socket=/var/lib/mysql/mysql.sock', 'postfix', 'postfixsqlpassword'] );
$sql_select_white_black_list = undef;
$sql_select_policy = 'SELECT "Y" as local, 1 as id FROM domain WHERE CONCAT("@",domain) IN (%k)';
1; # insure a defined return value
EOF
sed -i -e "s/your_domain_tld/$your_domain_tld/w /dev/stdout" /etc/amavisd/amavisd.conf
sed -i -e "s/your_host_tld/$your_host_tld/w /dev/stdout" /etc/amavisd/amavisd.conf
sed -i -e "s/postfixsqlpassword/$Postfix_Database_Password/w /dev/stdout" /etc/amavisd/amavisd.conf

Depending on how you setup roundcubemail.conf for httpd

sa-update
systemctl enable amavisd
systemctl enable spamassassin
systemctl stop postfix
systemctl start spamassassin
systemctl start amavisd # This will also run ClamAV
systemctl start postfix

Test the amavis connection with

telnet localhost 10024
ehlo localhost
quit

telnet localhost 10025
ehlo localhost
quit

You should get

telnet localhost 10024
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 [::1] ESMTP amavisd-new service ready
ehlo localhost
250-[::1]
250-VRFY
250-PIPELINING
250-SIZE
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 XFORWARD NAME ADDR PORT PROTO HELO IDENT SOURCE

telnet localhost 10025
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.domain.com ESMTP
ehlo localhost
250-mail.domain.com
250-PIPELINING
250-SIZE 20480000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN


Configuring Postfixadmin

Postfixadmin is the email domain and email account management tool.

Create a base /usr/share/postfixadmin/config.local.php


bind 'set disable-completion on'
cat <<\EOF>/usr/share/postfixadmin/config.local.php || exit 1
<?php
/**
 * Contains configuration options that override the default config file
 */

/*****************************************************************
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 * You have to set \$CONF['configured'] = true; before the
 * application will run!
 * Doing this implies you have changed this file as required.
 * i.e. configuring database etc; specifying setup.php password etc.
 */
$CONF['configured'] = true;

// In order to setup Postfixadmin, you MUST specify a hashed password here.
// To create the hash, visit /postfixadmin/setup.php in a browser and type a password into the field,
// on submission it will be echoed out to you as a hashed value.
$CONF['setup_password'] = 'changeme';
$CONF['database_password'] = 'postfixsqlpassword';
// Default Aliases
// The default aliases that need to be created for all domains.
$CONF['default_aliases'] = array (
	'abuse' => 'abuse@change-this-to-your.domain.tld',
	'hostmaster' => 'hostmaster@change-this-to-your.domain.tld',
	'postmaster' => 'postmaster@change-this-to-your.domain.tld',
	'webmaster' => 'webmaster@change-this-to-your.domain.tld'
);
$CONF['admin_email'] = 'postfixadmin_email';
$CONF['dovecotpw'] = "/usr/bin/doveadm pw";
$CONF['page_size'] = '20';
$CONF['aliases'] = '50';
$CONF['mailboxes'] = '50';
$CONF['maxquota'] = '100';
$CONF['transport'] = 'YES';
$CONF['vacation'] = 'YES';
$CONF['vacation_domain'] = 'autoreply.change-this-to-your.domain.tld';
$CONF['special_alias_control'] = 'YES';
$CONF['footer_text'] = 'Return to change-this-to-your.domain.tld';
$CONF['footer_link'] = 'http://change-this-to-your.domain.tld';
$CONF['create_mailbox_subdirs']=array('Drafts','Spam','Sent','Trash');
$CONF['create_mailbox_subdirs_host']='localhost';
$CONF['create_mailbox_subdirs_prefix']='';
$CONF['quota'] = 'YES';
$CONF['used_quotas'] = 'YES';
$CONF['new_quota_table'] = 'YES';
// $CONF['create_mailbox_subdirs_hostoptions']=array('notls');
$CONF['create_mailbox_subdirs_hostoptions']=array('novalidate-cert','norsh');

//
// END OF CONFIG FILE
//
?>
EOF
bind 'set disable-completion off'
sed -i -e "s/postfixadmin_email/$admin_email/w /dev/stdout" /usr/share/postfixadmin/config.local.php
sed -i -e "s/postfixsqlpassword/$Postfix_Database_Password/w /dev/stdout" /usr/share/postfixadmin/config.local.php
sed -i -e "s/change-this-to-your.domain.tld/$your_domain_tld/gw /dev/stdout" /usr/share/postfixadmin/config.local.php

SELinux seems to be a problem for Postfixadmin (and Roundcubemail which needs the setsebool).

chcon -R -t httpd_sys_content_rw_t /usr/share/postfixadmin/templates_c
setsebool -P httpd_can_network_connect on

Now go to host/mailadmin/setup.php

Setup should proceed to create/update its database structures.

Next is creating the Setup Password by clicking by entering password and clicking on generate hash.  Then edit /usr/share/postfixadmin/config.local.php with the provided hash.

Use this password to add your postfixadmin admin account.  Use this account to log into host/mailadmin.  Create a mail domain; set up a DNS MX entry for it and create a user.  Postfixadmin should set up all the files for the user and send it a welcome message that will be the basis for testing the components.


Configuring Roundcubemail

Roundcubemail configuring is easier now with the installer, but there are a few permission items to take care of first.

chown root:apache /etc/roundcubemail
chmod 775 /etc/roundcubemail

Roundcubemail is written in php and a couple php defaults may not be right for Roundcubemail, particularly the file upload size and message size maximums.  The following will comment out the defaults and add new maximums.  Change them as you need.

sed -i -e "/^upload_max_filesize/s/^/#/w /dev/stdout" /etc/php.ini
sed -i -e '/^#upload_max_filesize/ a upload_max_filesize = 5M' /etc/php.ini
sed -i -e "/^post_max_size/s/^/#/w /dev/stdout" /etc/php.ini
sed -i -e '/^#post_max_size/ a post_max_size = 20M' /etc/php.ini

Depending on how you setup roundcubemail.conf for httpd

http://fqdn/roundcubemail/installer

The installer does most of the work.  Things to do in the install include:
In the General configuration, select Enchant spellcheck_engine.
In the Database section set your Roundcubemail MySQL password.
In the IMAP section, change the junk_mbox to Spam.
In the SMTP section,
set smtp_server to fqdn
Check "Use the current IMAP username and password for SMTP authentication"
In the Plugins section,
Check managesieve
Check password
Click the UPDATE CONFIG button, start a
cat > /etc/roundcubemail/config.inc.php
copy the content of the text box into the prompt and end with a Cntl-D.

Do not add a closing ?> tag.

Follow the instructions to test Roundcubemail.

If in your system 0 quota means no limit

cat <<\EOF>>/etc/roundcubemail/config.inc.php || exit 1
$config['quota_zero_as_unlimited'] = true;
EOF

To allow browser-autocompletion of username and host on login form.

cat <<\EOF>>/etc/roundcubemail/config.inc.php || exit 1
$config['login_autocomplete'] = 1;
EOF

To directly delete messages in Junk instead of moving to Trash.

cat <<\EOF>>/etc/roundcubemail/config.inc.php || exit 1
$config['delete_junk'] = true;
EOF

This quide uses 'Spam' for the 'Junk' folder.  To force Roundcubemail to display 'Spam', rather than 'Junk' as default.

cat <<\EOF>>/etc/roundcubemail/config.inc.php || exit 1
$config['show_real_foldernames'] = true;
EOF

Apache is configured to redirect to secure connections.  Roundcubemail can check and redirect if the Apache configuration is wrong with:

cat <<\EOF>>/etc/roundcubemail/config.inc.php || exit 1
$config['force_https'] = true;
EOF

Now we set up the manage sieve plugin.

cp /usr/share/roundcubemail/plugins/managesieve/config.inc.php.dist /usr/share/roundcubemail/plugins/managesieve/config.inc.php

For the password plugin:

cp /usr/share/roundcubemail/plugins/password/config.inc.php.dist /usr/share/roundcubemail/plugins/password/config.inc.php
cat <<\EOF>>/usr/share/roundcubemail/plugins/password/config.inc.php || exit 1
$config['password_db_dsn'] = 'mysql://postfix:postfixsqlpassword@localhost/postfix';
$config['password_query'] = 'UPDATE mailbox SET password=%c WHERE username=%u limit 1;';
EOF
sed -i -e "s/postfixsqlpassword/$Postfix_Database_Password/w /dev/stdout" /usr/share/roundcubemail/plugins/password/config.inc.php

Restart Apache.

systemctl restart httpd




Testing the Configuration

This completes configuring the software. Next are some tests to confirm things are working.

Use Postfixadmin to create a virtual domain and a user or so.  Use Roundcubemail to log into a user account and check receipt of the Postfixadmin welcome message.

Roundcubemail use did a basic test of dovecot imap access.  For a fuller test, use all of the following connection methods (via fqdn from a remote system).

# IMAP access
telnet localhost 143
telnet $your_host_tld 143
openssl s_client -connect $your_host_tld:993
openssl s_client -connect $your_host_tld:143 -starttls imap
# POP3 access
telnet localhost 110
telnet $your_host_tld 110
openssl s_client -connect $your_host_tld:995
openssl s_client -connect $your_host_tld:110 -starttls pop3



IMAP log in and some commands are

a login userid password
b select inbox
c list "" *
c fetch 1 body[header]
d lsub "" *
e logout



POP3 log in and some commands are

user userid
pass password
stat
retr 1
quit



For more on Dovecot testing see
http://wiki.dovecot.org/TestInstallation


For anti-virus testing, find sample.tar.gz.compl and change to that folder and untar it

cd /usr/share/doc/amavisd-new-2.10.1/test-messages
perl -pe 's/./chr(ord($&)^255)/sge' <sample.tar.gz.compl | zcat | tar xvf -



Then send the following emails.  virus-sample should be dropped, GTUBE should be moved to Spam, and README should end up in INBOX.

sendmail -i useremail < sample-virus-simple.txt
sendmail -i useremail < sample-spam-GTUBE-junk.txt
sendmail -i useremail < README

Updated


© Robert G. Moskowitz -- 2017