Mail Server + Registration System

Once I was looking for an answer to the question of how to make a fully functional registration system for an email server. I found the answer and decided to assemble several guides found on the Internet into one working model for debian based systems.

The first thing you need to do is to add A and MX dns entries for mail server, preferably on the website where you subscribe to the server:

Add the TXT SPF record to the server dns settings:

v=spf1 a mx ip4:xx.xx.xx.xx include:google.com ~all
Sample MX entry for DNS server:
mx.example.com	@	10	86400

You must have the encfs program installed to start your safe adventure with the e-mail server:

apt install encfs

mkdir /encrypted-mail /decrypted-mail
chgrp mail /decrypted-mail/
chmod -R g+rw /decrypted-mail/
gpasswd -a mail fuse
chgrp fuse /dev/fuse; chmod g+rw /dev/fuse
encfs /encrypted-mail /decrypted-mail --public

You may need to install fuse and modpobe it.

After mounting the encrypted folder, install postfix, dovecot and mysql:

apt install install postfix postfix-mysql dovecot-core dovecot-imapd dovecot-mysql mysql-server dovecot-lmtpd

Create a database for the mail server:

mysql_secure_installation
mysql

create database mailserver;
use mailserver;

GRANT SELECT,INSERT,UPDATE ON mailserver.* TO 'mailuser'@'127.0.0.1' IDENTIFIED BY 'mailuserpass';

FLUSH PRIVILEGES;

Create database tables:

CREATE TABLE `domains` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `users` (
  `id` int(11) NOT NULL auto_increment,
  `domain_id` int(11) NOT NULL,
  `username` varchar(255) NOT NULL,
  `email` varchar(100) NOT NULL,
  `password` varchar(60) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  FOREIGN KEY (domain_id) REFERENCES domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `aliases` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `mailserver`.`domains`
  (`id` ,`name`)
VALUES
  ('1', 'mx.example.com');
  ('2', 'example.com');

INSERT INTO `mailserver`.`aliases`
(`id`, `domain_id`, `source`, `destination`)
VALUES
('1', '1', 'webmaster@example.com', 'root@example.com');
('2', '1', 'www-data@example.com', 'root@example.com');

exit

Get ssl certificate for your domain:

git clone https://github.com/acmesh-official/acme.sh.git acme
cd acme && ./acme.sh --install
mkdir /etc/acme/live
acme.sh --issue --home /etc/acme/live -d mx.example

Move the default postfix configuration file to backup and create new one:

mv /etc/postfix/main.cf /etc/postfix/main.cf.orig
nano /etc/postfix/main.cf

smtpd_tls_cert_file=/etc/acme/live/mx.example/fullchain.cer 
smtpd_tls_key_file=/etc/acme/live/mx.example/mx.example.key
smtpd_use_tls=yes
smtpd_tls_auth_only = yes
smtp_tls_security_level = may
smtp_tls_loglevel = 2
smtpd_tls_received_header = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions =
        permit_sasl_authenticated,
        permit_mynetworks,
        reject_unauth_destination
mydestination = localhost
virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
local_recipient_maps = $virtual_mailbox_maps

Create the files needed for the symbiosis of postfix with the mysql database:

nano /etc/postfix/mysql-virtual-mailbox-domains.cf

user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM domains WHERE name='%s'

nano /etc/postfix/mysql-virtual-mailbox-maps.cf

user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM users WHERE email='%s'

nano /etc/postfix/mysql-virtual-alias-maps.cf

user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM aliases WHERE source='%s'

systemctl restart postfix

Now that postfix is set up, it’s time to set up dovecot:

mv /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
mv /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.orig
mv /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.orig
mv /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/dovecot-sql.conf.ext.orig
mv /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.orig
mv /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.orig
nano /etc/dovecot/dovecot.conf

protocols = imap

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

mail_location = maildir:/decrypted-mail/%d/%n
mail_privileged_group = mail
first_valid_uid = 0

nano /etc/dovecot/conf.d/10-auth.conf

disable_plaintext_auth = yes
auth_mechanisms = plain login
#INSERT a hashtag in front of the following import.  This separates your mail server's login from UNIX logins.
#!include auth-system.conf.ext
#REMOVE the hashtag in front of the following import.  This points it at mysql for authentication.
!include auth-sql.conf.ext

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

passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
  driver = static
  args = uid=mail gid=mail home=/decrypted-mail/%d/%n

nano /etc/dovecot/dovecot-sql.conf.ext

driver = mysql
connect = host=localhost dbname=mailserver user=mailuser password=mailuserpass
default_pass_scheme = BLF-CRYPT
password_query = SELECT email as user, password FROM users WHERE email='%u';

Configure the permissions for the dovecot folder:

chown -R mail:dovecot /etc/dovecot

chmod -R o-rwx /etc/dovecot

Configure the 10-master.conf file:

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

service imap-login {
  inet_listener imap {
    port = 0  
  }
service pop3-login {
  inet_listener pop3 {
    port = 0  
  }
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0666
    group = postfix
    user = postfix
  }

  # Create inet listener only if you can't use the above UNIX socket
  #inet_listener lmtp {
    # Avoid making LMTP visible for the entire internet
    #address =
    #port =
  #}
  user=mail
}
service auth {
  # auth_socket_path points to this userdb socket by default. It's typically
  # used by dovecot-lda, doveadm, possibly imap process, etc. Its default
  # permissions make it readable only by root, but you may need to relax these
  # permissions. Users that have access to this socket are able to get a list
  # of all usernames and get results of everyone's userdb lookups.
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }

  unix_listener auth-userdb {
    mode = 0600
    user = mail
    #group =
  }

  # Postfix smtp-auth
  #unix_listener /var/spool/postfix/private/auth {
  #  mode = 0666
  #}

  # Auth process is run as this user.
  user = dovecot
}
service auth-worker {
  # Auth worker process is run as root by default, so that it can access
  # /etc/shadow. If this isn't necessary, the user should be changed to
  # $default_internal_user.
  user = mail
}
service auth {
  # auth_socket_path points to this userdb socket by default. It's typically
  # used by dovecot-lda, doveadm, possibly imap process, etc. Its default
  # permissions make it readable only by root, but you may need to relax these
  # permissions. Users that have access to this socket are able to get a list
  # of all usernames and get results of everyone's userdb lookups.
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
}

  unix_listener auth-userdb {
    mode = 0600
    user = mail
    #group =
  }

  # Postfix smtp-auth
  #unix_listener /var/spool/postfix/private/auth {
  #  mode = 0666
  #}

  # Auth process is run as this user.
  user = dovecot
}
service auth-worker {
  # Auth worker process is run as root by default, so that it can access
  # /etc/shadow. If this isn't necessary, the user should be changed to
  # $default_internal_user.
  user = mail
}

And SSL configuration file:

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

ssl_cert = </etc/acme/live/mx.example/fullchain.cer 
ssl_key = </etc/acme/live/mx.example/mx.example.key
ssl = required

It is good to have tools such as opendmarc, opendkim and spf configured as well. Properly configured programs prevent messages from our server from landing into the spam folder on gmail, for example.

apt install opendkim opendkim-tool

mkdir -pv /etc/opendkim/
chown -Rv opendkim:opendkim /etc/opendkim
chmod go-rwx /etc/opendkim/*
cd /etc/opendkim/
opendkim-genkey -r -h rsa-sha256 -d mx.example -s mail
mv -v mail.private mail
cat mail.txt

nano /etc/opendkim/KeyTable
mx.example mx.example:mail:/etc/opendkim/mail

nano  /etc/opendkim/SigningTable
*@mx.example mx.example

nano /etc/opendkim/TrustedHosts
127.0.0.1


nano /etc/opendkim.conf
##
## opendkim.conf -- configuration file for OpenDKIM filter
##
Canonicalization        relaxed/relaxed
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts
KeyTable                refile:/etc/opendkim/KeyTable
LogWhy                  Yes
MinimumKeyBits          1024
Mode                    sv
PidFile                 /var/run/opendkim/opendkim.pid
SigningTable            refile:/etc/opendkim/SigningTable
Socket                  inet:8891@localhost
Syslog                  Yes
SyslogSuccess           Yes
TemporaryDirectory      /var/tmp
UMask                   022
UserID                  opendkim:opendkim

Add DNS record for DKIM:

HOST: mail._domainkey.mx.example.com
TXT: v=DKIM1; h=rsa-sha256; k=rsa; s=email; p=MIGfMAEGCSqGSIc3DQEBAQUAA4GNADCBiQKBgQCdQB1jKvQl6U9m3QEd1QJcV 1zERGt7fgdSD9PmxtcqySEJ1wTVha/jVeDfklbAakfALCzJ0XZxXEDCLGCmVRM8r2OTuCcwGyiZNsa2dqO4yu2ZNhPtehmvCn9X7EeHyTSNhUpsmP2zVsLydhbGHlYRNFij0VmWQAhYzDwkFXMfwIDRQAB
TTL: 7200

Get “p=” value from generated mail.txt file.

apt install opendmarc

chown -Rv opendmarc:opendmarc /etc/opendmarc
chmod go-rwx /etc/opendmarc/*

nano /etc/opendmarc.conf
AuthservID mx.example
PidFile /var/run/opendmarc/opendmarc.pid
RejectFailures false
Syslog true
SyslogFacility mail
TrustedAuthservIDs mx.example
IgnoreHosts /etc/opendmarc/ignore.hosts
UMask 0002
UserID opendmarc:opendmarc
Socket inet:8892@127.0.0.1
FailureReportsSentBy dmarc@mx.example.com
FailureReportsBcc dmarc@mx.example.com
FailureReports false
AutoRestart true
HistoryFile /var/log/opendmarc.log

Add DNS record for DMARC:

HOST: _dmarc.mx.example.com
TXT: v=DMARC1; p=none; pct=100; rua=mailto:dmarc@mx.example.com
TTL: 7200

Add these lines to postfix main.cf file:

nano /etc/postfix/main.cf

smtpd_milters           = inet:127.0.0.1:8891, inet:127.0.0.1:8892
non_smtpd_milters       = $smtpd_milters
milter_default_action   = accept

Then download and edit the files for your need and upload to your web server.

In the server.php file you should change the values for mailuser and mailuserpass (done earlier when configuring the mysql server). Everything else is up to your own ideas of what your registration page should look like.

Don’t forget to give the appropriate permissions to the downloaded files:

find /path/to/your/www -type d -exec chmod 755 {} +
find /path/to/your/www -type f -exec chmod 644 {} +
chown -R www-data.www-data /path/to/your/www

Go to example.com/register.php and try register some test account. You may notice that passwords are hashed and stored in the database in the bcrypt format with 12 cost option, so compared to the encrypted disk space it gives a pretty good result when it comes to data security of such a server.

The tutorials that I used to write this tube can be found at:

https://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/index.html

https://codewithawa.com/posts/complete-user-registration-system-using-php-and-mysql-database