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

server.js 32KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  1. var net = require('net');
  2. var EventEmitter = require('events').EventEmitter;
  3. var listenerCount = EventEmitter.listenerCount;
  4. var inherits = require('util').inherits;
  5. var ssh2_streams = require('ssh2-streams');
  6. var parseKey = ssh2_streams.utils.parseKey;
  7. var SSH2Stream = ssh2_streams.SSH2Stream;
  8. var SFTPStream = ssh2_streams.SFTPStream;
  9. var consts = ssh2_streams.constants;
  10. var DISCONNECT_REASON = consts.DISCONNECT_REASON;
  11. var CHANNEL_OPEN_FAILURE = consts.CHANNEL_OPEN_FAILURE;
  12. var ALGORITHMS = consts.ALGORITHMS;
  13. var Channel = require('./Channel');
  14. var KeepaliveManager = require('./keepalivemgr');
  15. var writeUInt32BE = require('./buffer-helpers').writeUInt32BE;
  16. var MAX_CHANNEL = Math.pow(2, 32) - 1;
  17. var MAX_PENDING_AUTHS = 10;
  18. var kaMgr;
  19. function Server(cfg, listener) {
  20. if (!(this instanceof Server))
  21. return new Server(cfg, listener);
  22. var hostKeys = {
  23. 'ssh-rsa': null,
  24. 'ssh-dss': null,
  25. 'ssh-ed25519': null,
  26. 'ecdsa-sha2-nistp256': null,
  27. 'ecdsa-sha2-nistp384': null,
  28. 'ecdsa-sha2-nistp521': null
  29. };
  30. var hostKeys_ = cfg.hostKeys;
  31. if (!Array.isArray(hostKeys_))
  32. throw new Error('hostKeys must be an array');
  33. var i;
  34. for (i = 0; i < hostKeys_.length; ++i) {
  35. var privateKey;
  36. if (Buffer.isBuffer(hostKeys_[i]) || typeof hostKeys_[i] === 'string')
  37. privateKey = parseKey(hostKeys_[i]);
  38. else
  39. privateKey = parseKey(hostKeys_[i].key, hostKeys_[i].passphrase);
  40. if (privateKey instanceof Error)
  41. throw new Error('Cannot parse privateKey: ' + privateKey.message);
  42. if (Array.isArray(privateKey))
  43. privateKey = privateKey[0]; // OpenSSH's newer format only stores 1 key for now
  44. if (privateKey.getPrivatePEM() === null)
  45. throw new Error('privateKey value contains an invalid private key');
  46. if (hostKeys[privateKey.type])
  47. continue;
  48. hostKeys[privateKey.type] = privateKey;
  49. }
  50. var algorithms = {
  51. kex: undefined,
  52. kexBuf: undefined,
  53. cipher: undefined,
  54. cipherBuf: undefined,
  55. serverHostKey: undefined,
  56. serverHostKeyBuf: undefined,
  57. hmac: undefined,
  58. hmacBuf: undefined,
  59. compress: undefined,
  60. compressBuf: undefined
  61. };
  62. if (typeof cfg.algorithms === 'object' && cfg.algorithms !== null) {
  63. var algosSupported;
  64. var algoList;
  65. algoList = cfg.algorithms.kex;
  66. if (Array.isArray(algoList) && algoList.length > 0) {
  67. algosSupported = ALGORITHMS.SUPPORTED_KEX;
  68. for (i = 0; i < algoList.length; ++i) {
  69. if (algosSupported.indexOf(algoList[i]) === -1)
  70. throw new Error('Unsupported key exchange algorithm: ' + algoList[i]);
  71. }
  72. algorithms.kex = algoList;
  73. }
  74. algoList = cfg.algorithms.cipher;
  75. if (Array.isArray(algoList) && algoList.length > 0) {
  76. algosSupported = ALGORITHMS.SUPPORTED_CIPHER;
  77. for (i = 0; i < algoList.length; ++i) {
  78. if (algosSupported.indexOf(algoList[i]) === -1)
  79. throw new Error('Unsupported cipher algorithm: ' + algoList[i]);
  80. }
  81. algorithms.cipher = algoList;
  82. }
  83. algoList = cfg.algorithms.serverHostKey;
  84. var copied = false;
  85. if (Array.isArray(algoList) && algoList.length > 0) {
  86. algosSupported = ALGORITHMS.SUPPORTED_SERVER_HOST_KEY;
  87. for (i = algoList.length - 1; i >= 0; --i) {
  88. if (algosSupported.indexOf(algoList[i]) === -1) {
  89. throw new Error('Unsupported server host key algorithm: '
  90. + algoList[i]);
  91. }
  92. if (!hostKeys[algoList[i]]) {
  93. // Silently discard for now
  94. if (!copied) {
  95. algoList = algoList.slice();
  96. copied = true;
  97. }
  98. algoList.splice(i, 1);
  99. }
  100. }
  101. if (algoList.length > 0)
  102. algorithms.serverHostKey = algoList;
  103. }
  104. algoList = cfg.algorithms.hmac;
  105. if (Array.isArray(algoList) && algoList.length > 0) {
  106. algosSupported = ALGORITHMS.SUPPORTED_HMAC;
  107. for (i = 0; i < algoList.length; ++i) {
  108. if (algosSupported.indexOf(algoList[i]) === -1)
  109. throw new Error('Unsupported HMAC algorithm: ' + algoList[i]);
  110. }
  111. algorithms.hmac = algoList;
  112. }
  113. algoList = cfg.algorithms.compress;
  114. if (Array.isArray(algoList) && algoList.length > 0) {
  115. algosSupported = ALGORITHMS.SUPPORTED_COMPRESS;
  116. for (i = 0; i < algoList.length; ++i) {
  117. if (algosSupported.indexOf(algoList[i]) === -1)
  118. throw new Error('Unsupported compression algorithm: ' + algoList[i]);
  119. }
  120. algorithms.compress = algoList;
  121. }
  122. }
  123. // Make sure we at least have some kind of valid list of support key
  124. // formats
  125. if (algorithms.serverHostKey === undefined) {
  126. var hostKeyAlgos = Object.keys(hostKeys);
  127. for (i = hostKeyAlgos.length - 1; i >= 0; --i) {
  128. if (!hostKeys[hostKeyAlgos[i]])
  129. hostKeyAlgos.splice(i, 1);
  130. }
  131. algorithms.serverHostKey = hostKeyAlgos;
  132. }
  133. if (!kaMgr
  134. && Server.KEEPALIVE_INTERVAL > 0
  135. && Server.KEEPALIVE_CLIENT_INTERVAL > 0
  136. && Server.KEEPALIVE_CLIENT_COUNT_MAX >= 0) {
  137. kaMgr = new KeepaliveManager(Server.KEEPALIVE_INTERVAL,
  138. Server.KEEPALIVE_CLIENT_INTERVAL,
  139. Server.KEEPALIVE_CLIENT_COUNT_MAX);
  140. }
  141. var self = this;
  142. EventEmitter.call(this);
  143. if (typeof listener === 'function')
  144. self.on('connection', listener);
  145. var streamcfg = {
  146. algorithms: algorithms,
  147. hostKeys: hostKeys,
  148. server: true
  149. };
  150. var keys;
  151. var len;
  152. for (i = 0, keys = Object.keys(cfg), len = keys.length; i < len; ++i) {
  153. var key = keys[i];
  154. if (key === 'privateKey'
  155. || key === 'publicKey'
  156. || key === 'passphrase'
  157. || key === 'algorithms'
  158. || key === 'hostKeys'
  159. || key === 'server') {
  160. continue;
  161. }
  162. streamcfg[key] = cfg[key];
  163. }
  164. if (typeof streamcfg.debug === 'function') {
  165. var oldDebug = streamcfg.debug;
  166. var cfgKeys = Object.keys(streamcfg);
  167. }
  168. this._srv = new net.Server(function(socket) {
  169. if (self._connections >= self.maxConnections) {
  170. socket.destroy();
  171. return;
  172. }
  173. ++self._connections;
  174. socket.once('close', function(had_err) {
  175. --self._connections;
  176. // since joyent/node#993bb93e0a, we have to "read past EOF" in order to
  177. // get an `end` event on streams. thankfully adding this does not
  178. // negatively affect node versions pre-joyent/node#993bb93e0a.
  179. sshstream.read();
  180. }).on('error', function(err) {
  181. sshstream.reset();
  182. sshstream.emit('error', err);
  183. });
  184. var conncfg = streamcfg;
  185. // prepend debug output with a unique identifier in case there are multiple
  186. // clients connected at the same time
  187. if (oldDebug) {
  188. conncfg = {};
  189. for (var i = 0, key; i < cfgKeys.length; ++i) {
  190. key = cfgKeys[i];
  191. conncfg[key] = streamcfg[key];
  192. }
  193. var debugPrefix = '[' + process.hrtime().join('.') + '] ';
  194. conncfg.debug = function(msg) {
  195. oldDebug(debugPrefix + msg);
  196. };
  197. }
  198. var sshstream = new SSH2Stream(conncfg);
  199. var client = new Client(sshstream, socket);
  200. socket.pipe(sshstream).pipe(socket);
  201. // silence pre-header errors
  202. function onClientPreHeaderError(err) {}
  203. client.on('error', onClientPreHeaderError);
  204. sshstream.once('header', function(header) {
  205. if (sshstream._readableState.ended) {
  206. // already disconnected internally in SSH2Stream due to incompatible
  207. // protocol version
  208. return;
  209. } else if (!listenerCount(self, 'connection')) {
  210. // auto reject
  211. return sshstream.disconnect(DISCONNECT_REASON.BY_APPLICATION);
  212. }
  213. client.removeListener('error', onClientPreHeaderError);
  214. self.emit('connection',
  215. client,
  216. { ip: socket.remoteAddress,
  217. family: socket.remoteFamily,
  218. port: socket.remotePort,
  219. header: header });
  220. });
  221. }).on('error', function(err) {
  222. self.emit('error', err);
  223. }).on('listening', function() {
  224. self.emit('listening');
  225. }).on('close', function() {
  226. self.emit('close');
  227. });
  228. this._connections = 0;
  229. this.maxConnections = Infinity;
  230. }
  231. inherits(Server, EventEmitter);
  232. Server.prototype.listen = function() {
  233. this._srv.listen.apply(this._srv, arguments);
  234. return this;
  235. };
  236. Server.prototype.address = function() {
  237. return this._srv.address();
  238. };
  239. Server.prototype.getConnections = function(cb) {
  240. this._srv.getConnections(cb);
  241. };
  242. Server.prototype.close = function(cb) {
  243. this._srv.close(cb);
  244. return this;
  245. };
  246. Server.prototype.ref = function() {
  247. this._srv.ref();
  248. };
  249. Server.prototype.unref = function() {
  250. this._srv.unref();
  251. };
  252. function Client(stream, socket) {
  253. EventEmitter.call(this);
  254. var self = this;
  255. this._sshstream = stream;
  256. var channels = this._channels = {};
  257. this._curChan = -1;
  258. this._sock = socket;
  259. this.noMoreSessions = false;
  260. this.authenticated = false;
  261. stream.on('end', function() {
  262. socket.resume();
  263. self.emit('end');
  264. }).on('close', function(hasErr) {
  265. self.emit('close', hasErr);
  266. }).on('error', function(err) {
  267. self.emit('error', err);
  268. }).on('drain', function() {
  269. self.emit('drain');
  270. }).on('continue', function() {
  271. self.emit('continue');
  272. });
  273. var exchanges = 0;
  274. var acceptedAuthSvc = false;
  275. var pendingAuths = [];
  276. var authCtx;
  277. // begin service/auth-related ================================================
  278. stream.on('SERVICE_REQUEST', function(service) {
  279. if (exchanges === 0
  280. || acceptedAuthSvc
  281. || self.authenticated
  282. || service !== 'ssh-userauth')
  283. return stream.disconnect(DISCONNECT_REASON.SERVICE_NOT_AVAILABLE);
  284. acceptedAuthSvc = true;
  285. stream.serviceAccept(service);
  286. }).on('USERAUTH_REQUEST', onUSERAUTH_REQUEST);
  287. function onUSERAUTH_REQUEST(username, service, method, methodData) {
  288. if (exchanges === 0
  289. || (authCtx
  290. && (authCtx.username !== username || authCtx.service !== service))
  291. // TODO: support hostbased auth
  292. || (method !== 'password'
  293. && method !== 'publickey'
  294. && method !== 'hostbased'
  295. && method !== 'keyboard-interactive'
  296. && method !== 'none')
  297. || pendingAuths.length === MAX_PENDING_AUTHS)
  298. return stream.disconnect(DISCONNECT_REASON.PROTOCOL_ERROR);
  299. else if (service !== 'ssh-connection')
  300. return stream.disconnect(DISCONNECT_REASON.SERVICE_NOT_AVAILABLE);
  301. // XXX: this really shouldn't be reaching into private state ...
  302. stream._state.authMethod = method;
  303. var ctx;
  304. if (method === 'keyboard-interactive') {
  305. ctx = new KeyboardAuthContext(stream, username, service, method,
  306. methodData, onAuthDecide);
  307. } else if (method === 'publickey') {
  308. ctx = new PKAuthContext(stream, username, service, method, methodData,
  309. onAuthDecide);
  310. } else if (method === 'hostbased') {
  311. ctx = new HostbasedAuthContext(stream, username, service, method,
  312. methodData, onAuthDecide);
  313. } else if (method === 'password') {
  314. ctx = new PwdAuthContext(stream, username, service, method, methodData,
  315. onAuthDecide);
  316. } else if (method === 'none')
  317. ctx = new AuthContext(stream, username, service, method, onAuthDecide);
  318. if (authCtx) {
  319. if (!authCtx._initialResponse)
  320. return pendingAuths.push(ctx);
  321. else if (authCtx._multistep && !this._finalResponse) {
  322. // RFC 4252 says to silently abort the current auth request if a new
  323. // auth request comes in before the final response from an auth method
  324. // that requires additional request/response exchanges -- this means
  325. // keyboard-interactive for now ...
  326. authCtx._cleanup && authCtx._cleanup();
  327. authCtx.emit('abort');
  328. }
  329. }
  330. authCtx = ctx;
  331. if (listenerCount(self, 'authentication'))
  332. self.emit('authentication', authCtx);
  333. else
  334. authCtx.reject();
  335. }
  336. function onAuthDecide(ctx, allowed, methodsLeft, isPartial) {
  337. if (authCtx === ctx && !self.authenticated) {
  338. if (allowed) {
  339. stream.removeListener('USERAUTH_REQUEST', onUSERAUTH_REQUEST);
  340. authCtx = undefined;
  341. self.authenticated = true;
  342. stream.authSuccess();
  343. pendingAuths = [];
  344. self.emit('ready');
  345. } else {
  346. stream.authFailure(methodsLeft, isPartial);
  347. if (pendingAuths.length) {
  348. authCtx = pendingAuths.pop();
  349. if (listenerCount(self, 'authentication'))
  350. self.emit('authentication', authCtx);
  351. else
  352. authCtx.reject();
  353. }
  354. }
  355. }
  356. }
  357. // end service/auth-related ==================================================
  358. var unsentGlobalRequestsReplies = [];
  359. function sendReplies() {
  360. var reply;
  361. while (unsentGlobalRequestsReplies.length > 0
  362. && unsentGlobalRequestsReplies[0].type) {
  363. reply = unsentGlobalRequestsReplies.shift();
  364. if (reply.type === 'SUCCESS')
  365. stream.requestSuccess(reply.buf);
  366. if (reply.type === 'FAILURE')
  367. stream.requestFailure();
  368. }
  369. }
  370. stream.on('GLOBAL_REQUEST', function(name, wantReply, data) {
  371. var reply = {
  372. type: null,
  373. buf: null
  374. };
  375. function setReply(type, buf) {
  376. reply.type = type;
  377. reply.buf = buf;
  378. sendReplies();
  379. }
  380. if (wantReply)
  381. unsentGlobalRequestsReplies.push(reply);
  382. if ((name === 'tcpip-forward'
  383. || name === 'cancel-tcpip-forward'
  384. || name === 'no-more-sessions@openssh.com'
  385. || name === 'streamlocal-forward@openssh.com'
  386. || name === 'cancel-streamlocal-forward@openssh.com')
  387. && listenerCount(self, 'request')
  388. && self.authenticated) {
  389. var accept;
  390. var reject;
  391. if (wantReply) {
  392. var replied = false;
  393. accept = function(chosenPort) {
  394. if (replied)
  395. return;
  396. replied = true;
  397. var bufPort;
  398. if (name === 'tcpip-forward'
  399. && data.bindPort === 0
  400. && typeof chosenPort === 'number') {
  401. bufPort = Buffer.allocUnsafe(4);
  402. writeUInt32BE(bufPort, chosenPort, 0);
  403. }
  404. setReply('SUCCESS', bufPort);
  405. };
  406. reject = function() {
  407. if (replied)
  408. return;
  409. replied = true;
  410. setReply('FAILURE');
  411. };
  412. }
  413. if (name === 'no-more-sessions@openssh.com') {
  414. self.noMoreSessions = true;
  415. accept && accept();
  416. return;
  417. }
  418. self.emit('request', accept, reject, name, data);
  419. } else if (wantReply)
  420. setReply('FAILURE');
  421. });
  422. stream.on('CHANNEL_OPEN', function(info) {
  423. // do early reject in some cases to prevent wasteful channel allocation
  424. if ((info.type === 'session' && self.noMoreSessions)
  425. || !self.authenticated) {
  426. var reasonCode = CHANNEL_OPEN_FAILURE.ADMINISTRATIVELY_PROHIBITED;
  427. return stream.channelOpenFail(info.sender, reasonCode);
  428. }
  429. var localChan = nextChannel(self);
  430. var accept;
  431. var reject;
  432. var replied = false;
  433. if (localChan === false) {
  434. // auto-reject due to no channels available
  435. return stream.channelOpenFail(info.sender,
  436. CHANNEL_OPEN_FAILURE.RESOURCE_SHORTAGE);
  437. }
  438. // be optimistic, reserve channel to prevent another request from trying to
  439. // take the same channel
  440. channels[localChan] = true;
  441. reject = function() {
  442. if (replied)
  443. return;
  444. replied = true;
  445. delete channels[localChan];
  446. var reasonCode = CHANNEL_OPEN_FAILURE.ADMINISTRATIVELY_PROHIBITED;
  447. return stream.channelOpenFail(info.sender, reasonCode);
  448. };
  449. switch (info.type) {
  450. case 'session':
  451. if (listenerCount(self, 'session')) {
  452. accept = function() {
  453. if (replied)
  454. return;
  455. replied = true;
  456. stream.channelOpenConfirm(info.sender,
  457. localChan,
  458. Channel.MAX_WINDOW,
  459. Channel.PACKET_SIZE);
  460. return new Session(self, info, localChan);
  461. };
  462. self.emit('session', accept, reject);
  463. } else
  464. reject();
  465. break;
  466. case 'direct-tcpip':
  467. if (listenerCount(self, 'tcpip')) {
  468. accept = function() {
  469. if (replied)
  470. return;
  471. replied = true;
  472. stream.channelOpenConfirm(info.sender,
  473. localChan,
  474. Channel.MAX_WINDOW,
  475. Channel.PACKET_SIZE);
  476. var chaninfo = {
  477. type: undefined,
  478. incoming: {
  479. id: localChan,
  480. window: Channel.MAX_WINDOW,
  481. packetSize: Channel.PACKET_SIZE,
  482. state: 'open'
  483. },
  484. outgoing: {
  485. id: info.sender,
  486. window: info.window,
  487. packetSize: info.packetSize,
  488. state: 'open'
  489. }
  490. };
  491. return new Channel(chaninfo, self);
  492. };
  493. self.emit('tcpip', accept, reject, info.data);
  494. } else
  495. reject();
  496. break;
  497. case 'direct-streamlocal@openssh.com':
  498. if (listenerCount(self, 'openssh.streamlocal')) {
  499. accept = function() {
  500. if (replied)
  501. return;
  502. replied = true;
  503. stream.channelOpenConfirm(info.sender,
  504. localChan,
  505. Channel.MAX_WINDOW,
  506. Channel.PACKET_SIZE);
  507. var chaninfo = {
  508. type: undefined,
  509. incoming: {
  510. id: localChan,
  511. window: Channel.MAX_WINDOW,
  512. packetSize: Channel.PACKET_SIZE,
  513. state: 'open'
  514. },
  515. outgoing: {
  516. id: info.sender,
  517. window: info.window,
  518. packetSize: info.packetSize,
  519. state: 'open'
  520. }
  521. };
  522. return new Channel(chaninfo, self);
  523. };
  524. self.emit('openssh.streamlocal', accept, reject, info.data);
  525. } else
  526. reject();
  527. break;
  528. default:
  529. // auto-reject unsupported channel types
  530. reject();
  531. }
  532. });
  533. stream.on('NEWKEYS', function() {
  534. if (++exchanges > 1)
  535. self.emit('rekey');
  536. });
  537. if (kaMgr) {
  538. this.once('ready', function() {
  539. kaMgr.add(stream);
  540. });
  541. }
  542. }
  543. inherits(Client, EventEmitter);
  544. Client.prototype.end = function() {
  545. return this._sshstream.disconnect(DISCONNECT_REASON.BY_APPLICATION);
  546. };
  547. Client.prototype.x11 = function(originAddr, originPort, cb) {
  548. var opts = {
  549. originAddr: originAddr,
  550. originPort: originPort
  551. };
  552. return openChannel(this, 'x11', opts, cb);
  553. };
  554. Client.prototype.forwardOut = function(boundAddr, boundPort, remoteAddr,
  555. remotePort, cb) {
  556. var opts = {
  557. boundAddr: boundAddr,
  558. boundPort: boundPort,
  559. remoteAddr: remoteAddr,
  560. remotePort: remotePort
  561. };
  562. return openChannel(this, 'forwarded-tcpip', opts, cb);
  563. };
  564. Client.prototype.openssh_forwardOutStreamLocal = function(socketPath, cb) {
  565. var opts = {
  566. socketPath: socketPath
  567. };
  568. return openChannel(this, 'forwarded-streamlocal@openssh.com', opts, cb);
  569. };
  570. Client.prototype.rekey = function(cb) {
  571. var stream = this._sshstream;
  572. var ret = true;
  573. var error;
  574. try {
  575. ret = stream.rekey();
  576. } catch (ex) {
  577. error = ex;
  578. }
  579. // TODO: re-throw error if no callback?
  580. if (typeof cb === 'function') {
  581. if (error) {
  582. process.nextTick(function() {
  583. cb(error);
  584. });
  585. } else
  586. this.once('rekey', cb);
  587. }
  588. return ret;
  589. };
  590. function Session(client, info, localChan) {
  591. this.subtype = undefined;
  592. var ending = false;
  593. var self = this;
  594. var outgoingId = info.sender;
  595. var channel;
  596. var chaninfo = {
  597. type: 'session',
  598. incoming: {
  599. id: localChan,
  600. window: Channel.MAX_WINDOW,
  601. packetSize: Channel.PACKET_SIZE,
  602. state: 'open'
  603. },
  604. outgoing: {
  605. id: info.sender,
  606. window: info.window,
  607. packetSize: info.packetSize,
  608. state: 'open'
  609. }
  610. };
  611. function onREQUEST(info) {
  612. var replied = false;
  613. var accept;
  614. var reject;
  615. if (info.wantReply) {
  616. // "real session" requests will have custom accept behaviors
  617. if (info.request !== 'shell'
  618. && info.request !== 'exec'
  619. && info.request !== 'subsystem') {
  620. accept = function() {
  621. if (replied || ending || channel)
  622. return;
  623. replied = true;
  624. return client._sshstream.channelSuccess(outgoingId);
  625. };
  626. }
  627. reject = function() {
  628. if (replied || ending || channel)
  629. return;
  630. replied = true;
  631. return client._sshstream.channelFailure(outgoingId);
  632. };
  633. }
  634. if (ending) {
  635. reject && reject();
  636. return;
  637. }
  638. switch (info.request) {
  639. // "pre-real session start" requests
  640. case 'env':
  641. if (listenerCount(self, 'env')) {
  642. self.emit('env', accept, reject, {
  643. key: info.key,
  644. val: info.val
  645. });
  646. } else
  647. reject && reject();
  648. break;
  649. case 'pty-req':
  650. if (listenerCount(self, 'pty')) {
  651. self.emit('pty', accept, reject, {
  652. cols: info.cols,
  653. rows: info.rows,
  654. width: info.width,
  655. height: info.height,
  656. term: info.term,
  657. modes: info.modes,
  658. });
  659. } else
  660. reject && reject();
  661. break;
  662. case 'window-change':
  663. if (listenerCount(self, 'window-change')) {
  664. self.emit('window-change', accept, reject, {
  665. cols: info.cols,
  666. rows: info.rows,
  667. width: info.width,
  668. height: info.height
  669. });
  670. } else
  671. reject && reject();
  672. break;
  673. case 'x11-req':
  674. if (listenerCount(self, 'x11')) {
  675. self.emit('x11', accept, reject, {
  676. single: info.single,
  677. protocol: info.protocol,
  678. cookie: info.cookie,
  679. screen: info.screen
  680. });
  681. } else
  682. reject && reject();
  683. break;
  684. // "post-real session start" requests
  685. case 'signal':
  686. if (listenerCount(self, 'signal')) {
  687. self.emit('signal', accept, reject, {
  688. name: info.signal
  689. });
  690. } else
  691. reject && reject();
  692. break;
  693. // XXX: is `auth-agent-req@openssh.com` really "post-real session start"?
  694. case 'auth-agent-req@openssh.com':
  695. if (listenerCount(self, 'auth-agent'))
  696. self.emit('auth-agent', accept, reject);
  697. else
  698. reject && reject();
  699. break;
  700. // "real session start" requests
  701. case 'shell':
  702. if (listenerCount(self, 'shell')) {
  703. accept = function() {
  704. if (replied || ending || channel)
  705. return;
  706. replied = true;
  707. if (info.wantReply)
  708. client._sshstream.channelSuccess(outgoingId);
  709. channel = new Channel(chaninfo, client, { server: true });
  710. channel.subtype = self.subtype = info.request;
  711. return channel;
  712. };
  713. self.emit('shell', accept, reject);
  714. } else
  715. reject && reject();
  716. break;
  717. case 'exec':
  718. if (listenerCount(self, 'exec')) {
  719. accept = function() {
  720. if (replied || ending || channel)
  721. return;
  722. replied = true;
  723. if (info.wantReply)
  724. client._sshstream.channelSuccess(outgoingId);
  725. channel = new Channel(chaninfo, client, { server: true });
  726. channel.subtype = self.subtype = info.request;
  727. return channel;
  728. };
  729. self.emit('exec', accept, reject, {
  730. command: info.command
  731. });
  732. } else
  733. reject && reject();
  734. break;
  735. case 'subsystem':
  736. accept = function() {
  737. if (replied || ending || channel)
  738. return;
  739. replied = true;
  740. if (info.wantReply)
  741. client._sshstream.channelSuccess(outgoingId);
  742. channel = new Channel(chaninfo, client, { server: true });
  743. channel.subtype = self.subtype = (info.request + ':' + info.subsystem);
  744. if (info.subsystem === 'sftp') {
  745. var sftp = new SFTPStream({
  746. server: true,
  747. debug: client._sshstream.debug
  748. });
  749. channel.pipe(sftp).pipe(channel);
  750. return sftp;
  751. } else
  752. return channel;
  753. };
  754. if (info.subsystem === 'sftp' && listenerCount(self, 'sftp'))
  755. self.emit('sftp', accept, reject);
  756. else if (info.subsystem !== 'sftp' && listenerCount(self, 'subsystem')) {
  757. self.emit('subsystem', accept, reject, {
  758. name: info.subsystem
  759. });
  760. } else
  761. reject && reject();
  762. break;
  763. default:
  764. reject && reject();
  765. }
  766. }
  767. function onEOF() {
  768. ending = true;
  769. self.emit('eof');
  770. self.emit('end');
  771. }
  772. function onCLOSE() {
  773. ending = true;
  774. self.emit('close');
  775. }
  776. client._sshstream
  777. .on('CHANNEL_REQUEST:' + localChan, onREQUEST)
  778. .once('CHANNEL_EOF:' + localChan, onEOF)
  779. .once('CHANNEL_CLOSE:' + localChan, onCLOSE);
  780. }
  781. inherits(Session, EventEmitter);
  782. function AuthContext(stream, username, service, method, cb) {
  783. EventEmitter.call(this);
  784. var self = this;
  785. this.username = this.user = username;
  786. this.service = service;
  787. this.method = method;
  788. this._initialResponse = false;
  789. this._finalResponse = false;
  790. this._multistep = false;
  791. this._cbfinal = function(allowed, methodsLeft, isPartial) {
  792. if (!self._finalResponse) {
  793. self._finalResponse = true;
  794. cb(self, allowed, methodsLeft, isPartial);
  795. }
  796. };
  797. this._stream = stream;
  798. }
  799. inherits(AuthContext, EventEmitter);
  800. AuthContext.prototype.accept = function() {
  801. this._cleanup && this._cleanup();
  802. this._initialResponse = true;
  803. this._cbfinal(true);
  804. };
  805. AuthContext.prototype.reject = function(methodsLeft, isPartial) {
  806. this._cleanup && this._cleanup();
  807. this._initialResponse = true;
  808. this._cbfinal(false, methodsLeft, isPartial);
  809. };
  810. var RE_KBINT_SUBMETHODS = /[ \t\r\n]*,[ \t\r\n]*/g;
  811. function KeyboardAuthContext(stream, username, service, method, submethods, cb) {
  812. AuthContext.call(this, stream, username, service, method, cb);
  813. this._multistep = true;
  814. var self = this;
  815. this._cb = undefined;
  816. this._onInfoResponse = function(responses) {
  817. if (self._cb) {
  818. var callback = self._cb;
  819. self._cb = undefined;
  820. callback(responses);
  821. }
  822. };
  823. this.submethods = submethods.split(RE_KBINT_SUBMETHODS);
  824. this.on('abort', function() {
  825. self._cb && self._cb(new Error('Authentication request aborted'));
  826. });
  827. }
  828. inherits(KeyboardAuthContext, AuthContext);
  829. KeyboardAuthContext.prototype._cleanup = function() {
  830. this._stream.removeListener('USERAUTH_INFO_RESPONSE', this._onInfoResponse);
  831. };
  832. KeyboardAuthContext.prototype.prompt = function(prompts, title, instructions,
  833. cb) {
  834. if (!Array.isArray(prompts))
  835. prompts = [ prompts ];
  836. if (typeof title === 'function') {
  837. cb = title;
  838. title = instructions = undefined;
  839. } else if (typeof instructions === 'function') {
  840. cb = instructions;
  841. instructions = undefined;
  842. }
  843. for (var i = 0; i < prompts.length; ++i) {
  844. if (typeof prompts[i] === 'string') {
  845. prompts[i] = {
  846. prompt: prompts[i],
  847. echo: true
  848. };
  849. }
  850. }
  851. this._cb = cb;
  852. this._initialResponse = true;
  853. this._stream.once('USERAUTH_INFO_RESPONSE', this._onInfoResponse);
  854. return this._stream.authInfoReq(title, instructions, prompts);
  855. };
  856. function PKAuthContext(stream, username, service, method, pkInfo, cb) {
  857. AuthContext.call(this, stream, username, service, method, cb);
  858. this.key = { algo: pkInfo.keyAlgo, data: pkInfo.key };
  859. this.signature = pkInfo.signature;
  860. var sigAlgo;
  861. if (this.signature) {
  862. // TODO: move key type checking logic to ssh2-streams
  863. switch (pkInfo.keyAlgo) {
  864. case 'ssh-rsa':
  865. case 'ssh-dss':
  866. sigAlgo = 'sha1';
  867. break;
  868. case 'ssh-ed25519':
  869. sigAlgo = null;
  870. break;
  871. case 'ecdsa-sha2-nistp256':
  872. sigAlgo = 'sha256';
  873. break;
  874. case 'ecdsa-sha2-nistp384':
  875. sigAlgo = 'sha384';
  876. break;
  877. case 'ecdsa-sha2-nistp521':
  878. sigAlgo = 'sha512';
  879. break;
  880. }
  881. }
  882. this.sigAlgo = sigAlgo;
  883. this.blob = pkInfo.blob;
  884. }
  885. inherits(PKAuthContext, AuthContext);
  886. PKAuthContext.prototype.accept = function() {
  887. if (!this.signature) {
  888. this._initialResponse = true;
  889. this._stream.authPKOK(this.key.algo, this.key.data);
  890. } else {
  891. AuthContext.prototype.accept.call(this);
  892. }
  893. };
  894. function HostbasedAuthContext(stream, username, service, method, pkInfo, cb) {
  895. AuthContext.call(this, stream, username, service, method, cb);
  896. this.key = { algo: pkInfo.keyAlgo, data: pkInfo.key };
  897. this.signature = pkInfo.signature;
  898. var sigAlgo;
  899. if (this.signature) {
  900. // TODO: move key type checking logic to ssh2-streams
  901. switch (pkInfo.keyAlgo) {
  902. case 'ssh-rsa':
  903. case 'ssh-dss':
  904. sigAlgo = 'sha1';
  905. break;
  906. case 'ssh-ed25519':
  907. sigAlgo = null;
  908. break;
  909. case 'ecdsa-sha2-nistp256':
  910. sigAlgo = 'sha256';
  911. break;
  912. case 'ecdsa-sha2-nistp384':
  913. sigAlgo = 'sha384';
  914. break;
  915. case 'ecdsa-sha2-nistp521':
  916. sigAlgo = 'sha512';
  917. break;
  918. }
  919. }
  920. this.sigAlgo = sigAlgo;
  921. this.blob = pkInfo.blob;
  922. this.localHostname = pkInfo.localHostname;
  923. this.localUsername = pkInfo.localUsername;
  924. }
  925. inherits(HostbasedAuthContext, AuthContext);
  926. function PwdAuthContext(stream, username, service, method, password, cb) {
  927. AuthContext.call(this, stream, username, service, method, cb);
  928. this.password = password;
  929. }
  930. inherits(PwdAuthContext, AuthContext);
  931. function openChannel(self, type, opts, cb) {
  932. // ask the client to open a channel for some purpose
  933. // (e.g. a forwarded TCP connection)
  934. var localChan = nextChannel(self);
  935. var initWindow = Channel.MAX_WINDOW;
  936. var maxPacket = Channel.PACKET_SIZE;
  937. var ret = true;
  938. if (localChan === false)
  939. return cb(new Error('No free channels available'));
  940. if (typeof opts === 'function') {
  941. cb = opts;
  942. opts = {};
  943. }
  944. self._channels[localChan] = true;
  945. var sshstream = self._sshstream;
  946. sshstream.once('CHANNEL_OPEN_CONFIRMATION:' + localChan, function(info) {
  947. sshstream.removeAllListeners('CHANNEL_OPEN_FAILURE:' + localChan);
  948. var chaninfo = {
  949. type: type,
  950. incoming: {
  951. id: localChan,
  952. window: initWindow,
  953. packetSize: maxPacket,
  954. state: 'open'
  955. },
  956. outgoing: {
  957. id: info.sender,
  958. window: info.window,
  959. packetSize: info.packetSize,
  960. state: 'open'
  961. }
  962. };
  963. cb(undefined, new Channel(chaninfo, self, { server: true }));
  964. }).once('CHANNEL_OPEN_FAILURE:' + localChan, function(info) {
  965. sshstream.removeAllListeners('CHANNEL_OPEN_CONFIRMATION:' + localChan);
  966. delete self._channels[localChan];
  967. var err = new Error('(SSH) Channel open failure: ' + info.description);
  968. err.reason = info.reason;
  969. err.lang = info.lang;
  970. cb(err);
  971. });
  972. if (type === 'forwarded-tcpip')
  973. ret = sshstream.forwardedTcpip(localChan, initWindow, maxPacket, opts);
  974. else if (type === 'x11')
  975. ret = sshstream.x11(localChan, initWindow, maxPacket, opts);
  976. else if (type === 'forwarded-streamlocal@openssh.com') {
  977. ret = sshstream.openssh_forwardedStreamLocal(localChan,
  978. initWindow,
  979. maxPacket,
  980. opts);
  981. }
  982. return ret;
  983. }
  984. function nextChannel(self) {
  985. // get the next available channel number
  986. // fast path
  987. if (self._curChan < MAX_CHANNEL)
  988. return ++self._curChan;
  989. // slower lookup path
  990. for (var i = 0, channels = self._channels; i < MAX_CHANNEL; ++i)
  991. if (!channels[i])
  992. return i;
  993. return false;
  994. }
  995. Server.createServer = function(cfg, listener) {
  996. return new Server(cfg, listener);
  997. };
  998. Server.KEEPALIVE_INTERVAL = 1000;
  999. Server.KEEPALIVE_CLIENT_INTERVAL = 15000;
  1000. Server.KEEPALIVE_CLIENT_COUNT_MAX = 3;
  1001. module.exports = Server;
  1002. module.exports.IncomingClient = Client;