Rockstable Wiki:

SMTP

Netiquette

Analogue to
IETF RFC2142 MAILBOX NAMES FOR COMMON SERVICES, ROLES AND FUNCTIONS
it is recommended to support the following addresses.

Mailbox

Area

Usage

ABUSE

Customer Relations

Inappropriate public behaviour

NOC

Network Operations

Network infrastructure

SECURITY

Network Security

Security bulletins or queries

POSTMASTER

SMTP

Email related messages

HOSTMASTER

DNS

Domain related requests

WEBMASTER

HTTP

Error reports of users regarding webservices

PRIVACY

Privacy Security

Privacy violations

ROOT

Adminstration

General reports, notices generated by hosts

Specially postmaster and hostmaster are indispensable. These addresses should exist for every domain. To keep maintenance limited, i suggest creating aliases to central mailboxes of a selected domain.

/etc/aliases

   1 # See man 5 aliases for format
   2 root:           root@rockstable.it
   3 clamav:         root
   4 mailer-daemon:  postmaster
   5 
   6 postmaster:     postmaster@rockstable.it
   7 hostmaster:     hostmaster@rockstable.it
   8 webmaster:      webmaster@rockstable.it
   9 
  10 abuse:          abuse@rockstable.it
  11 noc:            noc@rockstable.it
  12 security:       security@rockstable.it
  13 privacy:        privacy@rockstable.it

newaliases

Dynamic IPs

Don't create a SMTP server on a dynamic IP. The provider dependent address space will probably be listed on a Blocklist like Spamhouse PBL. Spamhouse PBL Example for Vodafone dynamic IPs

A MX will work, but you will need a SMTP relay to send email to somebody that checks these blocklists (like freenet).

DNS records

Please see DNS#Mail eXchanger (MX)

Autodiscovery

Possibilities:

Sender Policy Framwork (SPF)

Only the server behind MX-Record may send email. v=spf1 mx -all Example SPF resource record

   1 $TTL    86400
   2 $ORIGIN rockstable.it.
   3 
   4 ; SOA RECORD WITH INCREMENTED SERIAL OMITTED
   5 
   6 @               MX      10      mx1
   7                 TXT     "v=spf1 mx -all"

Domain-based Message Authentication, Reporting and Conformance (DMARC)

Domain-based Message Authentication, Reporting, and Conformance (DMARC) is a scalable mechanism by which a mail-originating organization can express domain-level policies and preferences for message validation, disposition, and reporting, that a mail-receiving organization can use to improve mail handling.

Subdomains may have a separate DMARC-record, which is to be checked first and fallen back to the parent later.

Example DMARC resource record

   1 $TTL    86400
   2 $ORIGIN rockstable.it.
   3 
   4 ; SOA RECORD WITH INCREMENTED SERIAL OMITTED
   5 
   6 @               MX      10      mx1
   7 _dmarc          TXT     ( "v=DMARC1; p=none; pct=100; "
   8                           "rua=mailto:postmaster@rockstable.it!10m; "
   9                           "ruf=mailto:postmaster@rockstable.it!10m; "
  10                           "adkim=s; aspf=r"
  11 )
  12 ;rockstable.it._report._dmarc    TXT     "v=DMARC1;"

Key

Existence

Description

Values

Default

v=

REQUIRED

Version

must be DMARC1 and first in list

-

p=

REQUIRED

Requested Mail Receiver policy

none,quarantine or reject

-

sp=

OPTIONAL

Requested Mail Receiver policy for all subdomains

none,quarantine or reject

apply p=

pct=

OPTIONAL

Percentage of messages to be applied

0…100

100

fo

OPTIONAL

Failure reporting options

0 if all fail, 1 if any fail, d DKIM-specific or s SPF-specific

0

rf

OPTIONAL

Report format

arfr auth-failure report type

afrf

ri

OPTIONAL

Report interval [s]

u_int32

86400

rua=

OPTIONAL

Report user aggregation

mailto:comma@separat.ed,mailto:email@address.es

-

ruf=

OPTIONAL

Report user failure infomation

comma@separat.ed, email@address.es

-

adkim=

OPTIONAL

DKIM Identifier Alignment

s strict, r relaxed

r

aspf=

OPTIONAL

SPF Identifier Alignment

s strict, r relaxed

r

A DMARC report may also be send to a third party email-address. In this case the third party has to publish a record, to proof that it wants to receive the report. Which may also be a wildcard.

   1 $TTL    86400
   2 $ORIGIN thirdparty.example.net.
   3 
   4 example.com._report._dmarc  IN  TXT  "v=DMARC1"
   5 *._report._dmarc            IN  TXT  "v=DMARC1"

postfix

About

Possible system component layout

mail_system_layout.dot

attachment:mail_system_layout.dot.svg

Performance

CPU-Layout

Postfix spawns many processes and benefits from more CPUs.

So a dual core system (single socekt, sharing the same cache) is mandatory and probably also enough.

Mount a tmpfs in /tmp

Please see tmpfs

Install

   1 aptitude install mailutils postfix postfix-pcre exim4-daemon-light-

Tool to edit berkely dbs

   1 aptitude install db-util

Unix-Permissions

   1 root@mail /etc/postfix # find /etc/postfix -type f -exec chmod o-rx {} \;
   2 root@mail /etc/postfix # find /etc/postfix/ -type d -exec chmod -v 750 {} \;
   3 der Modus von „/etc/postfix/“ wurde von 0755 (rwxr-xr-x) in 0750 (rwxr-x---) geändert
   4 der Modus von „/etc/postfix/sasl“ wurde von 0755 (rwxr-xr-x) in 0750 (rwxr-x---) geändert
   5 Modus von „/etc/postfix/ldap“ als 0750 (rwxr-x---) erhalten
   6 root@mail /etc/postfix # find /etc/postfix/ -type d -exec chgrp -v postfix {} \;
   7 die Gruppe von „/etc/postfix/“ wurde von root in postfix geändert
   8 die Gruppe von „/etc/postfix/sasl“ wurde von root in postfix geändert
   9 die Gruppe von „/etc/postfix/ldap“ wurde von root in postfix geändert
  10 root@mail /etc/postfix # find /etc/postfix/ -exec chgrp -v postfix {} \;
  11 die Gruppe von „/etc/postfix/“ wurde als postfix erhalten
  12 die Gruppe von „/etc/postfix/sasl“ wurde als postfix erhalten
  13 die Gruppe von „/etc/postfix/sasl/smtpd.conf“ wurde von root in postfix geändert
  14 die Gruppe von „/etc/postfix/postfix-files“ wurde von root in postfix geändert
  15 die Gruppe von „/etc/postfix/postfix-script“ wurde von root in postfix geändert
  16 die Gruppe von „/etc/postfix/post-install“ wurde von root in postfix geändert
  17 die Gruppe von „/etc/postfix/dynamicmaps.cf“ wurde von root in postfix geändert
  18 die Gruppe von „/etc/postfix/master.cf“ wurde von root in postfix geändert
  19 die Gruppe von „/etc/postfix/ldap“ wurde als postfix erhalten
  20 die Gruppe von „/etc/postfix/ldap/mailenabled_distgroups.cf“ wurde von root in postfix geändert
  21 die Gruppe von „/etc/postfix/ldap/virtual_alias_maps_mailforwarding.cf“ wurde von root in postfix geändert
  22 die Gruppe von „/etc/postfix/ldap/transport_maps.cf“ wurde von root in postfix geändert
  23 die Gruppe von „/etc/postfix/ldap/virtual_alias_maps_sharedfolders.cf“ wurde von root in postfix geändert
  24 die Gruppe von „/etc/postfix/ldap/mailenabled_dynamic_distgroups.cf“ wurde von root in postfix geändert
  25 die Gruppe von „/etc/postfix/ldap/local_recipient_maps.cf“ wurde von root in postfix geändert
  26 die Gruppe von „/etc/postfix/ldap/virtual_alias_maps.cf“ wurde von root in postfix geändert
  27 die Gruppe von „/etc/postfix/ldap/mydestination.cf“ wurde von root in postfix geändert
  28 die Gruppe von „/etc/postfix/transport.db“ wurde von root in postfix geändert
  29 die Gruppe von „/etc/postfix/header_checks.inbound“ wurde von root in postfix geändert
  30 die Gruppe von „/etc/postfix/header_checks.inbound.db“ wurde von root in postfix geändert
  31 die Gruppe von „/etc/postfix/header_checks.internal“ wurde von root in postfix geändert
  32 die Gruppe von „/etc/postfix/header_checks.internal.db“ wurde von root in postfix geändert
  33 die Gruppe von „/etc/postfix/header_checks.submission“ wurde von root in postfix geändert
  34 die Gruppe von „/etc/postfix/header_checks.submission.db“ wurde von root in postfix geändert
  35 die Gruppe von „/etc/postfix/transport“ wurde von root in postfix geändert
  36 die Gruppe von „/etc/postfix/main.cf“ wurde von root in postfix geändert

Certificate management

   1 adduser postfix ssl-cert

Mailname

/etc/mailname

   1 mx1.rockstable.it

Otherwise postfix crashes "fatal:

   1 Oct 15 12:40:59 mail1 postfix[23596]: warning: valid_hostname: invalid character 47(decimal): /etc/mailname
   2 Oct 15 12:40:59 mail1 postfix[23596]: fatal: file /etc/postfix/main.cf: parameter myhostname: bad parameter value: /etc/mailname

Why?

This is failsafe but unfortunately keeps hostspecific information in main.cf

   1 mydomain = rockstable.it
   2 myhostname = mx1.$mydomain
   3 myorigin = $myhostname
   4 mydestination = $myhostname, localhost.$mydomain, localhost

Initialize the configuration structure

Prepare configuration structure

   1 declare -a FILES_DB
   2 FILES_DB=(
   3         /etc/postfix/access_client \
   4         /etc/postfix/access_helo \
   5         /etc/postfix/access_recipient \
   6         /etc/postfix/access_sender \
   7         /etc/postfix/body_checks \
   8         /etc/postfix/esmtp_access \
   9         /etc/postfix/header_checks \
  10         /etc/postfix/relay_domains \
  11         /etc/postfix/transport \
  12         /etc/postfix/virtual \
  13         /etc/postfix/virtual_alias_domains \
  14         /etc/postfix/virtual_mailbox_domains \
  15         )
  16 
  17 mkdir /etc/postfix/ldap
  18 for FILE in "${FILES_DB[@]}"; do
  19         install -o root -g root -m 0640 /dev/null "$FILE"  
  20 done

Trouble Shooting

postfix.org DEBUG README

When I had no clue what was going on, i took a look into the logs at a higher debugging level. In case of postfix it's as simple as adding -v to the appropriate line in /etc/postfix/master.cf. In this example smtpd -v.

/etc/postfix/master.cf

   1 # ==========================================================================
   2 # service type  private unpriv  chroot  wakeup  maxproc command + args
   3 #               (yes)   (yes)   (no)    (never) (100)
   4 # ==========================================================================
   5 smtp      inet  n       -       y       -       -       smtpd -v

In this case a reload is not sufficient, you need to restart the service.

   1 systemctl restart postfix.service

Don't forget to remove it later on.

Debug Peer List

To gather additional information about a misbehaving host add this line to
/etc/postfix/main.cf

   1 debug_peer_list = 127.0.0.1, REM.OTE.N.ET/SNM

And reload postfix

   1 postfix reload

Maintenance mode

/etc/postfix/main.cf

   1 ### MAINTENANCE MODE SWITCH -> soft_bounce
   2 ### CHANGES SERVER RESPONSES FROM
   3 ### HARD (5XX) to SOFT (4XX)
   4 ### DEFAULT: no
   5 soft_bounce = yes

And reload postfix

   1 postfix reload

Important commands and scripts

Don't forget about the following commands:

   1 postconf -d     #DEFAULTS
   2 postconf -n     #CHANGED
   3 postmap -q      #QUERY DATABASE FOR KEY
   4 mailq           #SHOW QUEUE
   5 postqueue -p    #SHOW QUEUE, SAME AS mailq
   6 postqueue -f    #FLUSH/SEND ANY MAIL IN QUEUE
   7 postqueue -i ID #IMMEDIATLY REQUEUE MAIL WITH ID
   8 postsuper -d ID #DELETE QUEUED EMAIL
   9 db_dump         #SHOW CONTENT OF BERKELEY DBS
  10 

Makefile

Nice trick to save some work in common an repeating tasks - use a Makefile.

Install make

   1 aptitude install make

/etc/postfix/Makefile

   1 postmap:
   2         postmap /etc/postfix/access_client
   3         postmap /etc/postfix/access_helo
   4         postmap /etc/postfix/access_sender
   5         postmap /etc/postfix/access_recipient
   6         postmap /etc/postfix/body_checks
   7         postmap /etc/postfix/esmtp_access
   8         postmap /etc/postfix/header_checks
   9         postmap /etc/postfix/relay_domains
  10         postmap /etc/postfix/transport
  11         postmap /etc/postfix/virtual
  12         postmap /etc/postfix/virtual_alias_domains
  13         postmap /etc/postfix/virtual_mailbox_domains
  14         newaliases
  15         @echo "Postfix databases refreshed - changes active."
  16 
  17 version_vim = $(shell dpkg-query --showformat='$${Version}' -W vim \
  18         |sed -r '/^$$/d;s/^(.*\:)?([0-9]+\.[0-9]).*/\2/' \
  19         |sed 's/\.//')
  20 
  21 vim_syntax:
  22         cat vim_syntax.vim >> /usr/share/vim/vim${version_vim}/syntax/pfmain.vim
  23 
  24 rsync_opts := -CavuzbP
  25 excludes   := --exclude '*.db' --exclude 'main.cf*' --exclude '*.swp'
  26 spath := /etc/postfix
  27 push_mail1:
  28         rsync ${rsync_opts} ${excludes} ${spath}/ mail1:${spath}
  29         rsync ${rsync_opts} /etc/aliases mail1:/etc/aliases
  30 pull_mail1:
  31         rsync ${rsync_opts} ${excludes} mail1:${spath}/ ${spath}
  32         rsync ${rsync_opts} mail1:/etc/aliases /etc/aliases
  33 
  34 push_mail3:
  35         rsync ${rsync_opts} ${excludes} ${spath}/ mail3:${spath}
  36         rsync ${rsync_opts} /etc/aliases mail3:/etc/aliases
  37 pull_mail3:
  38         rsync ${rsync_opts} ${excludes} mail3:${spath}/ ${spath}
  39         rsync ${rsync_opts} mail3:/etc/aliases /etc/aliases
  40 
  41 sync2mail1: push_mail1 pull_mail1
  42 sync2mail3: push_mail3 pull_mail3

Hint: Makefiles don't like lines starting with Space.

Define a syntax-file to be appended
/etc/postfix/vim_syntax.vim

   1 syntax keyword pfmainConf ciphers_insecure
   2 syntax keyword pfmainConf cipher_suite_minimum
   3 syntax keyword pfmainConf protocols_insecure
   4 syntax keyword pfmainConf tls_security_level
   5 
   6 syntax match pfmainRef "$\<ciphers_insecure\>"
   7 syntax match pfmainRef "$\<cipher_suite_minimum\>"
   8 syntax match pfmainRef "$\<protocols_insecure\>"
   9 syntax match pfmainRef "$\<tls_security_level\>"

   1 make postmap

Find and delete a stuck email by address

   1 ### WITH INTERMEDIATE JSON
   2 postqueue -j \
   3         |jq -s '.[]|select(.recipients[]|.address=="DST_EMAIL@ADDRESS.TLD")|.queue_id' \
   4         |xargs -L1 postsuper -d
   5 
   6 ### OLD SCHOOL
   7 postqueue -p \
   8         |grep -B2 "DST_EMAIL@ADDRESS.TLD" \
   9         |cut -d\  -f1 \
  10         |grep -Eo '^[0-9A-F]+' \
  11         |xargs -L1 postsuper -d 

Or as a script
/usr/local/sbin/postfix_delete_mail.sh

   1 #!/bin/bash
   2 
   3 DST="$1"
   4 declare -a IDS
   5 
   6 if [ -n "$DST" ]; then
   7         IDS=( $(postqueue -p \
   8                 |grep -B2 "$DST" \
   9                 |cut -d\  -f1 \
  10                 |grep -Eo '^[0-9A-F]+')
  11         )
  12         [ "${#IDS[@]}" -gt 0 ] && \
  13                 xargs -n1 postsuper -d <<< "${IDS[@]}"
  14 else
  15         cat <<-EOL
  16         Please specify destination email address
  17         as first positional parameter.
  18         EOL
  19 fi

Redirect stuck mail

/usr/local/sbin/postfix_redirect_mail.sh

   1 #!/bin/bash -e
   2 
   3 declare -a IDS
   4 
   5 SELF="$(basename $0)"
   6 
   7 usage () {
   8         cat <<-EOF
   9         $SELF
  10 
  11                 -h|--help)              Display this page
  12                 -n|--new-dest)          New target email-address
  13                 -o|--old-dest)          Old target email-address
  14                 -s|--skip-header)       Skip the old header
  15         EOF
  16 }
  17 
  18 # Note that we use `"$@"' to let each command-line parameter expand to a
  19 # separate word. The quotes around `$@' are essential!
  20 # We need TEMP as the `eval set --' would nuke the return value of getopt.
  21 TEMP=`getopt -o hn:o:s --long help,new-dest:,old-dest:,skip-header \
  22      -n "$SELF" -- "$@"`
  23 
  24 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
  25 
  26 # Note the quotes around `$TEMP': they are essential!
  27 eval set -- "$TEMP"
  28 
  29 while true ; do
  30         case "$1" in
  31                 -h|--help) usage; exit ; shift ;;
  32                 -n|--new-dest) DST_NEW="$2" ; shift 2 ;;
  33                 -o|--old-dest) DST_OLD="$2" ; shift 2 ;;
  34                 -s|--skip-header) SKIP_HEADER=true ; shift ;;
  35                 #-c|--c-long)
  36                 #       # c has an optional argument. As we are in quoted mode,
  37                 #       # an empty parameter will be generated if its optional
  38                 #       # argument is not found.
  39                 #       case "$2" in
  40                 #               "") echo "Option c, no argument"; shift 2 ;;
  41                 #               *)  echo "Option c, argument \`$2'" ; shift 2 ;;
  42                 #       esac ;;
  43                 --) shift ; break ;;
  44                 *) echo "Internal error!" ; exit 1 ;;
  45         esac
  46 done
  47 #echo "Remaining arguments:"
  48 #for arg do echo '--> '"\`$arg'" ; done
  49 
  50 ### SANITIZE USER INPUT
  51 if [ -z "$DST_NEW" ]; then
  52         echo "Please specify a new destination."
  53         exit 1
  54 elif [ -z "$DST_OLD" ];then
  55         echo "Please specify a old destination."
  56         exit 1
  57 fi
  58 
  59 ### GET MAIL FOR DST
  60 #IDS="$(postqueue -j \
  61 #       |jq -s '.[]|select(.recipients[]|.address=="'"$DST_OLD"'")|.queue_id' )"
  62 
  63 IDS=( $(postqueue -p \
  64         |grep -B2 "$DST_OLD" \
  65         |cut -d\  -f1 \
  66         |grep -Eo '^[0-9A-F]+')
  67 )
  68 
  69 if [ "${#IDS[@]}" -gt 0 ]; then
  70         DIR_TMP=$(mktemp -d)
  71 
  72         echo -e "Mail-IDs to be redirected:\n${IDS[@]}"
  73         ### PUT EMAILS ON HOLD
  74         xargs -L1 postsuper -h <<< ${IDS[@]}
  75 
  76         for ID in "${IDS[@]}"; do
  77                 if [ $SKIP_HEADER ]; then
  78                         postcat -qb "$ID" > "$DIR_TMP/$ID"
  79                 else
  80                         postcat -qbh "$ID" > "$DIR_TMP/$ID"
  81                 fi
  82 
  83                 FROM="$(postcat -qh "$ID" \
  84                         |grep '^From: ' \
  85                         |sed -r 's/^From: //')"
  86 
  87                 sendmail -f "$FROM" "$DST_NEW" < "$DIR_TMP/$ID"
  88                 [ "$?" == 0 ] && postsuper -d "$ID"
  89                 rm "$DIR_TMP/$ID"
  90         done
  91         rmdir "$DIR_TMP"
  92 else
  93         echo "No mail in queue for '$DST_OLD'. Exiting …"
  94 fi

Query MX-records

   1 while read LINE; do
   2         MX_RECORD="$(dig -t MX "$LINE" +short)";
   3         echo -e "$LINE:"'\t'"$MX_RECORD";
   4 done <<< "$(awk '{print $1}' /etc/postfix/virtual_mailbox_domains)" \
   5 | column -t

query_mx.sh

A extended version of this idea wrapped in a shell script queries MX records by their priority, resolves their corresponding A records. It also gathers the A-Records of the domain, because they are used as a fallback, if no MX-record is found. Furthermore it gathers the SPF and DMARC TXT-records. It proofed to be very useful during the establishment or migration of mail-servers.

query_mx.sh

   1 #!/bin/bash
   2 
   3 ### DOMAINS SHOULD BE A PATH TO A FILE
   4 FILE_DOMAINS="$1"
   5 
   6 if [ -f "$FILE_DOMAINS" ]; then
   7         DOMAINS="$(awk '{print $1}' "$FILE_DOMAINS" \
   8                 |grep -v '^\s*#')"
   9 
  10 else
  11         echo "File '$FILE_DOMAINS' does not exist."
  12         exit 1
  13 fi
  14 
  15 for DOMAIN in $DOMAINS; do
  16         echo
  17         declare -A MX_A_RECORDS MX_RECORDS
  18         MX_RECORDS_RAW="$(dig -t MX "$DOMAIN" +short)";
  19         ### FQDN OF MX IS KEY, PRIORITY IS VALUE
  20         eval MX_RECORDS=( "$(sed -r \
  21                 's/^([0-9]+)\s+([[:alnum:].-]+)/[\2]=\1/' \
  22                 <<< "$MX_RECORDS_RAW")" )
  23         if [ ${#MX_RECORDS[@]} -gt 0 ]; then
  24                 for MX in "${!MX_RECORDS[@]}"; do
  25                         MX_A_RECORDS+=( [$MX]="$(dig -t A "$MX" +short)" );
  26                 done
  27         fi
  28 
  29         declare -a A_RECORDS
  30         A_RECORDS=( $(dig -t A "$DOMAIN" +short) );
  31 
  32         TXT_RECORDS_RAW="$(dig -t TXT "$DOMAIN" +short)";
  33         TXT_RECORDS="$(grep -i -e spf <<< "$TXT_RECORDS_RAW")";
  34 
  35         DMARC_RECORD="$(dig -t TXT "_dmarc.$DOMAIN" +short)";
  36 
  37         cat <<-EOL
  38                 ### Domain: '$DOMAIN'
  39                 Number of MX-Records: ${#MX_RECORDS[@]}
  40         EOL
  41 
  42         if [ ${#MX_RECORDS[@]} -gt 0 ]; then
  43         cat <<-EOL
  44                 $(for MX in "${!MX_RECORDS[@]}"; do
  45                         echo -e "@ MX ${MX_RECORDS[$MX]} $MX"
  46                 done \
  47                 |column -t)
  48                 $(for MX in "${!MX_A_RECORDS[@]}"; do
  49                         echo -e "$MX A ${MX_A_RECORDS[$MX]}"
  50                 done \
  51                 |column -t)
  52         EOL
  53         fi
  54 
  55         cat <<-EOL
  56                 Number of A-Records: ${#A_RECORDS[@]}
  57         EOL
  58 
  59         if [ ${#A_RECORDS[@]} -gt 0 ]; then
  60         cat <<-EOL
  61                 $(for A_RECORD in "${A_RECORDS[@]}"; do
  62                         echo "@ A $A_RECORD"
  63                 done \
  64                 |column -t)
  65         EOL
  66         fi
  67 
  68         if [ -n "$TXT_RECORDS" ]; then
  69         cat <<-EOL
  70                 $(while read -r TXT; do
  71                         echo -e "@  TXT  $TXT"
  72                 done <<< "$TXT_RECORDS")
  73         EOL
  74         else
  75                 echo -e 'NO SPF;\t\tTXT record not found.'
  76         fi
  77 
  78         if [ "$DMARC_RECORD" ]; then
  79                 echo -e "@  TXT  $DMARC_RECORD"
  80         else
  81                 echo -e 'NO DMARC;\t_dmarc TXT record not found.'
  82         fi
  83 
  84 
  85         unset A_RECORDS MX_RECORDS MX_A_RECORDS TXT_RECORDS DMARC_RECORD
  86 done

Install the script

   1 install -o root -g root -m 755 query_mx.sh /usr/local/bin

Use it like

   1 query_mx.sh /etc/postfix/domains.txt \
   2         > "domains_$(date +%F_%T).txt"
   3 ### OR WITH YOUR POSTFIX DATABASES
   4 query_mx.sh /etc/postfix/virtual_alias_domains \
   5         > "domains_$(date +%F_%T).txt"

Compare custom config to postfix defaults

/usr/local/bin/postfix_compare_defaults.sh

   1 #!/bin/bash
   2 # Copyright Rockstable IT 2020
   3 # License: GPLv2+
   4 # Delivered as is, no warranty, no liability,…
   5 
   6 set -o pipefail
   7 
   8 ### INITIALIZE
   9 CONFIG_DIR="/etc/postfix"
  10 SELF="$(basename "$0")"
  11 
  12 usage () {
  13         cat <<-EOL
  14 
  15         $SELF - Compare postfix configuration to defaults.
  16 
  17         Synopsis:
  18             $PROGAM [-h|--help] [-c|--config_dir "/absolute/path/to/directory"]
  19 
  20         []: Optional
  21         EOL
  22 }
  23 
  24 
  25 # Note that we use `"$@"' to let each command-line parameter expand to a
  26 # separate word. The quotes around `$@' are essential!
  27 # We need TEMP as the `eval set --' would nuke the return value of getopt.
  28 TEMP=`getopt -o c:h --long config_dir:,help \
  29      -n "$SELF" -- "$@"`
  30 
  31 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
  32 
  33 # Note the quotes around `$TEMP': they are essential!
  34 eval set -- "$TEMP"
  35 
  36 while true ; do
  37         case "$1" in
  38                 -h|--help) usage; exit 0; shift ;;
  39                 -c|--config-dir) CONFIG_DIR="$2" ; shift 2 ;;
  40                 #-c|--c-long)
  41                 #       # c has an optional argument. As we are in quoted mode,
  42                 #       # an empty parameter will be generated if its optional
  43                 #       # argument is not found.
  44                 #       case "$2" in
  45                 #               "") echo "Option c, no argument"; shift 2 ;;
  46                 #               *)  echo "Option c, argument \`$2'" ; shift 2 ;;
  47                 #       esac ;;
  48                 --) shift ; break ;;
  49                 *) echo "Internal error!" ; exit 1 ;;
  50         esac
  51 done
  52 
  53 
  54 if [ "$CONFIG_DIR" == "/etc/postfix" ]; then
  55         echo "Comparing to default config_dir '/etc/postfix'";
  56         CONF_CURRENT="$(postconf -n)"
  57 elif [ -n "$CONFIG_DIR" ] && [ -d "$CONFIG_DIR" ]; then
  58         echo "Comparing to specifed config_dir '$CONFIG_DIR'";
  59         CONF_CURRENT="$(postconf -n -c "$CONFIG_DIR")"
  60 else
  61         cat <<-EOL
  62 
  63                 Error: Invalid argument to option '-c|--config_dir': '$CONFIG_DIR'.
  64                 Maybe:
  65                  * undefined
  66                  * not a directory
  67         EOL
  68         usage
  69         exit 1
  70 fi
  71 
  72 while read LINE; do
  73         KEY="$(sed -r 's/^([[:alnum:]_$-]+)\s*=.*$/\1/' <<< "$LINE")"
  74         echo -e "\n= Key: '$KEY' ="
  75         VALUE="$(sed -r 's/.*=\s*([^=]+)$/\1/' <<< "$LINE")"
  76         VALUE="$(fold -s <<< "$VALUE")"
  77         if [ -z "$VALUE" ] || grep -E '=$' <<< "$VALUE"; then
  78                 VALUE="NULL"
  79         fi
  80         echo -e "Current: '$VALUE'"
  81         DEFAULT="$(postconf -d "$KEY" 2>&1 \
  82                 |sed -r 's/.*=\s*([^=]+)$/\1/')"
  83         if grep -E 'unknown parameter' <<< "$DEFAULT"; then
  84                 echo "postconf -d '$KEY' -> warned unknown parameter - custom/typo?"
  85                 DEFAULT="NO DEFAULT"
  86         elif [ -z "$DEFAULT" ] || grep -Eq '=$' <<< "$DEFAULT"; then
  87                 DEFAULT="NULL"
  88         fi
  89         DEFAULT="$(fold -s <<< "$DEFAULT")"
  90         echo -e "Default: '$DEFAULT'"
  91 
  92 done <<< "${CONF_CURRENT}"

Analyse aliases file

/usr/local/bin/postfix_test_aliases.sh

   1 #!/bin/bash
   2 # Copyright Rockstable IT 2020
   3 # License: GPLv2+
   4 # Delivered as is, no warranty, no liability,…
   5 
   6 ### INITIALIZE
   7 declare -a DOMAIN_BLACKLIST
   8 BLACKLIST=false
   9 FILE="/etc/aliases"
  10 ALIAS_COUNT=0
  11 ADDRESS_COUNT=0
  12 KEEP=0
  13 KEEP_FDA=0
  14 KEEP_PURPOSE=0
  15 REMOVE=0
  16 REMOVE_BLACKLIST=0
  17 REMOVE_REDUNDANT=0
  18 LINE_NUMBER=0
  19 PRINT="ALL"
  20 PROGRAM="$(basename $0)"
  21 SYNTAX_ERROR_COUNT=0
  22 
  23 ### FUNCTIONS
  24 split () {
  25         local DELIM="$1"
  26         local STRING="$2"
  27         local IFS
  28         local OIFS
  29         ### ARRAY IS DECLARED LOCALLY (no -g)
  30         declare -a ARRAY
  31 
  32         OIFS="$IFS"
  33         IFS="$DELIM"
  34         ARRAY=( ${STRING// /} )
  35         IFS="$OIFS"
  36         echo "${ARRAY[@]}"
  37 }
  38 
  39 usage () {
  40         cat <<-EOL
  41         $PROGRAM - Analyse postfix aliases file
  42 
  43         Synopsis:
  44         $PROGRAM \\
  45                 [-a|--aliases /path/to/aliases] [-k|--keepable] [-r|--removeable [-s|--short] \\
  46                 [--] [domain1.tld] [domain2.tld2] […]
  47 
  48         []: optional
  49         (): mandatory
  50 
  51         Most useful when blacklist domain is specified.
  52         EOL
  53 }
  54 
  55 ### PARSE OPTIONS
  56 # Note that we use `"$@"' to let each command-line parameter expand to a
  57 # separate word. The quotes around `$@' are essential!
  58 # We need TEMP as the `eval set --' would nuke the return value of getopt.
  59 TEMP=`getopt -o a:hkrs --long aliases:,help,keepable,removeable,short \
  60      -n "$PROGRAM" -- "$@"`
  61 
  62 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
  63 
  64 # Note the quotes around `$TEMP': they are essential!
  65 eval set -- "$TEMP"
  66 
  67 while true ; do
  68         case "$1" in
  69                 -a|--aliases)           FILE="$2" ; shift 2 ;;
  70                 -h|--help)              usage; exit 0 ; shift ;;
  71                 -k|--keepable)          PRINT="KEEPABLE" ; shift ;;
  72                 -r|--removeable)        PRINT="REMOVABLE" ; shift ;;
  73                 -s|--short)             SHORT=true ; shift ;;
  74 
  75                 #-c|--c-long)
  76                 #       # c has an optional argument. As we are in quoted mode,
  77                 #       # an empty parameter will be generated if its optional
  78                 #       # argument is not found.
  79                 #       case "$2" in
  80                 #               "") echo "Option c, no argument"; shift 2 ;;
  81                 #               *)  echo "Option c, argument \`$2'" ; shift 2 ;;
  82                 #       esac ;;
  83                 --) shift ; break ;;
  84                 *) echo "Internal error!" ; exit 1 ;;
  85         esac
  86 done
  87 ##echo "Remaining arguments:"
  88 ##for arg do echo '--> '"\`$arg'" ; done
  89 
  90 DOMAIN_BLACKLIST=( "$@" )
  91 
  92 if [ ${#DOMAIN_BLACKLIST[@]} -ge 1 ]; then
  93         BLACKLIST=true
  94         echo "Blacklisted domains: '${DOMAIN_BLACKLIST[@]}'"
  95 fi
  96 
  97 ### MAIN
  98 
  99 # PREPARE FILE
 100 ALIASES_RAW="$(cat "$FILE")"
 101 ALIASES_CLEANED="$(sed -r '/^\s*(#.*)?$/d' <<< "$ALIASES_RAW")"
 102 ALIASES_JOINED="$(sed -zr 's/\n\s+/ /g' <<< "$ALIASES_CLEANED")"
 103 
 104 # ANALYSE LINES
 105 while read LINE; do
 106         ((ALIAS_COUNT++))
 107         unset DEST_ADDRESS
 108         unset FDA
 109 
 110         if grep -qE '^@' <<< "$LINE"; then
 111                 FDA=true
 112         fi
 113 
 114         ALIAS="$(sed -r 's/(^[^: ]+):?\s.*/\1/' <<< "$LINE")"
 115         DEST_STR="$(sed -r 's/(^[^: ]+):?\s+([^,]+)/\2/' <<< "$LINE")"
 116         LINE_NUMBER="$(awk '/'"$ALIAS"'/{ print NR; exit }' <<< "$ALIASES_RAW")"
 117 
 118         ### CHECK DESTINATION STRING
 119         COUNT_WORDS="$(wc -w <<< "$DEST_STR")"
 120         COUNT_COMMAS="$(tr -d -c ',' <<< "$DEST_STR" \
 121                 |awk '{ print length }')"
 122         if [ "$COUNT_WORDS" -gt 2 ] && \
 123                 [ "$COUNT_COMMAS" -lt "$((COUNT_WORDS-1))" ]; then
 124                 SYNTAX="Check of line '$LINE_NUMBER' failed:"
 125                 SYNTAX+=" Words: '$COUNT_WORDS'"
 126                 SYNTAX+=", Commas: '$COUNT_COMMAS'"
 127                 SYNTAX+=", Commas expected: '$((COUNT_WORDS-1))'"
 128                 ((SYNTAX_ERROR_COUNT++))
 129         else
 130                 SYNTAX="Fine."
 131         fi
 132 
 133         # GET LOCAL PART
 134         ALIAS_LOCAL="${ALIAS/@*/}"
 135         [ -z "$ALIAS_LOCAL" ] && ALIAS_LOCAL="NULL"
 136 
 137         # GET DOMAIN PART
 138         if grep -Eq '@[^@]+' <<< "${ALIAS}"; then
 139                 ALIAS_DOMAIN="${ALIAS/*@/}"
 140         else
 141                 ALIAS_DOMAIN="NULL"
 142         fi
 143 
 144         ### bash4.4
 145         #mapfile -d ';' -t LOCAL < <(printf '%s;' "${DEST/,\s+/,/}")
 146 
 147         declare -a DEST_ADDRESS
 148         DEST_ADDRESSES=( $(split "," "$DEST_STR") )
 149 
 150         for DEST_ADDRESS in ${DEST_ADDRESSES[@]}; do
 151                 ((ADDRESS_COUNT++))
 152                 unset ACTION
 153 
 154                 # GET LOCAL PART
 155                 DEST_LOCAL="${DEST_ADDRESS/@*/}"
 156                 [ -z "$DEST_LOCAL" ] && DEST_LOCAL="NULL"
 157 
 158                 # GET DOMAIN PART
 159                 if grep -Eq '@[^@]+' <<< "${DEST_ADDRESS}"; then
 160                         DEST_DOMAIN="${DEST_ADDRESS/*@/}"
 161                 else
 162                         DEST_DOMAIN="NULL"
 163                 fi
 164 
 165                 ### ANALYSE
 166                 if [ $FDA ]; then
 167                         ACTION="keep"
 168                         REASON="fda"
 169                 fi
 170 
 171                 if      [ $BLACKLIST ] && \
 172                         [ "$ALIAS_LOCAL" == "$DEST_LOCAL" ] && \
 173                         [ "$ALIAS_DOMAIN" == "NULL" ] && \
 174                         grep -Eq "$DEST_DOMAIN" <<< "${DOMAIN_BLACKLIST[@]}"; then
 175                         ACTION="remove"
 176                         REASON="blacklist"
 177                 elif [ "$ALIAS_LOCAL" == "$DEST_LOCAL" ] && \
 178                         [ "$ALIAS_DOMAIN" == "$DEST_DOMAIN" ]; then
 179                         ACTION="remove"
 180                         REASON="redundant"
 181                 else
 182                         ACTION="keep"
 183                         REASON="purpose"
 184                 fi
 185 
 186                 case "$ACTION" in
 187                         "keep") ((KEEP++));;
 188                         "remove") ((REMOVE++));;
 189                 esac
 190 
 191                 case "$REASON" in
 192                         "fda") ((KEEP_FDA++));;
 193                         "purpose") ((KEEP_PURPOSE++));;
 194                         "blacklist") ((REMOVE_BLACKLIST++));;
 195                         "redundant") ((REMOVE_REDUNDANT++));;
 196                 esac
 197 
 198 
 199                 ### OUTPUT
 200                 if [ "$PRINT" == "ALL" ] || \
 201                         ([ "$PRINT" == "REMOVABLE" ]    && [ "$ACTION" == "remove" ]) || \
 202                         ([ "$PRINT" == "KEEPABLE" ]     && [ "$ACTION" == "keep" ]) ; then
 203                         [ ! $SHORT ] && echo
 204                         echo "Alias: $ALIAS"
 205                         if [ ! $SHORT ]; then
 206                                 echo "Line:             $LINE_NUMBER"
 207                                 echo -e "Syntax:                $SYNTAX"
 208                                 echo "Raw Line: $LINE"
 209                                 echo "Alias local:      $ALIAS_LOCAL"
 210                                 echo "Alias domain:     $ALIAS_DOMAIN"
 211                                 echo "Dest Address:     $DEST_ADDRESS"
 212                                 echo "Dest Local:       $DEST_LOCAL"
 213                                 echo "Dest Domain:      $DEST_DOMAIN"
 214                                 echo "Action:           $ACTION"
 215                                 echo "Reason:           $REASON"
 216                                 [ $FDA ] && echo -e "Type:\t\tFull domain alias"
 217                         fi
 218                 fi
 219         done
 220 done <<< "$ALIASES_JOINED"
 221 
 222 REMOVE1=$((REMOVE_REDUNDANT+REMOVE_BLACKLIST))
 223 
 224 cat <<-EOL
 225 
 226 = Summary =
 227 
 228 $ALIAS_COUNT    aliases have been processed.
 229 $ADDRESS_COUNT  destination addresses have been compared.
 230 $KEEP   alias-destination pairs should be kept.
 231 $KEEP_FDA       alias-destination pairs are full domain aliases (catch all).
 232 $KEEP_PURPOSE   alias-destination pairs serve a purpuse.
 233 $REMOVE_REDUNDANT       alias-destination pairs are redundant.
 234 $REMOVE_BLACKLIST       alias-destination pairs are blacklisted.
 235 $REMOVE alias-destination pairs should be removed.
 236 $SYNTAX_ERROR_COUNT     syntax errors have been found.
 237 EOL

Send test mail

Send a test email from cli

   1 FROM="root@test1.example.org"
   2 DST="root"
   3 echo "Test1" |sendmail -f "$FROM" "$DST"

Or as a script /usr/local/bin/test_mail.sh

   1 #!/bin/bash
   2 
   3 FQDN="$(hostname -f)"
   4 FROM="$USER"
   5 TO="root"
   6 [ -n "$1" ] && FROM="$1"
   7 [ -n "$2" ] && TO="$2"
   8 
   9 {
  10 cat <<-EOF
  11 Subject: Test-Email from "$FQDN"
  12 
  13 Please ignore this email.
  14 It's is a simple testmail.
  15 
  16 HOST:   "$FQDN"
  17 FROM:   "$FROM"
  18 TO:     "$TO"
  19 
  20 EOF
  21 } |sendmail -f "$FROM" "$DST"

Compatibility Level

Postfix 3.0 introduces a safety net that runs Postfix programs with backwards-compatible default settings after an upgrade. The safety net will log a warning whenever a "new" default setting could have an negative effect on your mail flow.

Postfix compares the build in compatibility_level (at compile time) to the one specified in main.cf compatibility_level (default: 0). It determines settings in default state and takes actions if compatibility_level in main.cf is less than compiled in.

Sources:

Backwards-compatible default settings that may kick in:

Restrictions

TODO

   1 ### RESTRICTIONS
   2 ### SEE: http://www.postfix.org/SMTPD_ACCESS_README.html 
   3 ### ORDER:
   4 ### 1. client, helo, etrn
   5 ### 2. client, helo, sender, relay, recipient, data, or end-of-data
   6 ### EVALUATION IS DEFERED TO "RCPT TO"
   7 ### BY 'smtpd_delay_reject' = yes (DEFAULT: yes)

   1 

Choose your setup

Postfix:

  1. local ALIAS: shared domains, UNIX system accounts
  2. virtual MAILBOX: separate domains, non-UNIX accounts
  3. virtual ALIAS: separate domains, UNIX system accounts

vmail setup

Dovecot as backend

Please prepare the MDA dovecot first by following dovecot#vmail_setup and return here later on.

/etc/postfix/main.cf

   1 ### ALIASED DOMAINS AND ADDRESSES
   2 ### POINTING TO REMOTE DOMAINS
   3 #virtual_maps = 
   4 virtual_maps = hash:/etc/postfix/virtual
   5 virtual_alias_maps = $virtual_maps
   6 #virtual_alias_domains = $virtual_alias_maps
   7 virtual_alias_domains = hash:/etc/postfix/virtual_alias_domains
   8 
   9 ### MAILBOXED DOMAINS AND ADDRESSES WHERE THIS
  10 ### MTA IS FINAL TARGET
  11 
  12 ### ENSURE virtual_mailbox_maps IS EMPTY
  13 ### OR smtpd_reject_unlisted_recipient (default: yes)
  14 ### WILL ENFORCE EVERY USER TO BE LISTED IN THIS DB
  15 virtual_mailbox_maps = 
  16 #virtual_mailbox_domains = $virtual_mailbox_maps
  17 virtual_mailbox_domains = hash:/etc/postfix/virtual_mailbox_domains
  18 
  19 ### virtual_transport CAN BE OVERRIDDEN BY /etc/postfix/transports
  20 #virtual_transport = virtual
  21 virtual_transport = lmtp:unix:private/dovecot-lmtp

/etc/postfix/virtual_mailbox_domains

   1 ###PATTERN           WORD_IS_IGNORED
   2 rockstable.it        WORD

Query AD for virtual mailbox maps

If you don't have dovecot as a backend, you may use the following to proof that a mailbox exists by queriing AD/ldap.

Prepare your ADDC or ldap-server to support cryptography! If you use AD and bind to it with a username and password combination, you don't want your credentials to be transmitted in plain-text! The credentials should furthermore be system accounts, whose passwords do not expire.

Install the plugin postfix-ldap.

   1 aptitude install postfix-ldap

Provide the AD CA-Certificate at /etc/postfix/ldap/cacert.pem.

/etc/postfix/ldap/virtual_mailbox_users

   1 ### LOOKUP MAILBOX ADDRESSES IN AD
   2 ### FOR OPTIONS PLEASE SEE: man 5 ldap_table
   3 
   4 ### debuglevel (default: 0)
   5 #debuglevel = 0
   6 ### timeout (default: 10 seconds)
   7 timeout = 4
   8 ### version (default: 2)
   9 version = 3
  10 ### start_tls (default: no)
  11 start_tls = no
  12 ### tls_ca_cert_file (No default; set either this or tls_ca_cert_dir)
  13 tls_ca_cert_file = /etc/postfix/ldap/cacert.pem
  14 ### server_host (default: localhost)
  15 server_host =
  16         ldaps://dc1.example.com:636
  17         ldaps://dc2.example.com:636
  18 ### search_base (No default; you must configure this)
  19 search_base = CN=Users,DC=example,DC=com
  20 ### bind_dn (default: empty)
  21 bind_dn = CN=postfix,CN=Users,DC=example,DC=com
  22 ### bind_pw (default: empty)
  23 bind_pw = secure_long_password
  24 ### result_attribute (default: maildrop)
  25 result_attribute = mail
  26 #result_attribute = userPrincipalName
  27 
  28 ### query_filter (default: mailacceptinggeneralid=%s)
  29 #query_filter = (&(objectclass=person)(userPrincipalName=%s))
  30 #query_filter = (&(mail=%s)(objectclass=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
  31 #query_filter = (&(&(mail=%s)(!(mail=HealthMailbox*)))(objectclass=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
  32 #query_filter = (&(&(|(mail=%s)(proxyAddresses=smtp:%s))(!(mail=HealthMailbox*)))(objectclass=person))
  33 #query_filter = (&(|(objectclass=person)(objectclass=group))(&(|(mail=%s)(proxyAddresses=smtp:%s))(!(mail=HealthMailbox*))))
  34 query_filter = (&(|(objectclass=person)(objectclass=group))(&(|(mail=*)(proxyAddresses=smtp:*))(!(|(mail=HealthMailbox*)(mail=FederatedEmail.*)(mail=Migration.*)(mail=MsExchDiscoveryMailbox*)(proxyAddresses=smtp:SystemMailbox*))))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

Some notes to the query_filter:

/etc/postfix/ldap/virtual_mailbox_public_folders

   1 ### LOOKUP PUBLIC FOLDER ADDRESSES IN AD
   2 ### FOR OPTIONS PLEASE SEE: man 5 ldap_table
   3 
   4 ### debuglevel (default: 0)
   5 #debuglevel = 0
   6 ### timeout (default: 10 seconds)
   7 timeout = 4
   8 ### version (default: 2)
   9 version = 3
  10 ### start_tls (default: no)
  11 start_tls = no
  12 ### tls_ca_cert_file (No default; set either this or tls_ca_cert_dir)
  13 tls_ca_cert_file = /etc/postfix/ldap/cacert.pem
  14 ### server_host (default: localhost)
  15 server_host =
  16         ldaps://dc1.example.com:636
  17         ldaps://dc2.example.com:636
  18 ### search_base (No default; you must configure this)
  19 search_base = CN=Microsoft Exchange System Objects,DC=example,DC=com
  20 ### bind_dn (default: empty)
  21 bind_dn = CN=postfix,CN=Users,DC=example,DC=com
  22 ### bind_pw (default: empty)
  23 bind_pw = secure_long_password
  24 ### result_attribute (default: maildrop)
  25 result_attribute = mail
  26 
  27 ### query_filter (default: mailacceptinggeneralid=%s)
  28 #query_filter = (&(mail=%s)(objectclass=publicFolder)(!(msExchHideFromAddressLists=TRUE)))
  29 query_filter = (&(|(mail=%s)(proxyAddresses=smtp:%s))(objectclass=publicFolder)(!(msExchHideFromAddressLists=TRUE)))

Please make sure, to use correct unix-permissions so postfix can read (only) the credentials by group permissions.

   1 find /etc/postfix/ldap -type f \
   2         -exec chown root:postfix {} \; \
   3         -exec chmod 0640  {} \;

You can now reference it as a lookup in
/etc/postfx/main.cf

   1 
   2 ### ALIASED DOMAINS AND ADDRESSES
   3 ### POINTING TO LOCAL AND REMOTE DOMAINS
   4 virtual_alias_maps = hash:/etc/postfix/virtual_alias
   5 virtual_alias_domains = hash:/etc/postfix/virtual_alias_domains
   6 
   7 ### MAILBOXED DOMAINS AND ADDRESSES WHERE THIS
   8 ### MTA IS FINAL TARGET
   9 
  10 #virtual_mailbox_domains = $virtual_mailbox_maps
  11 virtual_mailbox_domains = hash:/etc/postfix/virtual_mailbox_domains
  12 
  13 ### smtpd_reject_unlisted_recipient (default: yes)
  14 ### ENFORCE EVERY USER TO BE LISTED IN THIS DB
  15 #virtual_mailbox_maps =
  16 virtual_mailbox_maps =
  17          proxy:ldap:/etc/postfix/ldap/virtual_mailbox_users
  18         ,proxy:ldap:/etc/postfix/ldap/virtual_mailbox_public_folders
  19         #,hash:/etc/postfix/virtual_mailbox_users
  20 

The proxy: delegates these lookups to the postfix proxymap daemon (man 8 proxymap). This may be interesting if you:

query_ldap.sh

Quick script to aid setup.

/usr/local/bin/query_ldap.sh

   1 #!/bin/bash
   2 
   3 ### CHECKS
   4 if ! which ldapsearch; then
   5         echo "ldap-utils are missing. Exiting…"
   6         exit 1
   7 fi
   8 
   9 ### INIT
  10 HOST="hostname.domain.tld"
  11 BASE_DN="dc=domain,dc=tld"
  12 BIND_DN="CN=Postfix,OU=Users,DC=domain,DC=tld"
  13 FILTER='(&(|(objectclass=person)(objectclass=group))(&(|(mail=*)(proxyAddresses=smtp:*))(!(|(mail=HealthMailbox*)(mail=FederatedEmail.*)(mail=Migration.*)(mail=MsExchDiscoveryMailbox*)(proxyAddresses=smtp:SystemMailbox*))))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))'
  14 ATTRIBUTES="objectClass=\* dn mail proxyAddresses"
  15 
  16 ### QUICK CLI OPTIONS
  17 if [ -n "$1" ]; then
  18         OPTIONS="$@"
  19 else
  20         OPTIONS="'$FILTER' $ATTRIBUTES"
  21 fi
  22 
  23 ### MAIN
  24 ldapsearch \
  25         -h "$HOST" \
  26         -xWD "$BIND_DN" \
  27         -b "$BASE_DN" \
  28         $OPTIONS

Virtual aliases

/etc/postfix/virtual_alias_domains

   1 ###PATTERN           WORD_IS_IGNORED
   2 rockstable.org       WORD

/etc/postfix/virtual

   1 ### /etc/postfix/virtual
   2 # SEPARATE DOMAINS, NON-UNIX-ACCOUNTS, ONLY ALIASES
   3 # SEE:
   4 # * /etc/postfix/virtual_mailbox_domains
   5 # * /etc/postfix/virtual_alias_domains
   6 # DOCS:
   7 # * /usr/share/doc/postfix/VIRTUAL_README.gz
   8 # * man 5 virtual
   9 #
  10 # IF RHS RESULT/DST IS A DOMAINLESS USER $myorigin IS APPENDED.
  11 
  12 ### FULL DOMAIN ALIASES
  13 @rockstable.org    @rockstable.it

Address verify cache

   1 ### ADDRESS VERIFY CACHE
   2 address_verify_map = btree:$data_directory/verify_cache
   3 #address_verify_negative_cache = yes
   4 address_verify_negative_cache = no
   5 #address_verify_negative_expire_time = 3d
   6 #address_verify_negative_refresh_time = 3h
   7 #address_verify_positive_expire_time = 31d
   8 #address_verify_positive_refresh_time = 7d
   9 address_verify_virtual_transport = $virtual_transport

You can look inside into the verify cache with the following command:

   1 db_dump -p /var/lib/postfix/verify_cache

Crypto

Certificates

Please take a look at LetsEncrypt

Hardening

   1 ### IF SYNTAX HIGHLIGHTING IS MISSING:
   2 ## -> "make vim_syntax"
   3 
   4 ciphers_insecure = RC4
   5 cipher_suite_minimum = high
   6 protocols_insecure = !SSLv2,!SSLv3,!TLSv1
   7 tls_security_level = may
   8 
   9 
  10 ### Cipherlists
  11 ##tls_export_cipherlist = aNULL:-aNULL:ALL:+RC4:@STRENGTH
  12 ##tls_high_cipherlist = aNULL:-aNULL:ALL:!EXPORT:!LOW:!MEDIUM:+RC4:@STRENGTH
  13 ##tls_low_cipherlist = aNULL:-aNULL:ALL:!EXPORT:+RC4:@STRENGTH
  14 ##tls_medium_cipherlist = aNULL:-aNULL:ALL:!EXPORT:!LOW:+RC4:@STRENGTH
  15 ##tls_null_cipherlist = eNULL:!aNULL
  16 
  17 
  18 ### LMTP Client
  19 #lmtp_enforce_tls (default: no)
  20 #lmtp_sasl_tls_security_options (default: $lmtp_sasl_security_options)
  21 #lmtp_sasl_tls_verified_security_options (default: $lmtp_sasl_tls_security_options)
  22 #lmtp_starttls_timeout (default: 300s)
  23 #lmtp_tls_CAfile (default: empty)
  24 #lmtp_tls_CApath (default: empty)
  25 #lmtp_tls_block_early_mail_reply (default: empty)
  26 #lmtp_tls_cert_file (default: empty)
  27 #lmtp_tls_ciphers (default: export)
  28 lmtp_tls_ciphers = $cipher_suite_minimum
  29 #lmtp_tls_dcert_file (default: empty)
  30 #lmtp_tls_dkey_file (default: $lmtp_tls_dcert_file)
  31 #lmtp_tls_eccert_file (default: empty)
  32 #lmtp_tls_eckey_file (default: empty)
  33 #lmtp_tls_enforce_peername (default: yes)
  34 #lmtp_tls_exclude_ciphers (default: empty)
  35 lmtp_tls_exclude_ciphers = $ciphers_insecure
  36 #lmtp_tls_fingerprint_cert_match (default: empty)
  37 #lmtp_tls_fingerprint_digest (default: md5)
  38 lmtp_tls_fingerprint_digest = sha1
  39 #lmtp_tls_force_insecure_host_tlsa_lookup (default: no)
  40 #lmtp_tls_key_file (default: $lmtp_tls_cert_file)
  41 #lmtp_tls_loglevel (default: 0)
  42 #lmtp_tls_mandatory_ciphers (default: empty)
  43 lmtp_tls_mandatory_ciphers = $cipher_suite_minimum
  44 #lmtp_tls_mandatory_exclude_ciphers (default: empty)
  45 lmtp_tls_mandatory_exclude_ciphers = $ciphers_insecure
  46 #lmtp_tls_mandatory_protocols (default: !SSLv2)
  47 lmtp_tls_mandatory_protocols  = $protocols_insecure
  48 #lmtp_tls_note_starttls_offer (default: no)
  49 #lmtp_tls_per_site (default: empty)
  50 #lmtp_tls_policy_maps (default: empty)
  51 #lmtp_tls_protocols (default: empty)
  52 lmtp_tls_protocols = $protocols_insecure
  53 #lmtp_tls_scert_verifydepth (default: 9)
  54 #lmtp_tls_secure_cert_match (default: nexthop)
  55 #lmtp_tls_security_level (default: empty)
  56 # possible values:      none, may, encrypt, dane, dane-only,
  57 #                       fingerprint, verify, secure
  58 lmtp_tls_security_level = $tls_security_level
  59 #lmtp_tls_session_cache_database (default: empty)
  60 #lmtp_tls_session_cache_timeout (default: 3600s)
  61 #lmtp_tls_trust_anchor_file (default: empty)
  62 #lmtp_tls_verify_cert_match (default: hostname)
  63 #lmtp_use_tls (default: no) <- deprecaded with 2.3 -> smtpd_tls_security_level
  64 
  65 
  66 ### SMTP Client
  67 #smtp_enforce_tls (default: no)
  68 #smtp_sasl_tls_security_options (default: $smtp_sasl_security_options)
  69 #smtp_sasl_tls_verified_security_options (default: $smtp_sasl_tls_security_options)
  70 #smtp_starttls_timeout (default: 300s)
  71 #smtp_tls_CAfile (default: empty)
  72 #smtp_tls_CApath (default: empty)
  73 #smtp_tls_block_early_mail_reply (default: no)
  74 #smtp_tls_cert_file (default: empty)
  75 #smtp_tls_cipherlist (default: empty) <- obsolete
  76 #smtp_tls_ciphers (default: export)
  77 smtp_tls_ciphers = $cipher_suite_minimum
  78 #smtp_tls_dcert_file (default: empty)
  79 #smtp_tls_dkey_file (default: $smtp_tls_dcert_file)
  80 #smtp_tls_eccert_file (default: empty)
  81 #smtp_tls_eckey_file (default: $smtp_tls_eccert_file)
  82 #smtp_tls_enforce_peername (default: yes)
  83 #smtp_tls_exclude_ciphers (default: empty)
  84 smtp_tls_exclude_ciphers = $ciphers_insecure
  85 #smtp_tls_fingerprint_cert_match (default: empty)
  86 #smtp_tls_fingerprint_digest (default: md5)
  87 smtp_tls_fingerprint_digest = sha1
  88 #smtp_tls_force_insecure_host_tlsa_lookup (default: no)
  89 #smtp_tls_key_file (default: $smtp_tls_cert_file)
  90 #smtp_tls_loglevel (default: 0)
  91 #smtp_tls_mandatory_ciphers (default: medium)
  92 smtp_tls_mandatory_ciphers = $cipher_suite_minimum
  93 #smtp_tls_mandatory_exclude_ciphers (default: empty)
  94 smtp_tls_mandatory_exclude_ciphers = $ciphers_insecure
  95 #smtp_tls_mandatory_protocols (default: !SSLv2)
  96 smtp_tls_mandatory_protocols = $protocols_insecure
  97 #smtp_tls_note_starttls_offer (default: no)
  98 #smtp_tls_per_site (default: empty)
  99 #smtp_tls_policy_maps (default: empty)
 100 #smtp_tls_protocols (default: !SSLv2)
 101 smtp_tls_protocols = $protocols_insecure
 102 #smtp_tls_scert_verifydepth (default: 9)
 103 #smtp_tls_secure_cert_match (default: nexthop, dot-nexthop)
 104 #smtp_tls_security_level (default: empty)
 105 # possible values:      none, may, encrypt, dane, dane-only,
 106 #                       fingerprint, verify, secure
 107 smtp_tls_security_level = $tls_security_level
 108 #smtp_tls_session_cache_database (default: empty)
 109 #smtp_tls_session_cache_timeout (default: 3600s)
 110 #smtp_tls_trust_anchor_file (default: empty)
 111 #smtp_tls_verify_cert_match (default: hostname)
 112 #smtp_use_tls (default: no) <- deprecaded with 2.3 -> smtp_tls_security_level
 113 
 114 
 115 ### SMTPD Server
 116 #smtpd_client_new_tls_session_rate_limit (default: 0)
 117 #smtpd_enforce_tls (default: no)
 118 #smtpd_sasl_tls_security_options (default: $smtpd_sasl_security_options)
 119 #smtpd_starttls_timeout (default: see postconf -d output)
 120 #smtpd_tls_CAfile (default: empty)
 121 #smtpd_tls_CApath (default: empty)
 122 #smtpd_tls_always_issue_session_ids (default: yes)
 123 #smtpd_tls_ask_ccert (default: no)
 124 #smtpd_tls_auth_only (default: no)
 125 #smtpd_tls_ccert_verifydepth (default: 9)
 126 #smtpd_tls_cert_file (default: empty)
 127 #smtpd_tls_cipherlist (default: empty) <- obsolete
 128 #smtpd_tls_ciphers (default: export)
 129 smtpd_tls_ciphers = $cipher_suite_minimum
 130 #smtpd_tls_dcert_file (default: empty)
 131 #smtpd_tls_dh1024_param_file (default: empty)
 132 #smtpd_tls_dh512_param_file (default: empty)
 133 #smtpd_tls_dkey_file (default: $smtpd_tls_dcert_file)
 134 #smtpd_tls_eccert_file (default: empty)
 135 #smtpd_tls_eckey_file (default: $smtpd_tls_eccert_file)
 136 #smtpd_tls_eecdh_grade (default: see postconf -d output)
 137 #smtpd_tls_exclude_ciphers (default: empty)
 138 smtpd_tls_exclude_ciphers = $ciphers_insecure
 139 #smtpd_tls_fingerprint_digest (default: md5)
 140 smtpd_tls_fingerprint_digest = sha1
 141 #smtpd_tls_key_file (default: $smtpd_tls_cert_file)
 142 #smtpd_tls_loglevel (default: 0)
 143 #smtpd_tls_mandatory_ciphers (default: medium)
 144 smtpd_tls_mandatory_ciphers = $cipher_suite_minimum
 145 #smtpd_tls_mandatory_exclude_ciphers (default: empty)
 146 smtpd_tls_mandatory_exclude_ciphers = $ciphers_insecure
 147 #smtpd_tls_mandatory_protocols (default: !SSLv2)
 148 smtpd_tls_mandatory_protocols = $protocols_insecure
 149 #smtpd_tls_protocols (default: none)
 150 smtpd_tls_protocols=$protocols_insecure
 151 #smtpd_tls_received_header (default: no)
 152 #smtpd_tls_req_ccert (default: no)
 153 #smtpd_tls_security_level (default: empty)
 154 # possible values:      none, may, encrypt
 155 smtpd_tls_security_level = $tls_security_level
 156 #smtpd_tls_session_cache_database (default: empty)
 157 #smtpd_tls_session_cache_timeout (default: 3600s)
 158 #smtpd_tls_wrappermode (default: no)
 159 #smtpd_use_tls (default: no) <- deprecaded with 2.3 -> smtpd_tls_security_level

You can delete all these comments in vim to get a clean version by:

   1 :g/^#[^#]/d

   1 ### Customs
   2 ciphers_insecure = RC4
   3 cipher_suite_minimum = high
   4 protocols_insecure = !SSLv2,!SSLv3,!TLSv1
   5 tls_security_level = may
   6 
   7 
   8 ### Cipherlists
   9 ##tls_export_cipherlist = aNULL:-aNULL:ALL:+RC4:@STRENGTH
  10 ##tls_high_cipherlist = aNULL:-aNULL:ALL:!EXPORT:!LOW:!MEDIUM:+RC4:@STRENGTH
  11 ##tls_low_cipherlist = aNULL:-aNULL:ALL:!EXPORT:+RC4:@STRENGTH
  12 ##tls_medium_cipherlist = aNULL:-aNULL:ALL:!EXPORT:!LOW:+RC4:@STRENGTH
  13 ##tls_null_cipherlist = eNULL:!aNULL
  14 
  15 
  16 ### LMTP Client
  17 lmtp_tls_ciphers = $cipher_suite_minimum
  18 lmtp_tls_exclude_ciphers = $ciphers_insecure
  19 lmtp_tls_fingerprint_digest = sha1
  20 lmtp_tls_mandatory_ciphers = $cipher_suite_minimum
  21 lmtp_tls_mandatory_exclude_ciphers = $ciphers_insecure
  22 lmtp_tls_mandatory_protocols  = $protocols_insecure
  23 lmtp_tls_protocols = $protocols_insecure
  24 lmtp_tls_security_level = $tls_security_level
  25 
  26 
  27 ### SMTP Client
  28 smtp_tls_ciphers = $cipher_suite_minimum
  29 smtp_tls_exclude_ciphers = $ciphers_insecure
  30 smtp_tls_fingerprint_digest = sha1
  31 smtp_tls_mandatory_ciphers = $cipher_suite_minimum
  32 smtp_tls_mandatory_exclude_ciphers = $ciphers_insecure
  33 smtp_tls_mandatory_protocols = $protocols_insecure
  34 smtp_tls_protocols = $protocols_insecure
  35 smtp_tls_security_level = $tls_security_level
  36 
  37 
  38 ### SMTPD Server
  39 smtpd_tls_ciphers = $cipher_suite_minimum
  40 smtpd_tls_exclude_ciphers = $ciphers_insecure
  41 smtpd_tls_fingerprint_digest = sha1
  42 smtpd_tls_mandatory_ciphers = $cipher_suite_minimum
  43 smtpd_tls_mandatory_exclude_ciphers = $ciphers_insecure
  44 smtpd_tls_mandatory_protocols = $protocols_insecure
  45 smtpd_tls_protocols=$protocols_insecure
  46 smtpd_tls_security_level = $tls_security_level

Perfect forward secrecy (PFS)

Diffie-Hellman-Parameters

First generate Diffie-Hellmann parameters and return here afterwards: OpenSSL#Generate or renew files with Diffie-Hellman-Parameters

Enable PFS in postfix

Defaults

   1 smtpd_tls_dh1024_param_file =
   2 smtpd_tls_dh512_param_file =
   3 smtpd_tls_eecdh_grade = auto
   4 tls_eecdh_auto_curves = X25519 X448 prime256v1 secp521r1 secp384r1
   5 tls_eecdh_strong_curve = prime256v1
   6 tls_eecdh_ultra_curve = secp384r1
   7 tlsproxy_tls_dh1024_param_file = $smtpd_tls_dh1024_param_file
   8 tlsproxy_tls_dh512_param_file = $smtpd_tls_dh512_param_file
   9 tlsproxy_tls_eecdh_grade = $smtpd_tls_eecdh_grade

Hardened

   1 smtpd_tls_dh1024_param_file = /etc/ssl/dhparam/dhparam_2048.pem
   2 smtpd_tls_dh512_param_file = /etc/ssl/dhparam/dhparam_1024.pem
   3 smtpd_tls_eecdh_grade = strong

Test

testssl.sh via ansible

https://testssl.sh/

Submission

Prepare postfix for dovecot submission:

Because dovecot-submissiond binds to tcp/587 we need to add an alternate submission port to /etc/services.

   1 submission-alt  10587/tcp                       # Submission [RFC4409]
   2 

Now we configure /etc/postfix/master.cf to fire up a submission-service bound to localhost.

Postgrey

Wenn eine Email an den Mail-Server versendet zugestellt wird, trägt "postgrey" ein Tripel aus (Client, Sender, Recipient) zusammen mit einem Timestamp in die Datenbank "/var/lib/postgrey/postgrey.db" ein, sollte dieses noch nicht dort zu finden sein. Postgrey versendet einem solchen unbestätigten Client dann ein Return-Code 450 "Try again later". Ist ein konfigurierbares Delay (Default: 300s) erreicht, so ist es dem Client erneut gestattet die Email einzuliefern. Die Datenbank wird standardmäßig von Triplets befreit, zu welchen es seit 35d keinen Kontakt mehr gab. Postgrey stellt Whitelists für clients und recipients bereit.

Ich halte postgrey im Produktivbetrieb für absolut einsetzbar, denn nur die erste Email eines Triplets wird um 5min verzögert.

Evtl. kann das Bereinigungsintervall auf einen höheren Wert gesetzt werden, um seltene Prozesse zu beschleunigen oder interne Server von denen ständig wechselnde Email-Adressen kommt, in die Whitelist eingetragen werden.

About Postgrey

Configuration

/etc/default/postgrey

   1 # postgrey startup options, created for Debian
   2 
   3 # you may want to set
   4 #   --delay=N   how long to greylist, seconds (default: 300)
   5 #   --max-age=N delete old entries after N days (default: 35)
   6 # see also the postgrey(8) manpage
   7 
   8 POSTGREY_OPTS="--inet=127.0.0.1:10023"
   9 
  10 # the --greylist-text commandline argument can not be easily passed through
  11 # POSTGREY_OPTS when it contains spaces.  So, insert your text here:
  12 #POSTGREY_TEXT="Your customized rejection message here"
  13 

Inclusion via restriction in /etc/postfix/main.cf

   1 smtpd_recipient_restrictions =
   2 
   3         check_policy_service inet:127.0.0.1:10023,
   4 

Whitelists

ls -1 /etc/postgrey/*

   1 /etc/postgrey/whitelist_clients
   2 /etc/postgrey/whitelist_clients.local
   3 /etc/postgrey/whitelist_recipients
   4 /etc/postgrey/whitelist_recipients.local

Whitelist format for recipient addresses

domain.addr              "domain.addr" domain and subdomains.
name@                    "name@.*" and extended addresses "name+blabla@.*".
name@domain.addr         "name@domain.addr" and extended addresses.
/regexp/                 anything that matches "regexp" (the full address is matched).

Whitelist format for client addresses

domain.addr              "domain.addr" domain and subdomains.
IP1.IP2.IP3.IP4          IP address IP1.IP2.IP3.IP4. You can also leave off one number, in which case only the first specified numbers will be checked.
IP1.IP2.IP3.IP4/MASK     CIDR-syle network. Example: 192.168.1.0/24
/regexp/                 anything that matches "regexp" (the full address is matched).

policy-weightd

policyd-weight(8) is a SMTP policy daemon written in perl(1) for postfix(1). It evaluates based on RBL/RHSBL results, HELO and MAIL FROM domain and subdomain arguments and the client IP address the possibility of forgery or SPAM. It is designed to be called before the SMTP DATA command at the RCPT TO stage. This way it is a) possible to reject a mail attempt before the body has been received and b) to keep multirecipient mail intact, i.e. provide the functionality of selective usage based on recipients.

Configuration

/etc/policyd-weight.conf

   1 #policyd-weight  uses  a perl(1) style configuration file which it reads
   2 #on  startup.  The  cache  re-reads  the  configuration  after  $MAINTE‐
   3 #NANCE_LEVEL  (default:  5) queries. If -f is not specified, it searches
   4 #for configuration files on following locations:
   5 
   6 
   7 #CACHE SETTINGS
   8 #       $CACHESIZE (default: 2000)
   9 #              Set the minimum size of the SPAM cache.
  10 #       $CACHEMAXSIZE (default: 4000)
  11 #              Set the maximum size of the SPAM cache.
  12 #       $CACHEREJECTMSG
  13 #              (default: 550 temporarily blocked because of previous errors)"
  14 #              Set the SMTP status code and a explanatory message for  rejected
  15 #              mails due to cached results
  16 #       $NTTL (default: 1)
  17 #              The client is penalized for that many retries.
  18 $NTTL=10;
  19 #       $NTIME (default: 30)
  20 #              The  $NTTL  counter will only be decremented if the client waits
  21 #              at least $NTIME seconds.
  22 $NTIME=5;
  23 #       $POSCACHESIZE (default: 1000)
  24 #              Set the minimum size of the HAM cache.
  25 #       $POSCACHEMAXSIZE (default: 2000)
  26 #              Set the maximum size of the HAM cache.
  27 #       $PTTL (default: 60)
  28 #              After that many queries the  HAM  entry  must  succeed  one  run
  29 #              through the RBL checks again.
  30 #       $PTIME (default: 3h)
  31 #              after  $PTIME in HAM Cache the client must pass one time the RBL
  32 #              checks again.  Values must be nonfractal.  Accepted  time-units:
  33 #              s(econds), m(inutes), h(ours), d(ays)
  34 #       $TEMP_PTIME (default: 1d)
  35 #              The  client  must  pass  this time the RBL checks in order to be
  36 #              listed as hard-HAM. After this time the client will pass immedi‐
  37 #              ately  for  PTTL  within  PTIME.  Values  must  be  non-fractal.
  38 #              Accepted time-units: s(econds), m(inutes), h(ours), d(ays)
  39 
  40 
  41 #DEBUG SETTINGS
  42 #       $DEBUG (default: 0)
  43 #              Turn debugging on (1) or off (0)
  44 
  45 
  46 
  47 #DNS SETTINGS
  48 #       $DNS_RETRIES (default: 2)
  49 #              How many times a single DNS query may be repeated
  50 #       $DNS_RETRY_IVAL (default: 2)
  51 #              Retry a query without response after that many seconds
  52 #       $MAXDNSERR (default: 3)
  53 #              If that many queries fail, the mail is accepted with  $MAXDNSER‐
  54 #              RMSG.
  55 #              In total DNS queries this means: $MAXDNSERR * $DNS_RETRIES
  56 
  57 
  58 #MISC SETTINGS
  59 #       $MAINTENANCE_LEVEL (default: 5)
  60 #              After  that  many  policy requests the cache (and in daemon mode
  61 #              child processes) checks for configuration file changes
  62 #       $MAXIDLECACHE (default: 60)
  63 #              After that many seconds of being idle the cache checks for  con‐
  64 #              figuration file changes.
  65 #       $PIDFILE (default: /var/run/policyd-weight.pid)
  66 #              Path and filename to store the master pid (daemon mode)
  67 #       $LOCKPATH (default: /tmp/.policyd-weight/)
  68 #              Directory   where   policyd-weight   stores  sockets  and  lock-
  69 #              files/directories. Its argument must contain a trailing slash.
  70 #       $SPATH (default: $LOCKPATH.'/polw.sock')
  71 #              Path and filename which the cache has to use for communication.
  72 #       $TCP_PORT (default: 12525)
  73 #              TCP port on which the policy server listens (daemon mode)
  74 #       $BIND_ADDRESS (default: '127.0.0.1')
  75 #              IP Address on which policyd-weight binds. Currently either  only
  76 #              one  or all IPs are supported. Specify 'all' if you want to lis‐
  77 #              ten on all IPs.
  78 #       $SOMAXCONN (default: 1024)
  79 #              Maximum connections which policyd-weight accepts.  This  is  set
  80 #              high enough to cover most scenarios.
  81 #       $USER (default: polw)
  82 #              Set the user under which policyd-weight runs
  83 #       $GROUP (default: $USER)
  84 #              Set the group under which policyd-weight runs
  85 
  86 
  87 #OUTPUT AND LOG SETTINGS
  88 #       $ADD_X_HEADER (default: 1)
  89 #              Insert a X-policyd-weight: header with evaluation messages.
  90 #              1 = on, 0 = off
  91 #       $LOG_BAD_RBL_ONLY (default: 1)
  92 #              Insert  only  RBL  results  in  logging strings if the RBL score
  93 #              changes the overall score. Thus RBLs with  a  GOOD  SCORE  of  0
  94 #              don't appear in logging strings if the RBL returned no BAD hit.
  95 #              1 = on, 0 = off
  96 #       $MAXDNSBLMSG (default: 550 Your MTA is listed in too many DNSBLs)
  97 #              The  message sent to the client if it was reject due to $MAXDNS‐
  98 #              BLHITS and/or $MAXDNSBLSCORE.
  99 #       $REJECTMSG (default: 550 Mail appeared to be SPAM or forged.  Ask  your
 100 #       Mail/DNS-Adminisrator  to  correct  HELO  and DNS MX settings or to get
 101 #       removed from DNSBLs)
 102 #              Set the SMTP status code for rejected mails and  a  message  why
 103 #              the action was taken
 104 
 105 
 106 
 107 #RESOURCE AND OPTIMIZATIONS
 108 #       $CHILDIDLE (default: 120)
 109 #              How  many  seconds  a  child  may be idle before it dies (daemon
 110 #              mode)
 111 #       $MAX_PROC (default: 50)
 112 #              Process limit on how many processes  policyd-weight  will  spawn
 113 #              (daemon mode)
 114 #       $MIN_PROC (default: 2)
 115 #              Minimum child processes which are kept alive in idle times (dae‐
 116 #              mon mode)
 117 #       $PUDP (default: 0)
 118 #              Set persistent UDP connections used for DNS queries  on  (1)  or
 119 #              off (0).
 120 
 121 
 122 
 123 #SCORE SETTINGS
 124 #       Positive values indicate a bad (SPAM) score, negative values indicate a
 125 #       good (HAM) score.
 126 #       @bogus_mx_score (2.1, 0)
 127 #              If the sender domain has neither  MX  nor  A  records  or  these
 128 #              records resolve to a bogus IP-Address (for instance private net‐
 129 #              works) then this check asigns the full score of  bogus_mx_score.
 130 #              If  there  is no MX but an A record of the sender domain then it
 131 #              receives a penalty only if DNSBL-listed.
 132 #              Log Entries:
 133 #              BOGUS_MX
 134 #               The sender A and MX records are bogus or empty.
 135 #              BAD_MX
 136 #               The sender domain has an empty  or  bogus  MX  record  and  the
 137 #               client is DNSBL listed.
 138 #              Related RFCs:
 139 #              [1918] Address Allocation for Private Internets
 140 #              [2821] Simple Mail Transfer Protocol (Sect 3.6 and Sect 5)
 141 #       @client_ip_eq_helo_score (1.5, -1.25)
 142 #              Define  scores  for  the  match of the reverse record (hostname)
 143 #              against the HELO argument. Reverse lookups are done, if the for‐
 144 #              ward lookups failed and are not trusted.
 145 #              Log Entries:
 146 #              REV_IP_EQ_HELO
 147 #               The  Client's  PTR  matched  the  HELO  argument.
 148 #              REV_IP_EQ_HELO_DOMAIN
 149 #               Domain portions  of Client PTR and HELO argument matched.
 150 #              RESOLVED_IP_IS_NOT_HELO
 151 #               Client  PTRs  found   but  did  not  match  HELO argument.
 152 #       @helo_score (1.5, -2)
 153 #              Define  scores for the match of the Client IP and its /24 subnet
 154 #              against the A records of HELO or MAIL FROM domain/host. It  also
 155 #              holds the bad score for MX verifications.
 156 #              Log Entries:
 157 #              CL_IP_EQ_HELO_NUMERIC
 158 #               Client IP matches the [IPv4] HELO.
 159 #              CL_IP_EQ_FROM_IP
 160 #               Client  IP  matches   the  A  record  of  the  MAIL FROM sender
 161 #               domain/host.
 162 #              CL_IP_EQ_HELO_IP
 163 #               Client  IP  matches  the  A  record  of the HELO argument.
 164 #              CL_IP_NE_HELO
 165 #               The IP and  the /24  subnet did  not  match  A/MX  records   of
 166 #               HELO  and MAIL FROM  arguments and their subdomains.
 167 #       @helo_from_mx_eq_ip_score (1.5, -3.1)
 168 #              Define  scores  for  the  match of Client IP against MX records.
 169 #              Positive (SPAM) values are used in case the  MAIL  FROM  matches
 170 #              not the HELO argument AND the client seems to be dynamic AND the
 171 #              client is no MX for HELO and  MAIL  FROM  arguments.  The  total
 172 #              DNSBL score is added to its bad score.
 173 #              Log Entries:
 174 #              CL_IP_EQ_FROM_MX
 175 #               Client IP  matches  the MAIL FROM domain/host MX record
 176 #              CL_IP_EQ_HELO_MX
 177 #               Client IP matches the HELO domain/host MX record
 178 #              CLIENT_NOT_MX/A_FROM_DOMAIN
 179 #               Client  is  not a verified  HELO and doesn't match A/MX records
 180 #               of MAIL FROM argument
 181 #              CLIENT/24_NOT_MX/A_FROM_DOMAIN
 182 #               Client's subnet does  not  match A/MX records of the MAIL  FROM
 183 #               argument
 184 #       $dnsbl_checks_only (default: 0)
 185 #              Disable  HELO/RHSBL  verifications  and  the  like.  Do only RBL
 186 #              checks.
 187 #              1 = on, 0 = off
 188 #       @dnsbl_score (default: see below)
 189 #              A list of RBLs to be checked. If you want that  a  host  is  not
 190 #              being  evaluated any further if it is listed on several lists or
 191 #              a very trustworthy list you can control a immediate REJECT  with
 192 #              $MAXDNSBLHITS  and/or  $MAXDNSBLSCORE.  A  list  of RBLs must be
 193 #              build as follows:
 194 #              @dnsbl_score = (
 195 #                  RBLHOST1,   HIT SCORE,  MISS SCORE,     LOG NAME,
 196 #                  RBLHOST2,   HIT SCORE,  MISS SCORE,     LOG NAME,
 197 #                  ...
 198 #              );
 199 #              The default is:
 200 #              @dnsbl_score = (
 201 #                  "dynablock.njabl.org",  3.25,   0,      "DYN_NJABL",
 202 #                  "dnsbl.njabl.org",      4.25,   -1.5,   "BL_NJABL",
 203 #                  "bl.spamcop.net",       1.75,   -1.5,   "SPAMCOP",
 204 #                  "sbl-xbl.spamhaus.org", 4.35,   -1.5,   "SBL_XBL_SPAMHAUS",
 205 #                  "list.dsbl.org",        4.35,   0,      "DSBL_ORG",
 206 #                  "ix.dnsbl.manitu.net",  4.35,   0,      "IX_MANITU",
 207 #                  "relays.ordb.org",      3.25,   0,      "ORDB_ORG"
 208 #              );
 209 #       @rhsbl_score (default: see below)
 210 #              Define a list of RHSBL host which are  queried  for  the  sender
 211 #              domain.  Results  get additionally scores of 0.5 * DNSBL results
 212 #              and @rhsbl_penalty_score.  A list of RHSBL hosts to  be  queried
 213 #              must be build as follows:
 214 #              @rhsbl_score = (
 215 #                  RHSBLHOST1,  HIT SCORE,  MISS SCORE,     LOG NAME,
 216 #                  RHSBLHOST2,  HIT SCORE,  MISS SCORE,     LOG NAME,
 217 #                  ...
 218 #              );
 219 #              The default is:
 220 #              @rhsbl_score = (
 221 #                  "rhsbl.ahbl.org",              1.8,     0,  "AHBL",
 222 #                  "dsn.rfc-ignorant.org",        3.2,     0,  "DSN_RFCI",
 223 #                  "postmaster.rfc-ignorant.org", 1 ,      0,  "PM_RFCI",
 224 #                  "abuse.rfc-ignorant.org",      1,       0,  "ABUSE_RFCI"
 225 #              );
 226 #       @rhsbl_penalty_score (3.1, 0)
 227 #              This  score  will be added to each RHSBL hit if following crite‐
 228 #              rias are met:
 229 #                  Sender has a random local-part (i.e. yztrzgb@example.tld)
 230 #               or MX records of sender domain are bogus
 231 #               or FROM matches not HELO
 232 #               or HELO is untrusted (Forward record matched, reverse record
 233 #                  did not match)
 234 #       $MAXDNSBLHITS (default: 2)
 235 #              If the client is listed in more than $MAXDNSBLHITS RBLs it  will
 236 #              be  rejected  immediately  with $MAXDNSBLMSG and without further
 237 #              evaluation. Results are cached by default.
 238 #       $MAXDNSBLSCORE (default: 8)
 239 #              If the BAD SCOREs of @dnsbl_score  listed  RBLs  reach  a  level
 240 #              greater  than $MAXDNSBLSCORE the client will be rejected immedi‐
 241 #              ately with $MAXDNSBLMSG and without further evaluation.  Results
 242 #              are cached by default.
 243 #       $REJECTLEVEL (default: 1)
 244 #              Score  results equal or greater than this level will be rejected
 245 #              with $REJECTMSG
 246 

Defaults

   1 ## This is a POSIX shell fragment sourced by /etc/init.d/policyd-weight
   2 ## variable: DAEMON_OPTS
   3 ## daemon options to policyd-weight, possible options:
   4 ## 
   5 ## Options
   6 ##    -D                   Don't detach master - run master in foreground
   7 ##    -d                   Debug, don't daemonize, log to STDOUT
   8 ##    -f /path/to/file     Specify a configuration file
   9 ##    -h                   This help
  10 ##    -k                   Kill cache instance
  11 ##    -s                   Show  cache entries and exit. With -d show debug
  12 ##                         cache entries
  13 ##    -v                   Show version and exit
  14 ##
  15 ## default: unset
  16 DAEMON_OPTS="-f /etc/policyd-weight.conf"

Inclusion via restriction in /etc/postfix/main.cf

   1 smtpd_recipient_restrictions =
   2 
   3         check_policy_service inet:127.0.0.1:12525,
   4 

rspamd

For installation see rspamd

/etc/postfix/main.cf

   1 ### RSPAMD
   2 
   3 #smtpd_milters = unix:/var/lib/rspamd/milter.sock
   4 # or for TCP socket
   5 smtpd_milters = inet:localhost:11332
   6 non_smtpd_milters = inet:localhost:11332
   7 
   8 # skip mail without checks if something goes wrong
   9 milter_default_action = accept
  10 
  11 # 6 is the default milter protocol version;
  12 # prior to Postfix 2.6 the default protocol was 2.
  13 # milter_protocol = 6
  14 
  15 milter_default_action = accept
  16 milter_mail_macros = i {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}
  17 #milter_mail_macros = i {auth_authen} {client_addr} {client_name} {mail_addr}

Rockstable Wiki: postfix (last edited 2021-01-18 08:44:43 by RockStable)