diff options
| author | Gab <24553253+gabrix73@users.noreply.github.com> | 2025-10-17 03:45:49 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-17 03:45:49 +0200 |
| commit | 2e32592d14457110c22fe3e0dd86f87f36f66227 (patch) | |
| tree | a5f9d261d7af3eaae57281aa1fc7619a03cd49cf | |
| parent | f28f4ed6ce479533757ec078883a3bc01b39fa56 (diff) | |
| download | yamnweb-2e32592d14457110c22fe3e0dd86f87f36f66227.tar.gz yamnweb-2e32592d14457110c22fe3e0dd86f87f36f66227.tar.xz yamnweb-2e32592d14457110c22fe3e0dd86f87f36f66227.zip | |
Update send_email_with_tor.php
| -rw-r--r-- | send_email_with_tor.php | 575 |
1 files changed, 395 insertions, 180 deletions
diff --git a/send_email_with_tor.php b/send_email_with_tor.php index c52de23..8bf6d2e 100644 --- a/send_email_with_tor.php +++ b/send_email_with_tor.php @@ -1,229 +1,444 @@ <?php -// Enable error display during development - comment out in production -// ini_set('display_errors', 1); -// ini_set('display_startup_errors', 1); -// error_reporting(E_ALL); +/** + * Secure YAMN Email Sender + * Implements all security requirements: + * - No metadata retention + * - Replay protection + * - Adaptive padding + * - Randomized delays + * - Secure file handling + * - Tor enforcement + */ -include 'download_remailers.php'; -include 'tor_extension.php'; +// Disable error display in production +ini_set('display_errors', 0); +ini_set('display_startup_errors', 0); +error_reporting(0); -if ($_SERVER['REQUEST_METHOD'] == 'POST') { - // Retrieve form data - $entryRemailer = isset($_POST['entry_remailer']) ? $_POST['entry_remailer'] : ''; - $middleRemailer = isset($_POST['middle_remailer']) ? $_POST['middle_remailer'] : ''; - $exitRemailer = isset($_POST['exit_remailer']) ? $_POST['exit_remailer'] : ''; - $from = isset($_POST['from']) ? $_POST['from'] : ''; - $replyTo = isset($_POST['reply_to']) ? $_POST['reply_to'] : ''; - $to = isset($_POST['to']) ? $_POST['to'] : ''; - $subject = isset($_POST['subject']) ? $_POST['subject'] : ''; - $newsgroups = isset($_POST['newsgroups']) ? $_POST['newsgroups'] : ''; - $references = isset($_POST['references']) ? $_POST['references'] : ''; - $data = isset($_POST['data']) ? $_POST['data'] : ''; - $copies = isset($_POST['copies']) ? intval($_POST['copies']) : 1; - - // Force Tor usage regardless of form input - $useTor = true; - - // Validation - $errors = []; - - // Required fields validation - if (empty($to)) { - $errors[] = "Recipient (To) field is required"; - } +require_once 'download_remailers_secure.php'; +require_once 'tor_extension_secure.php'; + +class SecureYamnSender { - if (empty($from)) { - $errors[] = "Sender (From) field is required"; - } + private $secureDir = '/opt/yamn-data/pool'; + private $replayCache = '/opt/yamn-data/cache/replay_cache.db'; + private $replayCacheTTL = 604800; // 7 days - if (empty($subject)) { - $errors[] = "Subject field is required"; - } + // Padding sizes (in bytes) + private $paddingSizes = [512, 1024, 2048, 4096, 8192, 16384, 32768]; - // Validate number of copies - if ($copies < 1 || $copies > 3) { - $errors[] = "Number of copies must be between 1 and 3"; - } + // Delay ranges (seconds) + private $minDelay = 10; + private $maxDelay = 120; - // Check if Tor is available - if (!isTorAvailable()) { - $errors[] = "Tor is required but not available on the system. Please install and start Tor service."; + public function __construct() { + // Ensure secure directories exist + if (!file_exists($this->secureDir)) { + mkdir($this->secureDir, 0700, true); + } + + // Initialize replay cache DB + $this->initReplayCache(); } - // If no errors, proceed with sending - if (empty($errors)) { - // Build the remailer chain - $chain = "$entryRemailer,$middleRemailer,$exitRemailer"; + /** + * Main send function + */ + public function sendEmail($postData) { + // Extract and validate input (no storage of raw data) + $validated = $this->validateInput($postData); + if ($validated['errors']) { + return ['success' => false, 'errors' => $validated['errors']]; + } - // Build headers - $headers = "Content-Type: text/plain; charset=utf-8\n"; - $headers .= "Content-Transfer-Encoding: 8bit\n"; - $headers .= "MIME-Version: 1.0\n"; + $data = $validated['data']; + + // Generate message-ID for replay protection + $messageId = $this->generateMessageId($data); - if (!empty($references)) { - $headers .= "References: $references\n"; + // Check for replay attack + if ($this->isReplay($messageId)) { + return [ + 'success' => false, + 'errors' => ['This message has already been sent recently'] + ]; } // Build message content - $messageContent = $headers . "From: $from\n"; + $messageContent = $this->buildMessage($data); - if (!empty($replyTo)) { - $messageContent .= "Reply-To: $replyTo\n"; + // Apply adaptive padding + $paddedMessage = $this->applyAdaptivePadding($messageContent); + + // Create secure temporary file + $messageFile = $this->createSecureMessageFile($paddedMessage); + + if (!$messageFile) { + return [ + 'success' => false, + 'errors' => ['Failed to create secure message file'] + ]; } - $messageContent .= "To: $to\nSubject: $subject\n"; + // Random delay to prevent timing correlation + $this->randomDelay(); - if (!empty($newsgroups)) { - $messageContent .= "Newsgroups: $newsgroups\n"; + // Build chain + $chain = implode(',', [ + $data['entry_remailer'], + $data['middle_remailer'], + $data['exit_remailer'] + ]); + + // Send via YAMN with Tor (always enforced) + try { + $result = sendYamnEmailSecure( + $chain, + $data['copies'], + $messageFile, + true // Force Tor + ); + + // Store message-ID in replay cache only if successful + if ($result['success']) { + $this->storeMessageId($messageId); + } + + return $result; + + } catch (Exception $e) { + // No detailed error logging + return [ + 'success' => false, + 'errors' => ['Failed to send message'] + ]; + } finally { + // ALWAYS secure delete the file + $this->secureDeleteFile($messageFile); } + } + + /** + * Validate input without storing sensitive data + */ + private function validateInput($post) { + $errors = []; + $data = []; - $messageContent .= "\n$data"; + // Required fields + $required = ['to', 'from', 'subject', 'entry_remailer', 'middle_remailer', 'exit_remailer']; + foreach ($required as $field) { + if (empty($post[$field])) { + $errors[] = ucfirst(str_replace('_', ' ', $field)) . " is required"; + } else { + $data[$field] = $post[$field]; + } + } + + // Optional fields + $optional = ['reply_to', 'newsgroups', 'references', 'data']; + foreach ($optional as $field) { + $data[$field] = isset($post[$field]) ? $post[$field] : ''; + } + + // Validate copies + $copies = isset($post['copies']) ? intval($post['copies']) : 1; + if ($copies < 1 || $copies > 3) { + $errors[] = "Number of copies must be between 1 and 3"; + } + $data['copies'] = $copies; + + // Check Tor availability + if (!isTorAvailable()) { + $errors[] = "Tor is required but not available"; + } - // Create a unique filename for the message - $messageFile = '/var/www/yamnweb/message_' . time() . '_' . rand(1000, 9999) . '.txt'; + // Email validation + if (!empty($data['to']) && !filter_var($data['to'], FILTER_VALIDATE_EMAIL)) { + $errors[] = "Invalid recipient email address"; + } - $write_success = file_put_contents($messageFile, $messageContent); - if ($write_success === false) { - echo "Error: Unable to write to message file. Check permissions."; - exit; + if (!empty($data['from']) && !filter_var($data['from'], FILTER_VALIDATE_EMAIL)) { + $errors[] = "Invalid sender email address"; } - // Log the operation - $log_entry = date('Y-m-d H:i:s') . " - Sending message: from $from to $to using Tor\n"; - file_put_contents('/var/www/yamnweb/send_log.txt', $log_entry, FILE_APPEND); + return [ + 'errors' => $errors, + 'data' => $data + ]; + } + + /** + * Generate deterministic message-ID for replay detection + */ + private function generateMessageId($data) { + // Use hash of critical fields (not entire message to allow minor variations) + $critical = $data['to'] . '|' . + $data['from'] . '|' . + $data['subject'] . '|' . + substr($data['data'], 0, 100); // First 100 chars of body - // Send the email with Tor (always enabled) + return hash('sha256', $critical); + } + + /** + * Initialize SQLite DB for replay protection + */ + private function initReplayCache() { try { - $result = sendYamnEmail($chain, $copies, $messageFile, $useTor); + $db = new SQLite3($this->replayCache); + $db->exec('CREATE TABLE IF NOT EXISTS message_cache ( + message_id TEXT PRIMARY KEY, + timestamp INTEGER NOT NULL + )'); + + // Create index for faster lookups + $db->exec('CREATE INDEX IF NOT EXISTS idx_timestamp ON message_cache(timestamp)'); + + $db->close(); } catch (Exception $e) { - echo "Error sending email: " . $e->getMessage(); - exit; - } - - // Clean up message file after sending - if (file_exists($messageFile)) { - unlink($messageFile); - } - - // Display result to user - if ($result['success']) { - echo "<!DOCTYPE html> - <html lang='en'> - <head> - <meta charset='UTF-8'> - <meta name='viewport' content='width=device-width, initial-scale=1.0'> - <title>Email Sent - YAMN Web Interface</title> - <link rel='stylesheet' href='styles.css'> - </head> - <body> - <div class='success-message'> - <h2>Email sent successfully!</h2> - <p>Your email has been added to the YAMN sending queue.</p> - <p>As per system policy, Tor was used for enhanced anonymity.</p> - <a href='index.php' class='button'>Return to Home</a> - </div> - </body> - </html>"; - } else { - echo "<!DOCTYPE html> - <html lang='en'> - <head> - <meta charset='UTF-8'> - <meta name='viewport' content='width=device-width, initial-scale=1.0'> - <title>Email Error - YAMN Web Interface</title> - <link rel='stylesheet' href='styles.css'> - <style> - pre { - background-color: #f8f8f8; - padding: 10px; - border-radius: 4px; - overflow-x: auto; - font-size: 12px; - text-align: left; - } - </style> - </head> - <body> - <div class='error-message'> - <h2>Error sending email</h2> - <p>An error occurred while processing your request.</p> - <p>Error details:</p> - <pre>"; - echo "===== ADD TO POOL COMMAND =====\n"; - echo htmlspecialchars($result['add_to_pool']['command']) . "\n\n"; - echo "===== ADD TO POOL OUTPUT =====\n"; - echo htmlspecialchars(implode("\n", $result['add_to_pool']['output'])) . "\n\n"; - echo "===== ADD TO POOL RETURN CODE =====\n"; - echo $result['add_to_pool']['return_var'] . "\n\n"; + // Fail silently - replay protection optional + } + } + + /** + * Check if message is a replay + */ + private function isReplay($messageId) { + try { + $db = new SQLite3($this->replayCache); - if (isset($result['send']) && $result['add_to_pool']['return_var'] == 0) { - echo "===== SEND COMMAND =====\n"; - echo htmlspecialchars($result['send']['command']) . "\n\n"; - echo "===== SEND OUTPUT =====\n"; - echo htmlspecialchars(implode("\n", $result['send']['output'])) . "\n\n"; - echo "===== SEND RETURN CODE =====\n"; - echo $result['send']['return_var'] . "\n"; - } - echo "</pre> - <p>Please check system logs for more details.</p> - <a href='index.php' class='button'>Return to Home</a> - </div> - </body> - </html>"; + // Clean old entries + $expiration = time() - $this->replayCacheTTL; + $db->exec("DELETE FROM message_cache WHERE timestamp < $expiration"); - // Detailed error logging - $logEntry = "==== ERROR LOG ====\n"; - $logEntry .= "Date: " . date('Y-m-d H:i:s') . "\n"; - $logEntry .= "From: $from\n"; - $logEntry .= "To: $to\n"; - $logEntry .= "Subject: $subject\n"; - if (!empty($newsgroups)) { - $logEntry .= "Newsgroups: $newsgroups\n"; - } - $logEntry .= "Chain: $chain\n"; - $logEntry .= "Copies: $copies\n"; - $logEntry .= "Using Tor: Yes\n"; - $logEntry .= "\n=== ADD TO POOL ===\n"; - $logEntry .= "Command: " . $result['add_to_pool']['command'] . "\n"; - $logEntry .= "Output: " . implode("\n", $result['add_to_pool']['output']) . "\n"; - $logEntry .= "Return Code: " . $result['add_to_pool']['return_var'] . "\n"; + // Check if message-ID exists + $stmt = $db->prepare('SELECT message_id FROM message_cache WHERE message_id = :id'); + $stmt->bindValue(':id', $messageId, SQLITE3_TEXT); + $result = $stmt->execute(); - if (isset($result['send'])) { - $logEntry .= "\n=== SEND ===\n"; - $logEntry .= "Command: " . $result['send']['command'] . "\n"; - $logEntry .= "Output: " . implode("\n", $result['send']['output']) . "\n"; - $logEntry .= "Return Code: " . $result['send']['return_var'] . "\n"; - } + $exists = $result->fetchArray() !== false; - $logEntry .= "----------------------------------------\n"; - error_log($logEntry, 3, "/var/www/yamnweb/email_errors.log"); + $db->close(); + return $exists; + + } catch (Exception $e) { + // If cache fails, allow message (better than blocking legitimate use) + return false; } - } else { - // Display validation errors - echo "<!DOCTYPE html> + } + + /** + * Store message-ID in cache + */ + private function storeMessageId($messageId) { + try { + $db = new SQLite3($this->replayCache); + + $stmt = $db->prepare('INSERT OR IGNORE INTO message_cache (message_id, timestamp) VALUES (:id, :ts)'); + $stmt->bindValue(':id', $messageId, SQLITE3_TEXT); + $stmt->bindValue(':ts', time(), SQLITE3_INTEGER); + $stmt->execute(); + + $db->close(); + } catch (Exception $e) { + // Fail silently + } + } + + /** + * Build message with proper headers + */ + private function buildMessage($data) { + $headers = "Content-Type: text/plain; charset=utf-8\n"; + $headers .= "Content-Transfer-Encoding: 8bit\n"; + $headers .= "MIME-Version: 1.0\n"; + + if (!empty($data['references'])) { + $headers .= "References: {$data['references']}\n"; + } + + $message = $headers . "From: {$data['from']}\n"; + + if (!empty($data['reply_to'])) { + $message .= "Reply-To: {$data['reply_to']}\n"; + } + + $message .= "To: {$data['to']}\n"; + $message .= "Subject: {$data['subject']}\n"; + + if (!empty($data['newsgroups'])) { + $message .= "Newsgroups: {$data['newsgroups']}\n"; + } + + $message .= "\n{$data['data']}"; + + return $message; + } + + /** + * Apply adaptive padding to prevent size correlation + */ + private function applyAdaptivePadding($message) { + $currentSize = strlen($message); + + // Find next padding size + $targetSize = $currentSize; + foreach ($this->paddingSizes as $size) { + if ($size >= $currentSize) { + $targetSize = $size; + break; + } + } + + // If message is larger than max padding size, round up to next 32KB + if ($currentSize >= max($this->paddingSizes)) { + $targetSize = ceil($currentSize / 32768) * 32768; + } + + // Generate random padding + $paddingSize = $targetSize - $currentSize; + if ($paddingSize > 0) { + // Use printable random characters for padding + $padding = "\n\n--- Padding ---\n"; + $padding .= base64_encode(random_bytes($paddingSize - strlen($padding))); + $message .= $padding; + } + + return $message; + } + + /** + * Create secure temporary file + */ + private function createSecureMessageFile($content) { + // Generate cryptographically secure random filename + $filename = bin2hex(random_bytes(32)) . '.tmp'; + $filepath = $this->secureDir . '/' . $filename; + + // Write with exclusive lock and restrictive permissions + $fp = fopen($filepath, 'w'); + if (!$fp) { + return false; + } + + flock($fp, LOCK_EX); + fwrite($fp, $content); + flock($fp, LOCK_UN); + fclose($fp); + + // Set restrictive permissions + chmod($filepath, 0600); + + return $filepath; + } + + /** + * Random delay to prevent timing attacks + */ + private function randomDelay() { + $delay = random_int($this->minDelay, $this->maxDelay); + sleep($delay); + } + + /** + * Secure file deletion (DoD 5220.22-M standard) + */ + private function secureDeleteFile($filepath) { + if (!file_exists($filepath)) { + return; + } + + $filesize = filesize($filepath); + + // Overwrite with random data 3 times + for ($i = 0; $i < 3; $i++) { + $fp = fopen($filepath, 'w'); + if ($fp) { + fwrite($fp, random_bytes($filesize)); + fclose($fp); + } + } + + // Overwrite with zeros + $fp = fopen($filepath, 'w'); + if ($fp) { + fwrite($fp, str_repeat("\0", $filesize)); + fclose($fp); + } + + // Finally delete + unlink($filepath); + } + + /** + * Render success response (minimal info) + */ + public function renderSuccess() { + return "<!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> - <title>Form Error - YAMN Web Interface</title> + <title>Message Queued</title> <link rel='stylesheet' href='styles.css'> </head> <body> - <div class='error-message'> - <h2>Form Errors</h2> - <ul>"; + <div class='success-message'> + <h2>Message queued successfully</h2> + <p>Your message has been added to the anonymous sending queue.</p> + <a href='index.php' class='button'>Return</a> + </div> + </body> + </html>"; + } + + /** + * Render error response (minimal info) + */ + public function renderError($errors) { + $errorList = ''; foreach ($errors as $error) { - echo "<li>" . htmlspecialchars($error) . "</li>"; + $errorList .= "<li>" . htmlspecialchars($error) . "</li>"; } - echo "</ul> - <a href='javascript:history.back()' class='button'>Back to Form</a> + + return "<!DOCTYPE html> + <html lang='en'> + <head> + <meta charset='UTF-8'> + <meta name='viewport' content='width=device-width, initial-scale=1.0'> + <title>Error</title> + <link rel='stylesheet' href='styles.css'> + </head> + <body> + <div class='error-message'> + <h2>Unable to process request</h2> + <ul>$errorList</ul> + <a href='javascript:history.back()' class='button'>Back</a> </div> </body> </html>"; } -} else { - // If not a POST request, redirect to home +} + +// Main execution +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { header("Location: index.php"); exit; } + +try { + $sender = new SecureYamnSender(); + $result = $sender->sendEmail($_POST); + + if ($result['success']) { + echo $sender->renderSuccess(); + } else { + echo $sender->renderError($result['errors']); + } + +} catch (Exception $e) { + // Generic error - no details leaked + echo (new SecureYamnSender())->renderError(['An error occurred. Please try again.']); +} ?> |
