s4:kdc: Implement KDC plugin hardware authentication policy
[samba.git] / source3 / libads / kerberos_keytab.c
index 8eb7b2a7c6baac508284906251fcccbed87a4359..8dac25a0ef9d9c9a3a5293610585642de38cde55 100644 (file)
@@ -53,7 +53,7 @@ static krb5_error_code ads_keytab_open(krb5_context context,
                                           keytab_str,
                                           sizeof(keytab_str) - 2);
                if (ret != 0) {
-                       DBG_WARNING("Failed to get default keytab name");
+                       DBG_WARNING("Failed to get default keytab name\n");
                        goto out;
                }
                keytab_name = keytab_str;
@@ -228,6 +228,104 @@ out:
        return ok;
 }
 
+static int add_kt_entry_etypes(krb5_context context, TALLOC_CTX *tmpctx,
+                              ADS_STRUCT *ads, const char *salt_princ_s,
+                              krb5_keytab keytab, krb5_kvno kvno,
+                              const char *srvPrinc, const char *my_fqdn,
+                              krb5_data *password, bool update_ads)
+{
+       krb5_error_code ret = 0;
+       char *princ_s = NULL;
+       char *short_princ_s = NULL;
+       krb5_enctype enctypes[4] = {
+               ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+               ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+               ENCTYPE_ARCFOUR_HMAC,
+               0
+       };
+       size_t i;
+
+       /* Construct our principal */
+       if (strchr_m(srvPrinc, '@')) {
+               /* It's a fully-named principal. */
+               princ_s = talloc_asprintf(tmpctx, "%s", srvPrinc);
+               if (!princ_s) {
+                       ret = -1;
+                       goto out;
+               }
+       } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
+               /* It's the machine account, as used by smbclient clients. */
+               princ_s = talloc_asprintf(tmpctx, "%s@%s",
+                                         srvPrinc, lp_realm());
+               if (!princ_s) {
+                       ret = -1;
+                       goto out;
+               }
+       } else {
+               /* It's a normal service principal.  Add the SPN now so that we
+                * can obtain credentials for it and double-check the salt value
+                * used to generate the service's keys. */
+
+               if (!service_or_spn_to_kerberos_princ(tmpctx,
+                                                     srvPrinc,
+                                                     my_fqdn,
+                                                     &princ_s,
+                                                     &short_princ_s)) {
+                       ret = -1;
+                       goto out;
+               }
+
+               /* According to http://support.microsoft.com/kb/326985/en-us,
+                  certain principal names are automatically mapped to the
+                  host/... principal in the AD account.
+                  So only create these in the keytab, not in AD.  --jerry */
+
+               if (update_ads && !strequal(srvPrinc, "cifs") &&
+                   !strequal(srvPrinc, "host")) {
+                       if (!ads_set_machine_account_spns(tmpctx,
+                                                         ads,
+                                                         srvPrinc,
+                                                         my_fqdn)) {
+                               ret = -1;
+                               goto out;
+                       }
+               }
+       }
+
+       for (i = 0; enctypes[i]; i++) {
+
+               /* add the fqdn principal to the keytab */
+               ret = smb_krb5_kt_add_password(context,
+                                              keytab,
+                                              kvno,
+                                              princ_s,
+                                              salt_princ_s,
+                                              enctypes[i],
+                                              password);
+               if (ret) {
+                       DBG_WARNING("Failed to add entry to keytab\n");
+                       goto out;
+               }
+
+               /* add the short principal name if we have one */
+               if (short_princ_s) {
+                       ret = smb_krb5_kt_add_password(context,
+                                                      keytab,
+                                                      kvno,
+                                                      short_princ_s,
+                                                      salt_princ_s,
+                                                      enctypes[i],
+                                                      password);
+                       if (ret) {
+                               DBG_WARNING("Failed to add short entry to keytab\n");
+                               goto out;
+                       }
+               }
+       }
+out:
+       return ret;
+}
+
 /**********************************************************************
  Adds a single service principal, i.e. 'host' to the system keytab
 ***********************************************************************/
@@ -239,31 +337,17 @@ int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc, bool update_ads)
        krb5_keytab keytab = NULL;
        krb5_data password;
        krb5_kvno kvno;
-        krb5_enctype enctypes[6] = {
-               ENCTYPE_DES_CBC_CRC,
-               ENCTYPE_DES_CBC_MD5,
-#ifdef HAVE_ENCTYPE_AES128_CTS_HMAC_SHA1_96
-               ENCTYPE_AES128_CTS_HMAC_SHA1_96,
-#endif
-#ifdef HAVE_ENCTYPE_AES256_CTS_HMAC_SHA1_96
-               ENCTYPE_AES256_CTS_HMAC_SHA1_96,
-#endif
-               ENCTYPE_ARCFOUR_HMAC,
-               0
-       };
-       char *princ_s = NULL;
-       char *short_princ_s = NULL;
        char *salt_princ_s = NULL;
        char *password_s = NULL;
        char *my_fqdn;
        TALLOC_CTX *tmpctx = NULL;
-       int i;
+       char **hostnames_array = NULL;
+       size_t num_hostnames = 0;
 
-       initialize_krb5_error_table();
-       ret = krb5_init_context(&context);
+       ret = smb_krb5_init_context_common(&context);
        if (ret) {
-               DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
-                         error_message(ret)));
+               DBG_ERR("kerberos init context failed (%s)\n",
+                       error_message(ret));
                return -1;
        }
 
@@ -274,13 +358,13 @@ int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc, bool update_ads)
 
        /* retrieve the password */
        if (!secrets_init()) {
-               DEBUG(1, (__location__ ": secrets_init failed\n"));
+               DBG_WARNING("secrets_init failed\n");
                ret = -1;
                goto out;
        }
        password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
        if (!password_s) {
-               DEBUG(1, (__location__ ": failed to fetch machine password\n"));
+               DBG_WARNING("failed to fetch machine password\n");
                ret = -1;
                goto out;
        }
@@ -291,137 +375,222 @@ int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc, bool update_ads)
        /* we need the dNSHostName value here */
        tmpctx = talloc_init(__location__);
        if (!tmpctx) {
-               DEBUG(0, (__location__ ": talloc_init() failed!\n"));
+               DBG_ERR("talloc_init() failed!\n");
                ret = -1;
                goto out;
        }
 
        my_fqdn = ads_get_dnshostname(ads, tmpctx, lp_netbios_name());
        if (!my_fqdn) {
-               DEBUG(0, (__location__ ": unable to determine machine "
-                         "account's dns name in AD!\n"));
+               DBG_ERR("unable to determine machine account's dns name in "
+                       "AD!\n");
                ret = -1;
                goto out;
        }
 
-       /* make sure we have a single instance of the computer account */
+       /* make sure we have a single instance of the computer account */
        if (!ads_has_samaccountname(ads, tmpctx, lp_netbios_name())) {
-               DEBUG(0, (__location__ ": unable to determine machine "
-                         "account's short name in AD!\n"));
+               DBG_ERR("unable to determine machine account's short name in "
+                       "AD!\n");
+               ret = -1;
+               goto out;
+       }
+
+       kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
+       if (kvno == -1) {
+               /* -1 indicates failure, everything else is OK */
+               DBG_WARNING("ads_get_machine_kvno failed to determine the "
+                           "system's kvno.\n");
+               ret = -1;
+               goto out;
+       }
+
+       salt_princ_s = kerberos_secrets_fetch_salt_princ();
+       if (salt_princ_s == NULL) {
+               DBG_WARNING("kerberos_secrets_fetch_salt_princ() failed\n");
                ret = -1;
                goto out;
        }
 
+       ret = add_kt_entry_etypes(context, tmpctx, ads, salt_princ_s, keytab,
+                                 kvno, srvPrinc, my_fqdn, &password,
+                                 update_ads);
+       if (ret != 0) {
+               goto out;
+       }
+
+       if (ADS_ERR_OK(ads_get_additional_dns_hostnames(tmpctx, ads,
+                                                       lp_netbios_name(),
+                                                       &hostnames_array,
+                                                       &num_hostnames))) {
+               size_t i;
+
+               for (i = 0; i < num_hostnames; i++) {
+
+                       ret = add_kt_entry_etypes(context, tmpctx, ads,
+                                                 salt_princ_s, keytab,
+                                                 kvno, srvPrinc,
+                                                 hostnames_array[i],
+                                                 &password, update_ads);
+                       if (ret != 0) {
+                               goto out;
+                       }
+               }
+       }
+
+out:
+       SAFE_FREE(salt_princ_s);
+       TALLOC_FREE(tmpctx);
+
+       if (keytab) {
+               krb5_kt_close(context, keytab);
+       }
+       if (context) {
+               krb5_free_context(context);
+       }
+       return (int)ret;
+}
+
+/**********************************************************************
+ Delete a single service principal, i.e. 'host' from the system keytab
+***********************************************************************/
+
+int ads_keytab_delete_entry(ADS_STRUCT *ads, const char *srvPrinc)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       krb5_error_code ret = 0;
+       krb5_context context = NULL;
+       krb5_keytab keytab = NULL;
+       char *princ_s = NULL;
+       krb5_principal princ = NULL;
+       char *short_princ_s = NULL;
+       krb5_principal short_princ = NULL;
+       bool ok;
+
+       ret = smb_krb5_init_context_common(&context);
+       if (ret) {
+               DBG_ERR("kerberos init context failed (%s)\n",
+                       error_message(ret));
+               goto out;
+       }
+
+       ret = ads_keytab_open(context, &keytab);
+       if (ret != 0) {
+               goto out;
+       }
+
        /* Construct our principal */
        if (strchr_m(srvPrinc, '@')) {
                /* It's a fully-named principal. */
-               princ_s = talloc_asprintf(tmpctx, "%s", srvPrinc);
+               princ_s = talloc_asprintf(frame, "%s", srvPrinc);
                if (!princ_s) {
                        ret = -1;
                        goto out;
                }
        } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
                /* It's the machine account, as used by smbclient clients. */
-               princ_s = talloc_asprintf(tmpctx, "%s@%s",
+               princ_s = talloc_asprintf(frame, "%s@%s",
                                          srvPrinc, lp_realm());
                if (!princ_s) {
                        ret = -1;
                        goto out;
                }
        } else {
-               /* It's a normal service principal.  Add the SPN now so that we
-                * can obtain credentials for it and double-check the salt value
-                * used to generate the service's keys. */
+               /*
+                * It's a normal service principal.
+                */
+               char *my_fqdn = NULL;
+               char *tmp = NULL;
+
+               /*
+                * SPN should have '/' otherwise we
+                * need to fallback and find our dnshostname
+                */
+               tmp = strchr_m(srvPrinc, '/');
+               if (tmp == NULL) {
+                       my_fqdn = ads_get_dnshostname(ads, frame, lp_netbios_name());
+                       if (!my_fqdn) {
+                               DBG_ERR("unable to determine machine account's dns name in "
+                                       "AD!\n");
+                               ret = -1;
+                               goto out;
+                       }
+               }
 
-               if (!service_or_spn_to_kerberos_princ(tmpctx,
+               ok = service_or_spn_to_kerberos_princ(frame,
                                                      srvPrinc,
                                                      my_fqdn,
                                                      &princ_s,
-                                                     &short_princ_s)) {
+                                                     &short_princ_s);
+               if (!ok) {
                        ret = -1;
                        goto out;
                }
+       }
 
-               /* According to http://support.microsoft.com/kb/326985/en-us,
-                  certain principal names are automatically mapped to the
-                  host/... principal in the AD account.
-                  So only create these in the keytab, not in AD.  --jerry */
+       ret = smb_krb5_parse_name(context, princ_s, &princ);
+       if (ret) {
+               DEBUG(1, (__location__ ": smb_krb5_parse_name(%s) "
+                         "failed (%s)\n", princ_s, error_message(ret)));
+               goto out;
+       }
 
-               if (update_ads && !strequal(srvPrinc, "cifs") &&
-                   !strequal(srvPrinc, "host")) {
-                       if (!ads_set_machine_account_spns(tmpctx,
-                                                         ads,
-                                                         srvPrinc,
-                                                         my_fqdn)) {
-                               ret = -1;
-                               goto out;
-                       }
+       if (short_princ_s != NULL) {
+               ret = smb_krb5_parse_name(context, short_princ_s, &short_princ);
+               if (ret) {
+                       DEBUG(1, (__location__ ": smb_krb5_parse_name(%s) "
+                                 "failed (%s)\n", short_princ_s, error_message(ret)));
+                       goto out;
                }
        }
 
-       kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
-       if (kvno == -1) {
-               /* -1 indicates failure, everything else is OK */
-               DEBUG(1, (__location__ ": ads_get_machine_kvno failed to "
-                        "determine the system's kvno.\n"));
-               ret = -1;
+       /* Seek and delete old keytab entries */
+       ret = smb_krb5_kt_seek_and_delete_old_entries(context,
+                                                     keytab,
+                                                     false, /* keep_old_kvno */
+                                                     -1,
+                                                     false, /* enctype_only */
+                                                     ENCTYPE_NULL,
+                                                     princ_s,
+                                                     princ,
+                                                     false); /* flush */
+       if (ret) {
                goto out;
        }
 
-       salt_princ_s = kerberos_secrets_fetch_salt_princ();
-       if (salt_princ_s == NULL) {
-               DBG_WARNING("kerberos_secrets_fetch_salt_princ() failed\n");
-               ret = -1;
+       if (short_princ_s == NULL) {
                goto out;
        }
 
-       for (i = 0; enctypes[i]; i++) {
-
-               /* add the fqdn principal to the keytab */
-               ret = smb_krb5_kt_add_entry(context,
-                                           keytab,
-                                           kvno,
-                                           princ_s,
-                                           salt_princ_s,
-                                           enctypes[i],
-                                           &password,
-                                           false,
-                                           false);
-               if (ret) {
-                       DEBUG(1, (__location__ ": Failed to add entry to keytab\n"));
-                       goto out;
-               }
-
-               /* add the short principal name if we have one */
-               if (short_princ_s) {
-                       ret = smb_krb5_kt_add_entry(context,
-                                                   keytab,
-                                                   kvno,
-                                                   short_princ_s,
-                                                   salt_princ_s,
-                                                   enctypes[i],
-                                                   &password,
-                                                   false,
-                                                   false);
-                       if (ret) {
-                               DEBUG(1, (__location__
-                                         ": Failed to add short entry to keytab\n"));
-                               goto out;
-                       }
-               }
+       /* Seek and delete old keytab entries */
+       ret = smb_krb5_kt_seek_and_delete_old_entries(context,
+                                                     keytab,
+                                                     false, /* keep_old_kvno */
+                                                     -1,
+                                                     false, /* enctype_only */
+                                                     ENCTYPE_NULL,
+                                                     short_princ_s,
+                                                     short_princ,
+                                                     false); /* flush */
+       if (ret) {
+               goto out;
        }
 
 out:
-       SAFE_FREE(salt_princ_s);
-       TALLOC_FREE(tmpctx);
-
+       if (princ) {
+               krb5_free_principal(context, princ);
+       }
+       if (short_princ) {
+               krb5_free_principal(context, short_princ);
+       }
        if (keytab) {
                krb5_kt_close(context, keytab);
        }
        if (context) {
                krb5_free_context(context);
        }
-       return (int)ret;
+       TALLOC_FREE(frame);
+       return ret;
 }
 
 /**********************************************************************
@@ -433,14 +602,12 @@ int ads_keytab_flush(ADS_STRUCT *ads)
        krb5_error_code ret = 0;
        krb5_context context = NULL;
        krb5_keytab keytab = NULL;
-       krb5_kvno kvno;
        ADS_STATUS aderr;
 
-       initialize_krb5_error_table();
-       ret = krb5_init_context(&context);
+       ret = smb_krb5_init_context_common(&context);
        if (ret) {
-               DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
-                         error_message(ret)));
+               DBG_ERR("kerberos init context failed (%s)\n",
+                       error_message(ret));
                return ret;
        }
 
@@ -449,22 +616,16 @@ int ads_keytab_flush(ADS_STRUCT *ads)
                goto out;
        }
 
-       kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
-       if (kvno == -1) {
-               /* -1 indicates a failure */
-               DEBUG(1, (__location__ ": Error determining the kvno.\n"));
-               goto out;
-       }
-
-       /* Seek and delete old keytab entries */
+       /* Seek and delete all old keytab entries */
        ret = smb_krb5_kt_seek_and_delete_old_entries(context,
                                                      keytab,
-                                                     kvno,
+                                                     false, /* keep_old_kvno */
+                                                     -1,
+                                                     false, /* enctype_only */
                                                      ENCTYPE_NULL,
                                                      NULL,
                                                      NULL,
-                                                     true,
-                                                     false);
+                                                     true); /* flush */
        if (ret) {
                goto out;
        }
@@ -473,6 +634,7 @@ int ads_keytab_flush(ADS_STRUCT *ads)
        if (!ADS_ERR_OK(aderr)) {
                DEBUG(1, (__location__ ": Error while clearing service "
                          "principal listings in LDAP.\n"));
+               ret = -1;
                goto out;
        }
 
@@ -568,11 +730,10 @@ int ads_keytab_create_default(ADS_STRUCT *ads)
 
        memset(princ_s, '\0', sizeof(princ_s));
 
-       initialize_krb5_error_table();
-       ret = krb5_init_context(&context);
+       ret = smb_krb5_init_context_common(&context);
        if (ret) {
-               DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
-                         error_message(ret)));
+               DBG_ERR("kerberos init context failed (%s)\n",
+                       error_message(ret));
                goto done;
        }
 
@@ -772,11 +933,10 @@ int ads_keytab_list(const char *keytab_name)
        ZERO_STRUCT(kt_entry);
        ZERO_STRUCT(cursor);
 
-       initialize_krb5_error_table();
-       ret = krb5_init_context(&context);
+       ret = smb_krb5_init_context_common(&context);
        if (ret) {
-               DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
-                         error_message(ret)));
+               DBG_ERR("kerberos init context failed (%s)\n",
+                       error_message(ret));
                return ret;
        }
 
@@ -819,7 +979,7 @@ int ads_keytab_list(const char *keytab_name)
 
                ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
                if (ret &&
-                   (asprintf(&etype_s, "UNKNOWN: %d\n", enctype) == -1)) {
+                   (asprintf(&etype_s, "UNKNOWN: %d", enctype) == -1)) {
                        TALLOC_FREE(princ_s);
                        goto out;
                }