root/crypto/krb5/src/lib/gssapi/spnego/spnego_mech.c
/*
 * Copyright (C) 2006,2008 by the Massachusetts Institute of Technology.
 * All rights reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * A module that implements the spnego security mechanism.
 * It is used to negotiate the security mechanism between
 * peers using the GSS-API.  SPNEGO is specified in RFC 4178.
 *
 */
/*
 * Copyright (c) 2006-2008, Novell, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *   * 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.
 *   * The copyright holder's name is not 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 OWNER 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.
 */
/* #pragma ident        "@(#)spnego_mech.c      1.7     04/09/28 SMI" */

#include        <k5-int.h>
#include        <k5-der.h>
#include        <krb5.h>
#include        <mglueP.h>
#include        "gssapiP_spnego.h"
#include        <gssapi_err_generic.h>


#define HARD_ERROR(v) ((v) != GSS_S_COMPLETE && (v) != GSS_S_CONTINUE_NEEDED)
typedef const gss_OID_desc *gss_OID_const;

/* private routines for spnego_mechanism */
static spnego_token_t make_spnego_token(const char *);
static gss_buffer_desc make_err_msg(const char *);
static int verify_token_header(struct k5input *, gss_OID_const);
static gss_OID get_mech_oid(OM_uint32 *minor_status, struct k5input *);
static gss_buffer_t get_octet_string(struct k5input *);
static gss_OID_set get_mech_set(OM_uint32 *, struct k5input *);
static OM_uint32 get_req_flags(struct k5input *, OM_uint32 *);
static OM_uint32 get_available_mechs(OM_uint32 *, gss_name_t, gss_cred_usage_t,
                                     gss_const_key_value_set_t,
                                     gss_cred_id_t *, gss_OID_set *,
                                     OM_uint32 *);
static OM_uint32 get_negotiable_mechs(OM_uint32 *, spnego_gss_ctx_id_t,
                                      spnego_gss_cred_id_t, gss_cred_usage_t);
static void release_spnego_ctx(spnego_gss_ctx_id_t *);
static spnego_gss_ctx_id_t create_spnego_ctx(int);
static int put_mech_set(gss_OID_set mechSet, gss_buffer_t buf);

static OM_uint32
process_mic(OM_uint32 *, gss_buffer_t, spnego_gss_ctx_id_t,
            gss_buffer_t *, OM_uint32 *, send_token_flag *);
static OM_uint32
handle_mic(OM_uint32 *, gss_buffer_t, int, spnego_gss_ctx_id_t,
           gss_buffer_t *, OM_uint32 *, send_token_flag *);

static OM_uint32
init_ctx_new(OM_uint32 *, spnego_gss_cred_id_t, send_token_flag *,
             spnego_gss_ctx_id_t *);
static OM_uint32
init_ctx_nego(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32, gss_OID,
              gss_buffer_t *, gss_buffer_t *, send_token_flag *);
static OM_uint32
init_ctx_cont(OM_uint32 *, spnego_gss_ctx_id_t, gss_buffer_t,
              gss_buffer_t *, gss_buffer_t *,
              OM_uint32 *, send_token_flag *);
static OM_uint32
init_ctx_reselect(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32,
                  gss_OID, gss_buffer_t *, gss_buffer_t *, send_token_flag *);
static OM_uint32
init_ctx_call_init(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t,
                   OM_uint32, gss_name_t, OM_uint32, OM_uint32, gss_buffer_t,
                   gss_channel_bindings_t,
                   gss_buffer_t, OM_uint32 *, send_token_flag *);

static OM_uint32
acc_ctx_new(OM_uint32 *, gss_buffer_t, spnego_gss_cred_id_t, gss_buffer_t *,
            gss_buffer_t *, OM_uint32 *, send_token_flag *,
            spnego_gss_ctx_id_t *);
static OM_uint32
acc_ctx_cont(OM_uint32 *, gss_buffer_t, spnego_gss_ctx_id_t, gss_buffer_t *,
             gss_buffer_t *, OM_uint32 *, send_token_flag *);
static OM_uint32
acc_ctx_vfy_oid(OM_uint32 *, spnego_gss_ctx_id_t, gss_OID,
                OM_uint32 *, send_token_flag *);
static OM_uint32
acc_ctx_call_acc(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t,
                 gss_buffer_t, gss_channel_bindings_t, gss_buffer_t,
                 OM_uint32 *, OM_uint32 *, send_token_flag *);

static gss_OID
negotiate_mech(spnego_gss_ctx_id_t, gss_OID_set, OM_uint32 *);

static int
make_spnego_tokenInit_msg(spnego_gss_ctx_id_t,
                        int,
                        gss_buffer_t,
                        OM_uint32, gss_buffer_t, send_token_flag,
                        gss_buffer_t);
static OM_uint32
make_spnego_tokenTarg_msg(uint8_t, gss_OID, gss_buffer_t,
                        gss_buffer_t, send_token_flag,
                        gss_buffer_t);

static OM_uint32
get_negTokenInit(OM_uint32 *, gss_buffer_t, gss_buffer_t,
                 gss_OID_set *, OM_uint32 *, gss_buffer_t *,
                 gss_buffer_t *);
static OM_uint32
get_negTokenResp(OM_uint32 *, struct k5input *, OM_uint32 *, gss_OID *,
                 gss_buffer_t *, gss_buffer_t *);

static int
is_kerb_mech(gss_OID oid);

/* SPNEGO oid structure */
static const gss_OID_desc spnego_oids[] = {
        {SPNEGO_OID_LENGTH, SPNEGO_OID},
};

const gss_OID_desc * const gss_mech_spnego = spnego_oids+0;
static const gss_OID_set_desc spnego_oidsets[] = {
        {1, (gss_OID) spnego_oids+0},
};
const gss_OID_set_desc * const gss_mech_set_spnego = spnego_oidsets+0;

static gss_OID_desc negoex_mech = { NEGOEX_OID_LENGTH, NEGOEX_OID };

static int make_NegHints(OM_uint32 *, gss_buffer_t *);
static OM_uint32
acc_ctx_hints(OM_uint32 *, spnego_gss_cred_id_t, gss_buffer_t *, OM_uint32 *,
              send_token_flag *, spnego_gss_ctx_id_t *);

/*
 * The Mech OID for SPNEGO:
 * { iso(1) org(3) dod(6) internet(1) security(5)
 *  mechanism(5) spnego(2) }
 */
static struct gss_config spnego_mechanism =
{
        {SPNEGO_OID_LENGTH, SPNEGO_OID},
        NULL,
        spnego_gss_acquire_cred,
        spnego_gss_release_cred,
        spnego_gss_init_sec_context,
#ifndef LEAN_CLIENT
        spnego_gss_accept_sec_context,
#else
        NULL,
#endif  /* LEAN_CLIENT */
        NULL,                           /* gss_process_context_token */
        spnego_gss_delete_sec_context,  /* gss_delete_sec_context */
        spnego_gss_context_time,        /* gss_context_time */
        spnego_gss_get_mic,             /* gss_get_mic */
        spnego_gss_verify_mic,          /* gss_verify_mic */
        spnego_gss_wrap,                /* gss_wrap */
        spnego_gss_unwrap,              /* gss_unwrap */
        spnego_gss_display_status,
        NULL,                           /* gss_indicate_mechs */
        spnego_gss_compare_name,
        spnego_gss_display_name,
        spnego_gss_import_name,
        spnego_gss_release_name,
        spnego_gss_inquire_cred,        /* gss_inquire_cred */
        NULL,                           /* gss_add_cred */
#ifndef LEAN_CLIENT
        spnego_gss_export_sec_context,          /* gss_export_sec_context */
        spnego_gss_import_sec_context,          /* gss_import_sec_context */
#else
        NULL,                           /* gss_export_sec_context */
        NULL,                           /* gss_import_sec_context */
#endif /* LEAN_CLIENT */
        NULL,                           /* gss_inquire_cred_by_mech */
        spnego_gss_inquire_names_for_mech,
        spnego_gss_inquire_context,     /* gss_inquire_context */
        NULL,                           /* gss_internal_release_oid */
        spnego_gss_wrap_size_limit,     /* gss_wrap_size_limit */
        spnego_gss_localname,
        NULL,                           /* gss_userok */
        NULL,                           /* gss_export_name */
        spnego_gss_duplicate_name,      /* gss_duplicate_name */
        NULL,                           /* gss_store_cred */
        spnego_gss_inquire_sec_context_by_oid, /* gss_inquire_sec_context_by_oid */
        spnego_gss_inquire_cred_by_oid, /* gss_inquire_cred_by_oid */
        spnego_gss_set_sec_context_option, /* gss_set_sec_context_option */
        spnego_gss_set_cred_option,     /* gssspi_set_cred_option */
        NULL,                           /* gssspi_mech_invoke */
        spnego_gss_wrap_aead,
        spnego_gss_unwrap_aead,
        spnego_gss_wrap_iov,
        spnego_gss_unwrap_iov,
        spnego_gss_wrap_iov_length,
        spnego_gss_complete_auth_token,
        spnego_gss_acquire_cred_impersonate_name,
        NULL,                           /* gss_add_cred_impersonate_name */
        spnego_gss_display_name_ext,
        spnego_gss_inquire_name,
        spnego_gss_get_name_attribute,
        spnego_gss_set_name_attribute,
        spnego_gss_delete_name_attribute,
        spnego_gss_export_name_composite,
        spnego_gss_map_name_to_any,
        spnego_gss_release_any_name_mapping,
        spnego_gss_pseudo_random,
        spnego_gss_set_neg_mechs,
        spnego_gss_inquire_saslname_for_mech,
        spnego_gss_inquire_mech_for_saslname,
        spnego_gss_inquire_attrs_for_mech,
        spnego_gss_acquire_cred_from,
        NULL,                           /* gss_store_cred_into */
        spnego_gss_acquire_cred_with_password,
        spnego_gss_export_cred,
        spnego_gss_import_cred,
        NULL,                           /* gssspi_import_sec_context_by_mech */
        NULL,                           /* gssspi_import_name_by_mech */
        NULL,                           /* gssspi_import_cred_by_mech */
        spnego_gss_get_mic_iov,
        spnego_gss_verify_mic_iov,
        spnego_gss_get_mic_iov_length
};

#ifdef _GSS_STATIC_LINK
#include "mglueP.h"

static int gss_spnegomechglue_init(void)
{
        struct gss_mech_config mech_spnego;

        memset(&mech_spnego, 0, sizeof(mech_spnego));
        mech_spnego.mech = &spnego_mechanism;
        mech_spnego.mechNameStr = "spnego";
        mech_spnego.mech_type = GSS_C_NO_OID;

        return gssint_register_mechinfo(&mech_spnego);
}
#else
gss_mechanism KRB5_CALLCONV
gss_mech_initialize(void)
{
        return (&spnego_mechanism);
}

MAKE_INIT_FUNCTION(gss_krb5int_lib_init);
MAKE_FINI_FUNCTION(gss_krb5int_lib_fini);
int gss_krb5int_lib_init(void);
#endif /* _GSS_STATIC_LINK */

int gss_spnegoint_lib_init(void)
{
        int err;

        err = k5_key_register(K5_KEY_GSS_SPNEGO_STATUS, NULL);
        if (err)
                return err;

#ifdef _GSS_STATIC_LINK
        return gss_spnegomechglue_init();
#else
        return 0;
#endif
}

void gss_spnegoint_lib_fini(void)
{
        k5_key_delete(K5_KEY_GSS_SPNEGO_STATUS);
}

static OM_uint32
create_spnego_cred(OM_uint32 *minor_status, gss_cred_id_t mcred,
                   spnego_gss_cred_id_t *cred_out)
{
        spnego_gss_cred_id_t spcred;

        *cred_out = NULL;
        spcred = calloc(1, sizeof(*spcred));
        if (spcred == NULL) {
                *minor_status = ENOMEM;
                return GSS_S_FAILURE;
        }
        spcred->mcred = mcred;
        *cred_out = spcred;
        return GSS_S_COMPLETE;
}

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_acquire_cred(OM_uint32 *minor_status,
                        gss_name_t desired_name,
                        OM_uint32 time_req,
                        gss_OID_set desired_mechs,
                        gss_cred_usage_t cred_usage,
                        gss_cred_id_t *output_cred_handle,
                        gss_OID_set *actual_mechs,
                        OM_uint32 *time_rec)
{
    return spnego_gss_acquire_cred_from(minor_status, desired_name, time_req,
                                        desired_mechs, cred_usage, NULL,
                                        output_cred_handle, actual_mechs,
                                        time_rec);
}

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_acquire_cred_from(OM_uint32 *minor_status,
                             const gss_name_t desired_name,
                             OM_uint32 time_req,
                             const gss_OID_set desired_mechs,
                             gss_cred_usage_t cred_usage,
                             gss_const_key_value_set_t cred_store,
                             gss_cred_id_t *output_cred_handle,
                             gss_OID_set *actual_mechs,
                             OM_uint32 *time_rec)
{
        OM_uint32 status, tmpmin;
        gss_OID_set amechs;
        gss_cred_id_t mcred = NULL;
        spnego_gss_cred_id_t spcred = NULL;
        dsyslog("Entering spnego_gss_acquire_cred\n");

        if (actual_mechs)
                *actual_mechs = NULL;

        if (time_rec)
                *time_rec = 0;

        /* We will obtain a mechglue credential and wrap it in a
         * spnego_gss_cred_id_rec structure.  Allocate the wrapper. */
        status = create_spnego_cred(minor_status, mcred, &spcred);
        if (status != GSS_S_COMPLETE)
                return (status);

        /*
         * Always use get_available_mechs to collect a list of
         * mechs for which creds are available.
         */
        status = get_available_mechs(minor_status, desired_name,
                                     cred_usage, cred_store, &mcred,
                                     &amechs, time_rec);

        if (actual_mechs && amechs != GSS_C_NULL_OID_SET) {
                (void) generic_gss_copy_oid_set(&tmpmin, amechs, actual_mechs);
        }
        (void) gss_release_oid_set(&tmpmin, &amechs);

        if (status == GSS_S_COMPLETE) {
                spcred->mcred = mcred;
                *output_cred_handle = (gss_cred_id_t)spcred;
        } else {
                free(spcred);
                *output_cred_handle = GSS_C_NO_CREDENTIAL;
        }

        dsyslog("Leaving spnego_gss_acquire_cred\n");
        return (status);
}

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_release_cred(OM_uint32 *minor_status,
                        gss_cred_id_t *cred_handle)
{
        spnego_gss_cred_id_t spcred = NULL;

        dsyslog("Entering spnego_gss_release_cred\n");

        if (minor_status == NULL || cred_handle == NULL)
                return (GSS_S_CALL_INACCESSIBLE_WRITE);

        *minor_status = 0;

        if (*cred_handle == GSS_C_NO_CREDENTIAL)
                return (GSS_S_COMPLETE);

        spcred = (spnego_gss_cred_id_t)*cred_handle;
        *cred_handle = GSS_C_NO_CREDENTIAL;
        gss_release_oid_set(minor_status, &spcred->neg_mechs);
        gss_release_cred(minor_status, &spcred->mcred);
        free(spcred);

        dsyslog("Leaving spnego_gss_release_cred\n");
        return (GSS_S_COMPLETE);
}

static spnego_gss_ctx_id_t
create_spnego_ctx(int initiate)
{
        spnego_gss_ctx_id_t spnego_ctx = NULL;

        spnego_ctx = malloc(sizeof(*spnego_ctx));
        if (spnego_ctx == NULL) {
                return (NULL);
        }

        spnego_ctx->magic_num = SPNEGO_MAGIC_ID;
        spnego_ctx->ctx_handle = GSS_C_NO_CONTEXT;
        spnego_ctx->mech_set = NULL;
        spnego_ctx->internal_mech = NULL;
        spnego_ctx->DER_mechTypes.length = 0;
        spnego_ctx->DER_mechTypes.value = NULL;
        spnego_ctx->mic_reqd = 0;
        spnego_ctx->mic_sent = 0;
        spnego_ctx->mic_rcvd = 0;
        spnego_ctx->mech_complete = 0;
        spnego_ctx->nego_done = 0;
        spnego_ctx->opened = 0;
        spnego_ctx->initiate = initiate;
        spnego_ctx->internal_name = GSS_C_NO_NAME;
        spnego_ctx->actual_mech = GSS_C_NO_OID;
        spnego_ctx->deleg_cred = GSS_C_NO_CREDENTIAL;
        spnego_ctx->negoex_step = 0;
        memset(&spnego_ctx->negoex_transcript, 0, sizeof(struct k5buf));
        spnego_ctx->negoex_seqnum = 0;
        K5_TAILQ_INIT(&spnego_ctx->negoex_mechs);
        spnego_ctx->kctx = NULL;
        memset(spnego_ctx->negoex_conv_id, 0, GUID_LENGTH);

        return (spnego_ctx);
}

/* iso(1) org(3) dod(6) internet(1) private(4) enterprises(1) samba(7165)
 * gssntlmssp(655) controls(1) spnego_req_mechlistMIC(2) */
static const gss_OID_desc spnego_req_mechlistMIC_oid =
        { 11, "\x2B\x06\x01\x04\x01\xB7\x7D\x85\x0F\x01\x02" };

/*
 * Return nonzero if the mechanism has reason to believe that a mechlistMIC
 * exchange will be required.  Microsoft servers erroneously require SPNEGO
 * mechlistMIC if they see an internal MIC within an NTLMSSP Authenticate
 * message, even if NTLMSSP was the preferred mechanism.
 */
static int
mech_requires_mechlistMIC(spnego_gss_ctx_id_t sc)
{
        OM_uint32 major, minor;
        gss_ctx_id_t ctx = sc->ctx_handle;
        gss_OID oid = (gss_OID)&spnego_req_mechlistMIC_oid;
        gss_buffer_set_t bufs;
        int result;

        major = gss_inquire_sec_context_by_oid(&minor, ctx, oid, &bufs);
        if (major != GSS_S_COMPLETE)
                return 0;

        /* Report true if the mech returns a single buffer containing a single
         * byte with value 1. */
        result = (bufs != NULL && bufs->count == 1 &&
                  bufs->elements[0].length == 1 &&
                  memcmp(bufs->elements[0].value, "\1", 1) == 0);
        (void) gss_release_buffer_set(&minor, &bufs);
        return result;
}

/* iso(1) org(3) dod(6) internet(1) private(4) enterprises(1) Microsoft(311)
 * security(2) mechanisms(2) NTLM(10) */
static const gss_OID_desc gss_mech_ntlmssp_oid =
        { 10, "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" };

/* iso(1) org(3) dod(6) internet(1) private(4) enterprises(1) samba(7165)
 * gssntlmssp(655) controls(1) ntlmssp_reset_crypto(3) */
static const gss_OID_desc ntlmssp_reset_crypto_oid =
        { 11, "\x2B\x06\x01\x04\x01\xB7\x7D\x85\x0F\x01\x03" };

/*
 * MS-SPNG section 3.3.5.1 warns that the NTLM mechanism requires special
 * handling of the crypto state to interop with Windows.  If the mechanism for
 * sc is SPNEGO, invoke a mechanism-specific operation on the context to reset
 * the RC4 state after producing or verifying a MIC.  Ignore a result of
 * GSS_S_UNAVAILABLE for compatibility with older versions of the mechanism
 * that do not support this functionality.
 */
static OM_uint32
ntlmssp_reset_crypto_state(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
                           OM_uint32 verify)
{
        OM_uint32 major, minor;
        gss_buffer_desc value;

        if (!g_OID_equal(sc->internal_mech, &gss_mech_ntlmssp_oid))
                return GSS_S_COMPLETE;

        value.length = sizeof(verify);
        value.value = &verify;
        major = gss_set_sec_context_option(&minor, &sc->ctx_handle,
                                           (gss_OID)&ntlmssp_reset_crypto_oid,
                                           &value);
        if (major == GSS_S_UNAVAILABLE)
                return GSS_S_COMPLETE;
        *minor_status = minor;
        return major;
}

/*
 * Both initiator and acceptor call here to verify and/or create mechListMIC,
 * and to consistency-check the MIC state.  handle_mic is invoked only if the
 * negotiated mech has completed and supports MICs.
 */
static OM_uint32
handle_mic(OM_uint32 *minor_status, gss_buffer_t mic_in,
           int send_mechtok, spnego_gss_ctx_id_t sc,
           gss_buffer_t *mic_out,
           OM_uint32 *negState, send_token_flag *tokflag)
{
        OM_uint32 ret;

        ret = GSS_S_FAILURE;
        *mic_out = GSS_C_NO_BUFFER;
        if (mic_in != GSS_C_NO_BUFFER) {
                if (sc->mic_rcvd) {
                        /* Reject MIC if we've already received a MIC. */
                        *negState = REJECT;
                        *tokflag = ERROR_TOKEN_SEND;
                        return GSS_S_DEFECTIVE_TOKEN;
                }
        } else if (sc->mic_reqd && !send_mechtok) {
                /*
                 * If the peer sends the final mechanism token, it
                 * must send the MIC with that token if the
                 * negotiation requires MICs.
                 */
                *negState = REJECT;
                *tokflag = ERROR_TOKEN_SEND;
                return GSS_S_DEFECTIVE_TOKEN;
        }
        ret = process_mic(minor_status, mic_in, sc, mic_out,
                          negState, tokflag);
        if (ret != GSS_S_COMPLETE) {
                return ret;
        }
        if (sc->mic_reqd) {
                assert(sc->mic_sent || sc->mic_rcvd);
        }
        if (sc->mic_sent && sc->mic_rcvd) {
                ret = GSS_S_COMPLETE;
                *negState = ACCEPT_COMPLETE;
                if (*mic_out == GSS_C_NO_BUFFER) {
                        /*
                         * We sent a MIC on the previous pass; we
                         * shouldn't be sending a mechanism token.
                         */
                        assert(!send_mechtok);
                        *tokflag = NO_TOKEN_SEND;
                } else {
                        *tokflag = CONT_TOKEN_SEND;
                }
        } else if (sc->mic_reqd) {
                *negState = ACCEPT_INCOMPLETE;
                ret = GSS_S_CONTINUE_NEEDED;
        } else if (*negState == ACCEPT_COMPLETE) {
                ret = GSS_S_COMPLETE;
        } else {
                ret = GSS_S_CONTINUE_NEEDED;
        }
        return ret;
}

/*
 * Perform the actual verification and/or generation of mechListMIC.
 */
static OM_uint32
process_mic(OM_uint32 *minor_status, gss_buffer_t mic_in,
            spnego_gss_ctx_id_t sc, gss_buffer_t *mic_out,
            OM_uint32 *negState, send_token_flag *tokflag)
{
        OM_uint32 ret, tmpmin;
        gss_qop_t qop_state;
        gss_buffer_desc tmpmic = GSS_C_EMPTY_BUFFER;

        ret = GSS_S_FAILURE;
        if (mic_in != GSS_C_NO_BUFFER) {
                ret = gss_verify_mic(minor_status, sc->ctx_handle,
                                     &sc->DER_mechTypes,
                                     mic_in, &qop_state);
                if (ret == GSS_S_COMPLETE)
                        ret = ntlmssp_reset_crypto_state(minor_status, sc, 1);
                if (ret != GSS_S_COMPLETE) {
                        *negState = REJECT;
                        *tokflag = ERROR_TOKEN_SEND;
                        return ret;
                }
                /* If we got a MIC, we must send a MIC. */
                sc->mic_reqd = 1;
                sc->mic_rcvd = 1;
        }
        if (sc->mic_reqd && !sc->mic_sent) {
                ret = gss_get_mic(minor_status, sc->ctx_handle,
                                  GSS_C_QOP_DEFAULT,
                                  &sc->DER_mechTypes,
                                  &tmpmic);
                if (ret == GSS_S_COMPLETE)
                        ret = ntlmssp_reset_crypto_state(minor_status, sc, 0);
                if (ret != GSS_S_COMPLETE) {
                        gss_release_buffer(&tmpmin, &tmpmic);
                        *tokflag = NO_TOKEN_SEND;
                        return ret;
                }
                *mic_out = malloc(sizeof(gss_buffer_desc));
                if (*mic_out == GSS_C_NO_BUFFER) {
                        gss_release_buffer(&tmpmin, &tmpmic);
                        *tokflag = NO_TOKEN_SEND;
                        return GSS_S_FAILURE;
                }
                **mic_out = tmpmic;
                sc->mic_sent = 1;
        }
        return GSS_S_COMPLETE;
}

/* Create a new SPNEGO context handle for the initial call to
 * spnego_gss_init_sec_context().  */
static OM_uint32
init_ctx_new(OM_uint32 *minor_status,
             spnego_gss_cred_id_t spcred,
             send_token_flag *tokflag,
             spnego_gss_ctx_id_t *sc_out)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = NULL;

        *sc_out = NULL;

        sc = create_spnego_ctx(1);
        if (sc == NULL)
                return GSS_S_FAILURE;

        /* determine negotiation mech set */
        ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_INITIATE);
        if (ret != GSS_S_COMPLETE)
                goto cleanup;

        /* Set an initial internal mech to make the first context token. */
        sc->internal_mech = &sc->mech_set->elements[0];

        if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) {
                ret = GSS_S_FAILURE;
                goto cleanup;
        }

        sc->ctx_handle = GSS_C_NO_CONTEXT;
        *sc_out = sc;
        sc = NULL;
        *tokflag = INIT_TOKEN_SEND;
        ret = GSS_S_COMPLETE;

cleanup:
        release_spnego_ctx(&sc);
        return ret;
}

/*
 * Called by second and later calls to spnego_gss_init_sec_context()
 * to decode reply and update state.
 */
static OM_uint32
init_ctx_cont(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
              gss_buffer_t buf, gss_buffer_t *responseToken,
              gss_buffer_t *mechListMIC, OM_uint32 *acc_negState,
              send_token_flag *tokflag)
{
        OM_uint32 ret, tmpmin;
        gss_OID supportedMech = GSS_C_NO_OID;
        struct k5input in;

        *acc_negState = UNSPECIFIED;
        *tokflag = ERROR_TOKEN_SEND;

        k5_input_init(&in, buf->value, buf->length);
        ret = get_negTokenResp(minor_status, &in, acc_negState, &supportedMech,
                               responseToken, mechListMIC);
        if (ret != GSS_S_COMPLETE)
                goto cleanup;

        /* Bail out now on a reject with no error token.  If we have an error
         * token, keep going and get a better error status from the mech. */
        if (*acc_negState == REJECT && *responseToken == GSS_C_NO_BUFFER) {
                if (!sc->nego_done) {
                        /* RFC 4178 says to return GSS_S_BAD_MECH on a
                         * mechanism negotiation failure. */
                        *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
                        map_errcode(minor_status);
                        ret = GSS_S_BAD_MECH;
                } else {
                        ret = GSS_S_FAILURE;
                }
                *tokflag = NO_TOKEN_SEND;
                goto cleanup;
        }
        /*
         * nego_done is false for the first call to init_ctx_cont()
         */
        if (!sc->nego_done) {
                ret = init_ctx_nego(minor_status, sc, *acc_negState,
                                    supportedMech, responseToken, mechListMIC,
                                    tokflag);
        } else if ((!sc->mech_complete && *responseToken == GSS_C_NO_BUFFER) ||
                   (sc->mech_complete && *responseToken != GSS_C_NO_BUFFER)) {
                /* Missing or spurious token from acceptor. */
                ret = GSS_S_DEFECTIVE_TOKEN;
        } else if (!sc->mech_complete ||
                   (sc->mic_reqd &&
                    (sc->ctx_flags & GSS_C_INTEG_FLAG))) {
                /* Not obviously done; we may decide we're done later in
                 * init_ctx_call_init or handle_mic. */
                *tokflag = CONT_TOKEN_SEND;
                ret = GSS_S_COMPLETE;
        } else {
                /* mech finished on last pass and no MIC required, so done. */
                *tokflag = NO_TOKEN_SEND;
                ret = GSS_S_COMPLETE;
        }
cleanup:
        if (supportedMech != GSS_C_NO_OID)
                generic_gss_release_oid(&tmpmin, &supportedMech);
        return ret;
}

/*
 * Consistency checking and mechanism negotiation handling for second
 * call of spnego_gss_init_sec_context().  Call init_ctx_reselect() to
 * update internal state if acceptor has counter-proposed.
 */
static OM_uint32
init_ctx_nego(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
              OM_uint32 acc_negState, gss_OID supportedMech,
              gss_buffer_t *responseToken, gss_buffer_t *mechListMIC,
              send_token_flag *tokflag)
{
        OM_uint32 ret;

        *tokflag = ERROR_TOKEN_SEND;
        ret = GSS_S_DEFECTIVE_TOKEN;

        /*
         * According to RFC 4178, both supportedMech and negState must be
         * present in the first acceptor token.  However, some Java
         * implementations include only a responseToken in the first
         * NegTokenResp.  In this case we can use sc->internal_mech as the
         * negotiated mechanism.  (We do not currently look at acc_negState
         * when continuing with the optimistic mechanism.)
         */
        if (supportedMech == GSS_C_NO_OID)
                supportedMech = sc->internal_mech;

        /*
         * If the mechanism we sent is not the mechanism returned from
         * the server, we need to handle the server's counter
         * proposal.  There is a bug in SAMBA servers that always send
         * the old Kerberos mech OID, even though we sent the new one.
         * So we will treat all the Kerberos mech OIDS as the same.
         */
        if (!(is_kerb_mech(supportedMech) &&
              is_kerb_mech(sc->internal_mech)) &&
            !g_OID_equal(supportedMech, sc->internal_mech)) {
                ret = init_ctx_reselect(minor_status, sc,
                                        acc_negState, supportedMech,
                                        responseToken, mechListMIC, tokflag);

        } else if (*responseToken == GSS_C_NO_BUFFER) {
                if (sc->mech_complete) {
                        /*
                         * Mech completed on first call to its
                         * init_sec_context().  Acceptor sends no mech
                         * token.
                         */
                        *tokflag = NO_TOKEN_SEND;
                        ret = GSS_S_COMPLETE;
                } else {
                        /*
                         * Reject missing mech token when optimistic
                         * mech selected.
                         */
                        *minor_status = ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR;
                        map_errcode(minor_status);
                        ret = GSS_S_DEFECTIVE_TOKEN;
                }
        } else if ((*responseToken)->length == 0 && sc->mech_complete) {
                /* Handle old IIS servers returning empty token instead of
                 * null tokens in the non-mutual auth case. */
                *tokflag = NO_TOKEN_SEND;
                ret = GSS_S_COMPLETE;
        } else if (sc->mech_complete) {
                /* Reject spurious mech token. */
                ret = GSS_S_DEFECTIVE_TOKEN;
        } else {
                *tokflag = CONT_TOKEN_SEND;
                ret = GSS_S_COMPLETE;
        }
        sc->nego_done = 1;
        return ret;
}

/*
 * Handle acceptor's counter-proposal of an alternative mechanism.
 */
static OM_uint32
init_ctx_reselect(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
                  OM_uint32 acc_negState, gss_OID supportedMech,
                  gss_buffer_t *responseToken, gss_buffer_t *mechListMIC,
                  send_token_flag *tokflag)
{
        OM_uint32 tmpmin;
        size_t i;

        gss_delete_sec_context(&tmpmin, &sc->ctx_handle,
                               GSS_C_NO_BUFFER);

        /* Find supportedMech in sc->mech_set. */
        for (i = 0; i < sc->mech_set->count; i++) {
                if (g_OID_equal(supportedMech, &sc->mech_set->elements[i]))
                        break;
        }
        if (i == sc->mech_set->count)
                return GSS_S_DEFECTIVE_TOKEN;
        sc->internal_mech = &sc->mech_set->elements[i];

        /*
         * A server conforming to RFC4178 MUST set REQUEST_MIC here, but
         * Windows Server 2003 and earlier implement (roughly) RFC2478 instead,
         * and send ACCEPT_INCOMPLETE.  Tolerate that only if we are falling
         * back to NTLMSSP.
         */
        if (acc_negState == ACCEPT_INCOMPLETE) {
                if (!g_OID_equal(supportedMech, &gss_mech_ntlmssp_oid))
                        return GSS_S_DEFECTIVE_TOKEN;
        } else if (acc_negState != REQUEST_MIC) {
                return GSS_S_DEFECTIVE_TOKEN;
        }

        sc->mech_complete = 0;
        sc->mic_reqd = (acc_negState == REQUEST_MIC);
        *tokflag = CONT_TOKEN_SEND;
        return GSS_S_COMPLETE;
}

/*
 * Wrap call to mechanism gss_init_sec_context() and update state
 * accordingly.
 */
static OM_uint32
init_ctx_call_init(OM_uint32 *minor_status,
                   spnego_gss_ctx_id_t sc,
                   spnego_gss_cred_id_t spcred,
                   OM_uint32 acc_negState,
                   gss_name_t target_name,
                   OM_uint32 req_flags,
                   OM_uint32 time_req,
                   gss_buffer_t mechtok_in,
                   gss_channel_bindings_t bindings,
                   gss_buffer_t mechtok_out,
                   OM_uint32 *time_rec,
                   send_token_flag *send_token)
{
        OM_uint32 ret, tmpret, tmpmin, mech_req_flags;
        gss_cred_id_t mcred;

        mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;

        mech_req_flags = req_flags;
        if (spcred == NULL || !spcred->no_ask_integ)
                mech_req_flags |= GSS_C_INTEG_FLAG;

        if (gss_oid_equal(sc->internal_mech, &negoex_mech)) {
                ret = negoex_init(minor_status, sc, mcred, target_name,
                                  mech_req_flags, time_req, mechtok_in,
                                  bindings, mechtok_out, time_rec);
        } else {
                ret = gss_init_sec_context(minor_status, mcred,
                                           &sc->ctx_handle, target_name,
                                           sc->internal_mech, mech_req_flags,
                                           time_req, bindings, mechtok_in,
                                           &sc->actual_mech, mechtok_out,
                                           &sc->ctx_flags, time_rec);
        }

        /* Bail out if the acceptor gave us an error token but the mech didn't
         * see it as an error. */
        if (acc_negState == REJECT && !GSS_ERROR(ret)) {
                ret = GSS_S_DEFECTIVE_TOKEN;
                goto fail;
        }

        if (ret == GSS_S_COMPLETE) {
                sc->mech_complete = 1;
                /*
                 * Microsoft SPNEGO implementations expect an even number of
                 * token exchanges.  So if we're sending a final token, ask for
                 * a zero-length token back from the server.  Also ask for a
                 * token back if this is the first token or if a MIC exchange
                 * is required.
                 */
                if (*send_token == CONT_TOKEN_SEND &&
                    mechtok_out->length == 0 &&
                    (!sc->mic_reqd || !(sc->ctx_flags & GSS_C_INTEG_FLAG)))
                        *send_token = NO_TOKEN_SEND;

                return GSS_S_COMPLETE;
        }

        if (ret == GSS_S_CONTINUE_NEEDED)
                return GSS_S_COMPLETE;

        if (*send_token != INIT_TOKEN_SEND) {
                *send_token = ERROR_TOKEN_SEND;
                return ret;
        }

        /*
         * Since this is the first token, we can fall back to later mechanisms
         * in the list.  Since the mechanism list is expected to be short, we
         * can do this with recursion.  If all mechanisms produce errors, the
         * caller should get the error from the first mech in the list.
         */
        gssalloc_free(sc->mech_set->elements->elements);
        memmove(sc->mech_set->elements, sc->mech_set->elements + 1,
                --sc->mech_set->count * sizeof(*sc->mech_set->elements));
        if (sc->mech_set->count == 0)
                goto fail;
        gss_release_buffer(&tmpmin, &sc->DER_mechTypes);
        if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0)
                goto fail;
        gss_delete_sec_context(&tmpmin, &sc->ctx_handle, GSS_C_NO_BUFFER);
        tmpret = init_ctx_call_init(&tmpmin, sc, spcred, acc_negState,
                                    target_name, req_flags, time_req,
                                    mechtok_in, bindings, mechtok_out,
                                    time_rec, send_token);
        if (HARD_ERROR(tmpret))
                goto fail;
        *minor_status = tmpmin;
        return tmpret;

fail:
        /* Don't output token on error from first call. */
        *send_token = NO_TOKEN_SEND;
        return ret;
}

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_init_sec_context(
                        OM_uint32 *minor_status,
                        gss_cred_id_t claimant_cred_handle,
                        gss_ctx_id_t *context_handle,
                        gss_name_t target_name,
                        gss_OID mech_type,
                        OM_uint32 req_flags,
                        OM_uint32 time_req,
                        gss_channel_bindings_t bindings,
                        gss_buffer_t input_token,
                        gss_OID *actual_mech,
                        gss_buffer_t output_token,
                        OM_uint32 *ret_flags,
                        OM_uint32 *time_rec)
{
        send_token_flag send_token = NO_TOKEN_SEND;
        OM_uint32 tmpmin, ret, negState = UNSPECIFIED, acc_negState;
        gss_buffer_t mechtok_in, mechListMIC_in, mechListMIC_out;
        gss_buffer_desc mechtok_out = GSS_C_EMPTY_BUFFER;
        spnego_gss_cred_id_t spcred = NULL;
        spnego_gss_ctx_id_t spnego_ctx = NULL;

        dsyslog("Entering init_sec_context\n");

        mechtok_in = mechListMIC_out = mechListMIC_in = GSS_C_NO_BUFFER;

        /*
         * This function works in three steps:
         *
         *   1. Perform mechanism negotiation.
         *   2. Invoke the negotiated or optimistic mech's gss_init_sec_context
         *      function and examine the results.
         *   3. Process or generate MICs if necessary.
         *
         * The three steps share responsibility for determining when the
         * exchange is complete.  If the selected mech completed in a previous
         * call and no MIC exchange is expected, then step 1 will decide.  If
         * the selected mech completes in this call and no MIC exchange is
         * expected, then step 2 will decide.  If a MIC exchange is expected,
         * then step 3 will decide.  If an error occurs in any step, the
         * exchange will be aborted, possibly with an error token.
         *
         * negState determines the state of the negotiation, and is
         * communicated to the acceptor if a continuing token is sent.
         * send_token is used to indicate what type of token, if any, should be
         * generated.
         */

        /* Validate arguments. */
        if (minor_status != NULL)
                *minor_status = 0;
        if (output_token != GSS_C_NO_BUFFER) {
                output_token->length = 0;
                output_token->value = NULL;
        }
        if (minor_status == NULL ||
            output_token == GSS_C_NO_BUFFER ||
            context_handle == NULL)
                return GSS_S_CALL_INACCESSIBLE_WRITE;

        if (actual_mech != NULL)
                *actual_mech = GSS_C_NO_OID;
        if (time_rec != NULL)
                *time_rec = 0;

        /* Step 1: perform mechanism negotiation. */
        spcred = (spnego_gss_cred_id_t)claimant_cred_handle;
        spnego_ctx = (spnego_gss_ctx_id_t)*context_handle;
        if (spnego_ctx == NULL) {
                ret = init_ctx_new(minor_status, spcred, &send_token,
                                   &spnego_ctx);
                if (ret != GSS_S_COMPLETE)
                        goto cleanup;
                *context_handle = (gss_ctx_id_t)spnego_ctx;
                acc_negState = UNSPECIFIED;
        } else {
                ret = init_ctx_cont(minor_status, spnego_ctx, input_token,
                                    &mechtok_in, &mechListMIC_in,
                                    &acc_negState, &send_token);
                if (ret != GSS_S_COMPLETE)
                        goto cleanup;
        }

        /* Step 2: invoke the selected or optimistic mechanism's
         * gss_init_sec_context function, if it didn't complete previously. */
        if (!spnego_ctx->mech_complete) {
                ret = init_ctx_call_init(minor_status, spnego_ctx, spcred,
                                         acc_negState, target_name, req_flags,
                                         time_req, mechtok_in, bindings,
                                         &mechtok_out, time_rec, &send_token);
                if (ret != GSS_S_COMPLETE)
                        goto cleanup;

                /* Give the mechanism a chance to force a mechlistMIC. */
                if (mech_requires_mechlistMIC(spnego_ctx))
                        spnego_ctx->mic_reqd = 1;
        }

        /* Step 3: process or generate the MIC, if the negotiated mech is
         * complete and supports MICs.  Also decide the outgoing negState. */
        negState = ACCEPT_INCOMPLETE;
        if (spnego_ctx->mech_complete &&
            (spnego_ctx->ctx_flags & GSS_C_INTEG_FLAG)) {

                ret = handle_mic(minor_status,
                                 mechListMIC_in,
                                 (mechtok_out.length != 0),
                                 spnego_ctx, &mechListMIC_out,
                                 &negState, &send_token);
                if (HARD_ERROR(ret))
                        goto cleanup;
        }

        if (ret_flags != NULL)
                *ret_flags = spnego_ctx->ctx_flags & ~GSS_C_PROT_READY_FLAG;

        ret = (send_token == NO_TOKEN_SEND || negState == ACCEPT_COMPLETE) ?
                GSS_S_COMPLETE : GSS_S_CONTINUE_NEEDED;

cleanup:
        if (send_token == INIT_TOKEN_SEND) {
                if (make_spnego_tokenInit_msg(spnego_ctx,
                                              0,
                                              mechListMIC_out,
                                              req_flags,
                                              &mechtok_out, send_token,
                                              output_token) < 0) {
                        ret = GSS_S_FAILURE;
                }
        } else if (send_token != NO_TOKEN_SEND) {
                if (send_token == ERROR_TOKEN_SEND)
                        negState = REJECT;
                if (make_spnego_tokenTarg_msg(negState, GSS_C_NO_OID,
                                              &mechtok_out, mechListMIC_out,
                                              send_token,
                                              output_token) < 0) {
                        ret = GSS_S_FAILURE;
                }
        }
        gss_release_buffer(&tmpmin, &mechtok_out);
        if (ret == GSS_S_COMPLETE) {
                spnego_ctx->opened = 1;
                if (actual_mech != NULL)
                        *actual_mech = spnego_ctx->actual_mech;
                /* Get an updated lifetime if we didn't call into the mech. */
                if (time_rec != NULL && *time_rec == 0) {
                        (void) gss_context_time(&tmpmin,
                                                spnego_ctx->ctx_handle,
                                                time_rec);
                }
        } else if (ret != GSS_S_CONTINUE_NEEDED) {
                if (spnego_ctx != NULL) {
                        gss_delete_sec_context(&tmpmin,
                                               &spnego_ctx->ctx_handle,
                                               GSS_C_NO_BUFFER);
                        release_spnego_ctx(&spnego_ctx);
                }
                *context_handle = GSS_C_NO_CONTEXT;
        }
        if (mechtok_in != GSS_C_NO_BUFFER) {
                gss_release_buffer(&tmpmin, mechtok_in);
                free(mechtok_in);
        }
        if (mechListMIC_in != GSS_C_NO_BUFFER) {
                gss_release_buffer(&tmpmin, mechListMIC_in);
                free(mechListMIC_in);
        }
        if (mechListMIC_out != GSS_C_NO_BUFFER) {
                gss_release_buffer(&tmpmin, mechListMIC_out);
                free(mechListMIC_out);
        }
        return ret;
} /* init_sec_context */

/* We don't want to import KRB5 headers here */
static const gss_OID_desc gss_mech_krb5_oid =
        { 9, "\052\206\110\206\367\022\001\002\002" };
static const gss_OID_desc gss_mech_krb5_wrong_oid =
        { 9, "\052\206\110\202\367\022\001\002\002" };

/*
 * NegHints ::= SEQUENCE {
 *    hintName       [0]  GeneralString      OPTIONAL,
 *    hintAddress    [1]  OCTET STRING       OPTIONAL
 * }
 */

#define HOST_PREFIX     "host@"
#define HOST_PREFIX_LEN (sizeof(HOST_PREFIX) - 1)

/* Encode the dummy hintname string (as specified in [MS-SPNG]) into a
 * DER-encoded [0] tagged GeneralString, and place the result in *outbuf. */
static int
make_NegHints(OM_uint32 *minor_status, gss_buffer_t *outbuf)
{
        OM_uint32 major_status;
        size_t hint_len, tlen;
        uint8_t *t;
        const char *hintname = "not_defined_in_RFC4178@please_ignore";
        const size_t hintname_len = strlen(hintname);
        struct k5buf buf;

        *outbuf = GSS_C_NO_BUFFER;
        major_status = GSS_S_FAILURE;

        hint_len = k5_der_value_len(hintname_len);
        tlen = k5_der_value_len(hint_len);

        t = gssalloc_malloc(tlen);
        if (t == NULL) {
                *minor_status = ENOMEM;
                goto errout;
        }
        k5_buf_init_fixed(&buf, t, tlen);

        k5_der_add_taglen(&buf, CONTEXT | 0x00, hint_len);
        k5_der_add_value(&buf, GENERAL_STRING, hintname, hintname_len);
        assert(buf.len == tlen);

        *outbuf = (gss_buffer_t)malloc(sizeof(gss_buffer_desc));
        if (*outbuf == NULL) {
                *minor_status = ENOMEM;
                goto errout;
        }
        (*outbuf)->value = (void *)t;
        (*outbuf)->length = tlen;

        t = NULL; /* don't free */

        *minor_status = 0;
        major_status = GSS_S_COMPLETE;

errout:
        if (t != NULL) {
                free(t);
        }

        return (major_status);
}

/*
 * Create a new SPNEGO context handle for the initial call to
 * spnego_gss_accept_sec_context() when the request is empty.  For empty
 * requests, we implement the Microsoft NegHints extension to SPNEGO for
 * compatibility with some versions of Samba.  See:
 * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/8e71cf53-e867-4b79-b5b5-38c92be3d472
 */
static OM_uint32
acc_ctx_hints(OM_uint32 *minor_status,
              spnego_gss_cred_id_t spcred,
              gss_buffer_t *mechListMIC,
              OM_uint32 *negState,
              send_token_flag *return_token,
              spnego_gss_ctx_id_t *sc_out)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = NULL;

        *mechListMIC = GSS_C_NO_BUFFER;
        *return_token = NO_TOKEN_SEND;
        *negState = REJECT;
        *minor_status = 0;
        *sc_out = NULL;

        ret = make_NegHints(minor_status, mechListMIC);
        if (ret != GSS_S_COMPLETE)
                goto cleanup;

        sc = create_spnego_ctx(0);
        if (sc == NULL) {
                ret = GSS_S_FAILURE;
                goto cleanup;
        }

        ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_ACCEPT);
        if (ret != GSS_S_COMPLETE)
                goto cleanup;

        if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) {
                ret = GSS_S_FAILURE;
                goto cleanup;
        }
        sc->internal_mech = GSS_C_NO_OID;

        *negState = ACCEPT_INCOMPLETE;
        *return_token = INIT_TOKEN_SEND;
        sc->firstpass = 1;
        *sc_out = sc;
        sc = NULL;
        ret = GSS_S_COMPLETE;

cleanup:
        release_spnego_ctx(&sc);

        return ret;
}

/*
 * Create a new SPNEGO context handle for the initial call to
 * spnego_gss_accept_sec_context().  Set negState to REJECT if the token is
 * defective, else ACCEPT_INCOMPLETE or REQUEST_MIC, depending on whether
 * the initiator's preferred mechanism is supported.
 */
static OM_uint32
acc_ctx_new(OM_uint32 *minor_status,
            gss_buffer_t buf,
            spnego_gss_cred_id_t spcred,
            gss_buffer_t *mechToken,
            gss_buffer_t *mechListMIC,
            OM_uint32 *negState,
            send_token_flag *return_token,
            spnego_gss_ctx_id_t *sc_out)
{
        OM_uint32 tmpmin, ret, req_flags;
        gss_OID_set mechTypes;
        gss_buffer_desc der_mechTypes;
        gss_OID mech_wanted;
        spnego_gss_ctx_id_t sc = NULL;

        ret = GSS_S_DEFECTIVE_TOKEN;
        der_mechTypes.length = 0;
        der_mechTypes.value = NULL;
        *mechToken = *mechListMIC = GSS_C_NO_BUFFER;
        mechTypes = GSS_C_NO_OID_SET;
        *return_token = ERROR_TOKEN_SEND;
        *negState = REJECT;
        *minor_status = 0;

        ret = get_negTokenInit(minor_status, buf, &der_mechTypes,
                               &mechTypes, &req_flags,
                               mechToken, mechListMIC);
        if (ret != GSS_S_COMPLETE) {
                goto cleanup;
        }

        sc = create_spnego_ctx(0);
        if (sc == NULL) {
                ret = GSS_S_FAILURE;
                *return_token = NO_TOKEN_SEND;
                goto cleanup;
        }

        ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_ACCEPT);
        if (ret != GSS_S_COMPLETE) {
                *return_token = NO_TOKEN_SEND;
                goto cleanup;
        }
        /*
         * Select the best match between the list of mechs
         * that the initiator requested and the list that
         * the acceptor will support.
         */
        mech_wanted = negotiate_mech(sc, mechTypes, negState);
        if (*negState == REJECT) {
                ret = GSS_S_BAD_MECH;
                goto cleanup;
        }

        sc->internal_mech = mech_wanted;
        sc->DER_mechTypes = der_mechTypes;
        der_mechTypes.length = 0;
        der_mechTypes.value = NULL;

        if (*negState == REQUEST_MIC)
                sc->mic_reqd = 1;

        *return_token = INIT_TOKEN_SEND;
        sc->firstpass = 1;
        *sc_out = sc;
        sc = NULL;
        ret = GSS_S_COMPLETE;

cleanup:
        release_spnego_ctx(&sc);
        gss_release_oid_set(&tmpmin, &mechTypes);
        if (der_mechTypes.length != 0)
                gss_release_buffer(&tmpmin, &der_mechTypes);

        return ret;
}

static OM_uint32
acc_ctx_cont(OM_uint32 *minstat,
             gss_buffer_t buf,
             spnego_gss_ctx_id_t sc,
             gss_buffer_t *responseToken,
             gss_buffer_t *mechListMIC,
             OM_uint32 *negState,
             send_token_flag *return_token)
{
        OM_uint32 ret, tmpmin;
        gss_OID supportedMech;
        struct k5input in;

        ret = GSS_S_DEFECTIVE_TOKEN;
        *negState = REJECT;
        *minstat = 0;
        supportedMech = GSS_C_NO_OID;
        *return_token = ERROR_TOKEN_SEND;
        *responseToken = *mechListMIC = GSS_C_NO_BUFFER;

        k5_input_init(&in, buf->value, buf->length);

        /* Attempt to work with old Sun SPNEGO. */
        if (in.len > 0 && *in.ptr == HEADER_ID) {
                ret = verify_token_header(&in, gss_mech_spnego);
                if (ret) {
                        *minstat = ret;
                        return GSS_S_DEFECTIVE_TOKEN;
                }
        }

        ret = get_negTokenResp(minstat, &in, negState, &supportedMech,
                               responseToken, mechListMIC);
        if (ret != GSS_S_COMPLETE)
                goto cleanup;

        if (*responseToken == GSS_C_NO_BUFFER &&
            *mechListMIC == GSS_C_NO_BUFFER) {

                ret = GSS_S_DEFECTIVE_TOKEN;
                goto cleanup;
        }
        if (supportedMech != GSS_C_NO_OID) {
                ret = GSS_S_DEFECTIVE_TOKEN;
                goto cleanup;
        }
        sc->firstpass = 0;
        *negState = ACCEPT_INCOMPLETE;
        *return_token = CONT_TOKEN_SEND;
cleanup:
        if (supportedMech != GSS_C_NO_OID) {
                generic_gss_release_oid(&tmpmin, &supportedMech);
        }
        return ret;
}

/*
 * Verify that mech OID is either exactly the same as the negotiated
 * mech OID, or is a mech OID supported by the negotiated mech.  MS
 * implementations can list a most preferred mech using an incorrect
 * krb5 OID while emitting a krb5 initiator mech token having the
 * correct krb5 mech OID.
 */
static OM_uint32
acc_ctx_vfy_oid(OM_uint32 *minor_status,
                spnego_gss_ctx_id_t sc, gss_OID mechoid,
                OM_uint32 *negState, send_token_flag *tokflag)
{
        OM_uint32 ret, tmpmin;
        gss_mechanism mech = NULL;
        gss_OID_set mech_set = GSS_C_NO_OID_SET;
        int present = 0;

        if (g_OID_equal(sc->internal_mech, mechoid))
                return GSS_S_COMPLETE;

        mech = gssint_get_mechanism(sc->internal_mech);
        if (mech == NULL || mech->gss_indicate_mechs == NULL) {
                *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
                map_errcode(minor_status);
                *negState = REJECT;
                *tokflag = ERROR_TOKEN_SEND;
                return GSS_S_BAD_MECH;
        }
        ret = mech->gss_indicate_mechs(minor_status, &mech_set);
        if (ret != GSS_S_COMPLETE) {
                *tokflag = NO_TOKEN_SEND;
                map_error(minor_status, mech);
                goto cleanup;
        }
        ret = gss_test_oid_set_member(minor_status, mechoid,
                                      mech_set, &present);
        if (ret != GSS_S_COMPLETE)
                goto cleanup;
        if (!present) {
                *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
                map_errcode(minor_status);
                *negState = REJECT;
                *tokflag = ERROR_TOKEN_SEND;
                ret = GSS_S_BAD_MECH;
        }
cleanup:
        gss_release_oid_set(&tmpmin, &mech_set);
        return ret;
}
#ifndef LEAN_CLIENT
/*
 * Wrap call to gss_accept_sec_context() and update state
 * accordingly.
 */
static OM_uint32
acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
                 spnego_gss_cred_id_t spcred, gss_buffer_t mechtok_in,
                 gss_channel_bindings_t bindings, gss_buffer_t mechtok_out,
                 OM_uint32 *time_rec, OM_uint32 *negState,
                 send_token_flag *tokflag)
{
        OM_uint32 ret, tmpmin;
        gss_OID_desc mechoid;
        gss_cred_id_t mcred;
        int negoex = gss_oid_equal(sc->internal_mech, &negoex_mech);

        if (sc->ctx_handle == GSS_C_NO_CONTEXT && !negoex) {
                /*
                 * mechoid is an alias; don't free it.
                 */
                ret = gssint_get_mech_type(&mechoid, mechtok_in);
                if (ret != GSS_S_COMPLETE) {
                        *tokflag = NO_TOKEN_SEND;
                        return ret;
                }
                ret = acc_ctx_vfy_oid(minor_status, sc, &mechoid,
                                      negState, tokflag);
                if (ret != GSS_S_COMPLETE)
                        return ret;
        }

        mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
        if (negoex) {
                ret = negoex_accept(minor_status, sc, mcred, mechtok_in,
                                    bindings, mechtok_out, time_rec);
        } else {
                (void) gss_release_name(&tmpmin, &sc->internal_name);
                (void) gss_release_cred(&tmpmin, &sc->deleg_cred);
                ret = gss_accept_sec_context(minor_status, &sc->ctx_handle,
                                             mcred, mechtok_in, bindings,
                                             &sc->internal_name,
                                             &sc->actual_mech, mechtok_out,
                                             &sc->ctx_flags, time_rec,
                                             &sc->deleg_cred);
        }
        if (ret == GSS_S_COMPLETE) {
#ifdef MS_BUG_TEST
                /*
                 * Force MIC to be not required even if we previously
                 * requested a MIC.
                 */
                char *envstr = getenv("MS_FORCE_NO_MIC");

                if (envstr != NULL && strcmp(envstr, "1") == 0 &&
                    !(sc->ctx_flags & GSS_C_MUTUAL_FLAG) &&
                    sc->mic_reqd) {

                        sc->mic_reqd = 0;
                }
#endif
                sc->mech_complete = 1;

                if (!sc->mic_reqd ||
                    !(sc->ctx_flags & GSS_C_INTEG_FLAG)) {
                        /* No MIC exchange required, so we're done. */
                        *negState = ACCEPT_COMPLETE;
                        ret = GSS_S_COMPLETE;
                } else {
                        /* handle_mic will decide if we're done. */
                        ret = GSS_S_CONTINUE_NEEDED;
                }
        } else if (ret != GSS_S_CONTINUE_NEEDED) {
                *negState = REJECT;
                *tokflag = ERROR_TOKEN_SEND;
        }
        return ret;
}

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_accept_sec_context(
                            OM_uint32 *minor_status,
                            gss_ctx_id_t *context_handle,
                            gss_cred_id_t verifier_cred_handle,
                            gss_buffer_t input_token,
                            gss_channel_bindings_t bindings,
                            gss_name_t *src_name,
                            gss_OID *mech_type,
                            gss_buffer_t output_token,
                            OM_uint32 *ret_flags,
                            OM_uint32 *time_rec,
                            gss_cred_id_t *delegated_cred_handle)
{
        OM_uint32 ret, tmpmin, negState;
        send_token_flag return_token;
        gss_buffer_t mechtok_in, mic_in, mic_out;
        gss_buffer_desc mechtok_out = GSS_C_EMPTY_BUFFER;
        spnego_gss_ctx_id_t sc = NULL;
        spnego_gss_cred_id_t spcred = NULL;
        int sendTokenInit = 0, tmpret;

        mechtok_in = mic_in = mic_out = GSS_C_NO_BUFFER;

        /*
         * This function works in three steps:
         *
         *   1. Perform mechanism negotiation.
         *   2. Invoke the negotiated mech's gss_accept_sec_context function
         *      and examine the results.
         *   3. Process or generate MICs if necessary.
         *
         * Step one determines whether the negotiation requires a MIC exchange,
         * while steps two and three share responsibility for determining when
         * the exchange is complete.  If the selected mech completes in this
         * call and no MIC exchange is expected, then step 2 will decide.  If a
         * MIC exchange is expected, then step 3 will decide.  If an error
         * occurs in any step, the exchange will be aborted, possibly with an
         * error token.
         *
         * negState determines the state of the negotiation, and is
         * communicated to the acceptor if a continuing token is sent.
         * return_token is used to indicate what type of token, if any, should
         * be generated.
         */

        /* Validate arguments. */
        if (minor_status != NULL)
                *minor_status = 0;
        if (output_token != GSS_C_NO_BUFFER) {
                output_token->length = 0;
                output_token->value = NULL;
        }
        if (src_name != NULL)
                *src_name = GSS_C_NO_NAME;
        if (mech_type != NULL)
                *mech_type = GSS_C_NO_OID;
        if (time_rec != NULL)
                *time_rec = 0;
        if (ret_flags != NULL)
                *ret_flags = 0;
        if (delegated_cred_handle != NULL)
                *delegated_cred_handle = GSS_C_NO_CREDENTIAL;

        if (minor_status == NULL ||
            output_token == GSS_C_NO_BUFFER ||
            context_handle == NULL)
                return GSS_S_CALL_INACCESSIBLE_WRITE;

        if (input_token == GSS_C_NO_BUFFER)
                return GSS_S_CALL_INACCESSIBLE_READ;

        /* Step 1: Perform mechanism negotiation. */
        sc = (spnego_gss_ctx_id_t)*context_handle;
        spcred = (spnego_gss_cred_id_t)verifier_cred_handle;
        if (sc == NULL && input_token->length == 0) {
                /* Process a request for NegHints. */
                ret = acc_ctx_hints(minor_status, spcred, &mic_out, &negState,
                                    &return_token, &sc);
                if (ret != GSS_S_COMPLETE)
                        goto cleanup;
                *context_handle = (gss_ctx_id_t)sc;
                sendTokenInit = 1;
                ret = GSS_S_CONTINUE_NEEDED;
        } else if (sc == NULL || sc->internal_mech == GSS_C_NO_OID) {
                if (sc != NULL) {
                        /* Discard the context from the NegHints request. */
                        release_spnego_ctx(&sc);
                        *context_handle = GSS_C_NO_CONTEXT;
                }
                /* Process an initial token; can set negState to
                 * REQUEST_MIC. */
                ret = acc_ctx_new(minor_status, input_token, spcred,
                                  &mechtok_in, &mic_in, &negState,
                                  &return_token, &sc);
                if (ret != GSS_S_COMPLETE)
                        goto cleanup;
                *context_handle = (gss_ctx_id_t)sc;
                ret = GSS_S_CONTINUE_NEEDED;
        } else {
                /* Process a response token.  Can set negState to
                 * ACCEPT_INCOMPLETE. */
                ret = acc_ctx_cont(minor_status, input_token, sc, &mechtok_in,
                                   &mic_in, &negState, &return_token);
                if (ret != GSS_S_COMPLETE)
                        goto cleanup;
                ret = GSS_S_CONTINUE_NEEDED;
        }

        /* Step 2: invoke the negotiated mechanism's gss_accept_sec_context
         * function. */
        /*
         * Handle mechtok_in and mic_in only if they are
         * present in input_token.  If neither is present, whether
         * this is an error depends on whether this is the first
         * round-trip.  RET is set to a default value according to
         * whether it is the first round-trip.
         */
        if (negState != REQUEST_MIC && mechtok_in != GSS_C_NO_BUFFER) {
                ret = acc_ctx_call_acc(minor_status, sc, spcred, mechtok_in,
                                       bindings, &mechtok_out, time_rec,
                                       &negState, &return_token);
        }

        /* Step 3: process or generate the MIC, if the negotiated mech is
         * complete and supports MICs. */
        if (!HARD_ERROR(ret) && sc->mech_complete &&
            (sc->ctx_flags & GSS_C_INTEG_FLAG)) {

                ret = handle_mic(minor_status, mic_in,
                                 (mechtok_out.length != 0),
                                 sc, &mic_out,
                                 &negState, &return_token);
        }

        if (!HARD_ERROR(ret) && ret_flags != NULL)
                *ret_flags = sc->ctx_flags & ~GSS_C_PROT_READY_FLAG;

cleanup:
        if (return_token == INIT_TOKEN_SEND && sendTokenInit) {
                assert(sc != NULL);
                tmpret = make_spnego_tokenInit_msg(sc, 1, mic_out, 0,
                                                   GSS_C_NO_BUFFER,
                                                   return_token, output_token);
                if (tmpret < 0)
                        ret = GSS_S_FAILURE;
        } else if (return_token != NO_TOKEN_SEND &&
                   return_token != CHECK_MIC) {
                tmpret = make_spnego_tokenTarg_msg(negState,
                                                   sc ? sc->internal_mech :
                                                   GSS_C_NO_OID,
                                                   &mechtok_out, mic_out,
                                                   return_token,
                                                   output_token);
                if (tmpret < 0)
                        ret = GSS_S_FAILURE;
        }
        if (ret == GSS_S_COMPLETE) {
                sc->opened = 1;
                if (sc->internal_name != GSS_C_NO_NAME &&
                    src_name != NULL) {
                        *src_name = sc->internal_name;
                        sc->internal_name = GSS_C_NO_NAME;
                }
                if (mech_type != NULL)
                        *mech_type = sc->actual_mech;
                /* Get an updated lifetime if we didn't call into the mech. */
                if (time_rec != NULL && *time_rec == 0) {
                        (void) gss_context_time(&tmpmin, sc->ctx_handle,
                                                time_rec);
                }
                if (delegated_cred_handle != NULL) {
                        *delegated_cred_handle = sc->deleg_cred;
                        sc->deleg_cred = GSS_C_NO_CREDENTIAL;
                }
        } else if (ret != GSS_S_CONTINUE_NEEDED) {
                if (sc != NULL) {
                        gss_delete_sec_context(&tmpmin, &sc->ctx_handle,
                                               GSS_C_NO_BUFFER);
                        release_spnego_ctx(&sc);
                }
                *context_handle = GSS_C_NO_CONTEXT;
        }
        gss_release_buffer(&tmpmin, &mechtok_out);
        if (mechtok_in != GSS_C_NO_BUFFER) {
                gss_release_buffer(&tmpmin, mechtok_in);
                free(mechtok_in);
        }
        if (mic_in != GSS_C_NO_BUFFER) {
                gss_release_buffer(&tmpmin, mic_in);
                free(mic_in);
        }
        if (mic_out != GSS_C_NO_BUFFER) {
                gss_release_buffer(&tmpmin, mic_out);
                free(mic_out);
        }
        return ret;
}
#endif /*  LEAN_CLIENT */

static struct {
        OM_uint32 status;
        const char *msg;
} msg_table[] = {
        { ERR_SPNEGO_NO_MECHS_AVAILABLE,
          N_("SPNEGO cannot find mechanisms to negotiate") },
        { ERR_SPNEGO_NO_CREDS_ACQUIRED,
          N_("SPNEGO failed to acquire creds") },
        { ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR,
          N_("SPNEGO acceptor did not select a mechanism") },
        { ERR_SPNEGO_NEGOTIATION_FAILED,
          N_("SPNEGO failed to negotiate a mechanism") },
        { ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR,
          N_("SPNEGO acceptor did not return a valid token") },
        { ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE,
          N_("Invalid NegoEx signature") },
        { ERR_NEGOEX_INVALID_MESSAGE_TYPE,
          N_("Invalid NegoEx message type") },
        { ERR_NEGOEX_INVALID_MESSAGE_SIZE,
          N_("Invalid NegoEx message size") },
        { ERR_NEGOEX_INVALID_CONVERSATION_ID,
          N_("Invalid NegoEx conversation ID") },
        { ERR_NEGOEX_AUTH_SCHEME_NOT_FOUND,
          N_("NegoEx authentication scheme not found") },
        { ERR_NEGOEX_MISSING_NEGO_MESSAGE,
          N_("Missing NegoEx negotiate message") },
        { ERR_NEGOEX_MISSING_AP_REQUEST_MESSAGE,
          N_("Missing NegoEx authentication protocol request message") },
        { ERR_NEGOEX_NO_AVAILABLE_MECHS,
          N_("No mutually supported NegoEx authentication schemes") },
        { ERR_NEGOEX_NO_VERIFY_KEY,
          N_("No NegoEx verify key") },
        { ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME,
          N_("Unknown NegoEx checksum scheme") },
        { ERR_NEGOEX_INVALID_CHECKSUM,
          N_("Invalid NegoEx checksum") },
        { ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION,
          N_("Unsupported critical NegoEx extension") },
        { ERR_NEGOEX_UNSUPPORTED_VERSION,
          N_("Unsupported NegoEx version") },
        { ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE,
          N_("NegoEx message out of sequence") },
};

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_display_status(
                OM_uint32 *minor_status,
                OM_uint32 status_value,
                int status_type,
                gss_OID mech_type,
                OM_uint32 *message_context,
                gss_buffer_t status_string)
{
        OM_uint32 maj = GSS_S_COMPLETE;
        const char *msg;
        size_t i;
        int ret;

        *message_context = 0;
        for (i = 0; i < sizeof(msg_table) / sizeof(*msg_table); i++) {
                if (status_value == msg_table[i].status) {
                        msg = dgettext(KRB5_TEXTDOMAIN, msg_table[i].msg);
                        *status_string = make_err_msg(msg);
                        return GSS_S_COMPLETE;
                }
        }

        /* Not one of our minor codes; might be from a mech.  Call back
         * to gss_display_status, but first check for recursion. */
        if (k5_getspecific(K5_KEY_GSS_SPNEGO_STATUS) != NULL) {
                /* Perhaps we returned a com_err code like ENOMEM. */
                const char *err = error_message(status_value);
                *status_string = make_err_msg(err);
                return GSS_S_COMPLETE;
        }
        /* Set a non-null pointer value; doesn't matter which one. */
        ret = k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, &ret);
        if (ret != 0) {
                *minor_status = ret;
                return GSS_S_FAILURE;
        }

        maj = gss_display_status(minor_status, status_value,
                                 status_type, mech_type,
                                 message_context, status_string);
        /* This is unlikely to fail; not much we can do if it does. */
        (void)k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, NULL);

        return maj;
}


/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_import_name(
                    OM_uint32 *minor_status,
                    gss_buffer_t input_name_buffer,
                    gss_OID input_name_type,
                    gss_name_t *output_name)
{
        OM_uint32 status;

        dsyslog("Entering import_name\n");

        status = gss_import_name(minor_status, input_name_buffer,
                        input_name_type, output_name);

        dsyslog("Leaving import_name\n");
        return (status);
}

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_release_name(
                        OM_uint32 *minor_status,
                        gss_name_t *input_name)
{
        OM_uint32 status;

        dsyslog("Entering release_name\n");

        status = gss_release_name(minor_status, input_name);

        dsyslog("Leaving release_name\n");
        return (status);
}

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_duplicate_name(
                        OM_uint32 *minor_status,
                        const gss_name_t input_name,
                        gss_name_t *output_name)
{
        OM_uint32 status;

        dsyslog("Entering duplicate_name\n");

        status = gss_duplicate_name(minor_status, input_name, output_name);

        dsyslog("Leaving duplicate_name\n");
        return (status);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_cred(
                        OM_uint32 *minor_status,
                        gss_cred_id_t cred_handle,
                        gss_name_t *name,
                        OM_uint32 *lifetime,
                        int *cred_usage,
                        gss_OID_set *mechanisms)
{
        OM_uint32 status;
        spnego_gss_cred_id_t spcred = NULL;
        gss_cred_id_t creds = GSS_C_NO_CREDENTIAL;
        OM_uint32 tmp_minor_status;
        OM_uint32 initiator_lifetime, acceptor_lifetime;

        dsyslog("Entering inquire_cred\n");

        /*
         * To avoid infinite recursion, if GSS_C_NO_CREDENTIAL is
         * supplied we call gss_inquire_cred_by_mech() on the
         * first non-SPNEGO mechanism.
         */
        spcred = (spnego_gss_cred_id_t)cred_handle;
        if (spcred == NULL) {
                status = get_available_mechs(minor_status,
                        GSS_C_NO_NAME,
                        GSS_C_BOTH,
                        GSS_C_NO_CRED_STORE,
                        &creds,
                        mechanisms, NULL);
                if (status != GSS_S_COMPLETE) {
                        dsyslog("Leaving inquire_cred\n");
                        return (status);
                }

                if ((*mechanisms)->count == 0) {
                        gss_release_cred(&tmp_minor_status, &creds);
                        gss_release_oid_set(&tmp_minor_status, mechanisms);
                        dsyslog("Leaving inquire_cred\n");
                        return (GSS_S_DEFECTIVE_CREDENTIAL);
                }

                assert((*mechanisms)->elements != NULL);

                status = gss_inquire_cred_by_mech(minor_status,
                        creds,
                        &(*mechanisms)->elements[0],
                        name,
                        &initiator_lifetime,
                        &acceptor_lifetime,
                        cred_usage);
                if (status != GSS_S_COMPLETE) {
                        gss_release_cred(&tmp_minor_status, &creds);
                        dsyslog("Leaving inquire_cred\n");
                        return (status);
                }

                if (lifetime != NULL)
                        *lifetime = (*cred_usage == GSS_C_ACCEPT) ?
                                acceptor_lifetime : initiator_lifetime;

                gss_release_cred(&tmp_minor_status, &creds);
        } else {
                status = gss_inquire_cred(minor_status, spcred->mcred,
                                          name, lifetime,
                                          cred_usage, mechanisms);
        }

        dsyslog("Leaving inquire_cred\n");

        return (status);
}

/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_compare_name(
                        OM_uint32 *minor_status,
                        const gss_name_t name1,
                        const gss_name_t name2,
                        int *name_equal)
{
        OM_uint32 status = GSS_S_COMPLETE;
        dsyslog("Entering compare_name\n");

        status = gss_compare_name(minor_status, name1, name2, name_equal);

        dsyslog("Leaving compare_name\n");
        return (status);
}

/*ARGSUSED*/
/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_display_name(
                        OM_uint32 *minor_status,
                        gss_name_t input_name,
                        gss_buffer_t output_name_buffer,
                        gss_OID *output_name_type)
{
        OM_uint32 status = GSS_S_COMPLETE;
        dsyslog("Entering display_name\n");

        status = gss_display_name(minor_status, input_name,
                        output_name_buffer, output_name_type);

        dsyslog("Leaving display_name\n");
        return (status);
}


/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_names_for_mech(
                                OM_uint32       *minor_status,
                                gss_OID         mechanism,
                                gss_OID_set     *name_types)
{
        OM_uint32   major, minor;

        dsyslog("Entering inquire_names_for_mech\n");
        /*
         * We only know how to handle our own mechanism.
         */
        if ((mechanism != GSS_C_NULL_OID) &&
            !g_OID_equal(gss_mech_spnego, mechanism)) {
                *minor_status = 0;
                return (GSS_S_FAILURE);
        }

        major = gss_create_empty_oid_set(minor_status, name_types);
        if (major == GSS_S_COMPLETE) {
                /* Now add our members. */
                if (((major = gss_add_oid_set_member(minor_status,
                                (gss_OID) GSS_C_NT_USER_NAME,
                                name_types)) == GSS_S_COMPLETE) &&
                    ((major = gss_add_oid_set_member(minor_status,
                                (gss_OID) GSS_C_NT_MACHINE_UID_NAME,
                                name_types)) == GSS_S_COMPLETE) &&
                    ((major = gss_add_oid_set_member(minor_status,
                                (gss_OID) GSS_C_NT_STRING_UID_NAME,
                                name_types)) == GSS_S_COMPLETE)) {
                        major = gss_add_oid_set_member(minor_status,
                                (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
                                name_types);
                }

                if (major != GSS_S_COMPLETE)
                        (void) gss_release_oid_set(&minor, name_types);
        }

        dsyslog("Leaving inquire_names_for_mech\n");
        return (major);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_unwrap(
                OM_uint32 *minor_status,
                gss_ctx_id_t context_handle,
                gss_buffer_t input_message_buffer,
                gss_buffer_t output_message_buffer,
                int *conf_state,
                gss_qop_t *qop_state)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_unwrap(minor_status,
                        sc->ctx_handle,
                        input_message_buffer,
                        output_message_buffer,
                        conf_state,
                        qop_state);

        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_wrap(
                OM_uint32 *minor_status,
                gss_ctx_id_t context_handle,
                int conf_req_flag,
                gss_qop_t qop_req,
                gss_buffer_t input_message_buffer,
                int *conf_state,
                gss_buffer_t output_message_buffer)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_wrap(minor_status,
                    sc->ctx_handle,
                    conf_req_flag,
                    qop_req,
                    input_message_buffer,
                    conf_state,
                    output_message_buffer);

        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_process_context_token(
                                OM_uint32       *minor_status,
                                const gss_ctx_id_t context_handle,
                                const gss_buffer_t token_buffer)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        /* SPNEGO doesn't have its own context tokens. */
        if (!sc->opened)
                return (GSS_S_DEFECTIVE_TOKEN);

        ret = gss_process_context_token(minor_status,
                                        sc->ctx_handle,
                                        token_buffer);

        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_delete_sec_context(
                            OM_uint32 *minor_status,
                            gss_ctx_id_t *context_handle,
                            gss_buffer_t output_token)
{
        OM_uint32 ret = GSS_S_COMPLETE;
        spnego_gss_ctx_id_t *ctx =
                    (spnego_gss_ctx_id_t *)context_handle;

        *minor_status = 0;

        if (context_handle == NULL)
                return (GSS_S_FAILURE);

        if (*ctx == NULL)
                return (GSS_S_COMPLETE);

        (void) gss_delete_sec_context(minor_status, &(*ctx)->ctx_handle,
                                      output_token);
        (void) release_spnego_ctx(ctx);

        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_context_time(
                        OM_uint32       *minor_status,
                        const gss_ctx_id_t context_handle,
                        OM_uint32       *time_rec)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_context_time(minor_status,
                            sc->ctx_handle,
                            time_rec);
        return (ret);
}
#ifndef LEAN_CLIENT
OM_uint32 KRB5_CALLCONV
spnego_gss_export_sec_context(
                            OM_uint32     *minor_status,
                            gss_ctx_id_t *context_handle,
                            gss_buffer_t interprocess_token)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = *(spnego_gss_ctx_id_t *)context_handle;

        /* We don't currently support exporting partially established
         * contexts. */
        if (!sc->opened)
                return GSS_S_UNAVAILABLE;

        ret = gss_export_sec_context(minor_status,
                                    &sc->ctx_handle,
                                    interprocess_token);
        if (sc->ctx_handle == GSS_C_NO_CONTEXT) {
                release_spnego_ctx(&sc);
                *context_handle = GSS_C_NO_CONTEXT;
        }
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_import_sec_context(
        OM_uint32               *minor_status,
        const gss_buffer_t      interprocess_token,
        gss_ctx_id_t            *context_handle)
{
        OM_uint32 ret, tmpmin;
        gss_ctx_id_t mctx;
        spnego_gss_ctx_id_t sc;
        int initiate, opened;

        ret = gss_import_sec_context(minor_status, interprocess_token, &mctx);
        if (ret != GSS_S_COMPLETE)
                return ret;

        ret = gss_inquire_context(&tmpmin, mctx, NULL, NULL, NULL, NULL, NULL,
                                  &initiate, &opened);
        if (ret != GSS_S_COMPLETE || !opened) {
                /* We don't currently support importing partially established
                 * contexts. */
                (void) gss_delete_sec_context(&tmpmin, &mctx, GSS_C_NO_BUFFER);
                return GSS_S_FAILURE;
        }

        sc = create_spnego_ctx(initiate);
        if (sc == NULL) {
                (void) gss_delete_sec_context(&tmpmin, &mctx, GSS_C_NO_BUFFER);
                return GSS_S_FAILURE;
        }
        sc->ctx_handle = mctx;
        sc->opened = 1;
        *context_handle = (gss_ctx_id_t)sc;
        return GSS_S_COMPLETE;
}
#endif /* LEAN_CLIENT */

OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_context(
                        OM_uint32       *minor_status,
                        const gss_ctx_id_t context_handle,
                        gss_name_t      *src_name,
                        gss_name_t      *targ_name,
                        OM_uint32       *lifetime_rec,
                        gss_OID         *mech_type,
                        OM_uint32       *ctx_flags,
                        int             *locally_initiated,
                        int             *opened)
{
        OM_uint32 ret = GSS_S_COMPLETE;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (src_name != NULL)
                *src_name = GSS_C_NO_NAME;
        if (targ_name != NULL)
                *targ_name = GSS_C_NO_NAME;
        if (lifetime_rec != NULL)
                *lifetime_rec = 0;
        if (mech_type != NULL)
                *mech_type = (gss_OID)gss_mech_spnego;
        if (ctx_flags != NULL)
                *ctx_flags = 0;
        if (locally_initiated != NULL)
                *locally_initiated = sc->initiate;
        if (opened != NULL)
                *opened = sc->opened;

        if (sc->ctx_handle != GSS_C_NO_CONTEXT) {
                ret = gss_inquire_context(minor_status, sc->ctx_handle,
                                          src_name, targ_name, lifetime_rec,
                                          mech_type, ctx_flags, NULL, NULL);
        }

        if (!sc->opened) {
                /*
                 * We are still doing SPNEGO negotiation, so report SPNEGO as
                 * the OID.  After negotiation is complete we will report the
                 * underlying mechanism OID.
                 */
                if (mech_type != NULL)
                        *mech_type = (gss_OID)gss_mech_spnego;

                /*
                 * Remove flags we don't support with partially-established
                 * contexts.  (Change this to keep GSS_C_TRANS_FLAG if we add
                 * support for exporting partial SPNEGO contexts.)
                 */
                if (ctx_flags != NULL) {
                        *ctx_flags &= ~GSS_C_PROT_READY_FLAG;
                        *ctx_flags &= ~GSS_C_TRANS_FLAG;
                }
        }

        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_wrap_size_limit(
        OM_uint32       *minor_status,
        const gss_ctx_id_t context_handle,
        int             conf_req_flag,
        gss_qop_t       qop_req,
        OM_uint32       req_output_size,
        OM_uint32       *max_input_size)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_wrap_size_limit(minor_status,
                                sc->ctx_handle,
                                conf_req_flag,
                                qop_req,
                                req_output_size,
                                max_input_size);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_localname(OM_uint32 *minor_status, const gss_name_t pname,
                     const gss_const_OID mech_type, gss_buffer_t localname)
{
        return gss_localname(minor_status, pname, GSS_C_NO_OID, localname);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_get_mic(
                OM_uint32 *minor_status,
                const gss_ctx_id_t context_handle,
                gss_qop_t  qop_req,
                const gss_buffer_t message_buffer,
                gss_buffer_t message_token)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_get_mic(minor_status,
                    sc->ctx_handle,
                    qop_req,
                    message_buffer,
                    message_token);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_verify_mic(
                OM_uint32 *minor_status,
                const gss_ctx_id_t context_handle,
                const gss_buffer_t msg_buffer,
                const gss_buffer_t token_buffer,
                gss_qop_t *qop_state)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_verify_mic(minor_status,
                            sc->ctx_handle,
                            msg_buffer,
                            token_buffer,
                            qop_state);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_sec_context_by_oid(
                OM_uint32 *minor_status,
                const gss_ctx_id_t context_handle,
                const gss_OID desired_object,
                gss_buffer_set_t *data_set)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        /* There are no SPNEGO-specific OIDs for this function. */
        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_UNAVAILABLE);

        ret = gss_inquire_sec_context_by_oid(minor_status,
                            sc->ctx_handle,
                            desired_object,
                            data_set);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_cred_by_oid(
                OM_uint32 *minor_status,
                const gss_cred_id_t cred_handle,
                const gss_OID desired_object,
                gss_buffer_set_t *data_set)
{
        OM_uint32 ret;
        spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle;
        gss_cred_id_t mcred;
        mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
        ret = gss_inquire_cred_by_oid(minor_status,
                                mcred,
                                desired_object,
                                data_set);
        return (ret);
}

/* This is the same OID as KRB5_NO_CI_FLAGS_X_OID. */
#define NO_CI_FLAGS_X_OID_LENGTH 6
#define NO_CI_FLAGS_X_OID "\x2a\x85\x70\x2b\x0d\x1d"
static const gss_OID_desc no_ci_flags_oid[] = {
        {NO_CI_FLAGS_X_OID_LENGTH, NO_CI_FLAGS_X_OID},
};

OM_uint32 KRB5_CALLCONV
spnego_gss_set_cred_option(
                OM_uint32 *minor_status,
                gss_cred_id_t *cred_handle,
                const gss_OID desired_object,
                const gss_buffer_t value)
{
        OM_uint32 ret;
        OM_uint32 tmp_minor_status;
        spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)*cred_handle;
        gss_cred_id_t mcred;

        mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
        ret = gss_set_cred_option(minor_status,
                                  &mcred,
                                  desired_object,
                                  value);
        if (ret == GSS_S_COMPLETE && spcred == NULL) {
                /*
                 * If the mechanism allocated a new credential handle, then
                 * we need to wrap it up in an SPNEGO credential handle.
                 */

                ret = create_spnego_cred(minor_status, mcred, &spcred);
                if (ret != GSS_S_COMPLETE) {
                        gss_release_cred(&tmp_minor_status, &mcred);
                        return (ret);
                }
                *cred_handle = (gss_cred_id_t)spcred;
        }

        if (ret != GSS_S_COMPLETE)
                return (ret);

        /* Recognize KRB5_NO_CI_FLAGS_X_OID and avoid asking for integrity. */
        if (g_OID_equal(desired_object, no_ci_flags_oid))
                spcred->no_ask_integ = 1;

        return (GSS_S_COMPLETE);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_set_sec_context_option(
                OM_uint32 *minor_status,
                gss_ctx_id_t *context_handle,
                const gss_OID desired_object,
                const gss_buffer_t value)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)*context_handle;

        /* There are no SPNEGO-specific OIDs for this function, and we cannot
         * construct an empty SPNEGO context with it. */
        if (sc == NULL || sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_UNAVAILABLE);

        ret = gss_set_sec_context_option(minor_status,
                            &sc->ctx_handle,
                            desired_object,
                            value);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_wrap_aead(OM_uint32 *minor_status,
                     gss_ctx_id_t context_handle,
                     int conf_req_flag,
                     gss_qop_t qop_req,
                     gss_buffer_t input_assoc_buffer,
                     gss_buffer_t input_payload_buffer,
                     int *conf_state,
                     gss_buffer_t output_message_buffer)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_wrap_aead(minor_status,
                            sc->ctx_handle,
                            conf_req_flag,
                            qop_req,
                            input_assoc_buffer,
                            input_payload_buffer,
                            conf_state,
                            output_message_buffer);

        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_unwrap_aead(OM_uint32 *minor_status,
                       gss_ctx_id_t context_handle,
                       gss_buffer_t input_message_buffer,
                       gss_buffer_t input_assoc_buffer,
                       gss_buffer_t output_payload_buffer,
                       int *conf_state,
                       gss_qop_t *qop_state)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_unwrap_aead(minor_status,
                              sc->ctx_handle,
                              input_message_buffer,
                              input_assoc_buffer,
                              output_payload_buffer,
                              conf_state,
                              qop_state);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_wrap_iov(OM_uint32 *minor_status,
                    gss_ctx_id_t context_handle,
                    int conf_req_flag,
                    gss_qop_t qop_req,
                    int *conf_state,
                    gss_iov_buffer_desc *iov,
                    int iov_count)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_wrap_iov(minor_status,
                           sc->ctx_handle,
                           conf_req_flag,
                           qop_req,
                           conf_state,
                           iov,
                           iov_count);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_unwrap_iov(OM_uint32 *minor_status,
                      gss_ctx_id_t context_handle,
                      int *conf_state,
                      gss_qop_t *qop_state,
                      gss_iov_buffer_desc *iov,
                      int iov_count)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_unwrap_iov(minor_status,
                             sc->ctx_handle,
                             conf_state,
                             qop_state,
                             iov,
                             iov_count);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_wrap_iov_length(OM_uint32 *minor_status,
                           gss_ctx_id_t context_handle,
                           int conf_req_flag,
                           gss_qop_t qop_req,
                           int *conf_state,
                           gss_iov_buffer_desc *iov,
                           int iov_count)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_wrap_iov_length(minor_status,
                                  sc->ctx_handle,
                                  conf_req_flag,
                                  qop_req,
                                  conf_state,
                                  iov,
                                  iov_count);
        return (ret);
}


OM_uint32 KRB5_CALLCONV
spnego_gss_complete_auth_token(
                OM_uint32 *minor_status,
                const gss_ctx_id_t context_handle,
                gss_buffer_t input_message_buffer)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_UNAVAILABLE);

        ret = gss_complete_auth_token(minor_status,
                                      sc->ctx_handle,
                                      input_message_buffer);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_acquire_cred_impersonate_name(OM_uint32 *minor_status,
                                         const gss_cred_id_t impersonator_cred_handle,
                                         const gss_name_t desired_name,
                                         OM_uint32 time_req,
                                         gss_OID_set desired_mechs,
                                         gss_cred_usage_t cred_usage,
                                         gss_cred_id_t *output_cred_handle,
                                         gss_OID_set *actual_mechs,
                                         OM_uint32 *time_rec)
{
        OM_uint32 status, tmpmin;
        gss_OID_set amechs = GSS_C_NULL_OID_SET;
        spnego_gss_cred_id_t imp_spcred = NULL, out_spcred = NULL;
        gss_cred_id_t imp_mcred, out_mcred = GSS_C_NO_CREDENTIAL;

        dsyslog("Entering spnego_gss_acquire_cred_impersonate_name\n");

        if (actual_mechs)
                *actual_mechs = NULL;

        if (time_rec)
                *time_rec = 0;

        imp_spcred = (spnego_gss_cred_id_t)impersonator_cred_handle;
        imp_mcred = imp_spcred ? imp_spcred->mcred : GSS_C_NO_CREDENTIAL;
        status = gss_inquire_cred(minor_status, imp_mcred, NULL, NULL,
                                  NULL, &amechs);
        if (status != GSS_S_COMPLETE)
                return status;

        status = gss_acquire_cred_impersonate_name(minor_status, imp_mcred,
                                                   desired_name, time_req,
                                                   amechs, cred_usage,
                                                   &out_mcred, actual_mechs,
                                                   time_rec);
        if (status != GSS_S_COMPLETE)
                goto cleanup;

        status = create_spnego_cred(minor_status, out_mcred, &out_spcred);
        if (status != GSS_S_COMPLETE)
                goto cleanup;

        out_mcred = GSS_C_NO_CREDENTIAL;
        *output_cred_handle = (gss_cred_id_t)out_spcred;

cleanup:
        (void) gss_release_oid_set(&tmpmin, &amechs);
        (void) gss_release_cred(&tmpmin, &out_mcred);

        dsyslog("Leaving spnego_gss_acquire_cred_impersonate_name\n");
        return (status);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_acquire_cred_with_password(OM_uint32 *minor_status,
                                      const gss_name_t desired_name,
                                      const gss_buffer_t password,
                                      OM_uint32 time_req,
                                      const gss_OID_set desired_mechs,
                                      gss_cred_usage_t cred_usage,
                                      gss_cred_id_t *output_cred_handle,
                                      gss_OID_set *actual_mechs,
                                      OM_uint32 *time_rec)
{
        OM_uint32 status, tmpmin;
        gss_OID_set amechs = GSS_C_NULL_OID_SET;
        gss_cred_id_t mcred = NULL;
        spnego_gss_cred_id_t spcred = NULL;

        dsyslog("Entering spnego_gss_acquire_cred_with_password\n");

        if (actual_mechs)
                *actual_mechs = NULL;

        if (time_rec)
                *time_rec = 0;

        status = get_available_mechs(minor_status, desired_name,
                                     cred_usage, GSS_C_NO_CRED_STORE,
                                     NULL, &amechs, NULL);
        if (status != GSS_S_COMPLETE)
            goto cleanup;

        status = gss_acquire_cred_with_password(minor_status, desired_name,
                                                password, time_req, amechs,
                                                cred_usage, &mcred,
                                                actual_mechs, time_rec);
        if (status != GSS_S_COMPLETE)
            goto cleanup;

        status = create_spnego_cred(minor_status, mcred, &spcred);
        if (status != GSS_S_COMPLETE)
                goto cleanup;

        mcred = GSS_C_NO_CREDENTIAL;
        *output_cred_handle = (gss_cred_id_t)spcred;

cleanup:

        (void) gss_release_oid_set(&tmpmin, &amechs);
        (void) gss_release_cred(&tmpmin, &mcred);

        dsyslog("Leaving spnego_gss_acquire_cred_with_password\n");
        return (status);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_display_name_ext(OM_uint32 *minor_status,
                            gss_name_t name,
                            gss_OID display_as_name_type,
                            gss_buffer_t display_name)
{
        OM_uint32 ret;
        ret = gss_display_name_ext(minor_status,
                                   name,
                                   display_as_name_type,
                                   display_name);
        return (ret);
}


OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_name(OM_uint32 *minor_status,
                        gss_name_t name,
                        int *name_is_MN,
                        gss_OID *MN_mech,
                        gss_buffer_set_t *attrs)
{
        OM_uint32 ret;
        ret = gss_inquire_name(minor_status,
                               name,
                               name_is_MN,
                               MN_mech,
                               attrs);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_get_name_attribute(OM_uint32 *minor_status,
                              gss_name_t name,
                              gss_buffer_t attr,
                              int *authenticated,
                              int *complete,
                              gss_buffer_t value,
                              gss_buffer_t display_value,
                              int *more)
{
        OM_uint32 ret;
        ret = gss_get_name_attribute(minor_status,
                                     name,
                                     attr,
                                     authenticated,
                                     complete,
                                     value,
                                     display_value,
                                     more);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_set_name_attribute(OM_uint32 *minor_status,
                              gss_name_t name,
                              int complete,
                              gss_buffer_t attr,
                              gss_buffer_t value)
{
        OM_uint32 ret;
        ret = gss_set_name_attribute(minor_status,
                                     name,
                                     complete,
                                     attr,
                                     value);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_delete_name_attribute(OM_uint32 *minor_status,
                                 gss_name_t name,
                                 gss_buffer_t attr)
{
        OM_uint32 ret;
        ret = gss_delete_name_attribute(minor_status,
                                        name,
                                        attr);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_export_name_composite(OM_uint32 *minor_status,
                                 gss_name_t name,
                                 gss_buffer_t exp_composite_name)
{
        OM_uint32 ret;
        ret = gss_export_name_composite(minor_status,
                                        name,
                                        exp_composite_name);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_map_name_to_any(OM_uint32 *minor_status,
                           gss_name_t name,
                           int authenticated,
                           gss_buffer_t type_id,
                           gss_any_t *output)
{
        OM_uint32 ret;
        ret = gss_map_name_to_any(minor_status,
                                  name,
                                  authenticated,
                                  type_id,
                                  output);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_release_any_name_mapping(OM_uint32 *minor_status,
                                    gss_name_t name,
                                    gss_buffer_t type_id,
                                    gss_any_t *input)
{
        OM_uint32 ret;
        ret = gss_release_any_name_mapping(minor_status,
                                           name,
                                           type_id,
                                           input);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_pseudo_random(OM_uint32 *minor_status,
                         gss_ctx_id_t context,
                         int prf_key,
                         const gss_buffer_t prf_in,
                         ssize_t desired_output_len,
                         gss_buffer_t prf_out)
{
        OM_uint32 ret;
        spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context;

        if (sc->ctx_handle == GSS_C_NO_CONTEXT)
                return (GSS_S_NO_CONTEXT);

        ret = gss_pseudo_random(minor_status,
                                sc->ctx_handle,
                                prf_key,
                                prf_in,
                                desired_output_len,
                                prf_out);
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_set_neg_mechs(OM_uint32 *minor_status,
                         gss_cred_id_t cred_handle,
                         const gss_OID_set mech_list)
{
        OM_uint32 ret;
        spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle;

        /* Store mech_list in spcred for use in negotiation logic. */
        gss_release_oid_set(minor_status, &spcred->neg_mechs);
        ret = generic_gss_copy_oid_set(minor_status, mech_list,
                                       &spcred->neg_mechs);
        if (ret == GSS_S_COMPLETE) {
                (void) gss_set_neg_mechs(minor_status,
                                         spcred->mcred,
                                         spcred->neg_mechs);
        }

        return (ret);
}

#define SPNEGO_SASL_NAME        "SPNEGO"
#define SPNEGO_SASL_NAME_LEN    (sizeof(SPNEGO_SASL_NAME) - 1)

OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_mech_for_saslname(OM_uint32 *minor_status,
                                     const gss_buffer_t sasl_mech_name,
                                     gss_OID *mech_type)
{
        if (sasl_mech_name->length == SPNEGO_SASL_NAME_LEN &&
            memcmp(sasl_mech_name->value, SPNEGO_SASL_NAME,
                   SPNEGO_SASL_NAME_LEN) == 0) {
                if (mech_type != NULL)
                        *mech_type = (gss_OID)gss_mech_spnego;
                return (GSS_S_COMPLETE);
        }

        return (GSS_S_BAD_MECH);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_saslname_for_mech(OM_uint32 *minor_status,
                                     const gss_OID desired_mech,
                                     gss_buffer_t sasl_mech_name,
                                     gss_buffer_t mech_name,
                                     gss_buffer_t mech_description)
{
        *minor_status = 0;

        if (!g_OID_equal(desired_mech, gss_mech_spnego))
                return (GSS_S_BAD_MECH);

        if (!g_make_string_buffer(SPNEGO_SASL_NAME, sasl_mech_name) ||
            !g_make_string_buffer("spnego", mech_name) ||
            !g_make_string_buffer("Simple and Protected GSS-API "
                                  "Negotiation Mechanism", mech_description))
                goto fail;

        return (GSS_S_COMPLETE);

fail:
        *minor_status = ENOMEM;
        return (GSS_S_FAILURE);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_inquire_attrs_for_mech(OM_uint32 *minor_status,
                                  gss_const_OID mech,
                                  gss_OID_set *mech_attrs,
                                  gss_OID_set *known_mech_attrs)
{
        OM_uint32 major, tmpMinor;

        /* known_mech_attrs is handled by mechglue */
        *minor_status = 0;

        if (mech_attrs == NULL)
            return (GSS_S_COMPLETE);

        major = gss_create_empty_oid_set(minor_status, mech_attrs);
        if (GSS_ERROR(major))
                goto cleanup;

#define MA_SUPPORTED(ma)    do {                                        \
                major = gss_add_oid_set_member(minor_status,            \
                                               (gss_OID)ma, mech_attrs); \
                if (GSS_ERROR(major))                                   \
                        goto cleanup;                                   \
        } while (0)

        MA_SUPPORTED(GSS_C_MA_MECH_NEGO);
        MA_SUPPORTED(GSS_C_MA_ITOK_FRAMED);

cleanup:
        if (GSS_ERROR(major))
                gss_release_oid_set(&tmpMinor, mech_attrs);

        return (major);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_export_cred(OM_uint32 *minor_status,
                       gss_cred_id_t cred_handle,
                       gss_buffer_t token)
{
        spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle;

        return (gss_export_cred(minor_status, spcred->mcred, token));
}

OM_uint32 KRB5_CALLCONV
spnego_gss_import_cred(OM_uint32 *minor_status,
                       gss_buffer_t token,
                       gss_cred_id_t *cred_handle)
{
        OM_uint32 ret;
        spnego_gss_cred_id_t spcred;
        gss_cred_id_t mcred;

        ret = gss_import_cred(minor_status, token, &mcred);
        if (GSS_ERROR(ret))
                return (ret);

        ret = create_spnego_cred(minor_status, mcred, &spcred);
        if (GSS_ERROR(ret))
            return (ret);

        *cred_handle = (gss_cred_id_t)spcred;
        return (ret);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_get_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle,
                       gss_qop_t qop_req, gss_iov_buffer_desc *iov,
                       int iov_count)
{
    spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

    if (sc->ctx_handle == GSS_C_NO_CONTEXT)
            return (GSS_S_NO_CONTEXT);

    return gss_get_mic_iov(minor_status, sc->ctx_handle, qop_req, iov,
                           iov_count);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_verify_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle,
                          gss_qop_t *qop_state, gss_iov_buffer_desc *iov,
                          int iov_count)
{
    spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

    if (sc->ctx_handle == GSS_C_NO_CONTEXT)
            return (GSS_S_NO_CONTEXT);

    return gss_verify_mic_iov(minor_status, sc->ctx_handle, qop_state, iov,
                              iov_count);
}

OM_uint32 KRB5_CALLCONV
spnego_gss_get_mic_iov_length(OM_uint32 *minor_status,
                              gss_ctx_id_t context_handle, gss_qop_t qop_req,
                              gss_iov_buffer_desc *iov, int iov_count)
{
    spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;

    if (sc->ctx_handle == GSS_C_NO_CONTEXT)
            return (GSS_S_NO_CONTEXT);

    return gss_get_mic_iov_length(minor_status, sc->ctx_handle, qop_req, iov,
                                  iov_count);
}

/*
 * We will release everything but the ctx_handle so that it
 * can be passed back to init/accept context. This routine should
 * not be called until after the ctx_handle memory is assigned to
 * the supplied context handle from init/accept context.
 */
static void
release_spnego_ctx(spnego_gss_ctx_id_t *ctx)
{
        spnego_gss_ctx_id_t context;
        OM_uint32 minor_stat;
        context = *ctx;

        if (context != NULL) {
                (void) gss_release_buffer(&minor_stat,
                                        &context->DER_mechTypes);

                (void) gss_release_oid_set(&minor_stat, &context->mech_set);

                (void) gss_release_name(&minor_stat, &context->internal_name);
                (void) gss_release_cred(&minor_stat, &context->deleg_cred);

                negoex_release_context(context);

                free(context);
                *ctx = NULL;
        }
}

/*
 * Can't use gss_indicate_mechs by itself to get available mechs for
 * SPNEGO because it will also return the SPNEGO mech and we do not
 * want to consider SPNEGO as an available security mech for
 * negotiation. For this reason, get_available_mechs will return
 * all available, non-deprecated mechs except SPNEGO and NegoEx-
 * only mechanisms.
 *
 * Note that gss_acquire_cred_from(GSS_C_NO_OID_SET) will filter
 * out hidden (GSS_C_MA_NOT_INDICATED) mechanisms such as NegoEx, so
 * calling gss_indicate_mechs_by_attrs() also works around that.
 *
 * If a ptr to a creds list is given, this function will attempt
 * to acquire creds for the creds given and trim the list of
 * returned mechanisms to only those for which creds are valid.
 *
 */
static OM_uint32
get_available_mechs(OM_uint32 *minor_status,
        gss_name_t name, gss_cred_usage_t usage,
        gss_const_key_value_set_t cred_store,
        gss_cred_id_t *creds, gss_OID_set *rmechs, OM_uint32 *time_rec)
{
        OM_uint32 major_status = GSS_S_COMPLETE, tmpmin;
        gss_OID_set mechs, goodmechs;
        gss_OID_set_desc except_attrs;
        gss_OID_desc attr_oids[3];

        *rmechs = GSS_C_NO_OID_SET;

        attr_oids[0] = *GSS_C_MA_DEPRECATED;
        attr_oids[1] = *GSS_C_MA_NOT_DFLT_MECH;
        attr_oids[2] = *GSS_C_MA_MECH_NEGO;     /* Exclude ourselves */
        except_attrs.count = sizeof(attr_oids) / sizeof(attr_oids[0]);
        except_attrs.elements = attr_oids;
        major_status = gss_indicate_mechs_by_attrs(minor_status,
                                                   GSS_C_NO_OID_SET,
                                                   &except_attrs,
                                                   GSS_C_NO_OID_SET, &mechs);

        /*
         * If the caller wanted a list of creds returned,
         * trim the list of mechanisms down to only those
         * for which the creds are valid.
         */
        if (mechs->count > 0 && major_status == GSS_S_COMPLETE &&
            creds != NULL) {
                major_status = gss_acquire_cred_from(minor_status, name,
                                                     GSS_C_INDEFINITE,
                                                     mechs, usage,
                                                     cred_store, creds,
                                                     &goodmechs, time_rec);

                /*
                 * Drop the old list in favor of the new
                 * "trimmed" list.
                 */
                if (major_status == GSS_S_COMPLETE) {
                        (void) gss_release_oid_set(&tmpmin, &mechs);
                        mechs = goodmechs;
                }
        }

        if (mechs->count > 0 && major_status == GSS_S_COMPLETE) {
                *rmechs = mechs;
        } else {
                (void) gss_release_oid_set(&tmpmin, &mechs);
                *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE;
                map_errcode(minor_status);
                if (major_status == GSS_S_COMPLETE)
                        major_status = GSS_S_FAILURE;
        }

        return (major_status);
}

/* Return true if mech asserts the GSS_C_MA_NEGOEX_AND_SPNEGO attribute. */
static int
negoex_and_spnego(gss_OID mech)
{
        OM_uint32 ret, minor;
        gss_OID_set attrs;
        int present;

        ret = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
        if (ret != GSS_S_COMPLETE || attrs == GSS_C_NO_OID_SET)
                return 0;

        (void) generic_gss_test_oid_set_member(&minor,
                                               GSS_C_MA_NEGOEX_AND_SPNEGO,
                                               attrs, &present);
        (void) gss_release_oid_set(&minor, &attrs);
        return present;
}

/*
 * Fill sc->mech_set with the SPNEGO-negotiable mechanism OIDs, and
 * sc->negoex_mechs with an entry for each NegoEx-negotiable mechanism.  Take
 * into account the mech set provided with gss_set_neg_mechs() if it exists.
 */
static OM_uint32
get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
                     spnego_gss_cred_id_t spcred, gss_cred_usage_t usage)
{
        OM_uint32 ret, tmpmin;
        gss_cred_id_t creds = GSS_C_NO_CREDENTIAL;
        gss_OID_set cred_mechs = GSS_C_NULL_OID_SET, mechs;
        unsigned int i;
        int present, added_negoex = 0;
        auth_scheme scheme;

        if (spcred != NULL) {
                /* Get the list of mechs in the mechglue cred. */
                ret = gss_inquire_cred(minor_status, spcred->mcred, NULL,
                                       NULL, NULL, &cred_mechs);
                if (ret != GSS_S_COMPLETE)
                        return (ret);
        } else {
                /* Start with the list of available mechs. */
                ret = get_available_mechs(minor_status, GSS_C_NO_NAME, usage,
                                          GSS_C_NO_CRED_STORE, &creds,
                                          &cred_mechs, NULL);
                if (ret != GSS_S_COMPLETE)
                        return (ret);
                gss_release_cred(&tmpmin, &creds);
        }

        /* If gss_set_neg_mechs() was called, use that to determine the
         * iteration order.  Otherwise iterate over the credential mechs. */
        mechs = (spcred != NULL && spcred->neg_mechs != GSS_C_NULL_OID_SET) ?
            spcred->neg_mechs : cred_mechs;

        ret = gss_create_empty_oid_set(minor_status, &sc->mech_set);
        if (ret != GSS_S_COMPLETE)
                goto cleanup;

        for (i = 0; i < mechs->count; i++) {
                if (mechs != cred_mechs) {
                        /* Intersect neg_mechs with cred_mechs. */
                        gss_test_oid_set_member(&tmpmin, &mechs->elements[i],
                                                cred_mechs, &present);
                        if (!present)
                                continue;
                }

                /* Query the auth scheme to see if this is a NegoEx mech. */
                ret = gssspi_query_mechanism_info(&tmpmin, &mechs->elements[i],
                                                  scheme);
                if (ret == GSS_S_COMPLETE) {
                        /* Add an entry for this mech to the NegoEx list. */
                        ret = negoex_add_auth_mech(minor_status, sc,
                                                   &mechs->elements[i],
                                                   scheme);
                        if (ret != GSS_S_COMPLETE)
                                goto cleanup;

                        /* Add the NegoEx OID to the SPNEGO list at the
                         * position of the first NegoEx mechanism. */
                        if (!added_negoex) {
                                ret = gss_add_oid_set_member(minor_status,
                                                             &negoex_mech,
                                                             &sc->mech_set);
                                if (ret != GSS_S_COMPLETE)
                                        goto cleanup;
                                added_negoex = 1;
                        }

                        /* Skip this mech in the SPNEGO list unless it asks for
                         * direct SPNEGO negotiation. */
                        if (!negoex_and_spnego(&mechs->elements[i]))
                                continue;
                }

                /* Add this mech to the SPNEGO list. */
                ret = gss_add_oid_set_member(minor_status, &mechs->elements[i],
                                             &sc->mech_set);
                if (ret != GSS_S_COMPLETE)
                        goto cleanup;
        }

        *minor_status = 0;

cleanup:
        if (ret != GSS_S_COMPLETE || sc->mech_set->count == 0) {
                *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE;
                map_errcode(minor_status);
                ret = GSS_S_FAILURE;
        }

        gss_release_oid_set(&tmpmin, &cred_mechs);
        return (ret);
}

/* following are token creation and reading routines */

/*
 * If in contains a tagged OID encoding, return a copy of the contents as a
 * gss_OID and advance in past the encoding.  Otherwise return NULL and do not
 * advance in.
 */
static gss_OID
get_mech_oid(OM_uint32 *minor_status, struct k5input *in)
{
        struct k5input oidrep;
        OM_uint32 status;
        gss_OID_desc oid;
        gss_OID mech_out = NULL;

        if (!k5_der_get_value(in, MECH_OID, &oidrep))
                return (NULL);

        oid.length = oidrep.len;
        oid.elements = (uint8_t *)oidrep.ptr;
        status = generic_gss_copy_oid(minor_status, &oid, &mech_out);
        if (status != GSS_S_COMPLETE) {
                map_errcode(minor_status);
                mech_out = NULL;
        }

        return (mech_out);
}

/*
 * If in contains a tagged octet string encoding, return a copy of the contents
 * as a gss_buffer_t and advance in past the encoding.  Otherwise return NULL
 * and do not advance in.
 */
static gss_buffer_t
get_octet_string(struct k5input *in)
{
        gss_buffer_t input_token;
        struct k5input ostr;

        if (!k5_der_get_value(in, OCTET_STRING, &ostr))
                return (NULL);

        input_token = (gss_buffer_t)malloc(sizeof (gss_buffer_desc));
        if (input_token == NULL)
                return (NULL);

        input_token->length = ostr.len;
        if (input_token->length > 0) {
                input_token->value = gssalloc_malloc(input_token->length);
                if (input_token->value == NULL) {
                        free(input_token);
                        return (NULL);
                }

                memcpy(input_token->value, ostr.ptr, input_token->length);
        } else {
                input_token->value = NULL;
        }
        return (input_token);
}

/*
 * verify that buff_in points to a sequence of der encoding. The mech
 * set is the only sequence of encoded object in the token, so if it is
 * a sequence of encoding, decode the mechset into a gss_OID_set and
 * return it, advancing the buffer pointer.
 */
static gss_OID_set
get_mech_set(OM_uint32 *minor_status, struct k5input *in)
{
        gss_OID_set returned_mechSet;
        OM_uint32 major_status, tmpmin;
        struct k5input seq;

        if (!k5_der_get_value(in, SEQUENCE_OF, &seq))
                return (NULL);

        major_status = gss_create_empty_oid_set(minor_status,
                                                &returned_mechSet);
        if (major_status != GSS_S_COMPLETE)
                return (NULL);

        while (!seq.status && seq.len > 0) {
                gss_OID_desc *oid = get_mech_oid(minor_status, &seq);

                if (oid == NULL) {
                        gss_release_oid_set(&tmpmin, &returned_mechSet);
                        return (NULL);
                }

                major_status = gss_add_oid_set_member(minor_status,
                                                      oid, &returned_mechSet);
                generic_gss_release_oid(minor_status, &oid);
                if (major_status != GSS_S_COMPLETE) {
                        gss_release_oid_set(&tmpmin, &returned_mechSet);
                        return (NULL);
                }
        }

        return (returned_mechSet);
}

/*
 * Encode mechSet into buf.
 */
static int
put_mech_set(gss_OID_set mechSet, gss_buffer_t buffer_out)
{
        uint8_t *ptr;
        size_t ilen, tlen, i;
        struct k5buf buf;

        ilen = 0;
        for (i = 0; i < mechSet->count; i++)
            ilen += k5_der_value_len(mechSet->elements[i].length);
        tlen = k5_der_value_len(ilen);

        ptr = gssalloc_malloc(tlen);
        if (ptr == NULL)
                return -1;
        k5_buf_init_fixed(&buf, ptr, tlen);

        k5_der_add_taglen(&buf, SEQUENCE_OF, ilen);
        for (i = 0; i < mechSet->count; i++) {
                k5_der_add_value(&buf, MECH_OID,
                                 mechSet->elements[i].elements,
                                 mechSet->elements[i].length);
        }
        assert(buf.len == tlen);

        buffer_out->value = ptr;
        buffer_out->length = tlen;
        return 0;
}

/* Decode SPNEGO request flags from the DER encoding of a bit string and set
 * them in *ret_flags. */
static OM_uint32
get_req_flags(struct k5input *in, OM_uint32 *req_flags)
{
        if (in->status || in->len != 4 ||
            k5_input_get_byte(in) != BIT_STRING ||
            k5_input_get_byte(in) != BIT_STRING_LENGTH ||
            k5_input_get_byte(in) != BIT_STRING_PADDING)
                return GSS_S_DEFECTIVE_TOKEN;

        *req_flags = k5_input_get_byte(in) >> 1;
        return GSS_S_COMPLETE;
}

static OM_uint32
get_negTokenInit(OM_uint32 *minor_status,
                 gss_buffer_t buf,
                 gss_buffer_t der_mechSet,
                 gss_OID_set *mechSet,
                 OM_uint32 *req_flags,
                 gss_buffer_t *mechtok,
                 gss_buffer_t *mechListMIC)
{
        OM_uint32 err;
        struct k5input in, seq, field;

        *minor_status = 0;
        der_mechSet->length = 0;
        der_mechSet->value = NULL;
        *mechSet = GSS_C_NO_OID_SET;
        *req_flags = 0;
        *mechtok = *mechListMIC = GSS_C_NO_BUFFER;

        k5_input_init(&in, buf->value, buf->length);

        /* Advance past the framing header. */
        err = verify_token_header(&in, gss_mech_spnego);
        if (err)
                return GSS_S_DEFECTIVE_TOKEN;

        /* Advance past the [0] tag for the NegotiationToken choice. */
        if (!k5_der_get_value(&in, CONTEXT, &seq))
                return GSS_S_DEFECTIVE_TOKEN;

        /* Advance past the SEQUENCE tag. */
        if (!k5_der_get_value(&seq, SEQUENCE, &seq))
                return GSS_S_DEFECTIVE_TOKEN;

        /* Get the contents of the mechTypes field.  Reject an empty field here
         * since we musn't allocate a zero-length buffer in the next step. */
        if (!k5_der_get_value(&seq, CONTEXT, &field) || field.len == 0)
                return GSS_S_DEFECTIVE_TOKEN;

        /* Store a copy of the contents for MIC computation. */
        der_mechSet->value = gssalloc_malloc(field.len);
        if (der_mechSet->value == NULL)
                return GSS_S_FAILURE;
        memcpy(der_mechSet->value, field.ptr, field.len);
        der_mechSet->length = field.len;

        /* Decode the contents into an OID set. */
        *mechSet = get_mech_set(minor_status, &field);
        if (*mechSet == NULL)
                return GSS_S_FAILURE;

        if (k5_der_get_value(&seq, CONTEXT | 0x01, &field)) {
                err = get_req_flags(&field, req_flags);
                if (err != GSS_S_COMPLETE)
                        return err;
        }

        if (k5_der_get_value(&seq, CONTEXT | 0x02, &field)) {
                *mechtok = get_octet_string(&field);
                if (*mechtok == GSS_C_NO_BUFFER)
                        return GSS_S_FAILURE;
        }

        if (k5_der_get_value(&seq, CONTEXT | 0x03, &field)) {
                *mechListMIC = get_octet_string(&field);
                if (*mechListMIC == GSS_C_NO_BUFFER)
                        return GSS_S_FAILURE;
        }

        return seq.status ? GSS_S_DEFECTIVE_TOKEN : GSS_S_COMPLETE;
}

/* Decode a NegotiationToken of type negTokenResp. */
static OM_uint32
get_negTokenResp(OM_uint32 *minor_status, struct k5input *in,
                 OM_uint32 *negState, gss_OID *supportedMech,
                 gss_buffer_t *responseToken, gss_buffer_t *mechListMIC)
{
        struct k5input seq, field, en;

        *negState = UNSPECIFIED;
        *supportedMech = GSS_C_NO_OID;
        *responseToken = *mechListMIC = GSS_C_NO_BUFFER;

        /* Advance past the [1] tag for the NegotiationToken choice. */
        if (!k5_der_get_value(in, CONTEXT | 0x01, &seq))
                return GSS_S_DEFECTIVE_TOKEN;

        /* Advance seq past the SEQUENCE tag (historically this code allows the
         * tag to be missing). */
        (void)k5_der_get_value(&seq, SEQUENCE, &seq);

        if (k5_der_get_value(&seq, CONTEXT, &field)) {
                if (!k5_der_get_value(&field, ENUMERATED, &en))
                        return GSS_S_DEFECTIVE_TOKEN;
                if (en.len != ENUMERATION_LENGTH)
                        return GSS_S_DEFECTIVE_TOKEN;
                *negState = *en.ptr;
        }

        if (k5_der_get_value(&seq, CONTEXT | 0x01, &field)) {
                *supportedMech = get_mech_oid(minor_status, &field);
                if (*supportedMech == GSS_C_NO_OID)
                        return GSS_S_DEFECTIVE_TOKEN;
        }

        if (k5_der_get_value(&seq, CONTEXT | 0x02, &field)) {
                *responseToken = get_octet_string(&field);
                if (*responseToken == GSS_C_NO_BUFFER)
                        return GSS_S_DEFECTIVE_TOKEN;
        }

        if (k5_der_get_value(&seq, CONTEXT | 0x04, &field)) {
                *mechListMIC = get_octet_string(&field);

                /* Handle Windows 2000 duplicate response token */
                if (*responseToken &&
                    ((*responseToken)->length == (*mechListMIC)->length) &&
                    !memcmp((*responseToken)->value, (*mechListMIC)->value,
                            (*responseToken)->length)) {
                        OM_uint32 tmpmin;

                        gss_release_buffer(&tmpmin, *mechListMIC);
                        free(*mechListMIC);
                        *mechListMIC = NULL;
                }
        }

        return seq.status ? GSS_S_DEFECTIVE_TOKEN : GSS_S_COMPLETE;
}

/*
 * This routine compares the received mechset to the mechset that
 * this server can support. It looks sequentially through the mechset
 * and the first one that matches what the server can support is
 * chosen as the negotiated mechanism. If one is found, negResult
 * is set to ACCEPT_INCOMPLETE if it's the first mech, REQUEST_MIC if
 * it's not the first mech, otherwise we return NULL and negResult
 * is set to REJECT. The returned pointer is an alias into
 * received->elements and should not be freed.
 *
 * NOTE: There is currently no way to specify a preference order of
 * mechanisms supported by the acceptor.
 */
static gss_OID
negotiate_mech(spnego_gss_ctx_id_t ctx, gss_OID_set received,
               OM_uint32 *negResult)
{
        size_t i, j;
        int wrong_krb5_oid;

        for (i = 0; i < received->count; i++) {
                gss_OID mech_oid = &received->elements[i];

                /* Accept wrong mechanism OID from MS clients */
                wrong_krb5_oid = 0;
                if (g_OID_equal(mech_oid, &gss_mech_krb5_wrong_oid)) {
                        mech_oid = (gss_OID)&gss_mech_krb5_oid;
                        wrong_krb5_oid = 1;
                }

                for (j = 0; j < ctx->mech_set->count; j++) {
                        if (g_OID_equal(mech_oid,
                                        &ctx->mech_set->elements[j])) {
                                *negResult = (i == 0) ? ACCEPT_INCOMPLETE :
                                        REQUEST_MIC;
                                return wrong_krb5_oid ?
                                        (gss_OID)&gss_mech_krb5_wrong_oid :
                                        &ctx->mech_set->elements[j];
                        }
                }
        }
        *negResult = REJECT;
        return (NULL);
}

/*
 * the next two routines make a token buffer suitable for
 * spnego_gss_display_status. These currently take the string
 * in name and place it in the token. Eventually, if
 * spnego_gss_display_status returns valid error messages,
 * these routines will be changes to return the error string.
 */
static spnego_token_t
make_spnego_token(const char *name)
{
        return (spnego_token_t)gssalloc_strdup(name);
}

static gss_buffer_desc
make_err_msg(const char *name)
{
        gss_buffer_desc buffer;

        if (name == NULL) {
                buffer.length = 0;
                buffer.value = NULL;
        } else {
                buffer.length = strlen(name)+1;
                buffer.value = make_spnego_token(name);
        }

        return (buffer);
}

/*
 * Create the client side spnego token passed back to gss_init_sec_context
 * and eventually up to the application program and over to the server.
 *
 * Use DER rules, definite length method per RFC 2478
 */
static int
make_spnego_tokenInit_msg(spnego_gss_ctx_id_t spnego_ctx, int negHintsCompat,
                          gss_buffer_t mic, OM_uint32 req_flags,
                          gss_buffer_t token, send_token_flag sendtoken,
                          gss_buffer_t outbuf)
{
        size_t f0len, f2len, f3len, fields_len, seq_len, choice_len;
        size_t mech_len, framed_len;
        uint8_t *t;
        struct k5buf buf;

        if (outbuf == GSS_C_NO_BUFFER)
                return (-1);

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

        /* Calculate the length of each field and the total fields length. */
        fields_len = 0;
        /* mechTypes [0] MechTypeList, previously assembled in spnego_ctx */
        f0len = spnego_ctx->DER_mechTypes.length;
        fields_len += k5_der_value_len(f0len);
        if (token != NULL) {
                /* mechToken [2] OCTET STRING OPTIONAL */
                f2len = k5_der_value_len(token->length);
                fields_len += k5_der_value_len(f2len);
        }
        if (mic != GSS_C_NO_BUFFER) {
                /* mechListMIC [3] OCTET STRING OPTIONAL */
                f3len = k5_der_value_len(mic->length);
                fields_len += k5_der_value_len(f3len);
        }

        /* Calculate the length of the sequence and choice. */
        seq_len = k5_der_value_len(fields_len);
        choice_len = k5_der_value_len(seq_len);

        /* Calculate the framed token length. */
        mech_len = k5_der_value_len(gss_mech_spnego->length);
        framed_len = k5_der_value_len(mech_len + choice_len);

        /* Allocate space and prepare a buffer. */
        t = gssalloc_malloc(framed_len);
        if (t == NULL)
                return (-1);
        k5_buf_init_fixed(&buf, t, framed_len);

        /* Add generic token framing. */
        k5_der_add_taglen(&buf, HEADER_ID, mech_len + choice_len);
        k5_der_add_value(&buf, MECH_OID, gss_mech_spnego->elements,
                         gss_mech_spnego->length);

        /* Add NegotiationToken choice tag and NegTokenInit sequence tag. */
        k5_der_add_taglen(&buf, CONTEXT | 0x00, seq_len);
        k5_der_add_taglen(&buf, SEQUENCE, fields_len);

        /* Add the already-encoded mechanism list as mechTypes. */
        k5_der_add_value(&buf, CONTEXT | 0x00, spnego_ctx->DER_mechTypes.value,
                         spnego_ctx->DER_mechTypes.length);

        if (token != NULL) {
                k5_der_add_taglen(&buf, CONTEXT | 0x02, f2len);
                k5_der_add_value(&buf, OCTET_STRING, token->value,
                                 token->length);
        }

        if (mic != GSS_C_NO_BUFFER) {
                uint8_t id = negHintsCompat ? SEQUENCE : OCTET_STRING;
                k5_der_add_taglen(&buf, CONTEXT | 0x03, f3len);
                k5_der_add_value(&buf, id, mic->value, mic->length);
        }

        assert(buf.len == framed_len);
        outbuf->length = framed_len;
        outbuf->value = t;

        return (0);
}

/*
 * create the server side spnego token passed back to
 * gss_accept_sec_context and eventually up to the application program
 * and over to the client.
 */
static OM_uint32
make_spnego_tokenTarg_msg(uint8_t status, gss_OID mech_wanted,
                          gss_buffer_t token, gss_buffer_t mic,
                          send_token_flag sendtoken,
                          gss_buffer_t outbuf)
{
        size_t f0len, f1len, f2len, f3len, fields_len, seq_len, choice_len;
        uint8_t *t;
        struct k5buf buf;

        if (outbuf == GSS_C_NO_BUFFER)
                return (GSS_S_DEFECTIVE_TOKEN);
        if (sendtoken == INIT_TOKEN_SEND && mech_wanted == GSS_C_NO_OID)
                return (GSS_S_DEFECTIVE_TOKEN);

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

        /* Calculate the length of each field and the total fields length. */
        fields_len = 0;
        /* negState [0] ENUMERATED { ... } OPTIONAL */
        f0len = k5_der_value_len(1);
        fields_len += k5_der_value_len(f0len);
        if (sendtoken == INIT_TOKEN_SEND) {
                /* supportedMech [1] MechType OPTIONAL */
                f1len = k5_der_value_len(mech_wanted->length);
                fields_len += k5_der_value_len(f1len);
        }
        if (token != NULL && token->length > 0) {
                /* mechToken [2] OCTET STRING OPTIONAL */
                f2len = k5_der_value_len(token->length);
                fields_len += k5_der_value_len(f2len);
        }
        if (mic != NULL) {
                /* mechListMIC [3] OCTET STRING OPTIONAL */
                f3len = k5_der_value_len(mic->length);
                fields_len += k5_der_value_len(f3len);
        }

        /* Calculate the length of the sequence and choice. */
        seq_len = k5_der_value_len(fields_len);
        choice_len = k5_der_value_len(seq_len);

        /* Allocate space and prepare a buffer. */
        t = gssalloc_malloc(choice_len);
        if (t == NULL)
                return (GSS_S_DEFECTIVE_TOKEN);
        k5_buf_init_fixed(&buf, t, choice_len);

        /* Add the choice tag and begin the sequence. */
        k5_der_add_taglen(&buf, CONTEXT | 0x01, seq_len);
        k5_der_add_taglen(&buf, SEQUENCE, fields_len);

        /* Add the negState field. */
        k5_der_add_taglen(&buf, CONTEXT | 0x00, f0len);
        k5_der_add_value(&buf, ENUMERATED, &status, 1);

        if (sendtoken == INIT_TOKEN_SEND) {
                /* Add the supportedMech field. */
                k5_der_add_taglen(&buf, CONTEXT | 0x01, f1len);
                k5_der_add_value(&buf, MECH_OID, mech_wanted->elements,
                                 mech_wanted->length);
        }

        if (token != NULL && token->length > 0) {
                /* Add the mechToken field. */
                k5_der_add_taglen(&buf, CONTEXT | 0x02, f2len);
                k5_der_add_value(&buf, OCTET_STRING, token->value,
                                 token->length);
        }

        if (mic != NULL) {
                /* Add the mechListMIC field. */
                k5_der_add_taglen(&buf, CONTEXT | 0x03, f3len);
                k5_der_add_value(&buf, OCTET_STRING, mic->value, mic->length);
        }

        assert(buf.len == choice_len);
        outbuf->length = choice_len;
        outbuf->value = t;

        return (0);
}

/* Advance in past the [APPLICATION 0] tag and thisMech field of an
 * InitialContextToken encoding, checking that thisMech matches mech. */
static int
verify_token_header(struct k5input *in, gss_OID_const mech)
{
        gss_OID_desc oid;
        struct k5input field;

        if (!k5_der_get_value(in, HEADER_ID, in))
                return (G_BAD_TOK_HEADER);
        if (!k5_der_get_value(in, MECH_OID, &field))
                return (G_BAD_TOK_HEADER);

        oid.length = field.len;
        oid.elements = (uint8_t *)field.ptr;
        return g_OID_equal(&oid, mech) ? 0 : G_WRONG_MECH;
}

/*
 * Return non-zero if the oid is one of the kerberos mech oids,
 * otherwise return zero.
 *
 * N.B. There are 3 oids that represent the kerberos mech:
 * RFC-specified GSS_MECH_KRB5_OID,
 * Old pre-RFC   GSS_MECH_KRB5_OLD_OID,
 * Incorrect MS  GSS_MECH_KRB5_WRONG_OID
 */

static int
is_kerb_mech(gss_OID oid)
{
        int answer = 0;
        OM_uint32 minor;
        extern const gss_OID_set_desc * const gss_mech_set_krb5_both;

        (void) gss_test_oid_set_member(&minor,
                oid, (gss_OID_set)gss_mech_set_krb5_both, &answer);

        return (answer);
}