summaryrefslogtreecommitdiff
path: root/src/lib/gssapi/spnego
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/gssapi/spnego')
-rw-r--r--src/lib/gssapi/spnego/Makefile.in24
-rw-r--r--src/lib/gssapi/spnego/deps18
-rw-r--r--src/lib/gssapi/spnego/gssapiP_spnego.h664
-rw-r--r--src/lib/gssapi/spnego/mech_spnego.exports1
-rw-r--r--src/lib/gssapi/spnego/spnego_mech.c4355
5 files changed, 5062 insertions, 0 deletions
diff --git a/src/lib/gssapi/spnego/Makefile.in b/src/lib/gssapi/spnego/Makefile.in
new file mode 100644
index 000000000000..c21ea230ca2a
--- /dev/null
+++ b/src/lib/gssapi/spnego/Makefile.in
@@ -0,0 +1,24 @@
+mydir=lib$(S)gssapi$(S)spnego
+BUILDTOP=$(REL)..$(S)..$(S)..
+LOCALINCLUDES = -I. -I$(srcdir) -I$(srcdir)/.. -I../generic -I$(srcdir)/../generic -I../mechglue -I$(srcdir)/../mechglue
+DEFINES=-D_GSS_STATIC_LINK=1
+
+##DOS##BUILDTOP = ..\..\..
+##DOS##PREFIXDIR=spnego
+##DOS##OBJFILE = ..\$(OUTPRE)spnego.lst
+
+##DOS##DLL_EXP_TYPE=GSS
+
+SRCS = $(srcdir)/spnego_mech.c
+
+OBJS = $(OUTPRE)spnego_mech.$(OBJEXT)
+
+STLIBOBJS = spnego_mech.o
+
+all-unix: all-libobjs
+
+##DOS##LIBOBJS = $(OBJS)
+
+clean-unix:: clean-libobjs
+
+@libobj_frag@
diff --git a/src/lib/gssapi/spnego/deps b/src/lib/gssapi/spnego/deps
new file mode 100644
index 000000000000..feb409e732d5
--- /dev/null
+++ b/src/lib/gssapi/spnego/deps
@@ -0,0 +1,18 @@
+#
+# Generated makefile dependencies follow.
+#
+spnego_mech.so spnego_mech.po $(OUTPRE)spnego_mech.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
+ $(BUILDTOP)/include/gssapi/gssapi_alloc.h $(BUILDTOP)/include/gssapi/gssapi_ext.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../generic/gssapiP_generic.h \
+ $(srcdir)/../generic/gssapi_ext.h $(srcdir)/../generic/gssapi_generic.h \
+ $(srcdir)/../mechglue/mechglue.h $(srcdir)/../mechglue/mglueP.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ ../generic/gssapi_err_generic.h gssapiP_spnego.h spnego_mech.c
diff --git a/src/lib/gssapi/spnego/gssapiP_spnego.h b/src/lib/gssapi/spnego/gssapiP_spnego.h
new file mode 100644
index 000000000000..84c63fc17d2a
--- /dev/null
+++ b/src/lib/gssapi/spnego/gssapiP_spnego.h
@@ -0,0 +1,664 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _GSSAPIP_SPNEGO_H_
+#define _GSSAPIP_SPNEGO_H_
+
+/* #pragma ident "@(#)gssapiP_spnego.h 1.3 03/09/18 SMI" */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <gssapi/gssapi.h>
+
+#define SEC_CONTEXT_TOKEN 1
+#define SPNEGO_SIZE_OF_INT 4
+
+#define ACCEPT_COMPLETE 0
+#define ACCEPT_INCOMPLETE 1
+#define REJECT 2
+#define REQUEST_MIC 3
+#define ACCEPT_DEFECTIVE_TOKEN 0xffffffffUL
+
+/*
+ * constants for der encoding/decoding routines.
+ */
+
+#define MECH_OID 0x06
+#define OCTET_STRING 0x04
+#define CONTEXT 0xa0
+#define SEQUENCE 0x30
+#define SEQUENCE_OF 0x30
+#define BIT_STRING 0x03
+#define BIT_STRING_LENGTH 0x02
+#define BIT_STRING_PADDING 0x01
+#define ENUMERATED 0x0a
+#define ENUMERATION_LENGTH 1
+#define HEADER_ID 0x60
+#define GENERAL_STRING 0x1b
+
+/*
+ * SPNEGO specific error codes (minor status codes)
+ */
+#define ERR_SPNEGO_NO_MECHS_AVAILABLE 0x20000001
+#define ERR_SPNEGO_NO_CREDS_ACQUIRED 0x20000002
+#define ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR 0x20000003
+#define ERR_SPNEGO_NEGOTIATION_FAILED 0x20000004
+#define ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR 0x20000005
+
+/*
+ * send_token_flag 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.
+ * ERROR_TOKEN_SEND = error token from peer needs to be sent.
+ */
+
+typedef enum {NO_TOKEN_SEND, INIT_TOKEN_SEND, CONT_TOKEN_SEND,
+ CHECK_MIC, ERROR_TOKEN_SEND} send_token_flag;
+
+/*
+ * The Mech OID:
+ * { iso(1) org(3) dod(6) internet(1) security(5)
+ * mechanism(5) spnego(2) }
+ */
+
+#define SPNEGO_OID_LENGTH 6
+#define SPNEGO_OID "\053\006\001\005\005\002"
+
+typedef void *spnego_token_t;
+
+/* spnego name structure for internal representation. */
+typedef struct {
+ gss_OID type;
+ gss_buffer_t buffer;
+ gss_OID mech_type;
+ gss_name_t mech_name;
+} spnego_name_desc, *spnego_name_t;
+
+/* Structure for credential */
+typedef struct {
+ gss_cred_id_t mcred; /* mechglue union of obtainable creds */
+ gss_OID_set neg_mechs; /* app-specified list of allowable mechs */
+ int no_ask_integ; /* do not request integ from mechs */
+} spnego_gss_cred_id_rec, *spnego_gss_cred_id_t;
+
+/* Structure for context handle */
+typedef struct {
+ OM_uint32 magic_num;
+ gss_buffer_desc DER_mechTypes;
+ gss_OID_set mech_set;
+ gss_OID internal_mech; /* alias into mech_set->elements */
+ gss_ctx_id_t ctx_handle;
+ int mic_reqd;
+ int mic_sent;
+ int mic_rcvd;
+ int firstpass;
+ int mech_complete;
+ int nego_done;
+ int initiate;
+ int opened;
+ OM_uint32 ctx_flags;
+ gss_name_t internal_name;
+ gss_OID actual_mech;
+} spnego_gss_ctx_id_rec, *spnego_gss_ctx_id_t;
+
+/*
+ * The magic number must be less than a standard pagesize
+ * to avoid a possible collision with a real address.
+ */
+#define SPNEGO_MAGIC_ID 0x00000fed
+
+/* SPNEGO oid declarations */
+extern const gss_OID_desc * const gss_mech_spnego;
+extern const gss_OID_set_desc * const gss_mech_set_spnego;
+
+#if defined(DEBUG) && defined(HAVE_SYSLOG_H)
+#include <syslog.h>
+#define dsyslog(a) syslog(LOG_DEBUG, a)
+#else
+#define dsyslog(a)
+#define SPNEGO_STATIC
+#endif /* DEBUG */
+
+/*
+ * declarations of internal name mechanism functions
+ */
+
+OM_uint32 KRB5_CALLCONV spnego_gss_acquire_cred
+(
+ OM_uint32 *, /* minor_status */
+ gss_name_t, /* desired_name */
+ OM_uint32, /* time_req */
+ gss_OID_set, /* desired_mechs */
+ gss_cred_usage_t, /* cred_usage */
+ gss_cred_id_t *, /* output_cred_handle */
+ gss_OID_set *, /* actual_mechs */
+ OM_uint32 * /* time_rec */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_release_cred
+(
+ OM_uint32 *, /* minor_status */
+ /* CSTYLED */
+ gss_cred_id_t * /* cred_handle */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_init_sec_context
+(
+ OM_uint32 *, /* minor_status */
+ gss_cred_id_t, /* claimant_cred_handle */
+ gss_ctx_id_t *, /* context_handle */
+ gss_name_t, /* target_name */
+ gss_OID, /* mech_type */
+ OM_uint32, /* req_flags */
+ OM_uint32, /* time_req */
+ gss_channel_bindings_t, /* input_chan_bindings */
+ gss_buffer_t, /* input_token */
+ gss_OID *, /* actual_mech_type */
+ gss_buffer_t, /* output_token */
+ OM_uint32 *, /* ret_flags */
+ OM_uint32 * /* time_rec */
+);
+
+#ifndef LEAN_CLIENT
+OM_uint32 KRB5_CALLCONV spnego_gss_accept_sec_context
+(
+ OM_uint32 *, /* minor_status */
+ gss_ctx_id_t *, /* context_handle */
+ gss_cred_id_t, /* verifier_cred_handle */
+ gss_buffer_t, /* input_token_buffer */
+ 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 */
+ /* CSTYLED */
+ gss_cred_id_t * /* delegated_cred_handle */
+);
+#endif /* LEAN_CLIENT */
+
+OM_uint32 KRB5_CALLCONV spnego_gss_compare_name
+(
+ OM_uint32 *, /* minor_status */
+ const gss_name_t, /* name1 */
+ const gss_name_t, /* name2 */
+ int * /* name_equal */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_display_name
+(
+ OM_uint32 *, /* minor_status */
+ gss_name_t, /* input_name */
+ gss_buffer_t, /* output_name_buffer */
+ gss_OID * /* output_name_type */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_display_status
+(
+ OM_uint32 *, /* minor_status */
+ OM_uint32, /* status_value */
+ int, /* status_type */
+ gss_OID, /* mech_type */
+ OM_uint32 *, /* message_context */
+ gss_buffer_t /* status_string */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_import_name
+(
+ OM_uint32 *, /* minor_status */
+ gss_buffer_t, /* input_name_buffer */
+ gss_OID, /* input_name_type */
+ /* CSTYLED */
+ gss_name_t * /* output_name */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_release_name
+(
+ OM_uint32 *, /* minor_status */
+ /* CSTYLED */
+ gss_name_t * /* input_name */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_duplicate_name
+(
+ OM_uint32 *, /* minor_status */
+ /* CSTYLED */
+ const gss_name_t, /* input_name */
+ gss_name_t * /* output_name */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_inquire_cred
+(
+ OM_uint32 *, /* minor_status */
+ gss_cred_id_t, /* cred_handle */
+ gss_name_t *, /* name */
+ OM_uint32 *, /* lifetime */
+ int *, /* cred_usage */
+ gss_OID_set * /* mechanisms */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_inquire_names_for_mech
+(
+ OM_uint32 *, /* minor_status */
+ gss_OID, /* mechanism */
+ gss_OID_set * /* name_types */
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_unwrap
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ gss_buffer_t input_message_buffer,
+ gss_buffer_t output_message_buffer,
+ int *conf_state,
+ gss_qop_t *qop_state
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_wrap
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ gss_buffer_t input_message_buffer,
+ int *conf_state,
+ gss_buffer_t output_message_buffer
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_process_context_token
+(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ const gss_buffer_t token_buffer
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_delete_sec_context
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_buffer_t output_token
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_context_time
+(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ OM_uint32 *time_rec
+);
+#ifndef LEAN_CLIENT
+OM_uint32 KRB5_CALLCONV spnego_gss_export_sec_context
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_buffer_t interprocess_token
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_import_sec_context
+(
+ OM_uint32 *minor_status,
+ const gss_buffer_t interprocess_token,
+ gss_ctx_id_t *context_handle
+);
+#endif /* LEAN_CLIENT */
+
+OM_uint32 KRB5_CALLCONV spnego_gss_inquire_context
+(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t *src_name,
+ gss_name_t *targ_name,
+ OM_uint32 *lifetime_rec,
+ gss_OID *mech_type,
+ OM_uint32 *ctx_flags,
+ int *locally_initiated,
+ int *opened
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_wrap_size_limit
+(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32 *max_input_size
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_get_mic
+(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_qop_t qop_req,
+ const gss_buffer_t message_buffer,
+ gss_buffer_t message_token
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_verify_mic
+(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ const gss_buffer_t msg_buffer,
+ const gss_buffer_t token_buffer,
+ gss_qop_t *qop_state
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_sec_context_by_oid
+(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ const gss_OID desired_object,
+ gss_buffer_set_t *data_set
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_cred_by_oid
+(
+ OM_uint32 *minor_status,
+ const gss_cred_id_t cred_handle,
+ const gss_OID desired_object,
+ gss_buffer_set_t *data_set
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_set_cred_option
+(
+ OM_uint32 *minor_status,
+ gss_cred_id_t *cred_handle,
+ const gss_OID desired_object,
+ const gss_buffer_t value
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_set_sec_context_option
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ const gss_OID desired_object,
+ const gss_buffer_t value
+);
+
+#ifdef _GSS_STATIC_LINK
+int gss_spnegoint_lib_init(void);
+void gss_spnegoint_lib_fini(void);
+#else
+gss_mechanism KRB5_CALLCONV gss_mech_initialize(void);
+#endif /* _GSS_STATIC_LINK */
+
+OM_uint32 KRB5_CALLCONV spnego_gss_wrap_aead
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ gss_buffer_t input_assoc_buffer,
+ gss_buffer_t input_payload_buffer,
+ int *conf_state,
+ gss_buffer_t output_message_buffer
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_unwrap_aead
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ gss_buffer_t input_message_buffer,
+ gss_buffer_t input_assoc_buffer,
+ gss_buffer_t output_payload_buffer,
+ int *conf_state,
+ gss_qop_t *qop_state
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_wrap_iov
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ int *conf_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_unwrap_iov
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int *conf_state,
+ gss_qop_t *qop_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count
+);
+
+OM_uint32 KRB5_CALLCONV spnego_gss_wrap_iov_length
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ int *conf_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_complete_auth_token
+(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_buffer_t input_message_buffer
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_acquire_cred_impersonate_name(
+ OM_uint32 *, /* minor_status */
+ const gss_cred_id_t, /* impersonator_cred_handle */
+ const gss_name_t, /* desired_name */
+ OM_uint32, /* time_req */
+ const gss_OID_set, /* desired_mechs */
+ gss_cred_usage_t, /* cred_usage */
+ gss_cred_id_t *, /* output_cred_handle */
+ gss_OID_set *, /* actual_mechs */
+ OM_uint32 *); /* time_rec */
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_acquire_cred_with_password(
+ OM_uint32 *minor_status,
+ const gss_name_t desired_name,
+ const gss_buffer_t password,
+ OM_uint32 time_req,
+ const gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_cred_id_t *output_cred_handle,
+ gss_OID_set *actual_mechs,
+ OM_uint32 *time_rec);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_display_name_ext
+(
+ OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_OID display_as_name_type,
+ gss_buffer_t display_name
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_name
+(
+ OM_uint32 *minor_status,
+ gss_name_t name,
+ int *name_is_MN,
+ gss_OID *MN_mech,
+ gss_buffer_set_t *attrs
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_get_name_attribute
+(
+ OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_buffer_t attr,
+ int *authenticated,
+ int *complete,
+ gss_buffer_t value,
+ gss_buffer_t display_value,
+ int *more
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_set_name_attribute
+(
+ OM_uint32 *minor_status,
+ gss_name_t name,
+ int complete,
+ gss_buffer_t attr,
+ gss_buffer_t value
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_delete_name_attribute
+(
+ OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_buffer_t attr
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_export_name_composite
+(
+ OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_buffer_t exp_composite_name
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_map_name_to_any
+(
+ OM_uint32 *minor_status,
+ gss_name_t name,
+ int authenticated,
+ gss_buffer_t type_id,
+ gss_any_t *output
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_release_any_name_mapping
+(
+ OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_buffer_t type_id,
+ gss_any_t *input
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_pseudo_random
+(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context,
+ int prf_key,
+ const gss_buffer_t prf_in,
+ ssize_t desired_output_len,
+ gss_buffer_t prf_out
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_set_neg_mechs
+(
+ OM_uint32 *minor_status,
+ gss_cred_id_t cred_handle,
+ const gss_OID_set mech_list
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_mech_for_saslname
+(
+ OM_uint32 *minor_status,
+ const gss_buffer_t sasl_mech_name,
+ gss_OID *mech_type
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_saslname_for_mech
+(
+ OM_uint32 *minor_status,
+ const gss_OID desired_mech,
+ gss_buffer_t sasl_mech_name,
+ gss_buffer_t mech_name,
+ gss_buffer_t mech_description
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_attrs_for_mech
+(
+ OM_uint32 *minor_status,
+ gss_const_OID mech,
+ gss_OID_set *mech_attrs,
+ gss_OID_set *known_mech_attrs
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_acquire_cred_from
+(
+ OM_uint32 *minor_status,
+ const gss_name_t desired_name,
+ OM_uint32 time_req,
+ const gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_const_key_value_set_t cred_store,
+ gss_cred_id_t *output_cred_handle,
+ gss_OID_set *actual_mechs,
+ OM_uint32 *time_rec
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_export_cred(
+ OM_uint32 *minor_status,
+ gss_cred_id_t cred_handle,
+ gss_buffer_t token
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_import_cred(
+ OM_uint32 *minor_status,
+ gss_buffer_t token,
+ gss_cred_id_t *cred_handle
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_get_mic_iov(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ gss_qop_t qop_req,
+ gss_iov_buffer_desc *iov,
+ int iov_count
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_verify_mic_iov(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ gss_qop_t *qop_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count
+);
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_get_mic_iov_length(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ gss_qop_t qop_req,
+ gss_iov_buffer_desc *iov,
+ int iov_count
+);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GSSAPIP_SPNEGO_H_ */
diff --git a/src/lib/gssapi/spnego/mech_spnego.exports b/src/lib/gssapi/spnego/mech_spnego.exports
new file mode 100644
index 000000000000..9d570e5c058e
--- /dev/null
+++ b/src/lib/gssapi/spnego/mech_spnego.exports
@@ -0,0 +1 @@
+gss_mech_initialize
diff --git a/src/lib/gssapi/spnego/spnego_mech.c b/src/lib/gssapi/spnego/spnego_mech.c
new file mode 100644
index 000000000000..9d6027ce8058
--- /dev/null
+++ b/src/lib/gssapi/spnego/spnego_mech.c
@@ -0,0 +1,4355 @@
+/*
+ * Copyright (C) 2006,2008 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * A module that implements the spnego security mechanism.
+ * It is used to negotiate the security mechanism between
+ * peers using the GSS-API. SPNEGO is specified in RFC 4178.
+ *
+ */
+/*
+ * Copyright (c) 2006-2008, Novell, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The copyright holder's name is not used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* #pragma ident "@(#)spnego_mech.c 1.7 04/09/28 SMI" */
+
+#include <k5-int.h>
+#include <krb5.h>
+#include <mglueP.h>
+#include "gssapiP_spnego.h"
+#include <gssapi_err_generic.h>
+
+
+#undef g_token_size
+#undef g_verify_token_header
+#undef g_make_token_header
+
+#define HARD_ERROR(v) ((v) != GSS_S_COMPLETE && (v) != GSS_S_CONTINUE_NEEDED)
+typedef const gss_OID_desc *gss_OID_const;
+
+/* der routines defined in libgss */
+extern unsigned int gssint_der_length_size(unsigned int);
+extern int gssint_get_der_length(unsigned char **, unsigned int,
+ unsigned int*);
+extern int gssint_put_der_length(unsigned int, unsigned char **, unsigned int);
+
+
+/* private routines for spnego_mechanism */
+static spnego_token_t make_spnego_token(const char *);
+static gss_buffer_desc make_err_msg(const char *);
+static int g_token_size(gss_OID_const, unsigned int);
+static int g_make_token_header(gss_OID_const, unsigned int,
+ unsigned char **, unsigned int);
+static int g_verify_token_header(gss_OID_const, unsigned int *,
+ unsigned char **,
+ int, unsigned int);
+static int g_verify_neg_token_init(unsigned char **, unsigned int);
+static gss_OID get_mech_oid(OM_uint32 *, unsigned char **, size_t);
+static gss_buffer_t get_input_token(unsigned char **, unsigned int);
+static gss_OID_set get_mech_set(OM_uint32 *, unsigned char **, unsigned int);
+static OM_uint32 get_req_flags(unsigned char **, OM_uint32, OM_uint32 *);
+static OM_uint32 get_available_mechs(OM_uint32 *, gss_name_t, gss_cred_usage_t,
+ gss_const_key_value_set_t,
+ gss_cred_id_t *, gss_OID_set *,
+ OM_uint32 *);
+static OM_uint32 get_negotiable_mechs(OM_uint32 *, spnego_gss_cred_id_t,
+ gss_cred_usage_t, gss_OID_set *);
+static void release_spnego_ctx(spnego_gss_ctx_id_t *);
+static spnego_gss_ctx_id_t create_spnego_ctx(int);
+static int put_mech_set(gss_OID_set mechSet, gss_buffer_t buf);
+static int put_input_token(unsigned char **, gss_buffer_t, unsigned int);
+static int put_mech_oid(unsigned char **, gss_OID_const, unsigned int);
+static int put_negResult(unsigned char **, OM_uint32, unsigned int);
+
+static OM_uint32
+process_mic(OM_uint32 *, gss_buffer_t, spnego_gss_ctx_id_t,
+ gss_buffer_t *, OM_uint32 *, send_token_flag *);
+static OM_uint32
+handle_mic(OM_uint32 *, gss_buffer_t, int, spnego_gss_ctx_id_t,
+ gss_buffer_t *, OM_uint32 *, send_token_flag *);
+
+static OM_uint32
+init_ctx_new(OM_uint32 *, spnego_gss_cred_id_t, send_token_flag *,
+ spnego_gss_ctx_id_t *);
+static OM_uint32
+init_ctx_nego(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32, gss_OID,
+ gss_buffer_t *, gss_buffer_t *,
+ OM_uint32 *, send_token_flag *);
+static OM_uint32
+init_ctx_cont(OM_uint32 *, spnego_gss_ctx_id_t, gss_buffer_t,
+ gss_buffer_t *, gss_buffer_t *,
+ OM_uint32 *, send_token_flag *);
+static OM_uint32
+init_ctx_reselect(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32,
+ gss_OID, gss_buffer_t *, gss_buffer_t *,
+ OM_uint32 *, send_token_flag *);
+static OM_uint32
+init_ctx_call_init(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t,
+ gss_name_t, OM_uint32, OM_uint32, gss_buffer_t,
+ gss_OID *, gss_buffer_t, OM_uint32 *, OM_uint32 *,
+ OM_uint32 *, send_token_flag *);
+
+static OM_uint32
+acc_ctx_new(OM_uint32 *, gss_buffer_t, spnego_gss_cred_id_t, gss_buffer_t *,
+ gss_buffer_t *, OM_uint32 *, send_token_flag *,
+ spnego_gss_ctx_id_t *);
+static OM_uint32
+acc_ctx_cont(OM_uint32 *, gss_buffer_t, spnego_gss_ctx_id_t, gss_buffer_t *,
+ gss_buffer_t *, OM_uint32 *, send_token_flag *);
+static OM_uint32
+acc_ctx_vfy_oid(OM_uint32 *, spnego_gss_ctx_id_t, gss_OID,
+ OM_uint32 *, send_token_flag *);
+static OM_uint32
+acc_ctx_call_acc(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t,
+ gss_buffer_t, gss_OID *, gss_buffer_t,
+ OM_uint32 *, OM_uint32 *, gss_cred_id_t *,
+ OM_uint32 *, send_token_flag *);
+
+static gss_OID
+negotiate_mech(gss_OID_set, gss_OID_set, OM_uint32 *);
+static int
+g_get_tag_and_length(unsigned char **, int, unsigned int, unsigned int *);
+
+static int
+make_spnego_tokenInit_msg(spnego_gss_ctx_id_t,
+ int,
+ gss_buffer_t,
+ OM_uint32, gss_buffer_t, send_token_flag,
+ gss_buffer_t);
+static int
+make_spnego_tokenTarg_msg(OM_uint32, gss_OID, gss_buffer_t,
+ gss_buffer_t, send_token_flag,
+ gss_buffer_t);
+
+static OM_uint32
+get_negTokenInit(OM_uint32 *, gss_buffer_t, gss_buffer_t,
+ gss_OID_set *, OM_uint32 *, gss_buffer_t *,
+ gss_buffer_t *);
+static OM_uint32
+get_negTokenResp(OM_uint32 *, unsigned char *, unsigned int,
+ OM_uint32 *, gss_OID *, gss_buffer_t *, gss_buffer_t *);
+
+static int
+is_kerb_mech(gss_OID oid);
+
+/* SPNEGO oid structure */
+static const gss_OID_desc spnego_oids[] = {
+ {SPNEGO_OID_LENGTH, SPNEGO_OID},
+};
+
+const gss_OID_desc * const gss_mech_spnego = spnego_oids+0;
+static const gss_OID_set_desc spnego_oidsets[] = {
+ {1, (gss_OID) spnego_oids+0},
+};
+const gss_OID_set_desc * const gss_mech_set_spnego = spnego_oidsets+0;
+
+static int make_NegHints(OM_uint32 *, gss_buffer_t *);
+static int put_neg_hints(unsigned char **, gss_buffer_t, unsigned int);
+static OM_uint32
+acc_ctx_hints(OM_uint32 *, spnego_gss_cred_id_t, gss_buffer_t *, OM_uint32 *,
+ send_token_flag *, spnego_gss_ctx_id_t *);
+
+/*
+ * The Mech OID for SPNEGO:
+ * { iso(1) org(3) dod(6) internet(1) security(5)
+ * mechanism(5) spnego(2) }
+ */
+static struct gss_config spnego_mechanism =
+{
+ {SPNEGO_OID_LENGTH, SPNEGO_OID},
+ NULL,
+ spnego_gss_acquire_cred,
+ spnego_gss_release_cred,
+ spnego_gss_init_sec_context,
+#ifndef LEAN_CLIENT
+ spnego_gss_accept_sec_context,
+#else
+ NULL,
+#endif /* LEAN_CLIENT */
+ NULL, /* gss_process_context_token */
+ spnego_gss_delete_sec_context, /* gss_delete_sec_context */
+ spnego_gss_context_time, /* gss_context_time */
+ spnego_gss_get_mic, /* gss_get_mic */
+ spnego_gss_verify_mic, /* gss_verify_mic */
+ spnego_gss_wrap, /* gss_wrap */
+ spnego_gss_unwrap, /* gss_unwrap */
+ spnego_gss_display_status,
+ NULL, /* gss_indicate_mechs */
+ spnego_gss_compare_name,
+ spnego_gss_display_name,
+ spnego_gss_import_name,
+ spnego_gss_release_name,
+ spnego_gss_inquire_cred, /* gss_inquire_cred */
+ NULL, /* gss_add_cred */
+#ifndef LEAN_CLIENT
+ spnego_gss_export_sec_context, /* gss_export_sec_context */
+ spnego_gss_import_sec_context, /* gss_import_sec_context */
+#else
+ NULL, /* gss_export_sec_context */
+ NULL, /* gss_import_sec_context */
+#endif /* LEAN_CLIENT */
+ NULL, /* gss_inquire_cred_by_mech */
+ spnego_gss_inquire_names_for_mech,
+ spnego_gss_inquire_context, /* gss_inquire_context */
+ NULL, /* gss_internal_release_oid */
+ spnego_gss_wrap_size_limit, /* gss_wrap_size_limit */
+ NULL, /* gssd_pname_to_uid */
+ NULL, /* gss_userok */
+ NULL, /* gss_export_name */
+ spnego_gss_duplicate_name, /* gss_duplicate_name */
+ NULL, /* gss_store_cred */
+ spnego_gss_inquire_sec_context_by_oid, /* gss_inquire_sec_context_by_oid */
+ spnego_gss_inquire_cred_by_oid, /* gss_inquire_cred_by_oid */
+ spnego_gss_set_sec_context_option, /* gss_set_sec_context_option */
+ spnego_gss_set_cred_option, /* gssspi_set_cred_option */
+ NULL, /* gssspi_mech_invoke */
+ spnego_gss_wrap_aead,
+ spnego_gss_unwrap_aead,
+ spnego_gss_wrap_iov,
+ spnego_gss_unwrap_iov,
+ spnego_gss_wrap_iov_length,
+ spnego_gss_complete_auth_token,
+ spnego_gss_acquire_cred_impersonate_name,
+ NULL, /* gss_add_cred_impersonate_name */
+ spnego_gss_display_name_ext,
+ spnego_gss_inquire_name,
+ spnego_gss_get_name_attribute,
+ spnego_gss_set_name_attribute,
+ spnego_gss_delete_name_attribute,
+ spnego_gss_export_name_composite,
+ spnego_gss_map_name_to_any,
+ spnego_gss_release_any_name_mapping,
+ spnego_gss_pseudo_random,
+ spnego_gss_set_neg_mechs,
+ spnego_gss_inquire_saslname_for_mech,
+ spnego_gss_inquire_mech_for_saslname,
+ spnego_gss_inquire_attrs_for_mech,
+ spnego_gss_acquire_cred_from,
+ NULL, /* gss_store_cred_into */
+ spnego_gss_acquire_cred_with_password,
+ spnego_gss_export_cred,
+ spnego_gss_import_cred,
+ NULL, /* gssspi_import_sec_context_by_mech */
+ NULL, /* gssspi_import_name_by_mech */
+ NULL, /* gssspi_import_cred_by_mech */
+ spnego_gss_get_mic_iov,
+ spnego_gss_verify_mic_iov,
+ spnego_gss_get_mic_iov_length
+};
+
+#ifdef _GSS_STATIC_LINK
+#include "mglueP.h"
+
+static int gss_spnegomechglue_init(void)
+{
+ struct gss_mech_config mech_spnego;
+
+ memset(&mech_spnego, 0, sizeof(mech_spnego));
+ mech_spnego.mech = &spnego_mechanism;
+ mech_spnego.mechNameStr = "spnego";
+ mech_spnego.mech_type = GSS_C_NO_OID;
+
+ return gssint_register_mechinfo(&mech_spnego);
+}
+#else
+gss_mechanism KRB5_CALLCONV
+gss_mech_initialize(void)
+{
+ return (&spnego_mechanism);
+}
+
+MAKE_INIT_FUNCTION(gss_krb5int_lib_init);
+MAKE_FINI_FUNCTION(gss_krb5int_lib_fini);
+int gss_krb5int_lib_init(void);
+#endif /* _GSS_STATIC_LINK */
+
+int gss_spnegoint_lib_init(void)
+{
+ int err;
+
+ err = k5_key_register(K5_KEY_GSS_SPNEGO_STATUS, NULL);
+ if (err)
+ return err;
+
+#ifdef _GSS_STATIC_LINK
+ return gss_spnegomechglue_init();
+#else
+ return 0;
+#endif
+}
+
+void gss_spnegoint_lib_fini(void)
+{
+}
+
+static OM_uint32
+create_spnego_cred(OM_uint32 *minor_status, gss_cred_id_t mcred,
+ spnego_gss_cred_id_t *cred_out)
+{
+ spnego_gss_cred_id_t spcred;
+
+ *cred_out = NULL;
+ spcred = calloc(1, sizeof(spnego_gss_cred_id_rec));
+ if (spcred == NULL) {
+ *minor_status = ENOMEM;
+ return GSS_S_FAILURE;
+ }
+ spcred->mcred = mcred;
+ *cred_out = spcred;
+ return GSS_S_COMPLETE;
+}
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_acquire_cred(OM_uint32 *minor_status,
+ gss_name_t desired_name,
+ OM_uint32 time_req,
+ gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_cred_id_t *output_cred_handle,
+ gss_OID_set *actual_mechs,
+ OM_uint32 *time_rec)
+{
+ return spnego_gss_acquire_cred_from(minor_status, desired_name, time_req,
+ desired_mechs, cred_usage, NULL,
+ output_cred_handle, actual_mechs,
+ time_rec);
+}
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_acquire_cred_from(OM_uint32 *minor_status,
+ const gss_name_t desired_name,
+ OM_uint32 time_req,
+ const gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_const_key_value_set_t cred_store,
+ gss_cred_id_t *output_cred_handle,
+ gss_OID_set *actual_mechs,
+ OM_uint32 *time_rec)
+{
+ OM_uint32 status, tmpmin;
+ gss_OID_set amechs;
+ gss_cred_id_t mcred = NULL;
+ spnego_gss_cred_id_t spcred = NULL;
+ dsyslog("Entering spnego_gss_acquire_cred\n");
+
+ if (actual_mechs)
+ *actual_mechs = NULL;
+
+ if (time_rec)
+ *time_rec = 0;
+
+ /* We will obtain a mechglue credential and wrap it in a
+ * spnego_gss_cred_id_rec structure. Allocate the wrapper. */
+ status = create_spnego_cred(minor_status, mcred, &spcred);
+ if (status != GSS_S_COMPLETE)
+ return (status);
+
+ /*
+ * Always use get_available_mechs to collect a list of
+ * mechs for which creds are available.
+ */
+ status = get_available_mechs(minor_status, desired_name,
+ cred_usage, cred_store, &mcred,
+ &amechs, time_rec);
+
+ if (actual_mechs && amechs != GSS_C_NULL_OID_SET) {
+ (void) gssint_copy_oid_set(&tmpmin, amechs, actual_mechs);
+ }
+ (void) gss_release_oid_set(&tmpmin, &amechs);
+
+ if (status == GSS_S_COMPLETE) {
+ spcred->mcred = mcred;
+ *output_cred_handle = (gss_cred_id_t)spcred;
+ } else {
+ free(spcred);
+ *output_cred_handle = GSS_C_NO_CREDENTIAL;
+ }
+
+ dsyslog("Leaving spnego_gss_acquire_cred\n");
+ return (status);
+}
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_release_cred(OM_uint32 *minor_status,
+ gss_cred_id_t *cred_handle)
+{
+ spnego_gss_cred_id_t spcred = NULL;
+
+ dsyslog("Entering spnego_gss_release_cred\n");
+
+ if (minor_status == NULL || cred_handle == NULL)
+ return (GSS_S_CALL_INACCESSIBLE_WRITE);
+
+ *minor_status = 0;
+
+ if (*cred_handle == GSS_C_NO_CREDENTIAL)
+ return (GSS_S_COMPLETE);
+
+ spcred = (spnego_gss_cred_id_t)*cred_handle;
+ *cred_handle = GSS_C_NO_CREDENTIAL;
+ gss_release_oid_set(minor_status, &spcred->neg_mechs);
+ gss_release_cred(minor_status, &spcred->mcred);
+ free(spcred);
+
+ dsyslog("Leaving spnego_gss_release_cred\n");
+ return (GSS_S_COMPLETE);
+}
+
+static spnego_gss_ctx_id_t
+create_spnego_ctx(int initiate)
+{
+ spnego_gss_ctx_id_t spnego_ctx = NULL;
+ spnego_ctx = (spnego_gss_ctx_id_t)
+ malloc(sizeof (spnego_gss_ctx_id_rec));
+
+ if (spnego_ctx == NULL) {
+ return (NULL);
+ }
+
+ spnego_ctx->magic_num = SPNEGO_MAGIC_ID;
+ spnego_ctx->ctx_handle = GSS_C_NO_CONTEXT;
+ spnego_ctx->mech_set = NULL;
+ spnego_ctx->internal_mech = NULL;
+ spnego_ctx->DER_mechTypes.length = 0;
+ spnego_ctx->DER_mechTypes.value = NULL;
+ spnego_ctx->mic_reqd = 0;
+ spnego_ctx->mic_sent = 0;
+ spnego_ctx->mic_rcvd = 0;
+ spnego_ctx->mech_complete = 0;
+ spnego_ctx->nego_done = 0;
+ spnego_ctx->opened = 0;
+ spnego_ctx->initiate = initiate;
+ spnego_ctx->internal_name = GSS_C_NO_NAME;
+ spnego_ctx->actual_mech = GSS_C_NO_OID;
+
+ return (spnego_ctx);
+}
+
+/* iso(1) org(3) dod(6) internet(1) private(4) enterprises(1) samba(7165)
+ * gssntlmssp(655) controls(1) spnego_req_mechlistMIC(2) */
+static const gss_OID_desc spnego_req_mechlistMIC_oid =
+ { 11, "\x2B\x06\x01\x04\x01\xB7\x7D\x85\x0F\x01\x02" };
+
+/*
+ * Return nonzero if the mechanism has reason to believe that a mechlistMIC
+ * exchange will be required. Microsoft servers erroneously require SPNEGO
+ * mechlistMIC if they see an internal MIC within an NTLMSSP Authenticate
+ * message, even if NTLMSSP was the preferred mechanism.
+ */
+static int
+mech_requires_mechlistMIC(spnego_gss_ctx_id_t sc)
+{
+ OM_uint32 major, minor;
+ gss_ctx_id_t ctx = sc->ctx_handle;
+ gss_OID oid = (gss_OID)&spnego_req_mechlistMIC_oid;
+ gss_buffer_set_t bufs;
+ int result;
+
+ major = gss_inquire_sec_context_by_oid(&minor, ctx, oid, &bufs);
+ if (major != GSS_S_COMPLETE)
+ return 0;
+
+ /* Report true if the mech returns a single buffer containing a single
+ * byte with value 1. */
+ result = (bufs != NULL && bufs->count == 1 &&
+ bufs->elements[0].length == 1 &&
+ memcmp(bufs->elements[0].value, "\1", 1) == 0);
+ (void) gss_release_buffer_set(&minor, &bufs);
+ return result;
+}
+
+/* iso(1) org(3) dod(6) internet(1) private(4) enterprises(1) Microsoft(311)
+ * security(2) mechanisms(2) NTLM(10) */
+static const gss_OID_desc gss_mech_ntlmssp_oid =
+ { 10, "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" };
+
+/* iso(1) org(3) dod(6) internet(1) private(4) enterprises(1) samba(7165)
+ * gssntlmssp(655) controls(1) ntlmssp_reset_crypto(3) */
+static const gss_OID_desc ntlmssp_reset_crypto_oid =
+ { 11, "\x2B\x06\x01\x04\x01\xB7\x7D\x85\x0F\x01\x03" };
+
+/*
+ * MS-SPNG section 3.3.5.1 warns that the NTLM mechanism requires special
+ * handling of the crypto state to interop with Windows. If the mechanism for
+ * sc is SPNEGO, invoke a mechanism-specific operation on the context to reset
+ * the RC4 state after producing or verifying a MIC. Ignore a result of
+ * GSS_S_UNAVAILABLE for compatibility with older versions of the mechanism
+ * that do not support this functionality.
+ */
+static OM_uint32
+ntlmssp_reset_crypto_state(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
+ OM_uint32 verify)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc value;
+
+ if (!g_OID_equal(sc->internal_mech, &gss_mech_ntlmssp_oid))
+ return GSS_S_COMPLETE;
+
+ value.length = sizeof(verify);
+ value.value = &verify;
+ major = gss_set_sec_context_option(&minor, &sc->ctx_handle,
+ (gss_OID)&ntlmssp_reset_crypto_oid,
+ &value);
+ if (major == GSS_S_UNAVAILABLE)
+ return GSS_S_COMPLETE;
+ *minor_status = minor;
+ return major;
+}
+
+/*
+ * Both initiator and acceptor call here to verify and/or create mechListMIC,
+ * and to consistency-check the MIC state. handle_mic is invoked only if the
+ * negotiated mech has completed and supports MICs.
+ */
+static OM_uint32
+handle_mic(OM_uint32 *minor_status, gss_buffer_t mic_in,
+ int send_mechtok, spnego_gss_ctx_id_t sc,
+ gss_buffer_t *mic_out,
+ OM_uint32 *negState, send_token_flag *tokflag)
+{
+ OM_uint32 ret;
+
+ ret = GSS_S_FAILURE;
+ *mic_out = GSS_C_NO_BUFFER;
+ if (mic_in != GSS_C_NO_BUFFER) {
+ if (sc->mic_rcvd) {
+ /* Reject MIC if we've already received a MIC. */
+ *negState = REJECT;
+ *tokflag = ERROR_TOKEN_SEND;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ } else if (sc->mic_reqd && !send_mechtok) {
+ /*
+ * If the peer sends the final mechanism token, it
+ * must send the MIC with that token if the
+ * negotiation requires MICs.
+ */
+ *negState = REJECT;
+ *tokflag = ERROR_TOKEN_SEND;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ ret = process_mic(minor_status, mic_in, sc, mic_out,
+ negState, tokflag);
+ if (ret != GSS_S_COMPLETE) {
+ return ret;
+ }
+ if (sc->mic_reqd) {
+ assert(sc->mic_sent || sc->mic_rcvd);
+ }
+ if (sc->mic_sent && sc->mic_rcvd) {
+ ret = GSS_S_COMPLETE;
+ *negState = ACCEPT_COMPLETE;
+ if (*mic_out == GSS_C_NO_BUFFER) {
+ /*
+ * We sent a MIC on the previous pass; we
+ * shouldn't be sending a mechanism token.
+ */
+ assert(!send_mechtok);
+ *tokflag = NO_TOKEN_SEND;
+ } else {
+ *tokflag = CONT_TOKEN_SEND;
+ }
+ } else if (sc->mic_reqd) {
+ *negState = ACCEPT_INCOMPLETE;
+ ret = GSS_S_CONTINUE_NEEDED;
+ } else if (*negState == ACCEPT_COMPLETE) {
+ ret = GSS_S_COMPLETE;
+ } else {
+ ret = GSS_S_CONTINUE_NEEDED;
+ }
+ return ret;
+}
+
+/*
+ * Perform the actual verification and/or generation of mechListMIC.
+ */
+static OM_uint32
+process_mic(OM_uint32 *minor_status, gss_buffer_t mic_in,
+ spnego_gss_ctx_id_t sc, gss_buffer_t *mic_out,
+ OM_uint32 *negState, send_token_flag *tokflag)
+{
+ OM_uint32 ret, tmpmin;
+ gss_qop_t qop_state;
+ gss_buffer_desc tmpmic = GSS_C_EMPTY_BUFFER;
+
+ ret = GSS_S_FAILURE;
+ if (mic_in != GSS_C_NO_BUFFER) {
+ ret = gss_verify_mic(minor_status, sc->ctx_handle,
+ &sc->DER_mechTypes,
+ mic_in, &qop_state);
+ if (ret == GSS_S_COMPLETE)
+ ret = ntlmssp_reset_crypto_state(minor_status, sc, 1);
+ if (ret != GSS_S_COMPLETE) {
+ *negState = REJECT;
+ *tokflag = ERROR_TOKEN_SEND;
+ return ret;
+ }
+ /* If we got a MIC, we must send a MIC. */
+ sc->mic_reqd = 1;
+ sc->mic_rcvd = 1;
+ }
+ if (sc->mic_reqd && !sc->mic_sent) {
+ ret = gss_get_mic(minor_status, sc->ctx_handle,
+ GSS_C_QOP_DEFAULT,
+ &sc->DER_mechTypes,
+ &tmpmic);
+ if (ret == GSS_S_COMPLETE)
+ ret = ntlmssp_reset_crypto_state(minor_status, sc, 0);
+ if (ret != GSS_S_COMPLETE) {
+ gss_release_buffer(&tmpmin, &tmpmic);
+ *tokflag = NO_TOKEN_SEND;
+ return ret;
+ }
+ *mic_out = malloc(sizeof(gss_buffer_desc));
+ if (*mic_out == GSS_C_NO_BUFFER) {
+ gss_release_buffer(&tmpmin, &tmpmic);
+ *tokflag = NO_TOKEN_SEND;
+ return GSS_S_FAILURE;
+ }
+ **mic_out = tmpmic;
+ sc->mic_sent = 1;
+ }
+ return GSS_S_COMPLETE;
+}
+
+/* Create a new SPNEGO context handle for the initial call to
+ * spnego_gss_init_sec_context(). */
+static OM_uint32
+init_ctx_new(OM_uint32 *minor_status,
+ spnego_gss_cred_id_t spcred,
+ send_token_flag *tokflag,
+ spnego_gss_ctx_id_t *sc_out)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = NULL;
+
+ *sc_out = NULL;
+
+ sc = create_spnego_ctx(1);
+ if (sc == NULL)
+ return GSS_S_FAILURE;
+
+ /* determine negotiation mech set */
+ ret = get_negotiable_mechs(minor_status, spcred, GSS_C_INITIATE,
+ &sc->mech_set);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+
+ /* Set an initial internal mech to make the first context token. */
+ sc->internal_mech = &sc->mech_set->elements[0];
+
+ if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+
+ sc->ctx_handle = GSS_C_NO_CONTEXT;
+ *sc_out = sc;
+ sc = NULL;
+ *tokflag = INIT_TOKEN_SEND;
+ ret = GSS_S_CONTINUE_NEEDED;
+
+cleanup:
+ release_spnego_ctx(&sc);
+ return ret;
+}
+
+/*
+ * Called by second and later calls to spnego_gss_init_sec_context()
+ * to decode reply and update state.
+ */
+static OM_uint32
+init_ctx_cont(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
+ gss_buffer_t buf, gss_buffer_t *responseToken,
+ gss_buffer_t *mechListMIC, OM_uint32 *negState,
+ send_token_flag *tokflag)
+{
+ OM_uint32 ret, tmpmin, acc_negState;
+ unsigned char *ptr;
+ gss_OID supportedMech = GSS_C_NO_OID;
+
+ *negState = REJECT;
+ *tokflag = ERROR_TOKEN_SEND;
+
+ ptr = buf->value;
+ ret = get_negTokenResp(minor_status, ptr, buf->length,
+ &acc_negState, &supportedMech,
+ responseToken, mechListMIC);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+ if (acc_negState == REJECT) {
+ *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
+ map_errcode(minor_status);
+ *tokflag = NO_TOKEN_SEND;
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+ /*
+ * nego_done is false for the first call to init_ctx_cont()
+ */
+ if (!sc->nego_done) {
+ ret = init_ctx_nego(minor_status, sc,
+ acc_negState,
+ supportedMech, responseToken,
+ mechListMIC,
+ negState, tokflag);
+ } else if ((!sc->mech_complete && *responseToken == GSS_C_NO_BUFFER) ||
+ (sc->mech_complete && *responseToken != GSS_C_NO_BUFFER)) {
+ /* Missing or spurious token from acceptor. */
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ } else if (!sc->mech_complete ||
+ (sc->mic_reqd &&
+ (sc->ctx_flags & GSS_C_INTEG_FLAG))) {
+ /* Not obviously done; we may decide we're done later in
+ * init_ctx_call_init or handle_mic. */
+ *negState = ACCEPT_INCOMPLETE;
+ *tokflag = CONT_TOKEN_SEND;
+ ret = GSS_S_CONTINUE_NEEDED;
+ } else {
+ /* mech finished on last pass and no MIC required, so done. */
+ *negState = ACCEPT_COMPLETE;
+ *tokflag = NO_TOKEN_SEND;
+ ret = GSS_S_COMPLETE;
+ }
+cleanup:
+ if (supportedMech != GSS_C_NO_OID)
+ generic_gss_release_oid(&tmpmin, &supportedMech);
+ return ret;
+}
+
+/*
+ * Consistency checking and mechanism negotiation handling for second
+ * call of spnego_gss_init_sec_context(). Call init_ctx_reselect() to
+ * update internal state if acceptor has counter-proposed.
+ */
+static OM_uint32
+init_ctx_nego(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
+ OM_uint32 acc_negState, gss_OID supportedMech,
+ gss_buffer_t *responseToken, gss_buffer_t *mechListMIC,
+ OM_uint32 *negState, send_token_flag *tokflag)
+{
+ OM_uint32 ret;
+
+ *negState = REJECT;
+ *tokflag = ERROR_TOKEN_SEND;
+ ret = GSS_S_DEFECTIVE_TOKEN;
+
+ /*
+ * According to RFC 4178, both supportedMech and negState must be
+ * present in the first acceptor token. However, some Java
+ * implementations include only a responseToken in the first
+ * NegTokenResp. In this case we can use sc->internal_mech as the
+ * negotiated mechanism. (We do not currently look at acc_negState
+ * when continuing with the optimistic mechanism.)
+ */
+ if (supportedMech == GSS_C_NO_OID)
+ supportedMech = sc->internal_mech;
+
+ /*
+ * If the mechanism we sent is not the mechanism returned from
+ * the server, we need to handle the server's counter
+ * proposal. There is a bug in SAMBA servers that always send
+ * the old Kerberos mech OID, even though we sent the new one.
+ * So we will treat all the Kerberos mech OIDS as the same.
+ */
+ if (!(is_kerb_mech(supportedMech) &&
+ is_kerb_mech(sc->internal_mech)) &&
+ !g_OID_equal(supportedMech, sc->internal_mech)) {
+ ret = init_ctx_reselect(minor_status, sc,
+ acc_negState, supportedMech,
+ responseToken, mechListMIC,
+ negState, tokflag);
+
+ } else if (*responseToken == GSS_C_NO_BUFFER) {
+ if (sc->mech_complete) {
+ /*
+ * Mech completed on first call to its
+ * init_sec_context(). Acceptor sends no mech
+ * token.
+ */
+ *negState = ACCEPT_COMPLETE;
+ *tokflag = NO_TOKEN_SEND;
+ ret = GSS_S_COMPLETE;
+ } else {
+ /*
+ * Reject missing mech token when optimistic
+ * mech selected.
+ */
+ *minor_status = ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR;
+ map_errcode(minor_status);
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ }
+ } else if ((*responseToken)->length == 0 && sc->mech_complete) {
+ /* Handle old IIS servers returning empty token instead of
+ * null tokens in the non-mutual auth case. */
+ *negState = ACCEPT_COMPLETE;
+ *tokflag = NO_TOKEN_SEND;
+ ret = GSS_S_COMPLETE;
+ } else if (sc->mech_complete) {
+ /* Reject spurious mech token. */
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ } else {
+ *negState = ACCEPT_INCOMPLETE;
+ *tokflag = CONT_TOKEN_SEND;
+ ret = GSS_S_CONTINUE_NEEDED;
+ }
+ sc->nego_done = 1;
+ return ret;
+}
+
+/*
+ * Handle acceptor's counter-proposal of an alternative mechanism.
+ */
+static OM_uint32
+init_ctx_reselect(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
+ OM_uint32 acc_negState, gss_OID supportedMech,
+ gss_buffer_t *responseToken, gss_buffer_t *mechListMIC,
+ OM_uint32 *negState, send_token_flag *tokflag)
+{
+ OM_uint32 tmpmin;
+ size_t i;
+
+ gss_delete_sec_context(&tmpmin, &sc->ctx_handle,
+ GSS_C_NO_BUFFER);
+
+ /* Find supportedMech in sc->mech_set. */
+ for (i = 0; i < sc->mech_set->count; i++) {
+ if (g_OID_equal(supportedMech, &sc->mech_set->elements[i]))
+ break;
+ }
+ if (i == sc->mech_set->count)
+ return GSS_S_DEFECTIVE_TOKEN;
+ sc->internal_mech = &sc->mech_set->elements[i];
+
+ /*
+ * A server conforming to RFC4178 MUST set REQUEST_MIC here, but
+ * Windows Server 2003 and earlier implement (roughly) RFC2478 instead,
+ * and send ACCEPT_INCOMPLETE. Tolerate that only if we are falling
+ * back to NTLMSSP.
+ */
+ if (acc_negState == ACCEPT_INCOMPLETE) {
+ if (!g_OID_equal(supportedMech, &gss_mech_ntlmssp_oid))
+ return GSS_S_DEFECTIVE_TOKEN;
+ } else if (acc_negState != REQUEST_MIC) {
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ sc->mech_complete = 0;
+ sc->mic_reqd = (acc_negState == REQUEST_MIC);
+ *negState = acc_negState;
+ *tokflag = CONT_TOKEN_SEND;
+ return GSS_S_CONTINUE_NEEDED;
+}
+
+/*
+ * Wrap call to mechanism gss_init_sec_context() and update state
+ * accordingly.
+ */
+static OM_uint32
+init_ctx_call_init(OM_uint32 *minor_status,
+ spnego_gss_ctx_id_t sc,
+ spnego_gss_cred_id_t spcred,
+ gss_name_t target_name,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ gss_buffer_t mechtok_in,
+ gss_OID *actual_mech,
+ gss_buffer_t mechtok_out,
+ OM_uint32 *ret_flags,
+ OM_uint32 *time_rec,
+ OM_uint32 *negState,
+ send_token_flag *send_token)
+{
+ OM_uint32 ret, tmpret, tmpmin, mech_req_flags;
+ gss_cred_id_t mcred;
+
+ mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
+
+ mech_req_flags = req_flags;
+ if (spcred == NULL || !spcred->no_ask_integ)
+ mech_req_flags |= GSS_C_INTEG_FLAG;
+
+ ret = gss_init_sec_context(minor_status,
+ mcred,
+ &sc->ctx_handle,
+ target_name,
+ sc->internal_mech,
+ mech_req_flags,
+ time_req,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ mechtok_in,
+ &sc->actual_mech,
+ mechtok_out,
+ &sc->ctx_flags,
+ time_rec);
+ if (ret == GSS_S_COMPLETE) {
+ sc->mech_complete = 1;
+ if (ret_flags != NULL)
+ *ret_flags = sc->ctx_flags;
+ /*
+ * Microsoft SPNEGO implementations expect an even number of
+ * token exchanges. So if we're sending a final token, ask for
+ * a zero-length token back from the server. Also ask for a
+ * token back if this is the first token or if a MIC exchange
+ * is required.
+ */
+ if (*send_token == CONT_TOKEN_SEND &&
+ mechtok_out->length == 0 &&
+ (!sc->mic_reqd ||
+ !(sc->ctx_flags & GSS_C_INTEG_FLAG))) {
+ /* The exchange is complete. */
+ *negState = ACCEPT_COMPLETE;
+ ret = GSS_S_COMPLETE;
+ *send_token = NO_TOKEN_SEND;
+ } else {
+ /* Ask for one more hop. */
+ *negState = ACCEPT_INCOMPLETE;
+ ret = GSS_S_CONTINUE_NEEDED;
+ }
+ return ret;
+ }
+
+ if (ret == GSS_S_CONTINUE_NEEDED)
+ return ret;
+
+ if (*send_token != INIT_TOKEN_SEND) {
+ *send_token = ERROR_TOKEN_SEND;
+ *negState = REJECT;
+ return ret;
+ }
+
+ /*
+ * Since this is the first token, we can fall back to later mechanisms
+ * in the list. Since the mechanism list is expected to be short, we
+ * can do this with recursion. If all mechanisms produce errors, the
+ * caller should get the error from the first mech in the list.
+ */
+ gssalloc_free(sc->mech_set->elements->elements);
+ memmove(sc->mech_set->elements, sc->mech_set->elements + 1,
+ --sc->mech_set->count * sizeof(*sc->mech_set->elements));
+ if (sc->mech_set->count == 0)
+ goto fail;
+ gss_release_buffer(&tmpmin, &sc->DER_mechTypes);
+ if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0)
+ goto fail;
+ tmpret = init_ctx_call_init(&tmpmin, sc, spcred, target_name,
+ req_flags, time_req, mechtok_in,
+ actual_mech, mechtok_out, ret_flags,
+ time_rec, negState, send_token);
+ if (HARD_ERROR(tmpret))
+ goto fail;
+ *minor_status = tmpmin;
+ return tmpret;
+
+fail:
+ /* Don't output token on error from first call. */
+ *send_token = NO_TOKEN_SEND;
+ *negState = REJECT;
+ return ret;
+}
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_init_sec_context(
+ OM_uint32 *minor_status,
+ gss_cred_id_t claimant_cred_handle,
+ gss_ctx_id_t *context_handle,
+ gss_name_t target_name,
+ gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ gss_channel_bindings_t input_chan_bindings,
+ gss_buffer_t input_token,
+ gss_OID *actual_mech,
+ gss_buffer_t output_token,
+ OM_uint32 *ret_flags,
+ OM_uint32 *time_rec)
+{
+ send_token_flag send_token = NO_TOKEN_SEND;
+ OM_uint32 tmpmin, ret, negState;
+ gss_buffer_t mechtok_in, mechListMIC_in, mechListMIC_out;
+ gss_buffer_desc mechtok_out = GSS_C_EMPTY_BUFFER;
+ spnego_gss_cred_id_t spcred = NULL;
+ spnego_gss_ctx_id_t spnego_ctx = NULL;
+
+ dsyslog("Entering init_sec_context\n");
+
+ mechtok_in = mechListMIC_out = mechListMIC_in = GSS_C_NO_BUFFER;
+ negState = REJECT;
+
+ /*
+ * This function works in three steps:
+ *
+ * 1. Perform mechanism negotiation.
+ * 2. Invoke the negotiated or optimistic mech's gss_init_sec_context
+ * function and examine the results.
+ * 3. Process or generate MICs if necessary.
+ *
+ * The three steps share responsibility for determining when the
+ * exchange is complete. If the selected mech completed in a previous
+ * call and no MIC exchange is expected, then step 1 will decide. If
+ * the selected mech completes in this call and no MIC exchange is
+ * expected, then step 2 will decide. If a MIC exchange is expected,
+ * then step 3 will decide. If an error occurs in any step, the
+ * exchange will be aborted, possibly with an error token.
+ *
+ * negState determines the state of the negotiation, and is
+ * communicated to the acceptor if a continuing token is sent.
+ * send_token is used to indicate what type of token, if any, should be
+ * generated.
+ */
+
+ /* Validate arguments. */
+ if (minor_status != NULL)
+ *minor_status = 0;
+ if (output_token != GSS_C_NO_BUFFER) {
+ output_token->length = 0;
+ output_token->value = NULL;
+ }
+ if (minor_status == NULL ||
+ output_token == GSS_C_NO_BUFFER ||
+ context_handle == NULL)
+ return GSS_S_CALL_INACCESSIBLE_WRITE;
+
+ if (actual_mech != NULL)
+ *actual_mech = GSS_C_NO_OID;
+
+ /* Step 1: perform mechanism negotiation. */
+ spcred = (spnego_gss_cred_id_t)claimant_cred_handle;
+ spnego_ctx = (spnego_gss_ctx_id_t)*context_handle;
+ if (spnego_ctx == NULL) {
+ ret = init_ctx_new(minor_status, spcred, &send_token,
+ &spnego_ctx);
+ if (ret != GSS_S_CONTINUE_NEEDED) {
+ goto cleanup;
+ }
+ *context_handle = (gss_ctx_id_t)spnego_ctx;
+ } else {
+ ret = init_ctx_cont(minor_status, spnego_ctx,
+ input_token, &mechtok_in,
+ &mechListMIC_in, &negState, &send_token);
+ if (HARD_ERROR(ret)) {
+ goto cleanup;
+ }
+ }
+
+ /* Step 2: invoke the selected or optimistic mechanism's
+ * gss_init_sec_context function, if it didn't complete previously. */
+ if (!spnego_ctx->mech_complete) {
+ ret = init_ctx_call_init(
+ minor_status, spnego_ctx, spcred,
+ target_name, req_flags,
+ time_req, mechtok_in,
+ actual_mech, &mechtok_out,
+ ret_flags, time_rec,
+ &negState, &send_token);
+
+ /* Give the mechanism a chance to force a mechlistMIC. */
+ if (!HARD_ERROR(ret) && mech_requires_mechlistMIC(spnego_ctx))
+ spnego_ctx->mic_reqd = 1;
+ }
+
+ /* Step 3: process or generate the MIC, if the negotiated mech is
+ * complete and supports MICs. */
+ if (!HARD_ERROR(ret) && spnego_ctx->mech_complete &&
+ (spnego_ctx->ctx_flags & GSS_C_INTEG_FLAG)) {
+
+ ret = handle_mic(minor_status,
+ mechListMIC_in,
+ (mechtok_out.length != 0),
+ spnego_ctx, &mechListMIC_out,
+ &negState, &send_token);
+ }
+cleanup:
+ if (send_token == INIT_TOKEN_SEND) {
+ if (make_spnego_tokenInit_msg(spnego_ctx,
+ 0,
+ mechListMIC_out,
+ req_flags,
+ &mechtok_out, send_token,
+ output_token) < 0) {
+ ret = GSS_S_FAILURE;
+ }
+ } else if (send_token != NO_TOKEN_SEND) {
+ if (make_spnego_tokenTarg_msg(negState, GSS_C_NO_OID,
+ &mechtok_out, mechListMIC_out,
+ send_token,
+ output_token) < 0) {
+ ret = GSS_S_FAILURE;
+ }
+ }
+ gss_release_buffer(&tmpmin, &mechtok_out);
+ if (ret == GSS_S_COMPLETE) {
+ spnego_ctx->opened = 1;
+ if (actual_mech != NULL)
+ *actual_mech = spnego_ctx->actual_mech;
+ if (ret_flags != NULL)
+ *ret_flags = spnego_ctx->ctx_flags;
+ } else if (ret != GSS_S_CONTINUE_NEEDED) {
+ if (spnego_ctx != NULL) {
+ gss_delete_sec_context(&tmpmin,
+ &spnego_ctx->ctx_handle,
+ GSS_C_NO_BUFFER);
+ release_spnego_ctx(&spnego_ctx);
+ }
+ *context_handle = GSS_C_NO_CONTEXT;
+ }
+ if (mechtok_in != GSS_C_NO_BUFFER) {
+ gss_release_buffer(&tmpmin, mechtok_in);
+ free(mechtok_in);
+ }
+ if (mechListMIC_in != GSS_C_NO_BUFFER) {
+ gss_release_buffer(&tmpmin, mechListMIC_in);
+ free(mechListMIC_in);
+ }
+ if (mechListMIC_out != GSS_C_NO_BUFFER) {
+ gss_release_buffer(&tmpmin, mechListMIC_out);
+ free(mechListMIC_out);
+ }
+ return ret;
+} /* init_sec_context */
+
+/* We don't want to import KRB5 headers here */
+static const gss_OID_desc gss_mech_krb5_oid =
+ { 9, "\052\206\110\206\367\022\001\002\002" };
+static const gss_OID_desc gss_mech_krb5_wrong_oid =
+ { 9, "\052\206\110\202\367\022\001\002\002" };
+
+/*
+ * verify that the input token length is not 0. If it is, just return.
+ * If the token length is greater than 0, der encode as a sequence
+ * and place in buf_out, advancing buf_out.
+ */
+
+static int
+put_neg_hints(unsigned char **buf_out, gss_buffer_t input_token,
+ unsigned int buflen)
+{
+ int ret;
+
+ /* if token length is 0, we do not want to send */
+ if (input_token->length == 0)
+ return (0);
+
+ if (input_token->length > buflen)
+ return (-1);
+
+ *(*buf_out)++ = SEQUENCE;
+ if ((ret = gssint_put_der_length(input_token->length, buf_out,
+ input_token->length)))
+ return (ret);
+ TWRITE_STR(*buf_out, input_token->value, input_token->length);
+ return (0);
+}
+
+/*
+ * NegHints ::= SEQUENCE {
+ * hintName [0] GeneralString OPTIONAL,
+ * hintAddress [1] OCTET STRING OPTIONAL
+ * }
+ */
+
+#define HOST_PREFIX "host@"
+#define HOST_PREFIX_LEN (sizeof(HOST_PREFIX) - 1)
+
+/* Encode the dummy hintname string (as specified in [MS-SPNG]) into a
+ * DER-encoded [0] tagged GeneralString, and place the result in *outbuf. */
+static int
+make_NegHints(OM_uint32 *minor_status, gss_buffer_t *outbuf)
+{
+ OM_uint32 major_status;
+ unsigned int tlen = 0;
+ unsigned int hintNameSize = 0;
+ unsigned char *ptr;
+ unsigned char *t;
+ const char *hintname = "not_defined_in_RFC4178@please_ignore";
+ const size_t hintname_len = strlen(hintname);
+
+ *outbuf = GSS_C_NO_BUFFER;
+ major_status = GSS_S_FAILURE;
+
+ /* Length of DER encoded GeneralString */
+ tlen = 1 + gssint_der_length_size(hintname_len) + hintname_len;
+ hintNameSize = tlen;
+
+ /* Length of DER encoded hintName */
+ tlen += 1 + gssint_der_length_size(hintNameSize);
+
+ t = gssalloc_malloc(tlen);
+ if (t == NULL) {
+ *minor_status = ENOMEM;
+ goto errout;
+ }
+
+ ptr = t;
+
+ *ptr++ = CONTEXT | 0x00; /* hintName identifier */
+ if (gssint_put_der_length(hintNameSize,
+ &ptr, tlen - (int)(ptr-t)))
+ goto errout;
+
+ *ptr++ = GENERAL_STRING;
+ if (gssint_put_der_length(hintname_len, &ptr, tlen - (int)(ptr-t)))
+ goto errout;
+
+ memcpy(ptr, hintname, hintname_len);
+ ptr += hintname_len;
+
+ *outbuf = (gss_buffer_t)malloc(sizeof(gss_buffer_desc));
+ if (*outbuf == NULL) {
+ *minor_status = ENOMEM;
+ goto errout;
+ }
+ (*outbuf)->value = (void *)t;
+ (*outbuf)->length = ptr - t;
+
+ t = NULL; /* don't free */
+
+ *minor_status = 0;
+ major_status = GSS_S_COMPLETE;
+
+errout:
+ if (t != NULL) {
+ free(t);
+ }
+
+ return (major_status);
+}
+
+/*
+ * Create a new SPNEGO context handle for the initial call to
+ * spnego_gss_accept_sec_context() when the request is empty. For empty
+ * requests, we implement the Microsoft NegHints extension to SPNEGO for
+ * compatibility with some versions of Samba. See:
+ * http://msdn.microsoft.com/en-us/library/cc247039(PROT.10).aspx
+ */
+static OM_uint32
+acc_ctx_hints(OM_uint32 *minor_status,
+ spnego_gss_cred_id_t spcred,
+ gss_buffer_t *mechListMIC,
+ OM_uint32 *negState,
+ send_token_flag *return_token,
+ spnego_gss_ctx_id_t *sc_out)
+{
+ OM_uint32 tmpmin, ret;
+ gss_OID_set supported_mechSet;
+ spnego_gss_ctx_id_t sc = NULL;
+
+ *mechListMIC = GSS_C_NO_BUFFER;
+ supported_mechSet = GSS_C_NO_OID_SET;
+ *return_token = NO_TOKEN_SEND;
+ *negState = REJECT;
+ *minor_status = 0;
+ *sc_out = NULL;
+
+ ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT,
+ &supported_mechSet);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+
+ ret = make_NegHints(minor_status, mechListMIC);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+
+ sc = create_spnego_ctx(0);
+ if (sc == NULL) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+ if (put_mech_set(supported_mechSet, &sc->DER_mechTypes) < 0) {
+ ret = GSS_S_FAILURE;
+ goto cleanup;
+ }
+ sc->internal_mech = GSS_C_NO_OID;
+
+ *negState = ACCEPT_INCOMPLETE;
+ *return_token = INIT_TOKEN_SEND;
+ sc->firstpass = 1;
+ *sc_out = sc;
+ sc = NULL;
+ ret = GSS_S_COMPLETE;
+
+cleanup:
+ release_spnego_ctx(&sc);
+ gss_release_oid_set(&tmpmin, &supported_mechSet);
+
+ return ret;
+}
+
+/*
+ * Create a new SPNEGO context handle for the initial call to
+ * spnego_gss_accept_sec_context(). Set negState to REJECT if the token is
+ * defective, else ACCEPT_INCOMPLETE or REQUEST_MIC, depending on whether
+ * the initiator's preferred mechanism is supported.
+ */
+static OM_uint32
+acc_ctx_new(OM_uint32 *minor_status,
+ gss_buffer_t buf,
+ spnego_gss_cred_id_t spcred,
+ gss_buffer_t *mechToken,
+ gss_buffer_t *mechListMIC,
+ OM_uint32 *negState,
+ send_token_flag *return_token,
+ spnego_gss_ctx_id_t *sc_out)
+{
+ OM_uint32 tmpmin, ret, req_flags;
+ gss_OID_set supported_mechSet, mechTypes;
+ gss_buffer_desc der_mechTypes;
+ gss_OID mech_wanted;
+ spnego_gss_ctx_id_t sc = NULL;
+
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ der_mechTypes.length = 0;
+ der_mechTypes.value = NULL;
+ *mechToken = *mechListMIC = GSS_C_NO_BUFFER;
+ supported_mechSet = mechTypes = GSS_C_NO_OID_SET;
+ *return_token = ERROR_TOKEN_SEND;
+ *negState = REJECT;
+ *minor_status = 0;
+
+ ret = get_negTokenInit(minor_status, buf, &der_mechTypes,
+ &mechTypes, &req_flags,
+ mechToken, mechListMIC);
+ if (ret != GSS_S_COMPLETE) {
+ goto cleanup;
+ }
+ ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT,
+ &supported_mechSet);
+ if (ret != GSS_S_COMPLETE) {
+ *return_token = NO_TOKEN_SEND;
+ goto cleanup;
+ }
+ /*
+ * Select the best match between the list of mechs
+ * that the initiator requested and the list that
+ * the acceptor will support.
+ */
+ mech_wanted = negotiate_mech(supported_mechSet, mechTypes, negState);
+ if (*negState == REJECT) {
+ ret = GSS_S_BAD_MECH;
+ goto cleanup;
+ }
+ sc = create_spnego_ctx(0);
+ if (sc == NULL) {
+ ret = GSS_S_FAILURE;
+ *return_token = NO_TOKEN_SEND;
+ goto cleanup;
+ }
+ sc->mech_set = mechTypes;
+ mechTypes = GSS_C_NO_OID_SET;
+ sc->internal_mech = mech_wanted;
+ sc->DER_mechTypes = der_mechTypes;
+ der_mechTypes.length = 0;
+ der_mechTypes.value = NULL;
+
+ if (*negState == REQUEST_MIC)
+ sc->mic_reqd = 1;
+
+ *return_token = INIT_TOKEN_SEND;
+ sc->firstpass = 1;
+ *sc_out = sc;
+ ret = GSS_S_COMPLETE;
+cleanup:
+ gss_release_oid_set(&tmpmin, &mechTypes);
+ gss_release_oid_set(&tmpmin, &supported_mechSet);
+ if (der_mechTypes.length != 0)
+ gss_release_buffer(&tmpmin, &der_mechTypes);
+
+ return ret;
+}
+
+static OM_uint32
+acc_ctx_cont(OM_uint32 *minstat,
+ gss_buffer_t buf,
+ spnego_gss_ctx_id_t sc,
+ gss_buffer_t *responseToken,
+ gss_buffer_t *mechListMIC,
+ OM_uint32 *negState,
+ send_token_flag *return_token)
+{
+ OM_uint32 ret, tmpmin;
+ gss_OID supportedMech;
+ unsigned int len;
+ unsigned char *ptr, *bufstart;
+
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ *negState = REJECT;
+ *minstat = 0;
+ supportedMech = GSS_C_NO_OID;
+ *return_token = ERROR_TOKEN_SEND;
+ *responseToken = *mechListMIC = GSS_C_NO_BUFFER;
+
+ ptr = bufstart = buf->value;
+#define REMAIN (buf->length - (ptr - bufstart))
+ if (REMAIN == 0 || REMAIN > INT_MAX)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ /*
+ * Attempt to work with old Sun SPNEGO.
+ */
+ if (*ptr == HEADER_ID) {
+ ret = g_verify_token_header(gss_mech_spnego,
+ &len, &ptr, 0, REMAIN);
+ if (ret) {
+ *minstat = ret;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ }
+ if (*ptr != (CONTEXT | 0x01)) {
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ ret = get_negTokenResp(minstat, ptr, REMAIN,
+ negState, &supportedMech,
+ responseToken, mechListMIC);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+
+ if (*responseToken == GSS_C_NO_BUFFER &&
+ *mechListMIC == GSS_C_NO_BUFFER) {
+
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto cleanup;
+ }
+ if (supportedMech != GSS_C_NO_OID) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto cleanup;
+ }
+ sc->firstpass = 0;
+ *negState = ACCEPT_INCOMPLETE;
+ *return_token = CONT_TOKEN_SEND;
+cleanup:
+ if (supportedMech != GSS_C_NO_OID) {
+ generic_gss_release_oid(&tmpmin, &supportedMech);
+ }
+ return ret;
+#undef REMAIN
+}
+
+/*
+ * Verify that mech OID is either exactly the same as the negotiated
+ * mech OID, or is a mech OID supported by the negotiated mech. MS
+ * implementations can list a most preferred mech using an incorrect
+ * krb5 OID while emitting a krb5 initiator mech token having the
+ * correct krb5 mech OID.
+ */
+static OM_uint32
+acc_ctx_vfy_oid(OM_uint32 *minor_status,
+ spnego_gss_ctx_id_t sc, gss_OID mechoid,
+ OM_uint32 *negState, send_token_flag *tokflag)
+{
+ OM_uint32 ret, tmpmin;
+ gss_mechanism mech = NULL;
+ gss_OID_set mech_set = GSS_C_NO_OID_SET;
+ int present = 0;
+
+ if (g_OID_equal(sc->internal_mech, mechoid))
+ return GSS_S_COMPLETE;
+
+ mech = gssint_get_mechanism(sc->internal_mech);
+ if (mech == NULL || mech->gss_indicate_mechs == NULL) {
+ *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
+ map_errcode(minor_status);
+ *negState = REJECT;
+ *tokflag = ERROR_TOKEN_SEND;
+ return GSS_S_BAD_MECH;
+ }
+ ret = mech->gss_indicate_mechs(minor_status, &mech_set);
+ if (ret != GSS_S_COMPLETE) {
+ *tokflag = NO_TOKEN_SEND;
+ map_error(minor_status, mech);
+ goto cleanup;
+ }
+ ret = gss_test_oid_set_member(minor_status, mechoid,
+ mech_set, &present);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+ if (!present) {
+ *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED;
+ map_errcode(minor_status);
+ *negState = REJECT;
+ *tokflag = ERROR_TOKEN_SEND;
+ ret = GSS_S_BAD_MECH;
+ }
+cleanup:
+ gss_release_oid_set(&tmpmin, &mech_set);
+ return ret;
+}
+#ifndef LEAN_CLIENT
+/*
+ * Wrap call to gss_accept_sec_context() and update state
+ * accordingly.
+ */
+static OM_uint32
+acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
+ spnego_gss_cred_id_t spcred, gss_buffer_t mechtok_in,
+ gss_OID *mech_type, gss_buffer_t mechtok_out,
+ OM_uint32 *ret_flags, OM_uint32 *time_rec,
+ gss_cred_id_t *delegated_cred_handle,
+ OM_uint32 *negState, send_token_flag *tokflag)
+{
+ OM_uint32 ret;
+ gss_OID_desc mechoid;
+ gss_cred_id_t mcred;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT) {
+ /*
+ * mechoid is an alias; don't free it.
+ */
+ ret = gssint_get_mech_type(&mechoid, mechtok_in);
+ if (ret != GSS_S_COMPLETE) {
+ *tokflag = NO_TOKEN_SEND;
+ return ret;
+ }
+ ret = acc_ctx_vfy_oid(minor_status, sc, &mechoid,
+ negState, tokflag);
+ if (ret != GSS_S_COMPLETE)
+ return ret;
+ }
+
+ mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
+ ret = gss_accept_sec_context(minor_status,
+ &sc->ctx_handle,
+ mcred,
+ mechtok_in,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &sc->internal_name,
+ mech_type,
+ mechtok_out,
+ &sc->ctx_flags,
+ time_rec,
+ delegated_cred_handle);
+ if (ret == GSS_S_COMPLETE) {
+#ifdef MS_BUG_TEST
+ /*
+ * Force MIC to be not required even if we previously
+ * requested a MIC.
+ */
+ char *envstr = getenv("MS_FORCE_NO_MIC");
+
+ if (envstr != NULL && strcmp(envstr, "1") == 0 &&
+ !(sc->ctx_flags & GSS_C_MUTUAL_FLAG) &&
+ sc->mic_reqd) {
+
+ sc->mic_reqd = 0;
+ }
+#endif
+ sc->mech_complete = 1;
+ if (ret_flags != NULL)
+ *ret_flags = sc->ctx_flags;
+
+ if (!sc->mic_reqd ||
+ !(sc->ctx_flags & GSS_C_INTEG_FLAG)) {
+ /* No MIC exchange required, so we're done. */
+ *negState = ACCEPT_COMPLETE;
+ ret = GSS_S_COMPLETE;
+ } else {
+ /* handle_mic will decide if we're done. */
+ ret = GSS_S_CONTINUE_NEEDED;
+ }
+ } else if (ret != GSS_S_CONTINUE_NEEDED) {
+ *negState = REJECT;
+ *tokflag = ERROR_TOKEN_SEND;
+ }
+ return ret;
+}
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_accept_sec_context(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_cred_id_t verifier_cred_handle,
+ gss_buffer_t input_token,
+ gss_channel_bindings_t input_chan_bindings,
+ gss_name_t *src_name,
+ gss_OID *mech_type,
+ gss_buffer_t output_token,
+ OM_uint32 *ret_flags,
+ OM_uint32 *time_rec,
+ gss_cred_id_t *delegated_cred_handle)
+{
+ OM_uint32 ret, tmpmin, negState;
+ send_token_flag return_token;
+ gss_buffer_t mechtok_in, mic_in, mic_out;
+ gss_buffer_desc mechtok_out = GSS_C_EMPTY_BUFFER;
+ spnego_gss_ctx_id_t sc = NULL;
+ spnego_gss_cred_id_t spcred = NULL;
+ int sendTokenInit = 0, tmpret;
+
+ mechtok_in = mic_in = mic_out = GSS_C_NO_BUFFER;
+
+ /*
+ * This function works in three steps:
+ *
+ * 1. Perform mechanism negotiation.
+ * 2. Invoke the negotiated mech's gss_accept_sec_context function
+ * and examine the results.
+ * 3. Process or generate MICs if necessary.
+ *
+ * Step one determines whether the negotiation requires a MIC exchange,
+ * while steps two and three share responsibility for determining when
+ * the exchange is complete. If the selected mech completes in this
+ * call and no MIC exchange is expected, then step 2 will decide. If a
+ * MIC exchange is expected, then step 3 will decide. If an error
+ * occurs in any step, the exchange will be aborted, possibly with an
+ * error token.
+ *
+ * negState determines the state of the negotiation, and is
+ * communicated to the acceptor if a continuing token is sent.
+ * return_token is used to indicate what type of token, if any, should
+ * be generated.
+ */
+
+ /* Validate arguments. */
+ if (minor_status != NULL)
+ *minor_status = 0;
+ if (output_token != GSS_C_NO_BUFFER) {
+ output_token->length = 0;
+ output_token->value = NULL;
+ }
+ if (src_name != NULL)
+ *src_name = GSS_C_NO_NAME;
+ if (mech_type != NULL)
+ *mech_type = GSS_C_NO_OID;
+ if (time_rec != NULL)
+ *time_rec = 0;
+ if (ret_flags != NULL)
+ *ret_flags = 0;
+ if (delegated_cred_handle != NULL)
+ *delegated_cred_handle = GSS_C_NO_CREDENTIAL;
+
+ if (minor_status == NULL ||
+ output_token == GSS_C_NO_BUFFER ||
+ context_handle == NULL)
+ return GSS_S_CALL_INACCESSIBLE_WRITE;
+
+ if (input_token == GSS_C_NO_BUFFER)
+ return GSS_S_CALL_INACCESSIBLE_READ;
+
+ /* Step 1: Perform mechanism negotiation. */
+ sc = (spnego_gss_ctx_id_t)*context_handle;
+ spcred = (spnego_gss_cred_id_t)verifier_cred_handle;
+ if (sc == NULL && input_token->length == 0) {
+ /* Process a request for NegHints. */
+ ret = acc_ctx_hints(minor_status, spcred, &mic_out, &negState,
+ &return_token, &sc);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+ *context_handle = (gss_ctx_id_t)sc;
+ sendTokenInit = 1;
+ ret = GSS_S_CONTINUE_NEEDED;
+ } else if (sc == NULL || sc->internal_mech == GSS_C_NO_OID) {
+ if (sc != NULL) {
+ /* Discard the context from the NegHints request. */
+ release_spnego_ctx(&sc);
+ *context_handle = GSS_C_NO_CONTEXT;
+ }
+ /* Process an initial token; can set negState to
+ * REQUEST_MIC. */
+ ret = acc_ctx_new(minor_status, input_token, spcred,
+ &mechtok_in, &mic_in, &negState,
+ &return_token, &sc);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+ *context_handle = (gss_ctx_id_t)sc;
+ ret = GSS_S_CONTINUE_NEEDED;
+ } else {
+ /* Process a response token. Can set negState to
+ * ACCEPT_INCOMPLETE. */
+ ret = acc_ctx_cont(minor_status, input_token, sc, &mechtok_in,
+ &mic_in, &negState, &return_token);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+ ret = GSS_S_CONTINUE_NEEDED;
+ }
+
+ /* Step 2: invoke the negotiated mechanism's gss_accept_sec_context
+ * function. */
+ /*
+ * Handle mechtok_in and mic_in only if they are
+ * present in input_token. If neither is present, whether
+ * this is an error depends on whether this is the first
+ * round-trip. RET is set to a default value according to
+ * whether it is the first round-trip.
+ */
+ if (negState != REQUEST_MIC && mechtok_in != GSS_C_NO_BUFFER) {
+ ret = acc_ctx_call_acc(minor_status, sc, spcred,
+ mechtok_in, mech_type, &mechtok_out,
+ ret_flags, time_rec,
+ delegated_cred_handle,
+ &negState, &return_token);
+ }
+
+ /* Step 3: process or generate the MIC, if the negotiated mech is
+ * complete and supports MICs. */
+ if (!HARD_ERROR(ret) && sc->mech_complete &&
+ (sc->ctx_flags & GSS_C_INTEG_FLAG)) {
+
+ ret = handle_mic(minor_status, mic_in,
+ (mechtok_out.length != 0),
+ sc, &mic_out,
+ &negState, &return_token);
+ }
+cleanup:
+ if (return_token == INIT_TOKEN_SEND && sendTokenInit) {
+ assert(sc != NULL);
+ tmpret = make_spnego_tokenInit_msg(sc, 1, mic_out, 0,
+ GSS_C_NO_BUFFER,
+ return_token, output_token);
+ if (tmpret < 0)
+ ret = GSS_S_FAILURE;
+ } else if (return_token != NO_TOKEN_SEND &&
+ return_token != CHECK_MIC) {
+ tmpret = make_spnego_tokenTarg_msg(negState,
+ sc ? sc->internal_mech :
+ GSS_C_NO_OID,
+ &mechtok_out, mic_out,
+ return_token,
+ output_token);
+ if (tmpret < 0)
+ ret = GSS_S_FAILURE;
+ }
+ if (ret == GSS_S_COMPLETE) {
+ sc->opened = 1;
+ if (sc->internal_name != GSS_C_NO_NAME &&
+ src_name != NULL) {
+ *src_name = sc->internal_name;
+ sc->internal_name = GSS_C_NO_NAME;
+ }
+ } else if (ret != GSS_S_CONTINUE_NEEDED) {
+ if (sc != NULL) {
+ gss_delete_sec_context(&tmpmin, &sc->ctx_handle,
+ GSS_C_NO_BUFFER);
+ release_spnego_ctx(&sc);
+ }
+ *context_handle = GSS_C_NO_CONTEXT;
+ }
+ gss_release_buffer(&tmpmin, &mechtok_out);
+ if (mechtok_in != GSS_C_NO_BUFFER) {
+ gss_release_buffer(&tmpmin, mechtok_in);
+ free(mechtok_in);
+ }
+ if (mic_in != GSS_C_NO_BUFFER) {
+ gss_release_buffer(&tmpmin, mic_in);
+ free(mic_in);
+ }
+ if (mic_out != GSS_C_NO_BUFFER) {
+ gss_release_buffer(&tmpmin, mic_out);
+ free(mic_out);
+ }
+ return ret;
+}
+#endif /* LEAN_CLIENT */
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_display_status(
+ OM_uint32 *minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ gss_OID mech_type,
+ OM_uint32 *message_context,
+ gss_buffer_t status_string)
+{
+ OM_uint32 maj = GSS_S_COMPLETE;
+ int ret;
+
+ dsyslog("Entering display_status\n");
+
+ *message_context = 0;
+ switch (status_value) {
+ case ERR_SPNEGO_NO_MECHS_AVAILABLE:
+ /* CSTYLED */
+ *status_string = make_err_msg(_("SPNEGO cannot find "
+ "mechanisms to negotiate"));
+ break;
+ case ERR_SPNEGO_NO_CREDS_ACQUIRED:
+ /* CSTYLED */
+ *status_string = make_err_msg(_("SPNEGO failed to acquire "
+ "creds"));
+ break;
+ case ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR:
+ /* CSTYLED */
+ *status_string = make_err_msg(_("SPNEGO acceptor did not "
+ "select a mechanism"));
+ break;
+ case ERR_SPNEGO_NEGOTIATION_FAILED:
+ /* CSTYLED */
+ *status_string = make_err_msg(_("SPNEGO failed to negotiate a "
+ "mechanism"));
+ break;
+ case ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR:
+ /* CSTYLED */
+ *status_string = make_err_msg(_("SPNEGO acceptor did not "
+ "return a valid token"));
+ break;
+ default:
+ /* Not one of our minor codes; might be from a mech. Call back
+ * to gss_display_status, but first check for recursion. */
+ if (k5_getspecific(K5_KEY_GSS_SPNEGO_STATUS) != NULL) {
+ /* Perhaps we returned a com_err code like ENOMEM. */
+ const char *err = error_message(status_value);
+ *status_string = make_err_msg(err);
+ break;
+ }
+ /* Set a non-null pointer value; doesn't matter which one. */
+ ret = k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, &ret);
+ if (ret != 0) {
+ *minor_status = ret;
+ maj = GSS_S_FAILURE;
+ break;
+ }
+ maj = gss_display_status(minor_status, status_value,
+ status_type, mech_type,
+ message_context, status_string);
+ /* This is unlikely to fail; not much we can do if it does. */
+ (void)k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, NULL);
+ break;
+ }
+
+ dsyslog("Leaving display_status\n");
+ return maj;
+}
+
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_import_name(
+ OM_uint32 *minor_status,
+ gss_buffer_t input_name_buffer,
+ gss_OID input_name_type,
+ gss_name_t *output_name)
+{
+ OM_uint32 status;
+
+ dsyslog("Entering import_name\n");
+
+ status = gss_import_name(minor_status, input_name_buffer,
+ input_name_type, output_name);
+
+ dsyslog("Leaving import_name\n");
+ return (status);
+}
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_release_name(
+ OM_uint32 *minor_status,
+ gss_name_t *input_name)
+{
+ OM_uint32 status;
+
+ dsyslog("Entering release_name\n");
+
+ status = gss_release_name(minor_status, input_name);
+
+ dsyslog("Leaving release_name\n");
+ return (status);
+}
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_duplicate_name(
+ OM_uint32 *minor_status,
+ const gss_name_t input_name,
+ gss_name_t *output_name)
+{
+ OM_uint32 status;
+
+ dsyslog("Entering duplicate_name\n");
+
+ status = gss_duplicate_name(minor_status, input_name, output_name);
+
+ dsyslog("Leaving duplicate_name\n");
+ return (status);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_cred(
+ OM_uint32 *minor_status,
+ gss_cred_id_t cred_handle,
+ gss_name_t *name,
+ OM_uint32 *lifetime,
+ int *cred_usage,
+ gss_OID_set *mechanisms)
+{
+ OM_uint32 status;
+ spnego_gss_cred_id_t spcred = NULL;
+ gss_cred_id_t creds = GSS_C_NO_CREDENTIAL;
+ OM_uint32 tmp_minor_status;
+ OM_uint32 initiator_lifetime, acceptor_lifetime;
+
+ dsyslog("Entering inquire_cred\n");
+
+ /*
+ * To avoid infinite recursion, if GSS_C_NO_CREDENTIAL is
+ * supplied we call gss_inquire_cred_by_mech() on the
+ * first non-SPNEGO mechanism.
+ */
+ spcred = (spnego_gss_cred_id_t)cred_handle;
+ if (spcred == NULL) {
+ status = get_available_mechs(minor_status,
+ GSS_C_NO_NAME,
+ GSS_C_BOTH,
+ GSS_C_NO_CRED_STORE,
+ &creds,
+ mechanisms, NULL);
+ if (status != GSS_S_COMPLETE) {
+ dsyslog("Leaving inquire_cred\n");
+ return (status);
+ }
+
+ if ((*mechanisms)->count == 0) {
+ gss_release_cred(&tmp_minor_status, &creds);
+ gss_release_oid_set(&tmp_minor_status, mechanisms);
+ dsyslog("Leaving inquire_cred\n");
+ return (GSS_S_DEFECTIVE_CREDENTIAL);
+ }
+
+ assert((*mechanisms)->elements != NULL);
+
+ status = gss_inquire_cred_by_mech(minor_status,
+ creds,
+ &(*mechanisms)->elements[0],
+ name,
+ &initiator_lifetime,
+ &acceptor_lifetime,
+ cred_usage);
+ if (status != GSS_S_COMPLETE) {
+ gss_release_cred(&tmp_minor_status, &creds);
+ dsyslog("Leaving inquire_cred\n");
+ return (status);
+ }
+
+ if (lifetime != NULL)
+ *lifetime = (*cred_usage == GSS_C_ACCEPT) ?
+ acceptor_lifetime : initiator_lifetime;
+
+ gss_release_cred(&tmp_minor_status, &creds);
+ } else {
+ status = gss_inquire_cred(minor_status, spcred->mcred,
+ name, lifetime,
+ cred_usage, mechanisms);
+ }
+
+ dsyslog("Leaving inquire_cred\n");
+
+ return (status);
+}
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_compare_name(
+ OM_uint32 *minor_status,
+ const gss_name_t name1,
+ const gss_name_t name2,
+ int *name_equal)
+{
+ OM_uint32 status = GSS_S_COMPLETE;
+ dsyslog("Entering compare_name\n");
+
+ status = gss_compare_name(minor_status, name1, name2, name_equal);
+
+ dsyslog("Leaving compare_name\n");
+ return (status);
+}
+
+/*ARGSUSED*/
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_display_name(
+ OM_uint32 *minor_status,
+ gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID *output_name_type)
+{
+ OM_uint32 status = GSS_S_COMPLETE;
+ dsyslog("Entering display_name\n");
+
+ status = gss_display_name(minor_status, input_name,
+ output_name_buffer, output_name_type);
+
+ dsyslog("Leaving display_name\n");
+ return (status);
+}
+
+
+/*ARGSUSED*/
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_names_for_mech(
+ OM_uint32 *minor_status,
+ gss_OID mechanism,
+ gss_OID_set *name_types)
+{
+ OM_uint32 major, minor;
+
+ dsyslog("Entering inquire_names_for_mech\n");
+ /*
+ * We only know how to handle our own mechanism.
+ */
+ if ((mechanism != GSS_C_NULL_OID) &&
+ !g_OID_equal(gss_mech_spnego, mechanism)) {
+ *minor_status = 0;
+ return (GSS_S_FAILURE);
+ }
+
+ major = gss_create_empty_oid_set(minor_status, name_types);
+ if (major == GSS_S_COMPLETE) {
+ /* Now add our members. */
+ if (((major = gss_add_oid_set_member(minor_status,
+ (gss_OID) GSS_C_NT_USER_NAME,
+ name_types)) == GSS_S_COMPLETE) &&
+ ((major = gss_add_oid_set_member(minor_status,
+ (gss_OID) GSS_C_NT_MACHINE_UID_NAME,
+ name_types)) == GSS_S_COMPLETE) &&
+ ((major = gss_add_oid_set_member(minor_status,
+ (gss_OID) GSS_C_NT_STRING_UID_NAME,
+ name_types)) == GSS_S_COMPLETE)) {
+ major = gss_add_oid_set_member(minor_status,
+ (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
+ name_types);
+ }
+
+ if (major != GSS_S_COMPLETE)
+ (void) gss_release_oid_set(&minor, name_types);
+ }
+
+ dsyslog("Leaving inquire_names_for_mech\n");
+ return (major);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_unwrap(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ gss_buffer_t input_message_buffer,
+ gss_buffer_t output_message_buffer,
+ int *conf_state,
+ gss_qop_t *qop_state)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_unwrap(minor_status,
+ sc->ctx_handle,
+ input_message_buffer,
+ output_message_buffer,
+ conf_state,
+ qop_state);
+
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_wrap(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ gss_buffer_t input_message_buffer,
+ int *conf_state,
+ gss_buffer_t output_message_buffer)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_wrap(minor_status,
+ sc->ctx_handle,
+ conf_req_flag,
+ qop_req,
+ input_message_buffer,
+ conf_state,
+ output_message_buffer);
+
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_process_context_token(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ const gss_buffer_t token_buffer)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ /* SPNEGO doesn't have its own context tokens. */
+ if (!sc->opened)
+ return (GSS_S_DEFECTIVE_TOKEN);
+
+ ret = gss_process_context_token(minor_status,
+ sc->ctx_handle,
+ token_buffer);
+
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_delete_sec_context(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_buffer_t output_token)
+{
+ OM_uint32 ret = GSS_S_COMPLETE;
+ spnego_gss_ctx_id_t *ctx =
+ (spnego_gss_ctx_id_t *)context_handle;
+
+ *minor_status = 0;
+
+ if (context_handle == NULL)
+ return (GSS_S_FAILURE);
+
+ if (*ctx == NULL)
+ return (GSS_S_COMPLETE);
+
+ (void) gss_delete_sec_context(minor_status, &(*ctx)->ctx_handle,
+ output_token);
+ (void) release_spnego_ctx(ctx);
+
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_context_time(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ OM_uint32 *time_rec)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_context_time(minor_status,
+ sc->ctx_handle,
+ time_rec);
+ return (ret);
+}
+#ifndef LEAN_CLIENT
+OM_uint32 KRB5_CALLCONV
+spnego_gss_export_sec_context(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_buffer_t interprocess_token)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = *(spnego_gss_ctx_id_t *)context_handle;
+
+ /* We don't currently support exporting partially established
+ * contexts. */
+ if (!sc->opened)
+ return GSS_S_UNAVAILABLE;
+
+ ret = gss_export_sec_context(minor_status,
+ &sc->ctx_handle,
+ interprocess_token);
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT) {
+ release_spnego_ctx(&sc);
+ *context_handle = GSS_C_NO_CONTEXT;
+ }
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_import_sec_context(
+ OM_uint32 *minor_status,
+ const gss_buffer_t interprocess_token,
+ gss_ctx_id_t *context_handle)
+{
+ OM_uint32 ret, tmpmin;
+ gss_ctx_id_t mctx;
+ spnego_gss_ctx_id_t sc;
+ int initiate, opened;
+
+ ret = gss_import_sec_context(minor_status, interprocess_token, &mctx);
+ if (ret != GSS_S_COMPLETE)
+ return ret;
+
+ ret = gss_inquire_context(&tmpmin, mctx, NULL, NULL, NULL, NULL, NULL,
+ &initiate, &opened);
+ if (ret != GSS_S_COMPLETE || !opened) {
+ /* We don't currently support importing partially established
+ * contexts. */
+ (void) gss_delete_sec_context(&tmpmin, &mctx, GSS_C_NO_BUFFER);
+ return GSS_S_FAILURE;
+ }
+
+ sc = create_spnego_ctx(initiate);
+ if (sc == NULL) {
+ (void) gss_delete_sec_context(&tmpmin, &mctx, GSS_C_NO_BUFFER);
+ return GSS_S_FAILURE;
+ }
+ sc->ctx_handle = mctx;
+ sc->opened = 1;
+ *context_handle = (gss_ctx_id_t)sc;
+ return GSS_S_COMPLETE;
+}
+#endif /* LEAN_CLIENT */
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_context(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t *src_name,
+ gss_name_t *targ_name,
+ OM_uint32 *lifetime_rec,
+ gss_OID *mech_type,
+ OM_uint32 *ctx_flags,
+ int *locally_initiated,
+ int *opened)
+{
+ OM_uint32 ret = GSS_S_COMPLETE;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (src_name != NULL)
+ *src_name = GSS_C_NO_NAME;
+ if (targ_name != NULL)
+ *targ_name = GSS_C_NO_NAME;
+ if (lifetime_rec != NULL)
+ *lifetime_rec = 0;
+ if (mech_type != NULL)
+ *mech_type = (gss_OID)gss_mech_spnego;
+ if (ctx_flags != NULL)
+ *ctx_flags = 0;
+ if (locally_initiated != NULL)
+ *locally_initiated = sc->initiate;
+ if (opened != NULL)
+ *opened = sc->opened;
+
+ if (sc->ctx_handle != GSS_C_NO_CONTEXT) {
+ ret = gss_inquire_context(minor_status, sc->ctx_handle,
+ src_name, targ_name, lifetime_rec,
+ mech_type, ctx_flags, NULL, NULL);
+ }
+
+ if (!sc->opened) {
+ /*
+ * We are still doing SPNEGO negotiation, so report SPNEGO as
+ * the OID. After negotiation is complete we will report the
+ * underlying mechanism OID.
+ */
+ if (mech_type != NULL)
+ *mech_type = (gss_OID)gss_mech_spnego;
+
+ /*
+ * Remove flags we don't support with partially-established
+ * contexts. (Change this to keep GSS_C_TRANS_FLAG if we add
+ * support for exporting partial SPNEGO contexts.)
+ */
+ if (ctx_flags != NULL) {
+ *ctx_flags &= ~GSS_C_PROT_READY_FLAG;
+ *ctx_flags &= ~GSS_C_TRANS_FLAG;
+ }
+ }
+
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_wrap_size_limit(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32 *max_input_size)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_wrap_size_limit(minor_status,
+ sc->ctx_handle,
+ conf_req_flag,
+ qop_req,
+ req_output_size,
+ max_input_size);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_get_mic(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_qop_t qop_req,
+ const gss_buffer_t message_buffer,
+ gss_buffer_t message_token)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_get_mic(minor_status,
+ sc->ctx_handle,
+ qop_req,
+ message_buffer,
+ message_token);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_verify_mic(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ const gss_buffer_t msg_buffer,
+ const gss_buffer_t token_buffer,
+ gss_qop_t *qop_state)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_verify_mic(minor_status,
+ sc->ctx_handle,
+ msg_buffer,
+ token_buffer,
+ qop_state);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_sec_context_by_oid(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ const gss_OID desired_object,
+ gss_buffer_set_t *data_set)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ /* There are no SPNEGO-specific OIDs for this function. */
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_UNAVAILABLE);
+
+ ret = gss_inquire_sec_context_by_oid(minor_status,
+ sc->ctx_handle,
+ desired_object,
+ data_set);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_cred_by_oid(
+ OM_uint32 *minor_status,
+ const gss_cred_id_t cred_handle,
+ const gss_OID desired_object,
+ gss_buffer_set_t *data_set)
+{
+ OM_uint32 ret;
+ spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle;
+ gss_cred_id_t mcred;
+ mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
+ ret = gss_inquire_cred_by_oid(minor_status,
+ mcred,
+ desired_object,
+ data_set);
+ return (ret);
+}
+
+/* This is the same OID as KRB5_NO_CI_FLAGS_X_OID. */
+#define NO_CI_FLAGS_X_OID_LENGTH 6
+#define NO_CI_FLAGS_X_OID "\x2a\x85\x70\x2b\x0d\x1d"
+static const gss_OID_desc no_ci_flags_oid[] = {
+ {NO_CI_FLAGS_X_OID_LENGTH, NO_CI_FLAGS_X_OID},
+};
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_set_cred_option(
+ OM_uint32 *minor_status,
+ gss_cred_id_t *cred_handle,
+ const gss_OID desired_object,
+ const gss_buffer_t value)
+{
+ OM_uint32 ret;
+ OM_uint32 tmp_minor_status;
+ spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)*cred_handle;
+ gss_cred_id_t mcred;
+
+ mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
+ ret = gss_set_cred_option(minor_status,
+ &mcred,
+ desired_object,
+ value);
+ if (ret == GSS_S_COMPLETE && spcred == NULL) {
+ /*
+ * If the mechanism allocated a new credential handle, then
+ * we need to wrap it up in an SPNEGO credential handle.
+ */
+
+ ret = create_spnego_cred(minor_status, mcred, &spcred);
+ if (ret != GSS_S_COMPLETE) {
+ gss_release_cred(&tmp_minor_status, &mcred);
+ return (ret);
+ }
+ *cred_handle = (gss_cred_id_t)spcred;
+ }
+
+ if (ret != GSS_S_COMPLETE)
+ return (ret);
+
+ /* Recognize KRB5_NO_CI_FLAGS_X_OID and avoid asking for integrity. */
+ if (g_OID_equal(desired_object, no_ci_flags_oid))
+ spcred->no_ask_integ = 1;
+
+ return (GSS_S_COMPLETE);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_set_sec_context_option(
+ OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ const gss_OID desired_object,
+ const gss_buffer_t value)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)*context_handle;
+
+ /* There are no SPNEGO-specific OIDs for this function, and we cannot
+ * construct an empty SPNEGO context with it. */
+ if (sc == NULL || sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_UNAVAILABLE);
+
+ ret = gss_set_sec_context_option(minor_status,
+ &sc->ctx_handle,
+ desired_object,
+ value);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_wrap_aead(OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ gss_buffer_t input_assoc_buffer,
+ gss_buffer_t input_payload_buffer,
+ int *conf_state,
+ gss_buffer_t output_message_buffer)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_wrap_aead(minor_status,
+ sc->ctx_handle,
+ conf_req_flag,
+ qop_req,
+ input_assoc_buffer,
+ input_payload_buffer,
+ conf_state,
+ output_message_buffer);
+
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_unwrap_aead(OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ gss_buffer_t input_message_buffer,
+ gss_buffer_t input_assoc_buffer,
+ gss_buffer_t output_payload_buffer,
+ int *conf_state,
+ gss_qop_t *qop_state)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_unwrap_aead(minor_status,
+ sc->ctx_handle,
+ input_message_buffer,
+ input_assoc_buffer,
+ output_payload_buffer,
+ conf_state,
+ qop_state);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_wrap_iov(OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ int *conf_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_wrap_iov(minor_status,
+ sc->ctx_handle,
+ conf_req_flag,
+ qop_req,
+ conf_state,
+ iov,
+ iov_count);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_unwrap_iov(OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int *conf_state,
+ gss_qop_t *qop_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_unwrap_iov(minor_status,
+ sc->ctx_handle,
+ conf_state,
+ qop_state,
+ iov,
+ iov_count);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_wrap_iov_length(OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ int *conf_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_wrap_iov_length(minor_status,
+ sc->ctx_handle,
+ conf_req_flag,
+ qop_req,
+ conf_state,
+ iov,
+ iov_count);
+ return (ret);
+}
+
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_complete_auth_token(
+ OM_uint32 *minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_buffer_t input_message_buffer)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_UNAVAILABLE);
+
+ ret = gss_complete_auth_token(minor_status,
+ sc->ctx_handle,
+ input_message_buffer);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_acquire_cred_impersonate_name(OM_uint32 *minor_status,
+ const gss_cred_id_t impersonator_cred_handle,
+ const gss_name_t desired_name,
+ OM_uint32 time_req,
+ gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_cred_id_t *output_cred_handle,
+ gss_OID_set *actual_mechs,
+ OM_uint32 *time_rec)
+{
+ OM_uint32 status, tmpmin;
+ gss_OID_set amechs = GSS_C_NULL_OID_SET;
+ spnego_gss_cred_id_t imp_spcred = NULL, out_spcred = NULL;
+ gss_cred_id_t imp_mcred, out_mcred = GSS_C_NO_CREDENTIAL;
+
+ dsyslog("Entering spnego_gss_acquire_cred_impersonate_name\n");
+
+ if (actual_mechs)
+ *actual_mechs = NULL;
+
+ if (time_rec)
+ *time_rec = 0;
+
+ imp_spcred = (spnego_gss_cred_id_t)impersonator_cred_handle;
+ imp_mcred = imp_spcred ? imp_spcred->mcred : GSS_C_NO_CREDENTIAL;
+ status = gss_inquire_cred(minor_status, imp_mcred, NULL, NULL,
+ NULL, &amechs);
+ if (status != GSS_S_COMPLETE)
+ return status;
+
+ status = gss_acquire_cred_impersonate_name(minor_status, imp_mcred,
+ desired_name, time_req,
+ amechs, cred_usage,
+ &out_mcred, actual_mechs,
+ time_rec);
+ if (status != GSS_S_COMPLETE)
+ goto cleanup;
+
+ status = create_spnego_cred(minor_status, out_mcred, &out_spcred);
+ if (status != GSS_S_COMPLETE)
+ goto cleanup;
+
+ out_mcred = GSS_C_NO_CREDENTIAL;
+ *output_cred_handle = (gss_cred_id_t)out_spcred;
+
+cleanup:
+ (void) gss_release_oid_set(&tmpmin, &amechs);
+ (void) gss_release_cred(&tmpmin, &out_mcred);
+
+ dsyslog("Leaving spnego_gss_acquire_cred_impersonate_name\n");
+ return (status);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_acquire_cred_with_password(OM_uint32 *minor_status,
+ const gss_name_t desired_name,
+ const gss_buffer_t password,
+ OM_uint32 time_req,
+ const gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_cred_id_t *output_cred_handle,
+ gss_OID_set *actual_mechs,
+ OM_uint32 *time_rec)
+{
+ OM_uint32 status, tmpmin;
+ gss_OID_set amechs = GSS_C_NULL_OID_SET;
+ gss_cred_id_t mcred = NULL;
+ spnego_gss_cred_id_t spcred = NULL;
+
+ dsyslog("Entering spnego_gss_acquire_cred_with_password\n");
+
+ if (actual_mechs)
+ *actual_mechs = NULL;
+
+ if (time_rec)
+ *time_rec = 0;
+
+ status = get_available_mechs(minor_status, desired_name,
+ cred_usage, GSS_C_NO_CRED_STORE,
+ NULL, &amechs, NULL);
+ if (status != GSS_S_COMPLETE)
+ goto cleanup;
+
+ status = gss_acquire_cred_with_password(minor_status, desired_name,
+ password, time_req, amechs,
+ cred_usage, &mcred,
+ actual_mechs, time_rec);
+ if (status != GSS_S_COMPLETE)
+ goto cleanup;
+
+ status = create_spnego_cred(minor_status, mcred, &spcred);
+ if (status != GSS_S_COMPLETE)
+ goto cleanup;
+
+ mcred = GSS_C_NO_CREDENTIAL;
+ *output_cred_handle = (gss_cred_id_t)spcred;
+
+cleanup:
+
+ (void) gss_release_oid_set(&tmpmin, &amechs);
+ (void) gss_release_cred(&tmpmin, &mcred);
+
+ dsyslog("Leaving spnego_gss_acquire_cred_with_password\n");
+ return (status);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_display_name_ext(OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_OID display_as_name_type,
+ gss_buffer_t display_name)
+{
+ OM_uint32 ret;
+ ret = gss_display_name_ext(minor_status,
+ name,
+ display_as_name_type,
+ display_name);
+ return (ret);
+}
+
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_name(OM_uint32 *minor_status,
+ gss_name_t name,
+ int *name_is_MN,
+ gss_OID *MN_mech,
+ gss_buffer_set_t *attrs)
+{
+ OM_uint32 ret;
+ ret = gss_inquire_name(minor_status,
+ name,
+ name_is_MN,
+ MN_mech,
+ attrs);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_get_name_attribute(OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_buffer_t attr,
+ int *authenticated,
+ int *complete,
+ gss_buffer_t value,
+ gss_buffer_t display_value,
+ int *more)
+{
+ OM_uint32 ret;
+ ret = gss_get_name_attribute(minor_status,
+ name,
+ attr,
+ authenticated,
+ complete,
+ value,
+ display_value,
+ more);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_set_name_attribute(OM_uint32 *minor_status,
+ gss_name_t name,
+ int complete,
+ gss_buffer_t attr,
+ gss_buffer_t value)
+{
+ OM_uint32 ret;
+ ret = gss_set_name_attribute(minor_status,
+ name,
+ complete,
+ attr,
+ value);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_delete_name_attribute(OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_buffer_t attr)
+{
+ OM_uint32 ret;
+ ret = gss_delete_name_attribute(minor_status,
+ name,
+ attr);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_export_name_composite(OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_buffer_t exp_composite_name)
+{
+ OM_uint32 ret;
+ ret = gss_export_name_composite(minor_status,
+ name,
+ exp_composite_name);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_map_name_to_any(OM_uint32 *minor_status,
+ gss_name_t name,
+ int authenticated,
+ gss_buffer_t type_id,
+ gss_any_t *output)
+{
+ OM_uint32 ret;
+ ret = gss_map_name_to_any(minor_status,
+ name,
+ authenticated,
+ type_id,
+ output);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_release_any_name_mapping(OM_uint32 *minor_status,
+ gss_name_t name,
+ gss_buffer_t type_id,
+ gss_any_t *input)
+{
+ OM_uint32 ret;
+ ret = gss_release_any_name_mapping(minor_status,
+ name,
+ type_id,
+ input);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_pseudo_random(OM_uint32 *minor_status,
+ gss_ctx_id_t context,
+ int prf_key,
+ const gss_buffer_t prf_in,
+ ssize_t desired_output_len,
+ gss_buffer_t prf_out)
+{
+ OM_uint32 ret;
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ ret = gss_pseudo_random(minor_status,
+ sc->ctx_handle,
+ prf_key,
+ prf_in,
+ desired_output_len,
+ prf_out);
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_set_neg_mechs(OM_uint32 *minor_status,
+ gss_cred_id_t cred_handle,
+ const gss_OID_set mech_list)
+{
+ OM_uint32 ret;
+ spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle;
+
+ /* Store mech_list in spcred for use in negotiation logic. */
+ gss_release_oid_set(minor_status, &spcred->neg_mechs);
+ ret = generic_gss_copy_oid_set(minor_status, mech_list,
+ &spcred->neg_mechs);
+ return (ret);
+}
+
+#define SPNEGO_SASL_NAME "SPNEGO"
+#define SPNEGO_SASL_NAME_LEN (sizeof(SPNEGO_SASL_NAME) - 1)
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_mech_for_saslname(OM_uint32 *minor_status,
+ const gss_buffer_t sasl_mech_name,
+ gss_OID *mech_type)
+{
+ if (sasl_mech_name->length == SPNEGO_SASL_NAME_LEN &&
+ memcmp(sasl_mech_name->value, SPNEGO_SASL_NAME,
+ SPNEGO_SASL_NAME_LEN) == 0) {
+ if (mech_type != NULL)
+ *mech_type = (gss_OID)gss_mech_spnego;
+ return (GSS_S_COMPLETE);
+ }
+
+ return (GSS_S_BAD_MECH);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_saslname_for_mech(OM_uint32 *minor_status,
+ const gss_OID desired_mech,
+ gss_buffer_t sasl_mech_name,
+ gss_buffer_t mech_name,
+ gss_buffer_t mech_description)
+{
+ *minor_status = 0;
+
+ if (!g_OID_equal(desired_mech, gss_mech_spnego))
+ return (GSS_S_BAD_MECH);
+
+ if (!g_make_string_buffer(SPNEGO_SASL_NAME, sasl_mech_name) ||
+ !g_make_string_buffer("spnego", mech_name) ||
+ !g_make_string_buffer("Simple and Protected GSS-API "
+ "Negotiation Mechanism", mech_description))
+ goto fail;
+
+ return (GSS_S_COMPLETE);
+
+fail:
+ *minor_status = ENOMEM;
+ return (GSS_S_FAILURE);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_inquire_attrs_for_mech(OM_uint32 *minor_status,
+ gss_const_OID mech,
+ gss_OID_set *mech_attrs,
+ gss_OID_set *known_mech_attrs)
+{
+ OM_uint32 major, tmpMinor;
+
+ /* known_mech_attrs is handled by mechglue */
+ *minor_status = 0;
+
+ if (mech_attrs == NULL)
+ return (GSS_S_COMPLETE);
+
+ major = gss_create_empty_oid_set(minor_status, mech_attrs);
+ if (GSS_ERROR(major))
+ goto cleanup;
+
+#define MA_SUPPORTED(ma) do { \
+ major = gss_add_oid_set_member(minor_status, \
+ (gss_OID)ma, mech_attrs); \
+ if (GSS_ERROR(major)) \
+ goto cleanup; \
+ } while (0)
+
+ MA_SUPPORTED(GSS_C_MA_MECH_NEGO);
+ MA_SUPPORTED(GSS_C_MA_ITOK_FRAMED);
+
+cleanup:
+ if (GSS_ERROR(major))
+ gss_release_oid_set(&tmpMinor, mech_attrs);
+
+ return (major);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_export_cred(OM_uint32 *minor_status,
+ gss_cred_id_t cred_handle,
+ gss_buffer_t token)
+{
+ spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle;
+
+ return (gss_export_cred(minor_status, spcred->mcred, token));
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_import_cred(OM_uint32 *minor_status,
+ gss_buffer_t token,
+ gss_cred_id_t *cred_handle)
+{
+ OM_uint32 ret;
+ spnego_gss_cred_id_t spcred;
+ gss_cred_id_t mcred;
+
+ ret = gss_import_cred(minor_status, token, &mcred);
+ if (GSS_ERROR(ret))
+ return (ret);
+
+ ret = create_spnego_cred(minor_status, mcred, &spcred);
+ if (GSS_ERROR(ret))
+ return (ret);
+
+ *cred_handle = (gss_cred_id_t)spcred;
+ return (ret);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_get_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle,
+ gss_qop_t qop_req, gss_iov_buffer_desc *iov,
+ int iov_count)
+{
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ return gss_get_mic_iov(minor_status, sc->ctx_handle, qop_req, iov,
+ iov_count);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_verify_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle,
+ gss_qop_t *qop_state, gss_iov_buffer_desc *iov,
+ int iov_count)
+{
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ return gss_verify_mic_iov(minor_status, sc->ctx_handle, qop_state, iov,
+ iov_count);
+}
+
+OM_uint32 KRB5_CALLCONV
+spnego_gss_get_mic_iov_length(OM_uint32 *minor_status,
+ gss_ctx_id_t context_handle, gss_qop_t qop_req,
+ gss_iov_buffer_desc *iov, int iov_count)
+{
+ spnego_gss_ctx_id_t sc = (spnego_gss_ctx_id_t)context_handle;
+
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT)
+ return (GSS_S_NO_CONTEXT);
+
+ return gss_get_mic_iov_length(minor_status, sc->ctx_handle, qop_req, iov,
+ iov_count);
+}
+
+/*
+ * We will release everything but the ctx_handle so that it
+ * can be passed back to init/accept context. This routine should
+ * not be called until after the ctx_handle memory is assigned to
+ * the supplied context handle from init/accept context.
+ */
+static void
+release_spnego_ctx(spnego_gss_ctx_id_t *ctx)
+{
+ spnego_gss_ctx_id_t context;
+ OM_uint32 minor_stat;
+ context = *ctx;
+
+ if (context != NULL) {
+ (void) gss_release_buffer(&minor_stat,
+ &context->DER_mechTypes);
+
+ (void) gss_release_oid_set(&minor_stat, &context->mech_set);
+
+ (void) gss_release_name(&minor_stat, &context->internal_name);
+
+ free(context);
+ *ctx = NULL;
+ }
+}
+
+/*
+ * Can't use gss_indicate_mechs by itself to get available mechs for
+ * SPNEGO because it will also return the SPNEGO mech and we do not
+ * want to consider SPNEGO as an available security mech for
+ * negotiation. For this reason, get_available_mechs will return
+ * all available, non-deprecated mechs except SPNEGO.
+ *
+ * If a ptr to a creds list is given, this function will attempt
+ * to acquire creds for the creds given and trim the list of
+ * returned mechanisms to only those for which creds are valid.
+ *
+ */
+static OM_uint32
+get_available_mechs(OM_uint32 *minor_status,
+ gss_name_t name, gss_cred_usage_t usage,
+ gss_const_key_value_set_t cred_store,
+ gss_cred_id_t *creds, gss_OID_set *rmechs, OM_uint32 *time_rec)
+{
+ unsigned int i;
+ int found = 0;
+ OM_uint32 major_status = GSS_S_COMPLETE, tmpmin;
+ gss_OID_set mechs, goodmechs;
+ gss_OID_set_desc except_attrs;
+ gss_OID_desc attr_oids[2];
+
+ attr_oids[0] = *GSS_C_MA_DEPRECATED;
+ attr_oids[1] = *GSS_C_MA_NOT_DFLT_MECH;
+ except_attrs.count = 2;
+ except_attrs.elements = attr_oids;
+ major_status = gss_indicate_mechs_by_attrs(minor_status,
+ GSS_C_NO_OID_SET,
+ &except_attrs,
+ GSS_C_NO_OID_SET, &mechs);
+
+ if (major_status != GSS_S_COMPLETE) {
+ return (major_status);
+ }
+
+ major_status = gss_create_empty_oid_set(minor_status, rmechs);
+
+ if (major_status != GSS_S_COMPLETE) {
+ (void) gss_release_oid_set(minor_status, &mechs);
+ return (major_status);
+ }
+
+ for (i = 0; i < mechs->count && major_status == GSS_S_COMPLETE; i++) {
+ if ((mechs->elements[i].length
+ != spnego_mechanism.mech_type.length) ||
+ memcmp(mechs->elements[i].elements,
+ spnego_mechanism.mech_type.elements,
+ spnego_mechanism.mech_type.length)) {
+
+ major_status = gss_add_oid_set_member(minor_status,
+ &mechs->elements[i],
+ rmechs);
+ if (major_status == GSS_S_COMPLETE)
+ found++;
+ }
+ }
+
+ /*
+ * If the caller wanted a list of creds returned,
+ * trim the list of mechanisms down to only those
+ * for which the creds are valid.
+ */
+ if (found > 0 && major_status == GSS_S_COMPLETE && creds != NULL) {
+ major_status = gss_acquire_cred_from(minor_status, name,
+ GSS_C_INDEFINITE,
+ *rmechs, usage,
+ cred_store, creds,
+ &goodmechs, time_rec);
+
+ /*
+ * Drop the old list in favor of the new
+ * "trimmed" list.
+ */
+ (void) gss_release_oid_set(&tmpmin, rmechs);
+ if (major_status == GSS_S_COMPLETE) {
+ (void) gssint_copy_oid_set(&tmpmin,
+ goodmechs, rmechs);
+ (void) gss_release_oid_set(&tmpmin, &goodmechs);
+ }
+ }
+
+ (void) gss_release_oid_set(&tmpmin, &mechs);
+ if (found == 0 || major_status != GSS_S_COMPLETE) {
+ *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE;
+ map_errcode(minor_status);
+ if (major_status == GSS_S_COMPLETE)
+ major_status = GSS_S_FAILURE;
+ }
+
+ return (major_status);
+}
+
+/*
+ * Return a list of mechanisms we are willing to negotiate for a credential,
+ * taking into account the mech set provided with gss_set_neg_mechs if it
+ * exists.
+ */
+static OM_uint32
+get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_cred_id_t spcred,
+ gss_cred_usage_t usage, gss_OID_set *rmechs)
+{
+ OM_uint32 ret, tmpmin;
+ gss_cred_id_t creds = GSS_C_NO_CREDENTIAL, *credptr;
+ gss_OID_set cred_mechs = GSS_C_NULL_OID_SET;
+ gss_OID_set intersect_mechs = GSS_C_NULL_OID_SET;
+ unsigned int i;
+ int present;
+
+ if (spcred == NULL) {
+ /*
+ * The default credentials were supplied. Return a list of all
+ * available mechs except SPNEGO. When initiating, trim this
+ * list to mechs we can acquire credentials for.
+ */
+ credptr = (usage == GSS_C_INITIATE) ? &creds : NULL;
+ ret = get_available_mechs(minor_status, GSS_C_NO_NAME, usage,
+ GSS_C_NO_CRED_STORE, credptr,
+ rmechs, NULL);
+ gss_release_cred(&tmpmin, &creds);
+ return (ret);
+ }
+
+ /* Get the list of mechs in the mechglue cred. */
+ ret = gss_inquire_cred(minor_status, spcred->mcred, NULL, NULL, NULL,
+ &cred_mechs);
+ if (ret != GSS_S_COMPLETE)
+ return (ret);
+
+ if (spcred->neg_mechs == GSS_C_NULL_OID_SET) {
+ /* gss_set_neg_mechs was never called; return cred_mechs. */
+ *rmechs = cred_mechs;
+ *minor_status = 0;
+ return (GSS_S_COMPLETE);
+ }
+
+ /* Compute the intersection of cred_mechs and spcred->neg_mechs,
+ * preserving the order in spcred->neg_mechs. */
+ ret = gss_create_empty_oid_set(minor_status, &intersect_mechs);
+ if (ret != GSS_S_COMPLETE) {
+ gss_release_oid_set(&tmpmin, &cred_mechs);
+ return (ret);
+ }
+
+ for (i = 0; i < spcred->neg_mechs->count; i++) {
+ gss_test_oid_set_member(&tmpmin,
+ &spcred->neg_mechs->elements[i],
+ cred_mechs, &present);
+ if (!present)
+ continue;
+ ret = gss_add_oid_set_member(minor_status,
+ &spcred->neg_mechs->elements[i],
+ &intersect_mechs);
+ if (ret != GSS_S_COMPLETE)
+ break;
+ }
+
+ gss_release_oid_set(&tmpmin, &cred_mechs);
+ if (intersect_mechs->count == 0 || ret != GSS_S_COMPLETE) {
+ gss_release_oid_set(&tmpmin, &intersect_mechs);
+ *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE;
+ map_errcode(minor_status);
+ return (GSS_S_FAILURE);
+ }
+
+ *rmechs = intersect_mechs;
+ *minor_status = 0;
+ return (GSS_S_COMPLETE);
+}
+
+/* following are token creation and reading routines */
+
+/*
+ * If buff_in is not pointing to a MECH_OID, then return NULL and do not
+ * advance the buffer, otherwise, decode the mech_oid from the buffer and
+ * place in gss_OID.
+ */
+static gss_OID
+get_mech_oid(OM_uint32 *minor_status, unsigned char **buff_in, size_t length)
+{
+ OM_uint32 status;
+ gss_OID_desc toid;
+ gss_OID mech_out = NULL;
+ unsigned char *start, *end;
+
+ if (length < 1 || **buff_in != MECH_OID)
+ return (NULL);
+
+ start = *buff_in;
+ end = start + length;
+
+ (*buff_in)++;
+ toid.length = *(*buff_in)++;
+
+ if ((*buff_in + toid.length) > end)
+ return (NULL);
+
+ toid.elements = *buff_in;
+ *buff_in += toid.length;
+
+ status = generic_gss_copy_oid(minor_status, &toid, &mech_out);
+
+ if (status != GSS_S_COMPLETE) {
+ map_errcode(minor_status);
+ mech_out = NULL;
+ }
+
+ return (mech_out);
+}
+
+/*
+ * der encode the given mechanism oid into buf_out, advancing the
+ * buffer pointer.
+ */
+
+static int
+put_mech_oid(unsigned char **buf_out, gss_OID_const mech, unsigned int buflen)
+{
+ if (buflen < mech->length + 2)
+ return (-1);
+ *(*buf_out)++ = MECH_OID;
+ *(*buf_out)++ = (unsigned char) mech->length;
+ memcpy(*buf_out, mech->elements, mech->length);
+ *buf_out += mech->length;
+ return (0);
+}
+
+/*
+ * verify that buff_in points to an octet string, if it does not,
+ * return NULL and don't advance the pointer. If it is an octet string
+ * decode buff_in into a gss_buffer_t and return it, advancing the
+ * buffer pointer.
+ */
+static gss_buffer_t
+get_input_token(unsigned char **buff_in, unsigned int buff_length)
+{
+ gss_buffer_t input_token;
+ unsigned int len;
+
+ if (g_get_tag_and_length(buff_in, OCTET_STRING, buff_length, &len) < 0)
+ return (NULL);
+
+ input_token = (gss_buffer_t)malloc(sizeof (gss_buffer_desc));
+ if (input_token == NULL)
+ return (NULL);
+
+ input_token->length = len;
+ if (input_token->length > 0) {
+ input_token->value = gssalloc_malloc(input_token->length);
+ if (input_token->value == NULL) {
+ free(input_token);
+ return (NULL);
+ }
+
+ memcpy(input_token->value, *buff_in, input_token->length);
+ } else {
+ input_token->value = NULL;
+ }
+ *buff_in += input_token->length;
+ return (input_token);
+}
+
+/*
+ * verify that the input token length is not 0. If it is, just return.
+ * If the token length is greater than 0, der encode as an octet string
+ * and place in buf_out, advancing buf_out.
+ */
+
+static int
+put_input_token(unsigned char **buf_out, gss_buffer_t input_token,
+ unsigned int buflen)
+{
+ int ret;
+
+ /* if token length is 0, we do not want to send */
+ if (input_token->length == 0)
+ return (0);
+
+ if (input_token->length > buflen)
+ return (-1);
+
+ *(*buf_out)++ = OCTET_STRING;
+ if ((ret = gssint_put_der_length(input_token->length, buf_out,
+ input_token->length)))
+ return (ret);
+ TWRITE_STR(*buf_out, input_token->value, input_token->length);
+ return (0);
+}
+
+/*
+ * verify that buff_in points to a sequence of der encoding. The mech
+ * set is the only sequence of encoded object in the token, so if it is
+ * a sequence of encoding, decode the mechset into a gss_OID_set and
+ * return it, advancing the buffer pointer.
+ */
+static gss_OID_set
+get_mech_set(OM_uint32 *minor_status, unsigned char **buff_in,
+ unsigned int buff_length)
+{
+ gss_OID_set returned_mechSet;
+ OM_uint32 major_status;
+ int length;
+ unsigned int bytes;
+ OM_uint32 set_length;
+ unsigned char *start;
+ int i;
+
+ if (**buff_in != SEQUENCE_OF)
+ return (NULL);
+
+ start = *buff_in;
+ (*buff_in)++;
+
+ length = gssint_get_der_length(buff_in, buff_length, &bytes);
+ if (length < 0 || buff_length - bytes < (unsigned int)length)
+ return NULL;
+
+ major_status = gss_create_empty_oid_set(minor_status,
+ &returned_mechSet);
+ if (major_status != GSS_S_COMPLETE)
+ return (NULL);
+
+ for (set_length = 0, i = 0; set_length < (unsigned int)length; i++) {
+ gss_OID_desc *temp = get_mech_oid(minor_status, buff_in,
+ buff_length - (*buff_in - start));
+ if (temp == NULL)
+ break;
+
+ major_status = gss_add_oid_set_member(minor_status,
+ temp, &returned_mechSet);
+ if (major_status == GSS_S_COMPLETE) {
+ set_length += returned_mechSet->elements[i].length +2;
+ if (generic_gss_release_oid(minor_status, &temp))
+ map_errcode(minor_status);
+ }
+ }
+
+ return (returned_mechSet);
+}
+
+/*
+ * Encode mechSet into buf.
+ */
+static int
+put_mech_set(gss_OID_set mechSet, gss_buffer_t buf)
+{
+ unsigned char *ptr;
+ unsigned int i;
+ unsigned int tlen, ilen;
+
+ tlen = ilen = 0;
+ for (i = 0; i < mechSet->count; i++) {
+ /*
+ * 0x06 [DER LEN] [OID]
+ */
+ ilen += 1 +
+ gssint_der_length_size(mechSet->elements[i].length) +
+ mechSet->elements[i].length;
+ }
+ /*
+ * 0x30 [DER LEN]
+ */
+ tlen = 1 + gssint_der_length_size(ilen) + ilen;
+ ptr = gssalloc_malloc(tlen);
+ if (ptr == NULL)
+ return -1;
+
+ buf->value = ptr;
+ buf->length = tlen;
+#define REMAIN (buf->length - ((unsigned char *)buf->value - ptr))
+
+ *ptr++ = SEQUENCE_OF;
+ if (gssint_put_der_length(ilen, &ptr, REMAIN) < 0)
+ return -1;
+ for (i = 0; i < mechSet->count; i++) {
+ if (put_mech_oid(&ptr, &mechSet->elements[i], REMAIN) < 0) {
+ return -1;
+ }
+ }
+ return 0;
+#undef REMAIN
+}
+
+/*
+ * Verify that buff_in is pointing to a BIT_STRING with the correct
+ * length and padding for the req_flags. If it is, decode req_flags
+ * and return them, otherwise, return NULL.
+ */
+static OM_uint32
+get_req_flags(unsigned char **buff_in, OM_uint32 bodysize,
+ OM_uint32 *req_flags)
+{
+ unsigned int len;
+
+ if (**buff_in != (CONTEXT | 0x01))
+ return (0);
+
+ if (g_get_tag_and_length(buff_in, (CONTEXT | 0x01),
+ bodysize, &len) < 0)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ if (*(*buff_in)++ != BIT_STRING)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ if (*(*buff_in)++ != BIT_STRING_LENGTH)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ if (*(*buff_in)++ != BIT_STRING_PADDING)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ *req_flags = (OM_uint32) (*(*buff_in)++ >> 1);
+ return (0);
+}
+
+static OM_uint32
+get_negTokenInit(OM_uint32 *minor_status,
+ gss_buffer_t buf,
+ gss_buffer_t der_mechSet,
+ gss_OID_set *mechSet,
+ OM_uint32 *req_flags,
+ gss_buffer_t *mechtok,
+ gss_buffer_t *mechListMIC)
+{
+ OM_uint32 err;
+ unsigned char *ptr, *bufstart;
+ unsigned int len;
+ gss_buffer_desc tmpbuf;
+
+ *minor_status = 0;
+ der_mechSet->length = 0;
+ der_mechSet->value = NULL;
+ *mechSet = GSS_C_NO_OID_SET;
+ *req_flags = 0;
+ *mechtok = *mechListMIC = GSS_C_NO_BUFFER;
+
+ ptr = bufstart = buf->value;
+ if ((buf->length - (ptr - bufstart)) > INT_MAX)
+ return GSS_S_FAILURE;
+#define REMAIN (buf->length - (ptr - bufstart))
+
+ err = g_verify_token_header(gss_mech_spnego,
+ &len, &ptr, 0, REMAIN);
+ if (err) {
+ *minor_status = err;
+ map_errcode(minor_status);
+ return GSS_S_FAILURE;
+ }
+ *minor_status = g_verify_neg_token_init(&ptr, REMAIN);
+ if (*minor_status) {
+ map_errcode(minor_status);
+ return GSS_S_FAILURE;
+ }
+
+ /* alias into input_token */
+ tmpbuf.value = ptr;
+ tmpbuf.length = REMAIN;
+ *mechSet = get_mech_set(minor_status, &ptr, REMAIN);
+ if (*mechSet == NULL)
+ return GSS_S_FAILURE;
+
+ tmpbuf.length = ptr - (unsigned char *)tmpbuf.value;
+ der_mechSet->value = gssalloc_malloc(tmpbuf.length);
+ if (der_mechSet->value == NULL)
+ return GSS_S_FAILURE;
+ memcpy(der_mechSet->value, tmpbuf.value, tmpbuf.length);
+ der_mechSet->length = tmpbuf.length;
+
+ err = get_req_flags(&ptr, REMAIN, req_flags);
+ if (err != GSS_S_COMPLETE) {
+ return err;
+ }
+ if (g_get_tag_and_length(&ptr, (CONTEXT | 0x02),
+ REMAIN, &len) >= 0) {
+ *mechtok = get_input_token(&ptr, len);
+ if (*mechtok == GSS_C_NO_BUFFER) {
+ return GSS_S_FAILURE;
+ }
+ }
+ if (g_get_tag_and_length(&ptr, (CONTEXT | 0x03),
+ REMAIN, &len) >= 0) {
+ *mechListMIC = get_input_token(&ptr, len);
+ if (*mechListMIC == GSS_C_NO_BUFFER) {
+ return GSS_S_FAILURE;
+ }
+ }
+ return GSS_S_COMPLETE;
+#undef REMAIN
+}
+
+static OM_uint32
+get_negTokenResp(OM_uint32 *minor_status,
+ unsigned char *buf, unsigned int buflen,
+ OM_uint32 *negState,
+ gss_OID *supportedMech,
+ gss_buffer_t *responseToken,
+ gss_buffer_t *mechListMIC)
+{
+ unsigned char *ptr, *bufstart;
+ unsigned int len;
+ int tmplen;
+ unsigned int tag, bytes;
+
+ *negState = ACCEPT_DEFECTIVE_TOKEN;
+ *supportedMech = GSS_C_NO_OID;
+ *responseToken = *mechListMIC = GSS_C_NO_BUFFER;
+ ptr = bufstart = buf;
+#define REMAIN (buflen - (ptr - bufstart))
+
+ if (g_get_tag_and_length(&ptr, (CONTEXT | 0x01), REMAIN, &len) < 0)
+ return GSS_S_DEFECTIVE_TOKEN;
+ if (*ptr++ == SEQUENCE) {
+ tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
+ if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ if (REMAIN < 1)
+ tag = 0;
+ else
+ tag = *ptr++;
+
+ if (tag == CONTEXT) {
+ tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
+ if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ if (g_get_tag_and_length(&ptr, ENUMERATED,
+ REMAIN, &len) < 0)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ if (len != ENUMERATION_LENGTH)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ if (REMAIN < 1)
+ return GSS_S_DEFECTIVE_TOKEN;
+ *negState = *ptr++;
+
+ if (REMAIN < 1)
+ tag = 0;
+ else
+ tag = *ptr++;
+ }
+ if (tag == (CONTEXT | 0x01)) {
+ tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
+ if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ *supportedMech = get_mech_oid(minor_status, &ptr, REMAIN);
+ if (*supportedMech == GSS_C_NO_OID)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ if (REMAIN < 1)
+ tag = 0;
+ else
+ tag = *ptr++;
+ }
+ if (tag == (CONTEXT | 0x02)) {
+ tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
+ if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ *responseToken = get_input_token(&ptr, REMAIN);
+ if (*responseToken == GSS_C_NO_BUFFER)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ if (REMAIN < 1)
+ tag = 0;
+ else
+ tag = *ptr++;
+ }
+ if (tag == (CONTEXT | 0x03)) {
+ tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes);
+ if (tmplen < 0 || REMAIN < (unsigned int)tmplen)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ *mechListMIC = get_input_token(&ptr, REMAIN);
+ if (*mechListMIC == GSS_C_NO_BUFFER)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ /* Handle Windows 2000 duplicate response token */
+ if (*responseToken &&
+ ((*responseToken)->length == (*mechListMIC)->length) &&
+ !memcmp((*responseToken)->value, (*mechListMIC)->value,
+ (*responseToken)->length)) {
+ OM_uint32 tmpmin;
+
+ gss_release_buffer(&tmpmin, *mechListMIC);
+ free(*mechListMIC);
+ *mechListMIC = NULL;
+ }
+ }
+ return GSS_S_COMPLETE;
+#undef REMAIN
+}
+
+/*
+ * der encode the passed negResults as an ENUMERATED type and
+ * place it in buf_out, advancing the buffer.
+ */
+
+static int
+put_negResult(unsigned char **buf_out, OM_uint32 negResult,
+ unsigned int buflen)
+{
+ if (buflen < 3)
+ return (-1);
+ *(*buf_out)++ = ENUMERATED;
+ *(*buf_out)++ = ENUMERATION_LENGTH;
+ *(*buf_out)++ = (unsigned char) negResult;
+ return (0);
+}
+
+/*
+ * This routine compares the 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_INCOMPLETE if it's the first mech, REQUEST_MIC if
+ * it's not the first mech, otherwise we return NULL and negResult
+ * is set to REJECT. The returned pointer is an alias into
+ * received->elements and should not be freed.
+ *
+ * NOTE: There is currently no way to specify a preference order of
+ * mechanisms supported by the acceptor.
+ */
+static gss_OID
+negotiate_mech(gss_OID_set supported, gss_OID_set received,
+ OM_uint32 *negResult)
+{
+ size_t i, j;
+
+ for (i = 0; i < received->count; i++) {
+ gss_OID mech_oid = &received->elements[i];
+
+ /* Accept wrong mechanism OID from MS clients */
+ if (g_OID_equal(mech_oid, &gss_mech_krb5_wrong_oid))
+ mech_oid = (gss_OID)&gss_mech_krb5_oid;
+
+ for (j = 0; j < supported->count; j++) {
+ if (g_OID_equal(mech_oid, &supported->elements[j])) {
+ *negResult = (i == 0) ? ACCEPT_INCOMPLETE :
+ REQUEST_MIC;
+ return &received->elements[i];
+ }
+ }
+ }
+ *negResult = REJECT;
+ return (NULL);
+}
+
+/*
+ * the next two routines make a token buffer suitable for
+ * spnego_gss_display_status. These currently take the string
+ * in name and place it in the token. Eventually, if
+ * spnego_gss_display_status returns valid error messages,
+ * these routines will be changes to return the error string.
+ */
+static spnego_token_t
+make_spnego_token(const char *name)
+{
+ return (spnego_token_t)strdup(name);
+}
+
+static gss_buffer_desc
+make_err_msg(const char *name)
+{
+ gss_buffer_desc buffer;
+
+ if (name == NULL) {
+ buffer.length = 0;
+ buffer.value = NULL;
+ } else {
+ buffer.length = strlen(name)+1;
+ buffer.value = make_spnego_token(name);
+ }
+
+ return (buffer);
+}
+
+/*
+ * Create the client side spnego token passed back to gss_init_sec_context
+ * and eventually up to the application program and over to the server.
+ *
+ * Use DER rules, definite length method per RFC 2478
+ */
+static int
+make_spnego_tokenInit_msg(spnego_gss_ctx_id_t spnego_ctx,
+ int negHintsCompat,
+ gss_buffer_t mechListMIC, OM_uint32 req_flags,
+ gss_buffer_t data, send_token_flag sendtoken,
+ gss_buffer_t outbuf)
+{
+ int ret = 0;
+ unsigned int tlen, dataLen = 0;
+ unsigned int negTokenInitSize = 0;
+ unsigned int negTokenInitSeqSize = 0;
+ unsigned int negTokenInitContSize = 0;
+ unsigned int rspTokenSize = 0;
+ unsigned int mechListTokenSize = 0;
+ unsigned int micTokenSize = 0;
+ unsigned char *t;
+ unsigned char *ptr;
+
+ if (outbuf == GSS_C_NO_BUFFER)
+ return (-1);
+
+ outbuf->length = 0;
+ outbuf->value = NULL;
+
+ /* calculate the data length */
+
+ /*
+ * 0xa0 [DER LEN] [mechTypes]
+ */
+ mechListTokenSize = 1 +
+ gssint_der_length_size(spnego_ctx->DER_mechTypes.length) +
+ spnego_ctx->DER_mechTypes.length;
+ dataLen += mechListTokenSize;
+
+ /*
+ * If a token from gss_init_sec_context exists,
+ * add the length of the token + the ASN.1 overhead
+ */
+ if (data != NULL) {
+ /*
+ * Encoded in final output as:
+ * 0xa2 [DER LEN] 0x04 [DER LEN] [DATA]
+ * -----s--------|--------s2----------
+ */
+ rspTokenSize = 1 +
+ gssint_der_length_size(data->length) +
+ data->length;
+ dataLen += 1 + gssint_der_length_size(rspTokenSize) +
+ rspTokenSize;
+ }
+
+ if (mechListMIC) {
+ /*
+ * Encoded in final output as:
+ * 0xa3 [DER LEN] 0x04 [DER LEN] [DATA]
+ * --s-- -----tlen------------
+ */
+ micTokenSize = 1 +
+ gssint_der_length_size(mechListMIC->length) +
+ mechListMIC->length;
+ dataLen += 1 +
+ gssint_der_length_size(micTokenSize) +
+ micTokenSize;
+ }
+
+ /*
+ * Add size of DER encoding
+ * [ SEQUENCE { MechTypeList | ReqFLags | Token | mechListMIC } ]
+ * 0x30 [DER_LEN] [data]
+ *
+ */
+ negTokenInitContSize = dataLen;
+ negTokenInitSeqSize = 1 + gssint_der_length_size(dataLen) + dataLen;
+ dataLen = negTokenInitSeqSize;
+
+ /*
+ * negTokenInitSize indicates the bytes needed to
+ * hold the ASN.1 encoding of the entire NegTokenInit
+ * SEQUENCE.
+ * 0xa0 [DER_LEN] + data
+ *
+ */
+ negTokenInitSize = 1 +
+ gssint_der_length_size(negTokenInitSeqSize) +
+ negTokenInitSeqSize;
+
+ tlen = g_token_size(gss_mech_spnego, negTokenInitSize);
+
+ t = (unsigned char *) gssalloc_malloc(tlen);
+
+ if (t == NULL) {
+ return (-1);
+ }
+
+ ptr = t;
+
+ /* create the message */
+ if ((ret = g_make_token_header(gss_mech_spnego, negTokenInitSize,
+ &ptr, tlen)))
+ goto errout;
+
+ *ptr++ = CONTEXT; /* NegotiationToken identifier */
+ if ((ret = gssint_put_der_length(negTokenInitSeqSize, &ptr, tlen)))
+ goto errout;
+
+ *ptr++ = SEQUENCE;
+ if ((ret = gssint_put_der_length(negTokenInitContSize, &ptr,
+ tlen - (int)(ptr-t))))
+ goto errout;
+
+ *ptr++ = CONTEXT | 0x00; /* MechTypeList identifier */
+ if ((ret = gssint_put_der_length(spnego_ctx->DER_mechTypes.length,
+ &ptr, tlen - (int)(ptr-t))))
+ goto errout;
+
+ /* We already encoded the MechSetList */
+ (void) memcpy(ptr, spnego_ctx->DER_mechTypes.value,
+ spnego_ctx->DER_mechTypes.length);
+
+ ptr += spnego_ctx->DER_mechTypes.length;
+
+ if (data != NULL) {
+ *ptr++ = CONTEXT | 0x02;
+ if ((ret = gssint_put_der_length(rspTokenSize,
+ &ptr, tlen - (int)(ptr - t))))
+ goto errout;
+
+ if ((ret = put_input_token(&ptr, data,
+ tlen - (int)(ptr - t))))
+ goto errout;
+ }
+
+ if (mechListMIC != GSS_C_NO_BUFFER) {
+ *ptr++ = CONTEXT | 0x03;
+ if ((ret = gssint_put_der_length(micTokenSize,
+ &ptr, tlen - (int)(ptr - t))))
+ goto errout;
+
+ if (negHintsCompat) {
+ ret = put_neg_hints(&ptr, mechListMIC,
+ tlen - (int)(ptr - t));
+ if (ret)
+ goto errout;
+ } else if ((ret = put_input_token(&ptr, mechListMIC,
+ tlen - (int)(ptr - t))))
+ goto errout;
+ }
+
+errout:
+ if (ret != 0) {
+ if (t)
+ free(t);
+ t = NULL;
+ tlen = 0;
+ }
+ outbuf->length = tlen;
+ outbuf->value = (void *) t;
+
+ return (ret);
+}
+
+/*
+ * create the server side spnego token passed back to
+ * gss_accept_sec_context and eventually up to the application program
+ * and over to the client.
+ */
+static int
+make_spnego_tokenTarg_msg(OM_uint32 status, gss_OID mech_wanted,
+ gss_buffer_t data, gss_buffer_t mechListMIC,
+ send_token_flag sendtoken,
+ gss_buffer_t outbuf)
+{
+ unsigned int tlen = 0;
+ unsigned int ret = 0;
+ unsigned int NegTokenTargSize = 0;
+ unsigned int NegTokenSize = 0;
+ unsigned int rspTokenSize = 0;
+ unsigned int micTokenSize = 0;
+ unsigned int dataLen = 0;
+ unsigned char *t;
+ unsigned char *ptr;
+
+ if (outbuf == GSS_C_NO_BUFFER)
+ return (GSS_S_DEFECTIVE_TOKEN);
+ if (sendtoken == INIT_TOKEN_SEND && mech_wanted == GSS_C_NO_OID)
+ return (GSS_S_DEFECTIVE_TOKEN);
+
+ outbuf->length = 0;
+ outbuf->value = NULL;
+
+ /*
+ * ASN.1 encoding of the negResult
+ * ENUMERATED type is 3 bytes
+ * ENUMERATED TAG, Length, Value,
+ * Plus 2 bytes for the CONTEXT id and length.
+ */
+ dataLen = 5;
+
+ /*
+ * calculate data length
+ *
+ * If this is the initial token, include length of
+ * mech_type and the negotiation result fields.
+ */
+ if (sendtoken == INIT_TOKEN_SEND) {
+ int mechlistTokenSize;
+ /*
+ * 1 byte for the CONTEXT ID(0xa0),
+ * 1 byte for the OID ID(0x06)
+ * 1 byte for OID Length field
+ * Plus the rest... (OID Length, OID value)
+ */
+ mechlistTokenSize = 3 + mech_wanted->length +
+ gssint_der_length_size(mech_wanted->length);
+
+ dataLen += mechlistTokenSize;
+ }
+ if (data != NULL && data->length > 0) {
+ /* Length of the inner token */
+ rspTokenSize = 1 + gssint_der_length_size(data->length) +
+ data->length;
+
+ dataLen += rspTokenSize;
+
+ /* Length of the outer token */
+ dataLen += 1 + gssint_der_length_size(rspTokenSize);
+ }
+ if (mechListMIC != NULL) {
+
+ /* Length of the inner token */
+ micTokenSize = 1 + gssint_der_length_size(mechListMIC->length) +
+ mechListMIC->length;
+
+ dataLen += micTokenSize;
+
+ /* Length of the outer token */
+ dataLen += 1 + gssint_der_length_size(micTokenSize);
+ }
+ /*
+ * Add size of DER encoded:
+ * NegTokenTarg [ SEQUENCE ] of
+ * NegResult[0] ENUMERATED {
+ * accept_completed(0),
+ * accept_incomplete(1),
+ * reject(2) }
+ * supportedMech [1] MechType OPTIONAL,
+ * responseToken [2] OCTET STRING OPTIONAL,
+ * mechListMIC [3] OCTET STRING OPTIONAL
+ *
+ * size = data->length + MechListMic + SupportedMech len +
+ * Result Length + ASN.1 overhead
+ */
+ NegTokenTargSize = dataLen;
+ dataLen += 1 + gssint_der_length_size(NegTokenTargSize);
+
+ /*
+ * NegotiationToken [ CHOICE ]{
+ * negTokenInit [0] NegTokenInit,
+ * negTokenTarg [1] NegTokenTarg }
+ */
+ NegTokenSize = dataLen;
+ dataLen += 1 + gssint_der_length_size(NegTokenSize);
+
+ tlen = dataLen;
+ t = (unsigned char *) gssalloc_malloc(tlen);
+
+ if (t == NULL) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+
+ ptr = t;
+
+ /*
+ * Indicate that we are sending CHOICE 1
+ * (NegTokenTarg)
+ */
+ *ptr++ = CONTEXT | 0x01;
+ if (gssint_put_der_length(NegTokenSize, &ptr, dataLen) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ *ptr++ = SEQUENCE;
+ if (gssint_put_der_length(NegTokenTargSize, &ptr,
+ tlen - (int)(ptr-t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+
+ /*
+ * First field of the NegTokenTarg SEQUENCE
+ * is the ENUMERATED NegResult.
+ */
+ *ptr++ = CONTEXT;
+ if (gssint_put_der_length(3, &ptr,
+ tlen - (int)(ptr-t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if (put_negResult(&ptr, status, tlen - (int)(ptr - t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if (sendtoken == INIT_TOKEN_SEND) {
+ /*
+ * Next, is the Supported MechType
+ */
+ *ptr++ = CONTEXT | 0x01;
+ if (gssint_put_der_length(mech_wanted->length + 2,
+ &ptr,
+ tlen - (int)(ptr - t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if (put_mech_oid(&ptr, mech_wanted,
+ tlen - (int)(ptr - t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ }
+ if (data != NULL && data->length > 0) {
+ *ptr++ = CONTEXT | 0x02;
+ if (gssint_put_der_length(rspTokenSize, &ptr,
+ tlen - (int)(ptr - t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if (put_input_token(&ptr, data,
+ tlen - (int)(ptr - t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ }
+ if (mechListMIC != NULL) {
+ *ptr++ = CONTEXT | 0x03;
+ if (gssint_put_der_length(micTokenSize, &ptr,
+ tlen - (int)(ptr - t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ if (put_input_token(&ptr, mechListMIC,
+ tlen - (int)(ptr - t)) < 0) {
+ ret = GSS_S_DEFECTIVE_TOKEN;
+ goto errout;
+ }
+ }
+ ret = GSS_S_COMPLETE;
+errout:
+ if (ret != GSS_S_COMPLETE) {
+ if (t)
+ free(t);
+ } else {
+ outbuf->length = ptr - t;
+ outbuf->value = (void *) t;
+ }
+
+ return (ret);
+}
+
+/* determine size of token */
+static int
+g_token_size(gss_OID_const mech, unsigned int body_size)
+{
+ int hdrsize;
+
+ /*
+ * Initialize the header size to the
+ * MECH_OID byte + the bytes needed to indicate the
+ * length of the OID + the OID itself.
+ *
+ * 0x06 [MECHLENFIELD] MECHDATA
+ */
+ hdrsize = 1 + gssint_der_length_size(mech->length) + mech->length;
+
+ /*
+ * Now add the bytes needed for the initial header
+ * token bytes:
+ * 0x60 + [DER_LEN] + HDRSIZE
+ */
+ hdrsize += 1 + gssint_der_length_size(body_size + hdrsize);
+
+ return (hdrsize + body_size);
+}
+
+/*
+ * generate token header.
+ *
+ * Use DER Definite Length method per RFC2478
+ * Use of indefinite length encoding will not be compatible
+ * with Microsoft or others that actually follow the spec.
+ */
+static int
+g_make_token_header(gss_OID_const mech,
+ unsigned int body_size,
+ unsigned char **buf,
+ unsigned int totallen)
+{
+ int ret = 0;
+ unsigned int hdrsize;
+ unsigned char *p = *buf;
+
+ hdrsize = 1 + gssint_der_length_size(mech->length) + mech->length;
+
+ *(*buf)++ = HEADER_ID;
+ if ((ret = gssint_put_der_length(hdrsize + body_size, buf, totallen)))
+ return (ret);
+
+ *(*buf)++ = MECH_OID;
+ if ((ret = gssint_put_der_length(mech->length, buf,
+ totallen - (int)(p - *buf))))
+ return (ret);
+ TWRITE_STR(*buf, mech->elements, mech->length);
+ return (0);
+}
+
+/*
+ * NOTE: This checks that the length returned by
+ * gssint_get_der_length() is not greater than the number of octets
+ * remaining, even though gssint_get_der_length() already checks, in
+ * theory.
+ */
+static int
+g_get_tag_and_length(unsigned char **buf, int tag,
+ unsigned int buflen, unsigned int *outlen)
+{
+ unsigned char *ptr = *buf;
+ int ret = -1; /* pessimists, assume failure ! */
+ unsigned int encoded_len;
+ int tmplen = 0;
+
+ *outlen = 0;
+ if (buflen > 1 && *ptr == tag) {
+ ptr++;
+ tmplen = gssint_get_der_length(&ptr, buflen - 1,
+ &encoded_len);
+ if (tmplen < 0) {
+ ret = -1;
+ } else if ((unsigned int)tmplen > buflen - (ptr - *buf)) {
+ ret = -1;
+ } else
+ ret = 0;
+ }
+ *outlen = tmplen;
+ *buf = ptr;
+ return (ret);
+}
+
+static int
+g_verify_neg_token_init(unsigned char **buf_in, unsigned int cur_size)
+{
+ unsigned char *buf = *buf_in;
+ unsigned char *endptr = buf + cur_size;
+ int seqsize;
+ int ret = 0;
+ unsigned int bytes;
+
+ /*
+ * Verify this is a NegotiationToken type token
+ * - check for a0(context specific identifier)
+ * - get length and verify that enoughd ata exists
+ */
+ if (g_get_tag_and_length(&buf, CONTEXT, cur_size, &bytes) < 0)
+ return (G_BAD_TOK_HEADER);
+
+ cur_size = bytes; /* should indicate bytes remaining */
+
+ /*
+ * Verify the next piece, it should identify this as
+ * a strucure of type NegTokenInit.
+ */
+ if (*buf++ == SEQUENCE) {
+ if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0)
+ return (G_BAD_TOK_HEADER);
+ /*
+ * Make sure we have the entire buffer as described
+ */
+ if (seqsize > endptr - buf)
+ return (G_BAD_TOK_HEADER);
+ } else {
+ return (G_BAD_TOK_HEADER);
+ }
+
+ cur_size = seqsize; /* should indicate bytes remaining */
+
+ /*
+ * Verify that the first blob is a sequence of mechTypes
+ */
+ if (*buf++ == CONTEXT) {
+ if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0)
+ return (G_BAD_TOK_HEADER);
+ /*
+ * Make sure we have the entire buffer as described
+ */
+ if (seqsize > endptr - buf)
+ return (G_BAD_TOK_HEADER);
+ } else {
+ return (G_BAD_TOK_HEADER);
+ }
+
+ /*
+ * At this point, *buf should be at the beginning of the
+ * DER encoded list of mech types that are to be negotiated.
+ */
+ *buf_in = buf;
+
+ return (ret);
+
+}
+
+/* verify token header. */
+static int
+g_verify_token_header(gss_OID_const mech,
+ unsigned int *body_size,
+ unsigned char **buf_in,
+ int tok_type,
+ unsigned int toksize)
+{
+ unsigned char *buf = *buf_in;
+ int seqsize;
+ gss_OID_desc toid;
+ int ret = 0;
+ unsigned int bytes;
+
+ if (toksize-- < 1)
+ return (G_BAD_TOK_HEADER);
+
+ if (*buf++ != HEADER_ID)
+ return (G_BAD_TOK_HEADER);
+
+ if ((seqsize = gssint_get_der_length(&buf, toksize, &bytes)) < 0)
+ return (G_BAD_TOK_HEADER);
+
+ if ((seqsize + bytes) != toksize)
+ return (G_BAD_TOK_HEADER);
+
+ if (toksize-- < 1)
+ return (G_BAD_TOK_HEADER);
+
+
+ if (*buf++ != MECH_OID)
+ return (G_BAD_TOK_HEADER);
+
+ if (toksize-- < 1)
+ return (G_BAD_TOK_HEADER);
+
+ toid.length = *buf++;
+
+ if (toksize < toid.length)
+ return (G_BAD_TOK_HEADER);
+ else
+ toksize -= toid.length;
+
+ toid.elements = buf;
+ buf += toid.length;
+
+ if (!g_OID_equal(&toid, mech))
+ ret = G_WRONG_MECH;
+
+ /*
+ * G_WRONG_MECH is not returned immediately because it's more important
+ * to return G_BAD_TOK_HEADER if the token header is in fact bad
+ */
+ if (toksize < 2)
+ return (G_BAD_TOK_HEADER);
+ else
+ toksize -= 2;
+
+ if (!ret) {
+ *buf_in = buf;
+ *body_size = toksize;
+ }
+
+ return (ret);
+}
+
+/*
+ * Return non-zero if the oid is one of the kerberos mech oids,
+ * otherwise return zero.
+ *
+ * N.B. There are 3 oids that represent the kerberos mech:
+ * RFC-specified GSS_MECH_KRB5_OID,
+ * Old pre-RFC GSS_MECH_KRB5_OLD_OID,
+ * Incorrect MS GSS_MECH_KRB5_WRONG_OID
+ */
+
+static int
+is_kerb_mech(gss_OID oid)
+{
+ int answer = 0;
+ OM_uint32 minor;
+ extern const gss_OID_set_desc * const gss_mech_set_krb5_both;
+
+ (void) gss_test_oid_set_member(&minor,
+ oid, (gss_OID_set)gss_mech_set_krb5_both, &answer);
+
+ return (answer);
+}