/*
 * 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, *detached_cert_file;
    bool show_banner, tryagent, notrivialauth, change_username;
    char *hostname, *fullhostname;
    int port;
    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;
    SeatPromptResult spr;
    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, *detached_cert_blob, *cert_pubkey_diagnosed;
    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;

    char *authplugin_cmd;
    Socket *authplugin;
    uint32_t authplugin_version;
    Plug authplugin_plug;
    bufchain authplugin_bc;
    strbuf *authplugin_incoming_msg;
    size_t authplugin_backlog;
    bool authplugin_eof;
    bool authplugin_ki_active;

    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_alg_and_publickey(
    struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob);
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 bool ssh2_userauth_ki_setup_prompts(
    struct ssh2_userauth_state *s, BinarySource *src, bool plugin);
static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s);
static void ssh2_userauth_ki_write_responses(
    struct ssh2_userauth_state *s, BinarySink *bs);

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, int port, const char *fullhostname,
    Filename *keyfile, Filename *detached_cert_file,
    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,
    const char *authplugin_cmd)
{
    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->port = port;
    s->fullhostname = dupstr(fullhostname);
    s->keyfile = filename_copy(keyfile);
    s->detached_cert_file = filename_copy(detached_cert_file);
    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);
    s->authplugin_cmd = dupstr(authplugin_cmd);
    bufchain_init(&s->authplugin_bc);

    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);
    filename_free(s->detached_cert_file);
    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);
    if (s->detached_cert_blob)
        strbuf_free(s->detached_cert_blob);
    if (s->cert_pubkey_diagnosed)
        strbuf_free(s->cert_pubkey_diagnosed);
    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->authplugin_cmd);
    if (s->authplugin)
        sk_close(s->authplugin);
    bufchain_clear(&s->authplugin_bc);
    if (s->authplugin_incoming_msg)
        strbuf_free(s->authplugin_incoming_msg);
    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 bool ssh2_userauth_signflags(struct ssh2_userauth_state *s,
                                    unsigned *signflags, const char **algname)
{
    *signflags = 0;                    /* default */

    const ssh_keyalg *alg = find_pubkey_alg(*algname);
    if (!alg)
        return false;          /* we don't know how to upgrade this */

    unsigned supported_flags = ssh_keyalg_supported_flags(alg);

    if (s->ppl.bpp->ext_info_rsa_sha512_ok &&
        (supported_flags & SSH_AGENT_RSA_SHA2_512)) {
        *signflags = SSH_AGENT_RSA_SHA2_512;
    } else if (s->ppl.bpp->ext_info_rsa_sha256_ok &&
               (supported_flags & SSH_AGENT_RSA_SHA2_256)) {
        *signflags = SSH_AGENT_RSA_SHA2_256;
    } else {
        return false;
    }

    *algname = ssh_keyalg_alternate_ssh_id(alg, *signflags);
    return true;
}

static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr,
                                int port, const char *err_msg, int err_code)
{
    struct ssh2_userauth_state *s = container_of(
        plug, struct ssh2_userauth_state, authplugin_plug);
    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */

    if (type == PLUGLOG_PROXY_MSG)
        ppl_logevent("%s", err_msg);
}

static void authplugin_plug_closing(
    Plug *plug, PlugCloseType type, const char *error_msg)
{
    struct ssh2_userauth_state *s = container_of(
        plug, struct ssh2_userauth_state, authplugin_plug);
    s->authplugin_eof = true;
    queue_idempotent_callback(&s->ppl.ic_process_queue);
}

static void authplugin_plug_receive(
    Plug *plug, int urgent, const char *data, size_t len)
{
    struct ssh2_userauth_state *s = container_of(
        plug, struct ssh2_userauth_state, authplugin_plug);
    bufchain_add(&s->authplugin_bc, data, len);
    queue_idempotent_callback(&s->ppl.ic_process_queue);
}

static void authplugin_plug_sent(Plug *plug, size_t bufsize)
{
    struct ssh2_userauth_state *s = container_of(
        plug, struct ssh2_userauth_state, authplugin_plug);
    s->authplugin_backlog = bufsize;
    queue_idempotent_callback(&s->ppl.ic_process_queue);
}

static const PlugVtable authplugin_plugvt = {
    .log = authplugin_plug_log,
    .closing = authplugin_plug_closing,
    .receive = authplugin_plug_receive,
    .sent = authplugin_plug_sent,
};

static strbuf *authplugin_newmsg(uint8_t type)
{
    strbuf *amsg = strbuf_new_nm();
    put_uint32(amsg, 0);               /* fill in later */
    put_byte(amsg, type);
    return amsg;
}

static void authplugin_send_free(struct ssh2_userauth_state *s, strbuf *amsg)
{
    PUT_32BIT_MSB_FIRST(amsg->u, amsg->len - 4);
    assert(s->authplugin);
    s->authplugin_backlog = sk_write(s->authplugin, amsg->u, amsg->len);
    strbuf_free(amsg);
}

static bool authplugin_expect_msg(struct ssh2_userauth_state *s,
                                  unsigned *type, BinarySource *src)
{
    if (s->authplugin_eof) {
        *type = PLUGIN_EOF;
        return true;
    }
    uint8_t len[4];
    if (!bufchain_try_fetch(&s->authplugin_bc, len, 4))
        return false;
    size_t size = GET_32BIT_MSB_FIRST(len);
    if (bufchain_size(&s->authplugin_bc) - 4 < size)
        return false;
    if (s->authplugin_incoming_msg) {
        strbuf_clear(s->authplugin_incoming_msg);
    } else {
        s->authplugin_incoming_msg = strbuf_new_nm();
    }
    bufchain_consume(&s->authplugin_bc, 4); /* eat length field */
    bufchain_fetch_consume(
        &s->authplugin_bc, strbuf_append(s->authplugin_incoming_msg, size),
        size);
    BinarySource_BARE_INIT_PL(
        src, ptrlen_from_strbuf(s->authplugin_incoming_msg));
    *type = get_byte(src);
    if (get_err(src))
        *type = PLUGIN_NOTYPE;
    return true;
}

static void authplugin_bad_packet(struct ssh2_userauth_state *s,
                                  unsigned type, const char *fmt, ...)
{
    strbuf *msg = strbuf_new();
    switch (type) {
      case PLUGIN_EOF:
        put_dataz(msg, "Unexpected end of file from auth helper plugin");
        break;
      case PLUGIN_NOTYPE:
        put_dataz(msg, "Received malformed packet from auth helper plugin "
                  "(too short to have a type code)");
        break;
      default:
        put_fmt(msg, "Received unknown message type %u "
                "from auth helper plugin", type);
        break;

      #define CASEDECL(name, value)                                     \
      case name:                                                        \
        put_fmt(msg, "Received unexpected %s message from auth helper " \
                "plugin", #name);                                       \
        break;
        AUTHPLUGIN_MSG_NAMES(CASEDECL);
      #undef CASEDECL
    }
    if (fmt) {
        put_dataz(msg, " (");
        va_list ap;
        va_start(ap, fmt);
        put_fmt(msg, fmt, ap);
        va_end(ap);
        put_dataz(msg, ")");
    }
    ssh_sw_abort(s->ppl.ssh, "%s", msg->s);
    strbuf_free(msg);
}

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;
        }
    }

    /*
     * If the user provided a detached certificate file, load that.
     */
    if (!filename_is_null(s->detached_cert_file)) {
        char *cert_error = NULL;
        strbuf *cert_blob = strbuf_new();
        char *algname = NULL;
        char *comment = NULL;

        ppl_logevent("Reading certificate file \"%s\"",
                     filename_to_str(s->detached_cert_file));
        int keytype = key_type(s->detached_cert_file);
        if (!(keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
              keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) {
            cert_error = dupstr(key_type_to_str(keytype));
            goto cert_load_done;
        }

        const char *error;
        bool success = ppk_loadpub_f(
            s->detached_cert_file, &algname,
            BinarySink_UPCAST(cert_blob), &comment, &error);

        if (!success) {
            cert_error = dupstr(error);
            goto cert_load_done;
        }

        const ssh_keyalg *certalg = find_pubkey_alg(algname);
        if (!certalg) {
            cert_error = dupprintf(
                "unrecognised certificate type '%s'", algname);
            goto cert_load_done;
        }

        if (!certalg->is_certificate) {
            cert_error = dupprintf(
                "key type '%s' is not a certificate", certalg->ssh_id);
            goto cert_load_done;
        }

        /* OK, store the certificate blob to substitute for the
         * public blob in all publickey auth packets. */
        if (s->detached_cert_blob)
            strbuf_free(s->detached_cert_blob);
        s->detached_cert_blob = cert_blob;
        cert_blob = NULL;      /* prevent free */

      cert_load_done:
        if (cert_error) {
            ppl_logevent("Unable to use this certificate file (%s)",
                         cert_error);
            ppl_printf(
                "Unable to use certificate file \"%s\" (%s)\r\n",
                filename_to_str(s->detached_cert_file), cert_error);
            sfree(cert_error);
        }

        if (cert_blob)
            strbuf_free(cert_blob);
        sfree(algname);
        sfree(comment);
    }

    /*
     * 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_dup(get_string(s->asrc));
                s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc));

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

            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:;
    }

    s->got_username = false;

    if (*s->authplugin_cmd) {
        s->authplugin_plug.vt = &authplugin_plugvt;
        s->authplugin = platform_start_subprocess(
            s->authplugin_cmd, &s->authplugin_plug, "plugin");
        ppl_logevent("Started authentication plugin: %s", s->authplugin_cmd);
    }

    if (s->authplugin) {
        strbuf *amsg = authplugin_newmsg(PLUGIN_INIT);
        put_uint32(amsg, PLUGIN_PROTOCOL_MAX_VERSION);
        put_stringz(amsg, s->hostname);
        put_uint32(amsg, s->port);
        put_stringz(amsg, s->username ? s->username : "");
        authplugin_send_free(s, amsg);

        BinarySource src[1];
        unsigned type;
        crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src));
        switch (type) {
          case PLUGIN_INIT_RESPONSE: {
            s->authplugin_version = get_uint32(src);
            ptrlen username = get_string(src);
            if (get_err(src)) {
                ssh_sw_abort(s->ppl.ssh, "Received malformed "
                             "PLUGIN_INIT_RESPONSE from auth helper plugin");
                return;
            }
            if (s->authplugin_version > PLUGIN_PROTOCOL_MAX_VERSION) {
                ssh_sw_abort(s->ppl.ssh, "Auth helper plugin announced "
                             "unsupported version number %"PRIu32,
                             s->authplugin_version);
                return;
            }
            if (username.len) {
                sfree(s->default_username);
                s->default_username = mkstr(username);
                ppl_logevent("Authentication plugin set username '%s'",
                             s->default_username);
            }
            break;
          }
          case PLUGIN_INIT_FAILURE: {
            ptrlen message = get_string(src);
            if (get_err(src)) {
                ssh_sw_abort(s->ppl.ssh, "Received malformed "
                             "PLUGIN_INIT_FAILURE from auth helper plugin");
                return;
            }
            /* This is a controlled error, so we need not completely
             * abandon the connection. Instead, inform the user, and
             * proceed as if the plugin was not present */
            ppl_printf("Authentication plugin failed to initialise:\r\n");
            seat_set_trust_status(s->ppl.seat, false);
            ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message));
            seat_set_trust_status(s->ppl.seat, true);
            sk_close(s->authplugin);
            s->authplugin = NULL;
            break;
          }
          default:
            authplugin_bad_packet(s, type, "expected PLUGIN_INIT_RESPONSE or "
                                  "PLUGIN_INIT_FAILURE");
            return;
        }
    }

    /*
     * 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!
     */
    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->spr = seat_get_userpass_input(
                ppl_get_iseat(&s->ppl), s->cur_prompt);
            while (s->spr.kind == SPRK_INCOMPLETE) {
                crReturnV;
                s->spr = seat_get_userpass_input(
                    ppl_get_iseat(&s->ppl), s->cur_prompt);
            }
            if (spr_is_abort(s->spr)) {
                /*
                 * seat_get_userpass_input() failed to get a username.
                 * Terminate.
                 */
                free_prompts(s->cur_prompt);
                s->cur_prompt = NULL;
                ssh_spr_close(s->ppl.ssh, s->spr, "username prompt");
                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(
                        ppl_get_iseat(&s->ppl),
                        "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(ppl_get_iseat(&s->ppl), 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(ppl_get_iseat(&s->ppl),
                                   PTRLEN_LITERAL("\r\n"));

                if (s->banner_scc) {
                    seat_set_trust_status(s->ppl.seat, true);
                    seat_antispoof_msg(ppl_get_iseat(&s->ppl),
                                       "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;
                char *alg_tmp = mkstr(s->agent_keyalg);
                const char *newalg = alg_tmp;
                if (ssh2_userauth_signflags(s, &s->signflags, &newalg))
                    s->agent_keyalg = ptrlen_from_asciz(newalg);
                sfree(alg_tmp);

                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 */
                ssh2_userauth_add_alg_and_publickey(
                    s, s->pktout, s->agent_keyalg, 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 */
                    ssh2_userauth_add_alg_and_publickey(
                        s, s->pktout, s->agent_keyalg, 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.
                 */
                const char *newalg = s->publickey_algorithm;
                if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) {
                    sfree(s->publickey_algorithm);
                    s->publickey_algorithm = dupstr(newalg);
                }

                /*
                 * 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 */
                ssh2_userauth_add_alg_and_publickey(
                    s, s->pktout, ptrlen_from_asciz(s->publickey_algorithm),
                    ptrlen_from_strbuf(s->publickey_blob));
                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->spr = seat_get_userpass_input(
                            ppl_get_iseat(&s->ppl), s->cur_prompt);
                        while (s->spr.kind == SPRK_INCOMPLETE) {
                            crReturnV;
                            s->spr = seat_get_userpass_input(
                                ppl_get_iseat(&s->ppl), s->cur_prompt);
                        }
                        if (spr_is_abort(s->spr)) {
                            /* 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_spr_close(s->ppl.ssh, s->spr,
                                          "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 */
                    pkblob = strbuf_new();
                    ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob));
                    ssh2_userauth_add_alg_and_publickey(
                        s, s->pktout,
                        ptrlen_from_asciz(s->publickey_algorithm),
                        ptrlen_from_strbuf(pkblob));

                    /*
                     * 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->authplugin) {
                    strbuf *amsg = authplugin_newmsg(PLUGIN_PROTOCOL);
                    put_stringz(amsg, "keyboard-interactive");
                    authplugin_send_free(s, amsg);

                    BinarySource src[1];
                    unsigned type;
                    crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src));
                    switch (type) {
                      case PLUGIN_PROTOCOL_REJECT: {
                        ptrlen message = PTRLEN_LITERAL("");
                        if (s->authplugin_version >= 2) {
                            /* draft protocol didn't include a message here */
                            message = get_string(src);
                        }
                        if (get_err(src)) {
                            ssh_sw_abort(s->ppl.ssh, "Received malformed "
                                         "PLUGIN_PROTOCOL_REJECT from auth "
                                         "helper plugin");
                            return;
                        }
                        if (message.len) {
                            /* If the plugin sent a message about
                             * _why_ it didn't want to do k-i, pass
                             * that message on to the user. (It might
                             * say, for example, what went wrong when
                             * it tried to open its config file.) */
                            ppl_printf("Authentication plugin failed to set "
                                       "up keyboard-interactive "
                                       "authentication:\r\n");
                            seat_set_trust_status(s->ppl.seat, false);
                            ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message));
                            seat_set_trust_status(s->ppl.seat, true);
                            ppl_logevent("Authentication plugin declined to "
                                         "help with keyboard-interactive: "
                                         "%.*s", PTRLEN_PRINTF(message));
                        } else {
                            ppl_logevent("Authentication plugin declined to "
                                         "help with keyboard-interactive");
                        }
                        s->authplugin_ki_active = false;
                        break;
                      }
                      case PLUGIN_PROTOCOL_ACCEPT:
                        s->authplugin_ki_active = true;
                        ppl_logevent("Authentication plugin agreed to help "
                                     "with keyboard-interactive");
                        break;
                      default:
                        authplugin_bad_packet(
                            s, type, "expected PLUGIN_PROTOCOL_ACCEPT or "
                            "PLUGIN_PROTOCOL_REJECT");
                        return;
                    }
                } else {
                    s->authplugin_ki_active = false;
                }

                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 we still have prompts to send to the user.
                 */
                if (!s->authplugin_ki_active) {
                    /*
                     * The simple case: INFO_REQUESTs are passed on to
                     * the user, and responses are sent straight back
                     * to the SSH server.
                     */
                    while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
                        if (!ssh2_userauth_ki_setup_prompts(
                                s, BinarySource_UPCAST(pktin), false))
                            return;
                        crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s));

                        if (spr_is_abort(s->spr)) {
                            /*
                             * 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_spr_close(s->ppl.ssh, s->spr, "keyboard-"
                                          "interactive authentication prompt");
                            return;
                        }

                        /*
                         * Send the response(s) to the server.
                         */
                        s->pktout = ssh_bpp_new_pktout(
                            s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
                        ssh2_userauth_ki_write_responses(
                            s, BinarySink_UPCAST(s->pktout));
                        s->pktout->minlen = 256;
                        pq_push(s->ppl.out_pq, s->pktout);

                        /*
                         * Get the next packet in case it's another
                         * INFO_REQUEST.
                         */
                        crMaybeWaitUntilV(
                            (pktin = ssh2_userauth_pop(s)) != NULL);
                    }
                } else {
                    /*
                     * The case where a plugin is involved:
                     * INFO_REQUEST from the server is sent to the
                     * plugin, which sends responses that we hand back
                     * to the server. But in the meantime, the plugin
                     * might send USER_REQUEST for us to pass to the
                     * user, and then we send responses to that.
                     */
                    while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
                        strbuf *amsg = authplugin_newmsg(
                            PLUGIN_KI_SERVER_REQUEST);
                        put_datapl(amsg, get_data(pktin, get_avail(pktin)));
                        authplugin_send_free(s, amsg);

                        BinarySource src[1];
                        unsigned type;
                        while (true) {
                            crMaybeWaitUntilV(authplugin_expect_msg(
                                                  s, &type, src));
                            if (type != PLUGIN_KI_USER_REQUEST)
                                break;

                            if (!ssh2_userauth_ki_setup_prompts(s, src, true))
                                return;
                            crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s));

                            if (spr_is_abort(s->spr)) {
                                /*
                                 * 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_spr_close(
                                    s->ppl.ssh, s->spr, "keyboard-"
                                    "interactive authentication prompt");
                                return;
                            }

                            /*
                             * Send the responses on to the plugin.
                             */
                            strbuf *amsg = authplugin_newmsg(
                                PLUGIN_KI_USER_RESPONSE);
                            ssh2_userauth_ki_write_responses(
                                s, BinarySink_UPCAST(amsg));
                            authplugin_send_free(s, amsg);
                        }

                        if (type != PLUGIN_KI_SERVER_RESPONSE) {
                            authplugin_bad_packet(
                                s, type, "expected PLUGIN_KI_SERVER_RESPONSE "
                                "or PLUGIN_PROTOCOL_USER_REQUEST");
                            return;
                        }

                        s->pktout = ssh_bpp_new_pktout(
                            s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
                        put_datapl(s->pktout, get_data(src, get_avail(src)));
                        s->pktout->minlen = 256;
                        pq_push(s->ppl.out_pq, s->pktout);

                        /*
                         * 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(
                        ppl_get_iseat(&s->ppl),
                        (s->authplugin_ki_active ?
                         "End of keyboard-interactive prompts from plugin" :
                         "End of keyboard-interactive prompts from server"));
                }

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

                if (s->authplugin_ki_active) {
                    /*
                     * As our last communication with the plugin, tell
                     * it whether the k-i authentication succeeded.
                     */
                    int plugin_msg = -1;
                    if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
                        plugin_msg = PLUGIN_AUTH_SUCCESS;
                    } else if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
                        /*
                         * Peek in the failure packet to see if it's a
                         * partial success.
                         */
                        BinarySource src[1];
                        BinarySource_BARE_INIT(
                            src, get_ptr(pktin), get_avail(pktin));
                        get_string(pktin); /* skip methods */
                        bool partial_success = get_bool(pktin);
                        if (!get_err(src)) {
                            plugin_msg = partial_success ?
                                PLUGIN_AUTH_SUCCESS : PLUGIN_AUTH_FAILURE;
                        }
                    }

                    if (plugin_msg >= 0) {
                        strbuf *amsg = authplugin_newmsg(plugin_msg);
                        authplugin_send_free(s, amsg);

                        /* Wait until we've actually sent it, in case
                         * we close the connection to the plugin
                         * before that outgoing message has left our
                         * own buffers */
                        crMaybeWaitUntilV(s->authplugin_backlog == 0);
                    }
                }
            } 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->spr = seat_get_userpass_input(
                    ppl_get_iseat(&s->ppl), s->cur_prompt);
                while (s->spr.kind == SPRK_INCOMPLETE) {
                    crReturnV;
                    s->spr = seat_get_userpass_input(
                        ppl_get_iseat(&s->ppl), s->cur_prompt);
                }
                if (spr_is_abort(s->spr)) {
                    /*
                     * 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_spr_close(s->ppl.ssh, s->spr, "password prompt");
                    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->spr = seat_get_userpass_input(
                            ppl_get_iseat(&s->ppl), s->cur_prompt);
                        while (s->spr.kind == SPRK_INCOMPLETE) {
                            crReturnV;
                            s->spr = seat_get_userpass_input(
                                ppl_get_iseat(&s->ppl), s->cur_prompt);
                        }
                        if (spr_is_abort(s->spr)) {
                            /*
                             * 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_spr_close(s->ppl.ssh, s->spr,
                                          "password-change prompt");
                            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 bool ssh2_userauth_ki_setup_prompts(
    struct ssh2_userauth_state *s, BinarySource *src, bool plugin)
{
    ptrlen name, inst;
    strbuf *sb;

    /*
     * We've got a fresh USERAUTH_INFO_REQUEST. Get the preamble and
     * start building a prompt.
     */
    name = get_string(src);
    inst = get_string(src);
    get_string(src); /* 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(src);
    for (uint32_t i = 0; i < s->num_prompts; i++) {
        s->is_trivial_auth = false;
        ptrlen prompt = get_string(src);
        bool echo = get_bool(src);

        if (get_err(src)) {
            ssh_proto_error(s->ppl.ssh, "%s sent truncated %s packet",
                            plugin ? "Plugin" : "Server",
                            plugin ? "PLUGIN_KI_USER_REQUEST" :
                            "SSH_MSG_USERAUTH_INFO_REQUEST");
            return false;
        }

        sb = strbuf_new();
        if (!prompt.len) {
            put_fmt(sb, "<%s failed to send prompt>: ",
                    plugin ? "plugin" : "server");
        } 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(
            ppl_get_iseat(&s->ppl),
            (plugin ?
             "Keyboard-interactive authentication prompts from plugin:" :
             "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 {
        if (plugin)
            put_datapl(sb, PTRLEN_LITERAL(
                           "Communication with authentication plugin"));
        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);

    return true;
}

static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s)
{
    s->spr = seat_get_userpass_input(
        ppl_get_iseat(&s->ppl), s->cur_prompt);
    return s->spr.kind != SPRK_INCOMPLETE;
}

static void ssh2_userauth_ki_write_responses(
    struct ssh2_userauth_state *s, BinarySink *bs)
{
    put_uint32(bs, s->num_prompts);
    for (uint32_t i = 0; i < s->num_prompts; i++)
        put_stringz(bs, prompt_get_result_ref(s->cur_prompt->prompts[i]));

    /*
     * 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;
}

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 the algorithm and public key strings to a
 * "publickey" auth packet. Deals with overriding both strings if the
 * user has provided a detached certificate which matches the public
 * key in question.
 */
static void ssh2_userauth_add_alg_and_publickey(
    struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob)
{
    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */

    if (s->detached_cert_blob) {
        ptrlen detached_cert_pl = ptrlen_from_strbuf(s->detached_cert_blob);
        strbuf *certbase = NULL, *pkbase = NULL;
        bool done = false;
        const ssh_keyalg *pkalg = find_pubkey_alg_len(alg);
        ssh_key *certkey = NULL, *pk = NULL;
        strbuf *fail_reason = strbuf_new();
        bool verbose = true;

        /*
         * Whether or not we send the certificate, we're likely to
         * generate a log message about it. But we don't want to log
         * once for the offer and once for the real auth attempt, so
         * we de-duplicate by remembering the last public key this
         * function saw. */
        if (!s->cert_pubkey_diagnosed)
            s->cert_pubkey_diagnosed = strbuf_new();
        if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->cert_pubkey_diagnosed),
                             pkblob)) {
            verbose = false;
        } else {
            /* Log this time, but arrange that we don't mention it next time */
            strbuf_clear(s->cert_pubkey_diagnosed);
            put_datapl(s->cert_pubkey_diagnosed, pkblob);
        }

        /*
         * Check that the public key we're replacing is compatible
         * with the certificate, in that they should have the same
         * base public key.
         */

        const ssh_keyalg *certalg = pubkey_blob_to_alg(detached_cert_pl);
        assert(certalg); /* we checked this before setting s->detached_blob */
        assert(certalg->is_certificate); /* and this too */

        certkey = ssh_key_new_pub(certalg, detached_cert_pl);
        if (!certkey) {
            put_fmt(fail_reason, "certificate key file is invalid");
            goto no_match;
        }

        certbase = strbuf_new();
        ssh_key_public_blob(ssh_key_base_key(certkey),
                            BinarySink_UPCAST(certbase));
        if (ptrlen_eq_ptrlen(pkblob, ptrlen_from_strbuf(certbase)))
            goto match;                /* yes, a match! */

        /*
         * If we reach here, the certificate's base key was not
         * identical to the key we're given. But it might still be
         * identical to the _base_ key of the key we're given, if we
         * were using a differently certified version of the same key.
         * In that situation, the detached cert should still override.
         */
        if (!pkalg) {
            put_fmt(fail_reason, "unable to identify algorithm of base key");
            goto no_match;
        }

        pk = ssh_key_new_pub(pkalg, pkblob);
        if (!pk) {
            put_fmt(fail_reason, "base public key is invalid");
            goto no_match;
        }

        pkbase = strbuf_new();
        ssh_key_public_blob(ssh_key_base_key(pk), BinarySink_UPCAST(pkbase));
        if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(pkbase),
                             ptrlen_from_strbuf(certbase)))
            goto match;                /* yes, a match on 2nd attempt! */

        /* Give up; we've tried to match these keys up and failed. */
        put_fmt(fail_reason, "base public key does not match certificate");
        goto no_match;

      match:
        /*
         * The two keys match, so insert the detached certificate into
         * the output packet in place of the public key we were given.
         *
         * However, we need to be a bit careful with the algorithm
         * name: we might need to upgrade it to one that matches the
         * original algorithm name. (If we were asked to add an
         * ssh-rsa key but were given algorithm name "rsa-sha2-512",
         * then instead of the certificate's algorithm name
         * ssh-rsa-cert-v01@... we need to write the corresponding
         * SHA-512 name rsa-sha2-512-cert-v01@... .)
         */
        if (verbose) {
            ppl_logevent("Sending public key with certificate from \"%s\"",
                         filename_to_str(s->detached_cert_file));
        }
        put_stringz(pkt, ssh_keyalg_related_alg(certalg, pkalg)->ssh_id);
        put_stringpl(pkt, ptrlen_from_strbuf(s->detached_cert_blob));
        done = true;
        goto out;

      no_match:
        /* Log that we didn't send the certificate, if this public key
         * isn't the same one as last call to this function. (Need to
         * avoid verbosely logging once for the offer and once for the
         * real auth attempt.) */
	if (verbose) {
            ppl_logevent("Not substituting certificate \"%s\" for public "
                         "key: %s", filename_to_str(s->detached_cert_file),
                         fail_reason->s);
            if (s->publickey_blob) {
                /* If the user provided a specific key file to use (i.e.
                 * this wasn't just a key we picked opportunistically out
                 * of an agent), then they probably _care_ that we didn't
                 * send the certificate, so we should make a loud error
                 * message about it as well as just commenting in the
                 * Event Log. */
                ppl_printf("Unable to use certificate \"%s\" with public "
                           "key \"%s\": %s\r\n",
                           filename_to_str(s->detached_cert_file),
                           filename_to_str(s->keyfile),
                           fail_reason->s);
            }
        }

      out:
        /* Whether we did that or not, free our stuff. */
        if (certbase)
            strbuf_free(certbase);
        if (pkbase)
            strbuf_free(pkbase);
        if (certkey)
            ssh_key_free(certkey);
        if (pk)
            ssh_key_free(pk);
        strbuf_free(fail_reason);

        /* And if we did, don't fall through to the alternative below */
        if (done)
            return;
    }

    /* In all other cases, just put in what we were given. */
    put_stringpl(pkt, alg);
    put_stringpl(pkt, pkblob);
}

/*
 * 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);
}
