@electerm/ssh2 0.8.11 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/install.js +20 -0
- package/lib/Channel.js +236 -450
- package/lib/agent.js +1080 -376
- package/lib/client.js +1698 -1258
- package/lib/http-agents.js +72 -51
- package/lib/index.js +43 -0
- package/lib/protocol/Protocol.js +2077 -0
- package/lib/protocol/SFTP.js +3778 -0
- package/lib/protocol/constants.js +342 -0
- package/lib/protocol/crypto/binding.gyp +14 -0
- package/lib/protocol/crypto/poly1305.js +43 -0
- package/lib/protocol/crypto/src/binding.cc +2003 -0
- package/lib/protocol/crypto.js +1602 -0
- package/lib/protocol/handlers.js +16 -0
- package/lib/protocol/handlers.misc.js +1214 -0
- package/lib/protocol/kex.js +1831 -0
- package/lib/protocol/keyParser.js +1481 -0
- package/lib/protocol/node-fs-compat.js +115 -0
- package/lib/protocol/utils.js +356 -0
- package/lib/protocol/zlib.js +255 -0
- package/lib/server.js +1226 -1019
- package/lib/utils.js +336 -0
- package/package.json +42 -9
- package/lib/SFTPWrapper.js +0 -145
- package/lib/buffer-helpers.js +0 -22
- package/lib/keepalivemgr.js +0 -80
package/lib/client.js
CHANGED
|
@@ -1,1240 +1,1565 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
// TODO:
|
|
2
|
+
// * add `.connected` or similar property to allow immediate connection
|
|
3
|
+
// status checking
|
|
4
|
+
// * add/improve debug output during user authentication phase
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
createHash,
|
|
9
|
+
getHashes,
|
|
10
|
+
randomFillSync,
|
|
11
|
+
} = require('crypto');
|
|
12
|
+
const { Socket } = require('net');
|
|
13
|
+
const { lookup: dnsLookup } = require('dns');
|
|
14
|
+
const EventEmitter = require('events');
|
|
15
|
+
const HASHES = getHashes();
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
COMPAT,
|
|
19
|
+
CHANNEL_EXTENDED_DATATYPE: { STDERR },
|
|
20
|
+
CHANNEL_OPEN_FAILURE,
|
|
21
|
+
DEFAULT_CIPHER,
|
|
22
|
+
DEFAULT_COMPRESSION,
|
|
23
|
+
DEFAULT_KEX,
|
|
24
|
+
DEFAULT_MAC,
|
|
25
|
+
DEFAULT_SERVER_HOST_KEY,
|
|
26
|
+
DISCONNECT_REASON,
|
|
27
|
+
DISCONNECT_REASON_BY_VALUE,
|
|
28
|
+
SUPPORTED_CIPHER,
|
|
29
|
+
SUPPORTED_COMPRESSION,
|
|
30
|
+
SUPPORTED_KEX,
|
|
31
|
+
SUPPORTED_MAC,
|
|
32
|
+
SUPPORTED_SERVER_HOST_KEY,
|
|
33
|
+
} = require('./protocol/constants.js');
|
|
34
|
+
const { init: cryptoInit } = require('./protocol/crypto.js');
|
|
35
|
+
const Protocol = require('./protocol/Protocol.js');
|
|
36
|
+
const { parseKey } = require('./protocol/keyParser.js');
|
|
37
|
+
const { SFTP } = require('./protocol/SFTP.js');
|
|
38
|
+
const {
|
|
39
|
+
bufferCopy,
|
|
40
|
+
makeBufferParser,
|
|
41
|
+
makeError,
|
|
42
|
+
readUInt32BE,
|
|
43
|
+
sigSSHToASN1,
|
|
44
|
+
writeUInt32BE,
|
|
45
|
+
} = require('./protocol/utils.js');
|
|
46
|
+
|
|
47
|
+
const { AgentContext, createAgent, isAgent } = require('./agent.js');
|
|
48
|
+
const {
|
|
49
|
+
Channel,
|
|
50
|
+
MAX_WINDOW,
|
|
51
|
+
PACKET_SIZE,
|
|
52
|
+
windowAdjust,
|
|
53
|
+
WINDOW_THRESHOLD,
|
|
54
|
+
} = require('./Channel.js');
|
|
55
|
+
const {
|
|
56
|
+
ChannelManager,
|
|
57
|
+
generateAlgorithmList,
|
|
58
|
+
isWritable,
|
|
59
|
+
onChannelOpenFailure,
|
|
60
|
+
onCHANNEL_CLOSE,
|
|
61
|
+
} = require('./utils.js');
|
|
62
|
+
|
|
63
|
+
const bufferParser = makeBufferParser();
|
|
64
|
+
const sigParser = makeBufferParser();
|
|
65
|
+
const RE_OPENSSH = /^OpenSSH_(?:(?![0-4])\d)|(?:\d{2,})/;
|
|
66
|
+
const noop = (err) => {};
|
|
67
|
+
|
|
68
|
+
class Client extends EventEmitter {
|
|
69
|
+
constructor() {
|
|
70
|
+
super();
|
|
71
|
+
|
|
72
|
+
this.config = {
|
|
73
|
+
host: undefined,
|
|
74
|
+
port: undefined,
|
|
75
|
+
localAddress: undefined,
|
|
76
|
+
localPort: undefined,
|
|
77
|
+
forceIPv4: undefined,
|
|
78
|
+
forceIPv6: undefined,
|
|
79
|
+
keepaliveCountMax: undefined,
|
|
80
|
+
keepaliveInterval: undefined,
|
|
81
|
+
readyTimeout: undefined,
|
|
82
|
+
ident: undefined,
|
|
83
|
+
|
|
84
|
+
username: undefined,
|
|
85
|
+
password: undefined,
|
|
86
|
+
privateKey: undefined,
|
|
87
|
+
tryKeyboard: undefined,
|
|
88
|
+
agent: undefined,
|
|
89
|
+
allowAgentFwd: undefined,
|
|
90
|
+
authHandler: undefined,
|
|
91
|
+
|
|
92
|
+
hostHashAlgo: undefined,
|
|
93
|
+
hostHashCb: undefined,
|
|
94
|
+
strictVendor: undefined,
|
|
95
|
+
debug: undefined
|
|
96
|
+
};
|
|
76
97
|
|
|
77
|
-
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.
|
|
82
|
-
|
|
98
|
+
this._agent = undefined;
|
|
99
|
+
this._readyTimeout = undefined;
|
|
100
|
+
this._chanMgr = undefined;
|
|
101
|
+
this._callbacks = undefined;
|
|
102
|
+
this._forwarding = undefined;
|
|
103
|
+
this._forwardingUnix = undefined;
|
|
104
|
+
this._acceptX11 = undefined;
|
|
105
|
+
this._agentFwdEnabled = undefined;
|
|
106
|
+
this._remoteVer = undefined;
|
|
107
|
+
|
|
108
|
+
this._protocol = undefined;
|
|
109
|
+
this._sock = undefined;
|
|
110
|
+
this._resetKA = undefined;
|
|
83
111
|
}
|
|
84
112
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
? cfg.localPort
|
|
93
|
-
: undefined);
|
|
94
|
-
this.config.forceIPv4 = cfg.forceIPv4 || false;
|
|
95
|
-
this.config.forceIPv6 = cfg.forceIPv6 || false;
|
|
96
|
-
this.config.keepaliveCountMax = (typeof cfg.keepaliveCountMax === 'number'
|
|
97
|
-
&& cfg.keepaliveCountMax >= 0
|
|
98
|
-
? cfg.keepaliveCountMax
|
|
99
|
-
: 3);
|
|
100
|
-
this.config.keepaliveInterval = (typeof cfg.keepaliveInterval === 'number'
|
|
101
|
-
&& cfg.keepaliveInterval > 0
|
|
102
|
-
? cfg.keepaliveInterval
|
|
103
|
-
: 0);
|
|
104
|
-
this.config.readyTimeout = (typeof cfg.readyTimeout === 'number'
|
|
105
|
-
&& cfg.readyTimeout >= 0
|
|
106
|
-
? cfg.readyTimeout
|
|
107
|
-
: 20000);
|
|
108
|
-
|
|
109
|
-
var algorithms = {
|
|
110
|
-
kex: undefined,
|
|
111
|
-
kexBuf: undefined,
|
|
112
|
-
cipher: undefined,
|
|
113
|
-
cipherBuf: undefined,
|
|
114
|
-
serverHostKey: undefined,
|
|
115
|
-
serverHostKeyBuf: undefined,
|
|
116
|
-
hmac: undefined,
|
|
117
|
-
hmacBuf: undefined,
|
|
118
|
-
compress: undefined,
|
|
119
|
-
compressBuf: undefined
|
|
120
|
-
};
|
|
121
|
-
var i;
|
|
122
|
-
if (typeof cfg.algorithms === 'object' && cfg.algorithms !== null) {
|
|
123
|
-
var algosSupported;
|
|
124
|
-
var algoList;
|
|
125
|
-
|
|
126
|
-
algoList = cfg.algorithms.kex;
|
|
127
|
-
if (Array.isArray(algoList) && algoList.length > 0) {
|
|
128
|
-
algosSupported = ALGORITHMS.SUPPORTED_KEX;
|
|
129
|
-
for (i = 0; i < algoList.length; ++i) {
|
|
130
|
-
if (algosSupported.indexOf(algoList[i]) === -1)
|
|
131
|
-
throw new Error('Unsupported key exchange algorithm: ' + algoList[i]);
|
|
132
|
-
}
|
|
133
|
-
algorithms.kex = algoList;
|
|
113
|
+
connect(cfg) {
|
|
114
|
+
if (this._sock && isWritable(this._sock)) {
|
|
115
|
+
this.once('close', () => {
|
|
116
|
+
this.connect(cfg);
|
|
117
|
+
});
|
|
118
|
+
this.end();
|
|
119
|
+
return this;
|
|
134
120
|
}
|
|
135
121
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
122
|
+
this.config.host = cfg.hostname || cfg.host || 'localhost';
|
|
123
|
+
this.config.port = cfg.port || 22;
|
|
124
|
+
this.config.localAddress = (typeof cfg.localAddress === 'string'
|
|
125
|
+
? cfg.localAddress
|
|
126
|
+
: undefined);
|
|
127
|
+
this.config.localPort = (typeof cfg.localPort === 'string'
|
|
128
|
+
|| typeof cfg.localPort === 'number'
|
|
129
|
+
? cfg.localPort
|
|
130
|
+
: undefined);
|
|
131
|
+
this.config.forceIPv4 = cfg.forceIPv4 || false;
|
|
132
|
+
this.config.forceIPv6 = cfg.forceIPv6 || false;
|
|
133
|
+
this.config.keepaliveCountMax = (typeof cfg.keepaliveCountMax === 'number'
|
|
134
|
+
&& cfg.keepaliveCountMax >= 0
|
|
135
|
+
? cfg.keepaliveCountMax
|
|
136
|
+
: 3);
|
|
137
|
+
this.config.keepaliveInterval = (typeof cfg.keepaliveInterval === 'number'
|
|
138
|
+
&& cfg.keepaliveInterval > 0
|
|
139
|
+
? cfg.keepaliveInterval
|
|
140
|
+
: 0);
|
|
141
|
+
this.config.readyTimeout = (typeof cfg.readyTimeout === 'number'
|
|
142
|
+
&& cfg.readyTimeout >= 0
|
|
143
|
+
? cfg.readyTimeout
|
|
144
|
+
: 20000);
|
|
145
|
+
this.config.ident = (typeof cfg.ident === 'string'
|
|
146
|
+
|| Buffer.isBuffer(cfg.ident)
|
|
147
|
+
? cfg.ident
|
|
148
|
+
: undefined);
|
|
149
|
+
|
|
150
|
+
const algorithms = {
|
|
151
|
+
kex: undefined,
|
|
152
|
+
serverHostKey: undefined,
|
|
153
|
+
cs: {
|
|
154
|
+
cipher: undefined,
|
|
155
|
+
mac: undefined,
|
|
156
|
+
compress: undefined,
|
|
157
|
+
lang: [],
|
|
158
|
+
},
|
|
159
|
+
sc: undefined,
|
|
160
|
+
};
|
|
161
|
+
let allOfferDefaults = true;
|
|
162
|
+
if (typeof cfg.algorithms === 'object' && cfg.algorithms !== null) {
|
|
163
|
+
algorithms.kex = generateAlgorithmList(cfg.algorithms.kex,
|
|
164
|
+
DEFAULT_KEX,
|
|
165
|
+
SUPPORTED_KEX);
|
|
166
|
+
if (algorithms.kex !== DEFAULT_KEX)
|
|
167
|
+
allOfferDefaults = false;
|
|
168
|
+
|
|
169
|
+
algorithms.serverHostKey =
|
|
170
|
+
generateAlgorithmList(cfg.algorithms.serverHostKey,
|
|
171
|
+
DEFAULT_SERVER_HOST_KEY,
|
|
172
|
+
SUPPORTED_SERVER_HOST_KEY);
|
|
173
|
+
if (algorithms.serverHostKey !== DEFAULT_SERVER_HOST_KEY)
|
|
174
|
+
allOfferDefaults = false;
|
|
175
|
+
|
|
176
|
+
algorithms.cs.cipher = generateAlgorithmList(cfg.algorithms.cipher,
|
|
177
|
+
DEFAULT_CIPHER,
|
|
178
|
+
SUPPORTED_CIPHER);
|
|
179
|
+
if (algorithms.cs.cipher !== DEFAULT_CIPHER)
|
|
180
|
+
allOfferDefaults = false;
|
|
181
|
+
|
|
182
|
+
algorithms.cs.mac = generateAlgorithmList(cfg.algorithms.hmac,
|
|
183
|
+
DEFAULT_MAC,
|
|
184
|
+
SUPPORTED_MAC);
|
|
185
|
+
if (algorithms.cs.mac !== DEFAULT_MAC)
|
|
186
|
+
allOfferDefaults = false;
|
|
187
|
+
|
|
188
|
+
algorithms.cs.compress = generateAlgorithmList(cfg.algorithms.compress,
|
|
189
|
+
DEFAULT_COMPRESSION,
|
|
190
|
+
SUPPORTED_COMPRESSION);
|
|
191
|
+
if (algorithms.cs.compress !== DEFAULT_COMPRESSION)
|
|
192
|
+
allOfferDefaults = false;
|
|
193
|
+
|
|
194
|
+
if (!allOfferDefaults)
|
|
195
|
+
algorithms.sc = algorithms.cs;
|
|
144
196
|
}
|
|
145
197
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
+ algoList[i]);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
algorithms.serverHostKey = algoList;
|
|
156
|
-
}
|
|
198
|
+
if (typeof cfg.username === 'string')
|
|
199
|
+
this.config.username = cfg.username;
|
|
200
|
+
else if (typeof cfg.user === 'string')
|
|
201
|
+
this.config.username = cfg.user;
|
|
202
|
+
else
|
|
203
|
+
throw new Error('Invalid username');
|
|
157
204
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
205
|
+
this.config.password = (typeof cfg.password === 'string'
|
|
206
|
+
? cfg.password
|
|
207
|
+
: undefined);
|
|
208
|
+
this.config.privateKey = (typeof cfg.privateKey === 'string'
|
|
209
|
+
|| Buffer.isBuffer(cfg.privateKey)
|
|
210
|
+
? cfg.privateKey
|
|
211
|
+
: undefined);
|
|
212
|
+
this.config.localHostname = (typeof cfg.localHostname === 'string'
|
|
213
|
+
? cfg.localHostname
|
|
214
|
+
: undefined);
|
|
215
|
+
this.config.localUsername = (typeof cfg.localUsername === 'string'
|
|
216
|
+
? cfg.localUsername
|
|
217
|
+
: undefined);
|
|
218
|
+
this.config.tryKeyboard = (cfg.tryKeyboard === true);
|
|
219
|
+
if (typeof cfg.agent === 'string' && cfg.agent.length)
|
|
220
|
+
this.config.agent = createAgent(cfg.agent);
|
|
221
|
+
else if (isAgent(cfg.agent))
|
|
222
|
+
this.config.agent = cfg.agent;
|
|
223
|
+
else
|
|
224
|
+
this.config.agent = undefined;
|
|
225
|
+
this.config.allowAgentFwd = (cfg.agentForward === true
|
|
226
|
+
&& this.config.agent !== undefined);
|
|
227
|
+
let authHandler = this.config.authHandler = (
|
|
228
|
+
typeof cfg.authHandler === 'function'
|
|
229
|
+
|| Array.isArray(cfg.authHandler)
|
|
230
|
+
? cfg.authHandler
|
|
231
|
+
: undefined
|
|
232
|
+
);
|
|
167
233
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
for (i = 0; i < algoList.length; ++i) {
|
|
172
|
-
if (algosSupported.indexOf(algoList[i]) === -1)
|
|
173
|
-
throw new Error('Unsupported compression algorithm: ' + algoList[i]);
|
|
174
|
-
}
|
|
175
|
-
algorithms.compress = algoList;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (algorithms.compress === undefined) {
|
|
179
|
-
if (cfg.compress) {
|
|
180
|
-
algorithms.compress = ['zlib@openssh.com', 'zlib'];
|
|
181
|
-
if (cfg.compress !== 'force')
|
|
182
|
-
algorithms.compress.push('none');
|
|
183
|
-
} else if (cfg.compress === false)
|
|
184
|
-
algorithms.compress = ['none'];
|
|
185
|
-
}
|
|
234
|
+
this.config.strictVendor = (typeof cfg.strictVendor === 'boolean'
|
|
235
|
+
? cfg.strictVendor
|
|
236
|
+
: true);
|
|
186
237
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
this.config.username = cfg.user;
|
|
191
|
-
else
|
|
192
|
-
throw new Error('Invalid username');
|
|
193
|
-
|
|
194
|
-
this.config.password = (typeof cfg.password === 'string'
|
|
195
|
-
? cfg.password
|
|
196
|
-
: undefined);
|
|
197
|
-
this.config.privateKey = (typeof cfg.privateKey === 'string'
|
|
198
|
-
|| Buffer.isBuffer(cfg.privateKey)
|
|
199
|
-
? cfg.privateKey
|
|
200
|
-
: undefined);
|
|
201
|
-
this.config.localHostname = (typeof cfg.localHostname === 'string'
|
|
202
|
-
&& cfg.localHostname.length
|
|
203
|
-
? cfg.localHostname
|
|
204
|
-
: undefined);
|
|
205
|
-
this.config.localUsername = (typeof cfg.localUsername === 'string'
|
|
206
|
-
&& cfg.localUsername.length
|
|
207
|
-
? cfg.localUsername
|
|
208
|
-
: undefined);
|
|
209
|
-
this.config.tryKeyboard = (cfg.tryKeyboard === true);
|
|
210
|
-
this.config.agent = (typeof cfg.agent === 'string' && cfg.agent.length
|
|
211
|
-
? cfg.agent
|
|
212
|
-
: undefined);
|
|
213
|
-
this.config.allowAgentFwd = (cfg.agentForward === true
|
|
214
|
-
&& this.config.agent !== undefined);
|
|
215
|
-
var authHandler = this.config.authHandler = (
|
|
216
|
-
typeof cfg.authHandler === 'function' ? cfg.authHandler : undefined
|
|
217
|
-
);
|
|
238
|
+
const debug = this.config.debug = (typeof cfg.debug === 'function'
|
|
239
|
+
? cfg.debug
|
|
240
|
+
: undefined);
|
|
218
241
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
? cfg.debug
|
|
225
|
-
: DEBUG_NOOP);
|
|
226
|
-
|
|
227
|
-
if (cfg.agentForward === true && !this.config.allowAgentFwd)
|
|
228
|
-
throw new Error('You must set a valid agent path to allow agent forwarding');
|
|
229
|
-
|
|
230
|
-
var callbacks = this._callbacks = [];
|
|
231
|
-
this._channels = {};
|
|
232
|
-
this._forwarding = {};
|
|
233
|
-
this._forwardingUnix = {};
|
|
234
|
-
this._acceptX11 = 0;
|
|
235
|
-
this._agentFwdEnabled = false;
|
|
236
|
-
this._curChan = -1;
|
|
237
|
-
this._remoteVer = undefined;
|
|
238
|
-
var privateKey;
|
|
239
|
-
|
|
240
|
-
if (this.config.privateKey) {
|
|
241
|
-
privateKey = parseKey(this.config.privateKey, cfg.passphrase);
|
|
242
|
-
if (privateKey instanceof Error)
|
|
243
|
-
throw new Error('Cannot parse privateKey: ' + privateKey.message);
|
|
244
|
-
if (Array.isArray(privateKey))
|
|
245
|
-
privateKey = privateKey[0]; // OpenSSH's newer format only stores 1 key for now
|
|
246
|
-
if (privateKey.getPrivatePEM() === null)
|
|
247
|
-
throw new Error('privateKey value does not contain a (valid) private key');
|
|
248
|
-
}
|
|
242
|
+
if (cfg.agentForward === true && !this.config.allowAgentFwd) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
'You must set a valid agent path to allow agent forwarding'
|
|
245
|
+
);
|
|
246
|
+
}
|
|
249
247
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
sock.destroy();
|
|
248
|
+
let callbacks = this._callbacks = [];
|
|
249
|
+
this._chanMgr = new ChannelManager(this);
|
|
250
|
+
this._forwarding = {};
|
|
251
|
+
this._forwardingUnix = {};
|
|
252
|
+
this._acceptX11 = 0;
|
|
253
|
+
this._agentFwdEnabled = false;
|
|
254
|
+
this._agent = (this.config.agent ? this.config.agent : undefined);
|
|
255
|
+
this._remoteVer = undefined;
|
|
256
|
+
let privateKey;
|
|
257
|
+
|
|
258
|
+
if (this.config.privateKey) {
|
|
259
|
+
privateKey = parseKey(this.config.privateKey, cfg.passphrase);
|
|
260
|
+
if (privateKey instanceof Error)
|
|
261
|
+
throw new Error(`Cannot parse privateKey: ${privateKey.message}`);
|
|
262
|
+
if (Array.isArray(privateKey)) {
|
|
263
|
+
// OpenSSH's newer format only stores 1 key for now
|
|
264
|
+
privateKey = privateKey[0];
|
|
265
|
+
}
|
|
266
|
+
if (privateKey.getPrivatePEM() === null) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
'privateKey value does not contain a (valid) private key'
|
|
269
|
+
);
|
|
273
270
|
}
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
if (sock.writable) {
|
|
277
|
-
// append dummy callback to keep correct callback order
|
|
278
|
-
callbacks.push(resetKA);
|
|
279
|
-
stream.ping();
|
|
280
|
-
} else
|
|
281
|
-
clearInterval(katimer);
|
|
282
|
-
}
|
|
283
|
-
function resetKA() {
|
|
284
|
-
if (kainterval > 0) {
|
|
285
|
-
kacount = 0;
|
|
286
|
-
clearInterval(katimer);
|
|
287
|
-
if (sock.writable)
|
|
288
|
-
katimer = setInterval(sendKA, kainterval);
|
|
289
271
|
}
|
|
290
|
-
}
|
|
291
|
-
this._resetKA = resetKA;
|
|
292
|
-
|
|
293
|
-
stream.on('USERAUTH_BANNER', function(msg) {
|
|
294
|
-
self.emit('banner', msg);
|
|
295
|
-
});
|
|
296
272
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}).on('error', function(err) {
|
|
305
|
-
clearTimeout(self._readyTimeout);
|
|
306
|
-
err.level = 'client-socket';
|
|
307
|
-
self.emit('error', err);
|
|
308
|
-
}).on('end', function() {
|
|
309
|
-
stream.unpipe(sock);
|
|
310
|
-
clearTimeout(self._readyTimeout);
|
|
311
|
-
clearInterval(katimer);
|
|
312
|
-
self.emit('end');
|
|
313
|
-
}).on('close', function() {
|
|
314
|
-
stream.unpipe(sock);
|
|
315
|
-
clearTimeout(self._readyTimeout);
|
|
316
|
-
clearInterval(katimer);
|
|
317
|
-
self.emit('close');
|
|
318
|
-
|
|
319
|
-
// notify outstanding channel requests of disconnection ...
|
|
320
|
-
var callbacks_ = callbacks;
|
|
321
|
-
var err = new Error('No response from server');
|
|
322
|
-
callbacks = self._callbacks = [];
|
|
323
|
-
for (i = 0; i < callbacks_.length; ++i)
|
|
324
|
-
callbacks_[i](err);
|
|
325
|
-
|
|
326
|
-
// simulate error for any channels waiting to be opened. this is safe
|
|
327
|
-
// against successfully opened channels because the success and failure
|
|
328
|
-
// event handlers are automatically removed when a success/failure response
|
|
329
|
-
// is received
|
|
330
|
-
var channels = self._channels;
|
|
331
|
-
var chanNos = Object.keys(channels);
|
|
332
|
-
self._channels = {};
|
|
333
|
-
for (i = 0; i < chanNos.length; ++i) {
|
|
334
|
-
var ev1 = stream.emit('CHANNEL_OPEN_FAILURE:' + chanNos[i], err);
|
|
335
|
-
// emitting CHANNEL_CLOSE should be safe too and should help for any
|
|
336
|
-
// special channels which might otherwise keep the process alive, such
|
|
337
|
-
// as agent forwarding channels which have open unix sockets ...
|
|
338
|
-
var ev2 = stream.emit('CHANNEL_CLOSE:' + chanNos[i]);
|
|
339
|
-
var earlyCb;
|
|
340
|
-
if (!ev1 && !ev2 && (earlyCb = channels[chanNos[i]])
|
|
341
|
-
&& typeof earlyCb === 'function') {
|
|
342
|
-
earlyCb(err);
|
|
273
|
+
let hostVerifier;
|
|
274
|
+
if (typeof cfg.hostVerifier === 'function') {
|
|
275
|
+
const hashCb = cfg.hostVerifier;
|
|
276
|
+
let hasher;
|
|
277
|
+
if (HASHES.indexOf(cfg.hostHash) !== -1) {
|
|
278
|
+
// Default to old behavior of hashing on user's behalf
|
|
279
|
+
hasher = createHash(cfg.hostHash);
|
|
343
280
|
}
|
|
281
|
+
hostVerifier = (key, verify) => {
|
|
282
|
+
if (hasher) {
|
|
283
|
+
hasher.update(key);
|
|
284
|
+
key = hasher.digest('hex');
|
|
285
|
+
}
|
|
286
|
+
const ret = hashCb(key, verify);
|
|
287
|
+
if (ret !== undefined)
|
|
288
|
+
verify(ret);
|
|
289
|
+
};
|
|
344
290
|
}
|
|
345
|
-
});
|
|
346
|
-
stream.on('drain', function() {
|
|
347
|
-
self.emit('drain');
|
|
348
|
-
}).once('header', function(header) {
|
|
349
|
-
self._remoteVer = header.versions.software;
|
|
350
|
-
if (header.greeting)
|
|
351
|
-
self.emit('greeting', header.greeting);
|
|
352
|
-
}).on('continue', function() {
|
|
353
|
-
self.emit('continue');
|
|
354
|
-
}).on('error', function(err) {
|
|
355
|
-
if (err.level === undefined)
|
|
356
|
-
err.level = 'protocol';
|
|
357
|
-
else if (err.level === 'handshake')
|
|
358
|
-
clearTimeout(self._readyTimeout);
|
|
359
|
-
self.emit('error', err);
|
|
360
|
-
}).on('end', function() {
|
|
361
|
-
sock.resume();
|
|
362
|
-
});
|
|
363
291
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
var ret = hashCb(hasher.digest('hex'), verify);
|
|
372
|
-
if (ret !== undefined)
|
|
373
|
-
verify(ret);
|
|
292
|
+
const sock = this._sock = (cfg.sock || new Socket());
|
|
293
|
+
let ready = false;
|
|
294
|
+
let sawHeader = false;
|
|
295
|
+
if (this._protocol)
|
|
296
|
+
this._protocol.cleanup();
|
|
297
|
+
const DEBUG_HANDLER = (!debug ? undefined : (p, display, msg) => {
|
|
298
|
+
debug(`Debug output from server: ${JSON.stringify(msg)}`);
|
|
374
299
|
});
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
authHandler = function authHandler(authsLeft, partial, cb) {
|
|
404
|
-
if (authPos === authsAllowed.length)
|
|
405
|
-
return false;
|
|
406
|
-
return authsAllowed[authPos++];
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
var hasSentAuth = false;
|
|
411
|
-
function doNextAuth(authName) {
|
|
412
|
-
hasSentAuth = true;
|
|
413
|
-
if (authName === false) {
|
|
414
|
-
stream.removeListener('USERAUTH_FAILURE', onUSERAUTH_FAILURE);
|
|
415
|
-
stream.removeListener('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
|
|
416
|
-
var err = new Error('All configured authentication methods failed');
|
|
417
|
-
err.level = 'client-authentication';
|
|
418
|
-
self.emit('error', err);
|
|
419
|
-
if (stream.writable)
|
|
420
|
-
self.end();
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
if (authsAllowed.indexOf(authName) === -1)
|
|
424
|
-
throw new Error('Authentication method not allowed: ' + authName);
|
|
425
|
-
curAuth = authName;
|
|
426
|
-
switch (curAuth) {
|
|
427
|
-
case 'password':
|
|
428
|
-
stream.authPassword(self.config.username, self.config.password);
|
|
429
|
-
break;
|
|
430
|
-
case 'publickey':
|
|
431
|
-
stream.authPK(self.config.username, privateKey);
|
|
432
|
-
stream.once('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
|
|
433
|
-
break;
|
|
434
|
-
case 'hostbased':
|
|
435
|
-
function hostbasedCb(buf, cb) {
|
|
436
|
-
var signature = privateKey.sign(buf);
|
|
437
|
-
if (signature instanceof Error) {
|
|
438
|
-
signature.message = 'Error while signing data with privateKey: '
|
|
439
|
-
+ signature.message;
|
|
440
|
-
signature.level = 'client-authentication';
|
|
441
|
-
self.emit('error', signature);
|
|
442
|
-
return tryNextAuth();
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
cb(signature);
|
|
300
|
+
const proto = this._protocol = new Protocol({
|
|
301
|
+
ident: this.config.ident,
|
|
302
|
+
offer: (allOfferDefaults ? undefined : algorithms),
|
|
303
|
+
onWrite: (data) => {
|
|
304
|
+
if (isWritable(sock))
|
|
305
|
+
sock.write(data);
|
|
306
|
+
},
|
|
307
|
+
onError: (err) => {
|
|
308
|
+
if (err.level === 'handshake')
|
|
309
|
+
clearTimeout(this._readyTimeout);
|
|
310
|
+
if (!proto._destruct)
|
|
311
|
+
sock.removeAllListeners('data');
|
|
312
|
+
this.emit('error', err);
|
|
313
|
+
try {
|
|
314
|
+
sock.end();
|
|
315
|
+
} catch {}
|
|
316
|
+
},
|
|
317
|
+
onHeader: (header) => {
|
|
318
|
+
sawHeader = true;
|
|
319
|
+
this._remoteVer = header.versions.software;
|
|
320
|
+
if (header.greeting)
|
|
321
|
+
this.emit('greeting', header.greeting);
|
|
322
|
+
},
|
|
323
|
+
onHandshakeComplete: (negotiated) => {
|
|
324
|
+
this.emit('handshake', negotiated);
|
|
325
|
+
if (!ready) {
|
|
326
|
+
ready = true;
|
|
327
|
+
proto.service('ssh-userauth');
|
|
446
328
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
329
|
+
},
|
|
330
|
+
debug,
|
|
331
|
+
hostVerifier,
|
|
332
|
+
messageHandlers: {
|
|
333
|
+
DEBUG: DEBUG_HANDLER,
|
|
334
|
+
DISCONNECT: (p, reason, desc) => {
|
|
335
|
+
if (reason !== DISCONNECT_REASON.BY_APPLICATION) {
|
|
336
|
+
if (!desc) {
|
|
337
|
+
desc = DISCONNECT_REASON_BY_VALUE[reason];
|
|
338
|
+
if (desc === undefined)
|
|
339
|
+
desc = `Unexpected disconnection reason: ${reason}`;
|
|
340
|
+
}
|
|
341
|
+
const err = new Error(desc);
|
|
342
|
+
err.code = reason;
|
|
343
|
+
this.emit('error', err);
|
|
344
|
+
}
|
|
345
|
+
sock.end();
|
|
346
|
+
},
|
|
347
|
+
SERVICE_ACCEPT: (p, name) => {
|
|
348
|
+
if (name === 'ssh-userauth')
|
|
349
|
+
tryNextAuth();
|
|
350
|
+
},
|
|
351
|
+
USERAUTH_BANNER: (p, msg) => {
|
|
352
|
+
this.emit('banner', msg);
|
|
353
|
+
},
|
|
354
|
+
USERAUTH_SUCCESS: (p) => {
|
|
355
|
+
// Start keepalive mechanism
|
|
356
|
+
resetKA();
|
|
357
|
+
|
|
358
|
+
clearTimeout(this._readyTimeout);
|
|
359
|
+
|
|
360
|
+
this.emit('ready');
|
|
361
|
+
},
|
|
362
|
+
USERAUTH_FAILURE: (p, authMethods, partialSuccess) => {
|
|
363
|
+
if (curAuth.type === 'agent') {
|
|
364
|
+
const pos = curAuth.agentCtx.pos();
|
|
365
|
+
debug && debug(`Client: Agent key #${pos + 1} failed`);
|
|
366
|
+
return tryNextAgentKey();
|
|
464
367
|
}
|
|
465
368
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
369
|
+
debug && debug(`Client: ${curAuth.type} auth failed`);
|
|
370
|
+
|
|
371
|
+
curPartial = partialSuccess;
|
|
372
|
+
curAuthsLeft = authMethods;
|
|
373
|
+
tryNextAuth();
|
|
374
|
+
},
|
|
375
|
+
USERAUTH_PASSWD_CHANGEREQ: (p, prompt) => {
|
|
376
|
+
if (curAuth.type === 'password') {
|
|
377
|
+
// TODO: support a `changePrompt()` on `curAuth` that defaults to
|
|
378
|
+
// emitting 'change password' as before
|
|
379
|
+
this.emit('change password', prompt, (newPassword) => {
|
|
380
|
+
proto.authPassword(
|
|
381
|
+
this.config.username,
|
|
382
|
+
this.config.password,
|
|
383
|
+
newPassword
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
USERAUTH_PK_OK: (p) => {
|
|
389
|
+
if (curAuth.type === 'agent') {
|
|
390
|
+
const key = curAuth.agentCtx.currentKey();
|
|
391
|
+
proto.authPK(curAuth.username, key, (buf, cb) => {
|
|
392
|
+
curAuth.agentCtx.sign(key, buf, {}, (err, signed) => {
|
|
393
|
+
if (err) {
|
|
394
|
+
err.level = 'agent';
|
|
395
|
+
this.emit('error', err);
|
|
396
|
+
} else {
|
|
397
|
+
return cb(signed);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
tryNextAgentKey();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
} else if (curAuth.type === 'publickey') {
|
|
404
|
+
proto.authPK(curAuth.username, curAuth.key, (buf, cb) => {
|
|
405
|
+
const signature = curAuth.key.sign(buf);
|
|
406
|
+
if (signature instanceof Error) {
|
|
407
|
+
signature.message =
|
|
408
|
+
`Error signing data with key: ${signature.message}`;
|
|
409
|
+
signature.level = 'client-authentication';
|
|
410
|
+
this.emit('error', signature);
|
|
411
|
+
return tryNextAuth();
|
|
412
|
+
}
|
|
413
|
+
cb(signature);
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
USERAUTH_INFO_REQUEST: (p, name, instructions, prompts) => {
|
|
418
|
+
if (curAuth.type === 'keyboard-interactive') {
|
|
419
|
+
const nprompts = (Array.isArray(prompts) ? prompts.length : 0);
|
|
420
|
+
if (nprompts === 0) {
|
|
421
|
+
debug && debug(
|
|
422
|
+
'Client: Sending automatic USERAUTH_INFO_RESPONSE'
|
|
423
|
+
);
|
|
424
|
+
proto.authInfoRes();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
// We sent a keyboard-interactive user authentication request and
|
|
428
|
+
// now the server is sending us the prompts we need to present to
|
|
429
|
+
// the user
|
|
430
|
+
curAuth.prompt(
|
|
514
431
|
name,
|
|
515
432
|
instructions,
|
|
516
|
-
|
|
433
|
+
'',
|
|
517
434
|
prompts,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
function onUSERAUTH_PK_OK() {
|
|
524
|
-
if (curAuth === 'agent') {
|
|
525
|
-
var agentKey = agentKeys[agentKeyPos];
|
|
526
|
-
var keyLen = readUInt32BE(agentKey, 0);
|
|
527
|
-
var pubKeyFullType = agentKey.toString('ascii', 4, 4 + keyLen);
|
|
528
|
-
var pubKeyType = pubKeyFullType.slice(4);
|
|
529
|
-
// Check that we support the key type first
|
|
530
|
-
// TODO: move key type checking logic to ssh2-streams
|
|
531
|
-
switch (pubKeyFullType) {
|
|
532
|
-
case 'ssh-rsa':
|
|
533
|
-
case 'ssh-dss':
|
|
534
|
-
case 'ecdsa-sha2-nistp256':
|
|
535
|
-
case 'ecdsa-sha2-nistp384':
|
|
536
|
-
case 'ecdsa-sha2-nistp521':
|
|
537
|
-
break;
|
|
538
|
-
default:
|
|
539
|
-
if (EDDSA_SUPPORTED && pubKeyFullType === 'ssh-ed25519')
|
|
540
|
-
break;
|
|
541
|
-
debug('DEBUG: Agent: Skipping unsupported key type: '
|
|
542
|
-
+ pubKeyFullType);
|
|
543
|
-
return tryNextAgentKey();
|
|
544
|
-
}
|
|
545
|
-
stream.authPK(self.config.username,
|
|
546
|
-
agentKey,
|
|
547
|
-
function(buf, cb) {
|
|
548
|
-
agentQuery(self.config.agent,
|
|
549
|
-
agentKey,
|
|
550
|
-
pubKeyType,
|
|
551
|
-
buf,
|
|
552
|
-
function(err, signed) {
|
|
553
|
-
if (err) {
|
|
554
|
-
err.level = 'agent';
|
|
555
|
-
self.emit('error', err);
|
|
556
|
-
} else {
|
|
557
|
-
var sigFullTypeLen = readUInt32BE(signed, 0);
|
|
558
|
-
if (4 + sigFullTypeLen + 4 < signed.length) {
|
|
559
|
-
var sigFullType = signed.toString('ascii', 4, 4 + sigFullTypeLen);
|
|
560
|
-
if (sigFullType !== pubKeyFullType) {
|
|
561
|
-
err = new Error('Agent key/signature type mismatch');
|
|
562
|
-
err.level = 'agent';
|
|
563
|
-
self.emit('error', err);
|
|
564
|
-
} else {
|
|
565
|
-
// skip algoLen + algo + sigLen
|
|
566
|
-
return cb(signed.slice(4 + sigFullTypeLen + 4));
|
|
435
|
+
(answers) => {
|
|
436
|
+
proto.authInfoRes(answers);
|
|
567
437
|
}
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
REQUEST_SUCCESS: (p, data) => {
|
|
442
|
+
if (callbacks.length)
|
|
443
|
+
callbacks.shift()(false, data);
|
|
444
|
+
},
|
|
445
|
+
REQUEST_FAILURE: (p) => {
|
|
446
|
+
if (callbacks.length)
|
|
447
|
+
callbacks.shift()(true);
|
|
448
|
+
},
|
|
449
|
+
GLOBAL_REQUEST: (p, name, wantReply, data) => {
|
|
450
|
+
switch (name) {
|
|
451
|
+
case 'hostkeys-00@openssh.com':
|
|
452
|
+
// Automatically verify keys before passing to end user
|
|
453
|
+
hostKeysProve(this, data, (err, keys) => {
|
|
454
|
+
if (err)
|
|
455
|
+
return;
|
|
456
|
+
this.emit('hostkeys', keys);
|
|
457
|
+
});
|
|
458
|
+
if (wantReply)
|
|
459
|
+
proto.requestSuccess();
|
|
460
|
+
break;
|
|
461
|
+
default:
|
|
462
|
+
// Auto-reject all other global requests, this can be especially
|
|
463
|
+
// useful if the server is sending us dummy keepalive global
|
|
464
|
+
// requests
|
|
465
|
+
if (wantReply)
|
|
466
|
+
proto.requestFailure();
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
CHANNEL_OPEN: (p, info) => {
|
|
470
|
+
// Handle incoming requests from server, typically a forwarded TCP or
|
|
471
|
+
// X11 connection
|
|
472
|
+
onCHANNEL_OPEN(this, info);
|
|
473
|
+
},
|
|
474
|
+
CHANNEL_OPEN_CONFIRMATION: (p, info) => {
|
|
475
|
+
const channel = this._chanMgr.get(info.recipient);
|
|
476
|
+
if (typeof channel !== 'function')
|
|
477
|
+
return;
|
|
478
|
+
|
|
479
|
+
const isSFTP = (channel.type === 'sftp');
|
|
480
|
+
const type = (isSFTP ? 'session' : channel.type);
|
|
481
|
+
const chanInfo = {
|
|
482
|
+
type,
|
|
483
|
+
incoming: {
|
|
484
|
+
id: info.recipient,
|
|
485
|
+
window: MAX_WINDOW,
|
|
486
|
+
packetSize: PACKET_SIZE,
|
|
487
|
+
state: 'open'
|
|
488
|
+
},
|
|
489
|
+
outgoing: {
|
|
490
|
+
id: info.sender,
|
|
491
|
+
window: info.window,
|
|
492
|
+
packetSize: info.packetSize,
|
|
493
|
+
state: 'open'
|
|
568
494
|
}
|
|
495
|
+
};
|
|
496
|
+
const instance = (
|
|
497
|
+
isSFTP
|
|
498
|
+
? new SFTP(this, chanInfo, { debug })
|
|
499
|
+
: new Channel(this, chanInfo)
|
|
500
|
+
);
|
|
501
|
+
this._chanMgr.update(info.recipient, instance);
|
|
502
|
+
channel(undefined, instance);
|
|
503
|
+
},
|
|
504
|
+
CHANNEL_OPEN_FAILURE: (p, recipient, reason, description) => {
|
|
505
|
+
const channel = this._chanMgr.get(recipient);
|
|
506
|
+
if (typeof channel !== 'function')
|
|
507
|
+
return;
|
|
508
|
+
|
|
509
|
+
const info = { reason, description };
|
|
510
|
+
onChannelOpenFailure(this, recipient, info, channel);
|
|
511
|
+
},
|
|
512
|
+
CHANNEL_DATA: (p, recipient, data) => {
|
|
513
|
+
const channel = this._chanMgr.get(recipient);
|
|
514
|
+
if (typeof channel !== 'object' || channel === null)
|
|
515
|
+
return;
|
|
516
|
+
|
|
517
|
+
// The remote party should not be sending us data if there is no
|
|
518
|
+
// window space available ...
|
|
519
|
+
// TODO: raise error on data with not enough window?
|
|
520
|
+
if (channel.incoming.window === 0)
|
|
521
|
+
return;
|
|
522
|
+
|
|
523
|
+
channel.incoming.window -= data.length;
|
|
524
|
+
|
|
525
|
+
if (channel.push(data) === false) {
|
|
526
|
+
channel._waitChanDrain = true;
|
|
527
|
+
return;
|
|
569
528
|
}
|
|
570
529
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (signature instanceof Error) {
|
|
578
|
-
signature.message = 'Error while signing data with privateKey: '
|
|
579
|
-
+ signature.message;
|
|
580
|
-
signature.level = 'client-authentication';
|
|
581
|
-
self.emit('error', signature);
|
|
582
|
-
return tryNextAuth();
|
|
583
|
-
}
|
|
584
|
-
cb(signature);
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
function onUSERAUTH_FAILURE(authsLeft, partial) {
|
|
589
|
-
stream.removeListener('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
|
|
590
|
-
stream.removeListener('USERAUTH_INFO_REQUEST', onUSERAUTH_INFO_REQUEST);
|
|
591
|
-
if (curAuth === 'agent') {
|
|
592
|
-
debug('DEBUG: Client: Agent key #' + (agentKeyPos + 1) + ' failed');
|
|
593
|
-
return tryNextAgentKey();
|
|
594
|
-
} else {
|
|
595
|
-
debug('DEBUG: Client: ' + curAuth + ' auth failed');
|
|
596
|
-
}
|
|
530
|
+
if (channel.incoming.window <= WINDOW_THRESHOLD)
|
|
531
|
+
windowAdjust(channel);
|
|
532
|
+
},
|
|
533
|
+
CHANNEL_EXTENDED_DATA: (p, recipient, data, type) => {
|
|
534
|
+
if (type !== STDERR)
|
|
535
|
+
return;
|
|
597
536
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
stream.once('USERAUTH_SUCCESS', function() {
|
|
603
|
-
stream.removeListener('USERAUTH_FAILURE', onUSERAUTH_FAILURE);
|
|
604
|
-
stream.removeListener('USERAUTH_INFO_REQUEST', onUSERAUTH_INFO_REQUEST);
|
|
537
|
+
const channel = this._chanMgr.get(recipient);
|
|
538
|
+
if (typeof channel !== 'object' || channel === null)
|
|
539
|
+
return;
|
|
605
540
|
|
|
606
|
-
|
|
607
|
-
|
|
541
|
+
// The remote party should not be sending us data if there is no
|
|
542
|
+
// window space available ...
|
|
543
|
+
// TODO: raise error on data with not enough window?
|
|
544
|
+
if (channel.incoming.window === 0)
|
|
545
|
+
return;
|
|
608
546
|
|
|
609
|
-
|
|
547
|
+
channel.incoming.window -= data.length;
|
|
548
|
+
|
|
549
|
+
if (!channel.stderr.push(data)) {
|
|
550
|
+
channel._waitChanDrain = true;
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
610
553
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
554
|
+
if (channel.incoming.window <= WINDOW_THRESHOLD)
|
|
555
|
+
windowAdjust(channel);
|
|
556
|
+
},
|
|
557
|
+
CHANNEL_WINDOW_ADJUST: (p, recipient, amount) => {
|
|
558
|
+
const channel = this._chanMgr.get(recipient);
|
|
559
|
+
if (typeof channel !== 'object' || channel === null)
|
|
560
|
+
return;
|
|
561
|
+
|
|
562
|
+
// The other side is allowing us to send `amount` more bytes of data
|
|
563
|
+
channel.outgoing.window += amount;
|
|
564
|
+
|
|
565
|
+
if (channel._waitWindow) {
|
|
566
|
+
channel._waitWindow = false;
|
|
567
|
+
|
|
568
|
+
if (channel._chunk) {
|
|
569
|
+
channel._write(channel._chunk, null, channel._chunkcb);
|
|
570
|
+
} else if (channel._chunkcb) {
|
|
571
|
+
channel._chunkcb();
|
|
572
|
+
} else if (channel._chunkErr) {
|
|
573
|
+
channel.stderr._write(channel._chunkErr,
|
|
574
|
+
null,
|
|
575
|
+
channel._chunkcbErr);
|
|
576
|
+
} else if (channel._chunkcbErr) {
|
|
577
|
+
channel._chunkcbErr();
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
CHANNEL_SUCCESS: (p, recipient) => {
|
|
582
|
+
const channel = this._chanMgr.get(recipient);
|
|
583
|
+
if (typeof channel !== 'object' || channel === null)
|
|
584
|
+
return;
|
|
585
|
+
|
|
586
|
+
this._resetKA();
|
|
587
|
+
|
|
588
|
+
if (channel._callbacks.length)
|
|
589
|
+
channel._callbacks.shift()(false);
|
|
590
|
+
},
|
|
591
|
+
CHANNEL_FAILURE: (p, recipient) => {
|
|
592
|
+
const channel = this._chanMgr.get(recipient);
|
|
593
|
+
if (typeof channel !== 'object' || channel === null)
|
|
594
|
+
return;
|
|
595
|
+
|
|
596
|
+
this._resetKA();
|
|
597
|
+
|
|
598
|
+
if (channel._callbacks.length)
|
|
599
|
+
channel._callbacks.shift()(true);
|
|
600
|
+
},
|
|
601
|
+
CHANNEL_REQUEST: (p, recipient, type, wantReply, data) => {
|
|
602
|
+
const channel = this._chanMgr.get(recipient);
|
|
603
|
+
if (typeof channel !== 'object' || channel === null)
|
|
604
|
+
return;
|
|
605
|
+
|
|
606
|
+
const exit = channel._exit;
|
|
607
|
+
if (exit.code !== undefined)
|
|
608
|
+
return;
|
|
609
|
+
switch (type) {
|
|
610
|
+
case 'exit-status':
|
|
611
|
+
channel.emit('exit', exit.code = data);
|
|
612
|
+
return;
|
|
613
|
+
case 'exit-signal':
|
|
614
|
+
channel.emit('exit',
|
|
615
|
+
exit.code = null,
|
|
616
|
+
exit.signal = `SIG${data.signal}`,
|
|
617
|
+
exit.dump = data.coreDumped,
|
|
618
|
+
exit.desc = data.errorMessage);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
614
621
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
622
|
+
// Keepalive request? OpenSSH will send one as a channel request if
|
|
623
|
+
// there is a channel open
|
|
624
|
+
|
|
625
|
+
if (wantReply)
|
|
626
|
+
p.channelFailure(channel.outgoing.id);
|
|
627
|
+
},
|
|
628
|
+
CHANNEL_EOF: (p, recipient) => {
|
|
629
|
+
const channel = this._chanMgr.get(recipient);
|
|
630
|
+
if (typeof channel !== 'object' || channel === null)
|
|
631
|
+
return;
|
|
632
|
+
|
|
633
|
+
if (channel.incoming.state !== 'open')
|
|
634
|
+
return;
|
|
635
|
+
channel.incoming.state = 'eof';
|
|
636
|
+
|
|
637
|
+
if (channel.readable)
|
|
638
|
+
channel.push(null);
|
|
639
|
+
if (channel.stderr.readable)
|
|
640
|
+
channel.stderr.push(null);
|
|
641
|
+
},
|
|
642
|
+
CHANNEL_CLOSE: (p, recipient) => {
|
|
643
|
+
onCHANNEL_CLOSE(this, recipient, this._chanMgr.get(recipient));
|
|
644
|
+
},
|
|
645
|
+
},
|
|
621
646
|
});
|
|
622
|
-
});
|
|
623
647
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
648
|
+
sock.pause();
|
|
649
|
+
|
|
650
|
+
// TODO: check keepalive implementation
|
|
651
|
+
// Keepalive-related
|
|
652
|
+
const kainterval = this.config.keepaliveInterval;
|
|
653
|
+
const kacountmax = this.config.keepaliveCountMax;
|
|
654
|
+
let kacount = 0;
|
|
655
|
+
let katimer;
|
|
656
|
+
const sendKA = () => {
|
|
657
|
+
if (++kacount > kacountmax) {
|
|
658
|
+
clearInterval(katimer);
|
|
659
|
+
if (sock.readable) {
|
|
660
|
+
const err = new Error('Keepalive timeout');
|
|
661
|
+
err.level = 'client-timeout';
|
|
662
|
+
this.emit('error', err);
|
|
663
|
+
sock.destroy();
|
|
664
|
+
}
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (isWritable(sock)) {
|
|
668
|
+
// Append dummy callback to keep correct callback order
|
|
669
|
+
callbacks.push(resetKA);
|
|
670
|
+
proto.ping();
|
|
671
|
+
} else {
|
|
672
|
+
clearInterval(katimer);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
function resetKA() {
|
|
676
|
+
if (kainterval > 0) {
|
|
677
|
+
kacount = 0;
|
|
678
|
+
clearInterval(katimer);
|
|
679
|
+
if (isWritable(sock))
|
|
680
|
+
katimer = setInterval(sendKA, kainterval);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
this._resetKA = resetKA;
|
|
629
684
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
685
|
+
const onDone = (() => {
|
|
686
|
+
let called = false;
|
|
687
|
+
return () => {
|
|
688
|
+
if (called)
|
|
689
|
+
return;
|
|
690
|
+
called = true;
|
|
691
|
+
if (wasConnected && !sawHeader) {
|
|
692
|
+
const err =
|
|
693
|
+
makeError('Connection lost before handshake', 'protocol', true);
|
|
694
|
+
this.emit('error', err);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
})();
|
|
698
|
+
const onConnect = (() => {
|
|
699
|
+
let called = false;
|
|
700
|
+
return () => {
|
|
701
|
+
if (called)
|
|
702
|
+
return;
|
|
703
|
+
called = true;
|
|
704
|
+
|
|
705
|
+
wasConnected = true;
|
|
706
|
+
debug && debug('Socket connected');
|
|
707
|
+
this.emit('connect');
|
|
708
|
+
|
|
709
|
+
cryptoInit.then(() => {
|
|
710
|
+
proto.start();
|
|
711
|
+
sock.on('data', (data) => {
|
|
712
|
+
try {
|
|
713
|
+
proto.parse(data, 0, data.length);
|
|
714
|
+
} catch (ex) {
|
|
715
|
+
this.emit('error', ex);
|
|
716
|
+
try {
|
|
717
|
+
if (isWritable(sock))
|
|
718
|
+
sock.end();
|
|
719
|
+
} catch {}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
638
722
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
723
|
+
// Drain stderr if we are connection hopping using an exec stream
|
|
724
|
+
if (sock.stderr && typeof sock.stderr.resume === 'function')
|
|
725
|
+
sock.stderr.resume();
|
|
726
|
+
|
|
727
|
+
sock.resume();
|
|
728
|
+
}).catch((err) => {
|
|
729
|
+
this.emit('error', err);
|
|
730
|
+
try {
|
|
731
|
+
if (isWritable(sock))
|
|
732
|
+
sock.end();
|
|
733
|
+
} catch {}
|
|
734
|
+
});
|
|
735
|
+
};
|
|
736
|
+
})();
|
|
737
|
+
let wasConnected = false;
|
|
738
|
+
sock.on('connect', onConnect)
|
|
739
|
+
.on('timeout', () => {
|
|
740
|
+
this.emit('timeout');
|
|
741
|
+
}).on('error', (err) => {
|
|
742
|
+
debug && debug(`Socket error: ${err.message}`);
|
|
743
|
+
clearTimeout(this._readyTimeout);
|
|
744
|
+
err.level = 'client-socket';
|
|
745
|
+
this.emit('error', err);
|
|
746
|
+
}).on('end', () => {
|
|
747
|
+
debug && debug('Socket ended');
|
|
748
|
+
onDone();
|
|
749
|
+
proto.cleanup();
|
|
750
|
+
clearTimeout(this._readyTimeout);
|
|
751
|
+
clearInterval(katimer);
|
|
752
|
+
this.emit('end');
|
|
753
|
+
}).on('close', () => {
|
|
754
|
+
debug && debug('Socket closed');
|
|
755
|
+
onDone();
|
|
756
|
+
proto.cleanup();
|
|
757
|
+
clearTimeout(this._readyTimeout);
|
|
758
|
+
clearInterval(katimer);
|
|
759
|
+
this.emit('close');
|
|
645
760
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
761
|
+
// Notify outstanding channel requests of disconnection ...
|
|
762
|
+
const callbacks_ = callbacks;
|
|
763
|
+
callbacks = this._callbacks = [];
|
|
764
|
+
const err = new Error('No response from server');
|
|
765
|
+
for (let i = 0; i < callbacks_.length; ++i)
|
|
766
|
+
callbacks_[i](err);
|
|
650
767
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
+ this.config.port
|
|
655
|
-
+ ' ...');
|
|
768
|
+
// Simulate error for any channels waiting to be opened
|
|
769
|
+
this._chanMgr.cleanup(err);
|
|
770
|
+
});
|
|
656
771
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
772
|
+
// Begin authentication handling ===========================================
|
|
773
|
+
let curAuth;
|
|
774
|
+
let curPartial = null;
|
|
775
|
+
let curAuthsLeft = null;
|
|
776
|
+
const authsAllowed = ['none'];
|
|
777
|
+
if (this.config.password !== undefined)
|
|
778
|
+
authsAllowed.push('password');
|
|
779
|
+
if (privateKey !== undefined)
|
|
780
|
+
authsAllowed.push('publickey');
|
|
781
|
+
if (this._agent !== undefined)
|
|
782
|
+
authsAllowed.push('agent');
|
|
783
|
+
if (this.config.tryKeyboard)
|
|
784
|
+
authsAllowed.push('keyboard-interactive');
|
|
785
|
+
if (privateKey !== undefined
|
|
786
|
+
&& this.config.localHostname !== undefined
|
|
787
|
+
&& this.config.localUsername !== undefined) {
|
|
788
|
+
authsAllowed.push('hostbased');
|
|
668
789
|
}
|
|
669
790
|
|
|
670
|
-
if ((
|
|
671
|
-
|
|
672
|
-
else
|
|
673
|
-
|
|
674
|
-
if (err) {
|
|
675
|
-
var error = new Error('Error while looking up '
|
|
676
|
-
+ (forceIPv4 ? 'IPv4' : 'IPv6')
|
|
677
|
-
+ ' address for host '
|
|
678
|
-
+ host
|
|
679
|
-
+ ': ' + err);
|
|
680
|
-
clearTimeout(self._readyTimeout);
|
|
681
|
-
error.level = 'client-dns';
|
|
682
|
-
self.emit('error', error);
|
|
683
|
-
self.emit('close');
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
host = address;
|
|
687
|
-
doConnect();
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
} else {
|
|
691
|
-
startTimeout();
|
|
692
|
-
stream.pipe(sock).pipe(stream);
|
|
693
|
-
}
|
|
791
|
+
if (Array.isArray(authHandler))
|
|
792
|
+
authHandler = makeSimpleAuthHandler(authHandler);
|
|
793
|
+
else if (typeof authHandler !== 'function')
|
|
794
|
+
authHandler = makeSimpleAuthHandler(authsAllowed);
|
|
694
795
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
self.emit('error', err);
|
|
701
|
-
sock.destroy();
|
|
702
|
-
}, self.config.readyTimeout);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
Client.prototype.end = function() {
|
|
708
|
-
if (this._sock
|
|
709
|
-
&& this._sock.writable
|
|
710
|
-
&& this._sshstream
|
|
711
|
-
&& this._sshstream.writable)
|
|
712
|
-
return this._sshstream.disconnect();
|
|
713
|
-
return false;
|
|
714
|
-
};
|
|
715
|
-
|
|
716
|
-
Client.prototype.destroy = function() {
|
|
717
|
-
this._sock && this._sock.destroy();
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
Client.prototype.exec = function(cmd, opts, cb) {
|
|
721
|
-
if (!this._sock
|
|
722
|
-
|| !this._sock.writable
|
|
723
|
-
|| !this._sshstream
|
|
724
|
-
|| !this._sshstream.writable)
|
|
725
|
-
throw new Error('Not connected');
|
|
796
|
+
let hasSentAuth = false;
|
|
797
|
+
const doNextAuth = (nextAuth) => {
|
|
798
|
+
if (hasSentAuth)
|
|
799
|
+
return;
|
|
800
|
+
hasSentAuth = true;
|
|
726
801
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
802
|
+
if (nextAuth === false) {
|
|
803
|
+
const err = new Error('All configured authentication methods failed');
|
|
804
|
+
err.level = 'client-authentication';
|
|
805
|
+
this.emit('error', err);
|
|
806
|
+
this.end();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
731
809
|
|
|
732
|
-
|
|
733
|
-
|
|
810
|
+
if (typeof nextAuth === 'string') {
|
|
811
|
+
// Remain backwards compatible with original `authHandler()` usage,
|
|
812
|
+
// which only supported passing names of next method to try using data
|
|
813
|
+
// from the `connect()` config object
|
|
734
814
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
815
|
+
const type = nextAuth;
|
|
816
|
+
if (authsAllowed.indexOf(type) === -1)
|
|
817
|
+
return skipAuth(`Authentication method not allowed: ${type}`);
|
|
738
818
|
|
|
739
|
-
|
|
819
|
+
const username = this.config.username;
|
|
820
|
+
switch (type) {
|
|
821
|
+
case 'password':
|
|
822
|
+
nextAuth = { type, username, password: this.config.password };
|
|
823
|
+
break;
|
|
824
|
+
case 'publickey':
|
|
825
|
+
nextAuth = { type, username, key: privateKey };
|
|
826
|
+
break;
|
|
827
|
+
case 'hostbased':
|
|
828
|
+
nextAuth = {
|
|
829
|
+
type,
|
|
830
|
+
username,
|
|
831
|
+
key: privateKey,
|
|
832
|
+
localHostname: this.config.localHostname,
|
|
833
|
+
localUsername: this.config.localUsername,
|
|
834
|
+
};
|
|
835
|
+
break;
|
|
836
|
+
case 'agent':
|
|
837
|
+
nextAuth = {
|
|
838
|
+
type,
|
|
839
|
+
username,
|
|
840
|
+
agentCtx: new AgentContext(this._agent),
|
|
841
|
+
};
|
|
842
|
+
break;
|
|
843
|
+
case 'keyboard-interactive':
|
|
844
|
+
nextAuth = {
|
|
845
|
+
type,
|
|
846
|
+
username,
|
|
847
|
+
prompt: (...args) => this.emit('keyboard-interactive', ...args),
|
|
848
|
+
};
|
|
849
|
+
break;
|
|
850
|
+
case 'none':
|
|
851
|
+
nextAuth = { type, username };
|
|
852
|
+
break;
|
|
853
|
+
default:
|
|
854
|
+
return skipAuth(
|
|
855
|
+
`Skipping unsupported authentication method: ${nextAuth}`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
} else if (typeof nextAuth !== 'object' || nextAuth === null) {
|
|
859
|
+
return skipAuth(
|
|
860
|
+
`Skipping invalid authentication attempt: ${nextAuth}`
|
|
861
|
+
);
|
|
862
|
+
} else {
|
|
863
|
+
const username = nextAuth.username;
|
|
864
|
+
if (typeof username !== 'string') {
|
|
865
|
+
return skipAuth(
|
|
866
|
+
`Skipping invalid authentication attempt: ${nextAuth}`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
const type = nextAuth.type;
|
|
870
|
+
switch (type) {
|
|
871
|
+
case 'password': {
|
|
872
|
+
const { password } = nextAuth;
|
|
873
|
+
if (typeof password !== 'string' && !Buffer.isBuffer(password))
|
|
874
|
+
return skipAuth('Skipping invalid password auth attempt');
|
|
875
|
+
nextAuth = { type, username, password };
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
case 'publickey': {
|
|
879
|
+
const key = parseKey(nextAuth.key, nextAuth.passphrase);
|
|
880
|
+
if (key instanceof Error)
|
|
881
|
+
return skipAuth('Skipping invalid key auth attempt');
|
|
882
|
+
if (!key.isPrivateKey())
|
|
883
|
+
return skipAuth('Skipping non-private key');
|
|
884
|
+
nextAuth = { type, username, key };
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
case 'hostbased': {
|
|
888
|
+
const { localHostname, localUsername } = nextAuth;
|
|
889
|
+
const key = parseKey(nextAuth.key, nextAuth.passphrase);
|
|
890
|
+
if (key instanceof Error
|
|
891
|
+
|| typeof localHostname !== 'string'
|
|
892
|
+
|| typeof localUsername !== 'string') {
|
|
893
|
+
return skipAuth('Skipping invalid hostbased auth attempt');
|
|
894
|
+
}
|
|
895
|
+
if (!key.isPrivateKey())
|
|
896
|
+
return skipAuth('Skipping non-private key');
|
|
897
|
+
nextAuth = { type, username, key, localHostname, localUsername };
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
case 'agent': {
|
|
901
|
+
let agent = nextAuth.agent;
|
|
902
|
+
if (typeof agent === 'string' && agent.length) {
|
|
903
|
+
agent = createAgent(agent);
|
|
904
|
+
} else if (!isAgent(agent)) {
|
|
905
|
+
return skipAuth(
|
|
906
|
+
`Skipping invalid agent: ${nextAuth.agent}`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
nextAuth = { type, username, agentCtx: new AgentContext(agent) };
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
case 'keyboard-interactive': {
|
|
913
|
+
const { prompt } = nextAuth;
|
|
914
|
+
if (typeof prompt !== 'function') {
|
|
915
|
+
return skipAuth(
|
|
916
|
+
'Skipping invalid keyboard-interactive auth attempt'
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
nextAuth = { type, username, prompt };
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
case 'none':
|
|
923
|
+
nextAuth = { type, username };
|
|
924
|
+
break;
|
|
925
|
+
default:
|
|
926
|
+
return skipAuth(
|
|
927
|
+
`Skipping unsupported authentication method: ${nextAuth}`
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
curAuth = nextAuth;
|
|
932
|
+
|
|
933
|
+
// Begin authentication method's process
|
|
934
|
+
try {
|
|
935
|
+
const username = curAuth.username;
|
|
936
|
+
switch (curAuth.type) {
|
|
937
|
+
case 'password':
|
|
938
|
+
proto.authPassword(username, curAuth.password);
|
|
939
|
+
break;
|
|
940
|
+
case 'publickey':
|
|
941
|
+
proto.authPK(username, curAuth.key);
|
|
942
|
+
break;
|
|
943
|
+
case 'hostbased':
|
|
944
|
+
proto.authHostbased(username,
|
|
945
|
+
curAuth.key,
|
|
946
|
+
curAuth.localHostname,
|
|
947
|
+
curAuth.localUsername,
|
|
948
|
+
(buf, cb) => {
|
|
949
|
+
const signature = curAuth.key.sign(buf);
|
|
950
|
+
if (signature instanceof Error) {
|
|
951
|
+
signature.message =
|
|
952
|
+
`Error while signing with key: ${signature.message}`;
|
|
953
|
+
signature.level = 'client-authentication';
|
|
954
|
+
this.emit('error', signature);
|
|
955
|
+
return tryNextAuth();
|
|
956
|
+
}
|
|
740
957
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
958
|
+
cb(signature);
|
|
959
|
+
});
|
|
960
|
+
break;
|
|
961
|
+
case 'agent':
|
|
962
|
+
curAuth.agentCtx.init((err) => {
|
|
963
|
+
if (err) {
|
|
964
|
+
err.level = 'agent';
|
|
965
|
+
this.emit('error', err);
|
|
966
|
+
return tryNextAuth();
|
|
967
|
+
}
|
|
968
|
+
tryNextAgentKey();
|
|
969
|
+
});
|
|
970
|
+
break;
|
|
971
|
+
case 'keyboard-interactive':
|
|
972
|
+
proto.authKeyboard(username);
|
|
973
|
+
break;
|
|
974
|
+
case 'none':
|
|
975
|
+
proto.authNone(username);
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
} finally {
|
|
979
|
+
hasSentAuth = false;
|
|
745
980
|
}
|
|
746
|
-
|
|
747
|
-
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
function skipAuth(msg) {
|
|
984
|
+
debug && debug(msg);
|
|
985
|
+
process.nextTick(tryNextAuth);
|
|
748
986
|
}
|
|
749
987
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
});
|
|
988
|
+
function tryNextAuth() {
|
|
989
|
+
hasSentAuth = false;
|
|
990
|
+
const auth = authHandler(curAuthsLeft, curPartial, doNextAuth);
|
|
991
|
+
if (hasSentAuth || auth === undefined)
|
|
992
|
+
return;
|
|
993
|
+
doNextAuth(auth);
|
|
757
994
|
}
|
|
758
995
|
|
|
759
|
-
|
|
760
|
-
if (
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
996
|
+
const tryNextAgentKey = () => {
|
|
997
|
+
if (curAuth.type === 'agent') {
|
|
998
|
+
const key = curAuth.agentCtx.nextKey();
|
|
999
|
+
if (key === false) {
|
|
1000
|
+
debug && debug('Agent: No more keys left to try');
|
|
1001
|
+
debug && debug('Client: agent auth failed');
|
|
1002
|
+
tryNextAuth();
|
|
1003
|
+
} else {
|
|
1004
|
+
const pos = curAuth.agentCtx.pos();
|
|
1005
|
+
debug && debug(`Agent: Trying key #${pos + 1}`);
|
|
1006
|
+
proto.authPK(curAuth.username, key);
|
|
1007
|
+
}
|
|
765
1008
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
const startTimeout = () => {
|
|
1012
|
+
if (this.config.readyTimeout > 0) {
|
|
1013
|
+
this._readyTimeout = setTimeout(() => {
|
|
1014
|
+
const err = new Error('Timed out while waiting for handshake');
|
|
1015
|
+
err.level = 'client-timeout';
|
|
1016
|
+
this.emit('error', err);
|
|
1017
|
+
sock.destroy();
|
|
1018
|
+
}, this.config.readyTimeout);
|
|
770
1019
|
}
|
|
771
|
-
}
|
|
1020
|
+
};
|
|
772
1021
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
Client.prototype.shell = function(wndopts, opts, cb) {
|
|
779
|
-
if (!this._sock
|
|
780
|
-
|| !this._sock.writable
|
|
781
|
-
|| !this._sshstream
|
|
782
|
-
|| !this._sshstream.writable)
|
|
783
|
-
throw new Error('Not connected');
|
|
784
|
-
|
|
785
|
-
// start an interactive terminal/shell session
|
|
786
|
-
var self = this;
|
|
787
|
-
|
|
788
|
-
if (typeof wndopts === 'function') {
|
|
789
|
-
cb = wndopts;
|
|
790
|
-
wndopts = opts = undefined;
|
|
791
|
-
} else if (typeof opts === 'function') {
|
|
792
|
-
cb = opts;
|
|
793
|
-
opts = undefined;
|
|
794
|
-
}
|
|
795
|
-
if (wndopts && (wndopts.x11 !== undefined || wndopts.env !== undefined)) {
|
|
796
|
-
opts = wndopts;
|
|
797
|
-
wndopts = undefined;
|
|
798
|
-
}
|
|
1022
|
+
if (!cfg.sock) {
|
|
1023
|
+
let host = this.config.host;
|
|
1024
|
+
const forceIPv4 = this.config.forceIPv4;
|
|
1025
|
+
const forceIPv6 = this.config.forceIPv6;
|
|
799
1026
|
|
|
800
|
-
|
|
801
|
-
if (err)
|
|
802
|
-
return cb(err);
|
|
1027
|
+
debug && debug(`Client: Trying ${host} on port ${this.config.port} ...`);
|
|
803
1028
|
|
|
804
|
-
|
|
1029
|
+
const doConnect = () => {
|
|
1030
|
+
startTimeout();
|
|
1031
|
+
sock.connect({
|
|
1032
|
+
host,
|
|
1033
|
+
port: this.config.port,
|
|
1034
|
+
localAddress: this.config.localAddress,
|
|
1035
|
+
localPort: this.config.localPort
|
|
1036
|
+
});
|
|
1037
|
+
sock.setNoDelay(true);
|
|
1038
|
+
sock.setMaxListeners(0);
|
|
1039
|
+
sock.setTimeout(typeof cfg.timeout === 'number' ? cfg.timeout : 0);
|
|
1040
|
+
};
|
|
805
1041
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1042
|
+
if ((!forceIPv4 && !forceIPv6) || (forceIPv4 && forceIPv6)) {
|
|
1043
|
+
doConnect();
|
|
1044
|
+
} else {
|
|
1045
|
+
dnsLookup(host, (forceIPv4 ? 4 : 6), (err, address, family) => {
|
|
1046
|
+
if (err) {
|
|
1047
|
+
const type = (forceIPv4 ? 'IPv4' : 'IPv6');
|
|
1048
|
+
const error = new Error(
|
|
1049
|
+
`Error while looking up ${type} address for '${host}': ${err}`
|
|
1050
|
+
);
|
|
1051
|
+
clearTimeout(this._readyTimeout);
|
|
1052
|
+
error.level = 'client-dns';
|
|
1053
|
+
this.emit('error', error);
|
|
1054
|
+
this.emit('close');
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
host = address;
|
|
1058
|
+
doConnect();
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
} else {
|
|
1062
|
+
// Custom socket passed in
|
|
1063
|
+
startTimeout();
|
|
1064
|
+
if (typeof sock.connecting === 'boolean') {
|
|
1065
|
+
// net.Socket
|
|
1066
|
+
|
|
1067
|
+
if (!sock.connecting) {
|
|
1068
|
+
// Already connected
|
|
1069
|
+
onConnect();
|
|
1070
|
+
}
|
|
1071
|
+
} else {
|
|
1072
|
+
// Assume socket/stream is already "connected"
|
|
1073
|
+
onConnect();
|
|
810
1074
|
}
|
|
811
|
-
if (todo.length)
|
|
812
|
-
todo.shift()();
|
|
813
1075
|
}
|
|
814
1076
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1077
|
+
return this;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
end() {
|
|
1081
|
+
if (this._sock && isWritable(this._sock)) {
|
|
1082
|
+
this._protocol.disconnect(DISCONNECT_REASON.BY_APPLICATION);
|
|
1083
|
+
this._sock.end();
|
|
820
1084
|
}
|
|
1085
|
+
return this;
|
|
1086
|
+
}
|
|
821
1087
|
|
|
822
|
-
|
|
823
|
-
|
|
1088
|
+
destroy() {
|
|
1089
|
+
this._sock && isWritable(this._sock) && this._sock.destroy();
|
|
1090
|
+
return this;
|
|
1091
|
+
}
|
|
824
1092
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
1093
|
+
exec(cmd, opts, cb) {
|
|
1094
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1095
|
+
throw new Error('Not connected');
|
|
1096
|
+
|
|
1097
|
+
if (typeof opts === 'function') {
|
|
1098
|
+
cb = opts;
|
|
1099
|
+
opts = {};
|
|
833
1100
|
}
|
|
834
1101
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
Client.prototype.subsys = function(name, cb) {
|
|
841
|
-
if (!this._sock
|
|
842
|
-
|| !this._sock.writable
|
|
843
|
-
|| !this._sshstream
|
|
844
|
-
|| !this._sshstream.writable)
|
|
845
|
-
throw new Error('Not connected');
|
|
846
|
-
|
|
847
|
-
return openChannel(this, 'session', function(err, chan) {
|
|
848
|
-
if (err)
|
|
849
|
-
return cb(err);
|
|
850
|
-
|
|
851
|
-
reqSubsystem(chan, name, function(err, stream) {
|
|
852
|
-
if (err)
|
|
853
|
-
return cb(err);
|
|
854
|
-
|
|
855
|
-
cb(undefined, stream);
|
|
856
|
-
});
|
|
857
|
-
});
|
|
858
|
-
};
|
|
859
|
-
|
|
860
|
-
Client.prototype.sftp = function(cb) {
|
|
861
|
-
if (!this._sock
|
|
862
|
-
|| !this._sock.writable
|
|
863
|
-
|| !this._sshstream
|
|
864
|
-
|| !this._sshstream.writable)
|
|
865
|
-
throw new Error('Not connected');
|
|
866
|
-
|
|
867
|
-
var self = this;
|
|
868
|
-
|
|
869
|
-
// start an SFTP session
|
|
870
|
-
return openChannel(this, 'session', function(err, chan) {
|
|
871
|
-
if (err)
|
|
872
|
-
return cb(err);
|
|
873
|
-
|
|
874
|
-
reqSubsystem(chan, 'sftp', function(err, stream) {
|
|
875
|
-
if (err)
|
|
876
|
-
return cb(err);
|
|
877
|
-
|
|
878
|
-
var serverIdentRaw = self._sshstream._state.incoming.identRaw;
|
|
879
|
-
var cfg = { debug: self.config.debug };
|
|
880
|
-
var sftp = new SFTPStream(cfg, serverIdentRaw);
|
|
881
|
-
|
|
882
|
-
function onError(err) {
|
|
883
|
-
sftp.removeListener('ready', onReady);
|
|
884
|
-
stream.removeListener('exit', onExit);
|
|
1102
|
+
const extraOpts = { allowHalfOpen: (opts.allowHalfOpen !== false) };
|
|
1103
|
+
|
|
1104
|
+
openChannel(this, 'session', extraOpts, (err, chan) => {
|
|
1105
|
+
if (err) {
|
|
885
1106
|
cb(err);
|
|
1107
|
+
return;
|
|
886
1108
|
}
|
|
887
1109
|
|
|
888
|
-
|
|
889
|
-
sftp.removeListener('error', onError);
|
|
890
|
-
stream.removeListener('exit', onExit);
|
|
891
|
-
cb(undefined, new SFTPWrapper(sftp));
|
|
892
|
-
}
|
|
1110
|
+
const todo = [];
|
|
893
1111
|
|
|
894
|
-
function
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
msg = 'Received exit code '
|
|
900
|
-
+ code
|
|
901
|
-
+ ' while establishing SFTP session';
|
|
902
|
-
} else {
|
|
903
|
-
msg = 'Received signal '
|
|
904
|
-
+ signal
|
|
905
|
-
+ ' while establishing SFTP session';
|
|
1112
|
+
function reqCb(err) {
|
|
1113
|
+
if (err) {
|
|
1114
|
+
chan.close();
|
|
1115
|
+
cb(err);
|
|
1116
|
+
return;
|
|
906
1117
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
err.signal = signal;
|
|
910
|
-
cb(err);
|
|
1118
|
+
if (todo.length)
|
|
1119
|
+
todo.shift()();
|
|
911
1120
|
}
|
|
912
1121
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1122
|
+
if (this.config.allowAgentFwd === true
|
|
1123
|
+
|| (opts
|
|
1124
|
+
&& opts.agentForward === true
|
|
1125
|
+
&& this._agent !== undefined)) {
|
|
1126
|
+
todo.push(() => reqAgentFwd(chan, reqCb));
|
|
1127
|
+
}
|
|
918
1128
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1129
|
+
if (typeof opts === 'object' && opts !== null) {
|
|
1130
|
+
if (typeof opts.env === 'object' && opts.env !== null)
|
|
1131
|
+
reqEnv(chan, opts.env);
|
|
1132
|
+
if ((typeof opts.pty === 'object' && opts.pty !== null)
|
|
1133
|
+
|| opts.pty === true) {
|
|
1134
|
+
todo.push(() => reqPty(chan, opts.pty, reqCb));
|
|
1135
|
+
}
|
|
1136
|
+
if ((typeof opts.x11 === 'object' && opts.x11 !== null)
|
|
1137
|
+
|| opts.x11 === 'number'
|
|
1138
|
+
|| opts.x11 === true) {
|
|
1139
|
+
todo.push(() => reqX11(chan, opts.x11, reqCb));
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
923
1142
|
|
|
924
|
-
|
|
1143
|
+
todo.push(() => reqExec(chan, cmd, opts, cb));
|
|
1144
|
+
todo.shift()();
|
|
925
1145
|
});
|
|
926
|
-
});
|
|
927
|
-
};
|
|
928
1146
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|| !this._sock.writable
|
|
932
|
-
|| !this._sshstream
|
|
933
|
-
|| !this._sshstream.writable)
|
|
934
|
-
throw new Error('Not connected');
|
|
1147
|
+
return this;
|
|
1148
|
+
}
|
|
935
1149
|
|
|
936
|
-
|
|
937
|
-
|
|
1150
|
+
shell(wndopts, opts, cb) {
|
|
1151
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1152
|
+
throw new Error('Not connected');
|
|
938
1153
|
|
|
939
|
-
|
|
940
|
-
|
|
1154
|
+
if (typeof wndopts === 'function') {
|
|
1155
|
+
cb = wndopts;
|
|
1156
|
+
wndopts = opts = undefined;
|
|
1157
|
+
} else if (typeof opts === 'function') {
|
|
1158
|
+
cb = opts;
|
|
1159
|
+
opts = undefined;
|
|
1160
|
+
}
|
|
1161
|
+
if (wndopts && (wndopts.x11 !== undefined || wndopts.env !== undefined)) {
|
|
1162
|
+
opts = wndopts;
|
|
1163
|
+
wndopts = undefined;
|
|
1164
|
+
}
|
|
941
1165
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
return
|
|
946
|
-
? had_err
|
|
947
|
-
: new Error('Unable to bind to ' + bindAddr + ':' + bindPort));
|
|
1166
|
+
openChannel(this, 'session', (err, chan) => {
|
|
1167
|
+
if (err) {
|
|
1168
|
+
cb(err);
|
|
1169
|
+
return;
|
|
948
1170
|
}
|
|
949
1171
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
if (
|
|
954
|
-
|
|
1172
|
+
const todo = [];
|
|
1173
|
+
|
|
1174
|
+
function reqCb(err) {
|
|
1175
|
+
if (err) {
|
|
1176
|
+
chan.close();
|
|
1177
|
+
cb(err);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (todo.length)
|
|
1181
|
+
todo.shift()();
|
|
955
1182
|
}
|
|
956
1183
|
|
|
957
|
-
|
|
1184
|
+
if (this.config.allowAgentFwd === true
|
|
1185
|
+
|| (opts
|
|
1186
|
+
&& opts.agentForward === true
|
|
1187
|
+
&& this._agent !== undefined)) {
|
|
1188
|
+
todo.push(() => reqAgentFwd(chan, reqCb));
|
|
1189
|
+
}
|
|
958
1190
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
}
|
|
1191
|
+
if (wndopts !== false)
|
|
1192
|
+
todo.push(() => reqPty(chan, wndopts, reqCb));
|
|
962
1193
|
|
|
963
|
-
|
|
964
|
-
|
|
1194
|
+
if (typeof opts === 'object' && opts !== null) {
|
|
1195
|
+
if (typeof opts.env === 'object' && opts.env !== null)
|
|
1196
|
+
reqEnv(chan, opts.env);
|
|
1197
|
+
if ((typeof opts.x11 === 'object' && opts.x11 !== null)
|
|
1198
|
+
|| opts.x11 === 'number'
|
|
1199
|
+
|| opts.x11 === true) {
|
|
1200
|
+
todo.push(() => reqX11(chan, opts.x11, reqCb));
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
965
1203
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|| !this._sshstream
|
|
970
|
-
|| !this._sshstream.writable)
|
|
971
|
-
throw new Error('Not connected');
|
|
1204
|
+
todo.push(() => reqShell(chan, cb));
|
|
1205
|
+
todo.shift()();
|
|
1206
|
+
});
|
|
972
1207
|
|
|
973
|
-
|
|
974
|
-
|
|
1208
|
+
return this;
|
|
1209
|
+
}
|
|
975
1210
|
|
|
976
|
-
|
|
977
|
-
|
|
1211
|
+
subsys(name, cb) {
|
|
1212
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1213
|
+
throw new Error('Not connected');
|
|
978
1214
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
return
|
|
983
|
-
? had_err
|
|
984
|
-
: new Error('Unable to unbind from '
|
|
985
|
-
+ bindAddr + ':' + bindPort));
|
|
1215
|
+
openChannel(this, 'session', (err, chan) => {
|
|
1216
|
+
if (err) {
|
|
1217
|
+
cb(err);
|
|
1218
|
+
return;
|
|
986
1219
|
}
|
|
987
1220
|
|
|
988
|
-
|
|
1221
|
+
reqSubsystem(chan, name, (err, stream) => {
|
|
1222
|
+
if (err) {
|
|
1223
|
+
cb(err);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
989
1226
|
|
|
990
|
-
|
|
1227
|
+
cb(undefined, stream);
|
|
1228
|
+
});
|
|
991
1229
|
});
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
return this._sshstream.cancelTcpipForward(bindAddr, bindPort, wantReply);
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
Client.prototype.forwardOut = function(srcIP, srcPort, dstIP, dstPort, cb) {
|
|
998
|
-
if (!this._sock
|
|
999
|
-
|| !this._sock.writable
|
|
1000
|
-
|| !this._sshstream
|
|
1001
|
-
|| !this._sshstream.writable)
|
|
1002
|
-
throw new Error('Not connected');
|
|
1003
1230
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
var cfg = {
|
|
1007
|
-
srcIP: srcIP,
|
|
1008
|
-
srcPort: srcPort,
|
|
1009
|
-
dstIP: dstIP,
|
|
1010
|
-
dstPort: dstPort
|
|
1011
|
-
};
|
|
1231
|
+
return this;
|
|
1232
|
+
}
|
|
1012
1233
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1234
|
+
forwardIn(bindAddr, bindPort, cb) {
|
|
1235
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1236
|
+
throw new Error('Not connected');
|
|
1015
1237
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|| !this._sock.writable
|
|
1019
|
-
|| !this._sshstream
|
|
1020
|
-
|| !this._sshstream.writable)
|
|
1021
|
-
throw new Error('Not connected');
|
|
1238
|
+
// Send a request for the server to start forwarding TCP connections to us
|
|
1239
|
+
// on a particular address and port
|
|
1022
1240
|
|
|
1023
|
-
|
|
1241
|
+
const wantReply = (typeof cb === 'function');
|
|
1024
1242
|
|
|
1025
|
-
if (!this.config.strictVendor
|
|
1026
|
-
|| (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
|
|
1027
1243
|
if (wantReply) {
|
|
1028
|
-
this._callbacks.push(
|
|
1244
|
+
this._callbacks.push((had_err, data) => {
|
|
1029
1245
|
if (had_err) {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1246
|
+
cb(had_err !== true
|
|
1247
|
+
? had_err
|
|
1248
|
+
: new Error(`Unable to bind to ${bindAddr}:${bindPort}`));
|
|
1249
|
+
return;
|
|
1033
1250
|
}
|
|
1034
1251
|
|
|
1035
|
-
|
|
1252
|
+
let realPort = bindPort;
|
|
1253
|
+
if (bindPort === 0 && data && data.length >= 4) {
|
|
1254
|
+
realPort = readUInt32BE(data, 0);
|
|
1255
|
+
if (!(this._protocol._compatFlags & COMPAT.DYN_RPORT_BUG))
|
|
1256
|
+
bindPort = realPort;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
this._forwarding[`${bindAddr}:${bindPort}`] = realPort;
|
|
1260
|
+
|
|
1261
|
+
cb(undefined, realPort);
|
|
1036
1262
|
});
|
|
1037
1263
|
}
|
|
1038
1264
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
cb(new Error('strictVendor enabled and server is not OpenSSH or compatible version'));
|
|
1043
|
-
});
|
|
1265
|
+
this._protocol.tcpipForward(bindAddr, bindPort, wantReply);
|
|
1266
|
+
|
|
1267
|
+
return this;
|
|
1044
1268
|
}
|
|
1045
1269
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1270
|
+
unforwardIn(bindAddr, bindPort, cb) {
|
|
1271
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1272
|
+
throw new Error('Not connected');
|
|
1048
1273
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|| !this._sock.writable
|
|
1052
|
-
|| !this._sshstream
|
|
1053
|
-
|| !this._sshstream.writable)
|
|
1054
|
-
throw new Error('Not connected');
|
|
1274
|
+
// Send a request to stop forwarding us new connections for a particular
|
|
1275
|
+
// address and port
|
|
1055
1276
|
|
|
1056
|
-
|
|
1057
|
-
var self = this;
|
|
1277
|
+
const wantReply = (typeof cb === 'function');
|
|
1058
1278
|
|
|
1059
|
-
if (!this.config.strictVendor
|
|
1060
|
-
|| (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
|
|
1061
1279
|
if (wantReply) {
|
|
1062
|
-
this._callbacks.push(
|
|
1280
|
+
this._callbacks.push((had_err) => {
|
|
1063
1281
|
if (had_err) {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1282
|
+
cb(had_err !== true
|
|
1283
|
+
? had_err
|
|
1284
|
+
: new Error(`Unable to unbind from ${bindAddr}:${bindPort}`));
|
|
1285
|
+
return;
|
|
1067
1286
|
}
|
|
1068
|
-
|
|
1287
|
+
|
|
1288
|
+
delete this._forwarding[`${bindAddr}:${bindPort}`];
|
|
1289
|
+
|
|
1069
1290
|
cb();
|
|
1070
1291
|
});
|
|
1071
1292
|
}
|
|
1072
1293
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
cb(new Error('strictVendor enabled and server is not OpenSSH or compatible version'));
|
|
1077
|
-
});
|
|
1294
|
+
this._protocol.cancelTcpipForward(bindAddr, bindPort, wantReply);
|
|
1295
|
+
|
|
1296
|
+
return this;
|
|
1078
1297
|
}
|
|
1079
1298
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1299
|
+
forwardOut(srcIP, srcPort, dstIP, dstPort, cb) {
|
|
1300
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1301
|
+
throw new Error('Not connected');
|
|
1082
1302
|
|
|
1083
|
-
|
|
1084
|
-
if (!this._sock
|
|
1085
|
-
|| !this._sock.writable
|
|
1086
|
-
|| !this._sshstream
|
|
1087
|
-
|| !this._sshstream.writable)
|
|
1088
|
-
throw new Error('Not connected');
|
|
1303
|
+
// Send a request to forward a TCP connection to the server
|
|
1089
1304
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1305
|
+
const cfg = {
|
|
1306
|
+
srcIP: srcIP,
|
|
1307
|
+
srcPort: srcPort,
|
|
1308
|
+
dstIP: dstIP,
|
|
1309
|
+
dstPort: dstPort
|
|
1310
|
+
};
|
|
1092
1311
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
if (wantReply) {
|
|
1096
|
-
this._callbacks.push(function(had_err) {
|
|
1097
|
-
if (had_err) {
|
|
1098
|
-
return cb(had_err !== true
|
|
1099
|
-
? had_err
|
|
1100
|
-
: new Error('Unable to unbind on ' + socketPath));
|
|
1101
|
-
}
|
|
1102
|
-
delete self._forwardingUnix[socketPath];
|
|
1103
|
-
cb();
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1312
|
+
if (typeof cb !== 'function')
|
|
1313
|
+
cb = noop;
|
|
1106
1314
|
|
|
1107
|
-
|
|
1108
|
-
wantReply);
|
|
1109
|
-
} else if (wantReply) {
|
|
1110
|
-
process.nextTick(function() {
|
|
1111
|
-
cb(new Error('strictVendor enabled and server is not OpenSSH or compatible version'));
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1315
|
+
openChannel(this, 'direct-tcpip', cfg, cb);
|
|
1114
1316
|
|
|
1115
|
-
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
|
-
Client.prototype.openssh_forwardOutStreamLocal = function(socketPath, cb) {
|
|
1119
|
-
if (!this._sock
|
|
1120
|
-
|| !this._sock.writable
|
|
1121
|
-
|| !this._sshstream
|
|
1122
|
-
|| !this._sshstream.writable)
|
|
1123
|
-
throw new Error('Not connected');
|
|
1124
|
-
|
|
1125
|
-
if (!this.config.strictVendor
|
|
1126
|
-
|| (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
|
|
1127
|
-
var cfg = { socketPath: socketPath };
|
|
1128
|
-
return openChannel(this, 'direct-streamlocal@openssh.com', cfg, cb);
|
|
1129
|
-
} else {
|
|
1130
|
-
process.nextTick(function() {
|
|
1131
|
-
cb(new Error('strictVendor enabled and server is not OpenSSH or compatible version'));
|
|
1132
|
-
});
|
|
1317
|
+
return this;
|
|
1133
1318
|
}
|
|
1134
1319
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1320
|
+
openssh_noMoreSessions(cb) {
|
|
1321
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1322
|
+
throw new Error('Not connected');
|
|
1323
|
+
|
|
1324
|
+
const wantReply = (typeof cb === 'function');
|
|
1325
|
+
|
|
1326
|
+
if (!this.config.strictVendor
|
|
1327
|
+
|| (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
|
|
1328
|
+
if (wantReply) {
|
|
1329
|
+
this._callbacks.push((had_err) => {
|
|
1330
|
+
if (had_err) {
|
|
1331
|
+
cb(had_err !== true
|
|
1332
|
+
? had_err
|
|
1333
|
+
: new Error('Unable to disable future sessions'));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1137
1336
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
var localChan = nextChannel(self);
|
|
1142
|
-
var initWindow = Channel.MAX_WINDOW;
|
|
1143
|
-
var maxPacket = Channel.PACKET_SIZE;
|
|
1144
|
-
var ret = true;
|
|
1337
|
+
cb();
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1145
1340
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1341
|
+
this._protocol.openssh_noMoreSessions(wantReply);
|
|
1342
|
+
return this;
|
|
1343
|
+
}
|
|
1148
1344
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1345
|
+
if (!wantReply)
|
|
1346
|
+
return this;
|
|
1347
|
+
|
|
1348
|
+
process.nextTick(
|
|
1349
|
+
cb,
|
|
1350
|
+
new Error(
|
|
1351
|
+
'strictVendor enabled and server is not OpenSSH or compatible version'
|
|
1352
|
+
)
|
|
1353
|
+
);
|
|
1153
1354
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
var sshstream = self._sshstream;
|
|
1157
|
-
sshstream.once('CHANNEL_OPEN_CONFIRMATION:' + localChan, onSuccess)
|
|
1158
|
-
.once('CHANNEL_OPEN_FAILURE:' + localChan, onFailure)
|
|
1159
|
-
.once('CHANNEL_CLOSE:' + localChan, onFailure);
|
|
1160
|
-
|
|
1161
|
-
if (type === 'session')
|
|
1162
|
-
ret = sshstream.session(localChan, initWindow, maxPacket);
|
|
1163
|
-
else if (type === 'direct-tcpip')
|
|
1164
|
-
ret = sshstream.directTcpip(localChan, initWindow, maxPacket, opts);
|
|
1165
|
-
else if (type === 'direct-streamlocal@openssh.com') {
|
|
1166
|
-
ret = sshstream.openssh_directStreamLocal(localChan,
|
|
1167
|
-
initWindow,
|
|
1168
|
-
maxPacket,
|
|
1169
|
-
opts);
|
|
1355
|
+
return this;
|
|
1170
1356
|
}
|
|
1171
1357
|
|
|
1172
|
-
|
|
1358
|
+
openssh_forwardInStreamLocal(socketPath, cb) {
|
|
1359
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1360
|
+
throw new Error('Not connected');
|
|
1361
|
+
|
|
1362
|
+
const wantReply = (typeof cb === 'function');
|
|
1363
|
+
|
|
1364
|
+
if (!this.config.strictVendor
|
|
1365
|
+
|| (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
|
|
1366
|
+
if (wantReply) {
|
|
1367
|
+
this._callbacks.push((had_err) => {
|
|
1368
|
+
if (had_err) {
|
|
1369
|
+
cb(had_err !== true
|
|
1370
|
+
? had_err
|
|
1371
|
+
: new Error(`Unable to bind to ${socketPath}`));
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
this._forwardingUnix[socketPath] = true;
|
|
1375
|
+
cb();
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1173
1378
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1379
|
+
this._protocol.openssh_streamLocalForward(socketPath, wantReply);
|
|
1380
|
+
return this;
|
|
1381
|
+
}
|
|
1177
1382
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1383
|
+
if (!wantReply)
|
|
1384
|
+
return this;
|
|
1385
|
+
|
|
1386
|
+
process.nextTick(
|
|
1387
|
+
cb,
|
|
1388
|
+
new Error(
|
|
1389
|
+
'strictVendor enabled and server is not OpenSSH or compatible version'
|
|
1390
|
+
)
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
return this;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
openssh_unforwardInStreamLocal(socketPath, cb) {
|
|
1397
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1398
|
+
throw new Error('Not connected');
|
|
1399
|
+
|
|
1400
|
+
const wantReply = (typeof cb === 'function');
|
|
1401
|
+
|
|
1402
|
+
if (!this.config.strictVendor
|
|
1403
|
+
|| (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
|
|
1404
|
+
if (wantReply) {
|
|
1405
|
+
this._callbacks.push((had_err) => {
|
|
1406
|
+
if (had_err) {
|
|
1407
|
+
cb(had_err !== true
|
|
1408
|
+
? had_err
|
|
1409
|
+
: new Error(`Unable to unbind from ${socketPath}`));
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
delete this._forwardingUnix[socketPath];
|
|
1413
|
+
cb();
|
|
1414
|
+
});
|
|
1191
1415
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1416
|
+
|
|
1417
|
+
this._protocol.openssh_cancelStreamLocalForward(socketPath, wantReply);
|
|
1418
|
+
return this;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (!wantReply)
|
|
1422
|
+
return this;
|
|
1423
|
+
|
|
1424
|
+
process.nextTick(
|
|
1425
|
+
cb,
|
|
1426
|
+
new Error(
|
|
1427
|
+
'strictVendor enabled and server is not OpenSSH or compatible version'
|
|
1428
|
+
)
|
|
1429
|
+
);
|
|
1430
|
+
|
|
1431
|
+
return this;
|
|
1194
1432
|
}
|
|
1195
1433
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
else if (typeof info === 'object' && info !== null) {
|
|
1208
|
-
err = new Error('(SSH) Channel open failure: ' + info.description);
|
|
1209
|
-
err.reason = info.reason;
|
|
1210
|
-
err.lang = info.lang;
|
|
1211
|
-
} else {
|
|
1212
|
-
err = new Error('(SSH) Channel open failure: '
|
|
1213
|
-
+ 'server closed channel unexpectedly');
|
|
1214
|
-
err.reason = err.lang = '';
|
|
1434
|
+
openssh_forwardOutStreamLocal(socketPath, cb) {
|
|
1435
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1436
|
+
throw new Error('Not connected');
|
|
1437
|
+
|
|
1438
|
+
if (typeof cb !== 'function')
|
|
1439
|
+
cb = noop;
|
|
1440
|
+
|
|
1441
|
+
if (!this.config.strictVendor
|
|
1442
|
+
|| (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
|
|
1443
|
+
openChannel(this, 'direct-streamlocal@openssh.com', { socketPath }, cb);
|
|
1444
|
+
return this;
|
|
1215
1445
|
}
|
|
1216
|
-
|
|
1446
|
+
process.nextTick(
|
|
1447
|
+
cb,
|
|
1448
|
+
new Error(
|
|
1449
|
+
'strictVendor enabled and server is not OpenSSH or compatible version'
|
|
1450
|
+
)
|
|
1451
|
+
);
|
|
1452
|
+
|
|
1453
|
+
return this;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
sftp(cb) {
|
|
1457
|
+
if (!this._sock || !isWritable(this._sock))
|
|
1458
|
+
throw new Error('Not connected');
|
|
1459
|
+
|
|
1460
|
+
openChannel(this, 'sftp', (err, sftp) => {
|
|
1461
|
+
if (err) {
|
|
1462
|
+
cb(err);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
reqSubsystem(sftp, 'sftp', (err, sftp_) => {
|
|
1467
|
+
if (err) {
|
|
1468
|
+
cb(err);
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function removeListeners() {
|
|
1473
|
+
sftp.removeListener('ready', onReady);
|
|
1474
|
+
sftp.removeListener('error', onError);
|
|
1475
|
+
sftp.removeListener('exit', onExit);
|
|
1476
|
+
sftp.removeListener('close', onExit);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function onReady() {
|
|
1480
|
+
// TODO: do not remove exit/close in case remote end closes the
|
|
1481
|
+
// channel abruptly and we need to notify outstanding callbacks
|
|
1482
|
+
removeListeners();
|
|
1483
|
+
cb(undefined, sftp);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function onError(err) {
|
|
1487
|
+
removeListeners();
|
|
1488
|
+
cb(err);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function onExit(code, signal) {
|
|
1492
|
+
removeListeners();
|
|
1493
|
+
let msg;
|
|
1494
|
+
if (typeof code === 'number')
|
|
1495
|
+
msg = `Received exit code ${code} while establishing SFTP session`;
|
|
1496
|
+
else if (signal !== undefined)
|
|
1497
|
+
msg = `Received signal ${signal} while establishing SFTP session`;
|
|
1498
|
+
else
|
|
1499
|
+
msg = 'Received unexpected SFTP session termination';
|
|
1500
|
+
const err = new Error(msg);
|
|
1501
|
+
err.code = code;
|
|
1502
|
+
err.signal = signal;
|
|
1503
|
+
cb(err);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
sftp.on('ready', onReady)
|
|
1507
|
+
.on('error', onError)
|
|
1508
|
+
.on('exit', onExit)
|
|
1509
|
+
.on('close', onExit);
|
|
1510
|
+
|
|
1511
|
+
sftp._init();
|
|
1512
|
+
});
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
return this;
|
|
1217
1516
|
}
|
|
1218
1517
|
}
|
|
1219
1518
|
|
|
1220
|
-
function
|
|
1221
|
-
//
|
|
1519
|
+
function openChannel(self, type, opts, cb) {
|
|
1520
|
+
// Ask the server to open a channel for some purpose
|
|
1521
|
+
// (e.g. session (sftp, exec, shell), or forwarding a TCP connection
|
|
1522
|
+
const initWindow = MAX_WINDOW;
|
|
1523
|
+
const maxPacket = PACKET_SIZE;
|
|
1222
1524
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1525
|
+
if (typeof opts === 'function') {
|
|
1526
|
+
cb = opts;
|
|
1527
|
+
opts = {};
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
const wrapper = (err, stream) => {
|
|
1531
|
+
cb(err, stream);
|
|
1532
|
+
};
|
|
1533
|
+
wrapper.type = type;
|
|
1226
1534
|
|
|
1227
|
-
|
|
1228
|
-
for (var i = 0, channels = self._channels; i < MAX_CHANNEL; ++i)
|
|
1229
|
-
if (!channels[i])
|
|
1230
|
-
return i;
|
|
1535
|
+
const localChan = self._chanMgr.add(wrapper);
|
|
1231
1536
|
|
|
1232
|
-
|
|
1537
|
+
if (localChan === -1) {
|
|
1538
|
+
cb(new Error('No free channels available'));
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
switch (type) {
|
|
1543
|
+
case 'session':
|
|
1544
|
+
case 'sftp':
|
|
1545
|
+
self._protocol.session(localChan, initWindow, maxPacket);
|
|
1546
|
+
break;
|
|
1547
|
+
case 'direct-tcpip':
|
|
1548
|
+
self._protocol.directTcpip(localChan, initWindow, maxPacket, opts);
|
|
1549
|
+
break;
|
|
1550
|
+
case 'direct-streamlocal@openssh.com':
|
|
1551
|
+
self._protocol.openssh_directStreamLocal(
|
|
1552
|
+
localChan, initWindow, maxPacket, opts
|
|
1553
|
+
);
|
|
1554
|
+
break;
|
|
1555
|
+
default:
|
|
1556
|
+
throw new Error(`Unsupported channel type: ${type}`);
|
|
1557
|
+
}
|
|
1233
1558
|
}
|
|
1234
1559
|
|
|
1235
1560
|
function reqX11(chan, screen, cb) {
|
|
1236
|
-
//
|
|
1237
|
-
|
|
1561
|
+
// Asks server to start sending us X11 connections
|
|
1562
|
+
const cfg = {
|
|
1238
1563
|
single: false,
|
|
1239
1564
|
protocol: 'MIT-MAGIC-COOKIE-1',
|
|
1240
1565
|
cookie: undefined,
|
|
@@ -1253,29 +1578,29 @@ function reqX11(chan, screen, cb) {
|
|
|
1253
1578
|
if (typeof screen.cookie === 'string')
|
|
1254
1579
|
cfg.cookie = screen.cookie;
|
|
1255
1580
|
else if (Buffer.isBuffer(screen.cookie))
|
|
1256
|
-
cfg.cookie = screen.cookie.
|
|
1581
|
+
cfg.cookie = screen.cookie.hexSlice(0, screen.cookie.length);
|
|
1257
1582
|
}
|
|
1258
1583
|
if (cfg.cookie === undefined)
|
|
1259
1584
|
cfg.cookie = randomCookie();
|
|
1260
1585
|
|
|
1261
|
-
|
|
1586
|
+
const wantReply = (typeof cb === 'function');
|
|
1262
1587
|
|
|
1263
1588
|
if (chan.outgoing.state !== 'open') {
|
|
1264
|
-
|
|
1265
|
-
|
|
1589
|
+
if (wantReply)
|
|
1590
|
+
cb(new Error('Channel is not open'));
|
|
1591
|
+
return;
|
|
1266
1592
|
}
|
|
1267
1593
|
|
|
1268
1594
|
if (wantReply) {
|
|
1269
|
-
chan._callbacks.push(
|
|
1595
|
+
chan._callbacks.push((had_err) => {
|
|
1270
1596
|
if (had_err) {
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
: new Error('Unable to request X11'));
|
|
1597
|
+
cb(had_err !== true ? had_err : new Error('Unable to request X11'));
|
|
1598
|
+
return;
|
|
1274
1599
|
}
|
|
1275
1600
|
|
|
1276
1601
|
chan._hasX11 = true;
|
|
1277
1602
|
++chan._client._acceptX11;
|
|
1278
|
-
chan.once('close',
|
|
1603
|
+
chan.once('close', () => {
|
|
1279
1604
|
if (chan._client._acceptX11)
|
|
1280
1605
|
--chan._client._acceptX11;
|
|
1281
1606
|
});
|
|
@@ -1284,20 +1609,20 @@ function reqX11(chan, screen, cb) {
|
|
|
1284
1609
|
});
|
|
1285
1610
|
}
|
|
1286
1611
|
|
|
1287
|
-
|
|
1612
|
+
chan._client._protocol.x11Forward(chan.outgoing.id, cfg, wantReply);
|
|
1288
1613
|
}
|
|
1289
1614
|
|
|
1290
1615
|
function reqPty(chan, opts, cb) {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
if (typeof opts === 'function')
|
|
1616
|
+
let rows = 24;
|
|
1617
|
+
let cols = 80;
|
|
1618
|
+
let width = 640;
|
|
1619
|
+
let height = 480;
|
|
1620
|
+
let term = 'vt100';
|
|
1621
|
+
let modes = null;
|
|
1622
|
+
|
|
1623
|
+
if (typeof opts === 'function') {
|
|
1299
1624
|
cb = opts;
|
|
1300
|
-
else if (typeof opts === 'object' && opts !== null) {
|
|
1625
|
+
} else if (typeof opts === 'object' && opts !== null) {
|
|
1301
1626
|
if (typeof opts.rows === 'number')
|
|
1302
1627
|
rows = opts.rows;
|
|
1303
1628
|
if (typeof opts.cols === 'number')
|
|
@@ -1312,149 +1637,154 @@ function reqPty(chan, opts, cb) {
|
|
|
1312
1637
|
modes = opts.modes;
|
|
1313
1638
|
}
|
|
1314
1639
|
|
|
1315
|
-
|
|
1640
|
+
const wantReply = (typeof cb === 'function');
|
|
1316
1641
|
|
|
1317
1642
|
if (chan.outgoing.state !== 'open') {
|
|
1318
|
-
|
|
1319
|
-
|
|
1643
|
+
if (wantReply)
|
|
1644
|
+
cb(new Error('Channel is not open'));
|
|
1645
|
+
return;
|
|
1320
1646
|
}
|
|
1321
1647
|
|
|
1322
1648
|
if (wantReply) {
|
|
1323
|
-
chan._callbacks.push(
|
|
1649
|
+
chan._callbacks.push((had_err) => {
|
|
1324
1650
|
if (had_err) {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1651
|
+
cb(had_err !== true
|
|
1652
|
+
? had_err
|
|
1653
|
+
: new Error('Unable to request a pseudo-terminal'));
|
|
1654
|
+
return;
|
|
1328
1655
|
}
|
|
1329
1656
|
cb();
|
|
1330
1657
|
});
|
|
1331
1658
|
}
|
|
1332
1659
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1660
|
+
chan._client._protocol.pty(chan.outgoing.id,
|
|
1661
|
+
rows,
|
|
1662
|
+
cols,
|
|
1663
|
+
height,
|
|
1664
|
+
width,
|
|
1665
|
+
term,
|
|
1666
|
+
modes,
|
|
1667
|
+
wantReply);
|
|
1341
1668
|
}
|
|
1342
1669
|
|
|
1343
1670
|
function reqAgentFwd(chan, cb) {
|
|
1344
|
-
|
|
1671
|
+
const wantReply = (typeof cb === 'function');
|
|
1345
1672
|
|
|
1346
1673
|
if (chan.outgoing.state !== 'open') {
|
|
1347
1674
|
wantReply && cb(new Error('Channel is not open'));
|
|
1348
|
-
return
|
|
1349
|
-
}
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
if (chan._client._agentFwdEnabled) {
|
|
1350
1678
|
wantReply && cb(false);
|
|
1351
|
-
return
|
|
1679
|
+
return;
|
|
1352
1680
|
}
|
|
1353
1681
|
|
|
1354
1682
|
chan._client._agentFwdEnabled = true;
|
|
1355
1683
|
|
|
1356
|
-
chan._callbacks.push(
|
|
1684
|
+
chan._callbacks.push((had_err) => {
|
|
1357
1685
|
if (had_err) {
|
|
1358
1686
|
chan._client._agentFwdEnabled = false;
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1687
|
+
if (wantReply) {
|
|
1688
|
+
cb(had_err !== true
|
|
1689
|
+
? had_err
|
|
1690
|
+
: new Error('Unable to request agent forwarding'));
|
|
1691
|
+
}
|
|
1362
1692
|
return;
|
|
1363
1693
|
}
|
|
1364
1694
|
|
|
1365
|
-
|
|
1695
|
+
if (wantReply)
|
|
1696
|
+
cb();
|
|
1366
1697
|
});
|
|
1367
1698
|
|
|
1368
|
-
|
|
1699
|
+
chan._client._protocol.openssh_agentForward(chan.outgoing.id, true);
|
|
1369
1700
|
}
|
|
1370
1701
|
|
|
1371
1702
|
function reqShell(chan, cb) {
|
|
1372
1703
|
if (chan.outgoing.state !== 'open') {
|
|
1373
1704
|
cb(new Error('Channel is not open'));
|
|
1374
|
-
return
|
|
1705
|
+
return;
|
|
1375
1706
|
}
|
|
1376
|
-
|
|
1707
|
+
|
|
1708
|
+
chan._callbacks.push((had_err) => {
|
|
1377
1709
|
if (had_err) {
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
: new Error('Unable to open shell'));
|
|
1710
|
+
cb(had_err !== true ? had_err : new Error('Unable to open shell'));
|
|
1711
|
+
return;
|
|
1381
1712
|
}
|
|
1382
1713
|
chan.subtype = 'shell';
|
|
1383
1714
|
cb(undefined, chan);
|
|
1384
1715
|
});
|
|
1385
1716
|
|
|
1386
|
-
|
|
1717
|
+
chan._client._protocol.shell(chan.outgoing.id, true);
|
|
1387
1718
|
}
|
|
1388
1719
|
|
|
1389
1720
|
function reqExec(chan, cmd, opts, cb) {
|
|
1390
1721
|
if (chan.outgoing.state !== 'open') {
|
|
1391
1722
|
cb(new Error('Channel is not open'));
|
|
1392
|
-
return
|
|
1723
|
+
return;
|
|
1393
1724
|
}
|
|
1394
|
-
|
|
1725
|
+
|
|
1726
|
+
chan._callbacks.push((had_err) => {
|
|
1395
1727
|
if (had_err) {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
: new Error('Unable to exec'));
|
|
1728
|
+
cb(had_err !== true ? had_err : new Error('Unable to exec'));
|
|
1729
|
+
return;
|
|
1399
1730
|
}
|
|
1400
1731
|
chan.subtype = 'exec';
|
|
1401
1732
|
chan.allowHalfOpen = (opts.allowHalfOpen !== false);
|
|
1402
1733
|
cb(undefined, chan);
|
|
1403
1734
|
});
|
|
1404
1735
|
|
|
1405
|
-
|
|
1736
|
+
chan._client._protocol.exec(chan.outgoing.id, cmd, true);
|
|
1406
1737
|
}
|
|
1407
1738
|
|
|
1408
1739
|
function reqEnv(chan, env) {
|
|
1409
1740
|
if (chan.outgoing.state !== 'open')
|
|
1410
|
-
return
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
var key;
|
|
1414
|
-
var val;
|
|
1415
|
-
|
|
1416
|
-
for (var i = 0, len = keys.length; i < len; ++i) {
|
|
1417
|
-
key = keys[i];
|
|
1418
|
-
val = env[key];
|
|
1419
|
-
ret = chan._client._sshstream.env(chan.outgoing.id, key, val, false);
|
|
1420
|
-
}
|
|
1741
|
+
return;
|
|
1742
|
+
|
|
1743
|
+
const keys = Object.keys(env || {});
|
|
1421
1744
|
|
|
1422
|
-
|
|
1745
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
1746
|
+
const key = keys[i];
|
|
1747
|
+
const val = env[key];
|
|
1748
|
+
chan._client._protocol.env(chan.outgoing.id, key, val, false);
|
|
1749
|
+
}
|
|
1423
1750
|
}
|
|
1424
1751
|
|
|
1425
1752
|
function reqSubsystem(chan, name, cb) {
|
|
1426
1753
|
if (chan.outgoing.state !== 'open') {
|
|
1427
1754
|
cb(new Error('Channel is not open'));
|
|
1428
|
-
return
|
|
1755
|
+
return;
|
|
1429
1756
|
}
|
|
1430
|
-
|
|
1757
|
+
|
|
1758
|
+
chan._callbacks.push((had_err) => {
|
|
1431
1759
|
if (had_err) {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1760
|
+
cb(had_err !== true
|
|
1761
|
+
? had_err
|
|
1762
|
+
: new Error(`Unable to start subsystem: ${name}`));
|
|
1763
|
+
return;
|
|
1435
1764
|
}
|
|
1436
1765
|
chan.subtype = 'subsystem';
|
|
1437
1766
|
cb(undefined, chan);
|
|
1438
1767
|
});
|
|
1439
1768
|
|
|
1440
|
-
|
|
1769
|
+
chan._client._protocol.subsystem(chan.outgoing.id, name, true);
|
|
1441
1770
|
}
|
|
1442
1771
|
|
|
1772
|
+
// TODO: inline implementation into single call site
|
|
1443
1773
|
function onCHANNEL_OPEN(self, info) {
|
|
1444
|
-
//
|
|
1774
|
+
// The server is trying to open a channel with us, this is usually when
|
|
1445
1775
|
// we asked the server to forward us connections on some port and now they
|
|
1446
1776
|
// are asking us to accept/deny an incoming connection on their side
|
|
1447
1777
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1778
|
+
let localChan = -1;
|
|
1779
|
+
let reason;
|
|
1450
1780
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1781
|
+
const accept = () => {
|
|
1782
|
+
const chanInfo = {
|
|
1453
1783
|
type: info.type,
|
|
1454
1784
|
incoming: {
|
|
1455
1785
|
id: localChan,
|
|
1456
|
-
window:
|
|
1457
|
-
packetSize:
|
|
1786
|
+
window: MAX_WINDOW,
|
|
1787
|
+
packetSize: PACKET_SIZE,
|
|
1458
1788
|
state: 'open'
|
|
1459
1789
|
},
|
|
1460
1790
|
outgoing: {
|
|
@@ -1464,111 +1794,221 @@ function onCHANNEL_OPEN(self, info) {
|
|
|
1464
1794
|
state: 'open'
|
|
1465
1795
|
}
|
|
1466
1796
|
};
|
|
1467
|
-
|
|
1797
|
+
const stream = new Channel(self, chanInfo);
|
|
1798
|
+
self._chanMgr.update(localChan, stream);
|
|
1468
1799
|
|
|
1469
|
-
self.
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1800
|
+
self._protocol.channelOpenConfirm(info.sender,
|
|
1801
|
+
localChan,
|
|
1802
|
+
MAX_WINDOW,
|
|
1803
|
+
PACKET_SIZE);
|
|
1473
1804
|
return stream;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1805
|
+
};
|
|
1806
|
+
const reject = () => {
|
|
1476
1807
|
if (reason === undefined) {
|
|
1477
|
-
if (localChan ===
|
|
1478
|
-
reason =
|
|
1808
|
+
if (localChan === -1)
|
|
1809
|
+
reason = CHANNEL_OPEN_FAILURE.RESOURCE_SHORTAGE;
|
|
1479
1810
|
else
|
|
1480
|
-
reason =
|
|
1811
|
+
reason = CHANNEL_OPEN_FAILURE.CONNECT_FAILED;
|
|
1481
1812
|
}
|
|
1482
1813
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1814
|
+
if (localChan !== -1)
|
|
1815
|
+
self._chanMgr.remove(localChan);
|
|
1485
1816
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|| (info.type === 'auth-agent@openssh.com'
|
|
1501
|
-
&& !self._agentFwdEnabled)
|
|
1502
|
-
);
|
|
1817
|
+
self._protocol.channelOpenFail(info.sender, reason, '');
|
|
1818
|
+
};
|
|
1819
|
+
const reserveChannel = () => {
|
|
1820
|
+
localChan = self._chanMgr.add();
|
|
1821
|
+
|
|
1822
|
+
if (localChan === -1) {
|
|
1823
|
+
reason = CHANNEL_OPEN_FAILURE.RESOURCE_SHORTAGE;
|
|
1824
|
+
if (self.config.debug) {
|
|
1825
|
+
self.config.debug(
|
|
1826
|
+
'Client: Automatic rejection of incoming channel open: '
|
|
1827
|
+
+ 'no channels available'
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1503
1831
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1832
|
+
return (localChan !== -1);
|
|
1833
|
+
};
|
|
1506
1834
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1835
|
+
const data = info.data;
|
|
1836
|
+
switch (info.type) {
|
|
1837
|
+
case 'forwarded-tcpip': {
|
|
1838
|
+
const val = self._forwarding[`${data.destIP}:${data.destPort}`];
|
|
1839
|
+
if (val !== undefined && reserveChannel()) {
|
|
1840
|
+
if (data.destPort === 0)
|
|
1841
|
+
data.destPort = val;
|
|
1842
|
+
self.emit('tcp connection', data, accept, reject);
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
break;
|
|
1516
1846
|
}
|
|
1847
|
+
case 'forwarded-streamlocal@openssh.com':
|
|
1848
|
+
if (self._forwardingUnix[data.socketPath] !== undefined
|
|
1849
|
+
&& reserveChannel()) {
|
|
1850
|
+
self.emit('unix connection', data, accept, reject);
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
break;
|
|
1854
|
+
case 'auth-agent@openssh.com':
|
|
1855
|
+
if (self._agentFwdEnabled
|
|
1856
|
+
&& typeof self._agent.getStream === 'function'
|
|
1857
|
+
&& reserveChannel()) {
|
|
1858
|
+
self._agent.getStream((err, stream) => {
|
|
1859
|
+
if (err)
|
|
1860
|
+
return reject();
|
|
1861
|
+
|
|
1862
|
+
const upstream = accept();
|
|
1863
|
+
upstream.pipe(stream).pipe(upstream);
|
|
1864
|
+
});
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
break;
|
|
1868
|
+
case 'x11':
|
|
1869
|
+
if (self._acceptX11 !== 0 && reserveChannel()) {
|
|
1870
|
+
self.emit('x11', data, accept, reject);
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
break;
|
|
1874
|
+
default:
|
|
1875
|
+
// Automatically reject any unsupported channel open requests
|
|
1876
|
+
reason = CHANNEL_OPEN_FAILURE.UNKNOWN_CHANNEL_TYPE;
|
|
1877
|
+
if (self.config.debug) {
|
|
1878
|
+
self.config.debug(
|
|
1879
|
+
'Client: Automatic rejection of unsupported incoming channel open '
|
|
1880
|
+
+ `type: ${info.type}`
|
|
1881
|
+
);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
if (reason === undefined) {
|
|
1886
|
+
reason = CHANNEL_OPEN_FAILURE.ADMINISTRATIVELY_PROHIBITED;
|
|
1887
|
+
if (self.config.debug) {
|
|
1888
|
+
self.config.debug(
|
|
1889
|
+
'Client: Automatic rejection of unexpected incoming channel open for: '
|
|
1890
|
+
+ info.type
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
reject();
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
const randomCookie = (() => {
|
|
1899
|
+
const buffer = Buffer.allocUnsafe(16);
|
|
1900
|
+
return () => {
|
|
1901
|
+
randomFillSync(buffer, 0, 16);
|
|
1902
|
+
return buffer.hexSlice(0, 16);
|
|
1903
|
+
};
|
|
1904
|
+
})();
|
|
1905
|
+
|
|
1906
|
+
function makeSimpleAuthHandler(authList) {
|
|
1907
|
+
if (!Array.isArray(authList))
|
|
1908
|
+
throw new Error('authList must be an array');
|
|
1517
1909
|
|
|
1518
|
-
|
|
1910
|
+
let a = 0;
|
|
1911
|
+
return (authsLeft, partialSuccess, cb) => {
|
|
1912
|
+
if (a === authList.length)
|
|
1913
|
+
return false;
|
|
1914
|
+
return authList[a++];
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function hostKeysProve(client, keys_, cb) {
|
|
1919
|
+
if (!client._sock || !isWritable(client._sock))
|
|
1920
|
+
return;
|
|
1519
1921
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1922
|
+
if (typeof cb !== 'function')
|
|
1923
|
+
cb = noop;
|
|
1522
1924
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1925
|
+
if (!Array.isArray(keys_))
|
|
1926
|
+
throw new TypeError('Invalid keys argument type');
|
|
1927
|
+
|
|
1928
|
+
const keys = [];
|
|
1929
|
+
for (const key of keys_) {
|
|
1930
|
+
const parsed = parseKey(key);
|
|
1931
|
+
if (parsed instanceof Error)
|
|
1932
|
+
throw parsed;
|
|
1933
|
+
keys.push(parsed);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
if (!client.config.strictVendor
|
|
1937
|
+
|| (client.config.strictVendor && RE_OPENSSH.test(client._remoteVer))) {
|
|
1938
|
+
client._callbacks.push((had_err, data) => {
|
|
1939
|
+
if (had_err) {
|
|
1940
|
+
cb(had_err !== true
|
|
1941
|
+
? had_err
|
|
1942
|
+
: new Error('Server failed to prove supplied keys'));
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// TODO: move all of this parsing/verifying logic out of the client?
|
|
1947
|
+
const ret = [];
|
|
1948
|
+
let keyIdx = 0;
|
|
1949
|
+
bufferParser.init(data, 0);
|
|
1950
|
+
while (bufferParser.avail()) {
|
|
1951
|
+
if (keyIdx === keys.length)
|
|
1952
|
+
break;
|
|
1953
|
+
const key = keys[keyIdx++];
|
|
1954
|
+
const keyPublic = key.getPublicSSH();
|
|
1955
|
+
|
|
1956
|
+
const sigEntry = bufferParser.readString();
|
|
1957
|
+
sigParser.init(sigEntry, 0);
|
|
1958
|
+
const type = sigParser.readString(true);
|
|
1959
|
+
let value = sigParser.readString();
|
|
1960
|
+
|
|
1961
|
+
let algo;
|
|
1962
|
+
if (type !== key.type) {
|
|
1963
|
+
if (key.type === 'ssh-rsa') {
|
|
1964
|
+
switch (type) {
|
|
1965
|
+
case 'rsa-sha2-256':
|
|
1966
|
+
algo = 'sha256';
|
|
1967
|
+
break;
|
|
1968
|
+
case 'rsa-sha2-512':
|
|
1969
|
+
algo = 'sha512';
|
|
1970
|
+
break;
|
|
1971
|
+
default:
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
} else {
|
|
1975
|
+
continue;
|
|
1976
|
+
}
|
|
1529
1977
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1978
|
+
|
|
1979
|
+
const sessionID = client._protocol._kex.sessionID;
|
|
1980
|
+
const verifyData = Buffer.allocUnsafe(
|
|
1981
|
+
4 + 29 + 4 + sessionID.length + 4 + keyPublic.length
|
|
1982
|
+
);
|
|
1983
|
+
let p = 0;
|
|
1984
|
+
writeUInt32BE(verifyData, 29, p);
|
|
1985
|
+
verifyData.utf8Write('hostkeys-prove-00@openssh.com', p += 4, 29);
|
|
1986
|
+
writeUInt32BE(verifyData, sessionID.length, p += 29);
|
|
1987
|
+
bufferCopy(sessionID, verifyData, 0, sessionID.length, p += 4);
|
|
1988
|
+
writeUInt32BE(verifyData, keyPublic.length, p += sessionID.length);
|
|
1989
|
+
bufferCopy(keyPublic, verifyData, 0, keyPublic.length, p += 4);
|
|
1990
|
+
|
|
1991
|
+
if (!(value = sigSSHToASN1(value, type)))
|
|
1992
|
+
continue;
|
|
1993
|
+
if (key.verify(verifyData, value, algo) === true)
|
|
1994
|
+
ret.push(key);
|
|
1537
1995
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
// automatically reject any unsupported channel open requests
|
|
1541
|
-
self.config.debug('DEBUG: Client: Automatic rejection of incoming channel open: unsupported type: '
|
|
1542
|
-
+ info.type);
|
|
1543
|
-
reason = consts.CHANNEL_OPEN_FAILURE.UNKNOWN_CHANNEL_TYPE;
|
|
1544
|
-
reject();
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1996
|
+
sigParser.clear();
|
|
1997
|
+
bufferParser.clear();
|
|
1547
1998
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
return buffer.toString('hex');
|
|
1554
|
-
};
|
|
1555
|
-
} else {
|
|
1556
|
-
return function randomCookie() {
|
|
1557
|
-
return crypto.randomBytes(16).toString('hex');
|
|
1558
|
-
};
|
|
1999
|
+
cb(null, ret);
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
client._protocol.openssh_hostKeysProve(keys);
|
|
2003
|
+
return;
|
|
1559
2004
|
}
|
|
1560
|
-
})();
|
|
1561
2005
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
Client.HTTPAgent = HTTPAgents.SSHTTPAgent;
|
|
1572
|
-
Client.HTTPSAgent = HTTPAgents.SSHTTPSAgent;
|
|
1573
|
-
|
|
1574
|
-
module.exports = Client; // backwards compatibility
|
|
2006
|
+
process.nextTick(
|
|
2007
|
+
cb,
|
|
2008
|
+
new Error(
|
|
2009
|
+
'strictVendor enabled and server is not OpenSSH or compatible version'
|
|
2010
|
+
)
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
module.exports = Client;
|