[Log-Datei] * Die Mail-Datei enthält EINE Mail (mit \r\n); sie liegt im Ordner INBOX als Nachricht 1 mit UID 7. */ [$_, $port, $mailFile] = $argv + [null, '14300', '']; $logFile = $argv[3] ?? ''; $raw = (string) file_get_contents($mailFile); $raw = str_replace(["\r\n", "\n"], ["\n", "\r\n"], $raw); [$headerPart, $bodyPart] = explode("\r\n\r\n", $raw, 2); $headerPart .= "\r\n\r\n"; /** Zerlegt die Mail in MIME-Teile: Teilnummer => [Kopf, Rumpf, Content-Type, Transferkodierung, Content-Disposition, Content-ID]. */ function mime_parts(string $head, string $body, string $no = ''): array { $out = []; $h = []; foreach (preg_split('/\r\n(?=\S)/', rtrim($head)) as $line) { if (strpos($line, ':') === false) continue; [$k, $v] = explode(':', $line, 2); $h[strtolower(trim($k))] = trim(preg_replace('/\r\n\s+/', ' ', $v)); } $ct = $h['content-type'] ?? 'text/plain'; if (stripos($ct, 'multipart/') === 0 && preg_match('/boundary="?([^";]+)"?/i', $ct, $m)) { $pieces = preg_split('/\r?\n?--' . preg_quote($m[1], '/') . '(?:--)?(?:\r\n?)?/', "\r\n" . $body); $n = 0; foreach ($pieces as $piece) { if (trim($piece) === '') continue; $n++; [$ph, $pb] = array_pad(explode("\r\n\r\n", ltrim($piece, "\r\n"), 2), 2, ''); $out['__children'][] = mime_parts($ph . "\r\n", $pb, $no === '' ? (string) $n : $no . '.' . $n); } $out['__multi'] = ['ct' => $ct, 'no' => $no]; return $out; } return ['__leaf' => ['no' => $no === '' ? '1' : $no, 'h' => $h, 'body' => $body]]; } function q(string $s): string { return '"' . addcslashes($s, '"\\') . '"'; } /** BODYSTRUCTURE-Ausdruck. */ function bodystructure(array $node): string { if (isset($node['__multi'])) { $inner = ''; foreach ($node['__children'] as $c) $inner .= bodystructure($c); $sub = 'MIXED'; if (preg_match('#multipart/(\w+)#i', $node['__multi']['ct'], $m)) $sub = strtoupper($m[1]); $bd = preg_match('/boundary="?([^";]+)"?/i', $node['__multi']['ct'], $b) ? $b[1] : 'x'; return '(' . $inner . ' ' . q($sub) . ' ("BOUNDARY" ' . q($bd) . ') NIL NIL)'; } $leaf = $node['__leaf']; $h = $leaf['h']; $ct = $h['content-type'] ?? 'text/plain; charset=us-ascii'; [$type, $sub] = array_pad(explode('/', strtolower(trim(explode(';', $ct)[0])), 2), 2, 'plain'); $params = []; preg_match_all('/;\s*([\w*-]+)=(?:"([^"]*)"|([^;\s]+))/', $ct, $pm, PREG_SET_ORDER); foreach ($pm as $p) $params[] = q(strtoupper($p[1])) . ' ' . q($p[2] !== '' ? $p[2] : ($p[3] ?? '')); $pstr = $params ? '(' . implode(' ', $params) . ')' : 'NIL'; $id = isset($h['content-id']) ? q($h['content-id']) : 'NIL'; $enc = strtoupper($h['content-transfer-encoding'] ?? '7BIT'); $size = strlen($leaf['body']); $lines = substr_count($leaf['body'], "\n"); $disp = 'NIL'; if (isset($h['content-disposition'])) { $dt = strtoupper(trim(explode(';', $h['content-disposition'])[0])); $dp = []; preg_match_all('/;\s*([\w*-]+)=(?:"([^"]*)"|([^;\s]+))/', $h['content-disposition'], $dm, PREG_SET_ORDER); foreach ($dm as $p) $dp[] = q(strtoupper($p[1])) . ' ' . q($p[2] !== '' ? $p[2] : ($p[3] ?? '')); $disp = '(' . q($dt) . ($dp ? ' (' . implode(' ', $dp) . ')' : ' NIL') . ')'; } $tail = ($type === 'text') ? " {$size} {$lines}" : " {$size}"; return '(' . q(strtoupper($type)) . ' ' . q(strtoupper($sub)) . ' ' . $pstr . ' ' . $id . ' NIL ' . q($enc) . $tail . ' NIL ' . $disp . ' NIL)'; } function find_leaf(array $node, string $no): ?array { if (isset($node['__leaf'])) return $node['__leaf']['no'] === $no ? $node['__leaf'] : null; foreach ($node['__children'] ?? [] as $c) if ($r = find_leaf($c, $no)) return $r; return null; } $tree = mime_parts($headerPart, $bodyPart); preg_match('/^Message-ID:\s*(<[^>]+>)/mi', $headerPart, $mm); $messageId = $mm[1] ?? ''; preg_match('/^Subject:\s*(.*)$/mi', $headerPart, $sm); $subject = trim($sm[1] ?? ''); $envelope = '("Mon, 03 Nov 2026 00:39:00 +0100" ' . q($subject) . ' (("Olaf" NIL "olaf" "example.de")) (("Olaf" NIL "olaf" "example.de")) (("Olaf" NIL "olaf" "example.de")) (("" NIL "einsendungen" "example.org")) NIL NIL NIL ' . q($messageId) . ')'; $log = function (string $line) use ($logFile): void { if ($logFile !== '') file_put_contents($logFile, $line . "\n", FILE_APPEND); }; $seenFlag = false; $server = stream_socket_server('tcp://127.0.0.1:' . $port, $errno, $errstr); if (!$server) { fwrite(STDERR, "Server nicht startbar: $errstr\n"); exit(1); } while (true) { $conn = @stream_socket_accept($server, 600); if (!$conn) break; fwrite($conn, "* OK [CAPABILITY IMAP4rev1] fake ready\r\n"); while (($line = fgets($conn)) !== false) { $line = rtrim($line, "\r\n"); $log('C: ' . $line); if (!preg_match('/^(\S+)\s+(.*)$/', $line, $m)) continue; [$_, $tag, $cmd] = $m; $send = function (string $s) use ($conn, $log): void { $log('S: ' . substr($s, 0, 200)); fwrite($conn, $s); }; $upper = strtoupper($cmd); if (strpos($upper, 'CAPABILITY') === 0) { $send("* CAPABILITY IMAP4rev1\r\n{$tag} OK done\r\n"); } elseif (strpos($upper, 'LOGIN') === 0) { $send("{$tag} OK logged in\r\n"); } elseif (strpos($upper, 'SELECT') === 0 || strpos($upper, 'EXAMINE') === 0) { $send("* 1 EXISTS\r\n* 0 RECENT\r\n* OK [UIDVALIDITY 1] ok\r\n* OK [UIDNEXT 8] ok\r\n* FLAGS (\\Seen \\Deleted)\r\n{$tag} OK [READ-WRITE] selected\r\n"); } elseif (strpos($upper, 'NOOP') === 0 || strpos($upper, 'CHECK') === 0) { $send("{$tag} OK done\r\n"); } elseif (strpos($upper, 'LOGOUT') === 0) { $send("* BYE bye\r\n{$tag} OK done\r\n"); break; } elseif (preg_match('/^(UID\s+)?STORE\b/i', $cmd)) { if (stripos($cmd, '\\Seen') !== false && stripos($cmd, '-FLAGS') === false) $seenFlag = true; $send("{$tag} OK stored\r\n"); } elseif (preg_match('/^(EXPUNGE|COPY|UID COPY)\b/i', $cmd)) { $send("{$tag} OK done\r\n"); } elseif (preg_match('/^(UID\s+)?(SEARCH|SORT)\b/i', $cmd, $sm2)) { $uid = !empty($sm2[1]); // Bereits als gelesen markiert: bei "UNSEEN" nichts finden $none = $seenFlag && stripos($cmd, 'UNSEEN') !== false; $send('* ' . strtoupper($sm2[2]) . ($none ? '' : ' ' . ($uid ? '7' : '1')) . "\r\n{$tag} OK done\r\n"); } elseif (preg_match('/^(UID\s+)?FETCH\s+(\S+)\s+(.*)$/i', $cmd, $fm)) { $isUid = $fm[1] !== ''; $items = $fm[3]; $parts = []; if ($isUid || stripos($items, 'UID') !== false) $parts[] = 'UID 7'; if (stripos($items, 'FLAGS') !== false) $parts[] = 'FLAGS (\\Seen)'; if (stripos($items, 'INTERNALDATE') !== false) $parts[] = 'INTERNALDATE "03-Nov-2026 00:39:00 +0100"'; if (stripos($items, 'RFC822.SIZE') !== false) $parts[] = 'RFC822.SIZE ' . strlen($raw); if (stripos($items, 'ENVELOPE') !== false) $parts[] = 'ENVELOPE ' . $envelope; if (stripos($items, 'BODYSTRUCTURE') !== false) $parts[] = 'BODYSTRUCTURE ' . bodystructure($tree); $literal = ''; if (preg_match('/BODY(?:\.PEEK)?\[([^\]]*)\]/i', $items, $bm)) { $sec = $bm[1]; if ($sec === '' || stripos($sec, 'HEADER') === 0) { $data = $sec === '' ? $raw : $headerPart; } else { $leaf = find_leaf($tree, $sec); $data = $leaf ? $leaf['body'] : ''; } $parts[] = 'BODY[' . $sec . '] {' . strlen($data) . "}\r\n" . $data; } $send('* 1 FETCH (' . implode(' ', $parts) . ")\r\n{$tag} OK done\r\n"); } else { $send("{$tag} BAD unknown command\r\n"); } } fclose($conn); }