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

to make:
	cc [-DSETSID -DDAEMON -DSTRCASECMP] -o mwhod mwhod.c

	MUD rwho daemon. This operates with 2 sockets open, one for datagrams
containing update information for the database, the other a stream connection
for users to connect to and download information about what MUDs are up and
who is on.

	mwhods can share their databases between eachother by being defined
as PEERs in the configuration file. The config file specifies a list of MUDs
or servers that we trust and will accept data from, along with passwords to
use when communicating with them. Information is stored about the number of
times a data item has been passed, and the generation count is incremented
each time the datum is re-forwarded. After a certain number of generations,
data will no longer be forwarded.

	The server makes a traverse of its database periodically and expires
all information that it has not received an update for in a given period of
time. This is settable arbitrarily, but should not be too high a value.
*/

#ifndef	lint
static	char	RCSid[] = "$Header: /usr/users/mjr/hacks/umud/RWHO/RCS/mwhod.c,v 1.31 91/06/06 00:01:15 mjr Exp $";
#endif

#include	<stdio.h>
#include	<sys/types.h>
#include	<sys/signal.h>
#include	<errno.h>
extern	int	errno;
#include	<ctype.h>
#include	<fcntl.h>
#include	<sys/file.h>
#include	<sys/time.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include	<netdb.h>

extern	char	*inet_ntoa();
extern	char	*malloc();

extern	char	*optarg;

/* change this? */
static	char	connectmsg[] = "Connected to Mud RWHO server...\n";

FILE	*logf = stderr;

/* hear nothing from a MUD for more than this, expire it */
#define	MUDTTL			800;
#define	PLYTTL			800;

/* name of server, in case we need to propagate */
char	*myname = (char *)0;

/* name of default muds database to load */
#define	DEFMUDTAB	"muds.dat"

/* ports to listen at */
#define	DGRAMPORT		6888
#define	STREAMPORT		6889


/* clean up our tables and propagate them every this many seconds */
#define	CLEANUPEVERY		120
int	cleantime	= CLEANUPEVERY;
int	proptime	= CLEANUPEVERY;


/* states of user table entries */
#define	U_ALIVE		01
#define	U_ZOMBIE	02
#define	U_KILL		04

#define	PNAMSIZ	32
struct	uent	{
	char		uid[PNAMSIZ];	/* user id (must be unique) */
	char		uname[PNAMSIZ];	/* user name (arbitrary text) */
	int		state;		/* user state */
	time_t		logon;		/* logon time at remote */
	time_t		ttl;		/* time to live of logon entry */
	time_t		upd;		/* last update on ttl */
	int		gen;		/* number of generations of prop */
	struct	uent	*next;
};



/* states of mudtable entries */
#define	MUD_UP		001
#define	MUD_PEER	002
#define	MUD_GUEST	004
#define	MUD_WIZ		010

struct	mudent	{
	struct	in_addr	maddr;		/* remote address */
	short		mport;		/* remote port */
	char		*mapw;		/* remote password */
	char		*mnam;		/* remote MUD name */
	char		*txt;		/* arbitrary text about MUD */
	time_t		up;		/* MUD's uptime */
	int		ucnt;		/* active (?) users */
	time_t		ttl;		/* time to live for MUD entry */
	time_t		upd;		/* last update on ttl */
	int		flgs;		/* flags */
	int		gen;		/* generation to PROPAGATE if peer */
	struct	uent	*usrs;
	struct	mudent	*next;
};
struct	mudent	*mt;
int	havepeers = 0;


void	writepidfile();
void	process_dgram();
void	process_stream();
void	mud_free_ulist();
void	mud_add_entry();
void	mud_zap_entry();
void	mud_add_user();
void	mud_zap_user();
void	mud_def_newmud();
void	clean_tables();
void	multicast_tables();

time_t	serverbooted;

int	dbgflg = 0;
int	logflg = 0;

char	*mudtab = DEFMUDTAB;
int	dgramfd;
int	streamfd;

/* load the default MUD table (or reload it) */
static	void
loaddefmudtab()
{
	if(dbgflg)
		printf("reloading mud table\n");
	(void)loadmudtab(mudtab);
}



static	void
cleandeath()
{
	(void)shutdown(dgramfd,2);
	(void)shutdown(streamfd,2);
	if(dbgflg)
		printf("shutdown\n");

	exit(0);
}




main(ac,av)
int	ac;
char	**av;
{
	struct	sockaddr_in	addr;
	struct	timeval		timo;
	fd_set			redy;
	fd_set			xcpt;
	time_t			lastclean = (time_t)0;
	time_t			lastprop = (time_t)0;
	time_t			now;
	char			rbuf[512];
	int			red;
	int			alen;
	int			fflg = 0;
	unsigned		mydgport = DGRAMPORT;
	unsigned		mystport = STREAMPORT;

	time(&serverbooted);

	while((alen = getopt(ac,av,"S:P:c:p:f:n:dlL:DF:")) != EOF) {
		switch(alen) {
		case 'S':
			mystport = atoi(optarg);
			break;

		case 'P':
			mydgport = atoi(optarg);
			break;

		case 'F':
			writepidfile(optarg);
			break;

		case 'f':
			mudtab = optarg;
			break;

		case 'n':
			myname = optarg;
			break;

		case 'c':
			cleantime = atoi(optarg);
			break;

		case 'p':
			proptime = atoi(optarg);
			break;

		case 'd':
			dbgflg++;
			break;

		case 'l':
			logflg++;
			break;

		case 'L':
			logf = fopen(optarg,"w");
			if(logf == (FILE *)0)
				perror(optarg);
			break;

		case 'D':
#ifdef	DAEMON
			(void)signal(SIGTERM,SIG_IGN);
			(void)signal(SIGHUP,SIG_IGN);
			(void)signal(SIGCHLD,SIG_IGN);
			(void)signal(SIGALRM,SIG_IGN);
			if(fork())
				exit(0);
#ifdef	SETSID
			(void)setsid();
#endif
#else
			fprintf(stderr,"cannot daemonize\n");
#endif
			break;

		default:
			fprintf(stderr,"usage: %s [-d] -f mudlist -n name\n",av[0]);
			exit(1);
		}
	}


	if(loadmudtab(mudtab))
		exit(1);

	(void)signal(SIGFPE,loaddefmudtab);
	(void)signal(SIGPIPE,SIG_IGN);
	(void)signal(SIGINT,cleandeath);

	if(havepeers && myname == (char *)0) {
		fprintf(stderr,"cannot have server name unset and propagate\n");
		exit(1);
	}

	/* set up datagram service */
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(mydgport);
	if((dgramfd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
		perror("socket");
		exit(1);
	}
	if(bind(dgramfd,(struct sockaddr *)&addr,sizeof(addr))) {
		perror("bind");
		exit(1);
	}
	alen = 1;
	setsockopt(dgramfd,SOL_SOCKET,SO_REUSEADDR,&alen,sizeof(alen));


	/* set up stream service */
	addr.sin_port = htons(mystport);
	if((streamfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
		perror("socket");
		exit(1);
	}
	if(bind(streamfd,(struct sockaddr *)&addr,sizeof(addr))) {
		perror("bind");
		exit(1);
	}
	alen = 1;
	setsockopt(streamfd,SOL_SOCKET,SO_REUSEADDR,&alen,sizeof(alen));
	if(listen(streamfd,5) == -1) {
		perror("listen");
		exit(1);
	}

	timo.tv_sec = cleantime > proptime ? cleantime : proptime;
	timo.tv_usec = 0;

	if(logflg && logf != (FILE *)0)
		fprintf(logf,"server running: port %d/%d\n",mystport,mydgport);
	if(dbgflg)
		printf("server running: port %d/%d\n",mystport,mydgport);
		


/* main thang */
looptop:


	/* how often to purge expired entries */
	time(&now);
	if(now - lastclean > cleantime) {
		clean_tables(now);
		lastclean = now;

		if(logf != (FILE *)0)
			fflush(logf);
	}

	/* how often to propagate */
	if(now - lastprop > proptime) {
		if(havepeers)
			multicast_tables(dgramfd);
		lastprop = now;
	}


	FD_ZERO(&redy);
	FD_ZERO(&xcpt);
	FD_SET(streamfd,&redy);
	FD_SET(streamfd,&xcpt);
	FD_SET(dgramfd,&redy);
	FD_SET(dgramfd,&xcpt);


	if((red = select(streamfd + 1,&redy,(fd_set *)0,&xcpt,&timo)) < 0)
		if(errno != EINTR) {
			perror("select");
			exit(1);
		}


	/* yawn */
	if(red <= 0)
		goto looptop;

	
	if(FD_ISSET(dgramfd,&redy)) {
		alen = sizeof(addr);
		red = recvfrom(dgramfd,rbuf,sizeof(rbuf),0,&addr,&alen);
		if(red > 0 && red < sizeof(rbuf)) {
			rbuf[red] = '\0';
			process_dgram(&addr.sin_addr,rbuf);
		}
	}


	if(FD_ISSET(streamfd,&redy)) {
		red = accept(streamfd,(struct sockaddr *)0,(int *)0);
		if(red >= 0)
			process_stream(red);
	}

	if(FD_ISSET(streamfd,&xcpt) || FD_ISSET(dgramfd,&xcpt)) {
		if(logf != (FILE *)0)
			fprintf(logf,"we are betrayed!!\n");
		exit(1);
	}


	goto looptop;
}





/* scan the MUD table */
struct	mudent	*
getmudent(mud,cflg)
char	*mud;
int	cflg;
{
	struct	mudent	*ret;

	for(ret = mt; ret != (struct mudent *)0; ret = ret->next) {
#ifdef	STRCASECMP
		if(ret->mnam != (char *)0 &&
			((!cflg && !strcasecmp(ret->mnam,mud)) ||
			(cflg && !strcmp(ret->mnam,mud))))
#else
		if(ret->mnam != (char *)0 && !strcmp(ret->mnam,mud))
#endif
			return(ret);
	}
	return((struct mudent *)0);
}






/* zip through our tables and kill anything with an expired time-to-live */
void
clean_tables(now)
time_t		now;
{
	struct	mudent	*m;
	struct	uent	*u;
	struct	uent	*p;
	struct	uent	*n;

	if(dbgflg)
		printf("cleaning deadwood out of tables\n");

	for(m = mt; m != (struct mudent *)0; m = m->next) {
		if(!(m->flgs & MUD_UP))
			continue;

		/* has the whole MUD entry table expired? */
		if(now - m->upd > m->ttl) {
			if(logflg && logf != (FILE *)0)
				fprintf(logf,"dead mud %s\n",m->mnam);
			if(dbgflg)
				printf("dead %s: now=%d, lastup=%d, ttl=%d\n",
					m->mnam,now,m->upd,m->ttl);
			m->flgs &= ~MUD_UP;
			mud_free_ulist(m);
			continue;
		}

		/* check for expired logins */
		for(p = u = m->usrs; u != (struct uent *)0; u = n) {
			n = u->next;
			if(now - u->upd > u->ttl || (u->state == U_KILL)) {
				if(logflg && logf != (FILE *)0)
					fprintf(logf,"dead user %s@%s\n",u->uid,m->mnam);
				if(dbgflg)
					printf("dead user %s@%s\n",u->uid,m->mnam);
				if(u->state == U_ALIVE)
					m->ucnt--;

				if(u == m->usrs)
					p = m->usrs = n;
				else
					p->next = n;
				free(u);
			} else {
				/* Only advance prev if we did NOT nuke */
				p = u;
			}
		}
	}
}




static	int
timedwrite(fd,buf,siz)
int	fd;
char	*buf;
int	siz;
{
	fd_set		wfd;
	struct	timeval	timo;

	FD_ZERO(&wfd);
	FD_SET(fd,&wfd);

	timo.tv_sec = 0;
	timo.tv_usec = 800;

	if(select(fd + 1,(fd_set *)0,&wfd,(fd_set *)0,&timo) != 1)
		return(-1);
	if(!FD_ISSET(fd,&wfd))
		return(-1);
	return(write(fd,buf,siz));
}





/* propagate out our tables to peer servers, if any */
void
multicast_tables(fd)
int	fd;
{
	struct	sockaddr_in	ad;
	struct	uent		*u;
	struct	mudent		*m;
	struct	mudent		*m2;
	char			v[512];

	if(dbgflg)
		printf("propagating tables\n");

	for(m = mt; m != (struct mudent *)0; m = m->next) {
		if(!(m->flgs & MUD_PEER))
			continue;

		/* setup address */
		ad.sin_port = htons(m->mport);
		ad.sin_family = AF_INET;
		bcopy(&m->maddr,&ad.sin_addr,sizeof(ad.sin_addr));
		if(dbgflg)
			printf("updating peer %s@%s, port %d\n",m->mnam,
				inet_ntoa(m->maddr),m->mport);


		for(m2 = mt; m2 != (struct mudent *)0; m2 = m2->next) {

			/* EXCLUDE ! */
			if(m2 == m || m2->gen >= m->gen ||
				m2->flgs & MUD_PEER || !(m2->flgs & MUD_UP))
				continue;

			if(dbgflg)
				printf("update mud %s->%s\n",m2->mnam,m->mnam);


			/* inform remote of the MUD */
			sprintf(v,"M\t%s\t%s\t%s\t%d\t%d\t%s",
				myname,m->mapw,m2->mnam,m2->up,
				m2->gen + 1,m2->txt != (char *)0 ? m2->txt: "");

			sendto(fd,v,strlen(v),0,&ad,sizeof(ad));

			
			for(u = m2->usrs; u != (struct uent *)0; u = u->next) {
				if(u->gen + 1 > m->gen || (u->state == U_KILL))
					continue;

				/*
				if the user is logged out send a byebye
				by propagating the zombie record.
				otherwise send a logged in record.
				*/
				if(u->state == U_ZOMBIE) {
					if(dbgflg)
						printf("deluser %s->%s\n",
							u->uid,m->mnam);
					sprintf(v,"Z\t%s\t%s\t%s\t%s",
						myname,m->mapw,m2->mnam,u->uid);
				} else {
					if(dbgflg)
						printf("send user %s->%s\n",
							u->uid,m->mnam);
					sprintf(v,
						"A\t%s\t%s\t%s\t%s\t%d\t%d\t%s",
						myname,m->mapw,m2->mnam,u->uid,
						u->logon,u->gen + 1,u->uname);
				}
				sendto(fd,v,strlen(v),0,&ad,sizeof(ad));
			}
		}
	}


	/* reap zombies - by marking them all as killable */
	for(m2 = mt; m2 != (struct mudent *)0; m2 = m2->next)
		for(u = m2->usrs; u != (struct uent *)0; u = u->next)
			if(u->state == U_ZOMBIE)
				u->state = U_KILL;
}





static	int
stream_mudent(fd,m,ucnt)
int		fd;
struct	mudent	*m;
int		*ucnt;
{
	struct	tm	*tim;
	struct	uent	*u;
	char		buf[512];
	char		ibuf[64];
	int		bl;

	if(m->txt == (char *)0)
		strcpy(ibuf,m->mnam);
	else
		sprintf(ibuf,"%.13s/%.22s",m->mnam,m->txt);

	/* is the MUD running or what? */
	if(!(m->flgs & MUD_UP) && m->up == (time_t)0) {
		tim = localtime(&serverbooted);
		sprintf(buf,
		"\n%-35.35s     not heard from since %d/%d/%d %2.2d:%2.2d\n",
			ibuf,
			tim->tm_mon,tim->tm_mday,tim->tm_year,
			tim->tm_hour,tim->tm_min);
	} else {
		tim = localtime(&m->up);
		sprintf(buf,
		"\n%-35.35s %-3d user%c           %s %d/%d/%d %2.2d:%2.2d\n",
			ibuf,m->ucnt,m->ucnt == 1 ? ' ' : 's',
			(m->flgs & MUD_UP) ? "up" : "last up",
			tim->tm_mon,tim->tm_mday,tim->tm_year,
			tim->tm_hour,tim->tm_min);
	}

	bl = strlen(buf);
	if(timedwrite(fd,buf,bl) != bl)
		return(1);

	if(!(m->flgs & MUD_UP))
		return(0);

	if((u = m->usrs) == (struct uent *)0 || m->ucnt <= 0)
		return(0);

	sprintf(buf,"%-35.35s %-17.17s      %s\n",
		"--name--","--id--","--login--");

	bl = strlen(buf);
	if(timedwrite(fd,buf,bl) != bl)
		return(1);

	while(u != (struct uent *)0) {
		if(u->state != U_ALIVE) {
			u = u->next;
			continue;
		}

		tim = localtime(&u->logon);
		sprintf(buf,"%-35.35s %-17.17s      %2.2d:%2.2d.%2.2d\n",
		u->uname[0] == '\0' ? u->uid : u->uname,
		u->uname[0] == '\0' ? "" : u->uid,
		tim->tm_hour,tim->tm_min,tim->tm_sec);

		bl = strlen(buf);
		if(timedwrite(fd,buf,bl) != bl)
			return(1);
		u = u->next;
		(*ucnt)++;
	}
	return(0);
}




static	void
lookup_who(fd,w)
int	fd;
char	*w;
{
	struct	mudent	*m;
	struct	uent	*u;
	struct	tm	*tim;
	char		buf[512];
	char		ibuf[64];
	int		bl;
	int		got = 0;
	static	char	nomsg[] = "nobody by that name is logged in\n";

	for(m = mt; m != (struct mudent *)0; m = m->next)
		for(u = m->usrs; u != (struct uent *)0; u = u->next) {
#ifdef	STRCASECMP
			if(u->state == U_ALIVE &&
			(!strcasecmp(u->uname,w) || !strcasecmp(u->uid,w))) {
#else
			if(u->state == U_ALIVE &&
			(!strcmp(u->uname,w) || !strcmp(u->uid,w))) {
#endif

				tim = localtime(&u->logon);
				sprintf(ibuf,"%.10s/%.35s",m->mnam,
				u->uname[0] == '\0' ? u->uid : u->uname);

				sprintf(buf,
				"%-35.35s %-17.17s %2.2d:%2.2d.%2.2d\n",
				ibuf,u->uname[0] == '\0' ? "" : u->uid,
				tim->tm_hour,tim->tm_min,tim->tm_sec);

				bl = strlen(buf);
				got++;
				if(timedwrite(fd,buf,bl) != bl)
					return;
			}
		}

	if(!got)
		(void)timedwrite(fd,nomsg,sizeof(nomsg) -1);
}




static	void
lookup_mud(fd,mud)
int	fd;
char	*mud;
{
	struct	mudent	*m;
	static	char	nomud[] = "no MUD by that name is running\n";
	int	dummy;

	if((m = getmudent(mud,0)) == (struct mudent *)0) {
		(void)timedwrite(fd,nomud,sizeof(nomud) -1);
		return;
	}
	(void)stream_mudent(fd,m,&dummy);
}





/* this is what most users will see, when they connect and ask who is on */
void
process_stream(fd)
int	fd;
{
	struct	mudent	*m;
	fd_set	redy;
	fd_set	xcpt;
	struct	timeval	timo;
	int	reqst = 0;
	char	rbuf[512];
	int	red;
	int	mcnt = 0;
	int	ucnt = 0;

	timo.tv_sec = 0;
	timo.tv_usec = 300;

	FD_ZERO(&redy);
	FD_ZERO(&xcpt);
	FD_SET(fd,&redy);
	FD_SET(fd,&xcpt);

	/* see if there's a request packet */
	if((red = select(fd + 1,&redy,(fd_set *)0,&xcpt,&timo)) > 0) {
		if((red = read(fd,rbuf,sizeof(rbuf))) > 0) {
			char	*p;
			if(!strncmp(rbuf,"mud=",4) || !strncmp(rbuf,"who=",4))
				reqst = 1;
			p = rbuf;
			while(*p != '\0') {
				if(*p == '\n') {
					*p = '\0';
					break;
				}
				p++;
			}
		}
	}


	if(dbgflg)
		printf("start user query\n");

	if(timedwrite(fd,connectmsg,sizeof(connectmsg)-1) != (sizeof(connectmsg)-1))
		goto done;

	if(reqst && !strncmp(rbuf,"mud=",4)) {
		if(dbgflg)
			printf("lookup_mud %s\n",rbuf);
		lookup_mud(fd,&rbuf[4]);
		goto done;
	}

	if(reqst && !strncmp(rbuf,"who=",4)) {
		if(dbgflg)
			printf("lookup_who %s\n",rbuf);
		lookup_who(fd,&rbuf[4]);
		goto done;
	}

	for(m = mt; m != (struct mudent *)0; m = m->next) {
		if(m->flgs & MUD_PEER)
			continue;
		if(!(m->flgs & MUD_UP)) {
			if(dbgflg)
				printf("skip down MUD %s\n",m->mnam);
			continue;
		}
		if(stream_mudent(fd,m,&ucnt))
			break;
		if(dbgflg)
			printf("sent mudent for MUD %s\n",m->mnam);
		mcnt++;
	}

	sprintf(rbuf,
		"\n\n       --- %d muds, %d players known to this server ---\n",
		mcnt,ucnt);
	(void)timedwrite(fd,rbuf,strlen(rbuf));

done:
	if(dbgflg)
		printf("end user query\n");
	close(fd);
}





/* process and deal with a datagram from a remote MUD or server */
void
process_dgram(who,buf)
struct	in_addr	*who;
char		*buf;
{
	struct	mudent	*m;
	char		*cp;
	char		*av[12];
	int		ac;
	int		x;


	/* tokenize at tabs */
	av[ac = 0] = cp = buf;
	while(*cp != '\0') {
		if(*cp == '\t') {
			if(ac > 10)
				return;
			*cp++ = '\0';
			av[++ac] = cp;
			av[ac+1] = (char *)0;
		} else
			cp++;
	}


	/* minimum req: OP, mudname, password */
	if(ac < 3) {
		if(logflg && logf != (FILE *)0)
			fprintf(logf,"malformed input: %s\n",inet_ntoa(*who));
		if(dbgflg)
			printf("malformed input from : %s\n",inet_ntoa(*who));
		return;
	}


	/* authenticate */
	for(m = mt; m != (struct mudent *)0; m = m->next) {
		if(m->flgs & MUD_GUEST)
			continue;
		if(m->mnam != (char *)0 && !strcmp(m->mnam,av[1])) {
			if(bcmp(who,&m->maddr,sizeof(struct in_addr))) {
				if(logf != (FILE *)0)
					fprintf(logf,"spoof %s from %s\n",
						av[1],inet_ntoa(*who));
				return;
			}
			break;
		}
	}


	/* unknown MUD */
	if(m == (struct mudent *)0) {
		if(logf != (FILE *)0)
			fprintf(logf,"probe from %s@%s",av[1],inet_ntoa(*who));
		if(logf != (FILE *)0 && ac > 2)
			fprintf(logf," (pass=%s)",av[2]);
		if(logf != (FILE *)0)
			fprintf(logf,"\n");
		return;
	}


	/* bogey */
	if(strcmp(m->mapw,av[2])) {
		if(logf != (FILE *)0)
			fprintf(logf,"bad passwd %s from %s@%s\n",
				av[2],av[1],inet_ntoa(*who));
		return;
	}


	/* nice to hear from you again - update remote's ttl */
	time(&m->upd);



	/*
	parameters passed are: operation, mudname, passwd
	since we have already used them by this point, we
	can now shift stuff around when we pass it to the
	actual operands themselves.
	*/
	switch(*av[0]) {


	/* remote MUD declares it is alive and booted */
	case 'U':
		mud_add_entry(ac - 2,&av[3],1);
		break;


	/* remote MUD existence update only (nondestructive) */
	case 'M':
		mud_add_entry(ac - 2,&av[3],0);
		break;


	/* remote MUD declares it is down and out */
	case 'D':
		mud_zap_entry(ac - 2,&av[3]);
		break;


	/* add a new logged in user */
	case 'A':
		mud_add_user(ac - 2,&av[3]);
		break;


	/* zap an old logged in user */
	case 'Z':
		mud_zap_user(ac - 2,&av[3]);
		break;


	default:
		return;
	}
}




/* read the MUD table of known servers and trusted MUDs */
loadmudtab(fil)
char	*fil;
{
	FILE		*inf;
	char		buf[BUFSIZ];

	if((inf = fopen(fil,"r")) == (FILE *)0) {
		perror(fil);
		return(1);
	}

	while(fgets(buf,sizeof(buf),inf) != (char *)0) {
		if(*buf == '#' || *buf == '\n')
			continue;
		mud_def_newmud(buf);
	}
	fclose(inf);
	return(0);
}




/* wipe the user list for a MUD */
void
mud_free_ulist(m)
struct	mudent	*m;
{
	struct	uent	*u;
	struct	uent	*n;

	if(dbgflg)
		printf("clearing user list for %s\n",m->mnam);
	u = m->usrs;
	while(u != (struct uent *)0) {
		n = u->next;
		if(dbgflg)
			printf("clearing user %s@%s\n",u->uid,m->mnam);
		free(u);
		u = n;
	}
	m->usrs = (struct uent *)0;
	m->ucnt = 0;
}




/*
add a user entry to a defined MUD if we can
parameters are: mudname username login-time propagation-generation
*/
void
mud_add_user(ac,av)
int	ac;
char	*av[];
{
	struct	uent	*u;
	struct	uent	*p1;
	struct	uent	*p2;
	struct	mudent	*m;
	int	tgen;

	if(ac < 4 || (m = getmudent(av[0],1)) == (struct mudent *)0) {
		if(dbgflg && ac > 3 && m == (struct mudent *)0)
			printf("unknown mud %s\n",av[0]);
		if(dbgflg && ac < 4)
			printf("add user: bad arg count %d\n",ac);
		return; 
	}

	if((tgen = atoi(av[3])) > m->gen) {
		if(dbgflg)
			printf("ignore gen %d %s@%s\n",tgen,av[1],m->mnam);
		return;
	}

	u = m->usrs;
	while(u != (struct uent *)0) {
		if(!strcmp(u->uid,av[1])) {
			time(&u->upd);
			u->gen = tgen;
			if(u->state != U_ALIVE) {
				u->state = U_ALIVE;
				if(dbgflg)
					printf("resurrect %s@%s\n",av[1],m->mnam);
				m->ucnt++;
			} else {
				if(dbgflg)
					printf("refresh %s@%s\n",av[1],m->mnam);
			}

			/* name change? */
			if(ac == 5 && strcmp(u->uname,av[4]))
				strncpy(u->uname,av[4],PNAMSIZ - 2);
			u->uname[PNAMSIZ - 1] = '\0';
			return;
		}
		u = u->next;
	}

	u = (struct uent *)malloc(sizeof(struct uent));
	if(u == (struct uent *)0)
		return;
	u->ttl = PLYTTL;
	u->upd = m->upd;
	u->state = U_ALIVE;
	u->logon = (time_t)atoi(av[2]);
	u->gen = tgen;
	strncpy(u->uid,av[1],PNAMSIZ - 2);
	u->uid[PNAMSIZ - 1] = '\0';

	u->uname[0] = '\0';
	if(ac == 5)
		strncpy(u->uname,av[4],PNAMSIZ - 2);
	else
		strncpy(u->uname,av[1],PNAMSIZ - 2);
	u->uname[PNAMSIZ - 1] = '\0';

	m->ucnt++;

	if(logflg && logf != (FILE *)0)
		fprintf(logf,"new entry %s@%s\n",av[1],m->mnam);
	if(dbgflg)
		printf("new entry %s@%s\n",av[1],m->mnam);

	p2 = (struct uent *)0;
	for(p1 = m->usrs; p1 != (struct uent *)0; p1 = p1->next) {
#ifdef	STRCASECMP
		if(strcasecmp(p1->uname,u->uname) >= 0)
#else
		if(strcmp(p1->uname,u->uname) >= 0)
#endif
			break;
		p2 = p1;
	}

	if(p2 == (struct uent *)0) {
		u->next = m->usrs;
		m->usrs = u;
	} else {
		u->next = p2->next;
		p2->next = u;
	}
}





/*
zap a user
parameters are: mudname username
*/
void
mud_zap_user(ac,av)
int	ac;
char	*av[];
{
	struct	mudent	*m;
	struct	uent	*u;

	if(ac != 2 || (m = getmudent(av[0],1)) == (struct mudent *)0) {
		if(dbgflg && ac == 2 && m == (struct mudent *)0)
			printf("unknown mud %s\n",av[0]);
		if(dbgflg && ac != 2)
			printf("zap user: bad arg count %d\n",ac);
		return; 
	}

	u = m->usrs;
	while(u != (struct uent *)0) {
		if(u->state == U_ALIVE && !strcmp(u->uid,av[1])) {
			m->ucnt--;
			u->state = U_ZOMBIE;
			if(logflg && logf != (FILE *)0)
				fprintf(logf,"logout %s@%s\n",av[1],m->mnam);
			if(dbgflg)
				printf("logout %s@%s\n",av[1],m->mnam);
		}
		u = u->next;
	}
}





/*
define a new entry for a MUD, or mark one as up and well
parameters are: mudname uptime propagation-generation moretext
*/
void
mud_add_entry(ac,av,flg)
int	ac;
char	*av[];
int	flg;
{
	struct	mudent	*m;
	int	tgen;

	if(ac < 3 || (m = getmudent(av[0],1)) == (struct mudent *)0) {
		struct	mudent	*m1;
		struct	mudent	*m2;

		if(ac < 3) {
			if(dbgflg)
				printf("add entry: bad arg count %d\n",ac);
			return; 
		}

		m = (struct mudent *)malloc(sizeof(struct mudent));
		if(m == (struct mudent *)0) {
			perror("malloc");
			return;
		}

		m->mapw = (char *)0;
		m->mnam = malloc((unsigned)(strlen(av[0]) + 1));
		if(m->mnam == (char *)0) {
			perror("malloc");
			return;
		}
		strcpy(m->mnam,av[0]);

		m->txt = (char *)0;
		m->ucnt = 0;
		m->flgs = MUD_GUEST;
		m->usrs = (struct uent *)0;
		m->gen = atoi(av[2]);

		m2 = (struct mudent *)0;
		for(m1 = mt; m1 != (struct mudent *)0; m1 = m1->next) {
#ifdef	STRCASECMP
			if(strcasecmp(m1->mnam,m->mnam) >= 0)
#else
			if(strcmp(m1->mnam,m->mnam) >= 0)
#endif
				break;
			m2 = m1;
		}

		if(m2 == (struct mudent *)0) {
			m->next = mt;
			mt = m;
		} else {
			m->next = m2->next;
			m2->next = m;
		}

		if(logflg && logf != (FILE *)0)
			fprintf(logf,"added mud table entry %s\n",m->mnam);
		if(dbgflg)
			printf("added mud table entry %s\n",m->mnam);
	} else {
		/* do not accept info that is too old */
		if((tgen = atoi(av[2])) > m->gen) {
			if(dbgflg)
				printf("ignored gen %d uptime %s\n",tgen,m->mnam);
			return;
		}
	}


	if(ac > 3) {
		/* replace name ? */
		if(m->txt != (char *)0 && strcmp(m->txt,av[3])) {
			free(m->txt);
			m->txt = (char *)0;
		}
		if(m->txt == (char *)0) {
			m->txt = malloc((unsigned)(strlen(av[3]) + 1));
			if(m->txt == (char *)0) {
				perror("malloc");
				return;
			}
			strcpy(m->txt,av[3]);
		}
	}

	m->flgs |= MUD_UP;
	m->ttl = MUDTTL;

	time(&m->upd);
	m->up = (time_t)atoi(av[1]);

	/* reset */
	if(flg)
		mud_free_ulist(m);

	if(dbgflg)
		printf("mark mud %s up\n",m->mnam);
}





/*
mark an old entry for a MUD as deleted/down
parameters are: mudname
*/
void
mud_zap_entry(ac,av)
int	ac;
char	*av[];
{
	struct	mudent	*m;
	int	tgen;

	if(ac != 1 || (m = getmudent(av[0],1)) == (struct mudent *)0) {
		if(dbgflg && ac == 1 && m == (struct mudent *)0)
			printf("unknown mud %s\n",av[0]);
		if(dbgflg && ac != 1)
			printf("zap entry: bad arg count %d\n",ac);
		return; 
	}
	m->flgs &= ~MUD_UP;
	if(logflg && logf != (FILE *)0)
		fprintf(logf,"mark mud %s down\n",m->mnam);
	if(dbgflg)
		fprintf(logf,"zapentry mark mud %s down\n",m->mnam);
	mud_free_ulist(m);
}



void
mud_def_newmud(buf)
char	*buf;
{
	struct	mudent	*mudp;
	struct	hostent	*hp;
	struct	in_addr	ad;
	int		found = 0;
	short		tmport;
	int		tmpflg = 0;
	int		tmpgen = 0;
	char		*cp;
	char		*xp;
	char		*ho;
	char		*nm;
	char		*pw;

	cp = buf;

	/* scan out flags */
	while(*cp != '\0' && !isspace(*cp)) {
		switch(*cp) {

		case 'C':	/* no-op */
			break;

		case 'W':	/* disused */
			break;

		case 'P':
			tmpflg |= MUD_PEER;
			havepeers++;
			break;

		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			tmpgen = *cp - '0';
			break;

		default:
			fprintf(stderr,"unknown flag %c\n",*cp);
			return;
		}
		cp++;
	}


	/* skip space */
	while(*cp != '\0' && isspace(*cp))
		cp++;


	/* scan out hostname */
	ho = cp;
	while(*cp != '\0' && !isspace(*cp))
		cp++;

	if(*cp == '\0') {
		fprintf(stderr,"malformed mud entry: %s\n",buf);
		return;
	}
	*cp++ = '\0';

	xp = ho;
	while(*xp != '\0' && (*xp == '.' || isdigit(*xp)))
		xp++;


	/* not all digits or dots */
	if(*xp != '\0') {
#ifndef	NO_HUGE_RESOLVER_CODE
		if((hp = gethostbyname(ho)) == (struct hostent *)0) {
			fprintf(stderr,"unknown host %s\n",ho);
			return;
		}
		bcopy(hp->h_addr,&ad,hp->h_length);
#else
		fprintf(stderr,"must use hardcoded IP octets\n");
#endif
	} else {
		unsigned long	f;

		if((f = inet_addr(ho)) == -1L) {
			fprintf(stderr,"cannot interpret %s\n",ho);
			return;
		}
		bcopy(&f,&ad,sizeof(f));
	}




	/* skip space */
	while(*cp != '\0' && isspace(*cp))
		cp++;

	/* scan out port # */
	xp = cp;
	while(*cp != '\0' && !isspace(*cp))
		cp++;

	if(*cp == '\0') {
		fprintf(stderr,"missing mud port: %s\n",ho);
		return;
	}
	*cp++ = '\0';
	tmport = atoi(xp);




	/* skip out spaces */
	while(isspace(*cp))
		cp++;

	/* get name */
	nm = cp;
	while(*cp != '\0' && !isspace(*cp))
		cp++;

	if(*cp == '\0' || *nm == '\0') {
		fprintf(stderr,"missing mud name: %s\n",ho);
		return;
	}
	*cp++ = '\0';





	/* skip out spaces */
	while(isspace(*cp))
		cp++;

	/* get password */
	pw = cp;
	while(*cp != '\0' && !isspace(*cp))
		cp++;

	if(*cp == '\0' || *pw == '\0') {
		fprintf(stderr,"missing mud password: %s\n",nm);
		return;
	}
	*cp++ = '\0';

	/* if new entry */
	if((mudp = getmudent(nm,0)) == (struct mudent *)0) {
		mudp = (struct mudent *)malloc(sizeof(struct mudent));
		if(mudp == (struct mudent *)0) {
			perror("malloc");
			return;
		}
		if(dbgflg)
			printf("alloc new table entry for %s\n",nm);
		mudp->usrs = (struct uent *)0;
		mudp->ucnt = 0;
	} else {
		if(dbgflg)
			printf("clearing mud table entry %s\n",mudp->mnam);
		found++;
		free(mudp->mapw);
		free(mudp->mnam);
		if(mudp->txt != (char *)0)
			free(mudp->txt);
	}


	/* misc text */
	while(isspace(*cp))
		cp++;

	mudp->mapw = malloc((unsigned)(strlen(pw) + 1));
	mudp->mnam = malloc((unsigned)(strlen(nm) + 1));
	if(mudp->mapw == (char *)0 || mudp->mnam == (char *)0) {
		perror("malloc");
		return;
	}
	strcpy(mudp->mapw,pw);
	strcpy(mudp->mnam,nm);

	mudp->txt = (char *)0;
	if(*cp != '\0') {
		char	*jnkp = cp;

		/* lose the newline */
		while(*jnkp != '\0' && *jnkp != '\n')
			jnkp++;
		if(*jnkp == '\n')
			*jnkp = '\0';

		/* save the text */
		mudp->txt = malloc((unsigned)(strlen(cp) + 1));
		if(mudp->txt == (char *)0) {
			perror("malloc");
			return;
		}
		strcpy(mudp->txt,cp);
	}

	mudp->up = (time_t)0;
	mudp->flgs = tmpflg;
	mudp->gen = tmpgen;
	mudp->mport = tmport;
	bcopy(&ad,&mudp->maddr,sizeof(ad));

	if(!found) {
		struct	mudent	*m2;
		struct	mudent	*m1;

		m2 = (struct mudent *)0;
		for(m1 = mt; m1 != (struct mudent *)0; m1 = m1->next) {
#ifdef	STRCASECMP
			if(strcasecmp(m1->mnam,mudp->mnam) >= 0)
#else
			if(strcmp(m1->mnam,mudp->mnam) >= 0)
#endif
				break;
			m2 = m1;
		}

		if(m2 == (struct mudent *)0) {
			mudp->next = mt;
			mt = mudp;
		} else {
			mudp->next = m2->next;
			m2->next = mudp;
		}
		if(dbgflg)
			printf("added mud table entry %s\n",mudp->mnam);
	}
}



void
writepidfile(f)
char	*f;
{
	FILE	*pf;
	if((pf = fopen(f,"w")) != (FILE *)0) {
		fprintf(pf,"%d\n",getpid());
		fclose(pf);
	}
}