diff options
| author | Gab <24553253+gabrix73@users.noreply.github.com> | 2026-08-13 14:04:30 +0200 |
|---|---|---|
| committer | Gab <24553253+gabrix73@users.noreply.github.com> | 2026-08-13 14:04:30 +0200 |
| commit | 994071243fc80990cf09e820b671006e9bd31c76 (patch) | |
| tree | b65e02724b98033240aa6f9d153acea021abc456 /index.php | |
| parent | 47d903de3bca4165e96d6ec8830eafbacf524cd2 (diff) | |
| download | yamnweb-994071243fc80990cf09e820b671006e9bd31c76.tar.gz yamnweb-994071243fc80990cf09e820b671006e9bd31c76.tar.xz yamnweb-994071243fc80990cf09e820b671006e9bd31c76.zip | |
Separate active YAMN code from Katzenpost PoC
Diffstat (limited to 'index.php')
| -rw-r--r-- | index.php | 392 |
1 files changed, 141 insertions, 251 deletions
@@ -1,6 +1,6 @@ <?php // yamnweb - YAMN Mixmaster Network Web Interface -// Secure anonymous email interface with Tor integration +// Tor is retained only for remailer-list downloads. Message transport uses Nym. // Enable error logging (disable display for security) error_reporting(E_ALL); @@ -16,7 +16,11 @@ if (!defined('FILTER_SANITIZE_STRING')) { // Configure session BEFORE starting it (critical for cookie-based sessions) ini_set('session.cookie_httponly', '1'); ini_set('session.use_only_cookies', '1'); -ini_set('session.cookie_secure', '0'); // Set to '1' if using HTTPS +// The public HTTPS endpoint gets a Secure cookie. The Onion service may use +// HTTP, so detect the current request instead of forcing one mode globally. +$requestIsHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') + || (isset($_SERVER['SERVER_PORT']) && (int)$_SERVER['SERVER_PORT'] === 443); +ini_set('session.cookie_secure', $requestIsHttps ? '1' : '0'); ini_set('session.cookie_samesite', 'Lax'); // Changed from 'Strict' to allow form submissions ini_set('session.gc_maxlifetime', 7200); // 2 hours session lifetime @@ -37,18 +41,15 @@ if ($_SESSION['session_test'] !== 'working') { die("Error: Session is not persisting. Check session.save_path permissions."); } -// Load optional dependencies (don't fail if missing) +// Tor is used only by the remailer-list downloader. if (file_exists('download_remailers.php')) { require_once 'download_remailers.php'; } else { error_log("Warning: download_remailers.php not found"); } -if (file_exists('tor_extension.php')) { - require_once 'tor_extension.php'; -} else { - error_log("Warning: tor_extension.php not found - Tor integration disabled"); -} +require_once __DIR__ . '/nym_sender.php'; +require_once __DIR__ . '/yamn_encoder.php'; // Theme management via cookie $theme = 'dark'; // Default @@ -220,7 +221,7 @@ if (isset($_SESSION['flash_message'])) { } // Handle form submission -if ($_SERVER['REQUEST_METHOD'] == 'POST') { +if ($_SERVER['REQUEST_METHOD'] === 'POST') { // CSRF validation - IMPROVED with better error handling if (!isset($_POST['csrf_token'])) { // Save error in session and redirect @@ -229,9 +230,6 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { header('Location: ' . $_SERVER['PHP_SELF']); exit; } elseif ($_POST['csrf_token'] !== $currentCsrfToken) { - // DEBUG: Log the mismatch for troubleshooting - error_log("CSRF Mismatch - POST: " . substr($_POST['csrf_token'], 0, 10) . "... SESSION: " . substr($currentCsrfToken, 0, 10) . "..."); - // Save error in session and redirect $_SESSION['flash_message'] = "Security token mismatch. Please try submitting again."; $_SESSION['flash_type'] = 'error'; @@ -239,6 +237,24 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { exit; } else { try { + if (isset($_POST['update_remailers'])) { + if (!class_exists('SecureRemailerDownloader')) { + throw new Exception('Remailer list updater is not available.'); + } + + set_time_limit(0); + $downloader = new SecureRemailerDownloader(); + if (!$downloader->forceUpdate()) { + throw new Exception('Remailer list update failed. The previous list was kept where possible.'); + } + + $_SESSION['flash_message'] = 'Remailer list and public keyring updated successfully.'; + $_SESSION['flash_type'] = 'success'; + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + header('Location: ' . $_SERVER['PHP_SELF']); + exit; + } + // 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) : ''; @@ -257,15 +273,22 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { throw new Exception("Number of copies must be between 1 and 3."); } - // Validate required fields - if (empty($to)) { - throw new Exception("Recipient (To) is required."); - } - 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."); @@ -276,125 +299,48 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { $resolvedMiddle = resolveRemailer($middleRemailer, $middleRemailers); $resolvedExit = resolveRemailer($exitRemailer, $exitRemailers); - // Build remailer chain with resolved names - $chain = "$resolvedEntry,$resolvedMiddle,$resolvedExit"; - - // Log the resolved chain for debugging - error_log("Resolved remailer chain: $chain (from: $entryRemailer,$middleRemailer,$exitRemailer)"); - - // Build message headers - $headers = "Content-Type: text/plain; charset=utf-8\n"; - $headers .= "Content-Transfer-Encoding: 8bit\n"; - $headers .= "MIME-Version: 1.0\n"; - - if (!empty($references)) { - $headers .= "References: $references\n"; + $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'); } - - // Build complete message - $messageContent = $headers . "From: $from\n"; - - if (!empty($replyTo)) { - $messageContent .= "Reply-To: $replyTo\n"; - } - - $messageContent .= "To: $to\nSubject: $subject\n"; - - if (!empty($newsgroups)) { - $messageContent .= "Newsgroups: $newsgroups\n"; - } - - $messageContent .= "\n$data"; - - // Verify YAMN executable exists - $yamnPath = '/opt/yamn-master/yamn'; - if (!file_exists($yamnPath)) { - throw new Exception("YAMN executable not found at: $yamnPath"); - } - - if (!is_executable($yamnPath)) { - throw new Exception("YAMN executable is not executable. Check permissions."); - } - - // Verify YAMN config file exists - $yamnConfig = '/opt/yamn-master/yamn.yml'; - if (!file_exists($yamnConfig)) { - throw new Exception("YAMN config file not found at: $yamnConfig"); - } - - // Ensure temp directory exists and is writable - $tempDir = '/var/www/yamnweb'; - if (!is_dir($tempDir)) { - throw new Exception("Temp directory does not exist: $tempDir"); - } - - if (!is_writable($tempDir)) { - throw new Exception("Temp directory is not writable: $tempDir"); - } - - // Ensure Maildir exists (required by YAMN) - $maildirBase = '/var/www/yamnweb/Maildir'; - $maildirDirs = [ - $maildirBase, - $maildirBase . '/tmp', - $maildirBase . '/new', - $maildirBase . '/cur' - ]; - - foreach ($maildirDirs as $dir) { - if (!is_dir($dir)) { - if (!@mkdir($dir, 0755, true)) { - throw new Exception("Failed to create Maildir directory: $dir"); - } - error_log("Created Maildir directory: $dir"); - } - } - - // Write message to temp file (use single file as in original) - $tempFile = $tempDir . '/message.txt'; - $writeResult = @file_put_contents($tempFile, $messageContent); - - if ($writeResult === false) { - throw new Exception("Failed to write message to temporary file: $tempFile"); - } - - // Verify file was written - if (!file_exists($tempFile)) { - throw new Exception("Temp file was not created: $tempFile"); - } - - // Log to debug.log - $debugLog = '/var/www/yamnweb/debug.log'; - file_put_contents($debugLog, date('Y-m-d H:i:s') . " - Starting email send\n", FILE_APPEND); - file_put_contents($debugLog, "To: $to | Chain: $chain | Copies: $copies\n", FILE_APPEND); - - // Use sendYamnEmail() from tor_extension.php - if (function_exists('sendYamnEmail')) { - $result = sendYamnEmail($chain, $copies, $tempFile, true); - - // Log result - file_put_contents($debugLog, date('Y-m-d H:i:s') . " - Result: " . ($result['success'] ? 'SUCCESS' : 'FAILED') . "\n", FILE_APPEND); - - // Clean up - @unlink($tempFile); - - if ($result['success']) { - // Success - save message in session and redirect (PRG pattern) - $_SESSION['flash_message'] = "✓ Message sent successfully via YAMN network ($copies " . ($copies > 1 ? "copies" : "copy") . ")"; + $responseFinished = false; + $onHandoff = null; + if (function_exists('fastcgi_finish_request')) { + $onHandoff = static function () use (&$responseFinished): void { + ignore_user_abort(true); + $_SESSION['flash_message'] = 'Message handed to the local Nym transport. The sender is completing delivery 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'; - - // Regenerate CSRF token after successful submission $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); - - // Redirect to prevent form resubmission on page reload + session_write_close(); header('Location: ' . $_SERVER['PHP_SELF']); - exit; - } else { - throw new Exception("YAMN send failed. Check debug.log for details."); - } - } else { - throw new Exception("sendYamnEmail() function not found. Check tor_extension.php"); + 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 by the Nym transport. 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(); @@ -427,11 +373,11 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { } ?> <!DOCTYPE html> -<html lang="en"> +<html lang="it"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>YAMN Web Interface</title> + <title>YAMN Web, invio anonimo</title> <style> :root { --transition-speed: 0.3s; @@ -536,6 +482,13 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { color: var(--text-secondary); } + .read-more { + color: var(--accent); + display: inline-block; + margin-top: 12px; + font-weight: bold; + } + .form-group { margin-bottom: 20px; } @@ -592,6 +545,12 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { font-size: 0.9em; } + small { + color: var(--text-secondary); + display: block; + margin-top: 6px; + } + button { width: 100%; padding: 15px; @@ -779,19 +738,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { </a> <h1>⚡ YAMN WEB INTERFACE ⚡</h1> - <div class="subtitle">Tor before Yamn Remailer Network</div> - - <!-- DEBUG: Session check (can be removed after troubleshooting) --> - <?php if (isset($_GET['debug'])): ?> - <div class="info-box" style="font-size: 11px; font-family: monospace;"> - <strong>DEBUG INFO:</strong> - Session ID: <?php echo substr(session_id(), 0, 16); ?>...<br> - CSRF Token: <?php echo substr($_SESSION['csrf_token'], 0, 16); ?>...<br> - PHP Version: <?php echo PHP_VERSION; ?><br> - Session Save Path: <?php echo session_save_path(); ?><br> - Cookie Params: <?php echo json_encode(session_get_cookie_params()); ?> - </div> - <?php endif; ?> + <div class="subtitle">YAMN composition, local encryption, send-only Nym transport</div> <?php if (!empty($message)): ?> <div class="message <?php echo $messageType; ?>"> @@ -800,128 +747,71 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { <?php endif; ?> <div class="info-box"> - <strong>Security Features Active:</strong> + <strong>This interface prepares and submits YAMN packets.</strong> <ul> - <li>Tor/Onion network before Yamn Mix Network</li> - <li>Onion Smtp Relay with traffic padding and timing randomization</li> - <li>Forward secrecy and metadata protection</li> - <li>No persistent message retention - No logs website</li> + <li>The YAMN chain consists of Entry, Middle, and Exit remailers.</li> + <li>The message is converted into an encrypted envelope before transport.</li> + <li>Nym transports only the encrypted envelope, not the individual message fields.</li> + <li>The service is send-only: it provides no inbox, fetch, reading, or reply retrieval.</li> </ul> + <a class="read-more" href="about.html">Read more: detailed architecture and operation</a> + </div> + + <div class="info-box"> + <strong>Remailer data</strong> + <p>The primary statistics source is Victor's YAMN pinger at <code>echolot.virebent.art</code>. Fallback pingers are tried automatically if needed.</p> + <form method="POST" action=""> + <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>"> + <button type="submit" name="update_remailers" value="1">Update remailer list and keyring</button> + </form> + <small>This may take a few minutes because the update uses Tor and tries fallback sources if necessary.</small> </div> <form method="POST" action="" id="yamnForm"> <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>"> <div class="form-group"> - <label>Remailer Chain <span class="required">*</span></label> + <label>Three-hop YAMN chain <span class="required">*</span></label> <div class="remailer-chain"> - <div> - <label for="entry_remailer">Entry Node</label> - <select name="entry_remailer" id="entry_remailer" required> - <option value="">-- Select Entry --</option> - <?php foreach ($entryRemailers as $remailer): ?> - <option value="<?php echo htmlspecialchars($remailer); ?>"> - <?php echo htmlspecialchars($remailer); ?> - </option> - <?php endforeach; ?> - </select> - </div> - <div> - <label for="middle_remailer">Middle Node</label> - <select name="middle_remailer" id="middle_remailer" required> - <option value="">-- Select Middle --</option> - <?php foreach ($middleRemailers as $remailer): ?> - <option value="<?php echo htmlspecialchars($remailer); ?>"> - <?php echo htmlspecialchars($remailer); ?> - </option> - <?php endforeach; ?> - </select> - </div> - <div> - <label for="exit_remailer">Exit Node</label> - <select name="exit_remailer" id="exit_remailer" required> - <option value="">-- Select Exit --</option> - <?php foreach ($exitRemailers as $remailer): ?> - <option value="<?php echo htmlspecialchars($remailer); ?>"> - <?php echo htmlspecialchars($remailer); ?> - </option> - <?php endforeach; ?> - </select> - </div> + <div><label for="entry_remailer">Entry</label><select name="entry_remailer" id="entry_remailer" required><?php foreach ($entryRemailers as $remailer): ?><option value="<?php echo htmlspecialchars($remailer); ?>"><?php echo htmlspecialchars($remailer); ?></option><?php endforeach; ?></select></div> + <div><label for="middle_remailer">Middle</label><select name="middle_remailer" id="middle_remailer" required><?php foreach ($middleRemailers as $remailer): ?><option value="<?php echo htmlspecialchars($remailer); ?>"><?php echo htmlspecialchars($remailer); ?></option><?php endforeach; ?></select></div> + <div><label for="exit_remailer">Exit</label><select name="exit_remailer" id="exit_remailer" required><?php foreach ($exitRemailers as $remailer): ?><option value="<?php echo htmlspecialchars($remailer); ?>"><?php echo htmlspecialchars($remailer); ?></option><?php endforeach; ?></select></div> </div> + <small>Entry receives the YAMN packet, Middle forwards it through the chain, and Exit delivers the decoded message.</small> </div> - - <div class="form-group"> - <label for="from">From Address <span class="required">*</span></label> - <input type="text" name="from" id="from" placeholder="Anonymous <anonymous@anonymous.com>" required> + <div class="form-group"><label for="from">From <span class="required">*</span></label><input type="text" name="from" id="from" placeholder="Anonymous <anon@example.org>" required><small>Used as the From header.</small></div> + <div class="form-group"><label for="reply_to">Reply-To address</label><input type="text" name="reply_to" id="reply_to"><small>Optional. It is not used to retrieve replies.</small></div> + <div class="form-group"><label for="to">Email recipient</label><input type="email" name="to" id="to"><small>Required for email delivery. Leave empty when publishing to a newsgroup.</small></div> + <div class="form-group"><label for="subject">Subject <span class="required">*</span></label><input type="text" name="subject" id="subject" required></div> + <div class="form-group"><label for="newsgroups">Newsgroup</label><input type="text" name="newsgroups" id="newsgroups" placeholder="misc.test"><small>Optional. When set, the message is routed through the configured Mail-to-News gateway. Leave empty for email.</small></div> + <div class="form-group"><label for="references">References</label><input type="text" name="references" id="references" placeholder="<message-id@example.org>"><small>Optional. Links the article to an existing thread.</small></div> + <div class="form-group"><label for="data">Message body <span class="required">*</span></label><textarea name="data" id="data" required></textarea> + <small>The server creates the encrypted YAMN envelope in memory and sends only that packet through Nym. Remailer queues may take several hours.</small> </div> - <div class="form-group"> - <label for="reply_to">Reply-To Address</label> - <input type="text" name="reply_to" id="reply_to" placeholder="No Reply <noreply@anonymous.com>"> - </div> - - <div class="form-group"> - <label for="to">To Address <span class="required">*</span></label> - <input type="email" name="to" id="to" placeholder="recipient@example.com or user@example.onion" required> - </div> - - <div class="form-group"> - <label for="subject">Subject</label> - <input type="text" name="subject" id="subject" placeholder="Message subject"> - </div> - - <div class="form-group"> - <label for="newsgroups">Newsgroups (optional)</label> - <input type="text" name="newsgroups" id="newsgroups" placeholder="alt.anonymous.messages"> - </div> - - <div class="form-group"> - <label for="references">References (optional)</label> - <input type="text" name="references" id="references" placeholder="Message-ID for threading"> - </div> - - <div class="form-group"> - <label for="data">Message Body <span class="required">*</span></label> - <textarea name="data" id="data" placeholder="Your anonymous message..." required></textarea> - </div> - - <div class="form-group"> - <label for="copies">Number of Copies (1-3)</label> - <input type="number" name="copies" id="copies" value="1" min="1" max="3" required> - <small>Multiple copies increase reliability through redundancy</small> - </div> - - <div class="form-group"> - <div class="checkbox-group"> - </div> - - <button type="submit">🚀 SEND </button> + <button type="submit">🚀 Submit message</button> </form> </div> <script> - // Auto-enable Tor checkbox for .onion addresses - document.getElementById('to').addEventListener('input', function() { - const torCheckbox = document.getElementById('use_tor'); - if (this.value.includes('.onion')) { - torCheckbox.checked = true; - } - }); - - // Prevent selecting same remailer multiple times - document.querySelectorAll('.remailer-chain select').forEach(select => { - select.addEventListener('change', function() { - const selects = document.querySelectorAll('.remailer-chain select'); - const values = Array.from(selects).map(s => s.value).filter(v => v); - - selects.forEach(s => { - Array.from(s.options).forEach(option => { - if (option.value && values.includes(option.value) && option.value !== '*' && s !== select) { - option.style.color = '#666'; - } else { - option.style.color = 'var(--text-primary)'; - } + const to = document.getElementById('to'); + const newsgroups = document.getElementById('newsgroups'); + const updateDestinationValidation = () => { + to.required = newsgroups.value.trim() === ''; + }; + newsgroups.addEventListener('input', updateDestinationValidation); + updateDestinationValidation(); + + document.querySelectorAll('.remailer-chain select').forEach((select) => { + select.addEventListener('change', () => { + const selected = Array.from(document.querySelectorAll('.remailer-chain select')) + .map((item) => item.value) + .filter((value) => value !== '*'); + document.querySelectorAll('.remailer-chain select').forEach((item) => { + Array.from(item.options).forEach((option) => { + option.disabled = option.value !== '*' && + selected.includes(option.value) && + option.value !== item.value; }); }); }); |
