| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333 | /* * Supporting routines used in common by all the various components of * the SSH system. */#include <assert.h>#include <stdlib.h>#include "putty.h"#include "mpint.h"#include "ssh.h"#include "storage.h"#include "bpp.h"#include "ppl.h"#include "channel.h"/* ---------------------------------------------------------------------- * Implementation of PacketQueue. */static void pq_ensure_unlinked(PacketQueueNode *node){    if (node->on_free_queue) {        node->next->prev = node->prev;        node->prev->next = node->next;    } else {        assert(!node->next);        assert(!node->prev);    }}void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node){    pq_ensure_unlinked(node);    node->next = &pqb->end;    node->prev = pqb->end.prev;    node->next->prev = node;    node->prev->next = node;    pqb->total_size += node->formal_size;    if (pqb->ic)        queue_idempotent_callback(pqb->ic);}void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node){    pq_ensure_unlinked(node);    node->prev = &pqb->end;    node->next = pqb->end.next;    node->next->prev = node;    node->prev->next = node;    pqb->total_size += node->formal_size;    if (pqb->ic)        queue_idempotent_callback(pqb->ic);}#ifndef WINSCPstatic PacketQueueNode pktin_freeq_head = {    &pktin_freeq_head, &pktin_freeq_head, true};#endif/*WINSCP static*/ void pktin_free_queue_callback(void *vctx){    struct callback_set * set = (struct callback_set *)vctx;    while (set->pktin_freeq_head->next != set->pktin_freeq_head) {        PacketQueueNode *node = set->pktin_freeq_head->next;        PktIn *pktin = container_of(node, PktIn, qnode);        set->pktin_freeq_head->next = node->next;        sfree(pktin);    }    set->pktin_freeq_head->prev = set->pktin_freeq_head;}#ifndef WINSCPstatic IdempotentCallback ic_pktin_free = {    pktin_free_queue_callback, NULL, false};#endifstatic inline void pq_unlink_common(PacketQueueBase *pqb,                                    PacketQueueNode *node){    node->next->prev = node->prev;    node->prev->next = node->next;    /* Check total_size doesn't drift out of sync downwards, by     * ensuring it doesn't underflow when we do this subtraction */    assert(pqb->total_size >= node->formal_size);    pqb->total_size -= node->formal_size;    /* Check total_size doesn't drift out of sync upwards, by checking     * that it's returned to exactly zero whenever a queue is     * emptied */    assert(pqb->end.next != &pqb->end || pqb->total_size == 0);}static PktIn *pq_in_after(PacketQueueBase *pqb,                          PacketQueueNode *prev, bool pop){    PacketQueueNode *node = prev->next;    if (node == &pqb->end)        return NULL;    if (pop) {        #ifdef WINSCP        struct callback_set * set = get_seat_callback_set(pqb->seat);        assert(set != NULL);        if (set->ic_pktin_free == NULL)        {            set->pktin_freeq_head = snew(PacketQueueNode);            set->pktin_freeq_head->next = set->pktin_freeq_head;            set->pktin_freeq_head->prev = set->pktin_freeq_head;            set->pktin_freeq_head->on_free_queue = TRUE;            set->ic_pktin_free = snew(IdempotentCallback);            set->ic_pktin_free->fn = pktin_free_queue_callback;            set->ic_pktin_free->ctx = set;            set->ic_pktin_free->queued = FALSE;            set->ic_pktin_free->set = set;        }        #endif        pq_unlink_common(pqb, node);        node->prev = set->pktin_freeq_head->prev; // WINSCP        node->next = set->pktin_freeq_head; // WINSCP        node->next->prev = node;        node->prev->next = node;        node->on_free_queue = true;        queue_idempotent_callback(set->ic_pktin_free); // WINSCP    }    return container_of(node, PktIn, qnode);}static PktOut *pq_out_after(PacketQueueBase *pqb,                            PacketQueueNode *prev, bool pop){    PacketQueueNode *node = prev->next;    if (node == &pqb->end)        return NULL;    if (pop) {        pq_unlink_common(pqb, node);        node->prev = node->next = NULL;    }    return container_of(node, PktOut, qnode);}void pq_in_init(PktInQueue *pq, Seat * seat) // WINSCP{    pq->pqb.ic = NULL;    pq->pqb.seat = seat;    pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;    pq->after = pq_in_after;    pq->pqb.total_size = 0;}void pq_out_init(PktOutQueue *pq, Seat * seat) // WINSCP{    pq->pqb.ic = NULL;    pq->pqb.seat = seat;    pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;    pq->after = pq_out_after;    pq->pqb.total_size = 0;}void pq_in_clear(PktInQueue *pq){    PktIn *pkt;    pq->pqb.ic = NULL;    while ((pkt = pq_pop(pq)) != NULL) {        /* No need to actually free these packets: pq_pop on a         * PktInQueue will automatically move them to the free         * queue. */    }}void pq_out_clear(PktOutQueue *pq){    PktOut *pkt;    pq->pqb.ic = NULL;    while ((pkt = pq_pop(pq)) != NULL)        ssh_free_pktout(pkt);}/* * Concatenate the contents of the two queues q1 and q2, and leave the * result in qdest. qdest must be either empty, or one of the input * queues. */void pq_base_concatenate(PacketQueueBase *qdest,                         PacketQueueBase *q1, PacketQueueBase *q2){    struct PacketQueueNode *head1, *tail1, *head2, *tail2;    size_t total_size = q1->total_size + q2->total_size;    /*     * Extract the contents from both input queues, and empty them.     */    head1 = (q1->end.next == &q1->end ? NULL : q1->end.next);    tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev);    head2 = (q2->end.next == &q2->end ? NULL : q2->end.next);    tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev);    q1->end.next = q1->end.prev = &q1->end;    q2->end.next = q2->end.prev = &q2->end;    q1->total_size = q2->total_size = 0;    /*     * Link the two lists together, handling the case where one or     * both is empty.     */    if (tail1)        tail1->next = head2;    else        head1 = head2;    if (head2)        head2->prev = tail1;    else        tail2 = tail1;    /*     * Check the destination queue is currently empty. (If it was one     * of the input queues, then it will be, because we emptied both     * of those just a moment ago.)     */    assert(qdest->end.next == &qdest->end);    assert(qdest->end.prev == &qdest->end);    /*     * If our concatenated list has anything in it, then put it in     * dest.     */    if (!head1) {        assert(!tail2);    } else {        assert(tail2);        qdest->end.next = head1;        qdest->end.prev = tail2;        head1->prev = &qdest->end;        tail2->next = &qdest->end;        if (qdest->ic)            queue_idempotent_callback(qdest->ic);    }    qdest->total_size = total_size;}/* ---------------------------------------------------------------------- * Low-level functions for the packet structures themselves. */static void ssh_pkt_BinarySink_write(BinarySink *bs,                                     const void *data, size_t len);PktOut *ssh_new_packet(void){    PktOut *pkt = snew(PktOut);    BinarySink_INIT(pkt, ssh_pkt_BinarySink_write);    pkt->data = NULL;    pkt->length = 0;    pkt->maxlen = 0;    pkt->downstream_id = 0;    pkt->additional_log_text = NULL;    pkt->qnode.next = pkt->qnode.prev = NULL;    pkt->qnode.on_free_queue = false;    return pkt;}static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len){    sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len);    memcpy(pkt->data + pkt->length, data, len);    pkt->length += len;    pkt->qnode.formal_size = pkt->length;}static void ssh_pkt_BinarySink_write(BinarySink *bs,                                     const void *data, size_t len){    PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut);    ssh_pkt_adddata(pkt, data, len);}void ssh_free_pktout(PktOut *pkt){    sfree(pkt->data);    sfree(pkt);}/* ---------------------------------------------------------------------- * Implement zombiechan_new() and its trivial vtable. */static void zombiechan_free(Channel *chan);static size_t zombiechan_send(    Channel *chan, bool is_stderr, const void *, size_t);static void zombiechan_set_input_wanted(Channel *chan, bool wanted);static void zombiechan_do_nothing(Channel *chan);static void zombiechan_open_failure(Channel *chan, const char *);static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof);static char *zombiechan_log_close_msg(Channel *chan) { return NULL; }static const ChannelVtable zombiechan_channelvt = {    /*.free =*/ zombiechan_free,    /*.open_confirmation =*/ zombiechan_do_nothing,    /*.open_failed =*/ zombiechan_open_failure,    /*.send =*/ zombiechan_send,    /*.send_eof =*/ zombiechan_do_nothing,    /*.set_input_wanted =*/ zombiechan_set_input_wanted,    /*.log_close_msg =*/ zombiechan_log_close_msg,    /*.want_close =*/ zombiechan_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 *zombiechan_new(void){    Channel *chan = snew(Channel);    chan->vt = &zombiechan_channelvt;    chan->initial_fixed_window_size = 0;    return chan;}static void zombiechan_free(Channel *chan){    assert(chan->vt == &zombiechan_channelvt);    sfree(chan);}static void zombiechan_do_nothing(Channel *chan){    assert(chan->vt == &zombiechan_channelvt);}static void zombiechan_open_failure(Channel *chan, const char *errtext){    assert(chan->vt == &zombiechan_channelvt);}static size_t zombiechan_send(Channel *chan, bool is_stderr,                              const void *data, size_t length){    assert(chan->vt == &zombiechan_channelvt);    return 0;}static void zombiechan_set_input_wanted(Channel *chan, bool enable){    assert(chan->vt == &zombiechan_channelvt);}static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof){    return true;}/* ---------------------------------------------------------------------- * Common routines for handling SSH tty modes. */static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version){    switch (our_opcode) {      case TTYMODE_ISPEED:        return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2;      case TTYMODE_OSPEED:        return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2;      default:        return our_opcode;    }}static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version){    if (ssh_version == 1) {        switch (real_opcode) {          case TTYMODE_ISPEED_SSH1:            return TTYMODE_ISPEED;          case TTYMODE_OSPEED_SSH1:            return TTYMODE_OSPEED;          default:            return real_opcode;        }    } else {        switch (real_opcode) {          case TTYMODE_ISPEED_SSH2:            return TTYMODE_ISPEED;          case TTYMODE_OSPEED_SSH2:            return TTYMODE_OSPEED;          default:            return real_opcode;        }    }}struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf){    struct ssh_ttymodes modes;    size_t i;    static const struct mode_name_type {        const char *mode;        int opcode;        enum { TYPE_CHAR, TYPE_BOOL } type;    } modes_names_types[] = {        #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR },        #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL },        #include "ttymode-list.h"        #undef TTYMODE_CHAR        #undef TTYMODE_FLAG    };    memset(&modes, 0, sizeof(modes));    for (i = 0; i < lenof(modes_names_types); i++) {        const struct mode_name_type *mode = &modes_names_types[i];        const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode);        char *to_free = NULL;        if (!sval)            sval = "N";                /* just in case */        /*         * sval[0] can be         *  - 'V', indicating that an explicit value follows it;         *  - 'A', indicating that we should pass the value through from         *    the local environment via get_ttymode; or         *  - 'N', indicating that we should explicitly not send this         *    mode.         */        if (sval[0] == 'A') {            sval = to_free = seat_get_ttymode(seat, mode->mode);        } else if (sval[0] == 'V') {            sval++;                    /* skip the 'V' */        } else {            /* else 'N', or something from the future we don't understand */            continue;        }        if (sval) {            /*             * Parse the string representation of the tty mode             * into the integer value it will take on the wire.             */            unsigned ival = 0;            switch (mode->type) {              case TYPE_CHAR:                if (*sval) {                    char *next = NULL;                    /* We know ctrlparse won't write to the string, so                     * casting away const is ugly but allowable. */                    ival = ctrlparse((char *)sval, &next);                    if (!next)                        ival = sval[0];                } else {                    ival = 255; /* special value meaning "don't set" */                }                break;              case TYPE_BOOL:                if (stricmp(sval, "yes") == 0 ||                    stricmp(sval, "on") == 0 ||                    stricmp(sval, "true") == 0 ||                    stricmp(sval, "+") == 0)                    ival = 1;      /* true */                else if (stricmp(sval, "no") == 0 ||                         stricmp(sval, "off") == 0 ||                         stricmp(sval, "false") == 0 ||                         stricmp(sval, "-") == 0)                    ival = 0;      /* false */                else                    ival = (atoi(sval) != 0);                break;              default:                unreachable("Bad mode->type");            }            modes.have_mode[mode->opcode] = true;            modes.mode_val[mode->opcode] = ival;        }        sfree(to_free);    }    {        unsigned ospeed, ispeed;        /* Unpick the terminal-speed config string. */        ospeed = ispeed = 38400;           /* last-resort defaults */        sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed);        /* Currently we unconditionally set these */        modes.have_mode[TTYMODE_ISPEED] = true;        modes.mode_val[TTYMODE_ISPEED] = ispeed;        modes.have_mode[TTYMODE_OSPEED] = true;        modes.mode_val[TTYMODE_OSPEED] = ospeed;    }    return modes;}struct ssh_ttymodes read_ttymodes_from_packet(    BinarySource *bs, int ssh_version){    struct ssh_ttymodes modes;    memset(&modes, 0, sizeof(modes));    while (1) {        unsigned real_opcode, our_opcode;        real_opcode = get_byte(bs);        if (real_opcode == TTYMODE_END_OF_LIST)            break;        if (real_opcode >= 160) {            /*             * RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255             * are not yet defined, and cause parsing to stop (they             * should only be used after any other data)."             *             * My interpretation of this is that if one of these             * opcodes appears, it's not a parse _error_, but it is             * something that we don't know how to parse even well             * enough to step over it to find the next opcode, so we             * stop parsing now and assume that the rest of the string             * is composed entirely of things we don't understand and             * (as usual for unsupported terminal modes) silently             * ignore.             */            return modes;        }        our_opcode = our_ttymode_opcode(real_opcode, ssh_version);        assert(our_opcode < TTYMODE_LIMIT);        modes.have_mode[our_opcode] = true;        if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127)            modes.mode_val[our_opcode] = get_byte(bs);        else            modes.mode_val[our_opcode] = get_uint32(bs);    }    return modes;}void write_ttymodes_to_packet(BinarySink *bs, int ssh_version,                              struct ssh_ttymodes modes){    unsigned i;    for (i = 0; i < TTYMODE_LIMIT; i++) {        if (modes.have_mode[i]) {            unsigned val = modes.mode_val[i];            unsigned opcode = real_ttymode_opcode(i, ssh_version);            put_byte(bs, opcode);            if (ssh_version == 1 && opcode >= 1 && opcode <= 127)                put_byte(bs, val);            else                put_uint32(bs, val);        }    }    put_byte(bs, TTYMODE_END_OF_LIST);}/* ---------------------------------------------------------------------- * Routine for allocating a new channel ID, given a means of finding * the index field in a given channel structure. */unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset){    const unsigned CHANNEL_NUMBER_OFFSET = 256;    search234_state ss;    /*     * First-fit allocation of channel numbers: we always pick the     * lowest unused one.     *     * Every channel before that, and no channel after it, has an ID     * exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So     * we can use the search234 system to identify the length of that     * initial sequence, in a single log-time pass down the channels     * tree.     */    search234_start(&ss, channels);    while (ss.element) {        unsigned localid = *(unsigned *)((char *)ss.element + localid_offset);        if (localid == ss.index + CHANNEL_NUMBER_OFFSET)            search234_step(&ss, +1);        else            search234_step(&ss, -1);    }    /*     * Now ss.index gives exactly the number of channels in that     * initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must     * give precisely the lowest unused channel number.     */    return ss.index + CHANNEL_NUMBER_OFFSET;}/* ---------------------------------------------------------------------- * Functions for handling the comma-separated strings used to store * lists of protocol identifiers in SSH-2. */void add_to_commasep_pl(strbuf *buf, ptrlen data){    if (buf->len > 0)        put_byte(buf, ',');    put_datapl(buf, data);}void add_to_commasep(strbuf *buf, const char *data){    add_to_commasep_pl(buf, ptrlen_from_asciz(data));}bool get_commasep_word(ptrlen *list, ptrlen *word){    const char *comma;    /*     * Discard empty list elements, should there be any, because we     * never want to return one as if it was a real string. (This     * introduces a mild tolerance of badly formatted data in lists we     * receive, but I think that's acceptable.)     */    while (list->len > 0 && *(const char *)list->ptr == ',') {        list->ptr = (const char *)list->ptr + 1;        list->len--;    }    if (!list->len)        return false;    comma = memchr(list->ptr, ',', list->len);    if (!comma) {        *word = *list;        list->len = 0;    } else {        size_t wordlen = comma - (const char *)list->ptr;        word->ptr = list->ptr;        word->len = wordlen;        list->ptr = (const char *)list->ptr + wordlen + 1;        list->len -= wordlen + 1;    }    return true;}/* ---------------------------------------------------------------------- * Functions for translating SSH packet type codes into their symbolic * string names. */#define TRANSLATE_UNIVERSAL(y, name, value)      \    if (type == value) return #name;#define TRANSLATE_KEX(y, name, value, ctx) \    if (type == value && pkt_kctx == ctx) return #name;#define TRANSLATE_AUTH(y, name, value, ctx) \    if (type == value && pkt_actx == ctx) return #name;const char *ssh1_pkt_type(int type){    SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y);    return "unknown";}const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type){    SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y);    return "unknown";}#undef TRANSLATE_UNIVERSAL#undef TRANSLATE_KEX#undef TRANSLATE_AUTH/* ---------------------------------------------------------------------- * Common helper function for clients and implementations of * PacketProtocolLayer. */void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new){    new->bpp = old->bpp;    ssh_ppl_setup_queues(new, old->in_pq, old->out_pq);    new->selfptr = old->selfptr;    new->seat = old->seat;    new->ssh = old->ssh;    *new->selfptr = new;    ssh_ppl_free(old);    /* The new layer might need to be the first one that sends a     * packet, so trigger a call to its main coroutine immediately. If     * it doesn't need to go first, the worst that will do is return     * straight away. */    queue_idempotent_callback(&new->ic_process_queue);}void ssh_ppl_free(PacketProtocolLayer *ppl){    delete_callbacks_for_context(get_seat_callback_set(ppl->seat), ppl); // WINSCP    ppl->vt->free(ppl);}static void ssh_ppl_ic_process_queue_callback(void *context){    PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;    ssh_ppl_process_queue(ppl);}void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,                          PktInQueue *inq, PktOutQueue *outq){    ppl->in_pq = inq;    ppl->out_pq = outq;    ppl->in_pq->pqb.ic = &ppl->ic_process_queue;    ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback;    ppl->ic_process_queue.ctx = ppl;    ppl->ic_process_queue.set = get_seat_callback_set(ppl->seat);    /* If there's already something on the input queue, it will want     * handling immediately. */    if (pq_peek(ppl->in_pq))        queue_idempotent_callback(&ppl->ic_process_queue);}void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text){    /* Messages sent via this function are from the SSH layer, not     * from the server-side process, so they always have the stderr     * flag set. */    SeatOutputType stderrflag = (SeatOutputType)-1; // WINSCP    seat_output(ppl->seat, stderrflag, text, strlen(text)); // WINSCP    sfree(text);}size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl){    return ppl->out_pq->pqb.total_size;}void ssh_ppl_default_final_output(PacketProtocolLayer *ppl){}static void ssh_ppl_prompts_callback(void *ctx){    ssh_ppl_process_queue((PacketProtocolLayer *)ctx);}prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl){    prompts_t *p = new_prompts();    p->callback = ssh_ppl_prompts_callback;    p->callback_ctx = ppl;    return p;}/* ---------------------------------------------------------------------- * Common helper functions for clients and implementations of * BinaryPacketProtocol. */static void ssh_bpp_input_raw_data_callback(void *context){    BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;    Ssh *ssh = bpp->ssh;               /* in case bpp is about to get freed */    ssh_bpp_handle_input(bpp);    /* If we've now cleared enough backlog on the input connection, we     * may need to unfreeze it. */    ssh_conn_processed_data(ssh);}static void ssh_bpp_output_packet_callback(void *context){    BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;    ssh_bpp_handle_output(bpp);}void ssh_bpp_common_setup(BinaryPacketProtocol *bpp){    pq_in_init(&bpp->in_pq, get_log_seat(bpp->logctx)); // WINSCP    pq_out_init(&bpp->out_pq, get_log_seat(bpp->logctx)); // WINSCP    bpp->input_eof = false;    bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback;    bpp->ic_in_raw.set = get_log_callback_set(bpp->logctx);    bpp->ic_in_raw.ctx = bpp;    bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback;    bpp->ic_out_pq.set = get_log_callback_set(bpp->logctx);    bpp->ic_out_pq.ctx = bpp;    bpp->out_pq.pqb.ic = &bpp->ic_out_pq;}void ssh_bpp_free(BinaryPacketProtocol *bpp){    // WINSCP    delete_callbacks_for_context(get_log_callback_set(bpp->logctx), bpp);    bpp->vt->free(bpp);}void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp,                               const char *msg, int category){    PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT);    put_uint32(pkt, category);    put_stringz(pkt, msg);    put_stringz(pkt, "en");            /* language tag */    pq_push(&bpp->out_pq, pkt);}#define BITMAP_UNIVERSAL(y, name, value)                        \    | (value >= y && value < y+32                               \       ? 1UL << (value >= y && value < y+32 ? (value-y) : 0)    \       : 0)#define BITMAP_CONDITIONAL(y, name, value, ctx) \    BITMAP_UNIVERSAL(y, name, value)#define SSH2_BITMAP_WORD(y) \    (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \                          BITMAP_CONDITIONAL, (32*y)))bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin){    #pragma warn -osh    static const unsigned valid_bitmap[] = {        SSH2_BITMAP_WORD(0),        SSH2_BITMAP_WORD(1),        SSH2_BITMAP_WORD(2),        SSH2_BITMAP_WORD(3),        SSH2_BITMAP_WORD(4),        SSH2_BITMAP_WORD(5),        SSH2_BITMAP_WORD(6),        SSH2_BITMAP_WORD(7),    };    #pragma warn +osh    if (pktin->type < 0x100 &&        !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) {        PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED);        put_uint32(pkt, pktin->sequence);        pq_push(&bpp->out_pq, pkt);        return true;    }    return false;}#undef BITMAP_UNIVERSAL#undef BITMAP_CONDITIONAL#undef SSH2_BITMAP_WORD/* ---------------------------------------------------------------------- * Centralised component of SSH host key verification. * * verify_ssh_host_key is called from both the SSH-1 and SSH-2 * transport layers, and does the initial work of checking whether the * host key is already known. If so, it returns success on its own * account; otherwise, it calls out to the Seat to give an interactive * prompt (the nature of which varies depending on the Seat itself). */SeatPromptResult verify_ssh_host_key(    InteractionReadySeat iseat, Conf *conf, const char *host, int port,    ssh_key *key, const char *keytype, char *keystr, const char *keydisp,    char **fingerprints, int ca_count,    void (*callback)(void *ctx, SeatPromptResult result), void *ctx){    /*     * First, check if the Conf includes a manual specification of the     * expected host key. If so, that completely supersedes everything     * else, including the normal host key cache _and_ including     * manual overrides: we return success or failure immediately,     * entirely based on whether the key matches the Conf.     */    if (conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) {        if (fingerprints) {            size_t i; // WINSCP            for (i = 0; i < SSH_N_FPTYPES; i++) {                /*                 * Each fingerprint string we've been given will have                 * things like 'ssh-rsa 2048' at the front of it. Strip                 * those off and narrow down to just the hash at the end                 * of the string.                 */                const char *fingerprint = fingerprints[i];                if (!fingerprint)                    continue;                                    { // WINSCP                const char *p = strrchr(fingerprint, ' ');                fingerprint = p ? p+1 : fingerprint;                if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,                                         fingerprint))                    return SPR_OK;                } // WINSCP            }        }        if (key) {            /*             * Construct the base64-encoded public key blob and see if             * that's listed.             */            strbuf *binblob;            char *base64blob;            int atoms, i;            binblob = strbuf_new();            ssh_key_public_blob(key, BinarySink_UPCAST(binblob));            atoms = (binblob->len + 2) / 3;            base64blob = snewn(atoms * 4 + 1, char);            for (i = 0; i < atoms; i++)                base64_encode_atom(binblob->u + 3*i,                                   binblob->len - 3*i, base64blob + 4*i);            base64blob[atoms * 4] = '\0';            strbuf_free(binblob);            if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,                                     base64blob)) {                sfree(base64blob);                return SPR_OK;            }            sfree(base64blob);        }        return SPR_SW_ABORT("Host key not in manually configured list");    }    /*     * Next, check the host key cache.     */    { // WINSCP    int storage_status = 1; // WINSCP check_stored_host_key(host, port, keytype, keystr);    if (storage_status == 0) /* matching key was found in the cache */        return SPR_OK;    /*     * The key is either missing from the cache, or does not match.     * Either way, fall back to an interactive prompt from the Seat.     */    { // WINSCP    SeatDialogText *text = seat_dialog_text_new();    const SeatDialogPromptDescriptions *pds =        seat_prompt_descriptions(iseat.seat);    FingerprintType fptype_default =        ssh2_pick_default_fingerprint(fingerprints);    seat_dialog_text_append(        text, SDT_TITLE, "%s Security Alert", appname);    { // WINSCP    HelpCtx helpctx;    if (key && ssh_key_alg(key)->is_certificate) {        seat_dialog_text_append(            text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!");        seat_dialog_text_append(            text, SDT_PARA, "This server presented a certified host key:");        seat_dialog_text_append(            text, SDT_DISPLAY, "%s (port %d)", host, port);        if (ca_count) {            seat_dialog_text_append(                text, SDT_PARA, "which was signed by a different "                "certification authority from the %s %s is configured to "                "trust for this server.", ca_count > 1 ? "ones" : "one",                appname);            if (storage_status == 2) {                seat_dialog_text_append(                    text, SDT_PARA, "ALSO, that key does not match the key "                    "%s had previously cached for this server.", appname);                seat_dialog_text_append(                    text, SDT_PARA, "This means that either another "                    "certification authority is operating in this realm AND "                    "the server administrator has changed the host key, or "                    "you have actually connected to another computer "                    "pretending to be the server.");            } else {                seat_dialog_text_append(                    text, SDT_PARA, "This means that either another "                    "certification authority is operating in this realm, or "                    "you have actually connected to another computer "                    "pretending to be the server.");            }        } else {            // assert(storage_status == 2); WINSCP            seat_dialog_text_append(                text, SDT_PARA, "which does not match the certified key %s "                "had previously cached for this server.", appname);            seat_dialog_text_append(                text, SDT_PARA, "This means that either the server "                "administrator has changed the host key, or you have actually "                "connected to another computer pretending to be the server.");        }        seat_dialog_text_append(            text, SDT_PARA, "The new %s key fingerprint is:", keytype);        seat_dialog_text_append(            text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);        helpctx = HELPCTX(errors_cert_mismatch);    } else if (storage_status == 1) {        seat_dialog_text_append(            text, SDT_PARA, "The host key is not cached for this server:");        seat_dialog_text_append(            text, SDT_DISPLAY, "%s (port %d)", host, port);        seat_dialog_text_append(            text, SDT_PARA, "You have no guarantee that the server is the "            "computer you think it is.");        seat_dialog_text_append(            text, SDT_PARA, "The server's %s key fingerprint is:", keytype);        seat_dialog_text_append(            text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);        helpctx = HELPCTX(errors_hostkey_absent);    } else {        seat_dialog_text_append(            text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!");        seat_dialog_text_append(            text, SDT_PARA, "The host key does not match the one %s has "            "cached for this server:", appname);        seat_dialog_text_append(            text, SDT_DISPLAY, "%s (port %d)", host, port);        seat_dialog_text_append(            text, SDT_PARA, "This means that either the server administrator "            "has changed the host key, or you have actually connected to "            "another computer pretending to be the server.");        seat_dialog_text_append(            text, SDT_PARA, "The new %s key fingerprint is:", keytype);        seat_dialog_text_append(            text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);        helpctx = HELPCTX(errors_hostkey_changed);    }    /* The above text is printed even in batch mode. Here's where we stop if     * we can't present interactive prompts. */    seat_dialog_text_append(        text, SDT_BATCH_ABORT, "Connection abandoned.");    if (storage_status == 1) {        seat_dialog_text_append(            text, SDT_PARA, "If you trust this host, %s to add the key to "            "%s's cache and carry on connecting.",            pds->hk_accept_action, appname);        if (key && ssh_key_alg(key)->is_certificate) {            seat_dialog_text_append(                text, SDT_PARA, "(Storing this certified key in the cache "                "will NOT cause its certification authority to be trusted "                "for any other key or host.)");        }        seat_dialog_text_append(            text, SDT_PARA, "If you want to carry on connecting just once, "            "without adding the key to the cache, %s.",            pds->hk_connect_once_action);        seat_dialog_text_append(            text, SDT_PARA, "If you do not trust this host, %s to abandon the "            "connection.", pds->hk_cancel_action);        seat_dialog_text_append(            text, SDT_PROMPT, "Store key in cache?");    } else {        seat_dialog_text_append(            text, SDT_PARA, "If you were expecting this change and trust the "            "new key, %s to update %s's cache and carry on connecting.",            pds->hk_accept_action, appname);        if (key && ssh_key_alg(key)->is_certificate) {            seat_dialog_text_append(                text, SDT_PARA, "(Storing this certified key in the cache "                "will NOT cause its certification authority to be trusted "                "for any other key or host.)");        }        seat_dialog_text_append(            text, SDT_PARA, "If you want to carry on connecting but without "            "updating the cache, %s.", pds->hk_connect_once_action);        seat_dialog_text_append(            text, SDT_PARA, "If you want to abandon the connection "            "completely, %s to cancel. %s is the ONLY guaranteed safe choice.",            pds->hk_cancel_action, pds->hk_cancel_action_Participle);        seat_dialog_text_append(            text, SDT_PROMPT, "Update cached key?");    }    seat_dialog_text_append(text, SDT_MORE_INFO_KEY,                            "Full text of host's public key");    seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_BLOB, "%s", keydisp);    if (fingerprints[SSH_FPTYPE_SHA256]) {        seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "SHA256 fingerprint");        seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s",                                fingerprints[SSH_FPTYPE_SHA256]);    }    if (fingerprints[SSH_FPTYPE_MD5]) {        seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "MD5 fingerprint");        seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s",                                fingerprints[SSH_FPTYPE_MD5]);    }    { // WINSCP    SeatPromptResult toret = seat_confirm_ssh_host_key(        iseat, host, port, keytype, keystr, text, helpctx, callback, ctx,        fingerprints, key && ssh_key_alg(key)->is_certificate, ca_count, false); // WINSCP    seat_dialog_text_free(text);    return toret;    } // WINSCP    } // WINSCP    } // WINSCP    } // WINSCP}SeatPromptResult confirm_weak_crypto_primitive(    InteractionReadySeat iseat, const char *algtype, const char *algname,    void (*callback)(void *ctx, SeatPromptResult result), void *ctx,    WeakCryptoReason wcr){    SeatDialogText *text = seat_dialog_text_new();    const SeatDialogPromptDescriptions *pds =        seat_prompt_descriptions(iseat.seat);    seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname);    switch (wcr) {      case WCR_BELOW_THRESHOLD:        seat_dialog_text_append(            text, SDT_PARA,            "The first %s supported by the server is %s, "            "which is below the configured warning threshold.",            algtype, algname);        break;      case WCR_TERRAPIN:      case WCR_TERRAPIN_AVOIDABLE:        seat_dialog_text_append(            text, SDT_PARA,            "The %s selected for this session is %s, "            "which, with this server, is vulnerable to the 'Terrapin' attack "            "CVE-2023-48795, potentially allowing an attacker to modify "            "the encrypted session.",            algtype, algname);        seat_dialog_text_append(            text, SDT_PARA,            "Upgrading, patching, or reconfiguring this SSH server is the "            "best way to avoid this vulnerability, if possible.");        if (wcr == WCR_TERRAPIN_AVOIDABLE) {            seat_dialog_text_append(                text, SDT_PARA,                "You can also avoid this vulnerability by abandoning "                "this connection, moving ChaCha20 to below the "                "'warn below here' line in PuTTY's SSH cipher "                "configuration (so that an algorithm without the "                "vulnerability will be selected), and starting a new "                "connection.");        }        break;      default:        unreachable("bad WeakCryptoReason");    }    /* In batch mode, we print the above information and then this     * abort message, and stop. */    seat_dialog_text_append(text, SDT_BATCH_ABORT, "Connection abandoned.");    seat_dialog_text_append(        text, SDT_PARA, "To accept the risk and continue, %s. "        "To abandon the connection, %s.",        pds->weak_accept_action, pds->weak_cancel_action);    seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?");    { // WINSCP    SeatPromptResult toret = seat_confirm_weak_crypto_primitive(        iseat, text, callback, ctx,        algtype, algname, wcr); // WINSCP    seat_dialog_text_free(text);    return toret;    } // WINSCP}SeatPromptResult confirm_weak_cached_hostkey(    InteractionReadySeat iseat, const char *algname, const char **betteralgs,    void (*callback)(void *ctx, SeatPromptResult result), void *ctx){    SeatDialogText *text = seat_dialog_text_new();    const SeatDialogPromptDescriptions *pds =        seat_prompt_descriptions(iseat.seat);    seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname);    seat_dialog_text_append(        text, SDT_PARA,        "The first host key type we have stored for this server "        "is %s, which is below the configured warning threshold.", algname);    seat_dialog_text_append(        text, SDT_PARA,        "The server also provides the following types of host key "        "above the threshold, which we do not have stored:");    { // WINSCP    const char **p; // WINSCP    for (p = betteralgs; *p; p++)        seat_dialog_text_append(text, SDT_DISPLAY, "%s", *p);    } // WINSCP    /* In batch mode, we print the above information and then this     * abort message, and stop. */    seat_dialog_text_append(text, SDT_BATCH_ABORT, "Connection abandoned.");    seat_dialog_text_append(        text, SDT_PARA, "To accept the risk and continue, %s. "        "To abandon the connection, %s.",        pds->weak_accept_action, pds->weak_cancel_action);    seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?");    { // WINSCP    SeatPromptResult toret = seat_confirm_weak_cached_hostkey(        iseat, text, callback, ctx);    seat_dialog_text_free(text);    return toret;    } // WINSCP}/* ---------------------------------------------------------------------- * Common functions shared between SSH-1 layers. */bool ssh1_common_get_specials(    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx){    /*     * Don't bother offering IGNORE if we've decided the remote     * won't cope with it, since we wouldn't bother sending it if     * asked anyway.     */    if (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {        add_special(ctx, "IGNORE message", SS_NOP, 0);        return true;    }    return false;}bool ssh1_common_filter_queue(PacketProtocolLayer *ppl){    PktIn *pktin;    ptrlen msg;    while ((pktin = pq_peek(ppl->in_pq)) != NULL) {        switch (pktin->type) {          case SSH1_MSG_DISCONNECT:            msg = get_string(pktin);            ssh_remote_error(ppl->ssh,                             "Remote side sent disconnect message:\n\"%.*s\"",                             PTRLEN_PRINTF(msg));            /* don't try to pop the queue, because we've been freed! */            return true;               /* indicate that we've been freed */          case SSH1_MSG_DEBUG:            msg = get_string(pktin);            ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg));            pq_pop(ppl->in_pq);            break;          case SSH1_MSG_IGNORE:            /* Do nothing, because we're ignoring it! Duhh. */            pq_pop(ppl->in_pq);            break;          default:            return false;        }    }    return false;}void ssh1_compute_session_id(    unsigned char *session_id, const unsigned char *cookie,    RSAKey *hostkey, RSAKey *servkey){    ssh_hash *hash = ssh_hash_new(&ssh_md5);    size_t i; // WINSCP    for (i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;)        put_byte(hash, mp_get_byte(hostkey->modulus, i));    for (i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;)        put_byte(hash, mp_get_byte(servkey->modulus, i));    put_data(hash, cookie, 8);    ssh_hash_final(hash, session_id);}/* ---------------------------------------------------------------------- * Wrapper function to handle the abort-connection modes of a * SeatPromptResult without a lot of verbiage at every call site. * * Can become ssh_sw_abort or ssh_user_close, depending on the kind of * negative SeatPromptResult. */void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context){    if (spr.kind == SPRK_USER_ABORT) {        ssh_user_close(ssh, "User aborted at %s", context);    } else {        assert(spr.kind == SPRK_SW_ABORT);        { // WINSCP        char *err = spr_get_error_message(spr);        ssh_sw_abort(ssh, "%s", err);        sfree(err);        } // WINSCP    }}
 |