mail.libre.is

Documentation for Libre mail server.

Setting up Internet mail servers is a pain. It’s nothing like just setting up a web server…

The initial Administration section is for after the server has been set up and configured. Below it is how the server is set up.

Administration

Once everything is setup working…

ispmail_userctl

The easiest way to admin is to run this script:

sudo ispmail_userctl

Database

To admin directly, log into the database using the mailadmin database password:

mariadb -u mailadmin -p mailserver

List Virtual Domains

SELECT * FROM virtual_domains;

List Virtual Users

SELECT * FROM virtual_users;

List Virtual Aliases

SELECT * FROM virtual_aliases;

Add Virtual Domain

Add the domain:

INSERT INTO virtual_domains (name) VALUES ("example.org");

Add a Mail User

Generate a password with dovecot:

sudo dovecot pw -s BLF-CRYPT

Log into database as mailadmin and run this command, using the dovecot generated password string, and set the domain and user email.

Note, upstream docs are missing “(” and “)” for VALUES.

INSERT INTO virtual_users (domain_id, email, password) VALUES
  ((SELECT id FROM virtual_domains WHERE name='example.org'),
  'john@example.org','{BLF-CRYPT}$2y$05$.We…');

Add a User Alias

Example to add an alias. The first email should be the alias, the second email is where it should go to.

INSERT INTO virtual_aliases (domain_id, source, destination) VALUES
  ( (SELECT id FROM virtual_domains WHERE name='example.org'),
  'melissa@example.org', 'juila@example.net');

Change a User Password

Generate string for new password:

sudo dovecot pw -s BLF-CRYPT

Use that string:

UPDATE virtual_users SET password='{BLF-CRYPT}$2y$05$.We…' WHERE email='email@address';

Delete Virtual Domain

DELETE FROM virtual_domains where name='example.org';

Delete User

DELETE FROM virtual_users WHERE email='john@example.org';

Delete Alias

DELETE FROM virtual_aliases WHERE source='melissa@example.org';

Email Client Setup

Set up mail clients, such as Thunderbird, thusly.

Using example email address username@libre.is.

Incoming Server
Hostname: mail.libre.is
Protocol: IMAP
Port: 993
Connection Security: SSL/TLS
Authenication Method: Normal Password
Username: username@libre.is (same as email address)

Outgoing Server
Hostname: mail.libre.is
Port: 587
Connection Security: STARTTLS
Authenication Method: Normal Password
Username: username@libre.is (same as email address)

Main Components

This install is based on this guide:

For more information and details about what is what, refer to that site.

The main components in use:

Certbot (Let’s Encrypt)

Debian

Dovecot

MariaDB

Postfix

redis

rspamd

unbound

DNS

Add a DNS mx record, so it returns result like this:

$ host -t mx libre.is
libre.is mail is handled by 10 mail.libre.is.

Set IP for mail.libre.is. Set up reverse DNS records.

Debian

Install Debian stable (bookworm). Install rsyslog for old school convenience:

sudo apt install rsyslog

Firewall

Open TCP ports.

# Web
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

# Postfix
-A INPUT -p tcp --dport 25 -j ACCEPT
-A INPUT -p tcp --dport 587 -j ACCEPT

# Dovecot imaps
-A INPUT -p tcp --dport 993 -j ACCEPT

# Dovecot sieve
-A INPUT -p tcp --dport 4190 -j ACCEPT

Apache

The Apache webserver is used out of laziness as it allows easy certificate updates with certbot. A webmail server won’t be running on the main mail server.

sudo apt install apache2
echo "mail.libre.is" | sudo tee /var/www/html/index.html

Open up firewall ports 80 and 443.

MariaDB

The main database server.

sudo apt install mariadb-server
sudo mariadb-admin password
mariadb -uroot -p

Add databases. Change password to something secure.

CREATE DATABASE mailserver;

GRANT ALL ON mailserver.* TO 'mailadmin'@'localhost' IDENTIFIED BY 'password';

GRANT SELECT ON mailserver.* TO 'mailserver'@'127.0.0.1' IDENTIFIED BY 'password';

USE mailserver;

CREATE TABLE IF NOT EXISTS `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 `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,
 `quota` bigint(11) NOT NULL DEFAULT 0,
 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 `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;

EXIT

Postfix

The main SMTP mail server.

sudo apt install postfix
sudo apt install postfix-mysql

Set up postfix to use MariaDB. Edit /etc/postfix/mysql-virtual-mailbox-domains.cf and add below, using the mailserver password used in MariaDB.

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

Edit /etc/postfix/mysql-virtual-mailbox-maps.cf and add below contents:

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

Edit /etc/postfix/mysql-virtual-alias-maps.cf and add below:

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

Edit /etc/postfix/mysql-email2email.cf and add:

user = mailserver
password = password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT email FROM virtual_users WHERE email='%s'

Then run these commands:

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
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

Redis

Note, the licensing of Redis has gone bad. The version in Debian is OK. But in the future, probably replace with a fork.

sudo apt install redis-server

rspamd

Spam control.

sudo apt install rspamd

Certbot

Encryption certificates with Let’s Encrypt. Not using an Apache webserver on the mail server makes getting new certificates a bit more complex.

sudo apt install certbot ca-certificates python3-certbot-apache
sudo certbot -d mail.libre.is
sudo systemctl restart apache2
echo "post-hook = systemctl restart postfix dovecot apache2" | \
  sudo tee /etc/letsencrypt/cli.ini

Dovecot

Just using encrypted IMAPS, not POP.

sudo apt install dovecot-mysql dovecot-pop3d dovecot-imapd \
  dovecot-managesieved dovecot-lmtpd

Note, since IPv6 isn’t being used, the dovecot install barfs. Edit /etc/dovecot/dovecot.conf and add this line, where appropriate:

listen = *

Note, this is removing the “::” from listen, which using IPv6. Then re-run the install so the packages are happy. Note, the re-install won’t overwrite the “listen” change.

sudo apt install --reinstall dovecot-mysql dovecot-pop3d dovecot-imapd \
  dovecot-managesieved dovecot-lmtpd

Add user and set up configs

sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 vmail -d /var/vmail -m
sudo chown -R vmail:vmail /var/vmail

sudo sed -i -e \
  's/auth_mechanisms = plain/auth_mechanisms = plain login/g' \
  /etc/dovecot/conf.d/10-auth.conf

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

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

sudo sed -i -e \
  's/^mail_location.*/mail_location = maildir:~\/Maildir/g' \
  /etc/dovecot/conf.d/10-mail.conf

sudo sed -i -e \
  's/#mail_plugins =/mail_plugins = quota/g' \
  /etc/dovecot/conf.d/10-mail.conf

Edit /etc/dovecot/conf.d/10-master.conf and add:

# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
  mode = 0660
  user = postfix
  group = postfix
}

Edit /etc/dovecot/conf.d/10-ssl.conf, set key locations, and make it required.

ssl = required
ssl_cert = </etc/letsencrypt/live/mail.libre.is/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.libre.is/privkey.pem

Edit /etc/dovecot/dovecot-sql.conf.ext file and add these lines at the bottom, changing the password to the mailserver database password.

driver = mysql

connect = \
  host=127.0.0.1 \
  dbname=mailserver \
  user=mailserver \
  password=password

user_query = SELECT email as user, \
  concat('*:bytes=', quota) AS quota_rule, \
  '/var/vmail/%d/%n' AS home, \
  5000 AS uid, 5000 AS gid \
  FROM virtual_users WHERE email='%u'

password_query = SELECT password FROM virtual_users WHERE email='%u'

iterate_query = SELECT email AS user FROM virtual_users

Set file permissions.

sudo chown root:root /etc/dovecot/dovecot-sql.conf.ext
sudo chmod 600 /etc/dovecot/dovecot-sql.conf.ext

Edit /etc/dovecot/conf.d/10-master.conf and change to:

service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    group = postfix
    mode = 0600
    user = postfix
  }
}

Restart dovecot server.

sudo systemctl restart dovecot

Run this to tell postfix to deliver to dovecot:

sudo postconf virtual_transport=lmtp:unix:private/dovecot-lmtp

Edit /etc/dovecot/conf.d/20-lmtp.conf and change line like this:

mail_plugins = $mail_plugins sieve

Restart dovecot again….

sudo systemctl restart dovecot

More postfix

More postfix configuration, now that the above is set up.

Set postfix to use dovecot for authentication:

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/mail.libre.is/fullchain.pem
sudo postconf smtpd_tls_key_file=/etc/letsencrypt/live/mail.libre.is/privkey.pem
sudo postconf smtp_tls_security_level=may

Edit /etc/postfix/master.cf and change thusly:

submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_client_restrictions=
  -o smtpd_helo_restrictions=
  -o smtpd_sender_restrictions=
  -o smtpd_relay_restrictions=
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

Run:

sudo postconf smtpd_sender_login_maps=mysql:/etc/postfix/mysql-email2email.cf

Restart postfix:

sudo systemctl restart postfix

Does it ever end? Edit /etc/postfix/master.cf and add to bottom of submission section.

-o smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject
sudo systemctl restart postfix

Allow aliases to send by adding this file (XXX check OK) /etc/postfix/aliases.cf with this contents:

SELECT email FROM virtual_users WHERE email='%s' UNION SELECT destination FROM virtual_aliases WHERE source='%s'

Make sure all is good:

sudo postfix check

rspamd Configuration

Configure postfix for rspamd.

sudo postconf smtpd_milters=inet:127.0.0.1:11332
sudo postconf non_smtpd_milters=inet:127.0.0.1:11332
sudo postconf milter_mail_macros="i {mail_addr} {client_addr} {client_name} {auth_authen}"

Edit /etc/rspamd/override.d/milter_headers.conf and add:

extended_spam_headers = true;

Edit /etc/dovecot/conf.d/90-sieve.conf and change:

sieve_after = /etc/dovecot/sieve-after

Create dir for new sieve filter:

sudo mkdir /etc/dovecot/sieve-after

Create /etc/dovecot/sieve-after/spam-to-folder.sieve with these contents:

require ["fileinto"];

if header :contains "X-Spam" "Yes" {
 fileinto "Junk";
 stop;
}

Then compile it:

sudo sievec /etc/dovecot/sieve-after/spam-to-folder.sieve

Set up redis by adding /etc/rspamd/override.d/redis.conf with this:

servers = "127.0.0.1";

Add this /etc/rspamd/override.d/classifier-bayes.conf with below contents:

autolearn = [-5, 10];

Add /etc/rspamd/local.d/classifier-bayes.conf with:

users_enabled = true;

Edit /etc/dovecot/conf.d/20-imap.conf and change:

mail_plugins = $mail_plugins quota imap_sieve

Edit /etc/dovecot/conf.d/90-sieve.conf and add below to “plugins” section:

# 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
sieve_plugins = sieve_imapsieve sieve_extprograms

Run:

sudo mkdir /etc/dovecot/sieve

Create /etc/dovecot/sieve/learn-spam.sieve with contents:

require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamd-learn-spam.sh";

Create /etc/dovecot/sieve/learn-ham.sieve and add:

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";
}
if string :matches "${mailbox}" ["*/Trash", "Trash"] {
  stop;
}
pipe :copy "rspamd-learn-ham.sh";

Run this to compile:

sudo sievec /etc/dovecot/sieve/learn-spam.sieve
sudo sievec /etc/dovecot/sieve/learn-ham.sieve
sudo chmod 600 /etc/dovecot/sieve/learn-{spam,ham}.{sieve,svbin}
sudo chown vmail:vmail /etc/dovecot/sieve/learn-{spam,ham}.{sieve,svbin}

Create /etc/dovecot/sieve/rspamd-learn-spam.sh with contents:

#!/bin/sh
exec /usr/bin/rspamc learn_spam

Create /etc/dovecot/sieve/rspamd-learn-ham.sh with contents:

#!/bin/sh
exec /usr/bin/rspamc learn_ham

Set ownership and permissions on scripts:

sudo chmod 700 /etc/dovecot/sieve/rspamd-learn-{spam,ham}.sh
sudo chown vmail:vmail /etc/dovecot/sieve/rspamd-learn-{spam,ham}.sh

Unbound

For proper spam filtering with rspam, unbound DNS resolver should be used.

sudo apt install unbound

Change /etc/resolv.conf to:

nameserver 127.0.0.1
options trust-ad

Also add to /etc/rspamd/local.d/options.inc

dns {
  nameserver = ["127.0.0.1"];
}

DKIM

Set up DNS for DKIM.

sudo apt install dnsutils
sudo mkdir /var/lib/rspamd/dkim
sudo chown _rspamd:_rspamd /var/lib/rspamd/dkim
sudo rspamadm dkim_keygen -d libre.is -s 2024090101

Add a 2024090101._domainkey TXT DNS record at the ISP, with contents of the “p=” and the rest, for example:

p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxenHupkYLPmFMbJjV9dQICKUl2xH/aexSRUwCuw7TJ9dkddqIN+6tyw4VKhnW8R0/UlbzlSFLmVgMU0uUkwTtVqyDHhtSU7LV/SkVYmUst4dTUF1r+8PvhAm7vobMYKdwvRsOq27ABtZc8P4oU2XXHqqa6LU8s4sNxs12hLW9swIDAQAB

Create /etc/rspamd/local.d/dkim_signing.conf with contents:

path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";

Create /etc/rspamd/dkim_selectors.map with contents:

libre.is 2024090101

Restart again…

sudo systemctl restart rspamd

Take teh contents from running dkim_keygen above and add it this file: /var/lib/rspamd/dkim/libre.is.2024090101.key

Just add the PRIVATE KEY section, not the last two lines. Fix it’s permissions:

sudo chown _rspamd /var/lib/rspamd/dkim/libre.is.2024090101.key
sudo chmod 400 /var/lib/rspamd/dkim/libre.is.2024090101.key

SPF

Set up SPF.

Add a DNS TXT record like this:

v=spf1 mx a ip4:70.39.110.156/32 include:mail.libre.is -all

DMARC

Create a DNS TXT record for the domain _dmarc.libre.is with contents:

v=DMARC1; p=reject; rua=mailto:postmaster@libre.is; ruf=mailto:postmaster@libre.is; fo=0; adkim=r; aspf=r; pct=100; rf=afrf; ri=86400; sp=reject

Python Admin

Admin with Python script.

sudo apt install python3-mysqldb python3-bcrypt
git clone https://github.com/cgzones/ispmail-userctl
cd ispmail-userctl/

Edit ispmail_userctl.py and change:

  • USE_BCRYPT: bool = True

Under DB_CONNECTION, change user and password to match database setup:

  • user=’mailadmin’

  • password=’’

Then move the script in place.

sudo cp -p ispmail_userctl.py /usr/local/sbin/ispmail_userctl
sudo chown root:root /usr/local/sbin/ispmail_userctl
sudo chmod 700 /usr/local/sbin/ispmail_userctl

Then just run it normally as root.

sudo ispmail_userctl

Spamhaus

Spamhaus may automagically list the IP to be blocked when it is new. To remove, just go to:

Then enter the IP address of the server and fill out the form. They will send a confirmation email with a link. Go to that link and it will be immediately removed.

Abuse

Set up an email alias for abuse@libre.is.

Follow instructions on this site to add the abuse email to their list: