summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNalin Dahyabhai <nalin.dahyabhai@pobox.com>2009-04-01 19:30:44 -0400
committerNalin Dahyabhai <nalin.dahyabhai@pobox.com>2009-04-01 19:30:44 -0400
commite9e45b52a7ccf311b98683ca2e3ee241a0ec8c08 (patch)
treea36c4b07f54e89c6146479cc44f594d3251144b8
parent959063b017e40f6f03007228bb1c3dc37a11d437 (diff)
downloadcredmonger-e9e45b52a7ccf311b98683ca2e3ee241a0ec8c08.tar.gz
credmonger-e9e45b52a7ccf311b98683ca2e3ee241a0ec8c08.tar.xz
credmonger-e9e45b52a7ccf311b98683ca2e3ee241a0ec8c08.zip
- first cut, not actually a working daemon
-rw-r--r--src/Makefile.am2
-rw-r--r--src/credmonger.c491
2 files changed, 493 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 2dd2a80..8214567 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,2 +1,4 @@
+AM_CFLAGS = @KRB5_CFLAGS@
+LDFLAGS = @KRB5_LIBS@
sbin_PROGRAMS = credmonger
credmonger_SOURCES = credmonger.c
diff --git a/src/credmonger.c b/src/credmonger.c
index 6ee3741..9af86bd 100644
--- a/src/credmonger.c
+++ b/src/credmonger.c
@@ -1,5 +1,496 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <errno.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <krb5.h>
+
+#define CONFIG_FILE "./credmongertab"
+#define FCC_PREFIX "FILE:"
+#define DEFAULT_RETRY_TIME 300
+
+/* A ccache that we need to maintain. */
+static struct monger_entry {
+ uid_t uid; /* UID of ccache owner. */
+ gid_t gid; /* GID of ccache owner. */
+ char *keytab; /* Name of keytab. */
+ char *principal_name; /* Client name. */
+ char *fccache; /* Name of existing ccache, or NULL. */
+ char *fccache_pattern; /* Pattern to use for naming ccache. */
+ krb5_timestamp when; /* When the creds in the ccache expire. */
+} **entries, **cleanup;
+
+/* Time to exit. */
+static int quit = 0;
+
+/* Read the configuration and return a list of ccache names. */
+static struct monger_entry **
+entries_read(void)
+{
+ char buf[LINE_MAX], *p, *uids, *keytab, *principal, *fccache_pattern;
+ char fccache[PATH_MAX];
+ struct monger_entry **list, **tmp, *entry;
+ struct passwd *pwd;
+ uid_t uid;
+ gid_t gid;
+ FILE *fp;
+ int n_entries;
+
+ list = NULL;
+ n_entries = 0;
+ fp = fopen(CONFIG_FILE, "r");
+ if (fp != NULL) {
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ /* Trim end-of-line characters. */
+ buf[strcspn(buf, "\r\n")] = '\0';
+ /* Skip comments. */
+ if (strchr("#:", buf[0]) != NULL) {
+ continue;
+ }
+ /* Trim end-of-line characters. */
+ uids = buf;
+ /* Skip ahead to the keytab name and close the user
+ * name. */
+ keytab = strchr(uids, ':');
+ if (keytab == NULL) {
+ fprintf(stderr, "No keytab name field.\n");
+ continue;
+ }
+ *keytab++ = '\0';
+ /* Skip ahead to the client principal name and close
+ * the keytab name. */
+ principal = strchr(keytab, ':');
+ if (principal == NULL) {
+ fprintf(stderr, "No principal name field.\n");
+ continue;
+ }
+ *principal++ = '\0';
+ /* Skip ahead to the ccache name pattern and close the
+ * principal name. */
+ fccache_pattern = strchr(principal, ':');
+ if (fccache_pattern != NULL) {
+ *fccache_pattern++ = '\0';
+ }
+ /* Now figure out the UID/GID. */
+ uid = (uid_t) strtol(uids, &p, 10);
+ if ((p == NULL) || (*p != '\0')) {
+ /* Not a number, so treat it as a user name. */
+ pwd = getpwnam(uids);
+ if (pwd != NULL) {
+ uid = pwd->pw_uid;
+ gid = pwd->pw_gid;
+ } else {
+ fprintf(stderr,
+ "Unknown user \"%s\".\n", uids);
+ continue;
+ }
+ } else {
+ /* Treat it as a number. */
+ pwd = getpwuid(uid);
+ if (pwd != NULL) {
+ gid = pwd->pw_gid;
+ } else {
+ fprintf(stderr,
+ "Unknown user %lu.\n",
+ (unsigned long) uid);
+ continue;
+ }
+ }
+ /* Now go back and figure out the ccache name pattern.
+ * If it's empty, assume that we want a "session" file
+ * in the temporary directory. */
+ if ((fccache_pattern == NULL) ||
+ (strlen(fccache_pattern) == 0)) {
+ snprintf(fccache, sizeof(fccache),
+ "FILE:%s/krb5cc_%lu_XXXXXX",
+ getenv("TMPDIR") ?: "/tmp",
+ (unsigned long) uid);
+ } else {
+ /* If the user supplied us with a typed ccache
+ * name, just use it as-is. */
+ if (strncmp(fccache_pattern, FCC_PREFIX,
+ strlen(FCC_PREFIX)) == 0) {
+ snprintf(fccache, sizeof(fccache),
+ "%s", fccache_pattern);
+ } else {
+ /* Mark it as a file-based ccache, and
+ * take the filename as-is. */
+ snprintf(fccache, sizeof(fccache),
+ "FILE:%s", fccache_pattern);
+ }
+ }
+ fccache_pattern = fccache;
+ /* Make space for this entry in the list. */
+ tmp = realloc(list, sizeof(*list) * (n_entries + 2));
+ if (tmp == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ break;
+ }
+ list = tmp;
+ /* Allocate this entry. */
+ list[n_entries] = malloc(sizeof(**list));
+ if (list[n_entries] == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ break;
+ }
+ entry = list[n_entries];
+ /* NULL-terminate the list. */
+ n_entries++;
+ list[n_entries] = NULL;
+ /* Initialize this entry. */
+ memset(entry, 0, sizeof(*entry));
+ entry->uid = uid;
+ entry->gid = gid;
+ entry->keytab = strdup(keytab);
+ if (entry->keytab == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ break;
+ }
+ entry->principal_name = strdup(principal);
+ if (entry->principal_name == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ break;
+ }
+ entry->fccache_pattern = strdup(fccache_pattern);
+ if (entry->fccache_pattern == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ break;
+ }
+ fprintf(stderr, "%ld:%s,%s,%s\n",
+ (unsigned long) entry->uid,
+ entry->keytab,
+ entry->principal_name,
+ entry->fccache_pattern);
+ }
+ fclose(fp);
+ }
+
+ return list;
+}
+
+/* Do the heavy lifting. */
+static void
+entries_poll(void)
+{
+ int i, j, fd, len, need_rename;
+ krb5_error_code ret;
+ krb5_context ctx;
+ krb5_creds creds;
+ krb5_keytab keytab;
+ krb5_ccache ccache;
+ krb5_principal client;
+ krb5_get_init_creds_opt *gic_opts;
+ char host[LINE_MAX], fccache[PATH_MAX + strlen(FCC_PREFIX) + 1];
+ char *principal_name, *oldfile;
+
+ /* Figure out the client hostname. */
+ memset(host, '\0', sizeof(host));
+ if (gethostname(host, sizeof(host) - 1) != 0) {
+ fprintf(stderr, "Error determining hostname.\n");
+ snprintf(host, sizeof(host), "localhost");
+ }
+
+ ctx = NULL;
+ gic_opts = NULL;
+ if (krb5_init_context(&ctx) != 0) {
+ fprintf(stderr, "Error initializing Kerberos.\n");
+ } else {
+ /* Initialize the get_init_creds options. */
+ if (krb5_get_init_creds_opt_alloc(ctx, &gic_opts) != 0) {
+ gic_opts = NULL;
+ } else {
+ krb5_get_init_creds_opt_set_forwardable(gic_opts, 0);
+ krb5_get_init_creds_opt_set_proxiable(gic_opts, 0);
+ }
+ /* Walk the list of entries. */
+ for (i = 0; (entries != NULL) && (entries[i] != NULL); i++) {
+ fprintf(stderr, "%ld:%s,%s,%s\n",
+ (unsigned long) entries[i]->uid,
+ entries[i]->keytab,
+ entries[i]->principal_name,
+ entries[i]->fccache_pattern);
+ /* Open the keytab. */
+ keytab = NULL;
+ if ((entries[i]->keytab == NULL) ||
+ (strlen(entries[i]->keytab) == 0)) {
+ ret = krb5_kt_default(ctx, &keytab);
+ } else {
+ ret = krb5_kt_resolve(ctx, entries[i]->keytab,
+ &keytab);
+ }
+ if (ret != 0) {
+ fprintf(stderr, "Error resolving keytab: %s.\n",
+ error_message(ret));
+ continue;
+ }
+ /* If the principal name ends with "/", append the
+ * local host name. Parse it. */
+ client = NULL;
+ len = strlen(entries[i]->principal_name);
+ if ((len > 0) &&
+ (entries[i]->principal_name[len - 1] == '/')) {
+ principal_name = malloc(len + strlen(host) + 1);
+ if (principal_name == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ krb5_kt_close(ctx, keytab);
+ continue;
+ }
+ strcpy(principal_name,
+ entries[i]->principal_name);
+ strcat(principal_name, host);
+ } else {
+ principal_name = strdup(entries[i]->principal_name);
+ if (principal_name == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ krb5_kt_close(ctx, keytab);
+ continue;
+ }
+ }
+ ret = krb5_parse_name(ctx, principal_name, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Error parsing client: %s.\n",
+ error_message(ret));
+ free(principal_name);
+ krb5_kt_close(ctx, keytab);
+ continue;
+ }
+ free(principal_name);
+ /* Now "unparse" it. */
+ principal_name = NULL;
+ ret = krb5_unparse_name(ctx, client, &principal_name);
+ if (ret != 0) {
+ fprintf(stderr, "Error unparsing name: %s.\n",
+ error_message(ret));
+ krb5_kt_close(ctx, keytab);
+ krb5_free_principal(ctx, client);
+ continue;
+ }
+ /* Okay, we have everything we need. Give it a shot. */
+ memset(&creds, 0, sizeof(creds));
+ ret = krb5_get_init_creds_keytab(ctx, &creds, client,
+ keytab, 0, NULL,
+ gic_opts);
+ if (ret != 0) {
+ fprintf(stderr, "Error getting creds for %s: "
+ "%s.\n", principal_name,
+ error_message(ret));
+ krb5_kt_close(ctx, keytab);
+ krb5_free_unparsed_name(ctx, principal_name);
+ krb5_free_principal(ctx, client);
+ continue;
+ }
+ /* Create a temporary file to hold the creds. */
+ snprintf(fccache, sizeof(fccache), "%s",
+ entries[i]->fccache_pattern);
+ need_rename = 0;
+ fd = mkstemp(fccache + strlen(FCC_PREFIX));
+ /* If we got EINVAL, then it's not a pattern, so create
+ * a pattern and note that we'll need to rename it over
+ * the "real" name when we're done setting it up. */
+ if ((fd == -1) && (errno == EINVAL)) {
+ need_rename++;
+ snprintf(fccache, sizeof(fccache),
+ "%sXXXXXX",
+ entries[i]->fccache_pattern);
+ fd = mkstemp(fccache + strlen(FCC_PREFIX));
+ }
+ if (fd == -1) {
+ fprintf(stderr, "Error creating temporary "
+ "ccache.\n");
+ krb5_free_cred_contents(ctx, &creds);
+ krb5_kt_close(ctx, keytab);
+ krb5_free_unparsed_name(ctx, principal_name);
+ krb5_free_principal(ctx, client);
+ continue;
+ }
+ close(fd);
+ /* Open the ccache. */
+ ccache = NULL;
+ ret = krb5_cc_resolve(ctx, fccache, &ccache);
+ if (ret != 0) {
+ fprintf(stderr, "Error opening temporary "
+ "ccache: %s.\n", error_message(ret));
+ unlink(fccache + strlen(FCC_PREFIX));
+ krb5_free_cred_contents(ctx, &creds);
+ krb5_kt_close(ctx, keytab);
+ krb5_free_unparsed_name(ctx, principal_name);
+ krb5_free_principal(ctx, client);
+ continue;
+ }
+ /* Write the client's name to the ccache. */
+ ret = krb5_cc_initialize(ctx, ccache, client);
+ if (ret != 0) {
+ fprintf(stderr, "Error initializing temporary "
+ "ccache: %s.\n", error_message(ret));
+ krb5_cc_close(ctx, ccache);
+ unlink(fccache + strlen(FCC_PREFIX));
+ krb5_free_cred_contents(ctx, &creds);
+ krb5_kt_close(ctx, keytab);
+ krb5_free_unparsed_name(ctx, principal_name);
+ krb5_free_principal(ctx, client);
+ continue;
+ }
+ /* Store the TGT. */
+ ret = krb5_cc_store_cred(ctx, ccache, &creds);
+ if (ret != 0) {
+ fprintf(stderr, "Error storing creds: %s.\n",
+ error_message(ret));
+ krb5_cc_close(ctx, ccache);
+ unlink(fccache + strlen(FCC_PREFIX));
+ krb5_free_cred_contents(ctx, &creds);
+ krb5_kt_close(ctx, keytab);
+ krb5_free_unparsed_name(ctx, principal_name);
+ krb5_free_principal(ctx, client);
+ continue;
+ }
+ /* Done with the ccache structure. */
+ krb5_cc_close(ctx, ccache);
+ /* Fixup permissions. */
+ chown(fccache + strlen(FCC_PREFIX),
+ entries[i]->uid, entries[i]->gid);
+ /* If the configuration doesn't want a unique ccache
+ * name, overwrite the destination with the contents of
+ * this temporary file. */
+ if (need_rename) {
+ rename(fccache + strlen(FCC_PREFIX),
+ entries[i]->fccache_pattern +
+ strlen(FCC_PREFIX));
+ entries[i]->fccache =
+ entries[i]->fccache_pattern;
+ entries[i]->when = creds.times.endtime;
+ } else {
+ /* Remove the old file, and record this one. */
+ oldfile = entries[i]->fccache;
+ entries[i]->fccache = strdup(fccache);
+ if (entries[i]->fccache == NULL) {
+ /* Better luck next time, which is
+ * soon. */
+ entries[i]->fccache = oldfile;
+ entries[i]->when = DEFAULT_RETRY_TIME;
+ unlink(fccache + strlen(FCC_PREFIX));
+ } else {
+ /* Get rid of a previously-initialized
+ * file. */
+ if (oldfile != NULL) {
+ unlink(oldfile + strlen(FCC_PREFIX));
+ free(oldfile);
+ }
+ /* Record the renewal time. */
+ entries[i]->when = creds.times.endtime;
+ }
+ }
+ fprintf(stderr, "Saved creds for \"%s\" to \"%s\".\n",
+ principal_name, entries[i]->fccache);
+ krb5_free_cred_contents(ctx, &creds);
+ krb5_kt_close(ctx, keytab);
+ krb5_free_unparsed_name(ctx, principal_name);
+ krb5_free_principal(ctx, client);
+ }
+ krb5_get_init_creds_opt_free(ctx, gic_opts);
+ krb5_free_context(ctx);
+ }
+
+ /* Walk the list of to-be-cleaned-up entries. */
+ for (i = 0; (cleanup != NULL) && (cleanup[i] != NULL); i++) {
+ /* Remove the ccache, if there is one. */
+ if (cleanup[i]->fccache != NULL) {
+ for (j = 0;
+ (entries != NULL) && (entries[j] != NULL);
+ j++) {
+ /* ... unless it's being used elsewhere. */
+ if ((entries[j]->fccache != NULL) &&
+ (strcmp(cleanup[i]->fccache,
+ entries[j]->fccache) == 0)) {
+ break;
+ }
+ }
+ if ((entries == NULL) || (entries[j] == NULL)) {
+ fprintf(stderr, "Removing \"%s\".\n",
+ cleanup[i]->fccache);
+ unlink(cleanup[i]->fccache +
+ strlen(FCC_PREFIX));
+ }
+ }
+ free(cleanup[i]->fccache);
+ /* Free the pattern if it's not a plain filename. */
+ if (cleanup[i]->fccache != cleanup[i]->fccache_pattern) {
+ free(cleanup[i]->fccache_pattern);
+ }
+ /* Finish up. */
+ free(cleanup[i]->principal_name);
+ free(cleanup[i]->keytab);
+ free(cleanup[i]);
+ }
+ free(cleanup);
+ cleanup = NULL;
+}
+
+static void
+entries_reload(void)
+{
+ struct monger_entry **list, **cleanup;
+ list = entries_read();
+ cleanup = entries;
+ entries = list;
+ entries_poll();
+}
+
+static void
+entries_unload(void)
+{
+ cleanup = entries;
+ entries = NULL;
+ entries_poll();
+}
+
+static int
+entries_waittime(void)
+{
+ int i;
+ krb5_timestamp last;
+ for (i = 0, last = 0; (entries != NULL) && (entries[i] != NULL); i++) {
+ if (entries[i]->fccache != NULL) {
+ if ((last == 0) || (entries[i]->when < last)) {
+ last = entries[i]->when;
+ }
+ }
+ }
+ if (last == 0) {
+ i = DEFAULT_RETRY_TIME;
+ } else {
+ i = (last - time(NULL)) * 9 / 10;
+ }
+ return i;
+}
+
+static void
+setquit(int signum)
+{
+ quit++;
+}
+
int
main(int argc, char **argv)
{
+ time_t now, howlong;
+ signal(SIGINT, setquit);
+ signal(SIGQUIT, setquit);
+ while (quit == 0) {
+ entries_reload();
+ now = time(NULL);
+ howlong = entries_waittime();
+ fprintf(stderr, "Waiting for %ld seconds.\n", (long) howlong);
+ sleep(howlong);
+ };
+ entries_unload();
return 0;
}