OpenSolaris_b135/cmd/listen/listen.c

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

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Network Listener Process
 *
 *		command line:
 *
 *		listen [ -m minor_prefix ] netspec
 *
 */

/* system include files	*/

#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <memory.h>
#include <sys/utsname.h>
#include <sys/tiuser.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mkdev.h>
#include <values.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <sys/ipc.h>
#include <sys/poll.h>
#include <sys/stropts.h>
#include <sac.h>
#include <utmpx.h>

/* listener include files */

#include "lsparam.h"		/* listener parameters		*/
#include "lsfiles.h"		/* listener files info		*/
#include "lserror.h"		/* listener error codes		*/
#include "lsnlsmsg.h"		/* NLPS listener protocol	*/
#include "lssmbmsg.h"		/* MS_NET identifier		*/
#include "lsdbf.h"		/* data base file stuff		*/
#include "listen.h"

/* defines	*/

#define NAMESIZE	(NAMEBUFSZ-1)

#define SPLhi()		Splflag = 1
#define SPLlo()		Splflag = 0

#define GEN	1
#define LOGIN	0

/* global variables	*/

int	NLPS_proc = 0;	/* set if process is a listener child		*/
pid_t	Pid;		/* listener's process ID 			*/
char	*Progname;	/* listener's basename (from argv[0])		*/
static	char Provbuf[PATHSIZE];
char	*Provider = Provbuf;	/* name of transport provider		*/
char	*Netspec = NETSPEC;
char	*Minor_prefix;		/* prefix for minor device names	*/
int	Dbf_entries;		/* number of private addresses in dbf file*/
int	Valid_addrs;		/* number of addresses bound		*/
struct	pollfd *Pollfds;	/* for polling fds			*/
dbf_t	*Dbfhead;		/* Beginning of in-memory database	*/
dbf_t	*Newdbf;		/* Beginning of in-memory database (reread) */
char	*Server_cmd_lines;	/* database space			*/
char	*New_cmd_lines;		/* database space (reread) 		*/
long	Ndesc;			/* Number of per-process file descriptors */
int	Readdb;			/* set to TRUE by SAC_READDB message	*/
struct	netconfig *Netconf;	/* netconfig structure for this network	*/

struct	call_list	Free_call;
struct	call_list	*Free_call_p = &Free_call; /* call free list 	*/
struct	call_list	*Priv_call;	/* call save pending list 	*/

/* FILE DESCRIPTOR MANAGEMENT:
 *
 * The listener uses 6 (sometimes 7) file descriptors:
 *	fd 0:	Originally opened to /dev/null, used to accept incoming calls.
 *	fd 1:	In the parent, a connection to _sacpipe.  Closed in the child
 *		and dup'ed to 0.
 *	fd 2:	In the parent, a connection to _pmpipe.  Dup'ed in the child
 *		to 0.
 *	fd 3:	Originally opened to /dev/null, this file descriptor is 
 *		reserved to open the STREAMS pipe when passing the connection
 *		to a standing server.
 *	fd 4:	Opened to the pid file.  We have to keep it open to keep the
 *		lock active.
 *	fd 5:	Opened to the log file.
 *	fd 6:	Opened to the debug file ONLY when compiled with DEBUGMODE.
 *
 * The remaining file descriptors are available for binding private addresses.
 */

#ifndef DEBUGMODE
#define USEDFDS	6
#else
#define	USEDFDS	7
FILE	*Debugfp;		/* for the debugging file	*/
#endif

int	Acceptfd;		/* to accept connections (fd 0)	*/
int	Sacpipefd;		/* pipe TO sac process (fd 1)	*/
int	Pmpipefd;		/* pipe FROM sac process (fd 2) */
int	Passfd;			/* pipe used to pass FD (fd 3)	*/
int	Pidfd;			/* locked pid file (fd 4)	*/
FILE	*Logfp;			/* for logging listener activity*/

struct	pmmsg	Pmmsg;		/* to respond to SAC		*/
int	State = PM_STARTING;	/* current SAC state		*/
char	Mytag[15];

char	Lastmsg[BUFSIZ];	/* contains last msg logged (by stampbuf) */
int	Logmax = LOGMAX;	/* number of entriet to allow in logfile  */

int	Splflag;		/* logfile critical region flag		  */

static char *badnspmsg = "Bad netspec on command line ( Pathname too long )";
static char *badstart  = "Listener failed to start properly";
static char *nologfile = "Unable to open listener log file during initialization";
static char *usage     = "Usage: listen [ -m minor_prefix ] network_device";
static char *nopmtag   = "Fatal error: Unable to get PMTAG from environment";
static char tzenv[BUFSIZ];

#define TIMEZONE	"/etc/TIMEZONE"
#define TZSTR	"TZ="

void	check_sac_mesg();	/* routine to process messages from sac */
void	rpc_register();		/* routine to register rpc services */
void	rpc_unregister();	/* routine to unregister rpc services */
extern	struct	netconfig	*getnetconfigent();
extern	char	*t_alloc();
extern	void	logexit();
extern	int	t_errno;
extern	int	errno;

#ifndef TRUE
#define	TRUE	1
#define FALSE	0
#endif

static void mod_prvaddr(void);
static void pitchcall(struct call_list *pending, struct t_discon *discon);
static void clr_call(struct t_call *call);
static void trycon(struct call_list *phead, int fd);
static void send_dis(struct call_list *phead, int fd);
static void doevent(struct call_list *phead, int fd);
static void listen(void);
static void rst_signals(void);
static void catch_signals(void);
static void net_open(void);
static void init_files(void);
static void pid_open(void);

int
main(int argc, char **argv)
{
	struct stat buf;
	int ret;
	char scratch[BUFSIZ];
	char log[BUFSIZ];
	char olog[BUFSIZ];
	char *scratch_p = scratch;
	char *mytag_p;
	FILE *fp;
	extern char *getenv();
	char *parse();
	int	c;
	extern	char *optarg;
	extern	int optind;
	int i;
	char	*Mytag_p = Mytag;

	/* Get my port monitor tag out of the environment		*/
	if ((mytag_p = getenv("PMTAG")) == NULL) {
		/* no place to write */
		exit(1);
	}
	strcpy(Mytag, mytag_p);

	/* open log file */
	sprintf(log, "%s/%s/%s", ALTDIR, Mytag_p, LOGNAME);
	sprintf(olog, "%s/%s/%s", ALTDIR, Mytag_p, OLOGNAME);
	if (stat(log, &buf) == 0) {
		/* file exists, try and save it but if we can't don't worry */
		unlink(olog);
		rename(log, olog);
	}
	if ((i = open(log, O_WRONLY|O_CREAT|O_APPEND, 0444)) < 0)
		logexit(1, nologfile);
	/* as stated above, the log file should be file descriptor 5 */
	if ((ret = fcntl(i, F_DUPFD, 5)) != 5)
		logexit(1, nologfile);
	Logfp = fdopen(ret, "a+");

	/* Get my port monitor tag out of the environment		*/
	if ((mytag_p = getenv("PMTAG")) == NULL) {
		logexit(1, nopmtag);
	}
	strcpy(Mytag, mytag_p);

	(void) umask(022);
	Readdb = FALSE;

	if (geteuid() != (uid_t) 0) {
		logmessage("Must be root to start listener");
		logexit(1, badstart);
	}

	while ((c = getopt(argc, argv, "m:")) != EOF) 
		switch (c) {
		case 'm':
			Minor_prefix = optarg;
			break;
		default:
			logexit(1, usage);
			break;
		}

	if ((Netspec = argv[optind]) == NULL) {
		logexit(1, usage);
	}
	if ((Netconf = getnetconfigent(Netspec)) == NULL) {
		sprintf(scratch, "no netconfig entry for <%s>", Netspec);
		logmessage(scratch);
		logexit(1, badstart);
	}
	if (!Minor_prefix)
		Minor_prefix = argv[optind];

	if ((int) strlen(Netspec) > PATHSIZE)  {
		logmessage(badnspmsg);
		logexit(1, badstart);
	}

	/* 
	 * SAC will start the listener in the correct directory, so we
	 * don't need to chdir there, as we did in older versions
	 */

	strcpy(Provbuf, "/dev/");
	strcat(Provbuf, Netspec);

	(void) umask(0);

	init_files();		/* open Accept, Sac, Pm, Pass files	*/
	pid_open();		/* create pid file			*/

#ifdef	DEBUGMODE
	sprintf(scratch, "%s/%s/%s", ALTDIR, Mytag, DBGNAME);
	Debugfp = fopen(scratch, "w");
#endif


#ifdef	DEBUGMODE
	if ((!Logfp) || (!Debugfp)) 
#else
	if (!Logfp)
#endif
		logexit(1, badstart);

/*
 * In case we started with no environment, find out what timezone we're
 * in.  This will get passed to children, so only need to do once.
 */

	if (getenv("TZ") == NULL) {
		fp = fopen(TIMEZONE, "r");
		if (fp) {
			while (fgets(tzenv, BUFSIZ, fp)) {
				if (tzenv[strlen(tzenv) - 1] == '\n')
					tzenv[strlen(tzenv) - 1] = '\0';
				if (!strncmp(TZSTR, tzenv, strlen(TZSTR))) {
					putenv(parse(tzenv));
					break;
				}
			}
			fclose(fp);
		}
		else {
			sprintf(scratch, "couldn't open %s, default to GMT", TIMEZONE);
			logmessage(scratch);
		}
	}

	logmessage("@(#)listen:listen.c	1.19.9.1");

#ifdef	DEBUGMODE
	logmessage("Listener process with DEBUG capability");
#endif

	sprintf(scratch, "Listener port monitor tag: %s", Mytag_p);
	logmessage(scratch);
	DEBUG((9, "Minor prefix: %s  Netspec %s", Minor_prefix, Netspec));

	/* fill in Pmmesg fields that always stay the same */

	Pmmsg.pm_maxclass = MAXCLASS;
	strcpy(Pmmsg.pm_tag, Mytag_p);
	Pmmsg.pm_size = 0;

	/* Find out what state to start in.  If not in env, exit */
	if ((scratch_p = getenv("ISTATE")) == NULL)
		logexit(1, "ERROR: ISTATE variable not set in environment");
	
	if (!strcmp(scratch_p, "enabled")) {
		State = PM_ENABLED;
		logmessage("Starting state: ENABLED");
	}
	else {
		State = PM_DISABLED;
		logmessage("Starting state: DISABLED");
	}

	/* try to get my "basename"		*/
	Progname = strrchr(argv[0], '/');
	if (Progname && Progname[1])
		++Progname;
	else
		Progname = argv[0];

	catch_signals();

	/* 
	 * Allocate memory for private address and file descriptor table 
	 * Here we are assuming that no matter how many private addresses
	 * exist in the system if the system limit is 20 then we will only
	 * get 20 file descriptors
	 */

	Ndesc = ulimit(4,0L);		/* get num of file des on system */

	read_dbf(DB_INIT);
	net_open();			/* init, open, bind names 	*/

	for (i = 3; i < Ndesc; i++)  {	/* leave stdout, stderr open	*/
		fcntl(i, F_SETFD, 1);	/* set close on exec flag*/
	}

	logmessage("Initialization Complete");

	listen();
	return (0);
}


/*
 * pid_open:
 *
 * open pidfile with specified oflags and modes and lock it
 *
 */

static char *pidopenmsg ="Can't create process ID file in home directory";
static char *pidlockmsg ="Can't lock PID file: listener may already be running";

static void
pid_open(void)
{
	int ret;
	unsigned int i;
	char pidstring[20];

	if ((Pidfd = open(PIDNAME, PIDOFLAG, PIDMODE)) == -1)  {
		logmessage(pidopenmsg);
		error(E_CREAT, EXIT | NOCORE | NO_MSG);
	}

	if (lockf(Pidfd, 2, 0L) == -1)  {
		logmessage(pidlockmsg);
		logexit(1, badstart);
	}

	Pid = getpid();
	i = sprintf(pidstring, "%ld", Pid) + 1;
	ftruncate(Pidfd, 0);

	while ((ret = write(Pidfd, pidstring, i)) != i) {
		if (errno == EINTR)
			continue;
		if (ret < 0)
			sys_error(E_PIDWRITE, EXIT);
		else
			error(E_PIDWRITE, EXIT);
	}

}

/*
 * init_files: open initial files for the listener (see FILE DESC MGMT comment)
 */

static char *pmopenmsg = "Can't open pipe to read SAC messages";
static char *sacopenmsg = "Can't open pipe to respond to SAC messages";

static void
init_files(void)
{
	close(0);
        if ((Acceptfd = open("/dev/null", O_RDWR)) != 0) {
		logmessage("Trouble opening /dev/null");
                sys_error(E_SYS_ERROR, EXIT | NOCORE);
	}

	close(1);
	if ((Sacpipefd = open(SACPIPE, O_RDWR|O_NDELAY)) != 1) {
		logmessage(sacopenmsg);
		error(E_CREAT, EXIT | NOCORE | NO_MSG);
	}

	close(2);
	if ((Pmpipefd = open(PMPIPE, O_RDWR|O_NDELAY)) != 2) {
		logmessage(pmopenmsg);
		error(E_CREAT, EXIT | NOCORE | NO_MSG);
	}

	close(3);
	if ((Passfd = dup(Acceptfd)) != 3) {
		logmessage("Trouble duping /dev/null");
                sys_error(E_SYS_ERROR, EXIT | NOCORE);
	}

}
		

/*
 * net_open: open and bind communications channels
 *		The name generation code in net_open, open_bind and bind is, 
 * 		for the	most part, specific to STARLAN NETWORK.  
 *		This name generation code is included in the listener
 *		as a developer debugging aid.
 */

static void
net_open(void)
{
#ifdef	CHARADDR
	char pbuf[NAMEBUFSZ + 1];
#endif	/* CHARADDR	*/
	int i;
	dbf_t *dp;
	char scratch[BUFSIZ];

	DEBUG((9,"in net_open"));

	/* set up free call list and pending connection lists */

	Free_call_p->cl_head = (struct callsave *) NULL;
	Free_call_p->cl_tail = (struct callsave *) NULL;

	/* Pending calls are linked in a structure, one per fild descriptor */
	if ((Priv_call = (struct call_list *) malloc(Ndesc *(sizeof(
				struct call_list)))) == NULL)  
		error(E_MALLOC,NOCORE | EXIT);

	i = 0;
	Valid_addrs = 0;
	/* first do static addrs */
	while ( (i < Dbf_entries) ) {
		dp = &Dbfhead[i];
		if (!(dp->dbf_sflags & DFLAG)) {
			if (add_prvaddr(dp) == 0)
				Valid_addrs++;
		}
		i++;
	}
	i = 0;
	/* second pass for dynamic addrs */
	while ( (i < Dbf_entries) ) {
		dp = &Dbfhead[i];
		if (dp->dbf_sflags & DFLAG) {
			if (add_prvaddr(dp) == 0)
				Valid_addrs++;
		}
		i++;
	}

	sprintf(scratch, "Net opened, %d %s bound, %d fds free", Valid_addrs, 
		(Valid_addrs == 1) ? "address" : "addresses",
		Ndesc-Valid_addrs-USEDFDS);
	logmessage(scratch);
}


/*
 * Following are some general queueing routines.  The call list head contains
 * a pointer to the head of the queue and to the tail of the queue.  Normally,
 * calls are added to the tail and removed from the head to ensure they are
 * processed in the order received, however, because of the possible interruption
 * of an acceptance with the resulting requeueing, it is necessary to have a
 * way to do a "priority queueing" which inserts at the head of the queue for
 * immediate processing
 */

/*
 * queue:
 *
 * add calls to tail of queue
 */


void
queue(head, cp)
struct call_list *head;
struct callsave *cp;
{
	DEBUG((9,"in queue"));
	if (head->cl_tail == (struct callsave *) NULL) {
		cp->c_np = (struct callsave *) NULL;
		head->cl_head = head->cl_tail = cp;
	}
	else {
		cp->c_np = head->cl_tail->c_np;
		head->cl_tail->c_np = cp;
		head->cl_tail = cp;
	}
}


/*
 * pqueue:
 *
 * priority queuer, add calls to head of queue
 */

void
pqueue(head, cp)
struct call_list *head;
struct callsave *cp;
{
	if (head->cl_head == (struct callsave *) NULL) {
		cp->c_np = (struct callsave *) NULL;
		head->cl_head = head->cl_tail = cp;
	}
	else {
		cp->c_np = head->cl_head;
		head->cl_head = cp;
	}
}


/*
 * dequeue:
 *
 * remove a call from the head of queue
 */


struct callsave *
dequeue(head)
struct call_list *head;
{
	struct callsave *ret;

	DEBUG((9,"in dequeue"));
	if (head->cl_head == (struct callsave *) NULL)  {
#ifdef OLD
		DEBUG((9,"cl_head = null"));
		error(E_CANT_HAPPEN, EXIT);
#endif
		DEBUG((9, "NULL return"));
		return((struct callsave *) NULL);
	}
	ret = head->cl_head;
	head->cl_head = ret->c_np;
	if (head->cl_head == (struct callsave *) NULL)
		head->cl_tail = (struct callsave *) NULL;
	return(ret);
}


/*
 * open_bind:
 *
 * open the network and bind the endpoint to 'name'
 * this routine is also used by listen(), so it can't exit
 * under all error conditions: 
 *	if there are no minor devices avaliable in the network driver, 
 * 		open_bind returns -1.  (error message will be logged).
 *	if the open fails because all file descriptors are in use, 
 *		open_bind returns -2.  (no message logged).  This should 
 *		only happen when too many private addresses are specified.
 *	if the bind fails, open_bind returns -3  (no message logged).  This
 *		happens when a duplicate address is bound, and the message
 *		should be logged by the routine that calls open_bind.
 * All other errors cause an exit.
 *
 * If clen is zero, transport provider picks the name and these
 * routines (open_bind and bind) ignore name and qlen -- 
 * this option is used when binding a name for accepting a connection 
 * (not for listening.)  You MUST supply a name, qlen and clen when
 * opening/binding a name for listening.
 *
 * Assumptions: driver returns ENXIO when all devices are allocated.
 */

int
open_bind(name, qlen, clen, conp, adrp)
char *name;
int qlen;
int clen;
unsigned int *conp;
char **adrp;
{
	int fd;
	int ret;

	DEBUG((9,"in open_bind, qlen=%d clen=%d conp=%d",qlen,clen,conp));
	while ((fd = t_open(Provider, NETOFLAG, NULL)) < 0) {
		if (t_errno == TSYSERR) {
			switch (errno) {
			case EINTR:
				continue;
			case EMFILE:
				return(-2);
				break;
			case ENXIO:
			case ENOSR:
			case ENOSPC:
			case EAGAIN:
				tli_error(E_FD1OPEN, CONTINUE);
				logmessage("No network minor devices (ENXIO/ENOSR)");
				return(-1);
				break;
			}
			DEBUG((9,"problem in t_open"));
			tli_error(E_FD1OPEN, EXIT);
		}
	}

	ret = bind(fd, name, qlen, clen, adrp);
	DEBUG((9, "bind returns %d", ret));

	if (ret < 0) {
		t_close(fd);
		return(-3);
	}
	if (conp)
		*conp = ret;
	return(fd);
}


int
bind(fd, name, qlen, clen, ap)
int fd;
char *name;
int qlen;
int clen;
char **ap;
{
	struct t_bind *req = (struct t_bind *)0;
	struct t_bind *ret = (struct t_bind *)0;
	char	*p, *q;
	unsigned int	retval;
	extern void	nlsaddr2c();
	extern int	memcmp();
	extern int	errno;

#ifdef	CHARADDR
	char pbuf[NAMEBUFSZ + 1];
#endif
	char scratch[BUFSIZ];

	DEBUG((9,"in bind, fd = %d, clen = %d", fd, clen));
	
	if (clen)  {
		errno = t_errno = 0;
		while (!(req = (struct t_bind *)t_alloc(fd,T_BIND,T_ALL)) ) {
			if ((t_errno != TSYSERR) || (errno != EAGAIN))
				tli_error( E_T_ALLOC, EXIT);
			else
				tli_error( E_T_ALLOC, CONTINUE);
		}

		errno = t_errno = 0;
		while (!(ret = (struct t_bind *)t_alloc(fd,T_BIND,T_ALL)) ) {
			if ((t_errno != TSYSERR) || (errno != EAGAIN))
				tli_error( E_T_ALLOC, EXIT);
			else
				tli_error( E_T_ALLOC, CONTINUE);
		}

		if (clen > (int) req->addr.maxlen)  {
			sprintf(scratch,"Truncating name size from %d to %d", 
				clen, req->addr.maxlen);
			logmessage(scratch);
			clen = req->addr.maxlen;
		}

		if (clen == -1) {
			req->addr.len = 0;
		}
		else {
			(void)memcpy(req->addr.buf, name, clen);
			req->addr.len = clen;
		}
		req->qlen = qlen;

#if defined(CHARADDR) && defined(DEBUGMODE)
		(void)memcpy(pbuf, req->addr.buf, req->addr.len);
		pbuf[req->addr.len] = (char)0;
		DEBUG((3,"bind: fd=%d, logical name=%c%s%c, len=%d",
			fd, '\"',pbuf, '\"', req->addr.len));
#endif	/* CHARADDR  && DEBUGMODE */


#if defined(CHARADDR) && defined(DEBUGMODE)
		(void)memcpy(pbuf, req->addr.buf, req->addr.len);
		pbuf[req->addr.len] = (char)0;
		DEBUG((3,"bind: fd=%d, address=%c%s%c, len=%d",
			fd, '\"',pbuf, '\"', req->addr.len));
#endif	/* CHARADDR  && DEBUGMODE */


	}

	if (t_bind(fd, req, ret))  {
		DEBUG((1,"t_bind failed; t_errno %d errno %d", t_errno, errno));
		if (qlen)	/* starup only */
			tli_error(E_T_BIND, EXIT | NOCORE);
		/* here during normal service */
		if ((t_errno == TNOADDR) || ((t_errno == TSYSERR) && (errno == EAGAIN))) {
			/* our name space is all used up */
			tli_error(E_T_BIND, CONTINUE);
			t_close(fd);
			if (clen)  {
				if ( t_free((char *)req, T_BIND) )
					tli_error(E_T_FREE, EXIT);
				if ( t_free((char *)ret, T_BIND) )
					tli_error(E_T_FREE, EXIT);
			}
			return(-1);
		}
		/* otherwise, irrecoverable error */
		tli_error(E_T_BIND, EXIT | NOCORE);
	}
	DEBUG((9, "t_bind succeeded"));

	if (clen)  {
		retval = ret->qlen;
		if (clen == -1) {
			/* dynamic address */
			*ap = (char *) malloc(((ret->addr.len) << 1) + 3);
			if (*ap) {
				(*ap)[0] = '\\';
				(*ap)[1] = 'x';
				nlsaddr2c(*ap+2,ret->addr.buf,(int)ret->addr.len);
			}
		}
		else if ( (ret->addr.len != req->addr.len) ||
		     (memcmp( req->addr.buf, ret->addr.buf, (int) req->addr.len)) )  {
			p = (char *) malloc(((ret->addr.len) << 1) + 1);
			q = (char *) malloc(((req->addr.len) << 1) + 1);
			if (p && q) {
				nlsaddr2c(p, ret->addr.buf, (int)ret->addr.len);
				nlsaddr2c(q, req->addr.buf, (int)req->addr.len);
				sprintf(scratch, "Requested address \\x%s", q);
				logmessage(scratch);
				sprintf(scratch, "Actual address    \\x%s", p);
				logmessage(scratch);
				free(p);
				free(q);
			}
			DEBUG((9, "failed to bind requested address"));
			t_unbind(fd);
			t_close(fd);
			if ( t_free((char *)req, T_BIND) )
				tli_error(E_T_FREE, EXIT);
			if ( t_free((char *)ret, T_BIND) )
				tli_error(E_T_FREE, EXIT);
			return(-1);
		}

		if ( t_free((char *)req, T_BIND) )
			tli_error(E_T_FREE, EXIT);

		if ( t_free((char *)ret, T_BIND) )
			tli_error(E_T_FREE, EXIT);
		return(retval);
	}
	return((unsigned int) 0);
}


/*
 * catch_signals:
 *		Ignore some, catch the rest. Use SIGTERM to kill me.
 */

sigset_t Oset;
struct sigaction Sigterm;
struct sigaction Sigcld;

static void
catch_signals(void)
{
	sigset_t sset;
	sigset_t eset;
	struct sigaction sigact;
	extern void sigterm();

	(void) sigfillset(&sset);
	(void) sigdelset(&sset, SIGTERM);
	(void) sigdelset(&sset, SIGCLD);
	(void) sigprocmask(SIG_SETMASK, &sset, &Oset);

	sigact.sa_flags = 0;
	sigact.sa_handler = sigterm;
	sigact.sa_mask = sset;
	sigaction(SIGTERM, &sigact, &Sigterm);
	sigact.sa_flags = SA_NOCLDWAIT;
	sigact.sa_handler = SIG_IGN;
	sigact.sa_mask = sset;
	sigaction(SIGCLD, &sigact, &Sigcld);
}


/*
 * rst_signals:
 *		After forking but before exec'ing a server,
 *		reset all signals to original setting.
 */

static void
rst_signals(void)
{
	struct sigaction sigact;

	sigaction(SIGTERM, &Sigterm, NULL);
	sigaction(SIGCLD, &Sigcld, NULL);
	sigprocmask(SIG_SETMASK, &Oset, NULL);
}


/*
 * sigterm:	Clean up and exit.
 */

void
sigterm()
{
	extern char *shaddr;
	extern char *sh2addr;

	error(E_SIGTERM, EXIT | NORMAL | NOCORE);	/* calls cleanup */
}


/*
 * listen:	listen for and process connection requests.
 */

static char *dbfnewdmsg = "Using new data base file";

static void
listen(void)
{
	int	i;
	dbf_t	*dbp	= Dbfhead;
	struct	pollfd	*sp;
	struct		call_list *phead; /* pending head */

	DEBUG((9,"in listen, tag %s", Pmmsg.pm_tag));
	
	if ((Pollfds = (struct pollfd *) malloc(Ndesc * sizeof(struct pollfd)))
			== NULL)
		error(E_MALLOC,NOCORE | EXIT);

	/* setup poll structures for sac messages and private addresses */
	sp = Pollfds;
	sp->fd = Pmpipefd;
	sp->events = POLLIN;
	sp->revents = 0;
	sp++;
	for (dbp = Dbfhead; dbp && dbp->dbf_svc_code; dbp++) {
		if (dbp->dbf_fd >= 0) {
			sp->fd = dbp->dbf_fd;
			DEBUG((9, "adding %d to poll struct", dbp->dbf_fd));
			sp->events = POLLIN;
			sp->revents = 0;
			sp++;
		}
	}
	errno = t_errno = 0;

	for (;;) {
		DEBUG((9,"listen(): TOP of loop"));

		/* +1 for Pmpipefd */
		if (poll(Pollfds, Valid_addrs + 1, -1) < 0) {
			if (errno == EINTR)
				continue;
			/* poll error */
			sys_error(E_POLL, EXIT);
		}
		else {
			/* incoming request or message */
			for (i = 0, sp = Pollfds; i < Valid_addrs + 1; i++, sp++) {
				switch (sp->revents) {
				case POLLIN:
					if (sp->fd == Pmpipefd) {
						DEBUG((9,"sac message received"));
						check_sac_mesg();
					}
					else {
						DEBUG((9,"Connection requested "));
						phead = ((sp->fd) + Priv_call);
						doevent(phead, (sp->fd));
						if (State == PM_ENABLED)
							trycon(phead, (sp->fd));
						else
							send_dis(phead, (sp->fd));
					}
					break;
				case 0:
					break;
				/* distinguish the various errors for the user */
				case POLLERR:
					logmessage("poll() returned POLLERR");
					error(E_SYS_ERROR, EXIT | NO_MSG);
				case POLLHUP:
					logmessage("poll() returned POLLHUP");
					error(E_SYS_ERROR, EXIT | NO_MSG);
				case POLLNVAL:
					logmessage("poll() returned POLLNVAL");
					error(E_SYS_ERROR, EXIT | NO_MSG);
				case POLLPRI:
					logmessage("poll() returned POLLPRI");
					error(E_SYS_ERROR, EXIT | NO_MSG);
				case POLLOUT:
					logmessage("poll() returned POLLOUT");
					error(E_SYS_ERROR, EXIT | NO_MSG);
				default:
					logmessage("poll() returned unrecognized event");
					error(E_SYS_ERROR, EXIT | NO_MSG);
				}
				sp->revents = 0;
			}
		}

		if (Readdb) {
			DEBUG((9,"dbf file has been modified"));
			logmessage("Re-reading database");
			/* have to close an fd because read_dbf needs it */
			close(Acceptfd);
			if (!read_dbf(DB_REREAD)) {
				/* MUST re-open Acceptfd to insure it is free later */
				dup(Passfd);
				mod_prvaddr();
			}
			else {
				dup(Passfd);
				logmessage(dbfnewdmsg);
			}
			Readdb = FALSE;
		}
	}
}


/*
 * check_sac_mesg:	check the pipe to see if SAC has sent a message
 */

void
check_sac_mesg()
{
	int	length;
	struct	sacmsg sacmsg;

	DEBUG((9, "in check_sac_mesg..."));
	
	/* read all messages out of pipe */
	while ((length = read(Pmpipefd, &sacmsg, sizeof(sacmsg))) != 0) {
		if (length < 0) {
			if (errno == EINTR)
				continue;
			DEBUG((9, "read of _pmpipe failed"));
			return;
		}

		switch (sacmsg.sc_type) {
		case SC_STATUS:
			DEBUG((9, "Got SC_STATUS message"));
			Pmmsg.pm_type = PM_STATUS;
			Pmmsg.pm_state = State;
			break;
		case SC_ENABLE:
			DEBUG((9, "Got SC_ENABLE message"));
			if (State != PM_ENABLED)
				logmessage("New state: ENABLED");
			Pmmsg.pm_type = PM_STATUS;
			State = PM_ENABLED;
			Pmmsg.pm_state = PM_ENABLED;
			break;
		case SC_DISABLE:
			DEBUG((9, "Got SC_DISABLE message"));
			if (State != PM_DISABLED)
				logmessage("New state: DISABLED");
			Pmmsg.pm_type = PM_STATUS;
			State = PM_DISABLED;
			Pmmsg.pm_state = PM_DISABLED;
			break;
		case SC_READDB:
			DEBUG((9, "Got SC_READDB message"));
			Readdb = TRUE;
			Pmmsg.pm_type = PM_STATUS;
			Pmmsg.pm_state = State;
			break;
		default:
			DEBUG((9, "Got UNKNOWN message"));
			Pmmsg.pm_type = PM_UNKNOWN;
			Pmmsg.pm_state = State;
			logmessage("Received unknown message from sac -- ignored");
			break;
		}
		DEBUG((9, "Responding with state %d", Pmmsg.pm_state));
		while (write(Sacpipefd, &Pmmsg, sizeof(Pmmsg)) != sizeof(Pmmsg)) {
			if (errno == EINTR)
				continue;
			DEBUG((9, "sanity response failed"));
			break;
		}
	}
}


/*
 * doevent:	handle an asynchronous event
 */

static void
doevent(struct call_list *phead, int fd)
{
	static struct t_discon *disc;
	struct callsave *current;
	struct t_call *call;
	char scratch[BUFSIZ];

	DEBUG((9, "in doevent"));
	switch (t_look(fd)) {
	case 0:
		sys_error(E_POLL, EXIT);
		/* no return */
	case T_LISTEN:
	DEBUG((9, "case t_listen "));
		current = dequeue(Free_call_p);
		call = current->c_cp;
		if (t_listen(fd, call) < 0) {
			tli_error(E_T_LISTEN, CONTINUE);
			clr_call(call);
			queue(Free_call_p, current);
			return;
		}
		queue(phead, current);
		DEBUG((9, "incoming call seq # %d", call->sequence));
		break;
	case T_DISCONNECT:
	DEBUG((9, "case t_disconnect"));
		if (disc == NULL) {
			while (!(disc = (struct t_discon *)t_alloc(fd, T_DIS, T_ALL)) ) {
		   		if (t_errno == TBADF)
					DEBUG((9,"listen - fd not transport end point"));
				if ((t_errno != TSYSERR) || (errno != EAGAIN))
					tli_error(E_T_ALLOC, EXIT);
				else  
					tli_error(E_T_ALLOC, CONTINUE);
			}
		}
		if (t_rcvdis(fd, disc) < 0) {
			tli_error(E_T_RCVDIS, EXIT);
			/* no return */
		}
		sprintf(scratch, "Disconnect on fd %d, seq # %d", fd, disc->sequence);
		logmessage(scratch);
		DEBUG((9, "incoming disconnect seq # %d", disc->sequence));
		pitchcall(phead, disc);
		break;
	default:
	DEBUG((9, "case default"));
		tli_error(E_T_LOOK, CONTINUE);
		break;
		
	}
}

/*
 * send_dis:	send a disconnect
 *		called when we are in state PM_DISABLED
 */

static void
send_dis(struct call_list *phead, int fd)
{
	struct t_call *call;
	struct callsave *current;
	char	scratch[BUFSIZ];

	DEBUG((9, "sending disconnect"));
	while (!EMPTYLIST(phead)) {
		current = dequeue(phead);
		call = current->c_cp;
		if (t_snddis(fd, call) < 0) {
			if (t_errno == TLOOK) {
				DEBUG((9, "collision during snddis"));
				pqueue(phead, current);
				return;
			}
			else
				tli_error(E_T_SNDDIS, CONTINUE);
		}
		sprintf(scratch, "Incoming call while disabled: fd %d, seq %d", fd, call->sequence);
		logmessage(scratch);
		clr_call(call);
		queue(Free_call_p, current);
	}
	return;
}


/*
 * trycon:	try to accept a connection
 */

static void
trycon(struct call_list *phead, int fd)
{
	struct callsave *current;
	struct t_call *call;
	int i;
	pid_t pid;
	dbf_t *dbp;
	char scratch[BUFSIZ];
	extern dbf_t *getentry();

	DEBUG((9, "in trycon"));
	while (!EMPTYLIST(phead)) {
		current = dequeue(phead);
		call = current->c_cp;

		if ((dbp = getentry(fd)) == NULL) {
			sprintf(scratch, "No service bound to incoming fd %d: call disconnected", fd);
			logmessage(scratch);
			t_snddis(fd, call);
			clr_call(call);
			queue(Free_call_p, current);
			continue;
		}

		if (dbp->dbf_flags & DBF_OFF) {
			sprintf(scratch, "Request for service on fd %d denied: disabled", fd);
			logmessage(scratch);
			t_snddis(fd, call);
			clr_call(call);
			queue(Free_call_p, current);
			continue;
		}

		DEBUG((9, "try to accept #%d", call->sequence));
		SPLhi();
		close(Acceptfd);
		if ((Acceptfd = open_bind(NULL, 0, 0, (unsigned int *) 0, NULL)) != 0) {
			error(E_OPENBIND, CONTINUE);
			clr_call(call);
			queue(Free_call_p, current);
			continue;	/* let transport provider generate disconnect */
		}
		SPLlo();
		if (t_accept(fd, Acceptfd, call) < 0) {
			if (t_errno == TLOOK) {
				t_close(Acceptfd);
				SPLhi();
				if (dup(Passfd) != 0)
					logmessage("Trouble duping fd 0");
				SPLlo();
				logmessage("Incoming call during t_accept -- queueing current call");
				DEBUG((9, "save call #%d", call->sequence));
				pqueue(phead, current);
				return;
			}
			else {
				t_close(Acceptfd);
				SPLhi();
				if (dup(Passfd) != 0)
					logmessage("Trouble duping fd 0");
				SPLlo();
				tli_error(E_T_ACCEPT, CONTINUE);
				clr_call(call);
				queue(Free_call_p, current);
				continue;
			}
		}

		sprintf(scratch, "Connect: fd %d, svctag %s, seq %d, type %s",
			fd, dbp->dbf_svc_code, call->sequence,
			(dbp->dbf_sflags & PFLAG) ? "passfd" : "exec");
		logmessage(scratch);

		DEBUG((9, "Accepted call %d", call->sequence));

		if (dbp->dbf_sflags & PFLAG) {

			close(Passfd);

			if (pushmod(Acceptfd, dbp->dbf_modules)) {
				sprintf(scratch, "Could not push modules: %s", dbp->dbf_modules);
				logmessage(scratch);
				goto cleanup;
			}

			/* doconfig needs a file descriptor, so use Passfd */
			DEBUG((9, "Running doconfig on %s", dbp->dbf_svc_code));
			if ((i = doconfig(Acceptfd, dbp->dbf_svc_code, NOASSIGN|NORUN)) != 0) {
				DEBUG((9, "doconfig exited with code %d", i));
				sprintf(scratch, "doconfig failed on line %d of script %s", i, dbp->dbf_svc_code);
				logmessage(scratch);
				goto cleanup;
			}

			/* open pipe to pass fd through */
			if ((Passfd = open(dbp->dbf_cmd_line, O_WRONLY)) < 0) {
				/* bad pipe? */
				sprintf(scratch,"Open failed: %s", dbp->dbf_cmd_line);
				logmessage(scratch);
				goto cleanup;
			}

			if (ioctl(Passfd, I_SENDFD, Acceptfd) < 0) {
				/* clean up call, log error */
				sprintf(scratch,"Passfd failed: %s", dbp->dbf_cmd_line);
				logmessage(scratch);
			}
cleanup:
			/* clean up this call */
			clr_call(call);
			t_close(Acceptfd);
			close(Passfd);
			Acceptfd = open("/dev/null", O_RDWR);
			Passfd = dup(Acceptfd);
			queue(Free_call_p, current);
		}
		else {
			if ((pid = fork()) < 0)
				log(E_FORK_SERVICE);
			else if (!pid) {
				setpgrp();
				/* so log files are correct */
				Pid = getpid();

				if (senviron(call))  {
					logmessage("Can't expand server's environment");
				}

				start_server(Acceptfd, dbp);
#ifdef	COREDUMP
				abort();
#endif
				exit(1); /* server failed, don't log */
					/* no return */
			}	
			/* only parent gets here */
			clr_call(call);
			t_close(Acceptfd);
			queue(Free_call_p, current);
			SPLhi();
			if (dup(Passfd) != 0)
				logmessage("Trouble duping fd 0");
			SPLlo();
		}
	}
}

/*
 * common code to  start a server process (for any service)
 * The first argument in argv is the full pathname of server. 
 * Before exec-ing the server, the caller's
 * logical address, opt and udata are addded to the environment. 
 */

static char homeenv[BUFSIZ];
static char pathenv[BUFSIZ];

int
start_server(netfd, dbp)
int netfd;
dbf_t *dbp;
{
	char	*path;
	char	**argvp;
	extern	char **environ;
	extern	char **mkdbfargv();
	struct passwd *pwdp;
	struct	group *grpp;
	char	msgbuf[256];
	int	i;


	argvp = mkdbfargv(dbp);
	path = *argvp;

	/* set up stdout and stderr before pushing optional modules	*/
	/* this child doesn't need access to _sacpipe and _pmpipe	*/

	(void) close(Sacpipefd);
	(void) close(Pmpipefd);

	if (dbp->dbf_flags & DBF_UTMP) {
		pid_t	tmp;
		struct	stat	sbuf;
		char	device[20];
		char	dummy[PMTAGSIZE + 1];
		struct	utmpx utline;

		/* 
		 * create a utmpx entry --
		 * we do an extra fork here to make init this process's
		 * parent.  this lets init clean up the utmpx entry when
		 * this proc dies.
		 *
		 * the utmpx routines need a file descriptor!
		 */

		DEBUG((9, "Creating a utmpx entry for this service "));
		if ((tmp = fork()) < 0) {
			logmessage("Can't fork to create utmpx entry");
			exit(2);
		}
		if (tmp)
			exit(0);	/* kill parent */

		/* 
		 * child continues processing, creating utmp and exec'ing
		 * the service
		 */

		setpgrp();
		if (fstat(0, &sbuf) < 0) {
			logmessage("Stat failed on fd 0: no line field "
			    "available for utmpx entry");
			*device = '\0';
		}
		else {
			if (minor(sbuf.st_rdev) < 100)
				sprintf(device, "%.9s%02d", Minor_prefix,
				    minor(sbuf.st_rdev));
			else
				sprintf(device, "%.8s%03d", Minor_prefix,
				    minor(sbuf.st_rdev));
			DEBUG((9, "Device: %s", device));
		}
		/*
		 * prepend a "." so this can be distinguished as a "funny"
		 * utmpx entry that may never get a DEAD_PROCESS entry in
		 * the wtmpx file.
		 */
		sprintf(dummy, ".%s", Mytag);
		/* XXX - utmp - fix login name length */
		strncpy(utline.ut_user, dummy, sizeof (utline.ut_user) - 1);
		sprintf(utline.ut_id, "ls%c%c", SC_WILDC, SC_WILDC);
		strncpy(utline.ut_line, device, sizeof (utline.ut_line) - 1);
		utline.ut_pid = getpid();
		utline.ut_type = USER_PROCESS;
		utline.ut_exit.e_termination = 0;
		utline.ut_exit.e_exit = 0;
		utline.ut_xtime = (time_t) time((time_t *)0);
		makeutx(&utline);
	}

	if (dup(0) != 1 || dup(0) != 2) {
		logmessage("Dup of fd 0 failed");
		exit(2); /* server, don't log */
	}


	if (pushmod(netfd, dbp->dbf_modules)) {
		logmessage("Can't push server's modules: exit");
		exit(2); /* server, don't log */
	}

	rst_signals();
	
	DEBUG((9, "Running doconfig on %s", dbp->dbf_svc_code));
	if ((i = doconfig(Acceptfd, dbp->dbf_svc_code, 0)) != 0) {
		DEBUG((9, "doconfig exited with code %d", i));
		sprintf(msgbuf, "doconfig failed on line %d of script %s", i, dbp->dbf_svc_code);
		logmessage(msgbuf);
		exit(2);
	}

	if ((pwdp = getpwnam(dbp->dbf_id)) == NULL)  {
		sprintf(msgbuf, "Missing or bad passwd entry for <%s>",dbp->dbf_id);
		logmessage(msgbuf);
		exit(2); /* server, don't log */
	}		

	if (setgid(pwdp->pw_gid)) {
		if ((grpp = getgrgid(pwdp->pw_gid)) == NULL) {
			sprintf(msgbuf, "No group entry for %ld", pwdp->pw_gid);
			logmessage(msgbuf);
			exit(2); /* server, don't log */
		}
		sprintf(msgbuf, "Cannot set group id to %s", grpp->gr_name);
		logmessage(msgbuf);
		exit(2); /* server, don't log */
	}

	if (setuid(pwdp->pw_uid)) {
		sprintf(msgbuf, "Cannot set user id to %s", dbp->dbf_id);
		logmessage(msgbuf);
		exit(2); /* server, don't log */
	}

	if (chdir(pwdp->pw_dir)) {
                sprintf(msgbuf, "Cannot chdir to %s", pwdp->pw_dir);
                logmessage(msgbuf);
                exit(2); /* server, don't log */
        }


	DEBUG((9, "New uid %ld New gid %ld", getuid(), getgid()));

	sprintf(homeenv, "HOME=%s", pwdp->pw_dir);
	putenv(homeenv);
	if (pwdp->pw_uid)
		sprintf(pathenv, "PATH=/usr/bin:");
	else
		sprintf(pathenv, "PATH=/usr/sbin:/usr/bin");
	putenv(pathenv);

	endpwent();

	execve(path, argvp, environ);

	/* exec returns only on failure!		*/

	logmessage("ERROR: could not exec server");
	sys_error(E_SYS_ERROR, CONTINUE);
	return(-1);
}


/*
 * senviron:	Update environment before exec-ing the server:
 *		The callers logical address is placed in the
 *		environment in hex/ascii character representation.
 *
 * Note:	no need to free the malloc'ed buffers since this process
 *		will either exec or exit.
 */

static char provenv[2*PATHSIZE];
static char prefenv[2*PATHSIZE];

int
senviron(call)
struct t_call *call;
{
	char *p;
	extern void nlsaddr2c();
	extern char *getenv();


/*
 * The following code handles the case where the listener was started with
 * no environment.  If so, supply a reasonable default path.  Parent already
 * set TZ on startup if it wasn't, so don't need to do it here.
 */

	if (getenv("PATH") == NULL)
		putenv("PATH=/usr/sbin:/usr/bin");

	if ((p = (char *)malloc(((call->addr.len)<<1) + 18)) == NULL)
		return(-1);
	strcpy(p, NLSADDR);
	strcat(p, "=");
	nlsaddr2c(p + strlen(p), call->addr.buf, (int)call->addr.len);
	DEBUG((7, "Adding %s to server's environment", p));
	putenv(p);

	if ((p = (char *)malloc(((call->opt.len)<<1) + 16)) == NULL)
		return(-1);
	strcpy(p, NLSOPT);
	strcat(p, "=");
	nlsaddr2c(p + strlen(p), call->opt.buf, (int)call->opt.len);
	DEBUG((7, "Adding %s to server's environment", p));
	putenv(p);

	p = provenv;
	strcpy(p, NLSPROVIDER);
	strcat(p, "=");
	strcat(p, Netspec);
	DEBUG((7, "Adding %s to environment", p));
	putenv(p);

	/*
	 * MPREFIX is NEW for SVR4.0.  It tells the nlps_server what to use
	 * as a minor device prefix.  THIS SHOULD BE DOCUMENTED!
	 */
	p = prefenv;
	strcpy(p, "MPREFIX");
	strcat(p, "=");
	strcat(p, Minor_prefix);
	DEBUG((7, "Adding %s to environment", p));
	putenv(p);

	if ((p = (char *)malloc(((call->udata.len)<<1) + 20)) == NULL)
		return(-1);
	strcpy(p, NLSUDATA);
	strcat(p, "=");
	if ((int)call->udata.len >= 0)
		nlsaddr2c(p + strlen(p), call->udata.buf, (int)call->udata.len);
	putenv(p);
	return (0);
}


/*
 * parse:	Parse TZ= string like init does for consistency
 *		Work on string in place since result will
 *		either be the same or shorter.
 */

char *
parse(s)
char *s;
{
	char *p;
	char *tp;
	char scratch[BUFSIZ];
	int delim;

	tp = p = s + strlen("TZ=");	/* skip TZ= in parsing */
	if ((*p == '"') || (*p == '\'')) {
		/* it is quoted */
		delim = *p++;
		for (;;) {
			if (*p == '\0') {
				/* etc/TIMEZONE ill-formed, go without TZ */
				sprintf(scratch, "%s ill-formed", TIMEZONE);
				logmessage(scratch);
				strcpy(s, "TZ=");
				return(s);
			}
			if (*p == delim) {
				*tp = '\0';
				return(s);
			}
			else {
				*tp++ = *p++;
			}
		}
	}
	else { /* look for comment or trailing whitespace */
		for ( ; *p && !isspace(*p) && *p != '#'; ++p)
			;
		/* if a comment or trailing whitespace, trash it */
		if (*p) {
			*p = '\0';
		}
		return(s);
	}
}


/*
 * clr_call:	clear out a call structure
 */

static void
clr_call(struct t_call *call)
{
	call->sequence = 0;
	call->addr.len = 0;
	call->opt.len = 0;
	call->udata.len = 0;
	memset(call->addr.buf, 0, (int)call->addr.maxlen);
	memset(call->opt.buf, 0, (int)call->opt.maxlen);
	memset(call->udata.buf, 0, (int)call->udata.maxlen);
}


/*
 * pitchcall: remove call from pending list
 */

static void
pitchcall(struct call_list *pending, struct t_discon *discon)
{
	struct callsave *p, *oldp;

	DEBUG((9, "pitching call, sequence # is %d", discon->sequence));
	if (EMPTYLIST(pending)) {
		discon->sequence = -1;
		return;
	}
	p = pending->cl_head;
	oldp = (struct callsave *) NULL;
	while (p) {
		if (p->c_cp->sequence == discon->sequence) {
			if (oldp == (struct callsave *) NULL) {
				pending->cl_head = p->c_np;
				if (pending->cl_head == (struct callsave *) NULL) {
					pending->cl_tail = (struct callsave *) NULL;
				}
			}
			else if (p == pending->cl_tail) {
				oldp->c_np = p->c_np;
				pending->cl_tail = oldp;
			}
			else {
				oldp->c_np = p->c_np;
			}
			clr_call(p->c_cp);
			queue(Free_call_p, p);
			discon->sequence = -1;
			return;
		}
		oldp = p;
		p = p->c_np;
	}
	logmessage("received disconnect with no pending call");
	discon->sequence = -1;
	return;
}

/*
 * add_prvaddr:  open and bind the private address specified in the database
 *               entry passed into the routine.  Update the maxcon and fd 
 *               entries in the database structure
 *
 *	This routine is very sloppy with malloc'ed memory, but addresses
 *	shouldn't ever change enough for this to matter.
 */

int
add_prvaddr(dbp)
dbf_t *dbp;
{
	extern	char	*t_alloc();
	int	j;
	struct	call_list *temp_pend;
	struct	callsave *tmp;
	char	scratch[BUFSIZ];
	int	bindfd;
	extern	struct	netbuf *stoa();
	char	str[NAMEBUFSZ];
	char	*lstr = str;
	struct	netbuf	netbuf;
	int	maxcon;
	char	*ap;
	int	clen;

	DEBUG((9,"in add_prvaddr, addr %s, svc %s",
		(dbp->dbf_sflags & DFLAG) ? "DYNAMIC" : dbp->dbf_prv_adr,
		dbp->dbf_svc_code)); 
	netbuf.buf = NULL;
	netbuf.maxlen = 0;
	netbuf.len = 0;
	if (!(dbp->dbf_sflags & DFLAG)) {
		strcpy(lstr, dbp->dbf_prv_adr);

		/* call stoa - convert from rfs address to netbuf */

		if (stoa(lstr, &netbuf) == (struct netbuf *)NULL)  {
			DEBUG((9,"stoa returned null, errno = %d\n",errno));
			error(1, E_MALLOC);
			return(-1);
		}
		clen = netbuf.len;
	}
	else {
		clen = -1;
	}
	if ((bindfd = open_bind(netbuf.buf, MAXCON, clen, &maxcon, &ap)) < 0) {
		switch (bindfd) {
		case -1:
			return(-1);
			break;
		case -2:
			sprintf(scratch, "  Service %s ignored: out of file descriptors", dbp->dbf_svc_code);
			logmessage(scratch);
			return(-1);
			break;
		case -3:
			sprintf(scratch, "  Service %s ignored: unable to bind requested address", dbp->dbf_svc_code);
			logmessage(scratch);
			return(-1);
			break;
		default:
			error(E_OPENBIND, EXIT);	
		}
	}
	if (clen == -1) {
		sprintf(scratch,"Service %s: fd %d dynamic addr %s", dbp->dbf_svc_code, bindfd, ap);
		dbp->dbf_prv_adr = ap;
	}
	else {
		sprintf(scratch,"Service %s: fd %d addr %s", dbp->dbf_svc_code, bindfd, dbp->dbf_prv_adr);
	}
	logmessage(scratch);
	rpc_register(dbp);
	temp_pend = Priv_call + bindfd;
	dbp->dbf_fd = bindfd;
	dbp->dbf_maxcon = maxcon;
	temp_pend->cl_head = (struct callsave *) NULL;
	temp_pend->cl_tail = (struct callsave *) NULL;
	for (j=0; j < maxcon; ++j)  {
		if ((tmp = (struct callsave *) malloc(sizeof(struct callsave))) == NULL)  {
			error (E_MALLOC, NOCORE | EXIT);
		}
		if ((tmp->c_cp = (struct t_call *) t_alloc(bindfd, T_CALL,
				T_ALL)) == NULL) {
			tli_error(E_T_ALLOC,EXIT);
		}
		queue(Free_call_p, tmp);	
	}
	return(0);
}

/*
 * mod_prvaddr -- after re-reading the database, take appropriate action for
 *		  new, deleted, or changed addresses.
 */
static void
mod_prvaddr(void)
{
	dbf_t	*entry_p;
	dbf_t	*oldentry_p;
	char	scratch[BUFSIZ];
	dbf_t	*svc_code_match();
	int	bound;
	struct	pollfd	*sp;

	DEBUG((9, "in mod_prvaddr..."));
	/* 
	 * for each entry in the new table, check for a svc code match.
	 * if there is a svc code match and the address matches, all we
	 * need to do is update the new table.  if the addresses are
	 * different, we need to remove the old one and replace it.
	 */
	for (entry_p = Newdbf; entry_p && entry_p->dbf_svc_code; entry_p++) {
		if ((oldentry_p = svc_code_match(entry_p->dbf_svc_code)) != NULL) {
			/* matched svc code.  see if address matches. */
			DEBUG((9, "MATCHED service code"));
			if ((strcmp(oldentry_p->dbf_prv_adr, entry_p->dbf_prv_adr) == 0) || ((oldentry_p->dbf_sflags & DFLAG) && (entry_p->dbf_sflags & DFLAG))) {
				DEBUG((9, "SAME addresses, old %s, new %s",
				oldentry_p->dbf_prv_adr, entry_p->dbf_prv_adr));
				/* update new table with fd, set old fd to -1 */
				DEBUG((9, "Old fd %d",  oldentry_p->dbf_fd));
				entry_p->dbf_fd = oldentry_p->dbf_fd;
				entry_p->dbf_maxcon = oldentry_p->dbf_maxcon;
				oldentry_p->dbf_fd = -1;
				if ((oldentry_p->dbf_sflags & DFLAG) && (entry_p->dbf_sflags & DFLAG)) {
					entry_p->dbf_prv_adr = oldentry_p->dbf_prv_adr;
				}
				if (entry_p->dbf_fd != -1) {
					sprintf(scratch, "Service %s: fd %d addr %s",
						entry_p->dbf_svc_code, entry_p->dbf_fd,
						entry_p->dbf_prv_adr);
					logmessage(scratch);
				}
				if ((oldentry_p->dbf_version != entry_p->dbf_version) || (oldentry_p->dbf_prognum != entry_p->dbf_prognum)) {
					rpc_unregister(oldentry_p);
					rpc_register(entry_p);
				}
			}
		}
	}

	/* now unbind the remaining addresses in the old table (fd != -1) */

	for (oldentry_p = Dbfhead; oldentry_p && oldentry_p->dbf_svc_code; oldentry_p++) {
		if (oldentry_p->dbf_fd != -1) {
			DEBUG((9, "deleting %s",  oldentry_p->dbf_svc_code));
			if (del_prvaddr(oldentry_p) == 0)
				Valid_addrs--;
		}
	}

	/* now bind all of the new addresses (fd == -1) */
	/* 
	 * this tries to bind any addresses that failed to bind successfully
	 * when the address changed.  This means that if a service is moved to
	 * an address that is being deleted, the first attempt to bind it will
	 * fail, the old address will be removed, and this bind will succeed
	 */

	/* first the static addrs */
	for (entry_p = Newdbf; entry_p && entry_p->dbf_svc_code; entry_p++) {
		if ((entry_p->dbf_fd == -1) && (!(entry_p->dbf_sflags & DFLAG))) {
			DEBUG((9, "adding %s",  entry_p->dbf_svc_code));
			if (add_prvaddr(entry_p) == 0)
				Valid_addrs++;
		}
	}
	/* then the dynamic addrs */
	for (entry_p = Newdbf; entry_p && entry_p->dbf_svc_code; entry_p++) {
		if ((entry_p->dbf_fd == -1) && (entry_p->dbf_sflags & DFLAG)) {
			DEBUG((9, "adding %s",  entry_p->dbf_svc_code));
			if (add_prvaddr(entry_p) == 0)
				Valid_addrs++;
		}
	}

	/* free old database, set up new pollfd table, and we're done */

	free(Dbfhead);
	free(Server_cmd_lines);
	Dbfhead = Newdbf;
	Newdbf = NULL;
	Server_cmd_lines = New_cmd_lines;
	sprintf(scratch, "Re-read complete, %d %s bound, %d fds free", Valid_addrs, 
		(Valid_addrs == 1) ? "address" : "addresses",
		Ndesc-Valid_addrs-USEDFDS);
	logmessage(scratch);

	/* Pollfds[0] is for _pmpipe */
	sp = &Pollfds[1];
	for (entry_p = Dbfhead; entry_p && entry_p->dbf_svc_code; entry_p++) {
		if (entry_p->dbf_fd >= 0) {
			sp->fd = entry_p->dbf_fd;
			DEBUG((9, "adding %d to poll struct", entry_p->dbf_fd));
			sp->events = POLLIN;
			sp->revents = 0;
			sp++;
		}
	}
}

/*
 * unbind the address, close the file descriptor, and free call structs
 */

int
del_prvaddr(dbp)
dbf_t	*dbp;
{
	struct	callsave	*tmp;
	struct	call_list	*q;
	struct	t_call		*call;
	int	i;
	char	scratch[BUFSIZ];

	DEBUG((9, "in del_prvaddr..."));
	rpc_unregister(dbp);
	if (dbp->dbf_fd < 0) 
		return -1;

	q = Priv_call + dbp->dbf_fd;
	i = 0;

	/* delete pending calls */
	while ((tmp = dequeue(q)) != NULL) {
		i++;
		call = tmp->c_cp;
		t_snddis(dbp->dbf_fd, call);
		t_free((char *)call, T_CALL);
		free(tmp);
	}

	/* delete free call structs we don't need */
	for ( ; i < dbp->dbf_maxcon; i++) {
		tmp = dequeue(Free_call_p);
		t_free((char *)tmp->c_cp, T_CALL);
		free(tmp);
	}

	t_unbind(dbp->dbf_fd);
	t_close(dbp->dbf_fd);
	sprintf(scratch, "Unbind %s: fd %d addr %s", dbp->dbf_svc_code, 
		dbp->dbf_fd, dbp->dbf_prv_adr);
	logmessage(scratch);
	dbp->dbf_fd = -1;
	return 0;
}


/* 
 * look through the old database file to see if this service code matches
 * one already present
 */

dbf_t *
svc_code_match(new_code)
char	*new_code;
{
	dbf_t	*dbp;

	for (dbp = Dbfhead; dbp && dbp->dbf_svc_code; dbp++) {
		if (strcmp(dbp->dbf_svc_code, new_code) == 0)
			return(dbp);
	}
	return((dbf_t *)NULL);
}


/*
 * register an rpc service with rpcbind
 */

void
rpc_register(dbp)
dbf_t *dbp;
{
	char	str[NAMEBUFSZ];
	char	scratch[BUFSIZ];
	char	*lstr = str;
	struct	netbuf	netbuf;
	extern	struct	netbuf *stoa();
	extern	int	errno;

	DEBUG((9, "in rpc_register"));
	if (dbp->dbf_prognum == -1 || dbp->dbf_version == -1)
		/* not an rpc service */
		return;

	rpc_unregister(dbp);
	netbuf.buf = NULL;
	netbuf.maxlen = 0;
	netbuf.len = 0;
	strcpy(lstr, dbp->dbf_prv_adr);
	if (stoa(lstr, &netbuf) == (struct netbuf *)NULL)  {
		DEBUG((9,"stoa returned null, errno = %d\n",errno));
		error(1, E_MALLOC);
		return;
	}
	if (rpcb_set(dbp->dbf_prognum, dbp->dbf_version, Netconf, &netbuf)) {
		sprintf(scratch,"  registered with rpcbind, prognum %d version %d", dbp->dbf_prognum, dbp->dbf_version);
		logmessage(scratch);
	}
	else {
		logmessage("rpcb_set failed, service not registered with rpcbind");
	}
	return;
}


/*
 * unregister an rpc service with rpcbind
 */

void
rpc_unregister(dbp)
dbf_t *dbp;
{
	DEBUG((9, "in rpc_unregister"));
	if (dbp->dbf_prognum == -1 || dbp->dbf_version == -1)
		/* not an rpc service */
		return;
	(void) rpcb_unset(dbp->dbf_prognum, dbp->dbf_version, Netconf);
}