/* netcommon.c - Network utility routines */

/* This file contains routines used by the networking code that do not
 * depend on the implementation of the networking code.  The network-specific
 * portions of the descriptor data structure are not used.
 */

#include "copyright.h"

#include <varargs.h>
#include <stdio.h>
#include <string.h>

#ifdef WANT_ANSI
#ifdef __STDC__
#include <stdlib.h>
#include <unistd.h>
#endif
#endif

#include "db.h"
#include "mudconf.h"
#include "interface.h"
#include "command.h"
#include "externs.h"
#include "rwho_clilib.h"

/* ---------------------------------------------------------------------------
 * timeval_sub: return difference between two times as a timeval
 */

struct timeval timeval_sub(struct timeval now, struct timeval then)
{
	now.tv_sec -= then.tv_sec;
	now.tv_usec -= then.tv_usec;
	if (now.tv_usec < 0) {
		now.tv_usec += 1000000;
		now.tv_sec--;
	}
	return now;
}

/* ---------------------------------------------------------------------------
 * msec_diff: return difference between two times in msec
 */

int msec_diff(struct timeval now, struct timeval then)
{
	return ((now.tv_sec - then.tv_sec) * 1000 + 
		(now.tv_usec - then.tv_usec) / 1000);
}

/* ---------------------------------------------------------------------------
 * msec_add: add milliseconds to a timeval
 */

struct timeval msec_add(struct timeval t, int x)
{
	t.tv_sec += x / 1000;
	t.tv_usec += (x % 1000) * 1000;
	if (t.tv_usec >= 1000000) {
		t.tv_sec += t.tv_usec / 1000000;
		t.tv_usec = t.tv_usec % 1000000;
	}
	return t;
}

/* ---------------------------------------------------------------------------
 * update_quotas: Update timeslice quotas
 */

struct timeval update_quotas(struct timeval last, struct timeval current)
{
int	nslices;
DESC	*d;

	nslices = msec_diff(current, last) / mudconf.timeslice;

	if (nslices > 0) {
		DESC_ITER_ALL(d) {
			d->quota += mudconf.cmd_quota_incr * nslices;
			if (d->quota > mudconf.cmd_quota_max)
				d->quota = mudconf.cmd_quota_max;
		}
	}
	return msec_add(last, nslices * mudconf.timeslice);
}

/* ---------------------------------------------------------------------------
 * raw_notify: write a message to a player
 */

void raw_notify(dbref player, const char *msg)
{
DESC	*d;

	if (!msg || !*msg) return;
	if (!(Flags(player) & PLAYER_CONNECT)) return;

	DESC_ITER_PLAYER(player, d) {
		queue_string(d, msg);
		queue_write(d, "\r\n", 2);
	}
}

/* ---------------------------------------------------------------------------
 * raw_broadcast: Send message to players who have indicated flags
 */
#ifdef NEVER
void raw_broadcast (int inflags, char *template,
		    int a1, int a2, int a3, int a4, int a5, int a6)
{
char	*buff;
DESC	*d;

	if (!template || !*template) return;

	buff=alloc_lbuf("raw_broadcast");
	sprintf(buff, template, a1, a2, a3, a4, a5, a6);

	DESC_ITER_CONN(d) {
		if ((Flags(d->player) & inflags) == inflags) {
			queue_string(d, buff);
			queue_write(d, "\r\n", 2);
			process_output(d);
		}
	}
	free_lbuf(buff);
}
#else
void raw_broadcast (va_alist)
va_dcl
{
char	*buff;
DESC	*d;
int inflags;
char *template;
va_list ap;

	va_start(ap);
	inflags = va_arg(ap, int);
	template = va_arg(ap, char *);

	if (!template || !*template) return;

	buff=alloc_lbuf("raw_broadcast");
	vsprintf(buff, template, ap);

	DESC_ITER_CONN(d) {
		if ((Flags(d->player) & inflags) == inflags) {
			queue_string(d, buff);
			queue_write(d, "\r\n", 2);
			process_output(d);
		}
	}
	free_lbuf(buff);
	va_end(ap);
}
#endif

/* ---------------------------------------------------------------------------
 * clearstrings: clear out prefix and suffix strings
 */

void clearstrings(DESC *d)
{
	if (d->output_prefix) {
		free_lbuf(d->output_prefix);
		d->output_prefix = NULL;
	}
	if (d->output_suffix) {
		free_lbuf(d->output_suffix);
		d->output_suffix = NULL;
	}
}

/* ---------------------------------------------------------------------------
 * queue_write: Add text to the output queue for the indicated descriptor.
 */

void queue_write(DESC *d, const char *b, int n)
{
int	left;
char	*buf;
TBLOCK	*tp;

	if (n <= 0) return;

	if (d->output_size + n > mudconf.output_limit)
		process_output(d);

	left = mudconf.output_limit - d->output_size - n;
	if (left < 0) {
		tp = d->output_head;
		if (tp == NULL) {
			STARTLOG(LOG_PROBLEMS,"QUE","WRITE")
				log_text((char *)"Flushing when output_head is null!");
			ENDLOG
		} else {
			STARTLOG(LOG_NET,"NET","WRITE")
				buf = alloc_lbuf("queue_write.LOG");
				sprintf(buf,
					"[%d/%s] Output buffer overflow, %d chars discarded by ",
					d->descriptor, d->addr, tp->hdr.nchars);
				log_text(buf);
				free_lbuf(buf);
				log_name(d->player);
			ENDLOG
			d->output_size -= tp->hdr.nchars;
			d->output_head = tp->hdr.nxt;
			d->output_lost += tp->hdr.nchars;
			if (d->output_head == NULL) d->output_tail = NULL;
			free_lbuf(tp);
		}
	}

	/* Allocate an output buffer if needed */

	if (d->output_head == NULL) {
		tp = (TBLOCK *)alloc_lbuf("queue_write.new");
		tp->hdr.nxt = NULL;
		tp->hdr.start = tp->data;
		tp->hdr.end = tp->data;
		tp->hdr.nchars = 0;
		d->output_head = tp;
		d->output_tail = tp;
	} else {
		tp = d->output_tail;
	}

	/* Now tp points to the last buffer in the chain */

	d->output_size += n;
	d->output_tot += n;
	do {

		/* See if there is enough space in the buffer to hold the
		 * string.  If so, copy it and update the pointers..
		 */

		left = LBUF_SIZE - (tp->hdr.end - (char *)tp);
		if (n <= left) {
			strncpy(tp->hdr.end, b, n);
			tp->hdr.end += n;
			tp->hdr.nchars += n;
			n = 0;
		} else {

			/* It didn't fit.  Copy what will fit and then allocate
			 * another buffer and retry.
			 */

			if (left > 0) {
				strncpy(tp->hdr.end, b, left);
				tp->hdr.end += left;
				tp->hdr.nchars += left;
				b += left;
				n -= left;
			}

			tp = (TBLOCK *)alloc_lbuf("queue_write.extend");
			tp->hdr.nxt = NULL;
			tp->hdr.start = tp->data;
			tp->hdr.end = tp->data;
			tp->hdr.nchars = 0;
			d->output_tail->hdr.nxt = tp;
			d->output_tail = tp;
		}
	} while (n > 0);			
}

void queue_string(DESC *d, const char *s)
{
  if (s)
    queue_write(d, s, strlen(s));
}

void freeqs(DESC *d)
{
TBLOCK	*tb, *tnext;
CBLOCK	*cb, *cnext;

	tb = d->output_head;
	while (tb) {
		tnext = tb->hdr.nxt;
		free_lbuf(tb);
		tb = tnext;
	}
	d->output_head = NULL;
	d->output_tail = NULL;

	cb = d->input_head;
	while (cb) {
		cnext = (CBLOCK *)cb->hdr.nxt;
		free_lbuf(cb);
		cb = cnext;
	}

	d->input_head = NULL;
	d->input_tail = NULL;

	if (d->raw_input)
		free_lbuf(d->raw_input);
	d->raw_input = NULL;
	d->raw_input_at = NULL;
}

/* ---------------------------------------------------------------------------
 * desc_addhash: Add a net descriptor to its player hash list.
 */

static void desc_addhash (DESC *d)
{
dbref	player;
DESC	*hdesc;

	player = d->player;
	hdesc = (DESC *)nhashfind((int) player, &mudstate.desc_htab);
	if (hdesc == NULL) {
		d->hashnext = NULL;
		nhashadd((int) player, (int *) d, &mudstate.desc_htab);
	} else {
		d->hashnext = hdesc;
		nhashrepl((int) player, (int *) d, &mudstate.desc_htab);
	}
}

/* ---------------------------------------------------------------------------
 * desc_delhash: Remove a net descriptor from its player hash list.
 */

static void desc_delhash (DESC *d)
{
DESC	*hdesc, *last;
dbref	player;

	player = d->player;
	last = NULL;
	hdesc = (DESC *)nhashfind((int) player, &mudstate.desc_htab);
	while (hdesc != NULL) {
		if (d == hdesc) {
			if (last == NULL) {
				if (d->hashnext == NULL) {
					nhashdelete((int) player,
						&mudstate.desc_htab);
				} else {
					nhashrepl((int) player,
						(int *)d->hashnext,
						&mudstate.desc_htab);
				}
			} else {
				last->hashnext = d->hashnext;
			}
			break;
		}
		last = hdesc;
		hdesc = hdesc->hashnext;
	}
	d->hashnext = NULL;
}

void welcome_user(DESC *d)
{
	if (d->host_info & H_REGISTRATION)
		fcache_dump(d, mudstate.creg_fcache);
	else
		fcache_dump(d, mudstate.conn_fcache);
}

int fcache_read (FBLOCK **cp, char *filename)
{
int	n, nmax, fd, tchars;
char	*bufp;
FBLOCK	*fp, *tfp;

	/* Free a prior buffer chain */

	fp = *cp;
	while (fp != NULL) {
		tfp = fp->hdr.nxt;
		free_mbuf(fp);
		fp = tfp;
	}
	*cp = NULL;

	/* Read the text file into a new chain */

	close(mudstate.reserved_fileid);
	if ((fd = open(filename, O_RDONLY, 0)) == -1) {

		/* Failure: log the event */

		STARTLOG(LOG_PROBLEMS,"FIL","OPEN")
			bufp = alloc_mbuf("fcache_read.LOG");
			sprintf(bufp, "Couldn't open file '%s'.", filename);
			log_text(bufp);
			free_mbuf(bufp);
		ENDLOG
		mudstate.reserved_fileid = open(DEV_NULL, O_RDWR, 0);
		return -1;
	}

	/* Success.  Allocate and initialize the first buffer */

	fp = (FBLOCK *)alloc_mbuf("fcache_read.first");
	fp->hdr.nxt = NULL;
	fp->hdr.nchars = 0;
	*cp = fp;
	tfp = NULL;
	tchars = 0;

	/* Read in the first chunk of the file */

	nmax = MBUF_SIZE - sizeof(FBLKHDR);
	bufp = fp->data;
	n = read(fd, bufp, nmax);
#ifdef VMS
	if ((n < nmax) && (n > 0))
	{
	    bufp[n] = bufp[n-1];
	    bufp[n-1] = '\r';
	    bufp[++n] = 0;
	}
#endif VMS

	while (n > 0) {

		/* If we didn't read in all we wanted, update the pointers and
		 * try to fill the current buffer
		 */

		fp->hdr.nchars += n;
		tchars += n;
		if (fp->hdr.nchars < (MBUF_SIZE - sizeof(FBLKHDR))) {
			nmax -= n;
			bufp += n;
		} else {

			/* We filled the current buffer.  Go get a new one. */

			tfp = fp;
			fp = (FBLOCK *)alloc_mbuf("fcache_read.next");
			fp->hdr.nxt = NULL;
			fp->hdr.nchars = 0;
			tfp->hdr.nxt = fp;
			nmax = MBUF_SIZE - sizeof(FBLKHDR);
			bufp = fp->data;
		}

		/* Read in the next chunk of the file */

		n = read(fd, bufp, nmax);
#ifdef VMS
		if ((n < nmax) && (n > 0))
		{
		    bufp[n] = bufp[n-1];
		    bufp[n-1] = '\r';
		    bufp[++n] = 0;
		}
#endif VMS
	}
	close(fd);
	mudstate.reserved_fileid = open(DEV_NULL, O_RDWR, 0);

	if (fp->hdr.nchars == 0) {
		free_mbuf(fp);
		if (tfp == NULL)
			*cp = NULL;
		else
			tfp->hdr.nxt = NULL;
	}
	return tchars;
}

void fcache_dump (DESC *d, FBLOCK *fp)
{
	while(fp != NULL) {
		queue_write(d, fp->data, fp->hdr.nchars);
		fp = fp->hdr.nxt;
	}
}

void fcache_send (dbref player, FBLOCK *fp)
{
DESC	*d;

	DESC_ITER_PLAYER(player, d) {
		fcache_dump(d, fp);
	}
}

void fcache_load (dbref player)
{
int	guest, conn, creg, regf, motd, wmotd, quit, down, site, crea, full;
char	*buf;

	guest = fcache_read(&mudstate.guest_fcache, mudconf.guest_file);
	conn = fcache_read(&mudstate.conn_fcache, mudconf.conn_file);
	creg = fcache_read(&mudstate.creg_fcache, mudconf.creg_file);
	regf = fcache_read(&mudstate.regf_fcache, mudconf.regf_file);
	motd = fcache_read(&mudstate.motd_fcache, mudconf.motd_file);
	wmotd = fcache_read(&mudstate.wizmotd_fcache, mudconf.wizmotd_file);
	quit = fcache_read(&mudstate.quit_fcache, mudconf.quit_file);
	down = fcache_read(&mudstate.down_fcache, mudconf.down_file);
	site = fcache_read(&mudstate.site_fcache, mudconf.site_file);
	crea = fcache_read(&mudstate.crea_fcache, mudconf.crea_file);
	full = fcache_read(&mudstate.full_fcache, mudconf.full_file);

	if ((player != NOTHING) && !Quiet(player)) {
		buf = alloc_lbuf("fcache_load");
		sprintf(buf, "File sizes: Guest...%d  Connect...%d  Conn/Reg...%d  Crea/Reg...%d  Motd...%d  Wizmotd...%d  Quit...%d  Down...%d  Full...%d  Conn/Badsite...%d  Newuser...%d",
			guest, conn, creg, regf, motd, wmotd, quit, down,
			full, site, crea);
		notify(player, buf);
		free_lbuf(buf);
	}
}

void fcache_init ()
{
	mudstate.guest_fcache = NULL;
	mudstate.conn_fcache = NULL;
	mudstate.creg_fcache = NULL;
	mudstate.regf_fcache = NULL;
	mudstate.motd_fcache = NULL;
	mudstate.wizmotd_fcache = NULL;
	mudstate.quit_fcache = NULL;
	mudstate.down_fcache = NULL;
	mudstate.site_fcache = NULL;
	mudstate.crea_fcache = NULL;
	mudstate.full_fcache = NULL;
	fcache_load(NOTHING);
}
	
void save_command(DESC *d, const char *command)
{
CBLOCK	*cp;

	cp = (CBLOCK *)alloc_lbuf("save_command");
	cp->hdr.nxt = NULL;
	strcpy(cp->cmd, command);
	if (d->input_tail == NULL)
		d->input_head = cp;
	else
		d->input_tail->hdr.nxt = cp;
	d->input_tail = cp;
}

static void set_userstring(char **userstring, const char *command)
{
	while (*command && isascii(*command) && isspace(*command))
		command++;
	if (!*command) {
		if (*userstring != NULL) {
			free_lbuf(*userstring);
			*userstring = NULL;
		}
	} else {
		if (*userstring == NULL)
			*userstring = alloc_lbuf("set_userstring");
		strcpy(*userstring, command);
	}
}

static void parse_connect(const char *msg, char *command,
	char *user, char *pass)
{
char	*p;

	while (*msg && isascii(*msg) && isspace(*msg))
		msg++;
	p = command;
	while (*msg && isascii(*msg) && !isspace(*msg))
		*p++ = *msg++;
	*p = '\0';
	while (*msg && isascii(*msg) && isspace(*msg))
		msg++;
	 p = user;
	while (*msg && isascii(*msg) && !isspace(*msg))
		*p++ = *msg++;
	*p = '\0';
	while (*msg && isascii(*msg) && isspace(*msg))
		msg++;
	p = pass;
	while (*msg && isascii(*msg) && !isspace(*msg))
		*p++ = *msg++;
	*p = '\0';
}

static const char * time_format_1(time_t dt)
{
register struct tm *delta;
static char buf[64];

	if (dt < 0) dt = 0;

	delta = gmtime(&dt);
	if (delta->tm_yday > 0) {
		sprintf(buf, "%dd %02d:%02d",
			delta->tm_yday, delta->tm_hour, delta->tm_min);
	} else {
		sprintf(buf, "%02d:%02d",
			delta->tm_hour, delta->tm_min);
	}
	return buf;
}

static const char *time_format_2(time_t dt)
{
register struct tm *delta;
static char buf[64];

	if (dt < 0) dt = 0;

	delta = gmtime(&dt);
	if (delta->tm_yday > 0) {
		sprintf(buf, "%dd", delta->tm_yday);
	} else if (delta->tm_hour > 0) {
		sprintf(buf, "%dh", delta->tm_hour);
	} else if (delta->tm_min > 0) {
		sprintf(buf, "%dm", delta->tm_min);
	} else {
		sprintf(buf, "%ds", delta->tm_sec);
	}
	return buf;
}

static void announce_connect(dbref player, DESC *d)
{
dbref	loc, aowner;
int	aflags, num;
char	*buf, *atr_temp, *time_str;
time_t	tt;
DESC	*dtemp;

	desc_addhash(d);
	if (atr_temp = atr_pget(player, A_TIMEOUT, &aowner, &aflags)) {
		d->timeout = atoi(atr_temp);
		if (d->timeout <= 0) d->timeout = mudconf.idle_timeout;
	}
	free_lbuf(atr_temp);

	loc = Location(player);
	s_Flags(player, Flags(player) | PLAYER_CONNECT);

	raw_notify(player, mudconf.motd_msg);
	if (Wizard(player))
		raw_notify(player, mudconf.wizmotd_msg);

	buf=alloc_mbuf("announce_connect");

#ifdef RWHO_IN_USE
	if (mudconf.rwho_transmit && (mudconf.control_flags & CF_RWHO_XMIT)) {
		sprintf(buf, "%d@%s", player, mudconf.mud_name);
		rwhocli_userlogin(buf, Name(player), time((time_t *) 0));
	}
#endif
	num = 0;
	DESC_ITER_PLAYER(player, dtemp) num++;

	if (num < 2)
		sprintf(buf, "%s has connected.", Name(player));
	else
		sprintf(buf, "%s has reconnected.", Name(player));
	notify_except(player, player, player, buf, 1);	/* Thanks, Micro. */
	if ((loc != NOTHING) && !(Dark(player) && Wizard(player)))
		notify_except(loc, player, player, buf, 1);
	free_mbuf(buf);

	if (Suspect(player)) {
		raw_broadcast(WIZARD, (char *)"[Suspect] %s has connected.",
			(int)Name(player), 0, 0, 0, 0, 0);
	}
	if (d->host_info & H_SUSPECT) {
		raw_broadcast(WIZARD,
			(char *)"[Suspect site: %s] %s has connected.",
			(int)(d->addr), (int)Name(player), 0, 0, 0, 0);
	}

	if (atr_temp = atr_pget(player, A_ACONNECT, &aowner, &aflags))
		wait_que(player, player, RU_ARG1_COPY, 0, NOTHING,
			atr_temp, (char **)NULL, 0);
        free_lbuf(atr_temp);

	time(&tt);
	time_str = ctime(&tt);
	time_str[strlen(time_str) - 1] = '\0';
	record_login(player, 1, time_str, d->addr);
	look_in (player, Location(player), 1);
}

void announce_disconnect(dbref player, DESC *d, const char *reason)
{
dbref	loc, aowner;
int	num, aflags;
char	*buf, *atr_temp;
DESC	*dtemp;
char	*argv[1];

	if (Suspect(player)) {
		raw_broadcast(WIZARD, 
			(char *)"[Suspect] %s has disconnected.",
			(int)Name(player), 0, 0, 0, 0, 0);
	}
	if (d->host_info & H_SUSPECT) {
		raw_broadcast(WIZARD,
			(char *)"[Suspect site: %s] %s has disconnected.",
			(int)d->addr, (int)Name(d->player), 0, 0, 0, 0);
	}

	loc = Location(player);
	num = 0;
	DESC_ITER_PLAYER(player, dtemp) num++;

	if (num < 2) {
		buf = alloc_mbuf("announce_disconnect.only");

#ifdef RWHO_IN_USE
		if (mudconf.rwho_transmit &&
		    (mudconf.control_flags & CF_RWHO_XMIT)) {
			sprintf(buf, "%d@%s", player, mudconf.mud_name);
			rwhocli_userlogout(buf);
		}
#endif
		sprintf(buf, "%s has disconnected.", Name(player));
		if ((loc != NOTHING) && !(Dark(player) && Wizard(player)))
			notify_except(loc, player, player, buf, 1);
		notify_except(player, player, player, buf, 1);
		free_mbuf(buf);

		argv[0] = (char *)reason;
		s_Flags(player, Flags(player) & ~PLAYER_CONNECT);
		atr_temp = atr_pget(player, A_ADISCONNECT, &aowner, &aflags);
		if (atr_temp && *atr_temp)
			wait_que(player, player, RU_ARG1_COPY, 0, NOTHING,
				atr_temp, argv, 1);
		free_lbuf(atr_temp);
		if (d->flags & DS_AUTODARK) {
			s_Flags(d->player, Flags(d->player) & ~DARK);
			d->flags &= ~DS_AUTODARK;
		}
	} else {
		buf = alloc_mbuf("announce_disconnect.partial");
		sprintf(buf, "%s has partially disconnected.", Name(player));
		if ((loc != NOTHING) && !(Dark(player) && Wizard(player)))
			notify_except(loc, player, player, buf, 1);
		notify_except(player, player, player, buf, 1);
		free_mbuf(buf);
	}
	desc_delhash(d);
}

int boot_off (dbref player, char *message)
{
DESC	*d, *dnext;
int	count;

	count = 0;
	DESC_SAFEITER_PLAYER(player, d, dnext) {
		if (message && *message) {
			queue_string(d, message);
			queue_string(d, "\r\n");
		}
		shutdownsock(d, R_BOOT);
		count++;
	}
	return count;
}

int boot_by_port (int port, int no_god, char *message)
{
DESC	*d, *dnext;
int	count;

	count = 0;
	DESC_SAFEITER_ALL(d, dnext) {
		if ((d->descriptor == port) && (!no_god || !God(d->player))) {
			if (message && *message) {
				queue_string(d, message);
				queue_string(d, "\r\n");
			}
			shutdownsock(d, R_BOOT);
			count++;
		}
	}
	return count;
}

void check_idle()
{
DESC	*d, *dnext;
time_t	now, idletime;

	time(&now);
	DESC_SAFEITER_ALL(d,dnext) {
		if (d->flags & DS_CONNECTED) {
			idletime = now - d->last_time;
			if (idletime > d->timeout) {
				queue_string(d,
					"*** Inactivity Timeout ***\r\n");
				shutdownsock(d, R_TIMEOUT);
			} else if (mudconf.idle_wiz_dark &&
				   (idletime > mudconf.idle_timeout) &&
				   Wizard(d->player) && !Dark(d->player)) {
				s_Flags(d->player, Flags(d->player) | DARK);
				d->flags |= DS_AUTODARK;
			}
		} else {
			idletime = now - d->connected_at;
			if (idletime > mudconf.conn_timeout) {
				queue_string(d,
					"*** Login Timeout ***\r\n");
				shutdownsock(d, R_TIMEOUT);
			}
		}
	}
}

static void dump_users(DESC *e, char *match, int key)
{
DESC	*d;
int	count;
time_t	now;
char	*buf, *fp, *sp, flist[4], slist[4];

	while (match && *match && isspace(*match))
		match++;
	if (!match || !*match)
		match = NULL;

	buf=alloc_mbuf("dump_users");
	time(&now);
	if (key == CMD_SESSION) {
		queue_string(e, "                               ");
		queue_string(e, "     Characters Input----  Characters Output---\r\n");
	}
	queue_string(e, "Player Name        On For Idle ");
	if (key == CMD_SESSION) {
		queue_string(e, "Port Pend  Lost     Total  Pend  Lost     Total\r\n");
	} else if ((e->flags & DS_CONNECTED) && Wizard(e->player) && (key == CMD_WHO)) {
		queue_string(e, "  Room    Cmds   Host\r\n");
	} else {
		if (Wizard(e->player))
			queue_string(e, "  ");
		else
			queue_string(e, " ");
		queue_string(e, mudstate.doing_hdr);
		queue_string(e, "\r\n");
	}
	count = 0;
	DESC_ITER_CONN(d) {
		if (!Dark(d->player) || Wizard(e->player)) {
			count++;
			if (match && !(string_prefix(Name(d->player), match)))
				continue;
			if ((key == CMD_SESSION) && !Wizard(e->player) &&
			    (d->player != e->player))
				continue;

			/* Get choice flags for wizards */

			fp = flist;
			sp = slist;
			if ((e->flags & DS_CONNECTED) && Wizard(e->player)) {
				if (Dark(d->player)) {
					if (d->flags & DS_AUTODARK)
						*fp++ = 'd';
					else
						*fp++ = 'D';
				}
				if (Unfindable(d->player)) *fp++ = 'U';
				if (Suspect(d->player)) *fp++ = '+';
				if (d->host_info & H_FORBIDDEN) *sp++ = 'F';
				if (d->host_info & H_REGISTRATION) *sp++ = 'R';
				if (d->host_info & H_SUSPECT) *sp++ = '+';
			}
			*fp = '\0';
			*sp = '\0';

			if ((e->flags & DS_CONNECTED) &&
			    Wizard(e->player) &&
			    (key == CMD_WHO)) {
				sprintf(buf,
					"%-16s%9s %4s%-3s#%-6d%5d%3s%s\r\n",
					Name(d->player),
					time_format_1(now - d->connected_at),
					time_format_2(now - d->last_time),
					flist,
					Location(d->player), d->command_count,
					slist,
					d->addr);
			} else if (key == CMD_SESSION) {
				sprintf(buf,
					"%-16s%9s %4s%5d%5d%6d%10d%6d%6d%10d\r\n",
					Name(d->player),
					time_format_1(now - d->connected_at),
					time_format_2(now - d->last_time),
					d->descriptor,
					d->input_size, d->input_lost,
					d->input_tot,
					d->output_size, d->output_lost,
					d->output_tot);
			} else if (Wizard(e->player)) {
				sprintf(buf, "%-16s%9s %4s%-3s%s\r\n",
					Name(d->player),
					time_format_1(now - d->connected_at),
					time_format_2(now - d->last_time),
					flist,
					d->doing);
			} else {
				sprintf(buf, "%-16s%9s %4s  %s\r\n",
					Name(d->player),
					time_format_1(now - d->connected_at),
					time_format_2(now - d->last_time),
					d->doing);
			}
			queue_string(e, buf);
		}
	}

	/* sometimes I like the ternary operator.... */

	sprintf(buf,"%d Player%slogged in.\r\n", count,
		(count == 1) ? " " : "s ");
	queue_string(e, buf);
	free_mbuf(buf);
}

/* ---------------------------------------------------------------------------
 * do_doing: Set the doing string that appears in the WHO report.
 * Idea from R'nice@TinyTIM.
 */

void do_doing (dbref player, dbref cause, int key, char *arg)
{
DESC	*d;
char	*c;
int	foundany, over;

	if (key == DOING_MESSAGE) {
		foundany = 0;
		over = 0;
		DESC_ITER_PLAYER(player, d) {
			c = d->doing;
			over = safe_copy_str(arg, d->doing, &c, 40);
			*c = '\0';
			foundany = 1;
		}
		if (foundany) {
			if (over) {
				notify(player,
					tprintf("Warning: %d characters lost.",
						over));
			}
			if (!Quiet(player)) notify(player, "Set.");
		} else {
			notify(player, "Not connected.");
		}
	} else if (key == DOING_HEADER) {
		if (!arg || !*arg) {
			strcpy(mudstate.doing_hdr, "Doing");
			over = 0;
		} else {
			c = mudstate.doing_hdr;
			over = safe_copy_str(arg, mudstate.doing_hdr, &c, 40);
			*c = '\0';
		}
		if (over) {
			notify(player,
				tprintf("Warning: %d characters lost.", over));
		}
		if (!Quiet(player)) notify(player, "Set.");
	} else {
		notify(player, tprintf("Poll: %s", mudstate.doing_hdr));
	}
}

NAMETAB logout_cmdtable[] = {
{(char *)"DOING",	5,	CA_PUBLIC,	CMD_DOING},
{(char *)"LOGOUT",	6,	CA_PUBLIC,	CMD_LOGOUT},
{(char *)"OUTPUTPREFIX",12,	CA_PUBLIC,	CMD_PREFIX|CMD_NOxFIX},
{(char *)"OUTPUTSUFFIX",12,	CA_PUBLIC,	CMD_SUFFIX|CMD_NOxFIX},
{(char *)"QUIT",	4,	CA_PUBLIC,	CMD_QUIT},
#ifdef LOCAL_RWHO_SERVER
{(char *)"RWHO",	4,	CA_PUBLIC,	CMD_RWHO},
#endif
{(char *)"SESSION",	7,	CA_PUBLIC,	CMD_SESSION},
{(char *)"WHO",		3,	CA_PUBLIC,	CMD_WHO},
{NULL,			0,	0,		0}};

void init_logout_cmdtab()
{
NAMETAB	*cp;

	/* Make the htab bigger than the number of entries so that we find
	 * things on the first check.  Remember that the admin can add aliases.
	 */

	hashinit(&mudstate.logout_cmd_htab, 19);
	for (cp=logout_cmdtable; cp->flag; cp++)
		hashadd(cp->name, (int *)cp, &mudstate.logout_cmd_htab);
}

static void failconn (const char *logcode, const char *logtype,
	const char *logreason, DESC *d, int disconnect_reason, dbref player,
	FBLOCK *filecache, char *motd_msg,
	char *command, char *user, char *password, char *cmdsave)
{
char	*buff;

	STARTLOG(LOG_LOGIN|LOG_SECURITY,logcode,"RJCT")
		buff=alloc_mbuf("failconn.LOG");
		sprintf(buff, "[%d/%s] %s rejected to ",
			d->descriptor, d->addr, logtype);
		log_text(buff);
		free_mbuf(buff);
		if (player != NOTHING)
			log_name(player);
		else
			log_text(user);
		log_text((char *)" (");
		log_text((char *)logreason);
		log_text((char *)")");
	ENDLOG
	fcache_dump(d, filecache);
	if (*motd_msg) {
		queue_string(d, motd_msg);
		queue_write(d, "\r\n", 2);
	}
	free_mbuf(command);
	free_mbuf(user);
	free_mbuf(password);
	shutdownsock(d, disconnect_reason);
	mudstate.debug_cmd = cmdsave;
	return;
}

static const char *connect_fail =
"Either that player does not exist, or has a different password.\r\n";
static const char *create_fail =
"Either there is already a player with that name, or that name is illegal.\r\n";

static int check_connect(DESC *d, const char *msg)
{
char	*command, *user, *password, *buff, *cmdsave;
dbref	player, aowner;
int	aflags, nplayers;
DESC	*d2;

	cmdsave = mudstate.debug_cmd;
	mudstate.debug_cmd = (char *)"< check_connect >";

	/* Hide the password length from SESSION */

	d->input_tot -= (strlen(msg) + 1);

	/* Crack the command apart */

	command = alloc_mbuf("check_conn.cmd");
	user = alloc_mbuf("check_conn.user");
	password = alloc_mbuf("check_conn.pass");
	parse_connect(msg, command, user, password);

	if (!strncmp(command, "co", 2)) {

		/* See if this connection would exceed the max #players */

		if (mudconf.max_players < 0) {
			nplayers = mudconf.max_players - 1;
		} else {
			nplayers = 0;
			DESC_ITER_CONN(d2)
				nplayers++;
		}

		player = connect_player(user, password, d->addr);
		if (player == NOTHING) {

			/* Not a player, or wrong password */

			queue_string(d, connect_fail);
			STARTLOG(LOG_LOGIN|LOG_SECURITY,"CON","BAD")
				buff=alloc_mbuf("check_conn.LOG.bad");
				sprintf(buff, "[%d/%s] Failed connect to '%s'",
					d->descriptor, d->addr, user);
				log_text(buff);
				free_mbuf(buff);
			ENDLOG
			if (--(d->retries_left) <= 0) {
				free_mbuf(command);
				free_mbuf(user);
				free_mbuf(password);
				shutdownsock(d, R_BADLOGIN);
				mudstate.debug_cmd = cmdsave;
				return 0;
			}
		} else if (((mudconf.control_flags & CF_LOGIN) &&
			    (nplayers < mudconf.max_players)) ||
			   Wizard(player) || God(player)) {

			/* Logins are enabled, or wiz or god */

			STARTLOG(LOG_LOGIN,"CON","LOGIN")
				buff=alloc_mbuf("check_conn.LOG.login");
				sprintf(buff, "[%d/%s] Connected to ",
					d->descriptor, d->addr);
				log_text(buff);
				log_name_and_loc(player);
				free_mbuf(buff);
			ENDLOG
			d->flags |= DS_CONNECTED;
			d->connected_at = time(0);
			d->player = player;

			/* Give the player the MOTD file and the settable MOTD
			 * message(s). Use raw notifies so the player doesn't
			 * try to match on the text. */

			if(Guest(player)) {
				fcache_dump(d, mudstate.guest_fcache);
			} else {
				buff = atr_get(player, A_LAST, &aowner, &aflags);
				if ((buff == NULL) || (*buff == '\0'))
					fcache_dump(d, mudstate.crea_fcache);
				else
					fcache_dump(d, mudstate.motd_fcache);
				if (Wizard(player))
					fcache_dump(d, mudstate.wizmotd_fcache);
				free_lbuf(buff);
			}
			announce_connect(player, d);
		} else if (!(mudconf.control_flags & CF_LOGIN)) {
			failconn("CON", "Connect", "Logins Disabled", d,
				R_GAMEDOWN, player, mudstate.down_fcache,
				mudconf.downmotd_msg, command, user, password,
				cmdsave);
			return 0;
		} else {
			failconn("CON", "Connect", "Game Full", d,
				R_GAMEFULL, player, mudstate.full_fcache,
				mudconf.fullmotd_msg, command, user, password,
				cmdsave);
			return 0;
		}
	} else if (!strncmp(command, "cr", 2)) {

		/* Enforce game down */

		if (!(mudconf.control_flags & CF_LOGIN)) {
			failconn("CRE", "Create", "Logins Disabled", d,
				R_GAMEDOWN, NOTHING, mudstate.down_fcache,
				mudconf.downmotd_msg, command, user, password,
				cmdsave);
			return 0;
		}

		/* Enforce max #players */

		if (mudconf.max_players < 0) {
			nplayers = mudconf.max_players;
		} else {
			nplayers = 0;
			DESC_ITER_CONN(d2)
				nplayers++;
		}
		if (nplayers > mudconf.max_players) {

			/* Too many players on, reject the attempt */

			failconn("CRE", "Create", "Game Full", d,
				R_GAMEFULL, NOTHING, mudstate.full_fcache,
				mudconf.fullmotd_msg, command, user, password,
				cmdsave);
			return 0;
		}

		if (d->host_info & H_REGISTRATION) {
			fcache_dump(d, mudstate.regf_fcache);
		} else {
			player = create_player(user, password, NOTHING, 0);
			if (player == NOTHING) {
				queue_string(d, create_fail);
				STARTLOG(LOG_SECURITY|LOG_PCREATES,"CON","BAD")
					buff=alloc_mbuf("check_conn.LOG.badcrea");
					sprintf(buff,
						"[%d/%s] Create of '%s' failed",
						d->descriptor, d->addr, user);
					log_text(buff);
					free_mbuf(buff);
				ENDLOG
			} else {
				STARTLOG(LOG_LOGIN|LOG_PCREATES,"CON","CREA")
					buff=alloc_mbuf("check_conn.LOG.create");
	 				sprintf(buff, "[%d/%s] Created ",
						d->descriptor, d->addr);
					log_text(buff);
					log_name(player);
					free_mbuf(buff);
				ENDLOG
				move_object(player, mudconf.start_room);
				d->flags |= DS_CONNECTED;
				d->connected_at = time(0);
				d->player = player;
				fcache_dump(d, mudstate.crea_fcache);
				announce_connect(player, d);
			}
		}
	} else {
		welcome_user(d);
	}
	free_mbuf(command);
	free_mbuf(user);
	free_mbuf(password);
	mudstate.debug_cmd = cmdsave;
	return 1;
}

int do_command(DESC *d, char *command)
{
char	*arg, *cmdsave;
NAMETAB	*cp;

	cmdsave = mudstate.debug_cmd;
	mudstate.debug_cmd = (char *)"< do_command >";

	/* Split off the command from the arguments */

	arg = command;
	while(*arg && !isspace(*arg)) arg++;
	if (*arg) *arg++ = '\0';

	/* Look up the command.  If we don't find it, turn it over to the
	 * normal logged-in command processor or to create/connect
	 */
	
	cp = (NAMETAB *)hashfind(command, &mudstate.logout_cmd_htab);

	if (cp == NULL) {
		if (*arg) *--arg = ' ';	/* restore nullified space */
		if (d->flags & DS_CONNECTED) {
			d->command_count++;
			if (d->output_prefix) {
				queue_string(d, d->output_prefix);
				queue_write(d, "\r\n", 2);
			}
			mudstate.curr_player = d->player;
			mudstate.curr_enactor = d->player;
			process_command(d->player, d->player, 1,
				command, (char **)NULL, 0);
			if (d->output_suffix) {
				queue_string(d, d->output_suffix);
				queue_write(d, "\r\n", 2);		
			}
			mudstate.debug_cmd = cmdsave;
			return 1;
		} else {
			mudstate.debug_cmd = cmdsave;
			return (check_connect(d, command));
		}
	}

	/* The command was in the logged-out command table.  Perform prefix
	 * and suffix processing, and invoke the command handler.
	 */

	d->command_count++;
	if (!(cp->flag & CMD_NOxFIX)) {
		if (d->output_prefix) {
			queue_string(d, d->output_prefix);
			queue_write(d, "\r\n", 2);
		}
	}
	if ((!check_access(d->player, cp->perm)) ||
	    ((cp->perm & CA_PLAYER) && !(d->flags & DS_CONNECTED))) {
		queue_string(d, "Permission denied.\r\n");
	} else {
		mudstate.debug_cmd = cp->name;
		switch (cp->flag & CMD_MASK) {
		case CMD_QUIT:
			shutdownsock(d, R_QUIT);
			mudstate.debug_cmd = cmdsave;
			return 0;
		case CMD_LOGOUT:
			shutdownsock(d, R_LOGOUT);
			break;
		case CMD_WHO:		
			dump_users(d, arg, CMD_WHO);
			break;
		case CMD_DOING:
			dump_users(d, arg, CMD_DOING);
			break;
		case CMD_SESSION:
			dump_users(d, arg, CMD_SESSION);
			break;
#ifdef LOCAL_RWHO_SERVER
		case CMD_RWHO:
			dump_rusers(d);
			break;
#endif
		case CMD_PREFIX:
			set_userstring(&d->output_prefix, arg);
			break;
		case CMD_SUFFIX:
			set_userstring(&d->output_suffix, arg);
			break;
		default:
			STARTLOG(LOG_BUGS,"BUG","PARSE")
				arg = alloc_lbuf("do_command.LOG");
				sprintf(arg,
					"Prefix command with no handler: '%s'",
					command);
				log_text(arg);
				free_lbuf(arg);
			ENDLOG
		}
	}
	if (!(cp->flag & CMD_NOxFIX)) {
		if (d->output_prefix) {
			queue_string(d, d->output_suffix);
			queue_write(d, "\r\n", 2);
		}
	}
	mudstate.debug_cmd = cmdsave;
	return 1;
}

void process_commands()
{
int	nprocessed;
DESC	*d, *dnext;
CBLOCK	*t;
char	*cmdsave;

	cmdsave = mudstate.debug_cmd;
	mudstate.debug_cmd = (char *)"process_commands";

	do {
		nprocessed = 0;
		DESC_SAFEITER_ALL(d,dnext) {
			if (d->quota > 0 && (t = d->input_head)) {
				d->quota--;
				nprocessed++;
				d->input_head = (CBLOCK *)t->hdr.nxt;
				if (!d->input_head)
					d->input_tail = NULL;
				d->input_size -= (strlen(t->cmd) + 1);
				do_command(d, t->cmd);
				free_lbuf(t);
			}
		}
	} while (nprocessed > 0);
	mudstate.debug_cmd = cmdsave;
}

/* ---------------------------------------------------------------------------
 * site_check: Check for site flags in a site list.
 */

int site_check (struct in_addr host, SITE *site_list)
{
SITE	*this;

	for (this=site_list; this; this=this->next) {
		if ((host.s_addr & this->mask.s_addr) == this->address.s_addr)
			return this->flag;
	}
	return 0;
}

/* --------------------------------------------------------------------------
 * site_list: Display information in a site list
 */

const char *access_statstrings (int flag)
{
const char *str;

	switch (flag) {
	case H_FORBIDDEN:
		str = "Forbidden";
		break;
	case H_REGISTRATION:
		str = "Registration";
		break;
	case 0:
		str = "Unrestricted";
		break;
	default:
		str = "Strange";
	}
	return str;
}

const char *suspect_statstrings (int flag)
{
const char *str;

	if (flag)
		str = "Suspected";
	else
		str = "Trusted";
	return str;
}

void site_list (dbref player, SITE *site_list, const char *header_txt,
	const char *stat_str())
{
char	*buff, *buff1, *str;
SITE	*this;

	buff = alloc_mbuf("site_list.buff");
	buff1 = alloc_sbuf("site_list.addr");
	sprintf(buff, "----- %s -----", header_txt);
	notify(player, buff);
        notify(player, "Address              Mask                 Status");
	for (this=site_list; this; this=this->next) {
		str = (char *)stat_str(this->flag);
		strcpy(buff1, inet_ntoa(this->mask));
		sprintf(buff, "%-20s %-20s %s",
			inet_ntoa(this->address), buff1, str);
		notify(player, buff);
	}

	free_mbuf(buff);
	free_sbuf(buff1);
}

/* ---------------------------------------------------------------------------
 * list_siteinfo: List information about specially-marked sites.
 */

void list_siteinfo (dbref player)
{
	site_list(player, mudstate.access_list, "Site Access",
		access_statstrings);
	site_list(player, mudstate.suspect_list, "Suspected Sites",
		suspect_statstrings);
}

/* ---------------------------------------------------------------------------
 * make_ulist: Make a list of connected user numbers for the LWHO function.
 */

void make_ulist (dbref player, char *buff)
{
char	*cp;
DESC	*d;

	cp = buff;
	DESC_ITER_CONN(d) {
		if (!Wizard(player) && Dark(d->player))
			continue;
		if (cp != buff)
			safe_chr(' ', buff, &cp);
		safe_chr('#', buff, &cp);
		safe_str(tprintf("%d", d->player), buff, &cp);
	}
	*cp = '\0';
}

/* ---------------------------------------------------------------------------
 * find_connected_name: Resolve a playername from the list of connected
 * players using prefix matching.  We only return a match if the prefix
 * was unique.
 */

dbref find_connected_name (dbref player, char *name)
{
DESC	*d;
dbref	found;

	found = NOTHING;
	DESC_ITER_CONN(d) {
		if (Good_obj(player) && !Wizard(player) && Dark(d->player))
			continue;
		if (!string_prefix(Name(d->player), name))
			continue;
		if ((found != NOTHING) && (found != d->player))
			return NOTHING;
		found = d->player;
	}
	return found;
}

/* ---------------------------------------------------------------------------
 * rwho_update: Send RWHO info to the remote RWHO server.
 */


#ifdef RWHO_IN_USE
void rwho_update(void)
{
DESC *d;
char *buf;

	if (!(mudconf.rwho_transmit && (mudconf.control_flags & CF_RWHO_XMIT)))
		return;
	buf = alloc_mbuf("rwho_update");
	rwhocli_pingalive();
	DESC_ITER_ALL(d) {
		if((d->flags & DS_CONNECTED) && !Dark(d->player)) {
			sprintf(buf, "%d@%s", d->player, mudconf.mud_name);
			rwhocli_userlogin(buf, Name(d->player), d->connected_at);
		}
	}
	free_mbuf(buf);
}
#endif