/
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.

	VMS port by Andrew Molitor (with many thanks!)
*/

#ifndef	lint
static	char	RCSid[] = "$Header: /usr/users/mjr/hacks/umud/RCS/net_vms.c,v 1.1 91/06/17 14:37:36 mjr Exp $";
#endif

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

#include	"multinet_root:[multinet.include.sys]types.h"
#include	"multinet_root:[multinet.include]netdb.h"
#include	"multinet_root:[multinet.include.sys]socket.h"
#include	"multinet_root:[multinet.include.netinet]in.h"
#include	"multinet_root:[multinet.include.vms]inetiodef.h"


#include	<ctype.h>
#include	<varargs.h>
#include	<ssdef.h>
#include	<libdtdef.h>

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

/*
	For the VMS version, we use event flags to synchronize most everything.
Writes we don't care about. All reads set flag 32 on completion, server fd
connects set flag 33, player connects set flag 34. We use 35 for the timer.
*/

/*
#define VMSNET_DEBUG
*/

/*
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 */

/*
	When we queue up a write, we stuff this in a queue, and wait
for it to complete.
*/

typedef struct obuff {
	struct obuff	*bck,*fwd;
	short int	iosb[4];
	char		buffer[MUDBUF];
} obuff;

/* This is the queue of buffers $qio'd for output */

static obuff *outQ = (obuff *)0;

/* 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. */

typedef	struct {
	int		flg;		/* flags */
	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 */
	obuff		*outputbuff;	/* The structure. */
	short int	iosb[4];	/* For read $qios */
	int		opcnt;		/* Number of oputstanding ops */
} 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		*lastiob = (Iob *)0;
static	int		onewrt = 0;	/* optimization */
static	int		iobtabsiz;	/* top bound of Iob table */
static	int		curriob;	/* highest Iob in use */
static	short int	seriosb[4];
static	short int	plyiosb[4];
static	Iob		*orphans = (Iob *)0;
static	long		timo[2];
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		timosec;	/* Seconds per tick */
static	int		nxtwhoiobptr;	/* used for programmed Iob traverse */

static void dec_opcnt();

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

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	timosec = cron_quantum;
	op = LIB$K_DELTA_SECONDS;
	lib$cvt_to_internal_time(&op,&timosec,timo);

	/*
	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 = 128;
	sys$clref(32L);
	sys$clref(33L);
	sys$clref(34L);
	sys$setef(35L);  /* SET this to get the timer going. */

	/* initialize the Iob hash table */
	iobtab = (Iob **)malloc((unsigned)(sizeof(Iob *) * iobtabsiz));
	if(iobtab == (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);

	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);
	}
	/* Queue up a wait for connects type thing. */

	sys$qio(33L,serfd,IO$_ACCEPT_WAIT,seriosb,0,0,0,0,0,0,0,0);


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

	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);
	}
	/* Queue up a wait for connects type thing. */

	sys$qio(34L,plyfd,IO$_ACCEPT_WAIT,plyiosb,0,0,0,0,0,0,0,0);

	/*
	In VMS, the iotab is ONLY for existing connects. curriob is
	the high-water mark, this indicates an empty table.
	*/
	curriob = -1;
	net_initted++;
	return(0);
}




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

	if(ip == (Iob *)0)
		return;
#ifdef VMSNET_DEBUG
	printf("Dropping Iob on fd %d.\n",ip->fd);
#endif
	if (ip->who[0]) {
		plogf("DISCONNECT %s on %d\n", ip->who, ip->fd);
#ifdef	USE_RWHO
		rwhocli_userlogout(ip->who);
#endif
	} else
		plogf("DISCONNECT %d\n", ip->fd);

	for(y = 0; y < iobtabsiz; y++) {
		if(iobtab[y] == ip) {
			iobtab[y] = (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 */
	/* y had BETTER not have been messed with */
	if(y == curriob)
		curriob--;

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

	if(ip->opcnt > 0){
		/* Stuff it on the orphaned Iob list for io_sync() to handle */

		ip->ip = (char *) orphans;
		orphans = ip;
		return;
	}
	(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;
			return(op);
		}

		if((*i->ip == '\n' || *i-> ip == '\r') && ic > 0) {
			*i->ip++ = '\0';
			if((i->ic = --ic) < 1)
				i->ip = i->ibuf;
			return(op);
		}

		/* nothing left in buffer, but no newline */
		if(--ic <= 0) {
			if(i->ic >= MUDBUF - 1)
				return((char *)0);

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

			/* resync */
			i->ip = i->ibuf;
			i->ic = 0;
			return((char *)0);
		}

		i->ip++;
	}
}



/*
flush a single Iob
*/
static	int
iobflush(ip)
Iob	*ip;
{
	int	xx = ip->op - ip->obuf;
	int	wr;

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

	/* Too much pending. Flush it. */

	if(ip->opcnt > 16){
		sys$qio(0,ip->fd,IO$_WRITEVBLK,0,0,0,"<Output Flushed>",16,
			0,0,0,0);
		ip->op = ip->obuf;
		return(0);
	}

	/* Queue this up. */
#ifdef VMSNET_DEBUG
	printf("Queueing %d bytes on fd %d.\n",xx,ip->fd);
#endif
	sys$qio(0,ip->fd,IO$_WRITEVBLK,(ip->outputbuff)->iosb,dec_opcnt,ip,
		ip->obuf,xx,0,0,0,0);
	ip->opcnt++;

	if(outQ == (obuff *)0){ /* Empty queue */
		(ip->outputbuff)->fwd = (ip->outputbuff)->bck = (obuff *)0;
		outQ = ip->outputbuff;
	} else {
		(ip->outputbuff)->fwd = outQ;
		(ip->outputbuff)->bck = outQ->bck;
		(outQ->bck)->fwd = ip->outputbuff;
		outQ->bck = ip->outputbuff;
	}

	/* Get a new obuffer. */
	if(getnewobuff(ip) == -1){
		ip->flg |= IOBERR;
		return(1);
	}
	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);
}



/*
	This trots down the list of orphaned Iob's, checking to see
if any have all outstanbding ops on them completed, and freeing those.
*/
static	void
purge_orphans()
{
	Iob *bp,*tmp;

	for(bp = orphans; bp != (Iob *) 0; bp = (Iob *) bp->ip) {
		if(bp->opcnt <= 0 && bp == orphans){
			orphans = (char *) bp->ip;
			free((mall_t) bp->ibuf);
			free((mall_t) bp->obuf);
			bp->ibuf = bp->obuf = (char *)0;
			free((mall_t)bp);
			bp = orphans;
		} else {
#ifdef VMSNET_DEBUG
      			printf("Orphaned Iob on fd %d has pending ops.\n",bp->fd);
#endif
			if(bp->ip != (char *)0 && ((Iob *)bp->ip)->opcnt <= 0){
				tmp = (Iob *) bp->ip;
				bp->ip = ((Iob *)bp->ip)->ip;
				free((mall_t) tmp->ibuf);
				free((mall_t) tmp->obuf);
				tmp->ibuf = tmp->obuf = (char *)0;
				free((mall_t) tmp);
			}
		}
	}
}




/* 
flush the connected (valid) Iobs
*/
void
io_sync()
{
	int	n;
	Iob	*bp;

#ifdef VMSNET_DEBUG
	printf("Syncing..\n");
#endif
	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) {
#ifdef VMSNET_DEBUG
		printf("Only one Iob to flush.\n");
#endif
		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);
		}
		purge_orphans();
		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]);
			}
		}
	}

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



/*
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, butthead.\n",(char *)0);
		logf("badlogin: ",line,"\n",(char *)0);
		return;
	}

	if(hv == -1) {
		bp->flg |= IOBKILL;
		onewrt = 2;
		return;
	}

	bp->flg |= IOBOK;

	/* 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\n", bp->who, bp->fd);
	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()
{
	Iob	*bp;
	int	n;
	int	seld;
	int	rd;
	char	*lp;
	time_t	now;
	int	x;
	int	eflags;
#ifdef VMSNET_DEBUG
	printf("Waiting...");
#endif

	if(!net_initted)
		return(1);

	/* Set up a timer. */
	sys$readef(35L,&eflags);

	 /* If the last timer has expired, or tick interval changed */

	if(eflags & 8 || cron_quantum != timosec){
		/* Has our tick interval changed? */

		if(cron_quantum != timosec){
			int op = LIB$K_DELTA_SECONDS;

			timosec = cron_quantum;
			lib$cvt_to_internal_time(&op,&timosec,timo);
		}
#ifdef VMSNET_DEBUG
		printf("Setting timer for %d seconds.\n",timosec);
#endif
		sys$cantim(1);
		sys$setimr(35L,timo,0,1,0);
	}
	sys$wflor(32L,15L); /* Wait on event flags 32, through 35. */
	sys$readef(32L,&eflags); /* Read this event flag group. */
#ifdef VMSNET_DEBUG
	printf("Event! flags are: %x\n",eflags);
#endif
	if(eflags & 7 == 0) /* None of the good flags are set. yawn. */
		return(0);

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

	/* new SERVER TO SERVER connection */
	if(eflags & 2){  /* 33 is masked 0x0002 in this group. */
		n = accept(serfd,(struct sockaddr *)0,(int *)0);
#ifdef VMSNET_DEBUG
		printf("New server connect on fd %d.\n",n);
#endif

		if(n == -1) {
			logf("aiee! connect on server port dropped\n",0);
			close(n);
		} else {
			/* deal with it */

			xact_in(n); /* This even closes the channel */
		}

		/* Queue another one */
		sys$qio(33L,serfd,IO$_ACCEPT_WAIT,seriosb,0,0,0,0,0,0,0,0);
	}


	/* new PLAYER connection */
	if(eflags & 4){ /* 34 is masked 0x0004 in this group */
		n = accept(plyfd,(struct sockaddr *)0,(int *)0);
#ifdef VMSNET_DEBUG
		printf("New player connect on fd %d.\n",n);
#endif

		if(n != -1) {
			/* Queue another one */
			sys$qio(34L,plyfd,IO$_ACCEPT_WAIT,plyiosb,
				0,0,0,0,0,0,0,0);

			bp = (Iob *)malloc(sizeof(Iob));
			if(bp == (Iob *)0) {
				logf("cannot alloc Iob: ",(char *)-1,"\n",0);
				return(-1);
			}
			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(getnewobuff(bp) == -1)
				return(-1);
			bp->op = bp->obuf;
			bp->opcnt = 0;

			/* queue up a read on the new socket */
#ifdef VMSNET_DEBUG
			printf("Queueing initial read.\n");
#endif
			sys$qio(32L,bp->fd,IO$_READVBLK,bp->iosb,
				dec_opcnt,bp,
				bp->ibuf + bp->ic,MUDBUF - bp->ic - 1,
				0,0,0,0);

			/* Put it in the iobtable */

			for(x=0; x < iobtabsiz; x++){
				if(iobtab[x] == (Iob *)0)
					break;
			}
			if(x == iobtabsiz || iobtab[x] != (Iob *)0){
				iobsay(bp,"No space!\n",(char *)0);
				iobdrop(bp);
			} else {
				iobtab[x] = bp;
				if(x > curriob){
					curriob = x;
				}
			}
			iobsay(bp,"c[onnect] objectid password\n",(char *)0);
		}
	}

mainloop:
	/* check input on existing fds. */

	/* Clear the event flag NOW. It's safe to do it here. */
	sys$clref(32L);
	for(n = 0; n <= curriob; n++) {
		if((bp = iobtab[n]) != (Iob *)0 && bp->iosb[0] == SS$_NORMAL) {
			rd = bp->iosb[1];
#ifdef VMSNET_DEBUG
			printf("Read %d bytes on fd %d\n",rd,bp->fd);
#endif
			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);
			}
			bp->opcnt++;

			/* queue up another read */
			sys$qio(32L,bp->fd,IO$_READVBLK,bp->iosb,
				dec_opcnt,bp,
				bp->ibuf + bp->ic,MUDBUF - bp->ic - 1,
				0,0,0,0);
			continue;
		}
		/* lastly, check exceptions in case of no input */
		if((bp = iobtab[n]) != (Iob *)0 && bp->iosb[0] != 0
				&& bp->iosb[0] != SS$_NORMAL) {
#ifdef VMSNET_DEBUG
			printf("Error %x on fd %d.\n",bp->iosb[0],bp->fd);
#endif
			/* default case */
			if(bp != (Iob *)0) {
				bp->flg |= IOBERR | IOBKILL;
				onewrt = 2;
			}
			continue;
		}
	}

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




/* ARGSUSED */
cmd__netconfig(argc,argv,who)
int	argc;
char	*argv[];
char	*who;
{
	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);
	}

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




/* ARGSUSED */
cmd_WHO(argc,argv,who,aswho)
int	argc;
char	*argv[];
char	*who;
char	*aswho;
{
	int	x;
	int	jj;
	time_t	now;
	char	xuf[180];
	extern	char	*sprintf();

	if(!net_initted)
		return(1);

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

	if(argc < 2) {
		for(x = 0; x < iobtabsiz; x++) {
			if(iobtab[x] != (Iob *)0 && (iobtab[x]->flg & IOBOK) &&
			iobtab[x]->who[0] != '\0') {
			sprintf(xuf,"%-21.21s  %3d:%02d.%02d   %3d:%02d.%02d (%s)\n",
				ut_name(iobtab[x]->who),
				(int)((now - iobtab[x]->ctim) / 3600),
				(int)((now - iobtab[x]->ctim) / 60),
				(int)((now - iobtab[x]->ctim) % 60),
				(int)((now - iobtab[x]->ltim) / 3600),
				(int)((now - iobtab[x]->ltim) / 60),
				(int)((now - iobtab[x]->ltim) % 60),
				iobtab[x]->who);
				say(who,xuf,(char *)0);
			}
		}
		return(0);
	}

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

		for(x = 0; x < iobtabsiz; x++) {
			if(iobtab[x] != (Iob *)0 && (iobtab[x]->flg & IOBOK) &&
			iobtab[x]->who[0] != '\0') {
				na = ut_name(iobtab[x]->who);
				if(na != (char *)0 && matchstr(na,argv[jj],0) != 0) {
					sprintf(xuf,"%-21.21s  %3d:%02d.%02d   %3d:%02d.%02d (%s)\n",
					na,
					(int)((now - iobtab[x]->ctim) / 3600),
					(int)((now - iobtab[x]->ctim) / 60),
					(int)((now - iobtab[x]->ctim) % 60),
					(int)((now - iobtab[x]->ltim) / 3600),
					(int)((now - iobtab[x]->ltim) / 60),
					(int)((now - iobtab[x]->ltim) % 60),
					iobtab[x]->who);
					say(who,xuf,(char *)0);
				}
			}
		}
	}
	return(0);
}




/*
	This is called when any queued operation on a socket completes.
It merely decrements the count of outstanding operations on the socket, so we
can tell when the socket is recycleable.
*/

void dec_opcnt(bp)
Iob *bp;
{
	bp->opcnt--;
}

/*
	Get a new obuff for an Iob, and hook it up.
*/

int getnewobuff(ip)
Iob *ip;
{
	obuff *skip;
	int found;

	/* Scan down the list of queued obuffs for one that's done. */

	skip = outQ;
	found = 0;
	if(skip != (obuff *)0){
		do {
			if(skip->iosb[0] != 0){ /* Hah! It's *done* */
				found = 1;
				break;
			}
			skip = skip->fwd;
		} while(skip != outQ);
	}

	if(!found){ /* Nothing free, malloc one */
#ifdef VMSNET_DEBUG
		printf("No free obuff's, mallocing one\n");
#endif
		if( (ip->outputbuff = (obuff *) malloc(sizeof(obuff)))
				== (obuff *)0){
			return(-1);
		}
	} else {
		/* skip points at something we can rip out of the queue */

		if(skip->fwd == skip->bck){
			outQ = (obuff *)0;
		} else {
			(skip->bck)->fwd = skip->fwd;
			(skip->fwd)->bck = skip->bck;
			if(outQ == skip)
				outQ = skip->fwd;
		}
		ip->outputbuff = skip;
	}
	ip->op = ip->obuf = (ip->outputbuff)->buffer;
#ifdef VMSNET_DEBUG
	printf("Got new obuff for Iob on fd %d.\n",ip->fd);
#endif
	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]->ltim;
			return(iobtab[nxtwhoiobptr++]->who);
		}

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