krb5 commit: Implement NegoEx

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/c2ca2f26eaf817a6a7ed42257c380437ab802bd9
commit c2ca2f26eaf817a6a7ed42257c380437ab802bd9
Author: Luke Howard <[email protected]>
Date:   Wed Dec 26 22:52:18 2018 +1100

    Implement NegoEx
    
    Implement draft-zhu-negoex.  Mechanisms supporting the NegoEx GSS
    extensions will be negotiated only through NegoEx, unless they assert
    the GSS_C_MA_NEGOEX_AND_SPNEGO mech attribute, in which case they may
    also be negotiated directly via SPNEGO.
    
    ticket: 8851

 doc/plugindev/gssapi.rst                |   25 +
 src/include/k5-trace.h                  |    5 +
 src/lib/gssapi/generic/gssapi_ext.h     |   45 ++
 src/lib/gssapi/generic/gssapi_generic.c |  129 +++---
 src/lib/gssapi/libgssapi_krb5.exports   |    3 +
 src/lib/gssapi/mechglue/Makefile.in     |    4 +
 src/lib/gssapi/mechglue/deps            |   17 +-
 src/lib/gssapi/mechglue/g_initialize.c  |    4 +
 src/lib/gssapi/mechglue/g_negoex.c      |  237 +++++++++
 src/lib/gssapi/mechglue/mglueP.h        |   31 ++
 src/lib/gssapi/spnego/Makefile.in       |    7 +-
 src/lib/gssapi/spnego/deps              |   36 ++-
 src/lib/gssapi/spnego/gssapiP_negoex.h  |  210 ++++++++
 src/lib/gssapi/spnego/gssapiP_spnego.h  |   41 ++-
 src/lib/gssapi/spnego/negoex_ctx.c      |  785 ++++++++++++++++++++++++++++++
 src/lib/gssapi/spnego/negoex_trace.c    |  121 +++++
 src/lib/gssapi/spnego/negoex_util.c     |  812 +++++++++++++++++++++++++++++++
 src/lib/gssapi/spnego/spnego_mech.c     |  407 ++++++++++------
 18 files changed, 2691 insertions(+), 228 deletions(-)

diff --git a/doc/plugindev/gssapi.rst b/doc/plugindev/gssapi.rst
index 28e62ae..cb1f462 100644
--- a/doc/plugindev/gssapi.rst
+++ b/doc/plugindev/gssapi.rst
@@ -31,6 +31,31 @@ the mechanism's status codes onto unique values, and then map them
 back again when **gss_display_status** is called.
 
 
+NegoEx modules
+--------------
+
+Some Windows GSSAPI mechanisms can only be negotiated via a Microsoft
+extension to SPNEGO called NegoEx.  Beginning with release 1.18,
+mechanism modules can support NegoEx as follows:
+
+* Implement the gssspi_query_meta_data(), gssspi_exchange_meta_data(),
+  and gssspi_query_mechanism_info() SPIs declared in
+  ``<gssapi/gssapi_ext.h>``.
+
+* Implement gss_inquire_sec_context_by_oid() and answer the
+  **GSS_C_INQ_NEGOEX_KEY** and **GSS_C_INQ_NEGOEX_VERIFY_KEY** OIDs
+  to provide the checksum keys for outgoing and incoming checksums,
+  respectively.  The answer must be in two buffers: the first buffer
+  contains the key contents, and the second buffer contains the key
+  encryption type as a four-byte little-endian integer.
+
+By default, NegoEx mechanisms will not be directly negotiated via
+SPNEGO.  If direct SPNEGO negotiation is required for
+interoperability, implement gss_inquire_attrs_for_mech() and assert
+the GSS_C_MA_NEGOEX_AND_SPNEGO attribute (along with any applicable
+RFC 5587 attributes).
+
+
 Interposer modules
 ------------------
 
diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h
index d08e5ec..1da53db 100644
--- a/src/include/k5-trace.h
+++ b/src/include/k5-trace.h
@@ -294,6 +294,11 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
 #define TRACE_MSPAC_DISCARD_UNVERF(c)           \
     TRACE(c, "Filtering out unverified MS PAC")
 
+#define TRACE_NEGOEX_INCOMING(c, seqnum, typestr, info)                 \
+    TRACE(c, "NegoEx received [{int}]{str}: {str}", (int)seqnum, typestr, info)
+#define TRACE_NEGOEX_OUTGOING(c, seqnum, typestr, info)                 \
+    TRACE(c, "NegoEx sending [{int}]{str}: {str}", (int)seqnum, typestr, info)
+
 #define TRACE_PREAUTH_CONFLICT(c, name1, name2, patype)                 \
     TRACE(c, "Preauth module {str} conflicts with module {str} for pa " \
           "type {patype}", name1, name2, patype)
diff --git a/src/lib/gssapi/generic/gssapi_ext.h b/src/lib/gssapi/generic/gssapi_ext.h
index d3fd2a5..218456e 100644
--- a/src/lib/gssapi/generic/gssapi_ext.h
+++ b/src/lib/gssapi/generic/gssapi_ext.h
@@ -237,6 +237,9 @@ OM_uint32 KRB5_CALLCONV gss_unwrap_aead
  */
 GSS_DLLIMP extern gss_OID GSS_C_INQ_SSPI_SESSION_KEY;
 
+GSS_DLLIMP extern gss_OID GSS_C_INQ_NEGOEX_KEY;
+GSS_DLLIMP extern gss_OID GSS_C_INQ_NEGOEX_VERIFY_KEY;
+
 OM_uint32 KRB5_CALLCONV gss_complete_auth_token
 	(OM_uint32 *minor_status,
 	 const gss_ctx_id_t context_handle,
@@ -578,6 +581,48 @@ gss_store_cred_into(
     gss_OID_set *,             /* elements_stored */
     gss_cred_usage_t *);       /* cred_usage_stored */
 
+/*
+ * A mech can make itself negotiable via NegoEx (draft-zhu-negoex) by
+ * implementing the following three SPIs, and also implementing
+ * gss_inquire_sec_context_by_oid() and answering the GSS_C_INQ_NEGOEX_KEY and
+ * GSS_C_INQ_NEGOEX_VERIFY_KEY OIDs.  The answer must be in two buffers: the
+ * first contains the key contents, and the second contains the key enctype as
+ * a four-byte little-endian integer.
+ *
+ * By default, NegoEx mechanisms will not be directly negotiated via SPNEGO.
+ * If direct SPNEGO negotiation is required for interoperability, implement
+ * gss_inquire_attrs_for_mech() and assert the GSS_C_MA_NEGOEX_AND_SPNEGO
+ * attribute (along with any applicable RFC 5587 attributes).
+ */
+
+OM_uint32 KRB5_CALLCONV
+gssspi_query_meta_data(
+    OM_uint32 *minor_status,
+    gss_const_OID mech_oid,
+    gss_cred_id_t cred_handle,
+    gss_ctx_id_t *context_handle,
+    const gss_name_t targ_name,
+    OM_uint32 req_flags,
+    gss_buffer_t meta_data);
+
+OM_uint32 KRB5_CALLCONV
+gssspi_exchange_meta_data(
+    OM_uint32 *minor_status,
+    gss_const_OID mech_oid,
+    gss_cred_id_t cred_handle,
+    gss_ctx_id_t *context_handle,
+    const gss_name_t targ_name,
+    OM_uint32 req_flags,
+    gss_const_buffer_t meta_data);
+
+OM_uint32 KRB5_CALLCONV
+gssspi_query_mechanism_info(
+    OM_uint32 *minor_status,
+    gss_const_OID mech_oid,
+    unsigned char auth_scheme[16]);
+
+GSS_DLLIMP extern gss_const_OID GSS_C_MA_NEGOEX_AND_SPNEGO;
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/lib/gssapi/generic/gssapi_generic.c b/src/lib/gssapi/generic/gssapi_generic.c
index 1b362c3..3601585 100644
--- a/src/lib/gssapi/generic/gssapi_generic.c
+++ b/src/lib/gssapi/generic/gssapi_generic.c
@@ -128,6 +128,10 @@ static const gss_OID_desc const_oids[] = {
      */
     /* GSS_C_INQ_SSPI_SESSION_KEY 1.2.840.113554.1.2.2.5.5 */
     {11, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x05"},
+    /* GSS_C_INQ_NEGOEX_KEY 1.2.840.113554.1.2.2.5.16 */
+    {11, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x10"},
+    /* GSS_C_INQ_NEGOEX_VERIFY_KEY 1.2.840.113554.1.2.2.5.17 */
+    {11, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x11"},
 
     /* RFC 5587 attributes, see below */
     {7, (void *)"\x2b\x06\x01\x05\x05\x0d\x01"},
@@ -157,6 +161,8 @@ static const gss_OID_desc const_oids[] = {
     {7, (void *)"\x2b\x06\x01\x05\x05\x0d\x19"},
     {7, (void *)"\x2b\x06\x01\x05\x05\x0d\x1a"},
     {7, (void *)"\x2b\x06\x01\x05\x05\x0d\x1b"},
+    /* GSS_C_MA_NEGOEX_AND_SPNEGO 1.2.840.113554.1.2.2.5.18 */
+    {11, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x12"},
 
     /*
      * GSS_SEC_CONTEXT_SASL_SSF_OID 1.2.840.113554.1.2.2.5.15
@@ -194,40 +200,43 @@ GSS_DLLIMP gss_OID GSS_C_NT_EXPORT_NAME         = oids+6;
 gss_OID gss_nt_exported_name                    = oids+6;
 
 GSS_DLLIMP gss_OID GSS_C_NT_COMPOSITE_EXPORT    = oids+7;
-
 GSS_DLLIMP gss_OID GSS_C_INQ_SSPI_SESSION_KEY   = oids+8;
+GSS_DLLIMP gss_OID GSS_C_INQ_NEGOEX_KEY         = oids+9;
+GSS_DLLIMP gss_OID GSS_C_INQ_NEGOEX_VERIFY_KEY  = oids+10;
+
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_CONCRETE     = oids+11;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_PSEUDO       = oids+12;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_COMPOSITE    = oids+13;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_NEGO         = oids+14;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_GLUE         = oids+15;
+GSS_DLLIMP gss_const_OID GSS_C_MA_NOT_MECH          = oids+16;
+GSS_DLLIMP gss_const_OID GSS_C_MA_DEPRECATED        = oids+17;
+GSS_DLLIMP gss_const_OID GSS_C_MA_NOT_DFLT_MECH     = oids+18;
+GSS_DLLIMP gss_const_OID GSS_C_MA_ITOK_FRAMED       = oids+19;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT         = oids+20;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG         = oids+21;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT_INIT    = oids+22;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG_INIT    = oids+23;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT_ANON    = oids+24;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG_ANON    = oids+25;
+GSS_DLLIMP gss_const_OID GSS_C_MA_DELEG_CRED        = oids+26;
+GSS_DLLIMP gss_const_OID GSS_C_MA_INTEG_PROT        = oids+27;
+GSS_DLLIMP gss_const_OID GSS_C_MA_CONF_PROT         = oids+28;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MIC               = oids+29;
+GSS_DLLIMP gss_const_OID GSS_C_MA_WRAP              = oids+30;
+GSS_DLLIMP gss_const_OID GSS_C_MA_PROT_READY        = oids+31;
+GSS_DLLIMP gss_const_OID GSS_C_MA_REPLAY_DET        = oids+32;
+GSS_DLLIMP gss_const_OID GSS_C_MA_OOS_DET           = oids+33;
+GSS_DLLIMP gss_const_OID GSS_C_MA_CBINDINGS         = oids+34;
+GSS_DLLIMP gss_const_OID GSS_C_MA_PFS               = oids+35;
+GSS_DLLIMP gss_const_OID GSS_C_MA_COMPRESS          = oids+36;
+GSS_DLLIMP gss_const_OID GSS_C_MA_CTX_TRANS         = oids+37;
+GSS_DLLIMP gss_const_OID GSS_C_MA_NEGOEX_AND_SPNEGO = oids+38;
+
+GSS_DLLIMP gss_OID GSS_C_SEC_CONTEXT_SASL_SSF = oids+39;
+
+static gss_OID_set_desc gss_ma_known_attrs_desc = { 28, oids+11 };
 
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_CONCRETE     = oids+9;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_PSEUDO       = oids+10;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_COMPOSITE    = oids+11;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_NEGO         = oids+12;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_GLUE         = oids+13;
-GSS_DLLIMP gss_const_OID GSS_C_MA_NOT_MECH          = oids+14;
-GSS_DLLIMP gss_const_OID GSS_C_MA_DEPRECATED        = oids+15;
-GSS_DLLIMP gss_const_OID GSS_C_MA_NOT_DFLT_MECH     = oids+16;
-GSS_DLLIMP gss_const_OID GSS_C_MA_ITOK_FRAMED       = oids+17;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT         = oids+18;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG         = oids+19;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT_INIT    = oids+20;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG_INIT    = oids+21;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT_ANON    = oids+22;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG_ANON    = oids+23;
-GSS_DLLIMP gss_const_OID GSS_C_MA_DELEG_CRED        = oids+24;
-GSS_DLLIMP gss_const_OID GSS_C_MA_INTEG_PROT        = oids+25;
-GSS_DLLIMP gss_const_OID GSS_C_MA_CONF_PROT         = oids+26;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MIC               = oids+27;
-GSS_DLLIMP gss_const_OID GSS_C_MA_WRAP              = oids+28;
-GSS_DLLIMP gss_const_OID GSS_C_MA_PROT_READY        = oids+29;
-GSS_DLLIMP gss_const_OID GSS_C_MA_REPLAY_DET        = oids+30;
-GSS_DLLIMP gss_const_OID GSS_C_MA_OOS_DET           = oids+31;
-GSS_DLLIMP gss_const_OID GSS_C_MA_CBINDINGS         = oids+32;
-GSS_DLLIMP gss_const_OID GSS_C_MA_PFS               = oids+33;
-GSS_DLLIMP gss_const_OID GSS_C_MA_COMPRESS          = oids+34;
-GSS_DLLIMP gss_const_OID GSS_C_MA_CTX_TRANS         = oids+35;
-
-GSS_DLLIMP gss_OID GSS_C_SEC_CONTEXT_SASL_SSF = oids+36;
-
-static gss_OID_set_desc gss_ma_known_attrs_desc = { 27, oids+9 };
 gss_OID_set gss_ma_known_attrs = &gss_ma_known_attrs_desc;
 
 static struct mech_attr_info_desc {
@@ -237,170 +246,176 @@ static struct mech_attr_info_desc {
     const char *long_desc;
 } mech_attr_info[] = {
     {
-        oids+9,
+        oids+11,
         "GSS_C_MA_MECH_CONCRETE",
         "concrete-mech",
         "Mechanism is neither a pseudo-mechanism nor a composite mechanism.",
     },
     {
-        oids+10,
+        oids+12,
         "GSS_C_MA_MECH_PSEUDO",
         "pseudo-mech",
         "Mechanism is a pseudo-mechanism.",
     },
     {
-        oids+11,
+        oids+13,
         "GSS_C_MA_MECH_COMPOSITE",
         "composite-mech",
         "Mechanism is a composite of other mechanisms.",
     },
     {
-        oids+12,
+        oids+14,
         "GSS_C_MA_MECH_NEGO",
         "mech-negotiation-mech",
         "Mechanism negotiates other mechanisms.",
     },
     {
-        oids+13,
+        oids+15,
         "GSS_C_MA_MECH_GLUE",
         "mech-glue",
         "OID is not a mechanism but the GSS-API itself.",
     },
     {
-        oids+14,
+        oids+16,
         "GSS_C_MA_NOT_MECH",
         "not-mech",
         "Known OID but not a mechanism OID.",
     },
     {
-        oids+15,
+        oids+17,
         "GSS_C_MA_DEPRECATED",
         "mech-deprecated",
         "Mechanism is deprecated.",
     },
     {
-        oids+16,
+        oids+18,
         "GSS_C_MA_NOT_DFLT_MECH",
         "mech-not-default",
         "Mechanism must not be used as a default mechanism.",
     },
     {
-        oids+17,
+        oids+19,
         "GSS_C_MA_ITOK_FRAMED",
         "initial-is-framed",
         "Mechanism's initial contexts are properly framed.",
     },
     {
-        oids+18,
+        oids+20,
         "GSS_C_MA_AUTH_INIT",
         "auth-init-princ",
         "Mechanism supports authentication of initiator to acceptor.",
     },
     {
-        oids+19,
+        oids+21,
         "GSS_C_MA_AUTH_TARG",
         "auth-targ-princ",
         "Mechanism supports authentication of acceptor to initiator.",
     },
     {
-        oids+20,
+        oids+22,
         "GSS_C_MA_AUTH_INIT_INIT",
         "auth-init-princ-initial",
         "Mechanism supports authentication of initiator using "
         "initial credentials.",
     },
     {
-        oids+21,
+        oids+23,
         "GSS_C_MA_AUTH_TARG_INIT",
         "auth-target-princ-initial",
         "Mechanism supports authentication of acceptor using "
         "initial credentials.",
     },
     {
-        oids+22,
+        oids+24,
         "GSS_C_MA_AUTH_INIT_ANON",
         "auth-init-princ-anon",
         "Mechanism supports GSS_C_NT_ANONYMOUS as an initiator name.",
     },
     {
-        oids+23,
+        oids+25,
         "GSS_C_MA_AUTH_TARG_ANON",
         "auth-targ-princ-anon",
         "Mechanism supports GSS_C_NT_ANONYMOUS as an acceptor name.",
     },
     {
-        oids+24,
+        oids+26,
         "GSS_C_MA_DELEG_CRED",
         "deleg-cred",
         "Mechanism supports credential delegation.",
     },
     {
-        oids+25,
+        oids+27,
         "GSS_C_MA_INTEG_PROT",
         "integ-prot",
         "Mechanism supports per-message integrity protection.",
     },
     {
-        oids+26,
+        oids+28,
         "GSS_C_MA_CONF_PROT",
         "conf-prot",
         "Mechanism supports per-message confidentiality protection.",
     },
     {
-        oids+27,
+        oids+29,
         "GSS_C_MA_MIC",
         "mic",
         "Mechanism supports Message Integrity Code (MIC) tokens.",
     },
     {
-        oids+28,
+        oids+30,
         "GSS_C_MA_WRAP",
         "wrap",
         "Mechanism supports wrap tokens.",
     },
     {
-        oids+29,
+        oids+31,
         "GSS_C_MA_PROT_READY",
         "prot-ready",
         "Mechanism supports per-message proteciton prior to "
         "full context establishment.",
     },
     {
-        oids+30,
+        oids+32,
         "GSS_C_MA_REPLAY_DET",
         "replay-detection",
         "Mechanism supports replay detection.",
     },
     {
-        oids+31,
+        oids+33,
         "GSS_C_MA_OOS_DET",
         "oos-detection",
         "Mechanism supports out-of-sequence detection.",
     },
     {
-        oids+32,
+        oids+34,
         "GSS_C_MA_CBINDINGS",
         "channel-bindings",
         "Mechanism supports channel bindings.",
     },
     {
-        oids+33,
+        oids+35,
         "GSS_C_MA_PFS",
         "pfs",
         "Mechanism supports Perfect Forward Security.",
     },
     {
-        oids+34,
+        oids+36,
         "GSS_C_MA_COMPRESS",
         "compress",
         "Mechanism supports compression of data inputs to gss_wrap().",
     },
     {
-        oids+35,
+        oids+37,
         "GSS_C_MA_CTX_TRANS",
         "context-transfer",
         "Mechanism supports security context export/import.",
     },
+    {
+        oids+38,
+        "GSS_C_MA_NEGOEX_AND_SPNEGO",
+        "negoex-only",
+        "NegoEx mechanism should also be negotiable through SPNEGO.",
+    },
 };
 
 OM_uint32
diff --git a/src/lib/gssapi/libgssapi_krb5.exports b/src/lib/gssapi/libgssapi_krb5.exports
index c292cb1..166acfa 100644
--- a/src/lib/gssapi/libgssapi_krb5.exports
+++ b/src/lib/gssapi/libgssapi_krb5.exports
@@ -1,5 +1,7 @@
 GSS_C_ATTR_LOCAL_LOGIN_USER
 GSS_C_INQ_SSPI_SESSION_KEY
+GSS_C_INQ_NEGOEX_KEY
+GSS_C_INQ_NEGOEX_VERIFY_KEY
 GSS_C_NT_ANONYMOUS
 GSS_C_NT_COMPOSITE_EXPORT
 GSS_C_NT_EXPORT_NAME
@@ -39,6 +41,7 @@ GSS_C_MA_CBINDINGS
 GSS_C_MA_PFS
 GSS_C_MA_COMPRESS
 GSS_C_MA_CTX_TRANS
+GSS_C_MA_NEGOEX_AND_SPNEGO
 GSS_C_SEC_CONTEXT_SASL_SSF
 gss_accept_sec_context
 gss_acquire_cred
diff --git a/src/lib/gssapi/mechglue/Makefile.in b/src/lib/gssapi/mechglue/Makefile.in
index 5e1ed6d..33d9319 100644
--- a/src/lib/gssapi/mechglue/Makefile.in
+++ b/src/lib/gssapi/mechglue/Makefile.in
@@ -1,6 +1,7 @@
 mydir=lib$(S)gssapi$(S)mechglue
 BUILDTOP=$(REL)..$(S)..$(S)..
 LOCALINCLUDES = -I. -I$(srcdir) -I$(srcdir)/.. -I../generic -I$(srcdir)/../generic -I../krb5 -I$(srcdir)/../krb5 -I../spnego -I$(srcdir)/../spnego
+
 DEFINES=-D_GSS_STATIC_LINK=1
 
 ##DOSBUILDTOP = ..\..\..
@@ -49,6 +50,7 @@ SRCS = \
 	$(srcdir)/g_mech_invoke.c \
 	$(srcdir)/g_mechattr.c \
 	$(srcdir)/g_mechname.c \
+	$(srcdir)/g_negoex.c \
 	$(srcdir)/g_oid_ops.c \
 	$(srcdir)/g_prf.c \
 	$(srcdir)/g_process_context.c \
@@ -113,6 +115,7 @@ OBJS = \
 	$(OUTPRE)g_mech_invoke.$(OBJEXT) \
 	$(OUTPRE)g_mechattr.$(OBJEXT) \
 	$(OUTPRE)g_mechname.$(OBJEXT) \
+	$(OUTPRE)g_negoex.$(OBJEXT) \
 	$(OUTPRE)g_oid_ops.$(OBJEXT) \
 	$(OUTPRE)g_prf.$(OBJEXT) \
 	$(OUTPRE)g_process_context.$(OBJEXT) \
@@ -177,6 +180,7 @@ STLIBOBJS = \
 	g_mech_invoke.o \
 	g_mechattr.o \
 	g_mechname.o \
+	g_negoex.o \
 	g_oid_ops.o \
 	g_prf.o \
 	g_process_context.o \
diff --git a/src/lib/gssapi/mechglue/deps b/src/lib/gssapi/mechglue/deps
index 26f62aa..95fd7f4 100644
--- a/src/lib/gssapi/mechglue/deps
+++ b/src/lib/gssapi/mechglue/deps
@@ -234,10 +234,11 @@ g_initialize.so g_initialize.po $(OUTPRE)g_initialize.$(OBJEXT): \
   $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../generic/gssapiP_generic.h \
   $(srcdir)/../generic/gssapi_ext.h $(srcdir)/../generic/gssapi_generic.h \
   $(srcdir)/../krb5/gssapiP_krb5.h $(srcdir)/../krb5/gssapi_krb5.h \
-  $(srcdir)/../spnego/gssapiP_spnego.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 \
+  $(srcdir)/../spnego/gssapiP_negoex.h $(srcdir)/../spnego/gssapiP_spnego.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-queue.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 \
@@ -324,6 +325,14 @@ g_mechname.so g_mechname.po $(OUTPRE)g_mechname.$(OBJEXT): \
   $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-platform.h \
   $(top_srcdir)/include/k5-thread.h ../generic/gssapi_err_generic.h \
   g_mechname.c mechglue.h mglueP.h
+g_negoex.so g_negoex.po $(OUTPRE)g_negoex.$(OBJEXT): \
+  $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
+  $(BUILDTOP)/include/gssapi/gssapi_alloc.h $(BUILDTOP)/include/gssapi/gssapi_ext.h \
+  $(COM_ERR_DEPS) $(srcdir)/../generic/gssapiP_generic.h \
+  $(srcdir)/../generic/gssapi_ext.h $(srcdir)/../generic/gssapi_generic.h \
+  $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-platform.h \
+  $(top_srcdir)/include/k5-thread.h ../generic/gssapi_err_generic.h \
+  g_negoex.c mechglue.h mglueP.h
 g_oid_ops.so g_oid_ops.po $(OUTPRE)g_oid_ops.$(OBJEXT): \
   $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
   $(BUILDTOP)/include/gssapi/gssapi_alloc.h $(BUILDTOP)/include/gssapi/gssapi_ext.h \
diff --git a/src/lib/gssapi/mechglue/g_initialize.c b/src/lib/gssapi/mechglue/g_initialize.c
index 120d73e..2f9ce7a 100644
--- a/src/lib/gssapi/mechglue/g_initialize.c
+++ b/src/lib/gssapi/mechglue/g_initialize.c
@@ -770,6 +770,10 @@ build_dynamicMech(void *dl, const gss_OID mech_type)
 	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_sec_context_by_mech);
 	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_name_by_mech);
 	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_cred_by_mech);
+	/* draft-zhu-negoex */
+	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_query_meta_data);
+	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_exchange_meta_data);
+	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_query_mechanism_info);
 
 	assert(mech_type != GSS_C_NO_OID);
 
diff --git a/src/lib/gssapi/mechglue/g_negoex.c b/src/lib/gssapi/mechglue/g_negoex.c
new file mode 100644
index 0000000..9360fa4
--- /dev/null
+++ b/src/lib/gssapi/mechglue/g_negoex.c
@@ -0,0 +1,237 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This file contains dispatch functions for the three GSSAPI extensions
+ * described in draft-zhu-negoex-04, renamed to use the gssspi_ prefix.  Since
+ * the only caller of these functions is SPNEGO, argument validation is
+ * omitted.
+ */
+
+#include "mglueP.h"
+
+OM_uint32 KRB5_CALLCONV
+gssspi_query_meta_data(OM_uint32 *minor_status, gss_const_OID mech_oid,
+                       gss_cred_id_t cred_handle, gss_ctx_id_t *context_handle,
+                       const gss_name_t targ_name, OM_uint32 req_flags,
+                       gss_buffer_t meta_data)
+{
+    OM_uint32 status, minor;
+    gss_union_ctx_id_t ctx = (gss_union_ctx_id_t)*context_handle;
+    gss_union_cred_t cred = (gss_union_cred_t)cred_handle;
+    gss_union_name_t union_name = (gss_union_name_t)targ_name;
+    gss_mechanism mech;
+    gss_OID selected_mech, public_mech;
+    gss_cred_id_t internal_cred = GSS_C_NO_CREDENTIAL;
+    gss_name_t internal_name = GSS_C_NO_NAME, imported_name = GSS_C_NO_NAME;
+    gss_ctx_id_t new_ctx = GSS_C_NO_CONTEXT, *internal_ctx;
+
+    *minor_status = 0;
+    meta_data->length = 0;
+    meta_data->value = NULL;
+
+    status = gssint_select_mech_type(minor_status, mech_oid, &selected_mech);
+    if (status != GSS_S_COMPLETE)
+        return status;
+    public_mech = gssint_get_public_oid(selected_mech);
+
+    mech = gssint_get_mechanism(selected_mech);
+    if (mech == NULL)
+        return GSS_S_BAD_MECH;
+    if (mech->gssspi_query_meta_data == NULL)
+        return GSS_S_UNAVAILABLE;
+
+    if (cred != NULL) {
+        internal_cred = gssint_get_mechanism_cred(cred, selected_mech);
+        if (internal_cred == GSS_C_NO_CREDENTIAL)
+            return GSS_S_NO_CRED;
+    }
+
+    if (union_name != NULL) {
+        if (union_name->mech_type != GSS_C_NO_OID &&
+            g_OID_equal(union_name->mech_type, selected_mech)) {
+            internal_name = union_name->mech_name;
+        } else {
+            status = gssint_import_internal_name(minor_status, selected_mech,
+                                                 union_name, &imported_name);
+            if (status != GSS_S_COMPLETE)
+                goto cleanup;
+            internal_name = imported_name;
+        }
+    }
+
+    internal_ctx = (ctx != NULL) ? &ctx->internal_ctx_id : &new_ctx;
+    status = mech->gssspi_query_meta_data(minor_status, public_mech,
+                                          internal_cred, internal_ctx,
+                                          internal_name, req_flags, meta_data);
+    if (status != GSS_S_COMPLETE) {
+        map_error(minor_status, mech);
+        goto cleanup;
+    }
+
+    /* If the mech created a context, wrap it in a union context. */
+    if (new_ctx != GSS_C_NO_CONTEXT) {
+        assert(ctx == NULL);
+        status = gssint_create_union_context(minor_status, selected_mech,
+                                             &ctx);
+        if (status != GSS_S_COMPLETE)
+            goto cleanup;
+
+        ctx->internal_ctx_id = new_ctx;
+        new_ctx = GSS_C_NO_CONTEXT;
+        *context_handle = (gss_ctx_id_t)ctx;
+    }
+
+cleanup:
+    if (imported_name != GSS_C_NO_NAME) {
+        (void)gssint_release_internal_name(&minor, selected_mech,
+                                           &imported_name);
+    }
+    if (new_ctx != GSS_C_NO_CONTEXT) {
+        (void)gssint_delete_internal_sec_context(&minor, &mech->mech_type,
+                                                 &new_ctx, GSS_C_NO_BUFFER);
+    }
+    return status;
+}
+
+OM_uint32 KRB5_CALLCONV
+gssspi_exchange_meta_data(OM_uint32 *minor_status, gss_const_OID mech_oid,
+                          gss_cred_id_t cred_handle,
+                          gss_ctx_id_t *context_handle,
+                          const gss_name_t targ_name, OM_uint32 req_flags,
+                          gss_const_buffer_t meta_data)
+{
+    OM_uint32 status, minor;
+    gss_union_ctx_id_t ctx = (gss_union_ctx_id_t)*context_handle;
+    gss_union_cred_t cred = (gss_union_cred_t)cred_handle;
+    gss_union_name_t union_name = (gss_union_name_t)targ_name;
+    gss_mechanism mech;
+    gss_OID selected_mech, public_mech;
+    gss_cred_id_t internal_cred = GSS_C_NO_CREDENTIAL;
+    gss_name_t internal_name = GSS_C_NO_NAME, imported_name = GSS_C_NO_NAME;
+    gss_ctx_id_t new_ctx = GSS_C_NO_CONTEXT, *internal_ctx;
+
+    *minor_status = 0;
+
+    status = gssint_select_mech_type(minor_status, mech_oid, &selected_mech);
+    if (status != GSS_S_COMPLETE)
+        return status;
+    public_mech = gssint_get_public_oid(selected_mech);
+
+    mech = gssint_get_mechanism(selected_mech);
+    if (mech == NULL)
+        return GSS_S_BAD_MECH;
+    if (mech->gssspi_exchange_meta_data == NULL)
+        return GSS_S_UNAVAILABLE;
+
+    if (cred != NULL) {
+        internal_cred = gssint_get_mechanism_cred(cred, selected_mech);
+        if (internal_cred == GSS_C_NO_CREDENTIAL)
+            return GSS_S_NO_CRED;
+    }
+
+    if (union_name != NULL) {
+        if (union_name->mech_type != GSS_C_NO_OID &&
+            g_OID_equal(union_name->mech_type, selected_mech)) {
+            internal_name = union_name->mech_name;
+        } else {
+            status = gssint_import_internal_name(minor_status, selected_mech,
+                                                 union_name, &imported_name);
+            if (GSS_ERROR(status))
+                return status;
+            internal_name = imported_name;
+        }
+    }
+
+    internal_ctx = (ctx != NULL) ? &ctx->internal_ctx_id : &new_ctx;
+    status = mech->gssspi_exchange_meta_data(minor_status, public_mech,
+                                             internal_cred, internal_ctx,
+                                             internal_name, req_flags,
+                                             meta_data);
+    if (status != GSS_S_COMPLETE) {
+        map_error(minor_status, mech);
+        goto cleanup;
+    }
+
+    /* If the mech created a context, wrap it in a union context. */
+    if (new_ctx != GSS_C_NO_CONTEXT) {
+        assert(ctx == NULL);
+        status = gssint_create_union_context(minor_status, selected_mech,
+                                             &ctx);
+        if (status != GSS_S_COMPLETE)
+            goto cleanup;
+
+        ctx->internal_ctx_id = new_ctx;
+        new_ctx = GSS_C_NO_CONTEXT;
+        *context_handle = (gss_ctx_id_t)ctx;
+    }
+
+cleanup:
+    if (imported_name != GSS_C_NO_NAME) {
+        (void)gssint_release_internal_name(&minor, selected_mech,
+                                           &imported_name);
+    }
+    if (new_ctx != GSS_C_NO_CONTEXT) {
+        (void)gssint_delete_internal_sec_context(&minor, &mech->mech_type,
+                                                 &new_ctx, GSS_C_NO_BUFFER);
+    }
+    return status;
+}
+
+OM_uint32 KRB5_CALLCONV
+gssspi_query_mechanism_info(OM_uint32 *minor_status, gss_const_OID mech_oid,
+                            unsigned char auth_scheme[16])
+{
+    OM_uint32 status;
+    gss_OID selected_mech, public_mech;
+    gss_mechanism mech;
+
+    *minor_status = 0;
+    memset(auth_scheme, 0, 16);
+
+    status = gssint_select_mech_type(minor_status, mech_oid, &selected_mech);
+    if (status != GSS_S_COMPLETE)
+        return status;
+    public_mech = gssint_get_public_oid(selected_mech);
+
+    mech = gssint_get_mechanism(selected_mech);
+    if (mech == NULL)
+        return GSS_S_BAD_MECH;
+    if (mech->gssspi_query_mechanism_info == NULL)
+        return GSS_S_UNAVAILABLE;
+
+    status = mech->gssspi_query_mechanism_info(minor_status, public_mech,
+                                               auth_scheme);
+    if (GSS_ERROR(status))
+        map_error(minor_status, mech);
+
+    return status;
+}
diff --git a/src/lib/gssapi/mechglue/mglueP.h b/src/lib/gssapi/mechglue/mglueP.h
index c296354..f16333c 100644
--- a/src/lib/gssapi/mechglue/mglueP.h
+++ b/src/lib/gssapi/mechglue/mglueP.h
@@ -702,6 +702,37 @@ typedef struct gss_config {
 	    int				/* iov_count */
 	);
 
+	/* NegoEx extensions added in 1.18 */
+
+	OM_uint32	(KRB5_CALLCONV *gssspi_query_meta_data)
+	(
+	    OM_uint32 *,		/* minor_status */
+	    gss_const_OID,		/* mech_oid */
+	    gss_cred_id_t,		/* cred_handle */
+	    gss_ctx_id_t *,		/* context_handle */
+	    const gss_name_t,		/* targ_name */
+	    OM_uint32,			/* req_flags */
+	    gss_buffer_t		/* meta_data */
+	/* */);
+
+	OM_uint32	(KRB5_CALLCONV *gssspi_exchange_meta_data)
+	(
+	    OM_uint32 *,		/* minor_status */
+	    gss_const_OID,		/* mech_oid */
+	    gss_cred_id_t,		/* cred_handle */
+	    gss_ctx_id_t *,		/* context_handle */
+	    const gss_name_t,		/* targ_name */
+	    OM_uint32,			/* req_flags */
+	    gss_const_buffer_t		/* meta_data */
+	/* */);
+
+	OM_uint32	(KRB5_CALLCONV *gssspi_query_mechanism_info)
+	(
+	    OM_uint32 *,		/* minor_status */
+	    gss_const_OID,		/* mech_oid */
+	    unsigned char[16]		/* auth_scheme */
+	/* */);
+
 } *gss_mechanism;
 
 /*
diff --git a/src/lib/gssapi/spnego/Makefile.in b/src/lib/gssapi/spnego/Makefile.in
index c21ea23..b44ad0e 100644
--- a/src/lib/gssapi/spnego/Makefile.in
+++ b/src/lib/gssapi/spnego/Makefile.in
@@ -9,11 +9,12 @@ DEFINES=-D_GSS_STATIC_LINK=1
 
 ##DOS##DLL_EXP_TYPE=GSS
 
-SRCS = $(srcdir)/spnego_mech.c
+SRCS = $(srcdir)/spnego_mech.c $(srcdir)/negoex_ctx.c $(srcdir)/negoex_util.c
 
-OBJS = $(OUTPRE)spnego_mech.$(OBJEXT)
+OBJS = $(OUTPRE)spnego_mech.$(OBJEXT) $(OUTPRE)negoex_ctx.$(OBJEXT) \
+	$(OUTPRE)negoex_util.$(OBJEXT)
 
-STLIBOBJS = spnego_mech.o
+STLIBOBJS = spnego_mech.o negoex_ctx.o negoex_util.o
 
 all-unix: all-libobjs
 
diff --git a/src/lib/gssapi/spnego/deps b/src/lib/gssapi/spnego/deps
index feb409e..1b5daff 100644
--- a/src/lib/gssapi/spnego/deps
+++ b/src/lib/gssapi/spnego/deps
@@ -11,8 +11,40 @@ spnego_mech.so spnego_mech.po $(OUTPRE)spnego_mech.$(OBJEXT): \
   $(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-plugin.h $(top_srcdir)/include/k5-queue.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_negoex.h gssapiP_spnego.h spnego_mech.c
+negoex_ctx.so negoex_ctx.po $(OUTPRE)negoex_ctx.$(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 \
+  $(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-queue.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_negoex.h gssapiP_spnego.h negoex_ctx.c
+negoex_util.so negoex_util.po $(OUTPRE)negoex_util.$(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 \
+  $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+  $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-input.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-queue.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
+  ../generic/gssapi_err_generic.h gssapiP_negoex.h gssapiP_spnego.h \
+  negoex_util.c
diff --git a/src/lib/gssapi/spnego/gssapiP_negoex.h b/src/lib/gssapi/spnego/gssapiP_negoex.h
new file mode 100644
index 0000000..44b08f5
--- /dev/null
+++ b/src/lib/gssapi/spnego/gssapiP_negoex.h
@@ -0,0 +1,210 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011-2018 PADL Software Pty Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "k5-int.h"
+
+/*
+ * { iso(1) identified-organization(3) dod(6) internet(1) private(4)
+ *   enterprise(1) microsoft (311) security(2) mechanisms(2) negoex(30) }
+ */
+#define NEGOEX_OID_LENGTH 10
+#define NEGOEX_OID "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e"
+
+#define MESSAGE_SIGNATURE   0x535458454F47454EULL
+
+#define EXTENSION_LENGTH                    12
+
+#define EXTENSION_FLAG_CRITICAL             0x80000000
+
+#define CHECKSUM_SCHEME_RFC3961             1
+
+#define NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM  23
+#define NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM   25
+
+#define CHECKSUM_HEADER_LENGTH              20
+
+#define GUID_LENGTH                         16
+
+typedef uint8_t auth_scheme[GUID_LENGTH];
+typedef uint8_t conversation_id[GUID_LENGTH];
+#define GUID_EQ(a, b) (memcmp(a, b, GUID_LENGTH) == 0)
+
+#define NEGO_MESSAGE_HEADER_LENGTH          96
+#define EXCHANGE_MESSAGE_HEADER_LENGTH      64
+#define VERIFY_MESSAGE_HEADER_LENGTH        80
+#define ALERT_MESSAGE_HEADER_LENGTH         72
+#define ALERT_LENGTH                        12
+#define ALERT_PULSE_LENGTH                  8
+
+#define ALERT_TYPE_PULSE                    1
+#define ALERT_VERIFY_NO_KEY                 1
+
+enum message_type {
+    INITIATOR_NEGO = 0,         /* NEGO_MESSAGE */
+    ACCEPTOR_NEGO,              /* NEGO_MESSAGE */
+    INITIATOR_META_DATA,        /* EXCHANGE_MESSAGE */
+    ACCEPTOR_META_DATA,         /* EXCHANGE_MESSAGE */
+    CHALLENGE,                  /* EXCHANGE_MESSAGE */
+    AP_REQUEST,                 /* EXCHANGE_MESSAGE */
+    VERIFY,                     /* VERIFY_MESSAGE */
+    ALERT,                      /* ALERT */
+};
+
+struct nego_message {
+    uint8_t random[32];
+    const uint8_t *schemes;
+    uint16_t nschemes;
+};
+
+struct exchange_message {
+    auth_scheme scheme;
+    gss_buffer_desc token;
+};
+
+struct verify_message {
+    auth_scheme scheme;
+    uint32_t cksum_type;
+    const uint8_t *cksum;
+    size_t cksum_len;
+    size_t offset_in_token;
+};
+
+struct alert_message {
+    auth_scheme scheme;
+    int verify_no_key;
+};
+
+struct negoex_message {
+    uint32_t type;
+    union {
+        struct nego_message n;
+        struct exchange_message e;
+        struct verify_message v;
+        struct alert_message a;
+    } u;
+};
+
+struct negoex_auth_mech {
+    K5_TAILQ_ENTRY(negoex_auth_mech) links;
+    gss_OID oid;
+    auth_scheme scheme;
+    gss_ctx_id_t mech_context;
+    gss_buffer_desc metadata;
+    krb5_keyblock key;
+    krb5_keyblock verify_key;
+    int complete;
+    int sent_checksum;
+    int verified_checksum;
+};
+
+/* negoex_util.c */
+
+OM_uint32
+negoex_parse_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+                   gss_const_buffer_t token,
+                   struct negoex_message **messages_out, size_t *count_out);
+
+
+struct nego_message *
+negoex_locate_nego_message(struct negoex_message *messages, size_t nmessages,
+                           enum message_type type);
+struct exchange_message *
+negoex_locate_exchange_message(struct negoex_message *messages,
+                               size_t nmessages, enum message_type type);
+struct verify_message *
+negoex_locate_verify_message(struct negoex_message *messages,
+                             size_t nmessages);
+struct alert_message *
+negoex_locate_alert_message(struct negoex_message *messages, size_t nmessages);
+
+void
+negoex_add_nego_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+                        uint8_t random[32]);
+void
+negoex_add_exchange_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+                            const auth_scheme scheme, gss_buffer_t token);
+void
+negoex_add_verify_message(spnego_gss_ctx_id_t ctx, const auth_scheme scheme,
+                          uint32_t cksum_type, const uint8_t *cksum,
+                          uint32_t cksum_len);
+
+void
+negoex_add_verify_no_key_alert(spnego_gss_ctx_id_t ctx,
+                               const auth_scheme scheme);
+
+OM_uint32
+negoex_random(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+              unsigned char *data, size_t length);
+
+void
+negoex_prep_context_for_spnego(spnego_gss_ctx_id_t ctx);
+
+OM_uint32
+negoex_prep_context_for_negoex(OM_uint32 *minor, spnego_gss_ctx_id_t ctx);
+
+void
+negoex_release_context(spnego_gss_ctx_id_t ctx);
+
+OM_uint32
+negoex_add_auth_mech(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+                     gss_const_OID oid, auth_scheme scheme);
+
+void
+negoex_delete_auth_mech(spnego_gss_ctx_id_t ctx,
+                        struct negoex_auth_mech *mech);
+
+void
+negoex_select_auth_mech(spnego_gss_ctx_id_t ctx,
+                        struct negoex_auth_mech *mech);
+
+struct negoex_auth_mech *
+negoex_locate_auth_scheme(spnego_gss_ctx_id_t ctx, const auth_scheme scheme);
+
+void
+negoex_common_auth_schemes(spnego_gss_ctx_id_t ctx,
+                           const uint8_t *schemes, uint16_t nschemes);
+
+void
+negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx,
+                             const uint8_t *schemes, uint16_t nschemes);
+
+/* negoex_ctx.c */
+
+OM_uint32
+negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+            gss_name_t target_name, OM_uint32 req_flags, OM_uint32 time_req,
+            gss_buffer_t input_token, gss_buffer_t output_token,
+            OM_uint32 *time_rec);
+
+OM_uint32
+negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+              gss_buffer_t input_token, gss_buffer_t output_token,
+              OM_uint32 *time_rec);
diff --git a/src/lib/gssapi/spnego/gssapiP_spnego.h b/src/lib/gssapi/spnego/gssapiP_spnego.h
index cad47ee..a937633 100644
--- a/src/lib/gssapi/spnego/gssapiP_spnego.h
+++ b/src/lib/gssapi/spnego/gssapiP_spnego.h
@@ -12,7 +12,12 @@
 extern "C" {
 #endif
 
+typedef struct spnego_ctx_st *spnego_gss_ctx_id_t;
+
 #include <gssapi/gssapi.h>
+#include <gssapi/gssapi_ext.h>
+#include <k5-queue.h>
+#include "gssapiP_negoex.h"
 
 #define	SEC_CONTEXT_TOKEN 1
 #define	SPNEGO_SIZE_OF_INT 4
@@ -41,13 +46,27 @@ extern "C" {
 #define GENERAL_STRING		0x1b
 
 /*
- * SPNEGO specific error codes (minor status codes)
+ * SPNEGO and NegoEx 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
+#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
+#define ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE		0x20000006
+#define ERR_NEGOEX_INVALID_MESSAGE_TYPE			0x20000007
+#define ERR_NEGOEX_INVALID_MESSAGE_SIZE			0x20000008
+#define ERR_NEGOEX_INVALID_CONVERSATION_ID		0x20000009
+#define ERR_NEGOEX_AUTH_SCHEME_NOT_FOUND		0x20000010
+#define ERR_NEGOEX_MISSING_NEGO_MESSAGE			0x20000011
+#define ERR_NEGOEX_MISSING_AP_REQUEST_MESSAGE		0x20000012
+#define ERR_NEGOEX_NO_AVAILABLE_MECHS			0x20000013
+#define ERR_NEGOEX_NO_VERIFY_KEY			0x20000014
+#define ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME		0x20000015
+#define ERR_NEGOEX_INVALID_CHECKSUM			0x20000016
+#define ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION	0x20000017
+#define ERR_NEGOEX_UNSUPPORTED_VERSION			0x20000018
+#define ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE		0x20000019
 
 /*
  * send_token_flag is used to indicate in later steps what type
@@ -89,7 +108,7 @@ typedef struct {
 } spnego_gss_cred_id_rec, *spnego_gss_cred_id_t;
 
 /* Structure for context handle */
-typedef struct {
+struct spnego_ctx_st {
 	OM_uint32	magic_num;
 	gss_buffer_desc DER_mechTypes;
 	gss_OID_set mech_set;
@@ -107,7 +126,13 @@ typedef struct {
 	gss_name_t internal_name;
 	gss_OID actual_mech;
 	gss_cred_id_t deleg_cred;
-} spnego_gss_ctx_id_rec, *spnego_gss_ctx_id_t;
+	int negoex_step;
+	struct k5buf negoex_transcript;
+	uint32_t negoex_seqnum;
+	conversation_id negoex_conv_id;
+	K5_TAILQ_HEAD(negoex_mech_list, negoex_auth_mech) negoex_mechs;
+	krb5_context kctx;
+};
 
 /*
  * The magic number must be less than a standard pagesize
diff --git a/src/lib/gssapi/spnego/negoex_ctx.c b/src/lib/gssapi/spnego/negoex_ctx.c
new file mode 100644
index 0000000..e69b720
--- /dev/null
+++ b/src/lib/gssapi/spnego/negoex_ctx.c
@@ -0,0 +1,785 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011-2018 PADL Software Pty Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "k5-platform.h"
+#include "gssapiP_spnego.h"
+#include <generic/gssapiP_generic.h>
+
+/*
+ * The initial context token emitted by the initiator is a INITIATOR_NEGO
+ * message followed by zero or more INITIATOR_META_DATA tokens, and zero
+ * or one AP_REQUEST tokens.
+ *
+ * Upon receiving this, the acceptor computes the list of mutually supported
+ * authentication mechanisms and performs the metadata exchange. The output
+ * token is ACCEPTOR_NEGO followed by zero or more ACCEPTOR_META_DATA tokens,
+ * and zero or one CHALLENGE tokens.
+ *
+ * Once the metadata exchange is complete and a mechanism is selected, the
+ * selected mechanism's context token exchange continues with AP_REQUEST and
+ * CHALLENGE messages.
+ *
+ * Once the context token exchange is complete, VERIFY messages are sent to
+ * authenticate the entire exchange.
+ */
+
+static void
+zero_and_release_buffer_set(gss_buffer_set_t *pbuffers)
+{
+    OM_uint32 tmpmin;
+    gss_buffer_set_t buffers = *pbuffers;
+    uint32_t i;
+
+    if (buffers != GSS_C_NO_BUFFER_SET) {
+        for (i = 0; i < buffers->count; i++)
+            zap(buffers->elements[i].value, buffers->elements[i].length);
+
+        gss_release_buffer_set(&tmpmin, &buffers);
+    }
+
+    *pbuffers = GSS_C_NO_BUFFER_SET;
+}
+
+static OM_uint32
+buffer_set_to_key(OM_uint32 *minor, gss_buffer_set_t buffers,
+                  krb5_keyblock *key)
+{
+    krb5_error_code ret;
+
+    /* Returned keys must be in two buffers, with the key contents in the first
+     * and the enctype as a 32-bit little-endian integer in the second. */
+    if (buffers->count != 2 || buffers->elements[1].length != 4) {
+        *minor = ERR_NEGOEX_NO_VERIFY_KEY;
+        return GSS_S_FAILURE;
+    }
+
+    krb5_free_keyblock_contents(NULL, key);
+
+    key->contents = k5memdup(buffers->elements[0].value,
+                             buffers->elements[0].length, &ret);
+    if (key->contents == NULL) {
+        *minor = ret;
+        return GSS_S_FAILURE;
+    }
+    key->length = buffers->elements[0].length;
+    key->enctype = load_32_le(buffers->elements[1].value);
+
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+get_session_keys(OM_uint32 *minor, struct negoex_auth_mech *mech)
+{
+    OM_uint32 major, tmpmin;
+    gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET;
+
+    major = gss_inquire_sec_context_by_oid(&tmpmin, mech->mech_context,
+                                           GSS_C_INQ_NEGOEX_KEY, &buffers);
+    if (major == GSS_S_COMPLETE) {
+        major = buffer_set_to_key(minor, buffers, &mech->key);
+        zero_and_release_buffer_set(&buffers);
+        if (major != GSS_S_COMPLETE)
+            return major;
+    }
+
+    major = gss_inquire_sec_context_by_oid(&tmpmin, mech->mech_context,
+                                           GSS_C_INQ_NEGOEX_VERIFY_KEY,
+                                           &buffers);
+    if (major == GSS_S_COMPLETE) {
+        major = buffer_set_to_key(minor, buffers, &mech->verify_key);
+        zero_and_release_buffer_set(&buffers);
+        if (major != GSS_S_COMPLETE)
+            return major;
+    }
+
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+emit_initiator_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
+{
+    OM_uint32 major;
+    uint8_t random[32];
+
+    major = negoex_random(minor, ctx, random, 32);
+    if (major != GSS_S_COMPLETE)
+        return major;
+
+    negoex_add_nego_message(ctx, INITIATOR_NEGO, random);
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+process_initiator_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+                       struct negoex_message *messages, size_t nmessages)
+{
+    struct nego_message *msg;
+
+    assert(!ctx->initiate && ctx->negoex_step == 1);
+
+    msg = negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO);
+    if (msg == NULL) {
+        *minor = ERR_NEGOEX_MISSING_NEGO_MESSAGE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes);
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+emit_acceptor_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
+{
+    OM_uint32 major;
+    uint8_t random[32];
+
+    major = negoex_random(minor, ctx, random, 32);
+    if (major != GSS_S_COMPLETE)
+        return major;
+
+    negoex_add_nego_message(ctx, ACCEPTOR_NEGO, random);
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+process_acceptor_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+                      struct negoex_message *messages, size_t nmessages)
+{
+    struct nego_message *msg;
+
+    msg = negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO);
+    if (msg == NULL) {
+        *minor = ERR_NEGOEX_MISSING_NEGO_MESSAGE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    /* Reorder and prune our mech list to match the acceptor's list (or a
+     * subset of it). */
+    negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes);
+
+    return GSS_S_COMPLETE;
+}
+
+static void
+query_meta_data(spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+                gss_name_t target, OM_uint32 req_flags)
+{
+    OM_uint32 major, minor;
+    struct negoex_auth_mech *p, *next;
+
+    K5_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) {
+        major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context,
+                                       target, req_flags, &p->metadata);
+        /* GSS_Query_meta_data failure removes mechanism from list. */
+        if (major != GSS_S_COMPLETE)
+            negoex_delete_auth_mech(ctx, p);
+    }
+}
+
+static void
+exchange_meta_data(spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+                   gss_name_t target, OM_uint32 req_flags,
+                   struct negoex_message *messages, size_t nmessages)
+{
+    OM_uint32 major, minor;
+    struct negoex_auth_mech *mech;
+    enum message_type type;
+    struct exchange_message *msg;
+    uint32_t i;
+
+    type = ctx->initiate ? ACCEPTOR_META_DATA : INITIATOR_META_DATA;
+
+    for (i = 0; i < nmessages; i++) {
+        if (messages[i].type != type)
+            continue;
+        msg = &messages[i].u.e;
+
+        mech = negoex_locate_auth_scheme(ctx, msg->scheme);
+        if (mech == NULL)
+            continue;
+
+        major = gssspi_exchange_meta_data(&minor, mech->oid, cred,
+                                          &mech->mech_context, target,
+                                          req_flags, &msg->token);
+        /* GSS_Exchange_meta_data failure removes mechanism from list. */
+        if (major != GSS_S_COMPLETE)
+            negoex_delete_auth_mech(ctx, mech);
+    }
+}
+
+/*
+ * In the initiator, if we are processing the acceptor's first reply, discard
+ * the optimistic context if the acceptor ignored the optimistic token.  If the
+ * acceptor continued the optimistic mech, discard all other mechs.
+ */
+static void
+check_optimistic_result(spnego_gss_ctx_id_t ctx,
+                        struct negoex_message *messages, size_t nmessages)
+{
+    struct negoex_auth_mech *mech;
+    OM_uint32 tmpmin;
+
+    assert(ctx->initiate && ctx->negoex_step == 2);
+
+    /* Do nothing if we didn't make an optimistic context. */
+    mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+    if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
+        return;
+
+    /* If the acceptor used the optimistic token, it will send an acceptor
+     * token or a checksum (or both) in its first reply. */
+    if (negoex_locate_exchange_message(messages, nmessages,
+                                       CHALLENGE) != NULL ||
+        negoex_locate_verify_message(messages, nmessages) != NULL) {
+        /* The acceptor continued the optimistic mech, and metadata exchange
+         * didn't remove it.  Commit to this mechanism. */
+        negoex_select_auth_mech(ctx, mech);
+    } else {
+        /* The acceptor ignored the optimistic token.  Restart the mech. */
+        (void)gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
+        krb5_free_keyblock_contents(NULL, &mech->key);
+        krb5_free_keyblock_contents(NULL, &mech->verify_key);
+        mech->complete = mech->sent_checksum = FALSE;
+    }
+}
+
+/* Perform an initiator step of the underlying mechanism exchange. */
+static OM_uint32
+mech_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+          gss_name_t target, OM_uint32 req_flags, OM_uint32 time_req,
+          struct negoex_message *messages, size_t nmessages,
+          gss_buffer_t output_token, OM_uint32 *time_rec)
+{
+    OM_uint32 major, first_major = 0, first_minor = 0;
+    struct negoex_auth_mech *mech = NULL;
+    gss_buffer_t input_token = GSS_C_NO_BUFFER;
+    struct exchange_message *msg;
+    int first_mech;
+
+    output_token->value = NULL;
+    output_token->length = 0;
+
+    /* Allow disabling of optimistic token for testing. */
+    if (ctx->negoex_step == 1 &&
+        secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL)
+        return GSS_S_COMPLETE;
+
+    if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+        *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS;
+        return GSS_S_FAILURE;
+    }
+
+    /*
+     * Get the input token.  The challenge could be for the optimistic mech,
+     * which we might have discarded in metadata exchange, so ignore the
+     * challenge if it doesn't match the first auth mech.
+     */
+    mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+    msg = negoex_locate_exchange_message(messages, nmessages, CHALLENGE);
+    if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme))
+        input_token = &msg->token;
+
+    if (mech->complete)
+        return GSS_S_COMPLETE;
+
+    first_mech = TRUE;
+
+    while (!K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+        mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+
+        major = gss_init_sec_context(minor, cred, &mech->mech_context, target,
+                                     mech->oid, req_flags, time_req,
+                                     GSS_C_NO_CHANNEL_BINDINGS, input_token,
+                                     &ctx->actual_mech, output_token,
+                                     &ctx->ctx_flags, time_rec);
+
+        if (major == GSS_S_COMPLETE)
+            mech->complete = 1;
+
+        if (!GSS_ERROR(major))
+            return get_session_keys(minor, mech);
+
+        /* Remember the error we got from the first mech. */
+        if (first_mech) {
+            first_major = major;
+            first_minor = *minor;
+        }
+
+        /* If we still have multiple mechs to try, move on to the next one. */
+        negoex_delete_auth_mech(ctx, mech);
+        first_mech = FALSE;
+        input_token = GSS_C_NO_BUFFER;
+    }
+
+    if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+        major = first_major;
+        *minor = first_minor;
+    }
+
+    return major;
+}
+
+/* Perform an acceptor step of the underlying mechanism exchange. */
+static OM_uint32
+mech_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+            gss_cred_id_t cred, struct negoex_message *messages,
+            size_t nmessages, gss_buffer_t output_token, OM_uint32 *time_rec)
+{
+    OM_uint32 major, tmpmin;
+    struct negoex_auth_mech *mech;
+    struct exchange_message *msg;
+
+    assert(!ctx->initiate && !K5_TAILQ_EMPTY(&ctx->negoex_mechs));
+
+    msg = negoex_locate_exchange_message(messages, nmessages, AP_REQUEST);
+    if (msg == NULL) {
+        /* No input token is okay on the first request or if the mech is
+         * complete. */
+        if (ctx->negoex_step == 1 ||
+            K5_TAILQ_FIRST(&ctx->negoex_mechs)->complete)
+            return GSS_S_COMPLETE;
+        *minor = ERR_NEGOEX_MISSING_AP_REQUEST_MESSAGE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    if (ctx->negoex_step == 1) {
+        /* Ignore the optimistic token if it isn't for our most preferred
+         * mech. */
+        mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+        if (!GUID_EQ(msg->scheme, mech->scheme))
+            return GSS_S_COMPLETE;
+    } else {
+        /* The initiator has selected a mech; discard other entries. */
+        mech = negoex_locate_auth_scheme(ctx, msg->scheme);
+        if (mech == NULL) {
+            *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS;
+            return GSS_S_FAILURE;
+        }
+        negoex_select_auth_mech(ctx, mech);
+    }
+
+    if (mech->complete)
+        return GSS_S_COMPLETE;
+
+    if (ctx->internal_name != GSS_C_NO_NAME)
+        gss_release_name(&tmpmin, &ctx->internal_name);
+    if (ctx->deleg_cred != GSS_C_NO_CREDENTIAL)
+        gss_release_cred(&tmpmin, &ctx->deleg_cred);
+
+    major = gss_accept_sec_context(minor, &mech->mech_context, cred,
+                                   &msg->token, GSS_C_NO_CHANNEL_BINDINGS,
+                                   &ctx->internal_name, &ctx->actual_mech,
+                                   output_token, &ctx->ctx_flags,
+                                   time_rec, &ctx->deleg_cred);
+
+    if (major == GSS_S_COMPLETE)
+        mech->complete = 1;
+
+    if (!GSS_ERROR(major)) {
+        major = get_session_keys(minor, mech);
+    } else if (ctx->negoex_step == 1) {
+        /* This was an optimistic token; pretend this never happened. */
+        major = GSS_S_COMPLETE;
+        *minor = 0;
+        gss_release_buffer(&tmpmin, output_token);
+        gss_delete_sec_context(&tmpmin, &mech->mech_context, GSS_C_NO_BUFFER);
+    }
+
+    return major;
+}
+
+static krb5_keyusage
+verify_keyusage(spnego_gss_ctx_id_t ctx, int make_checksum)
+{
+    /* Of course, these are the wrong way around in the spec. */
+    return (ctx->initiate ^ !make_checksum) ?
+        NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM;
+}
+
+static OM_uint32
+verify_checksum(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+                struct negoex_message *messages, size_t nmessages,
+                gss_buffer_t input_token, int *send_alert_out)
+{
+    krb5_error_code ret;
+    struct negoex_auth_mech *mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+    struct verify_message *msg;
+    krb5_crypto_iov iov[3];
+    krb5_keyusage usage = verify_keyusage(ctx, FALSE);
+    krb5_boolean valid;
+
+    *send_alert_out = FALSE;
+    assert(mech != NULL);
+
+    /* The other party may not be ready to send a verify token yet, or (in the
+     * first initiator step) may send one for a mechanism we don't support. */
+    msg = negoex_locate_verify_message(messages, nmessages);
+    if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme))
+        return GSS_S_COMPLETE;
+
+    /* A recoverable error may cause us to be unable to verify a token from the
+     * other party.  In this case we should send an alert. */
+    if (mech->verify_key.enctype == ENCTYPE_NULL) {
+        *send_alert_out = TRUE;
+        return GSS_S_COMPLETE;
+    }
+
+    /* Verify the checksum over the existing transcript and the portion of the
+     * input token leading up to the verify message. */
+    iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
+    iov[0].data = make_data(ctx->negoex_transcript.data,
+                            ctx->negoex_transcript.len);
+    iov[1].flags = KRB5_CRYPTO_TYPE_DATA;
+    iov[1].data = make_data(input_token->value, msg->offset_in_token);
+    iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
+    iov[2].data = make_data((uint8_t *)msg->cksum, msg->cksum_len);
+
+    ret = krb5_c_verify_checksum_iov(ctx->kctx, msg->cksum_type,
+                                     &mech->verify_key, usage, iov, 3, &valid);
+    if (ret) {
+        *minor = ret;
+        return GSS_S_FAILURE;
+    }
+    if (!valid || !krb5_c_is_keyed_cksum(msg->cksum_type)) {
+        *minor = ERR_NEGOEX_INVALID_CHECKSUM;
+        return GSS_S_BAD_SIG;
+    }
+
+    mech->verified_checksum = TRUE;
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+make_checksum(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
+{
+    krb5_error_code ret;
+    krb5_data d;
+    krb5_keyusage usage = verify_keyusage(ctx, TRUE);
+    krb5_checksum cksum;
+    struct negoex_auth_mech *mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+
+    assert(mech != NULL);
+
+    if (mech->key.enctype == ENCTYPE_NULL) {
+        if (mech->complete) {
+            *minor = ERR_NEGOEX_NO_VERIFY_KEY;
+            return GSS_S_UNAVAILABLE;
+        } else {
+            return GSS_S_COMPLETE;
+        }
+    }
+
+    d = make_data(ctx->negoex_transcript.data, ctx->negoex_transcript.len);
+    ret = krb5_c_make_checksum(ctx->kctx, 0, &mech->key, usage, &d, &cksum);
+    if (ret) {
+        *minor = ret;
+        return GSS_S_FAILURE;
+    }
+
+    negoex_add_verify_message(ctx, mech->scheme, cksum.checksum_type,
+                              cksum.contents, cksum.length);
+
+    mech->sent_checksum = TRUE;
+    krb5_free_checksum_contents(ctx->kctx, &cksum);
+    return GSS_S_COMPLETE;
+}
+
+/* If the other side sent a VERIFY_NO_KEY pulse alert, clear the checksum state
+ * on the mechanism so that we send another VERIFY message. */
+static void
+process_alerts(spnego_gss_ctx_id_t ctx,
+               struct negoex_message *messages, uint32_t nmessages)
+{
+    struct alert_message *msg;
+    struct negoex_auth_mech *mech;
+
+    msg = negoex_locate_alert_message(messages, nmessages);
+    if (msg != NULL && msg->verify_no_key) {
+        mech = negoex_locate_auth_scheme(ctx, msg->scheme);
+        if (mech != NULL) {
+            mech->sent_checksum = FALSE;
+            krb5_free_keyblock_contents(NULL, &mech->key);
+            krb5_free_keyblock_contents(NULL, &mech->verify_key);
+        }
+    }
+}
+
+static OM_uint32
+make_output_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+                  gss_buffer_t mech_output_token, int send_alert,
+                  gss_buffer_t output_token)
+{
+    OM_uint32 major;
+    struct negoex_auth_mech *mech;
+    enum message_type type;
+    size_t old_transcript_len = ctx->negoex_transcript.len;
+
+    output_token->length = 0;
+    output_token->value = NULL;
+
+    /* If the mech is complete and we previously sent a checksum, we just
+     * processed the last leg and don't need to send another token. */
+    if (mech_output_token->length == 0 &&
+        K5_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum)
+        return GSS_S_COMPLETE;
+
+    if (ctx->negoex_step == 1) {
+        if (ctx->initiate)
+            major = emit_initiator_nego(minor, ctx);
+        else
+            major = emit_acceptor_nego(minor, ctx);
+        if (major != GSS_S_COMPLETE)
+            return major;
+
+        type = ctx->initiate ? INITIATOR_META_DATA : ACCEPTOR_META_DATA;
+        K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
+            if (mech->metadata.length > 0) {
+                negoex_add_exchange_message(ctx, type, mech->scheme,
+                                            &mech->metadata);
+            }
+        }
+    }
+
+    mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+
+    if (mech_output_token->length > 0) {
+        type = ctx->initiate ? AP_REQUEST : CHALLENGE;
+        negoex_add_exchange_message(ctx, type, mech->scheme,
+                                    mech_output_token);
+    }
+
+    if (send_alert)
+        negoex_add_verify_no_key_alert(ctx, mech->scheme);
+
+    /* Try to add a VERIFY message if we haven't already done so. */
+    if (!mech->sent_checksum) {
+        major = make_checksum(minor, ctx);
+        if (major != GSS_S_COMPLETE)
+            return major;
+    }
+
+    if (ctx->negoex_transcript.data == NULL) {
+        *minor = ENOMEM;
+        return GSS_S_FAILURE;
+    }
+
+    /* Copy what we added to the transcript into the output token. */
+    output_token->length = ctx->negoex_transcript.len - old_transcript_len;
+    output_token->value = gssalloc_malloc(output_token->length);
+    if (output_token->value == NULL) {
+        *minor = ENOMEM;
+        return GSS_S_FAILURE;
+    }
+    memcpy(output_token->value,
+           (uint8_t *)ctx->negoex_transcript.data + old_transcript_len,
+           output_token->length);
+
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32
+negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+            gss_name_t target_name, OM_uint32 req_flags, OM_uint32 time_req,
+            gss_buffer_t input_token, gss_buffer_t output_token,
+            OM_uint32 *time_rec)
+{
+    OM_uint32 major, tmpmin;
+    gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
+    struct negoex_message *messages = NULL;
+    struct negoex_auth_mech *mech;
+    size_t nmessages = 0;
+    int send_alert = FALSE;
+
+    if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER &&
+        input_token->length != 0)
+        return GSS_S_DEFECTIVE_TOKEN;
+
+    major = negoex_prep_context_for_negoex(minor, ctx);
+    if (major != GSS_S_COMPLETE)
+        goto cleanup;
+
+    ctx->negoex_step++;
+
+    if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) {
+        major = negoex_parse_token(minor, ctx, input_token, &messages,
+                                   &nmessages);
+        if (major != GSS_S_COMPLETE)
+            goto cleanup;
+    }
+
+    process_alerts(ctx, messages, nmessages);
+
+    if (ctx->negoex_step == 1) {
+        /* Choose a random conversation ID. */
+        major = negoex_random(minor, ctx, ctx->negoex_conv_id, GUID_LENGTH);
+        if (major != GSS_S_COMPLETE)
+            goto cleanup;
+
+        /* Query each mech for its metadata (this may prune the mech list). */
+        query_meta_data(ctx, cred, target_name, req_flags);
+    } else if (ctx->negoex_step == 2) {
+        /* See if the mech processed the optimistic token. */
+        check_optimistic_result(ctx, messages, nmessages);
+
+        /* Pass the acceptor metadata to each mech to prune the list. */
+        exchange_meta_data(ctx, cred, target_name, req_flags,
+                           messages, nmessages);
+
+        /* Process the ACCEPTOR_NEGO message. */
+        major = process_acceptor_nego(minor, ctx, messages, nmessages);
+        if (major != GSS_S_COMPLETE)
+            goto cleanup;
+    }
+
+    /* Process the input token and/or produce an output token.  This may prune
+     * the mech list, but on success there will be at least one mech entry. */
+    major = mech_init(minor, ctx, cred, target_name, req_flags, time_req,
+                      messages, nmessages, &mech_output_token, time_rec);
+    if (major != GSS_S_COMPLETE)
+        goto cleanup;
+    assert(!K5_TAILQ_EMPTY(&ctx->negoex_mechs));
+
+    /* At this point in step 2 we have performed the metadata exchange and
+     * chosen a mech we can use, so discard any fallback mech entries. */
+    if (ctx->negoex_step == 2)
+        negoex_select_auth_mech(ctx, K5_TAILQ_FIRST(&ctx->negoex_mechs));
+
+    major = verify_checksum(minor, ctx, messages, nmessages, input_token,
+                            &send_alert);
+    if (major != GSS_S_COMPLETE)
+        goto cleanup;
+
+    if (input_token != GSS_C_NO_BUFFER) {
+        k5_buf_add_len(&ctx->negoex_transcript, input_token->value,
+                       input_token->length);
+    }
+
+    major = make_output_token(minor, ctx, &mech_output_token, send_alert,
+                              output_token);
+    if (major != GSS_S_COMPLETE)
+        goto cleanup;
+
+    mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+    major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
+        GSS_S_CONTINUE_NEEDED;
+
+cleanup:
+    free(messages);
+    gss_release_buffer(&tmpmin, &mech_output_token);
+    negoex_prep_context_for_spnego(ctx);
+    return major;
+}
+
+OM_uint32
+negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+              gss_buffer_t input_token, gss_buffer_t output_token,
+              OM_uint32 *time_rec)
+{
+    OM_uint32 major, tmpmin;
+    gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
+    struct negoex_message *messages = NULL;
+    struct negoex_auth_mech *mech;
+    size_t nmessages;
+    int send_alert = FALSE;
+
+    if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
+        major = GSS_S_DEFECTIVE_TOKEN;
+        goto cleanup;
+    }
+
+    major = negoex_prep_context_for_negoex(minor, ctx);
+    if (major != GSS_S_COMPLETE)
+        goto cleanup;
+
+    ctx->negoex_step++;
+
+    major = negoex_parse_token(minor, ctx, input_token, &messages, &nmessages);
+    if (major != GSS_S_COMPLETE)
+        goto cleanup;
+
+    process_alerts(ctx, messages, nmessages);
+
+    if (ctx->negoex_step == 1) {
+        /* Read the INITIATOR_NEGO message to prune the candidate mech list. */
+        major = process_initiator_nego(minor, ctx, messages, nmessages);
+        if (major != GSS_S_COMPLETE)
+            goto cleanup;
+
+        /*
+         * Pass the initiator metadata to each mech to prune the list, and
+         * query each mech for its acceptor metadata (which may also prune the
+         * list).
+         */
+        exchange_meta_data(ctx, cred, GSS_C_NO_NAME, 0, messages, nmessages);
+        query_meta_data(ctx, cred, GSS_C_NO_NAME, 0);
+
+        if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+            *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS;
+            major = GSS_S_FAILURE;
+            goto cleanup;
+        }
+    }
+
+    /*
+     * Process the input token and possibly produce an output token.  This may
+     * prune the list to a single mech.  Continue on error if an output token
+     * is generated, so that we send the token to the initiator.
+     */
+    major = mech_accept(minor, ctx, cred, messages, nmessages,
+                        &mech_output_token, time_rec);
+    if (major != GSS_S_COMPLETE && mech_output_token.length == 0)
+        goto cleanup;
+
+    if (major == GSS_S_COMPLETE) {
+        major = verify_checksum(minor, ctx, messages, nmessages, input_token,
+                                &send_alert);
+        if (major != GSS_S_COMPLETE)
+            goto cleanup;
+    }
+
+    k5_buf_add_len(&ctx->negoex_transcript,
+                   input_token->value, input_token->length);
+
+    major = make_output_token(minor, ctx, &mech_output_token, send_alert,
+                              output_token);
+    if (major != GSS_S_COMPLETE)
+        goto cleanup;
+
+    mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+    major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
+        GSS_S_CONTINUE_NEEDED;
+
+cleanup:
+    free(messages);
+    gss_release_buffer(&tmpmin, &mech_output_token);
+    negoex_prep_context_for_spnego(ctx);
+    return major;
+}
diff --git a/src/lib/gssapi/spnego/negoex_trace.c b/src/lib/gssapi/spnego/negoex_trace.c
new file mode 100644
index 0000000..04ed992
--- /dev/null
+++ b/src/lib/gssapi/spnego/negoex_trace.c
@@ -0,0 +1,121 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011-2018 PADL Software Pty Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "gssapiP_spnego.h"
+
+static int
+guid_to_string(const uint8_t guid[16], char *buffer, size_t bufsiz)
+{
+    uint32_t data1;
+    uint16_t data2, data3;
+
+    data1 = load_32_le(guid);
+    data2 = load_16_le(guid + 4);
+    data3 = load_16_le(guid + 6);
+
+    return snprintf(buffer, bufsiz,
+                    "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+                    data1, data2, data3, guid[8], guid[9], guid[10], guid[11],
+                    guid[12], guid[13], guid[14], guid[15]);
+}
+
+static void
+trace_auth_scheme(spnego_gss_ctx_id_t ctx, const char *prefix, int ind,
+                  const auth_scheme scheme)
+{
+    char trace_msg[128];
+    char szAuthScheme[37];
+
+    guid_to_string(scheme, szAuthScheme, sizeof(szAuthScheme));
+
+    snprintf(trace_msg, sizeof(trace_msg),
+             "NEGOEXTS: %20s[%02u] -- AuthScheme %s",
+             prefix, ind, szAuthScheme);
+    TRACE_NEGOEX_AUTH_SCHEMES(ctx->kctx, trace_msg);
+}
+
+void
+negoex_trace_auth_schemes(spnego_gss_ctx_id_t ctx, const char *prefix,
+                          const uint8_t *schemes, uint16_t nschemes)
+{
+    uint16_t i;
+
+    for (i = 0; i < nschemes; i++)
+        trace_auth_scheme(ctx, prefix, i, schemes + i * GUID_LENGTH);
+}
+
+void
+negoex_trace_ctx_auth_schemes(spnego_gss_ctx_id_t ctx, const char *prefix)
+{
+    negoex_auth_mech_t mech;
+    int ind = 0;
+
+    K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
+        trace_auth_scheme(ctx, prefix, ind++, mech->scheme);
+}
+
+void
+negoex_trace_message(spnego_gss_ctx_id_t ctx, int direction,
+                     enum message_type type, const conversation_id conv_id,
+                     unsigned int seqnum, unsigned int header_len,
+                     unsigned int msg_len)
+{
+    char trace_msg[128];
+    char conv_str[37];
+    char *typestr;
+
+    if (type == INITIATOR_NEGO)
+        typestr = "INITIATOR_NEGO";
+    else if (type == ACCEPTOR_NEGO)
+        typestr = "ACCEPTOR_NEGO";
+    else if (type == INITIATOR_META_DATA)
+        typestr = "INITIATOR_META_DATA";
+    else if (type == ACCEPTOR_META_DATA)
+        typestr = "ACCEPTOR_META_DATA";
+    else if (type == CHALLENGE)
+        typestr = "CHALLENGE";
+    else if (type == AP_REQUEST)
+        typestr = "AP_REQUEST";
+    else if (type == VERIFY)
+        typestr = "VERIFY";
+    else if (type == ALERT)
+        typestr = "ALERT";
+    else
+        typestr = "UNKNOWN";
+
+    guid_to_string(conv_id, conv_str, sizeof(conv_str));
+    snprintf(trace_msg, sizeof(trace_msg),
+             "NEGOEXTS%c %20s[%02u] -- ConvId %s HdrLength %u MsgLength %u",
+             direction ? '<' : '>', typestr, seqnum, conv_str, header_len,
+             msg_len);
+
+    TRACE_NEGOEX_MESSAGE(ctx->kctx, trace_msg);
+}
diff --git a/src/lib/gssapi/spnego/negoex_util.c b/src/lib/gssapi/spnego/negoex_util.c
new file mode 100644
index 0000000..7003684
--- /dev/null
+++ b/src/lib/gssapi/spnego/negoex_util.c
@@ -0,0 +1,812 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011-2018 PADL Software Pty Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "gssapiP_spnego.h"
+#include <generic/gssapiP_generic.h>
+#include "k5-input.h"
+
+static void
+release_auth_mech(struct negoex_auth_mech *mech);
+
+OM_uint32
+negoex_random(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+              uint8_t *data, size_t length)
+{
+    krb5_data d = make_data(data, length);
+
+    *minor = krb5_c_random_make_octets(ctx->kctx, &d);
+    return *minor ? GSS_S_FAILURE : GSS_S_COMPLETE;
+}
+
+/*
+ * SPNEGO functions expect to find the active mech context in ctx->ctx_handle,
+ * but the metadata exchange APIs force us to have one mech context per mech
+ * entry.  To address this mismatch, move the active mech context (if we have
+ * one) to ctx->ctx_handle at the end of NegoEx processing.
+ */
+void
+negoex_prep_context_for_spnego(spnego_gss_ctx_id_t ctx)
+{
+    struct negoex_auth_mech *mech;
+
+    mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+    if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
+        return;
+
+    assert(ctx->ctx_handle == GSS_C_NO_CONTEXT);
+    ctx->ctx_handle = mech->mech_context;
+    mech->mech_context = GSS_C_NO_CONTEXT;
+}
+
+OM_uint32
+negoex_prep_context_for_negoex(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
+{
+    krb5_error_code ret;
+    struct negoex_auth_mech *mech;
+
+    if (ctx->kctx != NULL) {
+        /* The context is already initialized for NegoEx.  Undo what
+         * negoex_prep_for_spnego() did, if applicable. */
+        if (ctx->ctx_handle != GSS_C_NO_CONTEXT) {
+            mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+            assert(mech != NULL && mech->mech_context == GSS_C_NO_CONTEXT);
+            mech->mech_context = ctx->ctx_handle;
+            ctx->ctx_handle = GSS_C_NO_CONTEXT;
+        }
+        return GSS_S_COMPLETE;
+    }
+
+    /* Initialize the NegoEX context fields.  (negoex_mechs is already set up
+     * by SPNEGO.) */
+    ret = krb5_init_context(&ctx->kctx);
+    if (ret) {
+        *minor = ret;
+        return GSS_S_FAILURE;
+    }
+
+    k5_buf_init_dynamic(&ctx->negoex_transcript);
+
+    return GSS_S_COMPLETE;
+}
+
+static void
+release_all_mechs(spnego_gss_ctx_id_t ctx)
+{
+    struct negoex_auth_mech *mech, *next;
+
+    K5_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next)
+        release_auth_mech(mech);
+    K5_TAILQ_INIT(&ctx->negoex_mechs);
+}
+
+void
+negoex_release_context(spnego_gss_ctx_id_t ctx)
+{
+    k5_buf_free(&ctx->negoex_transcript);
+    release_all_mechs(ctx);
+    krb5_free_context(ctx->kctx);
+    ctx->kctx = NULL;
+}
+
+static const char *
+typestr(enum message_type type)
+{
+    if (type == INITIATOR_NEGO)
+        return "INITIATOR_NEGO";
+    else if (type == ACCEPTOR_NEGO)
+        return "ACCEPTOR_NEGO";
+    else if (type == INITIATOR_META_DATA)
+        return "INITIATOR_META_DATA";
+    else if (type == ACCEPTOR_META_DATA)
+        return "ACCEPTOR_META_DATA";
+    else if (type == CHALLENGE)
+        return "CHALLENGE";
+    else if (type == AP_REQUEST)
+        return "AP_REQUEST";
+    else if (type == VERIFY)
+        return "VERIFY";
+    else if (type == ALERT)
+        return "ALERT";
+    else
+        return "UNKNOWN";
+}
+
+static void
+add_guid(struct k5buf *buf, const uint8_t guid[GUID_LENGTH])
+{
+    uint32_t data1 = load_32_le(guid);
+    uint16_t data2 = load_16_le(guid + 4), data3 = load_16_le(guid + 6);
+
+    k5_buf_add_fmt(buf, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+                   data1, data2, data3, guid[8], guid[9], guid[10], guid[11],
+                   guid[12], guid[13], guid[14], guid[15]);
+}
+
+static char *
+guid_to_string(const uint8_t guid[GUID_LENGTH])
+{
+    struct k5buf buf;
+
+    k5_buf_init_dynamic(&buf);
+    add_guid(&buf, guid);
+    return buf.data;
+}
+
+/* Check that the described vector lies within the message, and return a
+ * pointer to its first element. */
+static inline const uint8_t *
+vector_base(size_t offset, size_t count, size_t width,
+            const uint8_t *msg_base, size_t msg_len)
+{
+    if (offset > msg_len || count > (msg_len - offset) / width)
+        return NULL;
+    return msg_base + offset;
+}
+
+/* Trace a received message.  Call after the context sequence number is
+ * incremented. */
+static void
+trace_received_message(spnego_gss_ctx_id_t ctx,
+                       const struct negoex_message *msg)
+{
+    struct k5buf buf;
+    uint16_t i;
+    char *info = NULL;
+
+    if (msg->type == INITIATOR_NEGO || msg->type == ACCEPTOR_NEGO) {
+        k5_buf_init_dynamic(&buf);
+        for (i = 0; i < msg->u.n.nschemes; i++) {
+            add_guid(&buf, msg->u.n.schemes + i * GUID_LENGTH);
+            if (i + 1 < msg->u.n.nschemes)
+                k5_buf_add(&buf, " ");
+        }
+        info = buf.data;
+    } else if (msg->type == INITIATOR_META_DATA ||
+               msg->type == ACCEPTOR_META_DATA ||
+               msg->type == CHALLENGE || msg->type == AP_REQUEST) {
+        info = guid_to_string(msg->u.e.scheme);
+    } else if (msg->type == VERIFY) {
+        info = guid_to_string(msg->u.v.scheme);
+    } else if (msg->type == ALERT) {
+        info = guid_to_string(msg->u.a.scheme);
+    }
+
+    if (info == NULL)
+        return;
+
+    TRACE_NEGOEX_INCOMING(ctx->kctx, ctx->negoex_seqnum - 1,
+                          typestr(msg->type), info);
+    free(info);
+}
+
+/* Trace an outgoing message with a GUID info string.  Call after the context
+ * sequence number is incremented. */
+static void
+trace_outgoing_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+                       const uint8_t guid[GUID_LENGTH])
+{
+    char *info = guid_to_string(guid);
+
+    if (info == NULL)
+        return;
+    TRACE_NEGOEX_OUTGOING(ctx->kctx, ctx->negoex_seqnum - 1, typestr(type),
+                          info);
+    free(info);
+}
+
+static OM_uint32
+parse_nego_message(OM_uint32 *minor, struct k5input *in,
+                   const uint8_t *msg_base, size_t msg_len,
+                   struct nego_message *msg)
+{
+    const uint8_t *p;
+    uint64_t protocol_version;
+    uint32_t extension_type;
+    size_t offset, count, i;
+
+    p = k5_input_get_bytes(in, sizeof(msg->random));
+    if (p != NULL)
+        memcpy(msg->random, p, sizeof(msg->random));
+    protocol_version = k5_input_get_uint64_le(in);
+    if (protocol_version != 0) {
+        *minor = ERR_NEGOEX_UNSUPPORTED_VERSION;
+        return GSS_S_UNAVAILABLE;
+    }
+
+    offset = k5_input_get_uint32_le(in);
+    count = k5_input_get_uint16_le(in);
+    msg->schemes = vector_base(offset, count, GUID_LENGTH, msg_base, msg_len);
+    msg->nschemes = count;
+    if (msg->schemes == NULL) {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    offset = k5_input_get_uint32_le(in);
+    count = k5_input_get_uint16_le(in);
+    p = vector_base(offset, count, EXTENSION_LENGTH, msg_base, msg_len);
+    for (i = 0; i < count; i++) {
+        extension_type = load_32_le(p + i * EXTENSION_LENGTH);
+        if (extension_type & EXTENSION_FLAG_CRITICAL) {
+            *minor = ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION;
+            return GSS_S_UNAVAILABLE;
+        }
+    }
+
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_exchange_message(OM_uint32 *minor, struct k5input *in,
+                       const uint8_t *msg_base, size_t msg_len,
+                       struct exchange_message *msg)
+{
+    const uint8_t *p;
+    size_t offset, len;
+
+    p = k5_input_get_bytes(in, GUID_LENGTH);
+    if (p != NULL)
+        memcpy(msg->scheme, p, GUID_LENGTH);
+
+    offset = k5_input_get_uint32_le(in);
+    len = k5_input_get_uint32_le(in);
+    p = vector_base(offset, len, 1, msg_base, msg_len);
+    if (p == NULL) {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+    msg->token.value = (void *)p;
+    msg->token.length = len;
+
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_verify_message(OM_uint32 *minor, struct k5input *in,
+                     const uint8_t *msg_base, size_t msg_len,
+                     size_t token_offset, struct verify_message *msg)
+{
+    const uint8_t *p;
+    size_t offset, len;
+    uint32_t hdrlen, cksum_scheme;
+
+    p = k5_input_get_bytes(in, GUID_LENGTH);
+    if (p != NULL)
+        memcpy(msg->scheme, p, GUID_LENGTH);
+
+    hdrlen = k5_input_get_uint32_le(in);
+    if (hdrlen != CHECKSUM_HEADER_LENGTH) {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+    cksum_scheme = k5_input_get_uint32_le(in);
+    if (cksum_scheme != CHECKSUM_SCHEME_RFC3961) {
+        *minor = ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME;
+        return GSS_S_UNAVAILABLE;
+    }
+    msg->cksum_type = k5_input_get_uint32_le(in);
+
+    offset = k5_input_get_uint32_le(in);
+    len = k5_input_get_uint32_le(in);
+    msg->cksum = vector_base(offset, len, 1, msg_base, msg_len);
+    msg->cksum_len = len;
+    if (msg->cksum == NULL) {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    msg->offset_in_token = token_offset;
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_alert_message(OM_uint32 *minor, struct k5input *in,
+                    const uint8_t *msg_base, size_t msg_len,
+                    struct alert_message *msg)
+{
+    const uint8_t *p;
+    uint32_t atype, reason;
+    size_t alerts_offset, nalerts, value_offset, value_len, i;
+    struct k5input alerts_in, pulse_in;
+
+    p = k5_input_get_bytes(in, GUID_LENGTH);
+    if (p != NULL)
+        memcpy(msg->scheme, p, GUID_LENGTH);
+    (void)k5_input_get_uint32_le(in);  /* skip over ErrorCode */
+    alerts_offset = k5_input_get_uint32_le(in);
+    nalerts = k5_input_get_uint32_le(in);
+    p = vector_base(alerts_offset, nalerts, ALERT_LENGTH, msg_base, msg_len);
+    if (p == NULL) {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    /* Look for a VERIFY_NO_KEY pulse alert in the alerts vector. */
+    msg->verify_no_key = FALSE;
+    k5_input_init(&alerts_in, p, nalerts * ALERT_LENGTH);
+    for (i = 0; i < nalerts; i++) {
+        atype = k5_input_get_uint32_le(&alerts_in);
+        value_offset = k5_input_get_uint32_le(&alerts_in);
+        value_len = k5_input_get_uint32_le(&alerts_in);
+        p = vector_base(value_offset, value_len, 1, msg_base, msg_len);
+        if (p == NULL) {
+            *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+            return GSS_S_DEFECTIVE_TOKEN;
+        }
+
+        if (atype == ALERT_TYPE_PULSE && value_len >= ALERT_PULSE_LENGTH) {
+            k5_input_init(&pulse_in, p, value_len);
+            (void)k5_input_get_uint32_le(&pulse_in);  /* skip header length */
+            reason = k5_input_get_uint32_le(&pulse_in);
+            if (reason == ALERT_VERIFY_NO_KEY)
+                msg->verify_no_key = TRUE;
+        }
+    }
+
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_message(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, struct k5input *in,
+              const uint8_t *token_base, struct negoex_message *msg)
+{
+    OM_uint32 major;
+    const uint8_t *msg_base = in->ptr, *conv_id;
+    size_t token_remaining = in->len, header_len, msg_len;
+    uint64_t signature;
+    uint32_t type, seqnum;
+
+    signature = k5_input_get_uint64_le(in);
+    type = k5_input_get_uint32_le(in);
+    seqnum = k5_input_get_uint32_le(in);
+    header_len = k5_input_get_uint32_le(in);
+    msg_len = k5_input_get_uint32_le(in);
+    conv_id = k5_input_get_bytes(in, GUID_LENGTH);
+
+    if (in->status || msg_len > token_remaining || header_len > msg_len) {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+    if (signature != MESSAGE_SIGNATURE) {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+    if (seqnum != ctx->negoex_seqnum) {
+        *minor = ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+    if (seqnum == 0) {
+        memcpy(ctx->negoex_conv_id, conv_id, GUID_LENGTH);
+    } else if (!GUID_EQ(conv_id, ctx->negoex_conv_id)) {
+        *minor = ERR_NEGOEX_INVALID_CONVERSATION_ID;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    /* Restrict the input region to the header. */
+    in->len = header_len - (in->ptr - msg_base);
+
+    msg->type = type;
+    if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) {
+        major = parse_nego_message(minor, in, msg_base, msg_len, &msg->u.n);
+    } else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
+               type == CHALLENGE || type == AP_REQUEST) {
+        major = parse_exchange_message(minor, in, msg_base, msg_len,
+                                       &msg->u.e);
+    } else if (type == VERIFY) {
+        major = parse_verify_message(minor, in, msg_base, msg_len,
+                                     msg_base - token_base, &msg->u.v);
+    } else if (type == ALERT) {
+        major = parse_alert_message(minor, in, msg_base, msg_len, &msg->u.a);
+    } else {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_TYPE;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+    if (major != GSS_S_COMPLETE)
+        return major;
+
+    /* Reset the input buffer to the remainder of the token. */
+    if (!in->status)
+        k5_input_init(in, msg_base + msg_len, token_remaining - msg_len);
+
+    ctx->negoex_seqnum++;
+    trace_received_message(ctx, msg);
+    return GSS_S_COMPLETE;
+}
+
+/*
+ * Parse token into an array of negoex_message structures.  All pointer fields
+ * within the parsed messages are aliases into token, so the result can be
+ * freed with free().  An unknown protocol version, a critical extension, or an
+ * unknown checksum scheme will cause a parsing failure.  Increment the
+ * sequence number in ctx for each message, and record and check the
+ * conversation ID in ctx as appropriate.
+ */
+OM_uint32
+negoex_parse_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+                   gss_const_buffer_t token,
+                   struct negoex_message **messages_out, size_t *count_out)
+{
+    OM_uint32 major;
+    size_t count = 0;
+    struct k5input in;
+    struct negoex_message *messages = NULL, *newptr;
+
+    *messages_out = NULL;
+    *count_out = 0;
+    assert(token != GSS_C_NO_BUFFER);
+    k5_input_init(&in, token->value, token->length);
+
+    while (in.status == 0 && in.len > 0) {
+        newptr = realloc(messages, (count + 1) * sizeof(*newptr));
+        if (newptr == NULL) {
+            free(messages);
+            *minor = ENOMEM;
+            return GSS_S_FAILURE;
+        }
+        messages = newptr;
+
+        major = parse_message(minor, ctx, &in, token->value, &messages[count]);
+        if (major != GSS_S_COMPLETE)
+            break;
+
+        count++;
+    }
+
+    if (in.status) {
+        *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+        major = GSS_S_DEFECTIVE_TOKEN;
+    }
+    if (major != GSS_S_COMPLETE) {
+        free(messages);
+        return major;
+    }
+
+    *messages_out = messages;
+    *count_out = count;
+    return GSS_S_COMPLETE;
+}
+
+static struct negoex_message *
+locate_message(struct negoex_message *messages, size_t nmessages,
+               enum message_type type)
+{
+    uint32_t i;
+
+    for (i = 0; i < nmessages; i++) {
+        if (messages[i].type == type)
+            return &messages[i];
+    }
+
+    return NULL;
+}
+
+struct nego_message *
+negoex_locate_nego_message(struct negoex_message *messages, size_t nmessages,
+                           enum message_type type)
+{
+    struct negoex_message *msg = locate_message(messages, nmessages, type);
+
+    return (msg == NULL) ? NULL : &msg->u.n;
+}
+
+struct exchange_message *
+negoex_locate_exchange_message(struct negoex_message *messages,
+                               size_t nmessages, enum message_type type)
+{
+    struct negoex_message *msg = locate_message(messages, nmessages, type);
+
+    return (msg == NULL) ? NULL : &msg->u.e;
+}
+
+struct verify_message *
+negoex_locate_verify_message(struct negoex_message *messages,
+                             size_t nmessages)
+{
+    struct negoex_message *msg = locate_message(messages, nmessages, VERIFY);
+
+    return (msg == NULL) ? NULL : &msg->u.v;
+}
+
+struct alert_message *
+negoex_locate_alert_message(struct negoex_message *messages, size_t nmessages)
+{
+    struct negoex_message *msg = locate_message(messages, nmessages, ALERT);
+
+    return (msg == NULL) ? NULL : &msg->u.a;
+}
+
+/*
+ * Add the encoding of a MESSAGE_HEADER structure to buf, given the number of
+ * bytes of the payload following the full header.  Increment the sequence
+ * number in ctx.  Set *payload_start_out to the position of the payload within
+ * the message.
+ */
+static void
+put_message_header(spnego_gss_ctx_id_t ctx, enum message_type type,
+                   uint32_t payload_len, uint32_t *payload_start_out)
+{
+    size_t header_len;
+
+    if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO)
+        header_len = NEGO_MESSAGE_HEADER_LENGTH;
+    else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
+             type == CHALLENGE || type == AP_REQUEST)
+        header_len = EXCHANGE_MESSAGE_HEADER_LENGTH;
+    else if (type == VERIFY)
+        header_len = VERIFY_MESSAGE_HEADER_LENGTH;
+    else if (type == ALERT)
+        header_len = ALERT_MESSAGE_HEADER_LENGTH;
+    else
+        abort();
+
+    k5_buf_add_uint64_le(&ctx->negoex_transcript, MESSAGE_SIGNATURE);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, type);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, ctx->negoex_seqnum++);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, header_len);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, header_len + payload_len);
+    k5_buf_add_len(&ctx->negoex_transcript, ctx->negoex_conv_id, GUID_LENGTH);
+
+    *payload_start_out = header_len;
+}
+
+void
+negoex_add_nego_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+                        uint8_t random[32])
+{
+    struct negoex_auth_mech *mech;
+    uint32_t payload_start, seqnum = ctx->negoex_seqnum;
+    uint16_t nschemes;
+    struct k5buf buf;
+
+    nschemes = 0;
+    K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
+        nschemes++;
+
+    put_message_header(ctx, type, nschemes * GUID_LENGTH, &payload_start);
+    k5_buf_add_len(&ctx->negoex_transcript, random, 32);
+    /* ProtocolVersion */
+    k5_buf_add_uint64_le(&ctx->negoex_transcript, 0);
+    /* AuthSchemes vector */
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+    k5_buf_add_uint16_le(&ctx->negoex_transcript, nschemes);
+    /* Extensions vector */
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+    k5_buf_add_uint16_le(&ctx->negoex_transcript, 0);
+    /* Four bytes of padding to reach a multiple of 8 bytes. */
+    k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0", 4);
+
+    /* Payload (auth schemes); also build guid string for tracing. */
+    k5_buf_init_dynamic(&buf);
+    K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
+        k5_buf_add_len(&ctx->negoex_transcript, mech->scheme, GUID_LENGTH);
+        add_guid(&buf, mech->scheme);
+        k5_buf_add(&buf, " ");
+    }
+
+    if (buf.len > 0) {
+        k5_buf_truncate(&buf, buf.len - 1);
+        TRACE_NEGOEX_OUTGOING(ctx->kctx, seqnum, typestr(type), buf.data);
+        k5_buf_free(&buf);
+    }
+}
+
+void
+negoex_add_exchange_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+                            const auth_scheme scheme, gss_buffer_t token)
+{
+    uint32_t payload_start;
+
+    put_message_header(ctx, type, token->length, &payload_start);
+    k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
+    /* Exchange byte vector */
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, token->length);
+    /* Payload (token) */
+    k5_buf_add_len(&ctx->negoex_transcript, token->value, token->length);
+
+    trace_outgoing_message(ctx, type, scheme);
+}
+
+void
+negoex_add_verify_message(spnego_gss_ctx_id_t ctx, const auth_scheme scheme,
+                          uint32_t cksum_type, const uint8_t *cksum,
+                          uint32_t cksum_len)
+{
+    uint32_t payload_start;
+
+    put_message_header(ctx, VERIFY, cksum_len, &payload_start);
+    k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, CHECKSUM_HEADER_LENGTH);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, CHECKSUM_SCHEME_RFC3961);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, cksum_type);
+    /* ChecksumValue vector */
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, cksum_len);
+    /* Four bytes of padding to reach a multiple of 8 bytes. */
+    k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0", 4);
+    /* Payload (checksum contents) */
+    k5_buf_add_len(&ctx->negoex_transcript, cksum, cksum_len);
+
+    trace_outgoing_message(ctx, VERIFY, scheme);
+}
+
+/* Add an ALERT_MESSAGE containing a single ALERT_TYPE_PULSE alert with the
+ * reason ALERT_VERIFY_NO_KEY. */
+void
+negoex_add_verify_no_key_alert(spnego_gss_ctx_id_t ctx,
+                               const auth_scheme scheme)
+{
+    uint32_t payload_start;
+
+    put_message_header(ctx, ALERT, ALERT_LENGTH + ALERT_PULSE_LENGTH,
+                       &payload_start);
+    k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
+    /* ErrorCode */
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, 0);
+    /* Alerts vector */
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+    k5_buf_add_uint16_le(&ctx->negoex_transcript, 1);
+    /* Six bytes of padding to reach a multiple of 8 bytes. */
+    k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0\0\0", 6);
+    /* Payload part 1: a single ALERT element */
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_TYPE_PULSE);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript,
+                         payload_start + ALERT_LENGTH);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_PULSE_LENGTH);
+    /* Payload part 2: ALERT_PULSE */
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_PULSE_LENGTH);
+    k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_VERIFY_NO_KEY);
+
+    trace_outgoing_message(ctx, ALERT, scheme);
+}
+
+static void
+release_auth_mech(struct negoex_auth_mech *mech)
+{
+    OM_uint32 tmpmin;
+
+    if (mech == NULL)
+        return;
+
+    gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
+    generic_gss_release_oid(&tmpmin, &mech->oid);
+    gss_release_buffer(&tmpmin, &mech->metadata);
+    krb5_free_keyblock_contents(NULL, &mech->key);
+    krb5_free_keyblock_contents(NULL, &mech->verify_key);
+
+    free(mech);
+}
+
+void
+negoex_delete_auth_mech(spnego_gss_ctx_id_t ctx,
+                        struct negoex_auth_mech *mech)
+{
+    K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+    release_auth_mech(mech);
+}
+
+/* Remove all auth mech entries except for mech from ctx->mechs. */
+void
+negoex_select_auth_mech(spnego_gss_ctx_id_t ctx,
+                        struct negoex_auth_mech *mech)
+{
+    assert(mech != NULL);
+    K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+    release_all_mechs(ctx);
+    K5_TAILQ_INSERT_HEAD(&ctx->negoex_mechs, mech, links);
+}
+
+OM_uint32
+negoex_add_auth_mech(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+                     gss_const_OID oid, auth_scheme scheme)
+{
+    OM_uint32 major;
+    struct negoex_auth_mech *mech;
+
+    mech = calloc(1, sizeof(*mech));
+    if (mech == NULL) {
+        *minor = ENOMEM;
+        return GSS_S_FAILURE;
+    }
+
+    major = generic_gss_copy_oid(minor, (gss_OID)oid, &mech->oid);
+    if (major != GSS_S_COMPLETE) {
+        free(mech);
+        return major;
+    }
+
+    memcpy(mech->scheme, scheme, GUID_LENGTH);
+
+    K5_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links);
+
+    *minor = 0;
+    return GSS_S_COMPLETE;
+}
+
+struct negoex_auth_mech *
+negoex_locate_auth_scheme(spnego_gss_ctx_id_t ctx, const auth_scheme scheme)
+{
+    struct negoex_auth_mech *mech;
+
+    K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
+        if (GUID_EQ(mech->scheme, scheme))
+            return mech;
+    }
+
+    return NULL;
+}
+
+/* Prune ctx->mechs to the schemes present in schemes, and reorder them to
+ * match its order. */
+void
+negoex_common_auth_schemes(spnego_gss_ctx_id_t ctx,
+                           const uint8_t *schemes, uint16_t nschemes)
+{
+    struct negoex_mech_list list;
+    struct negoex_auth_mech *mech;
+    uint16_t i;
+
+    /* Construct a new list in the order of schemes. */
+    K5_TAILQ_INIT(&list);
+    for (i = 0; i < nschemes; i++) {
+        mech = negoex_locate_auth_scheme(ctx, schemes + i * GUID_LENGTH);
+        if (mech == NULL)
+            continue;
+        K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+        K5_TAILQ_INSERT_TAIL(&list, mech, links);
+    }
+
+    /* Release any leftover entries and replace the context list. */
+    release_all_mechs(ctx);
+    K5_TAILQ_CONCAT(&ctx->negoex_mechs, &list, links);
+}
+
+/* Prune ctx->mechs to the schemes present in schemes, but do not change
+ * their order. */
+void
+negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx,
+                             const uint8_t *schemes, uint16_t nschemes)
+{
+    struct negoex_auth_mech *mech, *next;
+    uint16_t i;
+    int found;
+
+    K5_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) {
+        found = FALSE;
+        for (i = 0; i < nschemes && !found; i++) {
+            if (GUID_EQ(mech->scheme, schemes + i * GUID_LENGTH))
+                found = TRUE;
+        }
+
+        if (!found)
+            negoex_delete_auth_mech(ctx, mech);
+    }
+}
diff --git a/src/lib/gssapi/spnego/spnego_mech.c b/src/lib/gssapi/spnego/spnego_mech.c
index 7aa03e7..8e0c3a3 100644
--- a/src/lib/gssapi/spnego/spnego_mech.c
+++ b/src/lib/gssapi/spnego/spnego_mech.c
@@ -98,8 +98,8 @@ 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 OM_uint32 get_negotiable_mechs(OM_uint32 *, spnego_gss_ctx_id_t,
+				      spnego_gss_cred_id_t, gss_cred_usage_t);
 static void release_spnego_ctx(spnego_gss_ctx_id_t *);
 static spnego_gss_ctx_id_t create_spnego_ctx(int);
 static int put_mech_set(gss_OID_set mechSet, gss_buffer_t buf);
@@ -148,7 +148,7 @@ acc_ctx_call_acc(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t,
 		 send_token_flag *);
 
 static gss_OID
-negotiate_mech(gss_OID_set, gss_OID_set, OM_uint32 *);
+negotiate_mech(spnego_gss_ctx_id_t, gss_OID_set, OM_uint32 *);
 static int
 g_get_tag_and_length(unsigned char **, int, unsigned int, unsigned int *);
 
@@ -185,6 +185,8 @@ static const gss_OID_set_desc spnego_oidsets[] = {
 };
 const gss_OID_set_desc * const gss_mech_set_spnego = spnego_oidsets+0;
 
+static gss_OID_desc negoex_mech = { NEGOEX_OID_LENGTH, NEGOEX_OID };
+
 static int make_NegHints(OM_uint32 *, gss_buffer_t *);
 static int put_neg_hints(unsigned char **, gss_buffer_t, unsigned int);
 static OM_uint32
@@ -331,7 +333,7 @@ create_spnego_cred(OM_uint32 *minor_status, gss_cred_id_t mcred,
 	spnego_gss_cred_id_t spcred;
 
 	*cred_out = NULL;
-	spcred = calloc(1, sizeof(spnego_gss_cred_id_rec));
+	spcred = calloc(1, sizeof(*spcred));
 	if (spcred == NULL) {
 		*minor_status = ENOMEM;
 		return GSS_S_FAILURE;
@@ -444,9 +446,8 @@ 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));
 
+	spnego_ctx = malloc(sizeof(*spnego_ctx));
 	if (spnego_ctx == NULL) {
 		return (NULL);
 	}
@@ -467,6 +468,12 @@ create_spnego_ctx(int initiate)
 	spnego_ctx->internal_name = GSS_C_NO_NAME;
 	spnego_ctx->actual_mech = GSS_C_NO_OID;
 	spnego_ctx->deleg_cred = GSS_C_NO_CREDENTIAL;
+	spnego_ctx->negoex_step = 0;
+	memset(&spnego_ctx->negoex_transcript, 0, sizeof(struct k5buf));
+	spnego_ctx->negoex_seqnum = 0;
+	K5_TAILQ_INIT(&spnego_ctx->negoex_mechs);
+	spnego_ctx->kctx = NULL;
+	memset(spnego_ctx->negoex_conv_id, 0, GUID_LENGTH);
 
 	return (spnego_ctx);
 }
@@ -677,8 +684,7 @@ init_ctx_new(OM_uint32 *minor_status,
 		return GSS_S_FAILURE;
 
 	/* determine negotiation mech set */
-	ret = get_negotiable_mechs(minor_status, spcred, GSS_C_INITIATE,
-				   &sc->mech_set);
+	ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_INITIATE);
 	if (ret != GSS_S_COMPLETE)
 		goto cleanup;
 
@@ -912,19 +918,19 @@ init_ctx_call_init(OM_uint32 *minor_status,
 	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 (gss_oid_equal(sc->internal_mech, &negoex_mech)) {
+		ret = negoex_init(minor_status, sc, mcred, target_name,
+				  mech_req_flags, time_req, mechtok_in,
+				  mechtok_out, time_rec);
+	} else {
+		ret = gss_init_sec_context(minor_status, mcred,
+					   &sc->ctx_handle, target_name,
+					   sc->internal_mech, mech_req_flags,
+					   time_req, GSS_C_NO_CHANNEL_BINDINGS,
+					   mechtok_in, &sc->actual_mech,
+					   mechtok_out, &sc->ctx_flags,
+					   time_rec);
+	}
 
 	/* Bail out if the acceptor gave us an error token but the mech didn't
 	 * see it as an error. */
@@ -1286,22 +1292,15 @@ acc_ctx_hints(OM_uint32 *minor_status,
 	      send_token_flag *return_token,
 	      spnego_gss_ctx_id_t *sc_out)
 {
-	OM_uint32 tmpmin, ret;
-	gss_OID_set supported_mechSet;
+	OM_uint32 ret;
 	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;
@@ -1311,7 +1310,12 @@ acc_ctx_hints(OM_uint32 *minor_status,
 		ret = GSS_S_FAILURE;
 		goto cleanup;
 	}
-	if (put_mech_set(supported_mechSet, &sc->DER_mechTypes) < 0) {
+
+	ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_ACCEPT);
+	if (ret != GSS_S_COMPLETE)
+		goto cleanup;
+
+	if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) {
 		ret = GSS_S_FAILURE;
 		goto cleanup;
 	}
@@ -1326,7 +1330,6 @@ acc_ctx_hints(OM_uint32 *minor_status,
 
 cleanup:
 	release_spnego_ctx(&sc);
-	gss_release_oid_set(&tmpmin, &supported_mechSet);
 
 	return ret;
 }
@@ -1348,7 +1351,7 @@ acc_ctx_new(OM_uint32 *minor_status,
 	    spnego_gss_ctx_id_t *sc_out)
 {
 	OM_uint32 tmpmin, ret, req_flags;
-	gss_OID_set supported_mechSet, mechTypes;
+	gss_OID_set mechTypes;
 	gss_buffer_desc der_mechTypes;
 	gss_OID mech_wanted;
 	spnego_gss_ctx_id_t sc = NULL;
@@ -1357,7 +1360,7 @@ acc_ctx_new(OM_uint32 *minor_status,
 	der_mechTypes.length = 0;
 	der_mechTypes.value = NULL;
 	*mechToken = *mechListMIC = GSS_C_NO_BUFFER;
-	supported_mechSet = mechTypes = GSS_C_NO_OID_SET;
+	mechTypes = GSS_C_NO_OID_SET;
 	*return_token = ERROR_TOKEN_SEND;
 	*negState = REJECT;
 	*minor_status = 0;
@@ -1368,8 +1371,15 @@ acc_ctx_new(OM_uint32 *minor_status,
 	if (ret != GSS_S_COMPLETE) {
 		goto cleanup;
 	}
-	ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT,
-				   &supported_mechSet);
+
+	sc = create_spnego_ctx(0);
+	if (sc == NULL) {
+		ret = GSS_S_FAILURE;
+		*return_token = NO_TOKEN_SEND;
+		goto cleanup;
+	}
+
+	ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_INITIATE);
 	if (ret != GSS_S_COMPLETE) {
 		*return_token = NO_TOKEN_SEND;
 		goto cleanup;
@@ -1379,19 +1389,12 @@ acc_ctx_new(OM_uint32 *minor_status,
 	 * that the initiator requested and the list that
 	 * the acceptor will support.
 	 */
-	mech_wanted = negotiate_mech(supported_mechSet, mechTypes, negState);
+	mech_wanted = negotiate_mech(sc, 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;
@@ -1403,10 +1406,12 @@ acc_ctx_new(OM_uint32 *minor_status,
 	*return_token = INIT_TOKEN_SEND;
 	sc->firstpass = 1;
 	*sc_out = sc;
+	sc = NULL;
 	ret = GSS_S_COMPLETE;
+
 cleanup:
+	release_spnego_ctx(&sc);
 	gss_release_oid_set(&tmpmin, &mechTypes);
-	gss_release_oid_set(&tmpmin, &supported_mechSet);
 	if (der_mechTypes.length != 0)
 		gss_release_buffer(&tmpmin, &der_mechTypes);
 
@@ -1543,8 +1548,9 @@ acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
 	OM_uint32 ret, tmpmin;
 	gss_OID_desc mechoid;
 	gss_cred_id_t mcred;
+	int negoex = gss_oid_equal(sc->internal_mech, &negoex_mech);
 
-	if (sc->ctx_handle == GSS_C_NO_CONTEXT) {
+	if (sc->ctx_handle == GSS_C_NO_CONTEXT && !negoex) {
 		/*
 		 * mechoid is an alias; don't free it.
 		 */
@@ -1562,11 +1568,18 @@ acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
 	mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
 	(void) gss_release_name(&tmpmin, &sc->internal_name);
 	(void) gss_release_cred(&tmpmin, &sc->deleg_cred);
-	ret = gss_accept_sec_context(minor_status, &sc->ctx_handle, mcred,
-				     mechtok_in, GSS_C_NO_CHANNEL_BINDINGS,
-				     &sc->internal_name, &sc->actual_mech,
-				     mechtok_out, &sc->ctx_flags, time_rec,
-				     &sc->deleg_cred);
+	if (negoex) {
+		ret = negoex_accept(minor_status, sc, mcred, mechtok_in,
+				    mechtok_out, time_rec);
+	} else {
+		ret = gss_accept_sec_context(minor_status, &sc->ctx_handle,
+					     mcred, mechtok_in,
+					     GSS_C_NO_CHANNEL_BINDINGS,
+					     &sc->internal_name,
+					     &sc->actual_mech, mechtok_out,
+					     &sc->ctx_flags, time_rec,
+					     &sc->deleg_cred);
+	}
 	if (ret == GSS_S_COMPLETE) {
 #ifdef MS_BUG_TEST
 		/*
@@ -1801,6 +1814,50 @@ cleanup:
 }
 #endif /*  LEAN_CLIENT */
 
+static struct {
+	OM_uint32 status;
+	const char *msg;
+} msg_table[] = {
+	{ ERR_SPNEGO_NO_MECHS_AVAILABLE,
+	  N_("SPNEGO cannot find mechanisms to negotiate") },
+	{ ERR_SPNEGO_NO_CREDS_ACQUIRED,
+	  N_("SPNEGO failed to acquire creds") },
+	{ ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR,
+	  N_("SPNEGO acceptor did not select a mechanism") },
+	{ ERR_SPNEGO_NEGOTIATION_FAILED,
+	  N_("SPNEGO failed to negotiate a mechanism") },
+	{ ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR,
+	  N_("SPNEGO acceptor did not return a valid token") },
+	{ ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE,
+	  N_("Invalid NegoEx signature") },
+	{ ERR_NEGOEX_INVALID_MESSAGE_TYPE,
+	  N_("Invalid NegoEx message type") },
+	{ ERR_NEGOEX_INVALID_MESSAGE_SIZE,
+	  N_("Invalid NegoEx message size") },
+	{ ERR_NEGOEX_INVALID_CONVERSATION_ID,
+	  N_("Invalid NegoEx conversation ID") },
+	{ ERR_NEGOEX_AUTH_SCHEME_NOT_FOUND,
+	  N_("NegoEx authentication scheme not found") },
+	{ ERR_NEGOEX_MISSING_NEGO_MESSAGE,
+	  N_("Missing NegoEx negotiate message") },
+	{ ERR_NEGOEX_MISSING_AP_REQUEST_MESSAGE,
+	  N_("Missing NegoEx authentication protocol request message") },
+	{ ERR_NEGOEX_NO_AVAILABLE_MECHS,
+	  N_("No mutually supported NegoEx authentication schemes") },
+	{ ERR_NEGOEX_NO_VERIFY_KEY,
+	  N_("No NegoEx verify key") },
+	{ ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME,
+	  N_("Unknown NegoEx checksum scheme") },
+	{ ERR_NEGOEX_INVALID_CHECKSUM,
+	  N_("Invalid NegoEx checksum") },
+	{ ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION,
+	  N_("Unsupported critical NegoEx extension") },
+	{ ERR_NEGOEX_UNSUPPORTED_VERSION,
+	  N_("Unsupported NegoEx version") },
+	{ ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE,
+	  N_("NegoEx message out of sequence") },
+};
+
 /*ARGSUSED*/
 OM_uint32 KRB5_CALLCONV
 spnego_gss_display_status(
@@ -1812,62 +1869,40 @@ spnego_gss_display_status(
 		gss_buffer_t status_string)
 {
 	OM_uint32 maj = GSS_S_COMPLETE;
+	const char *msg;
+	size_t i;
 	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;
+	for (i = 0; i < sizeof(msg_table) / sizeof(*msg_table); i++) {
+		if (status_value == msg_table[i].status) {
+			msg = dgettext(KRB5_TEXTDOMAIN, msg_table[i].msg);
+			*status_string = make_err_msg(msg);
+			return GSS_S_COMPLETE;
 		}
-		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");
+	/* Not one of our minor codes; might be from a mech.  Call back
+	 * to gss_display_status, but first check for recursion. */
+	if (k5_getspecific(K5_KEY_GSS_SPNEGO_STATUS) != NULL) {
+		/* Perhaps we returned a com_err code like ENOMEM. */
+		const char *err = error_message(status_value);
+		*status_string = make_err_msg(err);
+		return GSS_S_COMPLETE;
+	}
+	/* Set a non-null pointer value; doesn't matter which one. */
+	ret = k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, &ret);
+	if (ret != 0) {
+		*minor_status = ret;
+		return GSS_S_FAILURE;
+	}
+
+	maj = gss_display_status(minor_status, status_value,
+				 status_type, mech_type,
+				 message_context, status_string);
+	/* This is unlikely to fail; not much we can do if it does. */
+	(void)k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, NULL);
+
 	return maj;
 }
 
@@ -2899,6 +2934,12 @@ spnego_gss_set_neg_mechs(OM_uint32 *minor_status,
 	gss_release_oid_set(minor_status, &spcred->neg_mechs);
 	ret = generic_gss_copy_oid_set(minor_status, mech_list,
 				       &spcred->neg_mechs);
+	if (ret == GSS_S_COMPLETE) {
+		(void) gss_set_neg_mechs(minor_status,
+					 spcred->mcred,
+					 spcred->neg_mechs);
+	}
+
 	return (ret);
 }
 
@@ -3076,6 +3117,8 @@ release_spnego_ctx(spnego_gss_ctx_id_t *ctx)
 		(void) gss_release_name(&minor_stat, &context->internal_name);
 		(void) gss_release_cred(&minor_stat, &context->deleg_cred);
 
+		negoex_release_context(context);
+
 		free(context);
 		*ctx = NULL;
 	}
@@ -3086,7 +3129,12 @@ release_spnego_ctx(spnego_gss_ctx_id_t *ctx)
  * 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.
+ * all available, non-deprecated mechs except SPNEGO and NegoEx-
+ * only mechanisms.
+ *
+ * Note that gss_acquire_cred_from(GSS_C_NO_OID_SET) will filter
+ * out hidden (GSS_C_MA_NOT_INDICATED) mechanisms such as NegoEx, so
+ * calling gss_indicate_mechs_by_attrs() also works around that.
  *
  * If a ptr to a creds list is given, this function will attempt
  * to acquire creds for the creds given and trim the list of
@@ -3152,77 +3200,121 @@ get_available_mechs(OM_uint32 *minor_status,
 	return (major_status);
 }
 
+/* Return true if mech asserts the GSS_C_MA_NEGOEX_AND_SPNEGO attribute. */
+static int
+negoex_and_spnego(gss_OID mech)
+{
+	OM_uint32 ret, minor;
+	gss_OID_set attrs;
+	int present;
+
+	ret = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
+	if (ret != GSS_S_COMPLETE || attrs == GSS_C_NO_OID_SET)
+		return 0;
+
+	(void) generic_gss_test_oid_set_member(&minor,
+					       GSS_C_MA_NEGOEX_AND_SPNEGO,
+					       attrs, &present);
+	(void) gss_release_oid_set(&minor, &attrs);
+	return present;
+}
+
 /*
- * 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.
+ * Fill sc->mech_set with the SPNEGO-negotiable mechanism OIDs, and
+ * sc->negoex_mechs with an entry for each NegoEx-negotiable mechanism.  Take
+ * into account the mech set provided with gss_set_neg_mechs() if it exists.
  */
 static OM_uint32
-get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_cred_id_t spcred,
-		     gss_cred_usage_t usage, gss_OID_set *rmechs)
+get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
+		     spnego_gss_cred_id_t spcred, gss_cred_usage_t usage)
 {
 	OM_uint32 ret, tmpmin;
 	gss_cred_id_t creds = GSS_C_NO_CREDENTIAL;
-	gss_OID_set cred_mechs = GSS_C_NULL_OID_SET;
-	gss_OID_set intersect_mechs = GSS_C_NULL_OID_SET;
+	gss_OID_set cred_mechs = GSS_C_NULL_OID_SET, mechs;
 	unsigned int i;
-	int present;
+	int present, added_negoex = 0;
+	auth_scheme scheme;
 
-	if (spcred == NULL) {
-		/* The default credentials were supplied.  Return a list of all
-		 * permissible mechs we can acquire a cred for. */
+	if (spcred != NULL) {
+		/* Get the list of mechs in the mechglue cred. */
+		ret = gss_inquire_cred(minor_status, spcred->mcred, NULL,
+				       NULL, NULL, &cred_mechs);
+		if (ret != GSS_S_COMPLETE)
+			return (ret);
+	} else {
+		/* Start with the list of available mechs. */
 		ret = get_available_mechs(minor_status, GSS_C_NO_NAME, usage,
 					  GSS_C_NO_CRED_STORE, &creds,
-					  rmechs, NULL);
+					  &cred_mechs, NULL);
+		if (ret != GSS_S_COMPLETE)
+			return (ret);
 		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 gss_set_neg_mechs() was called, use that to determine the
+	 * iteration order.  Otherwise iterate over the credential mechs. */
+	mechs = (spcred != NULL && spcred->neg_mechs != GSS_C_NULL_OID_SET) ?
+	    spcred->neg_mechs : cred_mechs;
+
+	ret = gss_create_empty_oid_set(minor_status, &sc->mech_set);
 	if (ret != GSS_S_COMPLETE)
-		return (ret);
+		goto cleanup;
 
-	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);
-	}
+	for (i = 0; i < mechs->count; i++) {
+		if (mechs != cred_mechs) {
+			/* Intersect neg_mechs with cred_mechs. */
+			gss_test_oid_set_member(&tmpmin, &mechs->elements[i],
+						cred_mechs, &present);
+			if (!present)
+				continue;
+		}
 
-	/* 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);
-	}
+		/* Query the auth scheme to see if this is a NegoEx mech. */
+		ret = gssspi_query_mechanism_info(&tmpmin, &mechs->elements[i],
+						  scheme);
+		if (ret == GSS_S_COMPLETE) {
+			/* Add an entry for this mech to the NegoEx list. */
+			ret = negoex_add_auth_mech(minor_status, sc,
+						   &mechs->elements[i],
+						   scheme);
+			if (ret != GSS_S_COMPLETE)
+				goto cleanup;
+
+			/* Add the NegoEx OID to the SPNEGO list at the
+			 * position of the first NegoEx mechanism. */
+			if (!added_negoex) {
+				ret = gss_add_oid_set_member(minor_status,
+							     &negoex_mech,
+							     &sc->mech_set);
+				if (ret != GSS_S_COMPLETE)
+					goto cleanup;
+				added_negoex = 1;
+			}
 
-	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);
+			/* Skip this mech in the SPNEGO list unless it asks for
+			 * direct SPNEGO negotiation. */
+			if (!negoex_and_spnego(&mechs->elements[i]))
+				continue;
+		}
+
+		/* Add this mech to the SPNEGO list. */
+		ret = gss_add_oid_set_member(minor_status, &mechs->elements[i],
+					     &sc->mech_set);
 		if (ret != GSS_S_COMPLETE)
-			break;
+			goto cleanup;
 	}
 
-	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 = 0;
+
+cleanup:
+	if (ret != GSS_S_COMPLETE || sc->mech_set->count == 0) {
 		*minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE;
 		map_errcode(minor_status);
-		return (GSS_S_FAILURE);
+		ret = GSS_S_FAILURE;
 	}
 
-	*rmechs = intersect_mechs;
-	*minor_status = 0;
-	return (GSS_S_COMPLETE);
+	gss_release_oid_set(&tmpmin, &cred_mechs);
+	return (ret);
 }
 
 /* following are token creation and reading routines */
@@ -3680,23 +3772,30 @@ put_negResult(unsigned char **buf_out, OM_uint32 negResult,
  * mechanisms supported by the acceptor.
  */
 static gss_OID
-negotiate_mech(gss_OID_set supported, gss_OID_set received,
+negotiate_mech(spnego_gss_ctx_id_t ctx, gss_OID_set received,
 	       OM_uint32 *negResult)
 {
 	size_t i, j;
+	int wrong_krb5_oid;
 
 	for (i = 0; i < received->count; i++) {
 		gss_OID mech_oid = &received->elements[i];
 
 		/* Accept wrong mechanism OID from MS clients */
-		if (g_OID_equal(mech_oid, &gss_mech_krb5_wrong_oid))
+		wrong_krb5_oid = 0;
+		if (g_OID_equal(mech_oid, &gss_mech_krb5_wrong_oid)) {
 			mech_oid = (gss_OID)&gss_mech_krb5_oid;
+			wrong_krb5_oid = 1;
+		}
 
-		for (j = 0; j < supported->count; j++) {
-			if (g_OID_equal(mech_oid, &supported->elements[j])) {
+		for (j = 0; j < ctx->mech_set->count; j++) {
+			if (g_OID_equal(mech_oid,
+					&ctx->mech_set->elements[j])) {
 				*negResult = (i == 0) ? ACCEPT_INCOMPLETE :
 					REQUEST_MIC;
-				return &received->elements[i];
+				return wrong_krb5_oid ?
+					(gss_OID)&gss_mech_krb5_wrong_oid :
+					&ctx->mech_set->elements[j];
 			}
 		}
 	}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.