mit: make it possible to build with MIT kerberos and --picky-developer
[metze/samba/wip.git] / auth / credentials / credentials_krb5.c
index 2c93a8febc95cb15ff6ce622c0fcfdbbe50e65bd..4c903f2dda431f41b417f0c5ad8db6023254b5be 100644 (file)
@@ -26,6 +26,7 @@
 #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"
@@ -48,7 +49,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;
@@ -212,6 +213,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,
@@ -454,7 +518,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 && 
@@ -486,18 +553,8 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                }
        }
 
-
-       if (cred->ccache_obtained == CRED_UNINITIALISED) {
-               /* Only attempt to re-acquire ccache if it is not already in place.
-                * this is important for client-side use within frameworks with already acquired tickets
-                * like Apache+mod_auth_kerb+Python
-                */
-               ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
-                                                &ccache, error_string);
-       } else {
-               ccache = cred->ccache;
-       }
-
+       ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
+                                        &ccache, error_string);
        if (ret) {
                if (cli_credentials_get_kerberos_state(cred) == CRED_MUST_USE_KERBEROS) {
                        DEBUG(1, ("Failed to get kerberos credentials (kerberos required): %s\n", *error_string));
@@ -543,7 +600,7 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                return ret;
        }
 
-#ifdef SAMBA4_USES_HEIMDAL /* MIT lacks krb5_get_default_in_tkt_etypes */
+
        /*
         * transfer the enctypes from the smb_krb5_context to the gssapi layer
         *
@@ -555,9 +612,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;
 
@@ -566,7 +622,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) {
@@ -578,12 +634,19 @@ _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                        return ret;
                }
        }
-#endif
-#ifdef SAMBA4_USES_HEIMDAL /* MIT lacks GSS_KRB5_CRED_NO_CI_FLAGS_X */
 
-       /* 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);
@@ -669,6 +732,150 @@ _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;
+}
+
+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.
@@ -683,6 +890,10 @@ _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 *realm = cli_credentials_get_realm(cred);
+       const char *error_string;
+       const char *salt_principal;
 
        if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
                                          cred->username_obtained))) {
@@ -705,13 +916,30 @@ _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);
+       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),
-                                       cli_credentials_get_username(cred),
-                                       cli_credentials_get_realm(cred),
-                                       cli_credentials_get_kvno(cred),
-                                       &keytab, &keytab_name);
+                                           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;
@@ -727,6 +955,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;
@@ -828,12 +1061,12 @@ _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 */
+       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 */
+               /* 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);
        }