credentials: Workaround krb5_cc_remove_cred not implemented in MIT kerberos
authorSamuel Cabrero <scabrero@suse.de>
Wed, 27 Mar 2019 16:12:09 +0000 (17:12 +0100)
committerJeremy Allison <jra@samba.org>
Mon, 29 Apr 2019 19:15:48 +0000 (19:15 +0000)
Signed-off-by: Samuel Cabrero <scabrero@suse.de>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
Autobuild-User(master): Jeremy Allison <jra@samba.org>
Autobuild-Date(master): Mon Apr 29 19:15:48 UTC 2019 on sn-devel-184

auth/credentials/credentials_krb5.c

index 901c573..20e677e 100644 (file)
@@ -365,6 +365,144 @@ _PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred,
        return 0;
 }
 
+#ifndef SAMBA4_USES_HEIMDAL
+/*
+ * This function is a workaround for old MIT Kerberos versions which did not
+ * implement the krb5_cc_remove_cred function. It creates a temporary
+ * credentials cache to copy the credentials in the current cache
+ * except the one we want to remove and then overwrites the contents of the
+ * current cache with the temporary copy.
+ */
+static krb5_error_code krb5_cc_remove_cred_wrap(struct ccache_container *ccc,
+                                               krb5_creds *creds)
+{
+       krb5_ccache dummy_ccache = NULL;
+       krb5_creds cached_creds = {0};
+       krb5_cc_cursor cursor = NULL;
+       krb5_error_code code;
+       char *dummy_name;
+
+       dummy_name = talloc_asprintf(ccc,
+                                    "MEMORY:copy_ccache-%p",
+                                    &ccc->ccache);
+       if (dummy_name == NULL) {
+               return KRB5_CC_NOMEM;
+       }
+
+       code = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
+                              dummy_name,
+                              &dummy_ccache);
+       if (code != 0) {
+               DBG_ERR("krb5_cc_resolve failed: %s\n",
+                       smb_get_krb5_error_message(
+                               ccc->smb_krb5_context->krb5_context,
+                               code, ccc));
+               TALLOC_FREE(dummy_name);
+               return code;
+       }
+
+       TALLOC_FREE(dummy_name);
+
+       code = krb5_cc_start_seq_get(ccc->smb_krb5_context->krb5_context,
+                                    ccc->ccache,
+                                    &cursor);
+       if (code != 0) {
+               krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+                               dummy_ccache);
+
+               DBG_ERR("krb5_cc_start_seq_get failed: %s\n",
+                       smb_get_krb5_error_message(
+                               ccc->smb_krb5_context->krb5_context,
+                               code, ccc));
+               return code;
+       }
+
+       while ((code = krb5_cc_next_cred(ccc->smb_krb5_context->krb5_context,
+                                        ccc->ccache,
+                                        &cursor,
+                                        &cached_creds)) == 0) {
+               /* If the principal matches skip it and do not copy to the
+                * temporary cache as this is the one we want to remove */
+               if (krb5_principal_compare_flags(
+                               ccc->smb_krb5_context->krb5_context,
+                               creds->server,
+                               cached_creds.server,
+                               0)) {
+                       continue;
+               }
+
+               code = krb5_cc_store_cred(
+                               ccc->smb_krb5_context->krb5_context,
+                               dummy_ccache,
+                               &cached_creds);
+               if (code != 0) {
+                       krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+                                       dummy_ccache);
+                       DBG_ERR("krb5_cc_store_cred failed: %s\n",
+                               smb_get_krb5_error_message(
+                                       ccc->smb_krb5_context->krb5_context,
+                                       code, ccc));
+                       return code;
+               }
+       }
+
+       if (code == KRB5_CC_END) {
+               krb5_cc_end_seq_get(ccc->smb_krb5_context->krb5_context,
+                                   dummy_ccache,
+                                   &cursor);
+               code = 0;
+       }
+
+       if (code != 0) {
+               krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+                               dummy_ccache);
+               DBG_ERR("krb5_cc_next_cred failed: %s\n",
+                       smb_get_krb5_error_message(
+                               ccc->smb_krb5_context->krb5_context,
+                               code, ccc));
+               return code;
+       }
+
+       code = krb5_cc_initialize(ccc->smb_krb5_context->krb5_context,
+                                 ccc->ccache,
+                                 creds->client);
+       if (code != 0) {
+               krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+                               dummy_ccache);
+               DBG_ERR("krb5_cc_initialize failed: %s\n",
+                       smb_get_krb5_error_message(
+                               ccc->smb_krb5_context->krb5_context,
+                               code, ccc));
+               return code;
+       }
+
+       code = krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context,
+                                 dummy_ccache,
+                                 ccc->ccache);
+       if (code != 0) {
+               krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+                               dummy_ccache);
+               DBG_ERR("krb5_cc_copy_creds failed: %s\n",
+                       smb_get_krb5_error_message(
+                               ccc->smb_krb5_context->krb5_context,
+                               code, ccc));
+               return code;
+       }
+
+       code = krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+                              dummy_ccache);
+       if (code != 0) {
+               DBG_ERR("krb5_cc_destroy failed: %s\n",
+                       smb_get_krb5_error_message(
+                               ccc->smb_krb5_context->krb5_context,
+                               code, ccc));
+               return code;
+       }
+
+       return code;
+}
+#endif
+
 /*
  * Indicate the we failed to log in to this service/host with these
  * credentials.  The caller passes an unsigned int which they
@@ -429,6 +567,13 @@ _PUBLIC_ bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred
        }
 
        ret = krb5_cc_remove_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds);
+#ifndef SAMBA4_USES_HEIMDAL
+       if (ret == KRB5_CC_NOSUPP) {
+               /* Old MIT kerberos versions did not implement
+                * krb5_cc_remove_cred */
+               ret = krb5_cc_remove_cred_wrap(ccc, &creds);
+       }
+#endif
        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) {
@@ -438,6 +583,10 @@ _PUBLIC_ bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred
                 * creds don't exist, which is why we do a separate
                 * krb5_cc_retrieve_cred() above.
                 */
+               DBG_ERR("krb5_cc_remove_cred failed: %s\n",
+                       smb_get_krb5_error_message(
+                               ccc->smb_krb5_context->krb5_context,
+                               ret, ccc));
                return false;
        }
        return true;