| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 | /* * SSH agent forwarding. */#include <assert.h>#include <stdio.h>#include <stdlib.h>#include "putty.h"#include "ssh.h"#include "pageant.h"#include "channel.h"typedef struct agentf {    struct callback_set * callback_set; // WINSCP    SshChannel *c;    bufchain inbuffer;    agent_pending_query *pending;    bool input_wanted;    bool 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){    size_t 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_MSB_FIRST(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, af->callback_set); // WINSCP        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 size_t agentf_send(Channel *chan, bool is_stderr, const void *, size_t);static void agentf_send_eof(Channel *chan);static char *agentf_log_close_msg(Channel *chan);static void agentf_set_input_wanted(Channel *chan, bool wanted);static const ChannelVtable agentf_channelvt = {    // WINSCP    /*.free =*/ agentf_free,    /*.open_confirmation =*/ chan_remotely_opened_confirmation,    /*.open_failed =*/ chan_remotely_opened_failure,    /*.send =*/ agentf_send,    /*.send_eof =*/ agentf_send_eof,    /*.set_input_wanted =*/ agentf_set_input_wanted,    /*.log_close_msg =*/ agentf_log_close_msg,    /*.want_close =*/ chan_default_want_close,    /*.rcvd_exit_status =*/ chan_no_exit_status,    /*.rcvd_exit_signal =*/ chan_no_exit_signal,    /*.rcvd_exit_signal_numeric =*/ chan_no_exit_signal_numeric,    /*.run_shell =*/ chan_no_run_shell,    /*.run_command =*/ chan_no_run_command,    /*.run_subsystem =*/ chan_no_run_subsystem,    /*.enable_x11_forwarding =*/ chan_no_enable_x11_forwarding,    /*.enable_agent_forwarding =*/ chan_no_enable_agent_forwarding,    /*.allocate_pty =*/ chan_no_allocate_pty,    /*.set_env =*/ chan_no_set_env,    /*.send_break =*/ chan_no_send_break,    /*.send_signal =*/ chan_no_send_signal,    /*.change_window_size =*/ chan_no_change_window_size,    /*.request_response =*/ chan_no_request_response,};Channel *agentf_new(SshChannel *c, struct callback_set *callback_set) // WINSCP{    agentf *af = snew(agentf);    af->callback_set = callback_set; // WINSCP    af->c = c;    af->chan.vt = &agentf_channelvt;    af->chan.initial_fixed_window_size = 0;    af->rcvd_eof = false;    bufchain_init(&af->inbuffer);    af->pending = NULL;    af->input_wanted = true;    return &af->chan;}static void agentf_free(Channel *chan){    pinitassert(chan->vt == &agentf_channelvt);    agentf *af = container_of(chan, agentf, chan);    if (af->pending)        agent_cancel_query(af->pending);    bufchain_clear(&af->inbuffer);    sfree(af);}static size_t agentf_send(Channel *chan, bool is_stderr,                          const void *data, size_t length){    pinitassert(chan->vt == &agentf_channelvt);    agentf *af = container_of(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){    pinitassert(chan->vt == &agentf_channelvt);    agentf *af = container_of(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, bool wanted){    pinitassert(chan->vt == &agentf_channelvt);    agentf *af = container_of(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);}
 |