ASCII Smiley Face Daniel Dickinson Mini Headshot
The C Shore

Mail Gateway HOWTO

Yet Another Mail Gateway HOWTO

Version 0.1.0 (pre-alpha)

Copyright © 2018 Daniel F. Dickinson

Released under Creative Commons Attribution Share-Alike 4.0 License

Note

This is currently in ‘recipe’ format and doesn’t explain why or go into depth. Future plan for this doc is to be more detailed in those areas.

What you get

  • Incoming mail gateway (also can be a backup MX)
    • SMTP for incoming mail with queuing and forwarding to delivering MX when it is available.
    • We handle aliases to actual mailboxes
    • ‘actual mailboxes’ is handled by whatever handles the LMTP for recipients we’ve identified as having a mailbox and just being an alias.
  • Security
    • STARTTLS SSL when possible.
    • Only relay port 25 mail whose destination is the delivering MX.
    • Avoid being a backscatter source
      • Accept only mail for mailboxes known to the delivering mailhost or aliases shared among the gateway hosts (if more than one).
      • Sync known mailboxes and aliases with delivering mailhost (pushed to us when delivering mailhost is updated).
      • No bounces after mail accepted.
    • RBL antispam measures
    • Incoming DKIM, DMARC, and SPF (check if others’ mail is spoofed), bypass from VPN(?))
    • Outgoing DKIM, DMARC, and SPF (reducing opportunities for others to spoof us, if other servers check for these).
    • Use Let’s Encrypt HTTPS certificates from web server
    • Delivering mailhost only accessible via ‘Server VPN’ (doesn’t accept mail from random strangers nor serves delivered mail to strangers)
    • Transfers to final destination performed over VPN and LMTP TLS to delivering mailhost
    • When used as a Backup MX don’t relay through another MX, but deliver to delivering mailhost as above
      • Because we sync allowed aliases and mailboxes there is no reason to add the complication of going through a single point of failure (except of course the delivering mailhost, and in a large installation this too can be distributed among servers).
    • Outgoing mail gateway (point of origin load balances which gateway to use)
      • STARTTLS SSL when possible for outgoing mail
      • STARTTLS SSL required on the Submission port (i.e. the only port on which we allow mail to destinations other than our own domain(s))
      • SMTP AUTH required on the Submission port
      • Submission port only allowed for ‘Client VPN’ users
      • Outgoing DKIM, DMARC, and SPF (reducing opportunities for others to spoof us, if other servers check for these).
  • Admin
    • Some attack reduction and blocking
    • Some stats

Not in this document

Future work

  • DNSSEC in order to enable DANE

Out of Scope (i.e. Lots of Other Documentation Sources for These)

  • Initial host/instance setup
  • General admin utilities and convenience setup
  • Creating local certificates for private communication
  • Details on setting up OpenVPN for private channel

Deliberately Excluded

  • Bayesian spam filtering
    • For a personal server it makes more sense to take advantage of your client side spam filter because you won’t get all that much spam with this setup (the lightweight measures incorporated here take care of the majority of spam).
  • Virtual domains; for a personal server it’s overkill

Prerequisites

  • An internet accessible host with static IP (and no history of abuse)
    • NB This is a pretty much a hard requirement for a gateway mx because we want to be sure to receive and send mail without being restricted.
  • DNS Service (such as dyn.com, self-hosted, etc)
  • For this HOWTO: CentOS 7, 2 GiB RAM, 10 GiB HD (e.g. virtual HD)
  • Repos
    • Defaults + EPEL (to install epel do yum install epel-release)
  • Let’s Encrypt certificate and renewal mechanism (e.g. such as described in Yet Another Web Server HOWTO).

Packages

The following packages need to be installed for this setup (e.g. yum install package1 package2 ...)

Admin Tools

  • policycoreutils
  • policycoreutils-python

Mail Server

  • postfix

Authentication

  • cyrus-sasl
  • cyrus-sasl-plain
  • cyrus-sasl-md5

Antispam and Antispoofing

  • opendkim
  • opendmarc

Virus Detection / Filtering

  • clamav
  • clamav-scanner
  • clamav-scanner-systemd
  • clamav-milter
  • clamav-update

Stats

  • awstats

Attack Detection / Blocking

  • fail2ban
  • fail2ban-firewalld
  • fail2ban-server

VPN

  • openvpn

First Steps

  1. Configure networking,admin users etc for your host/instance
  2. (Optional) Install your preferred admin/monitoring utilities etc.
  3. Install “Admin Tools” listed above
  4. Add ‘EPEL’ repository listed above

Mail Server Configuration

Prerequisites: * Let’s Encrypt certificate for mail2.example.com * OpenVPN configured to allow the mail gateway to talk to the delivery mailhost (via LMTP) and for the local mailhost (usually the delivery host) to use talk the mail gateway to submit mail for relaying (i.e. the local mailhost is what clients will talk to and the local mailhost will talk to the gateway to send mail to to the internet). * Some guides for OpenVPN: * TBD

SMTP Inbound Preliminaries

  1. Install main “Mail Server” packages above
  2. Edit /etc/postfix/main.cf

    1. Set myhostname to your host’s hostname (e.g. myhostname = mail2.example.com)
    2. Set myorigin to $mydomain
    3. Set mydestination to $myhostname, localhost.$mydomain, localhost.localdomain, localhost, $mydomain, subdomain.$mydomain, ... (We don’t distinguish between mail from different subdomains in the HOWTO; If you need that you need to had virtual hosting parameters).
    4. Use unknown_local_recipient_reject_code = 550 5 Use local_recipient_maps = $alias_maps /etc/postfix/rcpt_mailboxes
    5. Set mynetworks_style to host
    6. Set

      smtpd_relay_restrictions =
          reject_invalid_hostname,
          reject_unknown_recipient_domain,
          permit_mynetworks,
          reject_unauth_destination
      
      smtpd_recipient_restrictions =
          reject_invalid_hostname,
          reject_unverified_recipient,
          permit_mynetworks,
          reject_rbl_client zen.spamhaus.org,
          permit
      
      smtpd_data_restrictions = reject_unauth_pipelining
      
    7. Comment out home_mailbox

    8. Comment out mail_spool_directory

    9. Comment out mailbox_command

    10. Set mailbox_transport = lmtp:inet:<openvpn_ip_of_delivery_mailhost>:<port>

    11. Comment out smtpd_banner

    12. Set

        smtpd_tls_cert_file = /etc/letsenrypt/live/mail2.example.com/fullchain.pem
        smtpd_tls_key_file = /etc/letsencrypt/live/mail2.example.com/privkey.pem
        smtpd_tls_security_level = may
        smtpd_tls_auth_only = yes
      
    13. For LMTP to the delivery mailhost you may wish to enable TLS as well. If so you, also need: lmtp_tls_CAfile = /path/to/possibly-private-CA.crt lmtp_tls_cert_file = /path/to/client-cert-on-possibly-private-CA.crt lmtp_tls_key_file = /path/to/client-key-on-possibly-private-CA.key lmtp_tls_security_level = encrypt

Virus Detection / Filtering

NB: To avoid becoming a backscatter source it is important that your Gateway MX does not accept mail your other gateways do not. That means using the same Antivirus/Antispam measures as in all your MXes, for the same users. Also you must not queue mail then bounce it. Mail should either be rejected at the SMTP stage, or delivered (even if only to to quarantine or junk mail folder).

SELinux Tweaks

  1. Create a file clamavmilter.te (e.g. in /root) as below

     module clamavmilter 1.0;
    
     require {
         type antivirus_t;
         type milter_port_t;
         class tcp_socket { name_bind };
     }
    
     #============= antivirus_t ==============
     allow antivirus_t milter_port_t:tcp_socket name_bind;
    
    1. Execute checkmodule -M -m clamavmilter.te -o clamavmilter.mod
    2. Perform semodule_package -o clamavmilter.pp -m clamavmilter.mod
    3. Run semodule -i clamavmilter.pp

Antivirus Mail Filter (Milter)

  1. Install “Virus Detection / Filtering” packages from the list earlier in this document.
  2. Edit /etc/clamd.d/scan.conf with the following (in addition to defaults)
    1. Comment out Example
    2. LogFacility LOG_MAIL
    3. LocalSocket /var/run/clamd.scan/clamd.sock
    4. LocalSocketGroup virusgroup
    5. LocalSocketMode 660
    6. FixStateSocket yes
    7. PhishingScanURLs no
    8. ExitOnOOM yes
  3. And setsebool -P antivirus_use_jit on
  4. Run freshclam
  5. Execute systemctl enable clamd@scan && systemctl restart clamd@scan
  6. Edit /etc/mail/clamav-milter.conf with the following (in addition to defaults)
    1. Comment out Example
    2. MilterSocket inet:8894@localhost
    3. ClamdSocket unix:/var/run/clamd.scan/clamd.sock
    4. OnInfected Reject
    5. AddHeader Add
    6. LogFacility LOG_MAIL
    7. LogInfected Basic
    8. SupportMultipleRecipients yes
  7. Run semanage port --add -t milter_port_t -p tcp 8894
  8. Execute usermod -a -G clamscan clamilt
  9. Perform systemctl enable clamav-milter && systemctl restart clamav-milter
  10. To /etc/postfix/main.cf add

       smtpd_milters = inet:localhost:8894
       milter_default_action = tempfail
    
  11. Do postfix reload

Antispam / Antispoofing

  1. Install “Antispam / Antispoofing” packages from list earlier in this document.
  2. Edit /etc/opendkim.conf such that the following are set (in addition to defaults):
    1. ReportAddress "Example.com Postmaster" <postmaster@example.com>
    2. QueryCache yes
    3. Comment out KeyFile /etc/opendkim/keys/default.private
  3. Perform systemctl enable opendkim && systemctl start opendkim
  4. Add the following script as /etc/cron.daily/opendmarc_public_suffix_list and make it executable.

        #!/bin/sh
    
        /usr/bin/wget -q -N -P /etc/opendmarc https://publicsuffix.org/list/effective_tld_names.dat
        /bin/chown opendmarc:opendmarc /etc/opendmarc/effective_tld_names.dat
    
    1. Edit /etc/opendmarc.conf such that the following are set (in addition to defaults):
      1. AuthservID HOSTNAME
      2. Autorestart true
      3. AutorestartRate 3/1m
      4. PublicSuffixList /etc/opendmarc/effective_tld_names.dat
      5. SyslogFacility mail
    2. Run systemctl enable opendmarc && systemctl start opendmarc
    3. To /etc/postfix/main.cf add (assuming also doing antivirus above)

      smtpd_milters = inet:localhost:8894 inet:localhost:8891 inet:localhost:8893
      non_smtpd_milters = inet:localhost:8891 inet:localhost:8893
      milter_default_action = tempfail
      
    4. Do postfix reload

Firewall: Allow SMTP

  1. Execute the following:

      firewall-cmd --permanent --add-service smtp
      firewall-cmd --complete-reload
    

DNS Setup

  • For IPv4 make sure you have DNS A records for your mail host e.g. mail2.example.com points to 3.48.28.88 (note this should not be a CNAME for the configuration in this HOWTO).
  • Also make sure reverse DNS entries are present: This is usually a service provided by your hosting provider (since they own the IP block and you can’t setup IP -> Name records without them) or ISP (if hosting on static IPs on an ISP that allows mail hosting on your own hardware).
  • Likewise for DNS for ipv6 if you support it (AAAA records in that case).
  • Assuming you already have an MX record for your primary MX, with priority < 20, create an an second DNS MX type entry for your domain (e.g. example.com) with contents 20 mail2.example.com.. If you have any additional backup MXes, repeat, replacing the 20 with larger numbers (e.g. 30, 40, …). The number represents the priority for remote mail hosts sending mail to your domain, with lower numbers being higher priority (earlier in the ordering of hosts).
  • The details depend on the DNS provider or server you are using.

SMTP Outbound Preliminaries

NB If you wish outbound can be on a different server. Also note this configuration is predicated on the assumption you are using something like Dovecot’s Submission relay over a secure channel (i.e. not exposed to the internet at large). Dovecot in this scenario authenticates the user and it’s Dovecot that authenticates using the credentials below.

  1. Add the following to /etc/postfix/main.cf

      smtp_tls_cert_file = /etc/letsenrypt/live/mail2.example.com/fullchain.pem
      smtp_tls_key_file = /etc/letsencrypt/live/mail2.example.com/privkey.pem
      smtp_tls_security_level = may
    
  2. Create Submission user(s): If using Dovecot Submission relay auth is already done by Dovecot so you only need one user for Dovecot to auth to Postfix (for this HOWTO).

    1. Ensure /etc/sasl2/smtpd.conf exists as below (plain and login are required and smtpd_tls_auth_only ensures that they will only be used over encrypted channels).

      pwcheck_method: auxprop mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5

    2. Restart saslauthd: systemctl enable saslauthd && systemctl restart saslauthd

    3. Create a user: saslpasswd2 -c -u example.com submission

    4. Execute chown root:postfix /etc/sasldb2

    5. Do chmod 640 /etc/sasldb2

SMTP (Outbound from SMTP AUTH users only)

Authentication against the Dovecot user database (below) is required via SMTP AUTH in order to send emails to the outside world.

  1. Execute semanage port --add -t smtp_port_t -p tcp 32841 This way we’re not exposing the normal submission port which avoids a lot of log spam from unsolicited logins.
  2. To /etc/postfix/master.cf, below the line with #tlsproxy ... add:

      32841 inet n       -       n       -       -       smtpd
      -o syslog_name=postfix/submission
      -o smtpd_sasl_path=sasl2/smtpd.conf
      -o smtpd_tls_security_level=encrypt
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_sasl_security_options=noanonymous,noplaintext
      -o smtpd_sasl_tls_security_options=noanonymous
      -o smtpd_sasl_local_domain=$mydomain
      -o smtpd_client_restrictions=permit_sasl_authenticated,reject
      -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
      -o smtpd_relay_restrictions=permit_sasl_authenticated,reject_unauth_destination
    
  3. Do firewall-cmd --permanent --new-service=altsubmission

  4. Do firewall-cmd --permanent --service=altsubmission --add-port=32841/tcp

  5. Execute firewall-cmd --permanent --zone=work --add-service=altsubmission

  6. Run firewall-cmd --complete-reload

Antispoofing (Outbound)

In order to help other servers ensure they are only accepting legitimate mail from your server(s), you should enable SPF and DKIM signing of outbout mail.

  1. Enable SPF for your domain. This is a DNS exercise. I recommend reading the various documents under “Deploying SPF” on the Open SPF Site
  2. Enable DKIM signing for your outbound mail gateway:

    1. cd /etc/opendkim/keys
    2. Execute opendkim-genkey -s $(hostname -s) --domain=$(hostname -d) --testmode (assuming you want your selector to by your gateway’s short name (e.g. mail2) and you are doing this for the domain your host reports by default (e.g. the results of hostname -d). Testmode reduces likeliehood you mail will be rejected until your remove the “t=y;” from your DNS TXT record (see below)
    3. Assuming your hostname is mail2.example.com you will have two files in this directory, named mail2.ke and mail2.txt. mail2.private is your private key for DKIM signing and needs restrictive permissions (the generator does this by default).
    4. chown opendkim:opendkim /etc/opendkim/keys/mail2.private
    5. Edit /etc/opendkim.conf as follows:
      1. Set Mode sv
      2. Set Domain example.com (obviously this should be your domain)
      3. Set Selector mail2
      4. Set KeyFile /etc/opendkim/keys/mail2.private
    6. Execute systemctl restart opendkim
    7. At your DNS provider create a TXT record name mail2._domainkey with the part of the contents of mail2.txt which look similar to

      "v=DKIM1; k=rsa; t=y; "
      "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHY7Zl+n3SUldTYRUEU1BErHkKN0Ya52gazp1R7FA7vN5RddPxW/sO9JVRLiWg6iAE4hxBp42YKfxOwEnxPADbBuiELKZ2ddxo2aDFAb9U/lp47k45u5i2T1AlEBeurUbdKh7Nypq4lLMXC2FHhezK33BuYR+3L7jxVj7FATylhwIDAQAB"
      

      NB If your provider only accepts a single line for TXT records, use a record such as:

      "v=DKIM1; k=rsa; t=y; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHY7Zl+n3SUldTYRUEU1BErHkKN0Ya52gazp1R7FA7vN5RddPxW/sO9JVRLiWg6iAE4hxBp42YKfxOwEnxPADbBuiELKZ2ddxo2aDFAb9U/lp47k45u5i2T1AlEBeurUbdKh7Nypq4lLMXC2FHhezK33BuYR+3L7jxVj7FATylhwIDAQAB"
      
    8. Send a test email to check-auth@verifier.port25.com

    9. If everything checks out okay, remove t=y; from your DNS record and (after TTL expired) send one more verification test.

Stats Configuration

Prerequisites: AWStats non-CGI setup such as the one described in Yet Another Web Server HOWTO

AWStats Configuration

  1. In /etc/awstats

    1. For mail server (SMTP) log stats

      1. cp awstats.localhost.localdomain.conf awstats.mail2.conf
      2. Set LogFile="/usr/share/awstats/tools/maillogconvert.pl standard < /var/log/maillog* |
      3. Set LogType=M
      4. Set the following:

        LogFormat="%time2 $email %email_r %host %host_r %method %url %code %bytesd"
        LevelForBrowsersDetection=0
        LevelForOSDetection=0
        LevelForRefererAnalyze=0
        LevelForRobotsDetection=0
        LevelForWormsDetection=0
        LevelForSearchEnginesDetection=0
        LevelForFileTypesDetection=0
        ShowMenu=1
        ShowSummary=HB
        ShowMonthStats=HB
        ShowDaysOfMonthStats=HB
        ShowDaysOfWeekStats=HB
        ShowHoursStats=HB
        ShowDomainsStats=0
        ShowHostsStats=HBL
        ShowAuthenticatedUsers=0
        ShowRobotsStats=0
        ShowEMailSenders=HBML
        ShowEMailReceivers=HBML
        ShowSessionsStats=0
        ShowPagesStats=0
        ShowFileTypesStats=0
        ShowFileSizesStats=0
        ShowBrowsersStats=0
        ShowOSStats=0
        ShowOriginStats=0
        ShowKeyphrasesStats=0
        ShowKeywordsStats=0
        ShowMiscStats=0
        ShowHTTPErrorsStats=0
        ShowSMTPErrorsStats=1
        SiteDomain=mail2.example.com
        

        HostAliases=“mail2.example.com”

        1. Comment out DirIcons=/awstatsicons

Cron Configuration

  1. To /etc/cron.hourly/awstats, add:

      /usr/share/awstats/tools/awstats_buildstaticpages.pl -config=mail2 -update -configdir="/etc/awstats" -awstatsprog="/usr/share/awstats/wwwroot/cgi-bin/awstats.pl -dir=/var/www/vhosts/mail2.exampe.com/awstats" >/dev/null
    
  2. And for full stats for the entire year, to /etc/cron.daily/awstats, add:

      /usr/share/awstats/tools/awstats_buildstaticpages.pl -config=mail2 -update -month=all -configdir="/etc/awstats" -awstatsprog="/usr/share/awstats/wwwroot/cgi-bin/awstats.pl -dir=/var/www/vhosts/mail2.example.com/awstats-year" >/dev/null
    

Attack Detection/Blocking

  1. To /etc/fail2bain/jail.local add the following:

      [postfix]
      port = smtp,submission,32841
      enabled = true
    
      [postfix-sasl]
      port = smtp,submission,32841
      enabled = true
    
      [postfix-rbl]
      port = smtp,submission,32841
      enabled = true
    
  2. And finally execute systemctl enable fail2ban && systemctl restart fail2ban