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

Fix null deref in SPNEGO acceptor [CVE-2014-4344]

When processing a continuation token, acc_ctx_cont was dereferencing
the initial byte of the token without checking the length.  This could
result in a null dereference.

CVE-2014-4344:

In MIT krb5 1.5 and newer, an unauthenticated or partially
authenticated remote attacker can cause a NULL dereference and
application crash during a SPNEGO negotiation by sending an empty
token as the second or later context token from initiator to acceptor.
The attacker must provide at least one valid context token in the
security context negotiation before sending the empty token.  This can
be done by an unauthenticated attacker by forcing SPNEGO to
renegotiate the underlying mechanism, or by using IAKERB to wrap an
unauthenticated AS-REQ as the first token.

    CVSSv2 Vector: AV:N/AC:L/Au:N/C:N/I:N/A:C/E:POC/RL:OF/RC:C

[kaduk@mit.edu: CVE summary, CVSSv2 vector]

ticket: 7970 (new)
subject: NULL dereference in SPNEGO acceptor for continuation tokens [CVE-2014-4344]
target_version: 1.12.2
tags: pullup


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)
{
	unsigned int	i;
	int		found = 0;
	OM_uint32 major_status = GSS_S_COMPLETE, tmpmin;
	gss_OID_set mechs, goodmechs;

	major_status = gss_indicate_mechs(minor_status, &mechs);

	if (major_status != GSS_S_COMPLETE) {
		return (major_status);
	}

	major_status = gss_create_empty_oid_set(minor_status, rmechs);

	if (major_status != GSS_S_COMPLETE) {
		(void) gss_release_oid_set(minor_status, &mechs);
		return (major_status);
	}

	for (i = 0; i < mechs->count && major_status == GSS_S_COMPLETE; i++) {
		if ((mechs->elements[i].length
		    != spnego_mechanism.mech_type.length) ||
		    memcmp(mechs->elements[i].elements,
			spnego_mechanism.mech_type.elements,
			spnego_mechanism.mech_type.length)) {

			major_status = gss_add_oid_set_member(minor_status,
							      &mechs->elements[i],
							      rmechs);
			if (major_status == GSS_S_COMPLETE)
				found++;
		}
	}

	/*
	 * 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 (found > 0 && major_status == GSS_S_COMPLETE && creds != NULL) {
		major_status = gss_acquire_cred_from(minor_status, name,
						     GSS_C_INDEFINITE,
						     *rmechs, usage,
						     cred_store, creds,
						     &goodmechs, NULL);

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

	(void) gss_release_oid_set(&tmpmin, &mechs);
	if (found == 0 || major_status != GSS_S_COMPLETE) {
		*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);
}


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;
	ret = gss_unwrap(minor_status,
			context_handle,
			input_message_buffer,
			output_message_buffer,
			conf_state,
			qop_state);

	return (ret);
}


acc_ctx_new(OM_uint32 *minor_status,
	    gss_buffer_t buf,
	    gss_ctx_id_t *ctx,
	    spnego_gss_cred_id_t spcred,
	    gss_buffer_t *mechToken,
	    gss_buffer_t *mechListMIC,
	    OM_uint32 *negState,
	    send_token_flag *return_token)
{
	OM_uint32 tmpmin, ret, req_flags;
	gss_OID_set supported_mechSet, 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;
	supported_mechSet = 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;
	}
	ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT,
				   &supported_mechSet);
	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(supported_mechSet, mechTypes, negState);
	if (*negState == REJECT) {
		ret = GSS_S_BAD_MECH;
		goto cleanup;
	}
	sc = (spnego_gss_ctx_id_t)*ctx;
	if (sc != NULL) {
		gss_release_buffer(&tmpmin, &sc->DER_mechTypes);
		assert(mech_wanted != GSS_C_NO_OID);
	} else
		sc = create_spnego_ctx();
	if (sc == NULL) {
		ret = GSS_S_FAILURE;
		*return_token = NO_TOKEN_SEND;
		goto cleanup;
	}
	sc->mech_set = mechTypes;
	mechTypes = GSS_C_NO_OID_SET;
	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;
	*ctx = (gss_ctx_id_t)sc;
	ret = GSS_S_COMPLETE;
cleanup:
	gss_release_oid_set(&tmpmin, &mechTypes);
	gss_release_oid_set(&tmpmin, &supported_mechSet);
	if (der_mechTypes.length != 0)
		gss_release_buffer(&tmpmin, &der_mechTypes);

	return ret;
}


create_spnego_ctx(void)
{
	spnego_gss_ctx_id_t spnego_ctx = NULL;
	spnego_ctx = (spnego_gss_ctx_id_t)
		malloc(sizeof (spnego_gss_ctx_id_rec));

	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->optionStr = NULL;
	spnego_ctx->DER_mechTypes.length = 0;
	spnego_ctx->DER_mechTypes.value = NULL;
	spnego_ctx->default_cred = GSS_C_NO_CREDENTIAL;
	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->internal_name = GSS_C_NO_NAME;
	spnego_ctx->actual_mech = GSS_C_NO_OID;

	check_spnego_options(spnego_ctx);

	return (spnego_ctx);
}


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


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

		if (context->optionStr != NULL) {
			free(context->optionStr);
			context->optionStr = NULL;
		}
		free(context);
		*ctx = NULL;
	}
}


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)
{
    return gss_verify_mic_iov(minor_status, context_handle, qop_state, iov,
			      iov_count);
}


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


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,
		  OM_uint32 *negState, 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];

	/*
	 * Windows 2003 and earlier don't correctly send a
	 * negState of request-mic when counter-proposing a
	 * mechanism.  They probably don't handle mechListMICs
	 * properly either.
	 */
	if (acc_negState != REQUEST_MIC)
		return GSS_S_DEFECTIVE_TOKEN;

	sc->mech_complete = 0;
	sc->mic_reqd = 1;
	*negState = REQUEST_MIC;
	*tokflag = CONT_TOKEN_SEND;
	return GSS_S_CONTINUE_NEEDED;
}


acc_ctx_cont(OM_uint32 *minstat,
	     gss_buffer_t buf,
	     gss_ctx_id_t *ctx,
	     gss_buffer_t *responseToken,
	     gss_buffer_t *mechListMIC,
	     OM_uint32 *negState,
	     send_token_flag *return_token)
{
	OM_uint32 ret, tmpmin;
	gss_OID supportedMech;
	spnego_gss_ctx_id_t sc;
	unsigned int len;
	unsigned char *ptr, *bufstart;

	sc = (spnego_gss_ctx_id_t)*ctx;
	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
}


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


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


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;
	ret = gss_wrap_size_limit(minor_status,
				context_handle,
				conf_req_flag,
				qop_req,
				req_output_size,
				max_input_size);
	return (ret);
}


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


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


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


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_OID *mech_type, gss_buffer_t mechtok_out,
		 OM_uint32 *ret_flags, OM_uint32 *time_rec,
		 gss_cred_id_t *delegated_cred_handle,
		 OM_uint32 *negState, send_token_flag *tokflag)
{
	OM_uint32 ret;
	gss_OID_desc mechoid;
	gss_cred_id_t mcred;

	if (sc->ctx_handle == GSS_C_NO_CONTEXT) {
		/*
		 * 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;
	ret = gss_accept_sec_context(minor_status,
				     &sc->ctx_handle,
				     mcred,
				     mechtok_in,
				     GSS_C_NO_CHANNEL_BINDINGS,
				     &sc->internal_name,
				     mech_type,
				     mechtok_out,
				     &sc->ctx_flags,
				     time_rec,
				     delegated_cred_handle);
	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 (ret_flags != NULL)
			*ret_flags = sc->ctx_flags;

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


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


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;
	ret = gss_verify_mic(minor_status,
			    context_handle,
			    msg_buffer,
			    token_buffer,
			    qop_state);
	return (ret);
}


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);
	spcred = malloc(sizeof(*spcred));
	if (spcred == NULL) {
		gss_release_cred(minor_status, &mcred);
		*minor_status = ENOMEM;
		return (GSS_S_FAILURE);
	}
	spcred->mcred = mcred;
	spcred->neg_mechs = GSS_C_NULL_OID_SET;
	*cred_handle = (gss_cred_id_t)spcred;
	return (ret);
}


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


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
}


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


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


negotiate_mech(gss_OID_set supported, gss_OID_set received,
	       OM_uint32 *negResult)
{
	size_t i, j;

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

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

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


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.
		 */

		spcred = malloc(sizeof(spnego_gss_cred_id_rec));
		if (spcred == NULL) {
			gss_release_cred(&tmp_minor_status, &mcred);
			*minor_status = ENOMEM;
			return (GSS_S_FAILURE);
		}
		spcred->mcred = mcred;
		spcred->neg_mechs = GSS_C_NULL_OID_SET;
		*cred_handle = (gss_cred_id_t)spcred;
	}

	return (ret);
}


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

	spcred = malloc(sizeof(spnego_gss_cred_id_rec));
	if (spcred == NULL) {
		*minor_status = ENOMEM;
		status = GSS_S_FAILURE;
		goto cleanup;
	}
	spcred->neg_mechs = GSS_C_NULL_OID_SET;
	spcred->mcred = mcred;
	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);
}


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


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


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;
	ret = gss_complete_auth_token(minor_status,
				      context_handle,
				      input_message_buffer);
	return (ret);
}


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,
	      OM_uint32 *negState, send_token_flag *tokflag)
{
	OM_uint32 ret;

	*negState = REJECT;
	*tokflag = ERROR_TOKEN_SEND;
	ret = GSS_S_DEFECTIVE_TOKEN;
	/*
	 * Both supportedMech and negState must be present in first
	 * acceptor token.
	 */
	if (supportedMech == GSS_C_NO_OID) {
		*minor_status = ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR;
		map_errcode(minor_status);
		return GSS_S_DEFECTIVE_TOKEN;
	}
	if (acc_negState == ACCEPT_DEFECTIVE_TOKEN) {
		*minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
		map_errcode(minor_status);
		return GSS_S_DEFECTIVE_TOKEN;
	}

	/*
	 * 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,
					negState, 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.
			 */
			*negState = ACCEPT_COMPLETE;
			*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. */
		*negState = ACCEPT_COMPLETE;
		*tokflag = NO_TOKEN_SEND;
		ret = GSS_S_COMPLETE;
	} else if (sc->mech_complete) {
		/* Reject spurious mech token. */
		ret = GSS_S_DEFECTIVE_TOKEN;
	} else {
		*negState = ACCEPT_INCOMPLETE;
		*tokflag = CONT_TOKEN_SEND;
		ret = GSS_S_CONTINUE_NEEDED;
	}
	sc->nego_done = 1;
	return ret;
}


check_spnego_options(spnego_gss_ctx_id_t spnego_ctx)
{
	spnego_ctx->optionStr = gssint_get_modOptions(
		(const gss_OID)&spnego_oids[0]);
}


init_ctx_call_init(OM_uint32 *minor_status,
		   spnego_gss_ctx_id_t sc,
		   spnego_gss_cred_id_t spcred,
		   gss_name_t target_name,
		   OM_uint32 req_flags,
		   OM_uint32 time_req,
		   gss_buffer_t mechtok_in,
		   gss_OID *actual_mech,
		   gss_buffer_t mechtok_out,
		   OM_uint32 *ret_flags,
		   OM_uint32 *time_rec,
		   OM_uint32 *negState,
		   send_token_flag *send_token)
{
	OM_uint32 ret, tmpret, tmpmin;
	gss_cred_id_t mcred;

	mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
	ret = gss_init_sec_context(minor_status,
				   mcred,
				   &sc->ctx_handle,
				   target_name,
				   sc->internal_mech,
				   (req_flags | GSS_C_INTEG_FLAG),
				   time_req,
				   GSS_C_NO_CHANNEL_BINDINGS,
				   mechtok_in,
				   &sc->actual_mech,
				   mechtok_out,
				   &sc->ctx_flags,
				   time_rec);
	if (ret == GSS_S_COMPLETE) {
		sc->mech_complete = 1;
		if (ret_flags != NULL)
			*ret_flags = sc->ctx_flags;
		/*
		 * 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))) {
			/* The exchange is complete. */
			*negState = ACCEPT_COMPLETE;
			ret = GSS_S_COMPLETE;
			*send_token = NO_TOKEN_SEND;
		} else {
			/* Ask for one more hop. */
			*negState = ACCEPT_INCOMPLETE;
			ret = GSS_S_CONTINUE_NEEDED;
		}
		return ret;
	}

	if (ret == GSS_S_CONTINUE_NEEDED)
		return ret;

	if (*send_token != INIT_TOKEN_SEND) {
		*send_token = ERROR_TOKEN_SEND;
		*negState = REJECT;
		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;
	tmpret = init_ctx_call_init(&tmpmin, sc, spcred, target_name,
				    req_flags, time_req, mechtok_in,
				    actual_mech, mechtok_out, ret_flags,
				    time_rec, negState, 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;
	*negState = REJECT;
	return ret;
}


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


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;
	ret = gss_unwrap_iov(minor_status,
			     context_handle,
			     conf_state,
			     qop_state,
			     iov,
			     iov_count);
	return (ret);
}


init_ctx_cont(OM_uint32 *minor_status, gss_ctx_id_t *ctx, gss_buffer_t buf,
	      gss_buffer_t *responseToken, gss_buffer_t *mechListMIC,
	      OM_uint32 *negState, send_token_flag *tokflag)
{
	OM_uint32 ret, tmpmin, acc_negState;
	unsigned char *ptr;
	spnego_gss_ctx_id_t sc;
	gss_OID supportedMech = GSS_C_NO_OID;

	sc = (spnego_gss_ctx_id_t)*ctx;
	*negState = REJECT;
	*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;
	if (acc_negState == REJECT) {
		*minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
		map_errcode(minor_status);
		*tokflag = NO_TOKEN_SEND;
		ret = GSS_S_FAILURE;
		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,
				    negState, 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. */
		*negState = ACCEPT_INCOMPLETE;
		*tokflag = CONT_TOKEN_SEND;
		ret = GSS_S_CONTINUE_NEEDED;
	} else {
		/* mech finished on last pass and no MIC required, so done. */
		*negState = ACCEPT_COMPLETE;
		*tokflag = NO_TOKEN_SEND;
		ret = GSS_S_COMPLETE;
	}
cleanup:
	if (supportedMech != GSS_C_NO_OID)
		generic_gss_release_oid(&tmpmin, &supportedMech);
	return ret;
}


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;

	ret = gss_inquire_context(minor_status,
				context_handle,
				src_name,
				targ_name,
				lifetime_rec,
				mech_type,
				ctx_flags,
				locally_initiated,
				opened);

	return (ret);
}


gss_mech_initialize(void)
{
	return (&spnego_mechanism);
}


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


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


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


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;
	ret = gss_wrap_aead(minor_status,
			    context_handle,
			    conf_req_flag,
			    qop_req,
			    input_assoc_buffer,
			    input_payload_buffer,
			    conf_state,
			    output_message_buffer);

	return (ret);
}


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


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;
	ret = gss_process_context_token(minor_status,
					context_handle,
					token_buffer);

	return (ret);
}


acc_ctx_hints(OM_uint32 *minor_status,
	      gss_ctx_id_t *ctx,
	      spnego_gss_cred_id_t spcred,
	      gss_buffer_t *mechListMIC,
	      OM_uint32 *negState,
	      send_token_flag *return_token)
{
	OM_uint32 tmpmin, ret;
	gss_OID_set supported_mechSet;
	spnego_gss_ctx_id_t sc = NULL;

	*mechListMIC = GSS_C_NO_BUFFER;
	supported_mechSet = GSS_C_NO_OID_SET;
	*return_token = NO_TOKEN_SEND;
	*negState = REJECT;
	*minor_status = 0;

	/* A hint request must be the first token received. */
	if (*ctx != GSS_C_NO_CONTEXT)
	    return GSS_S_DEFECTIVE_TOKEN;

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

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

	sc = create_spnego_ctx();
	if (sc == NULL) {
		ret = GSS_S_FAILURE;
		goto cleanup;
	}
	if (put_mech_set(supported_mechSet, &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;
	*ctx = (gss_ctx_id_t)sc;
	sc = NULL;
	ret = GSS_S_COMPLETE;

cleanup:
	release_spnego_ctx(&sc);
	gss_release_oid_set(&tmpmin, &supported_mechSet);

	return ret;
}


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


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;
	ret = gss_inquire_sec_context_by_oid(minor_status,
			    context_handle,
			    desired_object,
			    data_set);
	return (ret);
}


init_ctx_new(OM_uint32 *minor_status,
	     spnego_gss_cred_id_t spcred,
	     gss_ctx_id_t *ctx,
	     send_token_flag *tokflag)
{
	OM_uint32 ret;
	spnego_gss_ctx_id_t sc = NULL;

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

	/* determine negotiation mech set */
	ret = get_negotiable_mechs(minor_status, spcred, GSS_C_INITIATE,
				   &sc->mech_set);
	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;
	}
	/*
	 * The actual context is not yet determined, set the output
	 * context handle to refer to the spnego context itself.
	 */
	sc->ctx_handle = GSS_C_NO_CONTEXT;
	*ctx = (gss_ctx_id_t)sc;
	sc = NULL;
	*tokflag = INIT_TOKEN_SEND;
	ret = GSS_S_CONTINUE_NEEDED;

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


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
}


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


get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_cred_id_t spcred,
		     gss_cred_usage_t usage, gss_OID_set *rmechs)
{
	OM_uint32 ret, tmpmin;
	gss_cred_id_t creds = GSS_C_NO_CREDENTIAL, *credptr;
	gss_OID_set cred_mechs = GSS_C_NULL_OID_SET;
	gss_OID_set intersect_mechs = GSS_C_NULL_OID_SET;
	unsigned int i;
	int present;

	if (spcred == NULL) {
		/*
		 * The default credentials were supplied.  Return a list of all
		 * available mechs except SPNEGO.  When initiating, trim this
		 * list to mechs we can acquire credentials for.
		 */
		credptr = (usage == GSS_C_INITIATE) ? &creds : NULL;
		ret = get_available_mechs(minor_status, GSS_C_NO_NAME, usage,
					  GSS_C_NO_CRED_STORE, credptr,
					  rmechs);
		gss_release_cred(&tmpmin, &creds);
		return (ret);
	}

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

	if (spcred->neg_mechs == GSS_C_NULL_OID_SET) {
		/* gss_set_neg_mechs was never called; return cred_mechs. */
		*rmechs = cred_mechs;
		*minor_status = 0;
		return (GSS_S_COMPLETE);
	}

	/* Compute the intersection of cred_mechs and spcred->neg_mechs,
	 * preserving the order in spcred->neg_mechs. */
	ret = gss_create_empty_oid_set(minor_status, &intersect_mechs);
	if (ret != GSS_S_COMPLETE) {
		gss_release_oid_set(&tmpmin, &cred_mechs);
		return (ret);
	}

	for (i = 0; i < spcred->neg_mechs->count; i++) {
		gss_test_oid_set_member(&tmpmin,
					&spcred->neg_mechs->elements[i],
					cred_mechs, &present);
		if (!present)
			continue;
		ret = gss_add_oid_set_member(minor_status,
					     &spcred->neg_mechs->elements[i],
					     &intersect_mechs);
		if (ret != GSS_S_COMPLETE)
			break;
	}

	gss_release_oid_set(&tmpmin, &cred_mechs);
	if (intersect_mechs->count == 0 || ret != GSS_S_COMPLETE) {
		gss_release_oid_set(&tmpmin, &intersect_mechs);
		*minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE;
		map_errcode(minor_status);
		return (GSS_S_FAILURE);
	}

	*rmechs = intersect_mechs;
	*minor_status = 0;
	return (GSS_S_COMPLETE);
}


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);
	return (ret);
}


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;
	ret = gss_get_mic(minor_status,
		    context_handle,
		    qop_req,
		    message_buffer,
		    message_token);
	return (ret);
}


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


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 input_chan_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;
	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;
	negState = REJECT;

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

	/* Step 1: perform mechanism negotiation. */
	spcred = (spnego_gss_cred_id_t)claimant_cred_handle;
	if (*context_handle == GSS_C_NO_CONTEXT) {
		ret = init_ctx_new(minor_status, spcred,
				   context_handle, &send_token);
		if (ret != GSS_S_CONTINUE_NEEDED) {
			goto cleanup;
		}
	} else {
		ret = init_ctx_cont(minor_status, context_handle,
				    input_token, &mechtok_in,
				    &mechListMIC_in, &negState, &send_token);
		if (HARD_ERROR(ret)) {
			goto cleanup;
		}
	}

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

		/* Give the mechanism a chance to force a mechlistMIC. */
		if (!HARD_ERROR(ret) && 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. */
	if (!HARD_ERROR(ret) && 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);
	}
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 (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) {
		/*
		 * Now, switch the output context to refer to the
		 * negotiated mechanism's context.
		 */
		*context_handle = (gss_ctx_id_t)spnego_ctx->ctx_handle;
		if (actual_mech != NULL)
			*actual_mech = spnego_ctx->actual_mech;
		if (ret_flags != NULL)
			*ret_flags = spnego_ctx->ctx_flags;
		release_spnego_ctx(&spnego_ctx);
	} 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 */


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


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;
	ret = gss_unwrap_aead(minor_status,
			      context_handle,
			      input_message_buffer,
			      input_assoc_buffer,
			      output_payload_buffer,
			      conf_state,
			      qop_state);
	return (ret);
}


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_in != SEQUENCE_OF)
		return (NULL);

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

	length = gssint_get_der_length(buff_in, buff_length, &bytes);
	if (length < 0 || buff_length - 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;
			if (generic_gss_release_oid(minor_status, &temp))
				map_errcode(minor_status);
		}
	}

	return (returned_mechSet);
}


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


spnego_gss_context_time(
			OM_uint32	*minor_status,
			const gss_ctx_id_t context_handle,
			OM_uint32	*time_rec)
{
	OM_uint32 ret;
	ret = gss_context_time(minor_status,
			    context_handle,
			    time_rec);
	return (ret);
}


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

	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;
	if (desired_mechs == GSS_C_NO_OID_SET) {
		status = gss_inquire_cred(minor_status, imp_mcred, NULL, NULL,
					  NULL, &amechs);
		if (status != GSS_S_COMPLETE)
			return status;

		desired_mechs = amechs;
	}

	status = gss_acquire_cred_impersonate_name(minor_status, imp_mcred,
						   desired_name, time_req,
						   desired_mechs, cred_usage,
						   &out_mcred, actual_mechs,
						   time_rec);

	if (amechs != GSS_C_NULL_OID_SET)
		(void) gss_release_oid_set(minor_status, &amechs);

	out_spcred = malloc(sizeof(spnego_gss_cred_id_rec));
	if (out_spcred == NULL) {
		gss_release_cred(minor_status, &out_mcred);
		*minor_status = ENOMEM;
		return (GSS_S_FAILURE);
	}
	out_spcred->mcred = out_mcred;
	out_spcred->neg_mechs = GSS_C_NULL_OID_SET;
	*output_cred_handle = (gss_cred_id_t)out_spcred;

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


make_spnego_token(const char *name)
{
	return (spnego_token_t)strdup(name);
}


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


get_req_flags(unsigned char **buff_in, OM_uint32 bodysize,
	      OM_uint32 *req_flags)
{
	unsigned int len;

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

	if (g_get_tag_and_length(buff_in, (CONTEXT | 0x01),
				bodysize, &len) < 0)
		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);
}


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


make_NegHints(OM_uint32 *minor_status,
	      spnego_gss_cred_id_t spcred, gss_buffer_t *outbuf)
{
	gss_buffer_desc hintNameBuf;
	gss_name_t hintName = GSS_C_NO_NAME;
	gss_name_t hintKerberosName;
	gss_OID hintNameType;
	OM_uint32 major_status;
	OM_uint32 minor;
	unsigned int tlen = 0;
	unsigned int hintNameSize = 0;
	unsigned char *ptr;
	unsigned char *t;

	*outbuf = GSS_C_NO_BUFFER;

	if (spcred != NULL) {
		major_status = gss_inquire_cred(minor_status,
						spcred->mcred,
						&hintName,
						NULL,
						NULL,
						NULL);
		if (major_status != GSS_S_COMPLETE)
			return (major_status);
	}

	if (hintName == GSS_C_NO_NAME) {
		krb5_error_code code;
		krb5int_access kaccess;
		char hostname[HOST_PREFIX_LEN + MAXHOSTNAMELEN + 1] = HOST_PREFIX;

		code = krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION);
		if (code != 0) {
			*minor_status = code;
			return (GSS_S_FAILURE);
		}

		/* this breaks mutual authentication but Samba relies on it */
		code = (*kaccess.clean_hostname)(NULL, NULL,
						 &hostname[HOST_PREFIX_LEN],
						 MAXHOSTNAMELEN);
		if (code != 0) {
			*minor_status = code;
			return (GSS_S_FAILURE);
		}

		hintNameBuf.value = hostname;
		hintNameBuf.length = strlen(hostname);

		major_status = gss_import_name(minor_status,
					       &hintNameBuf,
					       GSS_C_NT_HOSTBASED_SERVICE,
					       &hintName);
		if (major_status != GSS_S_COMPLETE) {
			return (major_status);
		}
	}

	hintNameBuf.value = NULL;
	hintNameBuf.length = 0;

	major_status = gss_canonicalize_name(minor_status,
					     hintName,
					     (gss_OID)&gss_mech_krb5_oid,
					     &hintKerberosName);
	if (major_status != GSS_S_COMPLETE) {
		gss_release_name(&minor, &hintName);
		return (major_status);
	}
	gss_release_name(&minor, &hintName);

	major_status = gss_display_name(minor_status,
					hintKerberosName,
					&hintNameBuf,
					&hintNameType);
	if (major_status != GSS_S_COMPLETE) {
		gss_release_name(&minor, &hintName);
		return (major_status);
	}
	gss_release_name(&minor, &hintKerberosName);

	/*
	 * Now encode the name hint into a NegHints ASN.1 type
	 */
	major_status = GSS_S_FAILURE;

	/* Length of DER encoded GeneralString */
	tlen = 1 + gssint_der_length_size(hintNameBuf.length) +
		hintNameBuf.length;
	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(hintNameBuf.length,
				  &ptr, tlen - (int)(ptr-t)))
		goto errout;

	memcpy(ptr, hintNameBuf.value, hintNameBuf.length);
	ptr += hintNameBuf.length;

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

	gss_release_buffer(&minor, &hintNameBuf);

	return (major_status);
}


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;
	ret = gss_wrap_iov(minor_status,
			   context_handle,
			   conf_req_flag,
			   qop_req,
			   conf_state,
			   iov,
			   iov_count);
	return (ret);
}


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


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

	/*
	 * If this is still an SPNEGO mech, release it locally.
	 */
	if ((*ctx)->magic_num == SPNEGO_MAGIC_ID) {
		(void) gss_delete_sec_context(minor_status,
				    &(*ctx)->ctx_handle,
				    output_token);
		(void) release_spnego_ctx(ctx);
	} else {
		ret = gss_delete_sec_context(minor_status,
				    context_handle,
				    output_token);
	}

	return (ret);
}


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
}


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


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


spnego_gss_export_sec_context(
			    OM_uint32	  *minor_status,
			    gss_ctx_id_t *context_handle,
			    gss_buffer_t interprocess_token)
{
	OM_uint32 ret;
	ret = gss_export_sec_context(minor_status,
				    context_handle,
				    interprocess_token);
	return (ret);
}


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 = ACCEPT_DEFECTIVE_TOKEN;
	*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
}


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)
{
    return gss_get_mic_iov_length(minor_status, context_handle, qop_req, iov,
				  iov_count);
}


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;
	ret = gss_wrap(minor_status,
		    context_handle,
		    conf_req_flag,
		    qop_req,
		    input_message_buffer,
		    conf_state,
		    output_message_buffer);

	return (ret);
}


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


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


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;
	ret = gss_wrap_iov_length(minor_status,
				  context_handle,
				  conf_req_flag,
				  qop_req,
				  conf_state,
				  iov,
				  iov_count);
	return (ret);
}


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. */
	spcred = malloc(sizeof(spnego_gss_cred_id_rec));
	if (spcred == NULL) {
		*minor_status = ENOMEM;
		return (GSS_S_FAILURE);
	}
	spcred->neg_mechs = GSS_C_NULL_OID_SET;

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

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


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


spnego_gss_import_sec_context(
	OM_uint32		*minor_status,
	const gss_buffer_t	interprocess_token,
	gss_ctx_id_t		*context_handle)
{
	OM_uint32 ret;
	ret = gss_import_sec_context(minor_status,
				    interprocess_token,
				    context_handle);
	return (ret);
}


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;
	int ret;

	dsyslog("Entering display_status\n");

	*message_context = 0;
	switch (status_value) {
	    case ERR_SPNEGO_NO_MECHS_AVAILABLE:
		/* CSTYLED */
		*status_string = make_err_msg(_("SPNEGO cannot find "
						"mechanisms to negotiate"));
		break;
	    case ERR_SPNEGO_NO_CREDS_ACQUIRED:
		/* CSTYLED */
		*status_string = make_err_msg(_("SPNEGO failed to acquire "
						"creds"));
		break;
	    case ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR:
		/* CSTYLED */
		*status_string = make_err_msg(_("SPNEGO acceptor did not "
						"select a mechanism"));
		break;
	    case ERR_SPNEGO_NEGOTIATION_FAILED:
		/* CSTYLED */
		*status_string = make_err_msg(_("SPNEGO failed to negotiate a "
						"mechanism"));
		break;
	    case ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR:
		/* CSTYLED */
		*status_string = make_err_msg(_("SPNEGO acceptor did not "
						"return a valid token"));
		break;
	    default:
		/* 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);
			break;
		}
		/* 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;
			maj = GSS_S_FAILURE;
			break;
		}
		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);
		break;
	}

	dsyslog("Leaving display_status\n");
	return maj;
}


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


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


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;
	ret = gss_set_sec_context_option(minor_status,
			    context_handle,
			    desired_object,
			    value);
	return (ret);
}


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


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;
	ret = gss_pseudo_random(minor_status,
				context,
				prf_key,
				prf_in,
				desired_output_len,
				prf_out);
        return (ret);
}


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


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 input_chan_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 (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 || sc->internal_mech == GSS_C_NO_OID) {
		/* Process an initial token or request for NegHints. */
		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 (input_token->length == 0) {
			ret = acc_ctx_hints(minor_status,
					    context_handle, spcred,
					    &mic_out,
					    &negState,
					    &return_token);
			if (ret != GSS_S_COMPLETE)
				goto cleanup;
			sendTokenInit = 1;
			ret = GSS_S_CONTINUE_NEEDED;
		} else {
			/* Can set negState to REQUEST_MIC */
			ret = acc_ctx_new(minor_status, input_token,
					  context_handle, spcred,
					  &mechtok_in, &mic_in,
					  &negState, &return_token);
			if (ret != GSS_S_COMPLETE)
				goto cleanup;
			ret = GSS_S_CONTINUE_NEEDED;
		}
	} else {
		/* Process a response token.  Can set negState to
		 * ACCEPT_INCOMPLETE. */
		ret = acc_ctx_cont(minor_status, input_token,
				   context_handle, &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. */
	sc = (spnego_gss_ctx_id_t)*context_handle;
	/*
	 * 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, mech_type, &mechtok_out,
				       ret_flags, time_rec,
				       delegated_cred_handle,
				       &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);
	}
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) {
		*context_handle = (gss_ctx_id_t)sc->ctx_handle;
		if (sc->internal_name != GSS_C_NO_NAME &&
		    src_name != NULL) {
			*src_name = sc->internal_name;
			sc->internal_name = GSS_C_NO_NAME;
		}
		release_spnego_ctx(&sc);
	} 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;
}


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

}


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 char		*start, *end;

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

	start = *buff_in;
	end = start + length;

	(*buff_in)++;
	toid.length = *(*buff_in)++;

	if ((*buff_in + toid.length) > end)
		return (NULL);

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


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


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


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


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)
{
    return gss_get_mic_iov(minor_status, context_handle, qop_req, iov,
			   iov_count);
}


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


GetDataBlock(gif_source_ptr sinfo, U_CHAR *buf)
/* Read a GIF data block, which has a leading count byte */
/* A zero-length block marks the end of a data block sequence */
{
  int count;

  count = ReadByte(sinfo);
  if (count > 0) {
    if (!ReadOK(sinfo->pub.input_file, buf, count))
      ERREXIT(sinfo->cinfo, JERR_INPUT_EOF);
  }
  return count;
}

cjpeg: Fix FPE when compressing 0-width GIF

Fixes #493


GetCode(gif_source_ptr sinfo)
/* Fetch the next code_size bits from the GIF data */
/* We assume code_size is less than 16 */
{
  register int accum;
  int offs, count;

  while (sinfo->cur_bit + sinfo->code_size > sinfo->last_bit) {
    /* Time to reload the buffer */
    /* First time, share code with Clear case */
    if (sinfo->first_time) {
      sinfo->first_time = FALSE;
      return sinfo->clear_code;
    }
    if (sinfo->out_of_blocks) {
      WARNMS(sinfo->cinfo, JWRN_GIF_NOMOREDATA);
      return sinfo->end_code;   /* fake something useful */
    }
    /* preserve last two bytes of what we have -- assume code_size <= 16 */
    sinfo->code_buf[0] = sinfo->code_buf[sinfo->last_byte-2];
    sinfo->code_buf[1] = sinfo->code_buf[sinfo->last_byte-1];
    /* Load more bytes; set flag if we reach the terminator block */
    if ((count = GetDataBlock(sinfo, &sinfo->code_buf[2])) == 0) {
      sinfo->out_of_blocks = TRUE;
      WARNMS(sinfo->cinfo, JWRN_GIF_NOMOREDATA);
      return sinfo->end_code;   /* fake something useful */
    }
    /* Reset counters */
    sinfo->cur_bit = (sinfo->cur_bit - sinfo->last_bit) + 16;
    sinfo->last_byte = 2 + count;
    sinfo->last_bit = sinfo->last_byte * 8;
  }

  /* Form up next 24 bits in accum */
  offs = sinfo->cur_bit >> 3;   /* byte containing cur_bit */
  accum = UCH(sinfo->code_buf[offs + 2]);
  accum <<= 8;
  accum |= UCH(sinfo->code_buf[offs + 1]);
  accum <<= 8;
  accum |= UCH(sinfo->code_buf[offs]);

  /* Right-align cur_bit in accum, then mask off desired number of bits */
  accum >>= (sinfo->cur_bit & 7);
  sinfo->cur_bit += sinfo->code_size;
  return accum & ((1 << sinfo->code_size) - 1);
}


finish_input_gif(j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
{
  /* no work */
}


InitLZWCode(gif_source_ptr sinfo)
/* Initialize for a series of LZWReadByte (and hence GetCode) calls */
{
  /* GetCode initialization */
  sinfo->last_byte = 2;         /* make safe to "recopy last two bytes" */
  sinfo->code_buf[0] = 0;
  sinfo->code_buf[1] = 0;
  sinfo->last_bit = 0;          /* nothing in the buffer */
  sinfo->cur_bit = 0;           /* force buffer load on first call */
  sinfo->first_time = TRUE;
  sinfo->out_of_blocks = FALSE;

  /* LZWReadByte initialization: */
  /* compute special code values (note that these do not change later) */
  sinfo->clear_code = 1 << sinfo->input_code_size;
  sinfo->end_code = sinfo->clear_code + 1;
  ReInitLZW(sinfo);
}


SkipDataBlocks(gif_source_ptr sinfo)
/* Skip a series of data blocks, until a block terminator is found */
{
  U_CHAR buf[256];

  while (GetDataBlock(sinfo, buf) > 0)
    /* skip */;
}


LZWReadByte(gif_source_ptr sinfo)
/* Read an LZW-compressed byte */
{
  register int code;            /* current working code */
  int incode;                   /* saves actual input code */

  /* If any codes are stacked from a previously read symbol, return them */
  if (sinfo->sp > sinfo->symbol_stack)
    return (int)(*(--sinfo->sp));

  /* Time to read a new symbol */
  code = GetCode(sinfo);

  if (code == sinfo->clear_code) {
    /* Reinit state, swallow any extra Clear codes, and */
    /* return next code, which is expected to be a raw byte. */
    ReInitLZW(sinfo);
    do {
      code = GetCode(sinfo);
    } while (code == sinfo->clear_code);
    if (code > sinfo->clear_code) { /* make sure it is a raw byte */
      WARNMS(sinfo->cinfo, JWRN_GIF_BADDATA);
      code = 0;                 /* use something valid */
    }
    /* make firstcode, oldcode valid! */
    sinfo->firstcode = sinfo->oldcode = code;
    return code;
  }

  if (code == sinfo->end_code) {
    /* Skip the rest of the image, unless GetCode already read terminator */
    if (!sinfo->out_of_blocks) {
      SkipDataBlocks(sinfo);
      sinfo->out_of_blocks = TRUE;
    }
    /* Complain that there's not enough data */
    WARNMS(sinfo->cinfo, JWRN_GIF_ENDCODE);
    /* Pad data with 0's */
    return 0;                   /* fake something usable */
  }

  /* Got normal raw byte or LZW symbol */
  incode = code;                /* save for a moment */

  if (code >= sinfo->max_code) { /* special case for not-yet-defined symbol */
    /* code == max_code is OK; anything bigger is bad data */
    if (code > sinfo->max_code) {
      WARNMS(sinfo->cinfo, JWRN_GIF_BADDATA);
      incode = 0;               /* prevent creation of loops in symbol table */
    }
    /* this symbol will be defined as oldcode/firstcode */
    *(sinfo->sp++) = (UINT8)sinfo->firstcode;
    code = sinfo->oldcode;
  }

  /* If it's a symbol, expand it into the stack */
  while (code >= sinfo->clear_code) {
    *(sinfo->sp++) = sinfo->symbol_tail[code]; /* tail is a byte value */
    code = sinfo->symbol_head[code]; /* head is another LZW symbol */
  }
  /* At this point code just represents a raw byte */
  sinfo->firstcode = code;      /* save for possible future use */

  /* If there's room in table... */
  if ((code = sinfo->max_code) < LZW_TABLE_SIZE) {
    /* Define a new symbol = prev sym + head of this sym's expansion */
    sinfo->symbol_head[code] = (UINT16)sinfo->oldcode;
    sinfo->symbol_tail[code] = (UINT8)sinfo->firstcode;
    sinfo->max_code++;
    /* Is it time to increase code_size? */
    if (sinfo->max_code >= sinfo->limit_code &&
        sinfo->code_size < MAX_LZW_BITS) {
      sinfo->code_size++;
      sinfo->limit_code <<= 1;  /* keep equal to 2^code_size */
    }
  }

  sinfo->oldcode = incode;      /* save last input symbol for future use */
  return sinfo->firstcode;      /* return first byte of symbol's expansion */
}


ReadColorMap(gif_source_ptr sinfo, int cmaplen, JSAMPARRAY cmap)
/* Read a GIF colormap */
{
  int i;

  for (i = 0; i < cmaplen; i++) {
#if BITS_IN_JSAMPLE == 8
#define UPSCALE(x)  (x)
#else
#define UPSCALE(x)  ((x) << (BITS_IN_JSAMPLE - 8))
#endif
    cmap[CM_RED][i]   = (JSAMPLE)UPSCALE(ReadByte(sinfo));
    cmap[CM_GREEN][i] = (JSAMPLE)UPSCALE(ReadByte(sinfo));
    cmap[CM_BLUE][i]  = (JSAMPLE)UPSCALE(ReadByte(sinfo));
  }
}