UNPKG

47.3 kBJavaScriptView Raw
1var crypto = require('crypto');
2var Socket = require('net').Socket;
3var dnsLookup = require('dns').lookup;
4var EventEmitter = require('events').EventEmitter;
5var inherits = require('util').inherits;
6var HASHES = crypto.getHashes();
7
8var ssh2_streams = require('ssh2-streams');
9var SSH2Stream = ssh2_streams.SSH2Stream;
10var SFTPStream = ssh2_streams.SFTPStream;
11var consts = ssh2_streams.constants;
12var BUGS = consts.BUGS;
13var ALGORITHMS = consts.ALGORITHMS;
14var parseKey = ssh2_streams.utils.parseKey;
15
16var Channel = require('./Channel');
17var agentQuery = require('./agent');
18var SFTPWrapper = require('./SFTPWrapper');
19var readUInt32BE = require('./buffer-helpers').readUInt32BE;
20
21var MAX_CHANNEL = Math.pow(2, 32) - 1;
22var RE_OPENSSH = /^OpenSSH_(?:(?![0-4])\d)|(?:\d{2,})/;
23var DEBUG_NOOP = function(msg) {};
24
25function Client() {
26 if (!(this instanceof Client))
27 return new Client();
28
29 EventEmitter.call(this);
30
31 this.config = {
32 host: undefined,
33 port: undefined,
34 localAddress: undefined,
35 localPort: undefined,
36 forceIPv4: undefined,
37 forceIPv6: undefined,
38 keepaliveCountMax: undefined,
39 keepaliveInterval: undefined,
40 readyTimeout: undefined,
41
42 username: undefined,
43 password: undefined,
44 privateKey: undefined,
45 tryKeyboard: undefined,
46 agent: undefined,
47 allowAgentFwd: undefined,
48 authHandler: undefined,
49
50 hostHashAlgo: undefined,
51 hostHashCb: undefined,
52 strictVendor: undefined,
53 debug: undefined
54 };
55
56 this._readyTimeout = undefined;
57 this._channels = undefined;
58 this._callbacks = undefined;
59 this._forwarding = undefined;
60 this._forwardingUnix = undefined;
61 this._acceptX11 = undefined;
62 this._agentFwdEnabled = undefined;
63 this._curChan = undefined;
64 this._remoteVer = undefined;
65
66 this._sshstream = undefined;
67 this._sock = undefined;
68 this._resetKA = undefined;
69}
70inherits(Client, EventEmitter);
71
72Client.prototype.connect = function(cfg) {
73 var self = this;
74
75 if (this._sock && this._sock.writable) {
76 this.once('close', function() {
77 self.connect(cfg);
78 });
79 this.end();
80 return;
81 }
82
83 this.config.host = cfg.hostname || cfg.host || 'localhost';
84 this.config.port = cfg.port || 22;
85 this.config.localAddress = (typeof cfg.localAddress === 'string'
86 ? cfg.localAddress
87 : undefined);
88 this.config.localPort = (typeof cfg.localPort === 'string'
89 || typeof cfg.localPort === 'number'
90 ? cfg.localPort
91 : undefined);
92 this.config.forceIPv4 = cfg.forceIPv4 || false;
93 this.config.forceIPv6 = cfg.forceIPv6 || false;
94 this.config.keepaliveCountMax = (typeof cfg.keepaliveCountMax === 'number'
95 && cfg.keepaliveCountMax >= 0
96 ? cfg.keepaliveCountMax
97 : 3);
98 this.config.keepaliveInterval = (typeof cfg.keepaliveInterval === 'number'
99 && cfg.keepaliveInterval > 0
100 ? cfg.keepaliveInterval
101 : 0);
102 this.config.readyTimeout = (typeof cfg.readyTimeout === 'number'
103 && cfg.readyTimeout >= 0
104 ? cfg.readyTimeout
105 : 20000);
106
107 var algorithms = {
108 kex: undefined,
109 kexBuf: undefined,
110 cipher: undefined,
111 cipherBuf: undefined,
112 serverHostKey: undefined,
113 serverHostKeyBuf: undefined,
114 hmac: undefined,
115 hmacBuf: undefined,
116 compress: undefined,
117 compressBuf: undefined
118 };
119 var i;
120 if (typeof cfg.algorithms === 'object' && cfg.algorithms !== null) {
121 var algosSupported;
122 var algoList;
123
124 algoList = cfg.algorithms.kex;
125 if (Array.isArray(algoList) && algoList.length > 0) {
126 algosSupported = ALGORITHMS.SUPPORTED_KEX;
127 for (i = 0; i < algoList.length; ++i) {
128 if (algosSupported.indexOf(algoList[i]) === -1)
129 throw new Error('Unsupported key exchange algorithm: ' + algoList[i]);
130 }
131 algorithms.kex = algoList;
132 }
133
134 algoList = cfg.algorithms.cipher;
135 if (Array.isArray(algoList) && algoList.length > 0) {
136 algosSupported = ALGORITHMS.SUPPORTED_CIPHER;
137 for (i = 0; i < algoList.length; ++i) {
138 if (algosSupported.indexOf(algoList[i]) === -1)
139 throw new Error('Unsupported cipher algorithm: ' + algoList[i]);
140 }
141 algorithms.cipher = algoList;
142 }
143
144 algoList = cfg.algorithms.serverHostKey;
145 if (Array.isArray(algoList) && algoList.length > 0) {
146 algosSupported = ALGORITHMS.SUPPORTED_SERVER_HOST_KEY;
147 for (i = 0; i < algoList.length; ++i) {
148 if (algosSupported.indexOf(algoList[i]) === -1) {
149 throw new Error('Unsupported server host key algorithm: '
150 + algoList[i]);
151 }
152 }
153 algorithms.serverHostKey = algoList;
154 }
155
156 algoList = cfg.algorithms.hmac;
157 if (Array.isArray(algoList) && algoList.length > 0) {
158 algosSupported = ALGORITHMS.SUPPORTED_HMAC;
159 for (i = 0; i < algoList.length; ++i) {
160 if (algosSupported.indexOf(algoList[i]) === -1)
161 throw new Error('Unsupported HMAC algorithm: ' + algoList[i]);
162 }
163 algorithms.hmac = algoList;
164 }
165
166 algoList = cfg.algorithms.compress;
167 if (Array.isArray(algoList) && algoList.length > 0) {
168 algosSupported = ALGORITHMS.SUPPORTED_COMPRESS;
169 for (i = 0; i < algoList.length; ++i) {
170 if (algosSupported.indexOf(algoList[i]) === -1)
171 throw new Error('Unsupported compression algorithm: ' + algoList[i]);
172 }
173 algorithms.compress = algoList;
174 }
175 }
176 if (algorithms.compress === undefined) {
177 if (cfg.compress) {
178 algorithms.compress = ['zlib@openssh.com', 'zlib'];
179 if (cfg.compress !== 'force')
180 algorithms.compress.push('none');
181 } else if (cfg.compress === false)
182 algorithms.compress = ['none'];
183 }
184
185 if (typeof cfg.username === 'string')
186 this.config.username = cfg.username;
187 else if (typeof cfg.user === 'string')
188 this.config.username = cfg.user;
189 else
190 throw new Error('Invalid username');
191
192 this.config.password = (typeof cfg.password === 'string'
193 ? cfg.password
194 : undefined);
195 this.config.privateKey = (typeof cfg.privateKey === 'string'
196 || Buffer.isBuffer(cfg.privateKey)
197 ? cfg.privateKey
198 : undefined);
199 this.config.localHostname = (typeof cfg.localHostname === 'string'
200 && cfg.localHostname.length
201 ? cfg.localHostname
202 : undefined);
203 this.config.localUsername = (typeof cfg.localUsername === 'string'
204 && cfg.localUsername.length
205 ? cfg.localUsername
206 : undefined);
207 this.config.tryKeyboard = (cfg.tryKeyboard === true);
208 this.config.agent = (typeof cfg.agent === 'string' && cfg.agent.length
209 ? cfg.agent
210 : undefined);
211 this.config.allowAgentFwd = (cfg.agentForward === true
212 && this.config.agent !== undefined);
213 var authHandler = this.config.authHandler = (
214 typeof cfg.authHandler === 'function' ? cfg.authHandler : undefined
215 );
216
217 this.config.strictVendor = (typeof cfg.strictVendor === 'boolean'
218 ? cfg.strictVendor
219 : true);
220
221 var debug = this.config.debug = (typeof cfg.debug === 'function'
222 ? cfg.debug
223 : DEBUG_NOOP);
224
225 if (cfg.agentForward === true && !this.config.allowAgentFwd)
226 throw new Error('You must set a valid agent path to allow agent forwarding');
227
228 var callbacks = this._callbacks = [];
229 this._channels = {};
230 this._forwarding = {};
231 this._forwardingUnix = {};
232 this._acceptX11 = 0;
233 this._agentFwdEnabled = false;
234 this._curChan = -1;
235 this._remoteVer = undefined;
236 var privateKey;
237
238 if (this.config.privateKey) {
239 privateKey = parseKey(this.config.privateKey, cfg.passphrase);
240 if (privateKey instanceof Error)
241 throw new Error('Cannot parse privateKey: ' + privateKey.message);
242 if (Array.isArray(privateKey))
243 privateKey = privateKey[0]; // OpenSSH's newer format only stores 1 key for now
244 if (privateKey.getPrivatePEM() === null)
245 throw new Error('privateKey value does not contain a (valid) private key');
246 }
247
248 var stream = this._sshstream = new SSH2Stream({
249 algorithms: algorithms,
250 debug: (debug === DEBUG_NOOP ? undefined : debug)
251 });
252 var sock = this._sock = (cfg.sock || new Socket());
253
254 // drain stderr if we are connection hopping using an exec stream
255 if (this._sock.stderr)
256 this._sock.stderr.resume();
257
258 // keepalive-related
259 var kainterval = this.config.keepaliveInterval;
260 var kacountmax = this.config.keepaliveCountMax;
261 var kacount = 0;
262 var katimer;
263 function sendKA() {
264 if (++kacount > kacountmax) {
265 clearInterval(katimer);
266 if (sock.readable) {
267 var err = new Error('Keepalive timeout');
268 err.level = 'client-timeout';
269 self.emit('error', err);
270 sock.destroy();
271 }
272 return;
273 }
274 if (sock.writable) {
275 // append dummy callback to keep correct callback order
276 callbacks.push(resetKA);
277 stream.ping();
278 } else
279 clearInterval(katimer);
280 }
281 function resetKA() {
282 if (kainterval > 0) {
283 kacount = 0;
284 clearInterval(katimer);
285 if (sock.writable)
286 katimer = setInterval(sendKA, kainterval);
287 }
288 }
289 this._resetKA = resetKA;
290
291 stream.on('USERAUTH_BANNER', function(msg) {
292 self.emit('banner', msg);
293 });
294
295 sock.on('connect', function() {
296 debug('DEBUG: Client: Connected');
297 self.emit('connect');
298 if (!cfg.sock)
299 stream.pipe(sock).pipe(stream);
300 }).on('timeout', function() {
301 self.emit('timeout');
302 }).on('error', function(err) {
303 clearTimeout(self._readyTimeout);
304 err.level = 'client-socket';
305 self.emit('error', err);
306 }).on('end', function() {
307 stream.unpipe(sock);
308 clearTimeout(self._readyTimeout);
309 clearInterval(katimer);
310 self.emit('end');
311 }).on('close', function() {
312 stream.unpipe(sock);
313 clearTimeout(self._readyTimeout);
314 clearInterval(katimer);
315 self.emit('close');
316
317 // notify outstanding channel requests of disconnection ...
318 var callbacks_ = callbacks;
319 var err = new Error('No response from server');
320 callbacks = self._callbacks = [];
321 for (i = 0; i < callbacks_.length; ++i)
322 callbacks_[i](err);
323
324 // simulate error for any channels waiting to be opened. this is safe
325 // against successfully opened channels because the success and failure
326 // event handlers are automatically removed when a success/failure response
327 // is received
328 var channels = self._channels;
329 var chanNos = Object.keys(channels);
330 self._channels = {};
331 for (i = 0; i < chanNos.length; ++i) {
332 var ev1 = stream.emit('CHANNEL_OPEN_FAILURE:' + chanNos[i], err);
333 // emitting CHANNEL_CLOSE should be safe too and should help for any
334 // special channels which might otherwise keep the process alive, such
335 // as agent forwarding channels which have open unix sockets ...
336 var ev2 = stream.emit('CHANNEL_CLOSE:' + chanNos[i]);
337 var earlyCb;
338 if (!ev1 && !ev2 && (earlyCb = channels[chanNos[i]])
339 && typeof earlyCb === 'function') {
340 earlyCb(err);
341 }
342 }
343 });
344 stream.on('drain', function() {
345 self.emit('drain');
346 }).once('header', function(header) {
347 self._remoteVer = header.versions.software;
348 if (header.greeting)
349 self.emit('greeting', header.greeting);
350 }).on('continue', function() {
351 self.emit('continue');
352 }).on('error', function(err) {
353 if (err.level === undefined)
354 err.level = 'protocol';
355 else if (err.level === 'handshake')
356 clearTimeout(self._readyTimeout);
357 self.emit('error', err);
358 }).on('end', function() {
359 sock.resume();
360 });
361
362 if (typeof cfg.hostVerifier === 'function') {
363 if (HASHES.indexOf(cfg.hostHash) === -1)
364 throw new Error('Invalid host hash algorithm: ' + cfg.hostHash);
365 var hashCb = cfg.hostVerifier;
366 var hasher = crypto.createHash(cfg.hostHash);
367 stream.once('fingerprint', function(key, verify) {
368 hasher.update(key);
369 var ret = hashCb(hasher.digest('hex'), verify);
370 if (ret !== undefined)
371 verify(ret);
372 });
373 }
374
375 // begin authentication handling =============================================
376 var curAuth;
377 var curPartial = null;
378 var curAuthsLeft = null;
379 var agentKeys;
380 var agentKeyPos = 0;
381 var authsAllowed = ['none'];
382 if (this.config.password !== undefined)
383 authsAllowed.push('password');
384 if (privateKey !== undefined)
385 authsAllowed.push('publickey');
386 if (this.config.agent !== undefined)
387 authsAllowed.push('agent');
388 if (this.config.tryKeyboard)
389 authsAllowed.push('keyboard-interactive');
390 if (privateKey !== undefined
391 && this.config.localHostname !== undefined
392 && this.config.localUsername !== undefined) {
393 authsAllowed.push('hostbased');
394 }
395
396 if (authHandler === undefined) {
397 var authPos = 0;
398 authHandler = function authHandler(authsLeft, partial, cb) {
399 if (authPos === authsAllowed.length)
400 return false;
401 return authsAllowed[authPos++];
402 };
403 }
404
405 var hasSentAuth = false;
406 function doNextAuth(authName) {
407 hasSentAuth = true;
408 if (authName === false) {
409 stream.removeListener('USERAUTH_FAILURE', onUSERAUTH_FAILURE);
410 stream.removeListener('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
411 var err = new Error('All configured authentication methods failed');
412 err.level = 'client-authentication';
413 self.emit('error', err);
414 if (stream.writable)
415 self.end();
416 return;
417 }
418 if (authsAllowed.indexOf(authName) === -1)
419 throw new Error('Authentication method not allowed: ' + authName);
420 curAuth = authName;
421 switch (curAuth) {
422 case 'password':
423 stream.authPassword(self.config.username, self.config.password);
424 break;
425 case 'publickey':
426 stream.authPK(self.config.username, privateKey);
427 stream.once('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
428 break;
429 case 'hostbased':
430 function hostbasedCb(buf, cb) {
431 var signature = privateKey.sign(buf);
432 if (signature instanceof Error) {
433 signature.message = 'Error while signing data with privateKey: '
434 + signature.message;
435 signature.level = 'client-authentication';
436 self.emit('error', signature);
437 return tryNextAuth();
438 }
439
440 cb(signature);
441 }
442 stream.authHostbased(self.config.username,
443 privateKey,
444 self.config.localHostname,
445 self.config.localUsername,
446 hostbasedCb);
447 break;
448 case 'agent':
449 agentQuery(self.config.agent, function(err, keys) {
450 if (err) {
451 err.level = 'agent';
452 self.emit('error', err);
453 agentKeys = undefined;
454 return tryNextAuth();
455 } else if (keys.length === 0) {
456 debug('DEBUG: Agent: No keys stored in agent');
457 agentKeys = undefined;
458 return tryNextAuth();
459 }
460
461 agentKeys = keys;
462 agentKeyPos = 0;
463
464 stream.authPK(self.config.username, keys[0]);
465 stream.once('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
466 });
467 break;
468 case 'keyboard-interactive':
469 stream.authKeyboard(self.config.username);
470 stream.on('USERAUTH_INFO_REQUEST', onUSERAUTH_INFO_REQUEST);
471 break;
472 case 'none':
473 stream.authNone(self.config.username);
474 break;
475 }
476 }
477 function tryNextAuth() {
478 hasSentAuth = false;
479 var auth = authHandler(curAuthsLeft, curPartial, doNextAuth);
480 if (hasSentAuth || auth === undefined)
481 return;
482 doNextAuth(auth);
483 }
484 function tryNextAgentKey() {
485 if (curAuth === 'agent') {
486 if (agentKeyPos >= agentKeys.length)
487 return;
488 if (++agentKeyPos >= agentKeys.length) {
489 debug('DEBUG: Agent: No more keys left to try');
490 debug('DEBUG: Client: agent auth failed');
491 agentKeys = undefined;
492 tryNextAuth();
493 } else {
494 debug('DEBUG: Agent: Trying key #' + (agentKeyPos + 1));
495 stream.authPK(self.config.username, agentKeys[agentKeyPos]);
496 stream.once('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
497 }
498 }
499 }
500 function onUSERAUTH_INFO_REQUEST(name, instructions, lang, prompts) {
501 var nprompts = (Array.isArray(prompts) ? prompts.length : 0);
502 if (nprompts === 0) {
503 debug('DEBUG: Client: Sending automatic USERAUTH_INFO_RESPONSE');
504 return stream.authInfoRes();
505 }
506 // we sent a keyboard-interactive user authentication request and now the
507 // server is sending us the prompts we need to present to the user
508 self.emit('keyboard-interactive',
509 name,
510 instructions,
511 lang,
512 prompts,
513 function(answers) {
514 stream.authInfoRes(answers);
515 }
516 );
517 }
518 function onUSERAUTH_PK_OK() {
519 if (curAuth === 'agent') {
520 var agentKey = agentKeys[agentKeyPos];
521 var keyLen = readUInt32BE(agentKey, 0);
522 var pubKeyFullType = agentKey.toString('ascii', 4, 4 + keyLen);
523 var pubKeyType = pubKeyFullType.slice(4);
524 // Check that we support the key type first
525 switch (pubKeyFullType) {
526 case 'ssh-rsa':
527 case 'ssh-dss':
528 case 'ecdsa-sha2-nistp256':
529 case 'ecdsa-sha2-nistp384':
530 case 'ecdsa-sha2-nistp521':
531 break;
532 default:
533 debug('DEBUG: Agent: Skipping unsupported key type: '
534 + pubKeyFullType);
535 return tryNextAgentKey();
536 }
537 stream.authPK(self.config.username,
538 agentKey,
539 function(buf, cb) {
540 agentQuery(self.config.agent,
541 agentKey,
542 pubKeyType,
543 buf,
544 function(err, signed) {
545 if (err) {
546 err.level = 'agent';
547 self.emit('error', err);
548 } else {
549 var sigFullTypeLen = readUInt32BE(signed, 0);
550 if (4 + sigFullTypeLen + 4 < signed.length) {
551 var sigFullType = signed.toString('ascii', 4, 4 + sigFullTypeLen);
552 if (sigFullType !== pubKeyFullType) {
553 err = new Error('Agent key/signature type mismatch');
554 err.level = 'agent';
555 self.emit('error', err);
556 } else {
557 // skip algoLen + algo + sigLen
558 return cb(signed.slice(4 + sigFullTypeLen + 4));
559 }
560 }
561 }
562
563 tryNextAgentKey();
564 });
565 });
566 } else if (curAuth === 'publickey') {
567 stream.authPK(self.config.username, privateKey, function(buf, cb) {
568 var signature = privateKey.sign(buf);
569 if (signature instanceof Error) {
570 signature.message = 'Error while signing data with privateKey: '
571 + signature.message;
572 signature.level = 'client-authentication';
573 self.emit('error', signature);
574 return tryNextAuth();
575 }
576 cb(signature);
577 });
578 }
579 }
580 function onUSERAUTH_FAILURE(authsLeft, partial) {
581 stream.removeListener('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
582 stream.removeListener('USERAUTH_INFO_REQUEST', onUSERAUTH_INFO_REQUEST);
583 if (curAuth === 'agent') {
584 debug('DEBUG: Client: Agent key #' + (agentKeyPos + 1) + ' failed');
585 return tryNextAgentKey();
586 } else {
587 debug('DEBUG: Client: ' + curAuth + ' auth failed');
588 }
589
590 curPartial = partial;
591 curAuthsLeft = authsLeft;
592 tryNextAuth();
593 }
594 stream.once('USERAUTH_SUCCESS', function() {
595 stream.removeListener('USERAUTH_FAILURE', onUSERAUTH_FAILURE);
596 stream.removeListener('USERAUTH_INFO_REQUEST', onUSERAUTH_INFO_REQUEST);
597
598 // start keepalive mechanism
599 resetKA();
600
601 clearTimeout(self._readyTimeout);
602
603 self.emit('ready');
604 }).on('USERAUTH_FAILURE', onUSERAUTH_FAILURE);
605 // end authentication handling ===============================================
606
607 // handle initial handshake completion
608 stream.once('ready', function() {
609 stream.service('ssh-userauth');
610 stream.once('SERVICE_ACCEPT', function(svcName) {
611 if (svcName === 'ssh-userauth')
612 tryNextAuth();
613 });
614 });
615
616 // handle incoming requests from server, typically a forwarded TCP or X11
617 // connection
618 stream.on('CHANNEL_OPEN', function(info) {
619 onCHANNEL_OPEN(self, info);
620 });
621
622 // handle responses for tcpip-forward and other global requests
623 stream.on('REQUEST_SUCCESS', function(data) {
624 if (callbacks.length)
625 callbacks.shift()(false, data);
626 }).on('REQUEST_FAILURE', function() {
627 if (callbacks.length)
628 callbacks.shift()(true);
629 });
630
631 stream.on('GLOBAL_REQUEST', function(name, wantReply, data) {
632 // auto-reject all global requests, this can be especially useful if the
633 // server is sending us dummy keepalive global requests
634 if (wantReply)
635 stream.requestFailure();
636 });
637
638 if (!cfg.sock) {
639 var host = this.config.host;
640 var forceIPv4 = this.config.forceIPv4;
641 var forceIPv6 = this.config.forceIPv6;
642
643 debug('DEBUG: Client: Trying '
644 + host
645 + ' on port '
646 + this.config.port
647 + ' ...');
648
649 function doConnect() {
650 startTimeout();
651 self._sock.connect({
652 host: host,
653 port: self.config.port,
654 localAddress: self.config.localAddress,
655 localPort: self.config.localPort
656 });
657 self._sock.setNoDelay(true);
658 self._sock.setMaxListeners(0);
659 self._sock.setTimeout(typeof cfg.timeout === 'number' ? cfg.timeout : 0);
660 }
661
662 if ((!forceIPv4 && !forceIPv6) || (forceIPv4 && forceIPv6))
663 doConnect();
664 else {
665 dnsLookup(host, (forceIPv4 ? 4 : 6), function(err, address, family) {
666 if (err) {
667 var error = new Error('Error while looking up '
668 + (forceIPv4 ? 'IPv4' : 'IPv6')
669 + ' address for host '
670 + host
671 + ': ' + err);
672 clearTimeout(self._readyTimeout);
673 error.level = 'client-dns';
674 self.emit('error', error);
675 self.emit('close');
676 return;
677 }
678 host = address;
679 doConnect();
680 });
681 }
682 } else {
683 startTimeout();
684 stream.pipe(sock).pipe(stream);
685 }
686
687 function startTimeout() {
688 if (self.config.readyTimeout > 0) {
689 self._readyTimeout = setTimeout(function() {
690 var err = new Error('Timed out while waiting for handshake');
691 err.level = 'client-timeout';
692 self.emit('error', err);
693 sock.destroy();
694 }, self.config.readyTimeout);
695 }
696 }
697};
698
699Client.prototype.end = function() {
700 if (this._sock
701 && this._sock.writable
702 && this._sshstream
703 && this._sshstream.writable)
704 return this._sshstream.disconnect();
705 return false;
706};
707
708Client.prototype.destroy = function() {
709 this._sock && this._sock.destroy();
710};
711
712Client.prototype.exec = function(cmd, opts, cb) {
713 if (!this._sock
714 || !this._sock.writable
715 || !this._sshstream
716 || !this._sshstream.writable)
717 throw new Error('Not connected');
718
719 if (typeof opts === 'function') {
720 cb = opts;
721 opts = {};
722 }
723
724 var self = this;
725 var extraOpts = { allowHalfOpen: (opts.allowHalfOpen !== false) };
726
727 return openChannel(this, 'session', extraOpts, function(err, chan) {
728 if (err)
729 return cb(err);
730
731 var todo = [];
732
733 function reqCb(err) {
734 if (err) {
735 chan.close();
736 return cb(err);
737 }
738 if (todo.length)
739 todo.shift()();
740 }
741
742 if (self.config.allowAgentFwd === true
743 || (opts
744 && opts.agentForward === true
745 && self.config.agent !== undefined)) {
746 todo.push(function() {
747 reqAgentFwd(chan, reqCb);
748 });
749 }
750
751 if (typeof opts === 'object' && opts !== null) {
752 if (typeof opts.env === 'object' && opts.env !== null)
753 reqEnv(chan, opts.env);
754 if ((typeof opts.pty === 'object' && opts.pty !== null)
755 || opts.pty === true) {
756 todo.push(function() { reqPty(chan, opts.pty, reqCb); });
757 }
758 if ((typeof opts.x11 === 'object' && opts.x11 !== null)
759 || opts.x11 === 'number'
760 || opts.x11 === true) {
761 todo.push(function() { reqX11(chan, opts.x11, reqCb); });
762 }
763 }
764
765 todo.push(function() { reqExec(chan, cmd, opts, cb); });
766 todo.shift()();
767 });
768};
769
770Client.prototype.shell = function(wndopts, opts, cb) {
771 if (!this._sock
772 || !this._sock.writable
773 || !this._sshstream
774 || !this._sshstream.writable)
775 throw new Error('Not connected');
776
777 // start an interactive terminal/shell session
778 var self = this;
779
780 if (typeof wndopts === 'function') {
781 cb = wndopts;
782 wndopts = opts = undefined;
783 } else if (typeof opts === 'function') {
784 cb = opts;
785 opts = undefined;
786 }
787 if (wndopts && (wndopts.x11 !== undefined || wndopts.env !== undefined)) {
788 opts = wndopts;
789 wndopts = undefined;
790 }
791
792 return openChannel(this, 'session', function(err, chan) {
793 if (err)
794 return cb(err);
795
796 var todo = [];
797
798 function reqCb(err) {
799 if (err) {
800 chan.close();
801 return cb(err);
802 }
803 if (todo.length)
804 todo.shift()();
805 }
806
807 if (self.config.allowAgentFwd === true
808 || (opts
809 && opts.agentForward === true
810 && self.config.agent !== undefined)) {
811 todo.push(function() { reqAgentFwd(chan, reqCb); });
812 }
813
814 if (wndopts !== false)
815 todo.push(function() { reqPty(chan, wndopts, reqCb); });
816
817 if (typeof opts === 'object' && opts !== null) {
818 if (typeof opts.env === 'object' && opts.env !== null)
819 reqEnv(chan, opts.env);
820 if ((typeof opts.x11 === 'object' && opts.x11 !== null)
821 || opts.x11 === 'number'
822 || opts.x11 === true) {
823 todo.push(function() { reqX11(chan, opts.x11, reqCb); });
824 }
825 }
826
827 todo.push(function() { reqShell(chan, cb); });
828 todo.shift()();
829 });
830};
831
832Client.prototype.subsys = function(name, cb) {
833 if (!this._sock
834 || !this._sock.writable
835 || !this._sshstream
836 || !this._sshstream.writable)
837 throw new Error('Not connected');
838
839 return openChannel(this, 'session', function(err, chan) {
840 if (err)
841 return cb(err);
842
843 reqSubsystem(chan, name, function(err, stream) {
844 if (err)
845 return cb(err);
846
847 cb(undefined, stream);
848 });
849 });
850};
851
852Client.prototype.sftp = function(cb) {
853 if (!this._sock
854 || !this._sock.writable
855 || !this._sshstream
856 || !this._sshstream.writable)
857 throw new Error('Not connected');
858
859 var self = this;
860
861 // start an SFTP session
862 return openChannel(this, 'session', function(err, chan) {
863 if (err)
864 return cb(err);
865
866 reqSubsystem(chan, 'sftp', function(err, stream) {
867 if (err)
868 return cb(err);
869
870 var serverIdentRaw = self._sshstream._state.incoming.identRaw;
871 var cfg = { debug: self.config.debug };
872 var sftp = new SFTPStream(cfg, serverIdentRaw);
873
874 function onError(err) {
875 sftp.removeListener('ready', onReady);
876 stream.removeListener('exit', onExit);
877 cb(err);
878 }
879
880 function onReady() {
881 sftp.removeListener('error', onError);
882 stream.removeListener('exit', onExit);
883 cb(undefined, new SFTPWrapper(sftp));
884 }
885
886 function onExit(code, signal) {
887 sftp.removeListener('ready', onReady);
888 sftp.removeListener('error', onError);
889 var msg;
890 if (typeof code === 'number') {
891 msg = 'Received exit code '
892 + code
893 + ' while establishing SFTP session';
894 } else {
895 msg = 'Received signal '
896 + signal
897 + ' while establishing SFTP session';
898 }
899 var err = new Error(msg);
900 err.code = code;
901 err.signal = signal;
902 cb(err);
903 }
904
905 sftp.once('error', onError)
906 .once('ready', onReady)
907 .once('close', function() {
908 stream.end();
909 });
910
911 // OpenSSH server sends an exit-status if there was a problem spinning up
912 // an sftp server child process, so we listen for that here in order to
913 // properly raise an error.
914 stream.once('exit', onExit);
915
916 sftp.pipe(stream).pipe(sftp);
917 });
918 });
919};
920
921Client.prototype.forwardIn = function(bindAddr, bindPort, cb) {
922 if (!this._sock
923 || !this._sock.writable
924 || !this._sshstream
925 || !this._sshstream.writable)
926 throw new Error('Not connected');
927
928 // send a request for the server to start forwarding TCP connections to us
929 // on a particular address and port
930
931 var self = this;
932 var wantReply = (typeof cb === 'function');
933
934 if (wantReply) {
935 this._callbacks.push(function(had_err, data) {
936 if (had_err) {
937 return cb(had_err !== true
938 ? had_err
939 : new Error('Unable to bind to ' + bindAddr + ':' + bindPort));
940 }
941
942 var realPort = bindPort;
943 if (bindPort === 0 && data && data.length >= 4) {
944 realPort = readUInt32BE(data, 0);
945 if (!(self._sshstream.remoteBugs & BUGS.DYN_RPORT_BUG))
946 bindPort = realPort;
947 }
948
949 self._forwarding[bindAddr + ':' + bindPort] = realPort;
950
951 cb(undefined, realPort);
952 });
953 }
954
955 return this._sshstream.tcpipForward(bindAddr, bindPort, wantReply);
956};
957
958Client.prototype.unforwardIn = function(bindAddr, bindPort, cb) {
959 if (!this._sock
960 || !this._sock.writable
961 || !this._sshstream
962 || !this._sshstream.writable)
963 throw new Error('Not connected');
964
965 // send a request to stop forwarding us new connections for a particular
966 // address and port
967
968 var self = this;
969 var wantReply = (typeof cb === 'function');
970
971 if (wantReply) {
972 this._callbacks.push(function(had_err) {
973 if (had_err) {
974 return cb(had_err !== true
975 ? had_err
976 : new Error('Unable to unbind from '
977 + bindAddr + ':' + bindPort));
978 }
979
980 delete self._forwarding[bindAddr + ':' + bindPort];
981
982 cb();
983 });
984 }
985
986 return this._sshstream.cancelTcpipForward(bindAddr, bindPort, wantReply);
987};
988
989Client.prototype.forwardOut = function(srcIP, srcPort, dstIP, dstPort, cb) {
990 if (!this._sock
991 || !this._sock.writable
992 || !this._sshstream
993 || !this._sshstream.writable)
994 throw new Error('Not connected');
995
996 // send a request to forward a TCP connection to the server
997
998 var cfg = {
999 srcIP: srcIP,
1000 srcPort: srcPort,
1001 dstIP: dstIP,
1002 dstPort: dstPort
1003 };
1004
1005 return openChannel(this, 'direct-tcpip', cfg, cb);
1006};
1007
1008Client.prototype.openssh_noMoreSessions = function(cb) {
1009 if (!this._sock
1010 || !this._sock.writable
1011 || !this._sshstream
1012 || !this._sshstream.writable)
1013 throw new Error('Not connected');
1014
1015 var wantReply = (typeof cb === 'function');
1016
1017 if (!this.config.strictVendor
1018 || (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
1019 if (wantReply) {
1020 this._callbacks.push(function(had_err) {
1021 if (had_err) {
1022 return cb(had_err !== true
1023 ? had_err
1024 : new Error('Unable to disable future sessions'));
1025 }
1026
1027 cb();
1028 });
1029 }
1030
1031 return this._sshstream.openssh_noMoreSessions(wantReply);
1032 } else if (wantReply) {
1033 process.nextTick(function() {
1034 cb(new Error('strictVendor enabled and server is not OpenSSH or compatible version'));
1035 });
1036 }
1037
1038 return true;
1039};
1040
1041Client.prototype.openssh_forwardInStreamLocal = function(socketPath, cb) {
1042 if (!this._sock
1043 || !this._sock.writable
1044 || !this._sshstream
1045 || !this._sshstream.writable)
1046 throw new Error('Not connected');
1047
1048 var wantReply = (typeof cb === 'function');
1049 var self = this;
1050
1051 if (!this.config.strictVendor
1052 || (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
1053 if (wantReply) {
1054 this._callbacks.push(function(had_err) {
1055 if (had_err) {
1056 return cb(had_err !== true
1057 ? had_err
1058 : new Error('Unable to bind to ' + socketPath));
1059 }
1060 self._forwardingUnix[socketPath] = true;
1061 cb();
1062 });
1063 }
1064
1065 return this._sshstream.openssh_streamLocalForward(socketPath, wantReply);
1066 } else if (wantReply) {
1067 process.nextTick(function() {
1068 cb(new Error('strictVendor enabled and server is not OpenSSH or compatible version'));
1069 });
1070 }
1071
1072 return true;
1073};
1074
1075Client.prototype.openssh_unforwardInStreamLocal = function(socketPath, cb) {
1076 if (!this._sock
1077 || !this._sock.writable
1078 || !this._sshstream
1079 || !this._sshstream.writable)
1080 throw new Error('Not connected');
1081
1082 var wantReply = (typeof cb === 'function');
1083 var self = this;
1084
1085 if (!this.config.strictVendor
1086 || (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
1087 if (wantReply) {
1088 this._callbacks.push(function(had_err) {
1089 if (had_err) {
1090 return cb(had_err !== true
1091 ? had_err
1092 : new Error('Unable to unbind on ' + socketPath));
1093 }
1094 delete self._forwardingUnix[socketPath];
1095 cb();
1096 });
1097 }
1098
1099 return this._sshstream.openssh_cancelStreamLocalForward(socketPath,
1100 wantReply);
1101 } else if (wantReply) {
1102 process.nextTick(function() {
1103 cb(new Error('strictVendor enabled and server is not OpenSSH or compatible version'));
1104 });
1105 }
1106
1107 return true;
1108};
1109
1110Client.prototype.openssh_forwardOutStreamLocal = function(socketPath, cb) {
1111 if (!this._sock
1112 || !this._sock.writable
1113 || !this._sshstream
1114 || !this._sshstream.writable)
1115 throw new Error('Not connected');
1116
1117 if (!this.config.strictVendor
1118 || (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
1119 var cfg = { socketPath: socketPath };
1120 return openChannel(this, 'direct-streamlocal@openssh.com', cfg, cb);
1121 } else {
1122 process.nextTick(function() {
1123 cb(new Error('strictVendor enabled and server is not OpenSSH or compatible version'));
1124 });
1125 }
1126
1127 return true;
1128};
1129
1130function openChannel(self, type, opts, cb) {
1131 // ask the server to open a channel for some purpose
1132 // (e.g. session (sftp, exec, shell), or forwarding a TCP connection
1133 var localChan = nextChannel(self);
1134 var initWindow = Channel.MAX_WINDOW;
1135 var maxPacket = Channel.PACKET_SIZE;
1136 var ret = true;
1137
1138 if (localChan === false)
1139 return cb(new Error('No free channels available'));
1140
1141 if (typeof opts === 'function') {
1142 cb = opts;
1143 opts = {};
1144 }
1145
1146 self._channels[localChan] = cb;
1147
1148 var sshstream = self._sshstream;
1149 sshstream.once('CHANNEL_OPEN_CONFIRMATION:' + localChan, onSuccess)
1150 .once('CHANNEL_OPEN_FAILURE:' + localChan, onFailure)
1151 .once('CHANNEL_CLOSE:' + localChan, onFailure);
1152
1153 if (type === 'session')
1154 ret = sshstream.session(localChan, initWindow, maxPacket);
1155 else if (type === 'direct-tcpip')
1156 ret = sshstream.directTcpip(localChan, initWindow, maxPacket, opts);
1157 else if (type === 'direct-streamlocal@openssh.com') {
1158 ret = sshstream.openssh_directStreamLocal(localChan,
1159 initWindow,
1160 maxPacket,
1161 opts);
1162 }
1163
1164 return ret;
1165
1166 function onSuccess(info) {
1167 sshstream.removeListener('CHANNEL_OPEN_FAILURE:' + localChan, onFailure);
1168 sshstream.removeListener('CHANNEL_CLOSE:' + localChan, onFailure);
1169
1170 var chaninfo = {
1171 type: type,
1172 incoming: {
1173 id: localChan,
1174 window: initWindow,
1175 packetSize: maxPacket,
1176 state: 'open'
1177 },
1178 outgoing: {
1179 id: info.sender,
1180 window: info.window,
1181 packetSize: info.packetSize,
1182 state: 'open'
1183 }
1184 };
1185 cb(undefined, new Channel(chaninfo, self));
1186 }
1187
1188 function onFailure(info) {
1189 sshstream.removeListener('CHANNEL_OPEN_CONFIRMATION:' + localChan,
1190 onSuccess);
1191 sshstream.removeListener('CHANNEL_OPEN_FAILURE:' + localChan, onFailure);
1192 sshstream.removeListener('CHANNEL_CLOSE:' + localChan, onFailure);
1193
1194 delete self._channels[localChan];
1195
1196 var err;
1197 if (info instanceof Error)
1198 err = info;
1199 else if (typeof info === 'object' && info !== null) {
1200 err = new Error('(SSH) Channel open failure: ' + info.description);
1201 err.reason = info.reason;
1202 err.lang = info.lang;
1203 } else {
1204 err = new Error('(SSH) Channel open failure: '
1205 + 'server closed channel unexpectedly');
1206 err.reason = err.lang = '';
1207 }
1208 cb(err);
1209 }
1210}
1211
1212function nextChannel(self) {
1213 // get the next available channel number
1214
1215 // optimized path
1216 if (self._curChan < MAX_CHANNEL)
1217 return ++self._curChan;
1218
1219 // slower lookup path
1220 for (var i = 0, channels = self._channels; i < MAX_CHANNEL; ++i)
1221 if (!channels[i])
1222 return i;
1223
1224 return false;
1225}
1226
1227function reqX11(chan, screen, cb) {
1228 // asks server to start sending us X11 connections
1229 var cfg = {
1230 single: false,
1231 protocol: 'MIT-MAGIC-COOKIE-1',
1232 cookie: undefined,
1233 screen: 0
1234 };
1235
1236 if (typeof screen === 'function') {
1237 cb = screen;
1238 } else if (typeof screen === 'object' && screen !== null) {
1239 if (typeof screen.single === 'boolean')
1240 cfg.single = screen.single;
1241 if (typeof screen.screen === 'number')
1242 cfg.screen = screen.screen;
1243 if (typeof screen.protocol === 'string')
1244 cfg.protocol = screen.protocol;
1245 if (typeof screen.cookie === 'string')
1246 cfg.cookie = screen.cookie;
1247 else if (Buffer.isBuffer(screen.cookie))
1248 cfg.cookie = screen.cookie.toString('hex');
1249 }
1250 if (cfg.cookie === undefined)
1251 cfg.cookie = randomCookie();
1252
1253 var wantReply = (typeof cb === 'function');
1254
1255 if (chan.outgoing.state !== 'open') {
1256 wantReply && cb(new Error('Channel is not open'));
1257 return true;
1258 }
1259
1260 if (wantReply) {
1261 chan._callbacks.push(function(had_err) {
1262 if (had_err) {
1263 return cb(had_err !== true
1264 ? had_err
1265 : new Error('Unable to request X11'));
1266 }
1267
1268 chan._hasX11 = true;
1269 ++chan._client._acceptX11;
1270 chan.once('close', function() {
1271 if (chan._client._acceptX11)
1272 --chan._client._acceptX11;
1273 });
1274
1275 cb();
1276 });
1277 }
1278
1279 return chan._client._sshstream.x11Forward(chan.outgoing.id, cfg, wantReply);
1280}
1281
1282function reqPty(chan, opts, cb) {
1283 var rows = 24;
1284 var cols = 80;
1285 var width = 640;
1286 var height = 480;
1287 var term = 'vt100';
1288
1289 if (typeof opts === 'function')
1290 cb = opts;
1291 else if (typeof opts === 'object' && opts !== null) {
1292 if (typeof opts.rows === 'number')
1293 rows = opts.rows;
1294 if (typeof opts.cols === 'number')
1295 cols = opts.cols;
1296 if (typeof opts.width === 'number')
1297 width = opts.width;
1298 if (typeof opts.height === 'number')
1299 height = opts.height;
1300 if (typeof opts.term === 'string')
1301 term = opts.term;
1302 }
1303
1304 var wantReply = (typeof cb === 'function');
1305
1306 if (chan.outgoing.state !== 'open') {
1307 wantReply && cb(new Error('Channel is not open'));
1308 return true;
1309 }
1310
1311 if (wantReply) {
1312 chan._callbacks.push(function(had_err) {
1313 if (had_err) {
1314 return cb(had_err !== true
1315 ? had_err
1316 : new Error('Unable to request a pseudo-terminal'));
1317 }
1318 cb();
1319 });
1320 }
1321
1322 return chan._client._sshstream.pty(chan.outgoing.id,
1323 rows,
1324 cols,
1325 height,
1326 width,
1327 term,
1328 null,
1329 wantReply);
1330}
1331
1332function reqAgentFwd(chan, cb) {
1333 var wantReply = (typeof cb === 'function');
1334
1335 if (chan.outgoing.state !== 'open') {
1336 wantReply && cb(new Error('Channel is not open'));
1337 return true;
1338 } else if (chan._client._agentFwdEnabled) {
1339 wantReply && cb(false);
1340 return true;
1341 }
1342
1343 chan._client._agentFwdEnabled = true;
1344
1345 chan._callbacks.push(function(had_err) {
1346 if (had_err) {
1347 chan._client._agentFwdEnabled = false;
1348 wantReply && cb(had_err !== true
1349 ? had_err
1350 : new Error('Unable to request agent forwarding'));
1351 return;
1352 }
1353
1354 wantReply && cb();
1355 });
1356
1357 return chan._client._sshstream.openssh_agentForward(chan.outgoing.id, true);
1358}
1359
1360function reqShell(chan, cb) {
1361 if (chan.outgoing.state !== 'open') {
1362 cb(new Error('Channel is not open'));
1363 return true;
1364 }
1365 chan._callbacks.push(function(had_err) {
1366 if (had_err) {
1367 return cb(had_err !== true
1368 ? had_err
1369 : new Error('Unable to open shell'));
1370 }
1371 chan.subtype = 'shell';
1372 cb(undefined, chan);
1373 });
1374
1375 return chan._client._sshstream.shell(chan.outgoing.id, true);
1376}
1377
1378function reqExec(chan, cmd, opts, cb) {
1379 if (chan.outgoing.state !== 'open') {
1380 cb(new Error('Channel is not open'));
1381 return true;
1382 }
1383 chan._callbacks.push(function(had_err) {
1384 if (had_err) {
1385 return cb(had_err !== true
1386 ? had_err
1387 : new Error('Unable to exec'));
1388 }
1389 chan.subtype = 'exec';
1390 chan.allowHalfOpen = (opts.allowHalfOpen !== false);
1391 cb(undefined, chan);
1392 });
1393
1394 return chan._client._sshstream.exec(chan.outgoing.id, cmd, true);
1395}
1396
1397function reqEnv(chan, env) {
1398 if (chan.outgoing.state !== 'open')
1399 return true;
1400 var ret = true;
1401 var keys = Object.keys(env || {});
1402 var key;
1403 var val;
1404
1405 for (var i = 0, len = keys.length; i < len; ++i) {
1406 key = keys[i];
1407 val = env[key];
1408 ret = chan._client._sshstream.env(chan.outgoing.id, key, val, false);
1409 }
1410
1411 return ret;
1412}
1413
1414function reqSubsystem(chan, name, cb) {
1415 if (chan.outgoing.state !== 'open') {
1416 cb(new Error('Channel is not open'));
1417 return true;
1418 }
1419 chan._callbacks.push(function(had_err) {
1420 if (had_err) {
1421 return cb(had_err !== true
1422 ? had_err
1423 : new Error('Unable to start subsystem: ' + name));
1424 }
1425 chan.subtype = 'subsystem';
1426 cb(undefined, chan);
1427 });
1428
1429 return chan._client._sshstream.subsystem(chan.outgoing.id, name, true);
1430}
1431
1432function onCHANNEL_OPEN(self, info) {
1433 // the server is trying to open a channel with us, this is usually when
1434 // we asked the server to forward us connections on some port and now they
1435 // are asking us to accept/deny an incoming connection on their side
1436
1437 var localChan = false;
1438 var reason;
1439
1440 function accept() {
1441 var chaninfo = {
1442 type: info.type,
1443 incoming: {
1444 id: localChan,
1445 window: Channel.MAX_WINDOW,
1446 packetSize: Channel.PACKET_SIZE,
1447 state: 'open'
1448 },
1449 outgoing: {
1450 id: info.sender,
1451 window: info.window,
1452 packetSize: info.packetSize,
1453 state: 'open'
1454 }
1455 };
1456 var stream = new Channel(chaninfo, self);
1457
1458 self._sshstream.channelOpenConfirm(info.sender,
1459 localChan,
1460 Channel.MAX_WINDOW,
1461 Channel.PACKET_SIZE);
1462 return stream;
1463 }
1464 function reject() {
1465 if (reason === undefined) {
1466 if (localChan === false)
1467 reason = consts.CHANNEL_OPEN_FAILURE.RESOURCE_SHORTAGE;
1468 else
1469 reason = consts.CHANNEL_OPEN_FAILURE.CONNECT_FAILED;
1470 }
1471
1472 self._sshstream.channelOpenFail(info.sender, reason, '', '');
1473 }
1474
1475 if (info.type === 'forwarded-tcpip'
1476 || info.type === 'x11'
1477 || info.type === 'auth-agent@openssh.com'
1478 || info.type === 'forwarded-streamlocal@openssh.com') {
1479
1480 // check for conditions for automatic rejection
1481 var rejectConn = (
1482 (info.type === 'forwarded-tcpip'
1483 && self._forwarding[info.data.destIP
1484 + ':'
1485 + info.data.destPort] === undefined)
1486 || (info.type === 'forwarded-streamlocal@openssh.com'
1487 && self._forwardingUnix[info.data.socketPath] === undefined)
1488 || (info.type === 'x11' && self._acceptX11 === 0)
1489 || (info.type === 'auth-agent@openssh.com'
1490 && !self._agentFwdEnabled)
1491 );
1492
1493 if (!rejectConn) {
1494 localChan = nextChannel(self);
1495
1496 if (localChan === false) {
1497 self.config.debug('DEBUG: Client: Automatic rejection of incoming channel open: no channels available');
1498 rejectConn = true;
1499 } else
1500 self._channels[localChan] = true;
1501 } else {
1502 reason = consts.CHANNEL_OPEN_FAILURE.ADMINISTRATIVELY_PROHIBITED;
1503 self.config.debug('DEBUG: Client: Automatic rejection of incoming channel open: unexpected channel open for: '
1504 + info.type);
1505 }
1506
1507 // TODO: automatic rejection after some timeout?
1508
1509 if (rejectConn)
1510 reject();
1511
1512 if (localChan !== false) {
1513 if (info.type === 'forwarded-tcpip') {
1514 if (info.data.destPort === 0) {
1515 info.data.destPort = self._forwarding[info.data.destIP
1516 + ':'
1517 + info.data.destPort];
1518 }
1519 self.emit('tcp connection', info.data, accept, reject);
1520 } else if (info.type === 'x11') {
1521 self.emit('x11', info.data, accept, reject);
1522 } else if (info.type === 'forwarded-streamlocal@openssh.com') {
1523 self.emit('unix connection', info.data, accept, reject);
1524 } else {
1525 agentQuery(self.config.agent, accept, reject);
1526 }
1527 }
1528 } else {
1529 // automatically reject any unsupported channel open requests
1530 self.config.debug('DEBUG: Client: Automatic rejection of incoming channel open: unsupported type: '
1531 + info.type);
1532 reason = consts.CHANNEL_OPEN_FAILURE.UNKNOWN_CHANNEL_TYPE;
1533 reject();
1534 }
1535}
1536
1537var randomCookie = (function() {
1538 if (typeof crypto.randomFillSync === 'function') {
1539 var buffer = Buffer.alloc(16);
1540 return function randomCookie() {
1541 crypto.randomFillSync(buffer, 0, 16);
1542 return buffer.toString('hex');
1543 };
1544 } else {
1545 return function randomCookie() {
1546 return crypto.randomBytes(16).toString('hex');
1547 };
1548 }
1549})();
1550
1551Client.Client = Client;
1552Client.Server = require('./server');
1553// pass some useful utilities on to end user (e.g. parseKey())
1554Client.utils = ssh2_streams.utils;
1555// expose useful SFTPStream constants for sftp server usage
1556Client.SFTP_STATUS_CODE = SFTPStream.STATUS_CODE;
1557Client.SFTP_OPEN_MODE = SFTPStream.OPEN_MODE;
1558
1559module.exports = Client; // backwards compatibility