1008 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1008 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2011-2019 PADL Software Pty Ltd.
 | |
|  * All rights reserved.
 | |
|  *
 | |
|  * Redistribution and use in source and binary forms, with or without
 | |
|  * modification, are permitted provided that the following conditions
 | |
|  * are met:
 | |
|  *
 | |
|  * * Redistributions of source code must retain the above copyright
 | |
|  *   notice, this list of conditions and the following disclaimer.
 | |
|  *
 | |
|  * * Redistributions in binary form must reproduce the above copyright
 | |
|  *   notice, this list of conditions and the following disclaimer in
 | |
|  *   the documentation and/or other materials provided with the
 | |
|  *   distribution.
 | |
|  *
 | |
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | |
|  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | |
|  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 | |
|  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 | |
|  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 | |
|  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 | |
|  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 | |
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | |
|  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 | |
|  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | |
|  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 | |
|  * OF THE POSSIBILITY OF SUCH DAMAGE.
 | |
|  */
 | |
| 
 | |
| #include "spnego_locl.h"
 | |
| 
 | |
| /*
 | |
|  * The initial context token emitted by the initiator is a INITIATOR_NEGO
 | |
|  * message followed by zero or more INITIATOR_META_DATA tokens, and zero
 | |
|  * or one AP_REQUEST tokens.
 | |
|  *
 | |
|  * Upon receiving this, the acceptor computes the list of mutually supported
 | |
|  * authentication mechanisms and performs the metadata exchange. The output
 | |
|  * token is ACCEPTOR_NEGO followed by zero or more ACCEPTOR_META_DATA tokens,
 | |
|  * and zero or one CHALLENGE tokens.
 | |
|  *
 | |
|  * Once the metadata exchange is complete and a mechanism is selected, the
 | |
|  * selected mechanism's context token exchange continues with AP_REQUEST and
 | |
|  * CHALLENGE messages.
 | |
|  *
 | |
|  * Once the context token exchange is complete, VERIFY messages are sent to
 | |
|  * authenticate the entire exchange.
 | |
|  */
 | |
| 
 | |
| static OM_uint32
 | |
| buffer_set_to_crypto(OM_uint32 *minor,
 | |
| 		     krb5_context context,
 | |
| 		     gss_buffer_set_t buffers,
 | |
| 		     krb5_crypto *crypto)
 | |
| {
 | |
|     krb5_error_code ret;
 | |
|     krb5_keyblock keyblock;
 | |
|     OM_uint32 tmp;
 | |
| 
 | |
|     /*
 | |
|      * Returned keys must be in two buffers, with the key contents in
 | |
|      * the first and the enctype as a 32-bit little-endian integer in
 | |
|      * the second.
 | |
|      */
 | |
|     if (buffers->count != 2 ||
 | |
| 	buffers->elements[1].length != sizeof(tmp)) {
 | |
| 	*minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY;
 | |
| 	return GSS_S_FAILURE;
 | |
|     }
 | |
| 
 | |
|     if (*crypto != NULL) {
 | |
| 	krb5_crypto_destroy(context, *crypto);
 | |
| 	*crypto = NULL;
 | |
|     }
 | |
| 
 | |
|     keyblock.keyvalue.data = buffers->elements[0].value;
 | |
|     keyblock.keyvalue.length = buffers->elements[0].length;
 | |
|     _gss_mg_decode_le_uint32(buffers->elements[1].value, &tmp);
 | |
|     keyblock.keytype = tmp;
 | |
| 
 | |
|     ret = krb5_crypto_init(context, &keyblock, 0, crypto);
 | |
|     if (ret) {
 | |
| 	*minor = ret;
 | |
| 	return GSS_S_FAILURE;
 | |
|     }
 | |
| 
 | |
|     return GSS_S_COMPLETE;
 | |
| }
 | |
| 
 | |
| static OM_uint32
 | |
| get_session_keys(OM_uint32 *minor,
 | |
| 		 krb5_context context,
 | |
| 		 struct negoex_auth_mech *mech)
 | |
| {
 | |
|     OM_uint32 major, tmpMinor;
 | |
|     gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET;
 | |
| 
 | |
|     major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context,
 | |
| 					   GSS_C_INQ_NEGOEX_KEY, &buffers);
 | |
|     if (major == GSS_S_COMPLETE) {
 | |
| 	major = buffer_set_to_crypto(minor, context,
 | |
| 				     buffers, &mech->crypto);
 | |
| 	_gss_secure_release_buffer_set(&tmpMinor, &buffers);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    return major;
 | |
|     }
 | |
| 
 | |
|     major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context,
 | |
| 					   GSS_C_INQ_NEGOEX_VERIFY_KEY,
 | |
| 					   &buffers);
 | |
|     if (major == GSS_S_COMPLETE) {
 | |
| 	major = buffer_set_to_crypto(minor, context,
 | |
| 				     buffers, &mech->verify_crypto);
 | |
| 	_gss_secure_release_buffer_set(&tmpMinor, &buffers);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    return major;
 | |
|     }
 | |
| 
 | |
|     return GSS_S_COMPLETE;
 | |
| }
 | |
| 
 | |
| static OM_uint32
 | |
| emit_initiator_nego(OM_uint32 *minor, gssspnego_ctx ctx)
 | |
| {
 | |
|     uint8_t random[32];
 | |
|     struct negoex_auth_mech *mech;
 | |
|     size_t i = 0;
 | |
| 
 | |
|     krb5_generate_random_block(random, sizeof(random));
 | |
| 
 | |
|     HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
 | |
| 	_gss_negoex_log_auth_scheme(ctx->flags.local, ++i, mech->scheme);
 | |
| 
 | |
|     return _gss_negoex_add_nego_message(minor, ctx, INITIATOR_NEGO, random);
 | |
| }
 | |
| 
 | |
| static OM_uint32
 | |
| process_initiator_nego(OM_uint32 *minor,
 | |
| 		       gssspnego_ctx ctx,
 | |
| 		       struct negoex_message *messages,
 | |
| 		       size_t nmessages)
 | |
| {
 | |
|     struct nego_message *msg;
 | |
|     size_t i;
 | |
| 
 | |
|     heim_assert(!ctx->flags.local && ctx->negoex_step == 1,
 | |
| 		"NegoEx INITIATOR_NEGO token received after first leg");
 | |
| 
 | |
|     msg = _gss_negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO);
 | |
|     if (msg == NULL) {
 | |
| 	*minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE;
 | |
| 	return GSS_S_DEFECTIVE_TOKEN;
 | |
|     }
 | |
| 
 | |
|     for (i = 0; i < msg->nschemes; i++)
 | |
| 	_gss_negoex_log_auth_scheme(ctx->flags.local, i + 1, &msg->schemes[i * GUID_LENGTH]);
 | |
| 
 | |
|     _gss_negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes);
 | |
| 
 | |
|     return GSS_S_COMPLETE;
 | |
| }
 | |
| 
 | |
| static OM_uint32
 | |
| emit_acceptor_nego(OM_uint32 *minor, gssspnego_ctx ctx)
 | |
| {
 | |
|     uint8_t random[32];
 | |
| 
 | |
|     krb5_generate_random_block(random, 32);
 | |
| 
 | |
|     return _gss_negoex_add_nego_message(minor, ctx, ACCEPTOR_NEGO, random);
 | |
| }
 | |
| 
 | |
| static OM_uint32
 | |
| process_acceptor_nego(OM_uint32 *minor,
 | |
| 		      gssspnego_ctx ctx,
 | |
| 		      struct negoex_message *messages,
 | |
| 		      size_t nmessages)
 | |
| {
 | |
|     struct nego_message *msg;
 | |
| 
 | |
|     msg = _gss_negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO);
 | |
|     if (msg == NULL) {
 | |
| 	*minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE;
 | |
| 	return GSS_S_DEFECTIVE_TOKEN;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Reorder and prune our mech list to match the acceptor's list (or a
 | |
|      * subset of it).
 | |
|      */
 | |
|     _gss_negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes);
 | |
| 
 | |
|     return GSS_S_COMPLETE;
 | |
| }
 | |
| 
 | |
| static void
 | |
| query_meta_data(gssspnego_ctx ctx,
 | |
| 		struct gssspnego_optimistic_ctx *opt,
 | |
| 		gss_cred_id_t cred,
 | |
| 		OM_uint32 req_flags)
 | |
| {
 | |
|     OM_uint32 major, minor;
 | |
|     struct negoex_auth_mech *p, *next;
 | |
| 
 | |
|     /*
 | |
|      * Note that if we received an optimistic context token from SPNEGO,
 | |
|      * then we will call QMD after ISC, rather than before. Mechanisms
 | |
|      * must be prepared to handle this and must not assume the context
 | |
|      * will be NULL on entry.
 | |
|      */
 | |
|     HEIM_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) {
 | |
| 	if (opt != NULL && memcmp(opt->scheme, p->scheme, GUID_LENGTH) == 0)
 | |
| 	    p->mech_context = opt->gssctx;;
 | |
| 
 | |
| 	major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context,
 | |
| 				       ctx->target_name, req_flags, &p->metadata);
 | |
| 	/* GSS_Query_meta_data failure removes mechanism from list. */
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    _gss_negoex_delete_auth_mech(ctx, p);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| exchange_meta_data(gssspnego_ctx ctx,
 | |
| 		   gss_cred_id_t cred,
 | |
| 		   OM_uint32 req_flags,
 | |
| 		   struct negoex_message *messages,
 | |
| 		   size_t nmessages)
 | |
| {
 | |
|     OM_uint32 major, minor;
 | |
|     struct negoex_auth_mech *mech;
 | |
|     enum message_type type;
 | |
|     struct exchange_message *msg;
 | |
|     uint32_t i;
 | |
| 
 | |
|     type = ctx->flags.local ? ACCEPTOR_META_DATA : INITIATOR_META_DATA;
 | |
| 
 | |
|     for (i = 0; i < nmessages; i++) {
 | |
| 	if (messages[i].type != type)
 | |
| 	    continue;
 | |
| 	msg = &messages[i].u.e;
 | |
| 
 | |
| 	mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
 | |
| 	if (mech == NULL)
 | |
| 	    continue;
 | |
| 
 | |
| 	major = gssspi_exchange_meta_data(&minor, mech->oid, cred,
 | |
| 					  &mech->mech_context,
 | |
| 					  ctx->target_name,
 | |
| 					  req_flags, &msg->token);
 | |
| 	/* GSS_Exchange_meta_data failure removes mechanism from list. */
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    _gss_negoex_delete_auth_mech(ctx, mech);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| release_mech_crypto(struct negoex_auth_mech *mech)
 | |
| {
 | |
|     krb5_context context = NULL;
 | |
| 
 | |
|     if (mech->crypto || mech->verify_crypto)
 | |
| 	context = _gss_mg_krb5_context();
 | |
| 
 | |
|     if (mech->crypto) {
 | |
| 	krb5_crypto_destroy(context, mech->crypto);
 | |
| 	mech->crypto = NULL;
 | |
|     }
 | |
| 
 | |
|     if (mech->verify_crypto) {
 | |
| 	krb5_crypto_destroy(context, mech->verify_crypto);
 | |
| 	mech->verify_crypto = NULL;
 | |
|     }
 | |
| 
 | |
|     mech->sent_checksum = FALSE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * In the initiator, if we are processing the acceptor's first reply, discard
 | |
|  * the optimistic context if the acceptor ignored the optimistic token. If the
 | |
|  * acceptor continued the optimistic mech, discard all other mechs.
 | |
|  */
 | |
| static void
 | |
| check_optimistic_result(gssspnego_ctx ctx,
 | |
| 			struct negoex_message *messages,
 | |
| 			size_t nmessages)
 | |
| {
 | |
|     struct negoex_auth_mech *mech;
 | |
|     OM_uint32 tmpMinor;
 | |
| 
 | |
|     heim_assert(ctx->flags.local && ctx->negoex_step == 2,
 | |
| 		"NegoEx optimistic result should only be checked in second leg");
 | |
| 
 | |
|     /* Do nothing if we didn't make an optimistic context. */
 | |
|     mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
|     if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
 | |
| 	return;
 | |
| 
 | |
|     /*
 | |
|      * If the acceptor used the optimistic token, it will send an acceptor
 | |
|      * token or a checksum (or both) in its first reply.
 | |
|      */
 | |
|     if (_gss_negoex_locate_exchange_message(messages, nmessages,
 | |
| 					    CHALLENGE) != NULL ||
 | |
| 	_gss_negoex_locate_verify_message(messages, nmessages) != NULL) {
 | |
| 	/*
 | |
| 	 * The acceptor continued the optimistic mech, and metadata exchange
 | |
| 	 * didn't remove it. Commit to this mechanism.
 | |
| 	 */
 | |
| 	_gss_negoex_select_auth_mech(ctx, mech);
 | |
|     } else {
 | |
| 	/*
 | |
| 	 * The acceptor ignored the optimistic token. Restart the mech.
 | |
| 	 */
 | |
| 	gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER);
 | |
| 	release_mech_crypto(mech);
 | |
| 	mech->complete = FALSE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Perform an initiator step of the underlying mechanism exchange. */
 | |
| static OM_uint32
 | |
| mech_init(OM_uint32 *minor,
 | |
| 	  struct gssspnego_optimistic_ctx *opt,
 | |
| 	  gssspnego_ctx ctx,
 | |
| 	  gss_cred_id_t cred,
 | |
| 	  OM_uint32 req_flags,
 | |
| 	  OM_uint32 time_req,
 | |
| 	  const gss_channel_bindings_t input_chan_bindings,
 | |
| 	  struct negoex_message *messages,
 | |
| 	  size_t nmessages,
 | |
| 	  gss_buffer_t output_token,
 | |
| 	  int *mech_error)
 | |
| {
 | |
|     OM_uint32 major, first_major = GSS_S_COMPLETE, first_minor = 0;
 | |
|     struct negoex_auth_mech *mech = NULL;
 | |
|     gss_buffer_t input_token = GSS_C_NO_BUFFER;
 | |
|     struct exchange_message *msg;
 | |
|     int first_mech;
 | |
|     krb5_context context = _gss_mg_krb5_context();
 | |
| 
 | |
|     output_token->value = NULL;
 | |
|     output_token->length = 0;
 | |
| 
 | |
|     *mech_error = FALSE;
 | |
| 
 | |
|     /* Allow disabling of optimistic token for testing. */
 | |
|     if (ctx->negoex_step == 1 &&
 | |
| 	secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL)
 | |
| 	return GSS_S_COMPLETE;
 | |
| 
 | |
|     if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
 | |
| 	*minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
 | |
| 	return GSS_S_FAILURE;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Get the input token. The challenge could be for the optimistic mech,
 | |
|      * which we might have discarded in metadata exchange, so ignore the
 | |
|      * challenge if it doesn't match the first auth mech.
 | |
|      */
 | |
|     mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
|     msg = _gss_negoex_locate_exchange_message(messages, nmessages, CHALLENGE);
 | |
|     if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme))
 | |
| 	input_token = &msg->token;
 | |
| 
 | |
|     if (mech->complete)
 | |
| 	return GSS_S_COMPLETE;
 | |
| 
 | |
|     first_mech = TRUE;
 | |
|     major = GSS_S_BAD_MECH;
 | |
| 
 | |
|     while (!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
 | |
| 	mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
| 
 | |
| 	/*
 | |
| 	 * If SPNEGO generated an optimistic token when probing available
 | |
| 	 * mechanisms, we can reuse it here. This avoids a potentially
 | |
| 	 * expensive and redundant call to GSS_Init_sec_context();
 | |
| 	 */
 | |
| 	if (opt != NULL && memcmp(opt->scheme, mech->scheme, GUID_LENGTH) == 0) {
 | |
| 	    heim_assert(ctx->negoex_step == 1,
 | |
| 			"SPNEGO optimistic token only valid for NegoEx first leg");
 | |
| 
 | |
| 	    major = _gss_copy_buffer(minor, &opt->optimistic_token, output_token);
 | |
| 	    if (GSS_ERROR(major))
 | |
| 		return major;
 | |
| 
 | |
| 	    ctx->negotiated_mech_type = opt->negotiated_mech_type;
 | |
| 	    ctx->mech_flags = opt->optimistic_flags;
 | |
| 	    ctx->mech_time_rec = opt->optimistic_time_rec;
 | |
| 
 | |
| 	    mech->mech_context = opt->gssctx;
 | |
| 	    opt->gssctx = NULL; /* steal it */
 | |
| 
 | |
| 	    mech->complete = opt->complete;
 | |
| 	    major = GSS_S_COMPLETE;
 | |
| 	} else {
 | |
| 	    major = gss_init_sec_context(minor, cred, &mech->mech_context,
 | |
| 					 ctx->target_name, mech->oid,
 | |
| 					 req_flags, time_req,
 | |
| 					 input_chan_bindings, input_token,
 | |
| 					 &ctx->negotiated_mech_type, output_token,
 | |
| 					 &ctx->mech_flags, &ctx->mech_time_rec);
 | |
| 	    if (major == GSS_S_COMPLETE)
 | |
| 		mech->complete = 1;
 | |
| 	    else if (GSS_ERROR(major)) {
 | |
| 		gss_mg_collect_error(mech->oid, major, *minor);
 | |
| 		*mech_error = TRUE;
 | |
| 	    }
 | |
| 	}
 | |
| 	if (!GSS_ERROR(major))
 | |
| 	    return get_session_keys(minor, context, mech);
 | |
| 
 | |
| 	/* Remember the error we got from the first mech. */
 | |
| 	if (first_mech) {
 | |
| 	    first_major = major;
 | |
| 	    first_minor = *minor;
 | |
| 	}
 | |
| 
 | |
| 	/* If we still have multiple mechs to try, move on to the next one. */
 | |
| 	_gss_negoex_delete_auth_mech(ctx, mech);
 | |
| 	first_mech = FALSE;
 | |
| 	input_token = GSS_C_NO_BUFFER;
 | |
|     }
 | |
| 
 | |
|     if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
 | |
| 	major = first_major;
 | |
| 	*minor = first_minor;
 | |
|     }
 | |
| 
 | |
|     return major;
 | |
| }
 | |
| 
 | |
| /* Perform an acceptor step of the underlying mechanism exchange. */
 | |
| static OM_uint32
 | |
| mech_accept(OM_uint32 *minor,
 | |
| 	    gssspnego_ctx ctx,
 | |
| 	    gss_cred_id_t cred,
 | |
| 	    const gss_channel_bindings_t input_chan_bindings,
 | |
| 	    struct negoex_message *messages,
 | |
| 	    size_t nmessages,
 | |
| 	    gss_buffer_t output_token,
 | |
| 	    gss_cred_id_t *deleg_cred,
 | |
| 	    int *mech_error)
 | |
| {
 | |
|     OM_uint32 major, tmpMinor;
 | |
|     struct negoex_auth_mech *mech;
 | |
|     struct exchange_message *msg;
 | |
|     krb5_context context = _gss_mg_krb5_context();
 | |
| 
 | |
|     heim_assert(!ctx->flags.local && !HEIM_TAILQ_EMPTY(&ctx->negoex_mechs),
 | |
| 		"Acceptor NegoEx function called in wrong sequence");
 | |
| 
 | |
|     *mech_error = FALSE;
 | |
| 
 | |
|     msg = _gss_negoex_locate_exchange_message(messages, nmessages, AP_REQUEST);
 | |
|     if (msg == NULL) {
 | |
| 	/*
 | |
| 	 * No input token is okay on the first request or if the mech is
 | |
| 	 * complete.
 | |
| 	 */
 | |
| 	if (ctx->negoex_step == 1 ||
 | |
| 	    HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->complete)
 | |
| 	    return GSS_S_COMPLETE;
 | |
| 	*minor = (OM_uint32)NEGOEX_MISSING_AP_REQUEST_MESSAGE;
 | |
| 	return GSS_S_DEFECTIVE_TOKEN;
 | |
|     }
 | |
| 
 | |
|     if (ctx->negoex_step == 1) {
 | |
| 	/*
 | |
| 	 * Ignore the optimistic token if it isn't for our most preferred
 | |
| 	 * mech.
 | |
| 	 */
 | |
| 	mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
| 	if (!GUID_EQ(msg->scheme, mech->scheme)) {
 | |
| 	    _gss_mg_log(10, "negoex ignored optimistic token as not for preferred mech");
 | |
| 	    return GSS_S_COMPLETE;
 | |
| 	}
 | |
|     } else {
 | |
| 	/* The initiator has selected a mech; discard other entries. */
 | |
| 	mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
 | |
| 	if (mech == NULL) {
 | |
| 	    *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
 | |
| 	    return GSS_S_FAILURE;
 | |
| 	}
 | |
| 	_gss_negoex_select_auth_mech(ctx, mech);
 | |
|     }
 | |
| 
 | |
|     if (mech->complete)
 | |
| 	return GSS_S_COMPLETE;
 | |
| 
 | |
|     if (ctx->mech_src_name != GSS_C_NO_NAME)
 | |
| 	gss_release_name(&tmpMinor, &ctx->mech_src_name);
 | |
|     if (deleg_cred && *deleg_cred != GSS_C_NO_CREDENTIAL)
 | |
| 	gss_release_cred(&tmpMinor, deleg_cred);
 | |
| 
 | |
|     major = gss_accept_sec_context(minor, &mech->mech_context, cred,
 | |
| 				   &msg->token, input_chan_bindings,
 | |
| 				   &ctx->mech_src_name, &ctx->negotiated_mech_type,
 | |
| 				   output_token, &ctx->mech_flags,
 | |
| 				   &ctx->mech_time_rec, deleg_cred);
 | |
|     if (major == GSS_S_COMPLETE)
 | |
| 	mech->complete = 1;
 | |
| 
 | |
|     if (!GSS_ERROR(major)) {
 | |
| 	if (major == GSS_S_COMPLETE &&
 | |
| 	    !gss_oid_equal(ctx->negotiated_mech_type, mech->oid))
 | |
| 	    _gss_mg_log(1, "negoex client didn't send the mech they said they would");
 | |
| 
 | |
| 	major = get_session_keys(minor, context, mech);
 | |
|     } else if (ctx->negoex_step == 1) {
 | |
| 	gss_mg_collect_error(ctx->negotiated_mech_type, major, *minor);
 | |
| 	*mech_error = TRUE;
 | |
| 
 | |
| 	/* This was an optimistic token; pretend this never happened. */
 | |
| 	major = GSS_S_COMPLETE;
 | |
| 	*minor = 0;
 | |
| 	gss_release_buffer(&tmpMinor, output_token);
 | |
| 	gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER);
 | |
|     }
 | |
| 
 | |
|     return major;
 | |
| }
 | |
| 
 | |
| static krb5_keyusage
 | |
| verify_keyusage(gssspnego_ctx ctx, int make_checksum)
 | |
| {
 | |
|     /* Of course, these are the wrong way around in the spec. */
 | |
|     return (ctx->flags.local ^ !make_checksum) ?
 | |
| 	NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM;
 | |
| }
 | |
| 
 | |
| static OM_uint32
 | |
| verify_checksum(OM_uint32 *minor,
 | |
| 		gssspnego_ctx ctx,
 | |
| 		struct negoex_message *messages,
 | |
| 		size_t nmessages,
 | |
| 		gss_const_buffer_t input_token,
 | |
| 		int *send_alert_out)
 | |
| {
 | |
|     krb5_error_code ret;
 | |
|     struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
|     struct verify_message *msg;
 | |
|     krb5_context context = _gss_mg_krb5_context();
 | |
|     krb5_crypto_iov iov[3];
 | |
|     krb5_keyusage usage = verify_keyusage(ctx, FALSE);
 | |
| 
 | |
|     *send_alert_out = FALSE;
 | |
|     heim_assert(mech != NULL, "Invalid null mech when verifying NegoEx checksum");
 | |
| 
 | |
|     /*
 | |
|      * The other party may not be ready to send a verify token yet, or (in the
 | |
|      * first initiator step) may send one for a mechanism we don't support.
 | |
|      */
 | |
|     msg = _gss_negoex_locate_verify_message(messages, nmessages);
 | |
|     if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme))
 | |
| 	return GSS_S_COMPLETE;
 | |
| 
 | |
|     /*
 | |
|      * A recoverable error may cause us to be unable to verify a token from the
 | |
|      * other party. In this case we should send an alert.
 | |
|      */
 | |
|     if (mech->verify_crypto == NULL) {
 | |
| 	*send_alert_out = TRUE;
 | |
| 	return GSS_S_COMPLETE;
 | |
|     }
 | |
| 
 | |
|     if (!krb5_checksum_is_keyed(context, msg->cksum_type)) {
 | |
| 	*minor = (OM_uint32)NEGOEX_INVALID_CHECKSUM;
 | |
| 	return GSS_S_BAD_SIG;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Verify the checksum over the existing transcript and the portion of the
 | |
|      * input token leading up to the verify message.
 | |
|      */
 | |
|     iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
 | |
|     ret = krb5_storage_to_data(ctx->negoex_transcript, &iov[0].data);
 | |
|     if (ret) {
 | |
| 	*minor = ret;
 | |
| 	return GSS_S_FAILURE;
 | |
|     }
 | |
| 
 | |
|     iov[1].flags = KRB5_CRYPTO_TYPE_DATA;
 | |
|     iov[1].data.data = input_token->value;
 | |
|     iov[1].data.length = msg->offset_in_token;
 | |
| 
 | |
|     iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
 | |
|     iov[2].data.data = (uint8_t *)msg->cksum;
 | |
|     iov[2].data.length = msg->cksum_len;
 | |
| 
 | |
|     ret = krb5_verify_checksum_iov(context, mech->verify_crypto, usage,
 | |
| 				   iov, sizeof(iov) / sizeof(iov[0]), NULL);
 | |
|     if (ret == 0)
 | |
| 	mech->verified_checksum = TRUE;
 | |
|     else
 | |
| 	*minor = ret;
 | |
| 
 | |
|     krb5_data_free(&iov[0].data);
 | |
| 
 | |
|     return (ret == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
 | |
| }
 | |
| 
 | |
| static OM_uint32
 | |
| make_checksum(OM_uint32 *minor, gssspnego_ctx ctx)
 | |
| {
 | |
|     krb5_error_code ret;
 | |
|     krb5_context context = _gss_mg_krb5_context();
 | |
|     krb5_data d;
 | |
|     krb5_keyusage usage = verify_keyusage(ctx, TRUE);
 | |
|     krb5_checksum cksum;
 | |
|     struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
|     OM_uint32 major;
 | |
| 
 | |
|     heim_assert(mech != NULL, "Invalid null mech when making NegoEx checksum");
 | |
| 
 | |
|     if (mech->crypto == NULL) {
 | |
| 	if (mech->complete) {
 | |
| 	    *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY;
 | |
| 	    return GSS_S_UNAVAILABLE;
 | |
| 	} else {
 | |
| 	    return GSS_S_COMPLETE;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     ret = krb5_storage_to_data(ctx->negoex_transcript, &d);
 | |
|     if (ret) {
 | |
| 	*minor = ret;
 | |
| 	return GSS_S_FAILURE;
 | |
|     }
 | |
| 
 | |
|     ret = krb5_create_checksum(context, mech->crypto,
 | |
| 			       usage, 0, d.data, d.length, &cksum);
 | |
|     krb5_data_free(&d);
 | |
|     if (ret) {
 | |
| 	*minor = ret;
 | |
| 	return GSS_S_FAILURE;
 | |
|     }
 | |
| 
 | |
|     major = _gss_negoex_add_verify_message(minor, ctx, mech->scheme,
 | |
| 					   cksum.cksumtype,
 | |
| 					   cksum.checksum.data,
 | |
| 					   cksum.checksum.length);
 | |
|     free_Checksum(&cksum);
 | |
| 
 | |
|     if (major == GSS_S_COMPLETE)
 | |
| 	mech->sent_checksum = TRUE;
 | |
| 
 | |
|     return major;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * If the other side sent a VERIFY_NO_KEY pulse alert, clear the checksum state
 | |
|  * on the mechanism so that we send another VERIFY message.
 | |
|  */
 | |
| static void
 | |
| process_alerts(gssspnego_ctx ctx,
 | |
| 	       struct negoex_message *messages,
 | |
| 	       uint32_t nmessages)
 | |
| {
 | |
|     struct alert_message *msg;
 | |
|     struct negoex_auth_mech *mech;
 | |
| 
 | |
|     msg = _gss_negoex_locate_alert_message(messages, nmessages);
 | |
|     if (msg != NULL && msg->verify_no_key) {
 | |
| 	mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
 | |
| 	if (mech != NULL)
 | |
| 	    release_mech_crypto(mech);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static OM_uint32
 | |
| make_output_token(OM_uint32 *minor,
 | |
| 		  gssspnego_ctx ctx,
 | |
| 		  gss_buffer_t mech_output_token,
 | |
| 		  int send_alert,
 | |
| 		  gss_buffer_t output_token)
 | |
| {
 | |
|     OM_uint32 major, tmpMinor;
 | |
|     struct negoex_auth_mech *mech;
 | |
|     enum message_type type;
 | |
|     off_t old_transcript_len;
 | |
| 
 | |
|     output_token->length = 0;
 | |
|     output_token->value = NULL;
 | |
| 
 | |
|     old_transcript_len = krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR);
 | |
| 
 | |
|     /*
 | |
|      * If the mech is complete and we previously sent a checksum, we just
 | |
|      * processed the last leg and don't need to send another token.
 | |
|      */
 | |
|     if (mech_output_token->length == 0 &&
 | |
| 	HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum)
 | |
| 	return GSS_S_COMPLETE;
 | |
| 
 | |
|     if (ctx->negoex_step == 1) {
 | |
| 	if (ctx->flags.local)
 | |
| 	    major = emit_initiator_nego(minor, ctx);
 | |
| 	else
 | |
| 	    major = emit_acceptor_nego(minor, ctx);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    return major;
 | |
| 
 | |
| 	type = ctx->flags.local ? INITIATOR_META_DATA : ACCEPTOR_META_DATA;
 | |
| 	HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
 | |
| 	    if (mech->metadata.length > 0) {
 | |
| 		major = _gss_negoex_add_exchange_message(minor, ctx,
 | |
| 							 type, mech->scheme,
 | |
| 							 &mech->metadata);
 | |
| 		if (major != GSS_S_COMPLETE)
 | |
| 		    return major;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
| 
 | |
|     if (mech_output_token->length > 0) {
 | |
| 	type = ctx->flags.local ? AP_REQUEST : CHALLENGE;
 | |
| 	major = _gss_negoex_add_exchange_message(minor, ctx,
 | |
| 						 type, mech->scheme,
 | |
| 						 mech_output_token);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    return major;
 | |
|     }
 | |
| 
 | |
|     if (send_alert) {
 | |
| 	major = _gss_negoex_add_verify_no_key_alert(minor, ctx, mech->scheme);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    return major;
 | |
|     }
 | |
| 
 | |
|     /* Try to add a VERIFY message if we haven't already done so. */
 | |
|     if (!mech->sent_checksum) {
 | |
| 	major = make_checksum(minor, ctx);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    return major;
 | |
|     }
 | |
| 
 | |
|     heim_assert(ctx->negoex_transcript != NULL, "NegoEx context uninitialized");
 | |
| 
 | |
|     output_token->length =
 | |
| 	krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR) - old_transcript_len;
 | |
|     output_token->value = malloc(output_token->length);
 | |
|     if (output_token->value == NULL) {
 | |
| 	*minor = ENOMEM;
 | |
| 	return GSS_S_FAILURE;
 | |
|     }
 | |
| 
 | |
|     krb5_storage_seek(ctx->negoex_transcript, old_transcript_len, SEEK_SET);
 | |
| 
 | |
|     if (krb5_storage_read(ctx->negoex_transcript,
 | |
| 			  output_token->value,
 | |
| 			  output_token->length) != output_token->length) {
 | |
| 	*minor = ERANGE;
 | |
| 	gss_release_buffer(&tmpMinor, output_token);
 | |
| 	return GSS_S_FAILURE;
 | |
|     }
 | |
| 
 | |
|     krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_END);
 | |
| 
 | |
|     return GSS_S_COMPLETE;
 | |
| }
 | |
| 
 | |
| OM_uint32
 | |
| _gss_negoex_init(OM_uint32 *minor,
 | |
| 		 struct gssspnego_optimistic_ctx *opt,
 | |
| 		 gssspnego_ctx ctx,
 | |
| 		 gss_cred_id_t cred,
 | |
| 		 OM_uint32 req_flags,
 | |
| 		 OM_uint32 time_req,
 | |
| 		 const gss_channel_bindings_t input_chan_bindings,
 | |
| 		 gss_const_buffer_t input_token,
 | |
| 		 gss_buffer_t output_token)
 | |
| {
 | |
|     OM_uint32 major, tmpMinor;
 | |
|     gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
 | |
|     struct negoex_message *messages = NULL;
 | |
|     struct negoex_auth_mech *mech;
 | |
|     size_t nmessages = 0;
 | |
|     int send_alert = FALSE, mech_error = FALSE;
 | |
| 
 | |
|     output_token->length = 0;
 | |
|     output_token->value = NULL;
 | |
| 
 | |
|     if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER &&
 | |
| 	input_token->length != 0)
 | |
| 	return GSS_S_DEFECTIVE_TOKEN;
 | |
| 
 | |
|     major = _gss_negoex_begin(minor, ctx);
 | |
|     if (major != GSS_S_COMPLETE)
 | |
| 	goto cleanup;
 | |
| 
 | |
|     ctx->negoex_step++;
 | |
| 
 | |
|     if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) {
 | |
| 	major = _gss_negoex_parse_token(minor, ctx, input_token,
 | |
| 					&messages, &nmessages);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    goto cleanup;
 | |
|     }
 | |
| 
 | |
|     process_alerts(ctx, messages, nmessages);
 | |
| 
 | |
|     if (ctx->negoex_step == 1) {
 | |
| 	/* Choose a random conversation ID. */
 | |
| 	krb5_generate_random_block(ctx->negoex_conv_id, GUID_LENGTH);
 | |
| 
 | |
| 	/* Query each mech for its metadata (this may prune the mech list). */
 | |
| 	query_meta_data(ctx, opt, cred, req_flags);
 | |
|     } else if (ctx->negoex_step == 2) {
 | |
| 	/* See if the mech processed the optimistic token. */
 | |
| 	check_optimistic_result(ctx, messages, nmessages);
 | |
| 
 | |
| 	/* Pass the acceptor metadata to each mech to prune the list. */
 | |
| 	exchange_meta_data(ctx, cred, req_flags, messages, nmessages);
 | |
| 
 | |
| 	/* Process the ACCEPTOR_NEGO message. */
 | |
| 	major = process_acceptor_nego(minor, ctx, messages, nmessages);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    goto cleanup;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Process the input token and/or produce an output token. This may prune
 | |
|      * the mech list, but on success there will be at least one mech entry.
 | |
|      */
 | |
|     major = mech_init(minor, opt, ctx, cred, req_flags, time_req,
 | |
| 		      input_chan_bindings, messages, nmessages,
 | |
| 		      &mech_output_token, &mech_error);
 | |
|     if (major != GSS_S_COMPLETE)
 | |
| 	goto cleanup;
 | |
|     heim_assert(!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs),
 | |
| 		"Invalid empty NegoEx mechanism list");
 | |
| 
 | |
|     /*
 | |
|      * At this point in step 2 we have performed the metadata exchange and
 | |
|      * chosen a mech we can use, so discard any fallback mech entries.
 | |
|      */
 | |
|     if (ctx->negoex_step == 2)
 | |
| 	_gss_negoex_select_auth_mech(ctx, HEIM_TAILQ_FIRST(&ctx->negoex_mechs));
 | |
| 
 | |
|     major = verify_checksum(minor, ctx, messages, nmessages, input_token,
 | |
| 			    &send_alert);
 | |
|     if (major != GSS_S_COMPLETE)
 | |
| 	goto cleanup;
 | |
| 
 | |
|     if (input_token != GSS_C_NO_BUFFER) {
 | |
| 	if (krb5_storage_write(ctx->negoex_transcript,
 | |
| 			       input_token->value,
 | |
| 			       input_token->length) != input_token->length) {
 | |
| 	    major = GSS_S_FAILURE;
 | |
| 	    *minor = ENOMEM;
 | |
| 	    goto cleanup;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     major = make_output_token(minor, ctx, &mech_output_token, send_alert,
 | |
| 			      output_token);
 | |
|     if (major != GSS_S_COMPLETE)
 | |
| 	goto cleanup;
 | |
| 
 | |
|     mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
|     major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
 | |
| 	GSS_S_CONTINUE_NEEDED;
 | |
| 
 | |
| cleanup:
 | |
|     free(messages);
 | |
|     gss_release_buffer(&tmpMinor, &mech_output_token);
 | |
|     _gss_negoex_end(ctx);
 | |
| 
 | |
|     if (GSS_ERROR(major)) {
 | |
| 	if (!mech_error) {
 | |
| 	   krb5_context context = _gss_mg_krb5_context();
 | |
| 
 | |
| 	   gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
 | |
| 				   major, *minor,
 | |
| 				   "NegoEx failed to initialize security context: %s",
 | |
| 				   krb5_get_error_message(context, *minor));
 | |
| 	}
 | |
| 
 | |
| 	_gss_negoex_release_context(ctx);
 | |
|     }
 | |
| 
 | |
|     return major;
 | |
| }
 | |
| 
 | |
| OM_uint32
 | |
| _gss_negoex_accept(OM_uint32 *minor,
 | |
| 		   gssspnego_ctx ctx,
 | |
| 		   gss_cred_id_t cred,
 | |
| 		   gss_const_buffer_t input_token,
 | |
| 		   const gss_channel_bindings_t input_chan_bindings,
 | |
| 		   gss_buffer_t output_token,
 | |
| 		   gss_cred_id_t *deleg_cred)
 | |
| {
 | |
|     OM_uint32 major, tmpMinor;
 | |
|     gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
 | |
|     struct negoex_message *messages = NULL;
 | |
|     struct negoex_auth_mech *mech;
 | |
|     size_t nmessages;
 | |
|     int send_alert = FALSE, mech_error = FALSE;
 | |
| 
 | |
|     output_token->length = 0;
 | |
|     output_token->value = NULL;
 | |
|     if (deleg_cred)
 | |
| 	*deleg_cred = GSS_C_NO_CREDENTIAL;
 | |
| 
 | |
|     if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
 | |
| 	major = GSS_S_DEFECTIVE_TOKEN;
 | |
| 	goto cleanup;
 | |
|     }
 | |
| 
 | |
|     major = _gss_negoex_begin(minor, ctx);
 | |
|     if (major != GSS_S_COMPLETE)
 | |
| 	goto cleanup;
 | |
| 
 | |
|     ctx->negoex_step++;
 | |
| 
 | |
|     major = _gss_negoex_parse_token(minor, ctx, input_token,
 | |
| 				    &messages, &nmessages);
 | |
|     if (major != GSS_S_COMPLETE)
 | |
| 	goto cleanup;
 | |
| 
 | |
|     process_alerts(ctx, messages, nmessages);
 | |
| 
 | |
|     if (ctx->negoex_step == 1) {
 | |
| 	/*
 | |
| 	 * Read the INITIATOR_NEGO message to prune the candidate mech list.
 | |
| 	 */
 | |
| 	major = process_initiator_nego(minor, ctx, messages, nmessages);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    goto cleanup;
 | |
| 
 | |
| 	/*
 | |
| 	 * Pass the initiator metadata to each mech to prune the list, and
 | |
| 	 * query each mech for its acceptor metadata (which may also prune the
 | |
| 	 * list).
 | |
| 	 */
 | |
| 	exchange_meta_data(ctx, cred, 0, messages, nmessages);
 | |
| 	query_meta_data(ctx, NULL, cred, 0);
 | |
| 
 | |
| 	if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
 | |
| 	    *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
 | |
| 	    major = GSS_S_FAILURE;
 | |
| 	    goto cleanup;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Process the input token and possibly produce an output token. This may
 | |
|      * prune the list to a single mech. Continue on error if an output token
 | |
|      * is generated, so that we send the token to the initiator.
 | |
|      */
 | |
|     major = mech_accept(minor, ctx, cred, input_chan_bindings,
 | |
| 			messages, nmessages, &mech_output_token,
 | |
| 			deleg_cred, &mech_error);
 | |
|     if (major != GSS_S_COMPLETE && mech_output_token.length == 0)
 | |
| 	goto cleanup;
 | |
| 
 | |
|     if (major == GSS_S_COMPLETE) {
 | |
| 	major = verify_checksum(minor, ctx, messages, nmessages, input_token,
 | |
| 				&send_alert);
 | |
| 	if (major != GSS_S_COMPLETE)
 | |
| 	    goto cleanup;
 | |
|     }
 | |
| 
 | |
|     if (krb5_storage_write(ctx->negoex_transcript,
 | |
| 			   input_token->value,
 | |
| 			   input_token->length) != input_token->length) {
 | |
| 	major = GSS_S_FAILURE;
 | |
| 	*minor = ENOMEM;
 | |
| 	goto cleanup;
 | |
|     }
 | |
| 
 | |
|     major = make_output_token(minor, ctx, &mech_output_token, send_alert,
 | |
| 			      output_token);
 | |
|     if (major != GSS_S_COMPLETE)
 | |
| 	goto cleanup;
 | |
| 
 | |
|     mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
 | |
|     major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
 | |
| 	GSS_S_CONTINUE_NEEDED;
 | |
| 
 | |
| cleanup:
 | |
|     free(messages);
 | |
|     gss_release_buffer(&tmpMinor, &mech_output_token);
 | |
|     _gss_negoex_end(ctx);
 | |
| 
 | |
|     if (GSS_ERROR(major)) {
 | |
| 	if (!mech_error) {
 | |
| 	   krb5_context context = _gss_mg_krb5_context();
 | |
| 
 | |
| 	   gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
 | |
| 				   major, *minor,
 | |
| 				   "NegoEx failed to accept security context: %s",
 | |
| 				   krb5_get_error_message(context, *minor));
 | |
| 	}
 | |
| 
 | |
| 	_gss_negoex_release_context(ctx);
 | |
|     }
 | |
| 
 | |
|     return major;
 | |
| }
 | 
