From 2fd606a3aaa54eb9d6ea668674e20a20f937a0b5 Mon Sep 17 00:00:00 2001 From: Clemens Creutzburg Date: Fri, 21 Aug 2026 21:13:27 +0200 Subject: [PATCH] Diagnose fuer den PayPal-Postfachzugang Vor dem ersten Abruf soll klar sein, ob der Host das Postfach ueberhaupt erreicht - und auf welchem Weg. Das Skript meldet PHP-Version, ob die imap-Erweiterung geladen ist, ob die PAYPAL_*-Einstellungen gesetzt sind, und oeffnet das Postfach testweise ueber eine reine TLS-Verbindung. Damit ist auch ohne die Erweiterung belegbar, dass ein Abruf moeglich waere - seit PHP 8.4 ist imap kein Bestandteil von PHP mehr und laesst sich auf einem Webhosting nicht einfach nachruesten. Bewusst nur lesend: EXAMINE statt SELECT, kein Flag wird gesetzt. Das Passwort wird nie ausgegeben, nur seine Laenge als Tippfehler-Probe. Co-Authored-By: Claude Opus 5 --- docs/deployment.md | 16 +++ scripts/check-imap-support.php | 176 +++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 scripts/check-imap-support.php diff --git a/docs/deployment.md b/docs/deployment.md index 6d17e45..b5ef748 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -236,6 +236,22 @@ dieselbe Mailbox zustellen** (Catch-All), damit die pro Mandant generierten Adressen `zahlungen+@…` ankommen. IMAP-Zugangsdaten in `env.local.php` eintragen und die `imap`-Extension auf dem Host prüfen. +### Erreichbarkeit prüfen + +`scripts/check-imap-support.php` beantwortet in einem Lauf, ob dieser Host +das Postfach erreichen kann: PHP-Version, ob die `imap`-Erweiterung +geladen ist, ob die `PAYPAL_*`-Einstellungen gesetzt sind, und ob sich das +Postfach **auch ohne die Erweiterung** über eine reine TLS-Verbindung +öffnen lässt. Der Test ist nur lesend (`EXAMINE`, keine Flags), das +Passwort wird nie ausgegeben. + +Fehlt die Erweiterung: In Plesk unter *PHP-Einstellungen* prüfen, ob +`imap` angeboten wird — nachinstallieren kann sie nur der Anbieter +(`plesk-php8x-imap`). Seit **PHP 8.4** ist `imap` kein Bestandteil von PHP +mehr (nach PECL ausgelagert); auf 8.4/8.5 steht sie deshalb oft gar nicht +mehr zur Wahl, dann hilft ein PHP-Handler auf 8.3 — oder ein Abruf über +die TLS-Verbindung, die das Diagnoseskript bereits nachweist. + ### Erster Testlauf `scripts/fetch-paypal-payments.php --dry-run` fasst nichts an: es meldet diff --git a/scripts/check-imap-support.php b/scripts/check-imap-support.php new file mode 100644 index 0000000..53e811c --- /dev/null +++ b/scripts/check-imap-support.php @@ -0,0 +1,176 @@ +=')) { + echo "Hinweis: Ab PHP 8.4 ist imap kein Bestandteil von PHP mehr. Entweder den\n" + . "PHP-Handler dieses Vhosts auf 8.3 stellen oder den Abruf ueber eine reine\n" + . "TLS-Verbindung laufen lassen (siehe Test unten).\n"; +} + +echo "\n=== Einstellungen (env.local.php) ===\n"; +$host = app_env('PAYPAL_IMAP_HOST'); +$user = app_env('PAYPAL_IMAP_USER'); +$pass = app_env('PAYPAL_IMAP_PASS'); +$port = (int) app_env('PAYPAL_IMAP_PORT', '993'); +$flags = (string) app_env('PAYPAL_IMAP_FLAGS', '/imap/ssl'); +$mailbox = (string) app_env('PAYPAL_IMAP_MAILBOX', 'INBOX'); + +imap_check_line('PAYPAL_INBOX_BASE', (string) (app_env('PAYPAL_INBOX_BASE') ?? '(nicht gesetzt)')); +imap_check_line('PAYPAL_IMAP_HOST', (string) ($host ?? '(nicht gesetzt)')); +imap_check_line('PAYPAL_IMAP_PORT', (string) $port); +imap_check_line('PAYPAL_IMAP_FLAGS', $flags); +imap_check_line('PAYPAL_IMAP_USER', (string) ($user ?? '(nicht gesetzt)')); +// Das Passwort selbst gehoert nicht in ein Protokoll, das per Mail +// verschickt oder weitergereicht wird - nur die Laenge als Tippfehler-Probe. +imap_check_line('PAYPAL_IMAP_PASS', $pass === null ? '(nicht gesetzt)' : 'gesetzt (' . strlen($pass) . ' Zeichen)'); +imap_check_line('PAYPAL_IMAP_MAILBOX', $mailbox); + +if ($host === null || $user === null || $pass === null) { + echo "\nOhne Host, Benutzer und Passwort ist kein Verbindungstest moeglich.\n"; + exit(1); +} + +/** + * Liest die Antwort bis zur Zeile mit dem gesuchten Tag. + * + * @return array{ok: bool, lines: list} + */ +function imap_check_read($sock, string $tag): array +{ + $lines = []; + while (($line = fgets($sock)) !== false) { + $line = rtrim($line, "\r\n"); + $lines[] = $line; + if (str_starts_with($line, $tag . ' ')) { + return ['ok' => str_starts_with($line, $tag . ' OK'), 'lines' => $lines]; + } + } + + return ['ok' => false, 'lines' => $lines]; +} + +function imap_check_send($sock, string $tag, string $command): array +{ + fwrite($sock, $tag . ' ' . $command . "\r\n"); + + return imap_check_read($sock, $tag); +} + +echo "\n=== Verbindungstest ohne imap-Erweiterung (nur lesend) ===\n"; + +// Port 993 = TLS von Anfang an; alles andere (typisch 143) = STARTTLS. +$implizitesTls = $port === 993 || str_contains($flags, '/ssl'); +$adresse = ($implizitesTls ? 'ssl://' : 'tcp://') . $host . ':' . $port; +imap_check_line('Verbindung', $adresse . ($implizitesTls ? ' (TLS sofort)' : ' (STARTTLS)')); + +$fehlerNr = 0; +$fehlerText = ''; +$sock = @stream_socket_client($adresse, $fehlerNr, $fehlerText, 15); +if ($sock === false) { + echo "FEHLER: Verbindung nicht moeglich ({$fehlerNr}: {$fehlerText})\n"; + echo "Moegliche Ursachen: Host/Port falsch, ausgehende Verbindungen gesperrt,\n" + . "oder TLS-Zertifikat nicht akzeptiert.\n"; + exit(1); +} +stream_set_timeout($sock, 15); + +$greeting = rtrim((string) fgets($sock), "\r\n"); +imap_check_line('Begruessung', $greeting !== '' ? $greeting : '(keine)'); + +if (!$implizitesTls) { + $start = imap_check_send($sock, 'a0', 'STARTTLS'); + if (!$start['ok'] || !@stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + echo "FEHLER: STARTTLS fehlgeschlagen - ohne Verschluesselung wird nicht angemeldet.\n"; + exit(1); + } + imap_check_line('STARTTLS', 'ok'); +} + +// Anfuehrungszeichen und Backslashes im Passwort maskieren (IMAP-Quoting). +$quote = static fn(string $wert): string => '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $wert) . '"'; + +$login = imap_check_send($sock, 'a1', 'LOGIN ' . $quote($user) . ' ' . $quote($pass)); +if (!$login['ok']) { + echo "FEHLER: Anmeldung abgelehnt.\n"; + echo ' Antwort: ' . end($login['lines']) . "\n"; + fwrite($sock, "a9 LOGOUT\r\n"); + fclose($sock); + exit(1); +} +imap_check_line('Anmeldung', 'ok'); + +// EXAMINE statt SELECT: oeffnet das Postfach ausdruecklich nur lesend. +$examine = imap_check_send($sock, 'a2', 'EXAMINE ' . $quote($mailbox)); +if (!$examine['ok']) { + echo "FEHLER: Postfach '{$mailbox}' nicht zu oeffnen.\n"; + echo ' Antwort: ' . end($examine['lines']) . "\n"; + fwrite($sock, "a9 LOGOUT\r\n"); + fclose($sock); + exit(1); +} +$gesamt = 0; +foreach ($examine['lines'] as $line) { + if (preg_match('/^\* (\d+) EXISTS/', $line, $m)) { + $gesamt = (int) $m[1]; + } +} +imap_check_line('Postfach', $mailbox . ' geoeffnet'); +imap_check_line('Mails gesamt', (string) $gesamt); + +$search = imap_check_send($sock, 'a3', 'SEARCH UNSEEN'); +$ungelesen = 0; +foreach ($search['lines'] as $line) { + if (str_starts_with($line, '* SEARCH')) { + $ids = trim(substr($line, strlen('* SEARCH'))); + $ungelesen = $ids === '' ? 0 : count(preg_split('/\s+/', $ids) ?: []); + } +} +imap_check_line('davon ungelesen', (string) $ungelesen); + +fwrite($sock, "a9 LOGOUT\r\n"); +fclose($sock); + +echo "\nErgebnis: Postfach erreichbar und Zugangsdaten gueltig.\n"; +if (function_exists('imap_open')) { + echo "Die imap-Erweiterung ist vorhanden - scripts/fetch-paypal-payments.php\n" + . "laeuft ohne weitere Aenderung. Naechster Schritt: derselbe Task mit --dry-run.\n"; +} else { + echo "Die imap-Erweiterung fehlt, das Postfach ist aber ueber die reine\n" + . "TLS-Verbindung erreichbar - ein Abruf ohne Erweiterung ist also moeglich.\n"; +}