89 lines
3.9 KiB
JavaScript
89 lines
3.9 KiB
JavaScript
// 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 });
|
|
}
|