client.js 46 KB

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