root/usr/src/cmd/listen/listen.c
/*
 * 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 2014 Garrett D'Amore
 */
/*
 * 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   */

/*
 * 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 TZFILE  "/etc/default/init"
#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(TZFILE, "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",
                            TZFILE);
                        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);
                                        break;
                                case POLLHUP:
                                        logmessage("poll() returned POLLHUP");
                                        error(E_SYS_ERROR, EXIT | NO_MSG);
                                        break;
                                case POLLNVAL:
                                        logmessage("poll() returned POLLNVAL");
                                        error(E_SYS_ERROR, EXIT | NO_MSG);
                                        break;
                                case POLLPRI:
                                        logmessage("poll() returned POLLPRI");
                                        error(E_SYS_ERROR, EXIT | NO_MSG);
                                        break;
                                case POLLOUT:
                                        logmessage("poll() returned POLLOUT");
                                        error(E_SYS_ERROR, EXIT | NO_MSG);
                                        break;
                                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 */
                break;
        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/default/init ill-formed, go without TZ */
                                sprintf(scratch, "%s ill-formed", TZFILE);
                                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);
}