Your mail server is almost ready for use. But one puzzle piece is missing. Your users can already fetch emails but they need to be able to send emails, too. Mail server know how to send an email to another mail server by looking up MX records. And the receiving mail server will happily accept any email that is destined for a valid recipient. Mail clients however are expected to send email to their mail server first which then forwards email to the internet. When Postfix receives an email from a user and forwards it to another server this is called relaying. Postfix acts as a relay.
So why do the mail client not work the same way? Couldn’t they also just look up the MX record and send the email to the destination server? Technically, yes. But users are often sitting at home using a DSL line. And such IP networks are usually blocked off from sending mail. Some ISPs block the SMTP port on their routers. And most receiving mail servers check real-time blacklists to block DSL IP addresses. The reason is that home users are more likely to have infected Wind*ws PCs that send out spam.
Am I going too fast? Okay, let’s walk through the different scenarios one by one.
When someone on the internet sends an email to email@example.com, some other mail server will deliver the email using SMTP to your mail server. Postfix will determine that it’s responsible for email addresses in the example.org domain and accept the email. John can then use POP3 or IMAP to fetch the email from your server.
Outgoing email (without authentication)
John is on the internet somewhere and wants to send an email to firstname.lastname@example.org. Your mail server is not responsible for the “example.com” domain so it receives John’s email and would have to forward (relay) it to the mail server that is responsible for …@example.com email addresses. This may seem like a harmless scenario but your mail server must deny that:
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 John’s 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. This 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 John is actually John your server must reject the email.
Outgoing email (with authentication)
So how does John prove his identity? He needs to use authenticated SMTP. This is similar to the previous case but John’s email program will also send his username and password.
Of course we are making sure that his authentication happens over an encrypted connection so John’s password is blared out.
Postfix setting “mynetworks”
In addition to using SMTP authentication you can tell Postfix to always relay email for certain IP addresses. The mynetworks setting contains the list of IP networks or IP addresses that you trust. Usually you define your own local network here. The reason John had to authenticate in the above example is because he is not sending the email from your local network.
If your users are using the webmail interface running on your mail server then they will not need to authenticate. Roundcube sends email to localhost which is trusted according to the mynetworks setting.
Make Postfix use Dovecot for authentication
Enabling SMTP authentication in Postfix is surprisingly easy. You already configured Dovecot’s regarding user authentication. So let’s just make Postfix utilize 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/auth postconf smtpd_sasl_auth_enable=yes
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? That’s 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.
The following settings enable encryption, define the key and certificate and enforce that authentication must only occur over encrypted connections:
postconf smtpd_tls_security_level=may postconf smtpd_tls_auth_only=yes postconf smtpd_tls_cert_file=/etc/letsencrypt/live/webmail.example.org/fullchain.pem postconf smtpd_tls_key_file=/etc/letsencrypt/live/webmail.example.org/privkey.pem postconf smtp_tls_security_level=may
Note #1: In the past you may have configured your smtpd_recipient_restrictions to restrict relaying to authenticated users. Postfix nowadays has setting called “smtpd_relay_restrictions” that deals with relaying requests in the “RCPT TO” phase of the SMTP dialog. So essentially it works like the good old “smtpd_recipient_restrictions” but is checked first. smtpd_relay_restrictions has a reasonable default so authenticated relaying works automatically.
Note #2: Postfix 2.10 has changed the behavior of the smtpd_*_restrictions altogether. If a restriction makes Postfix reject an email it will wait until after the “RCPT TO” line. Read the Postfix documentation for a description of the reasons for that change.
The smtpd_tls_security_level is set to “may” to allow encrypted communication when Postfix receives an email. This not only applies to users sending emails but also remote mail servers that send email to you. It may sound tempting to change that to “encrypt” which would enforce encryption and reject any attempts to create an unencrypted SMTP connection. But unfortunately there are still mail servers in the world that can’t encrypt and would not be able to deliver email to you. So “may” is the proper setting. Same goes for smtp_tls_security_level for outgoing email sent to other mail servers.
Although emails may still get delivered unencrypted from other servers we should make sure that users don’t accidentally (or out of ignorance) send their passwords unencrypted over the internet. That’s what we enforce “smtpd_tls_auth_only=yes”. Now our users should be safe.
How SMTP authentication works
Are you curious how SMTP authentication looks on a protocol level? Let’s go through that. In previous versions of this guide we used “telnet” to connect to TCP port 25 and speak SMTP. But now we enforce encryption and can’t do SMTP authentication unencrypted. Let’s try the unencrypted part:
telnet localhost smtp
The server will let you in:
Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 mail ESMTP Postfix (Debian/GNU)
Postfix will present a list of features that are available during the SMTP dialog:
250-mailtest 250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-STARTTLS 250-ENHANCEDSTATUSCODES 250-8BITMIME 250 DSN
Let me briefly explain what these lines mean:
This is a feature to speed up SMTP communication. Usually the remote system has to wait for a response to every command it sends. Pipelining allows the remote server to send bulks of commands without waiting for a response. Postfix will just store these commands and execute them one by one. If you told Postfix to forbid pipelining it would disconnect the remote server when it tries to send bulks of commands without waiting for the proper reply. It is mainly a feature against spamming programs that don’t behave.
- SIZE 10240000
The remote server is allowed to send emails up to 10 MB large. This has long been a common maximum size for emails. However nowadays 40 MB or even more are more common sizes because emails have grown larger.
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 <email@example.com>”. It can be used to verify that a certain recipient email address is deliverable
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 your server start sending outstanding emails for that domain. It is rarely used.
This tells the remote system that it might start switching from this unencrypted to an encrypted connection by sending the “STARTTLS” command. It will then start negotiating a TLS-encrypted connection. You could compare it to an HTTP connection that suddenly switches over to an encrypted HTTPS connection. The advantage is that you can start talking SMTP on TCP port 25 and don’t have to open up a second TCP port like 465 which is the “SSMTP” (secure SMTP) port and only accepts encrypted connections.
This enables more three-digit return codes for various conditions. See the RFC2034 if you are curious.
In ancient times SMTP only processed 7-bit characters. You couldn’t transfer special characters like “Ä” or “ß” without special encoding. 8BITMIME allows a transmission of emails using 8-bit characters. Still many emails are specially encoded using ISO8859-1 or UTF-8.
It enables DSNs (delivery status notofications) that allows the sender to control the messages that Postfix creates when an email could not be delivered as intended
However one important line is missing here that would allow us to send our username and password:
250-AUTH PLAIN LOGIN
We told Postfix to only allow authentication when the connection is encrypted. So we are not offered authentication over this plaintext connection.
Are you still connected? Okay, good. So we need an encrypted connection using TLS. You could enter STARTTLS:
And the server would reply:
220 2.0.0 Ready to start TLS
However now it’s getting complicated because you would have to speak TLS encryption which is not a language that humans speak. So let’s quit this using the “QUIT” command.
But we can use OpenSSL to help us with the decryption. Now run:
openssl s_client -connect localhost:25 -starttls smtp
You will see a lot of output. OpenSSL has connected to TCP port 25 and issued a STARTTLS command to switch to an encrypted connection. So whatever you type now will get encrypted. Enter:
And Postfix will send a list of capabilities that will look like this:
250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-AUTH PLAIN LOGIN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250 DSN
And now that we are using an encrypted connection Postfix offers us to authenticate. So let us send the authentication string with a Base64-encoded password:
AUTH PLAIN am9obkBleGFtcGxlLm9yZwBqb2huQGV4YW1wbGUub3JnAHN1bW1lcnN1bg==
The server should accept that authentication:
235 2.7.0 Authentication successful
Excellent. You are logged in through SMTP. Disconnect from Postfix:
221 2.0.0 Bye
Authentication works. Well done.
You may wonder how I got to the long cryptic string that apparently contained John’s email address and his password. It is just a Base64-encoded (not encrypted!) version of the string “firstname.lastname@example.org<NULL-BYTE>email@example.com<NULL-BYTE>summersun”.
One way to create that string is using Perl:
perl -MMIME::Base64 -e \ 'print encode_base64("john\@example.org\0john\@example.org\0summersun")';
The submission port (optional)
Although I have been talking about SMTP on port 25 to relay mails there is actually a better way: using the submission port on TCP port 587 (as described in RFC 4409). The idea is to use port 25 for transporting email (MTA) from server to server and port 587 for submitting (MSA) email from a user to a mail server. As many users still use SMTP by default you will likely have to run both ports.
Edit your /etc/postfix/master.cf file and add a service for the submission port like this (mind the indentation from the second line on). Don’t use the suggested and commented-out submission service in the master.cf – just ignore it.
submission inet n - - - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_sasl_type=dovecot -o smtpd_sasl_path=private/auth -o smtpd_sasl_security_options=noanonymous -o smtpd_sender_login_maps=mysql:/etc/postfix/mysql-email2email.cf -o smtpd_sender_restrictions=reject_sender_login_mismatch -o smtpd_sasl_local_domain=$myhostname -o smtpd_client_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
Basically this new service uses the “smtpd” daemon which is the piece of software that responds if you open an SMTP connection on TCP port 25. But it gets a few extra options set…
- in the /var/log/mail.log mark the connections to the submission port as “postfix/submission/smtpd” (syslog)
- enforce encryption on this port (security level)
- enforce user authentication (sasl)
- make sure that an authenticated user can only send emails in their own name (sender_login_maps)
Restart the Postfix server:
service postfix restart
Your users can now use the submission port to send email.
Protecting against forged sender addresses
Wait a minute. We can make sure that a user can only send emails using their own sender address? Cool. How does that work? Take a look at this line we just used above when we added the submission service:
The smtpd_sender_login_maps is a Postfix mapping with two columns:
- the email address the user wants to use as a sender address
- the username that was used when authenticating at the mail server
The reason I am just using the /etc/postfix/mysql-email2email.cf mapping is that it fits perfectly here. That mapping maps the email address to itself if it exists. Confused? Look at the SQL query that the “email2email” mapping runs:
SELECT email FROM virtual_users WHERE email='%s'
So when a user wants to relay an email Postfix checks the virtual_users table looking for the sender address. Say that John wants to send out an email from his email address “firstname.lastname@example.org”. Postfix will check the virtual_users table if there are any rows with an email field like that. It will find one and return the email field – which is just again the email address. Postfix expects to get back the username that was used to login. We are using the email address as a login username – so that mapping works. Only if John would try to send an email with a forged sender address then Postfix will see that it does not match his account and reject the email. Your mail.log would read something like:
NOQUEUE: reject: RCPT from foo.bar[…]: 553 5.7.1 <email@example.com>: Sender address rejected: not owned by user firstname.lastname@example.org; from=<email@example.com> to=<…>
It is your own decision whether you want to use the smtpd_sender_restrictions like that.