summaryrefslogtreecommitdiff
path: root/client/keytab.c
diff options
context:
space:
mode:
Diffstat (limited to 'client/keytab.c')
-rw-r--r--client/keytab.c175
1 files changed, 170 insertions, 5 deletions
diff --git a/client/keytab.c b/client/keytab.c
index 5f2076f..9a7734e 100644
--- a/client/keytab.c
+++ b/client/keytab.c
@@ -17,13 +17,84 @@
#include <util/concat.h>
#include <util/messages-krb5.h>
#include <util/messages.h>
+#include <util/xmalloc.h>
+
+/* List of principals we have already encountered. */
+struct principal_name {
+ char *princ;
+ struct principal_name *next;
+};
/*
- * Given keytab data as a pointer to memory and a length and the path of a
- * second keytab, merge the keys in the memory keytab into the file keytab.
- * Currently, this doesn't do any cleanup of old kvnos and doesn't handle
- * duplicate kvnos correctly. Dies on any error.
+ * Given a context, a keytab file, and a realm, return a list of all
+ * principals in that file.
+ */
+static struct principal_name *
+keytab_principals(krb5_context ctx, const char *file, char *realm)
+{
+ char *princname = NULL, *princrealm = NULL;
+ bool found;
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+ krb5_error_code status;
+ struct principal_name *names = NULL, *current = NULL, *last = NULL;
+
+ memset(&entry, 0, sizeof(entry));
+ status = krb5_kt_resolve(ctx, file, &keytab);
+ if (status != 0)
+ die_krb5(ctx, status, "cannot open keytab %s", file);
+ status = krb5_kt_start_seq_get(ctx, keytab, &cursor);
+ if (status != 0)
+ die_krb5(ctx, status, "cannot read keytab %s", file);
+ while ((status = krb5_kt_next_entry(ctx, keytab, &entry, &cursor)) == 0) {
+ status = krb5_unparse_name(ctx, entry.principal, &princname);
+ if (status != 0)
+ die_krb5(ctx, status, "cannot unparse name for a principal");
+
+ /* Separate into principal and realm. */
+ princrealm = strchr(princname, '@');
+ if (princrealm != NULL) {
+ *princrealm = '\0';
+ princrealm++;
+ }
+ if (princrealm == NULL || strcmp(princrealm, realm) != 0)
+ continue;
+
+ /* Check to see if the principal has already been listed. */
+ found = false;
+ for (current = names; current != NULL; current = current->next) {
+ if (strcmp(current->princ, princname) == 0) {
+ found = true;
+ break;
+ }
+ last = current;
+ }
+ if (found == false) {
+ current = xmalloc(sizeof(struct principal_name));
+ current->princ = xstrdup(princname);
+ current->next = NULL;
+ if (last == NULL)
+ names = current;
+ else
+ last->next = current;
+ }
+ krb5_kt_free_entry(ctx, &entry);
+ free(princname);
+ }
+ if (status != KRB5_KT_END)
+ die_krb5(ctx, status, "error reading keytab %s", file);
+ krb5_kt_end_seq_get(ctx, keytab, &cursor);
+ krb5_kt_close(ctx, keytab);
+ return names;
+}
+
+
+/*
+ * Given two files containing keytab data, second keytab, merge the keys into
+ * the new file. Currently, this doesn't do any cleanup of old kvnos and
+ * doesn't handle duplicate kvnos correctly. Dies on any error.
*/
static void
merge_keytab(krb5_context ctx, const char *newfile, const char *file)
@@ -63,9 +134,36 @@ merge_keytab(krb5_context ctx, const char *newfile, const char *file)
/*
+ * Given a remctl object, the type and name of a keytab object, and
+ * references to keytab data and data length, call the correct wallet
+ * commands to download a keytab and return the keytab data. Returns the
+ * status of the remctl command.
+ */
+static int
+download_keytab(struct remctl *r, const char *type, const char *name,
+ char **data, size_t *length)
+{
+ const char *command[5];
+ int status;
+
+ command[0] = type;
+ command[1] = "get";
+ command[2] = "keytab";
+ command[3] = name;
+ command[4] = NULL;
+ status = run_command(r, command, data, length);
+ if (*data == NULL && status == 0) {
+ warn("no data returned by wallet server");
+ return 255;
+ }
+ return status;
+}
+
+
+/*
* Given a remctl object, the Kerberos context, the name of a keytab object,
* and a file name, call the correct wallet commands to download a keytab and
- * write it to that file. Returns the setatus or 255 on an internal error.
+ * write it to that file. Returns the status or 255 on an internal error.
*/
int
get_keytab(struct remctl *r, krb5_context ctx, const char *type,
@@ -105,3 +203,70 @@ get_keytab(struct remctl *r, krb5_context ctx, const char *type,
}
return 0;
}
+
+
+/*
+ * Given a remctl object, the Kerberos context, the type for the wallet
+ * interface, and a file name of a keytab, iterate through every existing
+ * principal in the keytab in the local realm, get fresh keys for those
+ * principals, and save the old and new keys to that file. Returns true on
+ * success and false on partial failure to retrieve all the keys.
+ */
+bool
+rekey_keytab(struct remctl *r, krb5_context ctx, const char *type,
+ const char *file)
+{
+ char *realm = NULL;
+ char *data = NULL;
+ char *tempfile, *backupfile;
+ size_t length = 0;
+ int status;
+ bool error = false, rekeyed = false;
+ struct principal_name *names, *current;
+
+ tempfile = concat(file, ".new", (char *) 0);
+ krb5_get_default_realm(ctx, &realm);
+ names = keytab_principals(ctx, file, realm);
+ for (current = names; current != NULL; current = current->next) {
+ status = download_keytab(r, type, current->princ, &data, &length);
+ if (status != 0) {
+ warn("error rekeying for principal %s", current->princ);
+ if (!rekeyed)
+ die("aborting, keytab unchanged");
+ error = true;
+ } else if (data != NULL) {
+ if (access(tempfile, F_OK) == 0)
+ append_file(tempfile, data, length);
+ else
+ write_file(tempfile, data, length);
+ rekeyed = true;
+ }
+ }
+
+ /* If no new keytab data, then leave the keytab as-is. */
+ if (!rekeyed)
+ die("no rekeyable principals found");
+
+ /*
+ * Now merge the original keytab file with the one containing the new
+ * keys. If there is an error, first make a backup of the current keytab
+ * file as keytab.old.
+ */
+ if (access(file, F_OK) != 0)
+ link(tempfile, file);
+ else {
+ if (error) {
+ data = read_file(file, &length);
+ backupfile = concat(file, ".old", (char *) 0);
+ overwrite_file(backupfile, data, length);
+ warn("partial failure to rekey keytab %s, old keytab left in %s",
+ file, backupfile);
+ free(backupfile);
+ }
+ merge_keytab(ctx, tempfile, file);
+ }
+ if (unlink(tempfile) < 0)
+ sysdie("unlink of temporary keytab file %s failed", tempfile);
+ free(tempfile);
+ return !error;
+}