summaryrefslogtreecommitdiff
path: root/src/slave/kpropd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/slave/kpropd.c')
-rw-r--r--src/slave/kpropd.c1614
1 files changed, 1614 insertions, 0 deletions
diff --git a/src/slave/kpropd.c b/src/slave/kpropd.c
new file mode 100644
index 000000000000..056c31a42f2f
--- /dev/null
+++ b/src/slave/kpropd.c
@@ -0,0 +1,1614 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* slave/kpropd.c */
+/*
+ * Copyright (C) 1998 by the FundsXpress, INC.
+ *
+ * All rights reserved.
+ *
+ * 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 FundsXpress. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. FundsXpress makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/*
+ * Copyright 1990,1991,2007 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * 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 "fake-addrinfo.h"
+
+#include <locale.h>
+#include <ctype.h>
+#include <sys/file.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/param.h>
+#include <netdb.h>
+#include <syslog.h>
+
+#include "kprop.h"
+#include <iprop_hdr.h>
+#include "iprop.h"
+#include <kadm5/admin.h>
+#include <kdb_log.h>
+
+#ifndef GETSOCKNAME_ARG3_TYPE
+#define GETSOCKNAME_ARG3_TYPE unsigned int
+#endif
+#ifndef GETPEERNAME_ARG3_TYPE
+#define GETPEERNAME_ARG3_TYPE unsigned int
+#endif
+
+#if defined(NEED_DAEMON_PROTO)
+extern int daemon(int, int);
+#endif
+
+#define SYSLOG_CLASS LOG_DAEMON
+
+int runonce = 0;
+
+/*
+ * This struct simulates the use of _kadm5_server_handle_t
+ *
+ * This is a COPY of kadm5_server_handle_t from
+ * lib/kadm5/clnt/client_internal.h!
+ */
+typedef struct _kadm5_iprop_handle_t {
+ krb5_ui_4 magic_number;
+ krb5_ui_4 struct_version;
+ krb5_ui_4 api_version;
+ char *cache_name;
+ int destroy_cache;
+ CLIENT *clnt;
+ krb5_context context;
+ kadm5_config_params params;
+ struct _kadm5_iprop_handle_t *lhandle;
+} *kadm5_iprop_handle_t;
+
+static char *kprop_version = KPROP_PROT_VERSION;
+
+static kadm5_config_params params;
+
+static char *progname;
+static int debug = 0;
+static int nodaemon = 0;
+static char *srvtab = NULL;
+static int standalone = 0;
+
+static pid_t fullprop_child = (pid_t)-1;
+
+static krb5_principal server; /* This is our server principal name */
+static krb5_principal client; /* This is who we're talking to */
+static krb5_context kpropd_context;
+static krb5_auth_context auth_context;
+static char *realm = NULL; /* Our realm */
+static char *def_realm = NULL; /* Ref pointer for default realm */
+static char *file = KPROPD_DEFAULT_FILE;
+static char *temp_file_name;
+static char *kdb5_util = KPROPD_DEFAULT_KDB5_UTIL;
+static char *kerb_database = NULL;
+static char *acl_file_name = KPROPD_ACL_FILE;
+
+static krb5_address *sender_addr;
+static krb5_address *receiver_addr;
+static const char *port = KPROP_SERVICE;
+
+static char **db_args = NULL;
+static int db_args_size = 0;
+
+static void parse_args(char **argv);
+static void do_standalone(void);
+static void doit(int fd);
+static krb5_error_code do_iprop(void);
+static void kerberos_authenticate(krb5_context context, int fd,
+ krb5_principal *clientp, krb5_enctype *etype,
+ struct sockaddr_storage *my_sin);
+static krb5_boolean authorized_principal(krb5_context context,
+ krb5_principal p,
+ krb5_enctype auth_etype);
+static void recv_database(krb5_context context, int fd, int database_fd,
+ krb5_data *confmsg);
+static void load_database(krb5_context context, char *kdb_util,
+ char *database_file_name);
+static void send_error(krb5_context context, int fd, krb5_error_code err_code,
+ char *err_text);
+static void recv_error(krb5_context context, krb5_data *inbuf);
+static unsigned int backoff_from_master(int *cnt);
+static kadm5_ret_t kadm5_get_kiprop_host_srv_name(krb5_context context,
+ const char *realm_name,
+ char **host_service_name);
+
+static void
+usage()
+{
+ fprintf(stderr,
+ _("\nUsage: %s [-r realm] [-s srvtab] [-dS] [-f slave_file]\n"),
+ progname);
+ fprintf(stderr, _("\t[-F kerberos_db_file ] [-p kdb5_util_pathname]\n"));
+ fprintf(stderr, _("\t[-x db_args]* [-P port] [-a acl_file]\n"));
+ fprintf(stderr, _("\t[-A admin_server]\n"));
+ exit(1);
+}
+
+typedef void (*sig_handler_fn)(int sig);
+
+static void
+signal_wrapper(int sig, sig_handler_fn handler)
+{
+#ifdef POSIX_SIGNALS
+ struct sigaction s_action;
+
+ memset(&s_action, 0, sizeof(s_action));
+ sigemptyset(&s_action.sa_mask);
+ s_action.sa_handler = handler;
+ sigaction(sig, &s_action, NULL);
+#else
+ signal(sig, handler);
+#endif
+}
+
+static void
+alarm_handler(int sig)
+{
+ static char *timeout_msg = "Full propagation timed out\n";
+
+ write(STDERR_FILENO, timeout_msg, strlen(timeout_msg));
+ exit(1);
+}
+
+static void
+usr1_handler(int sig)
+{
+ /* Nothing to do, just let the signal interrupt sleep(). */
+}
+
+static void
+kill_do_standalone(int sig)
+{
+ if (fullprop_child > 0) {
+ if (debug) {
+ fprintf(stderr, _("Killing fullprop child (%d)\n"),
+ (int)fullprop_child);
+ }
+ kill(fullprop_child, sig);
+ }
+ /* Make sure our exit status code reflects our having been signaled */
+ signal_wrapper(sig, SIG_DFL);
+ kill(getpid(), sig);
+}
+
+static void
+atexit_kill_do_standalone(void)
+{
+ if (fullprop_child > 0)
+ kill(fullprop_child, SIGHUP);
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code retval;
+ kdb_log_context *log_ctx;
+ int devnull, sock;
+ struct stat st;
+
+ setlocale(LC_ALL, "");
+ parse_args(argv);
+
+ if (fstat(0, &st) == -1) {
+ com_err(progname, errno, _("while checking if stdin is a socket"));
+ exit(1);
+ }
+ /*
+ * Detect whether we're running from inetd; if not then we're in
+ * standalone mode.
+ */
+ standalone = !S_ISSOCK(st.st_mode);
+
+ log_ctx = kpropd_context->kdblog_context;
+
+ signal_wrapper(SIGPIPE, SIG_IGN);
+
+ if (standalone) {
+ /* "ready" is a sentinel for the test framework. */
+ if (!debug && !nodaemon) {
+ daemon(0, 0);
+ } else {
+ printf(_("ready\n"));
+ fflush(stdout);
+ }
+ } else {
+ /*
+ * We're an inetd nowait service. Let's not risk anything
+ * read/write from/to the inetd socket unintentionally.
+ */
+ devnull = open("/dev/null", O_RDWR);
+ if (devnull == -1) {
+ syslog(LOG_ERR, _("Could not open /dev/null: %s"),
+ strerror(errno));
+ exit(1);
+ }
+
+ sock = dup(0);
+ if (sock == -1) {
+ syslog(LOG_ERR, _("Could not dup the inetd socket: %s"),
+ strerror(errno));
+ exit(1);
+ }
+
+ dup2(devnull, STDIN_FILENO);
+ dup2(devnull, STDOUT_FILENO);
+ dup2(devnull, STDERR_FILENO);
+ close(devnull);
+ doit(sock);
+ exit(0);
+ }
+
+ if (log_ctx == NULL || log_ctx->iproprole != IPROP_SLAVE) {
+ do_standalone();
+ /* do_standalone() should never return */
+ assert(0);
+ }
+
+ /*
+ * This is the iprop case. We'll fork a child to run do_standalone(). The
+ * parent will run do_iprop(). We try to kill the child if we get killed.
+ * Catch SIGUSR1, which can be used to interrupt the sleep timer and force
+ * an iprop request.
+ */
+ signal_wrapper(SIGHUP, kill_do_standalone);
+ signal_wrapper(SIGINT, kill_do_standalone);
+ signal_wrapper(SIGQUIT, kill_do_standalone);
+ signal_wrapper(SIGTERM, kill_do_standalone);
+ signal_wrapper(SIGSEGV, kill_do_standalone);
+ signal_wrapper(SIGUSR1, usr1_handler);
+ atexit(atexit_kill_do_standalone);
+ fullprop_child = fork();
+ switch (fullprop_child) {
+ case -1:
+ com_err(progname, errno, _("do_iprop failed.\n"));
+ break;
+ case 0:
+ do_standalone();
+ /* do_standalone() should never return */
+ /* NOTREACHED */
+ break;
+ default:
+ retval = do_iprop();
+ /* do_iprop() can return due to failures and runonce. */
+ kill(fullprop_child, SIGHUP);
+ wait(NULL);
+ if (retval)
+ com_err(progname, retval, _("do_iprop failed.\n"));
+ else
+ exit(0);
+ }
+
+ exit(1);
+}
+
+/* Use getaddrinfo to determine a wildcard listener address, preferring
+ * IPv6 if available. */
+static int
+get_wildcard_addr(struct addrinfo **res)
+{
+ struct addrinfo hints;
+ int error;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+ hints.ai_family = AF_INET6;
+ error = getaddrinfo(NULL, port, &hints, res);
+ if (error == 0)
+ return 0;
+ hints.ai_family = AF_INET;
+ return getaddrinfo(NULL, port, &hints, res);
+}
+
+static void
+do_standalone()
+{
+ struct sockaddr_in frominet;
+ struct addrinfo *res;
+ GETPEERNAME_ARG3_TYPE fromlen;
+ int finet, s, ret, error, val, status;
+ pid_t child_pid;
+ pid_t wait_pid;
+
+ error = get_wildcard_addr(&res);
+ if (error != 0) {
+ fprintf(stderr, _("getaddrinfo: %s\n"), gai_strerror(error));
+ exit(1);
+ }
+
+ finet = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (finet < 0) {
+ com_err(progname, errno, _("while obtaining socket"));
+ exit(1);
+ }
+
+ val = 1;
+ if (setsockopt(finet, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0)
+ com_err(progname, errno, _("while setting SO_REUSEADDR option"));
+
+#if defined(IPV6_V6ONLY)
+ /* Make sure dual-stack support is enabled on IPv6 listener sockets if
+ * possible. */
+ val = 0;
+ if (res->ai_family == AF_INET6 &&
+ setsockopt(finet, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) < 0)
+ com_err(progname, errno, _("while unsetting IPV6_V6ONLY option"));
+#endif
+
+ ret = bind(finet, res->ai_addr, res->ai_addrlen);
+ if (ret < 0) {
+ com_err(progname, errno, _("while binding listener socket"));
+ exit(1);
+ }
+ if (listen(finet, 5) < 0) {
+ com_err(progname, errno, "in listen call");
+ exit(1);
+ }
+ for (;;) {
+ memset(&frominet, 0, sizeof(frominet));
+ fromlen = sizeof(frominet);
+ if (debug)
+ fprintf(stderr, _("waiting for a kprop connection\n"));
+ s = accept(finet, (struct sockaddr *) &frominet, &fromlen);
+
+ if (s < 0) {
+ int e = errno;
+ if (e != EINTR) {
+ com_err(progname, e, _("while accepting connection"));
+ }
+ }
+ child_pid = fork();
+ switch (child_pid) {
+ case -1:
+ com_err(progname, errno, _("while forking"));
+ exit(1);
+ case 0:
+ close(finet);
+
+ doit(s);
+ close(s);
+ _exit(0);
+ default:
+ do {
+ wait_pid = waitpid(child_pid, &status, 0);
+ } while (wait_pid == -1 && errno == EINTR);
+ if (wait_pid == -1) {
+ /* Something bad happened; panic. */
+ if (debug) {
+ fprintf(stderr, _("waitpid() failed to wait for doit() "
+ "(%d %s)\n"), errno, strerror(errno));
+ }
+ com_err(progname, errno,
+ _("while waiting to receive database"));
+ exit(1);
+ }
+ if (debug) {
+ fprintf(stderr, _("Database load process for full propagation "
+ "completed.\n"));
+ }
+
+ close(s);
+
+ /* If we are the fullprop child in iprop mode, notify the parent
+ * process that it should poll for incremental updates. */
+ if (fullprop_child == 0)
+ kill(getppid(), SIGUSR1);
+ else if (runonce)
+ exit(0);
+ }
+ }
+ exit(0);
+}
+
+static void
+doit(int fd)
+{
+ struct sockaddr_storage from;
+ int on = 1;
+ GETPEERNAME_ARG3_TYPE fromlen;
+ krb5_error_code retval;
+ krb5_data confmsg;
+ int lock_fd;
+ mode_t omask;
+ krb5_enctype etype;
+ int database_fd;
+ char host[INET6_ADDRSTRLEN + 1];
+
+ signal_wrapper(SIGALRM, alarm_handler);
+ alarm(params.iprop_resync_timeout);
+ fromlen = sizeof(from);
+ if (getpeername(fd, (struct sockaddr *)&from, &fromlen) < 0) {
+#ifdef ENOTSOCK
+ if (errno == ENOTSOCK && fd == 0 && !standalone) {
+ fprintf(stderr,
+ _("%s: Standard input does not appear to be a network "
+ "socket.\n"
+ "\t(Not run from inetd, and missing the -S option?)\n"),
+ progname);
+ exit(1);
+ }
+#endif
+ fprintf(stderr, "%s: ", progname);
+ perror("getpeername");
+ exit(1);
+ }
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (caddr_t) &on,
+ sizeof(on)) < 0) {
+ com_err(progname, errno,
+ _("while attempting setsockopt (SO_KEEPALIVE)"));
+ }
+
+ if (getnameinfo((const struct sockaddr *) &from, fromlen,
+ host, sizeof(host), NULL, 0, 0) == 0) {
+ syslog(LOG_INFO, _("Connection from %s"), host);
+ if (debug)
+ fprintf(stderr, "Connection from %s\n", host);
+ }
+
+ /*
+ * Now do the authentication
+ */
+ kerberos_authenticate(kpropd_context, fd, &client, &etype, &from);
+
+ if (!authorized_principal(kpropd_context, client, etype)) {
+ char *name;
+
+ retval = krb5_unparse_name(kpropd_context, client, &name);
+ if (retval) {
+ com_err(progname, retval, "While unparsing client name");
+ exit(1);
+ }
+ if (debug) {
+ fprintf(stderr,
+ _("Rejected connection from unauthorized principal %s\n"),
+ name);
+ }
+ syslog(LOG_WARNING,
+ _("Rejected connection from unauthorized principal %s"),
+ name);
+ free(name);
+ exit(1);
+ }
+ omask = umask(077);
+ lock_fd = open(temp_file_name, O_RDWR | O_CREAT, 0600);
+ (void)umask(omask);
+ retval = krb5_lock_file(kpropd_context, lock_fd,
+ KRB5_LOCKMODE_EXCLUSIVE | KRB5_LOCKMODE_DONTBLOCK);
+ if (retval) {
+ com_err(progname, retval, _("while trying to lock '%s'"),
+ temp_file_name);
+ exit(1);
+ }
+ database_fd = open(temp_file_name, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (database_fd < 0) {
+ com_err(progname, errno, _("while opening database file, '%s'"),
+ temp_file_name);
+ exit(1);
+ }
+ recv_database(kpropd_context, fd, database_fd, &confmsg);
+ if (rename(temp_file_name, file)) {
+ com_err(progname, errno, _("while renaming %s to %s"),
+ temp_file_name, file);
+ exit(1);
+ }
+ retval = krb5_lock_file(kpropd_context, lock_fd, KRB5_LOCKMODE_SHARED);
+ if (retval) {
+ com_err(progname, retval, _("while downgrading lock on '%s'"),
+ temp_file_name);
+ exit(1);
+ }
+ load_database(kpropd_context, kdb5_util, file);
+ retval = krb5_lock_file(kpropd_context, lock_fd, KRB5_LOCKMODE_UNLOCK);
+ if (retval) {
+ com_err(progname, retval, _("while unlocking '%s'"), temp_file_name);
+ exit(1);
+ }
+ close(lock_fd);
+
+ /*
+ * Send the acknowledgement message generated in
+ * recv_database, then close the socket.
+ */
+ retval = krb5_write_message(kpropd_context, &fd, &confmsg);
+ if (retval) {
+ krb5_free_data_contents(kpropd_context, &confmsg);
+ com_err(progname, retval, _("while sending # of received bytes"));
+ exit(1);
+ }
+ krb5_free_data_contents(kpropd_context, &confmsg);
+ if (close(fd) < 0) {
+ com_err(progname, errno,
+ _("while trying to close database file"));
+ exit(1);
+ }
+
+ exit(0);
+}
+
+/* Default timeout can be changed using clnt_control() */
+static struct timeval full_resync_timeout = { 25, 0 };
+
+static kdb_fullresync_result_t *
+full_resync(CLIENT *clnt)
+{
+ static kdb_fullresync_result_t clnt_res;
+ uint32_t vers = IPROPX_VERSION_1; /* max version we support */
+ enum clnt_stat status;
+
+ memset(&clnt_res, 0, sizeof(clnt_res));
+
+ status = clnt_call(clnt, IPROP_FULL_RESYNC_EXT, (xdrproc_t)xdr_u_int32,
+ (caddr_t)&vers, (xdrproc_t)xdr_kdb_fullresync_result_t,
+ (caddr_t)&clnt_res, full_resync_timeout);
+ if (status == RPC_PROCUNAVAIL) {
+ status = clnt_call(clnt, IPROP_FULL_RESYNC, (xdrproc_t)xdr_void,
+ (caddr_t *)&vers,
+ (xdrproc_t)xdr_kdb_fullresync_result_t,
+ (caddr_t)&clnt_res, full_resync_timeout);
+ }
+
+ return (status == RPC_SUCCESS) ? &clnt_res : NULL;
+}
+
+/*
+ * Beg for incrementals from the KDC.
+ *
+ * Returns 0 on success IFF runonce is true.
+ * Returns non-zero on failure due to errors.
+ */
+krb5_error_code
+do_iprop()
+{
+ kadm5_ret_t retval;
+ krb5_principal iprop_svc_principal;
+ void *server_handle = NULL;
+ char *iprop_svc_princstr = NULL, *master_svc_princstr = NULL;
+ unsigned int pollin, backoff_time;
+ int backoff_cnt = 0, reinit_cnt = 0;
+ struct timeval iprop_start, iprop_end;
+ unsigned long usec;
+ time_t frrequested = 0, now;
+ kdb_incr_result_t *incr_ret;
+ kdb_last_t mylast;
+ kdb_fullresync_result_t *full_ret;
+ kadm5_iprop_handle_t handle;
+
+ if (debug)
+ fprintf(stderr, _("Incremental propagation enabled\n"));
+
+ pollin = params.iprop_poll_time;
+ if (pollin == 0)
+ pollin = 10;
+
+ if (master_svc_princstr == NULL) {
+ retval = kadm5_get_kiprop_host_srv_name(kpropd_context, realm,
+ &master_svc_princstr);
+ if (retval) {
+ com_err(progname, retval,
+ _("%s: unable to get kiprop host based "
+ "service name for realm %s\n"),
+ progname, realm);
+ return retval;
+ }
+ }
+
+ retval = sn2princ_realm(kpropd_context, NULL, KIPROP_SVC_NAME, realm,
+ &iprop_svc_principal);
+ if (retval) {
+ com_err(progname, retval,
+ _("while trying to construct host service principal"));
+ return retval;
+ }
+
+ retval = krb5_unparse_name(kpropd_context, iprop_svc_principal,
+ &iprop_svc_princstr);
+ if (retval) {
+ com_err(progname, retval,
+ _("while canonicalizing principal name"));
+ krb5_free_principal(kpropd_context, iprop_svc_principal);
+ return retval;
+ }
+ krb5_free_principal(kpropd_context, iprop_svc_principal);
+
+reinit:
+ /*
+ * Authentication, initialize rpcsec_gss handle etc.
+ */
+ if (debug) {
+ fprintf(stderr, _("Initializing kadm5 as client %s\n"),
+ iprop_svc_princstr);
+ }
+ retval = kadm5_init_with_skey(kpropd_context, iprop_svc_princstr,
+ srvtab,
+ master_svc_princstr,
+ &params,
+ KADM5_STRUCT_VERSION,
+ KADM5_API_VERSION_4,
+ db_args,
+ &server_handle);
+
+ if (retval) {
+ if (debug)
+ fprintf(stderr, _("kadm5 initialization failed!\n"));
+ if (retval == KADM5_RPC_ERROR) {
+ reinit_cnt++;
+ if (server_handle)
+ kadm5_destroy(server_handle);
+ server_handle = NULL;
+ handle = NULL;
+
+ com_err(progname, retval, _(
+ "while attempting to connect"
+ " to master KDC ... retrying"));
+ backoff_time = backoff_from_master(&reinit_cnt);
+ if (debug) {
+ fprintf(stderr, _("Sleeping %d seconds to re-initialize "
+ "kadm5 (RPC ERROR)\n"), backoff_time);
+ }
+ sleep(backoff_time);
+ goto reinit;
+ } else {
+ if (retval == KADM5_BAD_CLIENT_PARAMS ||
+ retval == KADM5_BAD_SERVER_PARAMS) {
+ com_err(progname, retval,
+ _("while initializing %s interface"),
+ progname);
+
+ usage();
+ }
+ reinit_cnt++;
+ com_err(progname, retval,
+ _("while initializing %s interface, retrying"),
+ progname);
+ backoff_time = backoff_from_master(&reinit_cnt);
+ if (debug) {
+ fprintf(stderr, _("Sleeping %d seconds to re-initialize "
+ "kadm5 (krb5kdc not running?)\n"),
+ backoff_time);
+ }
+ sleep(backoff_time);
+ goto reinit;
+ }
+ }
+
+ if (debug)
+ fprintf(stderr, _("kadm5 initialization succeeded\n"));
+
+ /*
+ * Reset re-initialization count to zero now.
+ */
+ reinit_cnt = backoff_time = 0;
+
+ /*
+ * Reset the handle to the correct type for the RPC call
+ */
+ handle = server_handle;
+
+ for (;;) {
+ incr_ret = NULL;
+ full_ret = NULL;
+
+ /*
+ * Get the most recent ulog entry sno + ts, which
+ * we package in the request to the master KDC
+ */
+ retval = ulog_get_last(kpropd_context, &mylast);
+ if (retval) {
+ com_err(progname, retval, _("reading update log header"));
+ goto done;
+ }
+
+ /*
+ * Loop continuously on an iprop_get_updates_1(),
+ * so that we can keep probing the master for updates
+ * or (if needed) do a full resync of the krb5 db.
+ */
+
+ if (debug) {
+ fprintf(stderr, _("Calling iprop_get_updates_1 "
+ "(sno=%u sec=%u usec=%u)\n"),
+ (unsigned int)mylast.last_sno,
+ (unsigned int)mylast.last_time.seconds,
+ (unsigned int)mylast.last_time.useconds);
+ }
+ gettimeofday(&iprop_start, NULL);
+ incr_ret = iprop_get_updates_1(&mylast, handle->clnt);
+ if (incr_ret == (kdb_incr_result_t *)NULL) {
+ clnt_perror(handle->clnt,
+ _("iprop_get_updates call failed"));
+ if (server_handle)
+ kadm5_destroy(server_handle);
+ server_handle = NULL;
+ handle = (kadm5_iprop_handle_t)NULL;
+ if (debug) {
+ fprintf(stderr, _("Reinitializing iprop because get updates "
+ "failed\n"));
+ }
+ goto reinit;
+ }
+
+ switch (incr_ret->ret) {
+
+ case UPDATE_FULL_RESYNC_NEEDED:
+ /*
+ * If we're already asked for a full resync and we still
+ * need one and the last one hasn't timed out then just keep
+ * asking for updates as eventually the resync will finish
+ * (or, if it times out we'll just try again). Note that
+ * doit() also applies a timeout to the full resync, thus
+ * it's OK for us to do the same here.
+ */
+ now = time(NULL);
+ if (frrequested &&
+ (now - frrequested) < params.iprop_resync_timeout) {
+ if (debug)
+ fprintf(stderr, _("Still waiting for full resync\n"));
+ break;
+ } else {
+ frrequested = now;
+ if (debug)
+ fprintf(stderr, _("Full resync needed\n"));
+ syslog(LOG_INFO, _("kpropd: Full resync needed."));
+
+ full_ret = full_resync(handle->clnt);
+ if (full_ret == NULL) {
+ clnt_perror(handle->clnt,
+ _("iprop_full_resync call failed"));
+ kadm5_destroy(server_handle);
+ server_handle = NULL;
+ handle = NULL;
+ goto reinit;
+ }
+ }
+
+ switch (full_ret->ret) {
+ case UPDATE_OK:
+ if (debug)
+ fprintf(stderr, _("Full resync request granted\n"));
+ syslog(LOG_INFO, _("Full resync request granted."));
+ backoff_cnt = 0;
+ break;
+
+ case UPDATE_BUSY:
+ /*
+ * Exponential backoff
+ */
+ if (debug)
+ fprintf(stderr, _("Exponential backoff\n"));
+ backoff_cnt++;
+ break;
+
+ case UPDATE_PERM_DENIED:
+ if (debug)
+ fprintf(stderr, _("Full resync permission denied\n"));
+ syslog(LOG_ERR, _("Full resync, permission denied."));
+ goto error;
+
+ case UPDATE_ERROR:
+ if (debug)
+ fprintf(stderr, _("Full resync error from master\n"));
+ syslog(LOG_ERR, _(" Full resync, "
+ "error returned from master KDC."));
+ goto error;
+
+ default:
+ backoff_cnt = 0;
+ if (debug) {
+ fprintf(stderr,
+ _("Full resync invalid result from master\n"));
+ }
+ syslog(LOG_ERR, _("Full resync, "
+ "invalid return from master KDC."));
+ break;
+ }
+ break;
+
+ case UPDATE_OK:
+ backoff_cnt = 0;
+ frrequested = 0;
+
+ /*
+ * ulog_replay() will convert the ulog updates to db
+ * entries using the kdb conv api and will commit
+ * the entries to the slave kdc database
+ */
+ if (debug) {
+ fprintf(stderr, _("Got incremental updates "
+ "(sno=%u sec=%u usec=%u)\n"),
+ (unsigned int)incr_ret->lastentry.last_sno,
+ (unsigned int)incr_ret->lastentry.last_time.seconds,
+ (unsigned int)incr_ret->lastentry.last_time.useconds);
+ }
+ retval = ulog_replay(kpropd_context, incr_ret, db_args);
+
+ if (retval) {
+ const char *msg =
+ krb5_get_error_message(kpropd_context, retval);
+ if (debug) {
+ fprintf(stderr, _("ulog_replay failed (%s), updates not "
+ "registered\n"), msg);
+ }
+ syslog(LOG_ERR, _("ulog_replay failed (%s), updates "
+ "not registered."), msg);
+ krb5_free_error_message(kpropd_context, msg);
+ break;
+ }
+
+ gettimeofday(&iprop_end, NULL);
+ usec = (iprop_end.tv_sec - iprop_start.tv_sec) * 1000000 +
+ iprop_end.tv_usec - iprop_start.tv_usec;
+ syslog(LOG_INFO, _("Incremental updates: %d updates / %lu us"),
+ incr_ret->updates.kdb_ulog_t_len, usec);
+ if (debug) {
+ fprintf(stderr, _("Incremental updates: %d updates / "
+ "%lu us\n"),
+ incr_ret->updates.kdb_ulog_t_len, usec);
+ }
+ break;
+
+ case UPDATE_PERM_DENIED:
+ if (debug)
+ fprintf(stderr, _("get_updates permission denied\n"));
+ syslog(LOG_ERR, _("get_updates, permission denied."));
+ goto error;
+
+ case UPDATE_ERROR:
+ if (debug)
+ fprintf(stderr, _("get_updates error from master\n"));
+ syslog(LOG_ERR, _("get_updates, error returned from master KDC."));
+ goto error;
+
+ case UPDATE_BUSY:
+ /*
+ * Exponential backoff
+ */
+ if (debug)
+ fprintf(stderr, _("get_updates master busy; backoff\n"));
+ backoff_cnt++;
+ break;
+
+ case UPDATE_NIL:
+ /*
+ * Master-slave are in sync
+ */
+ if (debug)
+ fprintf(stderr, _("KDC is synchronized with master.\n"));
+ backoff_cnt = 0;
+ frrequested = 0;
+ break;
+
+ default:
+ backoff_cnt = 0;
+ if (debug)
+ fprintf(stderr, _("get_updates invalid result from master\n"));
+ syslog(LOG_ERR, _("get_updates, invalid return from master KDC."));
+ break;
+ }
+
+ if (runonce == 1 && incr_ret->ret != UPDATE_FULL_RESYNC_NEEDED)
+ goto done;
+
+ /*
+ * Sleep for the specified poll interval (Default is 2 mts),
+ * or do a binary exponential backoff if we get an
+ * UPDATE_BUSY signal
+ */
+ if (backoff_cnt > 0) {
+ backoff_time = backoff_from_master(&backoff_cnt);
+ if (debug) {
+ fprintf(stderr, _("Busy signal received "
+ "from master, backoff for %d secs\n"),
+ backoff_time);
+ }
+ sleep(backoff_time);
+ } else {
+ if (debug) {
+ fprintf(stderr, _("Waiting for %d seconds before checking "
+ "for updates again\n"), pollin);
+ }
+ sleep(pollin);
+ }
+
+ }
+
+
+error:
+ if (debug)
+ fprintf(stderr, _("ERROR returned by master, bailing\n"));
+ syslog(LOG_ERR, _("ERROR returned by master KDC, bailing.\n"));
+done:
+ free(iprop_svc_princstr);
+ free(master_svc_princstr);
+ krb5_free_default_realm(kpropd_context, def_realm);
+ kadm5_destroy(server_handle);
+ krb5_db_fini(kpropd_context);
+ ulog_fini(kpropd_context);
+ krb5_free_context(kpropd_context);
+
+ return (runonce == 1) ? 0 : 1;
+}
+
+
+/* Do exponential backoff, since master KDC is BUSY or down. */
+static unsigned int
+backoff_from_master(int *cnt)
+{
+ unsigned int btime;
+
+ btime = (unsigned int)(2<<(*cnt));
+ if (btime > MAX_BACKOFF) {
+ btime = MAX_BACKOFF;
+ (*cnt)--;
+ }
+
+ return btime;
+}
+
+static void
+kpropd_com_err_proc(const char *whoami, long code, const char *fmt,
+ va_list args)
+#if !defined(__cplusplus) && (__GNUC__ > 2)
+ __attribute__((__format__(__printf__, 3, 0)))
+#endif
+ ;
+
+static void
+kpropd_com_err_proc(const char *whoami, long code, const char *fmt,
+ va_list args)
+{
+ char error_buf[8096];
+
+ error_buf[0] = '\0';
+ if (fmt)
+ vsnprintf(error_buf, sizeof(error_buf), fmt, args);
+ syslog(LOG_ERR, "%s%s%s%s%s", whoami ? whoami : "", whoami ? ": " : "",
+ code ? error_message(code) : "", code ? " " : "", error_buf);
+}
+
+static void
+parse_args(char **argv)
+{
+ char **newargs, *word, ch;
+ krb5_error_code retval;
+
+ memset(&params, 0, sizeof(params));
+
+ /* Since we may modify the KDB with ulog_replay(), we must read the KDC
+ * profile. */
+ retval = krb5int_init_context_kdc(&kpropd_context);
+ if (retval) {
+ com_err(argv[0], retval, _("while initializing krb5"));
+ exit(1);
+ }
+
+ progname = *argv++;
+ while ((word = *argv++) != NULL) {
+ /* We don't take any arguments, only options */
+ if (*word != '-')
+ usage();
+
+ word++;
+ while (word != NULL && (ch = *word++) != '\0') {
+ switch (ch) {
+ case 'A':
+ params.mask |= KADM5_CONFIG_ADMIN_SERVER;
+ params.admin_server = (*word != '\0') ? word : *argv++;
+ if (params.admin_server == NULL)
+ usage();
+ word = NULL;
+ break;
+ case 'f':
+ file = (*word != '\0') ? word : *argv++;
+ if (file == NULL)
+ usage();
+ word = NULL;
+ break;
+ case 'F':
+ kerb_database = (*word != '\0') ? word : *argv++;
+ if (kerb_database == NULL)
+ usage();
+ word = NULL;
+ break;
+ case 'p':
+ kdb5_util = (*word != '\0') ? word : *argv++;
+ if (kdb5_util == NULL)
+ usage();
+ word = NULL;
+ break;
+ case 'P':
+ port = (*word != '\0') ? word : *argv++;
+ if (port == NULL)
+ usage();
+ word = NULL;
+ break;
+ case 'r':
+ realm = (*word != '\0') ? word : *argv++;
+ if (realm == NULL)
+ usage();
+ word = NULL;
+ break;
+ case 's':
+ srvtab = (*word != '\0') ? word : *argv++;
+ if (srvtab == NULL)
+ usage();
+ word = NULL;
+ break;
+ case 'D':
+ nodaemon++;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'S':
+ /* Standalone mode is now auto-detected; see main(). */
+ break;
+ case 'a':
+ acl_file_name = (*word != '\0') ? word : *argv++;
+ if (acl_file_name == NULL)
+ usage();
+ word = NULL;
+ break;
+
+ case 't':
+ /* Undocumented option - for testing only. Run the kpropd
+ * server exactly once. */
+ runonce = 1;
+ break;
+
+ case 'x':
+ newargs = realloc(db_args,
+ (db_args_size + 2) * sizeof(*db_args));
+ if (newargs == NULL) {
+ com_err(argv[0], errno, _("copying db args"));
+ exit(1);
+ }
+ db_args = newargs;
+ db_args[db_args_size] = (*word != '\0') ? word : *argv++;
+ if (db_args[db_args_size] == NULL)
+ usage();
+ word = NULL;
+ db_args[db_args_size + 1] = NULL;
+ db_args_size++;
+ break;
+
+ default:
+ usage();
+ }
+ }
+ }
+
+ openlog("kpropd", LOG_PID | LOG_ODELAY, SYSLOG_CLASS);
+ if (!debug)
+ set_com_err_hook(kpropd_com_err_proc);
+
+ if (realm == NULL) {
+ retval = krb5_get_default_realm(kpropd_context, &def_realm);
+ if (retval) {
+ com_err(progname, retval, _("Unable to get default realm"));
+ exit(1);
+ }
+ realm = def_realm;
+ } else {
+ retval = krb5_set_default_realm(kpropd_context, realm);
+ if (retval) {
+ com_err(progname, retval, _("Unable to set default realm"));
+ exit(1);
+ }
+ }
+
+ /* Construct service name from local hostname. */
+ retval = sn2princ_realm(kpropd_context, NULL, KPROP_SERVICE_NAME, realm,
+ &server);
+ if (retval) {
+ com_err(progname, retval,
+ _("while trying to construct my service name"));
+ exit(1);
+ }
+
+ /* Construct the name of the temporary file. */
+ if (asprintf(&temp_file_name, "%s.temp", file) < 0) {
+ com_err(progname, ENOMEM,
+ _("while allocating filename for temp file"));
+ exit(1);
+ }
+
+ params.realm = realm;
+ params.mask |= KADM5_CONFIG_REALM;
+ retval = kadm5_get_config_params(kpropd_context, 1, &params, &params);
+ if (retval) {
+ com_err(progname, retval, _("while initializing"));
+ exit(1);
+ }
+ if (params.iprop_enabled == TRUE) {
+ ulog_set_role(kpropd_context, IPROP_SLAVE);
+
+ if (ulog_map(kpropd_context, params.iprop_logfile,
+ params.iprop_ulogsize)) {
+ com_err(progname, errno, _("Unable to map log!\n"));
+ exit(1);
+ }
+ }
+}
+
+/*
+ * Figure out who's calling on the other end of the connection....
+ */
+static void
+kerberos_authenticate(krb5_context context, int fd, krb5_principal *clientp,
+ krb5_enctype *etype, struct sockaddr_storage *my_sin)
+{
+ krb5_error_code retval;
+ krb5_ticket *ticket;
+ struct sockaddr_storage r_sin;
+ GETSOCKNAME_ARG3_TYPE sin_length;
+ krb5_keytab keytab = NULL;
+ char *name, etypebuf[100];
+
+ /* Set recv_addr and send_addr. */
+ sockaddr2krbaddr(context, my_sin->ss_family, (struct sockaddr *)my_sin,
+ &sender_addr);
+
+ sin_length = sizeof(r_sin);
+ if (getsockname(fd, (struct sockaddr *)&r_sin, &sin_length)) {
+ com_err(progname, errno, _("while getting local socket address"));
+ exit(1);
+ }
+
+ sockaddr2krbaddr(context, r_sin.ss_family, (struct sockaddr *)&r_sin,
+ &receiver_addr);
+
+ if (debug) {
+ retval = krb5_unparse_name(context, server, &name);
+ if (retval) {
+ com_err(progname, retval, _("while unparsing client name"));
+ exit(1);
+ }
+ fprintf(stderr, "krb5_recvauth(%d, %s, %s, ...)\n", fd, kprop_version,
+ name);
+ free(name);
+ }
+
+ retval = krb5_auth_con_init(context, &auth_context);
+ if (retval) {
+ syslog(LOG_ERR, _("Error in krb5_auth_con_ini: %s"),
+ error_message(retval));
+ exit(1);
+ }
+
+ retval = krb5_auth_con_setflags(context, auth_context,
+ KRB5_AUTH_CONTEXT_DO_SEQUENCE);
+ if (retval) {
+ syslog(LOG_ERR, _("Error in krb5_auth_con_setflags: %s"),
+ error_message(retval));
+ exit(1);
+ }
+
+ retval = krb5_auth_con_setaddrs(context, auth_context, receiver_addr,
+ sender_addr);
+ if (retval) {
+ syslog(LOG_ERR, _("Error in krb5_auth_con_setaddrs: %s"),
+ error_message(retval));
+ exit(1);
+ }
+
+ if (srvtab != NULL) {
+ retval = krb5_kt_resolve(context, srvtab, &keytab);
+ if (retval) {
+ syslog(LOG_ERR, _("Error in krb5_kt_resolve: %s"),
+ error_message(retval));
+ exit(1);
+ }
+ }
+
+ retval = krb5_recvauth(context, &auth_context, &fd, kprop_version, server,
+ 0, keytab, &ticket);
+ if (retval) {
+ syslog(LOG_ERR, _("Error in krb5_recvauth: %s"),
+ error_message(retval));
+ exit(1);
+ }
+
+ retval = krb5_copy_principal(context, ticket->enc_part2->client, clientp);
+ if (retval) {
+ syslog(LOG_ERR, _("Error in krb5_copy_prinicpal: %s"),
+ error_message(retval));
+ exit(1);
+ }
+
+ *etype = ticket->enc_part.enctype;
+
+ if (debug) {
+ retval = krb5_unparse_name(context, *clientp, &name);
+ if (retval) {
+ com_err(progname, retval, _("while unparsing client name"));
+ exit(1);
+ }
+
+ retval = krb5_enctype_to_string(*etype, etypebuf, sizeof(etypebuf));
+ if (retval) {
+ com_err(progname, retval, _("while unparsing ticket etype"));
+ exit(1);
+ }
+
+ fprintf(stderr, _("authenticated client: %s (etype == %s)\n"),
+ name, etypebuf);
+ free(name);
+ }
+
+ krb5_free_ticket(context, ticket);
+}
+
+static krb5_boolean
+authorized_principal(krb5_context context, krb5_principal p,
+ krb5_enctype auth_etype)
+{
+ char *name, *ptr, buf[1024];
+ krb5_error_code retval;
+ FILE *acl_file;
+ int end;
+ krb5_enctype acl_etype;
+
+ retval = krb5_unparse_name(context, p, &name);
+ if (retval)
+ return FALSE;
+
+ acl_file = fopen(acl_file_name, "r");
+ if (acl_file == NULL)
+ return FALSE;
+
+ while (!feof(acl_file)) {
+ if (!fgets(buf, sizeof(buf), acl_file))
+ break;
+ end = strlen(buf) - 1;
+ if (buf[end] == '\n')
+ buf[end] = '\0';
+ if (!strncmp(name, buf, strlen(name))) {
+ ptr = buf + strlen(name);
+
+ /* If the next character is not whitespace or null, then the match
+ * is only partial. Continue on to new lines. */
+ if (*ptr != '\0' && !isspace((int)*ptr))
+ continue;
+
+ /* Otherwise, skip trailing whitespace. */
+ for (; *ptr != '\0' && isspace((int)*ptr); ptr++) ;
+
+ /*
+ * Now, look for an etype string. If there isn't one, return true.
+ * If there is an invalid string, continue. If there is a valid
+ * string, return true only if it matches the etype passed in,
+ * otherwise continue.
+ */
+ if (*ptr != '\0' &&
+ ((retval = krb5_string_to_enctype(ptr, &acl_etype)) ||
+ (acl_etype != auth_etype)))
+ continue;
+
+ free(name);
+ fclose(acl_file);
+ return TRUE;
+ }
+ }
+ free(name);
+ fclose(acl_file);
+ return FALSE;
+}
+
+static void
+recv_database(krb5_context context, int fd, int database_fd,
+ krb5_data *confmsg)
+{
+ krb5_ui_4 database_size, received_size;
+ int n;
+ char buf[1024];
+ krb5_data inbuf, outbuf;
+ krb5_error_code retval;
+
+ /* Receive and decode size from client. */
+ retval = krb5_read_message(context, &fd, &inbuf);
+ if (retval) {
+ send_error(context, fd, retval, "while reading database size");
+ com_err(progname, retval,
+ _("while reading size of database from client"));
+ exit(1);
+ }
+ if (krb5_is_krb_error(&inbuf))
+ recv_error(context, &inbuf);
+ retval = krb5_rd_safe(context,auth_context,&inbuf,&outbuf,NULL);
+ if (retval) {
+ send_error(context, fd, retval, "while decoding database size");
+ krb5_free_data_contents(context, &inbuf);
+ com_err(progname, retval,
+ _("while decoding database size from client"));
+ exit(1);
+ }
+ memcpy(&database_size, outbuf.data, sizeof(database_size));
+ krb5_free_data_contents(context, &inbuf);
+ krb5_free_data_contents(context, &outbuf);
+ database_size = ntohl(database_size);
+
+ /* Initialize the initial vector. */
+ retval = krb5_auth_con_initivector(context, auth_context);
+ if (retval) {
+ send_error(context, fd, retval,
+ "failed while initializing i_vector");
+ com_err(progname, retval, _("while initializing i_vector"));
+ exit(1);
+ }
+
+ if (debug)
+ fprintf(stderr, _("Full propagation transfer started.\n"));
+
+ /* Now start receiving the database from the net. */
+ received_size = 0;
+ while (received_size < database_size) {
+ retval = krb5_read_message(context, &fd, &inbuf);
+ if (retval) {
+ snprintf(buf, sizeof(buf),
+ "while reading database block starting at offset %d",
+ received_size);
+ com_err(progname, retval, "%s", buf);
+ send_error(context, fd, retval, buf);
+ exit(1);
+ }
+ if (krb5_is_krb_error(&inbuf))
+ recv_error(context, &inbuf);
+ retval = krb5_rd_priv(context, auth_context, &inbuf, &outbuf, NULL);
+ if (retval) {
+ snprintf(buf, sizeof(buf),
+ "while decoding database block starting at offset %d",
+ received_size);
+ com_err(progname, retval, "%s", buf);
+ send_error(context, fd, retval, buf);
+ krb5_free_data_contents(context, &inbuf);
+ exit(1);
+ }
+ n = write(database_fd, outbuf.data, outbuf.length);
+ krb5_free_data_contents(context, &inbuf);
+ krb5_free_data_contents(context, &outbuf);
+ if (n < 0) {
+ snprintf(buf, sizeof(buf),
+ "while writing database block starting at offset %d",
+ received_size);
+ send_error(context, fd, errno, buf);
+ } else if ((unsigned int)n != outbuf.length) {
+ snprintf(buf, sizeof(buf),
+ "incomplete write while writing database block starting "
+ "at \noffset %d (%d written, %d expected)",
+ received_size, n, outbuf.length);
+ send_error(context, fd, KRB5KRB_ERR_GENERIC, buf);
+ }
+ received_size += outbuf.length;
+ }
+
+ /* OK, we've seen the entire file. Did we get too many bytes? */
+ if (received_size > database_size) {
+ snprintf(buf, sizeof(buf),
+ "Received %d bytes, expected %d bytes for database file",
+ received_size, database_size);
+ send_error(context, fd, KRB5KRB_ERR_GENERIC, buf);
+ }
+
+ if (debug)
+ fprintf(stderr, _("Full propagation transfer finished.\n"));
+
+ /* Create message acknowledging number of bytes received, but
+ * don't send it until kdb5_util returns successfully. */
+ database_size = htonl(database_size);
+ inbuf.data = (char *)&database_size;
+ inbuf.length = sizeof(database_size);
+ retval = krb5_mk_safe(context,auth_context,&inbuf,confmsg,NULL);
+ if (retval) {
+ com_err(progname, retval, "while encoding # of receieved bytes");
+ send_error(context, fd, retval, "while encoding # of received bytes");
+ exit(1);
+ }
+}
+
+
+static void
+send_error(krb5_context context, int fd, krb5_error_code err_code,
+ char *err_text)
+{
+ krb5_error error;
+ const char *text;
+ krb5_data outbuf;
+ char buf[1024];
+
+ memset(&error, 0, sizeof(error));
+ krb5_us_timeofday(context, &error.stime, &error.susec);
+ error.server = server;
+ error.client = client;
+
+ text = (err_text != NULL) ? err_text : error_message(err_code);
+
+ error.error = err_code - ERROR_TABLE_BASE_krb5;
+ if (error.error > 127) {
+ error.error = KRB_ERR_GENERIC;
+ if (err_text) {
+ snprintf(buf, sizeof(buf), "%s %s", error_message(err_code),
+ err_text);
+ text = buf;
+ }
+ }
+ error.text.length = strlen(text) + 1;
+ error.text.data = strdup(text);
+ if (error.text.data) {
+ if (!krb5_mk_error(context, &error, &outbuf)) {
+ (void)krb5_write_message(context, &fd, &outbuf);
+ krb5_free_data_contents(context, &outbuf);
+ }
+ free(error.text.data);
+ }
+}
+
+void
+recv_error(krb5_context context, krb5_data *inbuf)
+{
+ krb5_error *error;
+ krb5_error_code retval;
+
+ retval = krb5_rd_error(context, inbuf, &error);
+ if (retval) {
+ com_err(progname, retval,
+ _("while decoding error packet from client"));
+ exit(1);
+ }
+ if (error->error == KRB_ERR_GENERIC) {
+ if (error->text.data)
+ fprintf(stderr, _("Generic remote error: %s\n"), error->text.data);
+ } else if (error->error) {
+ com_err(progname,
+ (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5,
+ _("signaled from server"));
+ if (error->text.data) {
+ fprintf(stderr, _("Error text from client: %s\n"),
+ error->text.data);
+ }
+ }
+ krb5_free_error(context, error);
+ exit(1);
+}
+
+static void
+load_database(krb5_context context, char *kdb_util, char *database_file_name)
+{
+ static char *edit_av[10];
+ int error_ret, child_pid, count;
+
+ /* <sys/param.h> has been included, so BSD will be defined on
+ * BSD systems. */
+#if BSD > 0 && BSD <= 43
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(w) (w).w_retcode
+#endif
+ union wait waitb;
+#else
+ int waitb;
+#endif
+ kdb_log_context *log_ctx;
+
+ if (debug)
+ fprintf(stderr, "calling kdb5_util to load database\n");
+
+ log_ctx = context->kdblog_context;
+
+ edit_av[0] = kdb_util;
+ count = 1;
+ if (realm) {
+ edit_av[count++] = "-r";
+ edit_av[count++] = realm;
+ }
+ edit_av[count++] = "load";
+ if (kerb_database) {
+ edit_av[count++] = "-d";
+ edit_av[count++] = kerb_database;
+ }
+ if (log_ctx && log_ctx->iproprole == IPROP_SLAVE)
+ edit_av[count++] = "-i";
+ edit_av[count++] = database_file_name;
+ edit_av[count++] = NULL;
+
+ switch (child_pid = fork()) {
+ case -1:
+ com_err(progname, errno, _("while trying to fork %s"), kdb_util);
+ exit(1);
+ case 0:
+ execv(kdb_util, edit_av);
+ com_err(progname, errno, _("while trying to exec %s"), kdb_util);
+ _exit(1);
+ /*NOTREACHED*/
+ default:
+ if (debug)
+ fprintf(stderr, "Load PID is %d\n", child_pid);
+ if (wait(&waitb) < 0) {
+ com_err(progname, errno, _("while waiting for %s"), kdb_util);
+ exit(1);
+ }
+ }
+
+ if (!WIFEXITED(waitb)) {
+ com_err(progname, 0, _("%s load terminated"), kdb_util);
+ exit(1);
+ }
+
+ error_ret = WEXITSTATUS(waitb);
+ if (error_ret) {
+ com_err(progname, 0, _("%s returned a bad exit status (%d)"),
+ kdb_util, error_ret);
+ exit(1);
+ }
+ return;
+}
+
+/*
+ * Get the host base service name for the kiprop principal. Returns
+ * KADM5_OK on success. Caller must free the storage allocated
+ * for host_service_name.
+ */
+static kadm5_ret_t
+kadm5_get_kiprop_host_srv_name(krb5_context context, const char *realm_name,
+ char **host_service_name)
+{
+ char *name, *host;
+
+ host = params.admin_server; /* XXX */
+ if (asprintf(&name, "%s/%s", KADM5_KIPROP_HOST_SERVICE, host) < 0) {
+ free(host);
+ return ENOMEM;
+ }
+ *host_service_name = name;
+
+ return KADM5_OK;
+}