krb5-samba: interdomain trust uses different salt principal
[samba.git] / auth / credentials / credentials_krb5.c
index 74dbb0a5d8c8f44c6b59e5c1248562e9fd585217..d36797bf0f37f952a7102c8a51050225777e9ec4 100644 (file)
 
 #include "includes.h"
 #include "system/kerberos.h"
+#include "system/gssapi.h"
 #include "auth/kerberos/kerberos.h"
 #include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_internal.h"
 #include "auth/credentials/credentials_proto.h"
 #include "auth/credentials/credentials_krb5.h"
 #include "auth/kerberos/kerberos_credentials.h"
 #include "auth/kerberos/kerberos_srv_keytab.h"
 #include "auth/kerberos/kerberos_util.h"
+#include "auth/kerberos/pac_utils.h"
 #include "param/param.h"
+#include "../libds/common/flags.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
 
 static void cli_credentials_invalidate_client_gss_creds(
                                        struct cli_credentials *cred,
                                        enum credentials_obtained obtained);
 
+/* Free a memory ccache */
+static int free_mccache(struct ccache_container *ccc)
+{
+       if (ccc->ccache != NULL) {
+               krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+                               ccc->ccache);
+               ccc->ccache = NULL;
+       }
+
+       return 0;
+}
+
+/* Free a disk-based ccache */
+static int free_dccache(struct ccache_container *ccc)
+{
+       if (ccc->ccache != NULL) {
+               krb5_cc_close(ccc->smb_krb5_context->krb5_context,
+                             ccc->ccache);
+               ccc->ccache = NULL;
+       }
+
+       return 0;
+}
+
+static uint32_t smb_gss_krb5_copy_ccache(uint32_t *min_stat,
+                                        gss_cred_id_t cred,
+                                        struct ccache_container *ccc)
+{
+#ifndef SAMBA4_USES_HEIMDAL /* MIT 1.10 */
+       krb5_context context = ccc->smb_krb5_context->krb5_context;
+       krb5_ccache dummy_ccache = NULL;
+       krb5_creds creds = {0};
+       krb5_cc_cursor cursor = NULL;
+       krb5_principal princ = NULL;
+       krb5_error_code code;
+       char *dummy_name;
+       uint32_t maj_stat = GSS_S_FAILURE;
+
+       dummy_name = talloc_asprintf(ccc,
+                                    "MEMORY:gss_krb5_copy_ccache-%p",
+                                    &ccc->ccache);
+       if (dummy_name == NULL) {
+               *min_stat = ENOMEM;
+               return GSS_S_FAILURE;
+       }
+
+       /*
+        * Create a dummy ccache, so we can iterate over the credentials
+        * and find the default principal for the ccache we want to
+        * copy. The new ccache needs to be initialized with this
+        * principal.
+        */
+       code = krb5_cc_resolve(context, dummy_name, &dummy_ccache);
+       TALLOC_FREE(dummy_name);
+       if (code != 0) {
+               *min_stat = code;
+               return GSS_S_FAILURE;
+       }
+
+       /*
+        * We do not need set a default principal on the temporary dummy
+        * ccache, as we do consume it at all in this function.
+        */
+       maj_stat = gss_krb5_copy_ccache(min_stat, cred, dummy_ccache);
+       if (maj_stat != 0) {
+               krb5_cc_close(context, dummy_ccache);
+               return maj_stat;
+       }
+
+       code = krb5_cc_start_seq_get(context, dummy_ccache, &cursor);
+       if (code != 0) {
+               krb5_cc_close(context, dummy_ccache);
+               *min_stat = EINVAL;
+               return GSS_S_FAILURE;
+       }
+
+       code = krb5_cc_next_cred(context,
+                                dummy_ccache,
+                                &cursor,
+                                &creds);
+       if (code != 0) {
+               krb5_cc_close(context, dummy_ccache);
+               *min_stat = EINVAL;
+               return GSS_S_FAILURE;
+       }
+
+       do {
+               if (creds.ticket_flags & TKT_FLG_PRE_AUTH) {
+                       krb5_data *tgs;
+
+                       tgs = krb5_princ_component(context,
+                                                  creds.server,
+                                                  0);
+                       if (tgs != NULL && tgs->length >= 1) {
+                               int cmp;
+
+                               cmp = memcmp(tgs->data,
+                                            KRB5_TGS_NAME,
+                                            tgs->length);
+                               if (cmp == 0 && creds.client != NULL) {
+                                       princ = creds.client;
+                                       code = KRB5_CC_END;
+                                       break;
+                               }
+                       }
+               }
+
+               krb5_free_cred_contents(context, &creds);
+
+               code = krb5_cc_next_cred(context,
+                                        dummy_ccache,
+                                        &cursor,
+                                        &creds);
+       } while (code == 0);
+
+       if (code == KRB5_CC_END) {
+               krb5_cc_end_seq_get(context, dummy_ccache, &cursor);
+               code = 0;
+       }
+       krb5_cc_close(context, dummy_ccache);
+
+       if (code != 0 || princ == NULL) {
+               krb5_free_cred_contents(context, &creds);
+               *min_stat = EINVAL;
+               return GSS_S_FAILURE;
+       }
+
+       /*
+        * Set the default principal for the cache we copy
+        * into. This is needed to be able that other calls
+        * can read it with e.g. gss_acquire_cred() or
+        * krb5_cc_get_principal().
+        */
+       code = krb5_cc_initialize(context, ccc->ccache, princ);
+       if (code != 0) {
+               krb5_free_cred_contents(context, &creds);
+               *min_stat = EINVAL;
+               return GSS_S_FAILURE;
+       }
+       krb5_free_cred_contents(context, &creds);
+
+#endif /* SAMBA4_USES_HEIMDAL */
+
+       return gss_krb5_copy_ccache(min_stat,
+                                   cred,
+                                   ccc->ccache);
+}
+
 _PUBLIC_ int cli_credentials_get_krb5_context(struct cli_credentials *cred, 
                                     struct loadparm_context *lp_ctx,
                                     struct smb_krb5_context **smb_krb5_context) 
@@ -46,7 +201,7 @@ _PUBLIC_ int cli_credentials_get_krb5_context(struct cli_credentials *cred,
                return 0;
        }
 
-       ret = smb_krb5_init_context(cred, NULL, lp_ctx,
+       ret = smb_krb5_init_context(cred, lp_ctx,
                                    &cred->smb_krb5_context);
        if (ret) {
                cred->smb_krb5_context = NULL;
@@ -80,7 +235,8 @@ static int cli_credentials_set_from_ccache(struct cli_credentials *cred,
                                           enum credentials_obtained obtained,
                                           const char **error_string)
 {
-       
+       bool ok;
+       char *realm;
        krb5_principal princ;
        krb5_error_code ret;
        char *name;
@@ -107,11 +263,24 @@ static int cli_credentials_set_from_ccache(struct cli_credentials *cred,
                return ret;
        }
 
-       cli_credentials_set_principal(cred, name, obtained);
-
-       free(name);
+       ok = cli_credentials_set_principal(cred, name, obtained);
+       krb5_free_unparsed_name(ccache->smb_krb5_context->krb5_context, name);
+       if (!ok) {
+               krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
+               return ENOMEM;
+       }
 
+       realm = smb_krb5_principal_get_realm(ccache->smb_krb5_context->krb5_context,
+                                            princ);
        krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
+       if (realm == NULL) {
+               return ENOMEM;
+       }
+       ok = cli_credentials_set_realm(cred, realm, obtained);
+       SAFE_FREE(realm);
+       if (!ok) {
+               return ENOMEM;
+       }
 
        /* set the ccache_obtained here, as it just got set to UNINITIALISED by the calls above */
        cred->ccache_obtained = obtained;
@@ -119,21 +288,6 @@ static int cli_credentials_set_from_ccache(struct cli_credentials *cred,
        return 0;
 }
 
-/* Free a memory ccache */
-static int free_mccache(struct ccache_container *ccc)
-{
-       krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, ccc->ccache);
-
-       return 0;
-}
-
-/* Free a disk-based ccache */
-static int free_dccache(struct ccache_container *ccc) {
-       krb5_cc_close(ccc->smb_krb5_context->krb5_context, ccc->ccache);
-
-       return 0;
-}
-
 _PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred, 
                                        struct loadparm_context *lp_ctx,
                                        const char *name,
@@ -210,6 +364,69 @@ _PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred,
        return 0;
 }
 
+/*
+ * Indicate the we failed to log in to this service/host with these
+ * credentials.  The caller passes an unsigned int which they
+ * initialise to the number of times they would like to retry.
+ *
+ * This method is used to support re-trying with freshly fetched
+ * credentials in case a server is rebuilt while clients have
+ * non-expired tickets. When the client code gets a logon failure they
+ * throw away the existing credentials for the server and retry.
+ */
+_PUBLIC_ bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred,
+                                                   const char *principal,
+                                                   unsigned int *count)
+{
+       struct ccache_container *ccc;
+       krb5_creds creds, creds2;
+       int ret;
+
+       if (principal == NULL) {
+               /* no way to delete if we don't know the principal */
+               return false;
+       }
+
+       ccc = cred->ccache;
+       if (ccc == NULL) {
+               /* not a kerberos connection */
+               return false;
+       }
+
+       if (*count > 0) {
+               /* We have already tried discarding the credentials */
+               return false;
+       }
+       (*count)++;
+
+       ZERO_STRUCT(creds);
+       ret = krb5_parse_name(ccc->smb_krb5_context->krb5_context, principal, &creds.server);
+       if (ret != 0) {
+               return false;
+       }
+
+       ret = krb5_cc_retrieve_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds, &creds2);
+       if (ret != 0) {
+               /* don't retry - we didn't find these credentials to remove */
+               krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds);
+               return false;
+       }
+
+       ret = krb5_cc_remove_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds);
+       krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds);
+       krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds2);
+       if (ret != 0) {
+               /* don't retry - we didn't find these credentials to
+                * remove. Note that with the current backend this
+                * never happens, as it always returns 0 even if the
+                * creds don't exist, which is why we do a separate
+                * krb5_cc_retrieve_cred() above.
+                */
+               return false;
+       }
+       return true;
+}
+
 
 static int cli_credentials_new_ccache(struct cli_credentials *cred, 
                                      struct loadparm_context *lp_ctx,
@@ -301,8 +518,8 @@ _PUBLIC_ int cli_credentials_get_named_ccache(struct cli_credentials *cred,
            cred->ccache_obtained > CRED_UNINITIALISED) {
                time_t lifetime;
                bool expired = false;
-               ret = krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context, 
-                                          cred->ccache->ccache, &lifetime);
+               ret = smb_krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context,
+                                              cred->ccache->ccache, &lifetime);
                if (ret == KRB5_CC_END) {
                        /* If we have a particular ccache set, without
                         * an initial ticket, then assume there is a
@@ -452,7 +669,10 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
        OM_uint32 maj_stat, min_stat;
        struct gssapi_creds_container *gcc;
        struct ccache_container *ccache;
+#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
        gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
+       gss_OID oid = discard_const(GSS_KRB5_CRED_NO_CI_FLAGS_X);
+#endif
        krb5_enctype *etypes = NULL;
 
        if (cred->client_gss_creds_obtained >= cred->client_gss_creds_threshold && 
@@ -501,9 +721,14 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                return ENOMEM;
        }
 
-       maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL, 
-                                       &gcc->creds);
-       if ((maj_stat == GSS_S_FAILURE) && (min_stat == (OM_uint32)KRB5_CC_END || min_stat == (OM_uint32) KRB5_CC_NOTFOUND)) {
+       maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context,
+                                           ccache->ccache, NULL, NULL,
+                                           &gcc->creds);
+       if ((maj_stat == GSS_S_FAILURE) &&
+           (min_stat == (OM_uint32)KRB5_CC_END ||
+            min_stat == (OM_uint32)KRB5_CC_NOTFOUND ||
+            min_stat == (OM_uint32)KRB5_FCC_NOFILE))
+       {
                /* This CCACHE is no good.  Ensure we don't use it again */
                cli_credentials_unconditionally_invalidate_ccache(cred);
 
@@ -515,8 +740,9 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                        return ret;
                }
 
-               maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL,
-                                               &gcc->creds);
+               maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context,
+                                                   ccache->ccache, NULL, NULL,
+                                                   &gcc->creds);
 
        }
 
@@ -527,10 +753,11 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                } else {
                        ret = EINVAL;
                }
-               (*error_string) = talloc_asprintf(cred, "gss_krb5_import_cred failed: %s", error_message(ret));
+               (*error_string) = talloc_asprintf(cred, "smb_gss_krb5_import_cred failed: %s", error_message(ret));
                return ret;
        }
 
+
        /*
         * transfer the enctypes from the smb_krb5_context to the gssapi layer
         *
@@ -542,9 +769,8 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
         * and used for the AS-REQ, so it wasn't possible to disable the usage
         * of AES keys.
         */
-       min_stat = krb5_get_default_in_tkt_etypes(ccache->smb_krb5_context->krb5_context,
-                                                 KRB5_PDU_NONE,
-                                                 &etypes);
+       min_stat = smb_krb5_get_allowed_etypes(ccache->smb_krb5_context->krb5_context,
+                                              &etypes);
        if (min_stat == 0) {
                OM_uint32 num_ktypes;
 
@@ -553,7 +779,7 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds,
                                                           num_ktypes,
                                                           (int32_t *) etypes);
-               krb5_xfree (etypes);
+               SAFE_FREE(etypes);
                if (maj_stat) {
                        talloc_free(gcc);
                        if (min_stat) {
@@ -566,9 +792,18 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                }
        }
 
-       /* don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG */
+#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
+       /*
+        * Don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG.
+        *
+        * This allows us to disable SIGN and SEAL on a TLS connection with
+        * GSS-SPNENO. For example ldaps:// connections.
+        *
+        * https://groups.yahoo.com/neo/groups/cat-ietf/conversations/topics/575
+        * http://krbdev.mit.edu/rt/Ticket/Display.html?id=6938
+        */
        maj_stat = gss_set_cred_option(&min_stat, &gcc->creds,
-                                      GSS_KRB5_CRED_NO_CI_FLAGS_X,
+                                      oid,
                                       &empty_buffer);
        if (maj_stat) {
                talloc_free(gcc);
@@ -580,7 +815,7 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                (*error_string) = talloc_asprintf(cred, "gss_set_cred_option failed: %s", error_message(ret));
                return ret;
        }
-
+#endif
        cred->client_gss_creds_obtained = cred->ccache_obtained;
        talloc_set_destructor(gcc, free_gssapi_creds);
        cred->client_gss_creds = gcc;
@@ -607,8 +842,8 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
 {
        int ret;
        OM_uint32 maj_stat, min_stat;
-       struct ccache_container *ccc;
-       struct gssapi_creds_container *gcc;
+       struct ccache_container *ccc = NULL;
+       struct gssapi_creds_container *gcc = NULL;
        if (cred->client_gss_creds_obtained > obtained) {
                return 0;
        }
@@ -624,8 +859,9 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                return ret;
        }
 
-       maj_stat = gss_krb5_copy_ccache(&min_stat, 
-                                       gssapi_cred, ccc->ccache);
+       maj_stat = smb_gss_krb5_copy_ccache(&min_stat,
+                                           gssapi_cred,
+                                           ccc);
        if (maj_stat) {
                if (min_stat) {
                        ret = min_stat;
@@ -654,6 +890,73 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
        return ret;
 }
 
+static int cli_credentials_shallow_ccache(struct cli_credentials *cred)
+{
+       krb5_error_code ret;
+       const struct ccache_container *old_ccc = NULL;
+       struct ccache_container *ccc = NULL;
+       char *ccache_name = NULL;
+
+       old_ccc = cred->ccache;
+       if (old_ccc == NULL) {
+               return 0;
+       }
+
+       ccc = talloc(cred, struct ccache_container);
+       if (ccc == NULL) {
+               return ENOMEM;
+       }
+       *ccc = *old_ccc;
+       ccc->ccache = NULL;
+
+       ccache_name = talloc_asprintf(ccc, "MEMORY:%p", ccc);
+
+       ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
+                             ccache_name, &ccc->ccache);
+       if (ret != 0) {
+               TALLOC_FREE(ccc);
+               return ret;
+       }
+
+       talloc_set_destructor(ccc, free_mccache);
+
+       TALLOC_FREE(ccache_name);
+
+       ret = smb_krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context,
+                                    old_ccc->ccache, ccc->ccache);
+       if (ret != 0) {
+               TALLOC_FREE(ccc);
+               return ret;
+       }
+
+       cred->ccache = ccc;
+       cred->client_gss_creds = NULL;
+       cred->client_gss_creds_obtained = CRED_UNINITIALISED;
+       return ret;
+}
+
+_PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ctx,
+                                               struct cli_credentials *src)
+{
+       struct cli_credentials *dst;
+       int ret;
+
+       dst = talloc(mem_ctx, struct cli_credentials);
+       if (dst == NULL) {
+               return NULL;
+       }
+
+       *dst = *src;
+
+       ret = cli_credentials_shallow_ccache(dst);
+       if (ret != 0) {
+               TALLOC_FREE(dst);
+               return NULL;
+       }
+
+       return dst;
+}
+
 /* Get the keytab (actually, a container containing the krb5_keytab)
  * attached to this context.  If this hasn't been done or set before,
  * it will be generated from the password.
@@ -668,6 +971,11 @@ _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred,
        const char *keytab_name;
        krb5_keytab keytab;
        TALLOC_CTX *mem_ctx;
+       const char *username = cli_credentials_get_username(cred);
+       const char *upn = NULL;
+       const char *realm = cli_credentials_get_realm(cred);
+       char *salt_principal = NULL;
+       uint32_t uac_flags = 0;
 
        if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
                                          cred->username_obtained))) {
@@ -690,12 +998,48 @@ _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred,
                return ENOMEM;
        }
 
-       ret = smb_krb5_create_memory_keytab(mem_ctx, smb_krb5_context,
-                                       cli_credentials_get_password(cred),
-                                       cli_credentials_get_username(cred),
-                                       cli_credentials_get_realm(cred),
-                                       cli_credentials_get_kvno(cred),
-                                       &keytab, &keytab_name);
+       switch (cred->secure_channel_type) {
+       case SEC_CHAN_WKSTA:
+       case SEC_CHAN_RODC:
+               uac_flags = UF_WORKSTATION_TRUST_ACCOUNT;
+               break;
+       case SEC_CHAN_BDC:
+               uac_flags = UF_SERVER_TRUST_ACCOUNT;
+               break;
+       case SEC_CHAN_DOMAIN:
+       case SEC_CHAN_DNS_DOMAIN:
+               uac_flags = UF_INTERDOMAIN_TRUST_ACCOUNT;
+               break;
+       default:
+               upn = cli_credentials_get_principal(cred, mem_ctx);
+               if (upn == NULL) {
+                       TALLOC_FREE(mem_ctx);
+                       return ENOMEM;
+               }
+               uac_flags = UF_NORMAL_ACCOUNT;
+               break;
+       }
+
+       ret = smb_krb5_salt_principal(realm,
+                                     username, /* sAMAccountName */
+                                     upn, /* userPrincipalName */
+                                     uac_flags,
+                                     mem_ctx,
+                                     &salt_principal);
+       if (ret) {
+               talloc_free(mem_ctx);
+               return ret;
+       }
+
+       ret = smb_krb5_create_memory_keytab(mem_ctx,
+                                           smb_krb5_context->krb5_context,
+                                           cli_credentials_get_password(cred),
+                                           username,
+                                           realm,
+                                           salt_principal,
+                                           cli_credentials_get_kvno(cred),
+                                           &keytab,
+                                           &keytab_name);
        if (ret) {
                talloc_free(mem_ctx);
                return ret;
@@ -711,6 +1055,11 @@ _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred,
        cred->keytab_obtained = (MAX(cred->principal_obtained, 
                                     cred->username_obtained));
 
+       /* We make this keytab up based on a password.  Therefore
+        * match-by-key is acceptable, we can't match on the wrong
+        * principal */
+       ktc->password_based = true;
+
        talloc_steal(cred, ktc);
        cred->keytab = ktc;
        *_ktc = cred->keytab;
@@ -812,15 +1161,18 @@ _PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred,
                return ENOMEM;
        }
 
-       if (obtained < CRED_SPECIFIED) {
-               /* This creates a GSSAPI cred_id_t with the principal and keytab set */
-               maj_stat = gss_krb5_import_cred(&min_stat, NULL, NULL, ktc->keytab,
-                                               &gcc->creds);
-       } else {
-               /* This creates a GSSAPI cred_id_t with the principal and keytab set */
-               maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab,
-                                               &gcc->creds);
+       if (ktc->password_based || obtained < CRED_SPECIFIED) {
+               /*
+                * This creates a GSSAPI cred_id_t for match-by-key with only
+                * the keytab set
+                */
+               princ = NULL;
        }
+       maj_stat = smb_gss_krb5_import_cred(&min_stat,
+                                           smb_krb5_context->krb5_context,
+                                           NULL, princ,
+                                           ktc->keytab,
+                                           &gcc->creds);
        if (maj_stat) {
                if (min_stat) {
                        ret = min_stat;