Guest User

Untitled

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