Self-Hosted E-Mail

From The Brainwrecked Wiki
Revision as of 13:57, 20 June 2022 by BrainwreckedTech (talk | contribs) (Rainloop: Disable wiki markup on URL)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Setting up your own e-mail service from scratch can be a time-consuming task, but it also allows the ultimate control over your e-mail at no additional cost. Create any account(s) you want from any domain you own, and relieve yourself of the snooping from free e-mail service providers. All with the help of free software from Postfix, Dovecot, ISPmail (administer addresses), and Rainloop (web mail).

Do note that many, if not all, residential ISPs block port 25 to combat spam from any potentially botted customer PC on the ISP's network. SMTP by itself is not a secure protocol at all. As such, most SMTP servers are configured by default to reject unauthenticated plaintext requests to send mail from any IP that isn't localhost. The most common solution to this is to purchase Virtual Private Server (VPS) service, which can cost as little as US$3.00 per month. Another solution would be to purchase a Virtual Private Network (VPN) service with dedicated IP address and PTR record.

Prerequisites

Firewall
Make sure ports 25, 465, 587, 993, and 995 are open.
Database
Allows for the divorce of email addresses from UNIX/LDAP usernames.
Web Server
For web applications and Let's Encrypt SSL certificates.
Let's Encrypt
Allows the use of SSL-secured transmission instead of plaintext.
Redis
Rspamd uses this to cache recently seen mail servers and spam scores.

Required Packages

These packages are needed to set up your own e-mail service.

pacman -Syu --needed dovecot pigeonhole postfix rspamd swaks

Postfix Configuration

Create MariaDB Databases for Postfix

CREATE DATABASE MAIL;
CREATE USER 'mailuser'@'localhost' IDENTIFIED BY '<password>';
CREATE USER 'mailadmin'@'localhost' IDENTIFIED BY '<password>';
GRANT ALL ON MAIL.* TO 'mailadmin'@'localhost';
GRANT SELECT ON MAIL.* TO 'mailuser'@'localhost';
CREATE TABLE IF NOT EXISTS `MAIL`.`virtual_domains` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `MAIL`.`virtual_users` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`email` varchar(100) NOT NULL,
`password` varchar(150) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `MAIL`.`virtual_aliases` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Configure Postfix to Use MariaDB

/etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser
password = <password>
hosts = unix:/run/mysqld/mysqld.sock
dbname = MAIL
query = SELECT 1 FROM virtual_domains WHERE name='%s'
/etc/postfix/mysql-virtual-mailbox-maps.cf
user = mailuser
password = <password>
hosts = unix:/run/mysqld/mysqld.sock
dbname = MAIL
query = SELECT 1 FROM virtual_users WHERE email='%s'
/etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser
password = <password>
hosts = unix:/run/mysqld/mysqld.sock
dbname = MAIL
query = SELECT destination FROM virtual_aliases WHERE source='%s'
/etc/postfix/mysql-email2email.cf
user = mailuser
password = <password>
hosts = unix:/run/mysqld/mysqld.sock
dbname = MAIL
query = SELECT email FROM virtual_users WHERE email='%s'
sudo postconf virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
sudo postconf virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
sudo postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf,mysql:/etc/postfix/mysql-email2email.cf
sudo chgrp postfix /etc/postfix/mysql-*.cf
sudo chmod 640 /etc/postfix/mysql-*.cf

Configure Postfix for E-mail Filtering

Add the following lines to the end of the /etc/postfix/master.cf file:

/etc/postfix/master.cf
...
submission inet n - - - - smtpd
 -o syslog_name=postfix/submission
 -o smtpd_tls_security_level=encrypt
 -o smtpd_sasl_auth_enable=yes
 -o smtpd_sasl_type=dovecot
 -o smtpd_sasl_path=private/auth
 -o smtpd_sasl_security_options=noanonymous
 -o smtpd_sender_login_maps=mysql:/etc/postfix/mysql-email2email.cf
 -o smtpd_sender_restrictions=reject_sender_login_mismatch
 -o smtpd_sasl_local_domain=$myhostname
 -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
...
dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail:vmail argv=/usr/libexec/dovecot/deliver -f ${sender} -d ${recipient}
sudo postconf virtual_transport=lmtp:unix:private/dovecot-lmtp
sudo postconf smtpd_sasl_type=dovecot
sudo postconf smtpd_sasl_path=private/auth
sudo postconf smtpd_sasl_auth_enable=yes
sudo postconf smtpd_tls_security_level=may
sudo postconf smtpd_tls_auth_only=yes
sudo postconf smtpd_tls_cert_file=/etc/letsencrypt/live/<domain>/fullchain.pem
sudo postconf smtpd_tls_key_file=/etc/letsencrypt/live/<domain>/privkey.pem
sudo postconf smtp_tls_security_level=may
sudo postconf smtpd_milters=inet:127.0.0.1:11332
sudo postconf non_smtpd_milters=inet:127.0.0.1:11332
sudo postconf milter_protocol=6
sudo postconf milter_mail_macros="i {mail_addr} {client_addr} {client_name} {auth_authen}"

Dovecot Configuration

sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 vmail -d /srv/imap -m
sudo chown -R vmail:vmail /srv/mail
sudo cp /usr/share/doc/dovecot/example-config/dovecot.conf /etc/dovecot
sudo cp -r /usr/share/doc/dovecot/example-config/conf.d /etc/dovecot
/etc/dovecot/dovecot.conf
...

# Protocols we want to be serving.
# Don't include submission -- postfix does that.
protocols = imap pop3 lmtp sieve
....

# Add these lines to the end of the file
# Or just before the include blocks

service auth {

    unix_listener auth-client {
        group = postfix
        mode = 0660
        user = postfix
    }

    unix_listener auth-master {
        group = vmail
        mode = 0660
        user = vmail
    }

    user = root

}

service managesieve-login {

  inet_listener sieve {
    port = 4190
  }

}

service managesieve {
}

protocol sieve {
    managesieve_max_line_length = 65536
    managesieve_implementation_string = dovecot
    log_path = /var/log/dovecot-sieve-errors.log
    info_log_path = /var/log/dovecot-sieve.log
}

plugin {
    sieve = ~/dovecot.sieve
    sieve_global_path = /etc/dovecot/sieve/default.sieve
    sieve_dir = ~/sieve
    sieve_global_dir = /etc/dovecot/sieve/global/
}

lda_mailbox_autocreate = yes
lda_mailbox_autosubscribe = yes

protocol lda {
    mail_plugins = $mail_plugins autocreate sieve quota
    postmaster_address = postmaster@mydomain.com
    hostname = mail.mydomain.com
    auth_socket_path = /var/run/dovecot/auth-master
    log_path = /var/log/dovecot-lda-errors.log
    info_log_path = /var/log/dovecot-lda.log
}

protocol lmtp {
    mail_plugins = $mail_plugins autocreate sieve quota
    log_path = /var/log/dovecot-lmtp-errors.log
    info_log_path = /var/log/dovecot-lmtp.log
}
/etc/dovecot/conf.d/10-auth.conf
...
#!include auth-system.conf.ext
!include auth-sql.conf.ext
...
/etc/dovecot/conf.d/auth-sql.conf.ext
...
#userdb {
#  driver = sql
#  args = /etc/dovecot/dovecot-sql.conf.ext
#}
...
userdb {
  driver = static
  args = uid=vmail gid=vmail home=/srv/mail/%n@%d
}
/etc/dovecot/conf.d/10-mail.conf
...
mail_location = maildir:/srv/mail/%n@%d/Maildir
...
namespace inbox {
...
  separator = .
...
}
...
/etc/dovecot/conf.d/10-master.conf
...
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    group = postfix
    mode = 0600
    user = postfix
  }
...
}
...
service auth {
...
  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
...
}
...
/etc/dovecot/conf.d/10-ssl.conf
...
ssl = yes
...
ssl_cert = </etc/letsencrypt/live/<domain>/fullchain.pem
ssl_key = </etc/letsencrypt/live/<domain>/privkey.pem
...
ssl_ca = </etc/letsencrypt/live/<domain>/chain.pem
...
/etc/dovecot/conf.d/15-mailboxes.conf
...
mailbox INBOX.Drafts {
  special_use = \Drafts
  auto = subscribe
}
mailbox INBOX.Junk {
  special_use = \Junk
  auto = subscribe
  autoexpunge = 30d
}
mailbox INBOX.Trash {
  special_use = \Trash
  auto = subscribe
  autoexpunge = 30d
}
mailbox INBOX.Sent {
  special_use = \Sent
  auto = subscribe
}
...
/etc/dovecot/conf.d/20-imap.conf
...
protocol imap {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins imap_sieve
...
}
/etc/dovecot/conf.d/20-lmtp.conf
...
protocol lmtp {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins sieve
...
}
/etc/dovecot/conf.d/90-sieve.conf
...
sieve_after = /etc/dovecot/sieve-after
...
sieve_plugins = sieve_imapsieve sieve_extprograms
...
# From elsewhere to Junk folder
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/etc/dovecot/sieve/learn-spam.sieve

# From Junk folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/etc/dovecot/sieve/learn-ham.sieve

sieve_pipe_bin_dir = /etc/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe
}
sudo mkdir /etc/dovecot/sieve{-after,}
/etc/dovecot/sieve-after/spam-to-folder.sieve
require ["fileinto","mailbox"];

if header :contains "X-Spam" "Yes" {
 fileinto :create "INBOX.Junk";
 stop;
}
/etc/dovecot/sieve/learn-spam.sieve
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamd-learn-spam.sh";
/etc/dovecot/sieve/learn-ham.sieve
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamd-learn-ham.sh";
/etc/dovecot/dovecot-sql.conf.ext
driver = mysql
connect = host=/run/mysqld/mysqld.sock dbname=MAIL user=mailuser password=<password>
default_pass_scheme = SHA256-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
/etc/dovecot/sieve/rspamd-learn-spam.sh
#!/usr/bin/env sh
exec /usr/bin/rspamc learn_spam
/etc/dovecot/sieve/rspamd-learn-ham.sh
#!/usr/bin/env sh
exec /usr/bin/rspamc learn_ham
sudo sievec /etc/dovecot/sieve-after/spam-to-folder.sieve
sudo sievec /etc/dovecot/sieve/learn-spam.sieve
sudo sievec /etc/dovecot/sieve/learn-ham.sieve
sudo touch /var/log/{dovecot-lda-errors.log,dovecot-lda.log}
sudo touch /var/log/{dovecot-sieve-errors.log,dovecot-sieve.log}
sudo touch /var/log/{dovecot-lmtp-errors.log,dovecot-lmtp.log}
sudo mkdir -p /etc/dovecot/sieve/global
sudo chown vmail: -R /etc/dovecot/sieve
sudo chown vmail:mail /var/log/dovecot-*
sudo chown root:root /etc/dovecot/dovecot-sql.conf.ext
sudo chmod 600 /etc/dovecot/dovecot-sql.conf.ext
sudo chmod 600 /etc/dovecot/sieve/learn-{spam,ham}.sieve
sudo chmod 700 /etc/dovecot/sieve/rspamd-learn-{spam,ham}.sh
sudo chown vmail:vmail /etc/dovecot/sieve/learn-{spam,ham}.sieve
sudo chown vmail:vmail /etc/dovecot/sieve/rspamd-learn-{spam,ham}.sh

Rspamd Configuration

sudo mkdir /etc/rspamd/conf.d
/etc/rspamd/override.d/milter_headers.conf
extended_spam_headers = true;
/etc/rspamd/override.d/classifier-bayes.conf
autolearn = true;
sudo mkdir /var/lib/rspamd/dkim
sudo chown _rspamd:_rspamd /var/lib/rspamd/dkim
sudo rspamadm dkim_keygen -d <domain> -s $(date +%Y%m%d)
/var/lib/rspamd/dkim/<domain>.YYYYMMDD.key
-----BEGIN PRIVATE KEY-----
[base64 key]
-----END PRIVATE KEY-----

The second part of the output of rspamadm dkim_keygen can be directly pasted into a DNS zone if you are running your own server. If you only have control of your domain name through a 3rd party:

Type Host Value
TXT @ v=spf1 a mx ip4:<ip-address> -all
TXT _dmarc v=DMARC1; p=none; sp=none; rf=afrf; pct=100; ri=86400
TXT YYYYMMDD._domainkey v=DKIM1; k=rsa; p=AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789...
/etc/rspamd/local.d/dkim_signing.conf
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
/etc/rspamd/dkim_selectors.map
<domain> YYYYMMDD
sudo systemctl enable --now rspamd

ISPmail Admin

Start with the Non-Proxied Web Site template.

Then, in the site's root:

sudo wget 'https://www.ima.jungclaussen.com/dwn/dwn.php?v=0.9.6&f=.tar.gz'
sudo tar -xvf 'dwn.php?v=0.9.6&f=.tar.gz'
sudo mv ispmail*/htdocs/* ./
sudo rm -r 'dwn.php?v=0.9.6&f=.tar.gz' ispmail*

Then edit the configuration file:

<ispmail_root>/cfg/config.inc.php
<?php

// @package     ISPmail_Admin
// @author      Ole Jungclaussen
// @version     0.9.0

// SHOW PHP ERRORS (DEBUGGING)

// ini_set('display_startup_errors', 1);
// ini_set('display_errors', 1);
// error_reporting(-1);

// DATABASE ACCESS

define('IMA_CFG_DB_HOST',       '127.0.0.1');
define('IMA_CFG_DB_PORT',       '3306');
define('IMA_CFG_DB_USER',       'mailadmin');
define('IMA_CFG_DB_PASSWORD',   '<password>');
define('IMA_CFG_DB_DATABASE',   'MAIL');

// PASSWORD HASHES (enable only *one*)

define('IMA_CFG_USE_SHA256_HASHES', true);
// define('IMA_CFG_USE_MD5_HASHES', true);

// ACCESS CONTROL: uncomment the type you want to use.

define('IMA_CFG_LOGIN', IMA_LOGINTYPE_ACCOUNT);
// define('IMA_CFG_LOGIN', IMA_LOGINTYPE_ADM);
// define('IMA_CFG_LOGIN', IMA_LOGINTYPE_ADMAUTO);

// ADMINISTRATOR'S NAME AND PASSWORD

define('IMA_CFG_ADM_USER',  'admin');      // admin username
define('IMA_CFG_ADM_PASS',  '<password>'); // admin password

// LISTS
// Spread long lists on multiple pages.  Set number of maximum entries
// per page.  Changes take effect after login/logout.  If not defined,
// defaults to 65535.

// define('IMA_LIST_MAX_ENTRIES', 200);
?>

After finalization you should now be able to log in and set up your virtual mailboxes.

Rainloop

Start with the Non-Proxied Web Site template.

Change the following sections of the nginx conf file:

/etc/nginx/sites-available/<domain>.conf
...
	location ~ /data {
		deny			all;
	}
...
	location ~ \.php$ {
		fastcgi_pass		unix:/run/php-fpm/php-fpm.sock;
		fastcgi_index		index.php;
		include			fastcgi.conf;
		fastcgi_split_path_info	^(.+\.php)(.*)$;
		fastcgi_param		SCRIPT_FILENAME $document_root$fastcgi_script_name;
	}
...

Download Rainloop

sudo cd /srv/http/<domain>
sudo wget https://www.rainloop.net/repository/webmail/rainloop-community-latest.zip

Extract the zip file

sudo unzip rainloop-community-latest.zip

Set the correct permissions on the files

sudo find . -type d -exec chmod 755 {} \;
sudo find . -type f -exec chmod 644 {} \;

Set the correct owner on the whole web root

sudo chown -R http:http /srv/http/<dir>

After you finalize the setup, you should now be able to access the Admini Panel at http://<domain>/?admin. The default login is admin:12345

From the Admin Panel, add your domain under Domains. Use the public-facing domain for the Name, and localhost for the IMAP and SMTP servers.

Under Login, set the Default Domain to localhost.

Optionally, you can enable contacts. You will need to manually set up a database and user, but that is the only configuration needed to do on your part.

For Security, you should enable all of the Security options. You should also change the Admin Panel Access Credentials.

Click on the power-off icon in the upper-right corner to finish with the Admin Panel.

Navigate to http://<domain> to log in and access your mail.