diff options
Diffstat (limited to 'src/kdc/main.c')
| -rw-r--r-- | src/kdc/main.c | 1081 |
1 files changed, 1081 insertions, 0 deletions
diff --git a/src/kdc/main.c b/src/kdc/main.c new file mode 100644 index 000000000000..ebc852bba2e9 --- /dev/null +++ b/src/kdc/main.c @@ -0,0 +1,1081 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* kdc/main.c - Main procedure body for the KDC server process */ +/* + * Copyright 1990,2001,2008,2009,2016 by the Massachusetts Institute of + * Technology. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include "k5-int.h" +#include "com_err.h" +#include <kadm5/admin.h> +#include "adm_proto.h" +#include "kdc_util.h" +#include "kdc_audit.h" +#include "extern.h" +#include "kdc5_err.h" +#include "kdb_kt.h" +#include "net-server.h" +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include <locale.h> +#include <syslog.h> +#include <signal.h> +#include <netdb.h> +#include <unistd.h> +#include <ctype.h> +#include <sys/wait.h> + +#if defined(NEED_DAEMON_PROTO) +extern int daemon(int, int); +#endif + +static void usage (char *); + +static krb5_error_code setup_sam (void); + +static void initialize_realms(krb5_context kcontext, int argc, char **argv, + int *tcp_listen_backlog_out); + +static void finish_realms (void); + +static int nofork = 0; +static int workers = 0; +static int time_offset = 0; +static const char *pid_file = NULL; +static int rkey_init_done = 0; +static volatile int signal_received = 0; +static volatile int sighup_received = 0; + +#define KRB5_KDC_MAX_REALMS 32 + +static const char *kdc_progname; + +/* + * Static server_handle for this file. Other code will get access to + * it through the application handle that net-server.c uses. + */ +static struct server_handle shandle; + +/* + * We use krb5_klog_init to set up a com_err callback to log error + * messages. The callback also pulls the error message out of the + * context we pass to krb5_klog_init; however, we use realm-specific + * contexts for most of our krb5 library calls, so the error message + * isn't present in the global context. This wrapper ensures that the + * error message state from the call context is copied into the + * context known by krb5_klog. call_context can be NULL if the error + * code did not come from a krb5 library function. + */ +void +kdc_err(krb5_context call_context, errcode_t code, const char *fmt, ...) +{ + va_list ap; + + if (call_context) + krb5_copy_error_message(shandle.kdc_err_context, call_context); + va_start(ap, fmt); + com_err_va(kdc_progname, code, fmt, ap); + va_end(ap); +} + +/* + * Find the realm entry for a given realm. + */ +kdc_realm_t * +find_realm_data(struct server_handle *handle, char *rname, krb5_ui_4 rsize) +{ + int i; + kdc_realm_t **kdc_realmlist = handle->kdc_realmlist; + int kdc_numrealms = handle->kdc_numrealms; + + for (i=0; i<kdc_numrealms; i++) { + if ((rsize == strlen(kdc_realmlist[i]->realm_name)) && + !strncmp(rname, kdc_realmlist[i]->realm_name, rsize)) + return(kdc_realmlist[i]); + } + return((kdc_realm_t *) NULL); +} + +kdc_realm_t * +setup_server_realm(struct server_handle *handle, krb5_principal sprinc) +{ + kdc_realm_t *newrealm; + kdc_realm_t **kdc_realmlist = handle->kdc_realmlist; + int kdc_numrealms = handle->kdc_numrealms; + + if (sprinc == NULL) + return NULL; + + if (kdc_numrealms > 1) { + if (!(newrealm = find_realm_data(handle, sprinc->realm.data, + (krb5_ui_4) sprinc->realm.length))) + return NULL; + else + return newrealm; + } + else + return kdc_realmlist[0]; +} + +static void +finish_realm(kdc_realm_t *rdp) +{ + if (rdp->realm_name) + free(rdp->realm_name); + if (rdp->realm_mpname) + free(rdp->realm_mpname); + if (rdp->realm_stash) + free(rdp->realm_stash); + if (rdp->realm_listen) + free(rdp->realm_listen); + if (rdp->realm_tcp_listen) + free(rdp->realm_tcp_listen); + if (rdp->realm_keytab) + krb5_kt_close(rdp->realm_context, rdp->realm_keytab); + if (rdp->realm_hostbased) + free(rdp->realm_hostbased); + if (rdp->realm_no_referral) + free(rdp->realm_no_referral); + if (rdp->realm_context) { + if (rdp->realm_mprinc) + krb5_free_principal(rdp->realm_context, rdp->realm_mprinc); + if (rdp->realm_mkey.length && rdp->realm_mkey.contents) { + /* XXX shouldn't memset be zap for safety? */ + memset(rdp->realm_mkey.contents, 0, rdp->realm_mkey.length); + free(rdp->realm_mkey.contents); + } + krb5_db_fini(rdp->realm_context); + if (rdp->realm_tgsprinc) + krb5_free_principal(rdp->realm_context, rdp->realm_tgsprinc); + krb5_free_context(rdp->realm_context); + } + zapfree(rdp, sizeof(*rdp)); +} + +/* Set *val_out to an allocated string containing val1 and/or val2, separated + * by a space if both are set, or NULL if neither is set. */ +static krb5_error_code +combine(const char *val1, const char *val2, char **val_out) +{ + if (val1 == NULL && val2 == NULL) { + *val_out = NULL; + } else if (val1 != NULL && val2 != NULL) { + if (asprintf(val_out, "%s %s", val1, val2) < 0) { + *val_out = NULL; + return ENOMEM; + } + } else { + *val_out = strdup((val1 != NULL) ? val1 : val2); + if (*val_out == NULL) + return ENOMEM; + } + return 0; +} + +/* + * Initialize a realm control structure from the alternate profile or from + * the specified defaults. + * + * After we're complete here, the essence of the realm is embodied in the + * realm data and we should be all set to begin operation for that realm. + */ +static krb5_error_code +init_realm(kdc_realm_t * rdp, krb5_pointer aprof, char *realm, + char *def_mpname, krb5_enctype def_enctype, char *def_udp_listen, + char *def_tcp_listen, krb5_boolean def_manual, + krb5_boolean def_restrict_anon, char **db_args, char *no_referral, + char *hostbased) +{ + krb5_error_code kret; + krb5_boolean manual; + int kdb_open_flags; + char *svalue = NULL; + const char *hierarchy[4]; + krb5_kvno mkvno = IGNORE_VNO; + + memset(rdp, 0, sizeof(kdc_realm_t)); + if (!realm) { + kret = EINVAL; + goto whoops; + } + hierarchy[0] = KRB5_CONF_REALMS; + hierarchy[1] = realm; + hierarchy[3] = NULL; + + rdp->realm_name = strdup(realm); + if (rdp->realm_name == NULL) { + kret = ENOMEM; + goto whoops; + } + kret = krb5int_init_context_kdc(&rdp->realm_context); + if (kret) { + kdc_err(NULL, kret, _("while getting context for realm %s"), realm); + goto whoops; + } + if (time_offset != 0) + (void)krb5_set_time_offsets(rdp->realm_context, time_offset, 0); + + /* Handle master key name */ + hierarchy[2] = KRB5_CONF_MASTER_KEY_NAME; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &rdp->realm_mpname)) { + rdp->realm_mpname = (def_mpname) ? strdup(def_mpname) : + strdup(KRB5_KDB_M_NAME); + } + if (!rdp->realm_mpname) { + kret = ENOMEM; + goto whoops; + } + + /* Handle KDC addresses/ports */ + hierarchy[2] = KRB5_CONF_KDC_LISTEN; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &rdp->realm_listen)) { + /* Try the old kdc_ports configuration option. */ + hierarchy[2] = KRB5_CONF_KDC_PORTS; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &rdp->realm_listen)) + rdp->realm_listen = strdup(def_udp_listen); + } + if (!rdp->realm_listen) { + kret = ENOMEM; + goto whoops; + } + hierarchy[2] = KRB5_CONF_KDC_TCP_LISTEN; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, + &rdp->realm_tcp_listen)) { + /* Try the old kdc_tcp_ports configuration option. */ + hierarchy[2] = KRB5_CONF_KDC_TCP_PORTS; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, + &rdp->realm_tcp_listen)) + rdp->realm_tcp_listen = strdup(def_tcp_listen); + } + if (!rdp->realm_tcp_listen) { + kret = ENOMEM; + goto whoops; + } + /* Handle stash file */ + hierarchy[2] = KRB5_CONF_KEY_STASH_FILE; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &rdp->realm_stash)) + manual = def_manual; + else + manual = FALSE; + + hierarchy[2] = KRB5_CONF_RESTRICT_ANONYMOUS_TO_TGT; + if (krb5_aprof_get_boolean(aprof, hierarchy, TRUE, + &rdp->realm_restrict_anon)) + rdp->realm_restrict_anon = def_restrict_anon; + + /* Handle master key type */ + hierarchy[2] = KRB5_CONF_MASTER_KEY_TYPE; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &svalue) || + krb5_string_to_enctype(svalue, &rdp->realm_mkey.enctype)) + rdp->realm_mkey.enctype = manual ? def_enctype : ENCTYPE_UNKNOWN; + free(svalue); + svalue = NULL; + + /* Handle reject-bad-transit flag */ + hierarchy[2] = KRB5_CONF_REJECT_BAD_TRANSIT; + if (krb5_aprof_get_boolean(aprof, hierarchy, TRUE, + &rdp->realm_reject_bad_transit)) + rdp->realm_reject_bad_transit = TRUE; + + /* Handle assume des-cbc-crc is supported for session keys */ + hierarchy[2] = KRB5_CONF_DES_CRC_SESSION_SUPPORTED; + if (krb5_aprof_get_boolean(aprof, hierarchy, TRUE, + &rdp->realm_assume_des_crc_sess)) + rdp->realm_assume_des_crc_sess = TRUE; + + /* Handle ticket maximum life */ + hierarchy[2] = KRB5_CONF_MAX_LIFE; + if (krb5_aprof_get_deltat(aprof, hierarchy, TRUE, &rdp->realm_maxlife)) + rdp->realm_maxlife = KRB5_KDB_MAX_LIFE; + + /* Handle ticket renewable maximum life */ + hierarchy[2] = KRB5_CONF_MAX_RENEWABLE_LIFE; + if (krb5_aprof_get_deltat(aprof, hierarchy, TRUE, &rdp->realm_maxrlife)) + rdp->realm_maxrlife = KRB5_KDB_MAX_RLIFE; + + /* Handle KDC referrals */ + hierarchy[2] = KRB5_CONF_NO_HOST_REFERRAL; + (void)krb5_aprof_get_string_all(aprof, hierarchy, &svalue); + kret = combine(no_referral, svalue, &rdp->realm_no_referral); + if (kret) + goto whoops; + free(svalue); + svalue = NULL; + + hierarchy[2] = KRB5_CONF_HOST_BASED_SERVICES; + (void)krb5_aprof_get_string_all(aprof, hierarchy, &svalue); + kret = combine(hostbased, svalue, &rdp->realm_hostbased); + if (kret) + goto whoops; + free(svalue); + svalue = NULL; + + /* + * We've got our parameters, now go and setup our realm context. + */ + + /* Set the default realm of this context */ + if ((kret = krb5_set_default_realm(rdp->realm_context, realm))) { + kdc_err(rdp->realm_context, kret, + _("while setting default realm to %s"), realm); + goto whoops; + } + + /* first open the database before doing anything */ + kdb_open_flags = KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_KDC; + if ((kret = krb5_db_open(rdp->realm_context, db_args, kdb_open_flags))) { + kdc_err(rdp->realm_context, kret, + _("while initializing database for realm %s"), realm); + goto whoops; + } + + /* Assemble and parse the master key name */ + if ((kret = krb5_db_setup_mkey_name(rdp->realm_context, rdp->realm_mpname, + rdp->realm_name, (char **) NULL, + &rdp->realm_mprinc))) { + kdc_err(rdp->realm_context, kret, + _("while setting up master key name %s for realm %s"), + rdp->realm_mpname, realm); + goto whoops; + } + + /* + * Get the master key (note, may not be the most current mkey). + */ + if ((kret = krb5_db_fetch_mkey(rdp->realm_context, rdp->realm_mprinc, + rdp->realm_mkey.enctype, manual, + FALSE, rdp->realm_stash, + &mkvno, NULL, &rdp->realm_mkey))) { + kdc_err(rdp->realm_context, kret, + _("while fetching master key %s for realm %s"), + rdp->realm_mpname, realm); + goto whoops; + } + + if ((kret = krb5_db_fetch_mkey_list(rdp->realm_context, rdp->realm_mprinc, + &rdp->realm_mkey))) { + kdc_err(rdp->realm_context, kret, + _("while fetching master keys list for realm %s"), realm); + goto whoops; + } + + + /* Set up the keytab */ + if ((kret = krb5_ktkdb_resolve(rdp->realm_context, NULL, + &rdp->realm_keytab))) { + kdc_err(rdp->realm_context, kret, + _("while resolving kdb keytab for realm %s"), realm); + goto whoops; + } + + /* Preformat the TGS name */ + if ((kret = krb5_build_principal(rdp->realm_context, &rdp->realm_tgsprinc, + strlen(realm), realm, KRB5_TGS_NAME, + realm, (char *) NULL))) { + kdc_err(rdp->realm_context, kret, + _("while building TGS name for realm %s"), realm); + goto whoops; + } + + if (!rkey_init_done) { + krb5_data seed; + /* + * If all that worked, then initialize the random key + * generators. + */ + + seed.length = rdp->realm_mkey.length; + seed.data = (char *)rdp->realm_mkey.contents; + + if ((kret = krb5_c_random_add_entropy(rdp->realm_context, + KRB5_C_RANDSOURCE_TRUSTEDPARTY, &seed))) + goto whoops; + + rkey_init_done = 1; + } +whoops: + /* + * If we choked, then clean up any dirt we may have dropped on the floor. + */ + if (kret) { + + finish_realm(rdp); + } + return(kret); +} + +static krb5_sigtype +on_monitor_signal(int signo) +{ + signal_received = signo; + +#ifdef POSIX_SIGTYPE + return; +#else + return(0); +#endif +} + +static krb5_sigtype +on_monitor_sighup(int signo) +{ + sighup_received = 1; + +#ifdef POSIX_SIGTYPE + return; +#else + return(0); +#endif +} + +/* + * Kill the worker subprocesses given by pids[0..bound-1], skipping any which + * are set to -1, and wait for them to exit (so that we know the ports are no + * longer in use). + */ +static void +terminate_workers(pid_t *pids, int bound) +{ + int i, status, num_active = 0; + pid_t pid; + + /* Kill the active worker pids. */ + for (i = 0; i < bound; i++) { + if (pids[i] == -1) + continue; + kill(pids[i], SIGTERM); + num_active++; + } + + /* Wait for them to exit. */ + while (num_active > 0) { + pid = wait(&status); + if (pid >= 0) + num_active--; + } +} + +/* + * Create num worker processes and return successfully in each child. The + * parent process will act as a supervisor and will only return from this + * function in error cases. + */ +static krb5_error_code +create_workers(verto_ctx *ctx, int num) +{ + krb5_error_code retval; + int i, status; + pid_t pid, *pids; +#ifdef POSIX_SIGNALS + struct sigaction s_action; +#endif /* POSIX_SIGNALS */ + + /* + * Setup our signal handlers which will forward to the children. + * These handlers will be overriden in the child processes. + */ +#ifdef POSIX_SIGNALS + (void) sigemptyset(&s_action.sa_mask); + s_action.sa_flags = 0; + s_action.sa_handler = on_monitor_signal; + (void) sigaction(SIGINT, &s_action, (struct sigaction *) NULL); + (void) sigaction(SIGTERM, &s_action, (struct sigaction *) NULL); + (void) sigaction(SIGQUIT, &s_action, (struct sigaction *) NULL); + s_action.sa_handler = on_monitor_sighup; + (void) sigaction(SIGHUP, &s_action, (struct sigaction *) NULL); +#else /* POSIX_SIGNALS */ + signal(SIGINT, on_monitor_signal); + signal(SIGTERM, on_monitor_signal); + signal(SIGQUIT, on_monitor_signal); + signal(SIGHUP, on_monitor_sighup); +#endif /* POSIX_SIGNALS */ + + /* Create child worker processes; return in each child. */ + krb5_klog_syslog(LOG_INFO, _("creating %d worker processes"), num); + pids = calloc(num, sizeof(pid_t)); + if (pids == NULL) + return ENOMEM; + for (i = 0; i < num; i++) { + pid = fork(); + if (pid == 0) { + free(pids); + if (!verto_reinitialize(ctx)) { + krb5_klog_syslog(LOG_ERR, + _("Unable to reinitialize main loop")); + return ENOMEM; + } + retval = loop_setup_signals(ctx, &shandle, reset_for_hangup); + if (retval) { + krb5_klog_syslog(LOG_ERR, _("Unable to initialize signal " + "handlers in pid %d"), pid); + return retval; + } + + /* Avoid race condition */ + if (signal_received) + exit(0); + + /* Return control to main() in the new worker process. */ + return 0; + } + if (pid == -1) { + /* Couldn't fork enough times. */ + status = errno; + terminate_workers(pids, i); + free(pids); + return status; + } + pids[i] = pid; + } + + /* We're going to use our own main loop here. */ + loop_free(ctx); + + /* Supervise the worker processes. */ + while (!signal_received) { + /* Wait until a worker process exits or we get a signal. */ + pid = wait(&status); + if (pid >= 0) { + krb5_klog_syslog(LOG_ERR, _("worker %ld exited with status %d"), + (long) pid, status); + + /* Remove the pid from the table. */ + for (i = 0; i < num; i++) { + if (pids[i] == pid) + pids[i] = -1; + } + + /* When one worker process exits, terminate them all, so that KDC + * crashes behave similarly with or without worker processes. */ + break; + } + + /* Propagate HUP signal to worker processes if we received one. */ + if (sighup_received) { + sighup_received = 0; + for (i = 0; i < num; i++) { + if (pids[i] != -1) + kill(pids[i], SIGHUP); + } + } + } + if (signal_received) + krb5_klog_syslog(LOG_INFO, _("signal %d received in supervisor"), + signal_received); + + terminate_workers(pids, num); + free(pids); + exit(0); +} + +static krb5_error_code +setup_sam(void) +{ + krb5_context ctx = shandle.kdc_err_context; + return krb5_c_make_random_key(ctx, ENCTYPE_DES_CBC_MD5, &psr_key); +} + +static void +usage(char *name) +{ + fprintf(stderr, + _("usage: %s [-x db_args]* [-d dbpathname] [-r dbrealmname]\n" + "\t\t[-R replaycachename] [-m] [-k masterenctype]\n" + "\t\t[-M masterkeyname] [-p port] [-P pid_file]\n" + "\t\t[-n] [-w numworkers] [/]\n\n" + "where,\n" + "\t[-x db_args]* - Any number of database specific arguments.\n" + "\t\t\tLook at each database module documentation for " + "\t\t\tsupported arguments\n"), + name); + exit(1); +} + + +static void +initialize_realms(krb5_context kcontext, int argc, char **argv, + int *tcp_listen_backlog_out) +{ + int c; + char *db_name = (char *) NULL; + char *lrealm = (char *) NULL; + char *mkey_name = (char *) NULL; + krb5_error_code retval; + krb5_enctype menctype = ENCTYPE_UNKNOWN; + kdc_realm_t *rdatap = NULL; + krb5_boolean manual = FALSE; + krb5_boolean def_restrict_anon; + char *def_udp_listen = NULL; + char *def_tcp_listen = NULL; + krb5_pointer aprof = NULL; + const char *hierarchy[3]; + char *no_referral = NULL; + char *hostbased = NULL; + int db_args_size = 0; + char **db_args = NULL; + + extern char *optarg; + + if (!krb5_aprof_init(DEFAULT_KDC_PROFILE, KDC_PROFILE_ENV, &aprof)) { + hierarchy[0] = KRB5_CONF_KDCDEFAULTS; + hierarchy[1] = KRB5_CONF_KDC_LISTEN; + hierarchy[2] = (char *) NULL; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &def_udp_listen)) { + hierarchy[1] = KRB5_CONF_KDC_PORTS; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &def_udp_listen)) + def_udp_listen = NULL; + } + hierarchy[1] = KRB5_CONF_KDC_TCP_LISTEN; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &def_tcp_listen)) { + hierarchy[1] = KRB5_CONF_KDC_TCP_PORTS; + if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &def_tcp_listen)) + def_tcp_listen = NULL; + } + hierarchy[1] = KRB5_CONF_KDC_MAX_DGRAM_REPLY_SIZE; + if (krb5_aprof_get_int32(aprof, hierarchy, TRUE, &max_dgram_reply_size)) + max_dgram_reply_size = MAX_DGRAM_SIZE; + if (tcp_listen_backlog_out != NULL) { + hierarchy[1] = KRB5_CONF_KDC_TCP_LISTEN_BACKLOG; + if (krb5_aprof_get_int32(aprof, hierarchy, TRUE, + tcp_listen_backlog_out)) + *tcp_listen_backlog_out = DEFAULT_TCP_LISTEN_BACKLOG; + } + hierarchy[1] = KRB5_CONF_RESTRICT_ANONYMOUS_TO_TGT; + if (krb5_aprof_get_boolean(aprof, hierarchy, TRUE, &def_restrict_anon)) + def_restrict_anon = FALSE; + hierarchy[1] = KRB5_CONF_NO_HOST_REFERRAL; + if (krb5_aprof_get_string_all(aprof, hierarchy, &no_referral)) + no_referral = 0; + hierarchy[1] = KRB5_CONF_HOST_BASED_SERVICES; + if (krb5_aprof_get_string_all(aprof, hierarchy, &hostbased)) + hostbased = 0; + } + + if (def_udp_listen == NULL) { + def_udp_listen = strdup(DEFAULT_KDC_UDP_PORTLIST); + if (def_udp_listen == NULL) { + fprintf(stderr, _(" KDC cannot initialize. Not enough memory\n")); + exit(1); + } + } + if (def_tcp_listen == NULL) { + def_tcp_listen = strdup(DEFAULT_KDC_TCP_PORTLIST); + if (def_tcp_listen == NULL) { + fprintf(stderr, _(" KDC cannot initialize. Not enough memory\n")); + exit(1); + } + } + + /* + * Loop through the option list. Each time we encounter a realm name, use + * the previously scanned options to fill in for defaults. We do this + * twice if worker processes are used, so we must initialize optind. + */ + optind = 1; + while ((c = getopt(argc, argv, "x:r:d:mM:k:R:e:P:p:s:nw:4:T:X3")) != -1) { + switch(c) { + case 'x': + db_args_size++; + { + char **temp = realloc( db_args, sizeof(char*) * (db_args_size+1)); /* one for NULL */ + if( temp == NULL ) + { + fprintf(stderr, _("%s: KDC cannot initialize. Not enough " + "memory\n"), argv[0]); + exit(1); + } + + db_args = temp; + } + db_args[db_args_size-1] = optarg; + db_args[db_args_size] = NULL; + break; + + case 'r': /* realm name for db */ + if (!find_realm_data(&shandle, optarg, (krb5_ui_4) strlen(optarg))) { + if ((rdatap = (kdc_realm_t *) malloc(sizeof(kdc_realm_t)))) { + retval = init_realm(rdatap, aprof, optarg, mkey_name, + menctype, def_udp_listen, + def_tcp_listen, manual, + def_restrict_anon, db_args, + no_referral, hostbased); + if (retval) { + fprintf(stderr, _("%s: cannot initialize realm %s - " + "see log file for details\n"), + argv[0], optarg); + exit(1); + } + shandle.kdc_realmlist[shandle.kdc_numrealms] = rdatap; + shandle.kdc_numrealms++; + free(db_args), db_args=NULL, db_args_size = 0; + } + else + { + fprintf(stderr, _("%s: cannot initialize realm %s. Not " + "enough memory\n"), argv[0], optarg); + exit(1); + } + } + break; + case 'd': /* pathname for db */ + /* now db_name is not a seperate argument. + * It has to be passed as part of the db_args + */ + if( db_name == NULL ) { + if (asprintf(&db_name, "dbname=%s", optarg) < 0) { + fprintf(stderr, _("%s: KDC cannot initialize. Not enough " + "memory\n"), argv[0]); + exit(1); + } + } + + db_args_size++; + { + char **temp = realloc( db_args, sizeof(char*) * (db_args_size+1)); /* one for NULL */ + if( temp == NULL ) + { + fprintf(stderr, _("%s: KDC cannot initialize. Not enough " + "memory\n"), argv[0]); + exit(1); + } + + db_args = temp; + } + db_args[db_args_size-1] = db_name; + db_args[db_args_size] = NULL; + break; + case 'm': /* manual type-in of master key */ + manual = TRUE; + if (menctype == ENCTYPE_UNKNOWN) + menctype = ENCTYPE_DES_CBC_CRC; + break; + case 'M': /* master key name in DB */ + mkey_name = optarg; + break; + case 'n': + nofork++; /* don't detach from terminal */ + break; + case 'w': /* create multiple worker processes */ + workers = atoi(optarg); + if (workers <= 0) + usage(argv[0]); + break; + case 'k': /* enctype for master key */ + if (krb5_string_to_enctype(optarg, &menctype)) + com_err(argv[0], 0, _("invalid enctype %s"), optarg); + break; + case 'R': + /* Replay cache name; defunct since we don't use a replay cache. */ + break; + case 'P': + pid_file = optarg; + break; + case 'p': + if (def_udp_listen) + free(def_udp_listen); + def_udp_listen = strdup(optarg); + if (!def_udp_listen) { + fprintf(stderr, _(" KDC cannot initialize. Not enough " + "memory\n")); + exit(1); + } +#if 0 /* not yet */ + if (default_tcp_ports) + free(default_tcp_ports); + default_tcp_ports = strdup(optarg); +#endif + break; + case 'T': + time_offset = atoi(optarg); + break; + case '4': + break; + case 'X': + break; + case '?': + default: + usage(argv[0]); + } + } + + /* + * Check to see if we processed any realms. + */ + if (shandle.kdc_numrealms == 0) { + /* no realm specified, use default realm */ + if ((retval = krb5_get_default_realm(kcontext, &lrealm))) { + com_err(argv[0], retval, + _("while attempting to retrieve default realm")); + fprintf (stderr, + _("%s: %s, attempting to retrieve default realm\n"), + argv[0], krb5_get_error_message(kcontext, retval)); + exit(1); + } + if ((rdatap = (kdc_realm_t *) malloc(sizeof(kdc_realm_t)))) { + retval = init_realm(rdatap, aprof, lrealm, mkey_name, menctype, + def_udp_listen, def_tcp_listen, manual, + def_restrict_anon, db_args, no_referral, + hostbased); + if (retval) { + fprintf(stderr, _("%s: cannot initialize realm %s - see log " + "file for details\n"), argv[0], lrealm); + exit(1); + } + shandle.kdc_realmlist[0] = rdatap; + shandle.kdc_numrealms++; + } + krb5_free_default_realm(kcontext, lrealm); + } + + if (def_udp_listen) + free(def_udp_listen); + if (def_tcp_listen) + free(def_tcp_listen); + if (db_args) + free(db_args); + if (db_name) + free(db_name); + if (hostbased) + free(hostbased); + if (no_referral) + free(no_referral); + if (aprof) + krb5_aprof_finish(aprof); + + return; +} + +static krb5_error_code +write_pid_file(const char *path) +{ + FILE *file; + unsigned long pid; + + file = fopen(path, "w"); + if (file == NULL) + return errno; + pid = (unsigned long) getpid(); + if (fprintf(file, "%ld\n", pid) < 0 || fclose(file) == EOF) + return errno; + return 0; +} + +static void +finish_realms() +{ + int i; + + for (i = 0; i < shandle.kdc_numrealms; i++) { + finish_realm(shandle.kdc_realmlist[i]); + shandle.kdc_realmlist[i] = 0; + } + shandle.kdc_numrealms = 0; +} + +/* + outline: + + process args & setup + + initialize database access (fetch master key, open DB) + + initialize network + + loop: + listen for packet + + determine packet type, dispatch to handling routine + (AS or TGS (or V4?)) + + reflect response + + exit on signal + + clean up secrets, close db + + shut down network + + exit +*/ + +int main(int argc, char **argv) +{ + krb5_error_code retval; + krb5_context kcontext; + kdc_realm_t *realm; + verto_ctx *ctx; + int tcp_listen_backlog; + int errout = 0; + int i; + + setlocale(LC_ALL, ""); + if (strrchr(argv[0], '/')) + argv[0] = strrchr(argv[0], '/')+1; + + shandle.kdc_realmlist = malloc(sizeof(kdc_realm_t *) * + KRB5_KDC_MAX_REALMS); + if (shandle.kdc_realmlist == NULL) { + fprintf(stderr, _("%s: cannot get memory for realm list\n"), argv[0]); + exit(1); + } + memset(shandle.kdc_realmlist, 0, + (size_t) (sizeof(kdc_realm_t *) * KRB5_KDC_MAX_REALMS)); + + /* + * A note about Kerberos contexts: This context, "kcontext", is used + * for the KDC operations, i.e. setup, network connection and error + * reporting. The per-realm operations use the "realm_context" + * associated with each realm. + */ + retval = krb5int_init_context_kdc(&kcontext); + if (retval) { + com_err(argv[0], retval, _("while initializing krb5")); + exit(1); + } + krb5_klog_init(kcontext, "kdc", argv[0], 1); + shandle.kdc_err_context = kcontext; + kdc_progname = argv[0]; + /* N.B.: After this point, com_err sends output to the KDC log + file, and not to stderr. We use the kdc_err wrapper around + com_err to ensure that the error state exists in the context + known to the krb5_klog callback. */ + + initialize_kdc5_error_table(); + + /* + * Scan through the argument list + */ + initialize_realms(kcontext, argc, argv, &tcp_listen_backlog); + +#ifndef NOCACHE + retval = kdc_init_lookaside(kcontext); + if (retval) { + kdc_err(kcontext, retval, _("while initializing lookaside cache")); + finish_realms(); + return 1; + } +#endif + + ctx = loop_init(VERTO_EV_TYPE_NONE); + if (!ctx) { + kdc_err(kcontext, ENOMEM, _("while creating main loop")); + finish_realms(); + return 1; + } + + load_preauth_plugins(&shandle, kcontext, ctx); + load_authdata_plugins(kcontext); + + retval = setup_sam(); + if (retval) { + kdc_err(kcontext, retval, _("while initializing SAM")); + finish_realms(); + return 1; + } + + /* Add each realm's listener addresses to the loop. */ + for (i = 0; i < shandle.kdc_numrealms; i++) { + realm = shandle.kdc_realmlist[i]; + if (*realm->realm_listen != '\0') { + retval = loop_add_udp_address(KRB5_DEFAULT_PORT, + realm->realm_listen); + if (retval) + goto net_init_error; + } + if (*realm->realm_tcp_listen != '\0') { + retval = loop_add_tcp_address(KRB5_DEFAULT_PORT, + realm->realm_tcp_listen); + if (retval) + goto net_init_error; + } + } + + if (workers == 0) { + retval = loop_setup_signals(ctx, &shandle, reset_for_hangup); + if (retval) { + kdc_err(kcontext, retval, _("while initializing signal handlers")); + finish_realms(); + return 1; + } + } + if ((retval = loop_setup_network(ctx, &shandle, kdc_progname, + tcp_listen_backlog))) { + net_init_error: + kdc_err(kcontext, retval, _("while initializing network")); + finish_realms(); + return 1; + } + if (!nofork && daemon(0, 0)) { + kdc_err(kcontext, errno, _("while detaching from tty")); + finish_realms(); + return 1; + } + if (pid_file != NULL) { + retval = write_pid_file(pid_file); + if (retval) { + kdc_err(kcontext, retval, _("while creating PID file")); + finish_realms(); + return 1; + } + } + if (workers > 0) { + finish_realms(); + retval = create_workers(ctx, workers); + if (retval) { + kdc_err(kcontext, errno, _("creating worker processes")); + return 1; + } + /* We get here only in a worker child process; re-initialize realms. */ + initialize_realms(kcontext, argc, argv, NULL); + } + + /* Initialize audit system and audit KDC startup. */ + retval = load_audit_modules(kcontext); + if (retval) { + kdc_err(kcontext, retval, _("while loading audit plugin module(s)")); + finish_realms(); + return 1; + } + krb5_klog_syslog(LOG_INFO, _("commencing operation")); + if (nofork) + fprintf(stderr, _("%s: starting...\n"), kdc_progname); + kau_kdc_start(kcontext, TRUE); + + verto_run(ctx); + loop_free(ctx); + kau_kdc_stop(kcontext, TRUE); + krb5_klog_syslog(LOG_INFO, _("shutting down")); + unload_preauth_plugins(kcontext); + unload_authdata_plugins(kcontext); + unload_audit_modules(kcontext); + krb5_klog_close(kcontext); + finish_realms(); + if (shandle.kdc_realmlist) + free(shandle.kdc_realmlist); +#ifndef NOCACHE + kdc_free_lookaside(kcontext); +#endif + krb5_free_context(kcontext); + return errout; +} |
