Name lookup table. */ function getUsableRemailerKeyNames(): array { static $usableNames = null; if (is_array($usableNames)) { return $usableNames; } $usableNames = []; $keyring = yamnConfig('YAMN_PUBRING', '/opt/yamn-master/pubring.mix'); if (!is_readable($keyring)) { return $usableNames; } $today = gmdate('Y-m-d'); foreach (file($keyring, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) { $parts = preg_split('/\s+/', trim($line)); if (count($parts) !== 7) { continue; } [$name, , , , , $validFrom, $validUntil] = $parts; if (!preg_match('/^[a-z0-9_-]+$/', $name) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $validFrom) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $validUntil)) { continue; } if ($validFrom <= $today && $today <= $validUntil) { $usableNames[$name] = true; } } return $usableNames; } /** * Parse remailers from file and return array by type * Entry and Exit can use ANY remailer * Middle should use remailers with specific flags * * @param string $type 'entry', 'middle', or 'exit' * @return array Array of remailer names */ function getRemailers($type) { $remailers = ['*']; // Always include Random option $usableKeyNames = getUsableRemailerKeyNames(); // Try multiple file locations $files = [ '/opt/yamn-data/cache/remailers.txt', '/var/www/yamnweb/remailers.txt' ]; foreach ($files as $file) { if (!file_exists($file)) continue; $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $inDataSection = false; foreach ($lines as $line) { $line = trim($line); // Start parsing after separator line (--------) if (preg_match('/^-{10,}/', $line)) { $inDataSection = true; continue; } // Stop at Remailer-Capabilities section if (stripos($line, 'Remailer-Capabilities') !== false) { break; } // Only parse lines after the separator if (!$inDataSection) continue; // Skip empty lines if (empty($line)) continue; // Split line: name latency uptime [flags] // Example: "middleman 211112111211 :45 ++++++++++++ 100.0% D" $parts = preg_split('/\s+/', $line); // Must have at least name + latency if (count($parts) < 2) continue; // Extract ONLY the remailer name (first field) $remailerName = $parts[0]; // Validate name: lowercase letters, numbers, hyphens only if (!preg_match('/^[a-z0-9-]+$/', $remailerName)) continue; if (!isset($usableKeyNames[$remailerName])) continue; // Check if last field is 'D' (middle capability flag) $lastField = end($parts); $isMiddleCapable = ($lastField === 'D'); // LOGICA CORRETTA: Mutuamente esclusivi if ($isMiddleCapable) { // Remailers CON 'D' = SOLO middle if ($type === 'middle') { $remailers[] = $remailerName; } } else { // Remailers SENZA 'D' = SOLO entry/exit if ($type === 'entry' || $type === 'exit') { $remailers[] = $remailerName; } } } break; // Use first file found } return array_unique($remailers); } /** * Replace asterisk (*) with random remailer from available list * @param string $remailer Selected remailer (may be "*") * @param array $availableRemailers List of available remailers * @return string Actual remailer name */ function resolveRemailer($remailer, $availableRemailers) { if ($remailer === '*') { // Filter out the asterisk itself from candidates $candidates = array_filter($availableRemailers, function($r) { return $r !== '*'; }); if (empty($candidates)) { throw new Exception("No remailers available for random selection"); } // Pick random remailer $randomIndex = array_rand($candidates); return $candidates[$randomIndex]; } if (!in_array($remailer, $availableRemailers, true)) { throw new Exception("Selected remailer is no longer available. Reload the page and choose again."); } return $remailer; } // Load available remailers $entryRemailers = getRemailers('entry'); $middleRemailers = getRemailers('middle'); $exitRemailers = getRemailers('exit'); // Refresh stale remailer data during a browser reload. The downloader only // contacts Tor when either local file is missing or older than one day. if ($_SERVER['REQUEST_METHOD'] === 'GET' && class_exists('SecureRemailerDownloader')) { try { $downloader = new SecureRemailerDownloader(); if (!$downloader->downloadRemailers()) { error_log('Remailer refresh failed; keeping the locally available data.'); } // Reload after a successful refresh, or continue with the local list. $entryRemailers = getRemailers('entry'); $middleRemailers = getRemailers('middle'); $exitRemailers = getRemailers('exit'); } catch (Exception $e) { error_log("Failed to download remailers: " . $e->getMessage()); } } $message = ''; $messageType = ''; // Check for flash messages from previous redirect (PRG pattern) if (isset($_SESSION['flash_message'])) { $message = $_SESSION['flash_message']; $messageType = isset($_SESSION['flash_type']) ? $_SESSION['flash_type'] : 'info'; // Clear flash messages after displaying unset($_SESSION['flash_message']); unset($_SESSION['flash_type']); } // Handle form submission if ($_SERVER['REQUEST_METHOD'] === 'POST') { // CSRF validation - IMPROVED with better error handling if (!isset($_POST['csrf_token'])) { // Save error in session and redirect $_SESSION['flash_message'] = "Security token missing. Please reload the page and try again."; $_SESSION['flash_type'] = 'error'; header('Location: ' . $_SERVER['PHP_SELF']); exit; } elseif ($_POST['csrf_token'] !== $currentCsrfToken) { // Save error in session and redirect $_SESSION['flash_message'] = "Security token mismatch. Please try submitting again."; $_SESSION['flash_type'] = 'error'; header('Location: ' . $_SERVER['PHP_SELF']); exit; } else { try { // Get and sanitize form data with additional validation $entryRemailer = isset($_POST['entry_remailer']) ? filter_var($_POST['entry_remailer'], FILTER_SANITIZE_STRING) : ''; $middleRemailer = isset($_POST['middle_remailer']) ? filter_var($_POST['middle_remailer'], FILTER_SANITIZE_STRING) : ''; $exitRemailer = isset($_POST['exit_remailer']) ? filter_var($_POST['exit_remailer'], FILTER_SANITIZE_STRING) : ''; $from = isset($_POST['from']) ? filter_var($_POST['from'], FILTER_SANITIZE_STRING) : ''; $replyTo = isset($_POST['reply_to']) && is_string($_POST['reply_to']) ? trim($_POST['reply_to']) : ''; $to = isset($_POST['to']) ? filter_var($_POST['to'], FILTER_SANITIZE_EMAIL) : ''; $subject = isset($_POST['subject']) ? filter_var($_POST['subject'], FILTER_SANITIZE_STRING) : ''; $newsgroups = isset($_POST['newsgroups']) ? filter_var($_POST['newsgroups'], FILTER_SANITIZE_STRING) : ''; $references = isset($_POST['references']) && is_string($_POST['references']) ? trim($_POST['references']) : ''; $data = isset($_POST['data']) ? $_POST['data'] : ''; // Keep original formatting $copies = isset($_POST['copies']) ? intval($_POST['copies']) : 1; // Validate copies if ($copies < 1 || $copies > 3) { throw new Exception("Number of copies must be between 1 and 3."); } if (empty($from)) { throw new Exception("Sender (From) is required."); } $messageKind = trim($newsgroups) === '' ? 'email' : 'usenet'; if ($messageKind === 'email' && empty($to)) { throw new Exception("Email recipient is required when Newsgroup is empty."); } if ($messageKind === 'usenet') { $to = yamnConfig('YAMN_USENET_GATEWAY', 'mail2news@mail2news.tcpreset.net'); if (!filter_var($to, FILTER_VALIDATE_EMAIL)) { throw new Exception("Usenet gateway is not configured correctly."); } } // Validate remailer chain if (empty($entryRemailer) || empty($middleRemailer) || empty($exitRemailer)) { throw new Exception("All three remailers must be specified."); } // Resolve asterisks (*) to actual random remailers $resolvedEntry = resolveRemailer($entryRemailer, $entryRemailers); $resolvedMiddle = resolveRemailer($middleRemailer, $middleRemailers); $resolvedExit = resolveRemailer($exitRemailer, $exitRemailers); $encoded = encodeYamnMessage([ 'kind' => $messageKind, 'entry' => $resolvedEntry, 'chain' => [$resolvedEntry, $resolvedMiddle, $resolvedExit], 'from' => $from, 'reply_to' => $replyTo, 'to' => $to, 'subject' => $subject, 'newsgroup' => $newsgroups, 'body' => $data, 'references' => $references, ]); if (!$encoded['success']) { throw new Exception($encoded['error'] ?? 'YAMN encoding failed'); } $responseFinished = false; $onHandoff = null; if (function_exists('fastcgi_finish_request')) { $onHandoff = static function () use (&$responseFinished): void { ignore_user_abort(true); $_SESSION['flash_message'] = 'Message accepted into the local in-memory Nym queue. Delivery continues in the background; YAMN queues may take several hours, and this confirmation does not mean the message has been published or delivered yet.'; $_SESSION['flash_type'] = 'success'; $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); session_write_close(); header('Location: ' . $_SERVER['PHP_SELF']); fastcgi_finish_request(); $responseFinished = true; }; } $result = sendNymEnvelope($encoded['envelope'], 1, $encoded['entry_address'], $onHandoff); if ($responseFinished) { exit; } if (!$result['success']) { throw new Exception($result['error'] ?? 'Nym submission failed'); } $_SESSION['flash_message'] = 'Message accepted into the local in-memory Nym queue. Delivery through the YAMN queues may take several hours; this confirmation does not mean the message has been published or delivered yet.'; $_SESSION['flash_type'] = 'success'; $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); header('Location: ' . $_SERVER['PHP_SELF']); exit; } catch (Exception $e) { // Save error in session and redirect (PRG pattern for errors too) $_SESSION['flash_message'] = "✗ Error: " . $e->getMessage(); $_SESSION['flash_type'] = 'error'; error_log("YAMN submission error: " . $e->getMessage() . "\nStack: " . $e->getTraceAsString()); // Redirect to prevent form resubmission header('Location: ' . $_SERVER['PHP_SELF']); exit; } catch (Error $e) { // Catch PHP 7+ Error class (for fatal errors) $_SESSION['flash_message'] = "✗ Fatal Error: " . $e->getMessage(); $_SESSION['flash_type'] = 'error'; error_log("YAMN fatal error: " . $e->getMessage() . "\nStack: " . $e->getTraceAsString()); // Redirect to prevent form resubmission header('Location: ' . $_SERVER['PHP_SELF']); exit; } catch (Throwable $e) { // Catch everything else $_SESSION['flash_message'] = "✗ Unexpected error: " . $e->getMessage(); $_SESSION['flash_type'] = 'error'; error_log("YAMN unexpected error: " . $e->getMessage()); // Redirect to prevent form resubmission header('Location: ' . $_SERVER['PHP_SELF']); exit; } } } ?> YAMN Web, invio anonimo

⚡ YAMN WEB INTERFACE ⚡

Nym before YAMN: YAMN Web encrypts first, then Nym carries only the opaque envelope
This interface prepares and submits YAMN packets. Read more: detailed architecture and operation
Remailer data

Remailer statistics and public keys are checked automatically when this page loads. Local data no more than 24 hours old is reused without a network connection.

Missing or older data is downloaded through Tor. If a source is unavailable, the existing data remains usable and another download is not attempted until the next daily check.
Entry receives the YAMN packet, Middle forwards it through the chain, and Exit delivers the decoded message.
Used as the From header.
Optional. It is not used to retrieve replies.
Required for email delivery. Leave empty when publishing to a newsgroup.
Optional. When set, the message is routed through the configured Mail-to-News gateway. Leave empty for email.
Optional. Enter one or more space-separated Message-IDs, without the References: label. The last ID becomes In-Reply-To.
The server creates the encrypted YAMN envelope in memory and sends only that packet through Nym. Remailer queues may take several hours.