summaryrefslogtreecommitdiffstats
path: root/third_party/heimdal/lib/gssapi/krb5/acquire_cred.c
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/heimdal/lib/gssapi/krb5/acquire_cred.c')
-rw-r--r--third_party/heimdal/lib/gssapi/krb5/acquire_cred.c686
1 files changed, 686 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/gssapi/krb5/acquire_cred.c b/third_party/heimdal/lib/gssapi/krb5/acquire_cred.c
new file mode 100644
index 0000000..211dcaa
--- /dev/null
+++ b/third_party/heimdal/lib/gssapi/krb5/acquire_cred.c
@@ -0,0 +1,686 @@
+/*
+ * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "gsskrb5_locl.h"
+
+/*
+ * Find an element in a cred store. Returns GSS_S_COMPLETE if the cred store
+ * is absent or well formed, irrespective of whether the element exists. The
+ * caller should check for *value != NULL before using; values are typically
+ * optional, hence this behavior. (The caller should validate the return
+ * value at least once though, to check it is well-formed.)
+ */
+OM_uint32
+__gsskrb5_cred_store_find(OM_uint32 *minor_status,
+ gss_const_key_value_set_t cred_store,
+ const char *key,
+ const char **value)
+{
+ size_t i;
+
+ *value = NULL;
+
+ if (cred_store == GSS_C_NO_CRED_STORE)
+ return GSS_S_COMPLETE;
+ else if (cred_store->count == 0) {
+ *minor_status = GSS_KRB5_S_G_BAD_USAGE;
+ return GSS_S_NO_CRED;
+ }
+
+ for (i = 0; i < cred_store->count; i++) {
+ if (strcmp(key, cred_store->elements[i].key) == 0) {
+ if (*value) {
+ *value = NULL;
+ *minor_status = GSS_KRB5_S_G_BAD_USAGE;
+ return GSS_S_DUPLICATE_ELEMENT;
+ }
+ *value = cred_store->elements[i].value;
+ }
+ }
+
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32
+__gsskrb5_ccache_lifetime(OM_uint32 *minor_status,
+ krb5_context context,
+ krb5_ccache id,
+ krb5_principal principal,
+ OM_uint32 *lifetime)
+{
+ krb5_error_code kret;
+ time_t left;
+
+ kret = krb5_cc_get_lifetime(context, id, &left);
+ if (kret) {
+ *minor_status = kret;
+ return GSS_S_FAILURE;
+ }
+
+ *lifetime = left;
+
+ return GSS_S_COMPLETE;
+}
+
+
+
+
+static krb5_error_code
+get_system_keytab(krb5_context context,
+ gss_const_key_value_set_t cred_store,
+ krb5_keytab *keytab)
+{
+ krb5_error_code kret;
+ const char *cs_ktname;
+ OM_uint32 tmp;
+
+ __gsskrb5_cred_store_find(&tmp, cred_store, "keytab", &cs_ktname);
+
+ HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex);
+
+ if (cs_ktname)
+ kret = krb5_kt_resolve(context, cs_ktname, keytab);
+ else if (_gsskrb5_keytab != NULL) {
+ char *name = NULL;
+
+ kret = krb5_kt_get_full_name(context, _gsskrb5_keytab, &name);
+ if (kret == 0) {
+ kret = krb5_kt_resolve(context, name, keytab);
+ krb5_xfree(name);
+ }
+ } else
+ kret = krb5_kt_default(context, keytab);
+
+ HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex);
+
+ return (kret);
+}
+
+static krb5_error_code
+get_client_keytab(krb5_context context,
+ gss_const_key_value_set_t cred_store,
+ krb5_const_principal principal,
+ krb5_keytab *keytab)
+{
+ krb5_error_code ret;
+ const char *cs_ktname;
+ OM_uint32 tmp;
+
+ __gsskrb5_cred_store_find(&tmp, cred_store, "client_keytab", &cs_ktname);
+
+ if (cs_ktname)
+ ret = krb5_kt_resolve(context, cs_ktname, keytab);
+ else {
+ char *name = NULL;
+ ret = _krb5_kt_client_default_name(context, &name);
+ if (ret == 0)
+ ret = krb5_kt_resolve(context, name, keytab);
+ krb5_xfree(name);
+ }
+
+ if (ret == 0 && principal) {
+ krb5_keytab_entry entry;
+
+ ret = krb5_kt_get_entry(context, *keytab, principal,
+ 0, 0, &entry);
+ if (ret == 0)
+ krb5_kt_free_entry(context, &entry);
+ }
+
+ if (ret) {
+ if (*keytab) {
+ krb5_kt_close(context, *keytab);
+ *keytab = NULL;
+ }
+
+ ret = get_system_keytab(context, GSS_C_NO_CRED_STORE, keytab);
+ }
+
+ return ret;
+}
+
+static krb5_boolean
+is_valid_password_cred_store(gss_const_key_value_set_t cred_store)
+{
+ size_t i;
+
+ if (cred_store == GSS_C_NO_CRED_STORE)
+ return TRUE;
+
+ /* XXX don't check keytab, someday we will allow password+acceptor creds */
+ for (i = 0; i < cred_store->count; i++) {
+ if (strcmp(cred_store->elements[i].key, "ccache") == 0 ||
+ strcmp(cred_store->elements[i].key, "client_keytab") == 0)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * This function produces a cred with a MEMORY ccache containing a TGT
+ * acquired with a password.
+ */
+static OM_uint32
+acquire_cred_with_password(OM_uint32 *minor_status,
+ krb5_context context,
+ const char *password,
+ OM_uint32 time_req,
+ gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_const_key_value_set_t cred_store,
+ gsskrb5_cred handle)
+{
+ OM_uint32 ret = GSS_S_FAILURE;
+ krb5_creds cred;
+ krb5_init_creds_context ctx = NULL;
+ krb5_get_init_creds_opt *opt = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_error_code kret;
+ time_t now;
+ OM_uint32 left;
+ const char *realm;
+
+ if (!is_valid_password_cred_store(cred_store)) {
+ *minor_status = GSS_KRB5_S_G_BAD_PASSWORD_CRED_STORE;
+ return GSS_S_NO_CRED;
+ }
+
+ if (cred_usage == GSS_C_ACCEPT) {
+ /*
+ * TODO: Here we should eventually support user2user (when we get
+ * support for that via an extension to the mechanism
+ * allowing for more than two security context tokens),
+ * and/or new unique MEMORY keytabs (we have MEMORY keytab
+ * support, but we don't have a keytab equivalent of
+ * krb5_cc_new_unique()). Either way, for now we can't
+ * support this.
+ */
+ *minor_status = ENOTSUP; /* XXX Better error? */
+ return GSS_S_FAILURE;
+ }
+
+ memset(&cred, 0, sizeof(cred));
+
+ if (handle->principal == NULL) {
+ kret = krb5_get_default_principal(context, &handle->principal);
+ if (kret)
+ goto end;
+ }
+ realm = krb5_principal_get_realm(context, handle->principal);
+
+ kret = krb5_get_init_creds_opt_alloc(context, &opt);
+ if (kret == 0) {
+ krb5_get_init_creds_opt_set_default_flags(context, "gss_krb5", realm,
+ opt);
+ kret = krb5_init_creds_init(context, handle->principal, NULL, NULL, 0,
+ opt, &ctx);
+ }
+ if (kret == 0)
+ kret = _krb5_init_creds_set_fast_anon_pkinit_optimistic(context, ctx);
+ if (kret == 0)
+ kret = krb5_init_creds_set_password(context, ctx, password);
+
+ /*
+ * Get the current time before the AS exchange so we don't
+ * accidentally end up returning a value that puts advertised
+ * expiration past the real expiration.
+ *
+ * We need to do this because krb5_cc_get_lifetime() returns a
+ * relative time that we need to add to the current time. We ought
+ * to have a version of krb5_cc_get_lifetime() that returns absolute
+ * time...
+ */
+ krb5_timeofday(context, &now);
+
+ if (kret == 0)
+ kret = krb5_init_creds_get(context, ctx);
+ if (kret == 0)
+ kret = krb5_init_creds_get_creds(context, ctx, &cred);
+ if (kret == 0)
+ kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
+ if (kret == 0)
+ kret = krb5_cc_initialize(context, ccache, cred.client);
+ if (kret == 0)
+ kret = krb5_init_creds_store(context, ctx, ccache);
+ if (kret == 0)
+ kret = krb5_cc_store_cred(context, ccache, &cred);
+ if (kret)
+ goto end;
+
+ handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
+
+ ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
+ handle->principal, &left);
+ if (ret != GSS_S_COMPLETE)
+ goto end;
+ handle->endtime = now + left;
+ handle->ccache = ccache;
+ ccache = NULL;
+ ret = GSS_S_COMPLETE;
+
+end:
+ krb5_get_init_creds_opt_free(context, opt);
+ if (ctx)
+ krb5_init_creds_free(context, ctx);
+ if (ccache != NULL)
+ krb5_cc_destroy(context, ccache);
+ if (cred.client != NULL)
+ krb5_free_cred_contents(context, &cred);
+ if (ret != GSS_S_COMPLETE)
+ *minor_status = kret;
+ return (ret);
+}
+
+/*
+ * Acquires an initiator credential from a ccache or using a keytab.
+ */
+static OM_uint32
+acquire_initiator_cred(OM_uint32 *minor_status,
+ krb5_context context,
+ OM_uint32 time_req,
+ gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_const_key_value_set_t cred_store,
+ gsskrb5_cred handle)
+{
+ OM_uint32 ret;
+ krb5_creds cred;
+ krb5_get_init_creds_opt *opt;
+ krb5_principal def_princ = NULL;
+ krb5_ccache def_ccache = NULL;
+ krb5_ccache ccache = NULL; /* we may store into this ccache */
+ krb5_keytab keytab = NULL;
+ krb5_error_code kret = 0;
+ OM_uint32 left;
+ const char *cs_ccache_name;
+ time_t lifetime = 0;
+ time_t now;
+
+ memset(&cred, 0, sizeof(cred));
+
+ ret = __gsskrb5_cred_store_find(minor_status, cred_store,
+ "ccache", &cs_ccache_name);
+ if (GSS_ERROR(ret))
+ return ret;
+
+ ret = GSS_S_FAILURE;
+
+ /*
+ * Get current time early so we can set handle->endtime to a value that
+ * cannot accidentally be past the real endtime. We need a variant of
+ * krb5_cc_get_lifetime() that returns absolute endtime.
+ */
+ krb5_timeofday(context, &now);
+
+ /*
+ * First look for a ccache that has the desired_name (which may be
+ * the default credential name), unless a specific credential cache
+ * was included in cred_store.
+ *
+ * If we don't have an unexpired credential, acquire one with a
+ * keytab.
+ *
+ * If we acquire one with a keytab, save it in the ccache we found
+ * with the expired credential, if any.
+ *
+ * If we don't have any such ccache, then use a MEMORY ccache.
+ */
+
+ if (handle->principal != NULL && cs_ccache_name == NULL) {
+ /*
+ * Not default credential case. See if we can find a ccache in
+ * the cccol for the desired_name.
+ */
+ kret = krb5_cc_cache_match(context,
+ handle->principal,
+ &ccache);
+ if (kret == 0) {
+ kret = krb5_cc_get_lifetime(context, ccache, &lifetime);
+ if (kret == 0) {
+ if (lifetime > 0)
+ goto found;
+ else
+ goto try_keytab;
+ }
+ }
+ /*
+ * Fall through. We shouldn't find this in the default ccache
+ * either, but we'll give it a try, then we'll try using a keytab.
+ */
+ }
+
+ /*
+ * Either desired_name was GSS_C_NO_NAME (default cred) or
+ * krb5_cc_cache_match() failed (or found expired).
+ */
+ if (cs_ccache_name)
+ kret = krb5_cc_resolve(context, cs_ccache_name, &def_ccache);
+ else
+ kret = krb5_cc_default(context, &def_ccache);
+ if (kret != 0)
+ goto try_keytab;
+ kret = krb5_cc_get_lifetime(context, def_ccache, &lifetime);
+ if (kret != 0)
+ lifetime = 0;
+ kret = krb5_cc_get_principal(context, def_ccache, &def_princ);
+ if (kret != 0)
+ goto try_keytab;
+ /*
+ * Have a default ccache; see if it matches desired_name.
+ */
+ if (handle->principal == NULL ||
+ krb5_principal_compare(context, handle->principal,
+ def_princ) == TRUE) {
+ /*
+ * It matches.
+ *
+ * If we end up trying a keytab then we can write the result to
+ * the default ccache.
+ */
+ if (handle->principal == NULL) {
+ kret = krb5_copy_principal(context, def_princ, &handle->principal);
+ if (kret)
+ goto end;
+ }
+ if (ccache != NULL)
+ krb5_cc_close(context, ccache);
+ ccache = def_ccache;
+ def_ccache = NULL;
+ if (lifetime > 0)
+ goto found;
+ /* else we fall through and try using a keytab */
+ }
+
+try_keytab:
+ if (handle->principal == NULL) {
+ /* We need to know what client principal to use */
+ kret = krb5_get_default_principal(context, &handle->principal);
+ if (kret)
+ goto end;
+ }
+ kret = get_client_keytab(context, cred_store, handle->principal, &keytab);
+ if (kret)
+ goto end;
+
+ kret = krb5_get_init_creds_opt_alloc(context, &opt);
+ if (kret)
+ goto end;
+ krb5_timeofday(context, &now);
+ kret = krb5_get_init_creds_keytab(context, &cred, handle->principal,
+ keytab, 0, NULL, opt);
+ krb5_get_init_creds_opt_free(context, opt);
+ if (kret)
+ goto end;
+
+ /*
+ * We got a credential with a keytab. Save it if we can.
+ */
+ if (ccache == NULL) {
+ /*
+ * There's no ccache we can overwrite with the credentials we acquired
+ * with a keytab. We'll use a MEMORY ccache then.
+ *
+ * Note that an application that falls into this repeatedly will do an
+ * AS exchange every time it acquires a credential handle. Hopefully
+ * this doesn't happen much. A workaround is to kinit -k once so that
+ * we always re-initialize the matched/default ccache here. I.e., once
+ * there's a FILE/DIR ccache, we'll keep it frash automatically if we
+ * have a keytab, but if there's no FILE/DIR ccache, then we'll
+ * get a fresh credential *every* time we're asked.
+ */
+ kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
+ if (kret)
+ goto end;
+ handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
+ } /* else we'll re-initialize whichever ccache we matched above */
+
+ kret = krb5_cc_initialize(context, ccache, cred.client);
+ if (kret)
+ goto end;
+ kret = krb5_cc_store_cred(context, ccache, &cred);
+ if (kret)
+ goto end;
+
+found:
+ assert(handle->principal != NULL);
+ ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
+ handle->principal, &left);
+ if (ret != GSS_S_COMPLETE)
+ goto end;
+ handle->endtime = now + left;
+ handle->ccache = ccache;
+ ccache = NULL;
+ ret = GSS_S_COMPLETE;
+ kret = 0;
+
+end:
+ if (ccache != NULL) {
+ if ((handle->cred_flags & GSS_CF_DESTROY_CRED_ON_RELEASE) != 0)
+ krb5_cc_destroy(context, ccache);
+ else
+ krb5_cc_close(context, ccache);
+ }
+ if (def_ccache != NULL)
+ krb5_cc_close(context, def_ccache);
+ if (cred.client != NULL)
+ krb5_free_cred_contents(context, &cred);
+ if (def_princ != NULL)
+ krb5_free_principal(context, def_princ);
+ if (keytab != NULL)
+ krb5_kt_close(context, keytab);
+ if (ret != GSS_S_COMPLETE && kret != 0)
+ *minor_status = kret;
+ return (ret);
+}
+
+static OM_uint32
+acquire_acceptor_cred(OM_uint32 * minor_status,
+ krb5_context context,
+ OM_uint32 time_req,
+ gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_const_key_value_set_t cred_store,
+ gsskrb5_cred handle)
+{
+ OM_uint32 ret;
+ krb5_error_code kret;
+
+ ret = GSS_S_FAILURE;
+
+ kret = get_system_keytab(context, cred_store, &handle->keytab);
+ if (kret)
+ goto end;
+
+ /* check that the requested principal exists in the keytab */
+ if (handle->principal) {
+ krb5_keytab_entry entry;
+
+ kret = krb5_kt_get_entry(context, handle->keytab,
+ handle->principal, 0, 0, &entry);
+ if (kret)
+ goto end;
+ krb5_kt_free_entry(context, &entry);
+ ret = GSS_S_COMPLETE;
+ } else {
+ /*
+ * Check if there is at least one entry in the keytab before
+ * declaring it as an useful keytab.
+ */
+ krb5_keytab_entry tmp;
+ krb5_kt_cursor c;
+
+ kret = krb5_kt_start_seq_get (context, handle->keytab, &c);
+ if (kret)
+ goto end;
+ if (krb5_kt_next_entry(context, handle->keytab, &tmp, &c) == 0) {
+ krb5_kt_free_entry(context, &tmp);
+ ret = GSS_S_COMPLETE; /* ok found one entry */
+ }
+ krb5_kt_end_seq_get (context, handle->keytab, &c);
+ }
+end:
+ if (ret != GSS_S_COMPLETE) {
+ if (handle->keytab != NULL)
+ krb5_kt_close(context, handle->keytab);
+ if (kret != 0) {
+ *minor_status = kret;
+ }
+ }
+ return (ret);
+}
+
+
+OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from
+(OM_uint32 * minor_status,
+ gss_const_name_t desired_name,
+ OM_uint32 time_req,
+ gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_const_key_value_set_t cred_store,
+ gss_cred_id_t * output_cred_handle,
+ gss_OID_set *actual_mechs,
+ OM_uint32 *time_rec
+ )
+{
+ krb5_context context;
+ gsskrb5_cred handle;
+ OM_uint32 ret;
+ const char *password = NULL;
+
+ if (desired_mechs) {
+ int present = 0;
+
+ ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
+ desired_mechs, &present);
+ if (ret)
+ return ret;
+ if (!present) {
+ *minor_status = 0;
+ return GSS_S_BAD_MECH;
+ }
+ }
+
+ cred_usage &= GSS_C_OPTION_MASK;
+
+ if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE &&
+ cred_usage != GSS_C_BOTH) {
+ *minor_status = GSS_KRB5_S_G_BAD_USAGE;
+ return GSS_S_FAILURE;
+ }
+
+ ret = __gsskrb5_cred_store_find(minor_status, cred_store,
+ "password", &password);
+ if (GSS_ERROR(ret))
+ return ret;
+
+ GSSAPI_KRB5_INIT(&context);
+
+ *output_cred_handle = GSS_C_NO_CREDENTIAL;
+
+ handle = calloc(1, sizeof(*handle));
+ if (handle == NULL) {
+ *minor_status = ENOMEM;
+ return GSS_S_FAILURE;
+ }
+
+ handle->destination_realm = NULL;
+ HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
+
+ if (desired_name != GSS_C_NO_NAME) {
+ ret = _gsskrb5_canon_name(minor_status, context,
+ desired_name, &handle->principal);
+ if (ret) {
+ HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
+ free(handle);
+ return ret;
+ }
+ }
+
+ if (password) {
+ ret = acquire_cred_with_password(minor_status, context, password, time_req,
+ desired_mechs, cred_usage, cred_store, handle);
+ if (ret != GSS_S_COMPLETE) {
+ HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
+ krb5_free_principal(context, handle->principal);
+ free(handle);
+ return (ret);
+ }
+ } else {
+ /*
+ * Acquire a credential from the specified or background credential
+ * store (ccache, keytab).
+ */
+ if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) {
+ ret = acquire_initiator_cred(minor_status, context, time_req,
+ desired_mechs, cred_usage,
+ cred_store, handle);
+ if (ret != GSS_S_COMPLETE) {
+ HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
+ krb5_free_principal(context, handle->principal);
+ free(handle);
+ return (ret);
+ }
+ }
+ if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) {
+ ret = acquire_acceptor_cred(minor_status, context, time_req,
+ desired_mechs, cred_usage,
+ cred_store, handle);
+ if (ret != GSS_S_COMPLETE) {
+ HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
+ krb5_free_principal(context, handle->principal);
+ free(handle);
+ return (ret);
+ }
+ }
+ }
+ ret = gss_create_empty_oid_set(minor_status, &handle->mechanisms);
+ if (ret == GSS_S_COMPLETE)
+ ret = gss_add_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
+ &handle->mechanisms);
+ handle->usage = cred_usage;
+ if (ret == GSS_S_COMPLETE)
+ ret = _gsskrb5_inquire_cred(minor_status, (gss_cred_id_t)handle,
+ NULL, time_rec, NULL, actual_mechs);
+ if (ret != GSS_S_COMPLETE) {
+ if (handle->mechanisms != NULL)
+ gss_release_oid_set(NULL, &handle->mechanisms);
+ HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
+ krb5_free_principal(context, handle->principal);
+ free(handle);
+ return (ret);
+ }
+ *minor_status = 0;
+ *output_cred_handle = (gss_cred_id_t)handle;
+ return (GSS_S_COMPLETE);
+}