]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s3:lib: Sync machine password to keytab: helper functions
authorPavel Filipenský <pfilipen@redhat.com>
Mon, 6 Sep 2021 14:58:17 +0000 (16:58 +0200)
committerPavel Filipensky <pfilipensky@samba.org>
Fri, 26 Jul 2024 17:12:36 +0000 (17:12 +0000)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=6750

Signed-off-by: Pavel Filipenský <pfilipen@redhat.com>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
source3/libads/ads_proto.h
source3/libads/kerberos_keytab.c

index 23692bc7870b381ad2755f7d6c47be8c648ad0f8..edfa53237daf3cb779c81ae668aab70e84a5169a 100644 (file)
@@ -243,4 +243,6 @@ struct spn_struct {
 /* parse a windows style SPN, returns NULL if parsing fails */
 struct spn_struct *parse_spn(TALLOC_CTX *ctx, const char *srvprinc);
 
+NTSTATUS sync_pw2keytabs(void);
+
 #endif /* _LIBADS_ADS_PROTO_H_ */
index bfcdfbc2817764d872dfbf1e6591a782f94f0fff..6dd8db72fa4e86ae8c5d1b5e5d26be476af58113 100644 (file)
@@ -29,6 +29,7 @@
 #include "smb_krb5.h"
 #include "ads.h"
 #include "secrets.h"
+#include "librpc/gen_ndr/ndr_secrets.h"
 
 #ifdef HAVE_KRB5
 
 #define MAX_KEYTAB_NAME_LEN 1100
 #endif
 
+enum spn_spec_type {
+       SPN_SPEC_DEFAULT,
+       SPN_SPEC_SYNC,
+       SPN_SPEC_FULL,
+       SPN_SPEC_PREFIX
+};
+
+/* pw2kt_conf contains 1 parsed line from "sync machine password to keytab" */
+struct pw2kt_conf {
+       enum spn_spec_type spn_spec;
+       char *keytab;
+       bool sync_etypes;
+       bool sync_kvno;
+       bool additional_dns_hostnames;
+       bool netbios_aliases;
+       bool machine_password;
+       char **spn_spec_array;
+       size_t num_spn_spec;
+};
+
+/* State used by pw2kt */
+struct pw2kt_state {
+       /* Array of parsed lines from "sync machine password to keytab" */
+       struct pw2kt_conf *keytabs;
+       size_t num_keytabs;
+       bool sync_etypes;
+       bool sync_kvno;
+       bool sync_spns;
+       /* These are from DC */
+       krb5_kvno ad_kvno;
+       uint32_t ad_etypes;
+       char **ad_spn_array;
+       size_t ad_num_spns;
+       /* This is from secrets.db */
+       struct secrets_domain_info1 *info;
+};
+
+/* State used by pw2kt_process_keytab */
+struct pw2kt_process_state {
+       krb5_keytab keytab;
+       krb5_context context;
+       krb5_keytab_entry *array1;
+       krb5_keytab_entry *array2;
+       krb5_principal *princ_array;
+       krb5_enctype *enctypes;
+       krb5_enctype preferred_etype;
+};
+
+static ADS_STATUS pw2kt_scan_add_spn(TALLOC_CTX *ctx,
+                                    const char *spn,
+                                    struct pw2kt_conf *conf)
+{
+       conf->spn_spec_array = talloc_realloc(ctx,
+                                             conf->spn_spec_array,
+                                             char *,
+                                             conf->num_spn_spec + 1);
+       if (conf->spn_spec_array == NULL) {
+               return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+       }
+       conf->spn_spec_array[conf->num_spn_spec] = talloc_strdup(
+               conf->spn_spec_array, spn);
+       if (conf->spn_spec_array[conf->num_spn_spec] == NULL) {
+               return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+       }
+       conf->num_spn_spec++;
+
+       return ADS_SUCCESS;
+}
+
+/*
+ * Parse the smb.conf and find out if it is needed to read from DC:
+ *  - servicePrincipalNames
+ *  - msDs-KeyVersionNumber
+ */
+static ADS_STATUS pw2kt_scan_line(const char *line, struct pw2kt_state *state)
+{
+       char *keytabname = NULL;
+       char *spn_spec = NULL;
+       char *spn_val = NULL;
+       char *option = NULL;
+       struct pw2kt_conf *conf = NULL;
+       ADS_STATUS status;
+
+       state->keytabs = talloc_realloc(state,
+                                       state->keytabs,
+                                       struct pw2kt_conf,
+                                       state->num_keytabs + 1);
+       if (state->keytabs == NULL) {
+               return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+       }
+       conf = &state->keytabs[state->num_keytabs];
+       state->num_keytabs++;
+
+       keytabname = talloc_strdup(state->keytabs, line);
+       if (keytabname == NULL) {
+               return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+       }
+
+       ZERO_STRUCT(*conf);
+       conf->keytab = keytabname;
+       spn_spec = strchr_m(keytabname, ':');
+       if (spn_spec == NULL) {
+               DBG_ERR("Invalid format! ':' expected in '%s'\n", keytabname);
+               return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+       }
+       *spn_spec++ = 0;
+
+       /* reverse match with strrchr_m() */
+       while ((option = strrchr_m(spn_spec, ':')) != NULL) {
+               *option++ = 0;
+               if (strequal(option, "sync_kvno")) {
+                       conf->sync_kvno = state->sync_kvno = true;
+               } else if (strequal(option, "sync_etypes")) {
+                       conf->sync_etypes = state->sync_etypes = true;
+               } else if (strequal(option, "additional_dns_hostnames")) {
+                       conf->additional_dns_hostnames = true;
+               } else if (strequal(option, "netbios_aliases")) {
+                       conf->netbios_aliases = true;
+               } else if (strequal(option, "machine_password")) {
+                       conf->machine_password = true;
+               } else {
+                       DBG_WARNING("Unknown option '%s'!\n", option);
+                       return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+               }
+       }
+
+       spn_val = strchr_m(spn_spec, '=');
+       if (spn_val != NULL) {
+               *spn_val++ = 0;
+       }
+
+       if (strcmp(spn_spec, "account_name") == 0) {
+               conf->spn_spec = SPN_SPEC_DEFAULT;
+       } else if (strcmp(spn_spec, "sync_spns") == 0) {
+               conf->spn_spec = SPN_SPEC_SYNC;
+               state->sync_spns = true;
+       } else if (strcmp(spn_spec, "spns") == 0 ||
+                  strcmp(spn_spec, "spn_prefixes") == 0)
+       {
+               char *spn = NULL, *tmp = NULL;
+
+               conf->spn_spec = strcmp(spn_spec, "spns") == 0
+                                        ? SPN_SPEC_FULL
+                                        : SPN_SPEC_PREFIX;
+               conf->num_spn_spec = 0;
+               spn = spn_val;
+               while ((tmp = strchr_m(spn, ',')) != NULL) {
+                       *tmp++ = 0;
+                       status = pw2kt_scan_add_spn(state->keytabs, spn, conf);
+                       if (!ADS_ERR_OK(status)) {
+                               return status;
+                       }
+                       spn = tmp;
+               }
+               /* Do not forget the last entry */
+               return pw2kt_scan_add_spn(state->keytabs, spn, conf);
+       } else {
+               DBG_WARNING("Invalid SPN specifier: %s\n", spn_spec);
+               return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+       }
+
+       return ADS_SUCCESS;
+}
+
+/*
+ * Fill struct pw2kt_state with defaults if "sync machine password to keytab"
+ * is missing in smb.conf
+ */
+static ADS_STATUS pw2kt_default_cfg(const char *name, struct pw2kt_state *state)
+{
+       char *keytabname = NULL;
+       struct pw2kt_conf *conf = NULL;
+
+       state->keytabs = talloc_zero_array(state->keytabs,
+                                          struct pw2kt_conf,
+                                          1);
+       if (state->keytabs == NULL) {
+               return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+       }
+       conf = &state->keytabs[0];
+       state->num_keytabs = 1;
+
+       keytabname = talloc_strdup(state->keytabs, name);
+       if (keytabname == NULL) {
+               return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+       }
+
+       conf->spn_spec = SPN_SPEC_SYNC;
+       conf->keytab = keytabname;
+       conf->machine_password = true;
+       conf->sync_kvno = state->sync_kvno = true;
+       state->sync_spns = true;
+
+       return ADS_SUCCESS;
+}
+
+/*
+ * For the given principal add to the array entries created from all pw->keys[]
+ */
+static krb5_error_code pw2kt_process_add_pw(
+       struct pw2kt_process_state *state2,
+       krb5_principal princ,
+       krb5_kvno vno,
+       struct secrets_domain_info1_password *pw)
+{
+       uint16_t i;
+       size_t len = talloc_array_length(state2->array1);
+
+       for (i = 0; i < pw->num_keys; i++) {
+               krb5_keytab_entry *kt_entry = NULL;
+               krb5_keyblock *key = NULL;
+               krb5_keytab_entry *tmp_a = NULL;
+
+               if (state2->preferred_etype != -1 &&
+                   state2->preferred_etype != pw->keys[i].keytype)
+               {
+                       DBG_DEBUG("Skip enc type '%d'.\n", pw->keys[i].keytype);
+                       continue;
+               }
+
+               len++;
+               tmp_a = talloc_realloc(state2,
+                                      state2->array1,
+                                      krb5_keytab_entry,
+                                      len);
+               if (tmp_a == NULL) {
+                       return ENOMEM;
+               }
+               state2->array1 = tmp_a;
+               kt_entry = &state2->array1[len - 1];
+               ZERO_STRUCT(*kt_entry);
+               kt_entry->principal = princ;
+               kt_entry->vno = vno;
+
+               key = KRB5_KT_KEY(kt_entry);
+               KRB5_KEY_TYPE(key) = pw->keys[i].keytype;
+               KRB5_KEY_DATA(key) = pw->keys[i].value.data;
+               KRB5_KEY_LENGTH(key) = pw->keys[i].value.length;
+       }
+
+       return 0;
+}
+
+/*
+ * For the given principal add to the array entries based on password,
+ * old_password, older_password and next_change->password.
+ */
+static krb5_error_code pw2kt_process_add_info(
+       struct pw2kt_process_state *state2,
+       krb5_kvno kvno,
+       const char *princs,
+       struct secrets_domain_info1 *info)
+{
+       krb5_error_code ret;
+       krb5_principal princ = NULL;
+       krb5_principal *a = NULL;
+       size_t len;
+
+       ret = smb_krb5_parse_name(state2->context, princs, &princ);
+       if (ret != 0) {
+               DBG_ERR("Failed to parse principal: %s\n", princs);
+               return ret;
+       }
+       len = talloc_array_length(state2->princ_array);
+       a = talloc_realloc(state2,
+                          state2->princ_array,
+                          krb5_principal,
+                          len + 1);
+       if (a == NULL) {
+               (void)krb5_free_principal(state2->context, princ);
+               return ENOMEM;
+       }
+       a[len] = princ;
+       state2->princ_array = a;
+
+#define ADD_PW(K, P)                                                     \
+       if (info->P != NULL) {                                           \
+               ret = pw2kt_process_add_pw(state2, princ, (K), info->P); \
+               if (ret != 0) {                                          \
+                       DBG_ERR("Failed adding %s keys for %s.\n",       \
+                               #P,                                      \
+                               princs);                                 \
+                       return ret;                                      \
+               }                                                        \
+       }
+
+       ADD_PW(kvno, password);
+       ADD_PW(kvno - 1, old_password);
+       ADD_PW(kvno - 2, older_password);
+       if (info->next_change) {
+               ADD_PW(kvno == -1 ? -4 : kvno + 1, next_change->password);
+       }
+
+       return ret;
+}
+
+static int pw2kt_process_state_destructor(struct pw2kt_process_state *state2)
+{
+       int i;
+       size_t len2 = talloc_array_length(state2->array2);
+       size_t len_p = talloc_array_length(state2->princ_array);
+
+       for (i = 0; i < len2; i++) {
+               (void)smb_krb5_kt_free_entry(state2->context,
+                                            &state2->array2[i]);
+       }
+       for (i = 0; i < len_p; i++) {
+               (void)krb5_free_principal(state2->context,
+                                         state2->princ_array[i]);
+       }
+       (void)krb5_free_enctypes(state2->context, state2->enctypes);
+
+       return 0;
+}
+
+/* Read the whole keytab to krb5_keytab_entry array */
+static krb5_error_code pw2kt_process_kt2ar(struct pw2kt_process_state *state2)
+{
+       krb5_error_code ret = 0, ret2 = 0;
+       krb5_kt_cursor cursor;
+       krb5_keytab_entry *a = NULL;
+       krb5_keytab_entry e;
+       size_t num = 0;
+
+       ZERO_STRUCT(cursor);
+
+       ret = krb5_kt_start_seq_get(state2->context, state2->keytab, &cursor);
+       if (ret != 0) {
+               if (ret == KRB5_KT_END || ret == ENOENT) {
+                       ret = 0;
+               }
+               return ret;
+       }
+
+       for (;;) {
+               ret = samba_krb5_kt_next_entry(state2->context,
+                                              state2->keytab,
+                                              &e,
+                                              &cursor);
+               if (ret != 0) {
+                       break;
+               }
+               a = talloc_realloc(state2,
+                                  state2->array2,
+                                  krb5_keytab_entry,
+                                  num + 1);
+               if (a == NULL) {
+                       smb_krb5_kt_free_entry(state2->context, &e);
+                       return ENOMEM;
+               }
+               a[num++] = e;
+               state2->array2 = a;
+       }
+
+       if (ret == KRB5_KT_END || ret == ENOENT) {
+               ret = 0;
+       }
+       ret2 = krb5_kt_end_seq_get(state2->context, state2->keytab, &cursor);
+
+       return ret != 0 ? ret : ret2;
+}
+
+static ADS_STATUS pw2kt_process_keytab(struct pw2kt_state *state,
+                                      struct pw2kt_conf *keytabptr)
+{
+       krb5_error_code ret = 0;
+       krb5_kvno kvno = -1;
+       size_t i, j, len1 = 0, len2 = 0;
+       char *princ_s = NULL;
+       const char **netbios_alias = NULL;
+       const char **addl_hostnames = NULL;
+       size_t *index_array1 = NULL;
+       size_t *index_array2 = NULL;
+       struct pw2kt_process_state *state2 = NULL;
+
+       if (!keytabptr->machine_password) {
+               DBG_ERR("No 'machine_password' option for '%s'. Skip it.\n",
+                       keytabptr->keytab);
+               return ADS_SUCCESS;
+       }
+
+       state2 = talloc_zero(state, struct pw2kt_process_state);
+       if (state2 == NULL) {
+               return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+       }
+       talloc_set_destructor(state2, pw2kt_process_state_destructor);
+
+       ret = smb_krb5_init_context_common(&state2->context);
+       if (ret != 0) {
+               DBG_ERR("Krb init context failed (%s)\n", error_message(ret));
+               return ADS_ERROR_KRB5(ret);
+       }
+
+#define MATCH_ENCTYPE(TYPES, TYPE)                               \
+       ({                                                       \
+               int ei, result = 0;                              \
+               for (ei = 0; (TYPES)[ei] != 0; ei++) {           \
+                       if ((uint32_t)(TYPES)[ei] != (TYPE)) {   \
+                               continue;                        \
+                       }                                        \
+                       result = 1;                              \
+                       break;                                   \
+               }                                                \
+               result;                                          \
+       })
+
+#define COMMON_ENCTYPE(ETYPE)                         \
+       MATCH_ENCTYPE((state2->enctypes), (ETYPE)) && \
+               ((state->ad_etypes) & (ETYPE))
+
+       /*
+        * -1 means there is no information about supported encryption types
+        * from DC and all encoding types will be added to the keytab.
+        */
+       state2->preferred_etype = -1;
+
+       /* Find the highest common enc type for AD and KRB5 lib */
+       if (keytabptr->sync_etypes) {
+               ret = smb_krb5_get_allowed_etypes(state2->context,
+                                                 &state2->enctypes);
+               if (ret != 0) {
+                       DBG_ERR("Failed to get allowed enc types.\n");
+                       return ADS_ERROR_KRB5(ret);
+               }
+
+               if (COMMON_ENCTYPE(ENCTYPE_AES256_CTS_HMAC_SHA1_96)) {
+                       state2->preferred_etype =
+                               ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+               } else if (COMMON_ENCTYPE(ENCTYPE_AES128_CTS_HMAC_SHA1_96)) {
+                       state2->preferred_etype =
+                               ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+               } else if (COMMON_ENCTYPE(ENCTYPE_ARCFOUR_HMAC)) {
+                       state2->preferred_etype = ENCTYPE_ARCFOUR_HMAC;
+               } else {
+                       DBG_ERR("No common enctype for AD and KRB5 lib.\n");
+                       return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+               }
+       }
+
+       if (keytabptr->sync_kvno) {
+               kvno = state->ad_kvno;
+       }
+
+#define ADD_INFO(P)                                                   \
+       ret = pw2kt_process_add_info(state2, kvno, (P), state->info); \
+       if (ret != 0) {                                               \
+               return ADS_ERROR_KRB5(ret);                           \
+       }
+
+       /* Add ACCOUNTNAME$ entries */
+       switch (keytabptr->spn_spec) {
+       case SPN_SPEC_DEFAULT:
+               ADD_INFO(state->info->account_name);
+               break;
+       case SPN_SPEC_SYNC:
+               for (i = 0; i < state->ad_num_spns; i++) {
+                       ADD_INFO(state->ad_spn_array[i]);
+               }
+               break;
+       case SPN_SPEC_FULL:
+               for (i = 0; i < keytabptr->num_spn_spec; i++) {
+                       ADD_INFO(keytabptr->spn_spec_array[i]);
+               }
+               break;
+       case SPN_SPEC_PREFIX:
+               for (i = 0; i < keytabptr->num_spn_spec; i++) {
+                       princ_s = talloc_asprintf(talloc_tos(),
+                                                 "%s/%s@%s",
+                                                 keytabptr->spn_spec_array[i],
+                                                 lp_netbios_name(),
+                                                 lp_realm());
+                       if (princ_s == NULL) {
+                               return ADS_ERROR_KRB5(ENOMEM);
+                       }
+                       ADD_INFO(princ_s);
+
+                       if (!keytabptr->netbios_aliases) {
+                               goto additional_dns_hostnames;
+                       }
+                       for (netbios_alias = lp_netbios_aliases();
+                            netbios_alias != NULL && *netbios_alias != NULL;
+                            netbios_alias++)
+                       {
+                               /* Add PREFIX/netbiosname@REALM */
+                               princ_s = talloc_asprintf(
+                                       talloc_tos(),
+                                       "%s/%s@%s",
+                                       keytabptr->spn_spec_array[i],
+                                       *netbios_alias,
+                                       lp_realm());
+                               if (princ_s == NULL) {
+                                       return ADS_ERROR_KRB5(ENOMEM);
+                               }
+                               ADD_INFO(princ_s);
+
+                               /* Add PREFIX/netbiosname.domainname@REALM */
+                               princ_s = talloc_asprintf(
+                                       talloc_tos(),
+                                       "%s/%s.%s@%s",
+                                       keytabptr->spn_spec_array[i],
+                                       *netbios_alias,
+                                       lp_dnsdomain(),
+                                       lp_realm());
+                               if (princ_s == NULL) {
+                                       return ADS_ERROR_KRB5(ENOMEM);
+                               }
+                               ADD_INFO(princ_s);
+                       }
+
+additional_dns_hostnames:
+                       if (!keytabptr->additional_dns_hostnames) {
+                               continue;
+                       }
+                       for (addl_hostnames = lp_additional_dns_hostnames();
+                            addl_hostnames != NULL && *addl_hostnames != NULL;
+                            addl_hostnames++)
+                       {
+                               /* Add PREFIX/netbiosname@REALM */
+                               princ_s = talloc_asprintf(
+                                       talloc_tos(),
+                                       "%s/%s@%s",
+                                       keytabptr->spn_spec_array[i],
+                                       *addl_hostnames,
+                                       lp_realm());
+                               if (princ_s == NULL) {
+                                       return ADS_ERROR_KRB5(ENOMEM);
+                               }
+                               ADD_INFO(princ_s);
+                       }
+               }
+               break;
+       default:
+               return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+       }
+
+       ret = smb_krb5_kt_open(state2->context,
+                              keytabptr->keytab,
+                              true,
+                              &state2->keytab);
+       if (ret != 0) {
+               return ADS_ERROR_KRB5(ret);
+       }
+
+       /* The new entries are in array1. Read existing entries to array2. */
+       ret = pw2kt_process_kt2ar(state2);
+       if (ret != 0) {
+               return ADS_ERROR_KRB5(ret);
+       }
+
+       len1 = talloc_array_length(state2->array1);
+       len2 = talloc_array_length(state2->array2);
+
+       if (keytabptr->sync_kvno) {
+               goto sync_kvno;
+       }
+
+       /* copy existing entries VNO -1, -2, -3, -4 to VNO -11, -12, -13, -14 */
+       for (j = 0; j < len2; j++) {
+               krb5_keytab_entry e = state2->array2[j];
+               /* vno type is 'krb5_kvno' which is 'unsigned int' */
+               if (e.vno != -1 && e.vno != -2 && e.vno != -3 && e.vno != -4) {
+                       DBG_WARNING("Unexpected keytab entry with VNO = %d (it "
+                                   "should be -1, -2, -3, -4) in %s\n",
+                                   e.vno,
+                                   keytabptr->keytab);
+                       continue;
+               }
+               e.vno = state2->array2[j].vno - 10;
+               ret = samba_krb5_kt_add_entry(state2->context,
+                                             state2->keytab,
+                                             &e);
+               if (ret != 0) {
+                       return ADS_ERROR_KRB5(ret);
+               }
+       }
+       /* remove all old entries (they should have VNO -1, -2, -3, -4) */
+       for (j = 0; j < len2; j++) {
+               krb5_keytab_entry e = state2->array2[j];
+               if (e.vno != -1 && e.vno != -2 && e.vno != -3 && e.vno != -4) {
+                       DBG_WARNING("Unexpected keytab entry with VNO = %d (it "
+                                   "should be -1, -2, -3, -4) in %s\n",
+                                   e.vno,
+                                   keytabptr->keytab);
+               }
+               ret = samba_krb5_kt_remove_entry(state2->context,
+                                                state2->keytab,
+                                                &state2->array2[j]);
+               if (ret != 0) {
+                       D_WARNING("Failed to remove keytab entry from %s\n",
+                                 keytabptr->keytab);
+                       ret = 0; /* Be fault tolerant */
+               }
+       }
+       /* add new entries with VNO -1, -2, -3, -4 */
+       for (i = 0; i < len1; i++) {
+               ret = samba_krb5_kt_add_entry(state2->context,
+                                             state2->keytab,
+                                             &state2->array1[i]);
+               if (ret != 0) {
+                       return ADS_ERROR_KRB5(ret);
+               }
+       }
+       /* remove entries with VNO -11, -12, -13, -14 */
+       for (j = 0; j < len2; j++) {
+               krb5_keytab_entry e = state2->array2[j];
+               e.vno = state2->array2[j].vno - 10;
+               ret = samba_krb5_kt_remove_entry(state2->context,
+                                                state2->keytab,
+                                                &e);
+               if (ret != 0) {
+                       D_WARNING("Failed to remove keytab entry from %s\n",
+                                 keytabptr->keytab);
+                       ret = 0; /* Be fault tolerant */
+               }
+       }
+
+       ret = krb5_kt_close(state2->context, state2->keytab);
+       return ADS_ERROR_KRB5(ret);
+
+sync_kvno:
+
+       index_array1 = talloc_zero_array(state2, size_t, len1);
+       index_array2 = talloc_zero_array(state2, size_t, len2);
+       if (index_array1 == NULL || index_array2 == NULL) {
+               return ADS_ERROR_KRB5(ENOMEM);
+       }
+       /*
+        * Mark entries that are present in both arrays.
+        * These will not be added or removed.
+        */
+       for (i = 0; i < len1; i++) {
+               for (j = 0; j < len2; j++) {
+                       krb5_keytab_entry e2 = state2->array2[j];
+                       if (smb_krb5_kt_compare(
+                               state2->context,
+                               &state2->array1[i],
+                               e2.principal,
+                               e2.vno,
+                               KRB5_KEY_TYPE(KRB5_KT_KEY(&e2))
+                       ))
+                       {
+                               index_array1[i] = 1;
+                               index_array2[j] = 1;
+                       }
+               }
+       }
+
+       /* First add the new entries to the keytab.*/
+       for (i = 0; i < len1; i++) {
+               if (index_array1[i] == 0) {
+                       ret = samba_krb5_kt_add_entry(state2->context,
+                                                     state2->keytab,
+                                                     &state2->array1[i]);
+                       if (ret != 0) {
+                               return ADS_ERROR_KRB5(ret);
+                       }
+               }
+       }
+
+       /* Now, remove the old entries from the keytab. */
+       for (j = 0; j < len2; j++) {
+               if (index_array2[j] == 0) {
+                       ret = samba_krb5_kt_remove_entry(state2->context,
+                                                        state2->keytab,
+                                                        &state2->array2[j]);
+                       if (ret != 0) {
+                               D_WARNING("Failed to remove keytab entry from "
+                                         "%s\n",
+                                         keytabptr->keytab);
+                               ret = 0; /* Be fault tolerant */
+                       }
+               }
+       }
+
+       ret = krb5_kt_close(state2->context, state2->keytab);
+       return ADS_ERROR_KRB5(ret);
+}
+
+static ADS_STATUS pw2kt_get_dc_info(struct pw2kt_state *state)
+{
+       ADS_STATUS status;
+       LDAPMessage *res = NULL;
+       int count;
+       bool ok;
+       TALLOC_CTX *tmp_ctx = talloc_stackframe();
+       ADS_STRUCT *ads = ads_init(
+               tmp_ctx, lp_realm(), lp_workgroup(), NULL, ADS_SASL_SIGN);
+
+       if (ads == NULL) {
+               DBG_ERR("ads_init() failed\n");
+               TALLOC_FREE(tmp_ctx);
+               return ADS_ERROR(LDAP_NO_MEMORY);
+       }
+
+       status = ads_connect_machine(ads);
+       if (!ADS_ERR_OK(status)) {
+               DBG_ERR("Failed to refresh keytab, ads_connect() returned %s\n",
+                       ads_errstr(status));
+               TALLOC_FREE(tmp_ctx);
+               return status;
+       }
+
+       status = ads_find_machine_acct(ads, &res, lp_netbios_name());
+       if (!ADS_ERR_OK(status)) {
+               TALLOC_FREE(tmp_ctx);
+               return status;
+       }
+
+       count = ads_count_replies(ads, res);
+       if (count != 1) {
+               status = ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+               ads_msgfree(ads, res);
+               TALLOC_FREE(tmp_ctx);
+               return status;
+       }
+
+       if (state->sync_etypes) {
+               ok = ads_pull_uint32(ads,
+                                    res,
+                                    "msDS-SupportedEncryptionTypes",
+                                    &state->ad_etypes);
+               if (!ok) {
+                       DBG_WARNING("Failed to determine encryption types.\n");
+                       ads_msgfree(ads, res);
+                       TALLOC_FREE(tmp_ctx);
+                       return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+               }
+       }
+
+       if (state->sync_kvno) {
+               uint32_t kvno = -1;
+               ok = ads_pull_uint32(ads, res, "msDS-KeyVersionNumber", &kvno);
+               if (!ok) {
+                       DBG_WARNING("Failed to determine the system's kvno.\n");
+                       ads_msgfree(ads, res);
+                       TALLOC_FREE(tmp_ctx);
+                       return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+               }
+               state->ad_kvno = (krb5_kvno) kvno;
+       }
+
+       if (state->sync_spns) {
+               state->ad_spn_array = ads_pull_strings(ads,
+                                                      state,
+                                                      res,
+                                                      "servicePrincipalName",
+                                                      &state->ad_num_spns);
+               if (state->ad_spn_array == NULL) {
+                       DBG_WARNING("Failed to determine SPNs.\n");
+                       ads_msgfree(ads, res);
+                       TALLOC_FREE(tmp_ctx);
+                       return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+               }
+       }
+
+       ads_msgfree(ads, res);
+       TALLOC_FREE(tmp_ctx);
+       return status;
+}
+
+static bool pw2kt_default_keytab_name(char *name_str, size_t name_size)
+{
+       char keytab_str[MAX_KEYTAB_NAME_LEN] = {0};
+       const char *keytab_name = NULL;
+       krb5_context context = 0;
+       krb5_error_code ret;
+
+       switch (lp_kerberos_method()) {
+       case KERBEROS_VERIFY_SYSTEM_KEYTAB:
+       case KERBEROS_VERIFY_SECRETS_AND_KEYTAB:
+               ret = smb_krb5_init_context_common(&context);
+               if (ret) {
+                       DBG_ERR("kerberos init context failed (%s)\n",
+                               error_message(ret));
+                       return false;
+               }
+               ret = krb5_kt_default_name(context,
+                                          keytab_str,
+                                          sizeof(keytab_str) - 2);
+               krb5_free_context(context);
+               if (ret != 0) {
+                       DBG_WARNING("Failed to get default keytab name\n");
+                       return false;
+               }
+               if (strncmp(keytab_str, "WRFILE:", 7) == 0) {
+                       keytab_name = keytab_str + 7;
+               } else if (strncmp(keytab_str, "FILE:", 5) == 0) {
+                       keytab_name = keytab_str + 5;
+               } else {
+                       keytab_name = keytab_str;
+               }
+               break;
+
+       case KERBEROS_VERIFY_DEDICATED_KEYTAB:
+               keytab_name = lp_dedicated_keytab_file();
+               break;
+
+       default:
+               DBG_ERR("Invalid kerberos method set (%d)\n",
+                       lp_kerberos_method());
+               return false;
+       }
+
+       if (keytab_name == NULL || keytab_name[0] == '\0') {
+               DBG_ERR("Invalid keytab name\n");
+               return false;
+       }
+
+       if (strlen(keytab_name) + 1 > name_size) {
+               DBG_ERR("Too long keytab name\n");
+               return false;
+       }
+
+       (void)strncpy(name_str, keytab_name, name_size);
+
+       return true;
+}
+
+NTSTATUS sync_pw2keytabs(void)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       struct pw2kt_state *state = NULL;
+       const char **line = NULL;
+       const char **lp_ptr = NULL;
+       NTSTATUS status_nt;
+       ADS_STATUS status_ads;
+       int i;
+
+       DBG_DEBUG("Syncing machine password from secrets to keytabs.\n");
+
+       if (lp_server_role() != ROLE_DOMAIN_MEMBER) {
+               TALLOC_FREE(frame);
+               return NT_STATUS_OK; /* nothing todo */
+       }
+
+       state = talloc_zero(frame, struct pw2kt_state);
+       if (state == NULL) {
+               TALLOC_FREE(frame);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       lp_ptr = lp_sync_machine_password_to_keytab();
+       if (lp_ptr == NULL) {
+               char name[MAX_KEYTAB_NAME_LEN] = {0};
+               bool ok = pw2kt_default_keytab_name(name, sizeof(name));
+
+               if (!ok) {
+                       TALLOC_FREE(frame);
+                       DBG_WARNING("No default keytab name.\n");
+                       return NT_STATUS_OK; /* nothing todo */
+               }
+               status_ads = pw2kt_default_cfg(name, state);
+               if (!ADS_ERR_OK(status_ads)) {
+                       DBG_WARNING("Cannot create default configuration.\n");
+                       TALLOC_FREE(frame);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+               goto params_ready;
+       }
+
+       line = lp_ptr;
+       while (*line) {
+               DBG_DEBUG("Scanning line: %s\n", *line);
+               status_ads = pw2kt_scan_line(*line, state);
+               if (!ADS_ERR_OK(status_ads)) {
+                       TALLOC_FREE(frame);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+               line++;
+       }
+
+params_ready:
+       if (state->sync_etypes || state->sync_kvno || state->sync_spns) {
+               status_ads = pw2kt_get_dc_info(state);
+               if (!ADS_ERR_OK(status_ads)) {
+                       DBG_WARNING("cannot read from DC\n");
+                       TALLOC_FREE(frame);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+       } else {
+               DBG_DEBUG("No 'sync_etypes', 'sync_kvno' and 'sync_spns' in "
+                         "parameter 'sync machine password to keytab' => "
+                         "no need to talk to DC.\n");
+       }
+
+       if (!secrets_init()) {
+               DBG_WARNING("secrets_init failed\n");
+               TALLOC_FREE(frame);
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       status_nt = secrets_fetch_or_upgrade_domain_info(lp_workgroup(),
+                                                        frame,
+                                                        &state->info);
+       if (!NT_STATUS_IS_OK(status_nt)) {
+               DBG_WARNING("secrets_fetch_or_upgrade_domain_info(%s) - %s\n",
+                           lp_workgroup(),
+                           nt_errstr(status_nt));
+               TALLOC_FREE(frame);
+               return status_nt;
+       }
+
+       for (i = 0; i < state->num_keytabs; i++) {
+               status_ads = pw2kt_process_keytab(state, &state->keytabs[i]);
+               if (!ADS_ERR_OK(status_ads)) {
+                       TALLOC_FREE(frame);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+       }
+
+       TALLOC_FREE(frame);
+       return NT_STATUS_OK;
+}
+
 static krb5_error_code ads_keytab_open(krb5_context context,
                                       krb5_keytab *keytab)
 {