r11993: As well as making an in-MEMORY keytab, allow a file-based keytab to be updated.
authorAndrew Bartlett <abartlet@samba.org>
Thu, 1 Dec 2005 05:09:28 +0000 (05:09 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 18:46:56 +0000 (13:46 -0500)
This allows a new password to be written in, and old entries removed
(we keep kvno and kvno-1).

Clean up the code a lot, and add comments on what it is doing...

Andrew Bartlett

source/auth/kerberos/kerberos_util.c

index b9f36b16e5a986bcce49618f4c87d77cf74bc31c..47476aa1b64ca103884dfd676564d76329ba2760 100644 (file)
@@ -96,6 +96,11 @@ krb5_error_code salt_principal_from_credentials(TALLOC_CTX *parent_ctx,
        return ret;
 }
 
+/* Obtain the principal set on this context.  Requires a
+ * smb_krb5_context because we are doing krb5 principal parsing with
+ * the library routines.  The returned princ is placed in the talloc
+ * system by means of a destructor (do *not* free). */
+
 krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx, 
                                           struct cli_credentials *credentials, 
                                           struct smb_krb5_context *smb_krb5_context,
@@ -110,6 +115,7 @@ krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
        
        princ_string = cli_credentials_get_principal(credentials, mem_ctx);
 
+       /* A NULL here has meaning, as the gssapi server case will then use the principal from the client */
        if (!princ_string) {
                talloc_free(mem_ctx);
                princ = NULL;
@@ -120,6 +126,8 @@ krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
                              princ_string, princ);
 
        if (ret == 0) {
+               /* This song-and-dance effectivly puts the principal
+                * into talloc, so we can't loose it. */
                mem_ctx->smb_krb5_context = talloc_reference(mem_ctx, smb_krb5_context);
                mem_ctx->principal = *princ;
                talloc_set_destructor(mem_ctx, free_principal);
@@ -218,62 +226,142 @@ static int free_keytab(void *ptr) {
        return 0;
 }
 
- int create_memory_keytab(TALLOC_CTX *parent_ctx,
-                         struct cli_credentials *machine_account,
-                         struct smb_krb5_context *smb_krb5_context,
-                         struct keytab_container **keytab_container) 
+struct enctypes_container {
+       struct smb_krb5_context *smb_krb5_context;
+       krb5_enctype *enctypes;
+};
+
+static int free_enctypes(void *ptr) {
+       struct enctypes_container *etc = ptr;
+       free_kerberos_etypes(etc->smb_krb5_context->krb5_context, etc->enctypes);
+       return 0;
+}
+
+static krb5_error_code keytab_add_keys(TALLOC_CTX *parent_ctx,
+                                      const char *princ_string,
+                                      krb5_principal princ,
+                                      krb5_principal salt_princ,
+                                      int kvno,
+                                      const char *password_s,
+                                      struct smb_krb5_context *smb_krb5_context,
+                                      krb5_keytab keytab)
 {
+       int i;
        krb5_error_code ret;
-       const char *password_s;
-       char *old_secret;
-       krb5_data password;
-       int i, kvno;
        krb5_enctype *enctypes;
-       krb5_principal salt_princ;
-       krb5_principal princ;
-       krb5_keytab keytab;
        char *enctype_string = NULL;
-
+       struct enctypes_container *etc;
+       krb5_data password;
        TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
        if (!mem_ctx) {
                return ENOMEM;
        }
-       
-       *keytab_container = talloc(mem_ctx, struct keytab_container);
 
-       ret = krb5_kt_resolve(smb_krb5_context->krb5_context, "MEMORY:", &keytab);
-       if (ret) {
-               DEBUG(1,("failed to generate a new krb5 keytab: %s\n", 
+       etc = talloc(mem_ctx, struct enctypes_container);
+       if (!etc) {
+               talloc_free(mem_ctx);
+               return ENOMEM;
+       }
+       ret = get_kerberos_allowed_etypes(smb_krb5_context->krb5_context, 
+                                         &enctypes);
+       if (ret != 0) {
+               DEBUG(1,("keytab_add_keys: getting encrption types failed (%s)\n",
                         error_message(ret)));
                talloc_free(mem_ctx);
                return ret;
        }
 
-       (*keytab_container)->smb_krb5_context = talloc_reference(*keytab_container, smb_krb5_context);
-       (*keytab_container)->keytab = keytab;
+       etc->smb_krb5_context = talloc_reference(etc, smb_krb5_context);
+       etc->enctypes = enctypes;
 
-       talloc_set_destructor(*keytab_container, free_keytab);
+       talloc_set_destructor(etc, free_enctypes);
 
-       ret = salt_principal_from_credentials(mem_ctx, machine_account, 
-                                             smb_krb5_context, 
-                                             &salt_princ);
+       password.data = discard_const_p(char *, password_s);
+       password.length = strlen(password_s);
+
+       for (i=0; enctypes[i]; i++) {
+               krb5_keytab_entry entry;
+               ret = create_kerberos_key_from_string(smb_krb5_context->krb5_context, 
+                                                     salt_princ, &password, &entry.keyblock, enctypes[i]);
+               if (ret) {
+                       talloc_free(mem_ctx);
+                       return ret;
+               }
+
+                entry.principal = princ;
+                entry.vno       = kvno;
+               ret = krb5_kt_add_entry(smb_krb5_context->krb5_context, keytab, &entry);
+               if (ret != 0) {
+                       DEBUG(1, ("Failed to add entry for %s(kvno %d) to keytab: %s",
+                                 princ_string,
+                                 kvno,
+                                 smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
+                                                            ret, mem_ctx)));
+                       talloc_free(mem_ctx);
+                       krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
+                       return ret;
+               }
+
+               enctype_string = NULL;
+               krb5_keytype_to_string(smb_krb5_context->krb5_context, enctypes[i], &enctype_string);
+               DEBUG(5, ("Added %s(kvno %d) to keytab (%s)\n", 
+                         princ_string, kvno,
+                         enctype_string));
+               free(enctype_string);           
+               
+               krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
+       }
+       talloc_free(mem_ctx);
+       return 0;
+}
+
+static int create_keytab(TALLOC_CTX *parent_ctx,
+                        struct cli_credentials *machine_account,
+                        struct smb_krb5_context *smb_krb5_context,
+                        struct keytab_container *keytab_container,
+                        BOOL add_old) 
+{
+       krb5_error_code ret;
+       const char *password_s;
+       char *enctype_string;
+       const char *old_secret;
+       int kvno;
+       krb5_principal salt_princ;
+       krb5_principal princ;
+       krb5_keytab keytab;
+       const char *princ_string;
+
+       TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+       if (!mem_ctx) {
+               return ENOMEM;
+       }
+
+       keytab = keytab_container->keytab;
+       
+       princ_string = cli_credentials_get_principal(machine_account, mem_ctx);
+       /* Get the principal we will store the new keytab entries under */
+       ret = principal_from_credentials(mem_ctx, machine_account, smb_krb5_context, &princ);
        if (ret) {
-               DEBUG(1,("create_memory_keytab: makeing salt principal failed (%s)\n",
+               DEBUG(1,("create_keytab: makeing krb5 principal failed (%s)\n",
                         smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
                                                    ret, mem_ctx)));
                talloc_free(mem_ctx);
                return ret;
        }
 
-       ret = principal_from_credentials(mem_ctx, machine_account, smb_krb5_context, &princ);
+       /* The salt used to generate these entries may be different however, fetch that */
+       ret = salt_principal_from_credentials(mem_ctx, machine_account, 
+                                             smb_krb5_context, 
+                                             &salt_princ);
        if (ret) {
-               DEBUG(1,("create_memory_keytab: makeing krb5 principal failed (%s)\n",
+               DEBUG(1,("create_keytab: makeing salt principal failed (%s)\n",
                         smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
                                                    ret, mem_ctx)));
                talloc_free(mem_ctx);
                return ret;
        }
 
+       /* Finally, do the dance to get the password to put in the entry */
        password_s = cli_credentials_get_password(machine_account);
        if (!password_s) {
                /* If we don't have the plaintext password, try for
@@ -284,7 +372,7 @@ static int free_keytab(void *ptr) {
                mach_pwd = cli_credentials_get_nt_hash(machine_account, mem_ctx);
                if (!mach_pwd) {
                        talloc_free(mem_ctx);
-                       DEBUG(1, ("create_memory_keytab: Domain trust informaton for account %s not available\n",
+                       DEBUG(1, ("create_keytab: Domain trust informaton for account %s not available\n",
                                  cli_credentials_get_principal(machine_account, mem_ctx)));
                        return EINVAL;
                }
@@ -293,7 +381,7 @@ static int free_keytab(void *ptr) {
                                         mach_pwd->hash, sizeof(mach_pwd->hash), 
                                         &entry.keyblock);
                if (ret) {
-                       DEBUG(1, ("create_memory_keytab: krb5_keyblock_init failed: %s\n",
+                       DEBUG(1, ("create_keytab: krb5_keyblock_init failed: %s\n",
                                  smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
                                                             ret, mem_ctx)));
                        return ret;
@@ -321,103 +409,238 @@ static int free_keytab(void *ptr) {
 
                krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
 
-               talloc_steal(parent_ctx, *keytab_container);
+               /* Can't go any further, we only have this one key */
                talloc_free(mem_ctx);
                return 0;
        }
-               
+       
+       kvno = cli_credentials_get_kvno(machine_account);
        /* good, we actually have the real plaintext */
+       ret = keytab_add_keys(mem_ctx, princ_string, princ, salt_princ, 
+                      kvno, password_s, smb_krb5_context, keytab);
+       if (!ret) {
+               talloc_free(mem_ctx);
+               return ret;
+       }
 
-       ret = get_kerberos_allowed_etypes(smb_krb5_context->krb5_context, 
-                                         &enctypes);
+       if (!add_old || kvno == 0) {
+               talloc_free(mem_ctx);
+               return 0;
+       }
+
+       old_secret = cli_credentials_get_old_password(machine_account);
+       if (!old_secret) {
+               talloc_free(mem_ctx);
+               return 0;
+       }
+       
+       ret = keytab_add_keys(mem_ctx, princ_string, princ, salt_princ, 
+                             kvno - 1, old_secret, smb_krb5_context, keytab);
+       if (!ret) {
+               talloc_free(mem_ctx);
+               return ret;
+       }
+
+       talloc_free(mem_ctx);
+       return 0;
+}
+
+
+/*
+ * Walk the keytab, looking for entries of this principal name, with KVNO other than current kvno -1.
+ *
+ * These entries are now stale, we only keep the current, and previous entries around.
+ *
+ * Inspired by the code in Samba3 for 'use kerberos keytab'.
+ *
+ */
+
+static krb5_error_code remove_old_entries(TALLOC_CTX *parent_ctx,
+                                         struct cli_credentials *machine_account,
+                                         struct smb_krb5_context *smb_krb5_context,
+                                         krb5_keytab keytab, BOOL *found_previous)
+{
+       krb5_error_code ret, ret2;
+       krb5_kt_cursor cursor;
+       krb5_principal princ;
+       int kvno;
+       TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+       const char *princ_string;
+       if (!mem_ctx) {
+               return ENOMEM;
+       }
+
+       *found_previous = False;
+       princ_string = cli_credentials_get_principal(machine_account, mem_ctx);
+
+       /* Get the principal we will store the new keytab entries under */
+       ret = principal_from_credentials(mem_ctx, machine_account, smb_krb5_context, &princ);
        if (ret) {
-               DEBUG(1,("create_memory_keytab: getting encrption types failed (%s)\n",
-                        error_message(ret)));
+               DEBUG(1,("update_keytab: makeing krb5 principal failed (%s)\n",
+                        smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
+                                                   ret, mem_ctx)));
                talloc_free(mem_ctx);
                return ret;
        }
 
-       password.data = discard_const_p(char *, password_s);
-       password.length = strlen(password_s);
        kvno = cli_credentials_get_kvno(machine_account);
 
-       for (i=0; enctypes[i]; i++) {
+       /* for each entry in the keytab */
+       ret = krb5_kt_start_seq_get(smb_krb5_context->krb5_context, keytab, &cursor);
+       switch (ret) {
+       case 0:
+               break;
+       case ENOENT:
+       case KRB5_KT_END:
+               /* no point enumerating if there isn't anything here */
+               talloc_free(mem_ctx);
+               return 0;
+       default:
+               DEBUG(1,("failed to open keytab for read of old entries: %s\n",
+                        smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
+                                                   ret, mem_ctx)));
+               talloc_free(mem_ctx);
+               return ret;
+       }
+
+       while (!ret) {
                krb5_keytab_entry entry;
-               ret = create_kerberos_key_from_string(smb_krb5_context->krb5_context, 
-                                                     salt_princ, &password, &entry.keyblock, enctypes[i]);
+               ret = krb5_kt_next_entry(smb_krb5_context->krb5_context, keytab, &entry, &cursor);
                if (ret) {
-                       talloc_free(mem_ctx);
-                       return ret;
+                       break;
                }
-
-                entry.principal = princ;
-                entry.vno       = kvno;
-               ret = krb5_kt_add_entry(smb_krb5_context->krb5_context, keytab, &entry);
-               if (ret) {
-                       DEBUG(1, ("Failed to add entry for %s to keytab: %s",
-                                 cli_credentials_get_principal(machine_account, mem_ctx), 
-                                 smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
-                                                            ret, mem_ctx)));
-                       talloc_free(mem_ctx);
-                       krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
-                       return ret;
+               /* if it matches our principal */
+               if (!krb5_kt_compare(smb_krb5_context->krb5_context, &entry, princ, 0, 0)) {
+                       /* Free the entry, it wasn't the one we were looking for anyway */
+                       krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry);
+                       continue;
                }
 
-               enctype_string = NULL;
-               krb5_keytype_to_string(smb_krb5_context->krb5_context, enctypes[i], &enctype_string);
-               DEBUG(5, ("Added %s(kvno %d) to keytab (%s)\n", 
-                         cli_credentials_get_principal(machine_account, mem_ctx),
-                         cli_credentials_get_kvno(machine_account),
-                         enctype_string));
-               free(enctype_string);           
+               /* delete it, if it is not kvno -1 */
+               if (entry.vno != (kvno - 1 )) {
+                       /* Release the enumeration.  We are going to
+                        * have to start this from the top again,
+                        * because deletes during enumeration may not
+                        * always be consistant.
+                        *
+                        * Also, the enumeration locks the keytab
+                        */
                
-               krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
-       }
-
-       old_secret = cli_credentials_get_old_password(machine_account);
-       if (kvno != 0 && old_secret) {
-               password.data = discard_const_p(char *, old_secret);
-               password.length = strlen(old_secret);
-               
-               for (i=0; enctypes[i]; i++) {
-                       krb5_keytab_entry entry;
-                       ret = create_kerberos_key_from_string(smb_krb5_context->krb5_context, 
-                                                             salt_princ, &password, &entry.keyblock, enctypes[i]);
-                       if (ret) {
+                       krb5_kt_end_seq_get(smb_krb5_context->krb5_context, keytab, &cursor);
+
+                       ret = krb5_kt_remove_entry(smb_krb5_context->krb5_context, keytab, &entry);
+                       krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry);
+
+                       /* Deleted: Restart from the top */
+                       ret2 = krb5_kt_start_seq_get(smb_krb5_context->krb5_context, keytab, &cursor);
+                       if (ret2) {
+                               krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry);
+                               DEBUG(1,("failed to restart enumeration of keytab: %s\n",
+                                        smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
+                                                                   ret, mem_ctx)));
+                               
                                talloc_free(mem_ctx);
-                               return ret;
+                               return ret2;
                        }
-                       
-                       entry.principal = princ;
-                       entry.vno       = kvno - 1;
-                       ret = krb5_kt_add_entry(smb_krb5_context->krb5_context, keytab, &entry);
+
                        if (ret) {
-                               DEBUG(1, ("Failed to add 'old password' entry for %s to keytab: %s",
-                                         cli_credentials_get_principal(machine_account, mem_ctx), 
-                                         smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
-                                                                    ret, mem_ctx)));
-                               talloc_free(mem_ctx);
-                               krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
-                               return ret;
+                               break;
                        }
                        
-                       enctype_string = NULL;
-                       krb5_keytype_to_string(smb_krb5_context->krb5_context, enctypes[i], &enctype_string);
-                       DEBUG(5, ("Added %s(kvno %d) to keytab (%s)\n", 
-                                 cli_credentials_get_principal(machine_account, mem_ctx),
-                                 cli_credentials_get_kvno(machine_account),
-                         enctype_string));
-                       free(enctype_string);           
-
-                       krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
+               } else {
+                       *found_previous = True;
                }
+               
+               /* Free the entry, we don't need it any more */
+               krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry);
+               
+               
        }
+       krb5_kt_end_seq_get(smb_krb5_context->krb5_context, keytab, &cursor);
+
+       switch (ret) {
+       case 0:
+               break;
+       case ENOENT:
+       case KRB5_KT_END:
+               ret = 0;
+               break;
+       default:
+               DEBUG(1,("failed in deleting old entries for principal: %s: %s\n",
+                        princ_string, 
+                        smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
+                                                   ret, mem_ctx)));
+       }
+       talloc_free(mem_ctx);
+       return ret;
+}
 
-       free_kerberos_etypes(smb_krb5_context->krb5_context, enctypes);
-
-       talloc_steal(parent_ctx, *keytab_container);
+int update_keytab(TALLOC_CTX *parent_ctx,
+                 struct cli_credentials *machine_account,
+                 struct smb_krb5_context *smb_krb5_context,
+                 struct keytab_container *keytab_container) 
+{
+       krb5_error_code ret;
+       BOOL found_previous;
+       TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+       if (!mem_ctx) {
+               return ENOMEM;
+       }
+       
+       ret = remove_old_entries(mem_ctx, machine_account, 
+                                smb_krb5_context, keytab_container->keytab, &found_previous);
+       if (ret != 0) {
+               talloc_free(mem_ctx);
+               return ret;
+       }
+       
+       /* Create a new keytab.  If during the cleanout we found
+        * entires for kvno -1, then don't try and duplicate them.
+        * Otherwise, add kvno, and kvno -1 */
+       
+       ret = create_keytab(mem_ctx, machine_account, smb_krb5_context, 
+                           keytab_container, 
+                           found_previous ? False : True);
        talloc_free(mem_ctx);
-       return 0;
+       return ret;
 }
 
+int create_memory_keytab(TALLOC_CTX *parent_ctx,
+                        struct cli_credentials *machine_account,
+                        struct smb_krb5_context *smb_krb5_context,
+                        struct keytab_container **keytab_container) 
+{
+       krb5_error_code ret;
+       TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+       const char *keytab_name = "MEMORY:";
+       krb5_keytab keytab;
+       if (!mem_ctx) {
+               return ENOMEM;
+       }
+       
+       *keytab_container = talloc(mem_ctx, struct keytab_container);
+
+       /* Find the keytab */
+       ret = krb5_kt_resolve(smb_krb5_context->krb5_context, keytab_name, &keytab);
+       if (ret) {
+               DEBUG(1,("failed to resolve keytab: %s: %s\n",
+                        keytab_name,
+                        smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
+                                                   ret, mem_ctx)));
+               talloc_free(mem_ctx);
+               return ret;
+       }
+
+       (*keytab_container)->smb_krb5_context = talloc_reference(*keytab_container, smb_krb5_context);
+       (*keytab_container)->keytab = keytab;
+
+       talloc_set_destructor(*keytab_container, free_keytab);
+       
+       ret = update_keytab(mem_ctx, machine_account, smb_krb5_context, *keytab_container);
+       if (ret == 0) {
+               talloc_steal(parent_ctx, *keytab_container);
+       } 
+       talloc_free(mem_ctx);
+       return ret;
+}