Replace enum+union of local channel types with a vtable.
There's now an interface called 'Channel', which handles the local
side of an SSH connection-layer channel, in terms of knowing where to
send incoming channel data to, whether to close the channel, etc.
Channel and the previous 'struct ssh_channel' mutually refer. The
latter contains all the SSH-specific parts, and as much of the common
logic as possible: in particular, Channel doesn't have to know
anything about SSH packet formats, or which SSH protocol version is in
use, or deal with all the fiddly stuff about window sizes - with the
exception that x11fwd.c's implementation of it does have to be able to
ask for a small fixed initial window size for the bodgy system that
distinguishes upstream from downstream X forwardings.
I've taken the opportunity to move the code implementing the detailed
behaviour of agent forwarding out of ssh.c, now that all of it is on
the far side of a uniform interface. (This also means that if I later
implement agent forwarding directly to a Unix socket as an
alternative, it'll be a matter of changing just the one call to
agentf_new() that makes the Channel to plug into a forwarding.)
2018-09-12 14:03:47 +00:00
|
|
|
/*
|
|
|
|
* SSH agent forwarding.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include "putty.h"
|
|
|
|
#include "ssh.h"
|
|
|
|
#include "pageant.h"
|
|
|
|
#include "sshchan.h"
|
|
|
|
|
|
|
|
typedef struct agentf {
|
2018-09-14 12:47:13 +00:00
|
|
|
SshChannel *c;
|
Replace enum+union of local channel types with a vtable.
There's now an interface called 'Channel', which handles the local
side of an SSH connection-layer channel, in terms of knowing where to
send incoming channel data to, whether to close the channel, etc.
Channel and the previous 'struct ssh_channel' mutually refer. The
latter contains all the SSH-specific parts, and as much of the common
logic as possible: in particular, Channel doesn't have to know
anything about SSH packet formats, or which SSH protocol version is in
use, or deal with all the fiddly stuff about window sizes - with the
exception that x11fwd.c's implementation of it does have to be able to
ask for a small fixed initial window size for the bodgy system that
distinguishes upstream from downstream X forwardings.
I've taken the opportunity to move the code implementing the detailed
behaviour of agent forwarding out of ssh.c, now that all of it is on
the far side of a uniform interface. (This also means that if I later
implement agent forwarding directly to a Unix socket as an
alternative, it'll be a matter of changing just the one call to
agentf_new() that makes the Channel to plug into a forwarding.)
2018-09-12 14:03:47 +00:00
|
|
|
bufchain inbuffer;
|
|
|
|
agent_pending_query *pending;
|
|
|
|
int input_wanted;
|
|
|
|
int rcvd_eof;
|
|
|
|
|
|
|
|
Channel chan;
|
|
|
|
} agentf;
|
|
|
|
|
|
|
|
static void agentf_got_response(agentf *af, void *reply, int replylen)
|
|
|
|
{
|
|
|
|
af->pending = NULL;
|
|
|
|
|
|
|
|
if (!reply) {
|
|
|
|
/* The real agent didn't send any kind of reply at all for
|
|
|
|
* some reason, so fake an SSH_AGENT_FAILURE. */
|
|
|
|
reply = "\0\0\0\1\5";
|
|
|
|
replylen = 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
sshfwd_write(af->c, reply, replylen);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void agentf_callback(void *vctx, void *reply, int replylen);
|
|
|
|
|
|
|
|
static void agentf_try_forward(agentf *af)
|
|
|
|
{
|
|
|
|
unsigned datalen, length;
|
|
|
|
strbuf *message;
|
|
|
|
unsigned char msglen[4];
|
|
|
|
void *reply;
|
|
|
|
int replylen;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't try to parallelise agent requests. Wait for each one to
|
|
|
|
* return before attempting the next.
|
|
|
|
*/
|
|
|
|
if (af->pending)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the outgoing side of the channel connection is currently
|
|
|
|
* throttled, don't submit any new forwarded requests to the real
|
|
|
|
* agent. This causes the input side of the agent forwarding not
|
|
|
|
* to be emptied, exerting the required back-pressure on the
|
|
|
|
* remote client, and encouraging it to read our responses before
|
|
|
|
* sending too many more requests.
|
|
|
|
*/
|
|
|
|
if (!af->input_wanted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
/*
|
|
|
|
* Try to extract a complete message from the input buffer.
|
|
|
|
*/
|
|
|
|
datalen = bufchain_size(&af->inbuffer);
|
|
|
|
if (datalen < 4)
|
|
|
|
break; /* not even a length field available yet */
|
|
|
|
|
|
|
|
bufchain_fetch(&af->inbuffer, msglen, 4);
|
|
|
|
length = GET_32BIT(msglen);
|
|
|
|
|
|
|
|
if (length > AGENT_MAX_MSGLEN-4) {
|
|
|
|
/*
|
|
|
|
* If the remote has sent a message that's just _too_
|
|
|
|
* long, we should reject it in advance of seeing the rest
|
|
|
|
* of the incoming message, and also close the connection
|
|
|
|
* for good measure (which avoids us having to faff about
|
|
|
|
* with carefully ignoring just the right number of bytes
|
|
|
|
* from the overlong message).
|
|
|
|
*/
|
|
|
|
agentf_got_response(af, NULL, 0);
|
|
|
|
sshfwd_write_eof(af->c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length > datalen - 4)
|
|
|
|
break; /* a whole message is not yet available */
|
|
|
|
|
|
|
|
bufchain_consume(&af->inbuffer, 4);
|
|
|
|
|
|
|
|
message = strbuf_new_for_agent_query();
|
|
|
|
bufchain_fetch_consume(
|
|
|
|
&af->inbuffer, strbuf_append(message, length), length);
|
|
|
|
af->pending = agent_query(
|
|
|
|
message, &reply, &replylen, agentf_callback, af);
|
|
|
|
strbuf_free(message);
|
|
|
|
|
|
|
|
if (af->pending)
|
|
|
|
return; /* agent_query promised to reply in due course */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the agent gave us an answer immediately, pass it
|
|
|
|
* straight on and go round this loop again.
|
|
|
|
*/
|
|
|
|
agentf_got_response(af, reply, replylen);
|
|
|
|
sfree(reply);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we get here (i.e. we left the above while loop via 'break'
|
|
|
|
* rather than 'return'), that means we've determined that the
|
|
|
|
* input buffer for the agent forwarding connection doesn't
|
|
|
|
* contain a complete request.
|
|
|
|
*
|
|
|
|
* So if there's potentially more data to come, we can return now,
|
|
|
|
* and wait for the remote client to send it. But if the remote
|
|
|
|
* has sent EOF, it would be a mistake to do that, because we'd be
|
|
|
|
* waiting a long time. So this is the moment to check for EOF,
|
|
|
|
* and respond appropriately.
|
|
|
|
*/
|
|
|
|
if (af->rcvd_eof)
|
|
|
|
sshfwd_write_eof(af->c);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void agentf_callback(void *vctx, void *reply, int replylen)
|
|
|
|
{
|
|
|
|
agentf *af = (agentf *)vctx;
|
|
|
|
|
|
|
|
agentf_got_response(af, reply, replylen);
|
|
|
|
sfree(reply);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now try to extract and send further messages from the channel's
|
|
|
|
* input-side buffer.
|
|
|
|
*/
|
|
|
|
agentf_try_forward(af);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void agentf_free(Channel *chan);
|
|
|
|
static int agentf_send(Channel *chan, int is_stderr, const void *, int);
|
|
|
|
static void agentf_send_eof(Channel *chan);
|
|
|
|
static char *agentf_log_close_msg(Channel *chan);
|
|
|
|
static void agentf_set_input_wanted(Channel *chan, int wanted);
|
|
|
|
|
|
|
|
static const struct ChannelVtable agentf_channelvt = {
|
|
|
|
agentf_free,
|
|
|
|
chan_remotely_opened_confirmation,
|
|
|
|
chan_remotely_opened_failure,
|
|
|
|
agentf_send,
|
|
|
|
agentf_send_eof,
|
|
|
|
agentf_set_input_wanted,
|
|
|
|
agentf_log_close_msg,
|
|
|
|
chan_no_eager_close,
|
|
|
|
};
|
|
|
|
|
2018-09-14 12:47:13 +00:00
|
|
|
Channel *agentf_new(SshChannel *c)
|
Replace enum+union of local channel types with a vtable.
There's now an interface called 'Channel', which handles the local
side of an SSH connection-layer channel, in terms of knowing where to
send incoming channel data to, whether to close the channel, etc.
Channel and the previous 'struct ssh_channel' mutually refer. The
latter contains all the SSH-specific parts, and as much of the common
logic as possible: in particular, Channel doesn't have to know
anything about SSH packet formats, or which SSH protocol version is in
use, or deal with all the fiddly stuff about window sizes - with the
exception that x11fwd.c's implementation of it does have to be able to
ask for a small fixed initial window size for the bodgy system that
distinguishes upstream from downstream X forwardings.
I've taken the opportunity to move the code implementing the detailed
behaviour of agent forwarding out of ssh.c, now that all of it is on
the far side of a uniform interface. (This also means that if I later
implement agent forwarding directly to a Unix socket as an
alternative, it'll be a matter of changing just the one call to
agentf_new() that makes the Channel to plug into a forwarding.)
2018-09-12 14:03:47 +00:00
|
|
|
{
|
|
|
|
agentf *af = snew(agentf);
|
|
|
|
af->c = c;
|
|
|
|
af->chan.vt = &agentf_channelvt;
|
|
|
|
af->chan.initial_fixed_window_size = 0;
|
2018-09-24 13:40:35 +00:00
|
|
|
af->rcvd_eof = FALSE;
|
Replace enum+union of local channel types with a vtable.
There's now an interface called 'Channel', which handles the local
side of an SSH connection-layer channel, in terms of knowing where to
send incoming channel data to, whether to close the channel, etc.
Channel and the previous 'struct ssh_channel' mutually refer. The
latter contains all the SSH-specific parts, and as much of the common
logic as possible: in particular, Channel doesn't have to know
anything about SSH packet formats, or which SSH protocol version is in
use, or deal with all the fiddly stuff about window sizes - with the
exception that x11fwd.c's implementation of it does have to be able to
ask for a small fixed initial window size for the bodgy system that
distinguishes upstream from downstream X forwardings.
I've taken the opportunity to move the code implementing the detailed
behaviour of agent forwarding out of ssh.c, now that all of it is on
the far side of a uniform interface. (This also means that if I later
implement agent forwarding directly to a Unix socket as an
alternative, it'll be a matter of changing just the one call to
agentf_new() that makes the Channel to plug into a forwarding.)
2018-09-12 14:03:47 +00:00
|
|
|
bufchain_init(&af->inbuffer);
|
|
|
|
af->pending = NULL;
|
|
|
|
af->input_wanted = TRUE;
|
|
|
|
return &af->chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void agentf_free(Channel *chan)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &agentf_channelvt);
|
|
|
|
agentf *af = FROMFIELD(chan, agentf, chan);
|
|
|
|
|
|
|
|
if (af->pending)
|
|
|
|
agent_cancel_query(af->pending);
|
|
|
|
bufchain_clear(&af->inbuffer);
|
|
|
|
sfree(af);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int agentf_send(Channel *chan, int is_stderr,
|
|
|
|
const void *data, int length)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &agentf_channelvt);
|
|
|
|
agentf *af = FROMFIELD(chan, agentf, chan);
|
|
|
|
bufchain_add(&af->inbuffer, data, length);
|
|
|
|
agentf_try_forward(af);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We exert back-pressure on an agent forwarding client if and
|
|
|
|
* only if we're waiting for the response to an asynchronous agent
|
|
|
|
* request. This prevents the client running out of window while
|
|
|
|
* receiving the _first_ message, but means that if any message
|
|
|
|
* takes time to process, the client will be discouraged from
|
|
|
|
* sending an endless stream of further ones after it.
|
|
|
|
*/
|
|
|
|
return (af->pending ? bufchain_size(&af->inbuffer) : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void agentf_send_eof(Channel *chan)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &agentf_channelvt);
|
|
|
|
agentf *af = FROMFIELD(chan, agentf, chan);
|
|
|
|
|
|
|
|
af->rcvd_eof = TRUE;
|
|
|
|
|
|
|
|
/* Call try_forward, which will respond to the EOF now if
|
|
|
|
* appropriate, or wait until the queue of outstanding requests is
|
|
|
|
* dealt with if not. */
|
|
|
|
agentf_try_forward(af);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *agentf_log_close_msg(Channel *chan)
|
|
|
|
{
|
|
|
|
return dupstr("Agent-forwarding connection closed");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void agentf_set_input_wanted(Channel *chan, int wanted)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &agentf_channelvt);
|
|
|
|
agentf *af = FROMFIELD(chan, agentf, chan);
|
|
|
|
|
|
|
|
af->input_wanted = wanted;
|
|
|
|
|
|
|
|
/* Agent forwarding channels are buffer-managed by not asking the
|
|
|
|
* agent questions if the SSH channel isn't accepting input. So if
|
|
|
|
* it's started again, we should ask a question if we have one
|
|
|
|
* pending.. */
|
|
|
|
if (wanted)
|
|
|
|
agentf_try_forward(af);
|
|
|
|
}
|