summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGab <24553253+gabrix73@users.noreply.github.com>2025-10-17 03:45:49 +0200
committerGitHub <noreply@github.com>2025-10-17 03:45:49 +0200
commit2e32592d14457110c22fe3e0dd86f87f36f66227 (patch)
treea5f9d261d7af3eaae57281aa1fc7619a03cd49cf
parentf28f4ed6ce479533757ec078883a3bc01b39fa56 (diff)
downloadyamnweb-2e32592d14457110c22fe3e0dd86f87f36f66227.tar.gz
yamnweb-2e32592d14457110c22fe3e0dd86f87f36f66227.tar.xz
yamnweb-2e32592d14457110c22fe3e0dd86f87f36f66227.zip
Update send_email_with_tor.php
-rw-r--r--send_email_with_tor.php575
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.']);
+}
?>