/*
 * Packet protocol layer for the client side of the SSH-2 userauth
 * protocol (RFC 4252).
 */

#include <assert.h>

#include "putty.h"
#include "ssh.h"
#include "bpp.h"
#include "ppl.h"
#include "sshcr.h"

#ifndef NO_GSSAPI
#include "gssc.h"
#include "gss.h"
#endif

#define BANNER_LIMIT 131072

typedef struct agent_key {
    strbuf *blob, *comment;
    ptrlen algorithm;
} agent_key;

struct ssh2_userauth_state {
    int crState;

    PacketProtocolLayer *transport_layer, *successor_layer;
    Filename *keyfile;
    bool show_banner, tryagent, notrivialauth, change_username;
    char *hostname, *fullhostname;
    char *default_username;
    bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd;

    ptrlen session_id;
    enum {
        AUTH_TYPE_NONE,
        AUTH_TYPE_PUBLICKEY,
        AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
        AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
        AUTH_TYPE_PASSWORD,
        AUTH_TYPE_GSSAPI,      /* always QUIET */
        AUTH_TYPE_KEYBOARD_INTERACTIVE,
        AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
    } type;
    bool need_pw, can_pubkey, can_passwd, can_keyb_inter;
    int userpass_ret;
    bool tried_pubkey_config, done_agent;
    struct ssh_connection_shared_gss_state *shgss;
#ifndef NO_GSSAPI
    bool can_gssapi;
    bool can_gssapi_keyex_auth;
    bool tried_gssapi;
    bool tried_gssapi_keyex_auth;
    time_t gss_cred_expiry;
    Ssh_gss_buf gss_buf;
    Ssh_gss_buf gss_rcvtok, gss_sndtok;
    Ssh_gss_stat gss_stat;
#endif
    bool suppress_wait_for_response_packet;
    strbuf *last_methods_string;
    bool kbd_inter_refused;
    prompts_t *cur_prompt;
    uint32_t num_prompts;
    const char *username;
    char *locally_allocated_username;
    char *password;
    bool got_username;
    strbuf *publickey_blob;
    bool privatekey_available, privatekey_encrypted;
    char *publickey_algorithm;
    char *publickey_comment;
    void *agent_response_to_free;
    ptrlen agent_response;
    BinarySource asrc[1];          /* for reading SSH agent response */
    size_t agent_keys_len;
    agent_key *agent_keys;
    size_t agent_key_index, agent_key_limit;
    ptrlen agent_keyalg;
    unsigned signflags;
    int len;
    PktOut *pktout;
    bool is_trivial_auth;

    agent_pending_query *auth_agent_query;
    bufchain banner;
    bufchain_sink banner_bs;
    StripCtrlChars *banner_scc;
    bool banner_scc_initialised;

    StripCtrlChars *ki_scc;
    bool ki_scc_initialised;
    bool ki_printed_header;

    PacketProtocolLayer ppl;
};

static void ssh2_userauth_free(PacketProtocolLayer *);
static void ssh2_userauth_process_queue(PacketProtocolLayer *);
static bool ssh2_userauth_get_specials(
    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl,
                                      SessionSpecialCode code, int arg);
static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf);

static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *);
static void ssh2_userauth_agent_callback(void *, void *, int);
static void ssh2_userauth_add_sigblob(
    struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob);
static void ssh2_userauth_add_session_id(
    struct ssh2_userauth_state *s, strbuf *sigdata);
#ifndef NO_GSSAPI
static PktOut *ssh2_userauth_gss_packet(
    struct ssh2_userauth_state *s, const char *authtype);
#endif

static const PacketProtocolLayerVtable ssh2_userauth_vtable = {
    .free = ssh2_userauth_free,
    .process_queue = ssh2_userauth_process_queue,
    .get_specials = ssh2_userauth_get_specials,
    .special_cmd = ssh2_userauth_special_cmd,
    .reconfigure = ssh2_userauth_reconfigure,
    .queued_data_size = ssh_ppl_default_queued_data_size,
    .name = "ssh-userauth",
};

PacketProtocolLayer *ssh2_userauth_new(
    PacketProtocolLayer *successor_layer,
    const char *hostname, const char *fullhostname,
    Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth,
    const char *default_username, bool change_username,
    bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth,
    bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss)
{
    struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state);
    memset(s, 0, sizeof(*s));
    s->ppl.vt = &ssh2_userauth_vtable;

    s->successor_layer = successor_layer;
    s->hostname = dupstr(hostname);
    s->fullhostname = dupstr(fullhostname);
    s->keyfile = filename_copy(keyfile);
    s->show_banner = show_banner;
    s->tryagent = tryagent;
    s->notrivialauth = notrivialauth;
    s->default_username = dupstr(default_username);
    s->change_username = change_username;
    s->try_ki_auth = try_ki_auth;
    s->try_gssapi_auth = try_gssapi_auth;
    s->try_gssapi_kex_auth = try_gssapi_kex_auth;
    s->gssapi_fwd = gssapi_fwd;
    s->shgss = shgss;
    s->last_methods_string = strbuf_new();
    s->is_trivial_auth = true;
    bufchain_init(&s->banner);
    bufchain_sink_init(&s->banner_bs, &s->banner);

    return &s->ppl;
}

void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth,
                                       PacketProtocolLayer *transport)
{
    struct ssh2_userauth_state *s =
        container_of(userauth, struct ssh2_userauth_state, ppl);
    s->transport_layer = transport;
}

static void ssh2_userauth_free(PacketProtocolLayer *ppl)
{
    struct ssh2_userauth_state *s =
        container_of(ppl, struct ssh2_userauth_state, ppl);
    bufchain_clear(&s->banner);

    if (s->successor_layer)
        ssh_ppl_free(s->successor_layer);

    if (s->agent_keys) {
        for (size_t i = 0; i < s->agent_keys_len; i++) {
            strbuf_free(s->agent_keys[i].blob);
            strbuf_free(s->agent_keys[i].comment);
        }
        sfree(s->agent_keys);
    }
    sfree(s->agent_response_to_free);
    if (s->auth_agent_query)
        agent_cancel_query(s->auth_agent_query);
    filename_free(s->keyfile);
    sfree(s->default_username);
    sfree(s->locally_allocated_username);
    sfree(s->hostname);
    sfree(s->fullhostname);
    if (s->cur_prompt)
        free_prompts(s->cur_prompt);
    sfree(s->publickey_comment);
    sfree(s->publickey_algorithm);
    if (s->publickey_blob)
        strbuf_free(s->publickey_blob);
    strbuf_free(s->last_methods_string);
    if (s->banner_scc)
        stripctrl_free(s->banner_scc);
    if (s->ki_scc)
        stripctrl_free(s->ki_scc);
    sfree(s);
}

static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s)
{
    PktIn *pktin;
    ptrlen string;

    while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) {
        switch (pktin->type) {
          case SSH2_MSG_USERAUTH_BANNER:
            if (!s->show_banner) {
                pq_pop(s->ppl.in_pq);
                break;
            }

            string = get_string(pktin);
            if (string.len > BANNER_LIMIT - bufchain_size(&s->banner))
                string.len = BANNER_LIMIT - bufchain_size(&s->banner);
            if (!s->banner_scc_initialised) {
                s->banner_scc = seat_stripctrl_new(
                    s->ppl.seat, BinarySink_UPCAST(&s->banner_bs), SIC_BANNER);
                if (s->banner_scc)
                    stripctrl_enable_line_limiting(s->banner_scc);
                s->banner_scc_initialised = true;
            }
            if (s->banner_scc)
                put_datapl(s->banner_scc, string);
            else
                put_datapl(&s->banner_bs, string);
            pq_pop(s->ppl.in_pq);
            break;

          default:
            return;
        }
    }
}

static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s)
{
    ssh2_userauth_filter_queue(s);
    return pq_pop(s->ppl.in_pq);
}

static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
{
    struct ssh2_userauth_state *s =
        container_of(ppl, struct ssh2_userauth_state, ppl);
    PktIn *pktin;

    ssh2_userauth_filter_queue(s);     /* no matter why we were called */

    crBegin(s->crState);

#ifndef NO_GSSAPI
    s->tried_gssapi = false;
    s->tried_gssapi_keyex_auth = false;
#endif

    /*
     * Misc one-time setup for authentication.
     */
    s->publickey_blob = NULL;
    s->session_id = ssh2_transport_get_session_id(s->transport_layer);

    /*
     * Load the public half of any configured public key file for
     * later use.
     */
    if (!filename_is_null(s->keyfile)) {
        int keytype;
        ppl_logevent("Reading key file \"%s\"",
                     filename_to_str(s->keyfile));
        keytype = key_type(s->keyfile);
        if (keytype == SSH_KEYTYPE_SSH2 ||
            keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
            keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
            const char *error;
            s->publickey_blob = strbuf_new();
            if (ppk_loadpub_f(s->keyfile, &s->publickey_algorithm,
                              BinarySink_UPCAST(s->publickey_blob),
                              &s->publickey_comment, &error)) {
                s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2);
                if (!s->privatekey_available)
                    ppl_logevent("Key file contains public key only");
                s->privatekey_encrypted = ppk_encrypted_f(s->keyfile, NULL);
            } else {
                ppl_logevent("Unable to load key (%s)", error);
                ppl_printf("Unable to load key file \"%s\" (%s)\r\n",
                           filename_to_str(s->keyfile), error);
                strbuf_free(s->publickey_blob);
                s->publickey_blob = NULL;
            }
        } else {
            ppl_logevent("Unable to use this key file (%s)",
                         key_type_to_str(keytype));
            ppl_printf("Unable to use key file \"%s\" (%s)\r\n",
                       filename_to_str(s->keyfile),
                       key_type_to_str(keytype));
            s->publickey_blob = NULL;
        }
    }

    /*
     * Find out about any keys Pageant has (but if there's a public
     * key configured, filter out all others).
     */
    if (s->tryagent && agent_exists()) {
        ppl_logevent("Pageant is running. Requesting keys.");

        /* Request the keys held by the agent. */
        {
            strbuf *request = strbuf_new_for_agent_query();
            put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES);
            ssh2_userauth_agent_query(s, request);
            strbuf_free(request);
            crWaitUntilV(!s->auth_agent_query);
        }
        BinarySource_BARE_INIT_PL(s->asrc, s->agent_response);

        get_uint32(s->asrc); /* skip length field */
        if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) {
            size_t nkeys = get_uint32(s->asrc);
            size_t origpos = s->asrc->pos;

            /*
             * Check that the agent response is well formed.
             */
            for (size_t i = 0; i < nkeys; i++) {
                get_string(s->asrc);   /* blob */
                get_string(s->asrc);   /* comment */
                if (get_err(s->asrc)) {
                    ppl_logevent("Pageant's response was truncated");
                    goto done_agent_query;
                }
            }

            /*
             * Copy the list of public-key blobs out of the Pageant
             * response.
             */
            BinarySource_REWIND_TO(s->asrc, origpos);
            s->agent_keys_len = nkeys;
            s->agent_keys = snewn(s->agent_keys_len, agent_key);
            for (size_t i = 0; i < nkeys; i++) {
                s->agent_keys[i].blob = strbuf_new();
                put_datapl(s->agent_keys[i].blob, get_string(s->asrc));
                s->agent_keys[i].comment = strbuf_new();
                put_datapl(s->agent_keys[i].comment, get_string(s->asrc));

                /* Also, extract the algorithm string from the start
                 * of the public-key blob. */
                BinarySource src[1];
                BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
                    s->agent_keys[i].blob));
                s->agent_keys[i].algorithm = get_string(src);
            }

            ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys);

            if (s->publickey_blob) {
                /*
                 * If we've been given a specific public key blob,
                 * filter the list of keys to try from the agent down
                 * to only that one, or none if it's not there.
                 */
                ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob);
                size_t i;

                for (i = 0; i < nkeys; i++) {
                    if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf(
                                             s->agent_keys[i].blob)))
                        break;
                }

                if (i < nkeys) {
                    ppl_logevent("Pageant key #%"SIZEu" matches "
                                 "configured key file", i);
                    s->agent_key_index = i;
                    s->agent_key_limit = i+1;
                } else {
                    ppl_logevent("Configured key file not in Pageant");
                    s->agent_key_index = 0;
                    s->agent_key_limit = 0;
                }
            } else {
                /*
                 * Otherwise, try them all.
                 */
                s->agent_key_index = 0;
                s->agent_key_limit = nkeys;
            }
        } else {
            ppl_logevent("Failed to get reply from Pageant");
        }
      done_agent_query:;
    }

    /*
     * We repeat this whole loop, including the username prompt,
     * until we manage a successful authentication. If the user
     * types the wrong _password_, they can be sent back to the
     * beginning to try another username, if this is configured on.
     * (If they specify a username in the config, they are never
     * asked, even if they do give a wrong password.)
     *
     * I think this best serves the needs of
     *
     *  - the people who have no configuration, no keys, and just
     *    want to try repeated (username,password) pairs until they
     *    type both correctly
     *
     *  - people who have keys and configuration but occasionally
     *    need to fall back to passwords
     *
     *  - people with a key held in Pageant, who might not have
     *    logged in to a particular machine before; so they want to
     *    type a username, and then _either_ their key will be
     *    accepted, _or_ they will type a password. If they mistype
     *    the username they will want to be able to get back and
     *    retype it!
     */
    s->got_username = false;
    while (1) {
        /*
         * Get a username.
         */
        if (s->got_username && !s->change_username) {
            /*
             * We got a username last time round this loop, and
             * with change_username turned off we don't try to get
             * it again.
             */
        } else if ((s->username = s->default_username) == NULL) {
            s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
            s->cur_prompt->to_server = true;
            s->cur_prompt->from_server = false;
            s->cur_prompt->name = dupstr("SSH login name");
            add_prompt(s->cur_prompt, dupstr("login as: "), true);
            s->userpass_ret = seat_get_userpass_input(
                s->ppl.seat, s->cur_prompt);
            while (s->userpass_ret < 0) {
                crReturnV;
                s->userpass_ret = seat_get_userpass_input(
                    s->ppl.seat, s->cur_prompt);
            }
            if (!s->userpass_ret) {
                /*
                 * seat_get_userpass_input() failed to get a username.
                 * Terminate.
                 */
                free_prompts(s->cur_prompt);
                s->cur_prompt = NULL;
                ssh_user_close(s->ppl.ssh, "No username provided");
                return;
            }
            sfree(s->locally_allocated_username); /* for change_username */
            s->username = s->locally_allocated_username =
                prompt_get_result(s->cur_prompt->prompts[0]);
            free_prompts(s->cur_prompt);
            s->cur_prompt = NULL;
        } else {
            if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))
                ppl_printf("Using username \"%s\".\r\n", s->username);
        }
        s->got_username = true;

        /*
         * Send an authentication request using method "none": (a)
         * just in case it succeeds, and (b) so that we know what
         * authentication methods we can usefully try next.
         */
        s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;

        s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
        put_stringz(s->pktout, s->username);
        put_stringz(s->pktout, s->successor_layer->vt->name);
        put_stringz(s->pktout, "none");    /* method */
        pq_push(s->ppl.out_pq, s->pktout);
        s->type = AUTH_TYPE_NONE;

        s->tried_pubkey_config = false;
        s->kbd_inter_refused = false;
        s->done_agent = false;

        while (1) {
            /*
             * Wait for the result of the last authentication request,
             * unless the request terminated for some reason on our
             * own side.
             */
            if (s->suppress_wait_for_response_packet) {
                pktin = NULL;
                s->suppress_wait_for_response_packet = false;
            } else {
                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
            }

            /*
             * Now is a convenient point to spew any banner material
             * that we've accumulated. (This should ensure that when
             * we exit the auth loop, we haven't any left to deal
             * with.)
             *
             * Don't show the banner if we're operating in non-verbose
             * non-interactive mode. (It's probably a script, which
             * means nobody will read the banner _anyway_, and
             * moreover the printing of the banner will screw up
             * processing on the output of (say) plink.)
             *
             * The banner data has been sanitised already by this
             * point, but we still need to precede and follow it with
             * anti-spoofing header lines.
             */
            if (bufchain_size(&s->banner) &&
                (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) {
                if (s->banner_scc) {
                    seat_antispoof_msg(
                        s->ppl.seat,
                        "Pre-authentication banner message from server:");
                    seat_set_trust_status(s->ppl.seat, false);
                }

                bool mid_line = false;
                while (bufchain_size(&s->banner) > 0) {
                    ptrlen data = bufchain_prefix(&s->banner);
                    seat_banner_pl(s->ppl.seat, data);
                    mid_line =
                        (((const char *)data.ptr)[data.len-1] != '\n');
                    bufchain_consume(&s->banner, data.len);
                }
                bufchain_clear(&s->banner);

                if (mid_line)
                    seat_banner_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n"));

                if (s->banner_scc) {
                    seat_set_trust_status(s->ppl.seat, true);
                    seat_antispoof_msg(s->ppl.seat,
                                       "End of banner message from server");
                }
            }

            if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
                ppl_logevent("Access granted");
                goto userauth_success;
            }

            if (pktin && pktin->type != SSH2_MSG_USERAUTH_FAILURE &&
                s->type != AUTH_TYPE_GSSAPI) {
                ssh_proto_error(s->ppl.ssh, "Received unexpected packet "
                                "in response to authentication request, "
                                "type %d (%s)", pktin->type,
                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
                                              s->ppl.bpp->pls->actx,
                                              pktin->type));
                return;
            }

            /*
             * OK, we're now sitting on a USERAUTH_FAILURE message, so
             * we can look at the string in it and know what we can
             * helpfully try next.
             */
            if (pktin && pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
                ptrlen methods = get_string(pktin);
                bool partial_success = get_bool(pktin);

                if (!partial_success) {
                    /*
                     * We have received an unequivocal Access
                     * Denied. This can translate to a variety of
                     * messages, or no message at all.
                     *
                     * For forms of authentication which are attempted
                     * implicitly, by which I mean without printing
                     * anything in the window indicating that we're
                     * trying them, we should never print 'Access
                     * denied'.
                     *
                     * If we do print a message saying that we're
                     * attempting some kind of authentication, it's OK
                     * to print a followup message saying it failed -
                     * but the message may sometimes be more specific
                     * than simply 'Access denied'.
                     *
                     * Additionally, if we'd just tried password
                     * authentication, we should break out of this
                     * whole loop so as to go back to the username
                     * prompt (iff we're configured to allow
                     * username change attempts).
                     */
                    if (s->type == AUTH_TYPE_NONE) {
                        /* do nothing */
                    } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
                               s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
                        if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
                            ppl_printf("Server refused our key\r\n");
                        ppl_logevent("Server refused our key");
                    } else if (s->type == AUTH_TYPE_PUBLICKEY) {
                        /* This _shouldn't_ happen except by a
                         * protocol bug causing client and server to
                         * disagree on what is a correct signature. */
                        ppl_printf("Server refused public-key signature"
                                   " despite accepting key!\r\n");
                        ppl_logevent("Server refused public-key signature"
                                     " despite accepting key!");
                    } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
                        /* quiet, so no ppl_printf */
                        ppl_logevent("Server refused keyboard-interactive "
                                     "authentication");
                    } else if (s->type==AUTH_TYPE_GSSAPI) {
                        /* always quiet, so no ppl_printf */
                        /* also, the code down in the GSSAPI block has
                         * already logged this in the Event Log */
                    } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) {
                        ppl_logevent("Keyboard-interactive authentication "
                                     "failed");
                        ppl_printf("Access denied\r\n");
                    } else {
                        assert(s->type == AUTH_TYPE_PASSWORD);
                        ppl_logevent("Password authentication failed");
                        ppl_printf("Access denied\r\n");

                        if (s->change_username) {
                            /* XXX perhaps we should allow
                             * keyboard-interactive to do this too? */
                            goto try_new_username;
                        }
                    }
                } else {
                    ppl_printf("Further authentication required\r\n");
                    ppl_logevent("Further authentication required");
                }

                /*
                 * Save the methods string for use in error messages.
                 */
                strbuf_clear(s->last_methods_string);
                put_datapl(s->last_methods_string, methods);

                /*
                 * Scan it for method identifiers we know about.
                 */
                bool srv_pubkey = false, srv_passwd = false;
                bool srv_keyb_inter = false;
#ifndef NO_GSSAPI
                bool srv_gssapi = false, srv_gssapi_keyex_auth = false;
#endif

                for (ptrlen method; get_commasep_word(&methods, &method) ;) {
                    if (ptrlen_eq_string(method, "publickey"))
                        srv_pubkey = true;
                    else if (ptrlen_eq_string(method, "password"))
                        srv_passwd = true;
                    else if (ptrlen_eq_string(method, "keyboard-interactive"))
                        srv_keyb_inter = true;
#ifndef NO_GSSAPI
                    else if (ptrlen_eq_string(method, "gssapi-with-mic"))
                        srv_gssapi = true;
                    else if (ptrlen_eq_string(method, "gssapi-keyex"))
                        srv_gssapi_keyex_auth = true;
#endif
                }

                /*
                 * And combine those flags with our own configuration
                 * and context to set the main can_foo variables.
                 */
                s->can_pubkey = srv_pubkey;
                s->can_passwd = srv_passwd;
                s->can_keyb_inter = s->try_ki_auth && srv_keyb_inter;
#ifndef NO_GSSAPI
                s->can_gssapi = s->try_gssapi_auth && srv_gssapi &&
                    s->shgss->libs->nlibraries > 0;
                s->can_gssapi_keyex_auth = s->try_gssapi_kex_auth &&
                    srv_gssapi_keyex_auth &&
                    s->shgss->libs->nlibraries > 0 && s->shgss->ctx;
#endif
            }

            s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;

#ifndef NO_GSSAPI
            if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) {

                /* gssapi-keyex authentication */

                s->type = AUTH_TYPE_GSSAPI;
                s->tried_gssapi_keyex_auth = true;
                s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;

                if (s->shgss->lib->gsslogmsg)
                    ppl_logevent("%s", s->shgss->lib->gsslogmsg);

                ppl_logevent("Trying gssapi-keyex...");
                s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex");
                pq_push(s->ppl.out_pq, s->pktout);
                s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
                s->shgss->ctx = NULL;

                continue;
            } else
#endif /* NO_GSSAPI */

            if (s->can_pubkey && !s->done_agent &&
                s->agent_key_index < s->agent_key_limit) {

                /*
                 * Attempt public-key authentication using a key from Pageant.
                 */
                s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm;
                s->signflags = 0;
                if (ptrlen_eq_string(s->agent_keyalg, "ssh-rsa")) {
                    /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family,
                     * if the server has announced support for them. */
                    if (s->ppl.bpp->ext_info_rsa_sha512_ok) {
                        s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-512");
                        s->signflags = SSH_AGENT_RSA_SHA2_512;
                    } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) {
                        s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-256");
                        s->signflags = SSH_AGENT_RSA_SHA2_256;
                    }
                }

                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;

                ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index);

                /* See if server will accept it */
                s->pktout = ssh_bpp_new_pktout(
                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
                put_stringz(s->pktout, s->username);
                put_stringz(s->pktout, s->successor_layer->vt->name);
                put_stringz(s->pktout, "publickey");
                                                    /* method */
                put_bool(s->pktout, false); /* no signature included */
                put_stringpl(s->pktout, s->agent_keyalg);
                put_stringpl(s->pktout, ptrlen_from_strbuf(
                            s->agent_keys[s->agent_key_index].blob));
                pq_push(s->ppl.out_pq, s->pktout);
                s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;

                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
                if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {

                    /* Offer of key refused, presumably via
                     * USERAUTH_FAILURE. Requeue for the next iteration. */
                    pq_push_front(s->ppl.in_pq, pktin);

                } else {
                    strbuf *agentreq, *sigdata;
                    ptrlen comment = ptrlen_from_strbuf(
                        s->agent_keys[s->agent_key_index].comment);

                    if (seat_verbose(s->ppl.seat))
                        ppl_printf("Authenticating with public key "
                                   "\"%.*s\" from agent\r\n",
                                   PTRLEN_PRINTF(comment));

                    /*
                     * Server is willing to accept the key.
                     * Construct a SIGN_REQUEST.
                     */
                    s->pktout = ssh_bpp_new_pktout(
                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
                    put_stringz(s->pktout, s->username);
                    put_stringz(s->pktout, s->successor_layer->vt->name);
                    put_stringz(s->pktout, "publickey");
                                                        /* method */
                    put_bool(s->pktout, true);  /* signature included */
                    put_stringpl(s->pktout, s->agent_keyalg);
                    put_stringpl(s->pktout, ptrlen_from_strbuf(
                            s->agent_keys[s->agent_key_index].blob));

                    /* Ask agent for signature. */
                    agentreq = strbuf_new_for_agent_query();
                    put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST);
                    put_stringpl(agentreq, ptrlen_from_strbuf(
                            s->agent_keys[s->agent_key_index].blob));
                    /* Now the data to be signed... */
                    sigdata = strbuf_new();
                    ssh2_userauth_add_session_id(s, sigdata);
                    put_data(sigdata, s->pktout->data + 5,
                             s->pktout->length - 5);
                    put_stringsb(agentreq, sigdata);
                    /* And finally the flags word. */
                    put_uint32(agentreq, s->signflags);
                    ssh2_userauth_agent_query(s, agentreq);
                    strbuf_free(agentreq);
                    crWaitUntilV(!s->auth_agent_query);

                    if (s->agent_response.ptr) {
                        ptrlen sigblob;
                        BinarySource src[1];
                        BinarySource_BARE_INIT(src, s->agent_response.ptr,
                                               s->agent_response.len);
                        get_uint32(src); /* skip length field */
                        if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE &&
                            (sigblob = get_string(src), !get_err(src))) {
                            ppl_logevent("Sending Pageant's response");
                            ssh2_userauth_add_sigblob(
                                s, s->pktout,
                                ptrlen_from_strbuf(
                                    s->agent_keys[s->agent_key_index].blob),
                                sigblob);
                            pq_push(s->ppl.out_pq, s->pktout);
                            s->type = AUTH_TYPE_PUBLICKEY;
                            s->is_trivial_auth = false;
                        } else {
                            ppl_logevent("Pageant refused signing request");
                            ppl_printf("Pageant failed to "
                                       "provide a signature\r\n");
                            s->suppress_wait_for_response_packet = true;
                            ssh_free_pktout(s->pktout);
                        }
                    } else {
                        ppl_logevent("Pageant failed to respond to "
                                     "signing request");
                        ppl_printf("Pageant failed to "
                                   "respond to signing request\r\n");
                        s->suppress_wait_for_response_packet = true;
                        ssh_free_pktout(s->pktout);
                    }
                }

                /* Do we have any keys left to try? */
                if (++s->agent_key_index >= s->agent_key_limit)
                    s->done_agent = true;

            } else if (s->can_pubkey && s->publickey_blob &&
                       s->privatekey_available && !s->tried_pubkey_config) {

                ssh2_userkey *key;   /* not live over crReturn */
                char *passphrase;           /* not live over crReturn */

                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;

                s->tried_pubkey_config = true;

                /*
                 * Try the public key supplied in the configuration.
                 *
                 * First, try to upgrade its algorithm.
                 */
                if (!strcmp(s->publickey_algorithm, "ssh-rsa")) {
                    /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family,
                     * if the server has announced support for them. */
                    if (s->ppl.bpp->ext_info_rsa_sha512_ok) {
                        sfree(s->publickey_algorithm);
                        s->publickey_algorithm = dupstr("rsa-sha2-512");
                        s->signflags = SSH_AGENT_RSA_SHA2_512;
                    } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) {
                        sfree(s->publickey_algorithm);
                        s->publickey_algorithm = dupstr("rsa-sha2-256");
                        s->signflags = SSH_AGENT_RSA_SHA2_256;
                    }
                }

                /*
                 * Offer the public blob to see if the server is willing to
                 * accept it.
                 */
                s->pktout = ssh_bpp_new_pktout(
                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
                put_stringz(s->pktout, s->username);
                put_stringz(s->pktout, s->successor_layer->vt->name);
                put_stringz(s->pktout, "publickey");    /* method */
                put_bool(s->pktout, false);
                                                /* no signature included */
                put_stringz(s->pktout, s->publickey_algorithm);
                put_string(s->pktout, s->publickey_blob->s,
                           s->publickey_blob->len);
                pq_push(s->ppl.out_pq, s->pktout);
                ppl_logevent("Offered public key");

                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
                if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
                    /* Key refused. Give up. */
                    pq_push_front(s->ppl.in_pq, pktin);
                    s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
                    continue; /* process this new message */
                }
                ppl_logevent("Offer of public key accepted");

                /*
                 * Actually attempt a serious authentication using
                 * the key.
                 */
                if (seat_verbose(s->ppl.seat))
                    ppl_printf("Authenticating with public key \"%s\"\r\n",
                               s->publickey_comment);

                key = NULL;
                while (!key) {
                    const char *error;  /* not live over crReturn */
                    if (s->privatekey_encrypted) {
                        /*
                         * Get a passphrase from the user.
                         */
                        s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
                        s->cur_prompt->to_server = false;
                        s->cur_prompt->from_server = false;
                        s->cur_prompt->name = dupstr("SSH key passphrase");
                        add_prompt(s->cur_prompt,
                                   dupprintf("Passphrase for key \"%s\": ",
                                             s->publickey_comment),
                                   false);
                        s->userpass_ret = seat_get_userpass_input(
                            s->ppl.seat, s->cur_prompt);
                        while (s->userpass_ret < 0) {
                            crReturnV;
                            s->userpass_ret = seat_get_userpass_input(
                                s->ppl.seat, s->cur_prompt);
                        }
                        if (!s->userpass_ret) {
                            /* Failed to get a passphrase. Terminate. */
                            free_prompts(s->cur_prompt);
                            s->cur_prompt = NULL;
                            ssh_bpp_queue_disconnect(
                                s->ppl.bpp, "Unable to authenticate",
                                SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
                            ssh_user_close(s->ppl.ssh, "User aborted at "
                                           "passphrase prompt");
                            return;
                        }
                        passphrase =
                            prompt_get_result(s->cur_prompt->prompts[0]);
                        free_prompts(s->cur_prompt);
                        s->cur_prompt = NULL;
                    } else {
                        passphrase = NULL; /* no passphrase needed */
                    }

                    /*
                     * Try decrypting the key.
                     */
                    key = ppk_load_f(s->keyfile, passphrase, &error);
                    if (passphrase) {
                        /* burn the evidence */
                        smemclr(passphrase, strlen(passphrase));
                        sfree(passphrase);
                    }
                    if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
                        if (passphrase &&
                            (key == SSH2_WRONG_PASSPHRASE)) {
                            ppl_printf("Wrong passphrase\r\n");
                            key = NULL;
                            /* and loop again */
                        } else {
                            ppl_printf("Unable to load private key (%s)\r\n",
                                       error);
                            key = NULL;
                            s->suppress_wait_for_response_packet = true;
                            break; /* try something else */
                        }
                    } else {
                        /* FIXME: if we ever support variable signature
                         * flags, this is somewhere they'll need to be
                         * put */
                        char *invalid = ssh_key_invalid(key->key, 0);
                        if (invalid) {
                            ppl_printf("Cannot use this private key (%s)\r\n",
                                       invalid);
                            ssh_key_free(key->key);
                            sfree(key->comment);
                            sfree(key);
                            sfree(invalid);
                            key = NULL;
                            s->suppress_wait_for_response_packet = true;
                            break; /* try something else */
                        }
                    }
                }

                if (key) {
                    strbuf *pkblob, *sigdata, *sigblob;

                    /*
                     * We have loaded the private key and the server
                     * has announced that it's willing to accept it.
                     * Hallelujah. Generate a signature and send it.
                     */
                    s->pktout = ssh_bpp_new_pktout(
                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
                    put_stringz(s->pktout, s->username);
                    put_stringz(s->pktout, s->successor_layer->vt->name);
                    put_stringz(s->pktout, "publickey"); /* method */
                    put_bool(s->pktout, true); /* signature follows */
                    put_stringz(s->pktout, s->publickey_algorithm);
                    pkblob = strbuf_new();
                    ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob));
                    put_string(s->pktout, pkblob->s, pkblob->len);

                    /*
                     * The data to be signed is:
                     *
                     *   string  session-id
                     *
                     * followed by everything so far placed in the
                     * outgoing packet.
                     */
                    sigdata = strbuf_new();
                    ssh2_userauth_add_session_id(s, sigdata);
                    put_data(sigdata, s->pktout->data + 5,
                             s->pktout->length - 5);
                    sigblob = strbuf_new();
                    ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata),
                                 s->signflags, BinarySink_UPCAST(sigblob));
                    strbuf_free(sigdata);
                    ssh2_userauth_add_sigblob(
                        s, s->pktout, ptrlen_from_strbuf(pkblob),
                        ptrlen_from_strbuf(sigblob));
                    strbuf_free(pkblob);
                    strbuf_free(sigblob);

                    pq_push(s->ppl.out_pq, s->pktout);
                    ppl_logevent("Sent public key signature");
                    s->type = AUTH_TYPE_PUBLICKEY;
                    ssh_key_free(key->key);
                    sfree(key->comment);
                    sfree(key);
                    s->is_trivial_auth = false;
                }

#ifndef NO_GSSAPI
            } else if (s->can_gssapi && !s->tried_gssapi) {

                /* gssapi-with-mic authentication */

                ptrlen data;

                s->type = AUTH_TYPE_GSSAPI;
                s->tried_gssapi = true;
                s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;

                if (s->shgss->lib->gsslogmsg)
                    ppl_logevent("%s", s->shgss->lib->gsslogmsg);

                /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
                ppl_logevent("Trying gssapi-with-mic...");
                s->pktout = ssh_bpp_new_pktout(
                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
                put_stringz(s->pktout, s->username);
                put_stringz(s->pktout, s->successor_layer->vt->name);
                put_stringz(s->pktout, "gssapi-with-mic");
                ppl_logevent("Attempting GSSAPI authentication");

                /* add mechanism info */
                s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf);

                /* number of GSSAPI mechanisms */
                put_uint32(s->pktout, 1);

                /* length of OID + 2 */
                put_uint32(s->pktout, s->gss_buf.length + 2);
                put_byte(s->pktout, SSH2_GSS_OIDTYPE);

                /* length of OID */
                put_byte(s->pktout, s->gss_buf.length);

                put_data(s->pktout, s->gss_buf.value, s->gss_buf.length);
                pq_push(s->ppl.out_pq, s->pktout);
                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
                if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) {
                    ppl_logevent("GSSAPI authentication request refused");
                    pq_push_front(s->ppl.in_pq, pktin);
                    continue;
                }

                /* check returned packet ... */

                data = get_string(pktin);
                s->gss_rcvtok.value = (char *)data.ptr;
                s->gss_rcvtok.length = data.len;
                if (s->gss_rcvtok.length != s->gss_buf.length + 2 ||
                    ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE ||
                    ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length ||
                    memcmp((char *)s->gss_rcvtok.value + 2,
                           s->gss_buf.value,s->gss_buf.length) ) {
                    ppl_logevent("GSSAPI authentication - wrong response "
                                 "from server");
                    continue;
                }

                /* Import server name if not cached from KEX */
                if (s->shgss->srv_name == GSS_C_NO_NAME) {
                    s->gss_stat = s->shgss->lib->import_name(
                        s->shgss->lib, s->fullhostname, &s->shgss->srv_name);
                    if (s->gss_stat != SSH_GSS_OK) {
                        if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
                            ppl_logevent("GSSAPI import name failed -"
                                         " Bad service name");
                        else
                            ppl_logevent("GSSAPI import name failed");
                        continue;
                    }
                }

                /* Allocate our gss_ctx */
                s->gss_stat = s->shgss->lib->acquire_cred(
                    s->shgss->lib, &s->shgss->ctx, NULL);
                if (s->gss_stat != SSH_GSS_OK) {
                    ppl_logevent("GSSAPI authentication failed to get "
                                 "credentials");
                    /* The failure was on our side, so the server
                     * won't be sending a response packet indicating
                     * failure. Avoid waiting for it next time round
                     * the loop. */
                    s->suppress_wait_for_response_packet = true;
                    continue;
                }

                /* initial tokens are empty */
                SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
                SSH_GSS_CLEAR_BUF(&s->gss_sndtok);

                /* now enter the loop */
                do {
                    /*
                     * When acquire_cred yields no useful expiration, go with
                     * the service ticket expiration.
                     */
                    s->gss_stat = s->shgss->lib->init_sec_context
                        (s->shgss->lib,
                         &s->shgss->ctx,
                         s->shgss->srv_name,
                         s->gssapi_fwd,
                         &s->gss_rcvtok,
                         &s->gss_sndtok,
                         NULL,
                         NULL);

                    if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
                        s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
                        ppl_logevent("GSSAPI authentication initialisation "
                                     "failed");

                        if (s->shgss->lib->display_status(s->shgss->lib,
                                s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) {
                            ppl_logevent("%s", (char *)s->gss_buf.value);
                            sfree(s->gss_buf.value);
                        }

                        pq_push_front(s->ppl.in_pq, pktin);
                        break;
                    }
                    ppl_logevent("GSSAPI authentication initialised");

                    /*
                     * Client and server now exchange tokens until GSSAPI
                     * no longer says CONTINUE_NEEDED
                     */
                    if (s->gss_sndtok.length != 0) {
                        s->is_trivial_auth = false;
                        s->pktout =
                            ssh_bpp_new_pktout(
                                s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
                        put_string(s->pktout,
                                   s->gss_sndtok.value, s->gss_sndtok.length);
                        pq_push(s->ppl.out_pq, s->pktout);
                        s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
                    }

                    if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
                        crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);

                        if (pktin->type == SSH2_MSG_USERAUTH_GSSAPI_ERRTOK) {
                            /*
                             * Per RFC 4462 section 3.9, this packet
                             * type MUST immediately precede an
                             * ordinary USERAUTH_FAILURE.
                             *
                             * We currently don't know how to do
                             * anything with the GSSAPI error token
                             * contained in this packet, so we ignore
                             * it and just wait for the following
                             * FAILURE.
                             */
                            crMaybeWaitUntilV(
                                (pktin = ssh2_userauth_pop(s)) != NULL);
                            if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) {
                                ssh_proto_error(
                                    s->ppl.ssh, "Received unexpected packet "
                                    "after SSH_MSG_USERAUTH_GSSAPI_ERRTOK "
                                    "(expected SSH_MSG_USERAUTH_FAILURE): "
                                    "type %d (%s)", pktin->type,
                                    ssh2_pkt_type(s->ppl.bpp->pls->kctx,
                                                  s->ppl.bpp->pls->actx,
                                                  pktin->type));
                                return;
                            }
                        }

                        if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
                            ppl_logevent("GSSAPI authentication failed");
                            s->gss_stat = SSH_GSS_FAILURE;
                            pq_push_front(s->ppl.in_pq, pktin);
                            break;
                        } else if (pktin->type !=
                                   SSH2_MSG_USERAUTH_GSSAPI_TOKEN) {
                            ppl_logevent("GSSAPI authentication -"
                                         " bad server response");
                            s->gss_stat = SSH_GSS_FAILURE;
                            break;
                        }
                        data = get_string(pktin);
                        s->gss_rcvtok.value = (char *)data.ptr;
                        s->gss_rcvtok.length = data.len;
                    }
                } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);

                if (s->gss_stat != SSH_GSS_OK) {
                    s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
                    continue;
                }
                ppl_logevent("GSSAPI authentication loop finished OK");

                /* Now send the MIC */

                s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic");
                pq_push(s->ppl.out_pq, s->pktout);

                s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
                continue;
#endif
            } else if (s->can_keyb_inter && !s->kbd_inter_refused) {

                /*
                 * Keyboard-interactive authentication.
                 */

                s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;

                s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER;

                s->pktout = ssh_bpp_new_pktout(
                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
                put_stringz(s->pktout, s->username);
                put_stringz(s->pktout, s->successor_layer->vt->name);
                put_stringz(s->pktout, "keyboard-interactive");
                                                        /* method */
                put_stringz(s->pktout, "");     /* lang */
                put_stringz(s->pktout, "");     /* submethods */
                pq_push(s->ppl.out_pq, s->pktout);

                ppl_logevent("Attempting keyboard-interactive authentication");

                if (!s->ki_scc_initialised) {
                    s->ki_scc = seat_stripctrl_new(
                        s->ppl.seat, NULL, SIC_KI_PROMPTS);
                    if (s->ki_scc)
                        stripctrl_enable_line_limiting(s->ki_scc);
                    s->ki_scc_initialised = true;
                }

                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
                if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
                    /* Server is not willing to do keyboard-interactive
                     * at all (or, bizarrely but legally, accepts the
                     * user without actually issuing any prompts).
                     * Give up on it entirely. */
                    pq_push_front(s->ppl.in_pq, pktin);
                    s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
                    s->kbd_inter_refused = true; /* don't try it again */
                    continue;
                }

                s->ki_printed_header = false;

                /*
                 * Loop while the server continues to send INFO_REQUESTs.
                 */
                while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
                    ptrlen name, inst;
                    strbuf *sb;

                    /*
                     * We've got a fresh USERAUTH_INFO_REQUEST.
                     * Get the preamble and start building a prompt.
                     */
                    name = get_string(pktin);
                    inst = get_string(pktin);
                    get_string(pktin); /* skip language tag */
                    s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
                    s->cur_prompt->to_server = true;
                    s->cur_prompt->from_server = true;

                    /*
                     * Get any prompt(s) from the packet.
                     */
                    s->num_prompts = get_uint32(pktin);
                    for (uint32_t i = 0; i < s->num_prompts; i++) {
                        s->is_trivial_auth = false;
                        ptrlen prompt = get_string(pktin);
                        bool echo = get_bool(pktin);

                        if (get_err(pktin)) {
                            ssh_proto_error(
                                s->ppl.ssh, "Server sent truncated "
                                "SSH_MSG_USERAUTH_INFO_REQUEST packet");
                            return;
                        }

                        sb = strbuf_new();
                        if (!prompt.len) {
                            put_datapl(sb, PTRLEN_LITERAL(
                                "<server failed to send prompt>: "));
                        } else if (s->ki_scc) {
                            stripctrl_retarget(
                                s->ki_scc, BinarySink_UPCAST(sb));
                            put_datapl(s->ki_scc, prompt);
                            stripctrl_retarget(s->ki_scc, NULL);
                        } else {
                            put_datapl(sb, prompt);
                        }
                        add_prompt(s->cur_prompt, strbuf_to_str(sb), echo);
                    }

                    /*
                     * Make the header strings. This includes the
                     * 'name' (optional dialog-box title) and
                     * 'instruction' from the server.
                     *
                     * First, display our disambiguating header line
                     * if this is the first time round the loop -
                     * _unless_ the server has sent a completely empty
                     * k-i packet with no prompts _or_ text, which
                     * apparently some do. In that situation there's
                     * no need to alert the user that the following
                     * text is server- supplied, because, well, _what_
                     * text?
                     *
                     * We also only do this if we got a stripctrl,
                     * because if we didn't, that suggests this is all
                     * being done via dialog boxes anyway.
                     */
                    if (!s->ki_printed_header && s->ki_scc &&
                        (s->num_prompts || name.len || inst.len)) {
                        seat_antispoof_msg(
                            s->ppl.seat, "Keyboard-interactive authentication "
                            "prompts from server:");
                        s->ki_printed_header = true;
                        seat_set_trust_status(s->ppl.seat, false);
                    }

                    sb = strbuf_new();
                    if (name.len) {
                        if (s->ki_scc) {
                            stripctrl_retarget(s->ki_scc,
                                               BinarySink_UPCAST(sb));
                            put_datapl(s->ki_scc, name);
                            stripctrl_retarget(s->ki_scc, NULL);
                        } else {
                            put_datapl(sb, name);
                        }
                        s->cur_prompt->name_reqd = true;
                    } else {
                        put_datapl(sb, PTRLEN_LITERAL(
                            "SSH server authentication"));
                        s->cur_prompt->name_reqd = false;
                    }
                    s->cur_prompt->name = strbuf_to_str(sb);

                    sb = strbuf_new();
                    if (inst.len) {
                        if (s->ki_scc) {
                            stripctrl_retarget(s->ki_scc,
                                               BinarySink_UPCAST(sb));
                            put_datapl(s->ki_scc, inst);
                            stripctrl_retarget(s->ki_scc, NULL);
                        } else {
                            put_datapl(sb, inst);
                        }
                        s->cur_prompt->instr_reqd = true;
                    } else {
                        s->cur_prompt->instr_reqd = false;
                    }
                    if (sb->len)
                        s->cur_prompt->instruction = strbuf_to_str(sb);
                    else
                        strbuf_free(sb);

                    /*
                     * Our prompts_t is fully constructed now. Get the
                     * user's response(s).
                     */
                    s->userpass_ret = seat_get_userpass_input(
                        s->ppl.seat, s->cur_prompt);
                    while (s->userpass_ret < 0) {
                        crReturnV;
                        s->userpass_ret = seat_get_userpass_input(
                            s->ppl.seat, s->cur_prompt);
                    }
                    if (!s->userpass_ret) {
                        /*
                         * Failed to get responses. Terminate.
                         */
                        free_prompts(s->cur_prompt);
                        s->cur_prompt = NULL;
                        ssh_bpp_queue_disconnect(
                            s->ppl.bpp, "Unable to authenticate",
                            SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
                        ssh_user_close(s->ppl.ssh, "User aborted during "
                                       "keyboard-interactive authentication");
                        return;
                    }

                    /*
                     * Send the response(s) to the server.
                     */
                    s->pktout = ssh_bpp_new_pktout(
                        s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
                    put_uint32(s->pktout, s->num_prompts);
                    for (uint32_t i = 0; i < s->num_prompts; i++) {
                        put_stringz(s->pktout, prompt_get_result_ref(
                                        s->cur_prompt->prompts[i]));
                    }
                    s->pktout->minlen = 256;
                    pq_push(s->ppl.out_pq, s->pktout);

                    /*
                     * Free the prompts structure from this iteration.
                     * If there's another, a new one will be allocated
                     * when we return to the top of this while loop.
                     */
                    free_prompts(s->cur_prompt);
                    s->cur_prompt = NULL;

                    /*
                     * Get the next packet in case it's another
                     * INFO_REQUEST.
                     */
                    crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);

                }

                /*
                 * Print our trailer line, if we printed a header.
                 */
                if (s->ki_printed_header) {
                    seat_set_trust_status(s->ppl.seat, true);
                    seat_antispoof_msg(
                        s->ppl.seat,
                        "End of keyboard-interactive prompts from server");
                }

                /*
                 * We should have SUCCESS or FAILURE now.
                 */
                pq_push_front(s->ppl.in_pq, pktin);

            } else if (s->can_passwd) {
                s->is_trivial_auth = false;
                /*
                 * Plain old password authentication.
                 */
                bool changereq_first_time; /* not live over crReturn */

                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD;

                s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
                s->cur_prompt->to_server = true;
                s->cur_prompt->from_server = false;
                s->cur_prompt->name = dupstr("SSH password");
                add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
                                                    s->username, s->hostname),
                           false);

                s->userpass_ret = seat_get_userpass_input(
                    s->ppl.seat, s->cur_prompt);
                while (s->userpass_ret < 0) {
                    crReturnV;
                    s->userpass_ret = seat_get_userpass_input(
                        s->ppl.seat, s->cur_prompt);
                }
                if (!s->userpass_ret) {
                    /*
                     * Failed to get responses. Terminate.
                     */
                    free_prompts(s->cur_prompt);
                    s->cur_prompt = NULL;
                    ssh_bpp_queue_disconnect(
                        s->ppl.bpp, "Unable to authenticate",
                        SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
                    ssh_user_close(s->ppl.ssh, "User aborted during password "
                                   "authentication");
                    return;
                }
                /*
                 * Squirrel away the password. (We may need it later if
                 * asked to change it.)
                 */
                s->password = prompt_get_result(s->cur_prompt->prompts[0]);
                free_prompts(s->cur_prompt);
                s->cur_prompt = NULL;

                /*
                 * Send the password packet.
                 *
                 * We pad out the password packet to 256 bytes to make
                 * it harder for an attacker to find the length of the
                 * user's password.
                 *
                 * Anyone using a password longer than 256 bytes
                 * probably doesn't have much to worry about from
                 * people who find out how long their password is!
                 */
                s->pktout = ssh_bpp_new_pktout(
                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
                put_stringz(s->pktout, s->username);
                put_stringz(s->pktout, s->successor_layer->vt->name);
                put_stringz(s->pktout, "password");
                put_bool(s->pktout, false);
                put_stringz(s->pktout, s->password);
                s->pktout->minlen = 256;
                pq_push(s->ppl.out_pq, s->pktout);
                ppl_logevent("Sent password");
                s->type = AUTH_TYPE_PASSWORD;

                /*
                 * Wait for next packet, in case it's a password change
                 * request.
                 */
                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
                changereq_first_time = true;

                while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {

                    /*
                     * We're being asked for a new password
                     * (perhaps not for the first time).
                     * Loop until the server accepts it.
                     */

                    bool got_new = false; /* not live over crReturn */
                    ptrlen prompt;  /* not live over crReturn */

                    {
                        const char *msg;
                        if (changereq_first_time)
                            msg = "Server requested password change";
                        else
                            msg = "Server rejected new password";
                        ppl_logevent("%s", msg);
                        ppl_printf("%s\r\n", msg);
                    }

                    prompt = get_string(pktin);

                    s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
                    s->cur_prompt->to_server = true;
                    s->cur_prompt->from_server = false;
                    s->cur_prompt->name = dupstr("New SSH password");
                    s->cur_prompt->instruction = mkstr(prompt);
                    s->cur_prompt->instr_reqd = true;
                    /*
                     * There's no explicit requirement in the protocol
                     * for the "old" passwords in the original and
                     * password-change messages to be the same, and
                     * apparently some Cisco kit supports password change
                     * by the user entering a blank password originally
                     * and the real password subsequently, so,
                     * reluctantly, we prompt for the old password again.
                     *
                     * (On the other hand, some servers don't even bother
                     * to check this field.)
                     */
                    add_prompt(s->cur_prompt,
                               dupstr("Current password (blank for previously entered password): "),
                               false);
                    add_prompt(s->cur_prompt, dupstr("Enter new password: "),
                               false);
                    add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
                               false);

                    /*
                     * Loop until the user manages to enter the same
                     * password twice.
                     */
                    while (!got_new) {
                        s->userpass_ret = seat_get_userpass_input(
                            s->ppl.seat, s->cur_prompt);
                        while (s->userpass_ret < 0) {
                            crReturnV;
                            s->userpass_ret = seat_get_userpass_input(
                                s->ppl.seat, s->cur_prompt);
                        }
                        if (!s->userpass_ret) {
                            /*
                             * Failed to get responses. Terminate.
                             */
                            /* burn the evidence */
                            free_prompts(s->cur_prompt);
                            s->cur_prompt = NULL;
                            smemclr(s->password, strlen(s->password));
                            sfree(s->password);
                            ssh_bpp_queue_disconnect(
                                s->ppl.bpp, "Unable to authenticate",
                                SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
                            ssh_user_close(s->ppl.ssh, "User aborted during "
                                           "password changing");
                            return;
                        }

                        /*
                         * If the user specified a new original password
                         * (IYSWIM), overwrite any previously specified
                         * one.
                         * (A side effect is that the user doesn't have to
                         * re-enter it if they louse up the new password.)
                         */
                        if (s->cur_prompt->prompts[0]->result->s[0]) {
                            smemclr(s->password, strlen(s->password));
                                /* burn the evidence */
                            sfree(s->password);
                            s->password = prompt_get_result(
                                s->cur_prompt->prompts[0]);
                        }

                        /*
                         * Check the two new passwords match.
                         */
                        got_new = !strcmp(
                            prompt_get_result_ref(s->cur_prompt->prompts[1]),
                            prompt_get_result_ref(s->cur_prompt->prompts[2]));
                        if (!got_new)
                            /* They don't. Silly user. */
                            ppl_printf("Passwords do not match\r\n");

                    }

                    /*
                     * Send the new password (along with the old one).
                     * (see above for padding rationale)
                     */
                    s->pktout = ssh_bpp_new_pktout(
                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
                    put_stringz(s->pktout, s->username);
                    put_stringz(s->pktout, s->successor_layer->vt->name);
                    put_stringz(s->pktout, "password");
                    put_bool(s->pktout, true);
                    put_stringz(s->pktout, s->password);
                    put_stringz(s->pktout, prompt_get_result_ref(
                                    s->cur_prompt->prompts[1]));
                    free_prompts(s->cur_prompt);
                    s->cur_prompt = NULL;
                    s->pktout->minlen = 256;
                    pq_push(s->ppl.out_pq, s->pktout);
                    ppl_logevent("Sent new password");

                    /*
                     * Now see what the server has to say about it.
                     * (If it's CHANGEREQ again, it's not happy with the
                     * new password.)
                     */
                    crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
                    changereq_first_time = false;

                }

                /*
                 * We need to reexamine the current pktin at the top
                 * of the loop. Either:
                 *  - we weren't asked to change password at all, in
                 *    which case it's a SUCCESS or FAILURE with the
                 *    usual meaning
                 *  - we sent a new password, and the server was
                 *    either OK with it (SUCCESS or FAILURE w/partial
                 *    success) or unhappy with the _old_ password
                 *    (FAILURE w/o partial success)
                 * In any of these cases, we go back to the top of
                 * the loop and start again.
                 */
                pq_push_front(s->ppl.in_pq, pktin);

                /*
                 * We don't need the old password any more, in any
                 * case. Burn the evidence.
                 */
                smemclr(s->password, strlen(s->password));
                sfree(s->password);

            } else {
                ssh_bpp_queue_disconnect(
                    s->ppl.bpp,
                    "No supported authentication methods available",
                    SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE);
                ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
                             "available (server sent: %s)",
                             s->last_methods_string->s);
                return;
            }

        }
      try_new_username:;
    }

  userauth_success:
    if (s->notrivialauth && s->is_trivial_auth) {
        ssh_proto_error(s->ppl.ssh, "Authentication was trivial! "
                        "Abandoning session as specified in configuration.");
        return;
    }

    /*
     * We've just received USERAUTH_SUCCESS, and we haven't sent
     * any packets since. Signal the transport layer to consider
     * doing an immediate rekey, if it has any reason to want to.
     */
    ssh2_transport_notify_auth_done(s->transport_layer);

    /*
     * Finally, hand over to our successor layer, and return
     * immediately without reaching the crFinishV: ssh_ppl_replace
     * will have freed us, so crFinishV's zeroing-out of crState would
     * be a use-after-free bug.
     */
    {
        PacketProtocolLayer *successor = s->successor_layer;
        s->successor_layer = NULL;     /* avoid freeing it ourself */
        ssh_ppl_replace(&s->ppl, successor);
        return;   /* we've just freed s, so avoid even touching s->crState */
    }

    crFinishV;
}

static void ssh2_userauth_add_session_id(
    struct ssh2_userauth_state *s, strbuf *sigdata)
{
    if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) {
        put_datapl(sigdata, s->session_id);
    } else {
        put_stringpl(sigdata, s->session_id);
    }
}

static void ssh2_userauth_agent_query(
    struct ssh2_userauth_state *s, strbuf *req)
{
    void *response;
    int response_len;

    sfree(s->agent_response_to_free);
    s->agent_response_to_free = NULL;

    s->auth_agent_query = agent_query(req, &response, &response_len,
                                      ssh2_userauth_agent_callback, s);
    if (!s->auth_agent_query)
        ssh2_userauth_agent_callback(s, response, response_len);
}

static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen)
{
    struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav;

    s->auth_agent_query = NULL;
    s->agent_response_to_free = reply;
    s->agent_response = make_ptrlen(reply, replylen);

    queue_idempotent_callback(&s->ppl.ic_process_queue);
}

/*
 * Helper function to add an SSH-2 signature blob to a packet. Expects
 * to be shown the public key blob as well as the signature blob.
 * Normally just appends the sig blob unmodified as a string, except
 * that it optionally breaks it open and fiddle with it to work around
 * BUG_SSH2_RSA_PADDING.
 */
static void ssh2_userauth_add_sigblob(
    struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob)
{
    BinarySource pk[1], sig[1];
    BinarySource_BARE_INIT_PL(pk, pkblob);
    BinarySource_BARE_INIT_PL(sig, sigblob);

    /* dmemdump(pkblob, pkblob_len); */
    /* dmemdump(sigblob, sigblob_len); */

    /*
     * See if this is in fact an ssh-rsa signature and a buggy
     * server; otherwise we can just do this the easy way.
     */
    if ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) &&
        ptrlen_eq_string(get_string(pk), "ssh-rsa") &&
        ptrlen_eq_string(get_string(sig), "ssh-rsa")) {
        ptrlen mod_mp, sig_mp;
        size_t sig_prefix_len;

        /*
         * Find the modulus and signature integers.
         */
        get_string(pk);                /* skip over exponent */
        mod_mp = get_string(pk);       /* remember modulus */
        sig_prefix_len = sig->pos;
        sig_mp = get_string(sig);
        if (get_err(pk) || get_err(sig))
            goto give_up;

        /*
         * Find the byte length of the modulus, not counting leading
         * zeroes.
         */
        while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) {
            mod_mp.len--;
            mod_mp.ptr = (const char *)mod_mp.ptr + 1;
        }

        /* debug("modulus length is %d\n", len); */
        /* debug("signature length is %d\n", siglen); */

        if (mod_mp.len > sig_mp.len) {
            strbuf *substr = strbuf_new();
            put_data(substr, sigblob.ptr, sig_prefix_len);
            put_uint32(substr, mod_mp.len);
            put_padding(substr, mod_mp.len - sig_mp.len, 0);
            put_datapl(substr, sig_mp);
            put_stringsb(pkt, substr);
            return;
        }

        /* Otherwise fall through and do it the easy way. We also come
         * here as a fallback if we discover above that the key blob
         * is misformatted in some way. */
      give_up:;
    }

    put_stringpl(pkt, sigblob);
}

#ifndef NO_GSSAPI
static PktOut *ssh2_userauth_gss_packet(
    struct ssh2_userauth_state *s, const char *authtype)
{
    strbuf *sb;
    PktOut *p;
    Ssh_gss_buf buf;
    Ssh_gss_buf mic;

    /*
     * The mic is computed over the session id + intended
     * USERAUTH_REQUEST packet.
     */
    sb = strbuf_new();
    put_stringpl(sb, s->session_id);
    put_byte(sb, SSH2_MSG_USERAUTH_REQUEST);
    put_stringz(sb, s->username);
    put_stringz(sb, s->successor_layer->vt->name);
    put_stringz(sb, authtype);

    /* Compute the mic */
    buf.value = sb->s;
    buf.length = sb->len;
    s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic);
    strbuf_free(sb);

    /* Now we can build the real packet */
    if (strcmp(authtype, "gssapi-with-mic") == 0) {
        p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC);
    } else {
        p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
        put_stringz(p, s->username);
        put_stringz(p, s->successor_layer->vt->name);
        put_stringz(p, authtype);
    }
    put_string(p, mic.value, mic.length);

    return p;
}
#endif

static bool ssh2_userauth_get_specials(
    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
{
    /* No specials provided by this layer. */
    return false;
}

static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl,
                                      SessionSpecialCode code, int arg)
{
    /* No specials provided by this layer. */
}

static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
{
    struct ssh2_userauth_state *s =
        container_of(ppl, struct ssh2_userauth_state, ppl);
    ssh_ppl_reconfigure(s->successor_layer, conf);
}