sq cli_credentials_ccache_update_principal
[metze/samba/wip.git] / auth / credentials / credentials_krb5.c
index ca62e30ef73779163ef02d5b4b279ebcfa17f3e4..8057aac4b2807e8f95b76f2e0c9231c353273f01 100644 (file)
 #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,
@@ -63,6 +67,130 @@ static int free_dccache(struct ccache_container *ccc)
        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) 
@@ -102,12 +230,255 @@ _PUBLIC_ NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred,
        return NT_STATUS_OK;
 }
 
+/**
+ * @brief Initialize a Kerberos credential cache for later use.
+ *
+ * This functions initializes a Kerberos credential cache. If ccache_name is not
+ * specified it will either use the default credential cache or create a memory
+ * credential cache. This depends on the authentication credentials specified,
+ * username and password.
+ *
+ * @param[in]  cred     The credentials structure to init the cache on.
+ *
+ * @param[in]  lp_ctx   The loadparm context to use.
+ *
+ * @param[in]  ccache_name The name of the credential cache to open or
+ *                         NULL.
+ *
+ * @return true on success, false if an error occcured.
+ */
+_PUBLIC_ bool cli_credentials_ccache_init(struct cli_credentials *cred,
+                                         struct loadparm_context *lp_ctx,
+                                         const char *ccache_name)
+{
+       TALLOC_CTX *tmp_ctx;
+       krb5_error_code code;
+       enum credentials_obtained principal_obtained = CRED_UNINITIALISED;
+       struct ccache_container *ccc = NULL;
+       const char *principal;
+       const char *password = NULL;
+       bool use_memory_ccache = false;
+       bool ok = false;
+
+       if (cred->krb5_ccache_obtained != CRED_UNINITIALISED) {
+               return false;
+       }
+
+       tmp_ctx = talloc_new(cred);
+       if (tmp_ctx == NULL) {
+               return false;
+       }
+
+       principal = cli_credentials_get_principal_and_obtained(cred,
+                                                              tmp_ctx,
+                                                              &principal_obtained);
+       if (principal == NULL) {
+               goto done;
+       }
+
+       ccc = talloc_zero(tmp_ctx, struct ccache_container);
+       if (ccc == NULL) {
+               goto done;
+       }
+
+       code = cli_credentials_get_krb5_context(cred,
+                                               lp_ctx,
+                                               &ccc->smb_krb5_context);
+       if (principal_obtained == CRED_SPECIFIED) {
+               password = cli_credentials_get_password(cred);
+               if (password != NULL) {
+                       use_memory_ccache = true;
+               }
+       }
+
+       if (ccache_name == NULL && use_memory_ccache) {
+               ccache_name = talloc_asprintf(tmp_ctx, "MEMORY:cli_creds/%p", ccc);
+               if (ccache_name == NULL) {
+                       goto done;
+               }
+       }
+
+       if (ccache_name != NULL) {
+               code = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
+                                      ccache_name,
+                                      &ccc->ccache);
+       } else {
+               code = krb5_cc_default(ccc->smb_krb5_context->krb5_context,
+                                      &ccc->ccache);
+       }
+       if (code != 0) {
+               goto done;
+       }
+
+       if (use_memory_ccache) {
+               talloc_set_destructor(ccc, free_mccache);
+       } else {
+               talloc_set_destructor(ccc, free_dccache);
+       }
+
+       cred->krb5_ccache = talloc_steal(cred, ccc);
+       cred->krb5_ccache_obtained = CRED_SPECIFIED;
+
+       ok = true;
+done:
+       talloc_free(tmp_ctx);
+       return ok;
+}
+
+/**
+ * @brief Destroy a Kerberos credential cache.
+ *
+ * This function destroys any existing contents of a cache and closes it.
+ *
+ * @param[in]  cred     The cli_credentials structure.
+ *
+ * @return true on success, false otherwise.
+ */
+_PUBLIC_ bool cli_credentials_ccache_destroy(struct cli_credentials *cred)
+{
+       struct ccache_container *ccc = cred->krb5_ccache;
+       krb5_error_code code;
+
+       code = krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+                              ccc->ccache);
+       if (code != 0) {
+               return false;
+       }
+       ccc->ccache = NULL;
+
+       TALLOC_FREE(cred->krb5_ccache);
+       cred->krb5_ccache_obtained = CRED_UNINITIALISED;
+
+       return true;
+}
+
+/**
+ * @brief Reinitialize the Kerberos credential cache
+ *
+ * If the credential cache is a memory credential cache it will be destroyed
+ * and a new clean cache will be allocated. Existing caches will just be
+ * reopened.
+ *
+ * @param[in]  cred     The credential structure
+ *
+ * @param[in]  lp_ctx   The loadparm context.
+ *
+ * @return true on success, false otherwise.
+ */
+_PUBLIC_ bool cli_credentials_ccache_reinit(struct cli_credentials *cred,
+                                           struct loadparm_context *lp_ctx)
+{
+       krb5_context context;
+       krb5_error_code code;
+       char *tmp_name = NULL;
+       const char *ccache_name;
+       bool ok;
+       int cmp;
+
+       if (cred->krb5_ccache_obtained == CRED_UNINITIALISED) {
+               return false;
+       }
+       context = cred->krb5_ccache->smb_krb5_context->krb5_context;
+
+       code = krb5_cc_get_full_name(context,
+                                    cred->krb5_ccache->ccache,
+                                    &tmp_name);
+       if (code != 0) {
+               return false;
+       }
+
+       ccache_name = tmp_name;
+       cmp = strncasecmp_m(ccache_name, "MEMORY:", 7);
+       if (cmp == 0) {
+               ccache_name = NULL;
+       }
+
+       TALLOC_FREE(cred->krb5_ccache);
+       cred->krb5_ccache_obtained = CRED_UNINITIALISED;
+
+       ok = cli_credentials_ccache_init(cred, lp_ctx, ccache_name);
+       krb5_free_string(context, tmp_name);
+
+       return ok;
+}
+
+/**
+ * @brief Get the credential cache containter
+ *
+ * @param[in]  cred     The cli_credentials to get the ccache from.
+ *
+ * @return A pointer to the credential cache containter or NULL on error.
+ */
+_PUBLIC_ struct ccache_container *cli_credentials_ccache_get(struct cli_credentials *cred)
+{
+       return cred->krb5_ccache;
+}
+
+_PUBLIC_ bool cli_credentials_ccache_update_principal(struct cli_credentials *creds)
+{
+       krb5_context context;
+       struct ccache_container *ccc = cli_credentials_ccache_get(creds);
+       krb5_principal cc_principal = NULL;
+       krb5_error_code code;
+       char *principal;
+       char *realm;
+       bool ok;
+
+       if (ccc == NULL) {
+               return false;
+       }
+       context = ccc->smb_krb5_context->krb5_context;
+
+       code = krb5_cc_get_principal(context,
+                                    ccc->ccache,
+                                    &cc_principal);
+       if (code != 0) {
+               switch (code) {
+               /* Empty cache */
+               case KRB5_CC_NOTFOUND:
+               case KRB5_FCC_NOFILE:
+                       return true;
+               }
+               return false;
+       }
+
+       code = smb_krb5_unparse_name(creds,
+                                    context,
+                                    cc_principal,
+                                    &principal);
+       if (code != 0) {
+               return false;
+       }
+
+       ok = cli_credentials_set_principal(creds,
+                                          principal,
+                                          CRED_SPECIFIED);
+       TALLOC_FREE(principal);
+       if (!ok) {
+               krb5_free_principal(context, cc_principal);
+               return ok;
+       }
+
+       realm = smb_krb5_principal_get_realm(context, cc_principal);
+       krb5_free_principal(context, cc_principal);
+       if (realm == NULL) {
+               return false;
+       }
+       ok = cli_credentials_set_realm(creds,
+                                      realm,
+                                      CRED_SPECIFIED);
+       SAFE_FREE(realm);
+
+       return ok;
+}
+
 static int cli_credentials_set_from_ccache(struct cli_credentials *cred, 
                                           struct ccache_container *ccache,
                                           enum credentials_obtained obtained,
                                           const char **error_string)
 {
-       
+       bool ok;
+       char *realm;
        krb5_principal princ;
        krb5_error_code ret;
        char *name;
@@ -134,11 +505,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(
+               cred, 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);
+       TALLOC_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;
@@ -209,16 +593,17 @@ _PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred,
 
                if (ret) {
                        (*error_string) = error_message(ret);
+                       TALLOC_FREE(ccc);
                        return ret;
                }
+       }
+
+       cred->ccache = ccc;
+       cred->ccache_obtained = obtained;
 
-               cred->ccache = ccc;
-               cred->ccache_obtained = obtained;
-               talloc_steal(cred, ccc);
+       cli_credentials_invalidate_client_gss_creds(
+               cred, cred->ccache_obtained);
 
-               cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
-               return 0;
-       }
        return 0;
 }
 
@@ -579,9 +964,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);
 
@@ -593,8 +983,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);
 
        }
 
@@ -605,7 +996,7 @@ _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;
        }
 
@@ -694,8 +1085,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;
        }
@@ -711,8 +1102,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;
@@ -747,12 +1139,26 @@ static int cli_credentials_shallow_ccache(struct cli_credentials *cred)
        const struct ccache_container *old_ccc = NULL;
        struct ccache_container *ccc = NULL;
        char *ccache_name = NULL;
+       krb5_principal princ;
 
        old_ccc = cred->ccache;
        if (old_ccc == NULL) {
                return 0;
        }
 
+       ret = krb5_cc_get_principal(
+               old_ccc->smb_krb5_context->krb5_context,
+               old_ccc->ccache,
+               &princ);
+       if (ret != 0) {
+               /*
+                * This is an empty ccache. No point in copying anything.
+                */
+               cred->ccache = NULL;
+               return 0;
+       }
+       krb5_free_principal(old_ccc->smb_krb5_context->krb5_context, princ);
+
        ccc = talloc(cred, struct ccache_container);
        if (ccc == NULL) {
                return ENOMEM;
@@ -808,83 +1214,6 @@ _PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ct
        return dst;
 }
 
-static int smb_krb5_create_salt_principal(TALLOC_CTX *mem_ctx,
-                                         const char *samAccountName,
-                                         const char *realm,
-                                         const char **salt_principal,
-                                         const char **error_string)
-{
-       char *machine_username;
-       bool is_machine_account = false;
-       char *upper_realm;
-       TALLOC_CTX *tmp_ctx;
-       int rc = -1;
-
-       if (samAccountName == NULL) {
-               *error_string = "Cannot determine salt principal, no "
-                               "saltPrincipal or samAccountName specified";
-               return rc;
-       }
-
-       if (realm == NULL) {
-               *error_string = "Cannot make principal without a realm";
-               return rc;
-       }
-
-       tmp_ctx = talloc_new(mem_ctx);
-       if (tmp_ctx == NULL) {
-               *error_string = "Cannot allocate talloc context";
-               return rc;
-       }
-
-       upper_realm = strupper_talloc(tmp_ctx, realm);
-       if (upper_realm == NULL) {
-               *error_string = "Cannot allocate to upper case realm";
-               goto out;
-       }
-
-       machine_username = strlower_talloc(tmp_ctx, samAccountName);
-       if (!machine_username) {
-               *error_string = "Cannot duplicate samAccountName";
-               goto out;
-       }
-
-       if (machine_username[strlen(machine_username) - 1] == '$') {
-               machine_username[strlen(machine_username) - 1] = '\0';
-               is_machine_account = true;
-       }
-
-       if (is_machine_account) {
-               char *lower_realm;
-
-               lower_realm = strlower_talloc(tmp_ctx, realm);
-               if (lower_realm == NULL) {
-                       *error_string = "Cannot allocate to lower case realm";
-                       goto out;
-               }
-
-               *salt_principal = talloc_asprintf(mem_ctx,
-                                                 "host/%s.%s@%s",
-                                                 machine_username,
-                                                 lower_realm,
-                                                 upper_realm);
-       } else {
-               *salt_principal = talloc_asprintf(mem_ctx,
-                                                 "%s@%s",
-                                                 machine_username,
-                                                 upper_realm);
-       }
-       if (*salt_principal == NULL) {
-               *error_string = "Cannot create salt principal";
-               goto out;
-       }
-
-       rc = 0;
-out:
-       talloc_free(tmp_ctx);
-       return rc;
-}
-
 /* 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.
@@ -900,9 +1229,10 @@ _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred,
        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);
-       const char *error_string;
-       const char *salt_principal;
+       char *salt_principal = NULL;
+       uint32_t uac_flags = 0;
 
        if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
                                          cred->username_obtained))) {
@@ -925,16 +1255,34 @@ _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred,
                return ENOMEM;
        }
 
-       /*
-        * FIXME: Currently there is no better way than to create the correct
-        * salt principal by checking if the username ends with a '$'. It would
-        * be better if it is part of the credentials.
-        */
-       ret = smb_krb5_create_salt_principal(mem_ctx,
-                                            username,
-                                            realm,
-                                            &salt_principal,
-                                            &error_string);
+       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;
@@ -1071,14 +1419,17 @@ _PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred,
        }
 
        if (ktc->password_based || obtained < CRED_SPECIFIED) {
-               /* This creates a GSSAPI cred_id_t for match-by-key with only the 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, matching by name */
-               maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab,
-                                               &gcc->creds);
+               /*
+                * 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;