summaryrefslogtreecommitdiffstats
path: root/nym_sender.php
diff options
context:
space:
mode:
Diffstat (limited to 'nym_sender.php')
-rw-r--r--nym_sender.php115
1 files changed, 115 insertions, 0 deletions
diff --git a/nym_sender.php b/nym_sender.php
new file mode 100644
index 0000000..221de17
--- /dev/null
+++ b/nym_sender.php
@@ -0,0 +1,115 @@
+<?php
+declare(strict_types=1);
+
+require_once __DIR__ . '/yamn_config.php';
+
+/**
+ * Send an already encrypted YAMN envelope through the local Rust Nym sender.
+ *
+ * The handoff callback runs after the complete request has been written to
+ * the sender's stdin, but before waiting for the sender's bounded flush grace
+ * period. This lets the HTTP layer finish a FastCGI response while the lock
+ * remains held until the sender exits.
+ */
+function sendNymEnvelope(
+ string $payload,
+ int $copies = 1,
+ ?string $selectedEntryAddress = null,
+ ?callable $onHandoff = null
+): array
+{
+ $sender = yamnConfig('YAMN_NYM_SENDER', '/usr/local/bin/yamn-nym-submit');
+ $recipient = yamnConfig('YAMN_NYM_RECIPIENT');
+ $storage = yamnConfig('YAMN_NYM_STORAGE', '/var/lib/yamnweb/nym-client');
+ $lockPath = yamnConfig('YAMN_NYM_LOCK', '/var/lib/yamnweb/nym-submit.lock');
+ $entryAddress = $selectedEntryAddress ?? yamnConfig('YAMN_ENTRY_ADDRESS');
+
+ if (!is_executable($sender)) {
+ return ['success' => false, 'error' => 'Nym sender is not available'];
+ }
+ if ($recipient === '' || strlen($recipient) > 512 || preg_match('/\s/', $recipient)) {
+ return ['success' => false, 'error' => 'Nym recipient is not configured'];
+ }
+ if ($entryAddress === '' || strlen($entryAddress) > 320 || preg_match('/[\r\n\0\s]/', $entryAddress)) {
+ return ['success' => false, 'error' => 'YAMN entry address is not configured'];
+ }
+ if ($copies !== 1) {
+ return ['success' => false, 'error' => 'Multiple copies are not enabled for Nym'];
+ }
+ if ($payload === '' || strlen($payload) > 65536 || str_contains($payload, "\0")) {
+ return ['success' => false, 'error' => 'Invalid encrypted YAMN envelope'];
+ }
+
+ try {
+ $input = json_encode([
+ 'entry_address' => $entryAddress,
+ 'payload' => $payload,
+ ], JSON_THROW_ON_ERROR);
+ } catch (JsonException $exception) {
+ return ['success' => false, 'error' => 'Unable to encode Nym request'];
+ }
+
+ $lock = @fopen($lockPath, 'c');
+ if ($lock === false) {
+ return ['success' => false, 'error' => 'Nym sender lock is not available'];
+ }
+ if (!flock($lock, LOCK_EX | LOCK_NB)) {
+ fclose($lock);
+ return ['success' => false, 'error' => 'Nym sender is busy, please retry later'];
+ }
+
+ $descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
+ $environment = $_ENV;
+ $environment['YAMN_NYM_RECIPIENT'] = $recipient;
+ $environment['YAMN_NYM_STORAGE'] = $storage;
+ $process = proc_open([$sender], $descriptors, $pipes, null, $environment);
+ if (!is_resource($process)) {
+ flock($lock, LOCK_UN);
+ fclose($lock);
+ return ['success' => false, 'error' => 'Unable to start Nym sender'];
+ }
+
+ $inputLength = strlen($input);
+ $offset = 0;
+ while ($offset < $inputLength) {
+ $written = fwrite($pipes[0], substr($input, $offset));
+ if ($written === false || $written === 0) {
+ break;
+ }
+ $offset += $written;
+ }
+ if ($offset !== $inputLength) {
+ fclose($pipes[0]);
+ proc_terminate($process);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($process);
+ flock($lock, LOCK_UN);
+ fclose($lock);
+ return ['success' => false, 'error' => 'Unable to hand envelope to Nym sender'];
+ }
+ fclose($pipes[0]);
+
+ if ($onHandoff !== null) {
+ $onHandoff();
+ }
+
+ $stdout = stream_get_contents($pipes[1]);
+ fclose($pipes[1]);
+ $stderr = stream_get_contents($pipes[2]);
+ fclose($pipes[2]);
+ $status = proc_close($process);
+ flock($lock, LOCK_UN);
+ fclose($lock);
+
+ if ($status !== 0) {
+ return ['success' => false, 'error' => 'Nym submission failed'];
+ }
+
+ $response = json_decode($stdout, true);
+ if (!is_array($response) || ($response['success'] ?? false) !== true) {
+ return ['success' => false, 'error' => 'Invalid response from Nym sender'];
+ }
+
+ return ['success' => true];
+}