--- /dev/null
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * A module that implements the spnego security mechanism.
+ * It is used to negotiate the security mechanism between
+ * peers using the GSS-API.
+ *
+ */
+
+#pragma ident "@(#)spnego_mech.c 1.7 04/09/28 SMI"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "gssapiP_spnego.h"
+#include <mglueP.h>
+#include <gssapi_err_generic.h>
+#include <rpc/types.h>
+#include <libintl.h>
+
+/* der routines defined in libgss */
+extern unsigned int der_length_size(OM_uint32);
+extern int get_der_length(uchar_t **, OM_uint32, OM_uint32*);
+extern int put_der_length(OM_uint32, uchar_t **, OM_uint32);
+
+
+/* private routines for spnego_mechanism */
+static spnego_token_t make_spnego_token(char *);
+static gss_buffer_desc make_err_msg(char *);
+static int g_token_size(gss_OID, OM_uint32);
+static int g_make_token_header(gss_OID, int, uchar_t **, int);
+static int g_verify_token_header(gss_OID, int *, uchar_t **, int, int);
+static int g_verify_neg_token_init(uchar_t **, int);
+static OM_uint32 get_negResult(unsigned char **, int);
+static gss_OID get_mech_oid(OM_uint32 *, uchar_t **, size_t);
+static gss_buffer_t get_input_token(unsigned char **, int);
+static gss_OID_set get_mech_set(OM_uint32 *, unsigned char **, int);
+static OM_uint32 get_req_flags(uchar_t **, int *, OM_uint32 *);
+static OM_uint32 get_available_mechs(OM_uint32 *, gss_name_t,
+ gss_cred_usage_t, gss_cred_id_t *, gss_OID_set *);
+static void release_spnego_ctx(spnego_gss_ctx_id_t *);
+static void check_spnego_options(spnego_gss_ctx_id_t);
+static spnego_gss_ctx_id_t create_spnego_ctx(void);
+static int put_req_flags(uchar_t **, OM_uint32, int);
+static int put_mech_set(uchar_t **, gss_OID_set, int);
+static int put_input_token(uchar_t **, gss_buffer_t, int);
+static int put_mech_oid(uchar_t **, gss_OID_desc *, int);
+static int put_negResult(uchar_t **, OM_uint32, int);
+
+static gss_OID
+negotiate_mech_type(OM_uint32 *, gss_OID_set, gss_OID_set,
+ OM_uint32 *, bool_t *);
+static int
+g_get_tag_and_length(unsigned char **, uchar_t, int, int *);
+
+static int
+make_spnego_tokenInit_msg(spnego_gss_ctx_id_t, gss_OID_set,
+ OM_uint32, gss_buffer_t, send_token_flag,
+ gss_buffer_t);
+static int
+make_spnego_tokenTarg_msg(OM_uint32, gss_OID, gss_buffer_t,
+ gss_buffer_t, send_token_flag, int,
+ gss_buffer_t);
+
+/*
+ * The Mech OID for SPNEGO:
+ * { iso(1) org(3) dod(6) internet(1) security(5)
+ * mechanism(5) spnego(2) }
+ */
+static struct gss_config spnego_mechanism =
+{{SPNEGO_OID_LENGTH, SPNEGO_OID},
+ NULL,
+ spnego_gss_acquire_cred,
+ spnego_gss_release_cred,
+ spnego_gss_init_sec_context,
+ spnego_gss_accept_sec_context,
+/* EXPORT DELETE START */ /* CRYPT DELETE START */
+ spnego_gss_unseal, /* gss_unseal */
+/* EXPORT DELETE END */ /* CRYPT DELETE END */
+ NULL, /* gss_process_context_token */
+ spnego_gss_delete_sec_context, /* gss_delete_sec_context */
+ spnego_gss_context_time, /* gss_context_time */
+ spnego_gss_display_status,
+ NULL, /* gss_indicate_mechs */
+ NULL, /* gss_compare_name */
+ spnego_gss_display_name,
+ spnego_gss_import_name,
+ spnego_gss_release_name,
+ NULL, /* gss_inquire_cred */
+ NULL, /* gss_add_cred */
+/* EXPORT DELETE START */ /* CRYPT DELETE START */
+ spnego_gss_seal, /* gss_seal */
+/* EXPORT DELETE END */ /* CRYPT DELETE END */
+ spnego_gss_export_sec_context, /* gss_export_sec_context */
+ spnego_gss_import_sec_context, /* gss_import_sec_context */
+ NULL, /* gss_inquire_cred_by_mech */
+ spnego_gss_inquire_names_for_mech,
+ spnego_gss_inquire_context, /* gss_inquire_context */
+ NULL, /* gss_internal_release_oid */
+ spnego_gss_wrap_size_limit, /* gss_wrap_size_limit */
+ NULL, /* gss_pname_to_uid */
+ NULL, /* __gss_userok */
+ NULL, /* gss_export_name */
+/* EXPORT DELETE START */
+/* CRYPT DELETE START */
+#if 0
+/* CRYPT DELETE END */
+ spnego_gss_seal,
+ spnego_gss_unseal,
+/* CRYPT DELETE START */
+#endif
+/* CRYPT DELETE END */
+/* EXPORT DELETE END */
+ spnego_gss_sign, /* gss_sign */
+ spnego_gss_verify, /* gss_verify */
+ NULL, /* gss_store_cred */
+};
+
+gss_mechanism
+gss_mech_initialize(const gss_OID oid)
+{
+ dsyslog("Entering gss_mech_initialize\n");
+
+ if (oid == NULL ||
+ !g_OID_equal(oid, &spnego_mechanism.mech_type)) {
+ dsyslog("invalid spnego mechanism oid.\n");
+ return (NULL);
+ }
+
+ dsyslog("Leaving gss_mech_initialize\n");
+ return (&spnego_mechanism);
+}
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_acquire_cred(void *ctx,
+ 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)
+{
+ OM_uint32 status;
+ gss_OID_set amechs;
+ dsyslog("Entering spnego_gss_acquire_cred\n");
+
+ if (actual_mechs)
+ *actual_mechs = NULL;
+
+ if (time_rec)
+ *time_rec = 0;
+
+ /*
+ * If the user did not specify a list of mechs,
+ * use get_available_mechs to collect a list of
+ * mechs for which creds are available.
+ */
+ if (desired_mechs == GSS_C_NULL_OID_SET) {
+ status = get_available_mechs(minor_status,
+ desired_name, cred_usage,
+ output_cred_handle, &amechs);
+ } else {
+ /*
+ * The caller gave a specific list of mechanisms,
+ * so just get whatever creds are available.
+ * gss_acquire_creds will return the subset of mechs for
+ * which the given 'output_cred_handle' is valid.
+ */
+ status = gss_acquire_cred(minor_status,
+ desired_name, time_req,
+ desired_mechs, cred_usage,
+ output_cred_handle, &amechs,
+ time_rec);
+ }
+
+ if (actual_mechs && amechs != GSS_C_NULL_OID_SET) {
+ (void) gss_copy_oid_set(minor_status, amechs, actual_mechs);
+ }
+ (void) gss_release_oid_set(minor_status, &amechs);
+
+ dsyslog("Leaving spnego_gss_acquire_cred\n");
+ return (status);
+}
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_release_cred(void *ctx,
+ OM_uint32 *minor_status,
+ gss_cred_id_t *cred_handle)
+{
+ OM_uint32 status;
+
+ 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);
+
+ status = gss_release_cred(minor_status, cred_handle);
+
+ dsyslog("Leaving spnego_gss_release_cred\n");
+ return (status);
+}
+
+static void
+check_spnego_options(spnego_gss_ctx_id_t spnego_ctx)
+{
+ spnego_ctx->optionStr = __gss_get_modOptions(
+ (const gss_OID)&spnego_oids[0]);
+ if (spnego_ctx->optionStr != NULL &&
+ strstr(spnego_ctx->optionStr, "msinterop")) {
+ spnego_ctx->MS_Interop = 1;
+ } else {
+ spnego_ctx->MS_Interop = 0;
+ }
+}
+
+static spnego_gss_ctx_id_t
+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->internal_mech = NULL;
+ spnego_ctx->optionStr = NULL;
+ spnego_ctx->optimistic = 0;
+ spnego_ctx->MS_Interop = 0;
+ spnego_ctx->DER_mechTypes.length = NULL;
+ spnego_ctx->DER_mechTypes.value = GSS_C_NO_BUFFER;
+
+ check_spnego_options(spnego_ctx);
+
+ return (spnego_ctx);
+}
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_init_sec_context(void *ct,
+ 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)
+{
+ OM_uint32 ret = 0;
+ OM_uint32 status = 0;
+ OM_uint32 mstat;
+ OM_uint32 local_ret_flags = 0;
+
+ /*
+ * send_token is used to indicate in later steps
+ * what type of token, if any should be sent or processed.
+ * NO_TOKEN_SEND = no token should be sent
+ * INIT_TOKEN_SEND = initial token will be sent
+ * CONT_TOKEN_SEND = continuing tokens to be sent
+ * CHECK_MIC = no token to be sent, but have a MIC to check.
+ */
+ send_token_flag send_token = NO_TOKEN_SEND;
+
+ gss_OID_set mechSet;
+ spnego_gss_ctx_id_t spnego_ctx = NULL;
+ gss_buffer_t i_output_token = GSS_C_NO_BUFFER;
+ gss_buffer_t i_input_token = GSS_C_NO_BUFFER;
+ gss_buffer_t mechListMIC = NULL;
+ gss_cred_id_t *credlistptr = NULL, credlist;
+ gss_qop_t *qop_state = NULL;
+ unsigned char *ptr;
+ int len;
+
+ dsyslog("Entering init_sec_context\n");
+
+ if (context_handle == NULL)
+ return (GSS_S_FAILURE);
+
+ *minor_status = 0;
+ output_token->length = 0;
+ output_token->value = NULL;
+
+ if (actual_mech)
+ *actual_mech = NULL;
+
+ if (*context_handle == GSS_C_NO_CONTEXT) {
+
+ /* determine negotiation mech set */
+ if (claimant_cred_handle == GSS_C_NO_CREDENTIAL) {
+ credlistptr = &credlist;
+
+ mstat = get_available_mechs(minor_status,
+ GSS_C_NO_NAME, GSS_C_INITIATE,
+ credlistptr, &mechSet);
+ } else {
+ /*
+ * Use the list of mechs included in the
+ * cred that we were given.
+ */
+ mstat = gss_inquire_cred(minor_status,
+ claimant_cred_handle,
+ NULL, NULL, NULL,
+ &mechSet);
+ }
+ if (mstat != GSS_S_COMPLETE)
+ return (mstat);
+
+ if ((spnego_ctx = create_spnego_ctx()) == NULL) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+
+ /*
+ * need to pull the first mech from mechSet to do first
+ * init ctx
+ */
+ status = generic_gss_copy_oid(minor_status,
+ mechSet->elements,
+ &spnego_ctx->internal_mech);
+
+ if (status != GSS_S_COMPLETE) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+
+ if (input_token != NULL && input_token->value != NULL) {
+ 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.
+ */
+ spnego_ctx->ctx_handle = GSS_C_NO_CONTEXT;
+ *context_handle = (gss_ctx_id_t)spnego_ctx;
+ send_token = INIT_TOKEN_SEND;
+ ret = GSS_S_CONTINUE_NEEDED;
+ } else {
+ mechSet = NULL;
+ spnego_ctx = (spnego_gss_ctx_id_t)(*context_handle);
+
+ if (input_token == NULL || input_token->value == NULL) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+ ptr = (unsigned char *) input_token->value;
+
+ switch (get_negResult(&ptr, input_token->length)) {
+ case ACCEPT_DEFECTIVE_TOKEN:
+ *minor_status = 1;
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ break;
+ case ACCEPT_INCOMPLETE: {
+ /* pull out mech from token */
+ gss_OID internal_mech =
+ get_mech_oid(minor_status, &ptr,
+ input_token->length -
+ (ptr - (uchar_t *)input_token->value));
+
+ /*
+ * check if first mech in neg set, if it isn't,
+ * release and copy chosen mech to context,
+ * delete internal context from prior mech
+ */
+ if (internal_mech != NULL &&
+ ((internal_mech->length !=
+ spnego_ctx->internal_mech->length) ||
+ /* CSTYLED */
+ memcmp(spnego_ctx->internal_mech->elements,
+ internal_mech->elements,
+ spnego_ctx->internal_mech->length))) {
+
+ (void) gss_delete_sec_context(&mstat,
+ &spnego_ctx->ctx_handle, NULL);
+
+ spnego_ctx->ctx_handle = GSS_C_NO_CONTEXT;
+ (void) generic_gss_release_oid(
+ &mstat, &spnego_ctx->internal_mech);
+
+ status = generic_gss_copy_oid(
+ minor_status, internal_mech,
+ &spnego_ctx->internal_mech);
+
+ if (status != GSS_S_COMPLETE)
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ else
+ ret = GSS_S_COMPLETE;
+
+ (void) generic_gss_release_oid(&mstat,
+ &internal_mech);
+ } else if (internal_mech == NULL) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ send_token = NO_TOKEN_SEND;
+ } else {
+ ret = GSS_S_COMPLETE;
+ }
+ if (ret == GSS_S_COMPLETE) {
+ /*
+ * Check for a token, it may contain
+ * an error message.
+ */
+ if (*ptr == (CONTEXT | 0x02)) {
+ if (g_get_tag_and_length(&ptr,
+ (CONTEXT | 0x02),
+ input_token->length -
+ (ptr - (uchar_t *)input_token->value),
+ &len) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ } else {
+ i_input_token = get_input_token(&ptr,
+ len);
+ if (i_input_token != NULL) {
+ ret = GSS_S_CONTINUE_NEEDED;
+ send_token = CONT_TOKEN_SEND;
+ } else {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ send_token = NO_TOKEN_SEND;
+ }
+ }
+ }
+ }
+ break;
+ }
+ case ACCEPT_COMPLETE:
+ /* pull out mech from token */
+ if (spnego_ctx->internal_mech != NULL)
+ (void) generic_gss_release_oid(&mstat,
+ &spnego_ctx->internal_mech);
+
+ spnego_ctx->internal_mech =
+ get_mech_oid(minor_status, &ptr,
+ input_token->length -
+ (ptr - (uchar_t *)input_token->value));
+
+ if (spnego_ctx->internal_mech == NULL) {
+ /* CSTYLED */
+ *minor_status = ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR;
+ ret = GSS_S_FAILURE;
+ }
+
+ if (ret != GSS_S_FAILURE && *ptr == (CONTEXT | 0x02)) {
+ if (g_get_tag_and_length(&ptr, (CONTEXT | 0x02),
+ input_token->length -
+ (ptr - (uchar_t *)input_token->value),
+ &len) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ } else {
+ i_input_token = get_input_token(&ptr, len);
+ if (i_input_token != NULL) {
+ ret = GSS_S_COMPLETE;
+ send_token = CHECK_MIC;
+ } else {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ send_token = NO_TOKEN_SEND;
+ }
+ }
+ } else if (ret == GSS_S_CONTINUE_NEEDED ||
+ ret == GSS_S_COMPLETE) {
+ send_token = CHECK_MIC;
+ }
+
+ /*
+ * If we sent "optimistic" initial token,
+ * but the acceptor did not send a response token,
+ * this is an error.
+ */
+ if (ret == GSS_S_COMPLETE &&
+ i_input_token == GSS_C_NO_BUFFER &&
+ spnego_ctx->last_status == GSS_S_CONTINUE_NEEDED &&
+ spnego_ctx->optimistic) {
+ /* CSTYLED */
+ *minor_status = ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR;
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ send_token = NO_TOKEN_SEND;
+ }
+
+ if (send_token != NO_TOKEN_SEND) {
+ if (i_input_token == NULL)
+ ret = GSS_S_COMPLETE;
+ else
+ ret = GSS_S_CONTINUE_NEEDED;
+ send_token = CHECK_MIC;
+ }
+ break;
+
+ case REJECT:
+ ret = GSS_S_BAD_MECH;
+ *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
+ send_token = NO_TOKEN_SEND;
+ break;
+
+ default:
+ ret = GSS_S_FAILURE;
+ send_token = NO_TOKEN_SEND;
+ break;
+ }
+ }
+
+ if (actual_mech) {
+ (void) generic_gss_release_oid(&mstat, actual_mech);
+ ret = generic_gss_copy_oid(&mstat,
+ (gss_OID) gss_mech_spnego, actual_mech);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+ }
+
+ if (send_token == NO_TOKEN_SEND) {
+ output_token->length = 0;
+ output_token->value = NULL;
+ goto cleanup;
+ }
+
+ i_output_token = (gss_buffer_t)malloc(sizeof (gss_buffer_desc));
+
+ if (i_output_token == NULL) {
+ ret = status = GSS_S_FAILURE;
+ goto cleanup;
+ }
+
+ i_output_token->length = 0;
+ i_output_token->value = NULL;
+
+ if (ret == GSS_S_CONTINUE_NEEDED) {
+ gss_OID inner_mech_type = GSS_C_NO_OID;
+
+ status = gss_init_sec_context(minor_status,
+ claimant_cred_handle,
+ &spnego_ctx->ctx_handle,
+ target_name,
+ spnego_ctx->internal_mech,
+ req_flags,
+ time_req,
+ NULL,
+ i_input_token,
+ &inner_mech_type,
+ i_output_token,
+ &local_ret_flags,
+ time_rec);
+
+ if (ret_flags)
+ *ret_flags = local_ret_flags;
+
+ spnego_ctx->last_status = status;
+
+ if (i_input_token != GSS_C_NO_BUFFER) {
+ (void) gss_release_buffer(&mstat, i_input_token);
+ free(i_input_token);
+ }
+
+ if ((status != GSS_S_COMPLETE) &&
+ (status != GSS_S_CONTINUE_NEEDED)) {
+ ret = status;
+ }
+
+ /* create mic/check mic */
+ if ((i_output_token->length == 0) &&
+ (status == GSS_S_COMPLETE) &&
+ (local_ret_flags & GSS_C_INTEG_FLAG)) {
+ if (*ptr == (CONTEXT | 0x03) &&
+ g_get_tag_and_length(&ptr,
+ (CONTEXT | 0x03),
+ input_token->length -
+ (ptr - (uchar_t *)input_token->value),
+ &len) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ } else {
+ ret = GSS_S_COMPLETE;
+ mechListMIC = get_input_token(&ptr, len);
+ if (mechListMIC == NULL)
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ else if (!spnego_ctx->MS_Interop &&
+ spnego_ctx->DER_mechTypes.length > 0) {
+ status = gss_verify_mic(minor_status,
+ spnego_ctx->ctx_handle,
+ &spnego_ctx->DER_mechTypes,
+ mechListMIC,
+ qop_state);
+ }
+ }
+ }
+ }
+
+ if ((status == GSS_S_COMPLETE) &&
+ (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) {
+ (void) generic_gss_release_oid(&mstat, actual_mech);
+ ret = generic_gss_copy_oid(&mstat,
+ spnego_ctx->internal_mech, actual_mech);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+ }
+
+ release_spnego_ctx(&spnego_ctx);
+ } else if (ret == GSS_S_CONTINUE_NEEDED) {
+ if (make_spnego_tokenInit_msg(spnego_ctx,
+ mechSet, req_flags,
+ i_output_token, send_token,
+ output_token) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ }
+ }
+
+cleanup:
+ if (status != GSS_S_COMPLETE)
+ ret = status;
+ if (ret != GSS_S_COMPLETE &&
+ ret != GSS_S_CONTINUE_NEEDED) {
+ if (spnego_ctx != NULL &&
+ spnego_ctx->ctx_handle != NULL)
+ free(spnego_ctx->ctx_handle);
+
+ if (spnego_ctx != NULL)
+ release_spnego_ctx(&spnego_ctx);
+
+ *context_handle = GSS_C_NO_CONTEXT;
+
+ if (output_token)
+ (void) gss_release_buffer(&mstat, output_token);
+ }
+
+ if (i_output_token != GSS_C_NO_BUFFER) {
+ (void) gss_release_buffer(&mstat, i_output_token);
+ free(i_output_token);
+ }
+
+ if (mechListMIC != GSS_C_NO_BUFFER) {
+ (void) gss_release_buffer(&mstat, mechListMIC);
+ free(mechListMIC);
+ }
+
+ if (mechSet != NULL)
+ (void) gss_release_oid_set(&mstat, &mechSet);
+
+ if (credlistptr != NULL)
+ (void) gss_release_cred(&mstat, credlistptr);
+
+ return (ret);
+} /* init_sec_context */
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_accept_sec_context(void *ct,
+ 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)
+{
+ spnego_gss_ctx_id_t spnego_ctx = NULL;
+ gss_OID mech_wanted = NULL;
+ gss_OID_set mechSet = GSS_C_NO_OID_SET;
+ gss_OID_set supported_mechSet = GSS_C_NO_OID_SET;
+ gss_buffer_t i_output_token = GSS_C_NO_BUFFER;
+ gss_buffer_t i_input_token = GSS_C_NO_BUFFER;
+ gss_buffer_t mechListMIC = GSS_C_NO_BUFFER;
+ gss_cred_id_t acquired_cred = NULL;
+ gss_name_t internal_name = GSS_C_NO_NAME;
+ OM_uint32 status = GSS_S_COMPLETE;
+ OM_uint32 ret = GSS_S_COMPLETE;
+ unsigned char *ptr;
+ unsigned char *bufstart;
+ int bodysize;
+ int err, len;
+ OM_uint32 negResult;
+ OM_uint32 minor_stat;
+ OM_uint32 mstat;
+ OM_uint32 req_flags;
+ OM_uint32 mechsetlen;
+ gss_qop_t qop_state;
+ send_token_flag return_token = NO_TOKEN_SEND;
+ bool_t firstMech;
+ bool_t Need_Cred = FALSE;
+ OM_uint32 local_ret_flags = 0;
+ uchar_t *buf, *tmp;
+
+ dsyslog("Entering accept_sec_context\n");
+
+ if (context_handle == NULL)
+ return (GSS_S_FAILURE);
+
+ if (src_name)
+ *src_name = (gss_name_t)NULL;
+
+ output_token->length = 0;
+ output_token->value = NULL;
+ *minor_status = 0;
+
+ if (mech_type)
+ *mech_type = GSS_C_NULL_OID;
+
+ /* return a bogus cred handle */
+ if (delegated_cred_handle)
+ *delegated_cred_handle = GSS_C_NO_CREDENTIAL;
+
+ if (verifier_cred_handle == GSS_C_NO_CREDENTIAL) {
+ Need_Cred = TRUE;
+ }
+
+ /* Check for defective input token. */
+ ptr = bufstart = (unsigned char *) input_token->value;
+ if (err = g_verify_token_header((gss_OID)gss_mech_spnego, &bodysize,
+ &ptr, 0,
+ input_token->length)) {
+ *minor_status = err;
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ negResult = REJECT;
+ return_token = ERROR_TOKEN_SEND;
+ goto senderror;
+ }
+
+ /*
+ * set up of context, determine mech to be used, save mechset
+ * for use later in integrety check.
+ */
+ if (*context_handle == GSS_C_NO_CONTEXT) {
+ if ((spnego_ctx = create_spnego_ctx()) == NULL)
+ return (GSS_S_FAILURE);
+
+ /*
+ * Until the accept operation is complete, the
+ * context_handle returned should refer to
+ * the spnego context.
+ */
+ *context_handle = (gss_ctx_id_t)spnego_ctx;
+ minor_stat = get_available_mechs(minor_status,
+ GSS_C_NO_NAME, GSS_C_ACCEPT,
+ NULL, &supported_mechSet);
+
+ if (minor_stat != GSS_S_COMPLETE) {
+ release_spnego_ctx(&spnego_ctx);
+ *context_handle = GSS_C_NO_CONTEXT;
+ return (minor_stat);
+ }
+
+ if (Need_Cred) {
+ minor_stat = gss_acquire_cred(minor_status,
+ GSS_C_NO_NAME, NULL,
+ supported_mechSet,
+ GSS_C_ACCEPT,
+ &acquired_cred, NULL,
+ NULL);
+
+ if (minor_stat != GSS_S_COMPLETE) {
+ (void) gss_release_oid_set(minor_status,
+ &supported_mechSet);
+ release_spnego_ctx(&spnego_ctx);
+ *context_handle = GSS_C_NO_CONTEXT;
+ return (minor_stat);
+ } else {
+ verifier_cred_handle = acquired_cred;
+ }
+ }
+
+ if (err = g_verify_neg_token_init(&ptr, input_token->length)) {
+ *minor_status = err;
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ negResult = REJECT;
+ return_token = ERROR_TOKEN_SEND;
+ goto senderror;
+ }
+
+ /*
+ * Allocate space to hold the mechTypes
+ * because we need it later.
+ */
+ mechsetlen = input_token->length - (ptr - bufstart);
+ buf = (uchar_t *)malloc(mechsetlen);
+ if (buf == NULL) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+ (void) memcpy(buf, ptr, mechsetlen);
+ ptr = bufstart = buf;
+
+ /*
+ * Get pointers to the DER encoded MechSet so we
+ * can properly check and calculate a MIC later.
+ */
+ spnego_ctx->DER_mechTypes.value = ptr;
+ mechSet = get_mech_set(minor_status, &ptr, mechsetlen);
+ if (mechSet == NULL) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ negResult = REJECT;
+ return_token = ERROR_TOKEN_SEND;
+ goto senderror;
+ }
+ spnego_ctx->DER_mechTypes.length = ptr - bufstart;
+ mechsetlen -= (ptr - bufstart);
+
+ /*
+ * 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_type(minor_status,
+ supported_mechSet,
+ mechSet,
+ &negResult,
+ &firstMech);
+
+ (void) gss_release_oid_set(&minor_stat, &supported_mechSet);
+ (void) gss_release_oid_set(&minor_stat, &mechSet);
+ supported_mechSet = NULL;
+ mechSet = NULL;
+
+ if (get_req_flags(&ptr, (int *)&mechsetlen, &req_flags) ==
+ ACCEPT_DEFECTIVE_TOKEN) {
+ negResult = REJECT;
+ }
+
+ tmp = ptr;
+ if (negResult == ACCEPT_COMPLETE) {
+ if (g_get_tag_and_length(&ptr, (CONTEXT | 0x02),
+ mechsetlen,
+ &len) < 0) {
+ negResult = REJECT;
+ } else {
+ i_input_token = get_input_token(&ptr, len);
+ if (i_input_token == NULL) {
+ negResult = REJECT;
+ }
+ }
+ return_token = INIT_TOKEN_SEND;
+ }
+ if (negResult == REJECT) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ return_token = ERROR_TOKEN_SEND;
+ } else {
+ ret = GSS_S_CONTINUE_NEEDED;
+ return_token = INIT_TOKEN_SEND;
+ }
+
+ mechsetlen -= ptr - tmp;
+ /*
+ * Check to see if there is a MechListMIC field
+ */
+ if (negResult == ACCEPT_COMPLETE && mechsetlen > 0) {
+ tmp = ptr;
+ if (g_get_tag_and_length(&ptr, (CONTEXT | 0x03),
+ mechsetlen, &len) >= 0) {
+ mechListMIC = get_input_token(&ptr, len);
+ if (mechListMIC == GSS_C_NO_BUFFER) {
+ negResult = REJECT;
+ return_token = ERROR_TOKEN_SEND;
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ }
+ mechsetlen -= (ptr - tmp);
+ }
+ }
+ } else {
+ /*
+ * get internal input token and context for continued
+ * calls of spnego_gss_init_sec_context.
+ */
+ i_input_token = get_input_token(&ptr,
+ input_token->length -
+ (ptr - (uchar_t *)input_token->value));
+ if (i_input_token == NULL) {
+ negResult = REJECT;
+ return_token = ERROR_TOKEN_SEND;
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ } else {
+ spnego_ctx = (spnego_gss_ctx_id_t)(*context_handle);
+ return_token = CONT_TOKEN_SEND;
+ }
+ }
+
+ /*
+ * If we still don't have a cred, we have an error.
+ */
+ if (verifier_cred_handle == GSS_C_NO_CREDENTIAL) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+
+ /* If we have an error already, bail out */
+ if (ret != GSS_S_COMPLETE && ret != GSS_S_CONTINUE_NEEDED)
+ goto senderror;
+
+ if (i_input_token != GSS_C_NO_BUFFER) {
+ i_output_token = (gss_buffer_t)malloc(sizeof (gss_buffer_desc));
+
+ if (i_output_token == NULL) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+
+ i_output_token->length = 0;
+ i_output_token->value = NULL;
+
+ status = gss_accept_sec_context(&minor_stat,
+ &spnego_ctx->ctx_handle,
+ verifier_cred_handle,
+ i_input_token,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &internal_name,
+ mech_type,
+ i_output_token,
+ &local_ret_flags,
+ time_rec,
+ delegated_cred_handle);
+
+ if ((status != GSS_S_COMPLETE) &&
+ (status != GSS_S_CONTINUE_NEEDED)) {
+ *minor_status = minor_stat;
+ (void) gss_release_buffer(&mstat, i_input_token);
+
+ if (i_input_token != GSS_C_NO_BUFFER) {
+ free(i_input_token);
+ i_input_token = GSS_C_NO_BUFFER;
+ }
+
+ ret = status;
+
+ /*
+ * Reject the request with an error token.
+ */
+ negResult = REJECT;
+ return_token = ERROR_TOKEN_SEND;
+
+ goto senderror;
+ }
+
+ if (ret_flags)
+ *ret_flags = local_ret_flags;
+
+ if (i_input_token != GSS_C_NO_BUFFER) {
+ (void) gss_release_buffer(&mstat, i_input_token);
+ free(i_input_token);
+ i_input_token = GSS_C_NO_BUFFER;
+ }
+
+ /* If we got a MIC, verify it if possible */
+ if ((status == GSS_S_COMPLETE) &&
+ (local_ret_flags & GSS_C_INTEG_FLAG) &&
+ mechListMIC != GSS_C_NO_BUFFER &&
+ !spnego_ctx->MS_Interop) {
+
+ ret = gss_verify_mic(minor_status,
+ spnego_ctx->ctx_handle,
+ &spnego_ctx->DER_mechTypes,
+ mechListMIC,
+ &qop_state);
+
+ (void) gss_release_buffer(&mstat, mechListMIC);
+ free(mechListMIC);
+ mechListMIC = GSS_C_NO_BUFFER;
+
+ if (ret != GSS_S_COMPLETE) {
+ negResult = REJECT;
+ return_token = ERROR_TOKEN_SEND;
+ goto senderror;
+ }
+ }
+
+ /*
+ * If the MIC was verified OK, create a new MIC
+ * for the response message.
+ */
+ if (status == GSS_S_COMPLETE &&
+ (local_ret_flags & GSS_C_INTEG_FLAG) &&
+ !spnego_ctx->MS_Interop) {
+
+ mechListMIC = (gss_buffer_t)
+ malloc(sizeof (gss_buffer_desc));
+
+ if (mechListMIC == NULL ||
+ spnego_ctx->DER_mechTypes.length == 0) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+
+ ret = gss_get_mic(minor_status,
+ spnego_ctx->ctx_handle,
+ GSS_C_QOP_DEFAULT,
+ &spnego_ctx->DER_mechTypes,
+ mechListMIC);
+
+ if (ret != GSS_S_COMPLETE) {
+ negResult = REJECT;
+ return_token = ERROR_TOKEN_SEND;
+ goto senderror;
+ }
+ }
+ ret = status;
+
+ /*
+ * If we got this far OK, then switch the
+ * returned context handle to reference the
+ * "real" context handle for the mech.
+ */
+ if (status == GSS_S_COMPLETE) {
+ *context_handle =
+ (gss_ctx_id_t)spnego_ctx->ctx_handle;
+ if (internal_name != NULL && src_name != NULL)
+ *src_name = internal_name;
+ }
+
+
+ if (status == GSS_S_CONTINUE_NEEDED) {
+ if (return_token == INIT_TOKEN_SEND)
+ negResult = ACCEPT_INCOMPLETE;
+ }
+ }
+
+senderror:
+ if ((return_token == INIT_TOKEN_SEND) ||
+ (return_token == CONT_TOKEN_SEND) ||
+ (return_token == ERROR_TOKEN_SEND)) {
+ int MS_Interop = 0;
+
+ if (spnego_ctx)
+ MS_Interop = spnego_ctx->MS_Interop;
+
+ /*
+ * create response for the initiator.
+ */
+ err = make_spnego_tokenTarg_msg(negResult,
+ mech_wanted,
+ i_output_token,
+ mechListMIC,
+ return_token,
+ MS_Interop,
+ output_token);
+
+ (void) gss_release_buffer(&mstat, mechListMIC);
+ free(mechListMIC);
+
+ /*
+ * If we could not make the response token,
+ * we will have to fail without sending a response.
+ */
+ if (err) {
+ (void) gss_release_buffer(&mstat, output_token);
+ }
+ } else {
+ (void) gss_release_buffer(&mstat, output_token);
+ }
+ release_spnego_ctx(&spnego_ctx);
+
+cleanup:
+ if (ret != GSS_S_COMPLETE &&
+ ret != GSS_S_CONTINUE_NEEDED) {
+ if (spnego_ctx != NULL) {
+ (void) gss_delete_sec_context(&mstat,
+ &spnego_ctx->ctx_handle, NULL);
+
+ spnego_ctx->ctx_handle = NULL;
+
+ release_spnego_ctx(&spnego_ctx);
+ }
+ *context_handle = GSS_C_NO_CONTEXT;
+ }
+ if (mech_wanted != NULL) {
+ generic_gss_release_oid(&mstat, &mech_wanted);
+ }
+
+ (void) gss_release_cred(minor_status, &acquired_cred);
+ (void) gss_release_oid_set(minor_status, &supported_mechSet);
+
+ (void) gss_release_buffer(&mstat, i_output_token);
+ free(i_output_token);
+
+ return (ret);
+}
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_display_status(void *ctx,
+ OM_uint32 *minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ gss_OID mech_type,
+ OM_uint32 *message_context,
+ gss_buffer_t status_string)
+{
+ dsyslog("Entering display_status\n");
+
+ *message_context = 0;
+ switch (status_value) {
+ case ERR_SPNEGO_NO_MECHS_AVAILABLE:
+ /* CSTYLED */
+ *status_string = make_err_msg(gettext("SPNEGO cannot find mechanisms to negotiate"));
+ break;
+ case ERR_SPNEGO_NO_CREDS_ACQUIRED:
+ /* CSTYLED */
+ *status_string = make_err_msg(gettext("SPNEGO failed to acquire creds"));
+ break;
+ case ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR:
+ /* CSTYLED */
+ *status_string = make_err_msg(gettext("SPNEGO acceptor did not select a mechanism"));
+ break;
+ case ERR_SPNEGO_NEGOTIATION_FAILED:
+ /* CSTYLED */
+ *status_string = make_err_msg(gettext("SPNEGO failed to negotiate a mechanism"));
+ break;
+ case ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR:
+ /* CSTYLED */
+ *status_string = make_err_msg(gettext("SPNEGO acceptor did not return a valid token"));
+ break;
+ default:
+ status_string->length = 0;
+ status_string->value = "";
+ break;
+ }
+
+ dsyslog("Leaving display_status\n");
+ return (GSS_S_COMPLETE);
+}
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_import_name(void *ctx,
+ OM_uint32 *minor_status,
+ gss_buffer_t input_name_buffer,
+ gss_OID input_name_type,
+ gss_name_t *output_name)
+{
+ OM_uint32 status;
+
+ dsyslog("Entering import_name\n");
+
+ status = gss_import_name(minor_status, input_name_buffer,
+ input_name_type, output_name);
+
+ dsyslog("Leaving import_name\n");
+ return (status);
+}
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_release_name(void *ctx,
+ OM_uint32 *minor_status,
+ gss_name_t *input_name)
+{
+ OM_uint32 status;
+
+ dsyslog("Entering release_name\n");
+
+ status = gss_release_name(minor_status, input_name);
+
+ dsyslog("Leaving release_name\n");
+ return (status);
+}
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_display_name(void *ctx,
+ OM_uint32 *minor_status,
+ gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID *output_name_type)
+{
+ OM_uint32 status = GSS_S_COMPLETE;
+ dsyslog("Entering display_name\n");
+
+ status = gss_display_name(minor_status, input_name,
+ output_name_buffer, output_name_type);
+
+ dsyslog("Leaving display_name\n");
+ return (status);
+}
+
+/*ARGSUSED*/
+OM_uint32
+spnego_gss_inquire_names_for_mech(void *ctx,
+ OM_uint32 *minor_status,
+ gss_OID mechanism,
+ gss_OID_set *name_types)
+{
+ OM_uint32 major, minor;
+
+ dsyslog("Entering inquire_names_for_mech\n");
+ /*
+ * We only know how to handle our own mechanism.
+ */
+ if ((mechanism != GSS_C_NULL_OID) &&
+ !g_OID_equal(gss_mech_spnego, mechanism)) {
+ *minor_status = 0;
+ return (GSS_S_FAILURE);
+ }
+
+ major = gss_create_empty_oid_set(minor_status, name_types);
+ if (major == GSS_S_COMPLETE) {
+ /* Now add our members. */
+ if (((major = gss_add_oid_set_member(minor_status,
+ (gss_OID) GSS_C_NT_USER_NAME,
+ name_types)) == GSS_S_COMPLETE) &&
+ ((major = gss_add_oid_set_member(minor_status,
+ (gss_OID) GSS_C_NT_MACHINE_UID_NAME,
+ name_types)) == GSS_S_COMPLETE) &&
+ ((major = gss_add_oid_set_member(minor_status,
+ (gss_OID) GSS_C_NT_STRING_UID_NAME,
+ name_types)) == GSS_S_COMPLETE)) {
+ major = gss_add_oid_set_member(minor_status,
+ (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
+ name_types);
+ }
+
+ if (major != GSS_S_COMPLETE)
+ (void) gss_release_oid_set(&minor, name_types);
+ }
+
+ dsyslog("Leaving inquire_names_for_mech\n");
+ return (major);
+}
+
+OM_uint32
+spnego_gss_unseal(void *context,
+ 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,
+ int *qop_state)
+{
+ OM_uint32 ret;
+ ret = gss_unseal(minor_status,
+ context_handle,
+ input_message_buffer,
+ output_message_buffer,
+ conf_state,
+ qop_state);
+
+ return (ret);
+}
+
+OM_uint32
+spnego_gss_seal(void *context,
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ int qop_req,
+ gss_buffer_t input_message_buffer,
+ int *conf_state,
+ gss_buffer_t output_message_buffer)
+{
+ OM_uint32 ret;
+ ret = gss_seal(minor_status,
+ context_handle,
+ conf_req_flag,
+ qop_req,
+ input_message_buffer,
+ conf_state,
+ output_message_buffer);
+
+ return (ret);
+}
+
+OM_uint32
+spnego_gss_process_context_token(void *context,
+ 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);
+}
+
+OM_uint32
+spnego_gss_delete_sec_context(void *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;
+
+ if (context_handle == NULL)
+ return (GSS_S_FAILURE);
+
+ /*
+ * If this is still an SPNEGO mech, release it locally.
+ */
+ if (*ctx != NULL &&
+ (*ctx)->magic_num == SPNEGO_MAGIC_ID) {
+ (void) release_spnego_ctx(ctx);
+ } else {
+ ret = gss_delete_sec_context(minor_status,
+ context_handle,
+ output_token);
+ }
+
+ return (ret);
+}
+
+OM_uint32
+spnego_gss_context_time(void *context,
+ 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);
+}
+
+OM_uint32
+spnego_gss_export_sec_context(void *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);
+}
+
+OM_uint32
+spnego_gss_import_sec_context(void *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);
+}
+
+OM_uint32
+spnego_gss_inquire_context(void *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 *open)
+{
+ 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,
+ open);
+
+ return (ret);
+}
+
+OM_uint32
+spnego_gss_wrap_size_limit(void *context,
+ 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);
+}
+
+OM_uint32
+spnego_gss_sign(void *context,
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ int qop_req,
+ const gss_buffer_t message_buffer,
+ gss_buffer_t message_token)
+{
+ OM_uint32 ret;
+ ret = gss_sign(minor_status,
+ context_handle,
+ qop_req,
+ message_buffer,
+ message_token);
+ return (ret);
+}
+
+OM_uint32
+spnego_gss_verify(void *context,
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ const gss_buffer_t msg_buffer,
+ const gss_buffer_t token_buffer,
+ int *qop_state)
+{
+ OM_uint32 ret;
+ ret = gss_verify_mic(minor_status,
+ context_handle,
+ msg_buffer,
+ token_buffer,
+ (uint32_t *)qop_state);
+ return (ret);
+}
+
+/*
+ * We will release everything but the ctx_handle so that it
+ * can be passed back to init/accept context. This routine should
+ * not be called until after the ctx_handle memory is assigned to
+ * the supplied context handle from init/accept context.
+ */
+static void
+release_spnego_ctx(spnego_gss_ctx_id_t *ctx)
+{
+ spnego_gss_ctx_id_t context;
+ OM_uint32 minor_stat;
+ context = *ctx;
+
+ if (context != NULL) {
+ (void) gss_release_buffer(&minor_stat,
+ &context->DER_mechTypes);
+
+ (void) generic_gss_release_oid(&minor_stat,
+ &context->internal_mech);
+
+ if (context->optionStr != NULL) {
+ free(context->optionStr);
+ context->optionStr = NULL;
+ }
+ free(context);
+ *ctx = NULL;
+ }
+}
+
+/*
+ * Can't use gss_indicate_mechs by itself to get available mechs for
+ * SPNEGO because it will also return the SPNEGO mech and we do not
+ * want to consider SPNEGO as an available security mech for
+ * negotiation. For this reason, get_available_mechs will return
+ * all available mechs except SPNEGO.
+ *
+ * If a ptr to a creds list is given, this function will attempt
+ * to acquire creds for the creds given and trim the list of
+ * returned mechanisms to only those for which creds are valid.
+ *
+ */
+static OM_uint32
+get_available_mechs(OM_uint32 *minor_status,
+ gss_name_t name, gss_cred_usage_t usage,
+ gss_cred_id_t *creds, gss_OID_set *rmechs)
+{
+ int i;
+ int found = 0;
+ OM_uint32 stat = GSS_S_COMPLETE;
+ gss_OID_set mechs, goodmechs;
+
+ stat = gss_indicate_mechs(minor_status, &mechs);
+
+ if (stat != GSS_S_COMPLETE) {
+ return (stat);
+ }
+
+ stat = gss_create_empty_oid_set(minor_status, rmechs);
+
+ if (stat != GSS_S_COMPLETE) {
+ (void) gss_release_oid_set(minor_status, &mechs);
+ return (stat);
+ }
+
+ for (i = 0; i < mechs->count && stat == 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)) {
+
+ stat = gss_add_oid_set_member(minor_status,
+ &mechs->elements[i],
+ rmechs);
+ if (stat == 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 && stat == GSS_S_COMPLETE && creds != NULL) {
+ stat = gss_acquire_cred(minor_status,
+ name, NULL, *rmechs, usage, creds,
+ &goodmechs, NULL);
+
+ /*
+ * Drop the old list in favor of the new
+ * "trimmed" list.
+ */
+ (void) gss_release_oid_set(minor_status, rmechs);
+ if (stat == GSS_S_COMPLETE) {
+ (void) gss_copy_oid_set(minor_status,
+ goodmechs, rmechs);
+ (void) gss_release_oid_set(minor_status, &goodmechs);
+ }
+ }
+
+ (void) gss_release_oid_set(minor_status, &mechs);
+ if (found == 0 || stat != GSS_S_COMPLETE) {
+ *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE;
+ if (stat == GSS_S_COMPLETE)
+ stat = GSS_S_FAILURE;
+ }
+
+ return (stat);
+}
+
+/* following are token creation and reading routines */
+
+/*
+ * If buff_in is not pointing to a MECH_OID, then return NULL and do not
+ * advance the buffer, otherwise, decode the mech_oid from the buffer and
+ * place in gss_OID.
+ */
+static gss_OID
+get_mech_oid(OM_uint32 *minor_status, unsigned char **buff_in, size_t length)
+{
+ OM_uint32 status;
+ gss_OID_desc toid;
+ gss_OID mech_out = NULL;
+ uchar_t *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)
+ mech_out = NULL;
+
+ return (mech_out);
+}
+
+/*
+ * der encode the given mechanism oid into buf_out, advancing the
+ * buffer pointer.
+ */
+
+static int
+put_mech_oid(unsigned char **buf_out, gss_OID_desc *mech, int buflen)
+{
+ if (buflen < mech->length + 2)
+ return (-1);
+ *(*buf_out)++ = MECH_OID;
+ *(*buf_out)++ = (unsigned char) mech->length;
+ memcpy((void *)(*buf_out), mech->elements, mech->length);
+ *buf_out += mech->length;
+ return (0);
+}
+
+/*
+ * verify that buff_in points to an octet string, if it does not,
+ * return NULL and don't advance the pointer. If it is an octet string
+ * decode buff_in into a gss_buffer_t and return it, advancing the
+ * buffer pointer.
+ */
+static gss_buffer_t
+get_input_token(unsigned char **buff_in, int buff_length)
+{
+ gss_buffer_t input_token;
+ unsigned int bytes;
+
+ if (**buff_in != OCTET_STRING)
+ return (NULL);
+
+ (*buff_in)++;
+ input_token = (gss_buffer_t)malloc(sizeof (gss_buffer_desc));
+
+ if (input_token == NULL)
+ return (NULL);
+
+ input_token->length = get_der_length(buff_in, buff_length, &bytes);
+ if ((int)input_token->length == -1) {
+ free(input_token);
+ return (NULL);
+ }
+ input_token->value = malloc(input_token->length);
+
+ if (input_token->value == NULL) {
+ free(input_token);
+ return (NULL);
+ }
+
+ (void) memcpy(input_token->value, *buff_in, input_token->length);
+ *buff_in += input_token->length;
+ return (input_token);
+}
+
+/*
+ * verify that the input token length is not 0. If it is, just return.
+ * If the token length is greater than 0, der encode as an octet string
+ * and place in buf_out, advancing buf_out.
+ */
+
+static int
+put_input_token(unsigned char **buf_out, gss_buffer_t input_token,
+ 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 = put_der_length(input_token->length, buf_out,
+ input_token->length)))
+ return (ret);
+ TWRITE_STR(*buf_out, input_token->value, ((int)input_token->length));
+ return (0);
+}
+
+/*
+ * verify that buff_in points to a sequence of der encoding. The mech
+ * set is the only sequence of encoded object in the token, so if it is
+ * a sequence of encoding, decode the mechset into a gss_OID_set and
+ * return it, advancing the buffer pointer.
+ */
+static gss_OID_set
+get_mech_set(OM_uint32 *minor_status, unsigned char **buff_in, int buff_length)
+{
+ gss_OID_set returned_mechSet;
+ OM_uint32 major_status;
+ OM_uint32 length;
+ OM_uint32 bytes;
+ OM_uint32 set_length;
+ uchar_t *start;
+ int i;
+
+ if (**buff_in != SEQUENCE_OF)
+ return (NULL);
+
+ start = *buff_in;
+ (*buff_in)++;
+
+ length = get_der_length(buff_in, buff_length, &bytes);
+
+ 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 < length; i++) {
+ gss_OID_desc *temp = get_mech_oid(minor_status, buff_in,
+ buff_length - (*buff_in - start));
+ if (temp != NULL) {
+ major_status = gss_add_oid_set_member(minor_status,
+ temp, &returned_mechSet);
+ if (major_status == GSS_S_COMPLETE) {
+ set_length += returned_mechSet->elements[i].length +2;
+ generic_gss_release_oid(minor_status, &temp);
+ }
+ }
+ }
+
+ return (returned_mechSet);
+}
+
+/*
+ * der encode the passed mechSet and place it into buf_out,
+ * advancing the buffer pointer.
+ */
+static int
+put_mech_set(uchar_t **buf_out, gss_OID_set mechSet, int buflen)
+{
+ int i, ret;
+ OM_uint32 length = 0;
+ uchar_t *start;
+
+ if (buf_out == NULL || *buf_out == NULL)
+ return (-1);
+
+ start = *buf_out;
+
+ *(*buf_out)++ = SEQUENCE_OF;
+
+ for (i = 0; i < mechSet->count; i++) {
+ /*
+ * Mech OID ASN.1 size = 2 + length.
+ * 1 = 0x06, 1 for length of OID
+ * typically, less than 128, so only 1 byte needed.
+ */
+ length += 1 + der_length_size(mechSet->elements[i].length) +
+ mechSet->elements[i].length;
+ }
+ if (length > (buflen-1))
+ return (-1);
+
+ if (put_der_length(length, buf_out, buflen-1) < 0)
+ return (-1);
+
+ for (i = 0; i < mechSet->count; i++) {
+ if ((ret = put_mech_oid(buf_out, &mechSet->elements[i],
+ buflen - (int)(*buf_out - start))))
+ return (ret);
+ }
+ return (0);
+}
+
+/*
+ * Verify that buff_in is pointing to a BIT_STRING with the correct
+ * length and padding for the req_flags. If it is, decode req_flags
+ * and return them, otherwise, return NULL.
+ */
+static OM_uint32
+get_req_flags(unsigned char **buff_in, int *bodysize, OM_uint32 *req_flags)
+{
+ int len;
+ uchar_t *start = *buff_in;
+
+ if (**buff_in != (CONTEXT | 0x01))
+ return (0);
+
+ if (g_get_tag_and_length(buff_in, (CONTEXT | 0x01),
+ *bodysize, &len) < 0)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+
+ if (*(*buff_in)++ != BIT_STRING)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+
+ if (*(*buff_in)++ != BIT_STRING_LENGTH)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+
+ if (*(*buff_in)++ != BIT_STRING_PADDING)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+
+ *req_flags = (OM_uint32) (*(*buff_in)++ >> 1);
+ *bodysize -= *buff_in - start;
+ return (0);
+}
+
+/*
+ * der encode the passed req_flags into buf_out, advancing
+ * the buffer pointer.
+ */
+
+static int
+put_req_flags(unsigned char **buf_out, OM_uint32 req_flags, int buflen)
+{
+ int ret = 0;
+ if (buflen < 6)
+ return (-1);
+
+ *(*buf_out)++ = CONTEXT | 0x01;
+ if ((ret = put_der_length(4, buf_out, buflen-1)) != 0)
+ return (ret);
+
+ *(*buf_out)++ = BIT_STRING;
+ *(*buf_out)++ = BIT_STRING_LENGTH;
+ *(*buf_out)++ = BIT_STRING_PADDING;
+ *(*buf_out)++ = (unsigned char) (req_flags << 1);
+ return (ret);
+}
+
+/*
+ * get the negotiation results, decoding the ENUMERATED type result
+ * from the buffer, advancing the buffer pointer.
+ */
+static OM_uint32
+get_negResult(unsigned char **buff_in, int bodysize)
+{
+ unsigned char *iptr = *buff_in;
+ int len;
+ unsigned int bytes;
+ OM_uint32 result;
+ /*
+ * Verify that the data is ASN.1 encoded correctly
+ */
+ if (g_get_tag_and_length(buff_in, (CONTEXT | 0x01),
+ bodysize, &len) < 0)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+
+ if (*(*buff_in)++ == SEQUENCE) {
+ if ((len = get_der_length(buff_in,
+ bodysize - (*buff_in - iptr),
+ &bytes)) < 0)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+ } else {
+ return (ACCEPT_INCOMPLETE);
+ }
+
+ /*
+ * if we find an octet string, we need to return
+ * incomplete so that we process the token correctly.
+ * Anything else unexpected, we reject.
+ */
+ if (*(*buff_in)++ == CONTEXT) {
+ if ((len = get_der_length(buff_in, bodysize -
+ (*buff_in - iptr), &bytes)) < 0)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+ } else {
+ return (ACCEPT_INCOMPLETE);
+ }
+
+ if (*(*buff_in) == OCTET_STRING)
+ return (ACCEPT_INCOMPLETE);
+
+ if (*(*buff_in)++ != ENUMERATED)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+
+ if (*(*buff_in)++ != ENUMERATION_LENGTH)
+ return (ACCEPT_DEFECTIVE_TOKEN);
+
+ /*
+ * Save the result byte to return later.
+ * This is the result
+ */
+ result = (OM_uint32)*(*buff_in)++;
+
+ if (g_get_tag_and_length(buff_in, (CONTEXT | 0x01),
+ bodysize - (*buff_in - iptr),
+ &len) < 0)
+ result = ACCEPT_DEFECTIVE_TOKEN;
+
+ return (result);
+}
+
+/*
+ * der encode the passed negResults as an ENUMERATED type and
+ * place it in buf_out, advancing the buffer.
+ */
+
+static int
+put_negResult(uchar_t **buf_out, OM_uint32 negResult, int buflen)
+{
+ if (buflen < 3)
+ return (-1);
+ *(*buf_out)++ = ENUMERATED;
+ *(*buf_out)++ = ENUMERATION_LENGTH;
+ *(*buf_out)++ = (unsigned char) negResult;
+ return (0);
+}
+
+/*
+ * This routine compares the recieved mechset to the mechset that
+ * this server can support. It looks sequentially through the mechset
+ * and the first one that matches what the server can support is
+ * chosen as the negotiated mechanism. If one is found, negResult
+ * is set to ACCEPT_COMPLETE, otherwise we return NULL and negResult
+ * is set to REJECT. Also, for purposes of determining latter behavior,
+ * the flag, firstMech is used to indicate if the chosen mechanism is the
+ * first of the mechset or not.
+ */
+static gss_OID
+negotiate_mech_type(OM_uint32 *minor_status,
+ gss_OID_set supported_mechSet,
+ gss_OID_set mechset,
+ OM_uint32 *negResult,
+ bool_t *firstMech)
+{
+ gss_OID returned_mech;
+ OM_uint32 status;
+ int present;
+ int i;
+
+ for (i = 0; i < mechset->count; i++) {
+ gss_test_oid_set_member(minor_status, &mechset->elements[i],
+ supported_mechSet, &present);
+ if (present == TRUE) {
+ *negResult = ACCEPT_COMPLETE;
+
+ if (i == 0)
+ *firstMech = TRUE;
+ else
+ *firstMech = FALSE;
+
+ status = generic_gss_copy_oid(minor_status,
+ &mechset->elements[i],
+ &returned_mech);
+
+ if (status != GSS_S_COMPLETE) {
+ *negResult = REJECT;
+ return (NULL);
+ }
+
+ return (returned_mech);
+ }
+ }
+
+ *negResult = REJECT;
+ return (NULL);
+}
+
+/*
+ * the next two routines make a token buffer suitable for
+ * spnego_gss_display_status. These currently take the string
+ * in name and place it in the token. Eventually, if
+ * spnego_gss_display_status returns valid error messages,
+ * these routines will be changes to return the error string.
+ */
+static spnego_token_t
+make_spnego_token(char *name)
+{
+ spnego_token_t token;
+
+ token = (spnego_token_t)malloc(strlen(name)+1);
+
+ if (token == NULL)
+ return (NULL);
+ strcpy(token, name);
+ return (token);
+}
+
+static gss_buffer_desc
+make_err_msg(char *name)
+{
+ gss_buffer_desc buffer;
+
+ if (name == NULL) {
+ buffer.length = 0;
+ buffer.value = NULL;
+ } else {
+ buffer.length = strlen(name)+1;
+ buffer.value = make_spnego_token(name);
+ }
+
+ return (buffer);
+}
+
+/*
+ * Create the client side spnego token passed back to gss_init_sec_context
+ * and eventually up to the application program and over to the server.
+ *
+ * Use DER rules, definite length method per RFC 2478
+ */
+static int
+make_spnego_tokenInit_msg(spnego_gss_ctx_id_t spnego_ctx,
+ gss_OID_set mechSet, OM_uint32 req_flags,
+ gss_buffer_t data, send_token_flag sendtoken,
+ gss_buffer_t outbuf)
+{
+ OM_uint32 status, minor_stat;
+ int tlen, dataLen = 0, ret = 0;
+ int MechSetLen = 0;
+ int negTokenInitSize = 0;
+ int i;
+ unsigned char *t;
+ unsigned char *ptr;
+ unsigned char *MechListPtr = NULL;
+ gss_buffer_desc MICbuff;
+
+ if (outbuf == GSS_C_NO_BUFFER)
+ return (-1);
+
+ outbuf->length = 0;
+ outbuf->value = NULL;
+
+ /* calculate the data length */
+
+ /* no token generated if sendtoken is not init or cont */
+ if ((sendtoken < INIT_TOKEN_SEND) ||
+ (sendtoken > CONT_TOKEN_SEND)) {
+ return (-1);
+ }
+
+ /*
+ * if this is the init token, we will send the mechset and req_flags
+ * so include their length.
+ */
+ if (sendtoken == INIT_TOKEN_SEND) {
+ /*
+ * Count bytes for the mechSet data
+ * Encoded in final output as:
+ * 0xa0 [DER LEN] 0x30 [DER LEN] [DATA]
+ */
+ for (i = 0; i < mechSet->count; i++)
+ MechSetLen += 1 +
+ der_length_size(mechSet->elements[i].length) +
+ mechSet->elements[i].length;
+
+ MechSetLen += 1 + der_length_size(MechSetLen);
+ dataLen += 1 + der_length_size(MechSetLen) + MechSetLen;
+
+ MechListPtr = (uchar_t *)malloc(dataLen);
+ ptr = (uchar_t *)MechListPtr;
+
+ if (MechListPtr != NULL) {
+ if ((ret = put_mech_set(&ptr, mechSet, dataLen))) {
+ free(MechListPtr);
+ goto errout;
+ }
+ } else {
+ ret = -1;
+ goto errout;
+ }
+
+ /*
+ * The MIC is done over the DER encoded mechSet.
+ */
+ spnego_ctx->DER_mechTypes.value = MechListPtr;
+ spnego_ctx->DER_mechTypes.length = ptr - MechListPtr;
+
+ /*
+ * Only send the MIC if we are *NOT* interoperating
+ * with Microsoft.
+ */
+ if (!spnego_ctx->MS_Interop) {
+ /*
+ * MechListMIC = DER(MIC(DER(MechSet)))
+ * Calculate it here, stick it in the buffer later.
+ */
+ MICbuff.length = 0;
+ MICbuff.value = NULL;
+ status = gss_get_mic(&minor_stat,
+ spnego_ctx->ctx_handle,
+ GSS_C_QOP_DEFAULT,
+ &spnego_ctx->DER_mechTypes,
+ &MICbuff);
+ /*
+ * If the MIC operation succeeded, use it,
+ * but don't fail if it did not succeed.
+ * MIC is optional and is not supported by all
+ * mechanisms all the time.
+ */
+ if (status == GSS_S_COMPLETE) {
+ /*
+ * Encoded in final output as:
+ * 0xa3 [DER LEN] 0x04 [DER LEN] [DATA]
+ * --s-- -------tlen------------
+ */
+ tlen = 1 + der_length_size(MICbuff.length) +
+ MICbuff.length;
+
+ dataLen += 1 + der_length_size(tlen) + tlen;
+ }
+ }
+
+ /*
+ * 4 bytes for ret_flags:
+ * ASN.1 token + ASN.1 Length + Padding + Flags
+ * 0xa1 LENGTH BIT_STRING BIT_STRING_LEN PAD DATA
+ */
+ if (req_flags != 0)
+ dataLen += 6;
+ }
+
+ /*
+ * 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----------
+ */
+ tlen = 1 + der_length_size(data->length) + data->length;
+
+ dataLen += 1 + der_length_size(tlen) + tlen;
+ }
+
+ /*
+ * Add size of DER encoding
+ * [ SEQUENCE { MechTypeList | ReqFLags | Token | mechListMIC } ]
+ * 0x30 [DER_LEN] [data]
+ *
+ */
+ dataLen += 1 + der_length_size(dataLen);
+
+ /*
+ * negTokenInitSize indicates the bytes needed to
+ * hold the ASN.1 encoding of the entire NegTokenInit
+ * SEQUENCE.
+ * 0xa0 [DER_LEN] + data
+ *
+ */
+ negTokenInitSize = dataLen;
+
+ tlen = g_token_size((gss_OID)gss_mech_spnego,
+ negTokenInitSize + 1 +
+ der_length_size(negTokenInitSize));
+
+ t = (unsigned char *) malloc(tlen);
+
+ if (t == NULL) {
+ return (-1);
+ }
+
+ ptr = t;
+
+ /* create the message */
+ if ((ret = g_make_token_header((gss_OID)gss_mech_spnego,
+ 1 + negTokenInitSize +
+ der_length_size(negTokenInitSize),
+ &ptr, tlen)))
+ goto errout;
+
+ if (sendtoken == INIT_TOKEN_SEND) {
+ *ptr++ = CONTEXT; /* NegotiationToken identifier */
+ if ((ret = put_der_length(negTokenInitSize, &ptr, tlen)))
+ goto errout;
+
+ *ptr++ = SEQUENCE;
+ if ((ret = put_der_length(negTokenInitSize - 4, &ptr,
+ tlen - (int)(ptr-t))))
+ goto errout;
+
+ *ptr++ = CONTEXT; /* MechTypeList identifier */
+ if ((ret = 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 (req_flags != 0) {
+ if ((ret = put_req_flags(&ptr, req_flags,
+ tlen - (int)(ptr-t))))
+ goto errout;
+ }
+ }
+
+ if (data != NULL) {
+ *ptr++ = CONTEXT | 0x02;
+ if ((ret = put_der_length(data->length + 4,
+ &ptr, tlen - (int)(ptr - t))))
+ goto errout;
+
+ if ((ret = put_input_token(&ptr, data,
+ tlen - (int)(ptr - t))))
+ goto errout;
+
+ /*
+ * We are in "optimistic" mode if we send a token
+ * with out initial message.
+ */
+ spnego_ctx->optimistic = (sendtoken == INIT_TOKEN_SEND);
+ }
+
+ if (!spnego_ctx->MS_Interop && MICbuff.length > 0) {
+ /* We already calculated the MechListMIC above */
+ *ptr++ = CONTEXT | 0x03;
+ if ((ret = put_der_length(MICbuff.length,
+ &ptr, tlen - (int)(ptr - t))))
+ goto errout;
+
+ if ((ret = put_input_token(&ptr, &MICbuff,
+ tlen - (int)(ptr - t))))
+ goto errout;
+
+ (void) gss_release_buffer(&minor_stat, &MICbuff);
+ }
+
+errout:
+ if (ret != 0) {
+ if (t)
+ free(t);
+ t = NULL;
+ tlen = 0;
+ }
+ outbuf->length = tlen;
+ outbuf->value = (void *) t;
+
+ return (ret);
+}
+
+/*
+ * create the server side spnego token passed back to
+ * gss_accept_sec_context and eventually up to the application program
+ * and over to the client.
+ */
+static int
+make_spnego_tokenTarg_msg(OM_uint32 status, gss_OID mech_wanted,
+ gss_buffer_t data, gss_buffer_t mechListMIC,
+ send_token_flag sendtoken, int MS_Flag,
+ gss_buffer_t outbuf)
+{
+ int tlen;
+ int ret;
+ int NegTokenTargSize;
+ int negresultTokenSize;
+ int NegTokenSize;
+ int rspTokenSize;
+ int micTokenSize;
+ int dataLen = 0;
+ unsigned char *t;
+ unsigned char *ptr;
+
+ if (outbuf == GSS_C_NO_BUFFER)
+ 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.
+ */
+ negresultTokenSize = 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) {
+
+ if (mech_wanted != NULL) {
+ 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 +
+ der_length_size(mech_wanted->length);
+
+ dataLen = negresultTokenSize + mechlistTokenSize;
+ }
+ } else {
+ /*
+ * If this is a response from a server, count
+ * the space needed for the negResult field.
+ * LENGTH(2) + ENUM(2) + result
+ */
+ dataLen = negresultTokenSize;
+ }
+ if (data != NULL && data->length > 0) {
+ /* Length of the inner token */
+ rspTokenSize = 1 + der_length_size(data->length) +
+ data->length;
+
+ dataLen += rspTokenSize;
+
+ /* Length of the outer token */
+ dataLen += 1 + der_length_size(rspTokenSize);
+ }
+ if (mechListMIC != NULL) {
+
+ /* Length of the inner token */
+ micTokenSize = 1 + der_length_size(mechListMIC->length) +
+ mechListMIC->length;
+
+ dataLen += micTokenSize;
+
+ /* Length of the outer token */
+ dataLen += 1 + der_length_size(micTokenSize);
+ } else if (data != NULL && data->length > 0 && MS_Flag) {
+ dataLen += rspTokenSize;
+ dataLen += 1 + der_length_size(rspTokenSize);
+ }
+
+ /*
+ * 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 + der_length_size(NegTokenTargSize);
+
+ /*
+ * NegotiationToken [ CHOICE ]{
+ * negTokenInit [0] NegTokenInit,
+ * negTokenTarg [1] NegTokenTarg }
+ */
+ NegTokenSize = dataLen;
+ dataLen += 1 + der_length_size(NegTokenSize);
+
+ tlen = dataLen;
+ t = (unsigned char *) malloc(tlen);
+
+ if (t == NULL) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+
+ ptr = t;
+
+ if (sendtoken == INIT_TOKEN_SEND ||
+ sendtoken == ERROR_TOKEN_SEND) {
+ /*
+ * Indicate that we are sending CHOICE 1
+ * (NegTokenTarg)
+ */
+ *ptr++ = CONTEXT | 0x01;
+ if ((ret = put_der_length(NegTokenSize, &ptr, dataLen))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+
+ *ptr++ = SEQUENCE;
+ if ((ret = put_der_length(NegTokenTargSize, &ptr,
+ tlen - (int)(ptr-t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+
+ /*
+ * First field of the NegTokenTarg SEQUENCE
+ * is the ENUMERATED NegResult.
+ */
+ *ptr++ = CONTEXT;
+ if ((ret = put_der_length(3, &ptr,
+ tlen - (int)(ptr-t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if ((ret = put_negResult(&ptr, status,
+ tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+
+ if (sendtoken != ERROR_TOKEN_SEND && mech_wanted != NULL) {
+ /*
+ * Next, is the Supported MechType
+ */
+ *ptr++ = CONTEXT | 0x01;
+ if ((ret = put_der_length(mech_wanted->length + 2,
+ &ptr, tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if ((ret = put_mech_oid(&ptr, mech_wanted,
+ tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ }
+ }
+
+ if (data != NULL && data->length > 0) {
+ *ptr++ = CONTEXT | 0x02;
+ if ((ret = put_der_length(rspTokenSize, &ptr,
+ tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if ((ret = put_input_token(&ptr, data,
+ tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ }
+ if (mechListMIC != NULL) {
+ *ptr++ = CONTEXT | 0x03;
+ if ((ret = put_der_length(micTokenSize, &ptr,
+ tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if ((ret = put_input_token(&ptr, mechListMIC,
+ tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ } else if (data != NULL && data->length > 0 && MS_Flag) {
+ *ptr++ = CONTEXT | 0x03;
+ if ((ret = put_der_length(rspTokenSize, &ptr,
+ tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if ((ret = put_input_token(&ptr, data,
+ tlen - (int)(ptr - t)))) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ }
+ }
+errout:
+ if (ret != 0) {
+ if (t)
+ free(t);
+ } else {
+ outbuf->length = ptr - t;
+ outbuf->value = (void *) t;
+ }
+
+ return (ret);
+}
+
+/* determine size of token */
+static int
+g_token_size(gss_OID 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 + der_length_size(mech->length) + mech->length;
+
+ /*
+ * Now add the bytes needed for the initial header
+ * token bytes:
+ * 0x60 + [DER_LEN] + HDRSIZE
+ */
+ hdrsize += 1 + der_length_size(body_size + hdrsize);
+
+ return (hdrsize + body_size);
+}
+
+/*
+ * generate token header.
+ *
+ * Use DER Definite Length method per RFC2478
+ * Use of indefinite length encoding will not be compatible
+ * with Microsoft or others that actually follow the spec.
+ */
+static int
+g_make_token_header(gss_OID mech,
+ int body_size,
+ unsigned char **buf,
+ int totallen)
+{
+ int hdrsize, ret = 0;
+ unsigned char *p = *buf;
+
+ hdrsize = 1 + der_length_size(mech->length) + mech->length;
+
+ *(*buf)++ = HEADER_ID;
+ if ((ret = put_der_length(hdrsize + body_size, buf, totallen)))
+ return (ret);
+
+ *(*buf)++ = MECH_OID;
+ if ((ret = put_der_length(mech->length, buf,
+ totallen - (int)(p - *buf))))
+ return (ret);
+ TWRITE_STR(*buf, mech->elements, ((int)mech->length));
+ return (0);
+}
+
+static int
+g_get_tag_and_length(unsigned char **buf, uchar_t tag, int buflen, int *outlen)
+{
+ unsigned char *ptr = *buf;
+ int ret = -1; /* pessimists, assume failure ! */
+ OM_uint32 encoded_len;
+
+ if (buflen > 0 && *ptr == tag) {
+ ptr++;
+ *outlen = get_der_length(&ptr, buflen, &encoded_len);
+ if (*outlen < 0)
+ ret = *outlen;
+ if ((ptr + *outlen) > (*buf + buflen))
+ ret = -1;
+ else
+ ret = 0;
+ }
+
+ *buf = ptr;
+ return (ret);
+}
+
+static int
+g_verify_neg_token_init(unsigned char **buf_in, 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, &seqsize) < 0)
+ return (G_BAD_TOK_HEADER);
+
+ cur_size = seqsize; /* should indicate bytes remaining */
+
+ /*
+ * Verify the next piece, it should identify this as
+ * a strucure of type NegTokenInit.
+ */
+ if (*buf++ == SEQUENCE) {
+ if ((seqsize = get_der_length(&buf, cur_size, &bytes)) < 0)
+ return (G_BAD_TOK_HEADER);
+ /*
+ * Make sure we have the entire buffer as described
+ */
+ if (buf + seqsize > endptr)
+ 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 = get_der_length(&buf, cur_size, &bytes)) < 0)
+ return (G_BAD_TOK_HEADER);
+ /*
+ * Make sure we have the entire buffer as described
+ */
+ if (buf + bytes > endptr)
+ return (G_BAD_TOK_HEADER);
+ } else {
+ return (G_BAD_TOK_HEADER);
+ }
+
+ /*
+ * At this point, *buf should be at the beginning of the
+ * DER encoded list of mech types that are to be negotiated.
+ */
+ *buf_in = buf;
+
+ return (ret);
+
+}
+
+/* verify token header. */
+static int
+g_verify_token_header(gss_OID mech,
+ int *body_size,
+ unsigned char **buf_in,
+ int tok_type,
+ int toksize)
+{
+ unsigned char *buf = *buf_in;
+ int seqsize;
+ gss_OID_desc toid;
+ int ret = 0;
+ unsigned int bytes;
+
+ if ((toksize -= 1) < 0)
+ return (G_BAD_TOK_HEADER);
+
+ if (*buf++ != HEADER_ID)
+ return (G_BAD_TOK_HEADER);
+
+ if ((seqsize = get_der_length(&buf, toksize, &bytes)) < 0)
+ return (G_BAD_TOK_HEADER);
+
+ if ((seqsize + bytes) != toksize)
+ return (G_BAD_TOK_HEADER);
+
+ if ((toksize -= 1) < 0)
+ return (G_BAD_TOK_HEADER);
+
+
+ if (*buf++ != MECH_OID)
+ return (G_BAD_TOK_HEADER);
+
+ if ((toksize -= 1) < 0)
+ return (G_BAD_TOK_HEADER);
+
+ toid.length = *buf++;
+
+ if ((toksize -= toid.length) < 0)
+ return (G_BAD_TOK_HEADER);
+
+ 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) < 0)
+ return (G_BAD_TOK_HEADER);
+
+ if (!ret) {
+ *buf_in = buf;
+ *body_size = toksize;
+ }
+
+ return (ret);
+}