diff options
Diffstat (limited to 'nym_sender.php')
| -rw-r--r-- | nym_sender.php | 115 |
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]; +} |
