1 | 'use strict';
|
2 |
|
3 | const assert = require('assert');
|
4 | const { inspect } = require('util');
|
5 |
|
6 | const {
|
7 | fixture,
|
8 | mustCall,
|
9 | mustCallAtLeast,
|
10 | setup: setup_,
|
11 | } = require('./common.js');
|
12 |
|
13 | const debug = false;
|
14 |
|
15 | const clientCfg = { username: 'foo', password: 'bar' };
|
16 | const serverCfg = { hostKeys: [ fixture('ssh_host_rsa_key') ] };
|
17 |
|
18 | {
|
19 | const { client, server } = setup_(
|
20 | 'Exec with OpenSSH agent forwarding',
|
21 | {
|
22 | client: {
|
23 | ...clientCfg,
|
24 | agent: '/path/to/agent',
|
25 | },
|
26 | server: serverCfg,
|
27 |
|
28 | debug,
|
29 | },
|
30 | );
|
31 |
|
32 | server.on('connection', mustCall((conn) => {
|
33 | conn.on('authentication', mustCall((ctx) => {
|
34 | ctx.accept();
|
35 | })).on('ready', mustCall(() => {
|
36 | conn.on('session', mustCall((accept, reject) => {
|
37 | let sawAuthAgent = false;
|
38 | accept().on('auth-agent', mustCall((accept, reject) => {
|
39 | sawAuthAgent = true;
|
40 | accept && accept();
|
41 | })).on('exec', mustCall((accept, reject, info) => {
|
42 | assert(sawAuthAgent, 'Expected auth-agent before exec');
|
43 | assert(info.command === 'foo --bar',
|
44 | `Wrong exec command: ${info.command}`);
|
45 | const stream = accept();
|
46 | stream.exit(100);
|
47 | stream.end();
|
48 | conn.end();
|
49 | }));
|
50 | }));
|
51 | }));
|
52 | }));
|
53 |
|
54 | client.on('ready', mustCall(() => {
|
55 | client.exec('foo --bar', { agentForward: true }, mustCall((err, stream) => {
|
56 | assert(!err, `Unexpected exec error: ${err}`);
|
57 | stream.resume();
|
58 | }));
|
59 | }));
|
60 | }
|
61 |
|
62 | {
|
63 | const { client, server } = setup_(
|
64 | 'OpenSSH forwarded UNIX socket connection',
|
65 | {
|
66 | client: clientCfg,
|
67 | server: {
|
68 | ...serverCfg,
|
69 | ident: 'OpenSSH_7.1',
|
70 | },
|
71 |
|
72 | debug,
|
73 | },
|
74 | );
|
75 |
|
76 | const socketPath = '/foo';
|
77 | const events = [];
|
78 | const expected = [
|
79 | ['client', 'openssh_forwardInStreamLocal'],
|
80 | ['server', 'streamlocal-forward@openssh.com', { socketPath }],
|
81 | ['client', 'forward callback'],
|
82 | ['client', 'unix connection', { socketPath }],
|
83 | ['client', 'socket data', '1'],
|
84 | ['server', 'socket data', '2'],
|
85 | ['client', 'socket end'],
|
86 | ['server', 'cancel-streamlocal-forward@openssh.com', { socketPath }],
|
87 | ['client', 'cancel callback']
|
88 | ];
|
89 |
|
90 | server.on('connection', mustCall((conn) => {
|
91 | conn.on('authentication', mustCall((ctx) => {
|
92 | ctx.accept();
|
93 | })).on('ready', mustCall(() => {
|
94 | conn.once('request', mustCall((accept, reject, name, info) => {
|
95 | events.push(['server', name, info]);
|
96 | assert(name === 'streamlocal-forward@openssh.com',
|
97 | `Wrong request name: ${name}`);
|
98 | accept();
|
99 | conn.openssh_forwardOutStreamLocal(socketPath,
|
100 | mustCall((err, ch) => {
|
101 | assert(!err, `Unexpected error: ${err}`);
|
102 | ch.write('1');
|
103 | ch.on('data', mustCallAtLeast((data) => {
|
104 | events.push(['server', 'socket data', data.toString()]);
|
105 | ch.close();
|
106 | }));
|
107 | }));
|
108 |
|
109 | conn.on('request', mustCall((accept, reject, name, info) => {
|
110 | events.push(['server', name, info]);
|
111 | assert(name === 'cancel-streamlocal-forward@openssh.com',
|
112 | `Wrong request name: ${name}`);
|
113 | accept();
|
114 | }));
|
115 | }));
|
116 | }));
|
117 | }));
|
118 |
|
119 | client.on('ready', mustCall(() => {
|
120 |
|
121 | events.push(['client', 'openssh_forwardInStreamLocal']);
|
122 | client.openssh_forwardInStreamLocal(socketPath, mustCall((err) => {
|
123 | assert(!err, `Unexpected error: ${err}`);
|
124 | events.push(['client', 'forward callback']);
|
125 | }));
|
126 | client.on('unix connection', mustCall((info, accept, reject) => {
|
127 | events.push(['client', 'unix connection', info]);
|
128 | const stream = accept();
|
129 | stream.on('data', mustCallAtLeast((data) => {
|
130 | events.push(['client', 'socket data', data.toString()]);
|
131 | stream.write('2');
|
132 | })).on('end', mustCall(() => {
|
133 | events.push(['client', 'socket end']);
|
134 | client.openssh_unforwardInStreamLocal(socketPath,
|
135 | mustCall((err) => {
|
136 | assert(!err, `Unexpected error: ${err}`);
|
137 | events.push(['client', 'cancel callback']);
|
138 | client.end();
|
139 | }));
|
140 | }));
|
141 | }));
|
142 | })).on('close', mustCall(() => {
|
143 | assert.deepStrictEqual(
|
144 | events,
|
145 | expected,
|
146 | 'Events mismatch\n'
|
147 | + `Actual:\n${inspect(events)}\n`
|
148 | + `Expected:\n${inspect(expected)}`
|
149 | );
|
150 | }));
|
151 | }
|
152 |
|
153 | {
|
154 | const { client, server } = setup_(
|
155 | 'OpenSSH UNIX socket connection',
|
156 | {
|
157 | client: clientCfg,
|
158 | server: {
|
159 | ...serverCfg,
|
160 | ident: 'OpenSSH_8.0',
|
161 | },
|
162 |
|
163 | debug,
|
164 | },
|
165 | );
|
166 |
|
167 | const socketPath = '/foo/bar/baz';
|
168 | const response = 'Hello World';
|
169 |
|
170 | server.on('connection', mustCall((conn) => {
|
171 | conn.on('authentication', mustCall((ctx) => {
|
172 | ctx.accept();
|
173 | })).on('ready', mustCall(() => {
|
174 | conn.on('openssh.streamlocal', mustCall((accept, reject, info) => {
|
175 | assert.deepStrictEqual(
|
176 | info,
|
177 | { socketPath },
|
178 | `Wrong info: ${inspect(info)}`
|
179 | );
|
180 |
|
181 | const stream = accept();
|
182 | stream.on('close', mustCall(() => {
|
183 | client.end();
|
184 | })).end(response);
|
185 | stream.resume();
|
186 | }));
|
187 | }));
|
188 | }));
|
189 |
|
190 | client.on('ready', mustCall(() => {
|
191 | client.openssh_forwardOutStreamLocal(socketPath, mustCall((err, stream) => {
|
192 | assert(!err, `Unexpected error: ${err}`);
|
193 | let buf = '';
|
194 | stream.on('data', mustCallAtLeast((data) => {
|
195 | buf += data;
|
196 | })).on('close', mustCall(() => {
|
197 | assert(buf === response, `Wrong response: ${inspect(buf)}`);
|
198 | }));
|
199 | }));
|
200 | }));
|
201 | }
|
202 |
|
203 | {
|
204 | const { client, server } = setup_(
|
205 | 'OpenSSH 5.x workaround for binding on port 0',
|
206 | {
|
207 | client: clientCfg,
|
208 | server: {
|
209 | ...serverCfg,
|
210 | ident: 'OpenSSH_5.3',
|
211 | },
|
212 |
|
213 | debug,
|
214 | },
|
215 | );
|
216 |
|
217 | const boundAddr = 'good';
|
218 | const boundPort = 1337;
|
219 | const tcpInfo = {
|
220 | destIP: boundAddr,
|
221 | destPort: boundPort,
|
222 | srcIP: 'remote',
|
223 | srcPort: 12345,
|
224 | };
|
225 |
|
226 | server.on('connection', mustCall((conn) => {
|
227 | conn.on('authentication', mustCall((ctx) => {
|
228 | ctx.accept();
|
229 | })).on('ready', mustCall(() => {
|
230 | conn.on('request', mustCall((accept, reject, name, info) => {
|
231 | assert(name === 'tcpip-forward', `Unexpected request: ${name}`);
|
232 | assert(info.bindAddr === boundAddr, `Wrong addr: ${info.bindAddr}`);
|
233 | assert(info.bindPort === 0, `Wrong port: ${info.bindPort}`);
|
234 | accept(boundPort);
|
235 | conn.forwardOut(boundAddr,
|
236 | 0,
|
237 | tcpInfo.srcIP,
|
238 | tcpInfo.srcPort,
|
239 | mustCall((err, ch) => {
|
240 | assert(!err, `Unexpected error: ${err}`);
|
241 | client.end();
|
242 | }));
|
243 | }));
|
244 | }));
|
245 | }));
|
246 |
|
247 | client.on('ready', mustCall(() => {
|
248 |
|
249 | client.forwardIn(boundAddr, 0, mustCall((err, port) => {
|
250 | assert(!err, `Unexpected error: ${err}`);
|
251 | assert(port === boundPort, `Bad bound port: ${port}`);
|
252 | }));
|
253 | })).on('tcp connection', mustCall((details, accept, reject) => {
|
254 | assert.deepStrictEqual(
|
255 | details,
|
256 | tcpInfo,
|
257 | `Wrong tcp details: ${inspect(details)}`
|
258 | );
|
259 | accept();
|
260 | }));
|
261 | }
|