4.3BSD-UWisc/src/usr.etc/yp/ypxfr.c

Compare this file to the similar file:
Show the results in this format:

#ifndef lint
/* @(#)ypxfr.c	2.1 86/04/16 NFSSRC */ 
static	char sccsid[] = "@(#)ypxfr.c 1.1 86/02/05 Copyr 1984 Sun Micro";
#endif

/*
 * This is a user command which gets a yp data base from some running
 * server, and gets it to the local site by using the normal yp client
 * enumeration functions.  The map is copied to a temp name, then the real
 * map is removed and the temp map is moved to the real name.  ypxfr then
 * sends a "YPPROC_CLEAR" message to the local server to insure that he will
 * not hold a removed map open, so serving an obsolete version.  
 * 
 * ypxfr [-h <host>] [-d <domainname>] [-f] [-c] [-C tid prot ipadd port] map
 * 
 * where host may be either a name or an internet address of form ww.xx.yy.zz
 * 
 * If the host is ommitted, ypxfr will attempt to discover the master by 
 * using normal yp services.  If it can't get the record, it will use
 * the address of the callback, if specified. If the host is specified 
 * as an internet address, no yp services need to be locally available.  
 * 
 * If the domain is not specified, the default domain of the local machine
 * is used.
 * 
 * If the -f flag is used, the transfer will be done even if the master's
 * copy is not newer than the local copy.
 *
 * The -c flag suppresses the YPPROC_CLEAR request to the local ypserv.  It
 * may be used if ypserv isn't currently running to suppress the error message.
 * 
 * The -C flag is used to pass callback information to ypxfr when it is
 * activated by ypserv.  The callback information is used to send a
 * yppushresp_xfr message with transaction id "tid" to a yppush process
 * speaking a transient protocol number "prot".  The yppush program is
 * running on the node with IP address "ipadd", and is listening (UDP) on
 * "port".  
 *  
 */
#include <dbm.h>
#undef NULL
#define DATUM

#include <stdio.h>
#include <errno.h>
#include <sys/time.h>
#include <ctype.h>
#include <netdb.h>
#include <rpc/rpc.h>
#include <sys/socket.h>
#include <sys/dir.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <rpcsvc/ypclnt.h>
#include <rpcsvc/yp_prot.h>
#include <rpcsvc/ypv1_prot.h>

# define PARANOID 1		/* make sure maps have the right # entries */

#define UDPINTER_TRY 10			/* Seconds between tries for udp */
#define UDPTIMEOUT UDPINTER_TRY*4	/* Total timeout for udp */
#define CALLINTER_TRY 10		/* Seconds between callback tries */
#define CALLTIMEOUT CALLINTER_TRY*6	/* Total timeout for callback */


struct timeval udp_intertry = {
	UDPINTER_TRY,
	0
};
struct timeval udp_timeout = {
	UDPTIMEOUT,
	0
};
struct timeval tcp_timeout = {
	180,	/* timeout for map enumeration (could be long) */
	0
};


char *domain = NULL;
char *map = NULL;
char *master = NULL;		/* The name of the xfer peer as specified as a
				 *   -h option, or from querying the yp */
struct in_addr master_addr;	/* Addr of above */
struct dom_binding master_server;/* To talk to above */
unsigned int master_prog_vers;	/* YPVERS or YPOLDVERS */
char *master_name = NULL;	/* Map's master as contained in the map */
unsigned *master_version = NULL; /* Order number as contained in the map */
char *master_ascii_version;	/* ASCII order number as contained in the map */
bool fake_master_version = FALSE; /* TRUE only if there's no order number in
				  *  the map, and the user specified -f */
char yp_last_modified[] = "YP_LAST_MODIFIED";
char yp_master_name[] = "YP_MASTER_NAME";
bool force = FALSE;		/* TRUE iff user specified -f flag */
bool logging = FALSE;		/* TRUE iff no tty, but log file exists */
bool send_clear = TRUE;		/* FALSE iff user specified -c flag */
bool callback = FALSE;		/* TRUE iff -C flag set.  tid, proto, ipadd,
				 * and port will be set to point to the
				 * command line args. */
char *tid;
char *proto;
char *ipadd;
char *port;
int entry_count;		/* counts entries in the map */
char logfile[] = "/etc/yp/ypxfr.log";
char err_usage[] =
"Usage:\n\n\
ypxfr [-f] [-h <host>] [-d <domainname>] [-c] [-C tid prot ipadd port] map\n\n\
where\n\
	-f forces transfer even if the master's copy is not newer.\n\
	host may be either a name or an internet \n\
	     address of form ww.xx.yy.zz\n\
	-c inhibits sending a \"Clear map\" message to the local ypserv.\n\
	-C is used by ypserv to pass callback information.\n";
char err_bad_args[] =
	"%s argument is bad.\n";
char err_cant_get_kname[] =
	"Can't get %s back from system call.\n";
char err_null_kname[] =
	"%s hasn't been set on this machine.\n";
char err_bad_hostname[] = "hostname";
char err_bad_mapname[] = "mapname";
char err_bad_domainname[] = "domainname";
char err_udp_failure[] =
	"Can't set up a udp connection to ypserv on host %s.\n";
char ypdbpath[] = "/etc/yp";
char yptempname_prefix[] = "ypxfr_map.";
char ypbkupname_prefix[] = "ypxfr_bkup.";

void get_command_line_args();
bool get_master_addr();
bool bind_to_server();
bool ping_server();
bool  get_private_recs();
bool get_order();
bool get_v1order();
bool get_v2order();
bool get_master_name();
bool get_v1master_name();
bool get_v2master_name();
void find_map_master();
bool move_map();
unsigned get_local_version();
void mkfilename();
void mk_tmpname();
bool rename_map();
bool check_map_existence();
bool get_map();
bool add_private_entries();
bool new_mapfiles();
void del_mapfiles();
void set_output();
void logprintf();
bool send_ypclear();
void xfr_exit();
void send_callback();
int ypall_callback();
int map_yperr_to_pusherr();

extern u_long inet_addr();
extern struct hostent *gethostbyname();
extern int errno;

/*
 * This is the mainline for the ypxfr process.
 */

void
main(argc, argv)
	int argc;
	char **argv;
	
{
	int i;
	static char default_domain_name[YPMAXDOMAIN];
	static unsigned big = 0xffffffff;
	int status;
	
	set_output();

	get_command_line_args(argc, argv);

	if (!domain) {
		
		if (!getdomainname(default_domain_name, YPMAXDOMAIN) ) {
			domain = default_domain_name;
		} else {
			logprintf( err_cant_get_kname,
			    err_bad_domainname);
			xfr_exit(YPPUSH_RSRC);
		}

		if (strlen(domain) == 0) {
			logprintf( err_null_kname,
			    err_bad_domainname);
			xfr_exit(YPPUSH_RSRC);
		}
	}
	
	if (!master) {
		find_map_master();
	}
	
	if (!get_master_addr() ) {
		xfr_exit(YPPUSH_MADDR);
	}
		
	if (!bind_to_server(master, master_addr, &master_server,
	    &master_prog_vers) ) {
		xfr_exit(YPPUSH_RPC);
	}

	if (!get_private_recs(&status) ) {
		xfr_exit(status);
	}
	
	if (!master_version) {

		if (force) {
			master_version = &big;
			fake_master_version = TRUE;
		} else {
			logprintf(
    "Can't get order number for map %s from server at %s: use the -f flag.\n",
			  map, master);
			xfr_exit(YPPUSH_FORCE);
		}
	}
	
	if (!move_map(&status) ) {
		xfr_exit(status);
	}

	if (send_clear && !send_ypclear(&status) ) {
		xfr_exit(status);
	}

	if (logging) {
		logprintf("Transferred map %s from %s (%d entries).\n",
		    map, master, entry_count);
	}

	xfr_exit(YPPUSH_SUCC);
}

/*
 * This decides whether we're being run interactively or not, and, if not,
 * whether we're supposed to be logging or not.  If we are logging, it sets
 * up stderr to point to the log file, and sets the "logging"
 * variable.  If there's no logging, the output goes in the bit bucket.
 * Logging output differs from interactive output in the presence of a
 * timestamp, present only in the log file.  stderr is reset, too, because it
 * it's used by various library functions, including clnt_perror.
 */
void
set_output()
{
	if (!isatty(1)) {
		if (access(logfile, W_OK)) {
			(void) freopen("/dev/null", "w", stderr);
		} else {
			(void) freopen(logfile, "a", stderr);
			logging = TRUE;
		}
	}
}

/*
 * This constructs a logging record.
 */
void
logprintf(arg1,arg2,arg3,arg4,arg5,arg6,arg7)
{
	struct timeval t;

	fseek(stderr,0,2);
	if (logging) {
		(void) gettimeofday(&t, NULL);
		(void) fprintf(stderr, "%19.19s: ", ctime(&t.tv_sec));
	}
	(void) fprintf(stderr,arg1,arg2,arg3,arg4,arg5,arg6,arg7);
	fflush(stderr);
}

/*
 * This does the command line argument processing.
 */
void
get_command_line_args(argc, argv)
	int argc;
	char **argv;
	
{
	argv++;

	if (argc < 2) {
		logprintf( err_usage);
		xfr_exit(YPPUSH_BADARGS);
	}
	
	while (--argc) {

		if ( (*argv)[0] == '-') {

			switch ((*argv)[1]) {

			case 'f': {
				force = TRUE;
				argv++;
				break;
			}

			case 'c': {
				send_clear = FALSE;
				argv++;
				break;
			}

			case 'h': {

				if (argc > 1) {
 					argv++;
					argc--;
					master = *argv;
					argv++;

					if (strlen(master) > 256) {
						logprintf(
						    err_bad_args,
						    err_bad_hostname);
						xfr_exit(YPPUSH_BADARGS);
					}
					
				} else {
					logprintf( err_usage);
					xfr_exit(YPPUSH_BADARGS);
				}
				
				break;
			}
				
			case 'd': {

				if (argc > 1) {
					argv++;
					argc--;
					domain = *argv;
					argv++;

					if (strlen(domain) > YPMAXDOMAIN) {
						logprintf(
						    err_bad_args,
						    err_bad_domainname);
						xfr_exit(YPPUSH_BADARGS);
					}
					
				} else {
					logprintf( err_usage);
					xfr_exit(YPPUSH_BADARGS);
				}
				
				break;
			}

			case 'C': {

				if (argc > 5) {
					callback = TRUE;
					argv++;
					tid = *argv++;
					proto = *argv++;
					ipadd = *argv++;
					port = *argv++;
					argc -= 4;
				} else {
					logprintf( err_usage);
					xfr_exit(YPPUSH_BADARGS);
				}
				
				break;
			}

			default: {
				logprintf( err_usage);
				xfr_exit(YPPUSH_BADARGS);
			}
			
			}
			
		} else {

			if (!map) {
				map = *argv;
				argv++;
			
				if (strlen(map) > YPMAXMAP) {
					logprintf( err_bad_args,
				   	err_bad_mapname);
					xfr_exit(YPPUSH_BADARGS);
				}
				
			} else {
				logprintf( err_usage);
				xfr_exit(YPPUSH_BADARGS);
			}
		}
	}

	if (!map) {
		logprintf( err_usage);
		xfr_exit(YPPUSH_BADARGS);
	}
}

/*
 * This checks to see if the master name is an ASCII internet address, in
 * which case it's translated to an internet address here, or is a host
 * name.  In the second case, the standard library routine gethostbyname(3n)
 * (which uses the yp services) is called to do the translation.  
 */
bool
get_master_addr()
{
	struct in_addr tempaddr;
	struct hostent *h;
	bool error = FALSE;

	if (master==NULL) {
		/*
		 * if we were unable to get the master name, use the
		 * address of the person who called us.
		 */
	    if (callback) {
	       master_addr.s_addr = inet_addr(ipadd);
	       master = ipadd;
	       return (TRUE);
	    }
	    return (FALSE);
	}

	if (isdigit(*master) ) {
		tempaddr.s_addr = inet_addr(master);

		if ((int) tempaddr.s_addr != -1) {
			master_addr = tempaddr;
		} else {
			error = TRUE;
		}

	} else {

		if (h = gethostbyname(master) ) {
			(void) bcopy(h->h_addr, (char *) &master_addr,
			    h->h_length);
		} else {
			error = TRUE;
		}
	}
	
	if (error) {
		logprintf(
		   "Can't translate master name %s to an address.\n", master);
		return (FALSE);
	} else {
		return (TRUE);
	}
}

/*
 * This sets up a udp connection to speak the correct program and version
 * to a yp server.  vers is set to one of YPVERS or YPOLDVERS to reflect which
 * language the server speaks.
 */
bool
bind_to_server(host, host_addr, pdomb, vers)
	char *host;
	struct in_addr host_addr;
	struct dom_binding *pdomb;
	unsigned int *vers;
{
	if (ping_server(host, host_addr, pdomb, YPVERS)) {
		*vers = YPVERS;
		return (TRUE);
	} else {
		if (ping_server(host, host_addr, pdomb, YPOLDVERS)) {
			*vers = YPOLDVERS;
			return (TRUE);
		} else {
			return (FALSE);
		}
	}
}

/*
 * This sets up a UDP channel to a server which is assumed to speak an input
 * version of YPPROG.  The channel is tested by pinging the server.  In all
 * error cases except "Program Version Number Mismatch", the error is
 * reported, and in all error cases, the client handle is destroyed and the
 * socket associated with the channel is closed.
 */
bool
ping_server(host, host_addr, pdomb, vers)
	char *host;
	struct in_addr host_addr;
	struct dom_binding *pdomb;
	unsigned int vers;
{
	enum clnt_stat rpc_stat;
	
	pdomb->dom_server_addr.sin_addr = host_addr;
	pdomb->dom_server_addr.sin_family = AF_INET;
	pdomb->dom_server_addr.sin_port = htons((u_short) 0);
	pdomb->dom_server_port = htons((u_short) 0);
	pdomb->dom_socket = RPC_ANYSOCK;

	if (pdomb->dom_client = clntudp_create(&(pdomb->dom_server_addr),
	    YPPROG, vers, udp_intertry, &(pdomb->dom_socket )) ) {
		    
		rpc_stat = clnt_call(pdomb->dom_client, YPBINDPROC_NULL,
		    xdr_void, 0, xdr_void, 0, udp_timeout);

		if (rpc_stat == RPC_SUCCESS) {
			return (TRUE);
		} else {
			clnt_destroy(pdomb->dom_client);
			close(pdomb->dom_socket);
			
			if (rpc_stat != RPC_PROGVERSMISMATCH) {
				(void) clnt_perror(pdomb->dom_client,
				     "ypxfr: bind_to_server clnt_call error");
			}
			
			return (FALSE);
		}
	} else {
		logprintf("bind_to_server clntudp_create error");
		(void) clnt_pcreateerror("");
		fflush(stderr);
		return (FALSE);
	}
}

/*
 * This gets values for the YP_LAST_MODIFIED and YP_MASTER_NAME keys from the
 * master server's version of the map.  Values are held in static variables
 * here.  In the success cases, global pointer variables are set to point at
 * the local statics.  
 */
bool
get_private_recs(pushstat)
	int *pushstat;
{
	static char anumber[20];
	static unsigned number;
	static char name[YPMAXPEER + 1];
	int status;

	status = 0;
	
	if (get_order(anumber, &number, &status) ) {
		master_version = &number;
		master_ascii_version = anumber;
	} else {

		if (status != 0) {
			*pushstat = status;
			return (FALSE);
		}
	}

	if (get_master_name(name, &status) ) {
		master_name = name;
	} else {

		if (status != 0) {
			*pushstat = status;
			return (FALSE);
		}
		master_name = master;
	}

	return (TRUE);
}

/*
 * This gets the map's order number from the master server
 */
bool
get_order(an, n, pushstat)
	char *an;
	unsigned *n;
	int *pushstat;
{
	if (master_prog_vers == YPVERS) {
		return (get_v2order(an, n, pushstat) );
	} else {
		return (get_v1order(an, n, pushstat) );
	}
}

bool
get_v1order(an, n, pushstat)
	char *an;
	unsigned *n;
	int *pushstat;
{
	struct yprequest req;
	struct ypresponse resp;
	bool retval;
	char errmsg[256];

	retval = FALSE;
	req.yp_reqtype = YPMATCH_REQTYPE;
	req.ypmatch_req_domain = domain;
	req.ypmatch_req_map = map;
	req.ypmatch_req_keyptr = yp_last_modified;
	req.ypmatch_req_keysize = (sizeof (yp_last_modified)) - 1;
	
	resp.ypmatch_resp_valptr = NULL;
	resp.ypmatch_resp_valsize = 0;

	if(clnt_call(master_server.dom_client, YPOLDPROC_MATCH, _xdr_yprequest,
	    &req, _xdr_ypresponse, &resp, udp_timeout) == RPC_SUCCESS) {

		if (resp.ypmatch_resp_status == YP_TRUE) {
			bcopy(resp.ypmatch_resp_valptr, an,
			    resp.ypmatch_resp_valsize);
			an[resp.ypmatch_resp_valsize] = '\0';
			*n = atoi(an);
			retval = TRUE;
		} else if (resp.ypmatch_resp_status != YP_NOKEY) {
			*pushstat = map_yperr_to_pusherr(
			    resp.ypmatch_resp_status);

			if (!logging) {
				logprintf(
    "(info) Can't get order number from ypserv at %s.  Reason: %s.\n",
				    master, yperr_string(ypprot_err(
				    (unsigned) resp.ypmatch_resp_status)) );
			}
		}

		CLNT_FREERES(master_server.dom_client, _xdr_ypresponse, &resp);
	} else {
		*pushstat = YPPUSH_RPC;
		(void) sprintf(errmsg,
		    "ypxfr(get_v1order) RPC call to %s failed", master);
		clnt_perror(master_server.dom_client, errmsg);
	}

	return(retval);
}

bool
get_v2order(an, n, pushstat)
	char *an;
	unsigned *n;
	int *pushstat;
{
	struct ypreq_nokey req;
	struct ypresp_order resp;
	int retval;
	char errmsg[256];

	req.domain = domain;
	req.map = map;
	
	/*
	 * Get the map''s order number, null-terminate it and store it,
	 * and convert it to binary and store it again.
	 */
	retval = FALSE;
	
	if((enum clnt_stat) clnt_call(master_server.dom_client,
	    YPPROC_ORDER, xdr_ypreq_nokey, &req, xdr_ypresp_order, &resp,
	    udp_timeout) == RPC_SUCCESS) {

		if (resp.status == YP_TRUE) {
			sprintf(an, "%d", resp.ordernum);
			*n = resp.ordernum;
			retval = TRUE;
		} else if (resp.status != YP_BADDB) {
			*pushstat = ypprot_err(resp.status);
			
			if (!logging) {
				logprintf(
    "(info) Can't get order number from ypserv at %s.  Reason: %s.\n",
				    master, yperr_string(
				    ypprot_err(resp.status)) );
			}
		}

		CLNT_FREERES(master_server.dom_client, xdr_ypresp_order,
		    &resp);
	} else {
		*pushstat = YPPUSH_RPC;
		logprintf("ypxfr(get_v2order) RPC call to %s failed", master);
		clnt_perror(master_server.dom_client, "");
	}

	return (retval);
}

/*
 * This gets the map's master name from the master server
 */
bool
get_master_name(name, pushstat)
	char *name;
	int *pushstat;
{
	if (master_prog_vers == YPVERS) {
		return (get_v2master_name(name, pushstat));
	} else {
		return (get_v1master_name(name, pushstat));
	}
}

bool
get_v1master_name(name, pushstat)
	char *name;
	int *pushstat;
{
	struct yprequest req;
	struct ypresponse resp;
	bool retval;
	char errmsg[256];

	retval = FALSE;
	req.yp_reqtype = YPMATCH_REQTYPE;
	req.ypmatch_req_domain = domain;
	req.ypmatch_req_map = map;
	req.ypmatch_req_keyptr = yp_master_name;
	req.ypmatch_req_keysize = (sizeof (yp_master_name)) -1;
	
	resp.ypmatch_resp_valptr = NULL;
	resp.ypmatch_resp_valsize = 0;

	if(clnt_call(master_server.dom_client, YPOLDPROC_MATCH, _xdr_yprequest,
	    &req, _xdr_ypresponse, &resp, udp_timeout) == RPC_SUCCESS) {

		if (resp.ypmatch_resp_status == YP_TRUE) {
			bcopy(resp.ypmatch_resp_valptr, name,
			    resp.ypmatch_resp_valsize);
			name[resp.ypmatch_resp_valsize] = '\0';
			retval = TRUE;
		} else if (resp.ypmatch_resp_status != YP_NOKEY) {
			*pushstat = map_yperr_to_pusherr(
			    resp.ypmatch_resp_status);

			logprintf(
	"(info) Can't get master name from ypserv at %s. Reason: %s.\n",
				    master, 
	yperr_string(ypprot_err((unsigned) resp.ypmatch_resp_status)) );
		}
		
		CLNT_FREERES(master_server.dom_client, _xdr_ypresponse, &resp);
	} else {
		*pushstat = YPPUSH_RPC;
		(void) sprintf(errmsg,
		   "ypxfr(get_v1master_name) RPC call to %s failed", master);
		clnt_perror(master_server.dom_client, errmsg);
	}

	return(retval);
}

bool
get_v2master_name(name, pushstat)
	char *name;
	int *pushstat;
{
	struct ypreq_nokey req;
	struct ypresp_master resp;
	int retval;
	char errmsg[256];

	req.domain = domain;
	req.map = map;
	resp.master = NULL;
	retval = FALSE;
	
	if((enum clnt_stat) clnt_call(master_server.dom_client,
	    YPPROC_MASTER, xdr_ypreq_nokey, &req, xdr_ypresp_master, &resp,
	    udp_timeout) == RPC_SUCCESS) {

		if (resp.status == YP_TRUE) {
			strcpy(name, resp.master);
			retval = TRUE;
		} else if (resp.status != YP_BADDB) {
			*pushstat = ypprot_err(resp.status);

			if (!logging) {
				logprintf(
"(info) Can't get master name from ypserv at %s. Reason: %s.\n",
				    master, yperr_string(
				    ypprot_err(resp.status)) );
			}
		}
	
		CLNT_FREERES(master_server.dom_client, xdr_ypresp_master,
		    &resp);
	} else {
		*pushstat = YPPUSH_RPC;
		logprintf(
		   "ypxfr(get_v2master_name) RPC call to %s failed", master);
		clnt_perror(master_server.dom_client, "");
	}

	return (retval);
}

/*
 * This tries to get the master name for the named map, from any
 * server's version, using the vanilla yp client interface.  If we get a
 * name back, the global "master" gets pointed to it.
 */
void
find_map_master()
{
	int err;
		
	if (err = yp_master(domain, map, &master)) {
		logprintf("Can't get master of %s. Reason: %s.\n", map,
		    yperr_string(err));
	}
	
	yp_unbind(domain);
}

/*
 * This does the work of transferrring the map.
 */
bool
move_map(pushstat)
	int *pushstat;
{
	unsigned local_version;
	char map_name[MAXNAMLEN + 1];
	char tmp_name[MAXNAMLEN + 1];
	char bkup_name[MAXNAMLEN + 1];
	char an[11];
	unsigned n;

	mkfilename(map_name);
	
	if (!force) {
		local_version = get_local_version(map_name);

		if (local_version >= *master_version) {
			logprintf(
			    "Map %s at %s is not more recent than local.\n",
			    map, master);
			*pushstat = YPPUSH_AGE;
			return (FALSE);
		}
	}
	 
	mk_tmpname(yptempname_prefix, tmp_name);
	
	if (!new_mapfiles(tmp_name) ) {
		logprintf(
		    "Can't create temp map %s.\n", tmp_name);
		*pushstat = YPPUSH_FILE;
		return (FALSE);
	}

	if (dbminit(tmp_name) < 0) {
		logprintf(
		    "Can't dbm init temp map %s.\n", tmp_name);
		del_mapfiles(tmp_name);
		*pushstat = YPPUSH_DBM;
		return (FALSE);
	}

	if (!get_map(tmp_name, pushstat) ) {
		del_mapfiles(tmp_name);
		return (FALSE);
	}

	if (!add_private_entries(tmp_name) ) {
		del_mapfiles(tmp_name);
		*pushstat = YPPUSH_DBM;
		return (FALSE);
	}

	if (dbmclose(tmp_name) < 0) {
		logprintf(
		    "Can't do dbm close operation on temp map %s.\n",
		    tmp_name);
		del_mapfiles(tmp_name);
		*pushstat = YPPUSH_DBM;
		return (FALSE);
	}

	if (!get_order(an, &n, pushstat)) {
		return(FALSE);
	}
	if (n != *master_version) {
		logprintf(
		    "Version skew at %s while transferring map %s.\n",
		    master, map);
		del_mapfiles(tmp_name);
		*pushstat = YPPUSH_SKEW;
		return (FALSE);
	}

# ifdef PARANOID
	if (!count_mismatch(tmp_name,entry_count)) {
		del_mapfiles(tmp_name);
		*pushstat = YPPUSH_DBM;
		return(FALSE);
	}
# endif PARANOID

	if (!check_map_existence(map_name) ) {

		if (!rename_map(tmp_name, map_name) ) {
			del_mapfiles(tmp_name);
			logprintf(
			  "Rename error:  couldn't mv %s to %s.\n",
			    tmp_name, map_name);
			*pushstat = YPPUSH_FILE;
			return (FALSE);
		}
		
	} else {
		mk_tmpname(ypbkupname_prefix, bkup_name);
	
		if (!rename_map(map_name, bkup_name) ) {
			(void) rename_map(bkup_name, map_name);
			logprintf(
			  "Rename error:  check that old %s is still intact.\n",
			    map_name);
			del_mapfiles(tmp_name);
			*pushstat = YPPUSH_FILE;
			return (FALSE);
		}

		if (rename_map(tmp_name, map_name) ) {
			del_mapfiles(bkup_name);
		} else {
			del_mapfiles(tmp_name);
			(void) rename_map(bkup_name, map_name);
				logprintf(
			  "Rename error:  check that old %s is still intact.\n",
			    map_name);
			*pushstat = YPPUSH_FILE;
			return (FALSE);
		}
	}

	return (TRUE);
}

/*
 * This tries to get the order number out of the local version of the map.
 * If the attempt fails for any version, the function will return "0"
 */
unsigned
get_local_version(name)
	char *name;
{
	datum key;
	datum val;
	char number[11];
	
	if (!check_map_existence(name) ) {
		return (0);
	}

	if (dbminit(name) < 0) {
		return (0);
	}
		
	key.dptr = yp_last_modified;
	key.dsize = (sizeof (yp_last_modified) - 1);
	val = fetch(key);
	(void) dbmclose(name);

	if (!val.dptr) {
		return (0);
	}

	if (val.dsize == 0 || val.dsize > 10) {
		return (0);
	}

	(void) bcopy(val.dptr, number, val.dsize);
	number[val.dsize] = '\0';
	return ((unsigned) atoi(number) );
}

/*
 * This constructs a file name for a map, minus its ".dir" or ".pag" extensions
 */
void
mkfilename(ppath)
	char *ppath;
{

	if ( (strlen(domain) + strlen(map) + strlen(ypdbpath) + 3) 
	    > (MAXNAMLEN + 1) ) {
		logprintf( "Map name string too long.\n");
	}

	(void) strcpy(ppath, ypdbpath);
	(void) strcat(ppath, "/");
	(void) strcat(ppath, domain);
	(void) strcat(ppath, "/");
	(void) strcat(ppath, map);
}

/*
 * This returns a temporary name for a map transfer minus its ".dir" or
 * ".pag" extensions.
 */
void
mk_tmpname(prefix, xfr_name)
	char *prefix;
	char *xfr_name;
{
	char xfr_anumber[10];
	long xfr_number;

	if (!xfr_name) {
		return;
	}

	xfr_number = getpid();
	(void) sprintf(xfr_anumber, "%d", xfr_number);
	
	(void) strcpy(xfr_name, ypdbpath);
	(void) strcat(xfr_name, "/");
	(void) strcat(xfr_name, domain);
	(void) strcat(xfr_name, "/");
	(void) strcat(xfr_name, prefix);
	(void) strcat(xfr_name, map);
	(void) strcat(xfr_name, ".");
	(void) strcat(xfr_name, xfr_anumber);
}

/*
 * This deletes the .pag and .dir files which implement a map.
 *
 * Note:  No error checking is done here for a garbage input file name or for
 * failed unlink operations.
 */
void
del_mapfiles(basename)
	char *basename;
{
	char dbfilename[MAXNAMLEN + 1];

	if (!basename) {
		return;
	}
	
	strcpy(dbfilename, basename);
	strcat(dbfilename, ".pag");
	unlink(dbfilename);
	strcpy(dbfilename, basename);
	strcat(dbfilename, ".dir");
	unlink(dbfilename);
}

/*
 * This checks to see if the source map files exist, then renames them to the
 * target names.  This is a boolean function.  The file names from.pag and
 * from.dir will be changed to to.pag and to.dir in the success case.
 *
 * Note:  If the second of the two renames fails, yprename_map will try to
 * un-rename the first pair, and leave the world in the state it was on entry.
 * This might fail, too, though...
 */
bool
rename_map(from, to)
	char *from;
	char *to;
{
	char fromfile[MAXNAMLEN + 1];
	char tofile[MAXNAMLEN + 1];

	if (!from || !to) {
		return (FALSE);
	}
	
	if (!check_map_existence(from) ) {
		return (FALSE);
	}
	
	(void) strcpy(fromfile, from);
	(void) strcat(fromfile, ".pag");
	(void) strcpy(tofile, to);
	(void) strcat(tofile, ".pag");
	
	if (rename(fromfile, tofile) ) {
		logprintf( "Can't mv %s to %s.\n", fromfile,
		    tofile);
		return (FALSE);
	}
	
	(void) strcpy(fromfile, from);
	(void) strcat(fromfile, ".dir");
	(void) strcpy(tofile, to);
	(void) strcat(tofile, ".dir");
	
	if (rename(fromfile, tofile) ) {
		logprintf( "Can't mv %s to %s.\n", fromfile,
		    tofile);
		(void) strcpy(fromfile, from);
		(void) strcat(fromfile, ".pag");
		(void) strcpy(tofile, to);
		(void) strcat(tofile, ".pag");
		
		if (rename(tofile, fromfile) ) {
			logprintf(
			    "Can't recover from rename failure.\n");
			return (FALSE);
		}
		
		return (FALSE);
	}
	
	return (TRUE);
}

/*
 * This performs an existence check on the dbm data base files <pname>.pag and
 * <pname>.dir.
 */
bool
check_map_existence(pname)
	char *pname;
{
	char dbfile[MAXNAMLEN + 1];
	struct stat filestat;
	int len;

	if (!pname || ((len = strlen(pname)) == 0) ||
	    (len + 5) > (MAXNAMLEN + 1) ) {
		return (FALSE);
	}
		
	errno = 0;
	(void) strcpy(dbfile, pname);
	(void) strcat(dbfile, ".dir");

	if (stat(dbfile, &filestat) != -1) {
		(void) strcpy(dbfile, pname);
		(void) strcat(dbfile, ".pag");

		if (stat(dbfile, &filestat) != -1) {
			return (TRUE);
		} else {

			if (errno != ENOENT) {
				logprintf(
				    "Stat error on map file %s.\n",
				    dbfile);
			}

			return (FALSE);
		}

	} else {

		if (errno != ENOENT) {
			logprintf(
			    "Stat error on map file %s.\n",
			    dbfile);
		}

		return (FALSE);
	}
}

/*
 * This creates <pname>.dir and <pname>.pag
 */
bool
new_mapfiles(pname)
	char *pname;
{
	char dbfile[MAXNAMLEN + 1];
	int f;
	int len;

	if (!pname || ((len = strlen(pname)) == 0) ||
	    (len + 5) > (MAXNAMLEN + 1) ) {
		return (FALSE);
	}
		
	errno = 0;
	(void) strcpy(dbfile, pname);
	(void) strcat(dbfile, ".dir");

	if ((f = open(dbfile, (O_WRONLY | O_CREAT | O_TRUNC), 0644)) >= 0) {
		(void) close(f);
		(void) strcpy(dbfile, pname);
		(void) strcat(dbfile, ".pag");

		if ((f = open(dbfile, (O_WRONLY | O_CREAT | O_TRUNC),
		    0644)) >= 0) {
			(void) close(f);
			return (TRUE);
		} else {
			return (FALSE);
		}

	} else {
		return (FALSE);
	}
}

count_callback(status) 
	int status;
{
	if (status != YP_TRUE) {
		
		if (status != YP_NOMORE) {
			logprintf(
			    "Error from ypserv on %s (ypall_callback) = %s.\n",
			    master, yperr_string(ypprot_err(status)));
		}
		
		return(TRUE);
	}

	entry_count++;
	return(FALSE);
}

/*
 * This counts the entries in the dbm file after the transfer to
 * make sure that the dbm file was built correctly.  
 * Returns TRUE if everything is OK, FALSE if they mismatch.
 */
count_mismatch(pname,oldcount)
	char *pname;
	int oldcount;
{
	datum key, value;
	struct dom_binding domb;
	enum clnt_stat s;
	struct ypreq_nokey allreq;
	struct ypall_callback cbinfo;

    entry_count = 0;
    dbminit(pname);
    for (key = firstkey(); key.dptr != NULL; key = nextkey(key))
	entry_count++;
    dbmclose(pname);

    if (oldcount != entry_count) {
	logprintf( 
		  "*** Count mismatch in dbm file %s: old=%d, new=%d ***\n",
		    map, oldcount, entry_count);
	return(FALSE);
    }

# ifdef REALLY_PARANOID
	domb.dom_server_addr.sin_addr = master_addr;
	domb.dom_server_addr.sin_family = AF_INET;
	domb.dom_server_addr.sin_port = htons((u_short) 0);
	domb.dom_server_port = htons((u_short) 0);
	domb.dom_socket = RPC_ANYSOCK;

	if ((domb.dom_client = clnttcp_create(&(domb.dom_server_addr),
	    YPPROG, master_prog_vers, &(domb.dom_socket), 0, 0)) ==
	    (CLIENT *) NULL) {
		clnt_pcreateerror(
		    "ypxfr (mismatch) - TCP channel create failure");
		return(FALSE);
	}

	if (master_prog_vers == YPVERS) {
		int tmpstat;

		allreq.domain = domain;
		allreq.map = map;
		cbinfo.foreach = count_callback;
		tmpstat = 0;
		cbinfo.data = (char *) &tmpstat;
	
		entry_count = 0;
		s = clnt_call(domb.dom_client, YPPROC_ALL, xdr_ypreq_nokey,
		    &allreq, xdr_ypall, &cbinfo, tcp_timeout);

		if (tmpstat == 0) {
			if (s == RPC_SUCCESS) {
			} else {
				clnt_perror(domb.dom_client,
			   "ypxfr (get_map/all) - RPC clnt_call (TCP) failure");
		    return(FALSE);
			}
			
		} else {
		    return(FALSE);
		}
		
	} else {
	    logprintf("Wrong version number!\n");
	    return(FALSE);
	}
    clnt_destroy(domb.dom_client);
    close(domb.dom_socket);
    entry_count += 2;			/* add in YP_entries */
    if (oldcount != entry_count) {
	logprintf(
	"*** Count mismatch after enumerate %s: old=%d, new=%d ***\n",
		    map, oldcount, entry_count);
	return(FALSE);
    }
# endif REALLY_PARANOID

    return(TRUE);
}

/*
 * This sets up a TCP connection to the master server, and either gets
 * ypall_callback to do all the work of writing it to the local dbm file
 * (if the ypserv is current version), or does it itself for an old ypserv.  
 */
bool
get_map(pname, pushstat)
	char *pname;
	int *pushstat;
{
	struct dom_binding domb;
	enum clnt_stat s;
	struct ypreq_nokey allreq;
	struct ypall_callback cbinfo;
	struct yprequest oldreq;
	struct ypresponse resp;
	bool retval = FALSE;
	int tmpstat;
	
	domb.dom_server_addr.sin_addr = master_addr;
	domb.dom_server_addr.sin_family = AF_INET;
	domb.dom_server_addr.sin_port = htons((u_short) 0);
	domb.dom_server_port = htons((u_short) 0);
	domb.dom_socket = RPC_ANYSOCK;

	if ((domb.dom_client = clnttcp_create(&(domb.dom_server_addr),
	    YPPROG, master_prog_vers, &(domb.dom_socket), 0, 0)) ==
	    (CLIENT *) NULL) {
		clnt_pcreateerror(
			"ypxfr (get_map) - TCP channel create failure");
		*pushstat = YPPUSH_RPC;
		return(FALSE);
	}

	entry_count = 0;
	if (master_prog_vers == YPVERS) {
		allreq.domain = domain;
		allreq.map = map;
		cbinfo.foreach = ypall_callback;
		tmpstat = 0;
		cbinfo.data = (char *) &tmpstat;
	
		s = clnt_call(domb.dom_client, YPPROC_ALL, xdr_ypreq_nokey,
		    &allreq, xdr_ypall, &cbinfo, tcp_timeout);

		if (tmpstat == 0) {
			
			if (s == RPC_SUCCESS) {
				retval = TRUE;
			} else {
				clnt_perror(domb.dom_client,
			   "ypxfr (get_map/all) - RPC clnt_call (TCP) failure");
				*pushstat = YPPUSH_RPC;
			}
			
		} else {
			*pushstat = tmpstat;
		}
		
	} else {
		datum inkey, inval;

		oldreq.yp_reqtype = YPFIRST_REQTYPE;
		oldreq.ypfirst_req_domain = domain;
		oldreq.ypfirst_req_map = map;
	
		resp.ypfirst_resp_keyptr = NULL;
		resp.ypfirst_resp_keysize = 0;
		resp.ypfirst_resp_valptr = NULL;
		resp.ypfirst_resp_valsize = 0;
	
		if((s = clnt_call(domb.dom_client, YPOLDPROC_FIRST,
		    _xdr_yprequest, &oldreq, _xdr_ypresponse,
		    &resp, tcp_timeout)) != RPC_SUCCESS) {
			clnt_perror(domb.dom_client,
			 "ypxfr (get_map/first) - RPC clnt_call (TCP) failure");
			*pushstat = YPPUSH_RPC;
			goto cleanup;
		}

		if (resp.ypfirst_resp_status != YP_TRUE) {
			logprintf(
			    "Error from ypserv on %s (get first) = %s.\n",
			    master, yperr_string(ypprot_err(
			    resp.ypfirst_resp_status)));
			*pushstat = YPPUSH_RPC;
			goto cleanup;
		}

		inkey = resp.ypfirst_resp_keydat;
		inval = resp.ypfirst_resp_valdat;
	
		/*
		 * Continue to get the next entries in the map as long as
		 * there are no errors, and there are entries remaining.  
		 */
		oldreq.yp_reqtype = YPNEXT_REQTYPE;
		oldreq.ypnext_req_domain = domain;
		oldreq.ypnext_req_map = map;

		while (TRUE) {
			if (strncmp(inkey.dptr,"YP_",3)) {
				if (store(inkey, inval) < 0) {
					logprintf(
				    "Can't do dbm store into temp map %s.\n",
				    	    pname);
					*pushstat = YPPUSH_DBM;
					goto cleanup;
				}
				entry_count++;
			}
			CLNT_FREERES(domb.dom_client, _xdr_ypresponse, &resp);

			oldreq.ypnext_req_keydat = inkey;
			resp.ypnext_resp_keydat.dptr = NULL;
			resp.ypnext_resp_valdat.dptr = NULL;
			resp.ypnext_resp_keydat.dsize = 0;
			resp.ypnext_resp_valdat.dsize = 0;
	
			if ((s = clnt_call(domb.dom_client, YPOLDPROC_NEXT,
		    	    _xdr_yprequest, &oldreq, _xdr_ypresponse,
		    	    &resp, tcp_timeout)) != RPC_SUCCESS) {
				clnt_perror(domb.dom_client,
			  "ypxfr (get_map/next) - RPC clnt_call (TCP) failure");
				*pushstat = YPPUSH_RPC;
				break;
			}

			if (resp.ypnext_resp_status != YP_TRUE) {

				if (resp.ypnext_resp_status == YP_NOMORE) {
					retval = TRUE;
				} else {
					logprintf(
			    "Error from ypserv on %s (get next) = %d.\n",
			    		    master, yperr_string(ypprot_err(
			    resp.ypnext_resp_status)));
					*pushstat = YPPUSH_RPC;
				}
				
				break;
			}

			inkey = resp.ypnext_resp_keydat;
			inval = resp.ypnext_resp_valdat;
		}
	}
cleanup:
	clnt_destroy(domb.dom_client);
	close(domb.dom_socket);
	return (retval);
}

/*
 * This sticks each key-value pair into the current map.  It returns FALSE as
 * long as it wants to keep getting called back, and TRUE on error conditions
 * and "No more k-v pairs".
 */
int
ypall_callback(status, key, kl, val, vl, pushstat)
	int status;
	char *key;
	int kl;
	char *val;
	int vl;
	int *pushstat;
{
	datum keydat;
	datum valdat;
	datum test;

	if (status != YP_TRUE) {
		
		if (status != YP_NOMORE) {
			logprintf(
			    "Error from ypserv on %s (ypall_callback) = %s.\n",
			    master, yperr_string(ypprot_err(status)));
			*pushstat = map_yperr_to_pusherr(status);
		}
		
		return(TRUE);
	}

	keydat.dptr = key;
	keydat.dsize = kl;
	valdat.dptr = val;
	valdat.dsize = vl;
	entry_count++;

# ifdef PARANOID
	test = fetch(keydat);
	if (test.dptr!=NULL) {
		logprintf("Duplicate key %s in map %s\n",key,map);
		*pushstat  = YPPUSH_DBM;
		return(TRUE);
	}
# endif PARANOID
	if (store(keydat, valdat) < 0) {
		logprintf(
		    "Can't do dbm store into temp map %s.\n",map);
		*pushstat  = YPPUSH_DBM;
		return(TRUE);
	}
# ifdef PARANOID
	test = fetch(keydat);
	if (test.dptr==NULL) {
		logprintf("Key %s was not inserted into dbm file %s\n",
			key,map);
		*pushstat  = YPPUSH_DBM;
		return(TRUE);
	}
# endif PARANOID
	return(FALSE);
}

/*
 * This maps a YP_xxxx error code into a YPPUSH_xxxx error code
 */
int
map_yperr_to_pusherr(yperr)
	int yperr;
{
	int reason;

	switch (yperr) {
	
 	case YP_NOMORE:
		reason = YPPUSH_SUCC;
		break;

 	case YP_NOMAP:
		reason = YPPUSH_NOMAP;
		break;

 	case YP_NODOM:
		reason = YPPUSH_NODOM;
		break;

 	case YP_NOKEY:
		reason = YPPUSH_YPERR;
		break;

 	case YP_BADARGS:
		reason = YPPUSH_BADARGS;
		break;

 	case YP_BADDB:
		reason = YPPUSH_YPERR;
		break;

	default:
		reason = YPPUSH_XFRERR;
		break;
	}
	
  	return(reason);
}

/*
 * This writes the last-modified and master entries into the new dbm file
 */
bool
add_private_entries(pname)
	char *pname;
{
	datum key;
	datum val;

	if (!fake_master_version) {
		key.dptr = yp_last_modified;
		key.dsize = sizeof (yp_last_modified) - 1;
		val.dptr = master_ascii_version;
		val.dsize = strlen(master_ascii_version);

		if (store(key, val) < 0) {
			logprintf(
			    "Can't do dbm store into temp map %s.\n",
		    	    pname);
			return (FALSE);
		}
		entry_count++;
	}
	
	if (master_name) {
		key.dptr = yp_master_name;
		key.dsize = sizeof (yp_master_name) - 1;
		val.dptr = master_name;
		val.dsize = strlen(master_name);
		if (store(key, val) < 0) {
			logprintf(
			    "Can't do dbm store into temp map %s.\n",
		    	    pname);
			return (FALSE);
		}
		entry_count++;
	}
	
	return (TRUE);
}
	
	
/*
 * This sends a YPPROC_CLEAR message to the local ypserv process.  If the
 * local ypserv is a v.1 protocol guy, we'll just say we succeeded.  Such a
 * situation is outlandish - why are they running the old ypserv at the same
 * place they are running ypxfr?  And who are they, anyway?
 */
bool
send_ypclear(pushstat)
	int *pushstat;
{
	struct sockaddr_in myaddr;
	struct dom_binding domb;
	char local_host_name[256];
	unsigned int progvers;

	get_myaddress(&myaddr);

	if (gethostname(local_host_name, 256)) {
		logprintf( "Can't get local machine name.\n");
		*pushstat = YPPUSH_RSRC;
		return (FALSE);
	}

	if (!bind_to_server(local_host_name, myaddr.sin_addr, &domb,
	    &progvers) ) {
		*pushstat = YPPUSH_CLEAR;
		return (FALSE);
	}

	if (progvers == YPOLDVERS)
		return (TRUE);

	if((enum clnt_stat) clnt_call(domb.dom_client,
	    YPPROC_CLEAR, xdr_void, 0, xdr_void, 0,
	    udp_timeout) != RPC_SUCCESS) {
		logprintf(
		"Can't send ypclear message to ypserv on the local machine.\n");
		xfr_exit(YPPUSH_CLEAR);
	}

	return (TRUE);
}

/*
 * This decides if send_callback has to get called, and does the process exit.
 */
void
xfr_exit(status)
	int status;
{
	if (callback) {
		send_callback(&status);
	}

	if (status == YPPUSH_SUCC) {
		exit(0);
	} else {
		exit(1);
	}
}

/*
 * This sets up a UDP connection to the yppush process which contacted our
 * parent ypserv, and sends him a status on the requested transfer.
 */
void
send_callback(status)
	int *status;
{
	struct yppushresp_xfr resp;
	struct dom_binding domb;

	resp.transid = (unsigned long) htonl(atoi(tid));
	resp.status = (unsigned long) htonl(*status);

	domb.dom_server_addr.sin_addr.s_addr = inet_addr(ipadd);
	domb.dom_server_addr.sin_family = AF_INET;
	domb.dom_server_addr.sin_port = (unsigned short) htons(atoi(port));
	domb.dom_server_port = domb.dom_server_addr.sin_port;
	domb.dom_socket = RPC_ANYSOCK;

	udp_intertry.tv_sec = CALLINTER_TRY;
	udp_timeout.tv_sec = CALLTIMEOUT;

	if ((domb.dom_client = clntudp_create(&(domb.dom_server_addr),
	    (unsigned long) htons(atoi(proto)), YPPUSHVERS, 
	    udp_intertry, &(domb.dom_socket) ) ) == NULL) {
		*status = YPPUSH_RPC;
		return;
	}	
	
	if((enum clnt_stat) clnt_call(domb.dom_client,
	    YPPUSHPROC_XFRRESP, xdr_yppushresp_xfr, &resp, xdr_void, 0, 
	    udp_timeout) != RPC_SUCCESS) {
		*status = YPPUSH_RPC;
		return;
	} 
}