Created at:

Modified at:

NetBSD mail server with Postfix, BIND (for DNS), Dovecot, Pigeonhole (Sieve), SSL, DKIM and SPF

Note: This is not a quick HOWTO on how to build a mail server with NetBSD. This is a detailed documentation on how to build a mail server with NetBSD. You won't be able to make it work without understand some important concepts, so read it carefully and take some time (days?) to study concepts, read the references and work on it.

Note: I'm not an experienced sysadmin and I have never setup a mail server. This document is an effort to document what I learnt. If you see any error on have any suggestion to improve this guide, please, let me know.

Contact me

Note: This is a detailed guide on how to configure Postfix, Dovecot and other tools to have a full email server in NetBSD. For information about common tools and other information, see my Postfix notes

Postfix notes

Introduction

Why this guide?

I took a glance at several guides on the internet, with different operating systems. I finally found a very good one, (that some guys on the NetBSD IRC channel -- #netbsd at irc.libera.chat -- recommended), which is linked below but it is still incomplete. I followed it but it still has some holes I intend to fill.

Home mail server with Postfix + Dovecot on NetBSD

Also, documenting stuff is the way I found to learn the steps I did "in loco" and also to help people with what I learnt.

Why a personal mail server?

TODO

Software needed

I did the configuration in this guide with the following software:

A bit of history

I decided it would be useful to have a section about mail history because it helps understand why it works the way it works. E-mail is a set of protocols and extensions piled up over time. For those who grew up in the age of social networks, it might to be difficult to understand some stuff without some historic background. For instance: it was always a mystery for me why mail server and IMAP server were different things.

So, let's begin on what e-mail is. I strongly recommend reading the Wikipedia page on Email, that has a nice background. For us it is enough to know that communication via what today is known as "e-mail" began by 1960s but "modern" e-mail was standardized in 1982, with the SMTP protocol.

Wikipedia page on Email

SMTP Protocol

What was happening at that time? People didn't have a lot of computers every time like nowadays. Although personal computers were going to be mainstream very soon, computers were still limited to some niches and their place were universities, big companies and so on.

But, at these places, people still didn't have workstation computers like we have on every desk nowadays. They used mainframes. And how they connected to mainframes? Using a computer terminal.

Computer terminal

So Alice and Bob had to connect their terminals to the mainframe they used. This connection was made in different ways but RS 232 became the most used standard. It is interesting to see that this explains why Unix (and other operating systems at that time, like Multics) had have such a improvement on user isolation and time and resource sharing for the hundreds (thousands?) of users on the same computer (Some time ago I found an old video from AT&T starring Brian Kernighan, Dennis Ritchie and others. That video shows a typical office at that time, with people using terminals to connect to a Unix system.

RS-323

"AT&T Archives: The UNIX Operating System video"

Although Alice and Bob are connected to the same computer, they could be in different locations. People developed programs so they could send messages to each other (Does anybody know more? Please, let me know :-]). What if we could also send messages to people connected to other computers? No problem! Some message delivery systems were designed but SMTP got standardized in 1982. If two mainframes were running mail server systems, they could exchange messages using SMTP using the port 25::

This is what a mail server (an SMTP server) does: it sends message from one server to another and, at the receiving side, it delivers to the users mailbox. Period. At that time people didn't laptops, computers and smartphones to fetch their e-mail boxes. One should simple login into the mainframe, using the computer terminal, and send an e-mail. Sysadmins trusted each other, they had control over computers and there were not many of them, even less connected to what we call "the Internet".

But soon this reality changed. Computers were getting more numerous. DNS, the Domain Name System was invented, and the invention of the World Wide Web made computers rise in popularity.

Domain Name System

World Wide Web

Nowadays one person can have many computers. Mainframes got scarce and people have more processing power than mainframes. We are connected to the Internet 24x7. The situation looks like this:

We now have many problems. Two important ones are:

1. How one should send an e-mail to other if the receiver now has many computers? What computer should send it (and what should receive)?

2. People are now admins of their own set of computers and the internet wizards have no control of it, like they had on the mainframes age. We then had started to have problems with spam, etc.

So, many other communication tools showed up: social networks, VoIP software, etc., much of them centralized stuff. But the good and old e-mail survived! Extensions were developed (while keeping protocols backward compatible) to make it safer and avoid spam. Now we have something like this:

Mail servers still use port 25 to communicate with each other. But users use other ports and protocols developed later to fetch e-mails from the mail server to their devices, or to send e-mails (There are also other manners to access your e-mail. With the web, web clients sprung up and it is nowadays one of the most common ways to access your inbox (not to mention proprietary API's that most applications use). Gmail is a good example of that. There is also web clients that are free software like Roundcube but, as I said earlier, they are not the focus of this article.

What we are learning in this guide is to make the mail server communicate with other mail servers and also configure software to allow users to fetch their mail server.

Part 1: A simple mail server

In this part we are going to make our mail server talk to other mail servers. For this to work, we are going to need support for other system. Probably, the most important one is the DNS.

Before we begin, let's make some important definitions:

1. We are going to setup a server called example.com. Our main user is "alice" (so we want to setup the e-mail alice@example.com. In a company environment, DNS, mail server, IMAP server, etc., is kept separately for security and other reasons. Since it is a personal server, we are going to install every service we need to the same machine.

2. You can do it in your home, but you we'll need a fixed IP mainly because of Reverse DNS lookup. I find it easier (and maybe cheaper!) to just rent a small virtual private server. Let's suppose our IP is 123.45.67.89.

Reverse DNS lookup

Setting up DNS with BIND

*Important:* Until now, I was just a DNS user. My first BIND configuration is this and there may be (serious) errors. Please, let me know if you see something wrong!

We are not enter the details of how DNS works. I had a user view of that and I had to discover a bit more to configure the e-mail server, but is still a very limited view. I will, though, list the wrong concepts I had because I believe other people may have the same difficulties.

*Note:* Everything I know here is because I read the great book "DNS and BIND", by Cricket Liu and Paul Albitz. It explains very well what DNS is and how to configure BIND (probably the most used DNS server). At the time I write this I have read only the first 5 chapters, which was enough for a basic setup, but the whole book seems promising!

DNS and BIND

I though that the sole purpose of DNS was to to tell a requester the IP address of a given name, but it can inform many more things and some of them are used for the e-mail service.

Let's make some tests. Type in your terminal:

    $ dig +short gmail.com
    216.58.202.165

The dig tool will show the IP address associated with the gmail.com domain. This is called the A registry (A stands for Address). Looking for the A registry is the default but we can issue the command with it explicitly:

    $ dig +short A gmail.com
    216.58.202.165

(dig is a DNS lookup utility. There are more references to the more famous nslookup but I prefer using dig. We are going to use the +short flag because we are not interested in the details of DNS query for now.

dig man page

nslookup

(It will use the default DNS server configured in your system (in /etc/resolv.conf). If you want to use another, use the @ character. For instance, to run the same command using the Google DNS server, type: dig @@8.8.8.8 +short gmail.com.)

What does it mean? If we send an e-mail to foo@gmail.com, will it be sent to IP address 216.58.202.165? No! The mail system actually queries for another DNS registry called MX. Let's take a look::

    $ dig +short MX gmail.com
    40 alt4.gmail-smtp-in.l.google.com.
    20 alt2.gmail-smtp-in.l.google.com.
    30 alt3.gmail-smtp-in.l.google.com.
    10 alt1.gmail-smtp-in.l.google.com.
    5 gmail-smtp-in.l.google.com.

Those are the servers that can receive e-mails when we send something to the Gmail servers. We also need to specify this in our DNS setting.

Will we run our own server? Yes, and we'll show how to make that with the BIND DNS server.

BIND DNS server

Note: A note on registrars: the company you registered your domain in may have their own DNS server. The company where I registered mine had, but it was buggy and had a broken interface, so I decided to setup my own DNS server.

First, configure the /etc/named.conf file. NetBSD defaults are OK, but you have to add your own zone:

    zone "example.com" {
            type master;
            notify no;
            file "example.com";
    };

Now we need to create the zone file, /etc/namedb/example.com:

    $TTL    3600
    @       IN      SOA     example.com. hostmaster.example.com.  (
                                    2017090901      ; Serial
                                    3600            ; Refresh
                                    300             ; Retry
                                    3600000         ; Expire
                                    3600 )          ; Minimum
                    IN      NS      ns.example.com.
                    IN      NS      puck.nether.net.
                    IN      MX      1 mail.example.com.
    mail            IN      A       123.45.67.89
    ns              IN      A       123.45.67.89
    example.com.    IN      A       123.45.67.89

This is where magic happens. We are not explaining every detail of this file. For more information, refer to the chapter 4 of "DNS and BIND". Some lines deserve special attention, though:

a. Line 2 (the one that starts with "@"): Just add your domainname. *Don't forget the trailing dot*. (We use the trailing dot whenever we want to specify an "absolute domain" (like an absolute path in filesystem that starts with a trailing slash, absolute domains end with a trailing dot). Again, refer to chapter 4 of _DNS and BIND_ book. See that we have relative domain names ("smtp" becomes "smtp.example.com.") and absolute ("example.com." is just "example.com.").)

b. Lines 3 to 7: that follow are for DNS synchronization between servers. Defaults are ok. Increment the first number whenever you make a change to this zone, so other DNS servers know when you have newer information to be synced.

c. Line 8: This is important: Here is where we configure the NS registry for this DNS entry. "NS" stands for Name Server and it is a server that will be queried when a client wants to know the IP address of any subdomains of example.com domain.

It is important to explain things a bit: let's imagine someone is querying for server "smtp.example.com". Your ISP DNS server knows that the server responsible for the zone (in DNS parlance, it is called "authoritative server" for a given zone) is "ns.example.com", i.e., it has every information about any subdomains for "example.com". So things work like this: user queries his ISP DNS that queries "ns.example.com" that answers to the ISP DNS that answers to the user. That is why we need to set this line.

In your registrar, you need to add the nameservers responsible for your zone. In my registrar I add the name of the nameserver ("ns.example.com") and its IP addres.

d. Line 9: This is the name for the secondary DNS server. Registrars usually (Always?) require at least two DNS servers. Unfortunatelly we can't afford a second one. So we use one of the free secondary DNS servers that exist. I use PUCK Free Secondary DNS Service but you'll find others in Free Secondary DNS list list. After you setup your secondary DNS server, it will easily synchronize information from the master server ("ns.example.com"). (For more information about DNS synchronization, see chapter 2 of _DNS and BIND_ book).

PUCK Free Secondary DNS Service

Free Secondary DNS list

e. Line 10: The MX record is where we specify our mail server. Any message addressed to anybody@example.com is delivered in one of the MX servers. You can have more than one server (if some is down, others can take over -- just remind the example we did before: dig +short MX gmail.com). But since we can't afford a backup server, we'll work with one only.

f. Lines 11 to 13: Now we specify the A records, i.e., the IP address of each server. Unfortunatelly we have no money and they are actually the same! Could they have the same domain name? Yes, but we would have to give different names if we use different IP addresses for each server later, so it is better to have different names even if they have the same IP to make further improvements easier.

Important: At the time of this writing, I was not sure if I should include the PTR records. The company where I rent the VPS already had a reverse DNS record for this machine (with a different address, though). Is it enough? If someone know that, please, let me know.

After all this, we just add BIND daemon (named) to /etc/rc.conf and start it:

    # echo 'named=YES' >> /etc/rc.conf
    # /etc/rc.d/named start

Don't forget to tell your registrar about your nameserver and the secondary nameserver, too.

Setting up Postfix

We now have a our DNS records working. Without them (specially the MX records) it would be impossible to have a mail server. (Or at least very difficult? Mail servers predate DNS, but nowadays I find it very difficult to setup one without DNS.)

Now, let's add configuration to the Postfix mail server, that comes by default in NetBSD.

Let's first change /etc/postfix/main.cf file. We are going to list every option to add to the file and explain them:

    myhostname = smtp.example.com
    mydestination = $mydomain, $myhostname, localhost.$mydomain, localhost
    home_mailbox = Maildir/
    mynetworks_style = host

a. Line 1: Just the domain name and the name of this host. (Does it have to be the same of the output of the hostname command or does it have to be the entry of it in the DNS?)

hostname man page

b. Line 2: Who we want to deliver messages to.

c. Line 3: We want to user to stored e-mails instead of more traditional (and limited).

Maildir

mbox

d. Line 4: We can specify who we can relay e-mail from. By default, it is "subnet". If you are hosting your e-mail server in a VPS (Virtual Private Server), like me, other machines on the same host can be on the same network, so we set it to host, allowing only ourselves to relay e-mail. See [HILDEBRANDT2005], page 55).

There is one more setting you have to make. Now in file /etc/postfix/master.cf. Change line::

    #smtp      inet  n       -       n       -       -       smtpd

To::

    smtp      inet  n       -       n       -       -       smtpd -o myhostname=example.com

Important: Replace example.com by the name you get from reverse DNS lookup. Sometimes it is configured by the company you contract your VPS from. To discover the domain name related to your IP address, use the dig(1) tool:

        $ dig +short +x 123.45.67.89

This is really a **very important** setting since that receivers will query for address related to your IP and compare this to your e-mail headers Postfix add using this setting. If they don't match, mail serves closes the connection and don't receive the message. This is a common protection against spam that fakes DNS names.

VoilĂ ! This configuration is enough for a 1982 configuration. And, believe or not, you can send and receive e-mails to and from your mail server. There are two problems, though: one is that we'll send plain text message, without any cryptography. The other is that chances that your e-mail fall into the receiver's spam folder are high because we are not using modern stuff here. Let's learn how to configure them.

Part 2: Improving our mail server

Enabling SSL for Postfix

We are going to use openssl to create our public and private keys. Let's create our keys.

openssl man page

We can do that with two ways. The first one is to have a self-signed certificate and the second one is ask for a company to generate the certificate for you.

Generating a self signed certificate is simple:

    # mkdir -p /etc/ssl/{certs,private}
    # cp /usr/share/examples/openssl/openssl.cnf /etc/openssl/
    # openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/example.com.key -out /etc/ssl/certs/example.crt
    # chmod 600 /etc/ssl/private/example.com.key

You can use Let's Encrypt to generate SSL certificates signed by a CA (Let's Encrypt), but you may also use self-signed certificates.

Let's Encrypt

discussion about using a self-signed certificate for SMTP

*Note:* If you use StartSSL, they will generate the certificates and give you a zip file with them. Inside the zip file there are already directories with the certificates ready for common applications like Apache. We have not a specific directory for Postfix but we have a directory called OtherServer. You will find three files there: 1_Intermediate.crt, 2_example.com.crt and root.crt. The certificate wil are going to generate is a concateation of 2_example.com.cr and 1_Intermediate.crt (attention: the order matters). So, generate that:

        # cat 2_example.com.crt 1_Intermediate.crt > /etc/ssl/certs/example.com.crt

After that, we should add more settings to Postfix /etc/postfix/main.cf file:

    smtpd_tls_security_level=may
    smtpd_use_tls=yes
    smtpd_tls_auth_only=yes
    smtpd_tls_cert_file=/etc/ssl/certs/example.com.crt
    smtpd_tls_key_file=/etc/ssl/private/example.com.key
    smtpd_tls_loglevel=1

You are not explaining these options since most of them are self-explanatory. But if you want to know the details, check the Postfix documentation

Postfix documentation

Now, just start postfix!

    # /etc/rc.d/postfix start

You are now able to send and receive e-mails from and to this machine. Make a test: 1. login into this machine (this is like connecting via a serial line to your mainframe! :-]), chose an existing e-mail address (your's or of a friend) and send it an e-mail:

    $ echo "message body comes here" | mail -s "subject" myfriend@email.com

It will receive the e-mail. If it don't, check logs for postfix in /var/log/maillog. Also, never forget to check the spam folder. Some later configurations are still needed if we want reduce the probability of letting our e-mails be dropped to the spam folder.

Allowing devices check our e-mail with IMAP

TODO: describe better what is SASL

We are now able to send and receive people e-mail. E-mail you receive shall appear in /var/mail/yourusername. We are in a 1982 configuration with some extensions: SSL mainly. Without it is almost impossible to exchange e-mail with other servers nowadays.

But we don't want that configuration. We don't want to login to this machine in a console or ssh whenever we need to check our e-mails. Also, we want to sync all our devices to fetch and send e-mails. So we are going to configure our IMAP server.

As I mentioned, Postfix is e-mail server, i.e., it handles the SMTP protocol (and extensions) and does e-mail exchanging with other servers. It doesn't provide any support for IMAP. To make IMAP to work, we'll need to install another daemon and configure Postfix to talk to it. We are going to install Dovecot.

Dovecot

Postfix with SASL: Configuration with Dovecot

Build it from source via pkgsrc (mail/dovecot2) or just install the binary package. Check pkgsrc documentation for details.

pkgsrc

Configuration of Dovecot is straightforward. Open the /usr/pkg/etc/dovecot/dovecot.conf file in your favorite editor. First, change the line:

    protocols = imap pop3 lmtp

to:

    protocols = imap lmtp

We are not going to use POP3 (anybody uses?).

Now, in file /usr/pkg/etc/dovecot/conf.d/10-ssl.conf add or uncomment the following lines:

    ssl = yes
    ssl_cert = </etc/ssl/certs/example.com.crt
    ssl_key = </etc/ssl/private/example.com.key

Now edit file /usr/pkg/etc/dovecot/conf.d/10-mail.conf and add (or uncomment) this line:

    mail_location = maildir:~/Maildir

It indicates to store users' e-mails in their home directories, using the Maildir format.

Now, just start Dovecot:

    # cp /usr/pkg/share/examples/rc.d/dovecot /etc/rc.d
    # echo 'dovecot=YES' >> /etc/rc.conf
    # /etc/rc.d/dovecot start

There are one important things to note here: with this setup, Postfix and Dovecot **are not** talking to each other. What happens here is that Postfix deliver e-mails to users' home directory and Dovecot reads from the same location. It works but this setup has some limitations, like not allowing e-mail filtering. To know the advantages and how to setup LMTP take a look in section "Making Postfix talk to Dovecot via LMTP".

Allowing devices to send e-mail using SMTP

We can now configure our devices to fetch e-mail from our server using the IMAP protocol but we cannot send yet. The SMTP port Postfix is using, port 25, is solely for other server communication purposes. But, for this, Postfix will enable us to use another port, mainly 587. This is called submission, i.e., e-mail submission and port 587 is reserved for that. (Other ports and proposals for e-mail submission have existed.

discussion about SMTPS and submission confusion

Configure that is also pretty simple. First, open file /etc/postfix/master.cf again and uncomment the submission line and add some lines after that. It will be like:

    submission inet n       -       n       -       -       smtpd
      -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_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

*Important:* Don't remove spaces at the beginning of the line. They mean the continuation of the command on the line before.

Also add this line to /etc/postfix/main.cf:

    smtp_tls_security_level = may

Realize that smtp* is different from smtpd* (see the trailing d) settings. The former is for the submission port (587), the later is for SMTP port (25).

Restart Postfix:

    # /etc/rc.d/postfix restart

Now you have a full e-mail server! But it is not done yet, we'll have to configure all the extensions to it so we don't be marked as spam.

Making Postfix talk to Dovecot via LMTP

As we stated in section "Allowing devices check our e-mail with IMAP" Postfix and Dovecot just write/read e-mails from the same location but they don't talk to each other. This is simple but it doesn't allow us to use any of the advanced features Dovecot provides. To fix this, we are going to setup both Postfix and Dovecot to use the LMTP protocol. So, Postfix will deliver e-mail to Dovecot explicitly:

LMTP

Let's first start setting up Dovecot. It is a very simple configuration. According to the "Dovecot with Postfix LMTP guide", we need to change file /usr/pkg/etc/dovecot/conf.d/10-master.conf, configuring LMTP service::

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

Dovecot with Postfix LMTP guide

In this example, we are using a Unix Domain Socket (it is useful specially if Dovecot is installed at the same machine as Postfix), but you can use a inet socket too. Don't forget to set permissions and ownership correctly.

It is enough at the Dovecot side. Now let's do that at the Postfix side. Edit file /etc/posfix/main.cf. First, since we are delivering our e-mail to Dovecot via LMTP, setting home_mailbox (that we configured at "Setting up Postfix" is now useless. You can commment or exclude it. Now we have to add the mailbox_transport setting::

    mailbox_transport = lmtp:unix:private/dovecot-lmtp

You can also delete the home_mailbox from /etc/main.cf because we won't use it anymore.

*Note:* By default, user authentication is done with the full e-mail (i.e., clients should provide the full e-mail for login information, and not only the username. We recommend keeping this as is, specially if you want to user virtual domains as we explain in Setting up virtual users (and virtual domains). If you really change to change it, according to this edit /usr/pkg/etc/dovecot/conf.d/10-auth.conf and change variable auth_username_format:

        auth_username_format = %Ln

discussion about dovecot complaining user does not exist

Now, don't forget to restart Postfix and Dovecot:

    # /etc/rc.d/postfix restart
    # /etc/rc.d/dovecot restart

To make sure your LMTP setting works, always take a look at the /var/log/maillog file.

E-mail filtering with Pigeonhole (Sieve)

The Sieve programming language was created specially for e-mail filtering. In the Dovecot world it is done by the Pigeonhole project. We'll describe how to setup it here.

Sieve programming language

Pigeonhole project

First, make sure your LMTP setting works. See "Making Postfix talk to Dovecot via LMTP" for more information. Then, install the mail/dovecot2-pigeonhole package.

Once it is working, let's enable the sieve plugin for LMTP. Edit file /usr/pkg/etc/dovecot/conf.d/20-lmtp.conf, locate line starting with mail_plugins and add it:

    protocol lmtp {
      mail_plugins = $mail_plugins sieve
    }

And just restart Dovecot:

    # /etc/rc.d/dovecot restart

Simple, ahm?

Now, include your rules in your ~/sieve file. For example:

    require ["fileinto"];
    if header :contains "subject" "test" {
        fileinto "test";
        discard;
        stop;
    }

This will make that every e-mail with the "test" word in the subject header is put into the "test" mailbox and discarded from inbox.

To validate your sieve script, use the sieve-filter command:.

    $ sieve-filter -v -C -u test /path/to/sieve/example.sieve 'INBOX'

sieve-filter man page

a good example of sieve-filter command

It will print what the filter would do in all messages that are in box INBOX. If you want to execute the rules, pass -e and -W flags. But *be careful*! The man page states that it can be very destructive. So, be sure of what you are doing! For troubleshooting, never forget to look at /var/log/maillog.

Pigeonhole Sieve configuration

Setting up virtual users (and virtual domains)

For now, we now have a full workable e-mail server with different capabilities plugged in, but we are still using system users. If you want to add a new e-mail user you'll have to add it as a Unix system user, creating all the environment for a normal user, with home directory, login shell and so on. If you need to do so but don't want to let your users login via ssh, you can set their shells to /sbin/nologin.

But this solution doesn't scale. For this, both Postfix and Dovecot provide a feature called "virtual users" that you can use. There is also one more reason to use virtual users: you can also set virtual domains together and provide e-mail service for more than one domain, in the same server! A similar feature to virtual hosts of HTTP servers.

Postfix virtual users are completely unrelated to Dovecot virtual users. So, we can have different setups:

1. Postfix *without* virtual users and Dovecot *with* virtual users. 2. Both Postfix and Dovecot *with* virtual users.

The first one, AFAIK, let you use system users but allow you to setup a different password that users use for IMAP and system access. The second one is useful if you want to provide true virtual e-mail users and also virtual domains support. We are going to explain how to setup the second case.

We are going to use the simple "passwd-file" method, on which we store user and password information in a plain text file, but several other methods, like storing users and password information in SQL database or LDAP. Page linked below helped me a lot understand this.

how to build a virtual user e-mail server

First, we need to make a few changes to /etc/postfix/main.cf:

    myhostname = other.com
    virtual_mailbox_domains = /etc/postfix/vhosts
    virtual_mailbox_base = /home/maildeliverer
    virtual_mailbox_maps = hash:/etc/postfix/vmaps
    virtual_uid_maps = static:1008
    virtual_gid_maps = static:1008
    virtual_transport = lmtp:unix:private/dovecot-lmtp
    # Also, you can now delete line mailbox_transport we added before.

So, let's explain what each variable mean:

myhostname
We need to set myhostname to a different domain we are going to use in our e-mails. All virtual domains are all going to the /etc/postfix/vhosts file.
myorigin
Set it to either $mydomain or $myhostname or, if the hostname is different from the domain you want to get e-mail delivered to (this can happen if you are using virtual domains), set it to the domain name. This is good to delivering e-mail correctly if internal tools (such as cron(8) wants to send e-mails to users like root. After doing that, you still probably don't have a "root@example.com" account, so you can redirect it to whoever you want by appending a line to /etc/postfix/aliases (See [HILDEBRANDT2005], page 20.). Check "Configuring aliases for our virtual users" for more information.
virtual_mailbox_domains
The file with a list of virtual domains we are going to work with. See below.
virtual_mailbox_base
The directory where e-mails will be stored. Let's create a system user for this and call it "maildeliverer" and let our virtual users boxes located at /home/maildeliverer.
virtual_mailbox_maps
The file with a list of virtual users. See below.
virtual_uid_maps
The uid (user id) virtual mail boxes will be created with. The uid of "maildeliverer".
virtual_gid_maps
The gid (gropu id) virtual mail boxes will be created with. The gid of "maildeliverer". In NetBSD, every new user is, by default, part of the "users" group (gid = 100) but it is wise to let a group that don't mix with other users groups. In this example we create a user with gid = 1008.
virtual_transport
Now that we are using virtual users we have to set this variable. It has the same value as the mailbox_transport variable. By the way, we are not going to need mailbox_transport anymore, so you can delete it.

The content of /etc/postfix/vhosts is just a list of virtual hosts per line::

example.com example2.com

And the content of /etc/postfix/vmaps is a list of records per line, where each record is compound with user login information and where the e-mail will be stored::

    alice@example.com       Maildir-alice
    bob@example.com         Maildir-bob

The mailboxes will be created relative to value of variable virtual_mailbox_base with uid and gid set to virtual_uid_maps and virtual_gid_maps.

To add groups and user commands, use commands groupadd and useradd commands.

groupadd man page

useradd man page

After you have created the /etc/postfix/vmaps file, enter the directory and execute the postmap program:

    # cd /etc/postfix
    # postmap vmaps

It will create file vmaps.db that postfix will use to load virtual user information.

It is worth to note that, although we have options such as virtual_mailbox_base, virtual_uid_maps, virtual_gid_maps and also the mailboxes in file /etc/postfix/vmaps, they are not being used (really?). We added this to show how postfix can work with virtual users *alone* but we are passing e-mails to Dovecot with LMTP, setting virtual_transport variable. So, those setting are just ignored (can anybody confirm this?).

So, let's now see how we configure Dovecot virtual users.

In file /usr/pkg/etc/dovecot/conf.d/10-auth.conf, disable or comment line:

    #!include auth-system.conf.ext

And uncomment or add line:

    !include auth-passwdfile.conf.ext

Then, let's configure file /usr/pkg/etc/dovecot/conf.d/auth-passwdfile.conf.ext:

    mail_location = maildir:/home/maildeliverer/%n/Maildir

    passwd {
      driver = passwd-file
      args = scheme=CRYPT username_format=%u /usr/pkg/etc/dovecot/users
    }

    userdb {
      driver = passwd-file
      args = username_format=%u /usr/pkg/etc/dovecot/users
    }

*Note:* This configuration let us configure virtual users mailbox at /home/maildeliverer/<username>/Maildir. %n is replaced by the username part of the e-mail (the one before @). If we have different users in different domains that have the same user name (e.g.: alice@foo.com and alice@bar.com) you'd better include the domain name in the mail_location information, like this:

        mail_location = maildir:/home/maildeliverer/%d/%n/Maildir

We are pointing to file /usr/pkg/etc/dovecot/users. Let's create this file with this content:

    alice@example.com:{CRAM-MD5}e02d374fde0dc75a17a557039a3a5338c7743304777dccd376f332bee68d2cf6:1008:1008::/home/maildeliverer/alice
    bob@example.com:{CRAM-MD5}124ff4a48a846c9bf4146c32ba7a57ad2fb2b30cfef8d06c0f1c35eccb1598bc:1008:1008::/home/maildeliverer/bob

The format of this file is:

    username@domain:crypt-password:uid:gid::virtualuserhome

To generate the password part, use the command:

    # doveadm pw

Make sure the uid and gid parameters are set to the user you want to have access to the virtual boxes. For more information about this file, see

AuthDatabase/PasswdFile

VirtualUsers in the Dovecot wiki

Finally, restart Postfix and Dovecot:

    # /etc/rc.d/postfix restart
    # /etc/rc.d/dovecot restart

Configuring aliases for our virtual users

Once you have configured virtual users you can now configure virtual aliases, which are straightforward. You first need to tell where your aliases file is. First, add the following line to /etc/postfix/main.cf:

    virtual_alias_map = hash:/etc/postfix/virtual_aliases

Now, create the /etc/postfix/virtual_aliases file with the content you wish:

    secondary@example.com   alice@example.com

In NetBSD, if you want to receive e-mails from root (the system security framework itself sends e-mails to the local user root), also add to the virtual_aliases file:

    root@example.com        foo@example.com

To make root@example.com work, do not forget to set myorigin option. See the "Setting up virtual users (and virtual domains)" section.

Now, generate the /etc/postfix/virtual_aliases.db file with:

    # postmap virtual_aliases

They might start working without the need to restart postfix.

*Note:* Aliases (which you'd set with the alias_maps option) and virtual aliases (which you'd set with the virtual_alias_maps option) are different things.

discussion about the difference between aliases and virtual aliases

Mailbox permissions in Dovecot

I don't like to let clients create their own mailboxes. Some are broken and create things without my permission. Also, when migrating a different account to your new server, or changing your mailboxes names, you may want to restrict this.

In file /usr/pkg/etc/dovecot/conf.d/20-imap.conf, make sure you have the following line:

    mail_plugins = acl

And add the plugin configurations in /usr/pkg/etc/dovecot/conf.d/90-acl.conf:

    plugin {
      acl = vfile:/usr/pkg/etc/dovecot/global-acls:cache_secs=300
    }

In this example we specify a global ACL file but other settings are possible. The contents of this file is something like this:

    * user=username lrwstipek

Where each letter at the end of the line means something. See the Dovecot wiki page about ACL for the meaning of the letters. The existence of k letter, for instance, grants mailbox creation permission so if you want to forbid users to create mailboxes, remove the k letter and restart the Dovecot server.

Dovecot wiki page about ACL

answer to a question I made has a more detailed explanation about permissions in Dovecot

Changing default message size limit

By default, Postfix limits message size at 10 MB which is fine for most uses but it is a low limit for today's nonsense web age. It will not deliver your message and send an error e-mail to the sender, telling her/him the message is too big.

article about problems with the web

article about web obesity

To change that, just add or change parameter message_size_limit in main.cf:

    # 30 MB message size limit -- oh god!  how we end up here?!
    message_size_limit = 31457280

Note that the size is in bytes.

Part 3: Improving it even more: E-mail authentication

Having a mail server configured with SSL is an important step to make your server be recognized as "legit" for other mail servers. Other crucial feature is reverse DNS. But those features are not enough. As time passed, other features were developed to make spam and fake e-mails difficult. The ones we are going to show here are SPF, DKIM and DMARC.

We are not going to explain what SPF, DKIM and DMARC are. For a great explanation about that see the page . There is also News.

Why your Marketing Email will Land in my Spam Folder: The Non-marketing Guide: DKIM, SPF & DMARC

a discussion in Hacker News about the previous link

SPF

SPF stands for Sender Policy Framework and it is a simple setting you add to your DNS setting so e-mail filters can query who is able to send e-mails signed as your domain.

Sender Policy Framework

Basically, you list in a DNS entry what servers are able to send e-mail using your domain. If we are using one simple e-mail server (if you can afford, choose to have redundancy) it is as simple as::

    example.com.    IN      TXT     "v=spf1 mx -all"

With mx, it will look for domains of all MX servers you declared in your DNS.

Pay attention to the -all part. Basically, this line tells you that mail host, and only mail, can send e-mail. Everything else should be ignored.

Since we changed our DNS zone, don't forget to increase the zone file serial number, so this change gets.

SPF Record Lookup page, useful for debugging SPF issues

You can now use dig to check for your SPF record:

    $ dig +short @ns.example.com TXT example.com

DKIM

DKIM is a kind of e-mail authentication mechanism. It uses your private key to create a signature of your e-mail that is added to the e-mail headers. Other servers use your public key (that is added as a TXT record of your DNS server) to check the signature. It is a very effective way to check your e-mail is authentic and not a fake one.

I followed this guide:

How To Install and Configure DKIM with Postfix on Debian Wheezy

First install it with pkgsrc (mail/opendkim). Then, generate the keys:

    # cd /var/db/opendkim
    # opendkim-genkey -s mail -d example.com

It will generate, inside var/db/opendkim, two files: mail.private, where your private key exists, and mail.txt where your DNS entry with public key exists. I actually prefer to rename them to the domain name, so we avoid name clashes if we choose to support more domains::

    # mv mail.txt example.com.txt
    # mv mail.private example.com.private

Generate the keys pair step for every domain you have. It seems it is possible to have one key for different domains, but this is not our objective.

example.com.txt has the exact entry for DNS. Just append it to your DNS zone configuration:

    # cat example.com.txt >> /etc/namedb/example.com

Don't forget to increase the zone file serial number, so this change gets propagated to other DNS servers.

*Note:* The setting for **one** domain is pretty simple. Just edit the OpenDKIM configuration file (/usr/pkg/etc/opendkim.conf). Add (or uncomment) the following settings:

        Domain      example.com
        KeyFile     /var/db/opendkim/example.com.private
        Selector    mail

*Note:* If you want to have **multiple** domains, you should add (or uncomment) these settings instead:

        KeyTable        /var/db/opendkim/KeyTable.txt
        SigningTable    /var/db/opendkim/SigningTable.txt

Setup DKIM (DomainKeys) for Ubuntu, Postfix and Mailman

Then, create /var/db/opendkim/KeyTable.txt:

        mail._domainkey.example.com example.com:mail:/var/db/opendkim/example.com.private
        mail._domainkey.example2.com example2.com:mail:/var/db/opendkim/example2.com.private

Add one line per domain.

The contents of /var/db/opendkim/SigningTable.txt is:

        example.com mail._domainkey.example.com
        example2.com mail._domainkey.example2.com

Don't forget to fix permissions for these files:

        # chown opendkim /var/db/opendkim/KeyTable.txt
        # chown opendkim /var/db/opendkim/SigningTable.txt
        # chmod 600 /var/db/opendkim/KeyTable.txt
        # chmod 600 /var/db/opendkim/SigningTable.txt

No matter if you choose one domain or multiple domain setting, also add (or uncomment the following settings in /usr/pkg/etc/opendkim.conf:

    Socket      inet:12301@localhost

Basically, The Socket option will create a socket that we'll use to comunicate with Postfix. Another option would be to create a Unix Domain Socket with:

    Socket local:/var/db/opendkim/dkim-socket

If you chose to do so, don't forget to change UserID and maybe UMask options.

After that, add the following lines to /etc/postfix/main.cf:

    milter_protocol = 2
    milter_default_action = accept
    smtpd_milters = inet:localhost:12301
    non_smtpd_milters = inet:localhost:12301

Now, start OpenDKIM:

    # echo 'opendkim=YES' >> /etc/rc.conf
    # /etc/rc.d/opendkim start

And reload Postfix configuration:

    # /etc/rc.d/postfix reload

DKIM Test page, useful for DKIM debug

Also, you can always use dig to see this record is correct on the DNS server:

    $ dig +short @ns.example.com mail._domainkey.example.com

DMARC

TODO

Part 4: Protecting against spam

Reputation Block Lists (DNS Block Lists)

Reputation Block Lists, or RBLs, records spammers IP addresses so legit mail servers (like ours!) can protect against spammers. Although they are also called DNS Block Lists, they don't use domain addresses, but only IP addresses instead.

To query a block list you don't need any special tool besides your normal DNS tools. We are going to use the host command.

host command man page

The block lists works the following way: you query, via DNS, for the reverse IP of the probable spammer.

There are several block lists. We are going to use b.barracudacentral.org. They ask you to register your IP address (or the IP address of your mail server) but I could query it without having to register myself first,

Barracuda Central website

Once I received an spam from a e-mail server with IP 89.46.222.110. To check whether or not it is listed in the block list, reverse the IP address and prepend it to the block list address. Then, issue a DNS request:

    $ host 110.222.46.89 b.barracudacentral.org
    110.222.46.89.b.barracudacentral.org has address 127.0.0.2

It returned IP address 127.0.0.2. It means the IP is listed on the block list.

To add this check in postfix, add the following option to /etc/postfix/main.cf:

    smtpd_client_restrictions = reject_rbl_client b.barracudacentral.org

Or just append it to the smtpd_client_restrictions if you have other options as well.

Spam filtering

TODO

Part 5: Not necessary (but useful) things

Using recipient_delimiter

Posfix and Dovecot have a powerful feature that can be set with variable recipient_delimiter. You can setup a character to that text between the character and @ is stripped before delivering the e-mail. It is useful for address of the form::

    foo+bar@example.com

If recipient_delimiter is +, the e-mail will be delivered to user foo@example.com. I use it mainly to sign up to sites (e.g. foo+sitename@example.com), so I know if some of them is selling my e-mail to spammers, but it can be used to anything you can imagine:

recipient_delimiter example: delivering the e-mail to folders which name is the same name of the part that was stripped

To enable that, configure recipient_delimiter in /etc/postfix/main.cf. If you are using Dovecot virtual users, don't forget to uncomment or add the recipient_delimiter variable in /usr/pkg/etc/dovecot/conf.d/15-lda.conf. Make sure the delimiter character for both postfix and dovecot configuration are the same.

Implementing the "snooze" feature

Some e-mail providers or plugin developers offer the "snooze" feature for e-mail: you can "set" your e-mail to hide in your INBOX and it will return at a specific time later.

It is useful if you have a workflow like mine: I let all pending question in my INBOX but I will do some of them some days later. I don't want them to clutter my INBOX, though.

Unfortunatelly solutions focus on specific products. So, we just use doveadm move . We first create a set of boxes that we want to use as "temporary boxes": "1d", "2d", "3d", etc. For instance, if I move an e-mail to the "7d" box, the script will return it back to the INBOX 7 days later.

doveadm move manual

It can be implemented easily with a shell script. See:

    #!/bin/sh
    doveadm -v move -u user INBOX mailbox 1d all
    doveadm -v move -u user 1d mailbox 2d all
    doveadm -v move -u user 2d mailbox 3d all
    doveadm -v move -u user 3d mailbox 4d all
    doveadm -v move -u user 4d mailbox 5d all
    doveadm -v move -u user 5d mailbox 6d all
    doveadm -v move -u user 6d mailbox 7d all
    doveadm -v move -u user 7d mailbox 14d savedbefore 6days
    doveadm -v move -u user 14d mailbox 30d savedbefore 15days
    doveadm -v move -u user 30d mailbox 60d savedbefore 30days
    doveadm -v move -u user 60d mailbox 90d savedbefore 30days

Then I add it to the crontab:

    0 3 * * * $HOME/do_snooze.sh

So it runs every day, at 3 AM.

Basically it just moves an e-mail from "7d" to "6d" and then "6d" to "5d" the next time the script is runs. The order is very important. If it was reverted, on the same execution the e-mail would be moved from "7d" to "6d", then from "6d" to "5d" and we want this to happen only once per day.

Other mailboxes, "14d", "30d" and "60d" don't have intermediate boxes to rely on, so we use another feature ("savedbefore") that takes in account the time the e-mail was saved in the box.

This solution has disadvantages: it is not possible to exactly know in what box the e-mail is (unless you implemented a search feature that you can run from a web client, for example), but it is a simple solution and it works regardless of the client.

E-mail rewriting with Postfix

Postfix has advanced features, including some for e-mail rewriting. Those include cleanup methods, header_checks, milters and more.

cleanup man page

header_checks

i'm now aware of everything nor the details of them. My need is a bit strange: I have different accounts (can be different or the same domain) for this Postfix server. My personal account and other accounts for mail list discussion. I don't want to use my personal account for mail list, but I also don't want to setup different accounts in my MUA: I want to receive everything in my personal account and I want to answer them from my personal account as well, but, when answering the e-mail, I want it to be rewritten as it appears to have been sent from another account.

an example: my personal e-mail is alice@example.com, but I also have foolist@example.com to send and receive e-mails to and from the Foo project.

postfix people on IRC channel at freenode recommend me not to do that and told me setting up this configuration on MUA was a better choice. I insisted, though.

let me first show you what I've tried but it didn't work:

aliases
Setting up a Postfix alias (foolist@example.com to alice@example.com) would work at the receiving part, but not for sending, since it translates foolist@example.com to alice@example.com, but not the other way around.
header_checks
header_checks looks great and can indeed rewrite headers (writting headers, not the body, is enough), but I soon discovered that header_checks rules don't "save state" between lines, i.e., I cannot mix on the same rule both To: and From: headers.

Conditionally rewriting From and Reply-To headers in Postfix

After some researching, I decided to investigate how to write a milter and came across this page:

Postfix After-Queue Content Filter

This page has examples on how to make your own filter for rewriting an e-mail. anyway, I'm going to describe what I did.

first, in /etc/postfix/master.cf, add a new line for your filter::

    myrewrite unix  -       n       n       -       10      pipe  flags=Rq user=myrewrite null_sender= argv=/home/myrewrite/myrewrite.sh -f ${sender} -- ${recipient}

I called it "myrewrite". As recommended, I created a dedicated user for this also called "myrewrite".

now, still at /etc/postfix/master.cf, you should add a -o option to the server that you want it to call the myrewrite.sh script. The Postfix After-Queue Content Filter guide appends it to the smtp server, but since we want it to run just after we send it, we are going to append it to submission:

    submission inet ... do not change what comes here ...
      ... nor on lines within ...
      -o content_filter=myrewrite:dummy

We added the last line. Please, *note there exists two white space before -o*, meaning that we are appending it to the line just before.

Also, don't forget to create new account (for the foolist@example.com e-mail), no matter if you are using local Unix accounts, Postfix vmaps or Dovecot accounts.

Let's finally take a look at the myrewrite.sh script:

    #!/bin/sh

    # Based on example from http://www.postfix.org/FILTER_README.html

    SENDMAIL="/usr/sbin/sendmail -G -i"

    # E-mails we want to detect in "To:" header to enable rewritting.
    to_field='maillist@project.org'

    # Translation rules for e-mail
    from_field_old='alice@example.com'
    from_field_new='foolist@example.com'

    # Temporary mail file.
    mailfile="/var/spool/myrewrite/mail.$(date +%Y%m%d%H%M%S).$$"

    # Clean up when done or when aborting.
    trap "rm -f $mailfile" 0 1 2 3 15

    # Does the rewritting
    rewrite ()
    {
            # Awk script that does the actual rewrite.  We pass variables defined in
            # this shell script to awk variables.
            awk -v from_field_old="$from_field_old" \
                -v from_field_new="$from_field_new" \
                    '
                    BEGIN {
                            # We start on headers
                            on_headers = 1
                    }

                    /^[^[:space:]]+:/ && on_headers == 1 {
                            # While we are on headers region, let the header
                            # variable holds the name of the current header.  It
                            # also works for multiline headers.
                            split($0, fields, ":")
                            header = fields[1]
                    }

                    /^$/ {
                            # An empty line marks the beggining of the body part of
                            # the message.  We are not in headers region anymore.
                            on_headers = 0
                            header = ""
                    }

                    {
                            # For every line (header and body) do:
                            line = $0

                            # Replace the old e-mail for the new e-mail
                            gsub(from_field_old, from_field_new, line)

                            # Remove "DKIM-Signature" header (i.e., dont print it).
                            # This is specially important for two reasons:
                            #
                            # 1. We are going to rewrite the e-mail (body included)
                            #    so the signature changes.  The signature just added
                            #    is not valid anymore.
                            #
                            # 2. When sending the modified e-mail using sendmail,
                            #    another DKIM-Signature will be appended (specially
                            #    if we are using another domain).  If we dont
                            #    remove the first one, the e-mail will have two
                            #    DKIM-Signature.
                            if (header == "DKIM-Signature")
                                    next

                            # Finally, print the modified line.
                            print line
                    }
            ' "$mailfile" > "$mailfile.tmp"
            mv "$mailfile.tmp" "$mailfile"
    }

    # Writting the receiving e-mail to the "$mailfile" file.
    cat >"$mailfile"

    # Rewritting is disabled by default
    rewrite=no

    # If we see we want to send to "$to_field", rewrite
    grep -q "^To: .*$to_field" "$mailfile" && rewrite=yes

    # If rewritting is enabled
    if [ "$rewrite" = 'yes' ]; then
            # Rewrite the e-mail
            rewrite

            # The following changes the option list ("$@") to replace
            # $from_field_old for $from_field_new.  Rewritting headers is not enough
            # because the "Return-Path" header is writting using the value passed to
            # ``-f`` flag of sendmail.
            set -- $(echo "$@" | sed "s/$from_field_old/$from_field_new/g")
    fi

    # Finally, send it.
    $SENDMAIL "$@" < "$mailfile"

    exit $?

This is a simple and useful script.

References

The important references cited in this guide are:

Home mail server with Postfix + Dovecot on NetBSD

Postfix with SASL

How to build a virtual user e-mail server

Why your Marketing Email will Land in my Spam Folder

How To Install and Configure DKIM with Postfix on Debian Wheezy

Setup DKIM (DomainKeys) for Ubuntu, Postfix and Mailman - Ask Ubuntu

Books:

[HILDEBRANDT2005]
The Book of Postfix, 2005, by Ralf Hildebrandt and Patrick Koetter

Other resources:

Very good discussion with experts about standards for an SMTP server match the global email infrastructure postfix for 2 domains on 1 vps 1 ip