/*
 * Copyright (c) 1986-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)yp_b_subr.c	1.33	99/10/05 SMI"

#include "ypsym.h"
#include <stdlib.h>
#include "yp_b.h"
#include <string.h>
#include <limits.h>
#include <netconfig.h>
#include <netdir.h>
#include <rpc/clnt.h>
#include <syslog.h>
#include <sys/time.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/statvfs.h>
#include <rpcsvc/nis.h>

#ifndef NULL
#define	NULL	0
#endif

#define	YPSERVERS	"ypservers"

void ypbind_init_default();
static int ypbind_pipe_setdom();

static bool firsttime = TRUE;
static struct domain *known_domains;

extern struct netconfig *__rpc_getconf();
extern void *__rpc_setconf(), *__rpc_endconf();
extern CLIENT *__clnt_tp_create_bootstrap();
extern char *inet_ntoa();
extern int __rpc_get_local_uid();

extern listofnames *names();
extern void free_listofnames();

#define	PINGTIME	10	/* Timeout for the ypservers list */
#define	PINGTOTTIM 5	/* Total seconds for ping timeout */

static void broadcast_setup();
static void sigcld_handler();
static struct ypbind_binding *dup_ypbind_binding();
static struct netbuf *dup_netbuf();
static void free_ypbind_binding();
static void enable_exit();
static void ypbind_ping();
static struct domain *ypbind_point_to_domain();
static bool ypbind_broadcast_ack();
static int pong_servers();
void cache_binding();
void uncache_binding();

extern int setok;
extern int broadcast;
extern int cache_okay;

/*
 * Need to differentiate between RPC_UNKNOWNHOST returned by the RPC
 * library, and the same error caused by a local lookup failure in
 * /etc/hosts and/or /etc/inet/ipnodes.
 */
int hostNotKnownLocally;

/*ARGSUSED*/
void	*
ypbindproc_null_3(argp, clnt)
void	*argp;
CLIENT *clnt;
{
	static char	res;

	return ((void *) & res);
}

static void
enable_exit()
{
	static bool	done = FALSE;

	if (!done) {
		done = TRUE;
		sigset(SIGCHLD, (void (*)())sigcld_handler);
	}
}

int sigcld_event = 0;

static void
sigcld_handler()
{
	sigcld_event++;
#ifdef DEBUG
	fprintf(stderr, "ypbind sighandler: got SIGCLD signal (event=%d)\n",
		sigcld_event);
#endif
}


/*
 * This is a Unix SIGCHILD handler that notices when a broadcaster child
 * process has exited, and retrieves the exit status.  The broadcaster pid
 * is set to 0.  If the broadcaster succeeded, dom_report_success will be
 * be set to -1.
 */

void
broadcast_proc_exit()
{
	int pid, ret;
	siginfo_t infop;
	register struct domain *pdom;
	bool	succeeded = FALSE;

	sigcld_event = 0;
					/* ==== Why WEXITED? */
	while ((ret = waitid(P_ALL, 0, &infop, WNOHANG | WEXITED)) != -1) {
		switch (infop.si_code) {
		case CLD_EXITED:
			succeeded = infop.si_status == 0;
			break;
		case CLD_KILLED:
		case CLD_DUMPED:
			succeeded = FALSE;
			break;
		case CLD_TRAPPED:
		case CLD_STOPPED:
		case CLD_CONTINUED:
			enable_exit();
			return;
		}
		pid = infop.si_pid;

#ifdef DEBUG
		fprintf(stderr,
			"ypbind event_handler: got wait from %d status = %d\n",
			pid, infop.si_status);
#endif

	/* to aid the progeny print the infamous "not responding" message */
		firsttime = FALSE;

		for (pdom = known_domains; pdom != (struct domain *)NULL;
				pdom = pdom->dom_pnext) {

			if (pdom->dom_broadcaster_pid == pid) {
#ifdef DEBUG
			fprintf(stderr,
			"ypbind event_handler: got match %s\n", pdom->dom_name);
#endif
				if (succeeded) {
					broadcast_setup(pdom);
				}
				if (pdom->broadcaster_pipe != 0) {
					xdr_destroy(&(pdom->broadcaster_xdr));
					fclose(pdom->broadcaster_pipe);
					pdom->broadcaster_pipe = 0;
					pdom->broadcaster_fd = -1;
				}
				pdom->dom_broadcaster_pid = 0;

				break;
			}
		}
	}	/* while loop */
	enable_exit();
}

static void
broadcast_setup(pdom)
struct domain *pdom;
{
	ypbind_setdom req;

	memset(&req, 0, sizeof (req));
	if (pdom->broadcaster_pipe) {
		pdom->dom_report_success = -1;
		if (xdr_ypbind_setdom(&(pdom->broadcaster_xdr), &req)) {
#ifdef DEBUG
	fprintf(stderr, "parent: broadcast_setup: got xdr ok \n");
#endif
			ypbindproc_setdom_3(&req, (struct svc_req *)NULL,
			    (SVCXPRT *)NULL);
			xdr_free(xdr_ypbind_setdom, (char *)&req);
			gettimeofday(&(pdom->lastping), NULL);
		}
#ifdef DEBUG
		    else {
	fprintf(stderr, "ypbind parent: xdr_ypbind_setdom failed\n");
		}
#endif
	}
#ifdef DEBUG
	    else {
	fprintf(stderr, "ypbind: internal error -- no broadcaster pipe\n");
	}
#endif
}

#define	YPBIND_PINGHOLD_DOWN 5
/* Same as the ypbind_get_binding() routine in SunOS */
/*ARGSUSED*/
ypbind_resp *
ypbindproc_domain_3(argp, clnt)
ypbind_domain *argp;
CLIENT *clnt;
{
	static ypbind_resp resp;
	struct domain *cur_domain;
	int bpid;
	int fildes[2];

	memset((char *)&resp, 0, sizeof (resp));

#ifdef DEBUG
	fprintf(stderr, "\nypbindproc_domain_3: domain: %s\n",
		argp->ypbind_domainname);
#endif

	if ((cur_domain = ypbind_point_to_domain(argp->ypbind_domainname)) !=
	    (struct domain *) NULL) {
		if (cur_domain->dom_boundp) {

			struct timeval tp;

			(void) gettimeofday(&tp, NULL);
			if ((tp.tv_sec - cur_domain->lastping.tv_sec) >
					YPBIND_PINGHOLD_DOWN) {
#ifdef DEBUG
	fprintf(stderr, "domain is bound pinging: %s\n",
		argp->ypbind_domainname);
#endif
				(void) ypbind_ping(cur_domain);
			}
		}

		/*
		 * Bound or not, return the current state of the binding.
		 */

		if (cur_domain->dom_boundp) {
#ifdef DEBUG
	fprintf(stderr, "server is up for domain: %s\n",
		argp->ypbind_domainname);
#endif
			resp.ypbind_status = YPBIND_SUCC_VAL;
			resp.ypbind_resp_u.ypbind_bindinfo =
			    cur_domain->dom_binding;
		} else {
#ifdef DEBUG
	fprintf(stderr, "domain is NOT bound returning: %s %d\n",
		argp->ypbind_domainname, cur_domain->dom_error);
#endif
			resp.ypbind_status = YPBIND_FAIL_VAL;
			resp.ypbind_resp_u.ypbind_error =
			    cur_domain->dom_error;
		}

	} else {
		resp.ypbind_status = YPBIND_FAIL_VAL;
		resp.ypbind_resp_u.ypbind_error = YPBIND_ERR_RESC;
	}
	/*
	 * RETURN NOW: if successful, otherwise
	 * RETURN LATER: after spawning off a child to do the "broadcast" work.
	 */
	if (resp.ypbind_status == YPBIND_SUCC_VAL) {
#ifdef DEBUG
	fprintf(stderr, "yp_b_subr: returning success to yp_b_svc %d\n",
		resp.ypbind_status);
#endif
		return (&resp);
	}

	/* Go about the broadcast (really, pinging here) business */

	if ((cur_domain) && (!cur_domain->dom_boundp) &&
	    (!cur_domain->dom_broadcaster_pid)) {
#ifdef DEBUG
	fprintf(stderr, "yp_b_subr: fork: boundp=%d broadcast_pid=%d\n",
		cur_domain->dom_boundp, cur_domain->dom_broadcaster_pid);
#endif
		/*
		 * The current domain is unbound, and there is no child
		 * process active now.  Fork off a child who will beg to the
		 * ypservers list one by one or broadcast and accept whoever
		 * commands the right domain.
		 */
		if (pipe(fildes) < 0) {
#ifdef DEBUG
	fprintf(stderr, "yp_b_subr: returning pipe failure to yp_b_svc %d\n",
		resp.ypbind_status);
#endif
			return (&resp);
		}

		enable_exit();
		sighold(SIGCLD); /* add it to ypbind's signal mask */
		cur_domain->dom_report_success++;
		bpid = fork();
		if (bpid != 0) { /* parent */
			if (bpid > 0) { /* parent started */
				close(fildes[1]);
				cur_domain->dom_broadcaster_pid = bpid;
				cur_domain->broadcaster_fd = fildes[0];
				cur_domain->broadcaster_pipe =
						fdopen(fildes[0], "r");
				if (cur_domain->broadcaster_pipe)
			xdrstdio_create(&(cur_domain->broadcaster_xdr),
			(cur_domain->broadcaster_pipe), XDR_DECODE);

#ifdef DEBUG
	fprintf(stderr, "ypbindproc_domain_3: %s starting pid = %d try = %d\n",
	    cur_domain->dom_name, bpid,
	    cur_domain->dom_report_success);
	fprintf(stderr, "yp_b_subr: returning after spawning, to yp_b_svc %d\n",
		resp.ypbind_status);
#endif
				sigrelse(SIGCLD);
				/* remove it from ypbind's signal mask */
				return (&resp);
			} else { /* fork failed */
				perror("fork");
				close(fildes[0]);
				close(fildes[1]);
#ifdef DEBUG
	fprintf(stderr, "yp_b_subr: returning fork failure to yp_b_svc %d\n",
		resp.ypbind_status);
#endif
				sigrelse(SIGCLD);
				return (&resp);
			}
		} /* end parent */
		/* child only code */
		sigrelse(SIGCLD);
		close(fildes[0]);
		cur_domain->broadcaster_fd = fildes[1];
		cur_domain->broadcaster_pipe = fdopen(fildes[1], "w");
		if (cur_domain->broadcaster_pipe)
			xdrstdio_create(&(cur_domain->broadcaster_xdr),
				(cur_domain->broadcaster_pipe), XDR_ENCODE);
		else {
			perror("fdopen-pipe");
			exit(-1);
		}
		exit(pong_servers(cur_domain));
	}
#ifdef DEBUG
	fprintf(stderr, "yp_b_subr: lazy returns failure status yp_b_svc %d\n",
			resp.ypbind_status);
#endif
	return (&resp);
}


/*
 * call ypbindproc_domain_3 and convert results
 *
 * This adds support for YP clients that send requests on
 * ypbind version 1 & 2 (i.e. clients before we started
 * using universal addresses and netbufs). This is supported
 * for binary compatibility for static 4.x programs. The
 * assumption used to be that clients coming in with ypbind vers 1
 * should be given the address of a server serving ypserv version 1.
 * However, since yp_bind routines in 4.x YP library try
 * to connect with ypserv version 2, even if they requested
 * binding using ypbind version 1, the ypbind process will
 * "always" look for only ypserv version 2 servers for all
 * (ypbind vers 1, 2, & 3) clients.
 */
ypbind_resp_2 *
ypbindproc_domain_2(argp, clnt)
domainname_2 *argp;
CLIENT *clnt;
{
	ypbind_domain arg_3;
	ypbind_resp *resp_3;
	static ypbind_resp_2 resp;

	arg_3.ypbind_domainname = *argp;
	resp_3 = ypbindproc_domain_3(&arg_3, clnt);
	if (resp_3 == NULL)
		return (NULL);
	resp.ypbind_status = resp_3->ypbind_status;
	if (resp_3->ypbind_status == YPBIND_SUCC_VAL) {
		struct sockaddr_in *sin;
		struct ypbind_binding_2 *bi;

		sin = (struct sockaddr_in *)
		    resp_3->ypbind_resp_u.ypbind_bindinfo->ypbind_svcaddr->buf;
		if (sin->sin_family == AF_INET) {
			bi = &resp.ypbind_respbody_2.ypbind_bindinfo;
			memcpy(&(bi->ypbind_binding_port), &sin->sin_port, 2);
			memcpy(&(bi->ypbind_binding_addr), &sin->sin_addr, 4);
		} else {
			resp.ypbind_respbody_2.ypbind_error = YPBIND_ERR_NOSERV;
		}
	} else {
		resp.ypbind_respbody_2.ypbind_error =
		    resp_3->ypbind_resp_u.ypbind_error;
	}
	return (&resp);
}

/* used to exchange information between pong_servers and ypbind_broadcast_ack */
struct domain *process_current_domain;

int
pong_servers(domain_struct)
struct domain *domain_struct; /* to pass back */
{
	char *domain = domain_struct->dom_name;
	CLIENT *clnt2;
	char *servername;
	listofnames *list, *lin;
	char serverfile[MAXNAMLEN];
	struct timeval timeout;
	int isok = 0, res = -1;
	struct netconfig *nconf;
	void *handle;
	int nconf_count;

	if (broadcast) {
		enum clnt_stat stat = RPC_SUCCESS;
#ifdef DEBUG
	fprintf(stderr, "pong_servers: doing an rpc_broadcast\n");
#endif
		/*
		 * Here we do the real SunOS thing that users love. Do a
		 * broadcast on the network and find out the ypserv. No need
		 * to do "ypinit -c", no setting up /etc/hosts file, and no
		 * recursion looking up the server's IP address.
		 */
		process_current_domain = domain_struct;
		stat = rpc_broadcast(YPPROG, YPVERS, YPPROC_DOMAIN_NONACK,
			(xdrproc_t)xdr_ypdomain_wrap_string, (caddr_t)&domain,
			xdr_int,  (caddr_t)&isok,
			(resultproc_t) ypbind_broadcast_ack, "udp");
		if (stat == RPC_SYSTEMERROR || stat == RPC_UNKNOWNPROTO ||
			stat == RPC_CANTRECV || stat == RPC_CANTSEND ||
			stat == RPC_NOBROADCAST ||
			stat == RPC_N2AXLATEFAILURE) {
			syslog(LOG_ERR, "RPC/Transport subsystem failure %s\n",
				clnt_sperrno(stat));
			exit(-1);
		}
		if (domain_struct->broadcaster_pipe == 0)
			/* init binding case */
			return (domain_struct->dom_boundp - 1);
		if (domain_struct->dom_boundp) {
			res = ypbind_pipe_setdom(NULL, domain,
					NULL, domain_struct);
			if (domain_struct->dom_report_success > 0)
				syslog(LOG_ERR,
			"NIS server for domain \"%s\" OK", domain);
		} else if (firsttime == FALSE)
			syslog(LOG_ERR,
	"NIS server not responding for domain \"%s\"; still trying", domain);
		return (res);
	}
#ifdef DEBUG
	fprintf(stderr, "pong_servers: ponging servers one by one\n");
#endif
	/*
	 * Do the politically correct thing.. transport independent and
	 * secure (trusts only listed servers).
	 */

	/*
	 * get list of possible servers for this domain
	 */

	/*
	 * get alias for domain: Things of the past..
	 * sysvconfig();
	 * (void) yp_getalias(domain, domain_alias, NAME_MAX);
	*/
	sprintf(serverfile, "%s/%s/%s", BINDING, domain, YPSERVERS);
#ifdef DEBUG
	fprintf(stderr, "pong_servers: serverfile %s\n", serverfile);
#endif
	list = names(serverfile);
	lin = list;
	if (list == NULL) {
		syslog(LOG_ERR,
			"service not installed, use /usr/sbin/ypinit -c");
		return (-1);
	}

	for (list = lin; list; list = list->nextname) {
		servername = strtok(list->name, " \t\n");
		if (servername == NULL) continue;

		/* Check all datagram_v transports for this server */
		if ((handle = __rpc_setconf("datagram_v")) == NULL) {
			syslog(LOG_ERR,
			"ypbind: RPC operation on /etc/netconfig failed");
			free_listofnames(lin);
			return (-1);
		}

		nconf_count = 0;
		clnt2 = 0;
		while (clnt2 == 0 && (nconf = __rpc_getconf(handle)) != 0) {
			nconf_count++;
			/*
			 * We use only datagram here. It is expected to be udp.
			 * VERY IMPORTANT: __clnt_tp_create_bootstrap is a
			 * hacked up version that does not do netdir_getbyname.
			 */
			hostNotKnownLocally = 0;
			clnt2 =
	__clnt_tp_create_bootstrap(servername, YPPROG, YPVERS, nconf);
		}
		if (nconf_count == 0) {
			syslog(LOG_ERR,
			"ypbind: RPC operation on /etc/netconfig failed");
			free_listofnames(lin);
			return (-1);
		}

		if (clnt2 == 0) {
			if (rpc_createerr.cf_stat == RPC_UNKNOWNHOST &&
				hostNotKnownLocally) {
				syslog(LOG_ERR,
		"NIS server %s is not in local host files !", servername);
			}
			perror(servername);
			clnt_pcreateerror("ypbind");
			continue;
		}

		timeout.tv_sec = PINGTIME;
		timeout.tv_usec = 0;
		if ((enum clnt_stat) clnt_call(clnt2,
		    YPPROC_DOMAIN, (xdrproc_t)xdr_ypdomain_wrap_string,
		    (char *) &domain, xdr_int,
		    (char *) &isok, timeout) == RPC_SUCCESS) {
			if (isok) {
				if (domain_struct->dom_report_success > 0) {
					syslog(LOG_ERR,
				"NIS server for domain \"%s\" OK", domain);
				}
				if (domain_struct->broadcaster_pipe == 0) {
					/* init binding case --parent */
					struct netconfig *setnc;
					struct netbuf setua;
					struct ypbind_binding *b =
						domain_struct->dom_binding;

					setnc =
				getnetconfigent(clnt2->cl_netid);
					if (b == NULL) {
					/* ASSERT: This shouldn't happen ! */
						b =
				(struct ypbind_binding *)calloc(1, sizeof (*b));
						domain_struct->dom_binding = b;
						if (b == NULL) {
							__rpc_endconf(handle);
							clnt_destroy(clnt2);
							free_listofnames(lin);
							return (-2);
						}
					}


					b->ypbind_nconf = setnc;
					clnt_control(clnt2, CLGET_SVC_ADDR,
						(char *)&setua);
					if (b->ypbind_svcaddr) {
						if (b->ypbind_svcaddr->buf)
				free(b->ypbind_svcaddr->buf);
						free(b->ypbind_svcaddr);
					}
					b->ypbind_svcaddr = dup_netbuf(&setua);
					if (b->ypbind_servername)
						free(b->ypbind_servername);
					b->ypbind_servername =
						strdup(servername);
					b->ypbind_hi_vers = YPVERS;
					b->ypbind_lo_vers = YPVERS;
					__rpc_endconf(handle);
					domain_struct->dom_boundp = TRUE;
					clnt_destroy(clnt2);
					free_listofnames(lin);
					return (0);
				}
				res = ypbind_pipe_setdom(clnt2, domain,
					servername, domain_struct);
				__rpc_endconf(handle);
				clnt_destroy(clnt2);
				free_listofnames(lin);
				return (res);
			} else {
				syslog(LOG_ERR,
				    "server %s doesn't serve domain %s\n",
				    servername, domain);
			}
		} else {
			clnt_perror(clnt2, servername);
		}
		clnt_destroy(clnt2);
	}
	/*
	 * We tried all servers, none obliged !
	 * After ypbind is started up it will not be bound
	 * immediately.  This is normal, no error message
	 * is needed. Although, with the ypbind_init_default
	 * it will be bound immediately.
	 */
	if (firsttime == FALSE) {
		syslog(LOG_ERR,
"NIS server not responding for domain \"%s\"; still trying", domain);
	}
	free_listofnames(lin);
	__rpc_endconf(handle);
	return (-2);
}

struct netbuf *
dup_netbuf(inbuf)
	struct netbuf *inbuf;
{
	struct netbuf *outbuf;

	if (inbuf == NULL)
		return (NULL);
	if ((outbuf =
		(struct netbuf *)calloc(1, sizeof (struct netbuf))) == NULL)
		return (NULL);
	if ((outbuf->buf = malloc(inbuf->len)) == NULL) {
		free(outbuf);
		return (NULL);
	}
	outbuf->len = inbuf->len;
	outbuf->maxlen = inbuf->len;
	(void) memcpy(outbuf->buf, inbuf->buf, inbuf->len);
	return (outbuf);
}

/*
 * This is called by the broadcast rpc routines to process the responses
 * coming back from the broadcast request. Since the form of the request
 * which is used in ypbind_broadcast_bind is "respond only in the positive
 * case", we know that we have a server.
 * The internet address of the responding server will be picked up from
 * the saddr parameter, and stuffed into the domain.  The domain's boundp
 * field will be set TRUE.  The first responding server (or the first one
 * which is on a reserved port) will be the bound server for the domain.
 */
bool
ypbind_broadcast_ack(ptrue, nbuf, nconf)
	bool *ptrue;
	struct netbuf *nbuf;
	struct netconfig *nconf;
{
	struct ypbind_binding b;

	process_current_domain->dom_boundp = TRUE;
	b.ypbind_nconf = nconf;
	b.ypbind_svcaddr = nbuf;
	b.ypbind_servername = "\000";
	b.ypbind_hi_vers = YPVERS;
	b.ypbind_lo_vers = YPVERS;
	free_ypbind_binding(process_current_domain->dom_binding);
	process_current_domain->dom_binding = dup_ypbind_binding(&b);
	return (TRUE);
}

/*
 * WARNING: This routine is entered only by the child process.
 * Called if it pongs/broadcasts okay.
 */
static int
ypbind_pipe_setdom(client, domain, servername, opaque_domain)
CLIENT *client;
char *servername;
char *domain;
struct domain *opaque_domain;
{
	struct netconfig *setnc;
	struct netbuf setua;
	ypbind_binding setb;
	ypbind_setdom setd;
	int retval;

	setd.ypsetdom_domain = domain;
	if (client == NULL && opaque_domain->dom_binding) {
#ifdef DEBUG
	fprintf(stderr, "ypbind_pipe_setdom: child broadcast case ");
#endif
		/* ypbind_broadcast_ack already setup dom_binding for us */
		setd.ypsetdom_bindinfo = opaque_domain->dom_binding;
	} else if (client) {
#ifdef DEBUG
	fprintf(stderr, "ypbind_pipe_setdom: child unicast case ");
#endif
		setnc = getnetconfigent(client->cl_netid);
		if (setnc == NULL) {
#ifdef DEBUG
	fprintf(stderr, "PANIC: shouldn't happen\n");
#endif
			fclose(opaque_domain->broadcaster_pipe);
			close(opaque_domain->broadcaster_fd);
			return (-2);
		}
		clnt_control(client, CLGET_SVC_ADDR, (char *)&setua);
		setb.ypbind_nconf = setnc;
		setb.ypbind_svcaddr = &setua;
		setb.ypbind_servername = servername;
		setb.ypbind_hi_vers = YPVERS;
		setb.ypbind_lo_vers = YPVERS;
		setd.ypsetdom_bindinfo = &setb;
	/*
	 * Let's hardcode versions, that is the only ypserv we support anyway.
	 * Avoid the song and dance of recursively calling ypbind_ping
	 * for no reason. Consistent with the 4.1 policy, that if ypbind gets
	 * a request on new binder protocol, the requestor is looking for the
	 * new ypserv. And, we have even higher binder protocol version i.e. 3.
	 */
	} else
		return (-1);
#ifdef DEBUG
	fprintf(stderr,
		" saving server settings, \nsupports versions %d thru %d\n",
		setd.ypsetdom_bindinfo->ypbind_lo_vers,
		setd.ypsetdom_bindinfo->ypbind_hi_vers);
#endif

	if (opaque_domain->broadcaster_pipe == 0) {
#ifdef DEBUG
	fprintf(stderr, "PANIC: shouldn't be in this function\n");
#endif
		return (-2);
	}
#ifdef DEBUG
	fprintf(stderr, "child: doing xdr_ypbind_setdom\n");
#endif
	retval = xdr_ypbind_setdom(&(opaque_domain->broadcaster_xdr), &setd);
	xdr_destroy(&(opaque_domain->broadcaster_xdr));
	fclose(opaque_domain->broadcaster_pipe);
	close(opaque_domain->broadcaster_fd);
	/*
	 * This child process is about to exit. Don't bother freeing memory.
	 */
	if (!retval) {
#ifdef DEBUG
	fprintf(stderr,
		"YPBIND pipe_setdom failed \n(xdr failure) to server %s\n",
		servername ? servername : "");
#endif
		return (-3);
	}
#ifdef DEBUG
	fprintf(stderr, "ypbind_pipe_setdom: YPBIND OK-set to server %s\n",
		servername ? servername : "");
#endif
	return (0);
}

/* Same as ypbind_set_binding in SunOS */
/*
 *  We use a trick from SunOS to return an error to the ypset command
 *  when we are not allowing the domain to be set.  We do a svcerr_noprog()
 *  to send RPC_PROGUNAVAIL to ypset.  We also return NULL so that
 *  our caller (ypbindprog_3) won't try to return a result.  This
 *  hack is necessary because the YPBINDPROC_SETDOM procedure is defined
 *  in the protocol to return xdr_void, so we don't have a direct way to
 *  return an error to the client.
 */
/*ARGSUSED*/
void	*
ypbindproc_setdom_3(argp, rqstp, transp)
ypbind_setdom *argp;
struct svc_req *rqstp;
SVCXPRT *transp;
{
	struct domain *a_domain;
	struct netbuf *who;
	static char res; /* dummy for void * return */
	uid_t caller_uid;

	if (transp != NULL) {
		/* find out who originated the request */
		char *uaddr;
		struct netconfig *nconf;

		who = svc_getrpccaller(transp);
		if ((nconf = getnetconfigent(transp->xp_netid))
			== (struct netconfig *)NULL) {
			svcerr_systemerr(transp);
			return (0);
		}
		if (strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0) {
			uaddr = strdup("local host");
		} else {
			uaddr = taddr2uaddr(nconf, who);
		}
		if (setok != YPSETALL) {
		/* for -ypset, it falls through and let anybody do a setdom ! */
			if (strcmp(nconf->nc_protofmly, NC_LOOPBACK) != 0) {
				syslog(LOG_ERR,
"ypset request from %s not on loopback, \
cannot set ypbind to %s", uaddr ? uaddr : "unknown source",
argp->ypsetdom_bindinfo->ypbind_servername);
				if (uaddr)
					free(uaddr);
				freenetconfigent(nconf);
				svcerr_noprog(transp);
				return (0);
			}
			switch (setok) {
			case YPSETNONE:
				if (strcmp(nconf->nc_protofmly,
					NC_LOOPBACK) == 0)
					syslog(LOG_ERR,
"ypset request to %s from %s failed - ypset not allowed",
argp->ypsetdom_bindinfo->ypbind_servername, uaddr);
				if (uaddr)
					free(uaddr);
				freenetconfigent(nconf);
				svcerr_noprog(transp);
				return (0);
			case YPSETLOCAL:
				if (__rpc_get_local_uid(transp,
					&caller_uid) < 0) {
					syslog(LOG_ERR, "ypset request from \
unidentified local user on %s - ypset not allowed",
transp->xp_netid);
					if (uaddr)
						free(uaddr);
					freenetconfigent(nconf);
					svcerr_noprog(transp);
					return (0);
				}
				if (caller_uid != 0) {
					syslog(LOG_ERR,
"Set domain request to host %s \
from local non-root user %ld failed - ypset not allowed",
argp->ypsetdom_bindinfo->ypbind_servername, caller_uid);
					if (uaddr)
						free(uaddr);
					freenetconfigent(nconf);
					svcerr_noprog(transp);
					return (0);
				}
			}
		}
		syslog(LOG_ERR, "Set domain request from %s : \
setting server for domain %s to %s", uaddr ? uaddr : "UNKNOWN SOURCE",
argp->ypsetdom_domain, argp->ypsetdom_bindinfo->ypbind_servername);
		if (uaddr)
			free(uaddr);
		freenetconfigent(nconf);
	}

	if ((a_domain = ypbind_point_to_domain(argp->ypsetdom_domain))
	    != (struct domain *) NULL) {
		/* setting binding; old may be invalid */
		uncache_binding(a_domain);

		/* this does the set -- should copy the structure */
		free_ypbind_binding(a_domain->dom_binding);
		if ((a_domain->dom_binding =
		    dup_ypbind_binding(argp->ypsetdom_bindinfo)) == NULL) {
			syslog(LOG_ERR, "ypbindproc_setdom_3: out of memory, ",
				"dup_ypbind_binding failed\n");
			if (transp) {
				svcerr_noprog(transp);
				return (0);
			}
			return (&res);
		}
		gettimeofday(&(a_domain->lastping), NULL);
		a_domain->dom_boundp = TRUE;
		cache_binding(a_domain);
#ifdef DEBUG
	fprintf(stderr, "ypbindproc_setdom_3: setting domain %s to server %s\n",
		argp->ypsetdom_domain,
		argp->ypsetdom_bindinfo->ypbind_servername);
#endif
	}

	return (&res);
}

/*
 * This returns a pointer to a domain entry.  If no such domain existed on
 * the list previously, an entry will be allocated, initialized, and linked
 * to the list.  Note:  If no memory can be malloc-ed for the domain structure,
 * the functional value will be (struct domain *) NULL.
 */
static struct domain *
ypbind_point_to_domain(pname)
register char	*pname;
{
	register struct domain *pdom;
	char buf[300];

	for (pdom = known_domains; pdom != (struct domain *)NULL;
	    pdom = pdom->dom_pnext) {
		if (strcmp(pname, pdom->dom_name) == 0)
			return (pdom);
	}

	/* Not found.  Add it to the list */

	if (pdom = (struct domain *)calloc(1, sizeof (struct domain))) {
		pdom->dom_name = strdup(pname);
		if (pdom->dom_name == NULL) {
			free((char *)pdom);
			syslog(LOG_ERR,
				"ypbind_point_to_domain: strdup failed\n");
			return (NULL);
		}
		pdom->dom_pnext = known_domains;
		known_domains = pdom;
		pdom->dom_boundp = FALSE;
		pdom->dom_vers = YPVERS; /* This doesn't talk to old ypserv */
		pdom->dom_binding = NULL;
		pdom->dom_error = YPBIND_ERR_NOSERV;
		pdom->ping_clnt = (CLIENT *)NULL;
		pdom->dom_report_success = -1;
		pdom->dom_broadcaster_pid = 0;
		pdom->broadcaster_pipe = 0;
		pdom->bindfile = -1;
		pdom->lastping.tv_sec = 0;
		pdom->lastping.tv_usec = 0; /* require ping */
		pdom->cache_fp = 0;
		sprintf(buf, "%s/%s/cache_binding", BINDING, pdom->dom_name);
		pdom->cache_file = strdup(buf);
		/*
		 *  We don't give an error if pdom->cache_file is not set.
		 *  If we got null (out of memory), then we just won't use
		 *  the cache file in cache_binding() (assuming the
		 *  application gets that far.
		 */
	}
	else
		syslog(LOG_ERR, "ypbind_point_to_domain: malloc failed\n");

	return (pdom);
}

static void
ypbind_ping(pdom)
struct domain *pdom;
{
	struct timeval timeout;
	int	vers;
	int	isok;

	if (pdom->dom_boundp == FALSE)
		return;
	vers = pdom->dom_vers;

	if (pdom->ping_clnt == (CLIENT *) NULL) {
		pdom->ping_clnt = __nis_clnt_create(RPC_ANYFD,
					pdom->dom_binding->ypbind_nconf, 0,
					pdom->dom_binding->ypbind_svcaddr, 0,
					YPPROG, vers, 0, 0);
	}

	if (pdom->ping_clnt == (CLIENT *) NULL) {
		perror("clnt_tli_create");
		clnt_pcreateerror("ypbind_ping()");
		pdom->dom_boundp = FALSE;
		pdom->dom_error = YPBIND_ERR_NOSERV;
		return;
	}


#ifdef DEBUG
	fprintf(stderr, "ypbind: ypbind_ping()\n");
#endif
	timeout.tv_sec = PINGTOTTIM;
	timeout.tv_usec =  0;
	if (clnt_call(pdom->ping_clnt,
	    YPPROC_DOMAIN, (xdrproc_t)xdr_ypdomain_wrap_string,
		(char *)&pdom->dom_name, xdr_int, (char *) &isok,
		timeout) == RPC_SUCCESS) {
			pdom->dom_boundp = isok;
			pdom->dom_binding->ypbind_lo_vers = vers;
			pdom->dom_binding->ypbind_hi_vers = vers;
#ifdef DEBUG
	fprintf(stderr,
		"Server pinged successfully, supports versions %d thru %d\n",
		pdom->dom_binding->ypbind_lo_vers,
		pdom->dom_binding->ypbind_hi_vers);
#endif
	} else {
		clnt_perror(pdom->ping_clnt, "ping");
		pdom->dom_boundp = FALSE;
		pdom->dom_error = YPBIND_ERR_NOSERV;
	}
	(void) gettimeofday(&(pdom->lastping), NULL);
	if (pdom->ping_clnt)
		clnt_destroy(pdom->ping_clnt);
	pdom->ping_clnt = (CLIENT *)NULL;
	if (pdom->dom_boundp)
		cache_binding(pdom);
}

static struct ypbind_binding *
dup_ypbind_binding(a)
struct ypbind_binding *a;
{
	struct ypbind_binding *b;
	struct netconfig *nca, *ncb;
	struct netbuf *nxa, *nxb;
	int i;

	b = (struct ypbind_binding *)calloc(1, sizeof (*b));
	if (b == NULL)
		return (b);
	b->ypbind_hi_vers = a->ypbind_hi_vers;
	b->ypbind_lo_vers = a->ypbind_lo_vers;
	b->ypbind_servername =
		a->ypbind_servername ? strdup(a->ypbind_servername) : NULL;
	ncb = (b->ypbind_nconf =
		(struct netconfig *) calloc(1, sizeof (struct netconfig)));
	nxb = (b->ypbind_svcaddr =
		(struct netbuf *) calloc(1, sizeof (struct netbuf)));
	nca = a->ypbind_nconf;
	nxa = a->ypbind_svcaddr;
	ncb->nc_flag = nca->nc_flag;
	ncb->nc_protofmly =
		nca->nc_protofmly ? strdup(nca->nc_protofmly) : NULL;
	ncb->nc_proto =
		nca->nc_proto ? strdup(nca->nc_proto) : NULL;
	ncb->nc_semantics = nca->nc_semantics;
	ncb->nc_netid =
		nca->nc_netid ? strdup(nca->nc_netid) : NULL;
	ncb->nc_device =
		nca->nc_device ? strdup(nca->nc_device) : NULL;
	ncb->nc_nlookups = nca->nc_nlookups;
	ncb->nc_lookups = (char **)calloc(nca->nc_nlookups, sizeof (char *));
	if (ncb->nc_lookups == NULL) {
		if (ncb->nc_device)
			free(ncb->nc_device);
		if (ncb->nc_netid)
			free(ncb->nc_netid);
		if (ncb->nc_proto)
			free(ncb->nc_proto);
		if (ncb->nc_protofmly)
			free(ncb->nc_protofmly);
		if (nxb)
			free(nxb);
		if (ncb)
			free(ncb);
		if (b->ypbind_servername)
			free(b->ypbind_servername);
		if (b)
			free(b);
		return (NULL);
	}
	for (i = 0; i < nca->nc_nlookups; i++)
		ncb->nc_lookups[i] =
			nca->nc_lookups[i] ? strdup(nca->nc_lookups[i]) : NULL;
	for (i = 0; i < 8; i++)
		ncb->nc_unused[i] = nca->nc_unused[i];
	nxb->maxlen = nxa->maxlen;
	nxb->len = nxa->len;
	nxb->buf = malloc(nxa->maxlen);
	if (nxb->buf == NULL) {
		for (i = 0; i < nca->nc_nlookups; i++)
			if (ncb->nc_lookups[i])
				free(ncb->nc_lookups[i]);
		free(ncb->nc_lookups);
		if (ncb->nc_device)
			free(ncb->nc_device);
		if (ncb->nc_netid)
			free(ncb->nc_netid);
		if (ncb->nc_proto)
			free(ncb->nc_proto);
		if (ncb->nc_protofmly)
			free(ncb->nc_protofmly);
		if (nxb)
			free(nxb);
		if (ncb)
			free(ncb);
		if (b->ypbind_servername)
			free(b->ypbind_servername);
		if (b)
			free(b);
		return (NULL);
	}
	memcpy(nxb->buf, nxa->buf, nxb->len);
	return (b);
}

static void
free_ypbind_binding(b)
struct ypbind_binding *b;
{
	if (b == NULL)
		return;
	netdir_free((char *)b->ypbind_svcaddr, ND_ADDR);
	free(b->ypbind_servername);
	freenetconfigent(b->ypbind_nconf);
	free(b);
}

/*
 * Preloads teh default domain's domain binding. Domain binding for the
 * local node's default domain for ypserv version 2 (YPVERS) will be
 * set up. This may make it a little slower to start ypbind during
 * boot time, but would make it easy on other domains that rely on
 * this binding.
 */
void
ypbind_init_default()
{
	char domain[256];
	struct domain *cur_domain;

	if (getdomainname(domain, 256) == 0) {
		cur_domain = ypbind_point_to_domain(domain);

		if (cur_domain == (struct domain *)NULL) {
			abort();
		}
		(void) pong_servers(cur_domain);
	}
}

bool_t
xdr_ypbind_binding_2(xdrs, objp)
	register XDR *xdrs;
	ypbind_binding_2 *objp;
{
	if (!xdr_opaque(xdrs, (char *)&(objp->ypbind_binding_addr), 4))
		return (FALSE);
	if (!xdr_opaque(xdrs, (char *)&(objp->ypbind_binding_port), 2))
		return (FALSE);
	return (TRUE);
}

bool_t
xdr_ypbind_resp_2(xdrs, objp)
	register XDR *xdrs;
	ypbind_resp_2 *objp;
{
	if (!xdr_ypbind_resptype(xdrs, &objp->ypbind_status))
		return (FALSE);
	switch (objp->ypbind_status) {
	case YPBIND_FAIL_VAL:
		if (!xdr_u_long(xdrs, &objp->ypbind_respbody_2.ypbind_error))
			return (FALSE);
		break;
	case YPBIND_SUCC_VAL:
		if (!xdr_ypbind_binding_2(xdrs,
		    &objp->ypbind_respbody_2.ypbind_bindinfo))
			return (FALSE);
		break;
	default:
		return (FALSE);
	}
	return (TRUE);
}

/*
 *  The following is some caching code to improve the performance of
 *  yp clients.  In the days of yore, a client would talk to rpcbind
 *  to get the address for ypbind, then talk to ypbind to get the
 *  address of the server.  If a lot of clients are doing this at
 *  the same time, then rpcbind and ypbind get bogged down and clients
 *  start to time out.
 *
 *  We cache two things:  the current address for ypserv, and the
 *  transport addresses for talking to ypbind.  These are saved in
 *  files in /var/yp.  To get the address of ypserv, the client opens
 *  a file and reads the address.  It does not have to talk to rpcbind
 *  or ypbind.  If this file is not available, then it can read the
 *  the transport address for talking to ypbind without bothering
 *  rpcbind.  If this also fails, then it uses the old method of
 *  talking to rpcbind and then ypbind.
 *
 *  We lock the first byte of the cache files after writing to them.
 *  This indicates to the client that they contents are valid.  The
 *  client should test the lock.  If the lock is held, then it can
 *  use the contents.  If the lock test fails, then the contents should
 *  be ignored.
 */

/*
 *  Cache new binding information for a domain in a file.  If the
 *  new binding is the same as the old, then we skip it.  We xdr
 *  a 'ypbind_resp', which is what would be returned by a call to
 *  the YBINDPROCP_DOMAIN service.  We xdr the data because it is
 *  easier than writing the data out field by field.  It would be
 *  nice if there were an xdrfd_create() that was similar to
 *  xdrstdio_create().  Instead, we do an fdopen and use xdrstdio_create().
 */
void
cache_binding(pdom)
	struct domain *pdom;
{
	int st;
	int fd;
	XDR xdrs;
	struct ypbind_resp resp;

	if (!cache_okay)
		return;

	/* if the domain doesn't have a cache file, then skip it */
	if (pdom->cache_file == 0)
		return;

	/*
	 *  If we already had a cache file for this domain, remove it.  If
	 *  a client just started accessing it, then it will either find
	 *  it unlocked (and not use it), or continue to use it with
	 *  old information.  This is not a problem, the client will
	 *  either fail to talk to ypserv and try to bind again, or
	 *  will continue to use the old server.
	 */
	if (pdom->cache_fp) {
		fclose(pdom->cache_fp);    /* automatically unlocks */
		unlink(pdom->cache_file);
		pdom->cache_fp = 0;
	}

	fd = open(pdom->cache_file, O_CREAT|O_WRONLY, 0444);
	if (fd == -1)
		return;

	pdom->cache_fp = fdopen(fd, "w");
	if (pdom->cache_fp == 0) {
		close(fd);
		return;
	}

	xdrstdio_create(&xdrs, pdom->cache_fp, XDR_ENCODE);
	resp.ypbind_status = YPBIND_SUCC_VAL;
	resp.ypbind_resp_u.ypbind_bindinfo = pdom->dom_binding;

	if (!xdr_ypbind_resp(&xdrs, &resp)) {
		xdr_destroy(&xdrs);
		unlink(pdom->cache_file);
		fclose(pdom->cache_fp);
		pdom->cache_fp = 0;
		return;
	}
	xdr_destroy(&xdrs);    /* flushes xdr but leaves fp open */

	/* we lock the first byte to indicate that the file is valid */
	lseek(fd, 0L, SEEK_SET);
	st = lockf(fd, F_LOCK, 1);
	if (st == -1) {
		unlink(pdom->cache_file);
		fclose(pdom->cache_fp);
		pdom->cache_fp = 0;
	}
}

void
uncache_binding(pdom)
	struct domain *pdom;
{
	if (!cache_okay)
		return;

	if (pdom->cache_fp != 0) {
		unlink(pdom->cache_file);
		fclose(pdom->cache_fp);
		pdom->cache_fp = 0;
	}
}

/*
 *  Cache a transport address for talking to ypbind.  We convert the
 *  transport address to a universal address and save that in a file.
 *  The file lives in the binding directory because it does not depend
 *  on the domain.
 */
void
cache_transport(nconf, xprt, vers)
	struct netconfig *nconf;
	SVCXPRT *xprt;
	int vers;
{
	char filename[300];
	char *uaddr;
	int fd;
	int st;
	int len;

	if (!cache_okay)
		return;

	sprintf(filename, "%s/xprt.%s.%d",
		BINDING, nconf->nc_netid, vers);

	unlink(filename);    /* remove any old version */

	uaddr = taddr2uaddr(nconf, &xprt->xp_ltaddr);
	if (uaddr == 0)
		return;

	fd = open(filename, O_CREAT|O_WRONLY, 0444);    /* readable by all */
	if (fd == -1) {
		free(uaddr);
		return;
	}

	len = strlen(uaddr) + 1;    /* include terminating null */
	st = write(fd, uaddr, len);
	if (st != len) {
		close(fd);
		unlink(filename);
		free(uaddr);
		return;
	}

	free(uaddr);

	/* we lock the first byte to indicate that the file is valid */
	lseek(fd, 0L, SEEK_SET);
	st = lockf(fd, F_LOCK, 1);
	if (st == -1) {
		close(fd);
		unlink(filename);
	}
}

/*
 *  Create a file that clients can check to see if we are running.
 */
void
cache_pid()
{
	char filename[300];
	char spid[15];
	int fd;
	int st;
	int len;

	if (!cache_okay)
		return;

	sprintf(filename, "%s/ypbind.pid", BINDING);

	unlink(filename);    /* remove any old version */

	fd = open(filename, O_CREAT|O_WRONLY, 0444);    /* readable by all */
	if (fd == -1) {
		return;
	}

	sprintf(spid, "%d\n", getpid());

	len = strlen(spid);
	st = write(fd, spid, len);
	if (st != len) {
		close(fd);
		unlink(filename);
		return;
	}

	/* we lock the first byte to indicate that the file is valid */
	lseek(fd, 0L, SEEK_SET);
	st = lockf(fd, F_LOCK, 1);
	if (st == -1) {
		close(fd);
		unlink(filename);
	}

	/* we keep 'fd' open so that the lock will continue to be held */
}

/*
 *  We are called once at startup (when the known_domains list is empty)
 *  to clean up left-over files.  We are also called right before
 *  exiting.  In the latter case case we don't bother closing descriptors
 *  in the entries in the domain list because they will be closed
 *  automatically (and unlocked) when we exit.
 *
 *  We ignore the cache_okay flag because it is important that we remove
 *  all cache files (left-over files can temporarily confuse clients).
 */
void
clean_cache()
{
	struct domain *pdom;
	DIR *dir;
	struct dirent *dirent;
	char filename[300];

	/* close and unlink cache files for each domain */
	for (pdom = known_domains; pdom != (struct domain *)NULL;
	    pdom = pdom->dom_pnext) {
		if (pdom->cache_file)
			unlink(pdom->cache_file);
	}

	sprintf(filename, "%s/ypbind.pid", BINDING);
	unlink(filename);

	dir = opendir(BINDING);
	while ((dirent = readdir(dir)) != 0) {
		if (strncmp(dirent->d_name, "xprt.", 5) == 0) {
			sprintf(filename, "%s/%s", BINDING, dirent->d_name);
			unlink(filename);
			rewinddir(dir);  /* removing file may harm iteration */
		}
	}
	closedir(dir);
}

/*
 *  We only want to use the cache stuff on local file systems.
 *  For remote file systems (e.g., NFS, the locking overhead is
 *  worse than the overhead of loopback RPC, so the caching
 *  wouldn't buy us anything.  In addition, if the remote locking
 *  software isn't configured before we start, then we would
 *  block when we try to lock.
 *
 *  We don't have a direct way to tell if a file system is local
 *  or remote, so we assume it is local unless it is NFS.
 */
int
cache_check()
{
	int st;
	struct statvfs stbuf;

	st = statvfs(BINDING, &stbuf);
	if (st == -1)
		return (0);
	/* we use strncasecmp to get NFS, NFS3, nfs, nfs3, etc. */
	if (strncasecmp(stbuf.f_basetype, "NFS", 3) == 0)
		return (0);
	return (1);
}
