tmuck2.4/
tmuck2.4/admin/scripts/
tmuck2.4/docs/
tmuck2.4/minimal-db/
tmuck2.4/minimal-db/data/
tmuck2.4/minimal-db/logs/
tmuck2.4/minimal-db/muf/
tmuck2.4/old/
tmuck2.4/src/
tmuck2.4/src/compile/
tmuck2.4/src/editor/
tmuck2.4/src/game/
tmuck2.4/src/interface/
tmuck2.4/src/scripts/
tmuck2.4/src/utilprogs/
/* Copyright (c) 1992 by David Moore.  All rights reserved. */
#include "copyright.h"
/* interface.c,v 2.28 1997/08/16 22:31:43 dmoore Exp */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>

#if defined(_AIX) || defined(___AIX)
#include <sys/select.h>
#endif

#include "ansify.h"
#include "db.h"
#include "buffer.h"
#include "text.h"
#include "hostname.h"
#include "muf_con.h"
#include "params.h"
#include "externs.h"



/* Only things in this file should know what descriptor_info has in it,
   and generally everybody should talk to it via the provided interface.
   Namely: check_awake, connected_players, send_player, send_all,
   boot_off, emergency_shutdown, normal_shutdown, dump_status,
   set_up_socket, main_loop. */

/* Have a hash table by dbref of connected players??  This is a pain only
   because of multiply connected players. */
/* fd based searches are always by means of going down the linked list. */

/* Perhaps add in things to count total # of lines sent, total # of
   bytes sent.  Total # of lines received, # bytes received, etc.
   Other useful statistical information like that. */
struct descriptor_info {
    dbref player;		/* dbref of player, NOTHING if not connected */
    int descriptor;		/* fd of the socket connection */

    unsigned int close_down : 1; /* True if this connection should be closed */
    unsigned int output_flushed : 1; /* True if the output flushed. */

    time_t connect_time;	/* connect time of player */
    time_t last_time;		/* last time input received */

    ip_addr_t hostip;		/* address of the player */

    const char *output_prefix;	/* OUTPUTPREFIX */
    const char *output_suffix;	/* OUTPUTSUFFIX */

    struct text *input_queue;	/* lines from player */
    Buffer *input_buffer;	/* buffer for partial input line */
    int quota;			/* number of lines player gets this slice */
    int ed_quota;		/* number of lines when in editor */

    struct text *output_queue;	/* lines to player */
    const char *output_pos;	/* pointer to partially sent output line */

    struct descriptor_info *next;
    struct descriptor_info **prev;
};


int shutdown_flag = 0;
int dump_flag = 0;

static struct descriptor_info *available_descriptors = NULL;
static struct descriptor_info *connections = NULL;
static int ndescriptors;

static const char connect_fail[] = "Either that player does not exist, or has a different password.";
static const char create_fail[] = "Either there is already a player with that name, or that name is illegal.";
/* Used by lockout, if this exact character cannot connect from that site. */
static const char connect_illegal[] = "That character cannot be connected to from that site.";
static const char flushed_message[] = "<Output Flushed>\r\n";
static const char crash_message[] = "\r\nAieeeeee -- splat.\r\n";
static const char shutdown_message[] = "\r\nGoing down -- bye.\r\n";

static void queue_string(struct descriptor_info *, const char *, int);


/* Check size of max_command_len requirements. */
#if (BUFFER_LEN < MAX_COMMAND_LEN)
#error BUFFER_LEN must be >= MAX_COMMAND_LEN
#endif


#ifdef RWHOD
static const char *rwho_uid(const dbref who)
{
    static Buffer buf;

    Bufsprint(&buf, "%d@%s", who, RWHO_NAME);
    return Buftext(&buf);
}


static void rwho_update(void)
{
    struct descriptor_info *d;
    
    rwhocli_pingalive();
    
    for (d = connections; d; d = d->next) {
	if (d->player != NOTHING) {
	    rwhocli_userlogin(rwho_uid(d->player),
			      GetName(d->player), d->connect_time);
	}
    }
}
#endif /* RWHOD */


static void generate_descriptors(long count)
{
    struct descriptor_info *hunk;
    int num_4k;
    int i, j;
 
    /* Try allocating them in 4K sets.  The -4 is for some mallocs
       which want to stick some overhead in the space.  This keeps
       things on a single page (hopefully). */
    num_4k = (4096 - 4) / sizeof(struct descriptor_info);
 
    count = (count / num_4k) + 1;
 
    for (i = 0; i < count; i++) {
        MALLOC(hunk, struct descriptor_info, num_4k);
        for (j = 0; j < num_4k; j++) {
            hunk->next = available_descriptors;
            available_descriptors = hunk;
            hunk++;
        }
    }
}


static struct descriptor_info *make_new_descriptor(void)
{
    struct descriptor_info *result;

    if (!available_descriptors) {
	generate_descriptors(1);
    }
    result = available_descriptors;
    available_descriptors = result->next;

    return result;
}


static void free_descriptor(struct descriptor_info *entry)
{
    entry->next = available_descriptors;
    available_descriptors = entry;
}


static void process_output(struct descriptor_info *d)
{
    int cnt;
    int actual;

    if (d->close_down) return;	/* Marked to close, don't try to send. */

    while (1) {
	/* Loop taking lines off the front and trying to send them.
	   Stop if you run out of lines to send, or a write fails. */

	if (!d->output_pos) {
	    /* No partial line left from last time, fetch a new line! */
	
	    if (d->output_flushed) {
		/* Since output got flushed, sneak the message in on top
		   of the queue now. */
		insert_text(d->output_queue,
			    flushed_message, sizeof(flushed_message)-1,
			    TEXT_INSERT_FRONT);
		d->output_flushed = 0;
	    }

	    d->output_pos = text_line(d->output_queue, TEXT_FIRST_LINE, &cnt);
	} else {
	    /* Recalculate the characters remaining. */
	    const char *start;
	
	    start = text_line(d->output_queue, TEXT_FIRST_LINE, &cnt);
	    cnt -= (d->output_pos - start);
	}

	if (!d->output_pos) return; /* Out of lines to send. */

	/* Try to do the write. */
	actual = write(d->descriptor, d->output_pos, cnt);
	if (actual < 0) {
	    /* Couldn't write anything.  If it's any reason except blocking
	       mark the socket to be closed. */
	    if (errno != EWOULDBLOCK)
		d->close_down = 1;
	    return;
	}

	/* #### should kick out if we get actual=0.  or maybe if it
	   happens twice in a row or something.  basically treat it
	   like EWOULDBLOCK. */

	if (actual == cnt) {
	    /* The line was entirely written.  Delete it off of the
	       front, and fetch the next line. */
	    delete_text(d->output_queue, TEXT_FIRST_LINE, TEXT_FIRST_LINE);
	    d->output_pos = text_line(d->output_queue, TEXT_FIRST_LINE, &cnt);
	} else {
	    /* Partially written line.  Skip past amount really written
	       and decrement amount remaining to write. */
	    d->output_pos += actual;
	    cnt -= actual;
	}
    }
}


/* Checks if a player is connected to the server. */
int check_awake(const dbref player)
{
    /* Jingoro 5/21/91 */
    struct descriptor_info *d;
    int retval = 0;

    for (d = connections; d; d = d->next) {
	if (d->player == player)
	    retval++;
    }
    return retval;
}


/* Returns an array of dbrefs of connected players. */
dbref *connected_players(int *count)
{
    static dbref *who = NULL;
    static int size = 255;	/* Initial size of array, later actual size. */
    
    dbref *temp;
    int number;
    struct descriptor_info *d;

    /* Init the array if needed to the initial size. */
    if (who == NULL) MALLOC(who, dbref, size);
	
    /* First compute the number. */
    for (number = 0, d = connections; d; d = d->next) {
	if (d->player != NOTHING) number++;
    }

    /* Possibly resize array. */
    if (number > size) {
	REALLOC(who, dbref, number);
	size = number;
    }

    /* Fill in the array. */
    for (temp = who, d = connections; d; d = d->next) {
	if (d->player != NOTHING)
	    *temp++ = d->player;
    }
	
    *count = number;
    return who;
}


/* returns an array of descriptors of connected players */
int *connected_cons(int *count)
{
    /* Ben 8/4/92 */
    static int *list = NULL;
    static int size = 255; /* initial size, updated when the array expands */
 
    int *temp;
    int number;
    struct descriptor_info *d;

    if (!list) MALLOC(list, int, size);
 
    for (number = 0, d = connections; d; d = d->next)
        if(d->player != NOTHING)
            number++;
 
    if (number > size) {
	REALLOC(list, int, number);
	size = number;
    }
 
    for (temp = list, d = connections; d; d = d->next)
        if (d->player != NOTHING)
            *temp++ = d->descriptor;
 
    *count = number;
    return list;
}
 
/* fetches the info on connection, translates it to muf_ format */
int con_info(int descr, struct muf_con_info *dest)
{
    /* Ben 8/4/92 */
    struct descriptor_info *d;

    for (d = connections; d; d = d->next)
        if (d->descriptor == descr) {
            dest->player = d->player;
            dest->connect_time = d->connect_time;
	    dest->last_time = d->last_time;
            dest->hostname = lookup_host(d->hostip, HOSTNAME_NO_RESOLVE);
 
            return 1;  /* success! */
        }
 
    return 0;  /* failure */
}

 
/* boots a descriptor */
int boot_off_d(int descr)
{
    /* Ben 8/4/92 */
    struct descriptor_info *d;
 
    for (d = connections; d; d = d->next)
        if (d->descriptor == descr) {
            process_output(d);
            d->close_down = 1;
 
            return 1;  /* success! */
        }
 
    return 0;  /* failure */
}
 

/* Sends a message to the given player. */
int send_player(const dbref player, const char *msg, const int len)
{
    struct descriptor_info *d;
    int retval = 0;

    for (d = connections; d; d = d->next) {
	if (d->player == player) {
	    queue_string(d, msg, len);
	    retval++;
	}
    }
    return retval;
}


/* Send a message to connected players in specified room, unless the
   player is to be skipped. */
void send_except(const dbref where, const dbref except, const char *msg, const int len)
{
    struct descriptor_info *d;

    for (d = connections; d; d = d->next) {
	if ((d->player != NOTHING)
	    && (GetLoc(d->player) == where)
	    && (d->player != except)) {
	    queue_string(d, msg, len);
	}
    }
}


/* Send a message to all connected players.  If important, then try to
   flush the output queue also. */
/* Hmm, maybe important message should even go to non-logged in players? */
void send_all(const char *msg, const int len, const int important)
{
    struct descriptor_info *d;

    for (d = connections; d; d = d->next) {
	/* Skip non-connected connections. */
	if (d->player != NOTHING) {
	    queue_string(d, msg, len);
	    if (important)
		process_output(d); /* Make sure it can be seen. */
	}
    }
}


/* These timeval routines blatantly kept from original tinymud.  Should
   be totally nuked and stuck in a separate timequeue.c file to handle
   all of this stuff in a sane manner. */
static struct timeval timeval_sub(struct timeval now, const 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;
}


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


static struct timeval timeval_min(const struct timeval time1, const struct timeval time2)
{
    if (msec_diff(time1, time2) < 0)
        return time1;
    else
        return time2;
}


static struct timeval msec_add(struct timeval t, const 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;
}


#define get_quota(d) \
    ((d)->player != NOTHING && HasFlag((d)->player, IN_EDITOR) \
     ? (d)->ed_quota : (d)->quota)
#define dec_quota(d) \
    ((d)->player != NOTHING && HasFlag((d)->player, IN_EDITOR) \
     ? (d)->ed_quota-- : (d)->quota--)

static struct timeval update_quotas(struct timeval last, const struct timeval curr)
{
    int nslices;
    struct descriptor_info *d;
    
    nslices = msec_diff(curr, last) / COMMAND_TIME_MSEC;
    
    if (nslices > 0) {
	for (d = connections; d; d = d->next) {
	    d->quota += COMMANDS_PER_TIME * nslices;
	    if (d->quota > COMMAND_BURST_SIZE)
		d->quota = COMMAND_BURST_SIZE;

	    d->ed_quota += COMMANDS_PER_TIME * nslices * 5;
	    if (d->ed_quota > COMMAND_BURST_SIZE * 5)
		d->ed_quota = COMMAND_BURST_SIZE * 5;
	}
    }
    return msec_add(last, nslices * COMMAND_TIME_MSEC);
}


static void welcome_user(struct descriptor_info *d)
{
    FILE *f;
    Buffer buf;
    
    f = fopen(WELC_FILE, "r");

    if (f == NULL) {
	queue_string(d, DEFAULT_WELCOME_MESSAGE,
		     sizeof(DEFAULT_WELCOME_MESSAGE)-1);
	log_status("FILE: %s not found.", WELC_FILE);
    } else {
	while (Bufgets(&buf, f, NULL)) {
	    queue_string(d, Buftext(&buf), Buflen(&buf));
	}
	fclose(f);
    }
}


static struct descriptor_info *initializesock(const int s, ip_addr_t address)
{
    struct descriptor_info *d;
    int fcntl_result = 0;
    
    ndescriptors++;
#ifdef FNONBLOCK
    fcntl_result = fcntl(s, F_SETFL, FNONBLOCK); /* Preferred posix name. */
#else
#ifdef O_NDELAY
    fcntl_result = fcntl(s, F_SETFL, O_NDELAY);	/* Sys5 name. */
#else
    fcntl_result = fcntl(s, F_SETFL, FNDELAY); /* BSD name, wrong for sys5. */
#endif /* O_NDELAY */
#endif /* FNONBLOCK */
    if (fcntl_result == -1) {
	log_status("PERROR: initialsizesock: fcntl -- %s", strerror(errno));
	panic("Nonblocking fcntl failed");
    }

    d = make_new_descriptor();

    d->player = NOTHING;
    d->descriptor = s;

    d->close_down = 0;
    d->output_flushed = 0;

    d->connect_time = time(NULL);
    d->last_time = time(NULL);

    d->hostip = address;

    d->output_prefix = NULL;
    d->output_suffix = NULL;

    d->input_queue = make_new_text();
    d->input_buffer = NULL;
    d->quota = COMMAND_BURST_SIZE;
    d->ed_quota = COMMAND_BURST_SIZE * 5;

    d->output_queue = make_new_text();
    d->output_pos = NULL;

    if (connections)
	connections->prev = &d->next;
    d->next = connections;
    d->prev = &connections;
    connections = d;
    
    welcome_user(d);
    return d;
}


static struct descriptor_info *new_connection(const int sock)
{
    int newsock;
    struct sockaddr_in addr;
    int addr_len;
    ip_addr_t address;
    const char *hostname;
    
    addr_len = sizeof(addr);
    newsock = accept(sock, (struct sockaddr *) &addr, &addr_len);
    if (newsock < 0) {
	return 0;
    } else {
	address = addr.sin_addr.s_addr;
	hostname = lookup_host(address, HOSTNAME_OK_RESOLVE);
	if (!ok_conn_site(hostname)) {
	    log_status("REFUSED: connection from %s(%i) on fd %i.",
		       hostname, ntohs(addr.sin_port), newsock);
	    shutdown(newsock, 2);
	    close(newsock);
	    errno = 0;
	    return 0;
	} else {
	    log_status("ACCEPT: %s(%i) on fd %i.",
		       hostname, ntohs(addr.sin_port), newsock);
	    return initializesock(newsock, address);
	}
    }
}


static void shutdownsock(struct descriptor_info *d)
{
    shutdown(d->descriptor, 2);
    close(d->descriptor);

    if (d->output_prefix) FREE_STRING(d->output_prefix);
    if (d->output_suffix) FREE_STRING(d->output_suffix);

    free_text(d->input_queue);
    if (d->input_buffer) FREE(d->input_buffer);

    free_text(d->output_queue);

    *d->prev = d->next;
    if (d->next)
	d->next->prev = d->prev;
    free_descriptor(d);

    ndescriptors--;
}


static void close_connection(struct descriptor_info *d)
{
    const char *hostname;

    hostname = lookup_host(d->hostip, HOSTNAME_NO_RESOLVE);

    if (d->player == NOTHING) {
	log_status("DISCONNECT: Never connected from %s on fd %i.",
		   hostname, d->descriptor);
    } else {
	log_status("DISCONNECT: %u from %s on fd %i.",
		   d->player, d->player, hostname, d->descriptor);
	do_disconnect(d->player);
#ifdef RWHOD	
	rwhocli_userlogout(rwho_uid(d->player));
#endif
    }

    shutdownsock(d);
}


static void queue_string(struct descriptor_info *d, const char *s, int len)
{
    int where;
    int nbytes;

    /* we don't send anything if connection closed, so don't queue it. */
    if (d->close_down) return;

    /* Get the total number of bytes if we were to add this string (len),
       plus "\r\n" (2), to the queue. */
    nbytes = len + 2 + text_total_bytes(d->output_queue);

    if (nbytes > MAX_OUTPUT) {
	/* It'll be too much stuff, flush some output.  If a partial 
	   line is still waiting to be sent then start flushing from
	   line 2, otherwise from the start (line 1).  Flush enough
	   bytes to hold the string and the "\r\n". */
	where = TEXT_FIRST_LINE;
	if (d->output_pos) where++;
	flush_text(d->output_queue, where, len + 2);

	d->output_flushed = 1;
    }

    /* Insert the text on the last line of the queue. */
    insert2_text(d->output_queue, s, len, "\r\n", 2, TEXT_INSERT_END);
}


static void save_command(struct descriptor_info *d, Buffer *buf)
{
    insert_text(d->input_queue, Buftext(buf), Buflen(buf), TEXT_INSERT_END);
}


static void process_input(struct descriptor_info *d)
{
    Buffer buf;			/* To hold data read over socket. */
    int got;			/* # characters read in. */
    char *from;			/* Pointer into buf. */
    char *end;			/* Pointer to end of data in buf = from+got. */
    Buffer *to;			/* Same as d->input_buffer. */
    
    if (d->close_down) return;	/* Marked to close, don't bother reading. */

    /* If no buffer to hold text, make one. */
    if (!d->input_buffer) {
	MALLOC(d->input_buffer, Buffer, 1);
	Bufcpy(d->input_buffer, "");
    }
    to = d->input_buffer;

    /* Try to read in some data. */
    got = read(d->descriptor, Buftext(&buf), BUFFER_LEN);
    if (got <= 0) {
	/* Mark this socket to be closed. */
	d->close_down = 1;
	return;
    }

    /* Loop over all read characters copying them into the other buffer.
       Skip over nonprintables, and don't copy more than MAX_COMMAND_LEN.
       When a newline is seen store the line away, and reset the buffer
       back to the beginning to hold more data. If we see a backspace or
       delete, remove the previous character to make life easier for
       people with certain telnets. */
    for (from = Buftext(&buf), end = from + got; from < end; from++) {
	if (*from == '\n') {
	    if (Buflen(d->input_buffer)) {
		save_command(d, to);
		Bufcpy(to, "");
	    }
	} else if (isascii(*from) && isprint(*from)
		   && (Buflen(to) < MAX_COMMAND_LEN)) {
	    Bufcat_char(to, *from);
	} else if ((*from == '\b') || (*from == '\177')) {
	    Bufdel_char(to);
	}
    }

    /* No text left in the buffer. */
    if (!Buflen(to)) FREE(d->input_buffer);
}


static void set_userstring(const char **userstring, const char *command)
{
    if (*userstring) FREE_STRING(*userstring);

    while (*command && isspace(*command))
	command++;

    if (*command)
	*userstring = ALLOC_STRING(command);
}


static void parse_connect(const char *msg, char *command, char *user, char *pass)
{
    /* Get command. connect/create/etc. */
    while (*msg && isspace(*msg)) msg++;
    while (*msg && !isspace(*msg)) *command++ = *msg++;
    *command = '\0';

    /* Get user name. */
    while (*msg && isspace(*msg)) msg++;
    while (*msg && !isspace(*msg)) *user++ = *msg++;
    *user = '\0';

    /* Get password. */
    while (*msg && isspace(*msg)) msg++;
    while (*msg && !isspace(*msg)) *pass++ = *msg++;
    *pass = '\0';
}



static void check_connect(struct descriptor_info *d, const char *msg)
{
    Buffer command_buf, user_buf, password_buf;
    char *command, *user, *password;
    const char *hostname;
    dbref player;
    
    hostname = lookup_host(d->hostip, HOSTNAME_NO_RESOLVE);

    command = Buftext(&command_buf);
    user = Buftext(&user_buf);
    password = Buftext(&password_buf);

    parse_connect(msg, command, user, password);
    
    if (!strncmp (command, "co", 2)) {
	player = lookup_player(user);
	if ((player != NOTHING) && !ok_player_site(hostname, player)) {
	    /* This site locked out to this character. */
	    queue_string(d, connect_fail, sizeof(connect_fail)-1);
	    queue_string(d, connect_illegal, sizeof(connect_illegal)-1);
	    log_status("ILLEGAL CONNECT: %u from %s on fd %i.",
		       player, player, hostname, d->descriptor);
	    return;
	}

	player = connect_player(user, password);
	if (player == NOTHING) {
	    queue_string(d, connect_fail, sizeof(connect_fail)-1);
	    log_status("FAILED CONNECT: %s from %s on fd %i.",
		       user, hostname, d->descriptor);
	    return;
	}
    } else if (!strncmp(command, "cr", 2)) {
	if (!ok_create_site(hostname)) {
	    /* This site locked out to creation. */
	    queue_string(d, REG_MSG, sizeof(REG_MSG)-1);
	    log_status("ILLEGAL CREATE: %s from %s on fd %i.",
		       user, hostname, d->descriptor);
	    return;
	}

	player = create_player(user, password);
	if (player == NOTHING) {
	    queue_string(d, create_fail, sizeof(create_fail)-1);
	    log_status("FAILED CREATE: %s from %s on fd %i.",
		       user, hostname, d->descriptor);
	    return;
	} else {
	    log_status("CREATED: %u from %s on fd %i.",
		       player, player, hostname, d->descriptor);
	}
    } else {
	welcome_user(d);
	return;
    }
    /* Should only get here if connecting or just created. */
    log_status("CONNECTED: %u from %s on fd %i.",
	       player, player, hostname, d->descriptor);

    d->connect_time = time(NULL);
    d->player = player;

#ifdef RWHOD
    rwhocli_userlogin(rwho_uid(player), GetName(player), d->connect_time);
#endif

    do_connect(player);
}


static char *time_format_1(const long dt)
{
    static Buffer buf;

    if (dt >= TIME_DAY(1)) {
	Bufsprint(&buf, "%T", "%Jd %H:%M", dt);
    } else {
	Bufsprint(&buf, "%T", "%H:%M", dt);
    }

    return Buftext(&buf);
}


static char *time_format_2(const long dt)
{
    static Buffer buf;

    Bufsprint(&buf, "%T", "%i", dt);
    return Buftext(&buf);
}


static void dump_users(struct descriptor_info *e, const char *user)
{
    struct descriptor_info *d;
    int wizard, players;
    time_t now;
    const char *hostname;
    const char *header;
    Buffer pbuf;
    /* This buffers should be of sufficient size.  Ideally bufsprint
       should be fixed to accept field length designators then this
       could be done right.  But this sizes should work. */
    char buf[2048];
    
    wizard = (e->player == NOTHING) ? 0 : Wizard(e->player);
    
    while (*user && isspace(*user)) user++;
    if (!*user) user = NULL;
    
    (void) time(&now);

    if (wizard) {
#ifdef HOSTNAMES
	header = "Player Name               Location     On For Idle Host";
#else
	header = "Player Name                         Location     On For Idle Host";
#endif
    } else {
	header = "Player Name          On For Idle";
    }
    queue_string(e, header, strlen(header));
    
    
    d = connections;
    players = 0;
    while (d) {
	if ((d->player != NOTHING) && ++players &&
#ifdef DRUID_MUCK
	    (!HasFlag(d->player, INVISIBLE) || (wizard)) &&
#endif
	    (!user || muck_strprefix(GetName(d->player), user))) {
	    if (wizard) {
		hostname = lookup_host(d->hostip, HOSTNAME_NO_RESOLVE);
#ifdef HOSTNAMES
		Bufsprint(&pbuf, "%n(#%d)", d->player, d->player);
		sprintf(buf, "%-*s [%6ld] %10s %4s %-.26s",
			PLAYER_NAME_LIMIT + 9, Buftext(&pbuf),
			GetLoc(d->player),
			time_format_1(now - d->connect_time),
			time_format_2(now - d->last_time),
			hostname);
#else
			
		Bufsprint(&pbuf, "%u", e->player, d->player);
		sprintf(buf,"%-*s [%6ld] %10s %4s %-.26s",
			PLAYER_NAME_LIMIT + 19, Buftext(&pbuf),
			GetLoc(d->player),
			time_format_1(now - d->connect_time),
			time_format_2(now - d->last_time),
			hostname);
#endif
	    } else {
		sprintf(buf, "%-*s %10s %4s",
			PLAYER_NAME_LIMIT, GetName(d->player),
			time_format_1(now - d->connect_time),
			time_format_2(now - d->last_time));
	    }
	    queue_string(e, buf, strlen(buf));
	}
	d = d->next;
    }

    sprintf(buf, "%d player%s %s connected.", players,
	    (players == 1) ? "" : "s", (players == 1) ? "is" : "are");
    queue_string(e, buf, strlen(buf));
}


static void do_command(struct descriptor_info *d, const char *command, int len)
{
    if (!strcmp(command, QUIT_COMMAND)) {
	write(d->descriptor, LEAVE_MESSAGE, sizeof(LEAVE_MESSAGE)-1);
	d->close_down = 1;

    } else if (!strncmp(command, WHO_COMMAND, sizeof(WHO_COMMAND) - 1)) {
	if (d->output_prefix)
	    queue_string(d, d->output_prefix, strlen(d->output_prefix));
	dump_users(d, command + sizeof(WHO_COMMAND) - 1);
	if (d->output_suffix)
	    queue_string(d, d->output_suffix, strlen(d->output_suffix));

    } else if (!strncmp(command, PREFIX_COMMAND, sizeof(PREFIX_COMMAND) - 1)) {
	set_userstring(&d->output_prefix, command+sizeof(PREFIX_COMMAND) - 1);

    } else if (!strncmp(command, SUFFIX_COMMAND, sizeof(SUFFIX_COMMAND) - 1)) {
	set_userstring(&d->output_suffix, command+sizeof(SUFFIX_COMMAND) - 1);

    } else {
	if (d->player != NOTHING) {
	    /* Already connected player. */
	    if (d->output_prefix)
		queue_string(d, d->output_prefix, strlen(d->output_prefix));
	    process_command(d->player, command, len);
	    if (d->output_suffix)
		queue_string(d, d->output_suffix, strlen(d->output_suffix));
	} else {
	    /* Someone at welcome screen. */
	    check_connect(d, command);
	}
    }
}


static void process_commands(void)
{
    int nprocessed;
    struct descriptor_info *d;
    const char *command;
    int length;
    struct timeval start, now;

    /* In the old days most commands ran the same amount of time, with
       muf this is no longer entirely true.  So instead of processing
       until nothing more can be done which might take a long time, we
       kick out of processing if half of a second has passed. */
    /* #### ideally we should penalize a person for running a long
       program and not penalize people running shorter things.  This
       would involve potentially changing how the quotas are
       allocated, or maybe just subtracting off quota proportional to
       the command length instead of a constant 1.  You will still
       want some sort of kick out every so often to keep the perceived
       response time good, since no output is sent when in this
       loop. */

    gettimeofday(&start, NULL);
    do {
	nprocessed = 0;
	for (d = connections; d; d = d->next) {
	    if (d->close_down)
		continue;	/* Don't try to process this socket further. */
	    if (get_quota(d) > 0 && text_total_lines(d->input_queue)) {
		dec_quota(d);
		nprocessed++;
		command = text_line(d->input_queue, TEXT_FIRST_LINE, &length);
		do_command(d, command, length);
		delete_text(d->input_queue, TEXT_FIRST_LINE, TEXT_FIRST_LINE);
	    }
	}
	gettimeofday(&now, NULL);
    } while ((nprocessed > 0)
	     && (msec_diff(now, start) < (COMMAND_TIME_MSEC / 4)));
}


/* which == -1, boot all.
   which == 0, boot top.
   which == 1, boot most idle. */
int boot_off(const dbref player, int which)
{
    /* Fixed 2/29/92 by dmoore, no more @boot bug. */
    struct descriptor_info *d;
    struct descriptor_info *idlest;
    int cnt = 0;
    
    for (d = connections; d; d = d->next) {
	if (!d->close_down && d->player == player) {
	    if (which == -1) {
		process_output(d);	/* So they see the farewell message. */
		d->close_down = 1;
		cnt++;
	    } else if (which == 0) {
		process_output(d);
		d->close_down = 1;
		return 1;
	    } else {
		if (difftime(d->last_time, idlest->last_time) < 0) {
		    idlest = d;
		}
	    }
	}
    }

    if (which == 1) {
	process_output(idlest);
	idlest->close_down = 1;
	return 1;
    }

    return cnt;
}


void emergency_shutdown(void)
{
    struct descriptor_info *d;
    
    for (d = connections; d; d = d->next) {
	if (!d->close_down)
	    write(d->descriptor, crash_message, sizeof(crash_message)-1);
	if (shutdown(d->descriptor, 2) < 0)
	    log_status("PERROR: emergency_shutdown: shutdown -- %s",
		       strerror(errno));
	close(d->descriptor);
    }
    close_all_files();
}


void normal_shutdown(void)
{
    struct descriptor_info *d, *dnext;
    
    for (d = connections; d; d = dnext) {
	dnext = d->next;
	process_output(d);
	if (!d->close_down)
	    write(d->descriptor, shutdown_message, sizeof(shutdown_message)-1);

	shutdownsock(d);
    }
}


void dump_status(int signum)
{
    struct descriptor_info *d;
    time_t now;
    Buffer buf;
    const char *hostname;
    
    (void) time(&now);
    fprintf(stderr, "STATUS REPORT: ----------\n");
    for (d = connections; d; d = d->next) {
	if (d->player == NOTHING) {
	    Bufcpy(&buf, "CONNECT: ");
	} else {
	    Bufsprint(&buf, "PLAYING: %u ", d->player, d->player);
	}
	hostname = lookup_host(d->hostip, HOSTNAME_NO_RESOLVE);
	Bufcatlist(&buf, "HOSTNAME: ", hostname,
		   ", idle %d seconds, on fd %d.\n",
		   (const char *) 0);
	
	fprintf(stderr, Buftext(&buf), (int) difftime(now, d->last_time),
		d->descriptor);
    }
    fprintf(stderr, "END REPORT: -------------\n");
    signal(signum, dump_status);
}




/* Returning a listening fd on the specified port.  If the port is
   numeric then use that port #, if it is a text string then lookup
   that service name.  Unbind the port if it is already in use.
   Die if something doesn't work. */
int set_up_socket(const char *port)
{
    int sockfd, opt;
    u_short port_num;
    struct sockaddr_in serv_addr;
    
    /* Open up a tcp stream socket. */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	log_status("PERROR: set_up_socket: socket -- %s", strerror(errno));
	exit(1);
    }

    /* Set the address as reusable. */
    opt = 1;
    if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR,
		    (char *) &opt, sizeof(opt)) < 0) {
	log_status("PERROR: set_up_socket: setsockopt -- %s",
		   strerror(errno));
	exit(1);
    }
    
    /* Set up the port number. */
    port_num = atoi(port);
    if (port_num != 0) {
	/* It was a number, so convert to network byte order. */
	port_num = htons(port_num);
    } else {
	/* It wasn't a number, so look it up by service name. */
	struct servent *sp;
	
	if ((sp = getservbyname(port, "tcp")) == NULL) {
	    fprintf(stderr, "can't find requested service: %s\n", port);
	    exit(1);
	}
	
	port_num = sp->s_port;
    }
    
    /* Set up the serv_addr structure. */
    memset(&serv_addr, '\0', sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = port_num;	/* Already in network byte order. */
    
    /* Bind to the port. */
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
	log_status("PERROR: set_up_socket: bind -- %s", strerror(errno));
	exit(1);
    }
    
    return (sockfd);
}


void main_loop(const int listen_sock)
{
    struct fd_set input_set, output_set;
    time_t now;
    struct timeval last_slice, current_time;
    struct timeval next_slice;
    struct timeval timeout, slice_timeout;
#ifdef RWHOD
    struct timeval rwho_timeout;
#endif
    int maxd;
    struct descriptor_info *d, *dnext;
    struct descriptor_info *newd;
    int avail_descriptors;
    time_t last_rwho_update;
    time_t last_dump_time;
    
    maxd = listen_sock + 1;
    gettimeofday(&last_slice, (struct timezone *) 0);
    time(&now);
    last_rwho_update = now;
    last_dump_time = now;
        
    /* 4 fd's needed for: status log, stderr, muf/dump/help/other files,
       accepting socket. */
    avail_descriptors = muck_max_fds() - 4;
    /* I think that rwho uses one more, check that. FIX FIX FIX */
    
    /* Start listening. */
    listen(listen_sock, 5);
    while (shutdown_flag == 0) {
	
	/* Check to see if it's time to dump the muck. */
	time(&now);
	if (dump_flag || difftime(now, last_dump_time) >= DUMP_INTERVAL) {
	    fork_and_dump();
	    last_dump_time = now;
	    dump_flag = 0;
	    time(&now);		/* Dumping might take a long time. */
	}

#ifdef RWHOD
	if (difftime(now, last_rwho_update) >= RWHO_INTERVAL) {
	    rwho_update();
	    last_rwho_update = now;
	}
#endif
	
	gettimeofday(&current_time, NULL);
	last_slice = update_quotas(last_slice, current_time);
	
	process_commands();

	if (shutdown_flag)
	    break;

	/* Close down anyone who QUIT or was booted, or any sockets which
	   closed for other reasons. */
	for (d = connections; d; d = dnext) {
	    dnext = d->next;
	    if (d->close_down) close_connection(d);
	}

	/* Set up the fdsets for select. */
	FD_ZERO(&input_set);
	FD_ZERO(&output_set);
	if (ndescriptors < avail_descriptors)
	    FD_SET(listen_sock, &input_set);
	for (d = connections; d; d = d->next) {
	    if (!text_total_lines(d->input_queue))
		FD_SET(d->descriptor, &input_set);
	    if (text_total_lines(d->output_queue))
		FD_SET(d->descriptor, &output_set);
	}


	/* Figure out the timeout for select.  Start with the minimum
	   of time to next rwho update or dump.  If people still have
	   text waiting to be processed then use the slice_timeout. */
	time(&now);
	gettimeofday(&current_time, NULL);

	timeout.tv_usec = 0;
	timeout.tv_sec = difftime(now, last_dump_time);

#ifdef RWHOD
	rwho_timeout.tv_usec = 0;
	rwho_timeout.tv_sec = difftime(now, last_rwho_update);
	timeout = timeval_min(timeout, rwho_timeout);
#endif

	next_slice = msec_add(last_slice, COMMAND_TIME_MSEC);
	slice_timeout = timeval_sub(next_slice, current_time);
	for (d = connections; d; d = d->next) {
	    if (text_total_lines(d->input_queue)) {
	        /* If someone has queued lines and remaining quota, we
		   want to get back to them quickly.  But if they ran
		   out of quota, then we sleep for a slice. */
	        if (d->quota) {
		    timeout.tv_usec = 0;
		    timeout.tv_sec = 0;
		    break;
		}
		timeout = timeval_min(timeout, slice_timeout);
	    }
	}

	while (timeout.tv_usec < 0) {
	    timeout.tv_usec += 1000000;
	    timeout.tv_sec--;
	}
	if (timeout.tv_sec < 0)
	    timeout.tv_sec = 0;

	if (select(maxd, &input_set, &output_set, (void*)NULL, &timeout) < 0) {
	    if (errno == EINTR) {
		continue;	/* Go back to top of loop, interrupted. */
	    }

	    log_status("PERROR: main_loop: select -- %s", strerror(errno));
	    return;
	}
	
	time(&now);
	if (FD_ISSET(listen_sock, &input_set)) {
	    if (!(newd = new_connection(listen_sock))) {
		log_status("PERROR: main_loop: new_connection -- %s",
			   strerror(errno));
		/* NOTE: this used to exit the mud if this failed. */
	    } else {
		if (newd->descriptor >= maxd)
		    maxd = newd->descriptor + 1;
	    }
	}
	for (d = connections; d; d = d->next) {
	    if (FD_ISSET(d->descriptor, &input_set)) {
		d->last_time = now;
		process_input(d);
	    }
	    if (FD_ISSET(d->descriptor, &output_set))
		process_output(d);
	}
    }
}