From 994071243fc80990cf09e820b671006e9bd31c76 Mon Sep 17 00:00:00 2001
From: Gab <24553253+gabrix73@users.noreply.github.com>
Date: Thu, 13 Aug 2026 14:04:30 +0200
Subject: Separate active YAMN code from Katzenpost PoC
---
.gitignore | 3 +
README.md | 35 ++-
about.html | 93 ++++++++
deploy/yamn-nym-ingress.env.example | 6 +
deploy/yamn-nym-ingress.service | 32 +++
download_remailers.php | 20 +-
index.php | 392 ++++++++++++--------------------
ingress/Cargo.toml | 16 ++
ingress/README.md | 32 +++
ingress/src/main.rs | 245 ++++++++++++++++++++
katzenpost/README.md | 30 +++
katzenpost/cmd/yamn-dispatcher/main.go | 115 ++++++++++
katzenpost/cmd/yamn-submit/main.go | 73 ++++++
katzenpost/dispatcher/codec.go | 29 +++
katzenpost/dispatcher/envelope.go | 45 ++++
katzenpost/dispatcher/envelope_test.go | 21 ++
katzenpost/dispatcher/frame.go | 84 +++++++
katzenpost/dispatcher/frame_test.go | 46 ++++
katzenpost/dispatcher/reassembler.go | 91 ++++++++
katzenpost/go.mod | 35 +++
katzenpost/go.sum | 63 ++++++
katzenpost_sender.php | 32 +++
nym/Cargo.toml | 14 ++
nym/README.md | 22 ++
nym/src/main.rs | 127 +++++++++++
nym_sender.php | 115 ++++++++++
yamn/README.md | 14 ++
yamn/cmd/yamn-encode/main.go | 55 +++++
yamn/encoder/encoder.go | 400 +++++++++++++++++++++++++++++++++
yamn/encoder/encoder_test.go | 115 ++++++++++
yamn/go.mod | 7 +
yamn/go.sum | 4 +
yamn_config.php | 15 ++
yamn_encoder.php | 42 ++++
34 files changed, 2199 insertions(+), 269 deletions(-)
create mode 100644 .gitignore
create mode 100644 about.html
create mode 100644 deploy/yamn-nym-ingress.env.example
create mode 100644 deploy/yamn-nym-ingress.service
create mode 100644 ingress/Cargo.toml
create mode 100644 ingress/README.md
create mode 100644 ingress/src/main.rs
create mode 100644 katzenpost/README.md
create mode 100644 katzenpost/cmd/yamn-dispatcher/main.go
create mode 100644 katzenpost/cmd/yamn-submit/main.go
create mode 100644 katzenpost/dispatcher/codec.go
create mode 100644 katzenpost/dispatcher/envelope.go
create mode 100644 katzenpost/dispatcher/envelope_test.go
create mode 100644 katzenpost/dispatcher/frame.go
create mode 100644 katzenpost/dispatcher/frame_test.go
create mode 100644 katzenpost/dispatcher/reassembler.go
create mode 100644 katzenpost/go.mod
create mode 100644 katzenpost/go.sum
create mode 100644 katzenpost_sender.php
create mode 100644 nym/Cargo.toml
create mode 100644 nym/README.md
create mode 100644 nym/src/main.rs
create mode 100644 nym_sender.php
create mode 100644 yamn/README.md
create mode 100644 yamn/cmd/yamn-encode/main.go
create mode 100644 yamn/encoder/encoder.go
create mode 100644 yamn/encoder/encoder_test.go
create mode 100644 yamn/go.mod
create mode 100644 yamn/go.sum
create mode 100644 yamn_config.php
create mode 100644 yamn_encoder.php
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e625624
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/yamn/yamn-encode
+/katzenpost/yamn-dispatcher
+/katzenpost/yamn-submit
diff --git a/README.md b/README.md
index 1cbaf8f..dd4aca0 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,39 @@
-# Tor before Yamn Anonymous Remailers Network Gateway
+# YAMN Anonymous Remailers Network Gateway


-
+

-A hardened web interface for [YAMN (Yet Another Mix Network)](https://github.com/crooks/yamn) implementing military-grade operational security with mandatory Tor routing and zero metadata retention.
+A hardened web interface for [YAMN (Yet Another Mix Network)](https://github.com/crooks/yamn). The active message transport uses a local Rust sender built with the Nym SDK. Tor is retained only for downloading remailer statistics and public key material.
+
+## Current active path
+
+```text
+Browser -> PHP yamnweb -> Rust yamn-nym-submit -> Nym ingress service -> YAMN
+ ^
+ |
+ already encrypted YAMN envelope only
+```
+
+The Nym sender is send-only and receives only an already encrypted envelope.
+`YAMN_NYM_RECIPIENT` must identify a Nym ingress service that knows how to
+forward the envelope to YAMN. A normal SMTP address is not a valid replacement.
+The active YAMN encoder is maintained in `yamn/`. Katzenpost remains in
+`katzenpost/` as a separate standby PoC and is not on the active path.
+
+The YAMN encoder is built from `yamn/`:
+
+```bash
+cd yamn
+GOCACHE=/tmp/yamnweb-go-build go test ./...
+go build -o /usr/local/bin/yamn-encode ./cmd/yamn-encode
+```
+
+The PHP deployment must set `YAMN_ENCODER` and `YAMN_PUBRING`. The keyring is
+read locally and the encoder emits the standard YAMN armored envelope.
+
+The legacy Tor sending instructions below are retained as historical reference.
## 🎯 Mission Statement
@@ -757,4 +785,3 @@ Contributions welcome! Please:
---
**Status:** ✅ Operational | **Last Updated:** October 2025 | **Version:** 2.0
-
diff --git a/about.html b/about.html
new file mode 100644
index 0000000..fd7e52e
--- /dev/null
+++ b/about.html
@@ -0,0 +1,93 @@
+
+
+
+
+ ← Back to the interface
+ YAMN Web architecture and operation
+ YAMN Web is a send-only interface for preparing messages for delivery through the YAMN remailer network. The same composer handles email delivery and Usenet publication. The application is available through HTTPS and the existing Onion service.
+
+
+ Complete route
+ Browser
+ ↓ HTTPS or Onion
+PHP YAMN Web
+ ↓ local request, without a temporary message file
+YAMN encoder
+ ↓ encrypted YAMN envelope
+Rust Nym sender
+ ↓ Nym mixnet
+Nym ingress on kvara
+ ↓ Tor, forwards only the envelope to the selected Entry
+Selected YAMN Entry
+ ↓ YAMN packet layers
+Selected YAMN Middle
+ ↓ YAMN packet layers
+Selected YAMN Exit
+ ↓ SMTP delivery
+mail2news@mail2news.tcpreset.net
+ ↓ Mail2News conversion
+news.tcpreset.net → misc.test
+ The Nym sender does not receive the body, subject, or final recipient as separate parameters. It receives only the encrypted envelope produced by the YAMN encoder.
+
+
+ HTTPS and Onion service
+ The public HTTPS address remains https://yamnweb.virebent.art. The existing Onion address remains http://ai63yqfo2wu2j3ey7nmatyel52jjvcmfg4e4kkxp2y7x3bc6nue6czyd.onion:4343/, including its configured port. This interface neither generates nor changes the Onion address. Both access points serve the same application directory.
+ The HTTPS virtual host does not retain access logs. The application may emit technical PHP or service errors, but it does not intentionally log message contents.
+
+ The YAMN chain
+ The chain selected in the form has three roles:
+
+ - Entry: receives the YAMN packet from the transport route and removes the first layer.
+ - Middle: processes the intermediate hop and forwards the packet.
+ - Exit: removes the final layer and delivers the message through SMTP. For Usenet articles, the SMTP recipient is the service-configured Mail-to-News gateway.
+
+ The * option randomly selects an available remailer from the local list. This is intentional because remailer availability and reliability change over time.
+
+ Destinations
+ The composer uses the sender, optional Reply-To address, subject, and body for every message. Enter an email recipient for email delivery. To publish on Usenet, leave the email recipient empty and enter a Newsgroup; the application then adds the configured Mail-to-News recipient that converts the message leaving the remailer chain into an NNTP article.
+ Acceptance by the Nym transport confirms only that the route has started. Each remailer holds the packet in a queue for a variable delay, so delivery or Usenet publication may take several hours. This send-only interface does not read the final destination and cannot confirm publication itself.
+ References is optional and contains one or more Message-IDs used to link a new article to an existing thread. It does not enable reply retrieval or message fetching.
+
+ What is stored
+
+ - The public YAMN keyring and remailer list are stored as technical service data.
+ - The Nym client stores only the technical state required for its transport identity.
+ - The message text and encrypted envelope are not written to temporary files by the submission route.
+ - The web session stores the CSRF token and the minimum state required for the POST flow.
+ - The browser may retain cookies or typed data according to its own settings.
+
+
+
+ Important limitations
+ YAMN Web provides no inbox, fetch, message viewing, AEC, or reply retrieval. A message is accepted when the Nym sender accepts its transmission, but this is not a final delivery receipt.
+ During maintenance, the Nym ingress may operate in dry-run mode. In that mode it validates received packets without forwarding them to the SMTP remailer.
+
+
+ Responsible use
+ Check the recipient or newsgroup, chain, and References before submission. Do not include passwords, private keys, or information you do not want to transmit through the remailer network. Anonymity also depends on message content, timing, and sender behavior.
+ Back to message composition
+
+
+
diff --git a/deploy/yamn-nym-ingress.env.example b/deploy/yamn-nym-ingress.env.example
new file mode 100644
index 0000000..029c506
--- /dev/null
+++ b/deploy/yamn-nym-ingress.env.example
@@ -0,0 +1,6 @@
+YAMN_NYM_INGRESS_STORAGE=/var/lib/yamnweb/nym-ingress
+YAMN_TOR_SOCKS=127.0.0.1:9050
+YAMN_INGRESS_FROM=<>
+YAMN_DRY_RUN=false
+# Replace this list with the current, reviewed YAMN entry addresses.
+YAMN_ALLOWED_ENTRY_ADDRESSES=yamn@virebent.art,yamn@mixmin.net,yamn2@mixmin.net,yamn3@mixmin.net,yamn4@mixmin.net,yamn@gronk.ch,yamn@eocto.net,yamn@middleman.remailer.online,yamn@milton.redmv.net,yamn@yamn.paranoici.org,yamn@shalo.ca,yamn@tnetconsulting.net,yamn@yeahno.net
diff --git a/deploy/yamn-nym-ingress.service b/deploy/yamn-nym-ingress.service
new file mode 100644
index 0000000..8e51a03
--- /dev/null
+++ b/deploy/yamn-nym-ingress.service
@@ -0,0 +1,32 @@
+[Unit]
+Description=YAMN Nym ingress
+Documentation=https://docs.rs/nym-sdk/latest/nym_sdk/
+Wants=network-online.target
+After=network-online.target tor.service
+
+[Service]
+Type=simple
+User=yamn-ingress
+Group=yamn-ingress
+EnvironmentFile=/etc/yamnweb/yamn-nym-ingress.env
+ExecStart=/usr/local/bin/yamn-nym-ingress
+Restart=on-failure
+RestartSec=10s
+NoNewPrivileges=true
+PrivateTmp=true
+PrivateDevices=true
+ProtectHome=true
+ProtectSystem=strict
+ReadWritePaths=/var/lib/yamnweb/nym-ingress
+RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
+RestrictSUIDSGID=true
+LockPersonality=true
+MemoryDenyWriteExecute=true
+CapabilityBoundingSet=
+AmbientCapabilities=
+StandardOutput=journal
+StandardError=journal
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/download_remailers.php b/download_remailers.php
index 3b40607..d601ac6 100644
--- a/download_remailers.php
+++ b/download_remailers.php
@@ -61,9 +61,9 @@ class SecureRemailerDownloader {
/**
* Download remailer stats (mlist.txt)
*/
- private function downloadStats() {
+ private function downloadStats(bool $force = false) {
// Check if update is needed (with randomized interval)
- if (!$this->needsUpdate($this->remailersFile)) {
+ if (!$force && !$this->needsUpdate($this->remailersFile)) {
$this->log("Stats update not needed yet");
return true;
}
@@ -109,9 +109,9 @@ class SecureRemailerDownloader {
/**
* Download public keyring (pubring.mix)
*/
- private function downloadKeyring() {
+ private function downloadKeyring(bool $force = false) {
// Check if update is needed (with randomized interval)
- if (!$this->needsUpdate($this->pubringFile)) {
+ if (!$force && !$this->needsUpdate($this->pubringFile)) {
$this->log("Keyring update not needed yet");
return true;
}
@@ -538,8 +538,8 @@ class SecureRemailerDownloader {
* Force update of both stats and keyring
*/
public function forceUpdate() {
- $statsResult = $this->downloadStats();
- $keyringResult = $this->downloadKeyring();
+ $statsResult = $this->downloadStats(true);
+ $keyringResult = $this->downloadKeyring(true);
return $statsResult && $keyringResult;
}
@@ -572,12 +572,4 @@ class SecureRemailerDownloader {
}
}
-// Usage
-try {
- $downloader = new SecureRemailerDownloader();
- $downloader->downloadRemailers();
-} catch (Exception $e) {
- error_log("Remailer download failed: " . $e->getMessage());
-}
?>
-
diff --git a/index.php b/index.php
index 6b112c8..64c47d6 100644
--- a/index.php
+++ b/index.php
@@ -1,6 +1,6 @@
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') {
}
?>
-
+