value. The subject and body are predefined — only the recipient * and (optionally) the target server vary. * * Mail hygiene is kept deliberately lightweight but complete: a real From with * display name, matching Reply-To, a unique Message-ID, Date, correct MIME * headers, and Auto-Submitted so downstream systems know it is machine-generated * and should not auto-reply. The envelope sender is always SENDER_ADDRESS, which * is what SPF evaluates. * * @return array{subject:string, body:string, headers:array} */ function buildMessage(string $selfUrl): array { $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); // Subject stays ASCII so it needs no MIME encoded-word wrapping. $subject = 'Test message from the med0.de mail tools'; $body = "Hello,\r\n" . "\r\n" . "This is an automated TEST message sent by the med0.de mail-testing tools.\r\n" . "If you received it, delivery from " . SENDER_ADDRESS . " to your address is working.\r\n" . "You can safely ignore or delete this message — no action is required.\r\n" . "\r\n" . "Test page: " . $selfUrl . "\r\n" . "Sent (UTC): " . $now->format('Y-m-d H:i:s') . "\r\n" . "\r\n" . "— med0.de mail tools\r\n"; // A unique, domain-scoped Message-ID aids threading and spam scoring. $messageId = sprintf('<%s.%s@med0.de>', $now->format('YmdHis'), bin2hex(random_bytes(8))); $headers = [ 'From' => sprintf('%s <%s>', SENDER_NAME, SENDER_ADDRESS), 'Reply-To' => SENDER_ADDRESS, 'Message-ID' => $messageId, 'Date' => $now->format(DateTimeInterface::RFC2822), 'MIME-Version' => '1.0', 'Content-Type' => 'text/plain; charset=UTF-8', 'Content-Transfer-Encoding' => '8bit', 'Auto-Submitted' => 'auto-generated', // RFC 3834: do not auto-reply 'X-Mailer' => 'med0.de-test-tool', ]; return ['subject' => $subject, 'body' => $body, 'headers' => $headers]; } /** * Dispatches the test message. With $mailserver empty the message goes through * PHP's mail() (the local MTA does the usual MX routing). With $mailserver set it * is delivered by talking SMTP directly to that host — no local MTA involved. * * @param string[] $transcript filled with the SMTP conversation (direct mode only) */ function sendTestMail(string $recipient, string $selfUrl, ?string $mailserver, ?string &$error, ?array &$transcript): bool { $error = null; $transcript = []; $msg = buildMessage($selfUrl); if ($mailserver === null || $mailserver === '') { // Normal path: hand off to the local MTA. The 5th parameter sets the // envelope sender (Return-Path) so SPF checks the med0.de domain. $headerLines = []; foreach ($msg['headers'] as $k => $v) { $headerLines[] = $k . ': ' . $v; } $ok = @mail($recipient, $msg['subject'], $msg['body'], implode("\r\n", $headerLines), '-f' . SENDER_ADDRESS); if (!$ok) { $error = 'The local mail server rejected or failed to accept the message. ' . 'Check that this host is configured to send mail for med0.de.'; return false; } return true; } // Direct path: parse host[:port] (default 25) and speak SMTP to that server. $host = $mailserver; $port = 25; if (preg_match('/^(.+):(\d+)$/', $mailserver, $m)) { $host = $m[1]; $port = (int) $m[2]; } // Assemble the full RFC 5322 message (To/Subject go inside DATA here). $lines = ['To: ' . $recipient, 'Subject: ' . $msg['subject']]; foreach ($msg['headers'] as $k => $v) { $lines[] = $k . ': ' . $v; } $raw = implode("\r\n", $lines) . "\r\n\r\n" . $msg['body']; return smtpDeliver($host, $port, SENDER_ADDRESS, $recipient, $raw, $error, $transcript); } /** * Minimal SMTP client: connects, EHLOs, opportunistically upgrades with STARTTLS * when offered, then MAIL FROM / RCPT TO / DATA. No AUTH — this mirrors how an MTA * delivers straight to a recipient's mail exchanger on port 25. * * @param string[] $transcript */ function smtpDeliver(string $host, int $port, string $from, string $to, string $rawMessage, ?string &$error, array &$transcript): bool { $errno = 0; $errstr = ''; $stream = @stream_socket_client( sprintf('tcp://%s:%d', $host, $port), $errno, $errstr, SMTP_TIMEOUT, STREAM_CLIENT_CONNECT ); if (!is_resource($stream)) { $error = $errstr !== '' ? "Connection to $host:$port failed: $errstr (errno $errno)" : 'Connection failed.'; return false; } stream_set_timeout($stream, SMTP_TIMEOUT); // Reads a full (possibly multiline) reply and asserts the leading status code. $expect = function (string $code) use ($stream, &$transcript, &$error): bool { $status = ''; while (($line = fgets($stream, 4096)) !== false) { $transcript[] = 'S: ' . rtrim($line, "\r\n"); $status = substr($line, 0, 3); if (strlen($line) < 4 || $line[3] !== '-') { // last line has a space, not '-' break; } } if (strpos($status, $code) !== 0) { $error = sprintf('Expected %s but server said: %s', $code, trim($status . ' ...')); return false; } return true; }; $send = function (string $cmd) use ($stream, &$transcript): void { $transcript[] = 'C: ' . $cmd; fwrite($stream, $cmd . "\r\n"); }; try { if (!$expect('220')) { throw new RuntimeException($error); } $send('EHLO ' . EHLO_NAME); // Capture EHLO capabilities to decide whether STARTTLS is offered. $caps = ''; while (($line = fgets($stream, 4096)) !== false) { $transcript[] = 'S: ' . rtrim($line, "\r\n"); $caps .= $line; if (strlen($line) < 4 || $line[3] !== '-') { break; } } if (strpos($caps, '250') !== 0) { throw new RuntimeException('EHLO was rejected by ' . $host . '.'); } // Opportunistic STARTTLS — encrypt when offered, but do not require a // trusted certificate (recipient MXs routinely use self-signed certs). if (stripos($caps, 'STARTTLS') !== false) { $send('STARTTLS'); if (!$expect('220')) { throw new RuntimeException($error); } stream_context_set_option($stream, 'ssl', 'verify_peer', false); stream_context_set_option($stream, 'ssl', 'verify_peer_name', false); stream_context_set_option($stream, 'ssl', 'allow_self_signed', true); if (@stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT) !== true) { throw new RuntimeException('STARTTLS negotiation failed with ' . $host . '.'); } $transcript[] = '* TLS established'; $send('EHLO ' . EHLO_NAME); // RFC 3207: re-issue EHLO after the upgrade if (!$expect('250')) { throw new RuntimeException($error); } } $send('MAIL FROM:<' . $from . '>'); if (!$expect('250')) { throw new RuntimeException($error); } $send('RCPT TO:<' . $to . '>'); if (!$expect('25')) { throw new RuntimeException($error); } // 250 or 251 $send('DATA'); if (!$expect('354')) { throw new RuntimeException($error); } // Dot-stuff any line that begins with '.' then terminate with .. $data = preg_replace('/^\./m', '..', $rawMessage); $transcript[] = 'C: [message data, ' . strlen($data) . ' bytes]'; fwrite($stream, $data . "\r\n.\r\n"); if (!$expect('250')) { throw new RuntimeException($error); } $send('QUIT'); $expect('221'); // best-effort; delivery already accepted above fclose($stream); return true; } catch (\Throwable $e) { @fclose($stream); if ($error === null || $error === '') { $error = $e->getMessage(); } return false; } } session_start(); // ── Access gate ────────────────────────────────────────────────────────────── // A correct password unlocks the tool for the rest of the session. $gateEnabled = (ACCESS_PASSWORD !== ''); $authed = !$gateEnabled || !empty($_SESSION['stm_authed']); $loginError = null; $isPost = $_SERVER['REQUEST_METHOD'] === 'POST'; // The login form carries only "password"; the send form carries "recipient". $isLoginAttempt = $isPost && isset($_POST['password']) && !isset($_POST['recipient']); if (!$authed && $isLoginAttempt) { if (hash_equals(ACCESS_PASSWORD, (string) $_POST['password'])) { session_regenerate_id(true); // fresh id on privilege change (fixation defence) $_SESSION['stm_authed'] = true; $authed = true; } else { $loginError = 'Incorrect password.'; } } // Build an absolute URL to this page, used in the mail body and the form action. $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'tool.medowar.de'; $path = strtok($_SERVER['REQUEST_URI'] ?? '/send-test-mail.php', '?'); $selfUrl = $scheme . '://' . $host . $path; $recipient = trim((string) ($_POST['recipient'] ?? '')); $mailserver = trim((string) ($_POST['mailserver'] ?? '')); $sent = false; $sendError = null; $inputError = null; $transcript = []; $isSendAttempt = $authed && $isPost && isset($_POST['recipient']); if ($isSendAttempt) { $honeypot = trim((string) ($_POST[HONEYPOT_FIELD] ?? '')); $formTs = (int) ($_SESSION['stm_form_ts'] ?? 0); $elapsed = time() - $formTs; if ($honeypot !== '') { // (1) Honeypot: a hidden field only a bot would fill. Feign success, send nothing. $sent = true; } elseif ($formTs === 0 || $elapsed < MIN_FORM_SECONDS) { // (2) Time-trap: nobody fills and submits the form this fast — likely a bot. $inputError = 'That was submitted a little too quickly — please try again.'; } else { // FILTER_VALIDATE_EMAIL also rejects CR/LF, closing the header-injection door. $clean = filter_var($recipient, FILTER_VALIDATE_EMAIL); if ($clean === false) { $inputError = 'Please enter a valid recipient email address.'; } elseif ($mailserver !== '' && !preg_match('/^(?:[a-z0-9](?:-?[a-z0-9])*\.)+[a-z]{2,}(?::\d{1,5})?$|^\d{1,3}(?:\.\d{1,3}){3}(?::\d{1,5})?$/i', $mailserver)) { $inputError = 'Please enter a valid mailserver as host or host:port (or leave it blank for normal MX delivery).'; } else { $recipient = $clean; $sent = sendTestMail($recipient, $selfUrl, $mailserver !== '' ? $mailserver : null, $sendError, $transcript); } } } // Stamp a fresh render time so the next send has a timing baseline to check against. if ($authed) { $_SESSION['stm_form_ts'] = time(); } ?> Send Test Mail

✉️ Send Test Mail

🔒 This tool is password-protected. Enter the password to continue.
Sends a fixed, clearly-labelled test message from to the address you enter. The subject and body are predefined — the only input is the recipient. Use it to confirm that outbound delivery for med0.de reaches a given mailbox.

Leave mailserver empty to deliver via the local mail system (normal MX routing). Fill it to deliver directly to that host over SMTP (default port 25, opportunistic STARTTLS).

✅ Test message for .

Check the recipient's inbox (and spam folder).

❌ Could not send the test message.
SMTP conversation with

What gets sent

From') ?>
Reply-To
SubjectTest message from the med0.de mail tools
Body
Hello,

This is an automated TEST message sent by the med0.de mail-testing tools.
If you received it, delivery from  to your address is working.
You can safely ignore or delete this message — no action is required.

Test page: 

Sent (UTC): …

— med0.de mail tools