/*
 * 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	<krb5.h>
#include	<mglueP.h>
#include	"gssapiP_spnego.h"
#include	<gssapi_err_generic.h>


#undef g_token_size
#undef g_verify_token_header
#undef g_make_token_header

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

/* der routines defined in libgss */
extern unsigned int gssint_der_length_size(unsigned int);
extern int gssint_get_der_length(unsigned char **, unsigned int,
				 unsigned int*);
extern int gssint_put_der_length(unsigned int, unsigned char **, unsigned int);


/* 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 g_token_size(gss_OID_const, unsigned int);
static int g_make_token_header(gss_OID_const, unsigned int,
			       unsigned char **, unsigned int);
static int g_verify_token_header(gss_OID_const, unsigned int *,
				 unsigned char **,
				 int, unsigned int);
static int g_verify_neg_token_init(unsigned char **, unsigned int);
static gss_OID get_mech_oid(OM_uint32 *, unsigned char **, size_t);
static gss_buffer_t get_input_token(unsigned char **, unsigned int);
static gss_OID_set get_mech_set(OM_uint32 *, unsigned char **, unsigned int);
static OM_uint32 get_req_flags(unsigned char **, OM_uint32, 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 int put_input_token(unsigned char **, gss_buffer_t, unsigned int);
static int put_mech_oid(unsigned char **, gss_OID_const, unsigned int);
static int put_negResult(unsigned char **, OM_uint32, unsigned int);

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
g_get_tag_and_length(unsigned char **, int, unsigned int, unsigned int *);

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 int
make_spnego_tokenTarg_msg(OM_uint32, 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 *, unsigned char *, unsigned int,
		 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 int put_neg_hints(unsigned char **, gss_buffer_t, unsigned int);
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) gssint_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;
	unsigned char *ptr;
	gss_OID supportedMech = GSS_C_NO_OID;

	*acc_negState = UNSPECIFIED;
	*tokflag = ERROR_TOKEN_SEND;

	ptr = buf->value;
	ret = get_negTokenResp(minor_status, ptr, buf->length, 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" };

/*
 * verify that the input token length is not 0. If it is, just return.
 * If the token length is greater than 0, der encode as a sequence
 * and place in buf_out, advancing buf_out.
 */

static int
put_neg_hints(unsigned char **buf_out, gss_buffer_t input_token,
	      unsigned int buflen)
{
	int ret;

	/* if token length is 0, we do not want to send */
	if (input_token->length == 0)
		return (0);

	if (input_token->length > buflen)
		return (-1);

	*(*buf_out)++ = SEQUENCE;
	if ((ret = gssint_put_der_length(input_token->length, buf_out,
			    input_token->length)))
		return (ret);
	TWRITE_STR(*buf_out, input_token->value, input_token->length);
	return (0);
}

/*
 * 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;
	unsigned int tlen = 0;
	unsigned int hintNameSize = 0;
	unsigned char *ptr;
	unsigned char *t;
	const char *hintname = "not_defined_in_RFC4178@please_ignore";
	const size_t hintname_len = strlen(hintname);

	*outbuf = GSS_C_NO_BUFFER;
	major_status = GSS_S_FAILURE;

	/* Length of DER encoded GeneralString */
	tlen = 1 + gssint_der_length_size(hintname_len) + hintname_len;
	hintNameSize = tlen;

	/* Length of DER encoded hintName */
	tlen += 1 + gssint_der_length_size(hintNameSize);

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

	ptr = t;

	*ptr++ = CONTEXT | 0x00; /* hintName identifier */
	if (gssint_put_der_length(hintNameSize,
				  &ptr, tlen - (int)(ptr-t)))
		goto errout;

	*ptr++ = GENERAL_STRING;
	if (gssint_put_der_length(hintname_len, &ptr, tlen - (int)(ptr-t)))
		goto errout;

	memcpy(ptr, hintname, hintname_len);
	ptr += hintname_len;

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

	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;
	unsigned int len;
	unsigned char *ptr, *bufstart;

	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;

	ptr = bufstart = buf->value;
#define REMAIN (buf->length - (ptr - bufstart))
	if (REMAIN == 0 || REMAIN > INT_MAX)
		return GSS_S_DEFECTIVE_TOKEN;

	/*
	 * Attempt to work with old Sun SPNEGO.
	 */
	if (*ptr == HEADER_ID) {
		ret = g_verify_token_header(gss_mech_spnego,
					    &len, &ptr, 0, REMAIN);
		if (ret) {
			*minstat = ret;
			return GSS_S_DEFECTIVE_TOKEN;
		}
	}
	if (*ptr != (CONTEXT | 0x01)) {
		return GSS_S_DEFECTIVE_TOKEN;
	}
	ret = get_negTokenResp(minstat, ptr, REMAIN,
			       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;
#undef REMAIN
}

/*
 * 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 buff_in is not pointing to a MECH_OID, then return NULL and do not
 * advance the buffer, otherwise, decode the mech_oid from the buffer and
 * place in gss_OID.
 */
static gss_OID
get_mech_oid(OM_uint32 *minor_status, unsigned char **buff_in, size_t length)
{
	OM_uint32	status;
	gss_OID_desc 	toid;
	gss_OID		mech_out = NULL;
	unsigned int	bytes;
	int		oid_length;

	if (length < 1 || **buff_in != MECH_OID)
		return (NULL);
	(*buff_in)++;
	length--;

	oid_length = gssint_get_der_length(buff_in, length, &bytes);
	if (oid_length < 0 || length - bytes < (size_t)oid_length)
		return (NULL);

	toid.length = oid_length;
	toid.elements = *buff_in;
	*buff_in += toid.length;

	status = generic_gss_copy_oid(minor_status, &toid, &mech_out);

	if (status != GSS_S_COMPLETE) {
		map_errcode(minor_status);
		mech_out = NULL;
	}

	return (mech_out);
}

/*
 * der encode the given mechanism oid into buf_out, advancing the
 * buffer pointer.
 */

static int
put_mech_oid(unsigned char **buf_out, gss_OID_const mech, unsigned int buflen)
{
	if (buflen < mech->length + 2)
		return (-1);
	*(*buf_out)++ = MECH_OID;
	*(*buf_out)++ = (unsigned char) mech->length;
	memcpy(*buf_out, mech->elements, mech->length);
	*buf_out += mech->length;
	return (0);
}

/*
 * verify that buff_in points to an octet string, if it does not,
 * return NULL and don't advance the pointer. If it is an octet string
 * decode buff_in into a gss_buffer_t and return it, advancing the
 * buffer pointer.
 */
static gss_buffer_t
get_input_token(unsigned char **buff_in, unsigned int buff_length)
{
	gss_buffer_t input_token;
	unsigned int len;

	if (g_get_tag_and_length(buff_in, OCTET_STRING, buff_length, &len) < 0)
		return (NULL);

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

	input_token->length = 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, *buff_in, input_token->length);
	} else {
		input_token->value = NULL;
	}
	*buff_in += input_token->length;
	return (input_token);
}

/*
 * verify that the input token length is not 0. If it is, just return.
 * If the token length is greater than 0, der encode as an octet string
 * and place in buf_out, advancing buf_out.
 */

static int
put_input_token(unsigned char **buf_out, gss_buffer_t input_token,
		unsigned int buflen)
{
	int ret;

	/* if token length is 0, we do not want to send */
	if (input_token->length == 0)
		return (0);

	if (input_token->length > buflen)
		return (-1);

	*(*buf_out)++ = OCTET_STRING;
	if ((ret = gssint_put_der_length(input_token->length, buf_out,
			    input_token->length)))
		return (ret);
	TWRITE_STR(*buf_out, input_token->value, input_token->length);
	return (0);
}

/*
 * 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, unsigned char **buff_in,
	     unsigned int buff_length)
{
	gss_OID_set returned_mechSet;
	OM_uint32 major_status;
	int length;
	unsigned int bytes;
	OM_uint32 set_length;
	unsigned char		*start;
	int i;

	if (buff_length < 1 || **buff_in != SEQUENCE_OF)
		return (NULL);

	start = *buff_in;
	(*buff_in)++;

	length = gssint_get_der_length(buff_in, buff_length - 1, &bytes);
	if (length < 0 || buff_length - 1 - bytes < (unsigned int)length)
		return NULL;

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

	for (set_length = 0, i = 0; set_length < (unsigned int)length; i++) {
		gss_OID_desc *temp = get_mech_oid(minor_status, buff_in,
			buff_length - (*buff_in - start));
		if (temp == NULL)
			break;

		major_status = gss_add_oid_set_member(minor_status,
						      temp, &returned_mechSet);
		if (major_status == GSS_S_COMPLETE)
			set_length += returned_mechSet->elements[i].length +2;
		generic_gss_release_oid(minor_status, &temp);
	}

	return (returned_mechSet);
}

/*
 * Encode mechSet into buf.
 */
static int
put_mech_set(gss_OID_set mechSet, gss_buffer_t buf)
{
	unsigned char *ptr;
	unsigned int i;
	unsigned int tlen, ilen;

	tlen = ilen = 0;
	for (i = 0; i < mechSet->count; i++) {
		/*
		 * 0x06 [DER LEN] [OID]
		 */
		ilen += 1 +
			gssint_der_length_size(mechSet->elements[i].length) +
			mechSet->elements[i].length;
	}
	/*
	 * 0x30 [DER LEN]
	 */
	tlen = 1 + gssint_der_length_size(ilen) + ilen;
	ptr = gssalloc_malloc(tlen);
	if (ptr == NULL)
		return -1;

	buf->value = ptr;
	buf->length = tlen;
#define REMAIN (buf->length - ((unsigned char *)buf->value - ptr))

	*ptr++ = SEQUENCE_OF;
	if (gssint_put_der_length(ilen, &ptr, REMAIN) < 0)
		return -1;
	for (i = 0; i < mechSet->count; i++) {
		if (put_mech_oid(&ptr, &mechSet->elements[i], REMAIN) < 0) {
			return -1;
		}
	}
	return 0;
#undef REMAIN
}

/*
 * Verify that buff_in is pointing to a BIT_STRING with the correct
 * length and padding for the req_flags. If it is, decode req_flags
 * and return them, otherwise, return NULL.
 */
static OM_uint32
get_req_flags(unsigned char **buff_in, OM_uint32 bodysize,
	      OM_uint32 *req_flags)
{
	unsigned int len;

	if (bodysize < 1 || **buff_in != (CONTEXT | 0x01))
		return (0);

	if (g_get_tag_and_length(buff_in, (CONTEXT | 0x01),
				 bodysize, &len) < 0 || len != 4)
		return GSS_S_DEFECTIVE_TOKEN;

	if (*(*buff_in)++ != BIT_STRING)
		return GSS_S_DEFECTIVE_TOKEN;

	if (*(*buff_in)++ != BIT_STRING_LENGTH)
		return GSS_S_DEFECTIVE_TOKEN;

	if (*(*buff_in)++ != BIT_STRING_PADDING)
		return GSS_S_DEFECTIVE_TOKEN;

	*req_flags = (OM_uint32) (*(*buff_in)++ >> 1);
	return (0);
}

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;
	unsigned char *ptr, *bufstart;
	unsigned int len;
	gss_buffer_desc tmpbuf;

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

	ptr = bufstart = buf->value;
	if ((buf->length - (ptr - bufstart)) > INT_MAX)
		return GSS_S_FAILURE;
#define REMAIN (buf->length - (ptr - bufstart))

	err = g_verify_token_header(gss_mech_spnego,
				    &len, &ptr, 0, REMAIN);
	if (err) {
		*minor_status = err;
		map_errcode(minor_status);
		return GSS_S_FAILURE;
	}
	*minor_status = g_verify_neg_token_init(&ptr, REMAIN);
	if (*minor_status) {
		map_errcode(minor_status);
		return GSS_S_FAILURE;
	}

	/* alias into input_token */
	tmpbuf.value = ptr;
	tmpbuf.length = REMAIN;
	*mechSet = get_mech_set(minor_status, &ptr, REMAIN);
	if (*mechSet == NULL)
		return GSS_S_FAILURE;

	tmpbuf.length = ptr - (unsigned char *)tmpbuf.value;
	der_mechSet->value = gssalloc_malloc(tmpbuf.length);
	if (der_mechSet->value == NULL)
		return GSS_S_FAILURE;
	memcpy(der_mechSet->value, tmpbuf.value, tmpbuf.length);
	der_mechSet->length = tmpbuf.length;

	err = get_req_flags(&ptr, REMAIN, req_flags);
	if (err != GSS_S_COMPLETE) {
		return err;
	}
	if (g_get_tag_and_length(&ptr, (CONTEXT | 0x02),
				 REMAIN, &len) >= 0) {
		*mechtok = get_input_token(&ptr, len);
		if (*mechtok == GSS_C_NO_BUFFER) {
			return GSS_S_FAILURE;
		}
	}
	if (g_get_tag_and_length(&ptr, (CONTEXT | 0x03),
				 REMAIN, &len) >= 0) {
		*mechListMIC = get_input_token(&ptr, len);
		if (*mechListMIC == GSS_C_NO_BUFFER) {
			return GSS_S_FAILURE;
		}
	}
	return GSS_S_COMPLETE;
#undef REMAIN
}

static OM_uint32
get_negTokenResp(OM_uint32 *minor_status,
		 unsigned char *buf, unsigned int buflen,
		 OM_uint32 *negState,
		 gss_OID *supportedMech,
		 gss_buffer_t *responseToken,
		 gss_buffer_t *mechListMIC)
{
	unsigned char *ptr, *bufstart;
	unsigned int len;
	int tmplen;
	unsigned int tag, bytes;

	*negState = UNSPECIFIED;
	*supportedMech = GSS_C_NO_OID;
	*responseToken = *mechListMIC = GSS_C_NO_BUFFER;
	ptr = bufstart = buf;
#define REMAIN (buflen - (ptr - bufstart))

	if (g_get_tag_and_length(&ptr, (CONTEXT | 0x01), REMAIN, &len) < 0)
		return GSS_S_DEFECTIVE_TOKEN;
	if (*ptr++ == SEQUENCE) {
		tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
		if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
			return GSS_S_DEFECTIVE_TOKEN;
	}
	if (REMAIN < 1)
		tag = 0;
	else
		tag = *ptr++;

	if (tag == CONTEXT) {
		tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
		if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
			return GSS_S_DEFECTIVE_TOKEN;

		if (g_get_tag_and_length(&ptr, ENUMERATED,
					 REMAIN, &len) < 0)
			return GSS_S_DEFECTIVE_TOKEN;

		if (len != ENUMERATION_LENGTH)
			return GSS_S_DEFECTIVE_TOKEN;

		if (REMAIN < 1)
			return GSS_S_DEFECTIVE_TOKEN;
		*negState = *ptr++;

		if (REMAIN < 1)
			tag = 0;
		else
			tag = *ptr++;
	}
	if (tag == (CONTEXT | 0x01)) {
		tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
		if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
			return GSS_S_DEFECTIVE_TOKEN;

		*supportedMech = get_mech_oid(minor_status, &ptr, REMAIN);
		if (*supportedMech == GSS_C_NO_OID)
			return GSS_S_DEFECTIVE_TOKEN;

		if (REMAIN < 1)
			tag = 0;
		else
			tag = *ptr++;
	}
	if (tag == (CONTEXT | 0x02)) {
		tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
		if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
			return GSS_S_DEFECTIVE_TOKEN;

		*responseToken = get_input_token(&ptr, REMAIN);
		if (*responseToken == GSS_C_NO_BUFFER)
			return GSS_S_DEFECTIVE_TOKEN;

		if (REMAIN < 1)
			tag = 0;
		else
			tag = *ptr++;
	}
	if (tag == (CONTEXT | 0x03)) {
		tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
		if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
			return GSS_S_DEFECTIVE_TOKEN;

		*mechListMIC = get_input_token(&ptr, REMAIN);
		if (*mechListMIC == GSS_C_NO_BUFFER)
			return GSS_S_DEFECTIVE_TOKEN;

                /* 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 GSS_S_COMPLETE;
#undef REMAIN
}

/*
 * der encode the passed negResults as an ENUMERATED type and
 * place it in buf_out, advancing the buffer.
 */

static int
put_negResult(unsigned char **buf_out, OM_uint32 negResult,
	      unsigned int buflen)
{
	if (buflen < 3)
		return (-1);
	*(*buf_out)++ = ENUMERATED;
	*(*buf_out)++ = ENUMERATION_LENGTH;
	*(*buf_out)++ = (unsigned char) negResult;
	return (0);
}

/*
 * 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 mechListMIC, OM_uint32 req_flags,
			  gss_buffer_t data, send_token_flag sendtoken,
			  gss_buffer_t outbuf)
{
	int ret = 0;
	unsigned int tlen, dataLen = 0;
	unsigned int negTokenInitSize = 0;
	unsigned int negTokenInitSeqSize = 0;
	unsigned int negTokenInitContSize = 0;
	unsigned int rspTokenSize = 0;
	unsigned int mechListTokenSize = 0;
	unsigned int micTokenSize = 0;
	unsigned char *t;
	unsigned char *ptr;

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

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

	/* calculate the data length */

	/*
	 * 0xa0 [DER LEN] [mechTypes]
	 */
	mechListTokenSize = 1 +
		gssint_der_length_size(spnego_ctx->DER_mechTypes.length) +
		spnego_ctx->DER_mechTypes.length;
	dataLen += mechListTokenSize;

	/*
	 * If a token from gss_init_sec_context exists,
	 * add the length of the token + the ASN.1 overhead
	 */
	if (data != NULL) {
		/*
		 * Encoded in final output as:
		 * 0xa2 [DER LEN] 0x04 [DER LEN] [DATA]
		 * -----s--------|--------s2----------
		 */
		rspTokenSize = 1 +
			gssint_der_length_size(data->length) +
			data->length;
		dataLen += 1 + gssint_der_length_size(rspTokenSize) +
			rspTokenSize;
	}

	if (mechListMIC) {
		/*
		 * Encoded in final output as:
		 * 0xa3 [DER LEN] 0x04 [DER LEN] [DATA]
		 *	--s--     -----tlen------------
		 */
		micTokenSize = 1 +
			gssint_der_length_size(mechListMIC->length) +
			mechListMIC->length;
		dataLen += 1 +
			gssint_der_length_size(micTokenSize) +
			micTokenSize;
	}

	/*
	 * Add size of DER encoding
	 * [ SEQUENCE { MechTypeList | ReqFLags | Token | mechListMIC } ]
	 *   0x30 [DER_LEN] [data]
	 *
	 */
	negTokenInitContSize = dataLen;
	negTokenInitSeqSize = 1 + gssint_der_length_size(dataLen) + dataLen;
	dataLen = negTokenInitSeqSize;

	/*
	 * negTokenInitSize indicates the bytes needed to
	 * hold the ASN.1 encoding of the entire NegTokenInit
	 * SEQUENCE.
	 * 0xa0 [DER_LEN] + data
	 *
	 */
	negTokenInitSize = 1 +
		gssint_der_length_size(negTokenInitSeqSize) +
		negTokenInitSeqSize;

	tlen = g_token_size(gss_mech_spnego, negTokenInitSize);

	t = (unsigned char *) gssalloc_malloc(tlen);

	if (t == NULL) {
		return (-1);
	}

	ptr = t;

	/* create the message */
	if ((ret = g_make_token_header(gss_mech_spnego, negTokenInitSize,
			    &ptr, tlen)))
		goto errout;

	*ptr++ = CONTEXT; /* NegotiationToken identifier */
	if ((ret = gssint_put_der_length(negTokenInitSeqSize, &ptr, tlen)))
		goto errout;

	*ptr++ = SEQUENCE;
	if ((ret = gssint_put_der_length(negTokenInitContSize, &ptr,
					 tlen - (int)(ptr-t))))
		goto errout;

	*ptr++ = CONTEXT | 0x00; /* MechTypeList identifier */
	if ((ret = gssint_put_der_length(spnego_ctx->DER_mechTypes.length,
					 &ptr, tlen - (int)(ptr-t))))
		goto errout;

	/* We already encoded the MechSetList */
	(void) memcpy(ptr, spnego_ctx->DER_mechTypes.value,
		      spnego_ctx->DER_mechTypes.length);

	ptr += spnego_ctx->DER_mechTypes.length;

	if (data != NULL) {
		*ptr++ = CONTEXT | 0x02;
		if ((ret = gssint_put_der_length(rspTokenSize,
				&ptr, tlen - (int)(ptr - t))))
			goto errout;

		if ((ret = put_input_token(&ptr, data,
			tlen - (int)(ptr - t))))
			goto errout;
	}

	if (mechListMIC != GSS_C_NO_BUFFER) {
		*ptr++ = CONTEXT | 0x03;
		if ((ret = gssint_put_der_length(micTokenSize,
				&ptr, tlen - (int)(ptr - t))))
			goto errout;

		if (negHintsCompat) {
			ret = put_neg_hints(&ptr, mechListMIC,
					    tlen - (int)(ptr - t));
			if (ret)
				goto errout;
		} else if ((ret = put_input_token(&ptr, mechListMIC,
				tlen - (int)(ptr - t))))
			goto errout;
	}

errout:
	if (ret != 0) {
		if (t)
			free(t);
		t = NULL;
		tlen = 0;
	}
	outbuf->length = tlen;
	outbuf->value = (void *) t;

	return (ret);
}

/*
 * 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 int
make_spnego_tokenTarg_msg(OM_uint32 status, gss_OID mech_wanted,
			  gss_buffer_t data, gss_buffer_t mechListMIC,
			  send_token_flag sendtoken,
			  gss_buffer_t outbuf)
{
	unsigned int tlen = 0;
	unsigned int ret = 0;
	unsigned int NegTokenTargSize = 0;
	unsigned int NegTokenSize = 0;
	unsigned int rspTokenSize = 0;
	unsigned int micTokenSize = 0;
	unsigned int dataLen = 0;
	unsigned char *t;
	unsigned char *ptr;

	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;

	/*
	 * ASN.1 encoding of the negResult
	 * ENUMERATED type is 3 bytes
	 *  ENUMERATED TAG, Length, Value,
	 * Plus 2 bytes for the CONTEXT id and length.
	 */
	dataLen = 5;

	/*
	 * calculate data length
	 *
	 * If this is the initial token, include length of
	 * mech_type and the negotiation result fields.
	 */
	if (sendtoken == INIT_TOKEN_SEND) {
		int mechlistTokenSize;
		/*
		 * 1 byte for the CONTEXT ID(0xa0),
		 * 1 byte for the OID ID(0x06)
		 * 1 byte for OID Length field
		 * Plus the rest... (OID Length, OID value)
		 */
		mechlistTokenSize = 3 + mech_wanted->length +
			gssint_der_length_size(mech_wanted->length);

		dataLen += mechlistTokenSize;
	}
	if (data != NULL && data->length > 0) {
		/* Length of the inner token */
		rspTokenSize = 1 + gssint_der_length_size(data->length) +
			data->length;

		dataLen += rspTokenSize;

		/* Length of the outer token */
		dataLen += 1 + gssint_der_length_size(rspTokenSize);
	}
	if (mechListMIC != NULL) {

		/* Length of the inner token */
		micTokenSize = 1 + gssint_der_length_size(mechListMIC->length) +
			mechListMIC->length;

		dataLen += micTokenSize;

		/* Length of the outer token */
		dataLen += 1 + gssint_der_length_size(micTokenSize);
	}
	/*
	 * Add size of DER encoded:
	 * NegTokenTarg [ SEQUENCE ] of
	 *    NegResult[0] ENUMERATED {
	 *	accept_completed(0),
	 *	accept_incomplete(1),
	 *	reject(2) }
	 *    supportedMech [1] MechType OPTIONAL,
	 *    responseToken [2] OCTET STRING OPTIONAL,
	 *    mechListMIC   [3] OCTET STRING OPTIONAL
	 *
	 * size = data->length + MechListMic + SupportedMech len +
	 *	Result Length + ASN.1 overhead
	 */
	NegTokenTargSize = dataLen;
	dataLen += 1 + gssint_der_length_size(NegTokenTargSize);

	/*
	 * NegotiationToken [ CHOICE ]{
	 *    negTokenInit  [0]	 NegTokenInit,
	 *    negTokenTarg  [1]	 NegTokenTarg }
	 */
	NegTokenSize = dataLen;
	dataLen += 1 + gssint_der_length_size(NegTokenSize);

	tlen = dataLen;
	t = (unsigned char *) gssalloc_malloc(tlen);

	if (t == NULL) {
		ret = GSS_S_DEFECTIVE_TOKEN;
		goto errout;
	}

	ptr = t;

	/*
	 * Indicate that we are sending CHOICE 1
	 * (NegTokenTarg)
	 */
	*ptr++ = CONTEXT | 0x01;
	if (gssint_put_der_length(NegTokenSize, &ptr, dataLen) < 0) {
		ret = GSS_S_DEFECTIVE_TOKEN;
		goto errout;
	}
	*ptr++ = SEQUENCE;
	if (gssint_put_der_length(NegTokenTargSize, &ptr,
				  tlen - (int)(ptr-t)) < 0) {
		ret = GSS_S_DEFECTIVE_TOKEN;
		goto errout;
	}

	/*
	 * First field of the NegTokenTarg SEQUENCE
	 * is the ENUMERATED NegResult.
	 */
	*ptr++ = CONTEXT;
	if (gssint_put_der_length(3, &ptr,
				  tlen - (int)(ptr-t)) < 0) {
		ret = GSS_S_DEFECTIVE_TOKEN;
		goto errout;
	}
	if (put_negResult(&ptr, status, tlen - (int)(ptr - t)) < 0) {
		ret = GSS_S_DEFECTIVE_TOKEN;
		goto errout;
	}
	if (sendtoken == INIT_TOKEN_SEND) {
		/*
		 * Next, is the Supported MechType
		 */
		*ptr++ = CONTEXT | 0x01;
		if (gssint_put_der_length(mech_wanted->length + 2,
					  &ptr,
					  tlen - (int)(ptr - t)) < 0) {
			ret = GSS_S_DEFECTIVE_TOKEN;
			goto errout;
		}
		if (put_mech_oid(&ptr, mech_wanted,
				 tlen - (int)(ptr - t)) < 0) {
			ret = GSS_S_DEFECTIVE_TOKEN;
			goto errout;
		}
	}
	if (data != NULL && data->length > 0) {
		*ptr++ = CONTEXT | 0x02;
		if (gssint_put_der_length(rspTokenSize, &ptr,
					  tlen - (int)(ptr - t)) < 0) {
			ret = GSS_S_DEFECTIVE_TOKEN;
			goto errout;
		}
		if (put_input_token(&ptr, data,
				    tlen - (int)(ptr - t)) < 0) {
			ret = GSS_S_DEFECTIVE_TOKEN;
			goto errout;
		}
	}
	if (mechListMIC != NULL) {
		*ptr++ = CONTEXT | 0x03;
		if (gssint_put_der_length(micTokenSize, &ptr,
					  tlen - (int)(ptr - t)) < 0) {
			ret = GSS_S_DEFECTIVE_TOKEN;
			goto errout;
		}
		if (put_input_token(&ptr, mechListMIC,
				    tlen - (int)(ptr - t)) < 0) {
			ret = GSS_S_DEFECTIVE_TOKEN;
			goto errout;
		}
	}
	ret = GSS_S_COMPLETE;
errout:
	if (ret != GSS_S_COMPLETE) {
		if (t)
			free(t);
	} else {
		outbuf->length = ptr - t;
		outbuf->value = (void *) t;
	}

	return (ret);
}

/* determine size of token */
static int
g_token_size(gss_OID_const mech, unsigned int body_size)
{
	int hdrsize;

	/*
	 * Initialize the header size to the
	 * MECH_OID byte + the bytes needed to indicate the
	 * length of the OID + the OID itself.
	 *
	 * 0x06 [MECHLENFIELD] MECHDATA
	 */
	hdrsize = 1 + gssint_der_length_size(mech->length) + mech->length;

	/*
	 * Now add the bytes needed for the initial header
	 * token bytes:
	 * 0x60 + [DER_LEN] + HDRSIZE
	 */
	hdrsize += 1 + gssint_der_length_size(body_size + hdrsize);

	return (hdrsize + body_size);
}

/*
 * generate token header.
 *
 * Use DER Definite Length method per RFC2478
 * Use of indefinite length encoding will not be compatible
 * with Microsoft or others that actually follow the spec.
 */
static int
g_make_token_header(gss_OID_const mech,
		    unsigned int body_size,
		    unsigned char **buf,
		    unsigned int totallen)
{
	int ret = 0;
	unsigned int hdrsize;
	unsigned char *p = *buf;

	hdrsize = 1 + gssint_der_length_size(mech->length) + mech->length;

	*(*buf)++ = HEADER_ID;
	if ((ret = gssint_put_der_length(hdrsize + body_size, buf, totallen)))
		return (ret);

	*(*buf)++ = MECH_OID;
	if ((ret = gssint_put_der_length(mech->length, buf,
			    totallen - (int)(p - *buf))))
		return (ret);
	TWRITE_STR(*buf, mech->elements, mech->length);
	return (0);
}

/*
 * NOTE: This checks that the length returned by
 * gssint_get_der_length() is not greater than the number of octets
 * remaining, even though gssint_get_der_length() already checks, in
 * theory.
 */
static int
g_get_tag_and_length(unsigned char **buf, int tag,
		     unsigned int buflen, unsigned int *outlen)
{
	unsigned char *ptr = *buf;
	int ret = -1; /* pessimists, assume failure ! */
	unsigned int encoded_len;
	int tmplen = 0;

	*outlen = 0;
	if (buflen > 1 && *ptr == tag) {
		ptr++;
		tmplen = gssint_get_der_length(&ptr, buflen - 1,
						&encoded_len);
		if (tmplen < 0) {
			ret = -1;
		} else if ((unsigned int)tmplen > buflen - (ptr - *buf)) {
			ret = -1;
		} else
			ret = 0;
	}
	*outlen = tmplen;
	*buf = ptr;
	return (ret);
}

static int
g_verify_neg_token_init(unsigned char **buf_in, unsigned int cur_size)
{
	unsigned char *buf = *buf_in;
	unsigned char *endptr = buf + cur_size;
	int seqsize;
	int ret = 0;
	unsigned int bytes;

	/*
	 * Verify this is a NegotiationToken type token
	 * - check for a0(context specific identifier)
	 * - get length and verify that enoughd ata exists
	 */
	if (g_get_tag_and_length(&buf, CONTEXT, cur_size, &bytes) < 0)
		return (G_BAD_TOK_HEADER);

	cur_size = bytes; /* should indicate bytes remaining */

	/*
	 * Verify the next piece, it should identify this as
	 * a strucure of type NegTokenInit.
	 */
	if (*buf++ == SEQUENCE) {
		if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0)
			return (G_BAD_TOK_HEADER);
		/*
		 * Make sure we have the entire buffer as described
		 */
		if (seqsize > endptr - buf)
			return (G_BAD_TOK_HEADER);
	} else {
		return (G_BAD_TOK_HEADER);
	}

	cur_size = seqsize; /* should indicate bytes remaining */

	/*
	 * Verify that the first blob is a sequence of mechTypes
	 */
	if (*buf++ == CONTEXT) {
		if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0)
			return (G_BAD_TOK_HEADER);
		/*
		 * Make sure we have the entire buffer as described
		 */
		if (seqsize > endptr - buf)
			return (G_BAD_TOK_HEADER);
	} else {
		return (G_BAD_TOK_HEADER);
	}

	/*
	 * At this point, *buf should be at the beginning of the
	 * DER encoded list of mech types that are to be negotiated.
	 */
	*buf_in = buf;

	return (ret);

}

/* verify token header. */
static int
g_verify_token_header(gss_OID_const mech,
		    unsigned int *body_size,
		    unsigned char **buf_in,
		    int tok_type,
		    unsigned int toksize)
{
	unsigned char *buf = *buf_in;
	int seqsize;
	gss_OID_desc toid;
	int ret = 0;
	unsigned int bytes;

	if (toksize-- < 1)
		return (G_BAD_TOK_HEADER);

	if (*buf++ != HEADER_ID)
		return (G_BAD_TOK_HEADER);

	if ((seqsize = gssint_get_der_length(&buf, toksize, &bytes)) < 0)
		return (G_BAD_TOK_HEADER);

	if ((seqsize + bytes) != toksize)
		return (G_BAD_TOK_HEADER);

	if (toksize-- < 1)
		return (G_BAD_TOK_HEADER);


	if (*buf++ != MECH_OID)
		return (G_BAD_TOK_HEADER);

	if (toksize-- < 1)
		return (G_BAD_TOK_HEADER);

	toid.length = *buf++;

	if (toksize < toid.length)
		return (G_BAD_TOK_HEADER);
	else
		toksize -= toid.length;

	toid.elements = buf;
	buf += toid.length;

	if (!g_OID_equal(&toid, mech))
		ret = G_WRONG_MECH;

	/*
	 * G_WRONG_MECH is not returned immediately because it's more important
	 * to return G_BAD_TOK_HEADER if the token header is in fact bad
	 */
	if (toksize < 2)
		return (G_BAD_TOK_HEADER);
	else
		toksize -= 2;

	if (!ret) {
		*buf_in = buf;
		*body_size = toksize;
	}

	return (ret);
}

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