// Row 280: fixture-only sshd startup. A TCP connect alone does not prove ownership. import { spawn } from 'node:child_process'; import { once } from 'node:events'; import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { connect } from 'node:net'; import { basename, join } from 'node:path'; export const ephemeralRange = () => { const text = readFileSync('/proc/sys/net/ipv4/ip_local_port_range', 'utf8').trim(); if (!/^\d+\s+\d+$/.test(text)) throw Error('invalid ephemeral port range'); const range = text.split(/\s+/).map(Number); if (range[0] < 1 || range[1] > 65535 || range[0] > range[1]) throw Error('invalid ephemeral port range'); return range; }; export function pickPort(rand = Math.random) { const [lo, hi] = ephemeralRange(); const below = Math.max(0, Math.min(lo, 65536) - 1024); const above = Math.max(0, 65535 - Math.max(hi, 1023)); const sample = rand(); if (!below && !above) throw Error('no scratch sshd port available'); if (!(sample >= 0 && sample < 1)) throw Error('invalid random sample'); const offset = Math.floor(sample * (below + above)); return offset < below ? 1024 + offset : Math.max(hi + 1, 1024) + offset - below; } const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const listening = port => new Promise(resolve => { const socket = connect(port, '127.0.0.1'); const done = result => { socket.destroy(); resolve(result); }; socket.setTimeout(200, () => done(false)); socket.on('connect', () => done(true)); socket.on('error', () => done(false)); }); const stop = async daemon => { if (daemon.pid === undefined || daemon.exitCode !== null || daemon.signalCode !== null) return; const exited = once(daemon, 'exit'); daemon.kill('SIGKILL'); await exited; }; export async function startScratchSshd(t, dir, config, { firstPort } = {}) { let lastLog; for (let attempt = 0; attempt < 5; attempt++) { const port = attempt === 0 && firstPort !== undefined ? firstPort : pickPort(); const configFile = join(dir, `sshd-${attempt}.conf`); lastLog = join(dir, `sshd-${attempt}.log`); writeFileSync(configFile, [`Port ${port}`, ...config, ''].join('\n')); writeFileSync(lastLog, ''); const daemon = spawn('/usr/bin/sshd', ['-D', '-f', configFile, '-E', lastLog], { stdio: 'ignore' }); t.after(() => stop(daemon)); const saveLog = () => { if (!process.env.MOSAIC_SSHD_EVIDENCE) return; const evidence = join(process.env.MOSAIC_SSHD_EVIDENCE, basename(dir)); mkdirSync(evidence, { recursive: true }); writeFileSync(join(evidence, basename(lastLog)), readFileSync(lastLog)); writeFileSync(join(evidence, basename(configFile)), readFileSync(configFile)); }; let spawnError; daemon.on('error', error => { spawnError = error; }); const deadline = Date.now() + 3000; while (Date.now() < deadline && !spawnError && daemon.exitCode === null && daemon.signalCode === null) { if (await listening(port) && daemon.exitCode === null && daemon.signalCode === null && readFileSync(lastLog, 'utf8').split('\n').some(line => (line === `Server listening on 127.0.0.1 port ${port}.` || line === `Server listening on 127.0.0.1 port ${port}.\r`))) { saveLog(); return port; } await sleep(30); } await stop(daemon); saveLog(); } throw Error(`scratch sshd did not start: ${lastLog}`); } // Long remote sleeps remain in flight for their original duration, but cannot outlive the fixture. export function installSleepStub(dir) { const script = `const fs = require("node:fs"); const end = Date.now() + Number(process.argv[2]) * 1000; const timer = setInterval(() => { if (!fs.existsSync(process.argv[1]) || Date.now() >= end) { clearInterval(timer); } }, 50);`; writeFileSync(join(dir, 'fake', 'sleep'), `#!/bin/sh case "$#:$1" in 1:30|1:60) exec '${process.execPath}' -e '${script}' '${dir}' "$1" ;; *) exec /usr/bin/sleep "$@" ;; esac `, { mode: 0o755 }); }