website

website.git
git clone git://git.lenczewski.org/website.git
Log | Files | Refs

selfhosted-mail.html (40500B)


      1 <h2>Summary</h2>
      2 <p>
      3   I think that E-Mail is very much taken for granted nowadays, with how easy it
      4   is to get access to, and with how much people use it every day. Now, most people
      5   will interact with email via an online portal created by the email provider,
      6   or via a third-party app that talks with the servers operated by said provider.
      7   I, unfortunately, am not as sane as these people, and so want to bring as much of
      8   the infrastructure I use on a daily basis under my own control as possible. This
      9   post attempts to document my journey of setting up a relatively basic mail server
     10   using Postfix and Dovecot.
     11 </p>
     12 
     13 <hr />
     14 
     15 <div id="intro">
     16   <h3>Is this for Me?</h3>
     17   <p>
     18     I would love to say "YES", and claim that setting up a mail server is something
     19     that anyone and everyone can and should be doing. I choose to believe that
     20     having control over your data and the code that operates on said data is
     21     valuable. That does not mean that it is the cheapest, simplest, and most
     22     maintenance-free option available. After all, GMail is free, any web-based
     23     mail is as simple as clicking on a URL, and all serious email providers by
     24     necessity have a hands off experience that a child can use (a more accessible
     25     UI means a wider user-base).
     26   </p>
     27   <p>
     28     I chose to set my own mail server up for two reasons: I want to learn how
     29     mail works, and I want to own my infrastructure. I am able to do this because
     30     I have spare compute (in the form of a Raspberry Pi 4B), and because I am
     31     willing to put in the time required to fix and maintain this system if
     32     it breaks. It is not "mission critical" to me. Therefore if this sounds like
     33     you, and you would also like to set up your own mail server, I would encourage
     34     you to do so!
     35   </p>
     36 </div>
     37 
     38 <hr />
     39 
     40 <div id="links">
     41   <h3>Links and References</h3>
     42   <p>
     43     Much of the work I did is built on that of those who have done this thing before.
     44     Below, I have tried to compile a list of the links that I used during my setup:
     45   </p>
     46   <ul>
     47     <li>
     48       <span><a href="https://wiki.archlinux.org/title/Mail_server">Arch Wiki Mail Server</a></span>
     49       overview post. Gives an example of some of the software alternatives you can
     50       use when setting up your own mail server.
     51     </li>
     52     <li>
     53       <span><a href="https://wiki.gentoo.org/wiki/Complete_Virtual_Mail_Server">Gentoo Wiki Virtual Mail Server Article</a></span>,
     54    giving an overview of how to set up a mail server with a web-based management
     55       console, and a database backend.
     56     </li>
     57     <li>
     58       <span><a href="https://www.c0ffee.net/blog/mail-server-guide/">c0ffee's mail server guide</a></span>,
     59       an excellent overview of how to set up postfix and dovecot on FreeBSD. Also
     60       implements some additional services (LDAP for unified user authentication
     61       with the mail server, and some text search / mail filtering services).
     62     </li>
     63     <li>
     64       <span><a href="https://www.postfix.org/TLS_README.html">Postfix TLS documentation</a></span>,
     65       necessary when trying to secure our smtp and submissions services.
     66     </li>
     67     <li>
     68       <span><a href="https://doc.dovecot.org/2.3/configuration_manual/howto/simple_virtual_install/">Minimal Dovecot install</a></span>,
     69       without SSL and using a static configuration for mail users. Not directly
     70       useful (due to the lack of encryption), but the static configuration is
     71       something I tend to prefer. More generally, the <span><a href="https://doc.dovecot.org/2.3/">Dovecot docs</a></span> are
     72       a good resource when trying to fix a broken install (if verbose).
     73     </li>
     74     <li>
     75       <span><a href="https://dmarc.org/overview/">DMARC overview</a></span>,
     76       explains what fields exist in a DMARC record and what they mean.
     77     </li>
     78     <li>
     79       <span><a href="https://arminreiter.com/2022/01/create-your-own-certificate-authority-ca-using-openssl/">Armin Reiter's CA certificate tutorial</a></span>. A simple setup, simply explained.
     80     </li>
     81     <li>
     82       <span><a href="https://jamielinux.com/docs/openssl-certificate-authority/introduction.html">Jaime Nguyen's CA tutorial</a></span>,
     83       for a more complex (and realistic) setup. Also excellently explained!
     84     </li>
     85   </ul>
     86 </div>
     87 
     88 <hr />
     89 
     90 <div id="theory">
     91   <h3>What are we Actually Trying to do?</h3>
     92   <p>
     93     So we want to set up our own email, how do we go about this? E-Mail is broken
     94     down into largely three independent parts:
     95   </p>
     96   <ol>
     97     <li>software that sends and receives email - i.e. postfix</li>
     98     <li>software that manages and "delivers email" to an electronic mailbox - i.e. dovecot</li>
     99     <li>software that lets you interact with your mailbox - i.e. your email client</li>
    100   </ol>
    101 
    102   <aside>
    103     <small>
    104       Note: in more formal terminology, the software that sends and receives email is
    105       called a <span><a href="https://en.wikipedia.org/wiki/Message_transfer_agent">mail transfer agent</a></span>,
    106       the software that manages the mailbox (in other words, "delivers" mail to the
    107       mailbox) is called a <span><a href="https://en.wikipedia.org/wiki/Mail_delivery_agent">mail delivery agent</a></span>,
    108       and the software that lets you interact with your own mailbox is called a
    109       <span><a href="https://wiki.archlinux.org/title/Email_client">mail user agent</a></span>.
    110     </small>
    111   </aside>
    112 
    113   <p>
    114     So, in order to have our own mail server, we will need to install a MTA and
    115     configure it such that it can talk to other MTAs and both send mail to and
    116     receive mail from them, we will need to install a MDA to deliver any mail
    117     received by our MTA to our mailbox, and finally we will need to setup our
    118     email client to talk to both the MTA (to allow us to send emails we compose)
    119     and the MDA (to fetch and display the contents of our mailbox).
    120   </p>
    121   <p>
    122     As mentioned above, for our MTA we will install postfix, and for our MDA we
    123     will install dovecot. We choose these because they seem fairly common, and
    124     have decent enough configuration options. For the email client, I personally
    125     use mutt on my desktop and laptops, and thunderbird on my Android phone, and
    126     will show the minimal configuration necessary for those.
    127   </p>
    128 
    129   <h3>Our Configuration</h3>
    130   <p>
    131     Behold, a pictoral representation of our desired setup. I will explain later,
    132     but a picture is worth a thousand words anyway:
    133   </p>
    134 
    135   <div class="container">
    136     <img src="/res/blog/mail-layout.svg" alt="Our Proposed E-Mail Server Setup" />
    137   </div>
    138 
    139   <p>
    140     So, explanation time. Postfix is our MTA, and so holds both a SMTP client for
    141     sending our mail to external mail servers, and a SMTP server to receive
    142     incoming mail from external servers and our mail clients. Traditionally,
    143     SMTP servers ran over port 25, port 465, or port 587. Port 25 was used for
    144     unencrypted traffic, port 465 for traffic implicitly encrypted with SSL/TLS,
    145     and port 587 was for initially unencrypted traffic that was upgraded using
    146     the STARTTLS command. We will definitely not use STARTTLS due to it being
    147     an upgrade protocol (hence, starting in cleartext) and having known STARTTLS
    148     stripping attacks (see <a href="https://www.usenix.org/conference/usenixsecurity21/presentation/poddebniak">this presentation</a>,
    149     for example). Additionally, since <a href="https://www.rfc-editor.org/rfc/rfc8314.html#section-1.1">RFC8314</a>,
    150     the IETF has designated TCP port 465 as the "submissions" port for implicit
    151     TLS. But this leaves us with the question of using port 25.
    152   </p>
    153   <p>
    154     In a perfect world, we would encrypt all the things, and it would be cheap,
    155     and no-one could every spy on our traffic, and it would all be sunshine and
    156     rainbows. Unfortunately, we do not live in such a world. As such, we have to
    157     accept that despite client-to-server SMTP traffic over port 465 being encrypted,
    158     server-to-server SMTP traffic over port 25 can at best use STARTTLS. This is
    159     unavoidable, as we very much want other email servers to talk to us. After all,
    160     what use is a mail service that only ever sends email? So, we have to keep
    161     both open. Hence, our setup has SMTP over port 25 secured with STARTTLS,
    162     and submissions over port 465, secured with TLS1.3.
    163   </p>
    164   <aside>
    165     <small>
    166       Note: some online sources will tell you that port 465 is somehow "less secure"
    167       than port 587. How any port can be considered less secure than another one
    168       is anyones guess. Seeing as STARTTLS has known issues, and any TLS version
    169       less than TLS1.3 should not be used with security in mind, I will ignore
    170       such recommendations.
    171     </small>
    172   </aside>
    173 
    174   <p>
    175     Of note is the addition of OpenDKIM as a mail filter. This will allow us to
    176     sign outgoing mail with our DKIM keys, allowing external servers to verify
    177     that our mail comes from the correct domain and reducing the chance it is
    178     marked as spam. This is internal to our mail server, and communication
    179     will be done over a unix socket.
    180   </p>
    181 
    182   <p>
    183     Next, our MDA, dovecot. Dovecot will accept incoming mail received by postfix
    184     over the LMTP protocol (literally "Local Mail Transfer Protocol"), but this
    185     is internal to the mail server. However, it will still have to talk to external
    186     mail clients to send them the contents of our mailbox, which is typically done
    187     using the IMAP(S) protocol. Traditionally, port 143 and port 993 have both
    188     been used with port 143 sometimes being unencrypted and sometimes being
    189     encrypted with TLS, while port 993 had tended to be encrypted with SSL.
    190     Technically, TLS <em>is</em> SSL (<a href="https://tls12.xargs.org/#client-hello/annotated">a newer version</a>,
    191     but still internally treated as SSLv3), and so we will use IMAPS over port 993,
    192     again secured with TLS1.3, to avoid any confusion.
    193   </p>
    194 
    195   <p>
    196     Finally, mail clients. I will not talk too much about this, because this is
    197     as much personal preference as anything else. Note that not every mail client
    198     supports every mail server configuration. In particular, using client certificates
    199     seems unsupported or unimplemented in pretty much every mainstream mail client
    200     I have looked at. Granted, Outlook, Gmail, and Apple's Mail are not a massive
    201     sample size, but I believe that they are representative of what most users
    202     will run. As stated in the introduction, I will only cover my use case, which
    203     is mutt for desktop use, and thunderbird for android for mobile use. Your
    204     mileage may vary on this setup.
    205   </p>
    206 </div>
    207 
    208 <hr />
    209 
    210 <div id="preparation">
    211   <h3>Preparation</h3>
    212   <p>
    213     First thing to do is to create a user for our mail system. I personally
    214     dislike running a full-blown database for such small setups, especially on
    215     lower-end devices. We will keep our mail server using static configuration
    216     files as much as possible becase 1) this is easier, 2) it is faster and
    217     less resource hungry than spinning up a database engine, and 3) it gives you
    218     all the flexibility you would need for a personal mail server anyway. We must
    219     also set up the DNS records for our domain, to point to our mail server, and
    220     to set up both DMARC and SPF records.
    221   </p>
    222 
    223   <h4>Creating our vmail User</h4>
    224   <p>
    225     As root, perform the following commands:
    226   </p>
    227 
    228 <pre><code class="language-text">$ groupadd -g 5000 vmail
    229 $ useradd -u 5000 -g 5000 -d /var/vmail -M vmail
    230 $ mkdir /var/vmail
    231 $ chown vmail:vmail /var/vmail
    232 $ chmod 2770 /var/vmail
    233 </code></pre>
    234 
    235   <p>
    236     We need to create our vmail user and group. We don't necessarily want the
    237     <code>/var/vmail</code> directory to include any dotfiles (e.g. <code>.bashrc</code>,
    238     <code>.bash_profile</code>) so we create the home directory ourselves.
    239     Because we make it ourselves, we then need to ensure it is owned by the
    240     vmail user, and set its permissions. Instead of the traditional 0755 permission
    241     octal, we choose 2770: 2 meaning that the directory has the setgid access flag,
    242     the first 7 meaning the owner has read, write, and execute permissions, the
    243     next 7 meaning that all users with the same group have the same permissions,
    244     and the final 0 meaning that any other users have no access.
    245   </p>
    246 
    247   <aside>
    248     <small>
    249       Note: the setgid permission is interesting, in that it allows any user with
    250       access to a given directory to perform operations as though they were in
    251       the same group as the owner of the directory. This is similar to the setuid
    252       permission, where the operations are performed as though the user was the
    253       owner of the directory.
    254     </small>
    255   </aside>
    256 
    257   <h4>Creating our Local CA, and our User Certificates</h4>
    258   <p>
    259     To generate per-user certificates, we need to create a root "CA" certificate.
    260     I use the below script to do this:
    261   </p>
    262   <pre><code class="language-bash">#!/bin/sh
    263 
    264 # TODO: replace this with your domain
    265 DOMAIN="example.com"
    266 
    267 # password for out .p12 PCKS certificate file
    268 PCKSPASS="password"
    269 
    270 # TODO: replace with all services you want certificates for
    271 SERVICES="postfix dovecot"
    272 
    273 # TODO: replace will all users you want personal certificates for
    274 USERS="john"
    275 
    276 set -ex
    277 
    278 # certificate authority
    279 # NOTE: this is not how you should do a proper CA. really, the certificate we
    280 #       generate here should be help in an airgapped system, and a secondary
    281 #       certificate should be generated and signed by it. that way, this secondary
    282 #       certificate can be used to sign service / user certificates, and can be
    283 #       easily revoked if necessary (while our original, airgapped certificate
    284 #       is safe and cannot easily be leaked).
    285 if [ ! "${REFRESH_CA:-z}" = "z" ]; then
    286 	openssl ecparam -genkey -name prime256v1 -out ca.key
    287 	openssl req -x509 -new -key ca.key -sha512 -days 9999 -out ca.crt -subj "/CN=$DOMAIN"
    288 fi
    289 
    290 # service certificates
    291 mkdir -p services
    292 for serv in $SERVICES; do
    293 	mkdir -p services/$serv
    294 	openssl ecparam -genkey -name prime256v1 -noout -out services/$serv/ecc.key
    295 	openssl req -new -out services/$serv/ecc.csr -key services/$serv/ecc.key -subj "/CN=$DOMAIN/x500UniqueIdentifier=$serv"
    296 	openssl x509 -req -in services/$serv/ecc.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out services/$serv/ecc.crt -sha512 -days 365
    297 	cat services/$serv/ecc.crt services/$serv/ecc.key > services/$serv/ecc.$serv.pem
    298 
    299 	if [ ! "${SERVICE_RSA_CERTS:-z}" = "z" ]; then
    300 		openssl req -new -nodes -out services/$serv/rsa.csr -keyout services/$serv/rsa.key -newkey rsa:4096 -subj "/CN=$DOMAIN/x500UniqueIdentifier=$serv"
    301 		openssl x509 -req -in services/$serv/rsa.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out services/$serv/rsa.crt -sha512 -days 365
    302 		cat services/$serv/rsa.crt services/$serv/rsa.key > services/$serv/rsa.$serv.pem
    303 	fi
    304 done
    305 
    306 # user certificates
    307 mkdir -p users
    308 for user in $USERS; do
    309 	mkdir -p users/$user
    310 	openssl ecparam -genkey -name prime256v1 -noout -out users/$user/ecc.key
    311 	openssl req -new -out users/$user/ecc.csr -key users/$user/ecc.key -subj "/CN=$DOMAIN/emailAddress=$user@$DOMAIN/x500UniqueIdentifier=$user"
    312 	openssl x509 -req -in users/$user/ecc.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out users/$user/ecc.crt -sha512 -days 365
    313 	cat users/$user/ecc.crt users/$user/ecc.key > users/$user/ecc.$user.pem
    314 
    315   # we generate a pcks12 certificate to allow easy importing under android
    316 	openssl pkcs12 -export -out users/$user/ecc.$user.pfx.p12 -in users/$user/ecc.crt -inkey users/$user/ecc.key -passout "pass:$PCKSPASS"
    317 done</code></pre>
    318 
    319   <h4>Creating our DNS Records</h4>
    320   <p>
    321     Our mail server needs to be accessible over the web, and to do so we will
    322     edit our DNS zone on our DNS server (or on our DNS provider's
    323     online gateway). We will provide example records for BIND, but please adapt
    324     these to your chosen DNS server (or, the relevant fields and buttons in your
    325     DNS provider's web UI).
    326   </p>
    327 
    328   <p>
    329     At minimum, we need an MX record that will define a mail server for our
    330     domain, and an A/AAAA record to define the IP address of the mail server. Note
    331     that in the below examples, <code>x.y.z.w</code> refers to an IPv4 address,
    332     and <code>w.z.y.x</code> refers to the reversed form of the same address.
    333   </p>
    334 
    335   <pre><code class="language-text">example.com. IN MX 10 mail.example.com.
    336 mail.example.com. IN A x.y.z.w
    337 
    338 # Optionally, for IPv6 connectivity instead
    339 # mail.example.com. IN AAAA a:b:c:d:e:f:g:h
    340 
    341 # Optionally, can define a CNAME record to point mail.example.com to example.com instead of the A record
    342 # if our mail server is hosted on the same server as example.com
    343 # mail.example.com. IN CNAME example.com.</code></pre>
    344 
    345   <p>
    346     We must define a PTR record that allows a mail server to verify that the
    347     IP address an email is coming from is the one defined to send email (via its
    348     MX record). Not defining this PTR record means that you will be marked as
    349     spam, or simply rejected, by most email providers.
    350   </p>
    351 
    352   <pre><code class="language-text">w.z.y.x.in-addr.arpa. IN PTR example.com
    353 
    354 # If you chose to not alias your mail domain, use this instead
    355 # w.z.y.x.in-addr.arpa. IN PTR mail.example.com</code></pre>
    356 
    357   <p>
    358     The next record we must define is an SPF record for our root domain, to
    359     tell other mail servers how to accept mail for our domain. We want to only
    360     allow mail to be received from mail servers specified in our MX records,
    361     and any other mail should not be accepted (as it was not sent by us, and is
    362     spoofed).
    363   </p>
    364 
    365   <pre><code class="language-text">example.com. IN TXT "v=spf1 mx ~all"</code></pre>
    366 
    367   <p>
    368     After SPF, we must set up our DKIM record, which contains the public key that
    369     will be used by external mail recipients to verify mail sent from our email
    370     server. This is an anti-spam measure enforced by pretty much every mainstream
    371     mail provider, and so is pretty much necessary to set up. Note, that the actual
    372     value for this record will be given to us later when we set up OpenDKIM.
    373   </p>
    374 
    375   <pre><code class="language-text">mail._domainkey IN TXT ( "v=DKIM1; k=rsa; p=my/publickey+signature" )</code></pre>
    376 
    377   <p>
    378     Finally, we must set up DMARC, which is used to tell other mail servers what
    379     to do with email that fails the SPF or DKIM checks (and allows reports of
    380     such failures to be sent to a given address). This is again necessary for
    381     mail coming from our mail server to be marked as authenticated by mail providers.
    382   </p>
    383 
    384   <pre><code class="language-text">_dmarc.example.com. IN TXT "v=DMARC1;p=quarantine;ruf=mailto:forensic@example.com;rua=mailto:aggregate@example.com"</code></pre>
    385 </div>
    386 
    387 <hr />
    388 
    389 <div id="opendkim">
    390   <h3>OpenDKIM</h3>
    391   <p>
    392     DKIM (or, DomainKeys Identified Mail), is an anti-spam measure enforced by
    393     most mainstream email providers. It will sign all outgoing mail with a private
    394     key, whose corresponding public key is available via a DNS record.
    395   </p>
    396   <p>
    397     After installing the OpenDKIM package, you will need to modify the config
    398     file, possibly modify the postfix and opendkim users to allow for local
    399     unix socket access, and generate keys for your domain. The following
    400     <span><a href="https://wiki.gentoo.org/wiki/OpenDKIM">webpage</a></span> is
    401     what I followed to get a local unix socket, and this
    402     <span><a href="https://wiki.gentoo.org/wiki/Postfix/DKIM">webpage</a></span>
    403     is what I followed to actually create the keys and configure postfix. Your
    404     mileage may vary depending on your OS!
    405   </p>
    406   <p>
    407     My <code>/etc/opendkim/opendkim.conf</code> file is as follows:
    408   </p>
    409   <pre><code class="language-text"># This is a simple config file for signing and verifying
    410 
    411 #LogWhy                 yes
    412 Syslog                  yes
    413 SyslogSuccess           yes
    414 
    415 Canonicalization	relaxed/relaxed
    416 
    417 Domain			example.com
    418 Selector		mail
    419 KeyFile			/var/lib/opendkim/mail.private
    420 
    421 # To use a local socket instead, specify a path here. The "standard"
    422 # location is under /run/opendkim, and it's best to agree
    423 # on that directory so that various init systems can configure its
    424 # permissions and ownership automatically.
    425 Socket                 local:/run/opendkim/opendkim.sock
    426 
    427 #Socket                  inet:8891@localhost
    428 
    429 ReportAddress           postmaster@example.com
    430 SendReports             yes
    431 
    432 ## Hosts to sign email for - 127.0.0.1 is default
    433 ## See the OPERATION section of opendkim(8) for more information
    434 #
    435 # InternalHosts		192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
    436 
    437 ## For secondary mailservers - indicates not to sign or verify messages
    438 ## from these hosts
    439 #
    440 # PeerList		X.X.X.X
    441 
    442 # PidFile		/run/opendkim.pid
    443 
    444 # The UMask is really only used for the PID file (root:root) and the
    445 # local UNIX socket, if you're using one. It should be 0117 for the
    446 # socket.
    447 UMask			0117
    448 UserID			opendkim
    449 
    450 # For use with unbound
    451 #TrustAnchorFile	/etc/dnssec/root-anchors.txt</code></pre>
    452 </div>
    453 
    454 <hr />
    455 
    456 <div id="postfix">
    457   <h3>Postfix</h3>
    458   <p>
    459     Setting up postfix is relatively straightforward. Install the postfix package
    460     for your relevant distro. In my case, I enabled lmdb support to provide whatever
    461     "database" services postfix needed without resorting to a more heavyweight
    462     database engine.
    463   </p>
    464   <p>
    465     After installing postfix, we need to provide it two configuration files, a
    466     <code>/etc/postfix/main.cf</code> file defines settings for postfix itself,
    467     and a <code>/etc/postfix/master.cf</code> file that defines what services
    468     postfix provides (i.e. smtp(s), submission(s)).
    469   </p>
    470   <p>
    471     For informing postfix of the local email users (and their aliases!) we must
    472     first generate an "aliases" file. I chose to place this at
    473     <code>/etc/mail/aliases</code>. One potential aliases file can look as
    474     follows:
    475   </p>
    476   <pre><code class="language-text"># Basic system aliases -- these MUST be present.
    477 MAILER-DAEMON:      postmaster
    478 postmaster:         root
    479 
    480 # General redirections for pseudo accounts.
    481 adm:                root
    482 bin:                root
    483 daemon:             root
    484 exim:               root
    485 lp:                 root
    486 mail:               root
    487 named:              root
    488 nobody:             root
    489 postfix:            root
    490 
    491 # Well-known aliases -- these should be filled in!
    492 root:               john+root
    493 operator:           john+operator
    494 
    495 # dmarc report address
    496 report:             john+report
    497 
    498 # Standard RFC2142 aliases
    499 abuse:              postmaster
    500 ftp:                root
    501 hostmaster:         root
    502 news:               usenet
    503 noc:                root
    504 security:           root
    505 usenet:             root
    506 uucp:               root
    507 webmaster:          root
    508 www:                webmaster
    509 
    510 # trap decode to catch security attacks
    511 decode:             /dev/null
    512 
    513 # our actual users and their aliases
    514 john:             john</code></pre>
    515   <p>
    516     After creating this file for the first time, make sure to run the following
    517     command:
    518   </p>
    519   <pre><code class="language-text">$ newalias /etc/mail/aliases</code></pre>
    520   <p>
    521     Whenever you update the aliases file, you need to rerun the following,
    522     slightly different command:
    523   </p>
    524   <pre><code class="language-text">$ postalias /etc/mail/aliases</code></pre>
    525   <p>
    526     My <code>/etc/postfix/main.cf</code> file is as follows:
    527   </p>
    528   <pre><code class="language-text">mail_owner = postfix
    529 
    530 # TODO: change these to your real domain
    531 myhostname = mail.example.com
    532 mydomain = example.com
    533 
    534 myorigin = $mydomain
    535 
    536 #inet_interfaces = $myhostname, localhost
    537 inet_interfaces = 192.168.2.101, localhost
    538 inet_protocols = ipv4
    539 
    540 mydestination = $myhostname, $mydomain, localhost.$mydomain, localhost
    541 
    542 # our local user aliases we generated earlier
    543 alias_maps = lmdb:/etc/mail/aliases
    544 local_recipient_maps = $alias_maps
    545 
    546 address_verify_map = lmdb:$data_directory/verify_cache
    547 
    548 # reject mail
    549 unknown_local_recipient_reject_code = 550
    550 
    551 mynetworks_style = host
    552 # OR
    553 #mynetworks = 192.168.2.0/24, 127.0.0.0/8
    554 
    555 recipient_delimiter = +
    556 
    557 virtual_uid_maps = static:980
    558 virtual_gid_maps = static:980
    559 virtual_mailbox_base = /var/vmail
    560 
    561 # disable "new mail" notification for local users
    562 biff = no
    563 
    564 # reduce information given to snoopers, "NO UCE" can help with reducing spam
    565 smtpd_banner = $myhostname ESMTP NO UCE
    566 
    567 # require email addresses of form '<user>@<domain>.<tld>'
    568 allow_percent_hack = no
    569 swap_bangpath = no
    570 
    571 # dont allow spammers to use vrfy to scrape valid mail users
    572 disable_vrfy_command = yes
    573 
    574 # dont give information if mailbox does not exist
    575 show_user_unknown_table_name = no
    576 
    577 # maximum mta message size: 32 MiB
    578 # NOTE: we also set the maximum mailbox size to ensure we can receive the
    579 #       mta message, but otherwise we don't care about it because we will use
    580 #       dovecot for writing the mailbox
    581 message_size_limit = 33554432
    582 mailbox_size_limit = 33554432
    583 
    584 # opendkim
    585 smtpd_milters = unix:/run/opendkim/opendkim.sock
    586 non_smtpd_milters = $smtpd_milters
    587 
    588 # lmtp, we will set up dovecot in a second
    589 mailbox_transport = lmtp:unix:private/dovecot-lmtp
    590 
    591 # optional tls for sending mail, with optional DNSSEC verification
    592 smtp_tls_security_level = dane
    593 smtp_dns_support_level = dnssec
    594 # OR
    595 # optional tls for sending mail
    596 #smtp_tls_security_level = may
    597 
    598 # tls certificates to use when sending out mail
    599 # TODO: replace these with your ssl certificates
    600 smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
    601 smtp_tls_eccert_file = /root/certs/example.com/fullchain.pem
    602 smtp_tls_eckey_file = /root/certs/example.com/privkey.pem
    603 
    604 # tls settings for receiving mail, realistically this could be dropped to TLSv1.2
    605 smtpd_tls_mandatory_protocols = TLSv1.3
    606 smtpd_tls_mandatory_ciphers = high
    607 tls_ssl_options = no_ticket, no_compression
    608 
    609 # when receiving mail, require sender to use encryption
    610 smtpd_tls_security_level = encrypt
    611 
    612 # tls certificates for receiving mail
    613 # TODO: replace these with your ssl certificates
    614 smtpd_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
    615 smtpd_tls_eccert_file = /root/certs/example.com/fullchain.pem
    616 smtpd_tls_eckey_file = /root/certs/example.com/privkey.pem
    617 
    618 smtp_tls_loglevel = 1
    619 smtpd_tls_loglevel = 1
    620 
    621 #smtpd_tls_received_header = yes
    622 smtpd_tls_auth_only = yes
    623 
    624 # require that mail servers identify themselves to try to reduce spam
    625 smtpd_helo_required = yes
    626 
    627 # sasl, again we will set up dovecot in a second
    628 smtpd_sasl_type = dovecot
    629 smtpd_sasl_path = private/auth
    630 
    631 smtpd_sasl_local_domain=$mydomain
    632 
    633 broken_sasl_auth_clients = no
    634 
    635 smtpd_sasl_auth_enable = yes
    636 smtpd_sasl_authenticated_header = yes
    637 
    638 # don't allow plaintext auth methods on unencrypted connections
    639 smtpd_sasl_security_options = noanonymous, noplaintext
    640 
    641 # ... but plaintext auth is fine when using TLS
    642 smtpd_sasl_tls_security_options = noanonymous
    643 
    644 # mail restrictions, to limit spam and unauthorised connections
    645 
    646 smtpd_client_restrictions = permit_mynetworks,
    647 			   permit_sasl_authenticated,
    648 			   reject_unknown_reverse_client_hostname,
    649 			   reject_unauth_pipelining
    650 
    651 smtpd_helo_restrictions = permit_mynetworks,
    652 			 permit_sasl_authenticated,
    653 			 reject_invalid_helo_hostname,
    654 			 reject_non_fqdn_helo_hostname,
    655 			 reject_unauth_pipelining
    656 
    657 smtpd_sender_restrictions = permit_mynetworks,
    658 			   permit_sasl_authenticated,
    659 			   reject_non_fqdn_sender,
    660 			   reject_unknown_sender_domain,
    661 			   reject_unauth_pipelining
    662 
    663 smtpd_recipient_restrictions = permit_mynetworks,
    664 			      permit_sasl_authenticated,
    665 			      reject_non_fqdn_recipient,
    666 			      reject_unknown_recipient_domain,
    667 			      reject_unauth_pipelining,
    668 			      reject_unverified_recipient
    669 			      #reject_rbl_client zen.spamhaus.org,
    670 			      #reject_rbl_client bl.smapcop.net
    671 
    672 smtpd_relay_restrictions = permit_mynetworks,
    673 			  permit_sasl_authenticated,
    674 			  reject_unauth_destination
    675 
    676 smtpd_data_restrictions = permit_mynetworks,
    677 			 permit_sasl_authenticated,
    678 			 reject_multi_recipient_bounce,
    679 			 reject_unauth_pipelining</code></pre>
    680   <p>
    681     My <code>/etc/postfix/master.cf</code> file is as follows:
    682   </p>
    683   <pre><code class="language-text">#
    684 # Postfix master process configuration file.  For details on the format
    685 # of the file, see the master(5) manual page (command: "man 5 master" or
    686 # on-line: http://www.postfix.org/master.5.html).
    687 #
    688 # Do not forget to execute "postfix reload" after editing this file.
    689 #
    690 # ==========================================================================
    691 # service type  private unpriv  chroot  wakeup  maxproc command + args
    692 #               (yes)   (yes)   (no)    (never) (100)
    693 # ==========================================================================
    694 smtp      inet  n       -       n       -       -       smtpd
    695   -o smtpd_sasl_auth_enable=no
    696   -o smtpd_tls_ask_ccert=yes
    697 
    698 #submission inet n       -       n       -       -       smtpd
    699 #  -o syslog_name=postfix/submission
    700 #  -o smtpd_tls_req_ccert=yes
    701 
    702 submissions     inet  n       -       n       -       -       smtpd
    703   -o syslog_name=postfix/submissions
    704   -o smtpd_tls_wrappermode=yes
    705   -o smtpd_tls_req_ccert=yes
    706 
    707 pickup    unix  n       -       n       60      1       pickup
    708 cleanup   unix  n       -       n       -       0       cleanup
    709 qmgr      unix  n       -       n       300     1       qmgr
    710 #qmgr     unix  n       -       n       300     1       oqmgr
    711 tlsmgr    unix  -       -       n       1000?   1       tlsmgr
    712 rewrite   unix  -       -       n       -       -       trivial-rewrite
    713 bounce    unix  -       -       n       -       0       bounce
    714 defer     unix  -       -       n       -       0       bounce
    715 trace     unix  -       -       n       -       0       bounce
    716 verify    unix  -       -       n       -       1       verify
    717 flush     unix  n       -       n       1000?   0       flush
    718 proxymap  unix  -       -       n       -       -       proxymap
    719 proxywrite unix -       -       n       -       1       proxymap
    720 smtp      unix  -       -       n       -       -       smtp
    721 relay     unix  -       -       n       -       -       smtp
    722         -o syslog_name=${multi_instance_name?{$multi_instance_name}:{postfix}}/$service_name
    723 #       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
    724 showq     unix  n       -       n       -       -       showq
    725 error     unix  -       -       n       -       -       error
    726 retry     unix  -       -       n       -       -       error
    727 discard   unix  -       -       n       -       -       discard
    728 local     unix  -       n       n       -       -       local
    729 virtual   unix  -       n       n       -       -       virtual
    730 lmtp      unix  -       -       n       -       -       lmtp
    731 anvil     unix  -       -       n       -       1       anvil
    732 scache    unix  -       -       n       -       1       scache
    733 postlog   unix-dgram n  -       n       -       1       postlogd</code></pre>
    734 </div>
    735 
    736 <hr />
    737 
    738 <div id="dovecot">
    739   <h3>Dovecot</h3>
    740   <p>
    741     Dovecot is similarly straightforward to install and configure. After installing
    742     the dovecot package, we need to provide it with a single config file. However,
    743     since we want to have a simple password file as well, we must create that
    744     first.
    745   </p>
    746   <p>
    747     I chose to place my mail users' password file at <code>/etc/mail/users</code>.
    748     The contents of this file are similar to <code>/etc/shadow</code>, and
    749     user passwords can be added to it using the following command:
    750   </p>
    751   <pre><code class="language-text">$ echo "john:$(doveadm pw -p yourpassword)::::::" >> /etc/mail/users</code></pre>
    752   <p>
    753     Finally, we can finally create our final dovecot config.
    754   </p>
    755   <p>
    756     My <code>/etc/dovecot/dovecot.conf</code> is as follows:
    757   </p>
    758   <pre><code class="language-text">protocols = imap lmtp
    759 
    760 # debugging help
    761 #auth_verbose = yes
    762 #auth_debug = yes
    763 #mail_debug = yes
    764 #verbose_ssl = yes
    765 
    766 log_path = /var/log/dovecot.log
    767 
    768 dict {
    769   #quota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
    770 }
    771 
    772 # auth
    773 # ============================================================================
    774 
    775 disable_plaintext_auth = yes
    776 
    777 auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
    778 
    779 auth_username_format = %Ln
    780 auth_anonymous_username = anonymous
    781 
    782 auth_mechanisms = plain login
    783 
    784 # to support smtp clients who cannot present client certificates, but require
    785 # them for imap
    786 protocol !smtp {
    787   auth_ssl_require_client_cert = yes
    788 }
    789 
    790 # we will get the username from whatever client certificate was presented
    791 auth_ssl_username_from_cert = yes
    792 
    793 # NOTE: using auth cache means we MUST reload dovecot after changing passwords
    794 # NOTE: using auth cache requires different passdb driver
    795 #auth_cache_size = 10M
    796 #auth_cache_ttl = 300s
    797 #auth_cache_negative_ttl = 60s
    798 #auth_cache_verify_password_with_worker = yes
    799 
    800 # lets use a plaintext password file. really, we could omit this and only require
    801 # the client certificates, but this is a trivial additional layer of security that
    802 # is easy enough to swap out and might buy you some time if the certificate gets
    803 # leaked
    804 passdb {
    805   driver = passwd-file
    806   args = scheme=CRYPT username_format=%Ln /etc/mail/users
    807   # args = cache_key=%u:%w
    808 }
    809 
    810 # we must use our vmail user to store all mail for users
    811 userdb {
    812   driver = static
    813   args = uid=vmail gid=vmail home=/var/vmail/%Ln
    814 #  default_fields = quota_rule=*:storage=1G
    815 }
    816 
    817 # ssl
    818 # ============================================================================
    819 
    820 ssl = required
    821 
    822 # TODO: swap these out with your own tls certificates
    823 ssl_cert = &lt;/root/certs/example.com/fullchain.pem
    824 ssl_key = &lt;/root/certs/example.com/privkey.pem
    825 
    826 # NOTE: this is the root ca for our user certificates
    827 ssl_ca = &lt;/root/ca/ca.crt
    828 
    829 # TODO: do we actually care about verifying that our user certificates expired?
    830 ssl_require_crl = no
    831 
    832 ssl_client_require_valid_cert = yes
    833 ssl_verify_client_cert = yes
    834 
    835 # which certificate field to get the username from
    836 #ssl_cert_username_field = commonName
    837 #ssl_cert_username_field = emailAddress
    838 ssl_cert_username_field = x500UniqueIdentifier
    839 
    840 ssl_min_protocol = TLSv1.3
    841 ssl_cipher_list = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
    842 ssl_curve_list = X25519:prime256v1:secp384r1
    843 ssl_prefer_server_ciphers = yes
    844 ssl_options = no_ticket, no_compression
    845 
    846 # mail
    847 # ============================================================================
    848 
    849 mail_location = maildir:~/.maildir 
    850 
    851 namespace inbox {
    852   inbox = yes
    853   separator = /
    854 }
    855 
    856 mailbox_idle_check_interval = 30 secs
    857 
    858 # master
    859 # ============================================================================
    860 
    861 service imap-login {
    862   # NOTE: we do not expose our unsecured imap listener to our local (or wider) network
    863   inet_listener imap {
    864     address = 127.0.0.1, ::1
    865     port = 143
    866   }
    867 
    868   inet_listener imaps {
    869     port = 993
    870     ssl = yes
    871   }
    872 
    873   #service_count = 1
    874   #process_min_avail = 0
    875   #vsz_limit = $default_vsz_limit
    876 }
    877 
    878 service imap {
    879   #vsz_limit = $default_vsz_limit
    880   #process_limit = 1024
    881 }
    882 
    883 # here is the lmtp listener where postfix will deliver mail to
    884 service lmtp {
    885   unix_listener /var/spool/postfix/private/dovecot-lmtp {
    886     mode = 0666
    887     user = postfix
    888     group = postfix
    889   }
    890 }
    891 
    892 # here is where postfix will attempt to login via sasl
    893 service auth {
    894   # Postfix smtp-auth
    895   unix_listener /var/spool/postfix/private/auth {
    896     mode = 0666
    897     user = postfix
    898     group = postfix
    899   }
    900 }
    901 
    902 service auth-worker {
    903   #user = root
    904 }
    905 
    906 # mailboxes
    907 # ============================================================================
    908 
    909 namespace inbox {
    910   # These mailboxes are widely used and could perhaps be created automatically:
    911   mailbox Archive {
    912     special_use = \Archive
    913     auto = subscribe
    914   }
    915   mailbox Drafts {
    916     special_use = \Drafts
    917     auto = subscribe
    918   }
    919   mailbox Junk {
    920     special_use = \Junk
    921     auto = subscribe
    922   }
    923   mailbox Trash {
    924     special_use = \Trash
    925     auto = subscribe
    926   }
    927 
    928   # For \Sent mailboxes there are two widely used names. We'll mark both of
    929   # them as \Sent. User typically deletes one of them if duplicates are created.
    930   mailbox Sent {
    931     special_use = \Sent
    932     auto = subscribe
    933   }
    934   mailbox "Sent Messages" {
    935     special_use = \Sent
    936   }
    937 
    938   # If you have a virtual "All messages" mailbox:
    939   #mailbox virtual/All {
    940   #  special_use = \All
    941   #  comment = All my messages
    942   #}
    943 
    944   # If you have a virtual "Flagged" mailbox:
    945   #mailbox virtual/Flagged {
    946   #  special_use = \Flagged
    947   #  comment = All my flagged messages
    948   #}
    949 
    950   # If you have a virtual "Important" mailbox:
    951   #mailbox virtual/Important {
    952   #  special_use = \Important
    953   #  comment = All my important messages
    954   #}
    955 }</code></pre>
    956 </div>
    957 
    958 <hr />
    959 
    960 <div id="mutt">
    961   <h3>Configuring Mutt</h3>
    962   <p>
    963     I won't dwell too much on this, but the core setup that is necessary is
    964     the snippet below. Whether you include this directly in your
    965     <code>.muttrc</code>, or add this snippet in a per-user config, is up to
    966     you!
    967   </p>
    968   <pre><code class="language-text"># TODO: replace this with wherever you store the user certificate we generated
    969 #       earlier!
    970 set ssl_client_cert=/home/john/.config/mutt/ecc.john.pem
    971 
    972 # IMAP
    973 # ============================================================================
    974 
    975 set folder      = imaps://mail.example.com/
    976 
    977 set spoolfile	= +INBOX
    978 set postponed	= +Drafts
    979 set record	= +Sent
    980 
    981 # here you should add all mailboxes you added to your dovecot configuration
    982 mailboxes 	= +INBOX +Drafts +Sent
    983 
    984 # NOTE: this is not necessary, because we get our username from our client
    985 #       certificate. however, include it if you dont want to use such certificates
    986 set imap_user   = john
    987 
    988 # allow Mutt to open a new IMAP connection automatically
    989 unset imap_passive
    990 
    991 # keep the IMAP connection alive by polling intermittently (in seconds)
    992 set imap_keepalive = 300
    993 
    994 # how often to check for new mail (in seconds)
    995 set mail_check = 120
    996 
    997 # SMTP
    998 # ============================================================================
    999 
   1000 set smtp_authenticators = "login"
   1001 
   1002 set smtp_url	= smtps://$imap_user@mail.example.com/
   1003 
   1004 set realname	= "John Doe"
   1005 set from	= "john@example.com"</code></pre>
   1006 </div>
   1007 
   1008 <hr />
   1009 
   1010 <div id="thunderbird-android">
   1011   <h3>Configuring Thunderbird on Android</h3>
   1012   <p>
   1013     All that is required for setting up thunderbird is to first install the
   1014     previously generated CA certificate (or intermediate CA certificate, if using
   1015     a more proper approach) on your android device. Then, your user certificate
   1016     must also be installed, and has to be in the PCKS12 format for this:
   1017   </p>
   1018   <p>
   1019     To install both CA and user certificates, go to your android settings, and
   1020     search for "certificates". This page should show up. First install your CA
   1021     certificate (must be in .pem format), and then install your user certificate
   1022     (must be in .pfx.p12 format):
   1023   </p>
   1024   <div class="container">
   1025     <img src="/res/blog/android-15-cert-settings.png" alt="Android 15 Certificate Settings Page" />
   1026   </div>
   1027   <p>
   1028     After installing your certificates, continue with setting up thunderbird as
   1029     normal. Add a new account. Note that Thunderbird will not be able to lookup
   1030     your server's configuration the first time you press "Next", and will complain.
   1031     Just press "Next" again in this case to enter manual configuration:
   1032   </p>
   1033   <div class="container">
   1034     <img src="/res/blog/thunderbird-user-setup.png" alt="Thunderbird User Setup" />
   1035   </div>
   1036   <p>
   1037     Configure your "incoming", or imap, settings. This will allow you to read
   1038     mail sent to you. Make sure to your a "normal password", not any of the other
   1039     options, and select the user certificate we installed earlier:
   1040   </p>
   1041   <div class="container">
   1042     <img src="/res/blog/thunderbird-imap-setup.png" alt="Thunderbird IMAP Settings" />
   1043   </div>
   1044   <p>
   1045     Configure your "outgoing", or smtp, settings. This will allow you to send
   1046     mail. Make sure to again use "normal password", and provide the user certificate:
   1047   </p>
   1048   <div class="container">
   1049     <img src="/res/blog/thunderbird-smtp-setup.png" alt="Thunderbird SMTP Settings" />
   1050   </div>
   1051   <p>
   1052     Finally, enter your email address and choose what real name to give (and
   1053     optionally, what signature to have in all outgoing emails):
   1054   </p>
   1055   <div class="container">
   1056     <img src="/res/blog/thunderbird-final-setup.png" alt="Thunderbird Mail Settings" />
   1057   </div>
   1058   <p>
   1059     After this, your thunderbird client should be set up and working!
   1060   </p>
   1061 </div>
   1062 
   1063 <hr />
   1064 
   1065 <div id="conclusion">
   1066   <h3>Conclusion</h3>
   1067   <p>
   1068     That was how to set up a mail server. Hopefully this was useful to you!
   1069   </p>
   1070 </div>