new page on certbot. re-ordered pages
This commit is contained in:
parent
a29d2627d6
commit
13b2d7bedd
5 changed files with 379 additions and 228 deletions
|
|
@ -3,7 +3,7 @@ title: Installing Debian
|
|||
lastUpdated: 2025-08-14
|
||||
slug: ispmail-trixie/install-debian
|
||||
sidebar:
|
||||
order: 130
|
||||
order: 120
|
||||
---
|
||||
|
||||
import { Aside } from "@astrojs/starlight/components";
|
||||
|
|
@ -3,7 +3,7 @@ title: Install software packages
|
|||
lastUpdated: 2025-08-14
|
||||
slug: ispmail-trixie/install-the-software-packages
|
||||
sidebar:
|
||||
order: 140
|
||||
order: 130
|
||||
---
|
||||
|
||||
import { Aside } from "@astrojs/starlight/components";
|
||||
|
|
@ -11,14 +11,14 @@ import { Aside } from "@astrojs/starlight/components";
|
|||
Let's install the necessary Debian packages. Run this command as root on your server as _root_:
|
||||
|
||||
```sh
|
||||
apt update
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
apt -y install postfix-mysql dovecot-mysql \
|
||||
dovecot-imapd dovecot-lmtpd dovecot-managesieved \
|
||||
apache python3-certbot-apache libapache2-mod-php \
|
||||
php-intl php-mbstring php-xml unzip \
|
||||
apache2 python3-certbot-apache libapache2-mod-php \
|
||||
php-intl php-mbstring php-xml unzip certbot \
|
||||
roundcube-sqlite3 roundcube swaks ufw mutt \
|
||||
crowdsec crowdsec-firewall-bouncer unattended-upgrades \
|
||||
mariadb-server
|
||||
unattended-upgrades mariadb-server
|
||||
|
||||
postfix chroot off
|
||||
```
|
||||
|
|
@ -48,16 +48,16 @@ While the server is downloading and installing the packages, let me give you a q
|
|||
to Postfix to send emails.
|
||||
- **swaks** \
|
||||
The SWiss Army Knife of Smtp. Helpful command-line tool to send test emails to a mail server.
|
||||
- **certbot** \
|
||||
Gets a free TLS certificate from [_Let's Encrypt_](https://letsencrypt.org/) for encrypting network communication.
|
||||
- **ufw** \
|
||||
Universal FireWall. A simple tool to manage firewall rules to limit access to your server.
|
||||
Universal FireWall. A simple tool to manage firewall rules to limit access to your server. TODO: omit?
|
||||
- **rspamd** \
|
||||
It reliably detects spam. Also handles adding DKIM signature to outgoing email to prevent spoofing your domains.
|
||||
It reliably detects spam. Also handles adding DKIM signature to outgoing email to prevent spoofing your domains. TODO:
|
||||
later/optional
|
||||
- **mutt** \
|
||||
A console-based program that can speak IMAP and also read Maildirs directly. Very helpful for testing the
|
||||
functionality of your mail server. Think of it as a text-based Thunderbird.
|
||||
- **crowdsec** \
|
||||
Detects attacks to your mail server and blocks the IP address of the attacker. This is a more sophisticated version of
|
||||
fail2ban.
|
||||
- **unattended-upgrades** \
|
||||
Installs security updates automatically.
|
||||
|
||||
|
|
@ -65,8 +65,6 @@ While the server is downloading and installing the packages, let me give you a q
|
|||
|
||||
By default, Debian enables Postfix to run in chroot mode. While this may sound like a security improvement, even
|
||||
Postfix’s author advises against it, arguing that it mostly creates a false sense of security. For this reason, we are
|
||||
disabling chroot mode—which also simplifies some configuration details for us later on.
|
||||
disabling chroot mode—which also simplifies some configuration later on.
|
||||
|
||||
</Aside>
|
||||
|
||||
TODO: move crowdsec into optional chapter
|
||||
114
src/content/docs/ispmail-trixie/140-certbot.mdx
Normal file
114
src/content/docs/ispmail-trixie/140-certbot.mdx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
title: TLS certificate
|
||||
lastUpdated: 2025-08-24
|
||||
slug: ispmail-trixie/tls-certificate
|
||||
sidebar:
|
||||
order: 140
|
||||
---
|
||||
|
||||
Your mail server can't work properly without a valid TLS certificate. It will be used in three places:
|
||||
|
||||
1. the **webmail** interface (driven by the Apache web server)
|
||||
2. **Postfix** (to encrypt SMTP communication with other mail servers on the internet where possible)
|
||||
3. **Dovecot** (to encrypt IMAP/POP3 communication)
|
||||
|
||||
Your mail server will be able to host email addresses for many domains. But a TLS certificate is usually issued for a
|
||||
single [FQDN](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) like “mail.example.org”. That _common name_ is
|
||||
an important part of the certificate. It must match the name you use to talk to the server.
|
||||
|
||||
**Correct**: Your mail server is addressed as `mail.example.org`. The TLS certificate was also issued to
|
||||
`mail.example.org`.
|
||||
|
||||
**Wrong**: Your mail server is addressed as `smtp01.example.com`. But the TLS certificate was issued to
|
||||
`mail.example.org`.
|
||||
|
||||
So choose an FQDN for your mail server. For example I use `webmail.workaround.org` for my personal server.
|
||||
|
||||
## Certbot
|
||||
|
||||
You already have the `certbot` software and the `apache2` web server installed. Requesting a certificate from Let's
|
||||
Encrypt is as simple as running the next command. Please replace `mail.example.org` by the FQDN you chose.
|
||||
|
||||
```sh
|
||||
certbot --apache --register-unsafely-without-email --agree-tos -d mail.example.org
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Click here to learn what the options mean…</summary>
|
||||
|
||||
- **--apache**: Use the [HTTP challenge](https://letsencrypt.org/docs/challenge-types/) to verify that you are actually
|
||||
in control of the FQDN. The certbot plugin will add some configuration to intercept HTTP requests to
|
||||
`/.well-known/acme-challenge/…` on your web server. This is a fully automatic process that does not even have to stop
|
||||
your web server.
|
||||
- **--register-unsafely-without-email**: Do not ask for an email address to send to Let's Encrypt. Before June 2025
|
||||
Let's Encrypt used that address to inform you if your certificate is about to expire and the automatic renewal failed.
|
||||
Nowadays they do not need an email any more. But for historical reasons they still ask for it by default.
|
||||
- **--agree-tos**: Confirm that you have read Let's Encrypt's terms and conditions. Let's be honest – nobody reads that.
|
||||
- **-d**: The domain that the certificate will be issued to. This has to be the exact FQDN of your server.
|
||||
|
||||
</details>
|
||||
|
||||
The output should look like:
|
||||
|
||||
```
|
||||
Saving debug log to /var/log/letsencrypt/letsencrypt.log
|
||||
Requesting a certificate for mail.example.org
|
||||
|
||||
Successfully received certificate.
|
||||
Certificate is saved at: /etc/letsencrypt/live/mail.example.org/fullchain.pem
|
||||
Key is saved at: /etc/letsencrypt/live/mail.example.org/privkey.pem
|
||||
This certificate expires on 2025-11-22.
|
||||
These files will be updated when the certificate renews.
|
||||
Certbot has set up a scheduled task to automatically renew this certificate in the background.
|
||||
|
||||
Deploying certificate
|
||||
Successfully deployed certificate for mail.example.org to /etc/apache2/sites-available/000-default-le-ssl.conf
|
||||
Congratulations! You have successfully enabled HTTPS on https://mail.example.org
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
If you like Certbot, please consider supporting our work by:
|
||||
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
|
||||
* Donating to EFF: https://eff.org/donate-le
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Click here to learn which files have been created…</summary>
|
||||
|
||||
In /etc/letsencrypt/live/**mail.example.org** you will find a couple of files now:
|
||||
|
||||
- cert.pem: the certificate file
|
||||
- chain.pem: the _chaining_ or _intermediate_ certificate. This certificate provides information how the LetsEncrypt
|
||||
certificates are linked to other known certificate authorities. It is generally a good idea to always send this
|
||||
certificate along with your own for clients who may not know LetsEncrypt properly yet.
|
||||
- fullchain.pem: this file contains a concatenation of the _cert.pem_ and the _chain.pem_. This is the preferred file to
|
||||
use when a piece of software asks where to find the _certificate_.
|
||||
- privkey.pem: the private key file. Keep it secret.
|
||||
|
||||
</details>
|
||||
|
||||
## Renewal
|
||||
|
||||
Your new certificate is only valid for 3 months. Fortunately the renewal is happening automatically. A _systemd timer_
|
||||
checks your certificate frequently and triggers a renewal one month before it expires.
|
||||
|
||||
These commands will show you the definition and status of the timer and the renewal service:
|
||||
|
||||
```sh
|
||||
systemctl status certbot.timer
|
||||
systemctl cat certbot.timer
|
||||
systemctl status certbot.service
|
||||
systemctl cat certbot.service
|
||||
```
|
||||
|
||||
_Systemd timers_ are similar to cron jobs. They require more work to set up but have some nice features and make you
|
||||
look cooler.
|
||||
|
||||
It is important that services that use the certificate load the new certificate after an automatic renewal. This shell
|
||||
command will create a file `/etc/letsencrypt/cli.ini` that handles it:
|
||||
|
||||
```sh
|
||||
echo > /etc/letsencrypt/cli.ini "post-hook = systemctl reload postfix dovecot apache2"
|
||||
```
|
||||
|
||||
Perfect. You won't have to worry about the certificate again.
|
||||
|
|
@ -15,17 +15,7 @@ import { Steps } from "@astrojs/starlight/components";
|
|||
|
||||
import Box from "../../../components/Box.astro";
|
||||
|
||||
import StepList from "../../../components/StepList.astro";
|
||||
|
||||
<StepList
|
||||
steps={[
|
||||
{ title: "Install Astro", description: "Set up your project." },
|
||||
{ title: "Add Starlight", description: "Install the theme." },
|
||||
{ title: "Customize", description: "Adjust settings to your needs." },
|
||||
{ title: "Deploy", description: "Publish to production." },
|
||||
]}
|
||||
currentStep={2}
|
||||
/>
|
||||
import StepListReceive from "../../../components/StepListReceive.astro";
|
||||
|
||||
I have created a slideshow to help you understand the process of receiving an email from a remote mail server. Use the
|
||||
arrows to navigate between slides:
|
||||
|
|
@ -38,29 +28,17 @@ arrows to navigate between slides:
|
|||
))}
|
||||
</Splide>
|
||||
|
||||
---
|
||||
|
||||
Let's tackle the receiving of emails step by step:
|
||||
|
||||
## DNS
|
||||
|
||||
<ul class="steps steps-vertical lg:steps-horizontal">
|
||||
<li class="step step-primary">Register</li>
|
||||
<li class="step step-primary">Choose plan</li>
|
||||
<li class="step">Purchase</li>
|
||||
<li class="step">Receive Product</li>
|
||||
</ul>
|
||||
<StepListReceive currentStep={1} />
|
||||
|
||||
<Box icon='check-list'>
|
||||
<Steps>
|
||||
|
||||
1. **DNS is used to find your mail server. First the MX record. Then the A and/or AAAA record.**
|
||||
1. Postfix receives emails using SMTP (the Simple Mail Transport Protocol)
|
||||
1. MariaDB stores information about your domains and mail users
|
||||
1. rspamd checks if it is spam (optional)
|
||||
1. Dovecot saves it to disk
|
||||
|
||||
</Steps>
|
||||
</Box>
|
||||
|
||||
Let's begin with setting up the DNS records. Say that you want to receive emails for the `example.com` domain. If some
|
||||
other mail server on the internet wants to send an email to your server, it looks for two records:
|
||||
As shown in the slideshow, you need to have proper DNS records set up. Say that you want to receive emails for the
|
||||
`example.com` domain. If some other mail server on the internet wants to send an email to your server, it looks for two
|
||||
records:
|
||||
|
||||
- **MX record (`example.com`)**
|
||||
The MX (Mail Exchange) record tells other mail servers _where to deliver emails_ for the domain `example.com`.
|
||||
|
|
@ -101,20 +79,46 @@ If possible, also add a PTR record that makes the IP addresses point back to the
|
|||
servers may be more likely so flag your sent emails as spam if you do not have a PTR record. Preferable the forward
|
||||
(A/AAAA) and reverse (PTR) records match.
|
||||
|
||||
<div class="scroll-trigger" data-slide="5">
|
||||
dingdong
|
||||
</div>
|
||||
<Aside icon="approve-check-circle" title="Test it">
|
||||
|
||||
Use the `host` command to check your domain's MX record.
|
||||
|
||||
```sh
|
||||
$> host -t mx example.com
|
||||
example.com mail is handled by 10 smtp.example.com.
|
||||
```
|
||||
|
||||
Then check if that result resolves to an IP address:
|
||||
|
||||
```sh
|
||||
$> host smtp.example.com
|
||||
smtp.example.com has address 49.13.89.249
|
||||
smtp.example.com has IPv6 address 2a01:4f8:c012:62d1::1
|
||||
```
|
||||
|
||||
Preferably the IP addresses should resolve back to the mail server's name:
|
||||
|
||||
```sh
|
||||
$> host 49.13.89.249
|
||||
249.89.13.49.in-addr.arpa domain name pointer smtp.example.com.
|
||||
$> host 2a01:4f8:c012:62d1::1
|
||||
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.d.2.6.2.1.0.c.8.f.4.0.1.0.a.2.ip6.arpa domain name pointer smtp.example.com.
|
||||
```
|
||||
|
||||
</Aside>
|
||||
|
||||
## Postfix
|
||||
|
||||
<StepListReceive currentStep={2} />
|
||||
|
||||
Now that other mail servers can locate your mail server, the next step is to make sure it can receive and process
|
||||
incoming emails. This task is handled by Postfix, which communicates using the SMTP protocol.
|
||||
|
||||
Postfix knows if an email address is valid by checking for…
|
||||
|
||||
- Virtual Domains (am I responsible for this domain?)
|
||||
- Virtual Aliases (do I have to redirect this address to another address?)
|
||||
- Virtual Mailboxes (do I have a valid mailbox to store the email to?)
|
||||
- **Virtual Domains** (am I responsible for this domain?)
|
||||
- **Virtual Aliases** (do I have to redirect this address to another address?)
|
||||
- **Virtual Mailboxes** (do I have a valid mailbox to store the email to?)
|
||||
|
||||
### Virtual Domains
|
||||
|
||||
|
|
@ -128,9 +132,9 @@ _mapping_. Consider it as a _question_ and an _answer_.
|
|||
If the information is **not** available you just get no answer:
|
||||
|
||||
- Question: is `example.net` one of our virtual domains?
|
||||
- Answer: (no answer)
|
||||
- Answer: _(no answer)_
|
||||
|
||||
So Postfix hast to ask MariaDB that question:
|
||||
In SQL language Postfix has to ask MariaDB the question:
|
||||
|
||||
```sql
|
||||
SELECT "yes" FROM virtual_domains WHERE name='example.net'
|
||||
|
|
@ -166,9 +170,9 @@ and _answer_ game goes like this:
|
|||
Postfix would then check if there are further aliases:
|
||||
|
||||
- Question: is there an alias for `john@example.org`?
|
||||
- Answer: (no answer)
|
||||
- Answer: _(no answer)_
|
||||
|
||||
Apparently, there aren't any. So the email will go to `john@example.org`.
|
||||
Apparently, there aren't any more. So the email will go to `john@example.org`.
|
||||
|
||||
Run this code to create the appropriate mapping file:
|
||||
|
||||
|
|
@ -202,8 +206,9 @@ Just the same story as with virtual domains. The database returns `1` if there i
|
|||
that specific email address.
|
||||
|
||||
Actually the _answer_ is usually not `1` but the path to the mailbox on disk. But that applies only if Postfix would be
|
||||
saving the email. In our setup the email will be passed on to Dovecot in a later step. Dovecot will then handle the
|
||||
files on disk. Postfix just has to know is whether a mailbox exists. And that's why any _answer_ is sufficient here.
|
||||
saving the email to disk on its own. In our setup the email will be passed on to Dovecot in a later step. Dovecot will
|
||||
then handle the files on disk. Postfix just has to know is whether a mailbox exists. And that's why **any** _answer_ is
|
||||
sufficient here.
|
||||
|
||||
### Tell Postfix
|
||||
|
||||
|
|
@ -224,7 +229,7 @@ to restart Postfix.
|
|||
The last two lines set the owner of the config files (_user=root, group=postfix_) and make sure that _others_ have no
|
||||
access. After all a database password is found in these files.
|
||||
|
||||
### Test it
|
||||
<Aside icon="approve-check-circle" title="Test it">
|
||||
|
||||
Give the mappings a quick test using the `postmap -q` command (_q_ stands for _query_):
|
||||
|
||||
|
|
@ -243,3 +248,5 @@ john@example.org
|
|||
```
|
||||
|
||||
If you get anything else, please check those three config files. Perhaps the database password is wrong?
|
||||
|
||||
</Aside>
|
||||
|
|
|
|||
|
|
@ -7,197 +7,229 @@ sidebar:
|
|||
---
|
||||
|
||||
import { Aside } from "@astrojs/starlight/components";
|
||||
import StepListReceive from "../../../components/StepListReceive.astro";
|
||||
|
||||
In the previous chapter you have prepared Postfix to receive emails. As shown in the slideshow, Postfix hands incoming
|
||||
emails over to Dovecot which in turn saves it to a mailbox on disk.
|
||||
## About LMTP
|
||||
|
||||
So:
|
||||
<StepListReceive currentStep={3} />
|
||||
|
||||
- DNS is used to find your mail server. First the MX record. Then the A and/or AAAA record.
|
||||
- Postfix receives emails using SMTP (the Simple Mail Transport Protocol)
|
||||
- MariaDB stores information about your domains and mail users
|
||||
- rspamd checks if it is spam
|
||||
- Dovecot saves it to disk
|
||||
As explained in the previous section, Postfix speaks SMTP and receives the email from the internet. Postfix could even
|
||||
save the email to a mailbox on disk. But instead we will use Dovecot for the final delivery. Actually Dovecot's main
|
||||
purpose is to let users fetch their email using the IMAP protocol. But it provides additional features we can use as
|
||||
well.
|
||||
|
||||
## DNS
|
||||
So we need tell Postfix to hand over the incoming email to Dovecot. The communication between Postfix and Dovecot will
|
||||
happen using LMTP – the [local mail transfer protocol](https://en.wikipedia.org/wiki/Local_Mail_Transfer_Protocol).
|
||||
LMTP is a variant of SMTP with fewer features. It is meant for email communication between internal services that trust
|
||||
each other.
|
||||
|
||||
Let's begin with setting up the DNS records. Say that you want to receive emails for the `example.com` domain. If some
|
||||
other mail server on the internet wants to send an email to your server, it looks for two records:
|
||||
In this chapter we will mainly configure Dovecot so that it knows how to deliver incoming emails.
|
||||
|
||||
- **MX record (`example.com`)**
|
||||
The MX (Mail Exchange) record tells other mail servers _where to deliver emails_ for the domain `example.com`.
|
||||
Instead of pointing directly to an IP address, it points to a **hostname** (for example, `smtp.example.com`).
|
||||
MX records also include a **priority value**. If multiple mail servers are listed, the one with the lowest priority
|
||||
number is tried first. Others are used as fallback if the first is unavailable.
|
||||
## Setting up the vmail directory
|
||||
|
||||
- **A / AAAA record (`smtp.example.com`)**
|
||||
The hostname specified in the MX record (`smtp.example.com`) must resolve to an IP address so that other mail servers
|
||||
know how to reach it.
|
||||
- An **A record** maps the hostname to an IPv4 address.
|
||||
- An **AAAA record** maps the hostname to an IPv6 address.
|
||||
On your disk all the mailboxes will live in the directory `/var/vmail`. To separate the mailboxes from the rest of your
|
||||
system, let's create a new user and group that will own the mailboxes:
|
||||
|
||||
Once resolved, the sending mail server connects to that IP address on **TCP port 25**, which is the standard port for
|
||||
email delivery (SMTP).
|
||||
```sh
|
||||
groupadd --system vmail
|
||||
useradd --system --gid vmail vmail
|
||||
mkdir -p /var/vmail
|
||||
chown -R vmail:vmail /var/vmail
|
||||
```
|
||||
|
||||
<Aside>
|
||||
## Dovecot configuration
|
||||
|
||||
If no MX record is found, the remote mail server will instead directly look for an A or AAAA record of `example.com`.
|
||||
The configuration files for Dovecot are found in `/etc/dovecot/conf.d`. All these files are loaded by Dovecot. This is
|
||||
done by this magical line at the end of the `/etc/dovecot/dovecot.conf` file:
|
||||
|
||||
```
|
||||
!include conf.d/*.conf
|
||||
```
|
||||
|
||||
It loads all files in `/etc/dovecot/conf.d/` that end in “.conf” in alphanumerical order. Let's edit a couple of files
|
||||
for our purpose.
|
||||
|
||||
### 10-auth.conf
|
||||
|
||||
The `/etc/dovecot/conf.d/10-auth.conf` file is dealing with authentication. At the end of this file you will find a list
|
||||
of authentication backends that Dovecot ships with. By default it will use system users (those from /etc/passwd). But we
|
||||
want to use the MariaDB database backend so go ahead and change this block to:
|
||||
|
||||
```
|
||||
#!include auth-system.conf.ext
|
||||
!include auth-sql.conf.ext
|
||||
#!include auth-ldap.conf.ext
|
||||
#!include auth-passwdfile.conf.ext
|
||||
#!include auth-checkpassword.conf.ext
|
||||
#!include auth-static.conf.ext
|
||||
```
|
||||
|
||||
Every line starting with `#` is disabled. Only lines starting with `!` are considered.
|
||||
|
||||
### 10-mail.conf
|
||||
|
||||
Edit this file at `/etc/dovecot/conf.d/10-mail.conf`. It contains many settings regarding mailboxes, like where they are
|
||||
located, who owns them and what is their layout? Set this:
|
||||
|
||||
```
|
||||
mail_driver = maildir
|
||||
mail_home = /var/vmail/%{user | domain}/%{user | username}
|
||||
mail_path = ~/Maildir
|
||||
```
|
||||
|
||||
This looks more sophisticated than with versions of Dovecot before 2.4. What it means:
|
||||
|
||||
- **mail_driver**: Mailboxes are stored in
|
||||
[maildir](https://doc.dovecot.org/2.4.1/core/config/mailbox_formats/maildir.html#maildir-mailbox-format) format.
|
||||
- **mail_home**: Every mail user gets their own _home_ directory in `/var/vmail/DOMAIN/USER`. The `user` variable
|
||||
contains the email address like `john@example.org`. The expression `user | domain` splits off the _domain_ part from
|
||||
the email address. Similarly `user | username` takes the part before the `@` sign.
|
||||
- **mail_path**: Within the _home_ directory the actual mailbox will live in a subdirectory called `Maildir`. The reason
|
||||
is that we will store other data in the user's _home_ directory as well that should not conflict with the mailbox
|
||||
directory.
|
||||
|
||||
### 10-master.conf
|
||||
|
||||
This configuration file at `/etc/dovecot/conf.d/10-master.conf` deals with Dovecot's services.
|
||||
|
||||
So most settings are sane here and do not have to be changed. However one change is required in the “service auth”
|
||||
section because we want Postfix to allow Dovecot as an authentication service. Make it look like this:
|
||||
|
||||
```
|
||||
# Postfix smtp-auth
|
||||
unix_listener /var/spool/postfix/private/auth {
|
||||
mode = 0660
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
```
|
||||
|
||||
That way Dovecot will put a communication socket into `/var/spool/postfix/private/auth`. It will allow Postfix to
|
||||
authenticate your users which is relevant later when we set up _relaying_.
|
||||
|
||||
### 10-ssl.conf
|
||||
|
||||
TODO: verify
|
||||
|
||||
Earlier in this guide you created both a key and a certificate file to encrypt the communication with POP3, IMAPs and
|
||||
HTTPS between the users and your mail server. You need to tell Dovecot where to find these files:
|
||||
|
||||
```
|
||||
ssl\_cert = \</etc/letsencrypt/live/webmail.example.org/fullchain.pem
|
||||
ssl\_key = \</etc/letsencrypt/live/webmail.example.org/privkey.pem
|
||||
```
|
||||
|
||||
And enforce TLS encryption by setting:
|
||||
|
||||
```
|
||||
ssl = required
|
||||
```
|
||||
|
||||
See the [Dovecot documentation on SSL encryption](https://doc.dovecot.org/admin_manual/ssl/) for more information.
|
||||
|
||||
Next let’s take a look at how Dovecot knows about users and their passwords:
|
||||
|
||||
### auth-sql.conf.ext
|
||||
|
||||
Dovecot reads the `auth-sql.conf.ext` which defines how to find user information in your database. Open the file. There
|
||||
are two sections:
|
||||
|
||||
- userdb: where to find a user’s mailbox in the file system
|
||||
- passdb: where to find the user’s hashed password
|
||||
|
||||
By default Dovecot will run two queries at your database. One for the _userdb_ that gets information like the user ID,
|
||||
group ID, home directory and quota. And another for the _passdb_ that gets the hashed password.
|
||||
|
||||
The “userdb” section already reads:
|
||||
|
||||
```
|
||||
userdb {
|
||||
driver = sql
|
||||
args = /etc/dovecot/dovecot-sql.conf.ext
|
||||
}
|
||||
```
|
||||
|
||||
As you can see Dovecot uses an SQL database lookup to get that information. And it refers to the dovecot-sql.conf.ext
|
||||
file for more information. Let’s see…
|
||||
|
||||
## /etc/dovecot/dovecot-sql.conf.ext
|
||||
|
||||
(This configuration file is one level up and not in “conf.d”.)
|
||||
|
||||
You will find this file well documented although all configuration directives are commented out. Add these lines at the
|
||||
bottom of the file:
|
||||
|
||||
```
|
||||
driver = mysql
|
||||
|
||||
connect = \
|
||||
host=127.0.0.1 \
|
||||
dbname=mailserver \
|
||||
user=mailserver \
|
||||
password=x893dNj4stkHy1MKQq0USWBaX4ZZdq
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
<Aside type="tip" title="Backslashes">
|
||||
Ending a line with a backslash (\) means that it is continued on the next line. It keeps the configuration more
|
||||
readable when it is split over multiple lines.
|
||||
</Aside>
|
||||
|
||||
<Aside type="caution">
|
||||
What these lines mean:
|
||||
|
||||
The MX record must point to a hostname. It **must not** point to an IP address directly.
|
||||
- driver: the kind of database. MariaDB is the same kind as MySQL.
|
||||
- connect: where to find the MySQL database and how to access it (username, password)
|
||||
- user_query: an SQL query that returns the user name (=the email address), the quota, the home directory, user ID and
|
||||
group ID.
|
||||
- password_query: this SQL query just gets the password hash from the database
|
||||
- iterate_query: ‘doveadm’ uses this query to get a list of all users. That allows you to use the “doveadm user ‘\*'”
|
||||
command later.
|
||||
|
||||
</Aside>
|
||||
The _user_query_ gets several pieces of information from the database. Let’s look at it one by one:
|
||||
|
||||
In zone syntax you would create something like:
|
||||
- email AS user
|
||||
It gets the the _email_ field from the database which corresponds to the user name. Dovecot expects it in the _user_
|
||||
field so we set an alias to _“user”._
|
||||
- userdb_quota_rule
|
||||
This is the user’s quota in bytes. Think of it as the maximum possible space on disk that the user can occupy. As
|
||||
[documented](https://doc.dovecot.org/configuration_manual/quota/#per-user-quota) Dovecot expects the quota in a
|
||||
special format like “\*:bytes=10000” if the user should not be able to store more than 10,000 bytes. That’s why we
|
||||
begin the string with ‘\*:bytes=’.
|
||||
- userdb_home
|
||||
This leads to the directory where all emails and various control files for this user are located. The placeholder
|
||||
‘%d’ replaces the domain and ‘%n’ the user part. So for John that makes it “/var/vmail/example.org/john”.
|
||||
- userdb*uid and userdb_gid
|
||||
Those are the user ID and group ID of \_vmail* user – 5000 for both. Dovecot uses it to set the permissions of files
|
||||
it creates. As all users share the same system user “vmail” this is just a static number.s
|
||||
|
||||
## Fix permissions
|
||||
|
||||
Make sure that only root can access the SQL configuration file so nobody else is reading your database access passwords:
|
||||
|
||||
```
|
||||
@ IN MX 10 smtp
|
||||
smtp IN A 100.64.17.3
|
||||
smtp IN A fd7a:115c:a1e0::17
|
||||
chown root:root /etc/dovecot/dovecot-sql.conf.ext
|
||||
chmod go= /etc/dovecot/dovecot-sql.conf.ext
|
||||
```
|
||||
|
||||
If possible, also add a PTR record that makes the IP addresses point back to the name `smtp.example.com`. Other mail
|
||||
servers may be more likely so flag your sent emails as spam if you do not have a PTR record. Preferable the forward
|
||||
(A/AAAA) and reverse (PTR) records match.
|
||||
|
||||
## Postfix
|
||||
|
||||
Now that other mail servers can locate your mail server, the next step is to make sure it can receive and process
|
||||
incoming emails. This task is handled by Postfix, which communicates using the SMTP protocol.
|
||||
|
||||
Postfix knows if an email address is valid by checking for…
|
||||
|
||||
- Virtual Domains (am I responsible for this domain?)
|
||||
- Virtual Aliases (do I have to redirect this address to another address?)
|
||||
- Virtual Mailboxes (do I have a valid mailbox to store the email to?)
|
||||
|
||||
### Virtual Domains
|
||||
|
||||
There are different ways for Postfix to get that information. From text files, LDAP, MongoDB or PostgreSQL. Or via
|
||||
MariaDB – which is what we will use. But whatever kind of data source you configure, Postfix needs the information as a
|
||||
_mapping_. Consider it as a _question_ and an _answer_.
|
||||
|
||||
- Question: is `example.org` one of our virtual domains?
|
||||
- Answer: yes
|
||||
|
||||
If the information is **not** available you just get no answer:
|
||||
|
||||
- Question: is `example.net` one of our virtual domains?
|
||||
- Answer: (no answer)
|
||||
|
||||
So Postfix hast to ask MariaDB that question:
|
||||
|
||||
```sql
|
||||
SELECT "yes" FROM virtual_domains WHERE name='example.net'
|
||||
```
|
||||
|
||||
Run the following code in your shell to create a configuration file creating that mapping:
|
||||
|
||||
```sh
|
||||
cat > /etc/postfix/virtual-mailbox-domains.cf << EOF
|
||||
user = mailserver
|
||||
password = SECOND-PASSWORD-HERE
|
||||
hosts = 127.0.0.1
|
||||
dbname = mailserver
|
||||
query = SELECT 1 FROM virtual_domains WHERE name='%s'
|
||||
EOF
|
||||
```
|
||||
|
||||
This has created a configuration file called `/etc/postfix/virtual-mailbox-domains.cf`. The `user`, `password`, `hosts`
|
||||
and `dbname` definitions tell Postfix how to connect to the database. And the `query` asks the _question_ if a certain
|
||||
domain is present in the database table `virtual_domains`. Using `SELECT 1` we return just the number 1 if such an entry
|
||||
was found. In fact it does not matter what we return. Any _answer_ is fine.
|
||||
|
||||
Don't forget to replace the `SECOND-PASSWORD-HERE` by your own password for the `mailserver` user.
|
||||
|
||||
### Virtual Aliases
|
||||
|
||||
Now let's deal with aliases. As shown earlier an alias redirects an email to another email address. So the _question_
|
||||
and _answer_ game goes like this:
|
||||
|
||||
- Question: is there an alias for `jack@example.org`?
|
||||
- Answer: `john@example.org`
|
||||
|
||||
Postfix would then check if there are further aliases:
|
||||
|
||||
- Question: is there an alias for `john@example.org`?
|
||||
- Answer: (no answer)
|
||||
|
||||
Apparently, there aren't any. So the email will go to `john@example.org`.
|
||||
|
||||
Run this code to create the appropriate mapping file:
|
||||
|
||||
```sh
|
||||
cat > /etc/postfix/virtual-alias-maps.cf << EOF
|
||||
user = mailserver
|
||||
password = SECOND-PASSWORD-HERE
|
||||
hosts = 127.0.0.1
|
||||
dbname = mailserver
|
||||
query = SELECT destination FROM virtual_aliases WHERE source='%s'
|
||||
EOF
|
||||
```
|
||||
|
||||
The query now gets all the `destination` email addresses from the database for a certain `source` email address.
|
||||
|
||||
### Virtual Mailboxes
|
||||
|
||||
The last mapping we need is a query to find valid mailboxes. Without further ado:
|
||||
|
||||
```sh
|
||||
cat > /etc/postfix/virtual-mailbox-maps.cf << EOF
|
||||
user = mailserver
|
||||
password = SECOND-PASSWORD-HERE
|
||||
hosts = 127.0.0.1
|
||||
dbname = mailserver
|
||||
query = SELECT 1 FROM virtual_mailboxes WHERE email='%s'
|
||||
EOF
|
||||
```
|
||||
|
||||
Just the same story as with virtual domains. The database returns `1` if there is a `virtual_mailboxes` record matching
|
||||
that specific email address.
|
||||
|
||||
Actually the _answer_ is usually not `1` but the path to the mailbox on disk. But that applies only if Postfix would be
|
||||
saving the email. In our setup the email will be passed on to Dovecot in a later step. Dovecot will then handle the
|
||||
files on disk. Postfix just has to know is whether a mailbox exists. And that's why any _answer_ is sufficient here.
|
||||
|
||||
### Tell Postfix
|
||||
|
||||
You have created the config files for the three mappings. Now you just need to tell Postfix to use them:
|
||||
|
||||
```sh
|
||||
postconf virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
|
||||
postconf virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
|
||||
postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf
|
||||
|
||||
chown root:postfix /etc/postfix/*.cf
|
||||
chmod o= /etc/postfix/*.cf
|
||||
```
|
||||
|
||||
`postconf` is a command that changes configuration in `/etc/postfix/main.cf` and applies them instantly. You don't have
|
||||
to restart Postfix.
|
||||
|
||||
The last two lines set the owner of the config files (_user=root, group=postfix_) and make sure that _others_ have no
|
||||
access. After all a database password is found in these files.
|
||||
|
||||
### Test it
|
||||
|
||||
Give the mappings a quick test using the `postmap -q` command (_q_ stands for _query_):
|
||||
|
||||
```sh
|
||||
postmap -q example.org mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
|
||||
postmap -q jack@example.org mysql:/etc/postfix/mysql-virtual-alias-maps.cf
|
||||
postmap -q john@example.org mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
|
||||
```
|
||||
|
||||
These three commands use the three mapping files (`*.cf`) to query your three database tables. The result should show:
|
||||
Restart Dovecot from the shell:
|
||||
|
||||
```
|
||||
1
|
||||
john@example.org
|
||||
1
|
||||
systemctl restart dovecot
|
||||
```
|
||||
|
||||
If you get anything else, please check those three config files. Perhaps the database password is wrong?
|
||||
Look at your /var/log/mail.log logfile. You should see:
|
||||
|
||||
```
|
||||
... Dovecot v2.3.13 (f79e8e7e4) starting up for imap, lmtp, sieve, pop3 (core dumps disabled)
|
||||
```
|
||||
|
||||
If you get any error messages please double-check your configuration files.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue