Extended Brain Storage

OpenBSD: Postfix

Posted on September 21, 2016

The installation and configuration steps of Postfix with LDAP support in OpenBSD...


E-mail messages submitted by a mail user agent (MUA) are received by a message (or mail) submission agent (MSA), which is a software that cooperates with a mail transfer agent (MTA) for message delivery. Messages can be exchanged among several MTAs, hence the term mail exchangers (MX), before being delivered to a message (or mail) delivery agent (MDA), sometimes also called a local delivery agent (LDA). The messages can be fetched or retrieved either by a computer program called a mail retrieval agent (MRA), e.g. fetchmail, or directly by another computer application called a mail user agent (MUA).

Considering these various agents and distinguishing between push steps (->) and pull steps (=>), a detailed message flow can be depicted as:

MUA -> MSA -> MTA -> ... -> MTA -> MDA => MRA => MUA

Today, the functions of MSA and MTA are usually handled by the same software (SMTP server), and pretty much the same counts for MRA and MUA (e-mail client). For more details, please refer to mutt's Mailconcept page.

In order to allow SMTP servers to communicate, Simple Mail Transfer Protocol (SMTP) was designed and defined in RFC821 and RFC5321. For that purpose, TCP port 25 is used.

E-mail clients get messages from the LDA using Internet Message Access Protocol (IMAP) or Post Office Protocol (POP) and submit messages to the SMTP server using one of the following protocols:

To protect message submission integrity and privacy, SMTP servers use the Opportunistic TLS also known as STARTTLS.


OpenSMTPD service disabling:

$ rcctl stop smtpd
$ rcctl disable smtpd

Postfix installation (3.2 version with SASL and LDAP support):

$ pkg_add postfix
quirks-2.367 signed on 2017-10-03T11:21:28Z
Ambiguous: choose package for postfix
a       0: <None>
        1: postfix-3.2.2
        2: postfix-3.2.2-ldap
        3: postfix-3.2.2-mysql
        4: postfix-3.2.2-pgsql
        5: postfix-3.2.2-sasl2
        6: postfix-3.2.2-sasl2-ldap
        7: postfix-3.2.2-sasl2-mysql
        8: postfix-3.2.2-sasl2-pgsql
        9: postfix-3.3.20170910
        10: postfix-3.3.20170910-ldap
        11: postfix-3.3.20170910-mysql
        12: postfix-3.3.20170910-pgsql
        13: postfix-3.3.20170910-sasl2
        14: postfix-3.3.20170910-sasl2-ldap
        15: postfix-3.3.20170910-sasl2-mysql
        16: postfix-3.3.20170910-sasl2-pgsql
Your choice: 6
-> Creating /etc/mailer.conf.postfix
-> Creating Postfix spool directory and chroot area under /var/spool/postfix
-> Creating Postfix data directory under /var/postfix

| The existing configuration files in /etc/postfix have been preserved.
| You may want to compare them to the current sample files,
| /usr/local/share/examples/postfix, and update your configuration as needed.

postfix-3.2.2-sasl2-ldap: ok
The following new rcscripts were installed: /etc/rc.d/postfix
See rcctl(8) for details.
--- +postfix-3.2.2-sasl2-ldap -------------------
Postfix can be set up to replace sendmail entirely. Please read the
documentation at file:///usr/local/share/doc/postfix/html/index.html or
http://www.postfix.org/ carefully before you decide to do this!

To replace sendmail with Postfix you have to install a new mailer.conf
using the following command:


If you want to restore sendmail, this is done using the following command:


OpenBSD comes not only with its own MTA, but also own version of sendmail. Usage of Postfix version can be activated in the /etc/mailer.conf file using the following command:

$ /usr/local/sbin/postfix-enable

Should it be required, the /etc/mailer.conf settings can be reverted by running:

$ /usr/local/sbin/postfix-disable

Proper DNS Setup

For proper function of an MTA, the DNS records needs to be registered with the respective registrar. For this purpose, let's assume that the server.domain.tld server exists in the DNS and translates to a public IPv4 address (EGRESS_IP):

$ dig +noall +answer domain.tld -t MX
domain.tld.		1434	IN	MX	10 server.domain.tld.
$ dig +noall +answer server.domain.tld -t A
server.domain.tld.	1434	IN	A	EGRESS_IP

In OpenBSD, the domain name of the (e-mail) server appliance should be also specified in the /etc/myname file as follows:

$ echo "server.domain.tld" > /etc/myname

Unprivileged User Setup

Postfix does not need to run with superuser's privileges. Therefore, another system user (e.g. named vmail) should be created in order to manage e-mails for all virtual users:

$ useradd -m -g =uid -c "Virtual Postfix user" -d /var/vmail -s /sbin/nologin vmail

Verification of the previous command can be done by (the UID:GID numbers may differ):

$ grep vmail /etc/passwd
vmail:*:1001:1001:Virtual Postfix user:/var/vmail:/sbin/nologin

The home directory (/var/vmail) is used to store virtual users maildir folders, and is entirely managed by the MDA/LDA. A typical open source example of an LDA is Dovecot. From an MTA to an MDA/LDA, e-mails are usually delivered via Local Mail Transfer Protocol (LMTP). The content of the vmail folders can be shown as follows:

$ ls -lA /var/vmail

Users and Domains Setup

Considering an already configured LDAP storage and TLS certificates availability (either obtained using ACME or otherwise), the main configuration of Postfix can be done as follows (the values of: mynetworks, EGRESS_IP, myhostname, mydomain, smtpd_tls* need to be changed accordingly as well as the UID:GID numbers need to be aligned with those produced by the grep vmail /etc/passwd command):

$ vi /etc/postfix/main.cf
#mynetworks_style = host
mynetworks =, EGRESS_IP/32, [::ffff:]/104, [::1]/128
myhostname = server.domain.tld
mydomain = domain.tld
myorigin = $mydomain
mydestination = localhost localhost.$mydomain
alias_maps = hash:/etc/postfix/aliases
alias_database = hash:/etc/postfix/aliases
inet_interfaces = all
home_mailbox = Maildir/
## Virtual domain config
virtual_mailbox_domains = ldap:/etc/postfix/ldap-virtual_mailbox_domains.cf
virtual_mailbox_base = /var/vmail
virtual_mailbox_maps = ldap:/etc/postfix/ldap-virtual_mailbox_maps.cf
## These UID:GID numbers need to be aligned with those produced by "grep vmail /etc/passwd"
virtual_minimum_uid = 1001
virtual_uid_maps = static:1001
virtual_gid_maps = static:1001
virtual_alias_maps = ldap:/etc/postfix/ldap-virtual_alias_maps.cf
### TLS
#### Postfix SMTP client (http://www.postfix.org/postconf.5.html#smtp_tls_security_level)
smtp_use_tls = yes
smtp_tls_loglevel = 1
smtp_tls_security_level = may
smtp_tls_key_file = /etc/ssl/private/rohlix.eu.key
smtp_tls_cert_file = /etc/ssl/rohlix.eu.fullchain.pem
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_ciphers = high
smtp_tls_secure_cert_match = nexthop, dot-nexthop
smtp_tls_verify_cert_match = hostname, nexthop, dot-nexthop
#### Postfix SMTP server (http://www.postfix.org/postconf.5.html#smtpd_tls_security_level)
smtpd_use_tls = yes
smtpd_tls_loglevel = 1
#smtpd_tls_security_level = encrypt
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_key_file = /etc/ssl/private/rohlix.eu.key
smtpd_tls_cert_file = /etc/ssl/rohlix.eu.fullchain.pem
smtpd_tls_dh1024_param_file = /etc/postfix/dh4096.pem
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_ciphers = high
smtpd_tls_eecdh_grade = ultra
#### LMTP
lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
lmtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
lmtp_tls_mandatory_ciphers = high
tls_high_cipherlist = TLSv1.2:TLSv1.3:!CAMELLIA:!ARIA:!DSS:!ADH:!PSK:!RSA:!ECDHE-RSA-AES256-SHA384:!ECDHE-RSA-AES128-SHA256:!DHE-RSA-AES256-SHA256:!DHE-RSA-AES128-SHA256
#tls_high_cipherlist = TLSv1.2:TLSv1.3:!CAMELLIA:!ARIA:!DSS:!ADH:!PSK:!RSA:!ECDHE-RSA-AES128-SHA256:!DHE-RSA-AES256-SHA256:!DHE-RSA-AES128-SHA256
# as the list is already perfect, let the client choose
tls_preempt_cipherlist = no
tls_random_source = dev:/dev/urandom
tls_ssl_options = NO_COMPRESSION
### SASL
smtpd_sasl_type = dovecot
broken_sasl_auth_clients = yes
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
smtpd_relay_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
### LMTP
virtual_transport = lmtp:unix:private/dovecot-lmtp
### Fine tuning
# prevent spammers from searching for valid users
disable_vrfy_command = yes
# require properly formatted email addresses - prevents a lot of spam
strict_rfc821_envelopes = yes
# don't give any helpful info when a mailbox doesn't exist
show_user_unknown_table_name = no
# limit maximum e-mail size to 50MB. mailbox size must be at least as big as
# the message size for the mail to be accepted, but has no meaning after
# that since we are using Dovecot for delivery.
message_size_limit = 51200000
mailbox_size_limit = 51200000
# require addresses of the form "user@domain.tld"
allow_percent_hack = no
swap_bangpath = no
# allow plus-aliasing: "user+tag@domain.tld" delivers to "user" mailbox
recipient_delimiter = +
# mail servers required to identify themselves
smtpd_helo_required = yes

The tls_high_cipherlist option is inspired by the BetterCrypto's recommendations document called Applied Crypto Hardening.

Postfix DH parameters generation (will take some time):

$ openssl dhparam -out /etc/postfix/dh4096.pem 4096

Note: A publicly-referenced SMTP server MUST NOT require use of the STARTTLS extension in order to deliver mail locally, see RFC3207 or TLS_README for more details.

The general format including all Postfix configuration parameters can be found in the: Postfix Configuration Parameters manual page.

All listening daemons (services) are configured in the master.cf file. By default, at least the following lines (i.e. the smtp service) should be present in the master.cf file:

$ less /etc/postfix/master.cf
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd

The submission service (including STARTTLS and SASL support) can be activated in the master.cf as follows:

$ vi /etc/postfix/master.cf
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_wrappermode=no
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

The TLS has been fine-tuned to deny TLS fallback for clients that don't understand STARTTLS by the -o smtpd_tls_wrappermode=no option. Further reading is possible in the manual pages, such as: smtpd (esp. the "README FILES" section), Postfix TLS Support etc.

Postfix requires to understand the LDAP hierarchy in order to verify administered domains:

$ vi /etc/postfix/ldap-virtual_mailbox_domains.cf
version = 3
start_tls = yes
bind = yes
bind_dn = uid=postfix,ou=services,dc=domain,dc=tld
server_host = ldap://server.domain.tld:389
search_base = ou=domains,dc=domain,dc=tld
# query for a "domain" object and a domain component attribute
# and if present, return 0 (i.e. its "description" value)
query_filter = (&(ObjectClass=domain)(dc=%s))
result_attribute = description

Postfix requires to understand the LDAP hierarchy in order to verify users' aliases:

$ vi /etc/postfix/ldap-virtual_alias_maps.cf
version = 3
start_tls = yes
bind = yes
bind_dn = uid=postfix,ou=services,dc=domain,dc=tld
server_host = ldap://server.domain.tld:389
search_base = ou=people,dc=domain,dc=tld
# query for a "PostfixBookMailAccount" object and a mail alias attribute
# (in "username@domain.tld" mail format)
# and return its "mail" attribute value
query_filter = (&(objectClass=PostfixBookMailAccount)(mailAlias=%u@%d))
result_attribute = mail

Postfix requires to understand the LDAP hierarchy in order to verify users' mail storage directory:

$ vi /etc/postfix/ldap-virtual_mailbox_maps.cf
version = 3
start_tls = yes
bind = yes
bind_dn = uid=postfix,ou=services,dc=domain,dc=tld
server_host = ldap://server.domain.tld:389
search_base = ou=people,dc=domain,dc=tld
# query for a "PostfixBookMailAccount" object and a user id attribute
# and return its "mailStorageDirectory" attribute value
# (i.e. the relative path, e.g. "domain.tld/username/",
# to the physical UID's mail directory, e.g. "/var/vmail")
query_filter = (&(objectClass=PostfixBookMailAccount)(uid=%u))
result_attribute = mailStorageDirectory

In all of the three LDAP configuration files, the bind_dn, bind_pw, server_host and search_base parameters need to be changed accordingly.

Postfix can be started and checked as follows:

$ rcctl enable postfix
$ rcctl start postfix
$ tail -f /var/log/maillog


Connection test to submission port (authentication verification) can be performed using the following command (the -CAfile parameter needs to be specified if self-signed):

$ openssl s_client -host server.domain.tld -port 587 -starttls smtp
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = domain.tld
verify return:1
Certificate chain
 0 s:/CN=domain.tld
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
 1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
Server certificate
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
No client certificate CA names sent
Server Temp Key: ECDH, X25519, 253 bits
SSL handshake has read 3822 bytes and written 320 bytes
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-CHACHA20-POLY1305
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
250 DSN

Note: From the previous response can be seen that the submission port is open and Postfix works correctly. Since Dovecot (as a SASL mechanism) has not been set up yet, the previous error result is also alright (and expected):

$ tail -f /var/log/maillog
Jul 19 09:32:10 server postfix/submission/smtpd[50459]: connect from server.domain.tld[EGRESS_IP]
Jul 19 09:32:10 server postfix/submission/smtpd[50459]: Anonymous TLS connection established from server.domain.tld[EGRESS_IP]: TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)
Jul 19 09:32:10 server postfix/submission/smtpd[50459]: warning: SASL: Connect to private/auth failed: No such file or directory
Jul 19 09:32:10 server postfix/submission/smtpd[50459]: fatal: no SASL authentication mechanisms
Jul 19 09:32:11 server postfix/master[70682]: warning: process /usr/local/libexec/postfix/smtpd pid 50459 exit status 1
Jul 19 09:32:11 server postfix/master[70682]: warning: /usr/local/libexec/postfix/smtpd: bad command startup -- throttling

After a couple of hours of running Postfix, encryption statistics can be parsed from the maillog as follows (adopted from bettercrypto.org):

$ zegrep "TLS connection established from.*with cipher" /var/log/maillog | awk '{printf("%s %s %s %s\n", $12, $13, $14, $15)}' | sort | uniq -c | sort -n

Additional Hardening

Cleaning headers sent by clients to prevent valuable information leakage using Postfix service definition:

# vi /etc/postfix/master.cf
### For privacy reasons, remove header details provided by the MUA
submission inet n       -       y       -       -       smtpd
  -o cleanup_service_name=submission-header-cleanup
submission-header-cleanup unix n - n    -       0       cleanup
  -o header_checks=regexp:/etc/postfix/submission_header_cleanup

The headers can be specified (using REGEX) as follows:

# vi /etc/postfix/submission_header_cleanup
### For privacy reasons, removes headers details sent by MUAs
/^\s*Received:/                      IGNORE
/^\s*X-Originating-IP:/              IGNORE
/^\s*X-Mailer:/                      IGNORE
/^\s*User-Agent:/                    IGNORE
/^\s*X-Enigmail:/                    IGNORE
/^\s*X-Pgp-Agent:/                   IGNORE
### The Mime-Version header leaks user agent on Mac OS
/^\s*(Mime-Version:\s*[0-9\.]+)\s.+/ REPLACE $1


Now, everything is ready for installation of an IMAP server, such as Dovecot...

Tags: #OpenBSD #security #Postfix #OpenSMTPD #MTA #MSA #MDA #LDA #MUA #exchange #TLS

⏴ Previous Post Next Post ⏵