85.1.16. pepsi-stage-anti-spam

gate a message behind a GNU Taler payment

Handbuchabschnitt:

1

85.1.16.1.1. Name

pepsi-stage-anti-spam - die Pay-to-Send-Anti-Spam-Stage der Pepsi-Pipeline.

85.1.16.1.2. Übersicht

pepsi-stage-anti-spam [GLOBAL-OPTIONS] worker

85.1.16.1.3. Beschreibung

pepsi-stage-anti-spam ist ein Stage-Programm, das von pepsi-dispatch(1) ausgeführt wird. Es lädt einen pepsi.workqueue-Datensatz (und weigert sich zu handeln, sofern sein status nicht running ist), liest seinen [stage-<stage>]-Abschnitt und entscheidet anhand des state-JSON der Nachricht, was zu tun ist, und verlangt für eine andernfalls nicht klassifizierte Nachricht eine GNU-Taler-Zahlung, bevor sie die Pipeline weiter durchlaufen darf.

Die Entscheidung ist:

  • die Nachricht hat den leeren Umschlagabsender (ein Bounce oder eine automatische RFC-3834-Antwort) — sie wird nie zur Zahlung zurückgehalten und nie selbst gebounct. Wenn der Herkunftsnachweis aktiviert ist (ein [pepsi-origin]-Abschnitt, siehe HERKUNFTSNACHWEIS unten; standardmäßig an), wird verifiziert, dass der Bounce einer ist, den wir provoziert haben, und ein verifizierter Bounce wird zu NEXT_STAGE weitergeleitet. Jeder Bounce, den wir nicht verifizieren können — einer ohne gültigen Pepsi-Origin-Nachweis oder jeder Bounce, wenn der Herkunftsnachweis nicht konfiguriert ist — wird zu BOUNCE_TARGET_STAGE geroutet, wenn eine verdrahtet ist (z. B. eine Quarantäne- oder Discard-Stage), andernfalls unverändert zu NEXT_STAGE weitergeleitet (ein Bounce wird nie stillschweigend verworfen).

  • state.spam = true — die Nachricht wird verworfen (der Datensatz wird gelöscht).

  • state.spam = false oder state.paid = true — die Nachricht wird zu NEXT_STAGE weitergeleitet. Eine explizite Klassifizierung (von einer anderen Komponente gesetzt) übersteuert den Zahlungsablauf.

  • state.paid = false ist vorhanden — eine Bestellung wurde früher erstellt und die Nachricht wurde erneut geweckt (durch den pepsi-resume-Zahlungs-Webhook, der das /resume von pepsi-httpd aufruft, oder durch pepsi-dispatch(1), als die Frist verstrich). Die Stage fragt das Händler-Backend, ob die Bestellung nun bezahlt ist:

    • bezahlt — weiterleiten zu NEXT_STAGE;

    • unbezahlt und über die gespeicherte Frist hinaus — die Nachricht ablehnen: umleiten zum BOUNCE_STAGE der Stage (verdrahten Sie es mit pepsi-stage-bounce(1) für eine Ablehnungs-DSN oder pepsi-stage-discard(1), um sie stillschweigend zu verwerfen); wenn kein BOUNCE_STAGE konfiguriert ist, wird der Datensatz einfach gelöscht;

    • unbezahlt, aber noch vor der Frist — die Nachricht wird bis zur Frist erneut pausiert und eine Warnung protokolliert. Dies sollte im Normalbetrieb nicht vorkommen (eine pausierte Nachricht wacht nur bei Zahlung oder zur Frist auf). Sie bleibt paused, nicht pending: Ein Datensatz, den der Dispatcher sofort wieder beanspruchen könnte, liefe in einer Endlosschleife gegen die frühe Wiederaufnahme, die ihn geweckt hat.

  • kein paid-Feld (erste Begegnung) — die Stage erstellt eine v1-Händler-Bestellung, bepreist durch ORDER_CHOICES (order_id ist das externe Token der Nachricht, sodass das {{order_id}} des pepsi-resume-Webhooks zu dieser Nachricht zurück auflöst), und pausiert die Nachricht dann — bis zum Kontrollpunkt DELAY_DSN_AFTER, wenn einer konfiguriert ist und vor die Frist fällt, andernfalls bis PAYMENT_DEADLINE —, wobei sie paid: false und die absolute Frist in state festhält.

Die Frist wird in state.pay_deadline (Epoch-Sekunden) gespeichert, weil pepsi-dispatch(1) die timeout-Spalte des Datensatzes leert, wenn es eine pausierte Nachricht erneut einreiht, sodass sie sonst beim erneuten Lauf nicht wiederhergestellt werden könnte.

Nach dem Pausieren der Nachricht bei dieser ersten Begegnung erzeugt die Stage außerdem eine Zahlungsaufforderungs-Auto-Antwort an den Absender (wenn BLOCK_RESPONSE_STAGE konfiguriert ist). Die Antwort erklärt, dass der Empfänger von unbekannten Absendern eine Zahlung verlangt, bevor ihre Mail angenommen wird, und trägt die taler://pay/…-URI — sowohl als Link als auch als QR-Code (ein eingebetteter image/png-Teil, per cid: referenziert) — zusammen mit den akzeptablen Zahlungsoptionen (den ORDER_CHOICES, wobei jede Wahl, die eine Token-Familie betrifft, weggelassen wird) und einem Verweis auf https://wallet.taler.net/ für Absender ohne Wallet. Der Absender wird mit dem Anzeigenamen aus dem ursprünglichen From:-Header angesprochen, wenn einer verfügbar ist. Der menschenlesbare Teil ist ein text/html-Nachrichtentext, gerendert aus der Mustache-Vorlage payment-request.<lang>.body unter [pepsi] TEMPLATE_DIR. Die Sprache(n) werden aus state.language entnommen, wie von pepsi-stage-detect-language (früher in der Pipeline ausgeführt) gesetzt: Die Antwort rendert einen Abschnitt pro erkannter Sprache, die eine Vorlage hat, in der erkannten Reihenfolge aneinandergehängt, jeweils mit ihren auf diese Sprache lokalisierten Zahlungsoptionen. Wenn die Nachricht keine erkannte Sprache trägt — oder keine der erkannten Sprachen eine Vorlage hat — wird stattdessen die PAYMENT_MESSAGE_DEFAULT_LANGUAGE-Vorlage verwendet. Die Antwort verwendet den Null-Absender <> (sodass sie nie selbst gebounct oder abgeriegelt wird) und wird bei BLOCK_RESPONSE_STAGE (normalerweise eine Signier-/Relay-Kette) injiziert, sodass sie DKIM-signiert und wie jede andere ausgehende Mail zugestellt wird.

Die Antwort geht an einen Umschlagabsender, den niemand verifiziert hat, daher wird sie in zwei Fällen zurückgehalten — die Nachricht wird trotzdem zur Zahlung festgehalten. Erstens, wenn RFC 3834 besagt, dass die Nachricht nicht automatisch beantwortet werden darf (Auto-Submitted mit einem anderen Wert als no, Precedence: bulk|list|junk, X-Auto-Response-Suppress, ein beliebiges List-*-Feld, ein Dienstabsender, ein multipart/report). Zweitens, wenn demselben Absender innerhalb von BLOCK_RESPONSE_SUPPRESS (Standard eine Stunde) bereits eine Zahlungsaufforderung für dasselbe geschützte Postfach (den ersten Umschlagempfänger) geschickt wurde: Andernfalls würde eine Flut von Nachrichten mit einem gefälschten Absender zu derselben Flut von Aufforderungen an diese Adresse. Das Zeitfenster wird in einer Anweisung beansprucht und festgehalten (die Funktion payment_request_should_send über pepsi.payment_request_reply), nachdem die Nachricht pausiert und die Antwort gerendert wurde, sodass zwei Worker mit zwei Nachrichten desselben Absenders eine Aufforderung senden und ein Fehler vor diesem Punkt das Zeitfenster nicht verbraucht. Ein Absender, dessen zweite Nachricht in das Zeitfenster fällt, erhält dafür keine Aufforderung; diese Nachricht kann trotzdem bezahlt werden (ihre Bestellung existiert) und erzeugt andernfalls wie jede unbezahlte Nachricht bei Ablauf der Frist einen Bounce. 0 s beantwortet jede Nachricht.

Die Auto-Antwort trägt außerdem zwei maschinenlesbare Header, damit ein sendendes Pepsi die Forderung automatisch begleichen kann (siehe pepsi-stage-auto-pay(1)): einen Taler:-Header, der die taler://pay/…-URI enthält, und den Pepsi-Origin:-Header der ursprünglichen Nachricht, wörtlich kopiert (wenn die eingehende Nachricht einen trug). Das Zurückspiegeln des Herkunftsnachweises lässt den Ursprungsstandort verifizieren, dass die zurückkehrende Forderung für Mail ist, die er tatsächlich gesendet hat, und messen, was er pro ursprünglicher Nachricht ausgibt (alle Forderungen zu einem Original — z. B. eine Mailinglisten-Auffächerung — teilen sich eine Pepsi-Origin-Nonce). Der Header wird unverändert kopiert, nicht neu signiert: nur der Urheber kann ihn verifizieren (sein HMAC ist mit dem Secret des Urhebers verschlüsselt).

Die Antwort wird vor dem Pausieren der Nachricht gerendert. Kann keine Antwort erzeugt werden — insbesondere wenn die PAYMENT_MESSAGE_DEFAULT_LANGUAGE-Vorlage fehlt —, wird die Nachricht ohne eine solche zur Zahlung zurückgehalten, mit einer Warnung im Log; im Zeitfenster wird nichts beansprucht, sodass eine spätere Nachricht desselben Absenders beantwortet wird, sobald die Vorlage wieder da ist. pepsi-setup validiert vorab, dass diese Vorlage existiert. Das Injizieren der bereits gerenderten Antwort nach dem Pausieren erfolgt nach bestem Bemühen: Ein Datenbankfehler dort wird protokolliert, das dafür beanspruchte Zeitfenster wird zurückgegeben, und die Nachricht bleibt zur Zahlung pausiert.

85.1.16.1.3.1. Händler-Backend nicht verfügbar

Ein Händler-Backend, das nicht erreichbar ist oder eine Bestell- oder Statusanfrage mit einem Fehler beantwortet, ist das Problem des Hosts und nicht des Absenders. Die Nachricht bleibt zurückgehalten (paused, der Grund in state.last_error), und der Händler wird alle fünf Minuten erneut gefragt, nie über PAYMENT_DEADLINE hinaus; die Frist wird beim ersten Versuch festgelegt, sodass ein Ausfall sie nicht verlängert. Kann der Händler nach Ablauf der Frist immer noch nicht gefragt werden, ist die Stage fail-open: Die Nachricht wird ungeprüft an NEXT_STAGE zugestellt, mit einer Warnung im Log. Eine Zahlungsschranke, die nicht feststellen kann, ob bezahlt wurde, darf einen Händlerausfall nicht in verlorene Mail verwandeln. Eine Statusanfrage zu einer Bestellung, die der Händler nicht kennt (404), ist eine Antwort, kein Ausfall: Die Bestellung wird als unbezahlt behandelt.

Die Stage erfordert den gemeinsamen [pepsi-payments]-Abschnitt (das Händler-Backend und das Zugriffstoken; siehe pepsi.conf(5)) und, damit der Zahlungs-Webhook pausierte Nachrichten freigeben kann, das [pepsi-httpd]-RESUME_AUTHORIZATION_TOKEN (siehe pepsi-httpd(1)).

85.1.16.1.3.2. Mail, die die Schranke irrtümlich festhält

Vorsicht

Nur der Null-Umschlagabsender umgeht die Schranke. Alles andere, was nicht durch state.spam = false freigegeben wird — normalerweise gesetzt von pepsi-stage-check-whitelist(1), vor dieser Stage platziert —, wird festgehalten, auch Mail, für die niemand zahlen kann oder wird:

  • Pepsis eigene Secure-Link-Mail. Die Link-Benachrichtigung und die PIN-Mail von pepsi-stage-secure-link(1) tragen den ursprünglichen Absender als Umschlagabsender (bewusst, damit ein Fehler ihn erreicht), nicht <>. Tritt eine davon als eingehende Mail an ein geschütztes Postfach erneut in diese Installation ein — eine an einen lokalen Absender zurückgeschickte PIN, ein Link an einen lokalen Empfänger —, wird sie bis PAYMENT_DEADLINE festgehalten und dann zurückgewiesen, und die Funktion schlägt stillschweigend fehl. Eine im Portal verfasste Antwort wird unsigniert auf dem eingehenden Pfad injiziert, mit dem externen Empfänger als Absender, und wird wie jede andere Mail von dieser Adresse festgehalten. Lesebestätigungen verwenden den Null-Absender und passieren.

  • Beiträge an Mailinglisten. Ein Beitrag trägt das From: seines Autors, sodass eine Whitelist der Korrespondenten ihn nicht abdeckt, und seine List-*-Felder unterdrücken die Zahlungsaufforderung, sodass nicht einmal jemand gefragt wird: Jeder Beitrag an einen geschützten Abonnenten wird festgehalten und dann bei Ablauf der Frist zurückgewiesen.

Abwesenheitsnotizen (pepsi-stage-vacation(1)) und die eigenen Zahlungsaufforderungen dieser Stage verwenden den Null-Absender und werden nie zur Zahlung festgehalten; ist BOUNCE_TARGET_STAGE verdrahtet, unterliegen sie wie jede andere Nachricht mit Null-Absender der unten beschriebenen Prüfung des Herkunftsnachweises.

Die Abhilfe besteht darin, diese Absender in eine Gruppe aufzunehmen, die WHITELIST_NAME der Prüf-Stage benennt. Behalten Sie für Mail, die Ihre eigenen Domains erzeugen, die standardmäßige DKIM-Anforderung bei: Ein ausgerichtetes DKIM-Pass für example.com kann nur von Ihrer eigenen Signier-Stage stammen, sodass das Muster nicht durch ein gefälschtes From: erfüllt werden kann:

pepsi-whitelist add correspondents '^[^@]+@example\.com$'

(wiederholen Sie dies für jede bediente Domain und für die Domain von [pepsi-secure-link] NOTIFY_FROM, falls diese anderswo liegt). Nehmen Sie für eine Mailingliste deren Kennung in die Whitelist auf und benennen Sie den ARC-Versiegler, der für sie bürgen muss:

pepsi-whitelist add-list --sealer lists.example.org \
    correspondents users.lists.example.org

Siehe pepsi-whitelist(1).

85.1.16.1.4. Konfiguration

Die Optionen liegen im eigenen [stage-<name>]-Abschnitt der Stage (PROGRAM = pepsi-stage-anti-spam): das erforderliche NEXT_STAGE, BOUNCE_STAGE, BOUNCE_TARGET_STAGE (siehe HERKUNFTSNACHWEIS unten), die Bestellparameter (die erforderlichen ORDER_CHOICES sowie PAYMENT_DEADLINE, DELAY_DSN_AFTER, SUMMARY, FULFILLMENT_MESSAGE) und die Auto-Antwort-Parameter (BLOCK_RESPONSE_STAGE, BLOCK_RESPONSE_FROM, BLOCK_RESPONSE_SUBJECT, BLOCK_RESPONSE_SUPPRESS, PAYMENT_MESSAGE_DEFAULT_LANGUAGE). Die Stage erfordert außerdem den gemeinsamen [pepsi-payments]-Abschnitt; der Zahlungs-Webhook, der eine bezahlte Nachricht vor ihrer Frist freigibt, benötigt das [pepsi-httpd]-RESUME_AUTHORIZATION_TOKEN. Alle sind in pepsi.conf(5) dokumentiert.

85.1.16.1.5. Herkunftsnachweis

Jeden Null-Absender-Bounce bedingungslos weiterzuleiten ist nur deshalb sicher, weil ein Bounce nie selbst gebounct wird — aber es lässt einen Angreifer Backscatter (gefälschte Bounces) einschleusen, das Pepsi dann an einen gefälschten „ursprünglichen Absender“ zurückleitet. Um solche Fälschungen abzulehnen, ist der gemeinsame [pepsi-origin]-Abschnitt (pepsi.conf(5)) standardmäßig aktiviert — pepsi-setup(1) erzeugt beim ersten Lauf ein zufälliges Secret, wenn keines konfiguriert ist. Beide Relay-Stages (pepsi-stage-relay-to-internet(1) und pepsi-stage-relay-to-smarthost(1)) stempeln dann jede ausgehende Nachricht mit einem Pepsi-Origin-Header — einem HMAC über das Ursprungs-Absenderkonto, eine frische 128-Bit-Nonce, einen Zeitstempel und unser HOSTNAME — und halten die Nonce für zwei Wochen in pepsi.origin_nonce fest.

Ein echter Bounce bettet die ursprüngliche Nachricht (zumindest ihre Header und damit den Pepsi-Origin-Header) in seinen DSN-Nachrichtentext ein. Diese Stage durchsucht den gesamten zurückkehrenden Bounce nach diesem Header und vertraut dem Bounce nur, wenn beide Prüfungen bestehen, in dieser Reihenfolge:

  1. der HMAC des Headers verifiziert gegen unser Secret und sein host passt zu unserem HOSTNAME (eine schnelle, rein kryptografische Prüfung); und

  2. erst dann — die Nonce ist noch (und unabgelaufen) in pepsi.origin_nonce vorhanden.

Ein Bounce, den wir nicht verifizieren können — einer, der keinen solchen Header trägt, den HMAC nicht besteht oder dessen Nonce nicht mehr verfolgt wird, und überhaupt jeder Bounce, wenn der Herkunftsnachweis abgeschaltet ist ([pepsi-origin] ENABLED = no; keiner kann dann als unserer bewiesen werden) — wird zu BOUNCE_TARGET_STAGE geroutet, wenn eine verdrahtet ist, andernfalls unverändert zu NEXT_STAGE weitergeleitet (nie stillschweigend verworfen).

BOUNCE_TARGET_STAGE sollte normalerweise auf ein Quarantäne-Postfach oder eine Prüf-Stage zeigen, nicht auf eine harte pepsi-stage-discard(1)-Senke: Standard-Unzustellbarkeitsberichte großer MTAs (Postfix, Exim, Gmail, Exchange) geben die ursprünglichen Header zurück und verifizieren daher, aber ein echter Rest legitimer Bounces — Spam-Ablehnungshinweise, die wenig vom Original zitieren, Header-entfernende Gateways, minimale/veraltete Bouncer und Bounces, die nach dem zweiwöchigen Nonce-Fenster eintreffen — kann nicht verifiziert werden, und sie rundheraus zu verwerfen verliert Unzustellbarkeitsberichte, die Ihre eigenen Benutzer benötigen.

85.1.16.1.6. Dateien

<TEMPLATE_DIR>/payment-request.<lang>.body

Die Mustache-Vorlage für den Nachrichtentext der Zahlungsaufforderungs-Antwort (TEMPLATE_DIR ist die [pepsi]-Option, Standardwert ${DATADIR}/templates, also /usr/share/pepsi/templates bei einer Installation mit --prefix=/usr). Die Vorlage für PAYMENT_MESSAGE_DEFAULT_LANGUAGE (Standardwert payment-request.en.body) ist erforderlich; fügen Sie die Sprachen, die pepsi-stage-detect-language erkennen könnte, daneben hinzu. Der Vorlage werden die Variablen recipient_name / has_name, protected_mailbox, original_subject, pay_uri / pay_link, qr_cid, wallet_url, has_options und die payment_options-Liste übergeben (jeweils amount plus has_description und description, auf die Sprache dieser Vorlage lokalisiert).

85.1.16.1.7. State

Eingaben: state.spam / state.paid (Booleans, optional) klassifizieren die Nachricht; state.pay_deadline (Epoch-Sekunden) ist die Frist, die diese Stage geschrieben hat; state.dsn trägt das notify/orcpt des Empfängers (in eine Ablehnung gespiegelt).

Ausgaben: Bei der ersten Begegnung führt die Stage {"paid": false, "pay_deadline": <epoch>} in state zusammen und pausiert. Am DELAY_DSN_AFTER-Kontrollpunkt fügt sie state.delay_sent = true hinzu, sobald eine Delay-DSN gesendet wurde (sodass sie höchstens einmal feuert), und pausiert erneut. Bei einer Ablehnung leitet sie mit einem state.bounce-Objekt (kind = permanent) zu BOUNCE_STAGE um, für pepsi-stage-bounce(1). Das State-Layout wird in pepsi.state(7) beschrieben.

Übergänge (getrieben von state.spam/state.paid, PAYMENT_DEADLINE und dem Status der Händler-Bestellung):

  • gewhitelistet (state.spam = false) oder bereits bezahlt (state.paid = true) → weiterschalten zu NEXT_STAGE;

  • explizites state.spam = true → abschließen (der Datensatz wird gelöscht);

  • erste Begegnung → eine Taler-Bestellung erstellen, die Zahlungsaufforderung bei BLOCK_RESPONSE_STAGE injizieren (sofern nicht unterdrückt, siehe oben) und pausieren (bis zum Kontrollpunkt DELAY_DSN_AFTER, wenn einer konfiguriert ist und davor fällt, sonst bis PAYMENT_DEADLINE);

  • geweckt (Resume-Webhook oder Frist) und nun bezahlt → weiterschalten zu NEXT_STAGE;

  • geweckt, noch unbezahlt, Frist verstrichen → umleiten zu BOUNCE_STAGE (oder abschließen, wenn kein BOUNCE_STAGE konfiguriert ist);

  • Händler nicht verfügbar (siehe oben) → vor der Frist erneut pausieren, danach zu NEXT_STAGE weiterschalten;

  • geweckt, noch unbezahlt, Frist noch nicht erreicht → mit konfiguriertem DELAY_DSN_AFTER ist dies der Delay-Kontrollpunkt: dem Absender eine einmalige verzögerte DSN senden (RFC 3461, nur wenn er NOTIFY=DELAY angefordert hat), ohne die Nachricht freizugeben, dann bis PAYMENT_DEADLINE erneut pausieren; ohne DELAY_DSN_AFTER ist ein frühes Aufwachen anomal, und die Nachricht wird bis zur Frist erneut pausiert (paused).

85.1.16.1.8. Befehle

worker

Läuft als persistenter pepsi-dispatch(1)-Worker, der Nachrichten-IDs von der Standardeingabe liest.

85.1.16.1.9. Globale Optionen

Diese globalen Optionen stehen vor dem Unterbefehl.

-c FILE, –config FILE

Liest die Konfiguration aus FILE, statt die Standardorte zu durchsuchen.

-L LOGLEVEL, –log LOGLEVEL

Setzt die Log-Ausführlichkeit (Standardwert info).

-v, –verbose

Zeigt Log-Meldungen aus allen Quellen.

-h, –help; -V, –version

Gibt eine Verwendungsübersicht / die Version aus und beendet sich.

85.1.16.1.10. Exit-Status

0

Die Nachricht wurde verarbeitet (verworfen, weitergeleitet, zur Zahlung pausiert oder zur Ablehnung umgeleitet).

1

Ein Fehler ist aufgetreten (Nachricht nicht gefunden oder nicht running, oder eine adressbezogene Einstellung, die den Abschnitt der Stage kaputt macht). Der Grund wird in das Log geschrieben.

Ein Fehler des Hosts (die Datenbank, eine Vorlage oder ein Helfer, die sich nicht verwenden lassen) wird nicht als Fehlschlag gemeldet: Die Nachricht wird pausiert und erneut versucht, wie unter Stage-Fehler in pepsi-dispatch(1) beschrieben. Ein Abschnitt, der sich nicht parsen lässt, lässt den Worker den Start verweigern (Status 78), statt eine Nachricht nach der anderen fehlschlagen zu lassen. Ein fehlendes [pepsi-payments]-Backend ist ein solcher Abschnitt.

85.1.16.1.11. Beispiele

Nachricht 42 verarbeiten (sie muss running sein):

echo 42 | pepsi-stage-anti-spam -c /etc/pepsi/pepsi.conf worker

Eine Stage, die ein KUDOS berechnet und unbezahlte Mail nach zwei Tagen ablehnt:

[stage-anti-spam]
PROGRAM = pepsi-stage-anti-spam
NEXT_STAGE = srs
BOUNCE_STAGE = bounce
BLOCK_RESPONSE_STAGE = dkim-sign
PAYMENT_DEADLINE = 48 h
ORDER_CHOICES = [{"amount":"KUDOS:1"}]

85.1.16.1.12. Siehe auch

pepsi-config(1), pepsi-stage-bounce(1), pepsi-stage-discard(1), pepsi-httpd(1), pepsi-dispatch(1), pepsi-setup(1), pepsi.conf(5), pepsi.state(7)

85.1.16.1.13. Fehler

Melden Sie Fehler an den Pepsi-Issue-Tracker.