summaryrefslogtreecommitdiffstats
path: root/index.php
diff options
context:
space:
mode:
Diffstat (limited to 'index.php')
-rw-r--r--index.php392
1 files changed, 141 insertions, 251 deletions
diff --git a/index.php b/index.php
index 6b112c8..64c47d6 100644
--- a/index.php
+++ b/index.php
@@ -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 &lt;anon@example.org&gt;" 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="&lt;message-id@example.org&gt;"><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;
});
});
});