Warning

None of this has been tested yet!

MTA - Mail Transfer Server

Postfix Logo

Postfix is a free and open-source mail transfer agent (MTA) that routes and delivers electronic mail on a Linux system. It is estimated that around 26% of public mail servers on the internet run Postfix.

Install Software

$ sudo apt-get install postfix postfix-mysql

The installed version is Postfix version 2.11, released on January 15, 2014.

Main Configuration

Postfix uses a number of different configuration files, with the /etc/postfix/main.cf being the most important.

The documentation website has a page dedicated to main.cf which includes every possible configuration paramter.

The whole file, as presented below, is also provided for download at config/postfix/main.cf here.

General Settings

# General Mail Server Setttings
#

# What is my hostname (FQDN)?
myhostname = mail.example.net

# What is my domainname?
mydomain = example.net

# What domain name do I append to local sender addresses on outbound mail?
myorigin = $mydomain

# Do I have to append a dot and my domain-name (.$mydomain) to addresses?
append_dot_mydomain = no

# What characters in a recipient address separate a users from extensions?
recipient_delimiter = +

# Should I refuse mails with non-standard FROM or RCPT address formats?
strict_rfc821_envelopes = yes

# What domains do I receive mail for?
mydestination = server.example.net, server.lan, localhost.lan, localhost

# For which clients I will relay mail to other doemains?
mynetworks_style = host

# To which 3rd-party domains do I accept and forward (relay) mails?
relay_domains = 

# Where do I forward messages to root, postmaster or webmaster?
alias_maps = hash:/etc/postfix/aliases

# Which database do I have to build, when I update alias information?
alias_database = hash:/etc/postifx/aliases

# Which sender addresses I have to change before sending out mail?
sender_canonical_maps = hash:/etc/postfix/sender_canonical

# Which addresses I have to change before sending out mail?
canonical_maps = hash:/etc/postfix/canonical

# What trouble do I need to report to a human (postmaster)?
notify_classes = resource, software, 2bounce

# Do I need to notify local users of new mail?
biff = no

# What is the maximum message size (in bytes)?
# 25 Megabytes = 26,214,400 Bytes
message_size_limit = 26214400

# Whats the size limit of mailboxes (in bytes)?
# 100 Gigabytes = 107,374,182,400 Bytes
mailbox_size_limit = 107374182400

# After how long should I notify if mail-delivery is delayed?
delay_warning_time = 6h

TCP/IP Protocols Settings

# TCP/IP Protocols Settings
# run "sudo service postfix restart" after changing IP addresses.
#

# Do I use IPv4 or IPv6 or both?
inet_protocols = all

# On which interfaces / IP addresses do I listen for inbound connections?
inet_interfaces = 127.0.0.1, ::1, 192.0.2.40, 2001:db8::40

# Which IPv4 addres do I use for outbound connections?
smtp_bind_address = 192.0.2.40

# Which IPv6 addres do I use for outbound connections?
smtp_bind_address6 = 2001:db8::40

# Our Firewall/Gateway is doing NAT. What is my external IPv4 address?
proxy_interfaces = ipv4.example.net

General TLS Settings

Let the server choose the preferred cipher during handshake:

# General TLS Settings
#

# Where do I get Diffie-Hellmann key-exchange (DHE) parameters from (for perfect forward secrecy)
smtpd_tls_dh1024_param_file = /etc/ssl/dhparams/dh_4096.pem
smtpd_tls_dh512_param_file  = /etc/ssl/dhparams/dh_512.pem

# Should I enable server cipher preferences?
tls_preempt_cipherlist = yes

# What OpenSSL options do I enable?
tls_ssl_options = NO_COMPRESSION

# What ciphers suites do I use when I am high?
tls_high_cipherlist = kEDH+aRSA+AES128:kEECDH+aRSA+AES128:+SSLv3

SMTP Client Settings

# SMTP Client Settings
#

# Do I deliver outgoing mail directly to its destination server or a smarthost?
relayhost = smtp.myisp.com:587

# Do I need to login on the smarthost?
smtp_sasl_auth_enable = yes

# What username and password do I use when logging-in on the smarthost?
smtp_sasl_password_maps = hash:/etc/postfix/relay_password

# What methods for login should I avoid on the smarthost
smtp_sasl_security_options = noanonymous

#
smtp_generic_maps = hash:/etc/postfix/generic

# What DNS lookup methods do I use for outgoing SMTP sessions?
smtp_dns_support_level = DNSSEC

# Is TLS encryption required for outgoing connections?
smtp_tls_security_level = dane

# Where do I find trusted CA certificates to verify SMTP server certificates?
smtp_tls_CApath = /etc/ssl/certs

# Where do I cache outgoing SMTP TLS sessions?
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

SMTP Server Settings

# SMTP Server Settings
#

# How do I greet clients on incoming connections?
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)

# TLS certificate and key for incoming connections?
smtpd_tls_cert_file = /etc/ssl/certs/example.net.chained.cert.pem
smtpd_tls_key_file = /etc/ssl/private/example.net.key.pem

# Is TLS encryption required for incoming connections?
smtpd_tls_security_level = may

# Do I refuse password authentication over unencrypted connections?
smtpd_tls_auth_only = yes

# Which SSL and TLS protocols do I accept or reject on incoming connections?
smtpd_tls_protocols = !SSLv2, !SSLv3
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3

# What grade of encryption do I accept on incoming connections?
smtpd_tls_mandatory_ciphers = high

# What security grade do I use for ephemeral elliptic-curve Diffie-Hellman (EECDH)
# key exchange?
smtpd_tls_eecdh_grade = ultra

# Should I include protocol and cipher used in the "Received:" message headers?
smtpd_tls_received_header = yes

# Where do I cache incoming SMTP TLS sessions?
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

SMTP Server Restrictions

# SMTP Server Restrictions
#

# What restrictins apply to connecting clients?
smtp_client_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unknown_client_hostname

# Are connecting clients required to greet me properly before anything else?
smtpd_helo_required = yes

# What restrictins apply to SMTP HELO greetings
smtpd_helo_restrictions =
    permit_mynetworks
    reject_invalid_helo_hostname
    reject_non_fqdn_helo_hostname 
    reject_unknown_helo_hostname

# What restrictins apply to any mail sender address?
smtp_sender_restrictions =
    permit_mynetworks
    reject_non_fqdn_sender
    reject_unknown_sender_domain
    reject_unlisted_sender
    check_sender_access hash:/etc/postfix/sender_access
    warn_if_reject reject_unverified_sender

# What should I tell clients when their sender address verification fails?
unverified_sender_reject_reason = Sender address verification failed

# What restrictions apply to relayed mails?
smtpd_relay_restrictions = 
    permit_mynetworks 
    permit_sasl_authenticated 
    reject_unauth_destination

# What restrictins apply to any mail recipient address?
smtpd_recipient_restrictions =
    permit_auth_destination
    reject_non_fqdn_recipient
    reject_unauth_destination
    reject_unknown_recipient_domain
    reject_unlisted_recipient
    reject_unverified_recipient

# What restrictins apply to mail content?
smtpd_data_restrictions = reject_unauth_pipelining

Postscreen Settings

90% of todays SMTP connections come from Spambots and Zombies. Postscreen is a sort of a SMTP mail server firewall. It attempts to reject as many spambots as possible, before they even get to our main SMTP server, where processing takes a lot more time and resources, as on the screener. If done well the main SMTP server has to process only 10% of the connections.

Postscreen is described in detail in a online readme document.

# Postscreen Settings
#

# What networks are permanently whitelisted to send mails?
postscreen_access_list = 
    permit_mynetworks
    cidr:/etc/postfix/postscreen_access.cidr

# What to do with permanantely blacklisted clients
postscreen_blacklist_action = enforce

# What to do with clients who speak to me before its their turn?
postscreen_greet_action = enforce

 # What DNS blacklist threshold does a client need to get blacklisted?
postscreen_dnsbl_threshold = 3

# What DNS blacklists/whitelists can I ask about the connecting IP address?
postscreen_dnsbl_sites = 
    # DNS Blacklists
    zen.spamhaus.org*3 
    b.barracudacentral.org*2
    bl.spamcop.net*2
    ix.dnsbl.manitu.net*2
    dnsrbl.swinog.ch=127.0.0.3*2
    spamrbl.imp.ch==127.0.0.5*2
    uribl.swinog.ch*1
    wormrbl.imp.ch=127.0.0.5*2
    dnsbl-1.uceprotect.net*1
    dnsbl.sorbs.net=127.0.0.[2;3;6;7;10]*1
    # DNS Whitelists
    list.dnswl.org=127.0.[0..255].0*-1
    list.dnswl.org=127.0.[0..255].1*-2
    list.dnswl.org=127.0.[0..255].[2..3]*-3
    iadb.isipp.com=127.0.[0..255].[0..255]*-2
    iadb.isipp.com=127.3.100.[6..200]*-2
    wl.mailspike.net=127.0.0.[17;18]*-1
    wl.mailspike.net=127.0.0.[19;20]*-2

# What to do with DNS blacklisted clients?
postscreen_dnsbl_action = enforce

# What DNS whitelist threshold does a client need to get whitelisted
postscreen_dnsbl_whitelist_threshold = -2

# Should I check if clients try to pipeline commands?
postscreen_pipelining_enable = yes

# Should I check if clients send any non-SMTP commands?
postscreen_non_smtp_command_enable = yes

# Should I check if clients send bare newline characters (LF instead of CR/LF)?
postscreen_bare_newline_enable = yes

# What to do with clients that send bare newline characters (LF instead of CR/LF)?
postscreen_bare_newline_action = drop

Virtual Domain Settings

# Virtual Domain Settings
#

# How can I lookup virtual mail-domains hosted here?
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf

# How do I lookup individual mailbox addresses of virtual mail-domains?
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf

# How do I lookup aliases of virtual domain mailbox addresses?
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf

Map Files and Databases

Postfix uses several maps stored in files to translate things like domains and mail- addresses at runtime. As these maps can be quite large and change often, they are not saved in configuration files, but in map-files.

The map files are referenced in the postfix server-configuration. Examples already used above are the canonical_maps or virtual_mailbox_domains above.

Such configuration values usually contain a prefix like hash: or mysql:, which tell the server in what type of data store the mapping information is accessible.

If the prefix starts with hash: the map is initially is stored as plain text in the file referenced, but cached later in a hash-database for faster access. A hashed map or database can change anythme while the server is running, unlike configuration values, which are only read on server startup, and changes need a restart of the mail server.

It migth get complicated, the more such files are around as they use different formats, handling and hashing.

It therefore helps to create a Makefile, so refreshing anything needs just a single command-line. Create the /etc/postfix/Makefile as follows:

#
# Postfix hashed maps maintenance
#
MAPS = aliases.db canonical.db generic.db relay_password.db \
		sender_access.db sender_canonical.db 

all: $(MAPS)

# Mail aliases table
aliases.db: aliases.in
		@echo Rebuilding hashed DB for $@
		@postalias $<
		@mv $<.db $@

# All others
%.db: %.in
		@echo Rebuilding hashed DB for $@
		@postmap hash:$<
		@mv $<.db $@

After every change to any of the map files discussed below, their databases need to be refreshed as follows:

$ cd /etc/postfix
$ sudo make

Alias Map

The file /etc//postfix/aliases.in contains information on where mails for certain system accounts should be delivered to.

This is needed for notification and warning mails created by system programs (like cronjobs) to reach human beings (like the person responsible for system administration).

# See man 5 aliases for format
# run "sudo newaliases" after changing this file.
root:       hostmaster@example.net
postmaster: postmaster@example.net
webmaster:  webmaster@example.net
www-data:   webmaster@example.net

# OpenPGP Web Key Service
key-submission@example.net: webkey
key-submission@example.org: webkey
key-submission@example.com: webkey

The contents of the file are cached in the database /etc/postfix/aliases.db. Because of that the database must be refreshed after each and every change made in /etc/postfix/aliases.in:

$ cd /etc/postfix
$ sudo make

Canonical Sender Map

The file /etc//postfix/sender_canonical.in contains information on which sender- addresses of outgoing mails need to be changed, before mail is sent out.

This is needed mostly for system generated mails, as they contain local system account-names as senders, which will not be accepted as valid internet- mail addresses.

# see man 5 canonical
# run "postmap /etc/postfix/sender_canonical" after changing this file.
www-data webmaster@example.net
root server@example.net

The contents of the file are cached in the database /etc/postfix/sender-canonical.db. Because of that the database must be refreshed after each and every change made in /etc/postfix/sender_canonical.in:

$ cd /etc/postfix
$ sudo make

Canonical Map

The file /etc//postfix/canonical.in defines a map on which addresses need always be changed for sender and recipient in message headers and envelopes:

# see man 5 canonical
# run "sudo postmap /etc/postfix/canonical" after changing this file.this file.
root hostmaster@example.net
postmaster postmaster@example.net
webmaster webmaster@example.net
www-data webmaster@example.net

The contents of the file are cached in the database /etc/postfix/canonical.db. To update the database after changes run the following:

$ cd /etc/postfix
$ sudo make

SMTP Client Credentials

The file /etc//postfix/relay_password.in contains a lookup table with one username:password entry per remote hostname or domain used by the Postfix SMTP client when connecting to other SMTP servers when delivering mail.

smtp.mail-provider.com user@example.net:********

To update the cache after any changess:

$ cd /etc/postfix
$ sudo make

SMTP Generic Maps

The file /etc/postfix/generic contains a lookup table that perform address rewriting in the Postfix SMTP client, typically to transform a locally valid address into a globally valid address when sending mail across the Internet:

# Lookup table for address rewriting by the Postfix SMTP client
# run "sudo postmap /etc/postfix/generic" after changing this file.
root@server user@example.net
user@server user@example.net

This is needed when the local machine does not have its own Internet domain name, but uses something like localdomain.local instead.

Sender Address Verification Map

The file /etc/postfix/sender_access contains domain names which often are used by spammers to forge sender addresses. Postfix will use this list to verify all sender addresses from the domains listed:

# Don't do this when you handle lots of email.
aol.com     reject_unverified_sender
hotmail.com reject_unverified_sender
bigfoot.com reject_unverified_sender
yahoo.com   reject_unverified_sender

Postscreen Access List

In the file /etc/postfix/postscreen_access.cidr we define white- and blacklists for IP subnets and addresses, which allow us to skip Spambot tests by Postscreen and allow or deny connections right away for certain hosts.

Documentation is available here.

# Rules are evaluated in the order as specified.
# http://www.postfix.org/postconf.5.html#postscreen_access_list

# Private IPv4 addresses (RFC 1918)
10.0.0.0/8 permit
172.16.0.0/12 permit
192.168.0.0/16 permit

# Private IPv6 addresses (RFC 4193)
fc00::/7 permit

# Link-local IPv4 addresses (RFC 6890 and RFC 3927)
169.254.0.0/24 permit
169.254.255.0/24 permit
169.254.0.0/16 permit

# Link-local IPv6 addresses (RFC 4862 and RFC 4291)
fe80::/10 permit

# Global IPv6 subnet assigned to us (example.net)
2001:db8::/64 permit

Virtual Domain Map

We referenced this the file /etc/postfix/mysql-virtual-mailbox-domains.cf in the main configuration file main.cf above as virtual_mailbox_domains. It contains MySQL database server connection information, so Postfix can lookup the virtual domains hosted here:

user = mailuser
password = ********
hosts = localhost
dbname = mailserver
query = SELECT 1 FROM virtual_domains WHERE name='%s'

Use the user, password and database defined in Virtual Domains and Mailboxes.

Virtual Mailbox Map

Same as with the virtual domain map above, the following MySQL connection defined in /etc/postfix/mysql-virtual-mailbox-maps.cf is used to lookup which mailboxes are hosted here:

user = mailuser
password = ********
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_users WHERE email='%s'

Note that the only difference to the above one is the SQL query. looking for mailboxes instead of domains.

Virtual Alias Map

Finally we also create /etc/postfix/mysql-virtual-alias-maps.cf to give Postfix a way to lookup mailbox aliases at the MySQL database server:

user = mailuser
password = ********
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM virtual_aliases WHERE source='%s'

Master Process Configuration

Postfix consists of many daemons and utilities, some running all the time and others started when needed. The /etc/postfix/master.cf confiugration file is the centralized control center from where the Postfix master process gets his guidelines on what, when and how all these programs have to be started.

Where no command-line options are specified, the program uses relevant configuration values from the main.cf configuration file. Alternatively options can be overridden by command-line parameters like -o.

The official documentation website provides a manual page online.

The whole file, as presented below, is also provided for download at config/postfix/master.cf here.

Disable Default SMTP

The first daemon specified here is the SMTP daemon smtpd which runs on the dedicated SMTP TCP port 25, it is enabled by default, but as we use postscreen in front of it we comment that out:

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
#smtp      inet  n       -       -       -       -       smtpd

Postscreen SMTP Firewall

We enable postscreen on the second line instead of smtpd on the first line. This runs a mininmal SMTP subset to perform some compliance tests on incoming SMTP client connections. If the client passes all tests he gets whitelistet and the connection is passed along to the real SMTP server running behind it. That is what the second line above is for. An smtpd daemon that takes over connections from postscreen only. The tlsproxy handles TLS encryption for postscreen and dnsblog does the DNS blacklist lookups.

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtp       inet  n       -       n       -       1       postscreen
dnsblog    unix  -       -       n       -       0       dnsblog
tlsproxy   unix  -       -       n       -       0       tlsproxy

Postfix SMTP Daemon

If the conneting SMTP client has passed all the checks performed by postscreen, the client IP address is temporarely whitelisted.

If possible the TCP connection is then handed over to the SMTPD daemon internally (that is what the “pass” keyword means).

Sometimes this is not possible, depeneding on the checks. If the client had to start already some message delivery in order to complete a check, the connection can not be handed over to a new SMTP server, as this would confuse the SMTP client who is already in the middle of a message delivery. In this case, postscreen will tell the connecting SMTP client to abort message delivery for now and try again later. If the same client connects again later, it will be already whitelisted and then the connection is “passed” directly to the postfix SMTP daemon.

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtpd      pass  -       -       n       -       -       smtpd
  -o smtpd_proxy_filter=127.0.0.1:10024

The SMTP daemon will use Amavis as proxy by forwarding the SMTP client connection to the Amavis daemon running on TCP port 10024 on localhost.

After-Scan SMTP Reinjection

When Amvis has performed all scans and checks on a mail-message, it injects the message back to postfix on the postfix SMTP server running at TCP port 10025 on localhost.

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
127.0.0.1:10025 inet n   -       -       -       -       smtpd
  -o content_filter=
  -o smtpd_proxy_filter=
  -o local_recipient_maps=
  -o smtpd_authorized_xforward_hosts=127.0.0.0/8
  -o relay_recipient_maps=
  -o smtpd_restriction_classes=
  -o smtpd_client_restrictions=
  -o smtpd_helo_restrictions=
  -o smtpd_sender_restrictions=
  -o smtpd_data_restrictions=
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o mynetworks=127.0.0.0/8
  -o strict_rfc821_envelopes=yes
  -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
  -o inet_interfaces=127.0.0.1

The Submission Daemon

The submission daemon runs on TCP port 587 and lets mail clients (MUA) to submit mails. The submission daemon accepts only encrypted connections, allows only authenticated and authorized users, but also allows mails to be relayed out on the internet, besides accepting mail to its own hosted domains and mailboxes.

In practice, this is another copy of the SMTP daemon running on TCP port 587 instead of port 25 and some command-line options to override the default SMTP daemon settings from main.cf.

It is not enabled by default, we have to remove the comment (‘#’) symbol at the start of the line and the options in the following lines:

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
submission inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=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_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

Amavis Daemon

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
#
# Amavis.
#
amavis    unix  -       -       n       -       6       smtp
  -o smtp_data_done_timeout=1800
  -o smtp_send_xforward_command=yes
  -o disable_mime_output_conversion=yes
  -o disable_dns_lookups=yes