/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * 
 * 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.
 */


/*
 * slave/kpropd.c
 *
 * Copyright 1990,1991 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.
 * 
 *
 * XXX We need to modify the protocol so that an acknowledge is set
 * after each block, instead after the entire series is sent over.
 * The reason for this is so that error packets can get interpreted
 * right away.  If you don't do this, the sender may never get the
 * error packet, because it will die an EPIPE trying to complete the
 * write...
 */


#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 <libintl.h>
#include <locale.h>
#include <k5-int.h>
#include <socket-utils.h>
#include "com_err.h"
#include <errno.h>

#include "kprop.h"
#include <iprop_hdr.h>
#include "iprop.h"
#include <kadm5/admin.h>
#include <kdb/kdb_log.h>

/* Solaris Kerberos */
#include <libgen.h>

#define SYSLOG_CLASS LOG_DAEMON

char *poll_time = NULL;
char *def_realm = NULL;
boolean_t runonce = B_FALSE;

/*
 * This struct simulates the use of _kadm5_server_handle_t
 */
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;

char	*progname;
int     debug = 0;
char	*srvtab = 0;
int	standalone = 0;

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;
short 		port = 0;

void	PRS
	 (int, char**);
int	do_standalone
	 (iprop_role iproprole);
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 *);
int	convert_polltime
	(char *);
unsigned int	backoff_from_master
	(int *);

static void usage()
{
	fprintf(stderr,
		gettext("\nUsage: %s\n"), /* progname may be a long pathname */
		progname);

	fprintf(stderr,
		gettext("\t[-r realm] [-s srvtab] [-dS] [-f slave_file]\n"));

	fprintf(stderr,
		gettext("\t[-F kerberos_db_file ] [-p kdb5_util_pathname]\n"));
		
	fprintf(stderr, gettext("\t[-P port] [-a acl_file]\n"));

	exit(1);
}

int
main(argc, argv)
	int	argc;
	char	**argv;
{
	krb5_error_code retval;
	int ret = 0;
	kdb_log_context	*log_ctx;
	int iprop_supported;
	krb5_boolean is_master = FALSE;

	PRS(argc, argv);

	log_ctx = kpropd_context->kdblog_context;

	if (log_ctx && (log_ctx->iproprole == IPROP_SLAVE)) {
		/*
		 * We wanna do iprop !
		 */
		retval = krb5_db_supports_iprop(kpropd_context,
		    &iprop_supported);
		if (retval) {
			/* Solaris Kerberos: Keep error messages consistent */
			com_err(progname, retval,
				gettext("while determining if dbmodule plugin "
					    "supports iprop"));
			exit(1);
		}
		if (!iprop_supported) {
			/* Solaris Kerberos: Keep error messages consistent */
			com_err(progname, 0,
				gettext("Current dbmodule plugin does not support "
				    "iprop"));
			exit(1);
		}

		/*
		 * Solaris Kerberos:
		 * Ensure that kpropd is only run on a slave
		 */
		if (retval = kadm5_is_master(kpropd_context, def_realm,
		    &is_master)) {
			com_err(progname, retval,
			    gettext("while trying to determine whether host is "
			    "master KDC for realm %s"), def_realm);
			exit(1);
		}

		if (is_master == TRUE) {
			char *master = NULL;
			kadm5_get_master(kpropd_context, def_realm, &master);

			com_err(progname, 0,
			    gettext("%s is the master KDC for the realm %s. "
			    "%s can only be run on a slave KDC"),
			    master ? master : "unknown", def_realm, progname);
			exit(1);
		}

		retval = do_iprop(log_ctx);
		if (retval) {
			/* Solaris Kerberos: Keep error messages consistent */
			com_err(progname, retval,
			    gettext("while doing iprop"));
			exit(1);
		}

	} else {

		/*
		 * Solaris Kerberos:
		 * Ensure that the kpropd.acl file exists and contains at least
		 * 1 entry.
		 */
		FILE *tmp_acl_file;
		int seen_file = 0;
		char buf[1024];

		tmp_acl_file = fopen(acl_file_name, "r");
		if (!tmp_acl_file) {
			com_err(progname, errno,
			    gettext("while opening acl file %s"),
			    acl_file_name);
			exit(1);
		}

		while (!feof(tmp_acl_file) && !seen_file ) {
			if (!fgets(buf, sizeof(buf), tmp_acl_file))
				break;

			if (buf[0] != '#' && !isspace(buf[0]))
				seen_file = 1;
		}
		if (!seen_file) {
			com_err(progname, 0,
			    gettext("No entries found in %s. Can't "
			    "authorize propagation requests"), acl_file_name);
			exit(1);
		}
		fclose(tmp_acl_file);

		if (standalone)
			ret = do_standalone(IPROP_NULL);
		else
			doit(0);
	}

	exit(ret);
}

int do_standalone(iprop_role iproprole)
{
    struct	linger linger;
    struct	servent *sp;
    int	finet, fromlen, s;
    int	on = 1;
    int	ret, status = 0;
    struct	sockaddr_in6 sin6 = { AF_INET6 };
    int sin6_size = sizeof (sin6);

    /* listen for either ipv4 or ipv6 */
    finet = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
    if (finet < 0 ) {
	com_err(progname, errno, gettext("while obtaining socket"));
	exit(1);
    } 

    if(!port) {
	sp = getservbyname(KPROP_SERVICE, "tcp");
	if (sp == NULL) {
	    com_err(progname, 0, gettext("%s/tcp: unknown service"),
		    KPROP_SERVICE);
	    exit(1);
	}
	sin6.sin6_port = sp->s_port;
    } else
	sin6.sin6_port = port;

    /*
     * We need to close the socket immediately if iprop is enabled,
     * since back-to-back full resyncs are possible, so we do not
     * linger around for too long
     */
    if (iproprole == IPROP_SLAVE) {
	    if (setsockopt(finet, SOL_SOCKET, SO_REUSEADDR,
			(char *)&on, sizeof(on)) < 0)
		    com_err(progname, errno,
			    gettext("while setting socket option (SO_REUSEADDR)"));
	    linger.l_onoff = 1;
	    linger.l_linger = 2;
	    if (setsockopt(finet, SOL_SOCKET, SO_LINGER,
			(void *)&linger, sizeof(linger)) < 0)
		    com_err(progname, errno,
			    gettext("while setting socket option (SO_LINGER)"));
    }
    if ((ret = bind(finet, (struct sockaddr *)&sin6, sizeof(sin6))) < 0) {
	if (debug) {
	    on = 1;
	    fprintf(stderr,
		    gettext("%s: attempting to rebind socket "
		    "with SO_REUSEADDR\n"), progname);
	    if (setsockopt(finet, SOL_SOCKET, SO_REUSEADDR,
			(char *)&on, sizeof(on)) < 0) {
		com_err(progname, errno, 
			gettext("while setting socket option (SO_REUSEADDR)"));
	    }
	    ret = bind(finet, (struct sockaddr *) &sin6, sizeof(sin6));
	    }

	    if (ret < 0) {
	/*
	 * Solaris Kerberos:
	 * com_err will print the err msg associated with errno
	 */
#if 0
		perror(gettext("bind"));
#endif
		com_err(progname, errno, 
		    gettext("while binding listener socket"));
		exit(1);
	    }
	}
	if (!debug && (iproprole != IPROP_SLAVE)) {
	/* Solaris Kerberos: Indicate where further messages will be sent */
		fprintf(stderr,
		    gettext("%s: Logging to SYSLOG with LOG_DAEMON facility\n"),
		    progname);
		if (daemon(1, 0)) {
			com_err(progname, errno, gettext("while daemonizing"));
			exit(1);
		}
		rem_default_com_err_hook();
	}

#ifdef PID_FILE
	if ((pidfile = fopen(PID_FILE, "w")) != NULL) {
		fprintf(pidfile, gettext("%d\n"), getpid());
		fclose(pidfile);
	} else
		com_err(progname, errno,
		gettext("while opening pid file %s for writing"),
		PID_FILE);
#endif
	if (listen(finet, 5) < 0) {
		/* Solaris Kerberos: Keep error messages consistent */
		com_err(progname, errno, gettext("while listening on socket"));
		exit(1);
	}
	while (1) {
		int child_pid;

		s = accept(finet, (struct sockaddr *) &sin6, &sin6_size);

		if (s < 0) {
			if (errno != EINTR) {
				/* Solaris Kerberos: Keep error messages consistent */
				com_err(progname, errno,
		    gettext("while accepting connection")); 
			}
			continue;
		}
		if (debug && (iproprole != IPROP_SLAVE))
			child_pid = 0;
		else
			child_pid = fork();
		switch (child_pid) {
		case -1:
			com_err(progname, errno, gettext("while forking"));
			exit(1);
	    /*NOTREACHED*/
		case 0:
	    /* child */
			(void) close(finet);

			doit(s);
			close(s);
			_exit(0);
	    /*NOTREACHED*/
		default:
	    /* parent */
	    if (wait(&status) < 0) {
		com_err(progname, errno,
		    gettext("while waiting to receive database"));
		exit(1);
	    }

	    close(s);
	    if (iproprole == IPROP_SLAVE)
		close(finet);

	    if ((ret = WEXITSTATUS(status)) != 0)
		return (ret);
	}

	if (iproprole == IPROP_SLAVE)
	    break;
    }

    return (0);
}

void doit(fd)
	int	fd;
{
	struct sockaddr_storage from;
	socklen_t fromlen;
	int on = 1;
	struct hostent	*hp;
	krb5_error_code	retval;
	krb5_data confmsg;
	int lock_fd;
	mode_t omask;
	krb5_enctype etype;
	int database_fd;
	char ntop[NI_MAXHOST] = "";
	krb5_context doit_context;
	kdb_log_context *log_ctx;

	retval = krb5_init_context(&doit_context);
	if (retval) {
		com_err(progname, retval, gettext("while initializing krb5"));
		exit(1);
	}
	log_ctx = kpropd_context->kdblog_context;
	if (log_ctx && (log_ctx->iproprole == IPROP_SLAVE))
		ulog_set_role(doit_context, IPROP_SLAVE);

	fromlen = (socklen_t)sizeof (from);
	if (getpeername(fd, (struct sockaddr *) &from, &fromlen) < 0) {
		fprintf(stderr, "%s: ", progname);
		perror(gettext("getpeername"));
		exit(1);
	}
	if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (caddr_t) &on,
		       sizeof (on)) < 0) {
		com_err(progname, errno,
		gettext("while attempting setsockopt (SO_KEEPALIVE)"));
	}

	if (getnameinfo((struct sockaddr *)&from, fromlen, ntop, sizeof(ntop),
		NULL, 0, NI_NUMERICHOST) != 0) {

		/* getnameifo failed so use inet_ntop() to get printable addresses */
		if (from.ss_family == AF_INET) {

			inet_ntop(AF_INET,
			    (const void *)&ss2sin(&from)->sin_addr,
			    ntop, sizeof(ntop));

		} else if (from.ss_family == AF_INET6 &&
			! IN6_IS_ADDR_V4MAPPED(&ss2sin6(&from)->sin6_addr)) {

			ipaddr_t v4addr;

			inet_ntop(AF_INET6,
				(const void *)&ss2sin6(&from)->sin6_addr, ntop,
				sizeof(ntop));
		}
		/* ipv4 mapped ipv6 addrs handled later */
	}

	if (from.ss_family == AF_INET || from.ss_family == AF_INET6) {

		if (from.ss_family == AF_INET6 && 
			IN6_IS_ADDR_V4MAPPED(&ss2sin6(&from)->sin6_addr)) {

			ipaddr_t v4addr;

			/* coerce ipv4 mapped ipv6 addr to normal ipv4 addr */
			IN6_V4MAPPED_TO_IPADDR(&(ss2sin6(&from)->sin6_addr),
				v4addr);

			inet_ntop(AF_INET, (const void *) &v4addr,
				ntop, sizeof(ntop));
		}

		syslog(LOG_INFO, gettext("Connection from %s"), ntop);

		if (debug)
			printf("Connection from %s\n", ntop);

	} else {
		/* address family isn't either AF_INET || AF_INET6 */
		syslog(LOG_INFO, 
		    gettext("Connection from unknown address family:%d"),
		    from.ss_family);

		if (debug) {
			printf(gettext("Connection from unknown address family:%d"),
			    from.ss_family);
		}
	}

	/*
	 * Now do the authentication
	 */
	kerberos_authenticate(doit_context, fd, &client, &etype, from);

	if (!authorized_principal(doit_context, client, etype)) {
		char	*name;

		retval = krb5_unparse_name(doit_context, client, &name);
		if (retval) {
			/* Solaris Kerberos: Keep error messages consistent */
			com_err(progname, retval,
		    gettext("while unparsing client name"));
			exit(1);
		}
		syslog(LOG_WARNING,
		gettext("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(doit_context, lock_fd, 
				KRB5_LOCKMODE_EXCLUSIVE|KRB5_LOCKMODE_DONTBLOCK);
	if (retval) {
	    com_err(progname, retval, 
			gettext("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,
			gettext("while opening database file, '%s'"),
			temp_file_name);
		exit(1);
	}
	recv_database(doit_context, fd, database_fd, &confmsg);
	if (rename(temp_file_name, file)) {
		/* Solaris Kerberos: Keep error messages consistent */
		com_err(progname, errno, 
			gettext("while renaming %s to %s"),
			temp_file_name, file);
		exit(1);
	}
	retval = krb5_lock_file(doit_context, lock_fd, KRB5_LOCKMODE_SHARED);
	if (retval) {
	    com_err(progname, retval,
			gettext("while downgrading lock on '%s'"),
		    temp_file_name);
	    exit(1);
	}
	load_database(doit_context, kdb5_util, file);
	retval = krb5_lock_file(doit_context, lock_fd, KRB5_LOCKMODE_UNLOCK);
	if (retval) {
	    com_err(progname, retval, 
		gettext("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(doit_context, (void *) &fd, &confmsg);
	if (retval) { 
		krb5_free_data_contents(doit_context, &confmsg);
		com_err(progname, retval,
			gettext("while sending # of received bytes"));
		exit(1);
	}
	krb5_free_data_contents(doit_context, &confmsg);
	if (close(fd) < 0) {
		com_err(progname, errno,
			gettext("while trying to close database file"));
		exit(1);
	}
	
	exit(0);
}


/*
 * Routine to handle incremental update transfer(s) from master KDC
 */
krb5_error_code do_iprop(kdb_log_context *log_ctx) {
	CLIENT *cl;
	kadm5_ret_t retval;
	kadm5_config_params params;
	krb5_ccache cc;
	krb5_principal iprop_svc_principal;
	void *server_handle = NULL;
	char *iprop_svc_princstr = NULL;
	char *master_svc_princstr = NULL;
	char *admin_server = NULL;
	char *keytab_name = NULL;
	unsigned int pollin, backoff_time;
	int backoff_cnt = 0;
	int reinit_cnt = 0;
	int ret;
	boolean_t frdone = B_FALSE;

	kdb_incr_result_t *incr_ret;
	static kdb_last_t mylast;

	kdb_fullresync_result_t *full_ret;
	char *full_resync_arg = NULL;

	kadm5_iprop_handle_t handle;
	kdb_hlog_t *ulog;

	krb5_keytab kt;
	krb5_keytab_entry entry;
	char kt_name[MAX_KEYTAB_NAME_LEN];

	/*
	 * Solaris Kerberos:
	 * Delay daemonizing until some basic configuration checks have been
	 * performed
	 */
#if 0
	if (!debug)
		daemon(0, 0);
#endif
	pollin = (unsigned int)0;
	(void) memset((char *)&params, 0, sizeof (params));
	ulog = log_ctx->ulog;

	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)) {
			/* Solaris Kerberos: keep error messages consistent */
			com_err(progname, retval,
				gettext("while getting kiprop host based "
					"service name for realm %s"), def_realm);
			exit(1);
		}
	}

	/*
	 * Set cc to the default credentials cache
	 */
	if (retval = krb5_cc_default(kpropd_context, &cc)) {
		com_err(progname, retval,
			gettext("while opening default "
				"credentials cache"));
		exit(1);
	}

	retval = krb5_sname_to_principal(kpropd_context, NULL, KIPROP_SVC_NAME,
				KRB5_NT_SRV_HST, &iprop_svc_principal);
	if (retval) {
		com_err(progname, retval, gettext("while trying to construct "
						"host service principal"));
		exit(1);
	}

	/* Solaris Kerberos */
	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"));
			exit(1);
		}
	}

	if (retval = krb5_unparse_name(kpropd_context, iprop_svc_principal,
				&iprop_svc_princstr)) {
		com_err(progname, retval,
			gettext("while canonicalizing "
				"principal name"));
		krb5_free_principal(kpropd_context, iprop_svc_principal);
		exit(1);
	}

	/*
	 * Solaris Kerberos:
	 * Check to see if kiprop/<fqdn>@REALM is in the keytab
	 */
	kt_name[0] = '\0';
	if (retval = krb5_kt_default_name(kpropd_context, kt_name,
	    MAX_KEYTAB_NAME_LEN)){
		com_err(progname, retval, gettext ("while resolving the "
		    "name of the default keytab"));
	}

	if (retval = krb5_kt_default(kpropd_context, &kt)) {
		com_err(progname, retval, gettext ("while resolving default "
		    "keytab"));
		krb5_free_principal(kpropd_context, iprop_svc_principal);
		exit(1);
	} 

	if (retval = krb5_kt_get_entry(kpropd_context, kt, iprop_svc_principal,
	    0, 0, &entry)) {
		com_err(progname, retval, gettext("while retrieving entry %s "
		    "from %s"), iprop_svc_princstr,
		    kt_name[0] ? kt_name : "default keytab");
		krb5_kt_close(kpropd_context,kt);
		krb5_free_principal(kpropd_context, iprop_svc_principal);
		exit(1);
	}

	krb5_kt_close(kpropd_context,kt);
	krb5_free_principal(kpropd_context, iprop_svc_principal);

	if (!debug) {
	/* Solaris Kerberos: Indicate where further messages will be sent */
		fprintf(stderr, gettext("%s: Logging to SYSLOG\n"), progname);
		if (daemon(0, 0)) {
			com_err(progname, errno, gettext("while daemonizing"));
			exit(1);
		}
		rem_default_com_err_hook();
	}

reinit:
	/*
	 * Authentication, initialize rpcsec_gss handle etc.
	 */
	retval = kadm5_init_with_skey(iprop_svc_princstr, keytab_name,
				    master_svc_princstr,
				    &params,
				    KADM5_STRUCT_VERSION,
				    KADM5_API_VERSION_2,
				    NULL,
 				    &server_handle);

	if (retval) {
		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, gettext(
					"while attempting to connect"
					" to master KDC ... retrying"));
			backoff_time = backoff_from_master(&reinit_cnt);
			(void) sleep(backoff_time);
			goto reinit;
		} else {
			/* Solaris Kerberos: Be more verbose */
			com_err(progname, retval,
                                gettext("while initializing %s interface for "
				    "%s"), progname, iprop_svc_princstr);
			if (retval == KADM5_BAD_CLIENT_PARAMS ||
			    retval == KADM5_BAD_SERVER_PARAMS)
				usage();
			exit(1);
                }
	}

	/*
	 * 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;
	
	/*
	 * If we have reached this far, we have succesfully established
	 * a RPCSEC_GSS connection; we now start polling for updates
	 */
	if (poll_time == NULL) {
		if ((poll_time = (char *)strdup("2m")) == NULL) {
			/* Solaris Kerberos: Keep error messages consistent */
			com_err(progname, ENOMEM,
				gettext("while allocating poll_time"));
			exit(1);
		}
	}

	if (pollin == (unsigned int)0)
		pollin = convert_polltime(poll_time);

	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.
		 */

		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;
			goto reinit;
		}

		switch (incr_ret->ret) {

		case UPDATE_FULL_RESYNC_NEEDED:
			/*
			 * We dont do a full resync again, if the last
			 * X'fer was a resync and if the master sno is
			 * still "0", i.e. no updates so far.
			 */
			if ((frdone == B_TRUE) && (incr_ret->lastentry.last_sno
						== 0)) {
				break;
			} else {

				full_ret = iprop_full_resync_1((void *)
						&full_resync_arg, 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:
				backoff_cnt = 0;
				/* 
				 * We now listen on the kprop port for
				 * the full dump
				 */
				ret = do_standalone(log_ctx->iproprole);
				if (ret)
					syslog(LOG_WARNING,
					    gettext("kpropd: Full resync, "
					    "invalid return."));
				if (debug)
					if (ret)
						fprintf(stderr,
						    gettext("Full resync "
						    "was unsuccessful\n"));
					else
						fprintf(stderr,
						    gettext("Full resync "
						    "was successful\n"));
				frdone = B_TRUE;
				break;

			case UPDATE_BUSY:
				/*
				 * Exponential backoff
				 */
				backoff_cnt++;
				break;

			case UPDATE_FULL_RESYNC_NEEDED:
			case UPDATE_NIL:
			default:
				backoff_cnt = 0;
				frdone = B_FALSE;
				syslog(LOG_ERR, gettext("kpropd: Full resync,"
					" invalid return from master KDC."));
				break;

			case UPDATE_PERM_DENIED:
				syslog(LOG_ERR, gettext("kpropd: Full resync,"
					" permission denied."));
				goto error;

			case UPDATE_ERROR:
				syslog(LOG_ERR, gettext("kpropd: Full resync,"
					" error returned from master KDC."));
				goto error;
			}
			break;

		case UPDATE_OK:
			backoff_cnt = 0;
			frdone = B_FALSE;

			/*
			 * 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
			 */
			retval = ulog_replay(kpropd_context, incr_ret);

			if (retval) {
				syslog(LOG_ERR, gettext("kpropd: ulog_replay"
					" failed, updates not registered."));
				break;
			}

			if (debug)
				fprintf(stderr, gettext("Update transfer "
					"from master was OK\n"));
			break;

		case UPDATE_PERM_DENIED:
			syslog(LOG_ERR, gettext("kpropd: get_updates,"
						" permission denied."));
			goto error;

		case UPDATE_ERROR:
			syslog(LOG_ERR, gettext("kpropd: get_updates, error "
						"returned from master KDC."));
			goto error;

		case UPDATE_BUSY:
			/*
			 * Exponential backoff
			 */
			backoff_cnt++;
			break;

		case UPDATE_NIL:
			/*
			 * Master-slave are in sync
			 */
			if (debug)
				fprintf(stderr, gettext("Master, slave KDC's "
					"are in-sync, no updates\n"));
			backoff_cnt = 0;
			frdone = B_FALSE;
			break;

		default:
			backoff_cnt = 0;
			syslog(LOG_ERR, gettext("kpropd: get_updates,"
					" invalid return from master KDC."));
			break;
		}

		if (runonce == B_TRUE)
			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, gettext("Busy signal received "
					"from master, backoff for %d secs\n"),
					backoff_time);
			(void) sleep(backoff_time);
		}
		else
			(void) sleep(pollin);

	}


error:
	if (debug)
		fprintf(stderr, gettext("ERROR returned by master, bailing\n"));
	syslog(LOG_ERR, gettext("kpropd: ERROR returned by master KDC,"
			" bailing.\n"));
done:
	if (poll_time)
		free(poll_time);
	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,
			gettext("while closing default ccache"));
		exit(1);
	}
	if (def_realm)
		free(def_realm);
	if (server_handle)
		kadm5_destroy((void *)server_handle);
	if (kpropd_context)
		krb5_free_context(kpropd_context);

	if (runonce == B_TRUE)
		return (0);
	else
		exit(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);
}


/*
 * Routine to convert the `pollstr' string to seconds
 */
int convert_polltime(char *pollstr) {
	char *tokenptr = NULL;
	int len, polltime;

	len = polltime = 0;

	if ((len = strcspn(pollstr, "s")) < strlen(pollstr)) {
		tokenptr = malloc((len + 1) * sizeof(char));
		(void) strlcpy(tokenptr, pollstr, len + 1);
		polltime = atoi(tokenptr);
	}

	if ((len = strcspn(pollstr, "m")) < strlen(pollstr)) {
		tokenptr = malloc((len + 1) * sizeof(char));
		(void) strlcpy(tokenptr, pollstr, len + 1);
		polltime = atoi(tokenptr) * 60;
	}

	if ((len = strcspn(pollstr, "h")) < strlen(pollstr)) {
		tokenptr = malloc((len + 1) * sizeof(char));
		(void) strlcpy(tokenptr, pollstr, len + 1);
		polltime = atoi(tokenptr) * 3600;
	}

	if (tokenptr != NULL)
		free(tokenptr);
	/*
	 * If we have a bogus pollstr value, set polltime to the
	 * default of 2 mts (120 seconds).
	 */
	if (polltime == 0)
		polltime = 120;
	return (polltime);
}

static void
kpropd_com_err_proc(whoami, code, fmt, args)
	const char	*whoami;
	long		code;
	const char	*fmt;
	va_list		args;
{
	char	error_buf[8096];

	error_buf[0] = '\0';
	if (fmt)
		vsprintf(error_buf, fmt, args);
	syslog(LOG_ERR, "%s%s%s%s%s", whoami ? whoami : "", whoami ? ": " : "",
	       code ? error_message(code) : "", code ? " " : "", error_buf);
}

void PRS(argc,argv)
	int	argc;
	char	**argv;
{
	register char	*word, ch;
	char	*cp;
	int c;
	struct hostent *hp;
	char	my_host_name[MAXHOSTNAMELEN], buf[BUFSIZ];
	krb5_error_code	retval;
	static const char	tmp[] = ".temp";
	kadm5_config_params	params;

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
#define	TEXT_DOMAIN	"KPROPD_TEST"	/* Use this only if it weren't */
#endif

	(void) textdomain(TEXT_DOMAIN);

	(void) memset((char *) &params, 0, sizeof (params));

	retval = krb5_init_context(&kpropd_context);
	if (retval) {
		com_err(argv[0], retval, 
			gettext("while initializing krb5"));
		exit(1);
	}

	/* Solaris Kerberos: Sanitize progname */
	progname = basename(argv[0]);

	while ((c = getopt(argc, argv, "dtf:F:p:P:r:s:Sa:")) != EOF){
		switch (c) {
		case 'd':
			debug++;
			break;
		case 't':
			/*
			 * Undocumented option - for testing only.
			 *
			 * Option to run the kpropd server exactly
			 * once (this is true only if iprop is enabled).
			 */
			runonce = B_TRUE;
			break;

		case 'f':
			file = optarg;
			if (!file)
				usage();
			break;
		case 'F':
			kerb_database = optarg;
			if (!kerb_database)
				usage();
			break;
		case 'p':
			kdb5_util = optarg;
			if (!kdb5_util)
				usage();
			break;
		case 'P':
			port = htons(atoi(optarg));
			if (!port)
				usage();
			break;
		case 'r':
			realm = optarg;
			if (!realm)
				usage();
			params.realm = realm;
			params.mask |= KADM5_CONFIG_REALM;
			break;
		case 's':
			srvtab = optarg; 
			if (!srvtab)
				usage();
			break;
		case 'S':
			standalone++;
			break;
		case 'a':
			acl_file_name = optarg;
			if (!acl_file_name)
				usage();
			break;
		case '?':
				default:
					usage();
				}
				
			}
	/*
	 * If not in debug mode, switch com_err reporting to syslog
	 */
	if (! debug) {
	    openlog("kpropd", LOG_PID | LOG_ODELAY, SYSLOG_CLASS);
	    /*
	     * Solaris Kerberos:
	     * Don't replace default logging. Add a new logging channel.
	     * Stop logging to stderr when daemonizing
	     */
	    add_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) {
		/* Solaris Kerberos: Keep error messages consistent */
		com_err(progname, retval,
			gettext("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, 
			gettext("while constructing my service realm"));
		exit(1);
	    }
	}
	/*
	 * Construct the name of the temporary file.
	 */
	if ((temp_file_name = (char *) malloc(strlen(file) +
					       strlen(tmp) + 1)) == NULL) {
		com_err(progname, ENOMEM,
			gettext("while allocating filename for temp file"));
		exit(1);
	}
	strcpy(temp_file_name, file);
	strcat(temp_file_name, tmp);

	retval = kadm5_get_config_params(kpropd_context, 1, NULL, &params,
	    &params);
	if (retval) {
		com_err(progname, retval, gettext("while initializing"));
		exit(1);
	}
	if (params.iprop_enabled == TRUE) {
		ulog_set_role(kpropd_context, IPROP_SLAVE);
		poll_time = params.iprop_polltime;

		if (ulog_map(kpropd_context, &params, FKPROPD)) { 
		/* Solaris Kerberos: Keep error messages consistent */
 			com_err(progname, errno,
			    gettext("while mapping log")); 
			exit(1); 
		} 
	}

	/*
	 * 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) {
			/* Solaris Kerberos: Keep error messages consistent */
			com_err(progname, retval,
				gettext("while retrieving default realm"));
			exit(1);
		}
	}
}

/*
 * Figure out who's calling on the other end of the connection....
 */
void
kerberos_authenticate(context, fd, clientp, etype, ss)
    krb5_context 	  context;
    int		 	  fd;
    krb5_principal	* clientp;
    krb5_enctype	* etype;
    struct sockaddr_storage	  ss;
{
    krb5_error_code	  retval;
    krb5_ticket		* ticket;
    struct sockaddr_storage	  r_ss;
    int			  ss_length;
    krb5_keytab		  keytab = NULL;

    /*
     * Set recv_addr and send_addr
     */
    if (cvtkaddr(&ss, &sender_addr) == NULL) {
	com_err(progname, errno, 
		gettext("while converting socket address"));
	exit(1);
    }

    ss_length = sizeof (r_ss);
    if (getsockname(fd, (struct sockaddr *) &r_ss, &ss_length)) {
	com_err(progname, errno, 
		gettext("while getting local socket address"));
	exit(1);
    }

    if (cvtkaddr(&r_ss, &receiver_addr) == NULL) {
	com_err(progname, errno, 
		gettext("while converting socket address"));
	exit(1);
    }

    if (debug) {
	char *name;

	retval = krb5_unparse_name(context, server, &name);
	if (retval) {
	    /* Solaris Kerberos: Keep error messages consistent */
	    com_err(progname, retval, gettext("while unparsing server name"));
	    exit(1);
	}
	printf(gettext("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, gettext("Error in krb5_auth_con_init: %s"),
	       error_message(retval));
    	exit(1);
    }

    retval = krb5_auth_con_setflags(context, auth_context, 
				    KRB5_AUTH_CONTEXT_DO_SEQUENCE);
    if (retval) {
	syslog(LOG_ERR, gettext("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, gettext("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, gettext("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, gettext("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, gettext("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) {
	    /* Solaris Kerberos: Keep error messages consistent */
	    com_err(progname, retval, 
		gettext("while unparsing client name"));
	    exit(1);
	}

	retval = krb5_enctype_to_string(*etype, etypebuf, sizeof(etypebuf));
	if (retval) {
	    /* Solaris Kerberos: Keep error messages consistent */
	    com_err(progname, retval, gettext("while unparsing ticket etype"));
	    exit(1);
	}

	printf("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; /* This must be 4 bytes */
	int	received_size, 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, gettext("while reading database size"));
		com_err(progname, retval,
			gettext("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, gettext(
			   "while decoding database size"));
		krb5_free_data_contents(context, &inbuf);
		com_err(progname, retval,
			gettext("while decoding database size from client"));
		exit(1);
	}
	memcpy((char *) &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, gettext(
		     "failed while initializing i_vector"));
	  com_err(progname, retval, gettext("while initializing i_vector"));
	  exit(1);
	}

	/*
	 * 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),
			gettext("while reading database block starting at offset %d"),
				received_size);
			com_err(progname, retval, 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),
		gettext("while decoding database block starting at offset %d"),
				received_size);
			com_err(progname, retval, buf);
			send_error(context, fd, retval, buf);
			krb5_free_data_contents(context, &inbuf);
			exit(1);
		}
		n = write(database_fd, outbuf.data, outbuf.length);
		if (n < 0) {
			snprintf(buf, sizeof (buf),
				gettext(
"while writing database block starting at offset %d"),
				received_size);
			send_error(context, fd, errno, buf);
		} else if (n != outbuf.length) {
			snprintf(buf, sizeof (buf),
				gettext(
"incomplete write while writing database block starting at\n"
"offset %d (%d written, %d expected)"),
				received_size, n, outbuf.length);
			send_error(context, fd, KRB5KRB_ERR_GENERIC, buf);
		}
		received_size += outbuf.length;
		/* SUNWresync121: our krb5...contents sets length to 0 */
		krb5_free_data_contents(context, &inbuf);
		krb5_free_data_contents(context, &outbuf);

	}
	/*
	 * OK, we've seen the entire file.  Did we get too many bytes?
	 */
	if (received_size > database_size) {
		snprintf(buf, sizeof (buf),
		gettext("Received %d bytes, expected %d bytes for database file"),
			received_size, database_size);
		send_error(context, fd, KRB5KRB_ERR_GENERIC, buf);
	}
	/*
	 * 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,
			gettext("while encoding # of receieved bytes"));
		send_error(context, fd, retval,
			   gettext("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((char *)&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) {
			sprintf(buf, "%s %s", error_message(err_code),
				err_text);
			text = buf;
		}
	} 
	error.text.length = strlen(text) + 1;
	error.text.data = malloc(error.text.length);
	if (error.text.data) {
		strcpy(error.text.data, text);
		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,
			gettext("while decoding error packet from client"));
		exit(1);
	}
	if (error->error == KRB_ERR_GENERIC) {
		if (error->text.data)
			fprintf(stderr,
				gettext("Generic remote error: %s\n"),
				error->text.data);
	} else if (error->error) {
		com_err(progname, error->error + ERROR_TABLE_BASE_krb5,
			gettext("signalled from server"));
		if (error->text.data)
			fprintf(stderr,
				gettext("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, save_stderr = -1;
	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
	krb5_error_code	retval;
	kdb_log_context	*log_ctx;

	if (debug)
		printf(gettext("calling kdb_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, gettext("while trying to fork %s"),
			kdb_util);
		exit(1);
		/*NOTREACHED*/
	case 0:
		if (!debug) {
			save_stderr = dup(2);
			close(0);
			close(1);
			close(2);
			open("/dev/null", O_RDWR);
			dup(0);
			dup(0);
		}

		execv(kdb_util, edit_av);
		retval = errno;
		if (!debug)
			dup2(save_stderr, 2);
		com_err(progname, retval, gettext("while trying to exec %s"),
			kdb_util);
		_exit(1);
		/*NOTREACHED*/
	default:
		if (debug)
		    printf(gettext("Child PID is %d\n"), child_pid);
		if (wait(&waitb) < 0) {
			com_err(progname, errno, gettext("while waiting for %s"),
				kdb_util);
			exit(1);
		}
	}
	
	error_ret = WEXITSTATUS(waitb);
	if (error_ret) {
		com_err(progname, 0,
		    gettext("%s returned a bad exit status (%d)"),
			kdb_util, error_ret);
		exit(1);
	}
	return;
}