text: /* -*- 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 <locale.h>
  #include <stdio.h>
  #include <ctype.h>
  #include <sys/file.h>
  #include <signal.h>
  #include <string.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 "k5-int.h"
  #include "com_err.h"
  #include "fake-addrinfo.h"
  #include <errno.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
  
  char *def_realm = NULL;
  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;
  
  char    *progname;
  int     debug = 0;
  int     nodaemon = 0;
  char    *srvtab = 0;
  int     standalone = 0;
  
  pid_t fullprop_child = (pid_t)-1;
  
  krb5_principal  server;         /* This is our server principal name */
  krb5_principal  client;         /* This is who we're talking to */
  krb5_context kpropd_context;
  krb5_auth_context auth_context;
  char    *realm = NULL;          /* Our realm */
  char    *file = KPROPD_DEFAULT_FILE;
  char    *temp_file_name;
  char    *kdb5_util = KPROPD_DEFAULT_KDB5_UTIL;
  char    *kerb_database = NULL;
  char    *acl_file_name = KPROPD_ACL_FILE;
  
  krb5_address    *sender_addr;
  krb5_address    *receiver_addr;
  const char      *port = KPROP_SERVICE;
  
  char **db_args = NULL;
  int db_args_size = 0;
  
  void    PRS(char**);
  void    do_standalone(void);
  void    doit(int);
  krb5_error_code do_iprop(kdb_log_context *log_ctx);
  void    kerberos_authenticate(krb5_context, int, krb5_principal *,
                                krb5_enctype *, struct sockaddr_storage *);
  krb5_boolean authorized_principal(krb5_context, krb5_principal, krb5_enctype);
  void    recv_database(krb5_context, int, int, krb5_data *);
  void    load_database(krb5_context, char *, char *);
  void    send_error(krb5_context, int, krb5_error_code, char *);
  void    recv_error(krb5_context, krb5_data *);
  unsigned int backoff_from_master(int *);
  
  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"));
      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(argc, argv)
      int     argc;
      char    **argv;
  {
      krb5_error_code retval;
      kdb_log_context *log_ctx;
      int devnull, sock;
      struct stat st;
  
      setlocale(LC_ALL, "");
      PRS(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 so tests can use it 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(log_ctx);
          /* 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);
  }
  
  void
  do_standalone()
  {
      struct  sockaddr_in     frominet;
      struct addrinfo *res;
      int     finet, s;
      GETPEERNAME_ARG3_TYPE fromlen;
      int ret, error, val, status;
      pid_t child_pid;
      pid_t wait_pid;
  
      error = get_wildcard_addr(&res);
      if (error != 0) {
          (void) 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
  
      if ((ret = bind(finet, res->ai_addr, res->ai_addrlen)) < 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);
      }
      while (1) {
          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:
              (void) 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 (runonce)
                  exit(0);
          }
      }
      exit(0);
  }
  
  void doit(fd)
      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);
      }
      if ((database_fd = open(temp_file_name,
                              O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 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);
      }
      (void)close(lock_fd);
  
      /*
       * Send the acknowledgement message generated in
       * recv_database, then close the socket.
       */
      retval = krb5_write_message(kpropd_context, (void *) &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(kdb_log_context *log_ctx)
  {
      kadm5_ret_t retval;
      krb5_ccache cc;
      krb5_principal iprop_svc_principal;
      void *server_handle = NULL;
      char *iprop_svc_princstr = NULL;
      char *master_svc_princstr = NULL;
      unsigned int pollin, backoff_time;
      int backoff_cnt = 0;
      int reinit_cnt = 0;
      struct timeval iprop_start, iprop_end;
      unsigned long usec;
      time_t frrequested = 0;
      time_t now;
  
      kdb_incr_result_t *incr_ret;
      kdb_last_t mylast;
  
      kdb_fullresync_result_t *full_ret;
  
      kadm5_iprop_handle_t handle;
      kdb_hlog_t *ulog;
  
      if (debug)
          fprintf(stderr, _("Incremental propagation enabled\n"));
  
      ulog = log_ctx->ulog;
  
      pollin = params.iprop_poll_time;
      if (pollin == 0)
          pollin = 10;
  
      /*
       * Grab the realm info and check if iprop is enabled.
       */
      if (def_realm == NULL) {
          retval = krb5_get_default_realm(kpropd_context, &def_realm);
          if (retval) {
              com_err(progname, retval,
                      _("Unable to get default realm"));
              return retval;
          }
      }
  
      params.mask |= KADM5_CONFIG_REALM;
      params.realm = def_realm;
  
      if (master_svc_princstr == NULL) {
          if ((retval = kadm5_get_kiprop_host_srv_name(kpropd_context,
                                                       def_realm,
                                                       &master_svc_princstr))) {
              com_err(progname, retval,
                      _("%s: unable to get kiprop host based "
                        "service name for realm %s\n"),
                      progname, def_realm);
              return retval;
          }
      }
  
      /*
       * Set cc to the default credentials cache
       */
      if ((retval = krb5_cc_default(kpropd_context, &cc))) {
          com_err(progname, retval,
                  _("while opening default "
                    "credentials cache"));
          return retval;
      }
  
      retval = krb5_sname_to_principal(kpropd_context, NULL, KIPROP_SVC_NAME,
                                       KRB5_NT_SRV_HST, &iprop_svc_principal);
      if (retval) {
          com_err(progname, retval,
                  _("while trying to construct host service principal"));
          return retval;
      }
  
      /* XXX referrals? */
      if (krb5_is_referral_realm(krb5_princ_realm(kpropd_context,
                                                  iprop_svc_principal))) {
          krb5_data *r = krb5_princ_realm(kpropd_context,
                                          iprop_svc_principal);
          assert(def_realm != NULL);
          r->length = strlen(def_realm);
          r->data = strdup(def_realm);
          if (r->data == NULL) {
              com_err(progname, retval,
                      _("while determining local service principal name"));
              return retval;
          }
          /* XXX Memory leak: Old r->data value.  */
      }
      if ((retval = krb5_unparse_name(kpropd_context, iprop_svc_principal,
                                      &iprop_svc_princstr))) {
          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((void *) server_handle);
              server_handle = (void *)NULL;
              handle = (kadm5_iprop_handle_t)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);
              }
              (void) 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
           */
          mylast.last_sno = ulog->kdb_last_sno;
          mylast.last_time = ulog->kdb_last_time;
  
          /*
           * 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()\n"));
          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((void *)server_handle);
              server_handle = (void *)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 == (kdb_fullresync_result_t *)
                      NULL) {
                      clnt_perror(handle->clnt,
                                  _("iprop_full_resync call failed"));
                      if (server_handle)
                          kadm5_destroy((void *)
                                        server_handle);
                      server_handle = (void *)NULL;
                      handle = (kadm5_iprop_handle_t)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 from the master\n"));
              }
              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)
              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);
              }
              (void) sleep(backoff_time);
          } else {
              if (debug) {
                  fprintf(stderr, _("Waiting for %d seconds before checking "
                                    "for updates again\n"), pollin);
              }
              (void) sleep(pollin);
          }
  
      }
  
  
  error:
      if (debug)
          fprintf(stderr, _("ERROR returned by master, bailing\n"));
      syslog(LOG_ERR, _("ERROR returned by master KDC, bailing.\n"));
  done:
      if(iprop_svc_princstr)
          free(iprop_svc_princstr);
      if (master_svc_princstr)
          free(master_svc_princstr);
      if ((retval = krb5_cc_close(kpropd_context, cc))) {
          com_err(progname, retval, _("while closing default ccache"));
          return retval;
      }
      if (def_realm && kpropd_context)
          krb5_free_default_realm(kpropd_context, def_realm);
      if (server_handle)
          kadm5_destroy((void *)server_handle);
      if (kpropd_context)
          krb5_free_context(kpropd_context);
  
      if (runonce == 1)
          return (0);
      else
          return 1;
  }
  
  
  /*
   * Do exponential backoff, since master KDC is BUSY or down
   */
  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);
  }
  
  void PRS(argv)
      char    **argv;
  {
      register char   *word, ch;
      krb5_error_code retval;
      static const char       tmp[] = ".temp";
      kdb_log_context *log_ctx;
  
      (void) 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++)) {
          if (*word == '-') {
              word++;
              while (word && (ch = *word++)) {
                  switch(ch){
                  case 'f':
                      if (*word)
                          file = word;
                      else
                          file = *argv++;
                      if (!file)
                          usage();
                      word = 0;
                      break;
                  case 'F':
                      if (*word)
                          kerb_database = word;
                      else
                          kerb_database = *argv++;
                      if (!kerb_database)
                          usage();
                      word = 0;
                      break;
                  case 'p':
                      if (*word)
                          kdb5_util = word;
                      else
                          kdb5_util = *argv++;
                      if (!kdb5_util)
                          usage();
                      word = 0;
                      break;
                  case 'P':
                      port = (*word != '\0') ? word : *argv++;
                      if (port == NULL)
                          usage();
                      word = 0;
                      break;
                  case 'r':
                      if (*word)
                          realm = word;
                      else
                          realm = *argv++;
                      if (!realm)
                          usage();
                      word = 0;
                      break;
                  case 's':
                      if (*word)
                          srvtab = word;
                      else
                          srvtab = *argv++;
                      if (!srvtab)
                          usage();
                      word = 0;
                      break;
                  case 'D':
                      nodaemon++;
                      break;
                  case 'd':
                      debug++;
                      break;
                  case 'S':
                      /* Standalone mode is now auto-detected; see main(). */
                      break;
                  case 'a':
                      if (*word)
                          acl_file_name = word;
                      else
                          acl_file_name = *argv++;
                      if (!acl_file_name)
                          usage();
                      word = 0;
                      break;
  
                  case 't':
                      /*
                       * Undocumented option - for testing only.
                       *
                       * Option to run the kpropd server exactly
                       * once.
                       */
                      runonce = 1;
                      break;
  
                  case 'x':
                  {
                      char **new_db_args;
                      new_db_args = realloc(db_args,
                                            (db_args_size+2)*sizeof(*db_args));
                      if (new_db_args == NULL) {
                          com_err(argv[0], errno, _("copying db args"));
                          exit(1);
                      }
                      db_args = new_db_args;
                      if (*word)
                          db_args[db_args_size] = word;
                      else
                          db_args[db_args_size] = *argv++;
                      word = 0;
                      if (db_args[db_args_size] == NULL)
                          usage();
                      db_args[db_args_size+1] = NULL;
                      db_args_size++;
                  }
  
                  default:
                      usage();
                  }
  
              }
          } else
              /* We don't take any arguments, only options */
              usage();
      }
      openlog("kpropd", LOG_PID | LOG_ODELAY, SYSLOG_CLASS);
      if (!debug)
          set_com_err_hook(kpropd_com_err_proc);
      /*
       * Get my hostname, so we can construct my service name
       */
      retval = krb5_sname_to_principal(kpropd_context,
                                       NULL, KPROP_SERVICE_NAME,
                                       KRB5_NT_SRV_HST, &server);
      if (retval) {
          com_err(progname, retval,
                  _("while trying to construct my service name"));
          exit(1);
      }
      if (realm) {
          retval = krb5_set_principal_realm(kpropd_context, server, realm);
          if (retval) {
              com_err(progname, errno,
                      _("while constructing my service realm"));
              exit(1);
          }
      }
      /*
       * Construct the name of the temporary file.
       */
      if (asprintf(&temp_file_name, "%s%s", file, tmp) < 0) {
          com_err(progname, ENOMEM,
                  _("while allocating filename for temp file"));
          exit(1);
      }
  
      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, FKPROPD, db_args)) {
              com_err(progname, errno,
                      _("Unable to map log!\n"));
              exit(1);
          }
      }
      log_ctx = kpropd_context->kdblog_context;
      if (log_ctx && (log_ctx->iproprole == IPROP_SLAVE))
          ulog_set_role(kpropd_context, IPROP_SLAVE);
  }
  
  /*
   * Figure out who's calling on the other end of the connection....
   */
  void
  kerberos_authenticate(context, fd, clientp, etype, my_sin)
      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;
  
      /*
       * 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) {
          char *name;
  
          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) {
          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, (void *) &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) {
          char * name;
          char etypebuf[100];
  
          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);
  }
  
  krb5_boolean
  authorized_principal(context, p, auth_etype)
      krb5_context context;
      krb5_principal p;
      krb5_enctype auth_etype;
  {
      char                *name, *ptr;
      char                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)
          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 nul, then
                 the match is only partial.  continue on to new lines. */
              if (*ptr && !isspace((int) *ptr))
                  continue;
  
              /* otherwise, skip trailing whitespace */
              for (; *ptr && 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) &&
                  ((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;
  }
  
  void
  recv_database(context, fd, database_fd, confmsg)
      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, (void *) &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, (void *) &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);
      }
  }
  
  
  void
  send_error(context, fd, err_code, err_text)
      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;
  
      if (err_text)
          text = err_text;
      else
          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, (void *)&fd,&outbuf);
              krb5_free_data_contents(context, &outbuf);
          }
          free(error.text.data);
      }
  }
  
  void
  recv_error(context, inbuf)
      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);
  }
  
  void
  load_database(context, kdb_util, database_file_name)
      krb5_context context;
      char *kdb_util;
      char *database_file_name;
  {
      static char     *edit_av[10];
      int     error_ret;
      int     child_pid;
      int     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;
      char *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);
  }
  