Refactor gss-krb5 acquire_init_cred
authorGreg Hudson <ghudson@mit.edu>
Mon, 5 Sep 2011 16:34:35 +0000 (16:34 +0000)
committerGreg Hudson <ghudson@mit.edu>
Mon, 5 Sep 2011 16:34:35 +0000 (16:34 +0000)
These changes have no functional impact, but will allow the GSSAPI
changes for credential selection to be made with a minimum of code
movement.

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25159 dc483132-0cff-0310-8789-dd5450dbe970

src/lib/gssapi/krb5/acquire_cred.c

index b95a5397eedf4e429e858441ed6d1227e52b33b4..50c8ef4ad2ccdead64ef17a027f4f1dbbc683483 100644 (file)
@@ -262,282 +262,295 @@ acquire_accept_cred(krb5_context context,
 }
 #endif /* LEAN_CLIENT */
 
-/* get credentials corresponding to the default credential cache.
-   If successful, set the ccache-specific fields in cred.
-*/
-
-static OM_uint32
-acquire_init_cred(krb5_context context,
-                  OM_uint32 *minor_status,
-                  krb5_ccache req_ccache,
-                  krb5_principal desired_princ,
-                  gss_buffer_t password,
-                  krb5_gss_cred_id_rec *cred)
+#ifdef USE_KIM
+krb5_error_code
+get_ccache_kim(krb5_context context, krb5_principal desired_princ,
+               krb5_ccache *ccache_out)
 {
-    krb5_error_code code;
+    kim_error err;
+    kim_ccache kimccache = NULL;
+    kim_identity identity = NULL;
+    kim_credential_state state;
     krb5_ccache ccache;
-    krb5_principal ccache_princ = NULL, tmp_princ;
-    krb5_cc_cursor cur;
-    krb5_creds creds;
-    int got_endtime;
-    int caller_provided_ccache_name = 0;
-    krb5_data password_data, *cred_princ_realm;
-
-    cred->ccache = NULL;
 
-    /* load the GSS ccache name into the kg_context */
-
-    if (GSS_ERROR(kg_sync_ccache_name(context, minor_status)))
-        return GSS_S_FAILURE;
+    *ccache_out = NULL;
 
-    /* check to see if the caller provided a ccache name if so
-     * we will just use that and not search the cache collection */
-    if (GSS_ERROR(kg_caller_provided_ccache_name (minor_status, &caller_provided_ccache_name))) {
-        return GSS_S_FAILURE;
-    }
+    err = kim_identity_create_from_krb5_principal(&identity, context,
+                                                  desired_princ);
+    if (err)
+        goto cleanup;
 
-#if defined(USE_KIM) || defined(USE_LEASH)
-    if (desired_princ && !caller_provided_ccache_name && !req_ccache) {
-#if defined(USE_KIM)
-        kim_error err = KIM_NO_ERROR;
-        kim_ccache kimccache = NULL;
-        kim_identity identity = NULL;
-        kim_credential_state state;
+    err = kim_ccache_create_from_client_identity(&kimccache, identity);
+    if (err)
+        goto cleanup;
 
-        err = kim_identity_create_from_krb5_principal (&identity,
-                                                       context,
-                                                       desired_princ);
+    err = kim_ccache_get_state(kimccache, &state);
+    if (err)
+        goto cleanup;
 
-        if (!err) {
-            err = kim_ccache_create_from_client_identity (&kimccache, identity);
+    if (state != kim_credentials_state_valid) {
+        if (state == kim_credentials_state_needs_validation) {
+            err = kim_ccache_validate(kimccache, KIM_OPTIONS_DEFAULT);
+            if (err)
+                goto cleanup;
+        } else {
+            kim_ccache_free(&kimccache);
         }
+    }
 
-        if (!err) {
-            err = kim_ccache_get_state (kimccache, &state);
-        }
+    if (!kimccache && kim_library_allow_automatic_prompting()) {
+        /* ccache does not already exist, create a new one. */
+        err = kim_ccache_create_new(&kimccache, identity, KIM_OPTIONS_DEFAULT);
+        if (err)
+            goto cleanup;
+    }
 
-        if (!err && state != kim_credentials_state_valid) {
-            if (state == kim_credentials_state_needs_validation) {
-                err = kim_ccache_validate (kimccache, KIM_OPTIONS_DEFAULT);
-            } else {
-                kim_ccache_free (&kimccache);
-                ccache = NULL;
-            }
-        }
+    err = kim_ccache_get_krb5_ccache(kimccache, context, &ccache);
+    if (err)
+        goto cleanup;
 
-        if (!kimccache && kim_library_allow_automatic_prompting ()) {
-            /* ccache does not already exist, create a new one */
-            err = kim_ccache_create_new (&kimccache, identity,
-                                         KIM_OPTIONS_DEFAULT);
-        }
+    *ccache_out = ccache;
 
-        if (!err) {
-            err = kim_ccache_get_krb5_ccache (kimccache, context, &ccache);
-        }
+cleanup:
+    kim_ccache_free(&kimccache);
+    kim_identity_free(&identity);
+    return err;
+}
+#endif /* USE_KIM */
 
-        kim_ccache_free (&kimccache);
-        kim_identity_free (&identity);
+#ifdef USE_LEASH
+static krb5_error_code
+get_ccache_leash(krb5_context context, krb5_principal desired_princ,
+                 krb5_ccache *ccache_out)
+{
+    krb5_error_code code;
+    krb5_ccache ccache;
+    char ccname[256] = "";
 
-        if (err) {
-            *minor_status = err;
-            return GSS_S_CRED_UNAVAIL;
-        }
+    *ccache_out = NULL;
 
-#elif defined(USE_LEASH)
-        if ( hLeashDLL == INVALID_HANDLE_VALUE ) {
-            hLeashDLL = LoadLibrary(LEASH_DLL);
-            if ( hLeashDLL != INVALID_HANDLE_VALUE ) {
-                (FARPROC) pLeash_AcquireInitialTicketsIfNeeded =
-                    GetProcAddress(hLeashDLL, "not_an_API_Leash_AcquireInitialTicketsIfNeeded");
-            }
+    if (hLeashDLL == INVALID_HANDLE_VALUE) {
+        hLeashDLL = LoadLibrary(LEASH_DLL);
+        if (hLeashDLL != INVALID_HANDLE_VALUE) {
+            (FARPROC) pLeash_AcquireInitialTicketsIfNeeded =
+                GetProcAddress(hLeashDLL, "not_an_API_Leash_AcquireInitialTicketsIfNeeded");
         }
+    }
 
-        if ( pLeash_AcquireInitialTicketsIfNeeded ) {
-            char ccname[256]="";
-            pLeash_AcquireInitialTicketsIfNeeded(context, desired_princ, ccname, sizeof(ccname));
-            if (!ccname[0]) {
-                *minor_status = KRB5_CC_NOTFOUND;
-                return GSS_S_CRED_UNAVAIL;
-            }
+    if (pLeash_AcquireInitialTicketsIfNeeded) {
+        pLeash_AcquireInitialTicketsIfNeeded(context, desired_princ, ccname,
+                                             sizeof(ccname));
+        if (!ccname[0])
+            return KRB5_CC_NOTFOUND;
 
-            if ((code = krb5_cc_resolve (context, ccname, &ccache))) {
-                *minor_status = code;
-                return GSS_S_CRED_UNAVAIL;
-            }
-        } else {
-            /* leash dll not available, open the default credential cache */
+        code = krb5_cc_resolve(context, ccname, &ccache);
+        if (code)
+            return code;
+    } else {
+        /* leash dll not available, open the default credential cache. */
+        code = krb5int_cc_default(context, &ccache);
+        if (code)
+            return code;
+    }
 
-            if ((code = krb5int_cc_default(context, &ccache))) {
-                *minor_status = code;
-                return GSS_S_CRED_UNAVAIL;
-            }
-        }
+    *ccache_out = ccache;
+    return 0;
+}
 #endif /* USE_LEASH */
+
+/* Prepare to acquire credentials into ccache using password at
+ * init_sec_context time.  On success, cred takes ownership of ccache. */
+static krb5_error_code
+prep_ccache(krb5_context context, krb5_gss_cred_id_rec *cred,
+            krb5_ccache ccache, krb5_principal desired_princ,
+            gss_buffer_t password)
+{
+    krb5_error_code code;
+    krb5_principal ccache_princ;
+    krb5_data password_data = make_data(password->value, password->length);
+
+    /* Check the ccache principal or initialize a new cache. */
+    code = krb5_cc_get_principal(context, ccache, &ccache_princ);
+    if (code == 0) {
+        if (!krb5_principal_compare(context, ccache_princ, desired_princ))
+            return KG_CCACHE_NOMATCH;
+    } else if (code == KRB5_FCC_NOFILE) {
+        /* Cache file does not exist; create and initialize one. */
+        code = krb5_cc_initialize(context, ccache, desired_princ);
+        if (code)
+            return code;
     } else
-#endif /* USE_KIM || USE_LEASH */
-    {
-        if (req_ccache != NULL) {
-            /* Duplicate ccache handle */
-            code = krb5_cc_dup(context, req_ccache, &ccache);
-        } else {
-            /* Open the default credential cache */
-            code = krb5int_cc_default(context, &ccache);
-        }
-        if (code != 0) {
-            *minor_status = code;
-            return GSS_S_CRED_UNAVAIL;
-        }
+        return code;
+
+    /* Save the desired principal as the credential name if not already set. */
+    if (!cred->name) {
+        code = kg_init_name(context, desired_princ, NULL, NULL, NULL, 0,
+                            &cred->name);
+        if (code)
+            return code;
     }
 
-    /* turn off OPENCLOSE mode while extensive frobbing is going on */
+    /* Stash the password for later. */
+    code = krb5int_copy_data_contents_add0(context, &password_data,
+                                           &cred->password);
+    if (code)
+        return code;
+
+    cred->ccache = ccache;
+    return 0;
+}
+
+/* Check ccache and scan it for its expiry time.  On success, cred takes
+ * ownership of ccache. */
+static krb5_error_code
+scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred,
+            krb5_ccache ccache, krb5_principal desired_princ)
+{
+    krb5_error_code code;
+    krb5_principal ccache_princ = NULL, tgt_princ = NULL;
+    krb5_data *realm;
+    krb5_cc_cursor cursor;
+    krb5_creds creds;
+    krb5_timestamp endtime;
+    int got_endtime = 0, is_tgt;
+
+    /* Turn off OPENCLOSE mode while extensive frobbing is going on. */
     code = krb5_cc_set_flags(context, ccache, 0);
-    if (code == KRB5_FCC_NOFILE &&
-        password != GSS_C_NO_BUFFER && desired_princ != NULL) {
-        /* We will get initial creds later. */
-        code = krb5_cc_initialize(context, ccache, desired_princ);
-        if (code == 0)
-            code = krb5_cc_set_flags(context, ccache, 0);
-    }
-    if (code != 0) {
-        krb5_cc_close(context, ccache);
-        *minor_status = code;
-        return GSS_S_CRED_UNAVAIL;
-    }
+    if (code)
+        return code;
 
     code = krb5_cc_get_principal(context, ccache, &ccache_princ);
-    if (code != 0) {
-        krb5_cc_close(context, ccache);
-        *minor_status = code;
-        return GSS_S_FAILURE;
-    }
+    if (code != 0)
+        return code;
 
     /* Credentials cache principal must match the initiator name. */
-    if (desired_princ != NULL) {
-        if (!krb5_principal_compare(context, ccache_princ, desired_princ)) {
-            krb5_free_principal(context, ccache_princ);
-            krb5_cc_close(context, ccache);
-            *minor_status = KG_CCACHE_NOMATCH;
-            return GSS_S_CRED_UNAVAIL;
-        }
+    if (desired_princ != NULL &&
+        !krb5_principal_compare(context, ccache_princ, desired_princ)) {
+        code = KG_CCACHE_NOMATCH;
+        goto cleanup;
     }
 
-    /*
-     * If we are acquiring initiator-only default credentials, then set
-     * cred->name to the credentials cache principal name.
-     */
-    if (cred->name == NULL) {
-        if ((code = kg_init_name(context, ccache_princ, NULL, NULL, NULL,
-                                 KG_INIT_NAME_NO_COPY, &cred->name))) {
-            krb5_free_principal(context, ccache_princ);
-            krb5_cc_close(context, ccache);
-            *minor_status = code;
-            return GSS_S_FAILURE;
-        }
-    } else {
-        krb5_free_principal(context, ccache_princ);
+    /* Save the ccache principal as the credential name if not already set. */
+    if (!cred->name) {
+        code = kg_init_name(context, ccache_princ, NULL, NULL, NULL,
+                            KG_INIT_NAME_NO_COPY, &cred->name);
+        if (code)
+            goto cleanup;
+        ccache_princ = NULL;
     }
 
     assert(cred->name->princ != NULL);
-    cred_princ_realm = krb5_princ_realm(context, cred->name->princ);
+    realm = krb5_princ_realm(context, cred->name->princ);
+    code = krb5_build_principal_ext(context, &tgt_princ,
+                                    realm->length, realm->data,
+                                    KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
+                                    realm->length, realm->data,
+                                    0);
+    if (code)
+        return code;
 
-    if (password != GSS_C_NO_BUFFER) {
-        /* stash the password for later */
-        password_data.length = password->length;
-        password_data.data = (char *)password->value;
+    /* If there's a tgt for the principal's local realm in here, use its expiry
+     * time.  Otherwise use the first key. */
+    code = krb5_cc_start_seq_get(context, ccache, &cursor);
+    if (code) {
+        krb5_free_principal(context, tgt_princ);
+        return code;
+    }
+    while (!(code = krb5_cc_next_cred(context, ccache, &cursor, &creds))) {
+        is_tgt = krb5_principal_compare(context, tgt_princ, creds.server);
+        endtime = creds.times.endtime;
+        krb5_free_cred_contents(context, &creds);
+        if (is_tgt || !got_endtime)
+            cred->tgt_expire = creds.times.endtime;
+        got_endtime = 1;
+        if (is_tgt)
+            break;
+    }
+    krb5_cc_end_seq_get(context, ccache, &cursor);
+    if (code && code != KRB5_CC_END)
+        goto cleanup;
+    code = 0;
 
-        code = krb5int_copy_data_contents_add0(context, &password_data,
-                                               &cred->password);
-        if (code != 0) {
-            krb5_cc_close(context, ccache);
-            *minor_status = code;
-            return GSS_S_FAILURE;
-        }
+    if (!got_endtime) {         /* ccache is empty. */
+        code = KG_EMPTY_CCACHE;
+        goto cleanup;
+    }
 
-        /* restore the OPENCLOSE flag */
-        code = krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
-        if (code != 0) {
-            krb5_cc_close(context, ccache);
-            *minor_status = code;
-            return GSS_S_FAILURE;
-        }
+    (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
+    cred->ccache = ccache;
 
-        cred->ccache = ccache;
-        return GSS_S_COMPLETE;
-    }
+cleanup:
+    krb5_free_principal(context, ccache_princ);
+    krb5_free_principal(context, tgt_princ);
+    return code;
+}
 
-    /* iterate over the ccache, find the tgt */
+/* get credentials corresponding to the default credential cache.
+   If successful, set the ccache-specific fields in cred.
+*/
 
-    if ((code = krb5_cc_start_seq_get(context, ccache, &cur))) {
-        krb5_cc_close(context, ccache);
-        *minor_status = code;
-        return GSS_S_FAILURE;
-    }
+static OM_uint32
+acquire_init_cred(krb5_context context,
+                  OM_uint32 *minor_status,
+                  krb5_ccache req_ccache,
+                  krb5_principal desired_princ,
+                  gss_buffer_t password,
+                  krb5_gss_cred_id_rec *cred)
+{
+    krb5_error_code code;
+    krb5_ccache ccache = NULL;
+    int caller_ccname = 0;
 
-    /* this is hairy.  If there's a tgt for the principal's local realm
-       in here, that's what we want for the expire time.  But if
-       there's not, then we want to use the first key.  */
+    cred->ccache = NULL;
 
-    got_endtime = 0;
+    /* Load the GSS ccache name, if specified, into the context. */
+    if (GSS_ERROR(kg_sync_ccache_name(context, minor_status)))
+        return GSS_S_FAILURE;
+    if (GSS_ERROR(kg_caller_provided_ccache_name(minor_status,
+                                                 &caller_ccname)))
+        return GSS_S_FAILURE;
 
-    code = krb5_build_principal_ext(context, &tmp_princ,
-                                    cred_princ_realm->length,
-                                    cred_princ_realm->data,
-                                    KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
-                                    cred_princ_realm->length,
-                                    cred_princ_realm->data,
-                                    0);
-    if (code) {
-        krb5_cc_close(context, ccache);
+    /* Pick a credential cache. */
+    if (req_ccache != NULL) {
+        code = krb5_cc_dup(context, req_ccache, &ccache);
+    } else if (caller_ccname) {
+        /* Caller's ccache name has been set as the context default. */
+        code = krb5int_cc_default(context, &ccache);
+    } else if (desired_princ) {
+        /* Try to find an appropriate ccache for the desired name. */
+#if defined(USE_KIM)
+        code = get_ccache_kim(context, desired_princ, &ccache);
+#elif defined(USE_LEASH)
+        code = get_ccache_leash(context, desired_princ, &ccache);
+#else
+        code = 0;
+#endif
+    } else
+        code = 0;
+    if (code != 0) {
         *minor_status = code;
-        return GSS_S_FAILURE;
+        return GSS_S_CRED_UNAVAIL;
     }
-    while (!(code = krb5_cc_next_cred(context, ccache, &cur, &creds))) {
-        if (krb5_principal_compare(context, tmp_princ, creds.server)) {
-            cred->tgt_expire = creds.times.endtime;
-            got_endtime = 1;
-            *minor_status = 0;
-            code = 0;
-            krb5_free_cred_contents(context, &creds);
-            break;
-        }
-        if (got_endtime == 0) {
-            cred->tgt_expire = creds.times.endtime;
-            got_endtime = 1;
+    if (ccache == NULL) {
+        code = krb5int_cc_default(context, &ccache);
+        if (code != 0) {
+            *minor_status = code;
+            return GSS_S_CRED_UNAVAIL;
         }
-        krb5_free_cred_contents(context, &creds);
     }
-    krb5_free_principal(context, tmp_princ);
 
-    if (code && code != KRB5_CC_END) {
-        /* this means some error occurred reading the ccache */
-        krb5_cc_end_seq_get(context, ccache, &cur);
+    if (password != GSS_C_NO_BUFFER && desired_princ != NULL)
+        code = prep_ccache(context, cred, ccache, desired_princ, password);
+    else
+        code = scan_ccache(context, cred, ccache, desired_princ);
+    if (code != 0) {
         krb5_cc_close(context, ccache);
         *minor_status = code;
-        return GSS_S_FAILURE;
-    } else if (! got_endtime) {
-        /* this means the ccache was entirely empty */
-        krb5_cc_end_seq_get(context, ccache, &cur);
-        krb5_cc_close(context, ccache);
-        *minor_status = KG_EMPTY_CCACHE;
-        return GSS_S_FAILURE;
-    } else {
-        /* this means that we found an endtime to use. */
-        if ((code = krb5_cc_end_seq_get(context, ccache, &cur))) {
-            krb5_cc_close(context, ccache);
-            *minor_status = code;
-            return GSS_S_FAILURE;
-        }
-        if ((code = krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE))) {
-            krb5_cc_close(context, ccache);
-            *minor_status = code;
-            return GSS_S_FAILURE;
-        }
+        return GSS_S_CRED_UNAVAIL;
     }
 
-    /* the credentials match and are valid */
-
     cred->ccache = ccache;
-    /* minor_status is set while we are iterating over the ccache */
+    *minor_status = 0;
     return GSS_S_COMPLETE;
 }