/
umud/DOC/
umud/DOC/examples/
umud/DOC/internals/
umud/DOC/wizard/
umud/MISC/
umud/MISC/dbchk/
umud/RWHO/rwhod/
/*
	Copyright (C) 1991, Marcus J. Ranum. All rights reserved.
*/

#ifndef	lint
static	char	RCSid[] = "$Header: /usr/users/mjr/hacks/umud/RCS/net.c,v 1.9 91/09/19 12:56:06 mjr Exp $";
#endif

/* configure all options BEFORE including system stuff. */
#include	"config.h"


#ifdef	NOSYSTYPES_H
#include	<types.h>
#else
#include	<sys/types.h>
#endif
#include	<errno.h>
extern	int	errno;
#include	<stdio.h>
#include	<ctype.h>
#include	<varargs.h>
#include	<fcntl.h>
#include	<sys/file.h>
#include	<sys/time.h>
#include	<sys/signal.h>
#include	<sys/socket.h>
#include	<netinet/in.h>

#include	"mud.h"
#include	"vars.h"

#ifndef	FD_SET
#define NBBY    8
#define NFDBITS (sizeof(long) * NBBY)
#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#define FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)      bzero((char *)(p), sizeof(*(p)))
#endif

/*
as always the network code comprises the lioness' share of the MUD.
this module supports connection maintenance and buffering for
berkeley-style tcp/ip sockets. each connection has a pair of
buffers allocated, and once it has been authenticated as a given
player's connection, an entry into a player-name hash table is
made for faster access.
the manner in which buffers are shutdown is bizarre, but it is
all handled in io_sync() to prevent writes into buffers that have
been freed. this way buffers *WILL* hang around 'till the end of
the play/run.
*/


/* you can change PHSIZ, but don't mess with PHMAG */
#define	PHSIZ	31		/* width of internal name hash table */


/* Iob flags */
#define	IOBOK	001	/* OK/logged in player connection */
#define	IOBKILL	002	/* kill this IOB at sync time - it's dead */
#define	IOBERR	004	/* ignore this IOB - it is f***ed up. */
#define	IOBFL	010	/* Too many chars. Flush it. */

typedef	struct {
	int		flg;		/* flags */
	char		*host;		/* name of player's host */
	time_t		ltim;		/* last input time */
	time_t		ctim;		/* connect time */
	char		who[MAXOID];	/* player object ID */
	int		fd;		/* file desc */
	char		*obuf;		/* malloced output buffer */
	char		*op;		/* output buf ptr */
	char		*ibuf;		/* malloced input buffer */
	char		*ip;		/* input buf ptr */
	int		ic;		/* input byte cnt */
} Iob;




/*
player-name to Iob resolution map
an entry is made in this when the player is authenticated by the
login(), and is subsequently used to quickly map names to Iobs.
the mapping is destroyed when the iob is dropped by iobdrop().
don't mess with this code - it's icky.
*/
typedef struct	pmap	{
	Iob	*iob;
	struct	pmap	*n;
} Pmap;
static	Pmap	*pmaptab[PHSIZ];



static	Iob		**iobtab;	/* active Iob table */
static	Iob		**WHOlist;	/* Ordered WHO list */
static	int		WHOlast = -1;	/* Last active iob in WHOlist */
static	Iob		*lastiob = (Iob *)0;
static	int		onewrt = 0;	/* optimization */
static	int		iobtabsiz;	/* top bound of Iob table */
static	int		curriob;	/* highest Iob in use */
static	fd_set		liveset;	/* set of live Iobs for select() */
static	struct		timeval	timo;	/* timeout value */
static	int		serfd;		/* inter server service port */
static	int		plyfd;		/* main player service port */
struct	sockaddr_in	addr;		/* address of newly connected */
static	int		sport = NET_SRVPRT;	/* service port # */
static	int		pport = NET_PLYPRT;	/* play port # */
static	int		net_initted = 0;

static	int		nxtwhoiobptr;	/* used for programmed Iob traverse */


/*
set up the iob tables, iob maps, sockets, the whole bit
*/
io_init()
{
	int	x;

	signal(SIGPIPE,SIG_IGN);
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	timo.tv_sec = cron_quantum;
	timo.tv_usec = 0;

	/*
	if you ain't got dtablesize(), fudge this with whatever
	your systems max file descriptor value is. erring on the
	high side will waste a little memory is all.
	*/
	iobtabsiz = getdtablesize();

	/* initialize the Iob hash table */
	iobtab = (Iob **)malloc((unsigned)(sizeof(Iob *) * iobtabsiz));
	if(iobtab == (Iob **)0)
		return(1);

	WHOlist = (Iob **)malloc((unsigned)(sizeof(Iob *) * iobtabsiz));
	if(WHOlist == (Iob **)0)
		return(1);

	/* zero iob table */
	for(x = 0; x < iobtabsiz; x++)
		iobtab[x] = (Iob *)0;

	/* zero player to Iob hash table */
	for(x = 0; x < PHSIZ; x++)
		pmaptab[x] = (Pmap *)0;



	/* OPEN INTER SERVER FILE DESCRIPTOR AND BIND IT */
	if((serfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
		return(1);

	x = 1;
	setsockopt(serfd,SOL_SOCKET,SO_REUSEADDR,&x,sizeof(x));

	addr.sin_port = htons(sport);
	if(bind(serfd,(struct sockaddr *)&addr,sizeof(addr))) {
		logf("cannot bind socket: ",(char *)-1,"\n",0);
		return(1);
	}

	if(listen(serfd,5) == -1) {
		logf("cannot listen at socket: ",(char *)-1,"\n",0);
		return(1);
	}



	/* OPEN PLAYER ACCESS FILE DESCRIPTOR AND BIND IT */
	if((plyfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
		return(1);

	x = 1;
	setsockopt(plyfd,SOL_SOCKET,SO_REUSEADDR,&x,sizeof(x));

	addr.sin_port = htons(pport);
	if(bind(plyfd,(struct sockaddr *)&addr,sizeof(addr))) {
		logf("cannot bind socket: ",(char *)-1,"\n",0);
		return(1);
	}

	if(listen(plyfd,5) == -1) {
		logf("cannot listen at socket: ",(char *)-1,"\n",0);
		return(1);
	}

	/* VITAL! */
	FD_SET(plyfd,&liveset);
	FD_SET(serfd,&liveset);
	curriob = plyfd;

	net_initted++;
	return(0);
}




/*
disconnect an Iob (low level)
*/
static	void
iobdrop(ip)
Iob	*ip;
{
	int	x;
	Pmap	*pp;
	Pmap	*pr;

	if(ip == (Iob *)0)
		return;

	/* Blow it out of WHO list */

	for(x = 0; x <= WHOlast; x++){
		if(WHOlist[x] == ip){
			if(x < WHOlast)
				bcopy(&WHOlist[x+1],&WHOlist[x],
					(WHOlast - x)*sizeof(Iob *));
			else
				WHOlist[x] = (Iob *)0;
			WHOlast--;
			break;
		}
	}

	if (ip->who[0]) {
		plogf("DISCONNECT %s on %d from %s\n", ip->who, ip->fd,
		      ip->host ? ip->host : "unknown");
#ifdef	USE_RWHO
		rwhocli_userlogout(ip->who);
#endif
	} else
		plogf("DISCONNECT %d from %s\n", ip->fd, 
		      ip->host ? ip->host : "unknown");
	
	for(x = 0; x < iobtabsiz; x++) {
		if(iobtab[x] == ip) {
			iobtab[x] = (Iob *)0;
			break;
		}
	}

	/* unlink player map hash. this is somewhat convoluted. */
	for(x = 0; x < PHSIZ; x++) {
		pp = pmaptab[x];
		while(pp != (Pmap *)0) {
			if(pp->iob == ip && pp == pmaptab[x]) {
				pr = pp;
				pmaptab[x] = pp->n;
				free((mall_t)pr);
				break;
			} else {
				if(pp->n != (Pmap *)0 && pp->n->iob == ip) {
					pr = pp->n;
					pp->n = pp->n->n;
					free((mall_t)pr);
					break;
				}
			}
			pp = pp->n;
		}
	}


	/* adjust top iob in use count */
	if(ip->fd == curriob)
		curriob--;

	/* mark it dead in the live Iob set */
	FD_CLR(ip->fd,&liveset);

	/* final cleanup */
	(void)shutdown(ip->fd,2);
	(void)close(ip->fd);

	if(ip->host != (char *)0)
		(void)free((mall_t)ip->host);

	(void)free((mall_t)ip->ibuf);
	(void)free((mall_t)ip->obuf);

	/*
	this is done to cause a coredump in case someone's code is
	so stupid as to write into an Iob that has been closed down
	*/
	ip->ibuf = ip->obuf = (char *)0;
	(void)free((mall_t)ip);
}




/*
tokenize out a single line of text (destructive)
this function handles a buffer of player input, returning a sequence of
lines - it may move data around in the buffer, and may alter the content
of the buffer as it does so. this handles cases where a line may be
broken into two packets by the network - it re-assembles them efficiently.
*/
static	char	*
iobgl(i)
Iob	*i;
{
	char	*op;
	int	ic;

	while(i->ic > 0 && (*i->ip == '\n' || *i->ip == '\r')) {
		*i->ip++ = '\0';
		i->ic--;
	}

	if((ic = i->ic) <= 0) {
		i->ip = i->ibuf;
		i->ic = 0;
		return((char *)0);
	}

	op = i->ip;

	while(1) {
		if(*i->ip == '\r' && *(i->ip + 1) == '\n' && ic > 1) {
			*i->ip++ = '\0';
			*i->ip++ = '\0';
			if((i->ic = ic - 2) < 1)
				i->ip = i->ibuf;

			if(i->flg & IOBFL){ /* If we're supposed to flush */
				i->flg &= ~IOBFL;
				return((char *)0);
			} else {
				return(op);
			}
		}

		if((*i->ip == '\n' || *i-> ip == '\r') && ic > 0) {
			*i->ip++ = '\0';
			if((i->ic = --ic) < 1)
				i->ip = i->ibuf;
			if(i->flg & IOBFL){ /* If we're supposed to flush */
				i->flg &= ~IOBFL;
				return((char *)0);
			} else {
				return(op);
			}
		}

		/* nothing left in buffer, but no newline */
		if(--ic <= 0) {
			if(i->ic >= MUDBUF - 1){ /* Too Much! */
				i->ip = i->ibuf;
				i->ic = 0;

				/* Are we already flushing? */

				if(i->flg & IOBFL){
					return((char *)0);
				} else {
					/* set up to flush until EOLN */

					i->flg |= IOBFL;
					*(i->ibuf + (MUDBUF-1)) = '\0';
					return(i->ibuf);
				}
			}

			/* shift down */
			bcopy(op,i->ibuf,i->ic);

			/* resync */
			i->ip = i->ibuf;
			/* i->ic = 0; Why is this here? -- Andrew */
			return((char *)0);
		}

		if(!isprint(*i->ip))
			*i->ip = ' ';
		i->ip++;
	}
}



/*
flush a single Iob
doing the select there significantly increases the system call overhead
of the MUD, as opposed to doing nonblocking I/O. but that's life.
*/
static	int
iobflush(ip)
Iob	*ip;
{
	static		char	buff_over[] = "\n<buffer overrun>\n";
	fd_set		wfd;
	struct	timeval	wtimo;
	int		xx = ip->op - ip->obuf;
	int		wr;

	if(ip->flg & IOBERR)
		return(1);

	FD_ZERO(&wfd);
	FD_SET(ip->fd,&wfd);

	wtimo.tv_sec = 0;
	wtimo.tv_usec = 800;

	if(select(ip->fd + 1,(fd_set *)0,&wfd,(fd_set *)0,&wtimo) != 1 || 
		!FD_ISSET(ip->fd,&wfd)) {

		if(xx < MUDBUF)
			return(0);

		/* scrubba buffa */
		bcopy(buff_over,ip->obuf,sizeof(buff_over));
		ip->op = ip->obuf + sizeof(buff_over);

		return(0);
	}


	if((wr = write(ip->fd,ip->obuf,xx)) != xx) {
		/* partial write. god damn */
		if(wr > 0) {
			bcopy(ip->obuf + wr,ip->obuf,xx - wr);
			ip->op -= wr;
			return(0);
		}

		if(wr < 0 && errno == EWOULDBLOCK) {
			if(xx >= MUDBUF) {
				/* scrubba buffa */
				bcopy(buff_over,ip->obuf,sizeof(buff_over));
				ip->op = ip->obuf + sizeof(buff_over);
			}
			return(0);
		}

		/* fukit */
		ip->flg |= IOBERR;
		return(1);
	}
	ip->op = ip->obuf;
	return(0);
}




/* VARARGS1 */
void
say(who,va_alist)
char	*who;
va_dcl
{
	Pmap	*p;
	char	*s;
	va_list	ap;

	if(who == (char *)0 || who[0] == '\0' || !net_initted)
		return;

	for(p = pmaptab[objid_hash(who,PHSIZ)];p != (Pmap *)0; p = p->n) {

		/* wrong guy */
		if((p->iob->flg & IOBERR) || strcmp(p->iob->who,who))
			continue;

		if(lastiob != p->iob) {
			lastiob = p->iob;
			onewrt++;
		}

		va_start(ap);
		while((s = va_arg(ap,char *)) != (char *)0) {
			while(*s) {
				if((p->iob->op - p->iob->obuf) > MUDBUF - 1) {
					if(iobflush(p->iob))
						goto dropthru;
				} else {
					*p->iob->op++ = *s++;
				}
			}
		}
dropthru:
		va_end(ap);
	}
}




#ifdef CONNONLY
int
playerconn(who)
char	*who;
{
	Pmap	*p;

	if(who == (char *)0 || who[0] == '\0' || !net_initted)
		return(0);

	for(p = pmaptab[objid_hash(who,PHSIZ)];p != (Pmap *)0; p = p->n) {
		if((p->iob->flg & IOBERR) || strcmp(p->iob->who,who))
			/* wrong guy */
			continue;
		else
			return(1);
	}
	return(0);
}
#endif




void
io_logoff(who)
char	*who;
{
	Pmap	*p;

	if(who == (char *)0 || who[0] == '\0' || !net_initted)
		return;

	p = pmaptab[objid_hash(who,PHSIZ)];
	while(p != (Pmap *)0) {
		if(strcmp(p->iob->who,who)) {
			p = p->n;
			continue;
		}
		p->iob->flg |= IOBKILL;
		onewrt = 2;
		p = p->n;
	}
}




/* VARARGS1 */
void
iobsay(ip,va_alist)
Iob	*ip;
va_dcl
{
	char	*s;
	va_list	ap;

	if(ip->flg & IOBERR)
		return;

	if(lastiob != ip) {
		lastiob = ip;
		onewrt++;
	}

	va_start(ap);
	while((s = va_arg(ap,char *)) != (char *)0) {
		while(*s) {
			if((ip->op - ip->obuf) > MUDBUF - 1) {
				if(iobflush(ip))
					return;
			} else {
				*ip->op++ = *s++;
			}
		}
	}
	va_end(ap);
}




/* 
flush the connected (valid) Iobs
*/
void
io_sync()
{
	int	n;
	time_t	now = (time_t)0;

	if(!onewrt || !net_initted)
		return;

	/* WARNING!!! the calls to goodbye() actually may make more
	Iob writes as a result of a player hangup! Do NOT do much
	in goodbye!() or madness may result. */
	if(onewrt == 1 && lastiob != (Iob *)0) {
		if(iobflush(lastiob)) {
			if(lastiob->flg & IOBOK)
				goodbye(lastiob->who);
			iobdrop(lastiob);
			return;
		}

		if(lastiob->flg & IOBERR || lastiob->flg & IOBKILL) {
			if(lastiob->flg & IOBOK)
				goodbye(lastiob->who);
			iobdrop(lastiob);
		}
		return;
	}

	for(n = 0; n < iobtabsiz; n++) {
		if(iobtab[n] != (Iob *)0) {
			if(iobtab[n]->op > iobtab[n]->obuf) {
				if(iobflush(iobtab[n])) {
					if(iobtab[n]->flg & IOBOK)
						goodbye(iobtab[n]->who);
					iobdrop(iobtab[n]);
					continue;
				}
			}

			if(iobtab[n]->flg & IOBKILL || iobtab[n]->flg & IOBERR) {
				if(iobtab[n]->flg & IOBOK)
					goodbye(iobtab[n]->who);
				iobdrop(iobtab[n]);
				continue;
			}

			/* timeout logins */
			if(!(iobtab[n]->flg & IOBOK)) {
				if(now == (time_t)0)
					time(&now);
				if(now - iobtab[n]->ctim > 80)
					iobdrop(iobtab[n]);
			}
		}
	}

	onewrt = 1;
	lastiob = (Iob *)0;
}




/*
wrapper around the player authentication routine. said routine will
return a 1 if the login is valid, a 0 if it is not. if the login is
OK, we then fiddle some Iob values and make a hash-table map entry.
if login() returns -1, then we are to close the connection.
*/
static	void
io_dologin(bp,line)
Iob	*bp;
char	*line;
{
	Pmap	*pm;
	int	hv;

	hv = login(line,bp->who);

	if(hv == 0) {
		iobsay(bp,"Either that object does not exist, or the password was incorrect.\n",(char *)0);
		/* logf("badlogin: ",line,"\n",(char *)0); */
		plogf("badlogin: %s on %d from %s\n", line, bp->fd,
		      bp->host ? bp->host : "unknown");
		return;
	}

	if(hv == -1) {
		plogf("killconnection: '%s' on %d from %s\n", line, bp->fd,
		      bp->host ? bp->host : "unknown");
		bp->flg |= IOBKILL;
		onewrt = 2;
		return;
	}

	bp->flg |= IOBOK;

	/* Stuff this Iob into the WHO list */

	WHOlist[++WHOlast] = bp;

	/* now add a pointer to the character's name in the Iob hash */
	pm = (Pmap *)malloc(sizeof(Pmap));
	if(pm == (Pmap *)0)
		fatal("out of memory building new connection\n",(char *)0);

	pm->iob = bp;
	pm->n = pmaptab[(hv = objid_hash(bp->who,PHSIZ))];
	pmaptab[hv] = pm;

	plogf("CONNECT %s on %d from %s\n", bp->who, bp->fd,
	      bp->host ? bp->host : "unknown");
	welcome(bp->who);
#ifdef	USE_RWHO
	rwhocli_userlogin(bp->who,ut_name(bp->who),bp->ctim);
#endif
}



/*
main I/O loop - listens for new connections, accepts them, validates
them, reads input, and dispatches it.
*/
io_loop()
{
	fd_set	redy;
	fd_set	xcpt;
	Iob	*bp;
	int	n;
	int	seld;
	int	rd;
	char	*lp;
	time_t	now;
	static	char	*log_connect();
	char	*host;

	if(!net_initted)
		return(-1);

	/* sleep quantum may have changed */
	timo.tv_sec = cron_quantum;

	bcopy(&liveset,&redy,sizeof(redy));
	bcopy(&liveset,&xcpt,sizeof(xcpt));

	if((seld = select(curriob + 1,&redy,(fd_set *)0,&xcpt,&timo)) < 0) {
		if(errno != EINTR) {
			logf("select in loop failed: ",(char *)-1,"\n",0);
			return(1);
		}
	}

	/* yawn */
	if(seld <= 0)
		return(0);

	/* start the clock */
	(void)time(&now);

	/* new SERVER TO SERVER connection */
	if(FD_ISSET(serfd,&redy)) {

		n = accept(serfd,(struct sockaddr *)0,(int *)0);

		/* xact_in() handles *everything* including closing fd */
		if(n != -1) {
#ifdef	SO_LINGER
#ifdef        hpux
/*
*  HP-UX (release 7.0 B on a 9000/330, at least) claims to default to no-
*    lingering and, in any case, has a different syntax for passing the
*    linger value to setsockopt().  Don't use it.
*                                                    --- warlock
*/
#undef        SO_LINGER
#endif        /*  hpux  */
			struct	linger	ling;
	
			ling.l_onoff = 0;
			ling.l_linger = 0;
			setsockopt(n,SOL_SOCKET,SO_LINGER,&ling,sizeof(ling));
#endif
			host = log_connect(n,"server");
			if(host != (char *)0)	/* Toss the host in this case */
				free(host);
			xact_in(n);
		}
	}


	/* new PLAYER connection */
	if(FD_ISSET(plyfd,&redy)) {
		n = accept(plyfd,(struct sockaddr *)0,(int *)0);

		if(n != -1) {
#ifdef	SO_LINGER
			struct	linger	ling;
	
			ling.l_onoff = 0;
			ling.l_linger = 0;
			setsockopt(n,SOL_SOCKET,SO_LINGER,&ling,sizeof(ling));
#endif
			host = log_connect(n, "user");

			if(fcntl(n,F_SETFL,FNDELAY)) {
				logf("cannot set NDELAY: ",(char *)-1,"\n",0);
				goto mainloop;
			}

			bp = (Iob *)malloc(sizeof(Iob));
			if(bp == (Iob *)0) {
				logf("cannot alloc Iob: ",(char *)-1,"\n",0);
				return(-1);
			}
			bp->host = host;
			bp->flg = 0;
			bp->who[0] = '\0';
			bp->fd = n;
			bp->ctim = now;

			if((bp->ibuf = (char *)malloc(MUDBUF)) == (char *)0)
				return(-1);
			bp->ip = bp->ibuf;
			bp->ic = 0;

			if((bp->obuf = (char *)malloc(MUDBUF)) == (char *)0)
				return(-1);
			bp->op = bp->obuf;

			if(n > curriob)
				curriob = n;

			/* enter it into our live set */
			FD_SET(n,&liveset);
			iobtab[n] = bp;

			/* I added this next little block so I could display
			a file before the character logs in. Ed Hand 6/26/91*/
			{
				FILE  *cfl;
				char  cbuf[BUFSIZ];

				if((cfl = fopen("NEWS/connect.txt","r")) != (FILE *)0)
				{
					while(fgets(cbuf,sizeof(cbuf),cfl) != (char *)0)
						iobsay(bp,cbuf,(char *)0);
					(void)fclose(cfl);
				}
			}
			iobsay(bp,"c[onnect] objectid password\n",(char *)0);
		}
	}

mainloop:
	/* check input on existing fds. */
	for(n = 0; n <= curriob; n++) {
		if((bp = iobtab[n]) != (Iob *)0 && FD_ISSET(n,&redy)) {
			rd = read(bp->fd,bp->ibuf + bp->ic,MUDBUF - bp->ic - 1);
			if(rd <= 0) {
				bp->flg |= IOBERR | IOBKILL;
				onewrt = 2;
				continue;
			}

			/* increment count of bytes in buffer */
			bp->ic += rd;

			/* adjust last active time */
			bp->ltim = now;

			/* process input based on state of connection */
			while((lp = iobgl(bp)) != (char *)0) {
				if(bp->flg & IOBOK)
					run(bp->who,bp->who,lp,0,(char **)0,1);
				else
					io_dologin(bp,lp);
			}
			continue;
		}

		/* lastly, check exceptions in case of no input */
		if(FD_ISSET(n,&xcpt)) {
			if(n == serfd)
				fatal("server service fd died!!\n",(char *)0);
			if(n == plyfd)
				fatal("player service fd died!!\n",(char *)0);

			/* default case */
			if(bp != (Iob *)0) {
				bp->flg |= IOBERR | IOBKILL;
				onewrt = 2;
			}
			continue;
		}
	}

	/* e! */
	return(0);
}




/* ARGSUSED */
cmd__netconfig(argc,argv,who,aswho)
int	argc;
char	*argv[];
char	*who;
char	*aswho;
{
	static	char	*nactm = "network layer is already active.\n";
	static	char	*badp = "invalid port number.\n";

	/* configure service port */
	if(!strcmp(argv[1],"playport")) {
		int	tmpx;

		if(net_initted) {
			logf(nactm,(char *)0);
			return(1);
		}

		if(argc < 3 || (tmpx = atoi(argv[2])) <= 0) {
			logf(badp,(char *)0);
			return(1);
		}
		pport = tmpx;
		logf("player port is #",argv[2],"\n",(char *)0);
		return(0);
	}

	/* configure service port */
	if(!strcmp(argv[1],"servport")) {
		int	tmpx;

		if(net_initted) {
			logf(nactm,(char *)0);
			return(1);
		}

		if(argc < 3 || (tmpx = atoi(argv[2])) <= 0) {
			logf(badp,(char *)0);
			return(1);
		}
		sport = tmpx;
		logf("server port is #",argv[2],"\n",(char *)0);
		return(0);
	}

	/* set time-out of select (to match quantum?) */
	if(!strcmp(argv[1],"looptime")) {
		int	tmpx;

		if(argc < 3 || (tmpx = atoi(argv[2])) <= 0) {
			logf(badp,(char *)0);
			return(1);
		}
		timo.tv_sec = tmpx;
		logf("loop timeout is ",argv[2]," seconds\n",(char *)0);
		return(0);
	}

	if(!strcmp(argv[1],"help")) {
		say(who,argv[0]," playport port-number\n",(char *)0);
		say(who,argv[0]," servport port-number\n",(char *)0);
		say(who,argv[0]," looptime seconds\n",(char *)0);
		return(0);
	}

	logf("_netconfig: I don't understand ",argv[1],"\n",(char *)0);
	return(0);
}

static
cmd_WHO__format(buffer,flg,name,curtime,ltime,objname,host)
char	*buffer;
int	flg;
char	*name;
char	*objname;
time_t	curtime;
time_t	ltime;
char	*host;
{
	int ct_hours, ct_mins, ct_secs, lt_hours, lt_mins, lt_secs;
	char oidb[MAXOID+3];

	ct_hours = curtime / 3600;
	ct_mins = (curtime % 3600) / 60;
	ct_secs = curtime % 60;

	lt_hours = ltime / 3600;
	lt_mins = (ltime % 3600) / 60;
	lt_secs = ltime % 60;

	if(host == (char *)0)
		host = "unknown";

	if(flg){	/* Wiz WHO thang */
		oidb[0] = '(';
		strncpy(oidb+1,objname,MAXOID);
		strcat(oidb,")");
		sprintf(buffer,
			"%-17.17s  %3d:%02d:%02d %3d:%02d:%02d %-18.18s %-18.18s\n",
			name,
			ct_hours, ct_mins, ct_secs,
			lt_hours, lt_mins, lt_secs,
			oidb,host);

	} else {	/* Regular WHO */
		sprintf(buffer,"%-17.17s  %3d:%02d:%02d %3d:%02d:%02d (%s)\n",
			name,
			ct_hours, ct_mins, ct_secs,
			lt_hours, lt_mins, lt_secs,
			objname);
	}
}


/* TCP version of WHO */
/* ARGSUSED */
cmd_WHO(argc,argv,who,aswho)
int	argc;
char	*argv[];
char	*who;
char	*aswho;
{
	int	x;
	int	jj;
	int	wiz;
	time_t	now;
	char	xuf[180];

	if(!net_initted)
		return(1);

	wiz = ut_flagged(aswho,var_wiz);
	(void)time(&now);
	say(who,"Player Name          On For    Idle\n",(char *)0);

	if(argc < 2) {
		for(x = 0; x <= WHOlast; x++) {
			cmd_WHO__format(xuf,wiz,
				ut_name(WHOlist[x]->who),
				(now - WHOlist[x]->ctim),
				(now - WHOlist[x]->ltim),
				WHOlist[x]->who,
				WHOlist[x]->host);
			  say(who,xuf,(char *)0);
		}
		say(who,"There are ",itoa(WHOlast + 1,xuf),
			" players connected.\n",(char *)0);

		return(0);
	}

	for(jj = 1; jj < argc; jj++ ) {
		char	*na;

		for(x = 0; x <= WHOlast; x++) {
			na = ut_name(WHOlist[x]->who);
			if(na != (char *)0 && matchstr(na,argv[jj],0) != 0) {
				cmd_WHO__format(xuf,wiz,
					ut_name(WHOlist[x]->who),
					(now-WHOlist[x]->ctim),
					(now-WHOlist[x]->ltim),
					WHOlist[x]->who,
					WHOlist[x]->host);
				  say(who,xuf,(char *)0);
			}
		}
		say(who,"There are ",itoa(WHOlast + 1,xuf),
			" players connected.\n",(char *)0);
	}
	return(0);
}




/* reset pointer to live Iobs for programmed traverse */
void
io_rstnxtwho()
{
	nxtwhoiobptr = 0;
}




/* programmed traverse of object-IDs of logged-in players */
char
*io_nxtwho(timp)
time_t	*timp;
{
	if(nxtwhoiobptr > curriob || !net_initted)
		return((char *)0);

	while(nxtwhoiobptr <= curriob) {
		if(iobtab[nxtwhoiobptr] != (Iob *)0 &&
		(iobtab[nxtwhoiobptr]->flg & IOBOK) &&
		(iobtab[nxtwhoiobptr]->flg & (IOBKILL|IOBERR)) == 0 &&
		iobtab[nxtwhoiobptr]->who[0] != '\0') {
			if(timp != (time_t *)0)
				*timp = iobtab[nxtwhoiobptr]->ctim;
			return(iobtab[nxtwhoiobptr++]->who);
		}

		nxtwhoiobptr++;
	}
	return((char *)0);
}

/* Log (via logf) the essential statistics of a new connection. */
#include	<netdb.h>
static	char	*
log_connect(fd, desc)
int	fd;
char	*desc;
{
	struct sockaddr_in	sin;
	extern char		*inet_ntoa();
	char			*t;
	char			*name;
	int			sin_len;
#ifndef	NO_HUGE_RESOLVER_CODE
	struct hostent		*hp;
#endif
	
	sin_len = sizeof(sin);
	if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) {
		plogf("ACCEPT [%s] getpeername failed! on %d\n",
		      desc, fd);
		return((char *)0);
	}

#ifndef	NO_HUGE_RESOLVER_CODE
	hp = gethostbyaddr((char *)&sin.sin_addr, sizeof(struct in_addr),
			   AF_INET);
	if (hp != (struct hostent *)0) {
		plogf("ACCEPT [%s] from %s on %d\n", desc, hp->h_name, fd);
		if((name = (char *)malloc(strlen(hp->h_name)+1)) == (char *)0)
			return((char *)0);
		strcpy(name,hp->h_name);
		return(name);
	}
#endif

	t = inet_ntoa(sin.sin_addr);
	if (t == (char *)0) {
		plogf("ACCEPT [%s] inet_ntoa failed! on %d\n", fd);
		return((char *)0);
	}


	plogf("ACCEPT [%s] from %s on %d\n", desc, t, fd);
	if((name = (char *)malloc(strlen(t)+1)) == (char *)0)
		return((char *)0);
	strcpy(name,t);
	return(name);
}