Managing Mail: Installing Postfix Admin

#!/bin/bash

# We need to add a record in the DNS for postfixadmin
#
# For njalla domains login and go to domains > manage
#
# https://njal.la/
#
# Click the + add record to add an A record
#
# Enter:
#
# Type: A
# Name: postfixadmin
# IPv4 Address: 94.158.244.150 (WireGuard server dedicated static IP)

# After this script is run the user needs to open a URL and finish the PostfixAdmin setup at:

# https://postfixadmin.tuxmail.io/setup.php

# In our test setup we entered user1@tuxmail.com for the email addresss of the super admin, password penguin12
#
# To administer it:

# https://postfixadmin.tuxmail.io/main.php

# Set the initial variables we'll need for the script to execute

ROOT_MYSQL_PASSWORD="penguin"
POSTFIX_ADMIN_PASSWORD="penguin"
POSTFIX_ADMIN_SETUP_PASSWORD="penguin"
domain_name="tuxmail.io"
user1_email_address="user1"
user1_password="penguin12"

postfix_admin_download_url="https://github.com/postfixadmin/postfixadmin/archive/"
postfix_admin_version="3.3.14"
postfix_admin_download_file_name="postfixadmin-$postfix_admin_version.tar.gz"

postfix_super_admin_password="penguin12"

# Don't forget to repair partition before continuing

# Managing Mail: Installing Postfix Admin
apt update
apt install -y mariadb-server mariadb-client

# Start it at boot

systemctl enable mariadb

# Secure database with mysql_secure_installation

script -q -c 'mysql_secure_installation' <> "/var/www/postfixadmin/config.local.php"
<?php
\$CONF['configured'] = true;
\$CONF['database_type'] = 'mysqli';
\$CONF['database_host'] = 'localhost';
\$CONF['database_port'] = '3306';
\$CONF['database_user'] = 'postfixadmin';
\$CONF['database_password'] = '$POSTFIX_ADMIN_PASSWORD';
\$CONF['database_name'] = 'postfixadmin';
\$CONF['encrypt'] = 'dovecot:ARGON2I';
\$CONF['dovecotpw'] = "/usr/bin/doveadm pw -r 5";
if(@file_exists('/usr/bin/doveadm')) { // @ to silence openbase_dir stuff; see https://github.com/postfixadmin/postfixadmin/issues/171
\$CONF['dovecotpw'] = "/usr/bin/doveadm pw -r 5"; # debian
\$CONF['setup_password'] = password_hash("$POSTFIX_ADMIN_SETUP_PASSWORD", PASSWORD_DEFAULT);
}
EOF

# Create Apache Virtual Host Config File for PostfixAdmin

cat << EOF >> "/etc/apache2/sites-available/postfixadmin.conf"

ServerName postfixadmin.$domain_name
DocumentRoot /var/www/postfixadmin/public

ErrorLog ${APACHE_LOG_DIR}/postfixadmin_error.log
CustomLog ${APACHE_LOG_DIR}/postfixadmin_access.log combined

Options FollowSymLinks
AllowOverride All

Options FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all

EOF

# Enable virtual host

a2ensite postfixadmin.conf
systemctl restart apache2

# Install PHP modules needed by PostfixAdmin

apt install -y php8.1-fpm php8.1-imap php8.1-mbstring php8.1-mysql php8.1-curl php8.1-zip php8.1-xml php8.1-bz2 php8.1-intl php8.1-gmp php8.1-redis libapache2-mod-php

# Restart apache2

systemctl restart apache2

# Enable PHP 8.1 FPM in Apache2

a2enmod proxy_fcgi setenvif
systemctl restart apache2
a2enconf php8.1-fpm
systemctl reload apache2

# Install certbot and certbot apache plugin

apt install -y certbot python3-certbot-apache

# Run this command to obtain and install TLS certificate

certbot --apache --agree-tos --redirect --hsts --staple-ocsp --email $user1_email_address@$domain_name -d postfixadmin.$domain_name

# Enable PostfixAdmin to read Dovecot statistics

cat << EOF >> "/etc/dovecot/conf.d/10-master.conf"
service stats {
unix_listener stats-reader {
user = www-data
group = www-data
mode = 0660
}

unix_listener stats-writer {
user = www-data
group = www-data
mode = 0660
}
}
EOF

# Add the web server to the dovecot group

gpasswd -a www-data dovecot

# Restart dovecot

systemctl restart dovecot

# Grant permissions to the www-data user

setfacl -R -m u:www-data:rwx /var/run/dovecot/stats-reader /var/run/dovecot/stats-writer

# Add a SuperAdmin user

/var/www/postfixadmin/scripts/postfixadmin-cli admin add user1_email_address@domain_name --superadmin 1 --active 1 --password "$postfix_super_admin_password" --password2 "$postfix_super_admin_password"

# Configure Postfix to use virtual mailbox domains, add MySQL map support for Postfix

apt install -y postfix-mysql

# Add the below to tell postfix where to find stuff

cat << EOF >> "/etc/postfix/main.cf"
virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf
virtual_mailbox_maps =
proxy:mysql:/etc/postfix/sql/mysql_virtual_mailbox_maps.cf,
proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf
virtual_alias_maps =
proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf,
proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf,
proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf
virtual_transport = lmtp:unix:private/dovecot-lmtp
EOF

# Create the sql directory

mkdir /etc/postfix/sql/

cat << EOF >> "/etc/postfix/sql/mysql_virtual_domains_maps.cf"
user = postfixadmin
password = $POSTFIX_ADMIN_PASSWORD
hosts = localhost
dbname = postfixadmin
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
#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'
#expansion_limit = 100
EOF

cat << EOF >> "/etc/postfix/sql/mysql_virtual_mailbox_maps.cf"
user = postfixadmin
password = $POSTFIX_ADMIN_PASSWORD
hosts = localhost
dbname = postfixadmin
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
#expansion_limit = 100
EOF

cat << EOF >> "/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf"
user = postfixadmin
password = $POSTFIX_ADMIN_PASSWORD
hosts = localhost
dbname = postfixadmin
query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'
EOF

cat << EOF >> "/etc/postfix/sql/mysql_virtual_alias_maps.cf"
user = postfixadmin
password = $POSTFIX_ADMIN_PASSWORD
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
#expansion_limit = 100
EOF

cat << EOF >> "/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf"
user = postfixadmin
password = $POSTFIX_ADMIN_PASSWORD
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
EOF

cat << EOF >> "/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf"
# handles catch-all settings of target-domain
user = postfixadmin
password = $POSTFIX_ADMIN_PASSWORD
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
EOF

# Plaintext passwords, so set readable only by user postfix and root only

chmod 0640 /etc/postfix/sql/*
setfacl -R -m u:postfix:rx /etc/postfix/sql/

# Remove the apex domain name from the list

postconf -e "mydestination = \$myhostname, localhost.\$mydomain, localhost"

cat << EOF >> "/etc/postfix/main.cf"
virtual_mailbox_base = /var/vmail
virtual_minimum_uid = 2000
virtual_uid_maps = static:2000
virtual_gid_maps = static:2000
EOF

# Restart postfix for changed to take effect

systemctl restart postfix

# Create a user named vmail with ID 2000 and a group with ID 2000

adduser vmail --system --group --uid 2000 --disabled-login --no-create-home

# Create mail directory location

mkdir /var/vmail/

# Make vmail as the owner

chown vmail:vmail /var/vmail/ -R

# Add MySQL support for Dovecot

apt install dovecot-mysql

# Add mail_home = /var/vmail/%d/%n/ just below mail_location = maildir:~/Maildir in /etc/dovecot/conf.d/10-mail.conf

sed '/^mail_location = maildir:~\/Maildir/a\
mail_home = \/var\/vmail\/%d\/%n\/' /etc/dovecot/conf.d/10-mail.conf > /tmp/10-mail.conf-2

mv /tmp/10-mail.conf-2 /etc/dovecot/conf.d/10-mail.conf

# Replace auth_username_format = %n with auth_username_format = %u

sed -i "s/auth_username_format = %n/auth_username_format = %u/g" /etc/dovecot/conf.d/10-auth.conf

# Replace #auth_default_realm = with auth_default_realm = example.com

sed -i "s/#auth_default_realm =/auth_default_realm = $(echo $domain_name) /g" /etc/dovecot/conf.d/10-auth.conf

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

sed -i "s/#\!include auth-sql.conf.ext/\!include auth-sql.conf.ext/g" /etc/dovecot/conf.d/10-auth.conf

# Comment out !include auth-system.conf.ext so local Unix users can't send emails without registering email addresses in PostfixAdmin

sed -i "s/\!include auth-system.conf.ext/#\!include auth-system.conf.ext/g" /etc/dovecot/conf.d/10-auth.conf

# When a user tries to log in, Dovecot would use the Argon2 algorithm to generate a
# password hash from the password entered by the user, then compare it with the password hash stored in the database.

cat << EOF >> "/etc/dovecot/dovecot-sql.conf.ext"

driver = mysql

connect = host=localhost dbname=postfixadmin user=postfixadmin password=$POSTFIX_ADMIN_PASSWORD

default_pass_scheme = ARGON2I

password_query = SELECT username AS user,password FROM mailbox WHERE username = '%u' AND active='1'

user_query = SELECT maildir, 2000 AS uid, 2000 AS gid FROM mailbox WHERE username = '%u' AND active='1'

iterate_query = SELECT username AS user FROM mailbox
EOF

systemctl restart dovecot

# Add domain

/var/www/postfixadmin/scripts/postfixadmin-cli domain add $domain_name --mailboxes 0 --aliases 0 --active 1 --password-expiry 0

# Add user1

/var/www/postfixadmin/scripts/postfixadmin-cli mailbox add $user1_email_address@$domain_name --password "$user1_password" --password2 "$user1_password"

# Expunge old emails that have been placed in trash after two weeks

crontab -l | { cat; echo "@monthly setfacl -R -m u:www-data:rx /etc/letsencrypt/live/ /etc/letsencrypt/archive/ && doveadm expunge -A mailbox Junk savedbefore 2w"; } | crontab -

# Restrict which local users can send mail

cat << EOF >> "/etc/postfix/main.cf"
authorized_submit_users = root,www-data,vmail
EOF

# Restart postfix

systemctl restart postfix