diff --git a/README.md b/README.md index e08c929e..e25545f7 100644 --- a/README.md +++ b/README.md @@ -1217,6 +1217,9 @@ You can find more examples in the `examples` directory of this repository. * **forwardOut**(< _string_ >boundAddr, < _integer_ >boundPort, < _string_ >remoteAddr, < _integer_ >remotePort, < _function_ >callback) - _(void)_ - Alert the client of an incoming TCP connection on `boundAddr` on port `boundPort` from `remoteAddr` on port `remotePort`. `callback` has 2 parameters: < _Error_ >err, < _Channel_ >stream. +* **openssh_authAgent**(< _function_ >callback) - _boolean_ - Alert the client of an incoming `ssh-agent` socket connection. `callback` has 2 parameters: < _Error_ >err, < _Channel_ >stream. Returns `false` if you should wait for the `continue` event before sending any more traffic. + + * **openssh_forwardOutStreamLocal**(< _string_ >socketPath, < _function_ >callback) - _(void)_ - Alert the client of an incoming UNIX domain socket connection on `socketPath`. `callback` has 2 parameters: < _Error_ >err, < _Channel_ >stream. * **rekey**([< _function_ >callback]) - _(void)_ - Initiates a rekey with the client. If `callback` is supplied, it is added as a one-time handler for the `rekey` event. diff --git a/lib/server.js b/lib/server.js index 306d6584..a0b7a8a9 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1298,6 +1298,12 @@ class Client extends EventEmitter { return this; } + + openssh_authAgent(cb) { + openChannel(this, 'auth-agent@openssh.com', cb); + return this; + } + openssh_forwardOutStreamLocal(socketPath, cb) { const opts = { socketPath }; openChannel(this, 'forwarded-streamlocal@openssh.com', opts, cb); @@ -1362,6 +1368,9 @@ function openChannel(self, type, opts, cb) { case 'x11': self._protocol.x11(localChan, initWindow, maxPacket, opts); break; + case 'auth-agent@openssh.com': + self._protocol.openssh_authAgent(localChan, initWindow, maxPacket); + break; case 'forwarded-streamlocal@openssh.com': self._protocol.openssh_forwardedStreamLocal( localChan, initWindow, maxPacket, opts diff --git a/test/test-openssh.js b/test/test-openssh.js index 67e1b860..e27ba71f 100644 --- a/test/test-openssh.js +++ b/test/test-openssh.js @@ -1,6 +1,8 @@ 'use strict'; const assert = require('assert'); +const fs = require('fs'); +const net = require('net'); const { inspect } = require('util'); const { @@ -259,3 +261,76 @@ const serverCfg = { hostKeys: [ fixture('ssh_host_rsa_key') ] }; accept(); })); } + +{ + const { join } = require('path'); + const { tmpdir } = require('os'); + + const agentSockPath = process.platform === 'win32' + ? `\\\\.\\pipe\\ssh2-test-agent-${process.pid}` + : join(tmpdir(), `ssh2-test-agent-${process.pid}.sock`); + + const isWin = process.platform === 'win32'; + + if (!isWin && fs.existsSync(agentSockPath)) + fs.unlinkSync(agentSockPath); + + // Fake agent: echoes back whatever it receives + const agentServer = net.createServer(mustCall((sock) => { + sock.on('data', mustCallAtLeast((data) => { + sock.write(data); + })); + })); + + agentServer.listen(agentSockPath); + + const { client, server } = setup_( + 'Server-initiated openssh_authAgent channel', + { + client: { + ...clientCfg, + agent: agentSockPath, + }, + server: serverCfg, + debug, + }, + ); + + server.on('connection', mustCall((conn) => { + conn.on('authentication', mustCall((ctx) => { + ctx.accept(); + })).on('ready', mustCall(() => { + conn.on('session', mustCall((accept, reject) => { + accept().on('auth-agent', mustCall((accept, reject) => { + accept && accept(); + })).on('exec', mustCall((accept, reject, info) => { + const stream = accept(); + stream.exit(0); + stream.end(); + + conn.openssh_authAgent(mustCall((err, agentStream) => { + assert(!err, `Unexpected openssh_authAgent error: ${err}`); + + const testData = 'agent-ping'; + agentStream.on('data', mustCallAtLeast((data) => { + assert(data.toString() === testData, + `Expected '${testData}', got: '${data}'`); + conn.end(); + agentServer.close(); + if (!isWin && fs.existsSync(agentSockPath)) + fs.unlinkSync(agentSockPath); + })); + agentStream.write(testData); + })); + })); + })); + })); + })); + + client.on('ready', mustCall(() => { + client.exec('foo', { agentForward: true }, mustCall((err, stream) => { + assert(!err, `Unexpected exec error: ${err}`); + stream.resume(); + })); + })); +}