Postfix (2.9)

From Hexwiki
Jump to navigation Jump to search

The following describes a postfix site installation, using MySQL as a backend and Dovecot as the MDA. You will want to get nearly everything else regarding your mail working first - MySQL, the tables, and any components you may be using (ClamAV, Spamassassin, OpenDKIM), etc.

This installation is somewhat involved - but between it and the Spamassassin configuration given, you will have very little spam to deal with. Barely a piece of spam a day even makes it to spamassassin - and this is with e-mail addresses that have been public for years. About than one in a thousand make it through Spamassassin. My gmail accounts are let more spam through than this.

There are three key rules that drastically cut down on spam:

  1. Requiring forward-confirmed reverse DNS (reject_unknown_client_hostname in the following config)
  2. Block generic domain names that pass the above test.
  3. Block spammers claiming to be your users.

While it does not solve all spam, it makes what is left a great deal more manageable. The resulting successful spam-friendly providers get addressed in one fashion or another.

To start, after postfix is installed, run

postalias /etc/aliases

We will still be using these (occasionally).

/etc/postfix/main.cf

# There's not much left of Debian's default postfix configuration here.
smtpd_banner = $myhostname ESMTP $mail_name
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = /usr/share/doc/postfix
# TLS parameters
smtpd_tls_cert_file=/etc/maincert/example.crt
smtpd_tls_key_file=/etc/maincert/example.key
smtpd_use_tls=yes
smtp_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_sasl_security_options = noanonymous
smtpd_tls_protocols = !SSLv2, !SSLv3
smtp_tls_block_early_mail_reply = yes
smtpd_tls_security_level = may
smtpd_tls_ciphers = medium
smtp_tls_mandatory_ciphers = high
smtp_tls_exclude_ciphers = aNULL, NULL, MD5, ADH
# smtpd_sasl_auth_enable = yes
# Because we will be using dovecot...
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
myhostname = mail.example.com
# Most providers still use IPv4. Mailservers prefer to use IPv6 if they can, Google's among them.
smtp_bind_address = 68.233.227.82
smtp_bind_address6 = 2604:4500:0:7:3::2
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
# If you have multiple servers across the Internet, but want them all to send mail to the same machine,
# be sure to set myorigin to the one primary domain you want to use. 
myorigin = example.com
mydestination = examplename, localhost, ,
# Leave this blank if this is your main mx, otherwise, set it to your primary mailserver where you are directing mail:
# relayhost = mail.example.com
# Assuming mail.example.com is your highest-priority mx. 
relayhost =
# In mynetworks, IPv6 addresses need to be in brackets, like so.
mynetworks = 127.0.0.0/8 [::1]/128 198.51.100.187 [2001:db8::4]/128
# Make sure the ips of any secondary mxes are listed in mynetworks.
mailbox_size_limit = 0
# Having a nonstandard recipient delimiter is exceedingly handy.
recipient_delimiter = _
# You may not want all of them on.
inet_interfaces = all
html_directory = /usr/share/doc/postfix/html
# Be strict. Somewhat.
smtpd_hard_error_limit   = 3
smtpd_soft_error_limit   = 1
smtpd_junk_command_limit = 20
smtpd_helo_required      = yes
disable_vrfy_command     = no
strict_rfc821_envelopes  = no
# The next five lines were when I was dealing with Yahoo headaches. The defaults are probably fine.
maximal_backoff_time         = 19200s
qmgr_message_active_limit    = 65000
qmgr_message_recipient_limit = 65000
bounce_queue_lifetime        = 1w
maximal_queue_lifetime       = 1w
message_size_limit = 33554432
authorized_submit_users = !banusera, !banusertoo, static:all
# Milters
milter_default_action = accept
milter_protocol = 2
smtpd_milters = local:/var/run/opendkim/opendkim.sock
non_smtpd_milters = local:/var/run/opendkim/opendkim.sock
# Blocking non-FCrDNS hostnames stops about 300 pieces of spam per day, generic hostnames about 100. This would be much worse if they weren't blocked.
smtpd_client_restrictions = permit_mynetworks,
                            reject_unknown_client_hostname,
                            reject_unauth_pipelining,
                            check_client_access pcre:/etc/postfix/valid-domains,
                            check_client_access pcre:/etc/postfix/reject-domains,
                            permit
# reject-mydomains got hit hundreds of times per day just after turning it on. Have not had a peep lately.
smtpd_helo_restrictions = permit_mynetworks,
                          check_helo_access pcre:/etc/postfix/reject-nomailfrom,
                          check_helo_access mysql:/etc/postfix/reject-mydomains.cf,
                          reject_invalid_helo_hostname,
                          reject_non_fqdn_helo_hostname,
                          permit
smtpd_sender_restrictions = permit_mynetworks,
                            check_sender_access pcre:/etc/postfix/reject-nomailfrom,
                            check_sender_access mysql:/etc/postfix/reject-mydomains.cf,
                            reject_non_fqdn_sender,
                            reject_unknown_sender_domain,
                            permit
smtpd_recipient_restrictions = permit_mynetworks,
                               reject_unauth_destination,
                               check_recipient_access pcre:/etc/postfix/reject-users,
                               reject_non_fqdn_recipient,
                               permit
# Currently such a rare occurance that I don't see the need to discriminate yet. Saved for posterity, though.
# These block 'new' domains. See spameatingmonkey.net for details.
#                               reject_rhsbl_client fresh15.spameatingmonkey.net,
#                               reject_rhsbl_helo fresh15.spameatingmonkey.net,
#                               reject_rhsbl_sender fresh15.spameatingmonkey.net,
smtpd_data_restrictions = permit_mynetworks,
                          reject_multi_recipient_bounce,
                          permit
virtual_mailbox_base    = /var/vmail/
virtual_mailbox_limit   = 536870912
virtual_minimum_uid     = 999
virtual_uid_maps        = static:999
virtual_gid_maps        = static:999
virtual_mailbox_domains = mysql:/etc/postfix/virtual-domains.cf
smtpd_sender_login_maps = mysql:/etc/postfix/virtual-accounts.cf
virtual_mailbox_maps    = mysql:/etc/postfix/virtual-users.cf
virtual_alias_maps      = mysql:/etc/postfix/virtual-aliases.cf
virtual_transport       = dovecot
dovecot_destination_recipient_limit = 1
spamassassin_destination_recipient_limit = 1
sender_dependent_default_transport_maps = pcre:/etc/postfix/sdd_transport_maps

/etc/postfix/master.cf

# Some of this stuff covers a few odd things you may want to do, I will highlight
# them in these comments.
#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master").
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
localhost:smtp                  inet  n       -       n       -       -       smtpd
  -o smtpd_sasl_auth_enable=yes
# Run spamassassin and clamav only on incoming mail.
198.51.100.187:smtp              inet  n       -       n       -       -       smtpd
  -o content_filter=spamassassin
  -o smtpd_milters=local:/var/run/clamav/clamav-milter.ctl
[2001:db8::4]:smtp       inet  n       -       n       -       -       smtpd
  -o content_filter=spamassassin
  -o smtpd_milters=local:/var/run/clamav/clamav-milter.ctl
# I use the submission port to actually accept mail from users, including my own forums. 
# They need different rules, obviously.
198.51.100.187:submission        inet  n       -       n       -       -       smtpd
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_recipient_rate_limit=60
  -o smtpd_client_message_rate_limit=60
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_helo_restrictions=permit
  -o smtpd_sender_restrictions=reject_sender_login_mismatch,permit
  -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
  -o smtpd_data_restrictions=permit
  -o cleanup_service_name=submission_cleanup
  -o milter_macro_daemon_name=ORIGINATING
[2001:db8::4]:submission inet  n       -       n       -       -       smtpd
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_recipient_rate_limit=60
  -o smtpd_client_message_rate_limit=60
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_helo_restrictions=permit
  -o smtpd_sender_restrictions=reject_sender_login_mismatch,permit
  -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
  -o smtpd_data_restrictions=permit
  -o cleanup_service_name=submission_cleanup
  -o milter_macro_daemon_name=ORIGINATING
# Some policies may suggest that you setup a second mailing ip to segregate e.g. marketing mail from your
# more mission-critical mail. 'secondmailer' here and in sdd_transport_maps shows how to go about this.
secondmailer            unix  -       -       n       -       -       smtp
  -o smtp_bind_address=secondmaileripv4address
  -o smtp_bind_address6=secondmaileripv6address
  -o myhostname=example.com
  -o myorigin=example.com
  -o smtp_helo_name=example.com
  -o syslog_name=postfix-example
# The second mailer acts as its own mailserver, down to receiving mail.
secondmaileripv4address:smtp              inet  n       -       n       -       -       smtpd
  -o content_filter=spamassassin
  -o myhostname=example.com
  -o myorigin=example.com
  -o syslog_name=postfix-example
  -o smtpd_milters=local:/var/run/clamav/clamav-milter.ctl
[secondmaileripv6address]:smtp       inet  n       -       n       -       -       smtpd
  -o content_filter=spamassassin
  -o myhostname=example.com
  -o myorigin=example.com
  -o syslog_name=postfix-example
  -o smtpd_milters=local:/var/run/clamav/clamav-milter.ctl
#smtp      inet  n       -       -       -       -       smtpd
#smtp      inet  n       -       -       -       1       postscreen
#smtpd     pass  -       -       -       -       -       smtpd
#dnsblog   unix  -       -       -       -       0       dnsblog
#tlsproxy  unix  -       -       -       -       0       tlsproxy
#submission inet n       -       -       -       -       smtpd
#  -o syslog_name=postfix/submission
#  -o smtpd_tls_security_level=encrypt
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
#smtps     inet  n       -       -       -       -       smtpd
#  -o syslog_name=postfix/smtps
#  -o smtpd_tls_wrappermode=yes
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
#628       inet  n       -       -       -       -       qmqpd
pickup    fifo  n       -       -       60      1       pickup
cleanup   unix  n       -       n       -       0       cleanup
# Cleanup for header checks. This prevents user's IP addresses from leaking
# out to nosy people.
submission_cleanup   unix  n       -       n       -       0       cleanup
  -o header_checks=pcre:/etc/postfix/header_checks
  -o syslog_name=postfix/submission/cleanup
qmgr      fifo  n       -       n       300     1       qmgr
#qmgr     fifo  n       -       n       300     1       oqmgr
tlsmgr    unix  -       -       -       1000?   1       tlsmgr
rewrite   unix  -       -       n       -       -       trivial-rewrite
bounce    unix  -       -       -       -       0       bounce
defer     unix  -       -       -       -       0       bounce
trace     unix  -       -       -       -       0       bounce
verify    unix  -       -       -       -       1       verify
flush     unix  n       -       -       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       -       -       -       smtp
relay     unix  -       -       -       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       -       -       -       showq
error     unix  -       -       -       -       -       error
retry     unix  -       -       -       -       -       error
discard   unix  -       -       -       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       -       -       -       lmtp
anvil     unix  -       -       -       -       1       anvil
scache    unix  -       -       -       -       1       scache
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent.  See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
#   lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
#  mailbox_transport = lmtp:inet:localhost
#  virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus     unix  -       n       n       -       -       pipe
#  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix  -       n       n       -       -       pipe
#  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix  -       n       n       -       2       pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman   unix  -       n       n       -       -       pipe
  flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
  ${nexthop} ${user}
# The following are for dovecot and spamassassin, obviously. 
# -m ${extension} lets us sent delimited mail straight to the appropriate folder.
dovecot unix    -       n       n       -       -      pipe
  flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -m ${extension}
spamassassin unix  -       n       n       -       -       pipe
  user=debian-spamd argv=/usr/bin/spamc -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}

Supporting Files

You might have noticed that the above configuration refers to a lot of supporting map files. Some of these are optional, others are highly recommended.

/etc/postfix/header_checks

# This gets run through on cleanup, via cleanup_submission. You can see the chain for this in master.cf
# Here we delete a few common identifying marks, and replace the Received header with something explanatory.
/^Received: from/       REPLACE Received: from localhost (::1) (authenticated client)
/^X-Originating-IP:/    IGNORE
/^User-Agent:/          IGNORE
/^X-Mailer:/            IGNORE

/etc/postfix/valid-domains

# Some large mailer domains are starting to use names that are beginning 
# to look a lot like generic names. Here we have a couple of catch-alls,
# covering Google, Yahoo, AoL, Amazon, and many common providers.
# Hotmail is off in lalaland, so they need their own entry.
# Note that the goal here is in general to be forgiving - so long as we
# know that someone owning an ip and a domain name gave some thought to it.
/(^|-|\.)mail(\-|\.)/i                                         OK
/(^|-|\.)mx(\-|\.)/i                                           OK
/(^|-|\.)smtp(\-|\.)/i                                         OK
/\.hotmail\.com$/                                              OK

/etc/postfix/reject-domains

# The first two represend the overwhelming majority of these blocks.
# Some legitimate people have not bothered to give themselves a proper domain name,
# but frankly I'm not opening myself up again just to put up with their ignorance.
/(^|-|\.)[0-9a-f]{2}(\-+|\.)[0-9a-f]{2}(\-+|\.)[0-9]*[a-z]+/i  REJECT 554 Dynamic or Generic Hostname
/(^|-|\.)[0-9]+(\-+|\.)[0-9]+(\-+|\.)[0-9]*[a-z]+/i            REJECT 554 Dynamic or Generic Hostname
/(^|-|\.)(vps)[0-9]{2,}/i                                      REJECT 554 Dynamic or Generic Hostname
/(^|-|\.)[a-z]?[0-9a-f]{4,}(\-+|\.)(dip|dyn|pool)/i            REJECT 554 Dynamic or Generic Hostname

/etc/postfix/reject-nomailfrom

This is a completely optional file. If you have a big site that generates a lot of e-mail, you may want to use this on typoed domains, alternate tlds, etc. that you own. My server sends nearly a quarter-million e-mails per month - I almost never see this get hit.

/(^|@)([a-z0-9-]+\.)*example\.org$/                         REJECT 554 Domain does not send mail.
/(^|@)([a-z0-9-]+\.)*example\.net$/                         REJECT 554 Domain does not send mail.
/(^|@)([a-z0-9-]+\.)*example\.biz$/                         REJECT 554 Domain does not send mail.
/(^|@)([a-z0-9-]+\.)*example\.info$/                        REJECT 554 Domain does not send mail.

/etc/postfix/reject-users

# I use this to hide my admin user or users - those with su access (whether to root or not).
/^admin1@(local|example)\./          REJECT 550 User unknown
/^admin2@(local|example)\./          REJECT 550 User unknown
/^admin4@(local|example)\./          REJECT 550 User unknown

/etc/postfix/sdd_transport_maps

# This lets you run what amounts to multiple mailservers off of a single postfix instance,
# in the event that you want to segregate classes of mail. 
/@example\.com$/             secondmailerexample:
#/@example\.org$/                   exampleorgmailer:

/etc/postfix/reject-mydomains.cf

# Some of the most revolting spam is stuff that gets sent claiming to be you. It confuses users
# who don't know what's going on and pisses off those who do. Shut that down.
hosts = unix:/var/run/mysqld/mysqld.sock
user = vmreader
password = passforvmreader
dbname = mail
query = SELECT CONCAT('REJECT 554 You are not ',domain_name) FROM mail_domains WHERE active=1 AND domain_name='%s' OR '%s' LIKE CONCAT('%%.',domain_name)

/etc/postfix/virtual-accounts.cf

# WARNING!
# The underscore passed to SUBSTRING_INDEX here is because I'm using that as the recipient delimiter. If you use a different one in main.cf, you will want to
# change it in SUBSTRING_INDEX here. This allows people to send mail as their aliases, delimited addresses included.
hosts = unix:/var/run/mysqld/mysqld.sock
user = vmreader
password = passforvmreader
dbname = mail
query = (SELECT DISTINCT(CONCAT(u.username,'@',d.domain_name)) AS account FROM mail_users AS u, mail_domains AS d WHERE u.isactive >= 1 AND d.ID_DOMAIN=u.ID_DOMAIN AND d.domain_name='%d' AND u.username=SUBSTRING_INDEX('%u', '_', 1)) UNION (SELECT DISTINCT(CONCAT(u.username,'@',d.domain_name)) AS account FROM mail_aliases AS v, mail_users AS u, mail_domains AS d, mail_domains AS da WHERE v.ID_USER=u.id_USER AND u.ID_DOMAIN=d.ID_DOMAIN AND v.ID_DOMAIN=da.ID_DOMAIN AND da.domain_name='%d' AND v.alias_local='%u')

/etc/postfix/virtual-aliases.cf

# Sortof the inverse of the above - instead of letting us know who can send from a given address, this tells us where mail for a given address goes to.
hosts = unix:/var/run/mysqld/mysqld.sock
user = vmreader
password = passforvmreader
dbname = mail
query = SELECT DISTINCT(CONCAT(u.username,v.alias_ext,'@',d.domain_name)) FROM mail_aliases AS v, mail_users AS u, mail_domains AS d, mail_domains AS da WHERE v.ID_USER=u.id_USER AND u.ID_DOMAIN=d.ID_DOMAIN AND v.ID_DOMAIN=da.ID_DOMAIN AND da.domain_name='%d' AND v.alias_local='%u'

/etc/postfix/virtual-domains.cf

# By far the simplest of these.
hosts = unix:/var/run/mysqld/mysqld.sock
user = vmreader
password = passforvmreader
dbname = mail
query = SELECT 1 FROM mail_domains WHERE active=1 AND domain_name='%s'

/etc/postfix/virtual-users.cf

# Like above - just checks if a user exists.
hosts = unix:/var/run/mysqld/mysqld.sock
user = vmreader
password = passforvmreader
dbname = mail
query = SELECT 1 FROM mail_users AS u, mail_domains AS d WHERE u.isactive >= 1 AND d.ID_DOMAIN=u.ID_DOMAIN AND d.domain_name='%d' AND u.username='%u'