Zum Inhalt

Postfix

Inhalt

  • Postfix 3.10.6
  • Dovecot-SASL
  • postscreen
  • PostgreSQL-Lookup-Maps
  • Python-SPF-Engine 3.1.0

Einleitung

Dieses HowTo beschreibt die Installation und Konfiguration von Postfix auf FreeBSD 15+.

Postfix wird in diesem Setup als SMTP-Server mit Dovecot-SASL, postscreen und PostgreSQL-Lookup-Maps betrieben. Der aktuelle FreeBSD-Port mail/postfix steht auf 3.10.6 und bietet unter anderem den Flavor pgsql, der für dieses Setup die richtige Basis ist. postscreen ist der vorgelagerte SMTP-Schutzdienst für eingehende Verbindungen, und Postfix unterstützt Dovecot-SASL offiziell für SMTP AUTH. Für SQL-Maps gibt es mit pgsql den passenden PostgreSQL-Backend-Typ. (FreshPorts)

Zusätzlich wird in diesem HowTo die Python-SPF-Engine verwendet. Dieses Projekt stellt sowohl einen Postfix-Policy-Service als auch einen Milter für SPF-Prüfungen bereit. Auf FreeBSD wird dafür der Port mail/py-spf-engine in Version 3.1.0 verwendet. (FreshPorts)


Voraussetzungen

Zu den Voraussetzungen für dieses HowTo siehe bitte: Hosting System

Zusätzlich wird vorausgesetzt:

  • PostgreSQL ist bereits installiert und die Datenbankzugänge für den Benutzer postfix existieren bereits.
  • Dovecot ist bereits installiert und für SASL sowie LMTP vorbereitet.
  • Die Dovecot-Sockets unter /var/spool/postfix/private/ sind vorgesehen.
  • Die TLS-Zertifikate für mail.example.com existieren bereits.
  • Der Server soll virtuelle Domains und Mailboxen über PostgreSQL-Lookups bedienen.

Für Dovecot-SASL und Dovecot-LMTP ist genau dieser Aufbau üblich: Postfix greift auf Unix-Sockets innerhalb von /var/spool/postfix/private/ zu, weil der Zugriff von Postfix auf dieses Verzeichnis begrenzt ist. (doc.dovecot.org)


Vorbereitungen

DNS Records

Für dieses HowTo müssen zuvor folgende DNS-Records angelegt werden, sofern sie noch nicht existieren, oder entsprechend geändert werden, sofern sie bereits existieren.

Text Only
example.com.                     IN  MX      10 mail.example.com.

mail.example.com.                IN  A       __IPADDR4__
mail.example.com.                IN  AAAA    __IPADDR6__

_imap._tcp.example.com.          IN  SRV     0 0 0 .
_imaps._tcp.example.com.         IN  SRV     0 1 993 mail.example.com.
_pop3._tcp.example.com.          IN  SRV     0 0 0 .
_pop3s._tcp.example.com.         IN  SRV     0 0 0 .
_submission._tcp.example.com.    IN  SRV     0 1 587 mail.example.com.
_submissions._tcp.example.com.   IN  SRV     0 1 465 mail.example.com.

example.com.                     IN  TXT     "v=spf1 mx -all"

Gruppen / Benutzer / Passwörter

Für dieses HowTo sind keine zusätzlichen Systemgruppen oder Systembenutzer erforderlich.

Für dieses HowTo muss jedoch das Passwort für den PostgreSQL-Benutzer postfix bereits vorhanden sein, da die pgsql:-Maps in den Konfigurationsdateien darauf zugreifen.

Bash
# Passwort für PostgreSQL-Benutzer "postfix" prüfen
cat /var/db/passwords/user_postgresql_postfix

Verzeichnisse / Dateien

Für dieses HowTo sind keine zusätzlichen Verzeichnisse oder Dateien erforderlich.


Installation

Wir installieren mail/postfix@pgsql und dessen Abhängigkeiten.

Bash
mkdir -p /var/db/ports/mail_postfix
cat <<'EOF' > /var/db/ports/mail_postfix/options
_OPTIONS_READ=postfix-3.11.1
_FILE_COMPLETE_OPTIONS_LIST=BDB BLACKLISTD CDB DOCS EAI INST_BASE LDAP LMDB MONGO MYSQL NIS PCRE2 PGSQL SASL SQLITE TEST TLS TLSRPT SASLKMIT SASLKRB5
OPTIONS_FILE_SET+=BDB
OPTIONS_FILE_UNSET+=BLACKLISTD
OPTIONS_FILE_SET+=CDB
OPTIONS_FILE_UNSET+=DOCS
OPTIONS_FILE_SET+=EAI
OPTIONS_FILE_UNSET+=INST_BASE
OPTIONS_FILE_UNSET+=LDAP
OPTIONS_FILE_SET+=LMDB
OPTIONS_FILE_UNSET+=MONGO
OPTIONS_FILE_SET+=MYSQL
OPTIONS_FILE_UNSET+=NIS
OPTIONS_FILE_SET+=PCRE2
OPTIONS_FILE_SET+=PGSQL
OPTIONS_FILE_UNSET+=SASL
OPTIONS_FILE_UNSET+=SQLITE
OPTIONS_FILE_UNSET+=TEST
OPTIONS_FILE_SET+=TLS
OPTIONS_FILE_SET+=TLSRPT
OPTIONS_FILE_UNSET+=SASLKMIT
OPTIONS_FILE_UNSET+=SASLKRB5

EOF

portmaster -w -B -g -U --force-config mail/postfix@pgsql -n

pw groupmod mail -m postfix

Dienst in rc.conf eintragen

Der Dienst wird mittels sysrc in der rc.conf eingetragen und dadurch beim Systemstart automatisch gestartet.

Bash
sysrc postfix_enable=YES

Mailwrapper auf Postfix umstellen

Bash
mkdir -p /usr/local/etc/mail
install -b -m 0644 /usr/local/share/postfix/mailer.conf.postfix /usr/local/etc/mail/mailer.conf

Das FreeBSD-Handbook beschreibt für Postfix genau diesen Schritt mit /usr/local/etc/mail/mailer.conf, damit die Mailwrapper-Kommandos sauber auf Postfix zeigen. (docs.freebsd.org)


Konfiguration

Konfigurationsdatei main.cf

Bash
cat <<'EOF' > /usr/local/etc/postfix/main.cf
# =========================================================================
# SYSTEM & PATHS
# =========================================================================
compatibility_level = 3.11
myhostname = mail.$mydomain
mydomain = example.com
myorigin = $mydomain
mydestination = $myhostname localhost.$mydomain localhost localhost.localdomain
mail_spool_directory = /var/vmail
home_mailbox = .maildir/
openssl_path = /usr/local/bin/openssl
recipient_delimiter = +

# =========================================================================
# NETWORK & CONCURRENCY
# =========================================================================
inet_interfaces = all
inet_protocols = all
mynetworks = 127.0.0.0/8 [::1]/128 10.0.0.0/8 [fe80::]/10 __IPADDR4__/32 [__IPADDR6__]/64
default_destination_concurrency_limit = 20
local_destination_concurrency_limit = 10
amavisfeed_destination_recipient_limit = 2
dovecot_destination_recipient_limit = 1
lmtp_destination_recipient_limit = 1
anvil_rate_time_unit = 60s
smtpd_client_connection_count_limit = 50
smtpd_client_connection_rate_limit = 30
smtpd_client_message_rate_limit = 100
smtpd_client_recipient_rate_limit = 200
smtpd_proxy_options = speed_adjust
policyd-spf_time_limit = 3600

# =========================================================================
# VIRTUAL MAILBOX SETTINGS (PostgreSQL Proxied)
# =========================================================================
virtual_transport = lmtp:unix:private/dovecot-lmtp
mailbox_transport = lmtp:unix:private/dovecot-lmtp
local_transport = virtual
virtual_mailbox_base = /var/vmail
virtual_minimum_uid = 5000
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
local_recipient_maps = $virtual_mailbox_maps
relay_domains = $mydestination proxy:pgsql:${config_directory}/pgsql/relay_domains.cf
transport_maps = proxy:pgsql:${config_directory}/pgsql/transport_maps.cf
virtual_alias_domains = proxy:pgsql:${config_directory}/pgsql/virtual_alias_domains_maps.cf proxy:pgsql:${config_directory}/pgsql/virtual_alias_domains_catchall_maps.cf
virtual_alias_maps = proxy:pgsql:${config_directory}/pgsql/virtual_alias_maps.cf proxy:pgsql:${config_directory}/pgsql/virtual_alias_domains_mailbox_maps.cf
virtual_mailbox_domains = proxy:pgsql:${config_directory}/pgsql/virtual_mailbox_domains.cf
virtual_mailbox_maps = proxy:pgsql:${config_directory}/pgsql/virtual_mailbox_maps.cf

# =========================================================================
# TLS / SSL (Modern Strict Settings)
# =========================================================================
smtp_tls_CAfile = /etc/ssl/cert.pem
smtpd_tls_CAfile = /etc/ssl/cert.pem
smtpd_tls_chain_files = /usr/local/etc/letsencrypt/live/$myhostname/privkey.pem /usr/local/etc/letsencrypt/live/$myhostname/fullchain.pem
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_loglevel = 1
smtp_tls_loglevel = 1
smtpd_tls_received_header = yes
smtp_tls_note_starttls_offer = yes
smtp_tls_connection_reuse = yes
smtp_tls_mandatory_protocols = >=TLSv1.2
smtp_tls_protocols = >=TLSv1.2
smtpd_tls_mandatory_protocols = >=TLSv1.2
smtpd_tls_protocols = >=TLSv1.2
lmtp_tls_mandatory_protocols = >=TLSv1.2
lmtp_tls_protocols = >=TLSv1.2
tls_preempt_cipherlist = yes
tls_eecdh_auto_curves = X448 X25519 secp384r1 prime256v1 secp521r1
tls_high_cipherlist = CHACHA20 AESGCM !SSLv3 !TLSv1 !DSS !RSA !PSK !aNULL @STRENGTH
tls_medium_cipherlist = CHACHA20 AESGCM !SSLv3 !TLSv1 !DSS !RSA !PSK !aNULL @STRENGTH
tls_ssl_options = NO_RENEGOTIATION NO_SESSION_RESUMPTION_ON_RENEGOTIATION

# =========================================================================
# POSTSCREEN
# =========================================================================
postscreen_access_list = permit_mynetworks cidr:${config_directory}/postscreen_access.cidr cidr:${config_directory}/postscreen_whitelist.cidr
postscreen_cache_cleanup_interval = 12h
postscreen_cache_map = btree:${data_directory}/postscreen_cache
postscreen_greet_action = enforce
postscreen_bare_newline_action = enforce
postscreen_bare_newline_enable = yes
postscreen_non_smtp_command_enable = yes
postscreen_pipelining_enable = yes
postscreen_denylist_action = enforce
postscreen_dnsbl_action = enforce
postscreen_dnsbl_threshold = 5
postscreen_dnsbl_allowlist_threshold = -2
postscreen_dnsbl_reply_map = texthash:${config_directory}/postscreen_dnsbl_reply
postscreen_dnsbl_sites =
    list.dnswl.org=127.0.[0..255].0*-2
    list.dnswl.org=127.0.[0..255].1*-4
    list.dnswl.org=127.0.[0..255].2*-6
    list.dnswl.org=127.0.[0..255].3*-8
    zen.spamhaus.org=127.0.0.20*20
    zen.spamhaus.org=127.0.0.9*99
    zen.spamhaus.org=127.0.0.3*10
    zen.spamhaus.org=127.0.0.2*3
    zen.spamhaus.org=127.0.0.[4..7]*3
    zen.spamhaus.org=127.0.0.[10..11]*3
    swl.spamhaus.org*-10
    bl.mailspike.net=127.0.0.2*10
    bl.mailspike.net=127.0.0.10*5
    bl.mailspike.net=127.0.0.11*4
    bl.mailspike.net=127.0.0.12*3
    bl.mailspike.net=127.0.0.13*2
    bl.mailspike.net=127.0.0.14*1
    wl.mailspike.net=127.0.0.16*-2
    wl.mailspike.net=127.0.0.17*-4
    wl.mailspike.net=127.0.0.18*-6
    wl.mailspike.net=127.0.0.19*-8
    wl.mailspike.net=127.0.0.20*-10
    backscatter.spameatingmonkey.net*2
    bl.ipv6.spameatingmonkey.net*2
    bl.spameatingmonkey.net*2
    bl.spamcop.net*2
    db.wpbl.info*2
    psbl.surriel.com*2
    torexit.dan.me.uk*2
    tor.dan.me.uk*1

# =========================================================================
# AUTHENTICATION & RESTRICTIONS
# =========================================================================
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/dovecot-auth
smtpd_sasl_authenticated_header = yes
smtpd_client_port_logging = yes
smtpd_reject_unlisted_sender = yes
smtpd_client_restrictions = permit_mynetworks permit_sasl_authenticated
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks permit_sasl_authenticated check_helo_access pcre:${config_directory}/helo_access.pcre reject_non_fqdn_helo_hostname reject_unknown_helo_hostname
smtpd_sender_login_maps = proxy:pgsql:${config_directory}/pgsql/sender_login_maps.cf
smtpd_sender_restrictions = permit_mynetworks permit_sasl_authenticated reject_non_fqdn_sender reject_unknown_sender_domain reject_sender_login_mismatch check_sender_access pcre:${config_directory}/sender_access.pcre reject_unlisted_sender
smtpd_recipient_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unauth_destination
    check_recipient_access pcre:${config_directory}/recipient_checks.pcre
    reject_non_fqdn_recipient
    reject_unlisted_recipient
    reject_unknown_recipient_domain
#    check_policy_service unix:private/policyd-spf
    reject_rhsbl_sender         zen.spamhaus.org=127.0.1.[2..99]
    reject_rhsbl_helo           zen.spamhaus.org=127.0.1.[2..99]
    reject_rhsbl_reverse_client zen.spamhaus.org=127.0.1.[2..99]
    reject_rhsbl_sender         zen.spamhaus.org=127.0.2.[2..24]
    reject_rhsbl_helo           zen.spamhaus.org=127.0.2.[2..24]
    reject_rhsbl_reverse_client zen.spamhaus.org=127.0.2.[2..24]
    reject_rbl_client           zen.spamhaus.org=127.0.0.[2..255]
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination
smtpd_data_restrictions = reject_unauth_pipelining reject_multi_recipient_bounce
smtpd_etrn_restrictions = reject
receive_override_options = no_address_mappings
#recipient_bcc_maps = proxy:pgsql:${config_directory}/pgsql/recipient_bcc_maps.cf
#sender_bcc_maps = proxy:pgsql:${config_directory}/pgsql/sender_bcc_maps.cf
#sender_dependent_relayhost_maps = proxy:pgsql:${config_directory}/pgsql/sender_dependent_relayhost_maps.cf

# =========================================================================
# GENERAL & MILTERS
# =========================================================================
milter_default_action = accept
milter_protocol = 6
milter_connect_macros = j {client_name} {daemon_name} v
non_smtpd_milters = $smtpd_milters
smtpd_milters = unix:pyspf-milter/pyspf-milter.sock unix:opendkim/opendkim.sock unix:opendmarc/opendmarc.sock
alias_database = hash:${config_directory}/aliases
alias_maps = hash:${config_directory}/aliases
biff = no
append_dot_mydomain = no
allow_percent_hack = no
always_add_missing_headers = yes
disable_vrfy_command = yes
enable_long_queue_ids = yes
qmgr_message_recipient_limit = 10000
qmgr_message_active_limit = 20000
maximal_queue_lifetime = 5d
bounce_queue_lifetime = 1d
internal_mail_filter_classes = bounce
enable_original_recipient = no
mailbox_size_limit = 0
message_size_limit = 104857600
masquerade_domains = $mydomain
masquerade_exceptions = root mailer-daemon
remote_header_rewrite_domain = domain.invalid
show_user_unknown_table_name = no
smtp_dns_support_level = enabled
smtputf8_enable = no
soft_bounce = no
strict_rfc821_envelopes = yes
swap_bangpath = no
header_checks = pcre:${config_directory}/header_checks.pcre
body_checks = pcre:${config_directory}/body_checks.pcre
#mime_header_checks = pcre:${config_directory}/mime_header_checks.pcre
#nested_header_checks = pcre:${config_directory}/nested_header_checks.pcre
smtpd_command_filter = pcre:${config_directory}/command_filter.pcre
internal_mail_filter_classes = bounce
rbl_reply_maps = hash:${config_directory}/dnsbl_reply_map
local_header_rewrite_clients = permit_mynetworks permit_sasl_authenticated
EOF

# 1. Get Default Interface
DEF_IF="$(route -n get -inet default | awk '/interface:/ {print $2}')"

# 2. Get IPv4 IP
IP4="$(ifconfig "$DEF_IF" inet | awk '/inet / && $2 !~ /^127\./ {print $2}' | head -n 1)"
[ -n "$IP4" ] && sed -e "s|__IPADDR4__|$IP4|g" -i '' /usr/local/etc/postfix/main.cf

# 3. Get IPv6 IP
IP6="$(ifconfig "$DEF_IF" inet6 | awk '/inet6 / && $2 !~ /^fe80:/ && $2 !~ /^::1/ {print $2}' | head -n 1)"
[ -n "$IP6" ] && sed -e "s|__IPADDR6__|$IP6|g" -i '' /usr/local/etc/postfix/main.cf

Konfigurationsdatei master.cf

Bash
cat <<'EOF' > /usr/local/etc/postfix/master.cf
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       n       -       1       postscreen
smtpd     pass  -       -       n       -       -       smtpd
  -o content_filter=amavisfeed:[127.0.0.1]:10024
  -o milter_macro_daemon_name=VERIFYING
  -o receive_override_options=no_address_mappings
  -o smtp_send_xforward_command=yes
dnsblog   unix  -       -       n       -       0       dnsblog
tlsproxy  unix  -       -       n       -       0       tlsproxy
submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o content_filter=amavisfeed:[127.0.0.1]:10026
  -o local_header_rewrite_clients=static:all
  -o milter_macro_daemon_name=ORIGINATING
  -o receive_override_options=no_header_body_checks,no_milters
  -o smtp_send_xforward_command=yes
  -o smtpd_forbid_unauth_pipelining=no
  -o smtpd_hide_client_session=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_tls_security_level=encrypt
submissions inet n      -       n       -       -       smtpd
  -o syslog_name=postfix/submissions
  -o content_filter=amavisfeed:[127.0.0.1]:10026
  -o local_header_rewrite_clients=static:all
  -o milter_macro_daemon_name=ORIGINATING
  -o receive_override_options=no_header_body_checks,no_milters
  -o smtp_send_xforward_command=yes
  -o smtpd_forbid_unauth_pipelining=no
  -o smtpd_hide_client_session=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_tls_security_level=encrypt
  -o smtpd_tls_wrappermode=yes
pickup    unix  n       -       n       60      1       pickup
cleanup   unix  n       -       n       -       0       cleanup
qmgr      unix  n       -       n       300     1       qmgr
tlsmgr    unix  -       -       n       1000?   1       tlsmgr
rewrite   unix  -       -       n       -       -       trivial-rewrite
bounce    unix  -       -       n       -       0       bounce
defer     unix  -       -       n       -       0       bounce
trace     unix  -       -       n       -       0       bounce
verify    unix  -       -       n       -       1       verify
flush     unix  n       -       n       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       n       -       -       smtp
relay     unix  -       -       n       -       -       smtp
  -o syslog_name=${multi_instance_name?{$multi_instance_name}:{postfix}}/$service_name
#  -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       n       -       -       showq
error     unix  -       -       n       -       -       error
retry     unix  -       -       n       -       -       error
discard   unix  -       -       n       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       n       -       -       lmtp
anvil     unix  -       -       n       -       1       anvil
scache    unix  -       -       n       -       1       scache
postlog   unix-dgram n  -       n       -       1       postlogd

# ====================================================================
# External Interfacing Services
# ====================================================================
# Dovecot LDA Deliver Transport
dovecot   unix  -       n       n       -       -       pipe
  flags=DRXhu user=vmail:vmail argv=/usr/local/libexec/dovecot/dovecot-lda
  -f ${sender} -a ${recipient} -d ${user}@${nexthop} -m ${extension}

policyd-spf  unix  -    n       n       -       0       spawn
  user=nobody argv=/usr/local/bin/policyd-spf

# Vacation Auto-Responder
vacation  unix  -       n       n       -       -       pipe
  flags=Rq user=vacation argv=/var/db/postfixadmin/vacation.pl
  -f ${sender} -- ${recipient}

# Amavis Delivery Filter
amavisfeed  unix  -     -       n       -       10      smtp
  -o syslog_name=postfix/amavis
  -o local_header_rewrite_clients=
  -o max_use=20
  -o smtp_data_done_timeout=1200
  -o smtp_dns_support_level=disabled
  -o smtp_send_xforward_command=yes
  -o smtp_tls_note_starttls_offer=no

# Amavis Re-Injection Port (After filtering)
127.0.0.1:10025 inet n  -       n       -       -       smtpd
  -o syslog_name=postfix/10025
  -o content_filter=
  -o local_header_rewrite_clients=
  -o local_recipient_maps=
  -o mynetworks=127.0.0.0/8
  -o mynetworks_style=host
  -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_address_mappings,no_milters
  -o relay_recipient_maps=
  -o smtp_dns_support_level=disabled
  -o smtp_tls_security_level=none
  -o smtpd_client_connection_count_limit=0
  -o smtpd_client_connection_rate_limit=0
  -o smtpd_client_restrictions=permit_mynetworks,reject
  -o smtpd_data_restrictions=permit_mynetworks,reject
  -o smtpd_delay_reject=no
  -o smtpd_end_of_data_restrictions=permit_mynetworks,reject
  -o smtpd_error_sleep_time=0
  -o smtpd_hard_error_limit=1000
  -o smtpd_helo_restrictions=permit_mynetworks,reject
  -o smtpd_milters=
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o smtpd_relay_restrictions=permit_mynetworks,reject
  -o smtpd_restriction_classes=
  -o smtpd_sender_restrictions=permit_mynetworks,reject
  -o smtpd_soft_error_limit=1001
  -o smtpd_tls_security_level=none
  -o strict_rfc821_envelopes=yes

# OpenDKIM Interface Map (If used post-amavis)
127.0.0.1:10027 inet n  -       n       -       -       smtpd
  -o syslog_name=postfix/10027
  -o content_filter=
  -o local_header_rewrite_clients=
  -o local_recipient_maps=
  -o mynetworks=127.0.0.0/8
  -o mynetworks_style=host
  -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
  -o relay_recipient_maps=
  -o smtp_dns_support_level=disabled
  -o smtp_tls_security_level=none
  -o smtpd_client_connection_count_limit=0
  -o smtpd_client_connection_rate_limit=0
  -o smtpd_client_restrictions=permit_mynetworks,reject
  -o smtpd_data_restrictions=permit_mynetworks,reject
  -o smtpd_delay_reject=no
  -o smtpd_end_of_data_restrictions=permit_mynetworks,reject
  -o smtpd_error_sleep_time=0
  -o smtpd_hard_error_limit=1000
  -o smtpd_helo_restrictions=permit_mynetworks,reject
  -o smtpd_milters=unix:opendkim/opendkim.sock
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o smtpd_relay_restrictions=permit_mynetworks,reject
  -o smtpd_restriction_classes=
  -o smtpd_sender_restrictions=permit_mynetworks,reject
  -o smtpd_soft_error_limit=1001
  -o smtpd_tls_security_level=none
  -o strict_rfc821_envelopes=yes
EOF

PostgreSQL-Lookup-Dateien pgsql/*.cf

Bash
mkdir -p /usr/local/etc/postfix/pgsql

cat <<'EOF' > /usr/local/etc/postfix/pgsql/recipient_bcc_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    =
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/relay_domains.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT domain FROM domain WHERE domain='%s' AND backupmx = true AND ('%s' <> 'smtp' AND active = true)
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/sender_bcc_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    =
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/sender_dependent_relayhost_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    =
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/sender_login_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT username FROM mailbox WHERE username='%s' AND (('%s' = 'smtp' AND smtp_active = true) OR ('%s' <> 'smtp' AND active = true))
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/transport_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT REPLACE(transport, 'virtual', ':') AS transport FROM domain WHERE domain='%s' AND ('%s' <> 'smtp' AND active = true)
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/virtual_alias_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT goto FROM alias WHERE address='%s' AND ('%s' <> 'smtp' AND active = true)
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/virtual_alias_domains_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT goto FROM alias, alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = '%u' || '@' || alias_domain.target_domain AND ('%s' <> 'smtp' AND alias.active = true) AND ('%s' <> 'smtp' AND alias_domain.active = true)
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/virtual_alias_domains_catchall_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT goto FROM alias, alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = '%u' || '@' || alias_domain.target_domain AND ('%s' <> 'smtp' AND alias.active = true) AND ('%s' <> 'smtp' AND alias_domain.active = true)
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/virtual_alias_domains_mailbox_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT maildir FROM mailbox, alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = '%u' || '@' || alias_domain.target_domain AND (('%s' = 'smtp' AND mailbox.smtp_active = true) OR ('%s' <> 'smtp' AND mailbox.active = true)) AND ('%s' <> 'smtp' AND alias_domain.active = true)
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/virtual_mailbox_domains.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT domain FROM domain WHERE domain='%s' AND backupmx = false AND ('%s' <> 'smtp' AND active = true) AND NOT (transport LIKE 'smtp%%' OR transport LIKE 'relay%%')
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/virtual_mailbox_limits.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT quota FROM mailbox WHERE username='%s' AND (('%s' = 'smtp' AND smtp_active = true) OR ('%s' <> 'smtp' AND active = true))
EOF

cat <<'EOF' > /usr/local/etc/postfix/pgsql/virtual_mailbox_maps.cf
hosts    = localhost
user     = postfix
password = __PASSWORD_POSTFIX__
dbname   = postfixadmin
query    = SELECT maildir FROM mailbox WHERE username='%s' AND (('%s' = 'smtp' AND smtp_active = true) OR ('%s' <> 'smtp' AND active = true))
EOF

cat /var/db/passwords/user_postgresql_postfix | xargs -I % \
  sed -e "s|__PASSWORD_POSTFIX__|%|g" -i '' /usr/local/etc/postfix/pgsql/*.cf

chown -R root:postfix /usr/local/etc/postfix/pgsql
chmod 640 /usr/local/etc/postfix/pgsql/*
chmod 750 /usr/local/etc/postfix/pgsql


cat /var/db/passwords/user_postgresql_postfix

Postfix beschreibt pgsql:-Maps offiziell als passenden Weg, Postfix mit PostgreSQL-Lookup-Tabellen zu verbinden. (postfix.org)

Restriktionen und Lookup-Dateien

Bash
cp /etc/mail/aliases /usr/local/etc/postfix/aliases

cat <<'EOF' > /usr/local/etc/postfix/postscreen_access.cidr
127.0.0.0/8 permit
[::1]/128 permit
10.0.0.0/8 permit
[fe80::]/10 permit
__IPADDR4__/32 permit
[__IPADDR6__]/64 permit
EOF

cat <<'EOF' > /usr/local/etc/postfix/postscreen_whitelist.cidr
EOF

cat <<'EOF' > /usr/local/etc/postfix/body_checks.pcre
EOF

cat <<'EOF' > /usr/local/etc/postfix/header_checks.pcre
EOF

cat <<'EOF' > /usr/local/etc/postfix/command_filter.pcre
# Work around clients that send `RCPT TO:<'user@domain'>` (Outlook 2003/2007).
# WARNING: do not lose the parameters that follow the address.
/^(RCPT\s+TO:\s*<)'([^[:space:]]+)'(>.*)/   $1$2$3
EOF

cat <<'EOF' > /usr/local/etc/postfix/helo_access.pcre
#---------------------------------------------------------------------
# This file is part of iRedMail, which is an open source mail server
# solution for Red Hat(R) Enterprise Linux, CentOS, Debian and Ubuntu.
#
# iRedMail is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# iRedMail is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with iRedMail.  If not, see <http://www.gnu.org/licenses/>.
#---------------------------------------------------------------------

#
# Sample Postfix check_helo_access rule. It should be located at:
#   /etc/postfix/check_helo_access.pcre
#
# Shipped within iRedMail project:
#   * http://www.iredmail.org/

# Prepend HELO hostname of sender server
#/(.*)/ PREPEND X-Original-Helo: $1 (iRedMail: http://www.iredmail.org/)

# No one will use these in helo command.
/^(localhost)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/^(localhost.localdomain)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(\.local)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})

# Reject who use IP address as helo.
# Correct:      [xxx.xxx.xxx.xxx]
# Incorrect:    xxx.xxx.xxx.xxx
/^([0-9\.]+)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server sent non RFC compliant HELO identity (${1})

#
# This is the real HELO identify of these ISPs:
#   sohu.com    websmtp.sohu.com relay2nd.mail.sohu.com
#   126.com     m15-78.126.com
#   sina.com    mail2-209.sinamail.sina.com.cn
#   gmail.com   xx-out-NNNN.google.com
/^(126\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server seems to be impersonating another mail server (${1})
/^(sohu\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server seems to be impersonating another mail server (${1})
/^(gmail\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server seems to be impersonating another mail server (${1})
/^(google\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server seems to be impersonating another mail server (${1})
/^(yahoo\.com\.cn)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server seems to be impersonating another mail server (${1})
/^(yahoo\.co\.jp)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server seems to be impersonating another mail server (${1})

#
# Spammers.
#
/^(728154EA470B4AA\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(taj-co\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(CF8D3DB045C1455\.net)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(dsgsfdg\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(se\.nit7-ngbo\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(mail\.goo\.ne\.jp)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(n-ong_an\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(meqail\.teamefs-ine5tl\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(zzg\.jhf-sp\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(din_glo-ng\.net)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(fda-cnc\.ie\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(yrtaj-yrco\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(m\.am\.biz\.cn)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(xr_haig\.roup\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(hjn\.cn)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(we_blf\.com\.cn)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(netvigator\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(mysam\.biz)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(mail\.teams-intl\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(seningbo\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(nblf\.com\.cn)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(kdn\.ktguide\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(zzsp\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(nblongan\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(dpu\.cn)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(nbalton\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(cncie\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(xinhaigroup\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/^(wz\.com)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/(\.zj\.cn)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})
/(\.kornet)$/ REJECT ACCESS DENIED. Your email was rejected because it appears to come from a known spamming mail server (${1})

/^(dsldevice\.lan)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/^(system\.mail)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/^(speedtouch\.lan)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})

#
# Reject adsl spammers.
#
# match word `adsl` with word boundary `\b`.
/(\badsl\b)/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})

# bypass "[IP_ADDRESS]"
/^\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]$/ OK

# Bypass HELOs used by known big ISPs which contains IP address
/\.outbound-(email|mail)\.sendgrid\.net$/ OK
/^\d{1,3}-\d{1,3}-\d{1,3}-\d{1,3}\.mail-.*\.facebook\.com$/ OK
/^outbound-\d{1,3}-\d{1,3}-\d{1,3}-\d{1,3}\.pinterestmail\.com$/ OK
/\.outbound\.protection\.outlook\.com$/ OK
/^ec2-\d{1,3}-\d{1,3}-\d{1,3}-\d{1,3}\..*\.compute\.amazonaws\.com$/ OK
/^out\d{1,3}-\d{1,3}-\d{1,3}-\d{1,3}\.mail\.qq\.com$/ OK

# reject HELO which contains IP address
/(\d{1,3}[\.-]\d{1,3}[\.-]\d{1,3}[\.-]\d{1,3})/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(\d{1,3}\.ip\.-\d{1,3}-\d{1,3}-\d{1,3}\.eu)/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(pppoe)/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(dsl\.brasiltelecom\.net\.br)/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(dsl\.optinet\.hr)/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(dsl\.telesp\.net\.br)/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(dialup)/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(dhcp)/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(static-pool-[\d\.-]*\.flagman\.zp\.ua)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})

/(speedy\.com\.ar)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(speedyterra\.com\.br)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(static\.sbb\.rs)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})
/(static\.vsnl\.net\.in)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server appears to be on a dynamic IP address that should not be doing direct mail delivery (${1})

/(advance\.com\.ar)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(airtelbroadband\.in)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(bb\.netvision\.net\.il)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(broadband3\.iol\.cz)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(cable\.net\.co)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(catv\.broadband\.hu)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(chello\.nl)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(chello\.sk)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(client\.mchsi\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(comunitel\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(coprosys\.cz)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(dclient\.hispeed\.ch)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(dip0\.t-ipconnect\.de)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(domain\.invalid)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(dyn\.centurytel\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(embarqhsd\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(emcali\.net\.co)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(epm\.net\.co)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(fibertel\.com\.ar)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(freedom2surf\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(hgcbroadband\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(HINET-IP\.hinet\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(infonet\.by)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(is74\.ru)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(kievnet\.com\.ua)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(metrotel\.net\.co)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(nw\.nuvox\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(pldt\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(pool\.invitel\.hu)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(pool\.ukrtel\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(pools\.arcor-ip\.net)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(pppoe\.avangarddsl\.ru)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(retail\.telecomitalia\.it)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(revip2\.asianet\.co\.th)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(tim\.ro)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(tsi\.tychy\.pl)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(ttnet\.net\.tr)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(tttmaxnet\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(user\.veloxzone\.com\.br)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(utk\.ru)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(veloxzone\.com\.br)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(virtua\.com\.br)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(wanamaroc\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(wbt\.ru)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(wireless\.iaw\.on\.ca)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(business\.telecomitalia\.it)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(cotas\.com\.bo)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(marunouchi\.tokyo\.ocn\.ne\.jp)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(amedex\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/(aageneva\.com)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server does not identify itself correctly (${1})
/^ylmf-pc/ REJECT ACCESS DENIED

/(\.*wideragents\.com)$/ REJECT ACCESS DENIED (${1})
/(\.*resumekeep\.net)$/ REJECT ACCESS DENIED (${1})
/(\.*terracedrink\.com)$/ REJECT ACCESS DENIED (${1})
/(\.*sincemessage\.com)$/ REJECT ACCESS DENIED (${1})
/(\.*ordertranquility\.com)$/ REJECT ACCESS DENIED (${1})
EOF

cat <<'EOF' > /usr/local/etc/postfix/recipient_checks.pcre
/^\@/             550 Invalid address format.
/[!%\@].*\@/      550 This server disallows weird address syntax.
/^postmaster\@/   OK
/^hostmaster\@/   OK
/^security\@/     OK
/^abuse\@/        OK
/^admin\@/        OK
EOF

cat <<'EOF' > /usr/local/etc/postfix/sender_access.pcre
EOF

cat <<'EOF' > /usr/local/etc/postfix/submission_header_checks.pcre
^Received:/             IGNORE
EOF

cat <<'EOF' > /usr/local/etc/postfix/postscreen_dnsbl_reply
EOF

cat <<'EOF' > /usr/local/etc/postfix/dnsbl_reply_map
EOF

cat <<'EOF' > /usr/local/etc/postfix/mx_access
0.0.0.0/8                REJECT MX in RFC 1122 Broadcast Network
10.0.0.0/8               REJECT MX in RFC 1918 Private Network
100.64.0.0/10            REJECT MX in RFC 6598 Shared Address Space
127.0.0.0/8              REJECT MX in RFC 1122 Loopback Network
169.254.0.0/16           REJECT MX in RFC 3927 Link Local Network
172.16.0.0/12            REJECT MX in RFC 1918 Private Network
192.0.0.0/24             REJECT MX in RFC 6890 IETF Protocol Assignments Network
192.0.0.0/29             REJECT MX in RFC 6333 DS-Lite Network
192.0.2.0/24             REJECT MX in RFC 5737 Documentation (TEST-NET-1) Network
192.168.0.0/16           REJECT MX in RFC 1918 Private Network
198.18.0.0/15            REJECT MX in RFC 2544 Interconnect Device Benchmark Testing Network
198.51.100.0/24          REJECT MX in RFC 5737 Documentation (TEST-NET-2) Network
203.0.113.0/24           REJECT MX in RFC 5737 Documentation (TEST-NET-3) Network
224.0.0.0/4              REJECT MX in RFC 5771 Multicast Network
240.0.0.0/4              REJECT MX in RFC 1122 Reserved Network
255.255.255.255/32       REJECT MX in RFC 919  Limited Broadcast Destination Address
::/128                   REJECT MX in RFC 4291 Unspecified Address
::1/128                  REJECT MX in RFC 4291 Loopback Address
::ffff:0:0/96            REJECT MX in RFC 4291 IPv4-mapped Address
100::/64                 REJECT MX in RFC 6666 Discard-Only Network
2001::/23                REJECT MX in RFC 2928 IETF Protocol Assignements Network
2001::/32                REJECT MX in RFC 4380 TEREDO Network
2001:2::/48              REJECT MX in RFC 5180 Interconnect Device Benchmark Testing Network
2001:10::/28             REJECT MX in RFC 4843 ORCHID Network
2001:db8::/32            REJECT MX in RFC 3849 Documentation Network
fc00::/7                 REJECT MX in RFC 4193 Unique-Local Network
fe80::/10                REJECT MX in RFC 4291 Linked-Scoped Unicast Network
ff00::/8                 REJECT MX in RFC 4291 Multicast Network
EOF

# 1. Get Default Interface
DEF_IF="$(route -n get -inet default | awk '/interface:/ {print $2}')"

# 2. Get IPv4 IP
IP4="$(ifconfig "$DEF_IF" inet | awk '/inet / && $2 !~ /^127\./ {print $2}' | head -n 1)"
[ -n "$IP4" ] && sed -e "s|__IPADDR4__|$IP4|g" -i '' /usr/local/etc/postfix/postscreen_access.cidr

# 3. Get IPv6 IP
IP6="$(ifconfig "$DEF_IF" inet6 | awk '/inet6 / && $2 !~ /^fe80:/ && $2 !~ /^::1/ {print $2}' | head -n 1)"
[ -n "$IP6" ] && sed -e "s|__IPADDR6__|$IP6|g" -i '' /usr/local/etc/postfix/postscreen_access.cidr

postmap /usr/local/etc/postfix/postscreen_dnsbl_reply
postmap /usr/local/etc/postfix/dnsbl_reply_map
postmap /usr/local/etc/postfix/mx_access

/usr/local/bin/newaliases

postscreen ist genau für vorgeschaltete SMTP-Prüfungen auf eingehenden Verbindungen gedacht. Lookup-Tabellen und lokale Maps werden mit postmap gebaut; newaliases aktualisiert die Alias-Tabelle. (postfix.org)

Konfiguration prüfen

Vor dem ersten Start sollte die Konfiguration immer geprüft werden. Bei Postfix ist dafür postfix check der passende Weg. Zusätzlich ist postconf -n sinnvoll, um die aktiven Nicht-Default-Parameter auszugeben. Für Dovecot-SASL ist postconf -a nützlich, weil damit die unterstützten SASL-Servertypen sichtbar werden. (postfix.org)

Bash
postconf -a
postconf -n
postfix check

Datenbanken

Für dieses HowTo sind keine neuen Datenbanken erforderlich.

Postfix nutzt in diesem Setup bestehende PostgreSQL-Tabellen über pgsql:-Lookup-Dateien. (postfix.org)


Zusatzsoftware

Mögliche Zusatzsoftware wird hier installiert und konfiguriert.

Postscreen-Whitelist-Helfer

Bash
portmaster -w -B -g -U --force-config dns/rubygem-dnsruby -n
portmaster -w -B -g -U --force-config net/rubygem-ipaddress -n
portmaster -w -B -g -U --force-config devel/rubygem-optparse -n
portmaster -w -B -g -U --force-config devel/rubygem-pp -n

cat <<'EOF' > /usr/local/etc/postfix/postscreen_whitelist.rb
#!/usr/bin/env ruby

require 'rubygems'
require 'dnsruby'
require 'ipaddress'
require 'optparse'
require 'logger'
require 'thread'

# Initialize logger
LOGGER = Logger.new($stderr)
LOGGER.level = Logger::INFO

# Default values
DEFAULT_DOMAINS = [
  # Freemail providers
  "openpgp.org", "t-online.de", "telekom.de", "gmail.com", "googlemail.com", "google.com",
  "gmx.net", "gmx.com", "gmx.de", "web.de", "aol.com", "microsoft.com", "outlook.com",
  "live.com", "live.de", "msn.com",
  # Social
  "facebook.com", "instagram.com", "threads.com", "meta.com", "twitter.com", "x.com",
  "pinterest.com", "reddit.com", "linkedin.com", "xing.com", "xing.de",
  # Commerce
  "amazon.com", "amazon.de", "paypal.com", "paypal.de", "klarna.com", "klarna.de",
  "booking.com", "ebay.com", "ebay.de",
  # Bulk sender / misc
  "github.com", "openwall.com", "freebsd.org"
]
DEFAULT_OUTPUT = "postscreen_whitelist.cidr"

# Option parsing
options = {
  domains: nil,
  output: DEFAULT_OUTPUT,
  force: false,
  loglevel: "info",
  threads: 10
}

OptionParser.new do |opts|
  opts.banner = "Usage: postscreen_whitelist.rb [options]"
  opts.on("-dDOMAINS", "--domains=DOMAINS", "Comma-separated list of domains or path to file") do |d|
    options[:domains] = d
  end
  opts.on("-oFILE", "--output=FILE", "Output file (default: #{DEFAULT_OUTPUT})") do |o|
    options[:output] = o
  end
  opts.on("-f", "--force", "Force overwrite even if result count differs >10%") do
    options[:force] = true
  end
  opts.on("-lLEVEL", "--loglevel=LEVEL", "Logger level (debug, info, warn, error)") do |l|
    options[:loglevel] = l
  end
  opts.on("-tN", "--threads=N", Integer, "Number of parallel DNS threads (default: 10)") do |t|
    options[:threads] = t
  end
  opts.on("-h", "--help", "Print help") do
    puts opts
    exit
  end
end.parse!

LOGGER.level = Logger.const_get(options[:loglevel].upcase) rescue Logger::INFO

# Load domains from file or string
def load_domains(domains_arg)
  return DEFAULT_DOMAINS unless domains_arg
  if File.exist?(domains_arg)
    File.read(domains_arg).lines.map(&:strip).reject { |l| l.empty? || l.start_with?("#") }
  else
    domains_arg.split(",").map(&:strip)
  end
end

domains = load_domains(options[:domains])

# DNS cache (thread-safe)
class DnsCache
  def initialize
    @cache = {}
    @mutex = Mutex.new
  end
  def fetch(key)
    @mutex.synchronize { @cache[key] }
  end
  def store(key, value)
    @mutex.synchronize { @cache[key] = value }
  end
end

dns_cache = DnsCache.new

# DNS helpers (with caching)
def dns_query(resolver, name, type, cache)
  key = "#{name}:#{type}"
  if (cached = cache.fetch(key))
    return cached
  end
  begin
    records = resolver.getresources(name, type)
    cache.store(key, records)
    records
  rescue Dnsruby::ResolvError, Timeout::Error => e
    LOGGER.debug("DNS error for #{name} #{type}: #{e}")
    cache.store(key, [])
    []
  end
end

def a(names, resolver, cache)
  names.flat_map do |name|
    dns_query(resolver, name, "AAAA", cache) + dns_query(resolver, name, "A", cache)
  end.map { |r| r.address.to_s.downcase }
end

def mx(name, resolver, cache)
  dns_query(resolver, name, "MX", cache).flat_map { |r| a([r.exchange], resolver, cache) }
end

def get_spf_results(domain, resolver, cache)
  result = []
  txt_records = dns_query(resolver, domain, "TXT", cache) + dns_query(resolver, domain, "SPF", cache)
  spf_lines = txt_records.map { |r| r.strings.join }.uniq.select { |s| s =~ /^v=spf1/ }
  spf_lines.each do |line|
    line.split(/\s+/).each do |entry|
      next if entry == "v=spf1"
      case entry
      when /^redirect=(.+)/ then return get_spf_results($1, resolver, cache)
      when /^\??include:(.+)/ then result += get_spf_results($1, resolver, cache)
      when /^\??ip4:(.+)/ then result << $1
      when /^\??ip6:(.+)/ then result << $1
      when /^\??mx$/ then result += mx(domain, resolver, cache)
      when /^\??mx:(.+)/ then result += mx($1, resolver, cache)
      when /^\??a$/ then result += a([domain], resolver, cache)
      when /^\??a:(.+)/ then result += a([$1], resolver, cache)
      when /\.all/ then next
      else
        LOGGER.debug("Unrecognized SPF entry: domain=#{domain} entry=#{entry}")
      end
    end
  end
  # Normalize netmasks
  result.map! do |r|
    if m = r.match(/^(\d+\.\d+\.\d+\.\d+)\/(\d+)$/)
      i = IPAddress(r)
      "#{i.network.address}/#{i.network.prefix}"
    else
      r
    end
  end
  result.sort.uniq
end

# Parallel SPF fetching
def fetch_all_spf(domains, resolver, cache, thread_count)
  results = []
  queue = Queue.new
  domains.each { |d| queue << d }
  threads = Array.new(thread_count) do
    Thread.new do
      while !queue.empty?
        domain = queue.pop(true) rescue nil
        next unless domain
        begin
          spf = get_spf_results(domain, resolver, cache)
          LOGGER.info("Fetched SPF for #{domain}: #{spf.count} entries")
          results.concat(spf)
        rescue => e
          LOGGER.error("Failed to fetch SPF for #{domain}: #{e}")
        end
      end
    end
  end
  threads.each(&:join)
  results.uniq.sort
end

# File diffing and writing
def count_lines(file)
  File.exist?(file) ? File.read(file).lines.count : 0
end

old_lines = count_lines(options[:output])

resolver = Dnsruby::DNS.open
spf_results = fetch_all_spf(domains, resolver, dns_cache, options[:threads])

if old_lines > 0 && spf_results.count > 0
  ratio = old_lines.to_f / spf_results.count
  if (ratio < 0.9 || ratio > 1.1)
    LOGGER.warn("More than 10% difference in line count: old: #{old_lines}, new: #{spf_results.count}")
    unless options[:force]
      LOGGER.warn("Run with --force to overwrite anyway.")
      exit 1
    end
  end
end

# Backup old file
if File.exist?(options[:output])
  backup_file = "#{options[:output]}.bak"
  File.write(backup_file, File.read(options[:output]))
  LOGGER.info("Backup of old file saved at #{backup_file}")
end

File.write(options[:output], spf_results.join(" permit\n") + " permit\n")
LOGGER.info("Whitelist written to #{options[:output]} (#{spf_results.count} entries)")
EOF
chmod 755 /usr/local/etc/postfix/postscreen_whitelist.rb

/usr/local/etc/postfix/postscreen_whitelist.rb -f

Wir installieren mail/libmilter und dessen Abhängigkeiten.

mail/py-pymilter, das von mail/py-spf-engine verwendet wird, hängt auf FreeBSD standardmäßig an mail/libmilter. Daher ist diese Installation in deinem Aufbau fachlich konsistent. (FreshPorts)

Bash
mkdir -p /var/db/ports/mail_libmilter
cat <<'EOF' > /var/db/ports/mail_libmilter/options
_OPTIONS_READ=libmilter-8.18.2
_FILE_COMPLETE_OPTIONS_LIST=IPV6 MILTER_SHARED MILTER_POOL DOCS
OPTIONS_FILE_SET+=IPV6
OPTIONS_FILE_SET+=MILTER_SHARED
OPTIONS_FILE_SET+=MILTER_POOL
OPTIONS_FILE_UNSET+=DOCS

EOF

portmaster -w -B -g -U --force-config mail/libmilter -n

Wir installieren mail/py-spf-engine und dessen Abhängigkeiten.

mail/py-spf-engine liefert zwei Betriebsarten: den Policy-Service policyd-spf und den Milter pyspf-milter. Auf FreeBSD installiert der Port ein rc.d-Skript für pyspf-milter; der Policy-Service wird laut pkg-message typischerweise direkt aus master.cf heraus von Postfix gespawnt. (FreshPorts)

Bash
mkdir -p /var/db/ports/mail_py-pymilter
cat <<'EOF' > /var/db/ports/mail_py-pymilter/options
_OPTIONS_READ=py311-pymilter-1.0.6
_FILE_COMPLETE_OPTIONS_LIST= LIBMILTER BASE
OPTIONS_FILE_SET+=LIBMILTER
OPTIONS_FILE_UNSET+=BASE

EOF

mkdir -p /var/db/ports/dns_py-dnspython
cat <<'EOF' > /var/db/ports/dns_py-dnspython/options
_OPTIONS_READ=py311-dnspython-2.8.0
_FILE_COMPLETE_OPTIONS_LIST=DNSSEC DOH DOQ EXAMPLES IDNA TRIO
OPTIONS_FILE_SET+=DNSSEC
OPTIONS_FILE_SET+=DOH
OPTIONS_FILE_SET+=DOQ
OPTIONS_FILE_UNSET+=EXAMPLES
OPTIONS_FILE_SET+=IDNA
OPTIONS_FILE_SET+=TRIO

EOF

mkdir -p /var/db/ports/devel_py-pyasn1-modules
cat <<'EOF' > /var/db/ports/devel_py-pyasn1-modules/options
_OPTIONS_READ=py311-pyasn1-modules-0.4.1
_FILE_COMPLETE_OPTIONS_LIST=DOCS
OPTIONS_FILE_UNSET+=DOCS

EOF

mkdir -p /var/db/ports/www_py-httpcore
cat <<'EOF' > /var/db/ports/www_py-httpcore/options
_OPTIONS_READ=py311-httpcore-1.0.9
_FILE_COMPLETE_OPTIONS_LIST=ASYNCIO HTTP2 SOCKS TRIO
OPTIONS_FILE_SET+=ASYNCIO
OPTIONS_FILE_SET+=HTTP2
OPTIONS_FILE_SET+=SOCKS
OPTIONS_FILE_SET+=TRIO

EOF

mkdir -p /var/db/ports/devel_py-anyio
cat <<'EOF' > /var/db/ports/devel_py-anyio/options
_OPTIONS_READ=py311-anyio-4.12.1
_FILE_COMPLETE_OPTIONS_LIST=TRIO
OPTIONS_FILE_SET+=TRIO

EOF

mkdir -p /var/db/ports/www_py-httpx
cat <<'EOF' > /var/db/ports/www_py-httpx/options
_OPTIONS_READ=py311-httpx-0.28.1
_FILE_COMPLETE_OPTIONS_LIST=BROTLI CLI HTTP2 SOCKS ZSTD
OPTIONS_FILE_SET+=BROTLI
OPTIONS_FILE_UNSET+=CLI
OPTIONS_FILE_SET+=HTTP2
OPTIONS_FILE_SET+=SOCKS
OPTIONS_FILE_SET+=ZSTD

EOF

mkdir -p /var/db/ports/mail_py-spf-engine
cat <<'EOF' > /var/db/ports/mail_py-spf-engine/options
_OPTIONS_READ=py311-spf-engine-3.1.0
_FILE_COMPLETE_OPTIONS_LIST=DOCS
OPTIONS_FILE_UNSET+=DOCS

EOF

portmaster -w -B -g -U --force-config mail/py-spf-engine -n

pw groupmod pyspf-milter -m postfix

mkdir /var/spool/postfix/pyspf-milter
chown pyspf-milter:pyspf-milter /var/spool/postfix/pyspf-milter
chmod 770 /var/spool/postfix/pyspf-milter

Dienst in rc.conf eintragen

Bash
sysrc pyspf_milter_enable=YES

Konfigurationsdateien für SPF einrichten

Bash
cat <<'EOF' > /usr/local/etc/pyspf-milter/pyspf-milter.conf
debugLevel = 1
TestOnly = 1
Reason_Message = Message {rejectdefer} due to: {spf}. Please see {url}
HELO_reject = SPF_Not_Pass
Mail_From_reject = Fail
No_Mail = False
PermError_reject = False
TempError_Defer = False
skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
Header_Type = AR
Hide_Receiver = Yes
Authserv_Id = mail.example.com
Socket = local:/var/spool/postfix/pyspf-milter/pyspf-milter.sock
UserID = pyspf-milter
InternalHosts = 127.0.0.1
Mock = true
MacroList = daemon_name|VERIFYING
EOF

cat <<'EOF' > /usr/local/etc/python-policyd-spf/policyd-spf.conf
debugLevel = 1
TestOnly = 1
Reason_Message = Message {rejectdefer} due to: {spf}. Please see {url}
HELO_reject = SPF_Not_Pass
Mail_From_reject = Fail
No_Mail = False
PermError_reject = False
TempError_Defer = False
skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
Header_Type = AR
Hide_Receiver = Yes
Authserv_Id = mail.example.com
Socket = local:/var/spool/postfix/pyspf-milter/pyspf-milter.sock
UserID = pyspf-milter
InternalHosts = 127.0.0.1
Mock = true
MacroList = daemon_name|VERIFYING
EOF

Wichtig: Der FreeBSD-Port hat 2023 den Standardpfad für pyspf-milter geändert. Falls dein Setup einen abweichenden Pfad verwenden soll, kannst du ihn per pyspf_milter_conffile in rc.conf überschreiben. (FreeBSD Git)

Zusatzsoftware Konfiguration prüfen

Bash
service pyspf-milter start
service pyspf-milter status

Aufräumen

Überflüssige oder temporäre Verzeichnisse und Dateien entsorgen.

Zusatzsoftware Installation

Nicht erforderlich.

Zusatzsoftware Konfiguration

Nicht erforderlich.


Abschluss

Postfix kann nun gestartet werden.

Bash
service pyspf-milter start
service postfix start

Für spätere Änderungen:

Bash
service pyspf-milter restart
service postfix reload
service postfix restart

Für Funktionstests danach:

Bash
sockstat -4 -6 -l | egrep 'master|smtpd|submission|pyspf'
postqueue -p

Referenzen