don't return GSS_S_CREDENTIALS_EXPIRED if no expiry time
[mech_eap.orig] / util_sm.c
index 17af663..3bb28b8 100644 (file)
--- a/util_sm.c
+++ b/util_sm.c
 
 #include "gssapiP_eap.h"
 
+/* private flags */
+#define SM_FLAG_TRANSITED                   0x80000000
+
+#define SM_ASSERT_VALID(ctx, status)        do { \
+        assert(GSS_ERROR((status)) || \
+               ((status) == GSS_S_CONTINUE_NEEDED && ((ctx)->state > GSSEAP_STATE_INITIAL && (ctx)->state < GSSEAP_STATE_ESTABLISHED)) || \
+               ((status) == GSS_S_COMPLETE && (ctx)->state == GSSEAP_STATE_ESTABLISHED)); \
+    } while (0)
+
+#ifdef GSSEAP_DEBUG
 static const char *
 gssEapStateToString(enum gss_eap_state state)
 {
@@ -54,6 +64,11 @@ gssEapStateToString(enum gss_eap_state state)
     case GSSEAP_STATE_ACCEPTOR_EXTS:
         s = "ACCEPTOR_EXTS";
         break;
+#ifdef GSSEAP_ENABLE_REAUTH
+    case GSSEAP_STATE_REAUTHENTICATE:
+        s = "REAUTHENTICATE";
+        break;
+#endif
     case GSSEAP_STATE_ESTABLISHED:
         s = "ESTABLISHED";
         break;
@@ -65,11 +80,25 @@ gssEapStateToString(enum gss_eap_state state)
     return s;
 }
 
+void
+gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state)
+{
+    assert(state >= GSSEAP_STATE_INITIAL);
+    assert(state <= GSSEAP_STATE_ESTABLISHED);
+
+    fprintf(stderr, "GSS-EAP: state transition %s->%s\n",
+            gssEapStateToString(GSSEAP_SM_STATE(ctx)),
+            gssEapStateToString(state));
+
+    ctx->state = state;
+}
+#endif /* GSSEAP_DEBUG */
+
 static OM_uint32
-makeErrorToken(OM_uint32 *minor,
-               OM_uint32 majorStatus,
-               OM_uint32 minorStatus,
-               gss_buffer_set_t *outputToken)
+recordErrorToken(OM_uint32 *minor,
+                 gss_ctx_id_t ctx,
+                 OM_uint32 majorStatus,
+                 OM_uint32 minorStatus)
 {
     unsigned char errorData[8];
     gss_buffer_desc errorBuffer;
@@ -96,7 +125,39 @@ makeErrorToken(OM_uint32 *minor,
     errorBuffer.length = sizeof(errorData);
     errorBuffer.value = errorData;
 
-    return gss_add_buffer_set_member(minor, &errorBuffer, outputToken);
+    return gssEapRecordInnerContextToken(minor, ctx, &errorBuffer,
+                                         ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL);
+}
+
+static OM_uint32
+makeContextToken(OM_uint32 *minor,
+                 gss_ctx_id_t ctx,
+                 size_t headerOffset,
+                 gss_buffer_t outputToken)
+{
+    size_t tokSize, bodySize;
+    unsigned char *p;
+
+    assert(ctx->conversation.length > headerOffset);
+
+    bodySize = ctx->conversation.length - headerOffset;
+    tokSize = tokenSize(bodySize);
+
+    outputToken->value = GSSEAP_MALLOC(tokSize);
+    if (outputToken->value == NULL) {
+        *minor = ENOMEM;
+        return GSS_S_FAILURE;
+    }
+
+    outputToken->length = tokSize;
+
+    p = (unsigned char *)outputToken->value;
+
+    makeTokenHeader(bodySize, &p);
+    memcpy(p, (unsigned char *)ctx->conversation.value + headerOffset, bodySize);
+
+    *minor = 0;
+    return GSS_S_COMPLETE;
 }
 
 OM_uint32
@@ -110,18 +171,20 @@ gssEapSmStep(OM_uint32 *minor,
              gss_channel_bindings_t chanBindings,
              gss_buffer_t inputToken,
              gss_buffer_t outputToken,
-             struct gss_eap_sm *sm,
+             struct gss_eap_sm *sm, /* ordered by state */
              size_t smCount)
 {
     OM_uint32 major, tmpMajor, tmpMinor;
     gss_buffer_desc unwrappedInputToken = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc unwrappedOutputToken = GSS_C_EMPTY_BUFFER;
     gss_buffer_set_t innerInputTokens = GSS_C_NO_BUFFER_SET;
-    gss_buffer_set_t innerOutputTokens = GSS_C_NO_BUFFER_SET;
     OM_uint32 *inputTokenTypes = NULL, *outputTokenTypes = NULL;
     unsigned int smFlags = 0;
     size_t i, j;
     int initialContextToken = 0;
+    enum gss_eap_token_type tokType;
+    size_t headerOffset, firstTokenOffset;
+    size_t innerOutputTokenCount = 0;
 
     assert(smCount > 0);
 
@@ -131,18 +194,13 @@ gssEapSmStep(OM_uint32 *minor,
     outputToken->value = NULL;
 
     if (inputToken != GSS_C_NO_BUFFER && inputToken->length != 0) {
-        enum gss_eap_token_type tokType;
+        tokType = CTX_IS_INITIATOR(ctx) ?
+            TOK_TYPE_ACCEPTOR_CONTEXT : TOK_TYPE_INITIATOR_CONTEXT;
 
-        major = gssEapVerifyToken(minor, ctx, inputToken, &tokType,
-                                  &unwrappedInputToken);
+        major = gssEapVerifyContextToken(minor, ctx, inputToken, tokType,
+                                         &unwrappedInputToken);
         if (GSS_ERROR(major))
             goto cleanup;
-
-        if (tokType != TOK_TYPE_ESTABLISH_CONTEXT) {
-            major = GSS_S_DEFECTIVE_TOKEN;
-            *minor = GSSEAP_WRONG_TOK_ID;
-            goto cleanup;
-        }
     } else if (!CTX_IS_INITIATOR(ctx) || ctx->state != GSSEAP_STATE_INITIAL) {
         major = GSS_S_DEFECTIVE_TOKEN;
         *minor = GSSEAP_WRONG_SIZE;
@@ -151,7 +209,7 @@ gssEapSmStep(OM_uint32 *minor,
         initialContextToken = 1;
     }
 
-    if (ctx->state == GSSEAP_STATE_ESTABLISHED) {
+    if (CTX_IS_ESTABLISHED(ctx)) {
         major = GSS_S_BAD_STATUS;
         *minor = GSSEAP_CONTEXT_ESTABLISHED;
         goto cleanup;
@@ -164,124 +222,123 @@ gssEapSmStep(OM_uint32 *minor,
     if (GSS_ERROR(major))
         goto cleanup;
 
-    assert(innerInputTokens != GSS_C_NO_BUFFER_SET);
+    headerOffset = ctx->conversation.length;
 
-    major = gss_create_empty_buffer_set(minor, &innerOutputTokens);
-    if (GSS_ERROR(major))
-        goto cleanup;
+    assert(innerInputTokens != GSS_C_NO_BUFFER_SET);
 
-    assert(innerOutputTokens->count == 0);
-    assert(innerOutputTokens->elements == NULL);
+    /* Get ready to emit an output token */
+    tokType = CTX_IS_INITIATOR(ctx) ?
+        TOK_TYPE_INITIATOR_CONTEXT : TOK_TYPE_ACCEPTOR_CONTEXT;
 
-    innerOutputTokens->elements = (gss_buffer_desc *)GSSEAP_CALLOC(smCount,
-                                                                   sizeof(gss_buffer_desc));
-    if (innerOutputTokens->elements == NULL) {
-        major = GSS_S_FAILURE;
-        *minor = ENOMEM;
-        goto cleanup;
-    }
-
-    outputTokenTypes = (OM_uint32 *)GSSEAP_CALLOC(smCount, sizeof(OM_uint32));
-    if (outputTokenTypes == NULL) {
-        major = GSS_S_FAILURE;
-        *minor = ENOMEM;
+    major = gssEapRecordContextTokenHeader(minor, ctx, tokType);
+    if (GSS_ERROR(major))
         goto cleanup;
-    }
 
-    /*
-     * Process all the tokens that are valid for the current state. If
-     * the processToken function returns GSS_S_COMPLETE, the state is
-     * advanced until there is a token to send or the ESTABLISHED state
-     * is reached.
-     */
-    do {
-        major = GSS_S_COMPLETE;
-
-        for (i = 0; i < smCount; i++) {
-            struct gss_eap_sm *smp = &sm[i];
-            int processToken = 0;
-            gss_buffer_t innerInputToken = GSS_C_NO_BUFFER;
-            OM_uint32 *inputTokenType = NULL;
-            gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER;
-
-            if ((smp->validStates & ctx->state) == 0)
-                continue;
-
-            if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) {
-                processToken = 1;
-            } else if ((smFlags & SM_FLAG_TRANSITION) == 0) {
-                for (j = 0; j < innerInputTokens->count; j++) {
-                    if ((inputTokenTypes[j] & ITOK_TYPE_MASK) == smp->inputTokenType) {
-                        processToken = 1;
-                        if (innerInputToken != GSS_C_NO_BUFFER) {
-                            major = GSS_S_DEFECTIVE_TOKEN;
-                            *minor = GSSEAP_DUPLICATE_ITOK;
-                            break;
-                        }
+    firstTokenOffset = ctx->conversation.length;
+
+    /* Process all the tokens that are valid for the current state. */
+    for (i = 0; i < smCount; i++) {
+        struct gss_eap_sm *smp = &sm[i];
+        int processToken = 0;
+        gss_buffer_t innerInputToken = GSS_C_NO_BUFFER;
+        OM_uint32 *inputTokenType = NULL;
+        gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER;
+
+        if ((smp->validStates & ctx->state) == 0)
+            continue;
+
+        /*
+         * We special case the first call to gss_init_sec_context so that
+         * all token providers have the opportunity to generate an initial
+         * context token. Providers where inputTokenType is ITOK_TYPE_NONE
+         * are always called and generally act on state transition boundaries,
+         * for example to advance the state after a series of optional tokens
+         * (as is the case with the extension token exchange) or to generate
+         * a new token after the state was advanced by a provider which did
+         * not emit a token.
+         */
+        if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) {
+            processToken = 1;
+        } else if ((smFlags & SM_FLAG_TRANSITED) == 0) {
+            /* Don't regurgitate a token which belonds to a previous state. */
+            for (j = 0; j < innerInputTokens->count; j++) {
+                if ((inputTokenTypes[j] & ITOK_TYPE_MASK) == smp->inputTokenType) {
+                    if (processToken) {
+                        /* Check for duplicate inner tokens */
+                        major = GSS_S_DEFECTIVE_TOKEN;
+                        *minor = GSSEAP_DUPLICATE_ITOK;
+                        break;
                     }
+                    processToken = 1;
                     innerInputToken = &innerInputTokens->elements[j];
                     inputTokenType = &inputTokenTypes[j];
                 }
             }
+            if (GSS_ERROR(major))
+                break;
+        }
 
-#ifdef GSSEAP_DEBUG
-            fprintf(stderr, "GSS-EAP: state %d processToken %d inputTokenType %08x "
-                    "innerInputToken %p innerOutputTokensCount %zd\n",
-                    ctx->state, processToken, smp->inputTokenType,
-                    innerInputToken, innerOutputTokens->count);
-#endif
+        if (processToken) {
+            enum gss_eap_state oldState = ctx->state;
 
-            if (processToken) {
-                smFlags = 0;
+            smFlags = 0;
+            if (inputTokenType != NULL && (*inputTokenType & ITOK_FLAG_CRITICAL))
+                smFlags |= SM_FLAG_INPUT_TOKEN_CRITICAL;
 
-                major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
-                                         timeReq, chanBindings, innerInputToken,
-                                         &innerOutputToken, &smFlags);
-                if (GSS_ERROR(major))
-                    break;
+            major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
+                                      timeReq, chanBindings, innerInputToken,
+                                      &innerOutputToken, &smFlags);
+            if (GSS_ERROR(major))
+                break;
 
-                if (inputTokenType != NULL)
-                    *inputTokenType |= ITOK_FLAG_VERIFIED;
+            if (inputTokenType != NULL)
+                *inputTokenType |= ITOK_FLAG_VERIFIED;
+            if (ctx->state < oldState)
+                i = 0; /* restart */
+            else if (ctx->state != oldState)
+                smFlags |= SM_FLAG_TRANSITED;
 
-                if (innerOutputToken.value != NULL) {
-                    innerOutputTokens->elements[innerOutputTokens->count] = innerOutputToken;
-                    assert(smp->outputTokenType != ITOK_TYPE_NONE);
-                    outputTokenTypes[innerOutputTokens->count] = smp->outputTokenType;
-                    if (smp->critical)
-                        outputTokenTypes[innerOutputTokens->count] |= ITOK_FLAG_CRITICAL;
-                    innerOutputTokens->count++;
-                }
-                if (smFlags & SM_FLAG_STOP_EVAL)
-                    break;
-            } else if (smp->required && smp->inputTokenType != ITOK_TYPE_NONE) {
-                major = GSS_S_DEFECTIVE_TOKEN;
-                *minor = GSSEAP_MISSING_REQUIRED_ITOK;
-                break;
-            }
-        }
+            if (innerOutputToken.value != NULL) {
+                OM_uint32 outputTokenType = smp->outputTokenType;
 
-        if (GSS_ERROR(major) || (smFlags & SM_FLAG_TRANSITION) == 0)
-            break;
+                if (smFlags & SM_FLAG_OUTPUT_TOKEN_CRITICAL)
+                    outputTokenType |= ITOK_FLAG_CRITICAL;
 
-        assert(ctx->state < GSSEAP_STATE_ESTABLISHED);
+                assert(smp->outputTokenType != ITOK_TYPE_NONE);
 
-#ifdef GSSEAP_DEBUG
-        fprintf(stderr, "GSS-EAP: state transition %s->%s\n",
-                gssEapStateToString(ctx->state),
-                gssEapStateToString(GSSEAP_STATE_NEXT(ctx->state)));
-#endif
+                tmpMajor = gssEapRecordInnerContextToken(&tmpMinor, ctx,
+                                                         &innerOutputToken,
+                                                         outputTokenType);
+                if (GSS_ERROR(tmpMajor)) {
+                    major = tmpMajor;
+                    *minor = tmpMinor;
+                    break;
+                }
 
-        ctx->state = GSSEAP_STATE_NEXT(ctx->state);
+                innerOutputTokenCount++;
+            }
 
-        if (innerOutputTokens->count != 0 || (smFlags & SM_FLAG_FORCE_SEND_TOKEN)) {
-            assert(major == GSS_S_CONTINUE_NEEDED || ctx->state == GSSEAP_STATE_ESTABLISHED);
-            break; /* send any tokens if we have them */
+            /*
+             * Break out if we made a state transition and have some tokens to send.
+             */
+            if (smFlags & SM_FLAG_SEND_TOKEN) {
+                SM_ASSERT_VALID(ctx, major);
+                break;
+            }
+        } else if ((smp->itokFlags & SM_ITOK_FLAG_REQUIRED) &&
+            smp->inputTokenType != ITOK_TYPE_NONE) {
+            /* Check for required inner tokens */
+#ifdef GSSEAP_DEBUG
+            fprintf(stderr, "GSS-EAP: missing required token %08X\n",
+                    smp->inputTokenType);
+#endif
+            major = GSS_S_DEFECTIVE_TOKEN;
+            *minor = GSSEAP_MISSING_REQUIRED_ITOK;
+            break;
         }
-    } while (ctx->state != GSSEAP_STATE_ESTABLISHED);
-
-    assert(innerOutputTokens->count <= smCount);
+    }
 
-    /* Check we understood all critical tokens */
+    /* Check we understood all critical tokens sent by peer */
     if (!GSS_ERROR(major)) {
         for (j = 0; j < innerInputTokens->count; j++) {
             if ((inputTokenTypes[j] & ITOK_FLAG_CRITICAL) &&
@@ -293,57 +350,43 @@ gssEapSmStep(OM_uint32 *minor,
         }
     }
 
-    /* Emit an error token if we are the acceptor */
+    /* Optionaly emit an error token if we are the acceptor */
     if (GSS_ERROR(major)) {
         if (CTX_IS_INITIATOR(ctx))
             goto cleanup; /* return error directly to caller */
 
         /* replace any emitted tokens with error token */
-        gss_release_buffer_set(&tmpMinor, &innerOutputTokens);
+        ctx->conversation.length = firstTokenOffset;
 
-        tmpMajor = makeErrorToken(&tmpMinor, major, *minor, &innerOutputTokens);
+        tmpMajor = recordErrorToken(&tmpMinor, ctx, major, *minor);
         if (GSS_ERROR(tmpMajor)) {
             major = tmpMajor;
             *minor = tmpMinor;
             goto cleanup;
         }
 
-        outputTokenTypes[0] = ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL;
+        innerOutputTokenCount = 1;
     }
 
-#ifdef GSSEAP_DEBUG
-    for (i = 0; i < innerOutputTokens->count; i++) {
-        fprintf(stderr, "GSS-EAP: type %08x length %zd value %p\n",
-                outputTokenTypes[i],
-                innerOutputTokens->elements[i].length,
-                innerOutputTokens->elements[i].value);
-    }
-#endif
-
-    /* Format composite output token */
-    if (innerOutputTokens->count != 0 ||            /* inner tokens to send */
+    /* Format output token from inner tokens */
+    if (innerOutputTokenCount != 0 ||               /* inner tokens to send */
         !CTX_IS_INITIATOR(ctx) ||                   /* any leg acceptor */
-        ctx->state != GSSEAP_STATE_ESTABLISHED) {   /* non-last leg initiator */
-        tmpMajor = gssEapEncodeInnerTokens(&tmpMinor, innerOutputTokens,
-                                           outputTokenTypes, &unwrappedOutputToken);
-        if (tmpMajor == GSS_S_COMPLETE) {
-            tmpMajor = gssEapMakeToken(&tmpMinor, ctx, &unwrappedOutputToken,
-                                       TOK_TYPE_ESTABLISH_CONTEXT, outputToken);
-            if (GSS_ERROR(tmpMajor)) {
-                major = tmpMajor;
-                *minor = tmpMinor;
-                goto cleanup;
-            }
+        !CTX_IS_ESTABLISHED(ctx)) {                 /* non-last leg initiator */
+        tmpMajor = makeContextToken(&tmpMinor, ctx, headerOffset, outputToken);
+        if (GSS_ERROR(tmpMajor)) {
+            major = tmpMajor;
+            *minor = tmpMinor;
+            goto cleanup;
         }
     }
 
-    assert(GSS_ERROR(major) ||
-           (major == GSS_S_CONTINUE_NEEDED && (ctx->state > GSSEAP_STATE_INITIAL && ctx->state < GSSEAP_STATE_ESTABLISHED)) ||
-           (major == GSS_S_COMPLETE && ctx->state == GSSEAP_STATE_ESTABLISHED));
+    /* If the context is established, empty tokens only to be emitted by initiator */
+    assert(!CTX_IS_ESTABLISHED(ctx) || ((outputToken->length == 0) == CTX_IS_INITIATOR(ctx)));
+
+    SM_ASSERT_VALID(ctx, major);
 
 cleanup:
     gss_release_buffer_set(&tmpMinor, &innerInputTokens);
-    gss_release_buffer_set(&tmpMinor, &innerOutputTokens);
     if (inputTokenTypes != NULL)
         GSSEAP_FREE(inputTokenTypes);
     if (outputTokenTypes != NULL)