diff --git a/files/postfwd/postfwd.bl-hosts b/files/postfwd/postfwd.bl-hosts new file mode 100644 index 0000000..8e38a66 --- /dev/null +++ b/files/postfwd/postfwd.bl-hosts @@ -0,0 +1,99 @@ +# Sperrliste fuer den von Postfix gelieferten client_name, nicht fuer HELO. +# Perl-Regex pro Zeile; Punkte maskieren und Grenzen verankern. +# Beispiel exakter Host: ^mail\.example\.org$ +# Domaene einschliesslich Subdomains: (^|\.)example\.org$ +# Es handelt sich nicht um Postfix-pcre:-Tabellen (keine Ergebnis-Spalte). +# +# Nach Aenderungen: postfwd -f /etc/postfix/postfwd.cf -C +# Danach systemctl reload postfwd (setzt mit den gelieferten Optionen Zaehler zurueck). +# Der Dienst heisst postfwd, nicht postfwd2. file:-Listen werden beim Laden gelesen. +# Leerzeilen und Kommentare werden ignoriert. Dateien muessen lesbar sein. +# +# # Example: +# +# # Werkzeug +# (^|\.)katherina-remberg\.de$ + +# Werkzeug +(^|\.)katherina-remberg\.de$ + +# Mehr Energie für Ihre Schritte +(^|\.)elcoino\.de$ + +# Wiederherstellung des Sehvermogens ohne Operation +(^|\.)toonaca\.or\.mg$ + +# info re_zeptfrei ordern +(^|\.)radiotrabajandoparacristoirmp\.com$ + +# HL Group +(^|\.)group-hire\.com$ + +# Erinnerung: Überzahlung entdeckt – Ihre Rückerstattung wartet! +#mtasv\.net$ + +# edge.toprains.shop:w +(^|\.)edge\.toprains\.shop$ + +# Ideal für Apple- und Samsung-Fans +(^|\.)sdeals\.shop$ + +# Spiegel.de +(^|\.)delpieroacademy\.com$ + +# Kundensupport - photoTAN +#mailjet\.com$ + +# LOTTO-Rabatt +(^|\.)gdwr\.de$ + +# info mit ETFs die Millionen knacken? +(^|\.)movingcompanywheaton\.com$ + +# Specht Office +(^|\.)mta3\.dev\.60cr\.com$ + +# lichtbringer.sho +(^|\.)lichtbringer\.shop$ + +# insights.sternenpfad.shop +(^|\.)insights\.sternenpfad\.shop$ + +# info rezeptfre-i Bestellung +(^|\.)ugms\.org$ + +# info Herrenmeds anfordern +(^|\.)fullendoscopy\.mx$ + +# Premium-Werkzeugwagen: +(^|\.)minillq\.com$ + +# zaubermoment.shop +(^|\.)zaubermoment\.shop$ + +# Lustexperte +(^|\.)jetztpower\.shop$ + +# herzenstone.shop +(^|\.)herzenstone\.shop$ + +# Versand - Wichtige Neiuheit (a2hosted.com) +(^|\.)a2hosted\.com$ + +# Eleganz trifft Funktion: Metall-Kugelschreiber mit Logo +(^|\.)game\.cn$ + +# Ein Sprühstoß für die sofortige Erektion! +(^|\.)perfektepower\.shop$ + +# Home Security / preview.glanzpunkt.shop +(^|\.)glanzpunkt\.shop$ + +# Phishing IHK +(^|\.)rightappearance\.com$ + +# info rezeptf-rei Bestellung +(^|\.)sectiontrading\.com$ + +# Sofortiger zweisprachiger Sprachübersetzer +# - kein Eintrag - diff --git a/files/postfwd/postfwd.bl-nets b/files/postfwd/postfwd.bl-nets new file mode 100644 index 0000000..7044da6 --- /dev/null +++ b/files/postfwd/postfwd.bl-nets @@ -0,0 +1,108 @@ +# Gesperrte Client-IP-Adressen/IPv4-Netze; permanente REJECT-Antwort. +# Eine Adresse/ein Netz pro Zeile. IPv4-CIDR-Netze werden unterstuetzt. +# postfwd2 1.35: IPv6 nur als exakte Adresse OHNE /128, keine IPv6-Netze. +# Vorherige Vollfreistellungen haben Vorrang vor dieser Sperrliste. +# +# Nach Aenderungen: postfwd -f /etc/postfix/postfwd.cf -C +# Danach systemctl reload postfwd (setzt mit den gelieferten Optionen Zaehler zurueck). +# Der Dienst heisst postfwd, nicht postfwd2. file:-Listen werden beim Laden gelesen. +# Leerzeilen und Kommentare werden ignoriert. Dateien muessen lesbar sein. +# +# # Example: +# +# # Werkzeug +# 5.135.22.148/30 +# +# # Shop ABC +# #83.223.86.76 +# #2a01:30:0:13:286:96ff:fe4a:6eee + +# Werkzeug +5.135.22.148/30 + +# Mehr Energie für Ihre Schritte +5.196.53.204/30 + +# Wiederherstellung des Sehvermogens ohne Operation +31.28.27.0/24 + +# info re_zeptfrei ordern +45.61.128.0/18 + +# HL Group +45.132.181.0/24 + +# Erinnerung: Überzahlung entdeckt – Ihre Rückerstattung wartet! +#50.31.205.0/24 + +# edge.toprains.shop +51.89.16.112 + +# Ideal für Apple- und Samsung-Fans +51.195.36.112/26 + +# Bitcoin Boom / GHOSTnet GmbH +85.93.0.0/19 + +# Spiegel.de +85.93.19.234 + +# Kundensupport - photoTAN +#87.253.233.0/24 + +# LOTTO-Rabatt +89.22.116.0/24 + +# info mit ETFs die Millionen knacken? +89.144.4.211 + +# Specht Office +91.193.18.0/24 + +# lichtbringer.shop +94.23.144.0/21 + +# insights.sternenpfad.shop +94.23.152.0/21 + +# info rezeptfre-i Bestellung +104.244.72.0/21 + +# info Herrenmeds anfordern +107.189.0.0/19 + +# Premium-Werkzeugwagen: +162.220.163.128/25 + +# zaubermoment.shop +178.32.96.0/19 + +# Lustexperte +178.32.136.0/21 + +# herzenstone.shop +178.33.112.0/21 + +# ?? +181.214.99.0/24 + +# Versand - Wichtige Neiuheit (a2hosted.com) +185.91.69.0/24 + +# Eleganz trifft Funktion: Metall-Kugelschreiber mit Logo +185.173.235.0/24 + +# Ein Sprühstoß für die sofortige Erektion! +188.165.0.0/21 + +# Home Security / preview.glanzpunkt.shop +188.165.128.0/21 + +# Phishing IHK +191.96.209.0/24 + +# info rezeptf-rei Bestellung +198.98.48.0/20 + +# Sofortiger zweisprachiger Sprachübersetzer +213.202.222.185 diff --git a/files/postfwd/postfwd.bl-recipient-exeeds-msg-size-20mb b/files/postfwd/postfwd.bl-recipient-exeeds-msg-size-20mb new file mode 100644 index 0000000..65f1e83 --- /dev/null +++ b/files/postfwd/postfwd.bl-recipient-exeeds-msg-size-20mb @@ -0,0 +1,40 @@ +# Empfaenger mit zusaetzlichem 20-MiB-Limit, NUR bei Einzelempfaengern. +# Perl-Regex pro Zeile; z.B. +# ^user@example\.org$ +# @example\.org$ +# Die Regel greift nur bei recipient_count=1 am END-OF-MESSAGE und bei +# size > 20971520 Bytes. Genau 20 MiB bleiben erlaubt. +# Bei mehreren Empfaengern fehlt recipient dort; dafuer bietet diese Datei +# KEIN verlaessliches Limit. Fuer verbindliche Limits ist eine andere +# Loesung erforderlich. Die bisher leere Liste bleibt leer. +# Historischer Dateiname mit Schreibfehler bleibt aus Kompatibilitaet erhalten. +# +# +------------------+----------------------+----------------+----------------------+ +# | SMTP-Empfaenger | Empfaenger gelistet? | Nachricht | Wirkung dieser Regel | +# +------------------+----------------------+----------------+----------------------+ +# | Genau einer | Ja | Bis 20 MiB | Keine Ablehnung | +# | Genau einer | Ja | Ueber 20 MiB | REJECT | +# | Genau einer | Nein | Beliebig | Keine Ablehnung | +# | Mehrere | Einer oder mehrere | Beliebig | Keine Ablehnung | +# | | gelistet | | | +# +------------------+----------------------+----------------+----------------------+ +# +# 20 MiB = 20.971.520 Bytes, einschliesslich Header und MIME-Kodierung. +# +# Bei mehreren SMTP-Empfaengern liefert Postfix am Nachrichtenende +# keine einzelne Empfaengeradresse fuer den Listenvergleich. +# +# Das globale Postfix-Limit message_size_limit gilt weiterhin. +# +# +# # Example: +# +# # # annoying spammer domains +# # block all senders of maildomaindomain 'oopen.de' +# @acieu\.co\.uk$ +# +# # annoying spammer addresses +# # block sender address +# error@mailfrom.com +# sqek@eike\.se$ + diff --git a/files/postfwd/postfwd.bl-sender b/files/postfwd/postfwd.bl-sender new file mode 100644 index 0000000..8609231 --- /dev/null +++ b/files/postfwd/postfwd.bl-sender @@ -0,0 +1,113 @@ +# Sperrliste fuer Envelope-Absender (MAIL FROM), NICHT From:-Header. +# Perl-Regex pro Zeile; keine Postfix-pcre:-Tabellen mit Ergebnis-Spalte. +# +# Exakte Adresse: ^user@example\.org$ +# +# Domaene und Subdomains: @([^@]+\.)?example\.org$ +# +# Ablehnung mit REJECT, kein stilles DISCARD. Domain-Grenzen und maskierte +# Punkte vermeiden unbeabsichtigte Treffer auf aehnliche Domainnamen. +# + +# reported by MBR +^paul\.graber99@gmail\.com$ +^ludwigpestow@gmail\.com$ + +# annoying spammer domains +@([^@]+\.)?acieu\.co\.uk$ +@([^@]+\.)?inbox\.ru$ + +# ---- + +# Werkzeug +@([^@]+\.)?katherina-remberg\.de$ + +# Mehr Energie für Ihre Schritte +@([^@]+\.)?elcoino\.de$ + +# Wiederherstellung des Sehvermogens ohne Operation +@([^@]+\.)?toonaca\.or\.mg$ + +# info re_zeptfrei ordern +@([^@]+\.)?radiotrabajandoparacristoirmp\.com$ + +# HL Group +@([^@]+\.)?group-hire\.com$ + +# Erinnerung: Überzahlung entdeckt – Ihre Rückerstattung wartet! +@([^@]+\.)?toldfinancialcapital\.com$ + +# edge.toprains.shop +@([^@]+\.)?toprains\.shop$ + +# Ideal für Apple- und Samsung-Fans +@([^@]+\.)?sdeals\.shop$ + +# Spiegel.de +@([^@]+\.)?delpieroacademy\.com$ + +# Kundensupport - photoTAN +#@laurash.net + +# LOTTO-Rabatt +@([^@]+\.)?gdwr\.de$ + +# info mit ETFs die Millionen knacken? +@([^@]+\.)?movingcompanywheaton\.com$ + +# Specht Offic +^officeuf@jxb669\.com$ +^officeuf@ + +# edge.toprains.shop +@([^@]+\.)?edge\.toprains\.shop$ + +# lichtbringer.shop +@([^@]+\.)?lichtbringer\.shop$ + +# insights.sternenpfad.shop +@([^@]+\.)?insights\.sternenpfad\.shop$ + +# info rezeptfre-i Bestellung +@([^@]+\.)?ugms\.org$ + +# Premium-Werkzeugwagen: +@([^@]+\.)?ezhifeng\.co$ + +# zaubermoment.shop +@([^@]+\.)?zaubermoment\.shop$ + +# Lustexperte +@([^@]+\.)?jetztpower\.shop$ + +# herzenstone.shop +@([^@]+\.)?herzenstone\.shop$ + +# ?? 181.214.99.0/24 +@([^@]+\.)?imrx4k\.com$ + +# Versand - Wichtige Neiuheit (a2hosted.com) +@([^@]+\.)?a2hosted\.com$ + +# Eleganz trifft Funktion: Metall-Kugelschreiber mit Logo +@([^@]+\.)?izilian\.com$ + +# Ein Sprühstoß für die sofortige Erektion! +@([^@]+\.)?perfektepower\.shop$ + +# Home Security / preview.glanzpunkt.shop +@([^@]+\.)?glanzpunkt\.shop$ + +# Phishing IHK +@([^@]+\.)?rightappearance\.com$ + +# info rezeptf-rei Bestellung +@([^@]+\.)?sectiontrading\.com$ + +# Sofortiger zweisprachiger Sprachübersetzer +@([^@]+\.)?delavers\.de$ + +# --- + +# Google Mail Adresse +@([^@]+\.)?laravel\.digital$ diff --git a/files/postfwd/postfwd.bl-user b/files/postfwd/postfwd.bl-user new file mode 100644 index 0000000..0f8e292 --- /dev/null +++ b/files/postfwd/postfwd.bl-user @@ -0,0 +1,8 @@ +# Gesperrte SASL-Benutzernamen; permanente REJECT-Antwort. +# Exakter Name pro Zeile, keine Regex. Vorherige Vollfreistellungen haben Vorrang. +# +# Example: +# +# # give SASL usernames to block here +# ckubu@oopen.de + diff --git a/files/postfwd/postfwd.cf b/files/postfwd/postfwd.cf new file mode 100644 index 0000000..eaf6ec5 --- /dev/null +++ b/files/postfwd/postfwd.cf @@ -0,0 +1,227 @@ +# Statische Vorlage fuer postfwd 1.35, ohne Jinja-Variablen. +# +# POSTFIX-EINBINDUNG +# main.cf: smtpd_end_of_data_restrictions = +# check_policy_service inet:127.0.0.1:10040 +# Amavis-Rueckgabeport localhost:10025 in master.cf: +# -o smtpd_end_of_data_restrictions= +# Sonst kann die Wiedereinlieferung mit XFORWARD erneut gezaehlt werden. +# /etc/default/postfwd: ARGS="--summary=600 --cache=0" +# +# AUSWERTUNG +# Verschiedene Attribute einer Regel: UND. Gleiches Attribut mehrfach: ODER. +# DUNNO beendet diesen postfwd-Aufruf; Postfix setzt eigene Pruefungen fort. +# REJECT lehnt permanent, 450 temporaer ab. rate()/rcpt() laufen unterhalb +# des Limits weiter. jump(ID) springt zur bezeichneten Regel. +# final=yes ist keine Steueranweisung und wird NICHT verwendet. +# +# LIMITS (fuer nicht vollstaendig freigestellte, nicht lokale SMTP-Clients) +# +# +--------------------------------+-----------+-----------+------------------+ +# | Limit | Standard | Benutzer | Benutzer nur in | +# | | | 500er- | Empfaenger- | +# | | | Liste | Ausnahmeliste | +# +--------------------------------+-----------+-----------+------------------+ +# | Nachrichten / Stunde / Benutzer| 50 | 500 | 50 | +# | Empfaenger / Stunde / Benutzer| 250 | 1000 | 1000 | +# | Empfaenger / Nachricht | 89 | 89 | 89 | +# +--------------------------------+-----------+-----------+------------------+ +# +# Zusaetzlich fuer alle oben genannten Benutzer: +# +# +------------------------------------------+----------------------------+ +# | Client-IP | Nachrichten / Minute / IP | +# +------------------------------------------+----------------------------+ +# | Weder IP noch Hostname in Ausnahmeliste | 50 | +# | IP ODER Hostname in Ausnahmeliste | 100 | +# +------------------------------------------+----------------------------+ +# +# Zusaetzlich fuer nicht authentifizierte Clients: +# +# +------------------------------------------+----------------------------+ +# | client_name = unknown | 5 Nachrichten / 5 Minuten | +# | | je Client-IP | +# +------------------------------------------+----------------------------+ +# +# Unauthentifiziert + client_name=unknown: 5 Nachrichten / 300 Sekunden / IP. +# SASL: 50 Nachrichten / 3600 Sekunden / Benutzer; Ausnahme: 500. +# IP: 50 Nachrichten / 60 Sekunden; Ausnahme ueber IP ODER Hostname: 100. +# SASL: maximal 89 Empfaenger pro Nachricht (Ablehnung ab 90). +# SASL: 250 Empfaenger / 3600 Sekunden; Ausnahme: 1000. +# Jeder Benutzer der 500er-Nachrichtenliste bekommt auch die 1000er- +# Empfaengerausnahme. Die separate Empfaengerliste erlaubt nur 1000 statt +# 250 Empfaenger/Stunde, erhoeht aber nicht das Nachrichtenlimit. +# Benutzer der 500er-Nachrichtenliste sind vom IP-Minutenlimit (50 bzw. +# 100 Nachrichten / 60 Sekunden) ausgenommen: sie versenden meist +# automatisiert in Schueben und wuerden sonst trotz 500er-Ausnahme am +# Minutenlimit haengen bleiben. Fuer alle anderen bleibt das Minutenlimit +# aktiv. Die Empfaengerzahl pro Nachricht (max. 89) gilt fuer alle. +# +# ZAEHLWEISE +# rate() zaehlt END-OF-MESSAGE-Anfragen, rcpt() addiert recipient_count. +# Es sind Versandversuche, keine bestaetigten Zustellungen. Wiederholungen +# und spaeter abgelehnte Versuche koennen mitgezaehlt werden. Empfaenger +# werden aufsummiert, nicht dedupliziert. Kein gleitendes Kalenderstunden- +# budget: postfwd verwaltet befristete Zaehler ab ihrer Anlage. +# Gemeinsame IPs teilen sich das Minutenlimit, auch bei mehreren Benutzern. +# +# DATEIEN +# file: liest Listen beim Laden der Konfiguration. Danach neu laden/starten. +# Fuer IPs/Netze: client_address==file:... (CIDR-Vergleich). +# Fuer SASL: sasl_username==file:... (exakte Namen, keine Regex). +# Fuer Namen/Adressen: =~file:... (Perl-Regex, keine Postfix-pcre:-Tabelle). +# Alle referenzierten Dateien muessen fuer den Benutzer postfw lesbar sein. +# Keine Ausnahmeliste mit &&SASL_AUTH in derselben Regel kombinieren: +# das waere eine ODER-Verknuepfung auf sasl_username! + +# Nur diese Phase verwenden; keine Zaehleranlage in anderen SMTP-Phasen. +id=CHECK_PHASE + protocol_state!=END-OF-MESSAGE + action=DUNNO + +# Lokaler SMTP-Verkehr wird ausgenommen, IPv4-Loopback und IPv6-Loopback. +# Dies ersetzt NICHT die Abschaltung am Amavis-Rueckgabeport: XFORWARD +# kann dort die urspruengliche externe Clientadresse wiederherstellen. +id=SKIP_LOOPBACK + client_address==127.0.0.0/8, ::1 + action=DUNNO + +# 1. VOLLSTAENDIGE AUSNAHMEN +# Bewusst vor den Sperrlisten wie in der bisherigen Konfiguration. +# Diese Listen umgehen ALLE folgenden postfwd-Regeln, auch Blacklists. +# Fuer nur hoehere Versandlimits ausschliesslich die high-rate-Listen nutzen. +id=WHL_NETS + client_address==file:/etc/postfix/postfwd.wl-nets + action=DUNNO + +id=WHL_HOSTS + client_name=~file:/etc/postfix/postfwd.wl-hosts + action=DUNNO + +id=WHL_USERS + sasl_username==file:/etc/postfix/postfwd.wl-user + action=DUNNO + +# Achtung: Envelope-Absender ist keine authentifizierte Identitaet. +# Bestehende Freistellung bleibt erhalten; kein SPF-/DKIM-Nachweis hier. +id=WHL_SENDERS + sender=~file:/etc/postfix/postfwd.wl-sender + action=DUNNO + +# 2. SPERRLISTEN: permanente Ablehnung, kein stilles DISCARD. +id=BL_NETS + client_address==file:/etc/postfix/postfwd.bl-nets + action=REJECT Network $$client_address blocked by mailserver admins. Error:BL_NETS + +id=BL_HOSTS + client_name=~file:/etc/postfix/postfwd.bl-hosts + action=REJECT Client $$client_name blocked by mailserver admins. Error:BL_HOSTS + +id=BL_USERS + sasl_username==file:/etc/postfix/postfwd.bl-user + action=REJECT User blocked by mailserver admins. Error:BL_USERS + +id=BL_SENDER + sender=~file:/etc/postfix/postfwd.bl-sender + action=REJECT Sender blocked by mailserver admins. Error:BL_SENDER + +# 3. EMPFAENGERBEZOGENE GROESSE, NUR BEI GENAU EINEM EMPFAENGER +# 20 MiB = 20971520 Bytes; mit > bleibt genau dieser Wert erlaubt. +# Postfix liefert recipient am Nachrichtenende nur bei einem Empfaenger. +# Bei mehreren Empfaengern kann diese Regel KEIN Limit durchsetzen. +# Die bisher leere Empfaengerliste bleibt erhalten; solange sie leer ist, +# ist diese Zusatzpruefung inaktiv. Global gilt weiter message_size_limit. +# Fuer verbindliche empfaengerbezogene Limits auch bei Mehrfachadressierung +# ist eine andere Loesung, z.B. ein geeigneter Milter, erforderlich. +id=BLOCK_MSG_SIZE_20MB + recipient_count==1 + recipient=~file:/etc/postfix/postfwd.bl-recipient-exeeds-msg-size-20mb + size>20971520 + action=REJECT Message exceeds 20 MiB ($$size bytes). Error:BLOCK_MSG_SIZE_20MB + +# Permanente Empfaengerzahl-Pruefung vor die Rate-Zaehler stellen. +# Das bisher wirksame Limit bleibt erhalten: 89 erlaubt, ab 90 ablehnen. +id=BLOCK_MSG_RCPT + sasl_username!~^$ + recipient_count=>90 + action=REJECT Too many recipients, maximum is 89 per message. Error:BLOCK_MSG_RCPT + +# 4. UNBEKANNTE, UNAUTHENTIFIZIERTE CLIENTS +# 5 Nachrichten, NICHT 5 Empfaenger oder 5 TCP-Verbindungen. +id=RATE_UNKNOWN_CLIENT_ADDR + sasl_username=~^$ + client_name==unknown + action=rate(client_address/5/300/450 4.7.1 Only 5 messages per 5 minutes allowed. Error:RATE_UNKNOWN_CLIENT_ADDR) + +# 5. NACHRICHTEN PRO STUNDE UND SASL-BENUTZER +# Auswahl VOR dem Zaehlen. Die Ausnahme springt an der 50er-Regel vorbei. +# Unterhalb 500 wird nach RATE_MSG_EXCEPTION direkt mit der Empfaenger- +# summe (Abschnitt 7) fortgesetzt; die IP-Minutenlimits entfallen bewusst. +id=SELECT_MSG_EXCEPTION + sasl_username==file:/etc/postfix/postfwd.high-rate-number-messages + action=jump(RATE_MSG_EXCEPTION) + +id=RATE_MSG + sasl_username!~^$ + action=rate(sasl_username/50/3600/450 4.7.1 Maximum 50 messages per hour exceeded. Error:RATE_MSG) + +# Standardpfad muss die nachfolgende 500er-Regel ueberspringen. +id=AFTER_RATE_MSG + action=jump(SELECT_HIGH_RATE_IP) + +id=RATE_MSG_EXCEPTION + sasl_username!~^$ + action=rate(sasl_username/500/3600/450 4.7.1 Maximum 500 messages per hour exceeded. Error:RATE_MSG_EXCEPTION) + +# Nach erfolgreicher 500er-Pruefung die IP-Minutenlimits (Abschnitt 6) +# bewusst ueberspringen: automatisierte Versender wuerden sonst am +# Minutenlimit scheitern. Empfaengerlimit pro Stunde weiterhin pruefen. +id=AFTER_RATE_MSG_EXCEPTION + action=jump(SELECT_RCPT_EXCEPTION_MSG) + +# 6. NACHRICHTEN PRO MINUTE UND IP +# IP oder Hostname waehlen denselben 100er-Zaehler. Auch bei zwei Treffern +# nur eine Zaehlung. Hostname ist der von Postfix gelieferte client_name; +# nicht identisch mit einem ungeprueften HELO oder bloss irgendeinem PTR. +id=SELECT_HIGH_RATE_IP + client_address==file:/etc/postfix/postfwd.high_rate_ip + action=jump(HIGH_RATE_CLIENT_ADDR) + +id=SELECT_HIGH_RATE_HOST + client_name=~file:/etc/postfix/postfwd.high_rate_hostname + action=jump(HIGH_RATE_CLIENT_ADDR) + +id=RATE_CLIENT_ADDR + action=rate(client_address/50/60/450 4.7.1 Maximum 50 messages per minute per IP exceeded. Error:RATE_CLIENT_ADDR) + +id=AFTER_RATE_CLIENT_ADDR + action=jump(SELECT_RCPT_EXCEPTION_MSG) + +id=HIGH_RATE_CLIENT_ADDR + action=rate(client_address/100/60/450 4.7.1 Maximum 100 messages per minute per IP exceeded. Error:HIGH_RATE_CLIENT_ADDR) + +# 7. EMPFAENGERSUMME PRO STUNDE UND SASL-BENUTZER +# 500er-Nachrichtenausnahme erhaelt automatisch auch 1000 Empfaenger/Stunde. +# So scheitert sie nicht nach spaetestens 250 Ein-Empfaenger-Nachrichten. +# Die zweite Liste kann weitere Benutzer NUR fuer die Empfaengersumme nennen. +id=SELECT_RCPT_EXCEPTION_MSG + sasl_username==file:/etc/postfix/postfwd.high-rate-number-messages + action=jump(RATE_RCPT_HIGH) + +id=SELECT_RCPT_EXCEPTION_LIST + sasl_username==file:/etc/postfix/postfwd.high-rate-number-recipients + action=jump(RATE_RCPT_HIGH) + +id=RATE_RCPT + sasl_username!~^$ + action=rcpt(sasl_username/250/3600/450 4.7.1 Maximum 250 recipients per hour exceeded. Error:RATE_RCPT) + +id=AFTER_RATE_RCPT + action=jump(END) + +id=RATE_RCPT_HIGH + sasl_username!~^$ + action=rcpt(sasl_username/1000/3600/450 4.7.1 Maximum 1000 recipients per hour exceeded. Error:RATE_RCPT_HIGH) + +id=END + action=DUNNO diff --git a/files/postfwd/postfwd.high-rate-number-messages b/files/postfwd/postfwd.high-rate-number-messages new file mode 100644 index 0000000..f223b3e --- /dev/null +++ b/files/postfwd/postfwd.high-rate-number-messages @@ -0,0 +1,16 @@ +# SASL-Benutzer mit bis zu 500 Nachrichten UND 1000 Empfaengern pro Stunde. +# +# Exakter sasl_username pro Zeile, keine Regex. Leere Datei = keine Ausnahme. +# =========================================================================== +# +# interne Bemerkung +# Die Standardlimits 50 Nachrichten und 250 Empfaenger werden gezielt +# uebersprungen, ebenso das IP-Minutenlimit (50 bzw. 100 Nachrichten/Minute). +# Maximal 89 Empfaenger/Nachricht gelten weiterhin. +# Eine Nachricht mit 10 Empfaengern zaehlt als 1 Nachricht und 10 Empfaenger. +# Dies ist keine vollstaendige Freistellung; Blacklists gelten weiterhin. +# +# Example: +# +# buchsys@sprachenatelier-berlin.de +# todo@warenform.de diff --git a/files/postfwd/postfwd.high-rate-number-recipients b/files/postfwd/postfwd.high-rate-number-recipients new file mode 100644 index 0000000..d0135fb --- /dev/null +++ b/files/postfwd/postfwd.high-rate-number-recipients @@ -0,0 +1,17 @@ +# Zusaetzliche SASL-Benutzer mit bis zu 1000 Empfaengern pro Stunde. +# +# Exakter sasl_username pro Zeile, keine Regex. Das Nachrichtenlimit bleibt 50, +# ============================================================================= +# +# sofern der Benutzer nicht auch in postfwd.high-rate-number-messages steht. +# Benutzer der Nachrichtenliste haben die 1000er-Ausnahme bereits automatisch +# und muessen hier NICHT doppelt gepflegt werden. Leer ist zulaessig. +# Es werden Empfaengervorkommen gezaehlt, keine eindeutigen Adressen. +# +# Example: +# +# # give sasl usernames here +# # +# ckubu@oopen.de +# + diff --git a/files/postfwd/postfwd.high_rate_hostname b/files/postfwd/postfwd.high_rate_hostname new file mode 100644 index 0000000..6ad2679 --- /dev/null +++ b/files/postfwd/postfwd.high_rate_hostname @@ -0,0 +1,45 @@ +# ============================================================ +# Whitelist fuer erhoehtes Rate-Limit (100 Nachrichten/Minute) +# auf Basis des Client-Hostnamens (Reverse-DNS/PTR-Eintrag), +# statt der Standard-Grenze von 50/Minute aus RATE_CLIENT_ADDR +# +# Format: Perl-Regex pro Zeile (kein Wildcard/Glob!), umschliessende +# '/.../' sind optional. Keine Postfix-pcre:-Tabelle. +# +# postfwd vergleicht jede Zeile gegen den von Postfix aufgeloesten +# 'client_name'-Wert (PTR-Eintrag der Client-IP). +# Leerzeilen und Zeilen, die mit '#' beginnen, werden ignoriert. +# +# Example +# +# ^buchsys3\.buchsys\.de$ +# +# # buchsys.de +# # www.buchsys.de +# # mail.buchsys.com +# # buchsys.org +# #www.buchsys.co.uk +# (^|\.)buchsys\.[a-z0-9.-]+$ +# +# WICHTIG: +# - Diese Regel greift NUR, wenn Reverse-DNS fuer die jeweilige +# IP sauber konfiguriert ist und der PTR-Eintrag auf das Muster +# passt. Pruefe den tatsaechlichen 'client_name'-Wert vorher in +# den postfwd-Logs. +# - Ein zu breites Muster (z.B. /buchsys\.de/ ohne Anker) kann auch +# unerwuenschte Hosts matchen (z.B. "evil-buchsys.de.attacker.com"). +# Deshalb immer mit '^' und '$' bzw. '\.' vor der Domain arbeiten. +# - Jeder hier gematchte Hostname umgeht das strengere 50er-Limit +# und erlaubt bis zu 100 Nachrichten/Minute. Bitte nur Muster +# eintragen, denen du tatsaechlich vertraust. +# - Benutzer aus postfwd.high-rate-number-messages sind vom +# Minutenlimit ohnehin ausgenommen und brauchen hier keinen Eintrag. +# - Nach Aenderungen: postfwd -f /etc/postfix/postfwd.cf -C +# Danach: systemctl reload postfwd (Dienst heisst postfwd, nicht postfwd2) +# - Die Datei wird in postfwd.cf per 'client_name=~file:' eingelesen +# (Regel 'SELECT_HIGH_RATE_HOST', Zaehler in 'HIGH_RATE_CLIENT_ADDR'). +# - Die IP-basierte Auswahl (SELECT_HIGH_RATE_IP) ist zuverlaessiger +# und sollte die primaere Loesung sein; diese Hostname-Regel +# eignet sich vor allem als Ergaenzung bei wechselnden IPs. +# ============================================================ + diff --git a/files/postfwd/postfwd.high_rate_ip b/files/postfwd/postfwd.high_rate_ip new file mode 100644 index 0000000..5c9e904 --- /dev/null +++ b/files/postfwd/postfwd.high_rate_ip @@ -0,0 +1,34 @@ +# ============================================================ +# Whitelist fuer erhoehtes Rate-Limit (100 Nachrichten/Minute) +# statt der Standard-Grenze von 50/Minute aus RATE_CLIENT_ADDR +# +# Hier werden IP-Adressen bzw. CIDR-Netze von vertrauenswuerdigen +# Absende-Systemen eingetragen, die routinemaessig hoehere +# Mailmengen ueber eine feste Absender-IP verschicken, z. B. +# Kunden-Mailserver, angebundene Fachanwendungen/Buchungssysteme, +# Newsletter- oder Transaktionsmail-Versender. +# +# Format: eine IP-Adresse oder ein CIDR-Netz pro Zeile. +# Leerzeilen und Zeilen, die mit '#' beginnen, werden ignoriert. +# IPv4 und IPv6 werden unterstuetzt. +# +# Beispiele: +# +# 116.202.85.120 +# 203.0.113.0/24 +# +# WICHTIG: +# - Jede hier eingetragene IP umgeht das strengere 50er-Limit +# und darf bis zu 100 Nachrichten/Minute senden. Bitte nur +# IPs eintragen, denen du tatsaechlich vertraust (kompromittierte +# oder falsch eingetragene Systeme koennten sonst zum offenen +# Spam-Verteiler werden). +# - Benutzer aus postfwd.high-rate-number-messages sind vom +# Minutenlimit ohnehin ausgenommen und brauchen hier keinen Eintrag. +# - IPv6 nur als exakte Adresse OHNE /128 (postfwd 1.35). +# - Nach Aenderungen: postfwd -f /etc/postfix/postfwd.cf -C +# Danach: systemctl reload postfwd (Dienst heisst postfwd, nicht postfwd2) +# - Die Datei wird in postfwd.cf per 'client_address==file:' eingelesen +# (Regel 'SELECT_HIGH_RATE_IP', Zaehler in 'HIGH_RATE_CLIENT_ADDR'). +# ============================================================ + diff --git a/files/postfwd/postfwd.wl-hosts b/files/postfwd/postfwd.wl-hosts new file mode 100644 index 0000000..facfe01 --- /dev/null +++ b/files/postfwd/postfwd.wl-hosts @@ -0,0 +1,14 @@ +# Vollstaendige Freistellung anhand des von Postfix gelieferten client_name. +# +# Perl-Regex pro Zeile; +# +# Beispiel: +# ^mail\.example\.org$ +# +# # Domaene einschliesslich Subdomains: +# (^|\.)example\.org$ +# +# Umgeht auch Blacklists und alle Versandlimits. Fuer nur 100 Nachrichten pro +# Minute die Datei postfwd.high_rate_hostname verwenden. + + diff --git a/files/postfwd/postfwd.wl-nets b/files/postfwd/postfwd.wl-nets new file mode 100644 index 0000000..d59933f --- /dev/null +++ b/files/postfwd/postfwd.wl-nets @@ -0,0 +1,25 @@ +# Vollstaendige Freistellung fuer Client-IP-Adressen/IPv4-Netze. +# Umgeht auch die nachfolgenden Blacklists und alle Limits; nur bewusst nutzen. +# IPv4: einzelne Adresse oder CIDR-Netz. postfwd2 1.35 verarbeitet IPv6 hier +# nur als exakten Adresstext OHNE /128, nicht als IPv6-CIDR-Netz. +# +# Exakte IPv6-Schreibweise wie client_address von Postfix verwenden. +# Eine Adresse/ein Netz pro Zeile. +# +# Beispiele: +# +# 192.0.2.10 +# 192.0.2.0/24 +# +# # b.mx.oopen.de +# 2a01:4f8:271:1266::73 +# 2a01:4f8:271:1266::/64 +# +# +# d.mx.oopen.de (listen server) +95.217.204.227 +2a01:4f9:4a:47e5::227 + +# b.mx.oopen.de +162.55.82.73/32 +2a01:4f8:271:1266::73 diff --git a/files/postfwd/postfwd.wl-sender b/files/postfwd/postfwd.wl-sender new file mode 100644 index 0000000..49fd85b --- /dev/null +++ b/files/postfwd/postfwd.wl-sender @@ -0,0 +1,15 @@ +# Vollstaendige Freistellung anhand des Envelope-Absenders, NICHT From:-Header. +# +# Perl-Regex pro Zeile +# +# Example: +# +# ^noreply@example\.org$ +# +# ACHTUNG: +# Absender ist nachahmbar; diese Liste prueft weder SPF noch DKIM noch +# SASL-Identitaet. Treffer umgehen nachfolgende postfwd-Sperren und Limits. +# Der vorhandene Eintrag wurde als bisherige Ausnahme beibehalten. +# + +^noreply@login\.ubuntu\.com$ diff --git a/files/postfwd/postfwd.wl-user b/files/postfwd/postfwd.wl-user new file mode 100644 index 0000000..aff1963 --- /dev/null +++ b/files/postfwd/postfwd.wl-user @@ -0,0 +1,13 @@ +# Vollstaendige Freistellung fuer SASL-Benutzernamen. +# +# Exakter Name pro Zeile, keine Regex; wie von Postfix als sasl_username geliefert. +# +# Umgeht auch Blacklists und alle Limits. Fuer nur hoehere Limits stattdessen +# postfwd.high-rate-number-messages bzw. -recipients verwenden. +# +# Example: +# +# # give trusted sasl usernames here +# ckubu@oopen.de +# vertrieb@akweb.de + diff --git a/install_amavis.sh b/install_amavis.sh index 691ddca..464562e 100755 --- a/install_amavis.sh +++ b/install_amavis.sh @@ -5205,9 +5205,15 @@ fi ## - -o smtpd_sender_restrictions= ## - -o smtpd_recipient_restrictions=permit_mynetworks,reject ## - -o smtpd_data_restrictions= +## - -o smtpd_end_of_data_restrictions= ## - -o mynetworks=127.0.0.0/8,[::1]/128 ## - -o receive_override_options=no_unknown_recipient_checks ## - +## - - 'smtpd_end_of_data_restrictions=' disables the postfwd policy check +## - (main.cf) for mail reinjected by amavis. Otherwise every message +## - could be counted twice by the postfwd rate limits (XFORWARD restores +## - the original client address on reinjection). +## - ## - - take care not to have 'content_filter' set im main.cf postfix_master_cf="/etc/postfix/master.cf" echo "" @@ -5281,6 +5287,7 @@ localhost:10025 inet n - y - - smtpd -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions= + -o smtpd_end_of_data_restrictions= -o mynetworks=127.0.0.0/8,[::1]/128 -o receive_override_options=no_unknown_recipient_checks EOF @@ -5336,6 +5343,7 @@ localhost:10025 inet n - y - - smtpd -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions= + -o smtpd_end_of_data_restrictions= -o mynetworks=127.0.0.0/8,[::1]/128 -o receive_override_options=no_unknown_recipient_checks EOF @@ -5384,6 +5392,7 @@ localhost:10025 inet n - y - - smtpd -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions= + -o smtpd_end_of_data_restrictions= -o mynetworks=127.0.0.0/8,[::1]/128 -o receive_override_options=no_unknown_recipient_checks EOF @@ -5416,6 +5425,7 @@ localhost:10025 inet n - y - - smtpd -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions= + -o smtpd_end_of_data_restrictions= -o mynetworks=127.0.0.0/8,[::1]/128 -o receive_override_options=no_unknown_recipient_checks EOF diff --git a/install_postfix_advanced.sh b/install_postfix_advanced.sh index f39adb5..dc5c865 100755 --- a/install_postfix_advanced.sh +++ b/install_postfix_advanced.sh @@ -1186,444 +1186,167 @@ else fi fi -echononl " Adjust /etc/default/postfwd" -perl -i -n -p -e "s#^(\s*)(STARTUP=.*)#\#\1\2\nSTARTUP=1#" \ - /etc/default/postfwd > $log_file 2>&1 -if [[ $? -eq 0 ]] ; then - echo_ok -else - echo_failed - error "$(cat $log_file)" -fi +## - Adjust /etc/default/postfwd +## - +## - set_postfwd_default +## - +## - - VAR is set (uncommented) and has the wanted value -> nothing to do +## - - VAR is set with a different value -> comment out the current +## - setting and add the new setting directly below it +## - - VAR is not set at all -> append the new setting +## - +## - Quotes around the value are ignored when comparing ("1" == 1). +## - +postfwd_default_file="/etc/default/postfwd" -_file="/etc/postfix/postfwd.wl-nets" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Trusted networks whitelisted by postfwd -# -# Example: -# -# # web0.warenform.de -# #83.223.86.76 -# #2a01:30:0:505:286:96ff:fe4a:6ee -# #2a01:30:0:13:286:96ff:fe4a:6eee -# -# --- +set_postfwd_default() { + local _var="$1" + local _val="$2" + local _tmp_file -# give truested networrk adresses here -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi + echononl " ${_var}=${_val}" -_file="/etc/postfix/postfwd.wl-hosts" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Trusted hosts whitelisted by postfwd -# -# This file is called with '=~'. This means perl regexp is possible -# -# Example: -# -# # all hosts of domain 'oopen.de' -# \.oopen\.de$ -# -# # host a.mx.oopen.de -# a.mx.oopen.de -# -# --- + if [[ ! -f "$postfwd_default_file" ]] ; then + touch "$postfwd_default_file" > "$log_file" 2>&1 || { echo_failed ; error "$(cat $log_file)" ; return 1 ; } + fi -# give truested hostnames here + _tmp_file="$(mktemp)" + awk -v var="$_var" -v val="$_val" ' + function unq(s) { + gsub(/^[ \t]+|[ \t]+$/, "", s) + if (s ~ /^".*"$/ || s ~ /^\047.*\047$/) s = substr(s, 2, length(s) - 2) + return s + } + $0 ~ "^[ \t]*" var "=" { + cur = $0 ; sub("^[ \t]*" var "=", "", cur) + if (! done && unq(cur) == unq(val)) { print ; done = 1 ; next } + print "#" $0 + if (! done) { print var "=" val ; done = 1 } + next + } + { print } + END { if (! done) print var "=" val } + ' "$postfwd_default_file" > "$_tmp_file" 2> "$log_file" -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.wl-user" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# SASL Users whitelisted by postfwd -# --- - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.wl-sender" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Trusted senders whitelisted by postfwd -# -# This file is called with '=~'. This means perl regexp is possible -# -# Example: -# -# # all senders of maildomaindomain 'oopen.de' -# @oopen\.de$ -# -# # sender address ckubu@oopen.de -# ckubu@oopen.de -# -# --- - -# give trusted sender addresses here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-nets" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Networks blocked by postfwd -# -# Example: -# -# # web0.warenform.de -# #83.223.86.76 -# #2a01:30:0:505:286:96ff:fe4a:6ee -# #2a01:30:0:13:286:96ff:fe4a:6eee -# -# --- - -# give networks to block here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-hosts" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# hosts blocked by postfwd -# -# This file is called with '=~'. This means perl regexp is possible -# -# Example: -# -# # block all hosts of domain 'oopen.de' -# \.oopen\.de$ -# -# # block host a.mx.oopen.de -# a.mx.oopen.de -# -# --- - -# give hostnames to blocke here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-user" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# SASL Users blocked by postfwd -# --- - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-sender" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Sender addresses blocked by postfwd -# --- - -# annoying spammer domains -@acieu.co.uk - -# annoying spammer addresses -error@mailfrom.com - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-sender" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Sender addresses blocked by postfwd -# -# This file is called with '=~'. This means perl regexp is possible -# -# Example: -# -# # all senders of maildomaindomain 'oopen.de' -# @oopen\.de$ -# -# # sender address ckubu@oopen.de -# ckubu@oopen.de -# -# --- - -# annoying spammer domains -@acieu.co.uk - -# annoying spammer addresses -error@mailfrom.com - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -echononl " Create configuration file '/etc/postfix/postfwd.cf'.." -if [[ ! -f "/etc/postfix/postfwd.cf" ]]; then - cat << EOF > /etc/postfix/postfwd.cf - -#======= Definitions ============ - -# Match messages with an associated SASL username -&&SASL_AUTH { - sasl_username!~^\$ + if [[ $? -ne 0 ]] ; then + echo_failed + error "$(cat $log_file)" + elif cmp -s "$_tmp_file" "$postfwd_default_file" ; then + echo_skipped + else + # - cat instead of mv/cp keeps owner and permissions of the original file + cat "$_tmp_file" > "$postfwd_default_file" 2> "$log_file" + if [[ $? -eq 0 ]] ; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + fi + fi + rm -f "$_tmp_file" } -# Trusted networks -&&TRUSTED_NETS { - client_address==file:/etc/postfix/postfwd.wl-nets -} - -# Trusted hostnames -# client_name~=.warenform.de$ -&&TRUSTED_HOSTS { - client_name=~file:/etc/postfix/postfwd.wl-hosts -} - -# Trusted users -&&TRUSTED_USERS { - sasl_username==file:/etc/postfix/postfwd.wl-user -} - -# Trusted senders -&&TRUSTED_SENDERS { - sender=~file:/etc/postfix/postfwd.wl-sender -} - -# Blacklist networks -&&BLOCK_NETS { - client_address==file:/etc/postfix/postfwd.bl-nets -} - -# Blacklist hostnames -&&BLOCK_HOSTS { - client_name=~file:/etc/postfix/postfwd.bl-hosts -} - -# Blacklist users -&&BLOCK_USERS { - sasl_username==file:/etc/postfix/postfwd.bl-user -} - -# Blacklist sender adresses -&&BLOCK_SENDER { - # =~ - # using '=~' allows also matching entries for domains (i.e. @acieu.co.uk) - sender=~file:/etc/postfix/postfwd.bl-sender -} - -# Inbound emails only -&&INCOMING { - client_address!=127.0.0.1 -} +echo "" +echo " Adjust ${postfwd_default_file}" +set_postfwd_default STARTUP "1" +set_postfwd_default CONF "/etc/postfix/postfwd.cf" +set_postfwd_default INET "127.0.0.1" +set_postfwd_default PORT "10040" +set_postfwd_default ARGS '"--summary=600 --cache=0"' +echo "" -#======= Rule Sets ============ +## - Install postfwd configuration and list files shipped with this +## - repository (directory 'files/postfwd'). +## - +## - - postfwd.cf: installed; an existing, differing file is saved +## - as postfwd.cf. first. +## - - list files (whitelists, blacklists, high-rate exceptions): +## - only installed if not yet present - existing lists contain +## - local data and are never overwritten. +## - +postfwd_src_dir="${working_dir}/files/postfwd" +postfwd_conf_dir="/etc/postfix" -# --- -# -# Processing of the Rule Sets -# -# The parser checks the elements of a policy delegation request against the postfwd set -# of rules and, if necessary, triggers the configured action (action=). Similar to a -# classic firewall, a rule is considered true if every element of the set of rules (or -# one from every element list) applies to the comparison. I.e. the following rule: -# -# client_address=1.1.1.1, 1.1.1.2; client_name==unknown; action=REJECT -# -# triggers a REJECT if the -# -# Client address is equal (1.1.1.1 OR 1.1.1.2) AND the client name 'unknown' -# -# -# Note: -# If an element occurs more than once, an element list is formed: -# -# The following rule set is equivalent to the above: -# -# client_address=1.1.1.1; client_address=1.1.1.2; client_name==unknown; action=REJECT -# -# -# triggers a REJECT if (as above) the -# -# Client address (1.1.1.1 OR 1.1.1.2) AND the client name 'unknown' -# -# --- +postfwd_list_files=" +postfwd.wl-nets +postfwd.wl-hosts +postfwd.wl-user +postfwd.wl-sender +postfwd.bl-nets +postfwd.bl-hosts +postfwd.bl-user +postfwd.bl-sender +postfwd.bl-recipient-exeeds-msg-size-20mb +postfwd.high_rate_ip +postfwd.high_rate_hostname +postfwd.high-rate-number-messages +postfwd.high-rate-number-recipients +" -# Whitelists - -# Whitelist trusted networks -id=WHL_NETS - &&TRUSTED_NETS - action=DUNNO - -# Whitelist trusted hostnames -id=WHL_HOSTS - &&TRUSTED_HOSTS - action=DUNNO - -# Whitelist sasl users -id=WHL_USERS - &&TRUSTED_USERS - action=DUNNO - -# Whitelist senders -id=WHL_SENDERS - &&INCOMING - &&TRUSTED_SENDERS - action=DUNNO - - -# Blacklists - -# Block networks -id=BL_NETS - &&BLOCK_NETS - action=REJECT Network Address \$\$client_address blocked by Mailserver admins. Error: BL_NETS - -# Block hostname -id=BL_HOSTS - &&BLOCK_HOSTS - action=REJECT \$\$client_name blocked by Mailserver admins. Error: BL_HOSTS - -# Block users -id=BL_USERS - &&BLOCK_USERS - action=REJECT User is blocked by Mailserver admins. Error: BL_USERS - -# Blacklist sender -# -# Claim successful delivery and silently discard the message. -# -id=BL_SENDER - &&BLOCK_SENDER - #action=DISCARD - action=REJECT Sender address is blocked by Mailserver admins. Error: BL_SENDER - - -# Rate Limits - -# Throttle unknown clients to 5 recipients per 5 minutes: -id=RATE_UNKNOWN_CLIENT_ADDR - sasl_username =~ /^$/ - client_name==unknown - action=rate(client_address/5/300/450 4.7.1 only 5 recipients per 5 minutes allowed) - -# Block clients (ip-addresses) sending more than 50 messages per minute exceeded. Error:RATE_CLIENT) -id=RATE_CLIENT_ADDR - &&INCOMING - action=rate(\$\$client_address/50/60/421 421 4.7.0 Too many connections from \$\$client_address) - -# Block messages with more than 50 recipients -id=BLOCK_MSG_RCPT - &&INCOMING - &&SASL_AUTH - recipient_count=50 - action=REJECT Too many recipients, please reduce to less than 50 or consider using a mailing list. Error: BLOCK_MSG_RCPT - -# Block users sending more than 50 messages/hour -id=RATE_MSG - &&INCOMING - &&SASL_AUTH - action=rate(\$\$sasl_username/50/3600/450 4.7.1 Number messages per hour exceeded. Error:RATE_MSG) - -# Block users sending more than 250 recipients total/hour -id=RATE_RCPT - &&INCOMING - &&SASL_AUTH - action=rcpt(\$\$sasl_username/250/3600/450 4.7.1 Number recipients per hour exceeded. Error:RATE_RCPT) - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi +if [[ ! -d "$postfwd_src_dir" ]] || [[ ! -f "${postfwd_src_dir}/postfwd.cf" ]] ; then + error "Directory '${postfwd_src_dir}' (or postfwd.cf in it) not found - postfwd configuration NOT installed!" else - echo_skipped + + for _file in $postfwd_list_files ; do + echononl " Install list file '${postfwd_conf_dir}/${_file}'" + if [[ ! -f "${postfwd_src_dir}/${_file}" ]] ; then + echo_failed + error "Source file '${postfwd_src_dir}/${_file}' not found." + elif [[ -f "${postfwd_conf_dir}/${_file}" ]] ; then + echo_skipped + else + install -m 0644 -o root -g root "${postfwd_src_dir}/${_file}" \ + "${postfwd_conf_dir}/${_file}" > "$log_file" 2>&1 + if [[ $? -eq 0 ]] ; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + fi + fi + done + + _file="${postfwd_conf_dir}/postfwd.cf" + if [[ -f "$_file" ]] && ! cmp -s "${postfwd_src_dir}/postfwd.cf" "$_file" ; then + echononl " Backup existing '${_file}'" + cp -a "$_file" "${_file}.${backup_date}" > "$log_file" 2>&1 + if [[ $? -eq 0 ]] ; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + fi + fi + + echononl " Install configuration file '${_file}'" + if [[ -f "$_file" ]] && cmp -s "${postfwd_src_dir}/postfwd.cf" "$_file" ; then + echo_skipped + else + install -m 0644 -o root -g root "${postfwd_src_dir}/postfwd.cf" "$_file" > "$log_file" 2>&1 + if [[ $? -eq 0 ]] ; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + fi + fi + + # - Check configuration: postfwd reports missing list files, undefined + # - macros or invalid lines only as warnings and keeps running. + echononl " Check postfwd configuration" + _postfwd_check="$(postfwd --showconfig --file "$_file" 2>&1 \ + | grep -E '^\[LOG (warning|err|crit)')" + if [[ -z "$_postfwd_check" ]] ; then + echo_ok + else + echo_warning + warn "postfwd reports problems with '${_file}':\n\n${_postfwd_check}" + fi + fi + echononl " Enable Postfix firewall daemon at boot time .." if $systemd_exists ; then systemctl enable postfwd > /dev/null 2> $log_file diff --git a/install_postfwd.sh b/install_postfwd.sh index beec916..62c2a1c 100755 --- a/install_postfwd.sh +++ b/install_postfwd.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash +working_dir="$(dirname "$(realpath "$0")")" + tmp_err_msg=$(mktemp) +log_file="$tmp_err_msg" +backup_date="$(date +%Y-%m-%d-%H%M)" # ------------- # --- Some functions @@ -89,434 +93,166 @@ else fi fi -echononl " Adjust /etc/default/postfwd" -perl -i -n -p -e "s#^(\s*)(STARTUP=.*)#\#\1\2\nSTARTUP=1#" \ - /etc/default/postfwd > $tmp_err_msg 2>&1 -if [[ $? -eq 0 ]] ; then - echo_ok +## - Adjust /etc/default/postfwd +## - +## - set_postfwd_default +## - +## - - VAR is set (uncommented) and has the wanted value -> nothing to do +## - - VAR is set with a different value -> comment out the current +## - setting and add the new setting directly below it +## - - VAR is not set at all -> append the new setting +## - +## - Quotes around the value are ignored when comparing ("1" == 1). +## - +postfwd_default_file="/etc/default/postfwd" + +set_postfwd_default() { + local _var="$1" + local _val="$2" + local _tmp_file + + echononl " ${_var}=${_val}" + + if [[ ! -f "$postfwd_default_file" ]] ; then + touch "$postfwd_default_file" > "$log_file" 2>&1 || { echo_failed ; error "$(cat $log_file)" ; return 1 ; } + fi + + _tmp_file="$(mktemp)" + awk -v var="$_var" -v val="$_val" ' + function unq(s) { + gsub(/^[ \t]+|[ \t]+$/, "", s) + if (s ~ /^".*"$/ || s ~ /^\047.*\047$/) s = substr(s, 2, length(s) - 2) + return s + } + $0 ~ "^[ \t]*" var "=" { + cur = $0 ; sub("^[ \t]*" var "=", "", cur) + if (! done && unq(cur) == unq(val)) { print ; done = 1 ; next } + print "#" $0 + if (! done) { print var "=" val ; done = 1 } + next + } + { print } + END { if (! done) print var "=" val } + ' "$postfwd_default_file" > "$_tmp_file" 2> "$log_file" + + if [[ $? -ne 0 ]] ; then + echo_failed + error "$(cat $log_file)" + elif cmp -s "$_tmp_file" "$postfwd_default_file" ; then + echo_skipped + else + # - cat instead of mv/cp keeps owner and permissions of the original file + cat "$_tmp_file" > "$postfwd_default_file" 2> "$log_file" + if [[ $? -eq 0 ]] ; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + fi + fi + rm -f "$_tmp_file" +} + +echo "" +echo " Adjust ${postfwd_default_file}" +set_postfwd_default STARTUP "1" +set_postfwd_default CONF "/etc/postfix/postfwd.cf" +set_postfwd_default INET "127.0.0.1" +set_postfwd_default PORT "10040" +set_postfwd_default ARGS '"--summary=600 --cache=0"' +echo "" + + +## - Install postfwd configuration and list files shipped with this +## - repository (directory 'files/postfwd'). +## - +## - - postfwd.cf: installed; an existing, differing file is saved +## - as postfwd.cf. first. +## - - list files (whitelists, blacklists, high-rate exceptions): +## - only installed if not yet present - existing lists contain +## - local data and are never overwritten. +## - +postfwd_src_dir="${working_dir}/files/postfwd" +postfwd_conf_dir="/etc/postfix" + +postfwd_list_files=" +postfwd.wl-nets +postfwd.wl-hosts +postfwd.wl-user +postfwd.wl-sender +postfwd.bl-nets +postfwd.bl-hosts +postfwd.bl-user +postfwd.bl-sender +postfwd.bl-recipient-exeeds-msg-size-20mb +postfwd.high_rate_ip +postfwd.high_rate_hostname +postfwd.high-rate-number-messages +postfwd.high-rate-number-recipients +" + +if [[ ! -d "$postfwd_src_dir" ]] || [[ ! -f "${postfwd_src_dir}/postfwd.cf" ]] ; then + error "Directory '${postfwd_src_dir}' (or postfwd.cf in it) not found - postfwd configuration NOT installed!" else - echo_failed - error "$(cat $tmp_err_msg)" + + for _file in $postfwd_list_files ; do + echononl " Install list file '${postfwd_conf_dir}/${_file}'" + if [[ ! -f "${postfwd_src_dir}/${_file}" ]] ; then + echo_failed + error "Source file '${postfwd_src_dir}/${_file}' not found." + elif [[ -f "${postfwd_conf_dir}/${_file}" ]] ; then + echo_skipped + else + install -m 0644 -o root -g root "${postfwd_src_dir}/${_file}" \ + "${postfwd_conf_dir}/${_file}" > "$log_file" 2>&1 + if [[ $? -eq 0 ]] ; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + fi + fi + done + + _file="${postfwd_conf_dir}/postfwd.cf" + if [[ -f "$_file" ]] && ! cmp -s "${postfwd_src_dir}/postfwd.cf" "$_file" ; then + echononl " Backup existing '${_file}'" + cp -a "$_file" "${_file}.${backup_date}" > "$log_file" 2>&1 + if [[ $? -eq 0 ]] ; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + fi + fi + + echononl " Install configuration file '${_file}'" + if [[ -f "$_file" ]] && cmp -s "${postfwd_src_dir}/postfwd.cf" "$_file" ; then + echo_skipped + else + install -m 0644 -o root -g root "${postfwd_src_dir}/postfwd.cf" "$_file" > "$log_file" 2>&1 + if [[ $? -eq 0 ]] ; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + fi + fi + + # - Check configuration: postfwd reports missing list files, undefined + # - macros or invalid lines only as warnings and keeps running. + echononl " Check postfwd configuration" + _postfwd_check="$(postfwd --showconfig --file "$_file" 2>&1 \ + | grep -E '^\[LOG (warning|err|crit)')" + if [[ -z "$_postfwd_check" ]] ; then + echo_ok + else + echo_warning + warn "postfwd reports problems with '${_file}':\n\n${_postfwd_check}" + fi + fi -_file="/etc/postfix/postfwd.wl-nets" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Trusted networks whitelisted by postfwd -# -# Example: -# -# # web0.warenform.de -# #83.223.86.76 -# #2a01:30:0:505:286:96ff:fe4a:6ee -# #2a01:30:0:13:286:96ff:fe4a:6eee -# -# --- - -# give truested networrk adresses here -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.wl-hosts" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Trusted hosts whitelisted by postfwd -# -# This file is called with '=~'. This means perl regexp is possible -# -# Example: -# -# # all hosts of domain 'oopen.de' -# \.oopen\.de$ -# -# # host a.mx.oopen.de -# a.mx.oopen.de -# -# --- - -# give truested hostnames here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.wl-user" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# SASL Users whitelisted by postfwd -# -# Example: -# -# # give trusted sasl_user here -# admin@warenform.de -# -# --- - -# give trusted sasl_user here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.wl-sender" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Trusted senders whitelisted by postfwd -# -# This file is called with '=~'. This means perl regexp is possible -# -# Example: -# -# # all senders of maildomaindomain 'oopen.de' -# @oopen\.de$ -# -# # sender address ckubu@oopen.de -# ckubu@oopen.de -# -# --- - -# give trusted sender addresses here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-nets" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Networks blocked by postfwd -# -# Example: -# -# # web0.warenform.de -# #83.223.86.76 -# #2a01:30:0:505:286:96ff:fe4a:6ee -# #2a01:30:0:13:286:96ff:fe4a:6eee -# -# --- - -# give networks to block here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-hosts" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# hosts blocked by postfwd -# -# This file is called with '=~'. This means perl regexp is possible -# -# Example: -# -# # block all hosts of domain 'oopen.de' -# \.oopen\.de$ -# -# # block host a.mx.oopen.de -# a.mx.oopen.de -# -# --- - -# give hostnames to blocke here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-user" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# SASL Users blocked by postfwd -# -# Example: -# -# # give sasl_user to block here -# ckubu@warenform.de -# --- - -# give sasl_user to block here - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -_file="/etc/postfix/postfwd.bl-sender" -echononl " Create whitelist file '$_file' for postfwd" -if [[ ! -f "$_file" ]]; then - cat << EOF > "$_file" -# --- -# Sender addresses blocked by postfwd -# -# This file is called with '=~'. This means perl regexp is possible -# -# Example: -# -# # all senders of maildomaindomain 'oopen.de' -# @oopen\.de$ -# -# # sender address ckubu@oopen.de -# ckubu@oopen.de -# -# --- - -# annoying spammer domains -@acieu.co.uk - -# annoying spammer addresses -error@mailfrom.com - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -else - echo_skipped -fi - -echononl " Create configuration file '/etc/postfix/postfwd.cf'.." -#if [[ ! -f "/etc/postfix/postfwd.cf" ]]; then - cat << EOF > /etc/postfix/postfwd.cf - -#======= Definitions ============ - -# Match messages with an associated SASL username -&&SASL_AUTH { - sasl_username!~^\$ -} - -# Trusted networks -&&TRUSTED_NETS { - client_address==file:/etc/postfix/postfwd.wl-nets -} - -# Trusted hostnames -# client_name~=.warenform.de$ -&&TRUSTED_HOSTS { - client_name=~file:/etc/postfix/postfwd.wl-hosts -} - -# Trusted users -&&TRUSTED_USERS { - sasl_username==file:/etc/postfix/postfwd.wl-user -} - -# Trusted senders -&&TRUSTED_SENDERS { - sender=~file:/etc/postfix/postfwd.wl-sender -} - -# Blacklist networks -&&BLOCK_NETS { - client_address==file:/etc/postfix/postfwd.bl-nets -} - -# Blacklist hostnames -&&BLOCK_HOSTS { - client_name=~file:/etc/postfix/postfwd.bl-hosts -} - -# Blacklist users -&&BLOCK_USERS { - sasl_username==file:/etc/postfix/postfwd.bl-user -} - -# Blacklist sender adresses -&&BLOCK_SENDER { - # =~ - # using '=~' allows also matching entries for domains (i.e. @acieu.co.uk) - sender=~file:/etc/postfix/postfwd.bl-sender -} - -# Inbound emails only -&&INCOMING { - client_address!=127.0.0.1 -} - - -#======= Rule Sets ============ - -# --- -# -# Processing of the Rule Sets -# -# The parser checks the elements of a policy delegation request against the postfwd set -# of rules and, if necessary, triggers the configured action (action=). Similar to a -# classic firewall, a rule is considered true if every element of the set of rules (or -# one from every element list) applies to the comparison. I.e. the following rule: -# -# client_address=1.1.1.1, 1.1.1.2; client_name==unknown; action=REJECT -# -# triggers a REJECT if the -# -# Client address is equal (1.1.1.1 OR 1.1.1.2) AND the client name 'unknown' -# -# -# Note: -# If an element occurs more than once, an element list is formed: -# -# The following rule set is equivalent to the above: -# -# client_address=1.1.1.1; client_address=1.1.1.2; client_name==unknown; action=REJECT -# -# -# triggers a REJECT if (as above) the -# -# Client address (1.1.1.1 OR 1.1.1.2) AND the client name 'unknown' -# -# --- - -# Whitelists - -# Whitelist trusted networks -id=WHL_NETS - &&TRUSTED_NETS - action=DUNNO - -# Whitelist trusted hostnames -id=WHL_HOSTS - &&TRUSTED_HOSTS - action=DUNNO - -# Whitelist sasl users -id=WHL_USERS - &&TRUSTED_USERS - action=DUNNO - -# Whitelist senders -id=WHL_SENDERS - &&INCOMING - &&TRUSTED_SENDERS - action=DUNNO - - -# Blacklists - -# Block networks -id=BL_NETS - &&BLOCK_NETS - action=REJECT Network Address \$\$client_address blocked by Mailserver admins. Error: BL_NETS - -# Block hostname -id=BL_HOSTS - &&BLOCK_HOSTS - action=REJECT \$\$client_name blocked by Mailserver admins. Error: BL_HOSTS - -# Block users -id=BL_USERS - &&BLOCK_USERS - action=REJECT User is blocked by Mailserver admins. Error: BL_USERS - -# Blacklist sender -# -# Claim successful delivery and silently discard the message. -# -id=BL_SENDER - &&BLOCK_SENDER - #action=DISCARD - action=REJECT Sender address is blocked by Mailserver admins. Error: BL_SENDER - - -# Rate Limits - -# Throttle unknown clients to 5 recipients per 5 minutes: -id=RATE_UNKNOWN_CLIENT_ADDR - sasl_username =~ /^$/ - client_name==unknown - action=rate(client_address/5/300/450 4.7.1 only 5 recipients per 5 minutes allowed) - -# Block clients (ip-addresses) sending more than 50 messages per minute exceeded. Error:RATE_CLIENT) -id=RATE_CLIENT_ADDR - &&INCOMING - action=rate(\$\$client_address/50/60/421 421 4.7.0 Too many connections from \$\$client_address) - -# Block messages with more than 50 recipients -id=BLOCK_MSG_RCPT - &&INCOMING - &&SASL_AUTH - recipient_count=50 - action=REJECT Too many recipients, please reduce to less than 50 or consider using a mailing list. Error: BLOCK_MSG_RCPT - -# Block users sending more than 50 messages/hour -id=RATE_MSG - &&INCOMING - &&SASL_AUTH - action=rate(\$\$sasl_username/50/3600/450 4.7.1 Number messages per hour exceeded. Error:RATE_MSG) - -# Block users sending more than 250 recipients total/hour -id=RATE_RCPT - &&INCOMING - &&SASL_AUTH - action=rcpt(\$\$sasl_username/250/3600/450 4.7.1 Number recipients per hour exceeded. Error:RATE_RCPT) - -EOF - if [[ $? -eq 0 ]] ; then - echo_ok - else - echo_failed - fi -#else -# echo_skipped -#fi echononl " Restart Postfix firewall daemon 'postfwd'.." if $systemd_exists ; then diff --git a/update_postfwd_defaults.sh b/update_postfwd_defaults.sh new file mode 100755 index 0000000..765aa94 --- /dev/null +++ b/update_postfwd_defaults.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +# +# Update /etc/default/postfwd (idempotent) and reload postfwd. +# +# For every variable below: +# - already set to the wanted value -> nothing to do +# - set to a different value -> comment out the current setting +# and add the new one directly below +# - not set at all -> append the new setting +# +# Quotes around values are ignored when comparing (PORT="10040" == PORT=10040). +# +# At the end: +# - file changed -> 'systemctl restart postfwd' +# (ARGS, INET and PORT are start parameters of the +# daemon - a reload does NOT apply them) +# - file unchanged -> 'systemctl reload postfwd' +# (rereads postfwd.cf and the file: lists) +# + +postfwd_default_file="/etc/default/postfwd" + +# - Wanted settings: "VAR|VALUE" (VALUE written exactly as it should appear) +wanted_settings=( + 'STARTUP|1' + 'CONF|/etc/postfix/postfwd.cf' + 'INET|127.0.0.1' + 'PORT|10040' + 'ARGS|"--summary=600 --cache=0"' +) + + +# ------------- +# --- Some functions +# ------------- + +log_file="$(mktemp)" + +clean_up() { + rm -f "$log_file" + exit $1 +} + +echononl() { + echo -e -n "$*" +} +echo_ok() { + echo -e "\033[60G[ \033[32mok\033[m ]" +} +echo_skipped() { + echo -e "\033[60G[ \033[33m\033[1mskipped\033[m ]" +} +echo_failed() { + echo -e "\033[60G[ \033[1;31mfailed\033[m ]" +} +error() { + echo "" + echo -e "\t[ \033[31m\033[1mError\033[m ]: $*" + echo "" +} +fatal() { + error "$*" + echo -e "\t\033[31m\033[1mScript will be interrupted\033[m" + echo "" + clean_up 1 +} + +# - set_postfwd_default +# - +# - returns 0 if the file was changed, 1 if nothing changed, 2 on error +# - +set_postfwd_default() { + local _var="$1" + local _val="$2" + local _tmp_file + local _rval + + echononl " ${_var}=${_val}" + + _tmp_file="$(mktemp)" + awk -v var="$_var" -v val="$_val" ' + function unq(s) { + gsub(/^[ \t]+|[ \t]+$/, "", s) + if (s ~ /^".*"$/ || s ~ /^\047.*\047$/) s = substr(s, 2, length(s) - 2) + return s + } + $0 ~ "^[ \t]*" var "=" { + cur = $0 ; sub("^[ \t]*" var "=", "", cur) + if (! done && unq(cur) == unq(val)) { print ; done = 1 ; next } + print "#" $0 + if (! done) { print var "=" val ; done = 1 } + next + } + { print } + END { if (! done) print var "=" val } + ' "$postfwd_default_file" > "$_tmp_file" 2> "$log_file" + + if [[ $? -ne 0 ]] ; then + echo_failed + error "$(cat "$log_file")" + _rval=2 + elif cmp -s "$_tmp_file" "$postfwd_default_file" ; then + echo_skipped + _rval=1 + else + # - cat instead of mv/cp keeps owner and permissions of the original file + if cat "$_tmp_file" > "$postfwd_default_file" 2> "$log_file" ; then + echo_ok + _rval=0 + else + echo_failed + error "$(cat "$log_file")" + _rval=2 + fi + fi + + rm -f "$_tmp_file" + return $_rval +} + + +# ------------- +# --- Main +# ------------- + +[[ "$(id -u)" -eq 0 ]] || fatal "This script must be run as root." + +command -v postfwd > /dev/null 2>&1 || fatal "postfwd is not installed." + +if [[ ! -f "$postfwd_default_file" ]] ; then + touch "$postfwd_default_file" 2> "$log_file" || fatal "$(cat "$log_file")" +fi + +echo "" +echo "Update ${postfwd_default_file}" +echo "" + +file_changed=false +errors=false + +for _setting in "${wanted_settings[@]}" ; do + set_postfwd_default "${_setting%%|*}" "${_setting#*|}" + case $? in + 0) file_changed=true ;; + 2) errors=true ;; + esac +done + +echo "" + +if $errors ; then + fatal "Errors while updating '${postfwd_default_file}' - postfwd NOT reloaded." +fi + +if $file_changed ; then + echononl " Restart postfwd (start parameters changed)" + _action="restart" +else + echononl " Reload postfwd" + _action="reload" +fi + +if systemctl $_action postfwd > "$log_file" 2>&1 ; then + echo_ok +else + echo_failed + error "$(cat "$log_file")" + clean_up 1 +fi + +echo "" +clean_up 0