Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

client.js 47KB

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