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 = </root/certs/example.com/fullchain.pem 824 ssl_key = </root/certs/example.com/privkey.pem 825 826 # NOTE: this is the root ca for our user certificates 827 ssl_ca = </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>