()
| 66 | * @throws {Error} When the default runtime directory cannot be created or protected |
| 67 | */ |
| 68 | export function startControlServer() { |
| 69 | const socketPath = resolveControlSocketPath(config); |
| 70 | |
| 71 | // For the default path, place the socket in a private per-user runtime |
| 72 | // directory and fail closed if it cannot be protected, so a local attacker |
| 73 | // cannot pre-create the predictable socket path or read the socket. A custom |
| 74 | // `controlSocket` is trusted to the operator's chosen location. |
| 75 | if (!config.controlSocket) { |
| 76 | prepareRuntimeDir(path.dirname(socketPath)); |
| 77 | } |
| 78 | |
| 79 | try { |
| 80 | if (fs.existsSync(socketPath)) { |
| 81 | fs.unlinkSync(socketPath); |
| 82 | } |
| 83 | } catch (error) { |
| 84 | log.warn(`Failed to remove stale control socket ${socketPath}: ${error.message}.`); |
| 85 | } |
| 86 | |
| 87 | const server = net.createServer((socket) => { |
| 88 | let buffer = ''; |
| 89 | |
| 90 | socket.on('data', (chunk) => { |
| 91 | buffer += chunk.toString('utf8'); |
| 92 | |
| 93 | let newlineIndex; |
| 94 | |
| 95 | while ((newlineIndex = buffer.indexOf('\n')) !== -1) { |
| 96 | const line = buffer.slice(0, newlineIndex).trim(); |
| 97 | buffer = buffer.slice(newlineIndex + 1); |
| 98 | |
| 99 | if (!line) { |
| 100 | continue; |
| 101 | } |
| 102 | |
| 103 | let response; |
| 104 | |
| 105 | try { |
| 106 | response = handleCommand(JSON.parse(line)); |
| 107 | } catch { |
| 108 | response = { ok: false, error: 'Malformed control request.' }; |
| 109 | } |
| 110 | |
| 111 | socket.write(`${JSON.stringify(response)}\n`); |
| 112 | } |
| 113 | }); |
| 114 | |
| 115 | // The CLI hangs up as soon as it has the response; ignore the reset. |
| 116 | socket.on('error', () => {}); |
| 117 | }); |
| 118 | |
| 119 | server.on('error', (error) => { |
| 120 | log.error(`Pool control socket error: ${error.message}.`); |
| 121 | }); |
| 122 | |
| 123 | server.listen(socketPath, () => { |
| 124 | try { |
| 125 | fs.chmodSync(socketPath, 0o600); |
no test coverage detected