EAP Channel binding support
[mech_eap.git] / mech_eap / accept_sec_context.c
index afcd4d3..9888097 100644 (file)
@@ -59,7 +59,7 @@ static OM_uint32
 acceptReadyEap(OM_uint32 *minor, gss_ctx_id_t ctx, gss_cred_id_t cred)
 {
     OM_uint32 major, tmpMinor;
-    VALUE_PAIR *vp;
+    rs_const_avp *vp;
     gss_buffer_desc nameBuf = GSS_C_EMPTY_BUFFER;
 
     /* Cache encryption type derived from selected mechanism OID */
@@ -72,9 +72,10 @@ acceptReadyEap(OM_uint32 *minor, gss_ctx_id_t ctx, gss_cred_id_t cred)
 
     major = gssEapRadiusGetRawAvp(minor, ctx->acceptorCtx.vps,
                                   PW_USER_NAME, 0, &vp);
-    if (major == GSS_S_COMPLETE && vp->length) {
-        nameBuf.length = vp->length;
-        nameBuf.value = vp->vp_strvalue;
+    if (major == GSS_S_COMPLETE && rs_avp_length(vp) != 0) {
+        rs_avp_octets_value_byref((rs_avp *)vp,
+                                  (unsigned char **)&nameBuf.value,
+                                  &nameBuf.length);
     } else {
         ctx->gssFlags |= GSS_C_ANON_FLAG;
     }
@@ -88,15 +89,15 @@ acceptReadyEap(OM_uint32 *minor, gss_ctx_id_t ctx, gss_cred_id_t cred)
         return major;
 
     major = gssEapRadiusGetRawAvp(minor, ctx->acceptorCtx.vps,
-                                  PW_MS_MPPE_SEND_KEY, VENDORPEC_MS, &vp);
+                                  PW_MS_MPPE_SEND_KEY, VENDORPEC_MICROSOFT, &vp);
     if (GSS_ERROR(major)) {
         *minor = GSSEAP_KEY_UNAVAILABLE;
         return GSS_S_UNAVAILABLE;
     }
 
     major = gssEapDeriveRfc3961Key(minor,
-                                   vp->vp_octets,
-                                   vp->length,
+                                   rs_avp_octets_value_const_ptr(vp),
+                                   rs_avp_length(vp),
                                    ctx->encryptionType,
                                    &ctx->rfc3961Key);
     if (GSS_ERROR(major))
@@ -287,7 +288,7 @@ importInitiatorIdentity(OM_uint32 *minor,
 static OM_uint32
 setInitiatorIdentity(OM_uint32 *minor,
                      gss_ctx_id_t ctx,
-                     VALUE_PAIR **vps)
+                     struct rs_packet *req)
 {
     OM_uint32 major, tmpMinor;
     gss_buffer_desc nameBuf;
@@ -303,7 +304,7 @@ setInitiatorIdentity(OM_uint32 *minor,
         if (GSS_ERROR(major))
             return major;
 
-        major = gssEapRadiusAddAvp(minor, vps, PW_USER_NAME, 0, &nameBuf);
+        major = gssEapRadiusAddAvp(minor, req, PW_USER_NAME, 0, &nameBuf);
         if (GSS_ERROR(major))
             return major;
 
@@ -320,7 +321,7 @@ setInitiatorIdentity(OM_uint32 *minor,
 static OM_uint32
 setAcceptorIdentity(OM_uint32 *minor,
                     gss_ctx_id_t ctx,
-                    VALUE_PAIR **vps)
+                    struct rs_packet *req)
 {
     OM_uint32 major;
     gss_buffer_desc nameBuf;
@@ -328,7 +329,7 @@ setAcceptorIdentity(OM_uint32 *minor,
     krb5_principal krbPrinc;
     struct rs_context *rc = ctx->acceptorCtx.radContext;
 
-    assert(rc != NULL);
+    GSSEAP_ASSERT(rc != NULL);
 
     if (ctx->acceptorName == GSS_C_NO_NAME) {
         *minor = 0;
@@ -343,15 +344,15 @@ setAcceptorIdentity(OM_uint32 *minor,
     GSSEAP_KRB_INIT(&krbContext);
 
     krbPrinc = ctx->acceptorName->krbPrincipal;
-    assert(krbPrinc != NULL);
-    assert(KRB_PRINC_LENGTH(krbPrinc) >= 2);
+    GSSEAP_ASSERT(krbPrinc != NULL);
+    GSSEAP_ASSERT(KRB_PRINC_LENGTH(krbPrinc) >= 2);
 
     /* Acceptor-Service-Name */
     krbPrincComponentToGssBuffer(krbPrinc, 0, &nameBuf);
 
-    major = gssEapRadiusAddAvp(minor, vps,
+    major = gssEapRadiusAddAvp(minor, req,
                                PW_GSS_ACCEPTOR_SERVICE_NAME,
-                               VENDORPEC_UKERNA,
+                               0,
                                &nameBuf);
     if (GSS_ERROR(major))
         return major;
@@ -359,47 +360,39 @@ setAcceptorIdentity(OM_uint32 *minor,
     /* Acceptor-Host-Name */
     krbPrincComponentToGssBuffer(krbPrinc, 1, &nameBuf);
 
-    major = gssEapRadiusAddAvp(minor, vps,
+    major = gssEapRadiusAddAvp(minor, req,
                                PW_GSS_ACCEPTOR_HOST_NAME,
-                               VENDORPEC_UKERNA,
+                               0,
                                &nameBuf);
     if (GSS_ERROR(major))
         return major;
 
     if (KRB_PRINC_LENGTH(krbPrinc) > 2) {
         /* Acceptor-Service-Specific */
-        krb5_principal_data ssiPrinc = *krbPrinc;
-        char *ssi;
-
-        KRB_PRINC_LENGTH(&ssiPrinc) -= 2;
-        KRB_PRINC_NAME(&ssiPrinc) += 2;
-
-        *minor = krb5_unparse_name_flags(krbContext, &ssiPrinc,
-                                         KRB5_PRINCIPAL_UNPARSE_NO_REALM, &ssi);
+        *minor = krbPrincUnparseServiceSpecifics(krbContext,
+                                                 krbPrinc, &nameBuf);
         if (*minor != 0)
             return GSS_S_FAILURE;
 
-        nameBuf.value = ssi;
-        nameBuf.length = strlen(ssi);
-
-        major = gssEapRadiusAddAvp(minor, vps,
-                                   PW_GSS_ACCEPTOR_SERVICE_SPECIFIC,
-                                   VENDORPEC_UKERNA,
+        major = gssEapRadiusAddAvp(minor, req,
+                                   PW_GSS_ACCEPTOR_SERVICE_SPECIFICS,
+                                   0,
                                    &nameBuf);
-
-        if (GSS_ERROR(major)) {
-            krb5_free_unparsed_name(krbContext, ssi);
-            return major;
-        }
+#ifdef HAVE_HEIMDAL_VERSION
+        krb5_xfree(ssi);
+#else
         krb5_free_unparsed_name(krbContext, ssi);
+#endif
+        if (GSS_ERROR(major))
+            return major;
     }
 
     krbPrincRealmToGssBuffer(krbPrinc, &nameBuf);
     if (nameBuf.length != 0) {
         /* Acceptor-Realm-Name */
-        major = gssEapRadiusAddAvp(minor, vps,
+        major = gssEapRadiusAddAvp(minor, req,
                                    PW_GSS_ACCEPTOR_REALM_NAME,
-                                   VENDORPEC_UKERNA,
+                                   0,
                                    &nameBuf);
         if (GSS_ERROR(major))
             return major;
@@ -418,58 +411,35 @@ createRadiusHandle(OM_uint32 *minor,
                    gss_ctx_id_t ctx)
 {
     struct gss_eap_acceptor_ctx *actx = &ctx->acceptorCtx;
-    const char *configFile = RS_CONFIG_FILE;
-    const char *configStanza = "gss-eap";
-    struct rs_alloc_scheme ralloc;
     struct rs_error *err;
+    const char *configStanza = "gss-eap";
+    OM_uint32 major;
 
-    assert(actx->radContext == NULL);
-    assert(actx->radConn == NULL);
+    GSSEAP_ASSERT(actx->radContext == NULL);
+    GSSEAP_ASSERT(actx->radConn == NULL);
+    GSSEAP_ASSERT(cred != GSS_C_NO_CREDENTIAL);
 
-    if (rs_context_create(&actx->radContext) != 0) {
-        *minor = GSSEAP_RADSEC_CONTEXT_FAILURE;
-        return GSS_S_FAILURE;
-    }
+    major = gssEapCreateRadiusContext(minor, cred, &actx->radContext);
+    if (GSS_ERROR(major))
+        return major;
 
-    if (cred->radiusConfigFile.value != NULL)
-        configFile = (const char *)cred->radiusConfigFile.value;
     if (cred->radiusConfigStanza.value != NULL)
         configStanza = (const char *)cred->radiusConfigStanza.value;
 
-    ralloc.calloc  = GSSEAP_CALLOC;
-    ralloc.malloc  = GSSEAP_MALLOC;
-    ralloc.free    = GSSEAP_FREE;
-    ralloc.realloc = GSSEAP_REALLOC;
-
-    rs_context_set_alloc_scheme(actx->radContext, &ralloc);
-
-    if (rs_context_read_config(actx->radContext, configFile) != 0) {
-        err = rs_err_ctx_pop(actx->radContext);
-        goto fail;
-    }
-
-    if (rs_context_init_freeradius_dict(actx->radContext, NULL) != 0) {
-        err = rs_err_ctx_pop(actx->radContext);
-        goto fail;
-    }
-
     if (rs_conn_create(actx->radContext, &actx->radConn, configStanza) != 0) {
         err = rs_err_conn_pop(actx->radConn);
-        goto fail;
+        return gssEapRadiusMapError(minor, err);
     }
 
     if (actx->radServer != NULL) {
         if (rs_conn_select_peer(actx->radConn, actx->radServer) != 0) {
             err = rs_err_conn_pop(actx->radConn);
-            goto fail;
+            return gssEapRadiusMapError(minor, err);
         }
     }
 
     *minor = 0;
     return GSS_S_COMPLETE;
-
-fail:
-    return gssEapRadiusMapError(minor, err);
 }
 
 /*
@@ -492,7 +462,7 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor,
     struct rs_connection *rconn;
     struct rs_request *request = NULL;
     struct rs_packet *req = NULL, *resp = NULL;
-    struct radius_packet *frreq, *frresp;
+    int isAccessChallenge;
 
     if (ctx->acceptorCtx.radContext == NULL) {
         /* May be NULL from an imported partial context */
@@ -513,23 +483,22 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor,
         major = gssEapRadiusMapError(minor, rs_err_conn_pop(rconn));
         goto cleanup;
     }
-    frreq = rs_packet_frpkt(req);
 
-    major = setInitiatorIdentity(minor, ctx, &frreq->vps);
+    major = setInitiatorIdentity(minor, ctx, req);
     if (GSS_ERROR(major))
         goto cleanup;
 
-    major = setAcceptorIdentity(minor, ctx, &frreq->vps);
+    major = setAcceptorIdentity(minor, ctx, req);
     if (GSS_ERROR(major))
         goto cleanup;
 
-    major = gssEapRadiusAddAvp(minor, &frreq->vps,
+    major = gssEapRadiusAddAvp(minor, req,
                                PW_EAP_MESSAGE, 0, inputToken);
     if (GSS_ERROR(major))
         goto cleanup;
 
     if (ctx->acceptorCtx.state.length != 0) {
-        major = gssEapRadiusAddAvp(minor, &frreq->vps, PW_STATE, 0,
+        major = gssEapRadiusAddAvp(minor, req, PW_STATE, 0,
                                    &ctx->acceptorCtx.state);
         if (GSS_ERROR(major))
             goto cleanup;
@@ -550,14 +519,17 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor,
         goto cleanup;
     }
 
-    assert(resp != NULL);
+    GSSEAP_ASSERT(resp != NULL);
 
-    frresp = rs_packet_frpkt(resp);
-    switch (frresp->code) {
+    isAccessChallenge = 0;
+
+    switch (rs_packet_code(resp)) {
     case PW_ACCESS_CHALLENGE:
-    case PW_AUTHENTICATION_ACK:
+        isAccessChallenge = 1;
+        break;
+    case PW_ACCESS_ACCEPT:
         break;
-    case PW_AUTHENTICATION_REJECT:
+    case PW_ACCESS_REJECT:
         *minor = GSSEAP_RADIUS_AUTH_FAILURE;
         major = GSS_S_DEFECTIVE_CREDENTIAL;
         goto cleanup;
@@ -569,23 +541,27 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor,
         break;
     }
 
-    major = gssEapRadiusGetAvp(minor, frresp->vps, PW_EAP_MESSAGE, 0,
+    major = gssEapRadiusGetAvp(minor, resp, PW_EAP_MESSAGE, 0,
                                outputToken, TRUE);
-    if (major == GSS_S_UNAVAILABLE && frresp->code == PW_ACCESS_CHALLENGE) {
+    if (major == GSS_S_UNAVAILABLE && isAccessChallenge) {
         *minor = GSSEAP_MISSING_EAP_REQUEST;
         major = GSS_S_DEFECTIVE_TOKEN;
         goto cleanup;
     } else if (GSS_ERROR(major))
         goto cleanup;
 
-    if (frresp->code == PW_ACCESS_CHALLENGE) {
-        major = gssEapRadiusGetAvp(minor, frresp->vps, PW_STATE, 0,
+    if (isAccessChallenge) {
+        major = gssEapRadiusGetAvp(minor, resp, PW_STATE, 0,
                                    &ctx->acceptorCtx.state, TRUE);
         if (GSS_ERROR(major) && *minor != GSSEAP_NO_SUCH_ATTR)
             goto cleanup;
     } else {
-        ctx->acceptorCtx.vps = frresp->vps;
-        frresp->vps = NULL;
+        rs_avp **vps;
+
+        rs_packet_avps(resp, &vps);
+
+        ctx->acceptorCtx.vps = *vps;
+        *vps = NULL;
 
         major = acceptReadyEap(minor, ctx, cred);
         if (GSS_ERROR(major))
@@ -606,7 +582,7 @@ cleanup:
     if (resp != NULL)
         rs_packet_destroy(resp);
     if (GSSEAP_SM_STATE(ctx) == GSSEAP_STATE_INITIATOR_EXTS) {
-        assert(major == GSS_S_CONTINUE_NEEDED);
+        GSSEAP_ASSERT(major == GSS_S_CONTINUE_NEEDED);
 
         rs_conn_destroy(ctx->acceptorCtx.radConn);
         ctx->acceptorCtx.radConn = NULL;
@@ -631,7 +607,7 @@ eapGssSmAcceptGssFlags(OM_uint32 *minor,
     unsigned char *p;
     OM_uint32 initiatorGssFlags;
 
-    assert((ctx->flags & CTX_FLAG_KRB_REAUTH) == 0);
+    GSSEAP_ASSERT((ctx->flags & CTX_FLAG_KRB_REAUTH) == 0);
 
     if (inputToken->length < 4) {
         *minor = GSSEAP_TOK_TRUNC;
@@ -662,39 +638,41 @@ eapGssSmAcceptGssChannelBindings(OM_uint32 *minor,
                                  gss_buffer_t outputToken GSSEAP_UNUSED,
                                  OM_uint32 *smFlags GSSEAP_UNUSED)
 {
-    OM_uint32 major;
-    gss_iov_buffer_desc iov[2];
+    krb5_error_code code;
+    krb5_context krbContext;
+    krb5_data data;
+    krb5_checksum cksum;
+    krb5_boolean valid = FALSE;
 
-    iov[0].type = GSS_IOV_BUFFER_TYPE_DATA | GSS_IOV_BUFFER_FLAG_ALLOCATE;
-    iov[0].buffer.length = 0;
-    iov[0].buffer.value = NULL;
+    if (chanBindings == GSS_C_NO_CHANNEL_BINDINGS ||
+        chanBindings->application_data.length == 0)
+        return GSS_S_CONTINUE_NEEDED;
 
-    iov[1].type = GSS_IOV_BUFFER_TYPE_STREAM | GSS_IOV_BUFFER_FLAG_ALLOCATED;
+    GSSEAP_KRB_INIT(&krbContext);
 
-    /* XXX necessary because decrypted in place and we verify it later */
-    major = duplicateBuffer(minor, inputToken, &iov[1].buffer);
-    if (GSS_ERROR(major))
-        return major;
+    KRB_DATA_INIT(&data);
 
-    major = gssEapUnwrapOrVerifyMIC(minor, ctx, NULL, NULL,
-                                    iov, 2, TOK_TYPE_WRAP);
-    if (GSS_ERROR(major)) {
-        gssEapReleaseIov(iov, 2);
-        return major;
+    gssBufferToKrbData(&chanBindings->application_data, &data);
+
+    KRB_CHECKSUM_INIT(&cksum, ctx->checksumType, inputToken);
+
+    code = krb5_c_verify_checksum(krbContext, &ctx->rfc3961Key,
+                                  KEY_USAGE_GSSEAP_CHBIND_MIC,
+                                  &data, &cksum, &valid);
+    if (code != 0) {
+        *minor = code;
+        return GSS_S_FAILURE;
     }
 
-    if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS &&
-        !bufferEqual(&iov[0].buffer, &chanBindings->application_data)) {
-        major = GSS_S_BAD_BINDINGS;
+    if (valid == FALSE) {
         *minor = GSSEAP_BINDINGS_MISMATCH;
-    } else {
-        major = GSS_S_CONTINUE_NEEDED;
-        *minor = 0;
+        return GSS_S_BAD_BINDINGS;
     }
 
-    gssEapReleaseIov(iov, 2);
+    ctx->flags |= CTX_FLAG_CHANNEL_BINDINGS_VERIFIED;
 
-    return major;
+    *minor = 0;
+    return GSS_S_CONTINUE_NEEDED;
 }
 
 static OM_uint32
@@ -705,13 +683,27 @@ eapGssSmAcceptInitiatorMIC(OM_uint32 *minor,
                            gss_OID mech GSSEAP_UNUSED,
                            OM_uint32 reqFlags GSSEAP_UNUSED,
                            OM_uint32 timeReq GSSEAP_UNUSED,
-                           gss_channel_bindings_t chanBindings GSSEAP_UNUSED,
+                           gss_channel_bindings_t chanBindings,
                            gss_buffer_t inputToken,
                            gss_buffer_t outputToken GSSEAP_UNUSED,
                            OM_uint32 *smFlags GSSEAP_UNUSED)
 {
     OM_uint32 major;
 
+    /*
+     * The channel binding token is optional, however if the caller indicated
+     * bindings we must raise an error if it was absent.
+     *
+     * In the future, we might use a context option to allow the caller to
+     * indicate that missing bindings are acceptable.
+     */
+    if (chanBindings != NULL &&
+        chanBindings->application_data.length != 0 &&
+        (ctx->flags & CTX_FLAG_CHANNEL_BINDINGS_VERIFIED) == 0) {
+        *minor = GSSEAP_MISSING_BINDINGS;
+        return GSS_S_BAD_BINDINGS;
+    }
+
     major = gssEapVerifyTokenMIC(minor, ctx, inputToken);
     if (GSS_ERROR(major))
         return major;
@@ -830,7 +822,7 @@ static struct gss_eap_sm eapGssAcceptorSm[] = {
         ITOK_TYPE_GSS_CHANNEL_BINDINGS,
         ITOK_TYPE_NONE,
         GSSEAP_STATE_INITIATOR_EXTS,
-        SM_ITOK_FLAG_REQUIRED,
+        0,
         eapGssSmAcceptGssChannelBindings,
     },
     {
@@ -851,6 +843,13 @@ static struct gss_eap_sm eapGssAcceptorSm[] = {
 #endif
     {
         ITOK_TYPE_NONE,
+        ITOK_TYPE_ACCEPTOR_NAME_RESP,
+        GSSEAP_STATE_ACCEPTOR_EXTS,
+        0,
+        eapGssSmAcceptAcceptorName
+    },
+    {
+        ITOK_TYPE_NONE,
         ITOK_TYPE_ACCEPTOR_MIC,
         GSSEAP_STATE_ACCEPTOR_EXTS,
         0,
@@ -858,9 +857,9 @@ static struct gss_eap_sm eapGssAcceptorSm[] = {
     },
 };
 
-OM_uint32 GSSAPI_CALLCONV
-gss_accept_sec_context(OM_uint32 *minor,
-                       gss_ctx_id_t *context_handle,
+OM_uint32
+gssEapAcceptSecContext(OM_uint32 *minor,
+                       gss_ctx_id_t ctx,
                        gss_cred_id_t cred,
                        gss_buffer_t input_token,
                        gss_channel_bindings_t input_chan_bindings,
@@ -872,30 +871,6 @@ gss_accept_sec_context(OM_uint32 *minor,
                        gss_cred_id_t *delegated_cred_handle)
 {
     OM_uint32 major, tmpMinor;
-    gss_ctx_id_t ctx = *context_handle;
-
-    *minor = 0;
-
-    output_token->length = 0;
-    output_token->value = NULL;
-
-    if (src_name != NULL)
-        *src_name = GSS_C_NO_NAME;
-
-    if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
-        *minor = GSSEAP_TOK_TRUNC;
-        return GSS_S_DEFECTIVE_TOKEN;
-    }
-
-    if (ctx == GSS_C_NO_CONTEXT) {
-        major = gssEapAllocContext(minor, &ctx);
-        if (GSS_ERROR(major))
-            return major;
-
-        *context_handle = ctx;
-    }
-
-    GSSEAP_MUTEX_LOCK(&ctx->mutex);
 
     if (cred == GSS_C_NO_CREDENTIAL) {
         if (ctx->cred == GSS_C_NO_CREDENTIAL) {
@@ -914,15 +889,17 @@ gss_accept_sec_context(OM_uint32 *minor,
         cred = ctx->cred;
     }
 
-    GSSEAP_MUTEX_LOCK(&cred->mutex);
-
     /*
-     * Calling gssEapInquireCred() forces the default acceptor credential name
-     * to be resolved.
+     * Previously we acquired the credential mutex here, but it should not be
+     * necessary as the acceptor does not access any mutable elements of the
+     * credential handle.
      */
-    major = gssEapInquireCred(minor, cred, &ctx->acceptorName, NULL, NULL, NULL);
-    if (GSS_ERROR(major))
-        goto cleanup;
+
+    if (cred->name != GSS_C_NO_NAME) {
+        major = gssEapDuplicateName(minor, cred->name, &ctx->acceptorName);
+        if (GSS_ERROR(major))
+            goto cleanup;
+    }
 
     major = gssEapSmStep(minor,
                          cred,
@@ -967,16 +944,9 @@ gss_accept_sec_context(OM_uint32 *minor,
         }
     }
 
-    assert(CTX_IS_ESTABLISHED(ctx) || major == GSS_S_CONTINUE_NEEDED);
+    GSSEAP_ASSERT(CTX_IS_ESTABLISHED(ctx) || major == GSS_S_CONTINUE_NEEDED);
 
 cleanup:
-    if (cred != GSS_C_NO_CREDENTIAL)
-        GSSEAP_MUTEX_UNLOCK(&cred->mutex);
-    GSSEAP_MUTEX_UNLOCK(&ctx->mutex);
-
-    if (GSS_ERROR(major))
-        gssEapReleaseContext(&tmpMinor, context_handle);
-
     return major;
 }
 
@@ -1061,3 +1031,62 @@ eapGssSmAcceptGssReauth(OM_uint32 *minor,
     return major;
 }
 #endif /* GSSEAP_ENABLE_REAUTH */
+
+OM_uint32 GSSAPI_CALLCONV
+gss_accept_sec_context(OM_uint32 *minor,
+                       gss_ctx_id_t *context_handle,
+                       gss_cred_id_t cred,
+                       gss_buffer_t input_token,
+                       gss_channel_bindings_t input_chan_bindings,
+                       gss_name_t *src_name,
+                       gss_OID *mech_type,
+                       gss_buffer_t output_token,
+                       OM_uint32 *ret_flags,
+                       OM_uint32 *time_rec,
+                       gss_cred_id_t *delegated_cred_handle)
+{
+    OM_uint32 major, tmpMinor;
+    gss_ctx_id_t ctx = *context_handle;
+
+    *minor = 0;
+
+    output_token->length = 0;
+    output_token->value = NULL;
+
+    if (src_name != NULL)
+        *src_name = GSS_C_NO_NAME;
+
+    if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
+        *minor = GSSEAP_TOK_TRUNC;
+        return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    if (ctx == GSS_C_NO_CONTEXT) {
+        major = gssEapAllocContext(minor, &ctx);
+        if (GSS_ERROR(major))
+            return major;
+
+        *context_handle = ctx;
+    }
+
+    GSSEAP_MUTEX_LOCK(&ctx->mutex);
+
+    major = gssEapAcceptSecContext(minor,
+                                   ctx,
+                                   cred,
+                                   input_token,
+                                   input_chan_bindings,
+                                   src_name,
+                                   mech_type,
+                                   output_token,
+                                   ret_flags,
+                                   time_rec,
+                                   delegated_cred_handle);
+
+    GSSEAP_MUTEX_UNLOCK(&ctx->mutex);
+
+    if (GSS_ERROR(major))
+        gssEapReleaseContext(&tmpMinor, context_handle);
+
+    return major;
+}