/*
 * Copyright (c) 2010, JANET(UK)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of JANET(UK) nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*
 * Copyright (c) 1998-2003 Carnegie Mellon University.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any other legal
 *    details, please contact
 *      Office of Technology Transfer
 *      Carnegie Mellon University
 *      5000 Forbes Avenue
 *      Pittsburgh, PA  15213-3890
 *      (412) 268-4387, fax: (412) 268-7395
 *      tech-transfer@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <config.h>
#include <gssapi/gssapi.h>

#ifndef KRB5_HEIMDAL
#ifdef HAVE_GSSAPI_GSSAPI_EXT_H
#include <gssapi/gssapi_ext.h>
#endif
#endif

#include <fcntl.h>
#include <stdio.h>
#include <sasl.h>
#include <saslutil.h>
#include <saslplug.h>

#include "plugin_common.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <errno.h>
#include <assert.h>
#include "gs2_token.h"

#define GS2_CB_FLAG_MASK    0x0F
#define GS2_CB_FLAG_N       0x00
#define GS2_CB_FLAG_P       0x01
#define GS2_CB_FLAG_Y       0x02
#define GS2_NONSTD_FLAG     0x10

typedef struct context {
    gss_ctx_id_t gss_ctx;
    gss_name_t client_name;
    gss_name_t server_name;
    gss_cred_id_t server_creds;
    gss_cred_id_t client_creds;
    char *out_buf;
    unsigned out_buf_len;
    const sasl_utils_t *utils;
    char *authid;
    char *authzid;
    union {
        sasl_client_plug_t *client;
        sasl_server_plug_t *server;
    } plug;
    gss_OID mechanism;
    int gs2_flags;
    char *cbindingname;
    struct gss_channel_bindings_struct gss_cbindings;
    sasl_secret_t *password;
    unsigned int free_password;
    OM_uint32 lifetime;
} context_t;

static gss_OID_set gs2_mechs = GSS_C_NO_OID_SET;

static int gs2_get_init_creds(context_t *context,
                              sasl_client_params_t *params,
                              sasl_interact_t **prompt_need,
                              sasl_out_params_t *oparams);

static int gs2_verify_initial_message(context_t *text,
                                      sasl_server_params_t *sparams,
                                      const char *in,
                                      unsigned inlen,
                                      gss_buffer_t token);

static int gs2_make_header(context_t *text,
                           sasl_client_params_t *cparams,
                           const char *authzid,
                           char **out,
                           unsigned *outlen);

static int gs2_make_message(context_t *text,
                            sasl_client_params_t *cparams,
                            int initialContextToken,
                            gss_buffer_t token,
                            char **out,
                            unsigned *outlen);

static int gs2_get_mech_attrs(const sasl_utils_t *utils,
                              const gss_OID mech,
                              unsigned int *security_flags,
                              unsigned int *features,
                              const unsigned long **prompts);

static int gs2_indicate_mechs(const sasl_utils_t *utils);

static int gs2_map_sasl_name(const sasl_utils_t *utils,
                             const char *mech,
                             gss_OID *oid);

static int gs2_duplicate_buffer(const sasl_utils_t *utils,
                                const gss_buffer_t src,
                                gss_buffer_t dst);

static int gs2_unescape_authzid(const sasl_utils_t *utils,
                                char **in,
                                unsigned *inlen,
                                char **authzid);

static int gs2_escape_authzid(const sasl_utils_t *utils,
                              const char *in,
                              unsigned inlen,
                              char **authzid);

/* sasl_gs_log: only logs status string returned from gss_display_status() */
#define sasl_gs2_log(x,y,z) sasl_gs2_seterror_(x,y,z,1)
#define sasl_gs2_seterror(x,y,z) sasl_gs2_seterror_(x,y,z,0)

static int
sasl_gs2_seterror_(const sasl_utils_t *utils, OM_uint32 maj, OM_uint32 min,
                   int logonly);

static context_t *
sasl_gs2_new_context(const sasl_utils_t *utils)
{
    context_t *ret;

    ret = utils->malloc(sizeof(context_t));
    if (ret == NULL)
        return NULL;

    memset(ret, 0, sizeof(context_t));
    ret->utils = utils;

    return ret;
}

static int
sasl_gs2_free_context_contents(context_t *text)
{
    OM_uint32 min_stat;

    if (text == NULL)
        return SASL_OK;

    if (text->gss_ctx != GSS_C_NO_CONTEXT) {
        gss_delete_sec_context(&min_stat,&text->gss_ctx,
                               GSS_C_NO_BUFFER);
        text->gss_ctx = GSS_C_NO_CONTEXT;
    }

    if (text->client_name != GSS_C_NO_NAME) {
        gss_release_name(&min_stat,&text->client_name);
        text->client_name = GSS_C_NO_NAME;
    }

    if (text->server_name != GSS_C_NO_NAME) {
        gss_release_name(&min_stat,&text->server_name);
        text->server_name = GSS_C_NO_NAME;
    }

    if (text->server_creds != GSS_C_NO_CREDENTIAL) {
        gss_release_cred(&min_stat, &text->server_creds);
        text->server_creds = GSS_C_NO_CREDENTIAL;
    }

    if (text->client_creds != GSS_C_NO_CREDENTIAL) {
        gss_release_cred(&min_stat, &text->client_creds);
        text->client_creds = GSS_C_NO_CREDENTIAL;
    }

    if (text->authid != NULL) {
        text->utils->free(text->authid);
        text->authid = NULL;
    }

    if (text->authzid != NULL) {
        text->utils->free(text->authzid);
        text->authzid = NULL;
    }

    gss_release_buffer(&min_stat, &text->gss_cbindings.application_data);

    if (text->out_buf != NULL) {
        text->utils->free(text->out_buf);
        text->out_buf = NULL;
    }

    text->out_buf_len = 0;

    if (text->cbindingname != NULL) {
        text->utils->free(text->cbindingname);
        text->cbindingname = NULL;
    }

    if (text->free_password)
        _plug_free_secret(text->utils, &text->password);

    memset(text, 0, sizeof(*text));

    return SASL_OK;
}

static void
gs2_common_mech_dispose(void *conn_context, const sasl_utils_t *utils)
{
    sasl_gs2_free_context_contents((context_t *)(conn_context));
    utils->free(conn_context);
}

static void
gs2_common_mech_free(void *global_context __attribute__((unused)),
                     const sasl_utils_t *utils)
{
    OM_uint32 minor;

    if (gs2_mechs != GSS_C_NO_OID_SET) {
        gss_release_oid_set(&minor, &gs2_mechs);
        gs2_mechs = GSS_C_NO_OID_SET;
    }
}

/*****************************  Server Section  *****************************/

static int
gs2_server_mech_new(void *glob_context,
                    sasl_server_params_t *params,
                    const char *challenge __attribute__((unused)),
                    unsigned challen __attribute__((unused)),
                    void **conn_context)
{
    context_t *text;
    int ret;

    text = sasl_gs2_new_context(params->utils);
    if (text == NULL) {
        MEMERROR(params->utils);
        return SASL_NOMEM;
    }

    text->gss_ctx = GSS_C_NO_CONTEXT;
    text->client_name = GSS_C_NO_NAME;
    text->server_name = GSS_C_NO_NAME;
    text->server_creds = GSS_C_NO_CREDENTIAL;
    text->client_creds = GSS_C_NO_CREDENTIAL;
    text->plug.server = glob_context;

    ret = gs2_map_sasl_name(params->utils, text->plug.server->mech_name,
                            &text->mechanism);
    if (ret != SASL_OK) {
        gs2_common_mech_dispose(text, params->utils);
        return ret;
    }

    *conn_context = text;

    return SASL_OK;
}

static int
gs2_server_mech_step(void *conn_context,
                     sasl_server_params_t *params,
                     const char *clientin,
                     unsigned clientinlen,
                     const char **serverout,
                     unsigned *serveroutlen,
                     sasl_out_params_t *oparams)
{
    context_t *text = (context_t *)conn_context;
    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
    OM_uint32 maj_stat = GSS_S_FAILURE, min_stat = 0;
    gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc short_name_buf = GSS_C_EMPTY_BUFFER;
    gss_name_t without = GSS_C_NO_NAME;
    gss_OID_set_desc mechs;
    OM_uint32 out_flags = 0;
    int ret = SASL_OK, equal = 0;
    int initialContextToken = (text->gss_ctx == GSS_C_NO_CONTEXT);
    char *p;

    if (serverout == NULL) {
        PARAMERROR(text->utils);
        return SASL_BADPARAM;
    }

    *serverout = NULL;
    *serveroutlen = 0;

    if (initialContextToken) {
        name_buf.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
        name_buf.value = params->utils->malloc(name_buf.length + 1);
        if (name_buf.value == NULL) {
            MEMERROR(text->utils);
            ret = SASL_NOMEM;
            goto cleanup;
        }
        snprintf(name_buf.value, name_buf.length + 1,
                 "%s@%s", params->service, params->serverFQDN);
        maj_stat = gss_import_name(&min_stat,
                                   &name_buf,
                                   GSS_C_NT_HOSTBASED_SERVICE,
                                   &text->server_name);
        params->utils->free(name_buf.value);
        name_buf.value = NULL;

        if (GSS_ERROR(maj_stat))
            goto cleanup;

        assert(text->server_creds == GSS_C_NO_CREDENTIAL);

        mechs.count = 1;
        mechs.elements = (gss_OID)text->mechanism;

        if (params->gss_creds == GSS_C_NO_CREDENTIAL) {
            maj_stat = gss_acquire_cred(&min_stat,
                                        text->server_name,
                                        GSS_C_INDEFINITE,
                                        &mechs,
                                        GSS_C_ACCEPT,
                                        &text->server_creds,
                                        NULL,
                                        &text->lifetime);
            if (GSS_ERROR(maj_stat))
                goto cleanup;
        }

        ret = gs2_verify_initial_message(text,
                                         params,
                                         clientin,
                                         clientinlen,
                                         &input_token);
        if (ret != SASL_OK)
            goto cleanup;
    } else {
        input_token.value = (void *)clientin;
        input_token.length = clientinlen;
    }

    maj_stat = gss_accept_sec_context(&min_stat,
                                      &text->gss_ctx,
                                      (params->gss_creds != GSS_C_NO_CREDENTIAL)
                                        ? (gss_cred_id_t)params->gss_creds
                                        : text->server_creds,
                                      &input_token,
                                      &text->gss_cbindings,
                                      &text->client_name,
                                      NULL,
                                      &output_token,
                                      &out_flags,
                                      &text->lifetime,
                                      &text->client_creds);
    if (GSS_ERROR(maj_stat)) {
        sasl_gs2_log(text->utils, maj_stat, min_stat);
        text->utils->seterror(text->utils->conn, SASL_NOLOG,
                              "GS2 Failure: gss_accept_sec_context");
        ret = (maj_stat == GSS_S_BAD_BINDINGS) ? SASL_BADBINDING : SASL_BADAUTH;
        goto cleanup;
    }

    *serveroutlen = output_token.length;
    if (output_token.value != NULL) {
        ret = _plug_buf_alloc(text->utils, &text->out_buf,
                              &text->out_buf_len, *serveroutlen);
        if (ret != SASL_OK)
            goto cleanup;
        memcpy(text->out_buf, output_token.value, *serveroutlen);
        *serverout = text->out_buf;
    } else {
        /* No output token, send an empty string */
        *serverout = "";
        serveroutlen = 0;
    }

    if (maj_stat == GSS_S_CONTINUE_NEEDED) {
        ret = SASL_CONTINUE;
        goto cleanup;
    }

    assert(maj_stat == GSS_S_COMPLETE);

    if ((out_flags & GSS_C_SEQUENCE_FLAG) == 0)  {
        ret = SASL_BADAUTH;
        goto cleanup;
    }

    maj_stat = gss_display_name(&min_stat, text->client_name,
                                &name_buf, NULL);
    if (GSS_ERROR(maj_stat))
        goto cleanup;

    ret = gs2_duplicate_buffer(params->utils, &name_buf, &short_name_buf);
    if (ret != 0)
        goto cleanup;

    p = (char *)memchr(name_buf.value, '@', name_buf.length);
    if (p != NULL) {
        short_name_buf.length = (p - (char *)name_buf.value);

        maj_stat = gss_import_name(&min_stat,
                                   &short_name_buf,
                                   GSS_C_NT_USER_NAME,
                                   &without);
        if (GSS_ERROR(maj_stat)) {
            goto cleanup;
        }

        maj_stat = gss_compare_name(&min_stat, text->client_name,
                                    without, &equal);
        if (GSS_ERROR(maj_stat)) {
            goto cleanup;
        }

        if (equal)
            ((char *)short_name_buf.value)[short_name_buf.length] = '\0';
    }

    text->authid = (char *)short_name_buf.value;
    short_name_buf.value = NULL;
    short_name_buf.length = 0;

    if (text->authzid != NULL) {
        ret = params->canon_user(params->utils->conn,
                                 text->authzid, 0,
                                 SASL_CU_AUTHZID, oparams);
        if (ret != SASL_OK) {
            goto cleanup;
	}
    }

    ret = params->canon_user(params->utils->conn,
                             text->authid, 0,
                             text->authzid == NULL
                                ? (SASL_CU_AUTHZID | SASL_CU_AUTHID)
                                : SASL_CU_AUTHID,
                             oparams);
    if (ret != SASL_OK) {
        goto cleanup;
    }

    switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
    case GS2_CB_FLAG_N:
        oparams->cbindingdisp = SASL_CB_DISP_NONE;
        break;
    case GS2_CB_FLAG_P:
        oparams->cbindingdisp = SASL_CB_DISP_USED;
        oparams->cbindingname = text->cbindingname;
        break;
    case GS2_CB_FLAG_Y:
        oparams->cbindingdisp = SASL_CB_DISP_WANT;
        break;
    }

    if (text->client_creds != GSS_C_NO_CREDENTIAL)
        oparams->client_creds = &text->client_creds;
    else
        oparams->client_creds = NULL;

    oparams->gss_peer_name = text->client_name;
    oparams->gss_local_name = text->server_name;
    oparams->maxoutbuf = 0xFFFFFF;
    oparams->encode = NULL;
    oparams->decode = NULL;
    oparams->mech_ssf = 0;
    oparams->doneflag = 1;

    ret = SASL_OK;

cleanup:
    if (ret == SASL_OK && maj_stat != GSS_S_COMPLETE) {
        sasl_gs2_seterror(text->utils, maj_stat, min_stat);
        ret = SASL_FAIL;
    }

    if (initialContextToken) {
        gss_release_buffer(&min_stat, &input_token);
    }
    gss_release_buffer(&min_stat, &name_buf);
    gss_release_buffer(&min_stat, &short_name_buf);
    gss_release_buffer(&min_stat, &output_token);
    gss_release_name(&min_stat, &without);

    if (ret < SASL_OK) {
        sasl_gs2_free_context_contents(text);
    }

    return ret;
}

static int
gs2_common_plug_init(const sasl_utils_t *utils,
                     size_t plugsize,
                     int (*plug_alloc)(const sasl_utils_t *,
                                       void *,
                                       const gss_buffer_t,
                                       const gss_OID),
                     void **pluglist,
                     int *plugcount)
{
    OM_uint32 major, minor;
    size_t i, count = 0;
    void *plugs = NULL;

    *pluglist = NULL;
    *plugcount = 0;

    if (gs2_indicate_mechs(utils) != SASL_OK) {
        return SASL_NOMECH;
    }

    plugs = utils->malloc(gs2_mechs->count * plugsize);
    if (plugs == NULL) {
        MEMERROR(utils);
        return SASL_NOMEM;
    }
    memset(plugs, 0, gs2_mechs->count * plugsize);

    for (i = 0; i < gs2_mechs->count; i++) {
        gss_buffer_desc sasl_mech_name = GSS_C_EMPTY_BUFFER;

        major = gss_inquire_saslname_for_mech(&minor,
                                              &gs2_mechs->elements[i],
                                              &sasl_mech_name,
                                              GSS_C_NO_BUFFER,
                                              GSS_C_NO_BUFFER);
        if (GSS_ERROR(major))
            continue;

#define PLUG_AT(index)      (void *)((unsigned char *)plugs + (count * plugsize))

        if (plug_alloc(utils, PLUG_AT(count), &sasl_mech_name,
                       &gs2_mechs->elements[i]) == SASL_OK)
            count++;

        gss_release_buffer(&minor, &sasl_mech_name);
    }

    if (count == 0) {
        utils->free(plugs);
        return SASL_NOMECH;
    }

    *pluglist = plugs;
    *plugcount = count;

    return SASL_OK;
}

static int
gs2_server_plug_alloc(const sasl_utils_t *utils,
                      void *plug,
                      gss_buffer_t sasl_name,
                      gss_OID mech)
{
    int ret;
    sasl_server_plug_t *splug = (sasl_server_plug_t *)plug;
    gss_buffer_desc buf;

    memset(splug, 0, sizeof(*splug));

    ret = gs2_get_mech_attrs(utils, mech,
                             &splug->security_flags,
                             &splug->features,
                             NULL);
    if (ret != SASL_OK)
        return ret;

    ret = gs2_duplicate_buffer(utils, sasl_name, &buf);
    if (ret != SASL_OK)
        return ret;

    splug->mech_name = (char *)buf.value;
    splug->glob_context = plug;
    splug->mech_new = gs2_server_mech_new;
    splug->mech_step = gs2_server_mech_step;
    splug->mech_dispose = gs2_common_mech_dispose;
    splug->mech_free = gs2_common_mech_free;

    return SASL_OK;
}

static sasl_server_plug_t *gs2_server_plugins;
static int gs2_server_plugcount;

int
gs2_server_plug_init(const sasl_utils_t *utils,
                     int maxversion,
                     int *outversion,
                     sasl_server_plug_t **pluglist,
                     int *plugcount)
{
    int ret;

    *pluglist = NULL;
    *plugcount = 0;

    if (maxversion < SASL_SERVER_PLUG_VERSION)
        return SASL_BADVERS;

    *outversion = SASL_SERVER_PLUG_VERSION;

    if (gs2_server_plugins == NULL) {
        ret = gs2_common_plug_init(utils,
                                   sizeof(sasl_server_plug_t),
                                   gs2_server_plug_alloc,
                                   (void **)&gs2_server_plugins,
                                   &gs2_server_plugcount);
        if (ret != SASL_OK)
            return ret;
    }

    *pluglist = gs2_server_plugins;
    *plugcount = gs2_server_plugcount;

    return SASL_OK;
}

/*****************************  Client Section  *****************************/

static int gs2_client_mech_step(void *conn_context,
                                sasl_client_params_t *params,
                                const char *serverin,
                                unsigned serverinlen,
                                sasl_interact_t **prompt_need,
                                const char **clientout,
                                unsigned *clientoutlen,
                                sasl_out_params_t *oparams)
{
    context_t *text = (context_t *)conn_context;
    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
    OM_uint32 maj_stat = GSS_S_FAILURE, min_stat = 0;
    OM_uint32 req_flags, ret_flags;
    int ret = SASL_FAIL;
    int initialContextToken;

    *clientout = NULL;
    *clientoutlen = 0;

    if (text->gss_ctx == GSS_C_NO_CONTEXT) {
        ret = gs2_get_init_creds(text, params, prompt_need, oparams);
        if (ret != SASL_OK) {
            goto cleanup;
	}

        initialContextToken = 1;
    } else {
        initialContextToken = 0;
    }

    if (text->server_name == GSS_C_NO_NAME) { /* only once */
        name_buf.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
        name_buf.value = params->utils->malloc(name_buf.length + 1);
        if (name_buf.value == NULL) {
            ret = SASL_NOMEM;
            goto cleanup;
        }
        if (params->serverFQDN == NULL ||
            strlen(params->serverFQDN) == 0) {
            SETERROR(text->utils, "GS2 Failure: no serverFQDN");
            ret = SASL_FAIL;
            goto cleanup;
        }

        snprintf(name_buf.value, name_buf.length + 1,
                 "%s@%s", params->service, params->serverFQDN);

        maj_stat = gss_import_name(&min_stat,
                                   &name_buf,
                                   GSS_C_NT_HOSTBASED_SERVICE,
                                   &text->server_name);
        params->utils->free(name_buf.value);
        name_buf.value = NULL;

        if (GSS_ERROR(maj_stat)) {
	    ret = SASL_OK;
            goto cleanup;
	}
    }

    /* From GSSAPI plugin: apparently this is for some IMAP bug workaround */
    if (serverinlen == 0 && text->gss_ctx != GSS_C_NO_CONTEXT) {
        gss_delete_sec_context(&min_stat, &text->gss_ctx, GSS_C_NO_BUFFER);
        text->gss_ctx = GSS_C_NO_CONTEXT;
    }

    input_token.value = (void *)serverin;
    input_token.length = serverinlen;

    if (initialContextToken) {
        if ((text->plug.client->features & SASL_FEAT_GSS_FRAMING) == 0)
            text->gs2_flags |= GS2_NONSTD_FLAG;

        switch (params->cbindingdisp) {
        case SASL_CB_DISP_NONE:
            text->gs2_flags |= GS2_CB_FLAG_N;
            break;
        case SASL_CB_DISP_USED:
            text->gs2_flags |= GS2_CB_FLAG_P;
            break;
        case SASL_CB_DISP_WANT:
            text->gs2_flags |= GS2_CB_FLAG_Y;
            break;
        }

        ret = gs2_make_header(text, params,
                              strcmp(oparams->user, oparams->authid) ?
                                     (char *) oparams->user : NULL,
                              &text->out_buf, &text->out_buf_len);
        if (ret != 0) {
            goto cleanup;
	}
    }

    req_flags = GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG;

    maj_stat = gss_init_sec_context(&min_stat,
                                    (params->gss_creds != GSS_C_NO_CREDENTIAL)
                                        ? (gss_cred_id_t)params->gss_creds
                                        : text->client_creds,
                                    &text->gss_ctx,
                                    text->server_name,
                                    (gss_OID)text->mechanism,
                                    req_flags,
                                    GSS_C_INDEFINITE,
                                    &text->gss_cbindings,
                                    serverinlen ? &input_token : GSS_C_NO_BUFFER,
                                    NULL,
                                    &output_token,
                                    &ret_flags,
                                    &text->lifetime);
    if (GSS_ERROR(maj_stat)) {
	ret = SASL_OK;
        goto cleanup;
    }

    ret = gs2_make_message(text, params, initialContextToken, &output_token,
                           &text->out_buf, &text->out_buf_len);
    if (ret != 0) {
        goto cleanup;
    }

    *clientout = text->out_buf;
    *clientoutlen = text->out_buf_len;

    if (maj_stat == GSS_S_CONTINUE_NEEDED) {
        ret = SASL_CONTINUE;
        goto cleanup;
    }

    if (text->client_name != GSS_C_NO_NAME) {
        gss_release_name(&min_stat, &text->client_name);
    }
    maj_stat = gss_inquire_context(&min_stat,
                                   text->gss_ctx,
                                   &text->client_name,
                                   NULL,
                                   &text->lifetime,
                                   NULL,
                                   &ret_flags, /* flags */
                                   NULL,
                                   NULL);
    if (GSS_ERROR(maj_stat)) {
	ret = SASL_OK;
        goto cleanup;
    }

    if ((ret_flags & req_flags) != req_flags) {
        ret = SASL_BADAUTH;
        goto cleanup;
    }

    maj_stat = gss_display_name(&min_stat,
                                text->client_name,
                                &name_buf,
                                NULL);
    if (GSS_ERROR(maj_stat)) {
	ret = SASL_OK;
        goto cleanup;
    }

    oparams->gss_peer_name = text->server_name;
    oparams->gss_local_name = text->client_name;
    oparams->encode = NULL;
    oparams->decode = NULL;
    oparams->mech_ssf = 0;
    oparams->maxoutbuf = 0xFFFFFF;
    oparams->doneflag = 1;

    ret = SASL_OK;

cleanup:
    if (ret == SASL_OK && maj_stat != GSS_S_COMPLETE) {
        sasl_gs2_seterror(text->utils, maj_stat, min_stat);
        ret = SASL_FAIL;
    }

    gss_release_buffer(&min_stat, &output_token);
    gss_release_buffer(&min_stat, &name_buf);

    if (ret < SASL_OK) {
        sasl_gs2_free_context_contents(text);
    }

    return ret;
}

static int gs2_client_mech_new(void *glob_context,
                               sasl_client_params_t *params,
                               void **conn_context)
{
    context_t *text;
    int ret;

    text = sasl_gs2_new_context(params->utils);
    if (text == NULL) {
        MEMERROR(params->utils);
        return SASL_NOMEM;
    }

    text->gss_ctx = GSS_C_NO_CONTEXT;
    text->client_name = GSS_C_NO_NAME;
    text->server_creds = GSS_C_NO_CREDENTIAL;
    text->client_creds  = GSS_C_NO_CREDENTIAL;
    text->plug.client = glob_context;

    ret = gs2_map_sasl_name(params->utils, text->plug.client->mech_name,
                            &text->mechanism);
    if (ret != SASL_OK) {
        gs2_common_mech_dispose(text, params->utils);
        return ret;
    }

    *conn_context = text;

    return SASL_OK;
}

static int
gs2_client_plug_alloc(const sasl_utils_t *utils,
                      void *plug,
                      gss_buffer_t sasl_name,
                      gss_OID mech)
{
    int ret;
    sasl_client_plug_t *cplug = (sasl_client_plug_t *)plug;
    gss_buffer_desc buf;

    memset(cplug, 0, sizeof(*cplug));

    ret = gs2_get_mech_attrs(utils, mech,
                             &cplug->security_flags,
                             &cplug->features,
                             &cplug->required_prompts);
    if (ret != SASL_OK)
        return ret;

    ret = gs2_duplicate_buffer(utils, sasl_name, &buf);
    if (ret != SASL_OK)
        return ret;

    cplug->mech_name = (char *)buf.value;
    cplug->features |= SASL_FEAT_NEEDSERVERFQDN;
    cplug->glob_context = plug;
    cplug->mech_new = gs2_client_mech_new;
    cplug->mech_step = gs2_client_mech_step;
    cplug->mech_dispose = gs2_common_mech_dispose;
    cplug->mech_free = gs2_common_mech_free;

    return SASL_OK;
}

static sasl_client_plug_t *gs2_client_plugins;
static int gs2_client_plugcount;

int
gs2_client_plug_init(const sasl_utils_t *utils,
                     int maxversion,
                     int *outversion,
                     sasl_client_plug_t **pluglist,
                     int *plugcount)
{
    int ret;

    *pluglist = NULL;
    *plugcount = 0;

    if (maxversion < SASL_CLIENT_PLUG_VERSION)
        return SASL_BADVERS;

    *outversion = SASL_CLIENT_PLUG_VERSION;

    if (gs2_client_plugins == NULL) {
        ret = gs2_common_plug_init(utils,
                                   sizeof(sasl_client_plug_t),
                                   gs2_client_plug_alloc,
                                   (void **)&gs2_client_plugins,
                                   &gs2_client_plugcount);
        if (ret != SASL_OK)
            return ret;
    }

    *pluglist = gs2_client_plugins;
    *plugcount = gs2_client_plugcount;

    return SASL_OK;
}

/*
 * Copy header and application channel bindings to GSS channel bindings
 * structure in context.
 */
static int
gs2_save_cbindings(context_t *text,
                   gss_buffer_t header,
                   const sasl_channel_binding_t *cbinding)
{
    gss_buffer_t gss_cbindings = &text->gss_cbindings.application_data;
    size_t len;
    unsigned char *p;

    assert(gss_cbindings->value == NULL);

    /*
     * The application-data field MUST be set to the gs2-header, excluding
     * the initial [gs2-nonstd-flag ","] part, concatenated with, when a
     * gs2-cb-flag of "p" is used, the application's channel binding data.
     */
    len = header->length;
    if (text->gs2_flags & GS2_NONSTD_FLAG) {
        assert(len > 2);
        len -= 2;
    }
    if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_P &&
        cbinding != NULL) {
        len += cbinding->len;
    }

    gss_cbindings->length = len;
    gss_cbindings->value = text->utils->malloc(len);
    if (gss_cbindings->value == NULL)
        return SASL_NOMEM;

    p = (unsigned char *)gss_cbindings->value;
    if (text->gs2_flags & GS2_NONSTD_FLAG) {
        memcpy(p, (unsigned char *)header->value + 2, header->length - 2);
        p += header->length - 2;
    } else {
        memcpy(p, header->value, header->length);
        p += header->length;
    }

    if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_P &&
        cbinding != NULL) {
        memcpy(p, cbinding->data, cbinding->len);
    }

    return SASL_OK;
}

#define CHECK_REMAIN(n)     do { if (remain < (n)) return SASL_BADPROT; } while (0)

/*
 * Verify gs2-header, save authzid and channel bindings to context.
 */
static int
gs2_verify_initial_message(context_t *text,
                           sasl_server_params_t *sparams,
                           const char *in,
                           unsigned inlen,
                           gss_buffer_t token)
{
    OM_uint32 major, minor;
    char *p = (char *)in;
    unsigned remain = inlen;
    int ret;
    gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;

    assert(text->cbindingname == NULL);
    assert(text->authzid == NULL);

    token->length = 0;
    token->value = NULL;

    /* minimum header includes CB flag and non-zero GSS token */
    CHECK_REMAIN(4); /* [pny],,. */

    /* non-standard GSS framing flag */
    if (remain > 1 && memcmp(p, "F,", 2) == 0) {
        text->gs2_flags |= GS2_NONSTD_FLAG;
        remain -= 2;
        p += 2;
    }

    /* SASL channel bindings */
    CHECK_REMAIN(1); /* [pny] */
    remain--;
    switch (*p++) {
    case 'p':
        CHECK_REMAIN(1); /* = */
        remain--;
        if (*p++ != '=')
            return SASL_BADPROT;

        ret = gs2_unescape_authzid(text->utils, &p, &remain, &text->cbindingname);
        if (ret != SASL_OK)
            return ret;

        text->gs2_flags |= GS2_CB_FLAG_P;
        break;
    case 'n':
        text->gs2_flags |= GS2_CB_FLAG_N;
        break;
    case 'y':
        text->gs2_flags |= GS2_CB_FLAG_Y;
        break;
    }

    CHECK_REMAIN(1); /* , */
    remain--;
    if (*p++ != ',')
        return SASL_BADPROT;

    /* authorization identity */
    if (remain > 1 && memcmp(p, "a=", 2) == 0) {
        CHECK_REMAIN(2);
        remain -= 2;
        p += 2;

        ret = gs2_unescape_authzid(text->utils, &p, &remain, &text->authzid);
        if (ret != SASL_OK)
            return ret;
    }

    /* end of header */
    CHECK_REMAIN(1); /* , */
    remain--;
    if (*p++ != ',')
        return SASL_BADPROT;

    buf.length = inlen - remain;
    buf.value = (void *)in;

    /* stash channel bindings to pass into gss_accept_sec_context() */
    ret = gs2_save_cbindings(text, &buf, sparams->cbinding);
    if (ret != SASL_OK)
        return ret;

    if (text->gs2_flags & GS2_NONSTD_FLAG) {
        buf.length = remain;
        buf.value = p;
    } else {
        gss_buffer_desc tmp;

        tmp.length = remain;
        tmp.value = p;

        major = gss_encapsulate_token(&tmp, text->mechanism, &buf);
        if (GSS_ERROR(major))
            return SASL_NOMEM;
    }

    token->value = text->utils->malloc(buf.length);
    if (token->value == NULL)
        return SASL_NOMEM;

    token->length = buf.length;
    memcpy(token->value, buf.value, buf.length);

    if ((text->gs2_flags & GS2_NONSTD_FLAG) == 0)
        gss_release_buffer(&minor, &buf);

    return SASL_OK;
}

/*
 * Create gs2-header, save channel bindings to context.
 */
static int
gs2_make_header(context_t *text,
                sasl_client_params_t *cparams,
                const char *authzid,
                char **out,
                unsigned *outlen)
{
    size_t required = 0;
    size_t wire_authzid_len = 0, cbnamelen = 0;
    char *wire_authzid = NULL;
    char *p;
    int ret;
    gss_buffer_desc buf;

    *out = NULL;
    *outlen = 0;

    /* non-standard GSS framing flag */
    if (text->gs2_flags & GS2_NONSTD_FLAG)
        required += 2; /* F, */

    /* SASL channel bindings */
    switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
    case GS2_CB_FLAG_P:
        if (!SASL_CB_PRESENT(cparams))
            return SASL_BADPARAM;
        cbnamelen = strlen(cparams->cbinding->name);
        required += 1 /*=*/ + cbnamelen;
        /* fallthrough */
    case GS2_CB_FLAG_N:
    case GS2_CB_FLAG_Y:
        required += 2; /* [pny], */
        break;
    default:
        return SASL_BADPARAM;
    }

    /* authorization identity */
    if (authzid != NULL) {
        ret = gs2_escape_authzid(text->utils, authzid,
                                 strlen(authzid), &wire_authzid);
        if (ret != SASL_OK)
            return ret;

        wire_authzid_len = strlen(wire_authzid);
        required += 2 /* a= */ + wire_authzid_len;
    }

    required += 1; /* trailing comma */

    ret = _plug_buf_alloc(text->utils, out, outlen, required);
    if (ret != SASL_OK) {
        text->utils->free(wire_authzid);
        return ret;
    }

    *out = text->out_buf;
    *outlen = required;

    p = (char *)text->out_buf;
    if (text->gs2_flags & GS2_NONSTD_FLAG) {
        *p++ = 'F';
        *p++ = ',';
    }
    switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
    case GS2_CB_FLAG_P:
        memcpy(p, "p=", 2);
        memcpy(p + 2, cparams->cbinding->name, cbnamelen);
        p += 2 + cbnamelen;
        break;
    case GS2_CB_FLAG_N:
        *p++ = 'n';
        break;
    case GS2_CB_FLAG_Y:
        *p++ = 'y';
        break;
    }
    *p++ = ',';
    if (wire_authzid != NULL) {
        memcpy(p, "a=", 2);
        memcpy(p + 2, wire_authzid, wire_authzid_len);
        text->utils->free(wire_authzid);
        p += 2 + wire_authzid_len;
    }
    *p++ = ',';

    assert(p == (char *)text->out_buf + required);

    buf.length = required;
    buf.value = *out;

    ret = gs2_save_cbindings(text, &buf, cparams->cbinding);
    if (ret != SASL_OK)
        return ret;

    return SASL_OK;
}

/*
 * Convert a GSS token to a GS2 one
 */
static int
gs2_make_message(context_t *text,
                 sasl_client_params_t *cparams __attribute__((unused)),
                 int initialContextToken,
                 gss_buffer_t token,
                 char **out,
                 unsigned *outlen)
{
    OM_uint32 major, minor;
    int ret;
    unsigned header_len = 0;
    gss_buffer_desc decap_token = GSS_C_EMPTY_BUFFER;

    if (initialContextToken) {
        header_len = *outlen;

        major = gss_decapsulate_token(token, text->mechanism, &decap_token);
        if ((major == GSS_S_DEFECTIVE_TOKEN &&
             (text->plug.client->features & SASL_FEAT_GSS_FRAMING)) ||
            GSS_ERROR(major))
            return SASL_FAIL;

        token = &decap_token;
    }

    ret = _plug_buf_alloc(text->utils, out, outlen,
                          header_len + token->length);
    if (ret != 0)
        return ret;

    memcpy(*out + header_len, token->value, token->length);
    *outlen = header_len + token->length;

    if (initialContextToken)
        gss_release_buffer(&minor, &decap_token);

    return SASL_OK;
}

static const unsigned long gs2_required_prompts[] = {
    SASL_CB_LIST_END
};

/*
 * Map GSS mechanism attributes to SASL ones
 */
static int
gs2_get_mech_attrs(const sasl_utils_t *utils,
                   const gss_OID mech,
                   unsigned int *security_flags,
                   unsigned int *features,
                   const unsigned long **prompts)
{
    OM_uint32 major, minor;
    int present;
    gss_OID_set attrs = GSS_C_NO_OID_SET;

    major = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
    if (GSS_ERROR(major)) {
        utils->seterror(utils->conn, SASL_NOLOG,
                        "GS2 Failure: gss_inquire_attrs_for_mech");
        return SASL_FAIL;
    }

    *security_flags = SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE;
    *features = SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_CHANNEL_BINDING;
    if (prompts != NULL)
        *prompts = gs2_required_prompts;

#define MA_PRESENT(a)   (gss_test_oid_set_member(&minor, (gss_OID)(a), \
                                                 attrs, &present) == GSS_S_COMPLETE && \
                         present)

    if (MA_PRESENT(GSS_C_MA_PFS))
        *security_flags |= SASL_SEC_FORWARD_SECRECY;
    if (!MA_PRESENT(GSS_C_MA_AUTH_INIT_ANON))
        *security_flags |= SASL_SEC_NOANONYMOUS;
    if (MA_PRESENT(GSS_C_MA_DELEG_CRED))
        *security_flags |= SASL_SEC_PASS_CREDENTIALS;
    if (MA_PRESENT(GSS_C_MA_AUTH_TARG))
        *security_flags |= SASL_SEC_MUTUAL_AUTH;
    if (MA_PRESENT(GSS_C_MA_AUTH_INIT_INIT) && prompts != NULL)
        *prompts = NULL;
    if (MA_PRESENT(GSS_C_MA_ITOK_FRAMED))
        *features |= SASL_FEAT_GSS_FRAMING;

    gss_release_oid_set(&minor, &attrs);

    return SASL_OK;
}

/*
 * Enumerate GSS mechanisms that can be used for GS2
 */
static int gs2_indicate_mechs(const sasl_utils_t *utils)
{
    OM_uint32 major, minor;
    gss_OID_desc desired_oids[3];
    gss_OID_set_desc desired_attrs;
    gss_OID_desc except_oids[3];
    gss_OID_set_desc except_attrs;

    if (gs2_mechs != GSS_C_NO_OID_SET)
        return SASL_OK;

    desired_oids[0] = *GSS_C_MA_AUTH_INIT;
    desired_oids[1] = *GSS_C_MA_AUTH_TARG;
    desired_oids[2] = *GSS_C_MA_CBINDINGS;
    desired_attrs.count = sizeof(desired_oids)/sizeof(desired_oids[0]);
    desired_attrs.elements = desired_oids;

    except_oids[0] = *GSS_C_MA_MECH_NEGO;
    except_oids[1] = *GSS_C_MA_NOT_MECH;
    except_oids[2] = *GSS_C_MA_DEPRECATED;

    except_attrs.count = sizeof(except_oids)/sizeof(except_oids[0]);
    except_attrs.elements = except_oids;

    major = gss_indicate_mechs_by_attrs(&minor,
                                        &desired_attrs,
                                        &except_attrs,
                                        GSS_C_NO_OID_SET,
                                        &gs2_mechs);
    if (GSS_ERROR(major)) {
        utils->seterror(utils->conn, SASL_NOLOG,
                        "GS2 Failure: gss_indicate_mechs_by_attrs");
        return SASL_FAIL;
    }

    return (gs2_mechs->count > 0) ? SASL_OK : SASL_NOMECH;
}

/*
 * Map SASL mechanism name to OID
 */
static int
gs2_map_sasl_name(const sasl_utils_t *utils,
                  const char *mech,
                  gss_OID *oid)
{
    OM_uint32 major, minor;
    gss_buffer_desc buf;

    buf.length = strlen(mech);
    buf.value = (void *)mech;

    major = gss_inquire_mech_for_saslname(&minor, &buf, oid);
    if (GSS_ERROR(major)) {
        utils->seterror(utils->conn, SASL_NOLOG,
                        "GS2 Failure: gss_inquire_mech_for_saslname");
        return SASL_FAIL;
    }

    return SASL_OK;
}

static int
gs2_duplicate_buffer(const sasl_utils_t *utils,
                     const gss_buffer_t src,
                     gss_buffer_t dst)
{
    dst->value = utils->malloc(src->length + 1);
    if (dst->value == NULL)
        return SASL_NOMEM;

    memcpy(dst->value, src->value, src->length);
    ((char *)dst->value)[src->length] = '\0';
    dst->length = src->length;

    return SASL_OK;
}

static int
gs2_unescape_authzid(const sasl_utils_t *utils,
                     char **endp,
                     unsigned *remain,
                     char **authzid)
{
    char *in = *endp;
    size_t i, len, inlen = *remain;
    char *p;

    *endp = NULL;

    for (i = 0, len = 0; i < inlen; i++) {
        if (in[i] == ',') {
            *endp = &in[i];
            *remain -= i;
            break;
        } else if (in[i] == '=') {
            if (inlen <= i + 2)
                return SASL_BADPROT;
            i += 2;
        }
        len++;
    }

    if (len == 0 || *endp == NULL)
        return SASL_BADPROT;

    p = *authzid = utils->malloc(len + 1);
    if (*authzid == NULL)
        return SASL_NOMEM;

    for (i = 0; i < inlen; i++) {
        if (in[i] == ',')
            break;
        else if (in[i] == '=') {
            if (memcmp(&in[i + 1], "2C", 2) == 0)
                *p++ = ',';
            else if (memcmp(&in[i + 1], "3D", 2) == 0)
                *p++ = '=';
            else {
                utils->free(*authzid);
                *authzid = NULL;
                return SASL_BADPROT;
            }
            i += 2;
        } else
            *p++ = in[i];
    }

    *p = '\0';

    return SASL_OK;
}

static int
gs2_escape_authzid(const sasl_utils_t *utils,
                   const char *in,
                   unsigned inlen,
                   char **authzid)
{
    size_t i;
    char *p;

    p = *authzid = utils->malloc((inlen * 3) + 1);
    if (*authzid == NULL)
        return SASL_NOMEM;

    for (i = 0; i < inlen; i++) {
        if (in[i] == ',') {
            memcpy(p, "=2C", 3);
            p += 3;
        } else if (in[i] == '=') {
            memcpy(p, "=3D", 3);
            p += 3;
        } else {
            *p++ = in[i];
        }
    }

    *p = '\0';

    return SASL_OK;
}

#define GOT_CREDS(text, params) ((text)->client_creds != NULL || (params)->gss_creds != NULL)
#define CRED_ERROR(status)      ((status) == GSS_S_CRED_UNAVAIL || (status) == GSS_S_NO_CRED)

/*
 * Determine the authentication identity from the application supplied
 * GSS credential, the application supplied identity, and the default
 * GSS credential, in that order. Then, acquire credentials.
 */
static int
gs2_get_init_creds(context_t *text,
                   sasl_client_params_t *params,
                   sasl_interact_t **prompt_need,
                   sasl_out_params_t *oparams)
{
    int result = SASL_OK;
    const char *authid = NULL, *userid = NULL;
    int user_result = SASL_OK;
    int auth_result = SASL_OK;
    int pass_result = SASL_OK;
    OM_uint32 maj_stat = GSS_S_COMPLETE, min_stat = 0;
    gss_OID_set_desc mechs;
    gss_buffer_desc cred_authid = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;

    mechs.count = 1;
    mechs.elements = (gss_OID)text->mechanism;

    /*
     * Get the authentication identity from the application.
     */
    if (oparams->authid == NULL) {
        auth_result = _plug_get_authid(params->utils, &authid, prompt_need);
        if (auth_result != SASL_OK && auth_result != SASL_INTERACT) {
            result = auth_result;
            goto cleanup;
        }
    }

    /*
     * Get the authorization identity from the application.
     */
    if (oparams->user == NULL) {
        user_result = _plug_get_userid(params->utils, &userid, prompt_need);
        if (user_result != SASL_OK && user_result != SASL_INTERACT) {
            result = user_result;
            goto cleanup;
        }
    }

    /*
     * Canonicalize the authentication and authorization identities before
     * calling GSS_Import_name.
     */
    if (auth_result == SASL_OK && user_result == SASL_OK &&
        oparams->authid == NULL) {
        if (userid == NULL || userid[0] == '\0') {
            result = params->canon_user(params->utils->conn, authid, 0,
                                        SASL_CU_AUTHID | SASL_CU_AUTHZID,
                                        oparams);
        } else {
            result = params->canon_user(params->utils->conn,
                                        authid, 0, SASL_CU_AUTHID, oparams);
            if (result != SASL_OK)
                goto cleanup;

            result = params->canon_user(params->utils->conn,
                                        userid, 0, SASL_CU_AUTHZID, oparams);
            if (result != SASL_OK)
                goto cleanup;
        }

        if (oparams->authid != NULL) {
            name_buf.length = strlen(oparams->authid);
            name_buf.value = (void *)oparams->authid;

            assert(text->client_name == GSS_C_NO_NAME);

            maj_stat = gss_import_name(&min_stat,
                                       &name_buf,
                                       GSS_C_NT_USER_NAME,
                                       &text->client_name);
            if (GSS_ERROR(maj_stat))
                goto cleanup;
        }
    }

    /*
     * If application didn't provide an authid, then use the default
     * credential. If that doesn't work, give up.
     */
    if (!GOT_CREDS(text, params) && oparams->authid == NULL) {
        maj_stat = gss_acquire_cred(&min_stat,
                                    GSS_C_NO_NAME,
                                    GSS_C_INDEFINITE,
                                    &mechs,
                                    GSS_C_INITIATE,
                                    &text->client_creds,
                                    NULL,
                                    &text->lifetime);
        if (GSS_ERROR(maj_stat))
            goto cleanup;

        assert(text->client_name == GSS_C_NO_NAME);

        maj_stat = gss_inquire_cred(&min_stat,
                                    params->gss_creds
                                        ? (gss_cred_id_t)params->gss_creds
                                        : text->client_creds,
                                    &text->client_name,
                                    NULL,
                                    NULL,
                                    NULL);
        if (GSS_ERROR(maj_stat))
            goto cleanup;

        maj_stat = gss_display_name(&min_stat,
                                    text->client_name,
                                    &cred_authid,
                                    NULL);
        if (GSS_ERROR(maj_stat))
            goto cleanup;

        if (userid == NULL || userid[0] == '\0') {
            result = params->canon_user(params->utils->conn,
                                        cred_authid.value, cred_authid.length,
                                        SASL_CU_AUTHID | SASL_CU_AUTHZID,
                                        oparams);
        } else {
            result = params->canon_user(params->utils->conn,
                                        cred_authid.value, cred_authid.length,
                                        SASL_CU_AUTHID, oparams);
            if (result != SASL_OK)
                goto cleanup;

            result = params->canon_user(params->utils->conn,
                                        cred_authid.value, cred_authid.length,
                                        SASL_CU_AUTHZID, oparams);
            if (result != SASL_OK)
                goto cleanup;
        }
    }

    /*
     * Armed with the authentication identity, try to get a credential without
     * a password.
     */
    if (!GOT_CREDS(text, params) && text->client_name != GSS_C_NO_NAME) {
        maj_stat = gss_acquire_cred(&min_stat,
                                    text->client_name,
                                    GSS_C_INDEFINITE,
                                    &mechs,
                                    GSS_C_INITIATE,
                                    &text->client_creds,
                                    NULL,
                                    &text->lifetime);
        if (GSS_ERROR(maj_stat) && !CRED_ERROR(maj_stat))
            goto cleanup;
    }

    /*
     * If that failed, try to get a credential with a password.
     */
    if (!GOT_CREDS(text, params)) {
        if (text->password == NULL) {
            pass_result = _plug_get_password(params->utils, &text->password,
                                             &text->free_password, prompt_need);
            if (pass_result != SASL_OK && pass_result != SASL_INTERACT) {
                result = pass_result;
                goto cleanup;
            }
        }

        if (text->password != NULL) {
            gss_buffer_desc password_buf;

            password_buf.length = text->password->len;
            password_buf.value = text->password->data;

            maj_stat = gss_acquire_cred_with_password(&min_stat,
                                                      text->client_name,
                                                      &password_buf,
                                                      GSS_C_INDEFINITE,
                                                      &mechs,
                                                      GSS_C_INITIATE,
                                                      &text->client_creds,
                                                      NULL,
                                                      &text->lifetime);
            if (GSS_ERROR(maj_stat))
                goto cleanup;
        }
    }

    maj_stat = GSS_S_COMPLETE;

    /* free prompts we got */
    if (prompt_need && *prompt_need) {
        params->utils->free(*prompt_need);
        *prompt_need = NULL;
    }

    /* if there are prompts not filled in */
    if (user_result == SASL_INTERACT || auth_result == SASL_INTERACT ||
        pass_result == SASL_INTERACT) {
        /* make the prompt list */
        result =
            _plug_make_prompts(params->utils, prompt_need,
                               user_result == SASL_INTERACT ?
                               "Please enter your authorization name" : NULL,
                               NULL,
                               auth_result == SASL_INTERACT ?
                               "Please enter your authentication name" : NULL,
                               NULL,
                               pass_result == SASL_INTERACT ?
                               "Please enter your password" : NULL, NULL,
                               NULL, NULL, NULL,
                               NULL,
                               NULL, NULL);
        if (result == SASL_OK)
            result = SASL_INTERACT;
    }

cleanup:
    if (result == SASL_OK && maj_stat != GSS_S_COMPLETE) {
        sasl_gs2_seterror(text->utils, maj_stat, min_stat);
        result = SASL_FAIL;
    }

    gss_release_buffer(&min_stat, &cred_authid);

    return result;
}

static int
sasl_gs2_seterror_(const sasl_utils_t *utils, OM_uint32 maj, OM_uint32 min,
                   int logonly)
{
    OM_uint32 maj_stat, min_stat;
    gss_buffer_desc msg;
    OM_uint32 msg_ctx;
    int ret;
    char *out = NULL;
    unsigned int len, curlen = 0;
    const char prefix[] = "GS2 Error: ";

    len = sizeof(prefix);
    ret = _plug_buf_alloc(utils, &out, &curlen, 256);
    if (ret != SASL_OK)
        return SASL_OK;

    strcpy(out, prefix);

    msg_ctx = 0;
    while (1) {
        maj_stat = gss_display_status(&min_stat, maj,
                                      GSS_C_GSS_CODE, GSS_C_NULL_OID,
                                      &msg_ctx, &msg);

        if (GSS_ERROR(maj_stat)) {
            if (logonly) {
                utils->log(utils->conn, SASL_LOG_FAIL,
                        "GS2 Failure: (could not get major error message)");
            } else {
                utils->seterror(utils->conn, 0,
                                "GS2 Failure "
                                "(could not get major error message)");
            }
            utils->free(out);
            return SASL_OK;
        }

        len += len + msg.length;
        ret = _plug_buf_alloc(utils, &out, &curlen, len);
        if (ret != SASL_OK) {
            utils->free(out);
            return SASL_OK;
        }

        strcat(out, msg.value);

        gss_release_buffer(&min_stat, &msg);

        if (!msg_ctx)
            break;
    }

    /* Now get the minor status */

    len += 2;
    ret = _plug_buf_alloc(utils, &out, &curlen, len);
    if (ret != SASL_OK) {
        utils->free(out);
        return SASL_NOMEM;
    }

    strcat(out, " (");

    msg_ctx = 0;
    while (1) {
        maj_stat = gss_display_status(&min_stat, min,
                                      GSS_C_MECH_CODE, GSS_C_NULL_OID,
                                      &msg_ctx, &msg);

        if (GSS_ERROR(maj_stat)) {
            if (logonly) {
                utils->log(utils->conn, SASL_LOG_FAIL,
                        "GS2 Failure: (could not get minor error message)");
            } else {
                utils->seterror(utils->conn, 0,
                                "GS2 Failure "
                                "(could not get minor error message)");
            }
            utils->free(out);
            return SASL_OK;
        }

        len += len + msg.length;

        ret = _plug_buf_alloc(utils, &out, &curlen, len);
        if (ret != SASL_OK) {
            utils->free(out);
            return SASL_NOMEM;
        }

        strcat(out, msg.value);

        gss_release_buffer(&min_stat, &msg);

        if (!msg_ctx)
            break;
    }

    len += 1;
    ret = _plug_buf_alloc(utils, &out, &curlen, len);
    if (ret != SASL_OK) {
        utils->free(out);
        return SASL_NOMEM;
    }

    strcat(out, ")");

    if (logonly) {
        utils->log(utils->conn, SASL_LOG_FAIL, out);
    } else {
        utils->seterror(utils->conn, 0, out);
    }
    utils->free(out);

    return SASL_OK;
}
