add title to verbatim blocks. finish relaying page

This commit is contained in:
Christoph Haas 2025-09-10 23:32:18 +02:00
parent 68fbbb5890
commit 3cb34170c7
9 changed files with 194 additions and 237 deletions

View file

@ -10,7 +10,7 @@ import { Aside } from "@astrojs/starlight/components";
Let's install the necessary Debian packages. Run this command as root on your server as _root_: Let's install the necessary Debian packages. Run this command as root on your server as _root_:
```sh ```sh title="Run this on your server"
apt update apt update
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \
apt -y install postfix-mysql dovecot-mysql \ apt -y install postfix-mysql dovecot-mysql \

View file

@ -30,7 +30,7 @@ youre actually using MariaDB.
This step is simple. Connect to the database using the `mariadb` command in your shell: This step is simple. Connect to the database using the `mariadb` command in your shell:
```sh ```sh title="Run this on your server"
mariadb mariadb
``` ```

View file

@ -29,6 +29,7 @@ records:
- **A / AAAA record (`smtp.example.com`)** - **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 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. know how to reach it.
- An **A record** maps the hostname to an IPv4 address. - An **A record** maps the hostname to an IPv4 address.
- An **AAAA record** maps the hostname to an IPv6 address. - An **AAAA record** maps the hostname to an IPv6 address.
@ -63,14 +64,14 @@ servers may be more likely so flag your sent emails as spam if you do not have a
Use the `host` command to check your domain's MX record. Use the `host` command to check your domain's MX record.
```sh ```sh title="Run this on your server"
$> host -t mx example.com $> host -t mx example.com
example.com mail is handled by 10 smtp.example.com. example.com mail is handled by 10 smtp.example.com.
``` ```
Then check if that result resolves to an IP address: Then check if that result resolves to an IP address:
```sh ```sh title="Run this on your server"
$> host smtp.example.com $> host smtp.example.com
smtp.example.com has address 49.13.89.249 smtp.example.com has address 49.13.89.249
smtp.example.com has IPv6 address 2a01:4f8:c012:62d1::1 smtp.example.com has IPv6 address 2a01:4f8:c012:62d1::1
@ -78,7 +79,7 @@ smtp.example.com has IPv6 address 2a01:4f8:c012:62d1::1
Preferably the IP addresses should resolve back to the mail server's name: Preferably the IP addresses should resolve back to the mail server's name:
```sh ```sh title="Run this on your server"
$> host 49.13.89.249 $> host 49.13.89.249
249.89.13.49.in-addr.arpa domain name pointer smtp.example.com. 249.89.13.49.in-addr.arpa domain name pointer smtp.example.com.
$> host 2a01:4f8:c012:62d1::1 $> host 2a01:4f8:c012:62d1::1

View file

@ -40,7 +40,7 @@ Please replace `mail.example.org` by the FQDN you chose.
</Aside> </Aside>
```sh ```sh title="Run this on your server"
certbot --apache --register-unsafely-without-email --agree-tos -d mail.example.org certbot --apache --register-unsafely-without-email --agree-tos -d mail.example.org
``` ```
@ -108,7 +108,7 @@ checks your certificate frequently and triggers a renewal one month before it ex
These commands will show you the definition and status of the timer and the renewal service: These commands will show you the definition and status of the timer and the renewal service:
```sh ```sh title="Run this on your server"
systemctl status certbot.timer systemctl status certbot.timer
systemctl cat certbot.timer systemctl cat certbot.timer
systemctl status certbot.service systemctl status certbot.service
@ -121,7 +121,7 @@ look cooler.
It is important that services that use the certificate load the new certificate after an automatic renewal. This shell 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: command will create a file `/etc/letsencrypt/cli.ini` that handles it:
```sh ```sh title="Run this on your server"
echo > /etc/letsencrypt/cli.ini "post-hook = systemctl reload postfix dovecot apache2" echo > /etc/letsencrypt/cli.ini "post-hook = systemctl reload postfix dovecot apache2"
``` ```

View file

@ -47,7 +47,7 @@ SELECT "yes" FROM virtual_domains WHERE name='example.net'
Run the following code in your shell to create a configuration file creating that mapping: Run the following code in your shell to create a configuration file creating that mapping:
```sh ```sh title="Run this on your server"
cat > /etc/postfix/mariadb-virtual-mailbox-domains.cf << EOF cat > /etc/postfix/mariadb-virtual-mailbox-domains.cf << EOF
user = mailserver user = mailserver
password = MAILSERVER-PASSWORD-HERE password = MAILSERVER-PASSWORD-HERE
@ -81,7 +81,7 @@ Apparently, there aren't any more. So the email will go to `john@example.org`.
Run this code to create the appropriate mapping file: Run this code to create the appropriate mapping file:
```sh ```sh title="Run this on your server"
cat > /etc/postfix/mariadb-virtual-alias-maps.cf << EOF cat > /etc/postfix/mariadb-virtual-alias-maps.cf << EOF
user = mailserver user = mailserver
password = MAILSERVER-PASSWORD-HERE password = MAILSERVER-PASSWORD-HERE
@ -97,7 +97,7 @@ The query now gets all the `destination` email addresses from the database for a
The last mapping we need is a query to find valid mailboxes. Without further ado: The last mapping we need is a query to find valid mailboxes. Without further ado:
```sh ```sh title="Run this on your server"
cat > /etc/postfix/mariadb-virtual-mailbox-maps.cf << EOF cat > /etc/postfix/mariadb-virtual-mailbox-maps.cf << EOF
user = mailserver user = mailserver
password = MAILSERVER-PASSWORD-HERE password = MAILSERVER-PASSWORD-HERE
@ -119,7 +119,7 @@ sufficient here.
You have created the config files for the three mappings. Now you just need to tell Postfix to use them: You have created the config files for the three mappings. Now you just need to tell Postfix to use them:
```sh ```sh title="Run this on your server"
postconf virtual_mailbox_domains=mysql:/etc/postfix/mariadb-virtual-mailbox-domains.cf postconf virtual_mailbox_domains=mysql:/etc/postfix/mariadb-virtual-mailbox-domains.cf
postconf virtual_mailbox_maps=mysql:/etc/postfix/mariadb-virtual-mailbox-maps.cf postconf virtual_mailbox_maps=mysql:/etc/postfix/mariadb-virtual-mailbox-maps.cf
postconf virtual_alias_maps=mysql:/etc/postfix/mariadb-virtual-alias-maps.cf postconf virtual_alias_maps=mysql:/etc/postfix/mariadb-virtual-alias-maps.cf
@ -138,7 +138,7 @@ access. After all a database password is found in these files.
Give the mappings a quick test using the `postmap -q` command (_q_ stands for _query_): Give the mappings a quick test using the `postmap -q` command (_q_ stands for _query_):
```sh ```sh title="Run this on your server"
postmap -q example.org mysql:/etc/postfix/mariadb-virtual-mailbox-domains.cf postmap -q example.org mysql:/etc/postfix/mariadb-virtual-mailbox-domains.cf
postmap -q jack@example.org mysql:/etc/postfix/mariadb-virtual-alias-maps.cf postmap -q jack@example.org mysql:/etc/postfix/mariadb-virtual-alias-maps.cf
postmap -q john@example.org mysql:/etc/postfix/mariadb-virtual-mailbox-maps.cf postmap -q john@example.org mysql:/etc/postfix/mariadb-virtual-mailbox-maps.cf

View file

@ -18,7 +18,7 @@ In this chapter we will mainly configure Dovecot so that it knows how to deliver
On your disk all the mailboxes will live in the directory `/var/vmail`. To separate the mailboxes from the rest of your 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: system, let's create a new user and group that will own the mailboxes:
```sh ```sh title="Run this on your server"
groupadd --system vmail groupadd --system vmail
useradd --system --gid vmail vmail useradd --system --gid vmail vmail
mkdir -p /var/vmail mkdir -p /var/vmail
@ -161,7 +161,7 @@ a fixed schema for the home directory (`/var/vmail/DOMAIN/USER`) and the user-ID
Make sure that only root can access the SQL configuration file so nobody else is reading your database access passwords: Make sure that only root can access the SQL configuration file so nobody else is reading your database access passwords:
```sh ```sh title="Run this on your server"
chown root:root /etc/dovecot/conf.d/auth-sql.conf.ext chown root:root /etc/dovecot/conf.d/auth-sql.conf.ext
chmod go= /etc/dovecot/conf.d/auth-sql.conf.ext chmod go= /etc/dovecot/conf.d/auth-sql.conf.ext
``` ```
@ -170,13 +170,13 @@ chmod go= /etc/dovecot/conf.d/auth-sql.conf.ext
Restart Dovecot from the shell: Restart Dovecot from the shell:
```sh ```sh title="Run this on your server"
systemctl restart dovecot systemctl restart dovecot
``` ```
Check your logs: Check your logs:
```sh ```sh title="Run this on your server"
journalctl -fu dovecot journalctl -fu dovecot
``` ```
@ -196,20 +196,20 @@ However in the age of _systemd_ the output of the processes is written to system
To read the complete logs of Dovecot, run (u=unit): To read the complete logs of Dovecot, run (u=unit):
```sh ```sh title="Run this on your server"
journalctl -u dovecot journalctl -u dovecot
``` ```
To `tail -f` the logs (f=follow): To `tail -f` the logs (f=follow):
```sh ```sh title="Run this on your server"
journalctl -fu dovecot journalctl -fu dovecot
``` ```
However on a busy server it will show the entire journal and then follow any changes. You may want to limit the output However on a busy server it will show the entire journal and then follow any changes. You may want to limit the output
to the last 10 lines: to the last 10 lines:
```sh ```sh title="Run this on your server"
journalctl -n 10 -fu dovecot journalctl -n 10 -fu dovecot
``` ```
@ -221,7 +221,7 @@ If you prefer the old way of having log files, just install the `rsyslog` packag
To test the `userdb` query: To test the `userdb` query:
```sh ```sh title="Run this on your server"
doveadm user john@example.org doveadm user john@example.org
``` ```
@ -237,7 +237,7 @@ mail_path /var/vmail/example.org/john/Maildir
To test the `iterate` query that gets a list of all users: To test the `iterate` query that gets a list of all users:
```sh ```sh title="Run this on your server"
doveadm user '*' doveadm user '*'
``` ```

View file

@ -54,7 +54,7 @@ The “_virtual_transport_" in Postfix defines the service to use for delivering
created a socket file and is ready to listen to incoming LMTP connections. We just need to tell Postfix to send emails created a socket file and is ready to listen to incoming LMTP connections. We just need to tell Postfix to send emails
there. So please run: there. So please run:
```sh ```sh title="Run this on your server"
postconf virtual_transport=lmtp:unix:private/dovecot-lmtp postconf virtual_transport=lmtp:unix:private/dovecot-lmtp
``` ```
@ -73,7 +73,7 @@ TODO: move to optional chapter
Finally we are ready for our first email. Using the `swaks` command you can create a test email to `john@example.org` Finally we are ready for our first email. Using the `swaks` command you can create a test email to `john@example.org`
and send it to Postfix: and send it to Postfix:
```sh ```sh title="Run this on your server"
swaks --server localhost --to john@example.org swaks --server localhost --to john@example.org
``` ```
@ -122,7 +122,7 @@ If you see a `250 2.0.0 Ok: queued as…" near the end of this dialog, then Post
Check if Postfix managed to pass the email to Dovecot: Check if Postfix managed to pass the email to Dovecot:
```sh ```sh title="Run this on your server"
journalctl -n 10 -u dovecot journalctl -n 10 -u dovecot
``` ```
@ -134,7 +134,7 @@ Aug 24 21:36:22 mail dovecot[23214]: lmtp(john@example.org)<…><…>: msgid=<20
Does it say `saved mail to INBOX`? Awesome. The email should be visible in John's home directory. Check it: Does it say `saved mail to INBOX`? Awesome. The email should be visible in John's home directory. Check it:
```sh ```sh title="Run this on your server"
find /var/vmail/example.org/john find /var/vmail/example.org/john
``` ```

View file

@ -143,7 +143,7 @@ $config['plugins'] = array(
Plugins are configured through files located in the `/etc/roundcube/plugins` directory. Let's quickly set up the Plugins are configured through files located in the `/etc/roundcube/plugins` directory. Let's quickly set up the
password plugin: password plugin:
```sh ```sh title="Run this on your server"
cat > /etc/roundcube/plugins/password/config.inc.php << 'EOF' cat > /etc/roundcube/plugins/password/config.inc.php << 'EOF'
<?php <?php
$config['password_driver'] = 'sql'; $config['password_driver'] = 'sql';

View file

@ -10,33 +10,40 @@ import { Aside } from "@astrojs/starlight/components";
Your mail server is almost ready for use. But one puzzle piece is missing. Your users can receive emails but they cannot Your mail server is almost ready for use. But one puzzle piece is missing. Your users can receive emails but they cannot
send them yet. This part of the guide is a bit longer than others because forwarding emails has security implications. send them yet. This part of the guide is a bit longer than others because forwarding emails has security implications.
This is important to understand so don't skip it.
Let's begin with the difference of how **users** send emails versus how **mail servers** send emails. For comparison: Let's begin with the difference of how **users** send emails versus how **mail servers** send emails.
- A **mail server** fetches the _MX_ record for the destination domain of the recipients email address from DNS. That - A **mail server** fetches the _MX_ record for the destination domain of the recipients email address from DNS. That
tells it which mail server to talk to. Then it opens an SMTP connection (TCP port 25) and sends the email. tells it which mail server to talk to. Then it opens an SMTP connection (TCP port 25) and sends the email. A mail
- An **end user** with a mail client such as Thunderbird or Roundcube does not work that way. Mail clients do not server will not relay an email. It will only accept it for its own users.
include any functionality for fetching MX records. In addition, the user is usually on a dynamic IP address that other - An **end user** with a mail client such as Thunderbird or Roundcube cannot do that. Mail clients do not include any
mail servers will not trust and will likely reject. Instead, end users are expected to send their emails through their functionality for fetching MX records. In addition, the user is usually on a dynamic IP address that other mail
servers will not trust and will likely reject. Instead, end users are expected to send their emails through their
providers (your) mail server: they authenticate with their login credentials and then send the message. This process providers (your) mail server: they authenticate with their login credentials and then send the message. This process
is called relaying, because your mail server acts as an intermediary between the user and other mail servers on the is called relaying, because your mail server acts as an intermediary between the user and other mail servers on the
internet. For security reasons, authentication is required before the user is allowed to send emails. Users are also internet. For security reasons, authentication is required before the user is allowed to send emails. Users are also
expected to use the TCP port 587 which is called the _submission_ port. More on that later. expected to use the TCP port 587 which is called the _submission_ port. More on that later.
Postfix must only accept an email from a user, if…
- The user uses the **submission** port (and not the standard **SMTP** port)
- The user **authenticates** with the username (in our setup: the email address) and the password
- The connection is **encrypted** so that the password can be safely transmitted
- The **sender address** of the email matches the user's account
## SMTP versus Submission ## SMTP versus Submission
Let's compare those services and ports side-by-side: Let's compare those services and ports side-by-side:
| Service name | SMTP | Submission | Submissions | | Service name | SMTP | Submission | SubmissionS (S=secure) |
| -------------------- | --------------------------- | ---------------------------- | --------------------- | | -------------------- | --------------------------- | ---------------------------- | ---------------------- |
| TCP Port | 25 | 587 | 465 | | TCP Port | 25 | 587 | 465 |
| Meant for | Other mail servers | Our users | Our users |
| Used by | Mail transport agent (MTA) | Mail user agent (MUA) | Mail user agent (MUA) |
| Transport encryption | **Off**. STARTTLS optional. | **Off**. STARTTLS mandatory. | TLS all the time | | Transport encryption | **Off**. STARTTLS optional. | **Off**. STARTTLS mandatory. | TLS all the time |
| Authentication | Optional [^1] | Mandatory | Mandatory | | Authentication | Optional [^1] | Mandatory | Mandatory |
| Accepted sender | Anyone | Our own users | Our own users | | Accepted sender | Anyone | Our own users | Our own users |
| Accepted receivers | Our own users | Anyone | Anyone | | Accepted receivers | Our own users | Anyone | Anyone |
| Meant for | Servers | Our users | Our users |
| Used by | Mail transport agent (MTA) | Mail user agent (MUA) | Mail user agent (MUA) |
| Your home ISP | may block this port | will allow this port | will allow this port | | Your home ISP | may block this port | will allow this port | will allow this port |
[^1]: [^1]:
@ -54,61 +61,79 @@ communication.
## Configure the submission[s] services ## Configure the submission[s] services
All Postfix services are declared in the `/etc/postfix/master.cf` file. Most of them are used by Postfix internally, All Postfix services are declared in the `/etc/postfix/master.cf` file. Most of them are used by Postfix internally,
like `pickup`, `cleanup`, `rewrite` or `bounce`. Let's add a new service for `submission` by running: like `pickup`, `cleanup`, `rewrite` or `bounce`. But `submission` is not configured by default. Let's fix that.
```sh ```sh title="Run this on your server" title="Run this on your server"
postconf -M submission/inet="submission inet n - y - - smtpd" postconf -M submission/inet="submission inet n - y - - smtpd"
postconf -P "submission/inet/syslog_name=postfix/submission" postconf -P "submission/inet/syslog_name=postfix/submission"
postconf -P "submission/inet/smtpd_tls_security_level=encrypt" postconf -P "submission/inet/smtpd_tls_security_level=encrypt"
postconf -P "submission/inet/smtpd_sasl_auth_enable=yes" postconf -P "submission/inet/smtpd_sasl_auth_enable=yes"
postconf -P "submission/inet/smtpd_sasl_type=dovecot"
postconf -P "submission/inet/smtpd_sasl_path=private/auth"
postconf -P "submission/inet/smtpd_recipient_restrictions=permit_sasl_authenticated,reject" postconf -P "submission/inet/smtpd_recipient_restrictions=permit_sasl_authenticated,reject"
postconf -P "submission/inet/smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject" postconf -P "submission/inet/smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject"
postfix reload
``` ```
That looked confusing, right? What it did was define the `submission` service in the `/etc/postfix/master.cf` service These commands added a `submission` service to the `/etc/postfix/master.cf` file. You could have achieved the same thing
for you using the `postconf` command. You could have achieved the same thing by editing that file and adding this by editing that file and adding the following section, but I wanted to make it simpler for you to just copy/paste the
section: commands into your shell.
``` ```
submission inet n - y - - smtpd submission inet n - y - - smtpd
-o syslog_name=postfix/submission -o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt -o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes -o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
``` ```
Some notes on that: Some notes on that:
- Lines with options (`-o`) need to be indented so that Postfix understands that they belong to the same service. - Lines with **options** (`-o`) need to be indented so that Postfix understands that they belong to the same service.
- The word `submission` refers to the definition of the service in the `/etc/services` file. So it translates to TCP - The word **submission** refers to the definition of the service in the `/etc/services` file. So it translates to TCP
port 587. port 587.
- The `smtpd` at the end tells Postfix that connections should be handled as incoming SMTP connections. - The **smtpd** in the last column of the first row tells Postfix that connections should be handled as incoming SMTP
connections.
- **syslog_name** is adding `postfix/submission` to log files so that you can distinguish it from other connections. - **syslog_name** is adding `postfix/submission` to log files so that you can distinguish it from other connections.
- **smtpd_tls_security_level** enabled and enforces the use of STARTTLS. The connection starts without encryption but
the remote party is required to send the `STARTTLS` command to switch to an encrypted connection.
- **smtpd_sasl_auth_enable** enables authentication on this port. SASL is short for _Simple Authentication and Security - **smtpd_sasl_auth_enable** enables authentication on this port. SASL is short for _Simple Authentication and Security
Layer_. Layer_. That way only our own users can use this service.
- **smtpd_sasl_type** tells Postfix to use Dovecot's service to authentication a uaser.
- **smtpd_sasl_path** tells Postfix that it should talk to Dovecot through the socket file
`/var/spool/postfix/private/auth`. Earlier in this guide you told Dovecot to create that socket at that location.
- **smtpd_recipient_restrictions** define that only authenticated connections are permitted. Without a username and a - **smtpd_recipient_restrictions** define that only authenticated connections are permitted. Without a username and a
password your connection will be rejected. The **recipient** keyword tells us that this restricts who the recipient password your connection will be rejected. The **recipient** keyword tells us that this restricts who the recipient
can be. can be.
- **smtpd_sender_restrictions** deals with the **sender**. I will explain that in the next section. - **smtpd_sender_restrictions** restricts who the **sender** may be. I will explain that
[later](#protecting-against-forged-sender-addresses).
While you are at it, create the `submissions` (with an "s" at the end) service as well: While you are at it, create the `submissions` (with an "s" at the end) service as well:
```sh ```sh title="Run this on your server"
postconf -M submissions/inet="submissions inet n - y - - smtpd" postconf -M submissions/inet="submissions inet n - y - - smtpd"
postconf -P "submissions/inet/syslog_name=postfix/submissions" postconf -P "submissions/inet/syslog_name=postfix/submissions"
postconf -P "submissions/inet/smtpd_tls_wrappermode=yes" postconf -P "submissions/inet/smtpd_tls_wrappermode=yes"
postconf -P "submissions/inet/smtpd_sasl_auth_enable=yes" postconf -P "submissions/inet/smtpd_sasl_auth_enable=yes"
postconf -P "submissions/inet/smtpd_sasl_type=dovecot"
postconf -P "submissions/inet/smtpd_sasl_path=private/auth"
postconf -P "submissions/inet/smtpd_recipient_restrictions=permit_sasl_authenticated,reject" postconf -P "submissions/inet/smtpd_recipient_restrictions=permit_sasl_authenticated,reject"
postconf -P "submissions/inet/smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject" postconf -P "submissions/inet/smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject"
postfix reload
``` ```
The only difference to the `submission` (without the "s" at the end) is the `smtpd_tls_wrappermode` that switches on TLS The only difference to the `submission` (without the "s" at the end) is the `smtpd_tls_wrappermode` that switches on TLS
encryption right from the start of a connection. encryption right from the start of a connection.
## Enable encryption ## Set up encryption
We certainly do not want our users to send their password over the wire without encryption. So the following commands Whenever possible we should encrypt our traffic. For our users' passwords we make it mandatory. But even for
enable encryption, set the key and certificate paths for Postfix. Just run these commands server-to-server connections we should encourage it. Do not confuse it with end-to-end encryption of emails. That
required using PGP or S/MIME. What we are dealing here is _transport encryption_. It encrypts the communication between
a peer server and your mail server. What the peer server does with our email is beyond our control.
<Aside type="danger" title="Important"> <Aside type="danger" title="Important">
@ -116,7 +141,7 @@ Please replace `mail.example.org` by the FQDN you chose.
</Aside> </Aside>
```sh ```sh title="Run this on your server"
postconf smtp_tls_security_level=may postconf smtp_tls_security_level=may
postconf smtpd_tls_security_level=may postconf smtpd_tls_security_level=may
postconf smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.org/fullchain.pem postconf smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.org/fullchain.pem
@ -143,153 +168,12 @@ be set for the `submission` service to enable authentication there.[^1]
either from a remote mail server or one of your users. either from a remote mail server or one of your users.
</Aside> </Aside>
## Relaying
Feel free to send a test mail using the `submission` port and encryption (using STARTTLS):
```
swaks --server localhost:587 \
--from john@example.org \
--to lisa@example.com \
-tls \
--auth-user john@example.org \
--auth-password summersun
```
Also give the `submissions` service a try:
```
swaks --server localhost:465 \
--from john@example.org \
--to lisa@example.com \
--tls-on-connect \
--auth-user john@example.org \
--auth-password summersun
```
Did you notice the difference? This time we used port **465** and instead of the `-tls` option (which uses STARTTLS to
switch to encryption) we use `--tls-on-connect` that uses TLS right from from the start.
TODO: try sending from roundcube
Put simply, a mail server will accept an email if…
- it is responsible for handling mail for the recipients domain (the domain is listed as a virtual domain), or
- the sender has authenticated successfully with a valid username and password.
I have created two illustrations to explain it.
### Without authentication = denied
`john@example.org` is on the internet somewhere and wants to send an email to `lisa@example.com` (which is another
domain). Your mail server is not responsible for the _example.com_ domain so it receives Johns email and would have to
forward (relay) it to the actual mail server that is responsible for …@example.com email addresses. This may seem like a
harmless scenario but your mail server must deny that:
![Diagram that shows an outgoing unauthenticated email getting rejected](images/relaying-unauthenticated-relaying-denied.png)
Why? Because anyone can claim to be John and make your mail server forward mail. If an attacker (like a spammer) would
send millions of spam emails in Johns name through your server then other organisations will accuse *you* as the
operator of the mail server of spamming. Your mail server would be what people call an
_[open relay](https://en.wikipedia.org/wiki/Open_mail_relay)_. That is not what you want because your mail server would
get blacklisted and you will not be able to send out mail to most other servers. So without any proof that the sender is
indeed John, your server must reject the email.
Try for yourself:
```sh
swaks --server localhost:587 \
--from john@example.org \
--to list@example.com \
-tls
```
TODO: submissions does not work yet
Towards the end of the output you will find:
```
~> MAIL FROM:<john@example.org>
<~ 250 2.1.0 Ok
~> RCPT TO:<list@example.com>
<~* 554 5.7.1 <list@example.com>: Recipient address rejected: Access denied
```
### With authentication = accepted
So how does John prove his identity? He needs to use _authenticated_ SMTP. This is similar to the previous case but
Johns email program will also send his username and password.
![Diagram that shows an outgoing authenticated email getting relayed](images/relaying-authenticated-relaying.png)
We are making sure that his authentication happens over an encrypted connection so Johns password is safe.
Try for yourself and provide a valid user name and password:
```sh
swaks --server localhost:587 \
--from john@example.org \
--to list@example.com \
-tls \
--auth-user john@example.org \
--auth-password summersun
```
Towards the end of the output you will find:
```
<~ 250 2.0.0 Ok: queued as 7513F1F54B
```
You can even try a wrong password or a non-existing user. That should lead to…
```
<~* 535 5.7.8 Error: authentication failed: (reason unavailable)
```
TODO: a word on $mynetworks. Perhaps in the troubleshooting section later.
## Make Postfix use Dovecot for authentication
Enabling SMTP authentication in Postfix is surprisingly easy. You already configured Dovecot so it knows all about your
users from the SQL database. So lets just make Postfix use that by telling it to ask the Dovecot server to verify the
username and password. Postfix just needs some extra configuration. Run these commands on the shell:
```
postconf smtpd_sasl_type=dovecot
postconf smtpd_sasl_path=private/dovecot-auth
```
This enables SMTP authentication and tells Postfix that it can talk to Dovecot through a socket file located at
`/var/spool/postfix/private/auth`. Do you remember that Postfix runs in a sandboxed *chroot* directory? Thats at
/var/spool/postfix. It cannot access any files outside of that directory. But fortunately in a previous section you
edited the /etc/dovecot/conf.d/10-master.conf file and made Dovecot place a socket file into
`/var/spool/postfix/private/auth` to allow communication from Postfix.
TODO: collapsible on anti-spoofing
## Protecting against forged sender addresses ## Protecting against forged sender addresses
At this stage, a security issue remains. While Postfix is configured to relay emails only after a successful login with Before we can actually test the setup, there is another security issue we need to address. While Postfix is configured
a valid username and password, it does not prevent an authenticated user from impersonating another sender. For example, to relay emails only after a successful login with a valid username and password, it does not prevent an authenticated
someone could log in as `john@example.org` but send an email appearing to come from `brunhilde@example.net`. user from impersonating another sender. For example, someone could log in as `john@example.org` but send an email
appearing to come from `brunhilde@example.net`.
You want to see that this work? Let's pretend to be _brunhilde_ while authenticating as _john_:
```sh
swaks --server localhost:587 \
--from brunhilde@example.org \
--to list@example.com \
-tls \
--auth-user john@example.org \
--auth-password summersun
```
That should not be allowed. But Postfix does not know better and responds with:
```
<~ 250 2.0.0 Ok: queued as EEEB420F51
```
We should fix that. For that purpose Postfix provides a setting called We should fix that. For that purpose Postfix provides a setting called
[smtpd_sender_login_maps](http://www.postfix.org/postconf.5.html#smtpd_sender_login_maps). From the "maps" part you can [smtpd_sender_login_maps](http://www.postfix.org/postconf.5.html#smtpd_sender_login_maps). From the "maps" part you can
@ -304,7 +188,7 @@ sender, he must also authenticate as `john@example.org`.
As we use the email address also as a user name we simply need a mapping where the email address is listed on both As we use the email address also as a user name we simply need a mapping where the email address is listed on both
sides. Let's create it: sides. Let's create it:
```sh ```sh title="Run this on your server"
cat > /etc/postfix/mariadb-email2email.cf << EOF cat > /etc/postfix/mariadb-email2email.cf << EOF
user = mailserver user = mailserver
password = MAILSERVER-PASSWORD-HERE password = MAILSERVER-PASSWORD-HERE
@ -315,24 +199,20 @@ EOF
chown root:postfix /etc/postfix/mariadb-email2email.cf chown root:postfix /etc/postfix/mariadb-email2email.cf
chmod u=rw,g=r,o= /etc/postfix/mariadb-email2email.cf chmod u=rw,g=r,o= /etc/postfix/mariadb-email2email.cf
```
Tell Postfix to use this mapping for our purpose:
```
postconf smtpd_sender_login_maps=mysql:/etc/postfix/mariadb-email2email.cf postconf smtpd_sender_login_maps=mysql:/etc/postfix/mariadb-email2email.cf
``` ```
You have already added `smtpd_sender_restrictions=reject_sender_login_mismatch` earlier in the master.cf for the What you accomplished:
`submission` and `submissions` services. So you have achieved these three steps to prevent sender impersonation:
1. Create a mapping that matches an email address to itself. 1. Create the _mapping_ file
2. Tell Postfix to use that mapping for `smtpd_sender_login_maps` which controls who can be a sender. 1. Fix the permissions of it so that only Postfix can read it (there is a password in it)
3. Enforce that restriction for the `submission` and `submissions` services. 1. Make Postfix use that mapping for its `smtpd_sender_login_maps` setting
1. Add `smtpd_sender_restrictions=reject_sender_login_mismatch` to the `submission` and `submissions` services
You can now try to send email as a different user than you are logged in. Lets us swaks again to send a spoofed email: You can now try to send email as a different user than you are logged in. Lets us swaks again to send a spoofed email:
```sh ```sh title="Run this on your server"
swaks --server localhost:587 \ swaks --server localhost:587 \
--from brunhilde@example.org \ --from brunhilde@example.org \
--to list@example.com \ --to list@example.com \
@ -350,33 +230,113 @@ The server should rightfully reject that wrong sender:
<~ * 553 5.7.1 john@example.com: Sender address rejected: not owned by user john@example.org <~ * 553 5.7.1 john@example.com: Sender address rejected: not owned by user john@example.org
``` ```
You can also see that restriction in action by adding a fake persona in Roundcube and try to use it. In Now let's send a proper email from John:
Settings/Identities you can create another sender email address as in:
![Selecting another identity in Roundcube](images/relaying-roundcube-selecting-identity.png) ```sh title="Run this on your server"
swaks --server localhost:587 \
--from john@example.org \
--to list@example.com \
--tls \
--auth-user john@example.org \
--auth-password summersun
```
With this wrong sender address any email should get rejected. Roundcube will show you an error that is generated by That will work:
Postfix:
``` ```
553 5.7.1 <someoneelse@example.com>: <~ 250 2.0.0 Ok: queued as AFFF12292A
Sender address rejected:
not owned by user john@example.org
``` ```
<Aside type="tip" title="Allow aliases?"> <Aside type="tip" title="Allow aliases?">
If you want to allow users to send as one of their aliases you could create a new \*.cf file with a mapping query like
this: `SELECT email FROM virtual_users WHERE email='%s' UNION SELECT destination FROM virtual_aliases WHERE The setup so far works for personal mails from John. But if John would be a member of a distribution list
source='%s'` `pizza@example.org` then would not be able to use `pizza@example.org` as a sender address.
If you want to allow that, create a new \*.cf file with a mapping query like this:
`SELECT email FROM virtual_users WHERE email='%s' UNION SELECT destination FROM virtual_aliases WHERE source='%s'`.
</Aside> </Aside>
## How does authentication work? ## Test it
<details class="collapsible"> ### No encryption, no authentication
<summary>Click here if you want to learn how SMTP authentication works…</summary>
Are you curious how SMTP authentication looks on a low level? You probably are not. But lets do it anyway. Just once, So far you have set up the `submission` service(s), enabled encryption and connected Postfix to Dovecot for
so that you get the idea. authentication. Let's try it. Our first test email will be sent without encryption and authentication:
```sh title="Run this on your server"
swaks --server localhost:587 \
--from john@example.org \
--to lisa@example.com
```
Postfix should tell you that it will not accept an email without encryption:
```
<** 530 5.7.0 Must issue a STARTTLS command first
```
### With encryption, no authentication
Our second attempt will **enable** (STARTTLS) encryption but still no authentication:
```sh title="Run this on your server"
swaks --server localhost:587 \
--from john@example.org \
--to lisa@example.com \
-tls
```
Postfix will still reject the email because we refused to authenticate:
```
~> MAIL FROM:<john@example.org>
<~ 250 2.1.0 Ok
~> RCPT TO:<lisa@example.com>
<~* 554 5.7.1 <john@example.org>: Sender address rejected: Access denied
```
### With encryption and authentication
```sh title="Run this on your server"
swaks --server localhost:587 \
--from john@example.org \
--to lisa@example.com \
-tls \
--auth-user john@example.org \
--auth-password summersun
```
Also give the `submissions` service a try:
```sh title="Run this on your server"
swaks --server localhost:465 \
--from john@example.org \
--to lisa@example.com \
--tls-on-connect \
--auth-user john@example.org \
--auth-password summersun
```
Did you notice the difference? This time we used port **465** and instead of the `-tls` option (which uses STARTTLS to
switch to encryption) we use `--tls-on-connect` that uses TLS right from from the start.
Put simply, a mail server will accept an email if…
- it is responsible for handling mail for the recipients domain (the domain is listed as a virtual domain), or
- the sender has authenticated successfully with a valid username and password.
I have created two illustrations to explain it.
## Gory details of authentication
<Aside type="tip" title="For science">
This section is just here to show you the communication that happens on the network when relaying an email. It is no
mandatory reading matter. But relaying is a complex matter where things can go wrong. So taking a peek under hood may
improve your understanding.
</Aside>
``` ```
telnet localhost submission telnet localhost submission
@ -387,7 +347,7 @@ The server will let you in:
``` ```
Trying 127.0.0.1… Trying 127.0.0.1…
Connected to localhost. Connected to localhost.
Escape character is '^\]'. Escape character is '^]'.
220 webmail ESMTP Postfix (Debian/GNU) 220 webmail ESMTP Postfix (Debian/GNU)
``` ```
@ -426,8 +386,8 @@ Let me briefly explain what these lines mean:
nowadays 40 MB or even more are more common sizes because emails have grown larger. Some mail servers even allow 100 MB. nowadays 40 MB or even more are more common sizes because emails have grown larger. Some mail servers even allow 100 MB.
- VRFY - VRFY
Allows remote servers to verify a given name or email address. For example the remote server could send "VRFY john" and Allows remote servers to verify a given name or email address. For example the remote server could send "VRFY john" and
your server might respond `250 John Doe <john@example.org>`. It can be used to verify that a certain recipient email address your server might respond `250 Jo You can skip it for now.hn Doe <john@example.org>`. It can be used to verify that a certain
is deliverable recipient email address is deliverable
- ETRN - ETRN
A command that a remote system can send to flush the Postfix queue of mails for a certain domain. It can be used if the A command that a remote system can send to flush the Postfix queue of mails for a certain domain. It can be used if the
remote system had technical problems and failed to receive email for a while. Then it could send an ETRN command to make remote system had technical problems and failed to receive email for a while. Then it could send an ETRN command to make
@ -551,7 +511,3 @@ printf '\0john@example.org\0summersun' | base64
``` ```
As a result you will get the exact same string you used above with "AUTH PLAIN". As a result you will get the exact same string you used above with "AUTH PLAIN".
TODO: move further down
</details>