#!/bin/sh # # /etc/local-COMMON/postfix/postfix.sh # Copyright 2002-2010,2013-2016,2020 Jonas Smedegaard # # Auto-tweak plain installed postfix Debian package # # Depends: postfix (>= 3.4) # # TODO: # * Implement stuff from here: http://www.wsrcc.com/spam/ # * Implement stuff from here: http://www.muine.org/~hoang/postfix.html # * Implement stuff from here: http://jimsun.linxnet.com/misc/postfix-anti-UCE.txt # * Figure out a way to use chroot jail for TLS stuff. # * Use https://www.dnswl.org/ set -e # Let's standardize sort export LC_ALL=C warn() { echo >&2 "Warning: $1" } exit1() { echo >&2 "Error: $1" echo >&2 "Exiting..." exit 1 } FORCE=${FORCE:-} REDPILL=${REDPILL:-REDPILL} REALM=${REALM:-ORG} # superseded by redpill config mailhost TLS_CHAIN_FILES=${TLS_CHAIN_FILES:-} # TODO: maybe use AMaViS default of 20 (and instead tune spamassassin) # AMAVIS_MAXPROC=${AMAVIS_MAXPROC:-2} # TODO: check if DKIMPROXY_MAXPROC_IN should be handled too #DKIMPROXY_MAXPROC_IN=5 DKIMPROXY_MAXPROC_OUT=5 # resolve redpill config paths realmsdir="/etc/local-$REDPILL" thisrealm=$(cat "/etc/local-$REALM/realm" || dnsdomainname | tr '[:lower:]' '[:upper:]') if ! [ -d "$realmsdir" ]; then warn "Realms directory \"$realmsdir\" does not exist." fi catfilefromownrealm() { set -e file=$1 [ -d "$realmsdir" ] || exit 0 cat "$realmsdir/$thisrealm/$file" } catallfilesfromotherrealms() { set -e file=$1 [ -d "$realmsdir" ] || exit 0 [ -f "$realmsdir/realms" ] || exit 0 realms=$(sed 's/#.*//' < "$realmsdir/realms") for realm in $realms; do if [ "$thisrealm" != "$realm" ]; then cat "$realmsdir/$realm/$file" fi done } catfirstfile() { set -e file=$1 context=${2:-postfix} configdir= for dir in /etc/local "/etc/local-$REALM" "/etc/local-$REDPILL" /etc/local-COMMON; do if [ -d "$dir/$context" ] && [ -f "$dir/$context/$file" ]; then configdir="$dir/$context" break fi done if [ -z "$configdir" ]; then exit1 "ERROR: file \"$file\" not found." fi cat "$configdir/$file" } # TODO: support trailing comment. getperlvarfromfile() { set -e var=$1 default=$2 file=$3 context=${4:-postfix} catfirstfile "$file" "$context" \ | var=$var default=$default perl -n \ -e '/^\$$ENV{"var"}\h*=\h*(\d+)/ and $s=$1; END {print length($s) ? $s : $ENV{"default"}}' } getlinesfromfile() { set -e param=$1 shift replacements= for subparam in "$@"; do case "$subparam" in *=) oldparam=$(echo "$subparam" | awk -F= '{print $1}') replacements="$replacements;s/,*[^,]*${oldparam}[^,]*,*/,/" continue ;; *=*=*) oldparam=$(echo "$subparam" | awk -F= '{print $1}') newparam=$(echo "$subparam" | awk -F= '{print $2}') newparamfile=$(echo "$subparam" | awk -F= '{print $3}') ;; *) oldparam=$subparam newparam=$subparam newparamfile=$subparam ;; esac newparamvalues=$(getlinesfromfile "$newparamfile" | sed -e 's/.*=[ ]*//' -e 's/,/ /g') newstring= for newparamvalue in $newparamvalues; do newstring="${newstring}$newparam $newparamvalue," done replacements="$replacements;s/$oldparam/$newstring/" done printf "%s = " "$param" catfirstfile "$param" | sed 's/#.*//' | tr '\n' ',' | sed -e 's/^[, ]*//;s/[, ]\+/,/g' -e 's/\^/ /g' -e "s/,\$//$replacements" } thismailhost="$(cat /etc/local/mailhost || catfilefromownrealm mailhost)" #ENABLE_POSTGREY= #if [ -x /usr/sbin/postgrey ]; then # # FIXME: Use this somehow, and only warn below # ENABLE_POSTGREY=1 #else # exit1 "ERROR: Greylisting support (Debian package postgrey) missing." #fi dovecot= ENABLE_DOVECOT_LMTP= ENABLE_DOVECOT_DELIVER= if [ -x /usr/sbin/dovecot ]; then dovecot=1 if [ -x /usr/lib/dovecot/lmtp ]; then ENABLE_DOVECOT_LMTP=1 elif [ -x /usr/lib/dovecot/deliver ]; then warn "Dovecot LMTP missing - (Debian package dovecot-lmtp)." ENABLE_DOVECOT_DELIVER=1 else warn "Dovecot deliver missing." fi else warn "Dovecot missing - (Debian package dovecot-core or dovecot)." fi ENABLE_TLS= [ -z "$thismailhost" ] || TLS_CHAIN_FILES="/etc/ssl/private/$thismailhost.chain.key" if [ -f "$TLS_CHAIN_FILES" ]; then ENABLE_TLS=1 else warn "No TLS - requires key chain \"$TLS_CHAIN_FILES\"." fi # TODO: enable only on systems with user accounts ENABLE_SUBMISSION=1 # TODO: check that dovecot SASL is configured ENABLE_SASL_INBOUND= if [ -n "$ENABLE_TLS" ] && [ -n "$dovecot" ] && [ -n "$ENABLE_SUBMISSION" ]; then ENABLE_SASL_INBOUND=1 else warn "No inbound SASL authentication - requires TLS encryption and Dovecot." fi ENABLE_AMAVIS= if [ -x /usr/sbin/amavisd ] || [ -x /usr/sbin/amavisd-new ]; then ENABLE_AMAVIS=1 else warn "AMaViS missing (Debian package amavisd-new)." fi ENABLE_DKIMPROXY= if [ -x /usr/sbin/dkimproxy.in ]; then if [ "1" = "$ENABLE_AMAVIS" ]; then ENABLE_DKIMPROXY=1 else warn "No DKIM/Domainkey - requires DKIMproxy and AMaViS." fi else warn "DKIMproxy missing (Debian package dkimproxy)." fi confdir=/etc/postfix _postconf() { postconf -c "$tempdir" "$1" "$(echo "$2" | tr '\n' ' ')" } postmapfiles= tempdir=$(mktemp -td postfix.XXXXXX) cp -a -t "$tempdir" "$confdir"/* # Inspired by D. J. Bernstein: http://cr.yp.to/smtp/greeting.html _postconf -e smtpd_banner="\$myhostname NO UCE ESMTP \$mail_name (Debian/GNU)" # Some badly configured setup use hostname instead of FQDN # Disable completely: Effective, but hurts executive type guys using windows servers... :-( #if _postconf myhostname | grep -q '\.'; then # _postconf -e smtpd_helo_required=yes #fi _postconf -e smtpd_helo_required=no _postconf -e "$(getlinesfromfile permit_mx_backup_networks)" _postconf -e maps_rbl_domains= _postconf -e "$(getlinesfromfile smtpd_client_restrictions reject_rhsbl_client)" _postconf -e "$(getlinesfromfile smtpd_helo_restrictions)" _postconf -e "$(getlinesfromfile smtpd_sender_restrictions reject_rhsbl_sender ${ENABLE_DKIMPROXY:-sender_access_regex=})" _postconf -e "$(getlinesfromfile smtpd_recipient_restrictions reject_maps_rbl=reject_rbl_client=maps_rbl_domains)" _postconf -e "$(getlinesfromfile smtpd_data_restrictions)" # FIXME: clear only specific line (not whole file) when dkimproxy unused if [ -f "$tempdir/sender_access_regex" ]; then if [ -n "$ENABLE_DKIMPROXY" ]; then grep -q -F '/^/ FILTER dkimsign:[127.0.0.1]:10026' "$tempdir/sender_access_regex" \ || echo '/^/ FILTER dkimsign:[127.0.0.1]:10026' >> "$tempdir/sender_access_regex" else if grep -q -F '/^/ FILTER dkimsign:[127.0.0.1]:10026' "$tempdir/sender_access_regex"; then echo "echo '' > \"$confdir/sender_access_regex\"" >> "$tempdir/COMMANDS" rm "$tempdir/sender_access_regex" fi fi postmapfiles="$postmapfiles sender_access_regex" fi # Support exceptions to default response # (Day Old Bread (dob) lists need to reject only temporarily) _postconf -e rbl_reply_maps="hash:$confdir/rbl_reply_map" sed 's/#.*//' \ < /etc/local-COMMON/postfix/rbl_reply_map \ > "$tempdir/rbl_reply_map" postmapfiles="$postmapfiles rbl_reply_map" # Verify senders of common suspicious and known verifiable domains # (exclude verification of postmaster@ to not verify verification probes) # (add own domains before peers for (rare) cases of duplicates) # FIXME: somehow do this step only if enabled in smtpd_sender_restrictions # TODO: Properly implement exception exclusion like yahoo (which does not want to be checked any longer!) grep -v yahoo \ < /etc/local-COMMON/postfix/maildomains \ | sort \ | sed 's/$/ reject_unverified_sender/' \ > "$tempdir/sender_access" ( catfilefromownrealm maildomains | sort; catallfilesfromotherrealms maildomains | sort ) \ | sed 's/\(.*\)$/postmaster@\1 permit\n\1 reject_unverified_sender/' \ >> "$tempdir/sender_access" [ ! -f "$tempdir/sender_access.addon" ] || cat "$tempdir/sender_access.addon" \ >> "$tempdir/sender_access" postmapfiles="$postmapfiles sender_access" _postconf -e unverified_sender_reject_code=550 # Trust recipient verification too _postconf -e unverified_recipient_reject_code=550 if [ -n "$ENABLE_DOVECOT_LMTP" ]; then _postconf -e mailbox_transport=lmtp:unix:private/dovecot-lmtp _postconf -X mailbox_command elif [ -n "$ENABLE_DOVECOT_DELIVER" ]; then _postconf -X mailbox_transport _postconf -e mailbox_command=/usr/lib/dovecot/deliver else _postconf -X mailbox_transport _postconf -X mailbox_command fi # outbound opportunistic encryption _postconf -e smtp_tls_security_level=may if [ -n "$ENABLE_TLS" ]; then _postconf -e smtp_tls_chain_files="$TLS_CHAIN_FILES" else _postconf -X smtp_tls_chain_files fi _postconf -e smtp_tls_loglevel=1 # enforce TLS trust path towards peers catallfilesfromotherrealms mailhost | sort \ | sed 's/^/[/;s/$/]:submission secure/' \ > "$tempdir/tls_policy" [ ! -f "$tempdir/tls_policy.addon" ] || cat "$tempdir/tls_policy.addon" \ >> "$tempdir/tls_policy" postmapfiles="$postmapfiles tls_policy" _postconf -e smtp_tls_policy_maps="hash:$confdir/tls_policy" # inbound opportunistic encryption if [ -n "$ENABLE_TLS" ]; then _postconf -e smtpd_tls_security_level=may _postconf -e smtpd_tls_chain_files="$TLS_CHAIN_FILES" _postconf -e smtpd_tls_loglevel=1 _postconf -e smtpd_tls_auth_only=yes _postconf -e smtpd_tls_received_header=yes else _postconf -e smtpd_tls_security_level=none _postconf -X smtpd_tls_chain_files _postconf -X smtpd_tls_loglevel _postconf -X smtpd_tls_auth_only _postconf -X smtpd_tls_received_header fi # Avoid smtpd_tls_CApath or smtpd_tls_CAfile to trick outlook.com # See _postconf -X smtpd_tls_CApath _postconf -X smtpd_tls_CAfile # obsolete TLS-related settings _postconf -X smtpd_use_tls _postconf -X smtp_tls_CApath _postconf -X lmtp_tls_CApath _postconf -X smtp_tls_CAfile _postconf -X lmtp_tls_CAfile _postconf -X smtp_tls_cert_file _postconf -X smtp_tls_key_file _postconf -X smtpd_tls_cert_file _postconf -X smtpd_tls_key_file _postconf -X smtpd_tls_ask_ccert _postconf -X smtp_tls_note_starttls_offer _postconf -X smtpd_tls_session_cache_database _postconf -X smtpd_tls_session_cache_timeout _postconf -X smtp_tls_session_cache_database _postconf -X tls_random_exchange_name _postconf -X tls_random_source # submission # # if [ -n "$ENABLE_SUBMISSION" ]; then _postconf -Me submission/inet=" submission inet n - y - - smtpd -o syslog_name=postfix/\$service_name -o smtpd_tls_security_level=encrypt " _postconf -Me smtps/inet=" smtps inet n - y - - smtpd -o syslog_name=postfix/\$service_name -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes " if [ -n "$ENABLE_SASL_INBOUND" ]; then _postconf -Pe submission/inet/smtpd_sasl_auth_enable=yes _postconf -Pe smtps/inet/smtpd_sasl_auth_enable=yes else _postconf -PX submission/inet/smtpd_sasl_auth_enable _postconf -PX smtps/inet/smtpd_sasl_auth_enable fi else _postconf -MX submission/inet _postconf -MX smtps/inet fi if [ -n "$ENABLE_SASL_INBOUND" ]; then _postconf -e smtpd_sasl_local_domain=\$mydomain _postconf -e smtpd_sasl_type=dovecot _postconf -e smtpd_sasl_path=private/auth _postconf -e broken_sasl_auth_clients=yes else _postconf -X smtpd_sasl_local_domain _postconf -X smtpd_sasl_type _postconf -X smtpd_sasl_path _postconf -X broken_sasl_auth_clients fi # obsolete SASL-related settings _postconf -X smtpd_sasl_auth_enable _postconf -X smtpd_sasl_security_options if [ -n "$ENABLE_AMAVIS" ]; then amavis_maxproc=$(getperlvarfromfile max_servers "$AMAVIS_MAXPROC" amavisd.conf.addon amavis) _postconf -Me amavisfeed/unix=" amavisfeed unix - - n - $amavis_maxproc lmtp -o lmtp_data_done_timeout=1200s -o lmtp_send_xforward_command=yes -o disable_dns_lookups=yes -o max_use=$amavis_maxproc " _postconf -Me 127.0.0.1:10025/inet=' 127.0.0.1:10025 inet n - n - - smtpd -o content_filter= -o local_recipient_maps= -o relay_recipient_maps= -o smtpd_restriction_classes= -o smtpd_delay_reject=no -o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_helo_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions=reject_unauth_pipelining -o smtpd_end_of_data_restrictions= -o mynetworks=127.0.0.0/8 -o smtpd_error_sleep_time=0 -o smtpd_soft_error_limit=1001 -o smtpd_hard_error_limit=1000 -o smtpd_client_connection_count_limit=0 -o smtpd_client_connection_rate_limit=0 -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters ' _postconf -e receive_override_options=no_address_mappings if [ -n "$ENABLE_DKIMPROXY" ]; then _postconf -e content_filter='amavisfeed:[127.0.0.1]:10028' _postconf -Pe pickup/fifo/content_filter='dkimsign:127.0.0.1:10028' if [ -n "$ENABLE_SUBMISSION" ]; then _postconf -Pe submission/inet/content_filter='dkimsign:[127.0.0.1]:10028' _postconf -Pe smtps/inet/content_filter='dkimsign:[127.0.0.1]:10028' fi # FIXME: supersede DKIMPROXY_MAXPROC_OUT from /etc/default/dkimproxy _postconf -Me dkimsign/unix=" dkimsign unix - - n - $DKIMPROXY_MAXPROC_OUT smtp -o smtp_send_xforward_command=yes -o smtp_discard_ehlo_keywords=8bitmime,starttls " _postconf -Me 127.0.0.1:10029/inet=' 127.0.0.1:10029 inet n - n - - smtpd -o content_filter= -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters -o smtpd_helo_restrictions= -o smtpd_client_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o mynetworks=127.0.0.0/8 -o smtpd_authorized_xforward_hosts=127.0.0.0/8 ' else _postconf -MX dkimsign/unix _postconf -MX 127.0.0.1:10029/inet _postconf -e content_filter='amavisfeed:[127.0.0.1]:10024' _postconf -PX pickup/fifo/content_filter if [ -n "$ENABLE_SUBMISSION" ]; then _postconf -PX submission/inet/content_filter _postconf -PX smtps/inet/content_filter fi fi else _postconf -MX amavisfeed/unix _postconf -MX 127.0.0.1:10025/inet _postconf -X content_filter _postconf -X receive_override_options fi # obsolete settings _postconf -X max_use _postconf -MX smtp-amavis/unix diff -ruNw "$confdir" "$tempdir" || if [ $? -gt 1 ]; then exit $?; else needs_reload=1; fi if [ "1" = "$FORCE" ]; then do_update=y elif [ "1" = "$needs_reload" ]; then printf 'Above is the intended changes. OK to update (y/N)? ' read -r do_update fi case $do_update in y|Y) if [ -f "$tempdir/COMMANDS" ]; then sh -s < "$tempdir/COMMANDS" fi rm -f "$tempdir/COMMANDS" diff -q "$confdir/master.cf" "$tempdir/master.cf" || if [ $? -gt 1 ]; then exit $?; else needs_restart=1; fi cp -a -f -t "$confdir" "$tempdir"/* rm -rf "$tempdir" for file in $postmapfiles; do postmap "$confdir/$file" done if [ "1" = "$needs_restart" ]; then service postfix restart else service postfix force-reload fi if [ "1" = "$needs_reload" ]; then echo >&2 "Changes applied!" fi ;; *) if [ "1" = "$needs_reload" ]; then exit1 "Aborted!" fi ;; esac if [ "1" != "$needs_reload" ]; then echo >&2 "No changes needed!" fi # Based on this: http://jimsun.linxnet.com/misc/postfix-anti-UCE.txt # Support for trusted MX backup networks added # PCRE stuff avoided, as PCRE is only optional on newest Debian packages # RBLs replaced with those recommended by http://www.antispews.org/ # spam filter based on these: http://www.postfix.org/FILTER_README.html # https://www.ijs.si/software/amavisd/amavisd-new-docs.html # TLS based on this: http://www.postfix.org/TLS_README.html # Here's a convenient overview of different blackholes: # http://rbls.org/