Ubuntu 16.10 LNPA Stack Profile

by lucretia
47 deployments · 2 still active · last rev. 1 month ago

Sets up a LNPA (Linux, nginx, PostgreSQL, Ada) server on Ubuntu 16.10, updates the system and installs:

1) Add an "admin" user for doing all admin login's using ssh before "su -"
2) Email using Postfix, Dovecot and PostgreSQL.
3) SSL certificates using Let's Encrypt.
4) GIT
5) GNAT Ada compiler.

Compatible with: Ubuntu 16.10
						#!/bin/bash
# This block defines the variables the user of the script needs to input
# when deploying using this script.
#
# Basic system.
#
#<UDF name="SYS_HOSTNAME" example="somehostname" label="The hostname for the new Linode." />
#
#<UDF name="SYS_FQDN" example="somedomain.com" label="The new Linode's Fully Qualified Domain Name" />
#
#<UDF name="SYS_FQDN_1" default="" example="otherdomain1.com" label="This Linode's other Fully Qualified Domain Name" />
#
#<UDF name="SYS_FQDN_2" default="" example="otherdomain2.com" label="This Linode's other Fully Qualified Domain Name" />
#
#<UDF name="SYS_FQDN_3" default="" example="otherdomain3.com" label="This Linode's other Fully Qualified Domain Name" />
#
#<UDF name="SYS_FQDN_4" default="" example="otherdomain4.com" label="This Linode's other Fully Qualified Domain Name" />
#
#<UDF name="SYS_ADMIN_USER_NAME" default="admin" label="The username for the Administrator, this user will be doing all admin work via sudo." />
#
#<UDF name="SYS_ADMIN_USER_PASSWORD" label="The password for the 'admin' Linux user." />
#
#<UDF name="SYS_ADMIN_USER_SSHKEY" label="SSH key so you can login as the 'admin' user." />
#
#<UDF name="SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN" label="Password for 'admin@somehostname.somedomain.com' email user." />
#
#<UDF name="SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN_1" default="" label="Password for 'admin@somehostname.otherdomain1.com' email user." />
#
#<UDF name="SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN_2" default="" label="Password for 'admin@somehostname.otherdomain2.com' email user." />
#
#<UDF name="SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN_3" default="" label="Password for 'admin@somehostname.otherdomain3.com' email user." />
#
#<UDF name="SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN_4" default="" label="Password for 'admin@somehostname.otherdomain4.com' email user." />
#
# SSL
#
#<UDF name="SYS_ENABLE_LETSENCRYPT" default="off" oneof="on,off" label="Enable LetsEncrypt SSL certificates?" />
#
# PostgreSQL.
#
#<UDF name="SYS_POSTGRES_USER_PASSWORD" label="The password for the 'postgres' Linux user." />
#
#<UDF name="SYS_POSTGRES_DB_USER_PASSWORD" label="The password for the 'postgres' PostgreSQL user." />
#
# Email database.
#
#<UDF name="SYS_EMAIL_DB_SERVER_NAME" default="mailserver" label="The name for the PostgreSQL mail server database." />
#
#<UDF name="SYS_EMAIL_DB_USER_NAME" default="mailuser" label="The username who can administer the PostgreSQL 'mailserver' database." />
#
#<UDF name="SYS_EMAIL_DB_USER_PASSWORD" label="The password for the PostgreSQL 'mailuser' to enable access to the 'mailserver' database." />
#

LOG=/var/log/stackscript

# This won't source other scripts for some reason.

################################################################
# Modified version of https://www.linode.com/stackscripts/view/1
################################################################

function system_update {
    apt-get update
    apt-get -y install aptitude
    aptitude -y full-upgrade
}

function system_primary_ip {
    # returns the primary IP assigned to eth0
    echo $(ifconfig eth0 | awk '/inet / { print $2 }' | sed 's/addr://')
}

function system_primary_ip6 {
    # returns the primary IP assigned to eth0
    echo $(ifconfig eth0 | grep global | awk '/inet6 / { print $2 }' | sed 's/addr://')
}

function get_rdns {
    # calls host on an IP address and returns its reverse dns

    if [ ! -e /usr/bin/host ]; then
        aptitude -y install dnsutils > /dev/null
    fi
    echo $(host $1 | awk '/pointer/ {print $5}' | sed 's/\.$//')
}

function get_rdns_primary_ip {
    # returns the reverse dns of the primary IP assigned to this system
    echo $(get_rdns $(system_primary_ip))
}

function system_set_hostname {
    # $1 - The hostname to define
    HOSTNAME="$1"

    if [ ! -n "$HOSTNAME" ]; then
        echo "Hostname undefined"
        return 1;
    fi

    echo "$HOSTNAME" > /etc/hostname
    hostname -F /etc/hostname
}

function system_add_host_entry {
    # $1 - The Domain name to set to the IP
    # $2 - The FQDN to set to the IP
    IPADDR=$(system_primary_ip)
    IPADDR6=$(system_primary_ip6)
    DOMAIN="$1"
    FQDN="$2"

    if [ -z "$IPADDR" -o -z "$FQDN" ]; then
        echo "IP address and/or FQDN Undefined" >> $LOG
        return 1;
    fi

    # Insert the correct 127.0.1.1 loopback address back this domain just before the IPv6 stuff.
    #sed -i '/127.0.1.1/ i'"$DOMAIN"'.'"$FQDN"' '"$DOMAIN"'/' /etc/hosts
    sed -i '/^# The following/ i127.0.1.1\t'"$DOMAIN"'.'"$FQDN"'\t'"$DOMAIN" /etc/hosts

    # echo "$IPADDR $DOMAIN.$FQDN $DOMAIN"  >> /etc/hosts

    # Insert the IPv4 address just before the localhost line.
    sed -i '/127.0.0.1/ i'"$IPADDR"'\t'"$DOMAIN"'.'"$FQDN"'\t'"$DOMAIN" /etc/hosts

    # Append the IPv6 address to the end of the file.
    if [ ! -z "$IPADDR6" ]; then
	echo -e "$IPADDR6\t$DOMAIN.$FQDN\t$DOMAIN" >> /etc/hosts
    fi
}

function system_set_host_info {
    # Set ip4/6 hosts entries.
    system_add_host_entry "$SYS_HOSTNAME" "$SYS_FQDN"

    for i in `seq 1 4`;
    do
	FQDN="SYS_FQDN_$i"

	echo "  [system_set_host_info] Setting extra domain: ${!FQDN}" >> $LOG

	if [ ! -z ${!FQDN} ]; then
	    system_add_host_entry "$SYS_HOSTNAME" "${!FQDN}"
	fi
    done

    sed -i '/ubuntu.members.linode.com\tubuntu/d' /etc/hosts
}

function system_set_timezone {
    # Overwrites an existing entry.
    timedatectl set-timezone 'Europe/London'
}


###########################################################
# Users and Authentication
###########################################################

function user_add_sudo {
    # Installs sudo if needed and creates a user in the sudo group.
    #
    # $1 - Required - username
    # $2 - Required - password
    USERNAME="$1"
    USERPASS="$2"

    if [ ! -n "$USERNAME" ] || [ ! -n "$USERPASS" ]; then
        echo "No new username and/or password entered" >> $LOG
        return 1;
    fi

    aptitude -y install sudo
    adduser $USERNAME --disabled-password --gecos ""
    echo "$USERNAME:$USERPASS" | chpasswd
    usermod -aG sudo $USERNAME
}

function user_add_pubkey {
    # Adds the users public key to authorized_keys for the specified user. Make sure you wrap your input variables in double quotes, or the key may not load properly.
    #
    #
    # $1 - Required - username
    # $2 - Required - public key
    USERNAME="$1"
    USERPUBKEY="$2"

    if [ ! -n "$USERNAME" ] || [ ! -n "$USERPUBKEY" ]; then
        echo "Must provide a username and the location of a pubkey" >> $LOG
        return 1;
    fi

    if [ "$USERNAME" == "root" ]; then
        mkdir /root/.ssh
        echo "$USERPUBKEY" >> /root/.ssh/authorized_keys
        return 1;
    fi

    mkdir -p /home/$USERNAME/.ssh
    echo "$USERPUBKEY" >> /home/$USERNAME/.ssh/authorized_keys
    chown -R "$USERNAME":"$USERNAME" /home/$USERNAME/.ssh
}

function ssh_disable_root {
    # Disables root SSH access.
    sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
    touch /tmp/restart-ssh
}

###########################################################
# Postfix
###########################################################

function postfix_install_loopback_only {
    # Installs postfix and configure to listen only on the local interface. Also
    # allows for local mail delivery

    echo "postfix postfix/main_mailer_type select Internet Site" | debconf-set-selections
    echo "postfix postfix/mailname string localhost" | debconf-set-selections
    echo "postfix postfix/destinations string localhost.localdomain, localhost" | debconf-set-selections
    aptitude -y install postfix
    /usr/sbin/postconf -e "inet_interfaces = loopback-only"
    #/usr/sbin/postconf -e "local_transport = error:local delivery is disabled"

    touch /tmp/restart-postfix
}

#########################################################
# Taken from https://www.linode.com/stackscripts/view/123
#########################################################

function lower {
    # helper function
    echo $1 | tr '[:upper:]' '[:lower:]'
}

function system_sshd_edit_bool {
    # system_sshd_edit_bool (param_name, "Yes"|"No")
    VALUE=`lower $2`
    if [ "$VALUE" == "yes" ] || [ "$VALUE" == "no" ]; then
        sed -i "s/^#*\($1\).*/\1 $VALUE/" /etc/ssh/sshd_config
    fi
}

function system_sshd_permitrootlogin {
    system_sshd_edit_bool "PermitRootLogin" "$1"
}

function system_sshd_passwordauthentication {
    system_sshd_edit_bool "PasswordAuthentication" "$1"
}

function system_sshd_lockdown {
    system_sshd_permitrootlogin "no"
    system_sshd_passwordauthentication "no"

    echo 'AddressFamily inet' | sudo tee -a /etc/ssh/sshd_config

    systemctl restart sshd
}

# TODO: Need a way of detecting persistent abusers, mail their abuse@ip with log, then ban them permanently.
function system_security_fail2ban {
    aptitude -y install fail2ban
}

function system_security_ufw_configure_basic {
    # see https://help.ubuntu.com/community/UFW
    ufw logging on

    ufw default deny

    ufw allow ssh/tcp
    ufw limit ssh/tcp

    ufw allow http/tcp
    ufw allow https/tcp

    # TODO: smtp(s), pop3(s)

    ufw enable
}

################
# My stuff here.
################

function system_git {
    aptitude -y install git
}

function system_letsencrypt_get_domains {
    DOMAINS="-d $SYS_FQDN -d www.$SYS_FQDN"

    for i in `seq 1 4`;
    do
	FQDN="SYS_FQDN_$i"

	if [ ! -z ${!FQDN} ]; then
	    DOMAINS="$DOMAINS -d ${!FQDN} -d www.${!FQDN}"
	fi
    done

    echo $DOMAINS
}

function system_letsencrypt {
    git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

    cd /opt/letsencrypt

    DOMAINS=`system_letsencrypt_get_domains`

    ./letsencrypt-auto certonly --standalone $DOMAINS -m "$SYS_ADMIN_USER_NAME@$SYS_FQDN" -n --agree-tos >> $LOG 2>&1

    # Renew the SSL certs monthly.
    echo '@monthly root /opt/letsencrypt/letsencrypt-auto certonly --quiet --standalone --renew-by-default $DOMAINS >> /var/log/letsencrypt/letsencrypt-auto-update.log' | sudo tee --append /etc/crontab

    # Update the Git repo weekly.
    echo '@weekly root cd /opt/letsencrypt && git pull >> /var/log/letsencrypt/letsencrypt-auto-update.log' | sudo tee --append /etc/crontab
}

function system_mail_install_packages {
    aptitude -y install postgresql postfix-pgsql dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-pgsql

    #usermod -aG sudo postgres
}

function system_postgres_virtual_mail {
    SCHEMA="$SYS_EMAIL_DB_USER_NAME"

    sudo -u postgres createuser -U postgres -E "$SYS_EMAIL_DB_USER_NAME" >> $LOG 2>&1
    sudo -u postgres createdb -U postgres -O "$SYS_EMAIL_DB_USER_NAME" "$SYS_EMAIL_DB_SERVER_NAME" "$SYS_FQDN's email server" >> $LOG 2>&1

    sudo -u postgres psql -w -U postgres >> $LOG 2>&1 <<EOF
--  Add extensions to the mail server database.
\c $SYS_EMAIL_DB_SERVER_NAME
-- Ubuntu doesn't have Blowfish
CREATE EXTENSION pgcrypto;
CREATE EXTENSION citext;
EOF

    # Temporarily give access to the mail user without a password.
    sed -i '/postgres/ ilocal   all            mailuser                                     trust' /etc/postgresql/9.5/main/pg_hba.conf

    systemctl reload postgresql

    # Hash password.
    HASHED_SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN=$(doveadm pw -s SSHA512.base64 -p $SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN)
    echo "HASHED_SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN = $HASHED_SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN" >> $LOG

cat <<EOF > /tmp/psql-config.sql
--  Make sure the search path can find the schema by naming it after the user.
CREATE SCHEMA $SCHEMA AUTHORIZATION $SYS_EMAIL_DB_USER_NAME;

--  Create the mail server tables.
CREATE TABLE $SCHEMA.virtual_domains (
  domain_id SERIAL,
  domain_name varchar(253) NOT NULL UNIQUE,
  PRIMARY KEY (domain_id)
);

COMMENT ON TABLE virtual_domains IS 'Domains';

CREATE TABLE $SCHEMA.virtual_users (
  user_id SERIAL,
  domain_id INTEGER NOT NULL,
--  Can't do Blowfish
--  password varchar(60) NOT NULL,
  password varchar(108) NOT NULL,
  email citext NOT NULL UNIQUE,
  PRIMARY KEY (user_id),
  FOREIGN KEY (domain_id) REFERENCES virtual_domains(domain_id) ON DELETE CASCADE
);

COMMENT ON TABLE virtual_users IS 'Users';

CREATE TABLE $SCHEMA.virtual_aliases (
  alias_id SERIAL,
  domain_id INTEGER NOT NULL,
  source citext NOT NULL UNIQUE,
  destination citext NOT NULL,
  PRIMARY KEY (alias_id),
  FOREIGN KEY (domain_id) REFERENCES virtual_domains(domain_id) ON DELETE CASCADE
);

COMMENT ON TABLE virtual_aliases IS 'Email aliases';

--  Populate the database.
INSERT INTO virtual_domains
  (domain_name)
VALUES
  ('$SYS_FQDN');

INSERT INTO virtual_users
  (domain_id, password , email)
VALUES
  ((SELECT domain_id FROM virtual_domains WHERE domain_name = '$SYS_FQDN'),
--  Can't do Blowfish
--    crypt('$SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN', gen_salt('bf', 8)), '$SYS_ADMIN_USER_NAME@$SYS_FQDN');
    '$HASHED_SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN', '$SYS_ADMIN_USER_NAME@$SYS_FQDN');

--  Set initial aliases of root@ and abuse@ and send them to admin@.
INSERT INTO virtual_aliases
  (domain_id, source, destination)
VALUES
  ((SELECT domain_id FROM virtual_domains WHERE domain_name = '$SYS_FQDN'), 'root@$SYS_FQDN', '$SYS_ADMIN_USER_NAME@$SYS_FQDN'),
  ((SELECT domain_id FROM virtual_domains WHERE domain_name = '$SYS_FQDN'), 'abuse@$SYS_FQDN', '$SYS_ADMIN_USER_NAME@$SYS_FQDN');
EOF

    # Add the email addresses for the optional other FQDN's.
    for i in `seq 1 4`;
    do
	FQDN="SYS_FQDN_$i"
	EMAIL_PASSWORD="SYS_ADMIN_USER_EMAIL_PASSWORD_FQDN_$i"
	export HASHED_EMAIL_PASSWORD=$(sudo doveadm pw -s SSHA512.base64 -p ${!EMAIL_PASSWORD})
	echo "HASHED_EMAIL_PASSWORD = $HASHED_EMAIL_PASSWORD" >> $LOG
	if [ ! -z ${!FQDN} -o ! -z ${!EMAIL_PASSWORD} ]; then
cat <<EOF >> /tmp/psql-config.sql
INSERT INTO virtual_domains
  (domain_name)
VALUES
  ('${!FQDN}');

INSERT INTO virtual_users
  (domain_id, password , email)
VALUES
  ((SELECT domain_id FROM virtual_domains WHERE domain_name = '${!FQDN}'),
--  Can't do Blowfish
--    crypt('${!EMAIL_PASSWORD}', gen_salt('bf', 8)), '$SYS_ADMIN_USER_NAME@${!FQDN}');
    '$HASHED_EMAIL_PASSWORD', '$SYS_ADMIN_USER_NAME@${!FQDN}');

INSERT INTO virtual_aliases
  (domain_id, source, destination)
VALUES
  ((SELECT domain_id FROM virtual_domains WHERE domain_name = '${!FQDN}'), 'root@${!FQDN}', '$SYS_ADMIN_USER_NAME@${!FQDN}'),
  ((SELECT domain_id FROM virtual_domains WHERE domain_name = '${!FQDN}'), 'abuse@${!FQDN}', '$SYS_ADMIN_USER_NAME@${!FQDN}');
EOF
	fi
    done

    # Read in the SQL commands now to set up the database.
    sudo -u postgres psql -U "$SYS_EMAIL_DB_USER_NAME" -d "$SYS_EMAIL_DB_SERVER_NAME" -f /tmp/psql-config.sql >> $LOG 2>&1

    #########################
    # Lock down the database.
    sed -i 's/local   all             all                                     peer/local   all             all                                     md5/' /etc/postgresql/9.5/main/pg_hba.conf

    # Remove the trusted mail user, now that all users have to have md5 passwords.
    sed -i '/'"$SYS_EMAIL_DB_USER_NAME"'/d' /etc/postgresql/9.5/main/pg_hba.conf

    systemctl reload postgresql

    # Add a PostgreSQL password to the email server user.
    sudo -u postgres psql -U postgres >> $LOG 2>&1 <<EOF
ALTER USER $SYS_EMAIL_DB_USER_NAME WITH ENCRYPTED PASSWORD '$SYS_EMAIL_DB_USER_PASSWORD';
EOF
    # Add a PostgreSQL password to the postgres user.
    sudo -u postgres psql -U postgres >> $LOG 2>&1 <<EOF
ALTER USER postgres WITH ENCRYPTED PASSWORD '$SYS_POSTGRES_DB_USER_PASSWORD';
EOF

    # Change the Linux postgres user password
    echo "postgres:$SYS_POSTGRES_USER_PASSWORD" | chpasswd

    mkdir -p /etc/postfix/postgres

    cat <<EOF > /etc/postfix/postgres/virtual-mailbox-domains.cf
user = $SYS_EMAIL_DB_USER_NAME
password = $SYS_EMAIL_DB_USER_PASSWORD
hosts = 127.0.0.1
dbname = $SYS_EMAIL_DB_SERVER_NAME
query = SELECT 1 FROM virtual_domains WHERE domain_name='%s'
EOF

    cat <<EOF > /etc/postfix/postgres/virtual-mailbox-maps.cf
user = $SYS_EMAIL_DB_USER_NAME
password = $SYS_EMAIL_DB_USER_PASSWORD
hosts = 127.0.0.1
dbname = $SYS_EMAIL_DB_SERVER_NAME
query = SELECT 1 FROM virtual_users WHERE email='%s'
EOF

    cat <<EOF > /etc/postfix/postgres/virtual-alias-maps.cf
user = $SYS_EMAIL_DB_USER_NAME
password = $SYS_EMAIL_DB_USER_PASSWORD
hosts = 127.0.0.1
dbname = $SYS_EMAIL_DB_SERVER_NAME
query = SELECT destination FROM virtual_aliases WHERE source='%s'
EOF

    cat <<EOF > /etc/postfix/postgres/virtual-email2email.cf
user = $SYS_EMAIL_DB_USER_NAME
password = $SYS_EMAIL_DB_USER_PASSWORD
hosts = 127.0.0.1
dbname = $SYS_EMAIL_DB_SERVER_NAME
query = SELECT email FROM virtual_users WHERE email='%s'
EOF

    # Don't need this access anymore.
    #deluser postgres sudo
}

function postfix_dovecot {
    # Detemine encryption, try blowfish first.
    cp /etc/postfix/master.cf /etc/postfix/master.cf.orig
    cp /etc/postfix/main.cf /etc/postfix/main.cf.orig

    if [ "$SYS_ENABLE_LETSENCRYPT" == "yes" ]; then
	/usr/sbin/postconf -e "smtpd_tls_cert_file = /etc/letsencrypt/live/$SYS_FQDN/fullchain.pem"
	/usr/sbin/postconf -e "smtpd_tls_key_file = /etc/letsencrypt/live/$SYS_FQDN/privkey.pem"
    else
	LAST_DIR=`pwd`
	cd /usr/share/dovecot/
	echo "[Generating Dovecot keys]" >> $LOG
	cp dovecot-openssl.cnf dovecot-openssl.cnf.orig
	sed -i 's/organizationalUnitName = @commonName@/organizationalUnitName = test/' dovecot-openssl.cnf
	sed -i 's/commonName = @commonName@/commonName = test/' dovecot-openssl.cnf
	sed -i 's/emailAddress = @emailAddress@/emailAddress = '"$SYS_ADMIN_USER_NAME@$SYS_FQDN"'/' dovecot-openssl.cnf
	./mkcert.sh >> $LOG 2>&1
	cd $LAST_DIR
	unset LAST_DIR

	/usr/sbin/postconf -e "smtpd_tls_cert_file = /etc/dovecot/dovecot.pem"
	/usr/sbin/postconf -e "smtpd_tls_key_file = /etc/dovecot/private/dovecot.pem"
    fi

    /usr/sbin/postconf -e "smtpd_use_tls = yes"
    /usr/sbin/postconf -e "smtpd_tls_auth_only = yes"

    sed -i '/^smtpd_tls_session_cache_database/d' /etc/postfix/main.cf
    sed -i '/^smtp_tls_session_cache_database/d' /etc/postfix/main.cf

    /usr/sbin/postconf -e "mydestination = localhost.\$mydomain, localhost, mail.\$mydomain"
    #/usr/sbin/postconf -e "myorigin = /etc/mailname"
    /usr/sbin/postconf -e "myorigin = $SYS_FQDN"
    /usr/sbin/postconf -e "myhostname = mail.$SYS_FQDN"

    #echo "mail.$SYS_FQDN" > /etc/mailname

    #Enabling SMTP for authenticated users, and handing off authentication to Dovecot
    /usr/sbin/postconf -e "smtpd_sasl_type = dovecot"
    /usr/sbin/postconf -e "smtpd_sasl_path = private/auth"
    /usr/sbin/postconf -e "smtpd_sasl_auth_enable = yes"

    #/usr/sbin/postconf -e "mydestination = localhost"
    /usr/sbin/postconf -e "inet_interfaces = all"

    #Handing off local delivery to Dovecot's LMTP, and telling it where to store mail
    /usr/sbin/postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"

    /usr/sbin/postconf -e "virtual_mailbox_domains = pgsql:/etc/postfix/postgres/virtual-mailbox-domains.cf"
    /usr/sbin/postconf -e "virtual_mailbox_maps = pgsql:/etc/postfix/postgres/virtual-mailbox-maps.cf"
    /usr/sbin/postconf -e "virtual_alias_maps = pgsql:/etc/postfix/postgres/virtual-alias-maps.cf, pgsql:/etc/postfix/postgres/virtual-email2email.cf"

    touch /tmp/restart-postfix

    # Finish installing Dovecot
    cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
    cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.orig
    cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.orig
    cp /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/dovecot-sql.conf.ext.orig
    cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.orig
    cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.orig

    # Edit /etc/dovecot/dovecot.conf
    sed -i '/^\!include_try/ aprotocols = imap pop3 lmtp' /tmp/dovecot.conf

    # Edit /etc/dovecot/conf.d/10-mail.conf
    sed -i 's/^mail_location = mbox:~\/mail:INBOX=\/var\/mail\/%u/mail_location = maildir:\/var\/mail\/vhosts\/%d\/%n/' /etc/dovecot/conf.d/10-mail.conf
    sed -i 's/^#mail_privileged_group =/mail_privileged_group = mail/' /etc/dovecot/conf.d/10-mail.conf

    # Create the maildirs
    mkdir -p "/var/mail/vhosts/$SYS_FQDN"

    for i in `seq 1 4`;
    do
	FQDN="SYS_FQDN_$i"

	if [ ! -z ${!FQDN} ]; then
	    mkdir -p "/var/mail/vhosts/${!FQDN}"
	fi
    done

    # Create the mail user.
    groupadd -g 5000 vmail
    useradd -g vmail -u 5000 vmail -d /var/mail/vhosts -c "virtual mail user"
    chown -R vmail:vmail /var/mail

    # Edit /etc/dovecot/conf.d/10-auth.conf
    sed -i 's/^#disable_plaintext_auth = yes/disable_plaintext_auth = yes/' /etc/dovecot/conf.d/10-auth.conf
    sed -i 's/^auth_mechanisms = plain/auth_mechanisms = plain login/' /etc/dovecot/conf.d/10-auth.conf
    sed -i 's/^!include auth-system.conf.ext/#!include auth-system.conf.ext/' /etc/dovecot/conf.d/10-auth.conf
    sed -i 's/^#!include auth-sql.conf.ext/!include auth-sql.conf.ext/' /etc/dovecot/conf.d/10-auth.conf

    # Create new /etc/dovecot/conf.d/auth-sql.conf.ext
    cat <<EOF > /etc/dovecot/conf.d/auth-sql.conf.ext
passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
  driver = static
  args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}
EOF

    # Edit /etc/postfix/master.cf
    #sed -i '/submission/,+10 s/#//' /etc/postfix/master.cf
    #sed -i '/smtps/,+10 s/#//' /etc/postfix/master.cf

    cat <<EOF > /etc/postfix/master.cf
#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd
#smtp      inet  n       -       y       -       1       postscreen
#smtpd     pass  -       -       y       -       -       smtpd
#dnsblog   unix  -       -       y       -       0       dnsblog
#tlsproxy  unix  -       -       y       -       0       tlsproxy
#submission inet n       -       y       -       -       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=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
#smtps     inet  n       -       y       -       -       smtpd
#  -o syslog_name=postfix/smtps
#  -o smtpd_tls_wrappermode=yes
#  -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=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
#628       inet  n       -       y       -       -       qmqpd
pickup    unix  n       -       y       60      1       pickup
cleanup   unix  n       -       y       -       0       cleanup
qmgr      unix  n       -       n       300     1       qmgr
#qmgr     unix  n       -       n       300     1       oqmgr
tlsmgr    unix  -       -       y       1000?   1       tlsmgr
rewrite   unix  -       -       y       -       -       trivial-rewrite
bounce    unix  -       -       y       -       0       bounce
defer     unix  -       -       y       -       0       bounce
trace     unix  -       -       y       -       0       bounce
verify    unix  -       -       y       -       1       verify
flush     unix  n       -       y       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       y       -       -       smtp
relay     unix  -       -       y       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       y       -       -       showq
error     unix  -       -       y       -       -       error
retry     unix  -       -       y       -       -       error
discard   unix  -       -       y       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       y       -       -       lmtp
anvil     unix  -       -       y       -       1       anvil
scache    unix  -       -       y       -       1       scache
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent.  See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
#   lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
#  mailbox_transport = lmtp:inet:localhost
#  virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus     unix  -       n       n       -       -       pipe
#  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix  -       n       n       -       -       pipe
#  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
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/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix  -       n       n       -       2       pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman   unix  -       n       n       -       -       pipe
  flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
  ${nexthop} ${user}

EOF
    chmod -R o-rwx /etc/postfix
    service postfix restart

    # Edit /etc/dovecot/dovecot-sql.conf.ext
    cat <<EOF > /etc/dovecot/dovecot-sql.conf.ext
driver = pgsql
connect = host=127.0.0.1 dbname=$SYS_EMAIL_DB_SERVER_NAME user=$SYS_EMAIL_DB_USER_NAME password=$SYS_EMAIL_DB_USER_PASSWORD
#default_pass_scheme = BLF-CRYPT
default_pass_scheme = SSHA512
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
EOF
    # sed -i 's/^#driver =/driver = pgsql/' /etc/dovecot/dovecot-sql.conf.ext
    # sed -i 's/^#connect =/connect = host=127.0.0.1 dbname='"$SYS_EMAIL_DB_SERVER_NAME"' user='"$SYS_EMAIL_DB_USER_NAME"' password='"$SYS_EMAIL_DB_USER_PASSWORD"'/' /etc/dovecot/dovecot-sql.conf.ext
    # sed -i 's/^#default_pass_scheme = MD5/default_pass_scheme = BLF-CRYPT/' /etc/dovecot/dovecot-sql.conf.ext
    # sed -i '/^#password_query = \\/ ipassword_query = SELECT email as user, password FROM virtual_users WHERE email='\''%u'\'';' /etc/dovecot/dovecot-sql.conf.ext

    # Fix permissions
    chown -R vmail:dovecot /etc/dovecot
    chmod -R o-rwx /etc/dovecot

    # Disable unencrypted IMAP and POP3
    # IMAP
    sed -i 's/#port = 143/port = 0  # 143/' /etc/dovecot/conf.d/10-master.conf
    # sed -i '/#port = 993/ assl = yes' /etc/dovecot/conf.d/10-master.conf
    # sed -i 's/#port = 993/port = 0/' /etc/dovecot/conf.d/10-master.conf

    # POP3
    sed -i 's/#port = 110/port = 0   # 110/' /etc/dovecot/conf.d/10-master.conf
    # sed -i '/#port = 995/ assl = yes' /etc/dovecot/conf.d/10-master.conf
    # sed -i 's/#port = 995/port = 0/' /etc/dovecot/conf.d/10-master.conf

    # LMTP
    sed -i 's/unix_listener lmtp/unix_listener \/var\/spool\/postfix\/private\/dovecot-lmtp/' /etc/dovecot/conf.d/10-master.conf
    sed -i '/\/var\/spool\/postfix\/private\/dovecot-lmtp {/ a\ \ \ \ mode = 0600\n    user = postfix\n    group = postfix' /etc/dovecot/conf.d/10-master.conf

    # Auth
    sed -i '/unix_listener auth-userdb/ i\ \ unix_listener \/var\/spool\/postfix\/private\/auth {\n    mode = 0666\n    user = postfix\n    group = postfix\n  }\n' /etc/dovecot/conf.d/10-master.conf
    sed -i '/unix_listener auth-userdb/ a\ \ \ \ mode = 0600\n    user = vmail' /etc/dovecot/conf.d/10-master.conf
    sed -i '/# Postfix smtp-auth/ i\ \ # Auth process is run as this user.\n  user = dovecot\n' /etc/dovecot/conf.d/10-master.conf
    sed -i '/^service auth-worker/ a\ \ user = vmail' /etc/dovecot/conf.d/10-master.conf

    # Use Lets Encrypt SSL certs.
    sed -i 's/^ssl = no/ssl = yes/' /etc/dovecot/conf.d/10-ssl.conf

    if [ "$SYS_ENABLE_LETSENCRYPT" == "yes" ]; then
	sed -i 's/^#ssl_cert = <\/etc\/dovecot\/dovecot.pem/ssl_cert = <\/etc\/letsencrypt\/live\/'"$SYS_FQDN"'\/fullchain.pem/' /etc/dovecot/conf.d/10-ssl.conf
	sed -i 's/^#ssl_key = <\/etc\/dovecot\/private\/dovecot.pem/ssl_key = <\/etc\/letsencrypt\/live\/'"$SYS_FQDN"'\/privkey.pem/' /etc/dovecot/conf.d/10-ssl.conf
    else
	sed -i 's/^#ssl_cert = <\/etc\/dovecot\/dovecot.pem/ssl_cert = <\/etc\/dovecot\/dovecot.pem/' /etc/dovecot/conf.d/10-ssl.conf
	sed -i 's/^#ssl_key = <\/etc\/dovecot\/private\/dovecot.pem/ssl_key = <\/etc\/dovecot\/private\/dovecot.pem/' /etc/dovecot/conf.d/10-ssl.conf
    fi

    # Protect against POODLE attack.
    sed -i 's/^#ssl_protocols = !SSLv2/ssl_protocols = !SSLv3 !SSLv2/' /etc/dovecot/conf.d/10-ssl.conf

    service dovecot restart

    # Configure firewall rules to allow IMAPS, POP3S, SMTP(S)
    ufw allow imaps/tcp
    ufw allow pop3s/tcp
    ufw allow smtps/tcp

    ufw deny imap/tcp
    ufw deny pop-3/tcp

    ufw deny out smtp/tcp
}

function system_spam_detection {
    # ClamAV, Amavis
    echo "[system_spam_detection]"
}

####################################################################
# Install base system.
####################################################################

echo "[system_update]" >> $LOG
system_update

echo "[system_set_hostname $SYS_HOSTNAME]" >> $LOG
system_set_hostname "$SYS_HOSTNAME"

echo "[system_set_host_info]" >> $LOG
system_set_host_info

echo "[system_set_timezone]" >> $LOG
system_set_timezone

# Secure the server.

echo "user_add_sudo $SYS_ADMIN_USER_NAME" >> $LOG
user_add_sudo "$SYS_ADMIN_USER_NAME" "$SYS_ADMIN_USER_PASSWORD"

user_add_pubkey "$SYS_ADMIN_USER_NAME" "$SYS_ADMIN_USER_SSHKEY"

# Force SSH login's only.
echo "[sshd lockdown]" >> $LOG
system_sshd_lockdown

# Add firewall.
echo "[firewall]" >> $LOG
system_security_fail2ban
system_security_ufw_configure_basic

# Let's encrypt.
if [ "$SYS_ENABLE_LETSENCRYPT" == "yes" ]; then
    echo "[lets encrypt]" >> $LOG
    system_git
    system_letsencrypt
fi

####################################################################
# Mail: PostgreSQL, Postfix, Dovecot.
####################################################################
echo "[postfix_install_loopback_only]" >> $LOG
postfix_install_loopback_only

echo "[system_mail_install_packages]" >> $LOG
system_mail_install_packages

echo "[system_postgres_virtual_mail]" >> $LOG
system_postgres_virtual_mail

echo "[postfix_dovecot]" >> $LOG
postfix_dovecot

echo "[system_spam_detection]" >> $LOG
system_spam_detection

####################################################################
# Web server
####################################################################

####################################################################
# Ada
####################################################################

echo "Completed" >> $LOG