Update of /cvsroot/netrek/metaserver/OLD
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32574

Added Files:
	BecomeDaemon.c disp_info.c disp_new.c disp_old.c disp_web.c 
	main.c meta.h packets.h scan.c server.c 
Log Message:
Metaserver source files before the addition of metaserver solicit and
improved web port handing.

--Carlos V.




--- NEW FILE: disp_web.c ---
/*
 * disp_web.c - output in fancy new format
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "meta.h"

#define SEMI_VERBOSE		/* not quite fully verbose */

/* "when" should be the length of time in seconds */
static char *
nice_time(when)
time_t when;
{
    static char buf[64];

    if (when < 120)
	sprintf(buf, "%d seconds", when);
    else if (when/60 < 120)
	sprintf(buf, "%d minutes", when/60);
    else if (when/3600 < 48)
	sprintf(buf, "%d hours", when/3600);
    else
	sprintf(buf, "%d days", when/(3600*24));
    return (buf);
}


int
display_web(idx, port_idx)
int idx, port_idx;
{
    register int i;
    USER *up;
    SERVER *sp;
    time_t now;
    int srv, ago, *sorted;
    char flagbuf[8], buf[128];
    char *cp;

    up = &users[idx];
    up->data_size = up->buf_size = up->pos = 0;

    now = time(0);

    /* print header */
    Uprintf(idx, "HTTP/1.0 200 OK\n");
    Uprintf(idx, "Server: netrekmetarc/1.0\n");
    Uprintf(idx, "MIME-version: 1.0\n");
    Uprintf(idx, "Content-type: text/html\n\n");
    Uprintf(idx, "\
<title>MetaServer II Server List</title>\n\
<h1>MetaServer II Server List</h1>\n");

    Uprintf(idx, "<center><table border=2 cellpadding=6><tr>\
<td><h4>Server Host</h4></td>\
<td><h4>Port</h4></td>\
<td><h4>Minutes Ago</h4></td>\
<td><h4>Status</h4></td>\
<td><h4>Flags</h4></td>\
</tr>\n");

    /* print player info */
    sorted = sort_servers();
    for (srv = 0; srv < server_count; srv++) {
	/* (need to sort these?) */
	sp = &servers[sorted[srv]];
	if (sp->status == SS_WORKING) sp = &prev_info;
	Uprintf(idx, "\n<tr><td>%s</td><td>%d</td>\n", sp->hostname, sp->port);

	strcpy(flagbuf, "       ");
	for (i = 0; i < 6; i++)
	    if (sp->display_flags & flags[i].bit)
		flagbuf[i] = flags[i].chr;
	flagbuf[6] = sp->type[0];		/* hack in the type field */

	ago = (now - sp->last_update) / 60;
	switch (sp->status) {
	case SS_INIT:
	    Uprintf(idx, "<td colspan=3>(no data yet)</td>\n");
	    break;
	case SS_WORKING:
	    Uprintf(idx, "<td colspan=3>(scanning server now)</td>\n");
	    break;
	case SS_NOCONN:
	    switch (sp->why_dead) {
		case WD_CONN:
		    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, "* No connection", 
			flagbuf);
		    break;
		case WD_READ:
		    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, "* Bad read", 
                        flagbuf);
                    break;
		case WD_GARBAGE:
		    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, "* Garbaged read", 
                        flagbuf);
                    break;
		case WD_TIMEOUT:
		    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, "* Timed out", 
                        flagbuf);
                    break;
		case WD_FLOODING:
		    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, "* Flooding",
			flagbuf);
		    break;
		default:
	    	    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, "* Not responding", 
			flagbuf);
		    break;
	    }
	    break;
	case SS_EMPTY:
	    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, "Nobody playing", flagbuf);
	    break;
	case SS_OPEN:
	    sprintf(buf, "OPEN: %d player%s", sp->player_count,
		(sp->player_count != 1) ? "s" : "");
	    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, buf, flagbuf);
	    break;
	case SS_QUEUE:
	    sprintf(buf, "Wait queue: %d", sp->queue_size);
	    Uprintf(idx, "<td>%d</td><td>%s</td><td>%s</td>\n", ago, buf, flagbuf);
	    break;

	default:
	    /* huh? */
	    fprintf(stderr, "Internal ERROR: strange state in disp_new\n");
	    break;
	}
	Uprintf(idx, "</tr>\n");
    }

    /* print footer */
    Uprintf(idx, "</table></center>\n");
    Uprintf(idx, "<p><i>(This is MetaServerII v%s on port %d, \n",
	VERSION, ports[port_idx].port);
    cp = ctime(&now);
    cp[strlen(cp)-1] = '\0';	/* playing with fire? */
    Uprintf(idx, "date in %s is %s, data spans %s, \n", location, cp,
	nice_time(now-checktime));
    Uprintf(idx, "send administrative requests to <a href=mailto:%s>%s</a>, ", admin, admin);

    Uprintf(idx, "retry periods in minutes are ");
    Uprintf(idx, "down:%d empty:%d open:%d queue:%d)</i><p>\n\n",
	wait_noconn, wait_empty, wait_open, wait_queue);

    return(0);
}

--- NEW FILE: disp_old.c ---
/*
 * disp_old.c - output in format compatible with older metaserver
 * (also has Uprintf() and sort_servers() routines)
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "meta.h"

int
display_old(idx, port_idx)
int idx;
{
    USER *up;
    SERVER *sp;
    time_t now;
    int srv, *sorted;
    char buf[80], buf2[80];

    up = &users[idx];
    up->data_size = up->buf_size = up->pos = 0;

    now = time(0);

    /* print header */
    Uprintf(idx, "\n\nMETASERVER-II %s, %s\n", VERSION, VDATE);
    Uprintf(idx, "Server Check period: %d minutes\n", wait_open);

    now = time(0);

    /* print player info */
    sorted = sort_servers();
    for (srv = 0; srv < server_count; srv++) {
	/* (need to sort these?) */
	sp = &servers[sorted[srv]];
	if (sp->status == SS_WORKING) sp = &prev_info;
	sprintf(buf, "%s(%d)", sp->hostname, sp->port);
	Uprintf(idx, "%-40.40s: ", buf);

	strncpy(buf, ctime(&sp->last_update)+11, 8);
	*(buf+8) = '\0';

	strncpy(buf2, ctime(&sp->down_time)+4, 15);
	*(buf2+15) = '\0';

	switch (sp->status) {
	case SS_INIT:
	    Uprintf(idx, "Haven't yet checked this one...\n");
	    break;
	case SS_WORKING:
	    Uprintf(idx, "(scanning server now)\n");
	    break;
	case SS_NOCONN:
/*
	    Uprintf(idx, "Couldn't connect as of %s\n", buf);
*/
	    Uprintf(idx, "Down as of %s\n", buf2);
	    break;
	case SS_EMPTY:
	case SS_OPEN:
	    Uprintf(idx, "%2d Players online at %s\n", sp->player_count, buf);
	    break;
	case SS_QUEUE:
	    Uprintf(idx, "Wait queue %2d at %s\n", sp->queue_size, buf);
	    break;

	default:
	    /* huh? */
	    fprintf(stderr, "Internal ERROR: strange state in disp_new\n");
	    break;
	}
    }

    /* print footer */
    Uprintf(idx, "\n\tThat's all!\n\n");
}


#define INITIAL_SIZE	64000
#define MAX_PRINT	256
#ifdef BAD_IDEA
# define EXPAND_SIZE	4096		/* must be > MAX_PRINT */
#endif
/*
 * Print something to an expandable buffer.  Works like sprintf(), mostly.
 * Assumes that nothing we're passed will exceed MAX_PRINT bytes.
 *
 * When everybody has vsprintf, I'll do this right.
 */
void
Uprintf(idx, format, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
int idx;
char *format;
long arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7;
{
    USER *up;

    if (idx > 1024 || idx < 0) {
	fprintf(stderr, "Internal ERROR: bogus idx %d\n", idx);
	return;
    }

    up = &users[idx];
    if (!up->data_size) {
	if (up->buf != NULL) {
/* debug */
	    printf("HEY: Reassigning non-null buf for %d\n", idx);
/* endif */
	    log_msg("Reassigning non-null buf for %d\n", idx);
	    /* ought to free() here, but that might make us crash */
	}
	if ((up->buf = (char *) malloc(INITIAL_SIZE)) == NULL) {
	    fprintf(stderr, "malloc failure!\n");
	    log_msg("exit: malloc failure in Uprintf");
	    exit(1);
	}
	up->buf_size = INITIAL_SIZE;
    }
    if (up->buf_size - up->data_size < MAX_PRINT) {
#ifdef BAD_IDEA
	/* buffer too small, realloc */
	up->buf_size += EXPAND_SIZE;
	if ((up->buf = (char *) realloc(up->buf, up->buf_size)) == NULL) {
	    fprintf(stderr, "realloc failure!\n");
	    log_msg("exit: realloc failure in Uprintf");
	    exit(1);
	}
#else
	/* ran out of room; bummer */
	log_msg("NOTE: ran out of space in Uprintf()");
#endif
    }
    sprintf(up->buf + up->data_size, format, arg0, arg1, arg2, arg3, arg4,
	arg5, arg6, arg7);
    up->data_size += strlen(up->buf + up->data_size);
}


static int *alpha = NULL;		/* alphabetical host sorting order */

/*
 * Comparison function for qsort()
 */
static int
srvcmp(elmp1, elmp2)
int *elmp1, *elmp2;
{
    register SERVER *s1, *s2;
    register int elm1 = *elmp1, elm2 = *elmp2;
    int st, pc, ho, po;

    s1 = &servers[elm1];
    if (s1->status == SS_WORKING) s1 = &prev_info;
    s2 = &servers[elm2];
    if (s2->status == SS_WORKING) s2 = &prev_info;

    st = s1->status - s2->status;
    if (s1->status == SS_QUEUE)
	pc = s1->queue_size - s2->queue_size;
    else if (s1->status == SS_NOCONN)
	pc = 0;
    else
	pc = s1->player_count - s2->player_count;
    ho = alpha[elm1] - alpha[elm2];
    po = s1->port - s2->port;

    /*if ((st > 0) || (!st && pc < 0) || (!st && !pc && ho > 0))*/
    if ((st < 0) || (!st && pc > 0) || (!st && !pc && ho > 0) ||
	    (!st && !pc && !ho && po < 0))
	return (1);
    else
	return (-1);
    /* note there should not be a case in which two entries match exactly */
}

/*
 * Returns a pointer to an array of ints, indicating the sorted order of
 * the servers.
 *
 * Sorting order is:
 *   status
 *     player_count/queue_size
 *       alphabetical hostname
 *         port number
 *
 * IDEA: we can reduce the CPU usage by sorting the internal list by
 * host and port number once, during initialization.  However, this would
 * require a stable sort on the other categories, which qs doesn't
 * guarantee.
 */
int *
sort_servers()
{
    static int *sorted = NULL;
    register int i, j, *ip;
    int k;

    /* allocate some stuff first time through */
    if (sorted == NULL) {
	if (verbose) printf("Allocated sort_servers array\n");	/* DEBUG */
	if ((sorted = (int *) malloc(server_count * sizeof(int))) == NULL) {
	    fprintf(stderr, "malloc failure in sort_servers\n");
	    log_msg("exit: malloc failure in sort_servers");
	    exit(1);
	}

	/* to ease the CPU load, compute the alphabetical order once */
	if ((alpha = (int *) malloc(server_count * sizeof(int))) == NULL) {
	    fprintf(stderr, "malloc failure in sort_servers (alpha)\n");
	    log_msg("exit: malloc failure in sort_servers (alpha)");
	    exit(1);
	}
	for (i = 0, ip = sorted; i < server_count; i++, ip++)
	    *ip = i;
	for (i = 0; i < server_count-1; i++)
	    for (j = i+1; j < server_count; j++)
		if (strcmp(servers[sorted[i]].hostname,
			   servers[sorted[j]].hostname) > 0) {
		    k = sorted[i];
		    sorted[i] = sorted[j];
		    sorted[j] = k;
		}
	/* change sorted indices into parallel table of relative positions */
	for (i = 0, ip = sorted; i < server_count; i++, ip++)
	    alpha[*ip] = i;
    }

    /* intialize the sorted array to itself */
    for (i = 0, ip = sorted; i < server_count; i++, ip++)
	*ip = i;

    /* sort the array with the system qsort */
    qsort((char *) sorted, server_count, sizeof(int), srvcmp);

    return (sorted);
}

--- NEW FILE: disp_new.c ---
/*
 * disp_new.c - output in fancy new format
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "meta.h"

#define SEMI_VERBOSE		/* not quite fully verbose */

/* "when" should be the length of time in seconds */
char *
nice_time(when)
time_t when;
{
    static char buf[64];

    if (when < 120)
	sprintf(buf, "%d seconds", when);
    else if (when/60 < 120)
	sprintf(buf, "%d minutes", when/60);
    else if (when/3600 < 48)
	sprintf(buf, "%d hours", when/3600);
    else
	sprintf(buf, "%d days", when/(3600*24));
    return (buf);
}


int
display_new(idx, port_idx)
int idx, port_idx;
{
    register int i;
    USER *up;
    SERVER *sp;
    time_t now;
    int srv, ago, *sorted;
    char flagbuf[8], buf[128];
    char *cp;

    up = &users[idx];
    up->data_size = up->buf_size = up->pos = 0;

    now = time(0);

    /* print header */
    Uprintf(idx, "\n*** Connected to MetaServerII v%s on port %d ***\n",
	VERSION, ports[port_idx].port);
    cp = ctime(&now);
    cp[strlen(cp)-1] = '\0';	/* playing with fire? */
    Uprintf(idx, "Date in %s is %s (data spans %s)\n", location, cp,
	nice_time(now-checktime));
    Uprintf(idx, "E-mail administrative requests to %s.\n\n", admin);

    Uprintf(idx, "Retry periods (in minutes):  ");
    Uprintf(idx, "down:%d empty:%d open:%d queue:%d\n\n",
	wait_noconn, wait_empty, wait_open, wait_queue);
    Uprintf(idx, "Other interesting MetaServerII ports:  ");
    for (i = 0; i < port_count; i++) {
	if (ports[i].kind != DI_NEW)
	    Uprintf(idx, "%d ", ports[i].port);
    }
    Uprintf(idx, "\n\n");

    Uprintf(idx, "%-39s %-8s %-3s  %s\n", "", "", "Mins", "");
    Uprintf(idx, "%-39s %-8s %-3s  %-17s %s\n", "Server Host","Port","Ago",
	"Status", "Flags");
    Uprintf(idx, "--------------------------------------- ");
    Uprintf(idx, "-------- ---- ----------------- -------\n");

    /* print player info */
    sorted = sort_servers();
    for (srv = 0; srv < server_count; srv++) {
	/* (need to sort these?) */
	sp = &servers[sorted[srv]];
	if (sp->status == SS_WORKING) sp = &prev_info;
	Uprintf(idx, "-h %-36.36s -p %-5d ", sp->hostname, sp->port);

	strcpy(flagbuf, "       ");
	for (i = 0; i < 6; i++)
	    if (sp->display_flags & flags[i].bit)
		flagbuf[i] = flags[i].chr;
	flagbuf[6] = sp->type[0];		/* hack in the type field */

	ago = (now - sp->last_update) / 60;
	switch (sp->status) {
	case SS_INIT:
	    Uprintf(idx, "(no data yet)\n");
	    break;
	case SS_WORKING:
	    Uprintf(idx, "(scanning server now)\n");
	    break;
	case SS_NOCONN:
	    switch (sp->why_dead) {
		case WD_CONN:
		    Uprintf(idx, "%3d  %-17s %s\n", ago, "* No connection", 
			flagbuf);
		    break;
		case WD_READ:
		    Uprintf(idx, "%3d  %-17s %s\n", ago, "* Bad read", 
                        flagbuf);
                    break;
		case WD_GARBAGE:
		    Uprintf(idx, "%3d  %-17s %s\n", ago, "* Garbaged read", 
                        flagbuf);
                    break;
		case WD_TIMEOUT:
		    Uprintf(idx, "%3d  %-17s %s\n", ago, "* Timed out", 
                        flagbuf);
                    break;
		case WD_FLOODING:
		    Uprintf(idx, "%3d  %-17s %s\n", ago, "* Flooding",
			flagbuf);
		    break;
		default:
	    	    Uprintf(idx, "%3d  %-17s %s\n", ago, "* Not responding", 
			flagbuf);
		    break;
	    }
	    break;
	case SS_EMPTY:
	    Uprintf(idx, "%3d  %-17s %s\n", ago, "Nobody playing", flagbuf);
	    break;
	case SS_OPEN:
	    sprintf(buf, "OPEN: %d player%s", sp->player_count,
		(sp->player_count != 1) ? "s" : "");
	    Uprintf(idx, "%3d  %-17s %s\n", ago, buf, flagbuf);
	    break;
	case SS_QUEUE:
	    sprintf(buf, "Wait queue: %d", sp->queue_size);
	    Uprintf(idx, "%3d  %-17s %s\n", ago, buf, flagbuf);
	    break;

	default:
	    /* huh? */
	    fprintf(stderr, "Internal ERROR: strange state in disp_new\n");
	    break;
	}
    }

    /* print footer */
    Uprintf(idx, "\nThat's it!\n");

    return(0);
}


/*
 * Do the "verbose" output format
 */
int
display_verbose(idx, port_idx)
int idx;
{
    USER *up;
    SERVER *sp;
    time_t now;
    int srv, ago, *sorted;
    char buf[80];
    int i;

    up = &users[idx];
    up->data_size = up->buf_size = up->pos = 0;

    now = time(0);

    /* print header */
    Uprintf(idx, "\n*** Connected to MetaServerII v%s on port %d ***\n",
	VERSION, ports[port_idx].port);
    Uprintf(idx, "Date in %s is %s", location, ctime(&now));
    Uprintf(idx, "E-mail administrative requests to %s.\n", admin);
    Uprintf(idx, "Up continuously for %s", nice_time(now-uptime));
    if (uptime != checktime)
	Uprintf(idx, " (data spans %s).\n", nice_time(now-checktime));
    else
	Uprintf(idx, ".\n");
    Uprintf(idx, "Retry periods (in minutes):  ");
    Uprintf(idx, "down:%d empty:%d open:%d queue:%d\n\n",
	wait_noconn, wait_empty, wait_open, wait_queue);
    Uprintf(idx, "Other interesting MetaServerII ports:\n");
    for (i = 0; i < port_count; i++)
	if (ports[i].kind != DI_VERBOSE)
	    Uprintf(idx, "\t%d - %s\n", ports[i].port, ports[i].desc);
    Uprintf(idx, "\n");


    /* print player info */
    sorted = sort_servers();
    for (srv = 0; srv < server_count; srv++) {
	/* (need to sort these?) */
	sp = &servers[sorted[srv]];
	if (sp->status == SS_WORKING) sp = &prev_info;
#ifdef SEMI_VERBOSE
	if (sp->status == SS_NOCONN || sp->status == SS_EMPTY) continue;
#endif
	Uprintf(idx, "Server: %s [%c], port %d", sp->hostname,
		sp->type[0], sp->port);
	/* print comment if we've got one */
	if (strlen(sp->comment) > 0)
	    Uprintf(idx, " (%s)\n", sp->comment);
	else
	    Uprintf(idx, "\n");
	ago = (now - sp->last_update) / 60;
	strncpy(buf, ctime(&sp->last_update)+11, 8);
	*(buf+8) = '\0';
	if (sp->status != SS_INIT && sp->status != SS_WORKING)
	    Uprintf(idx, "Last checked at %s (%d minute%s ago).\n", buf, ago,
		(ago == 1) ? "" : "s");
	Uprintf(idx, "\n");

	switch (sp->status) {
	case SS_INIT:
	    Uprintf(idx, "This server has not been examined yet.\n");
	    break;
	case SS_WORKING:
	    Uprintf(idx,
		"Information for this server is being retrieved right now.\n");
	    break;
	case SS_NOCONN:
	    Uprintf(idx, "Index %d\n", sp->why_dead);
	    switch (sp->why_dead) {
	    case WD_UNKNOWN:
		Uprintf(idx, "The server is not responding.\n");
		break;
	    case WD_CONN:
		Uprintf(idx, "Unable to connect to server.\n");
		break;
	    case WD_READ:
		Uprintf(idx, "Got errors reading from server.\n");
		break;
	    case WD_GARBAGE:
		Uprintf(idx, "Server sent garbled data.\n");
		break;
	    case WD_TIMEOUT:
		Uprintf(idx, "Timed out waiting for response.\n");
		break;
	    case WD_FLOODING:
		Uprintf(idx, "Flooding metaserver, temporarily delisted.\n");
		break;
	    default:
		log_msg("got weird sp->why_dead (%d) in display_verbose()",
			sp->why_dead);
		Uprintf(idx, "The server is not responding.\n");
	    }
	    break;
	case SS_EMPTY:
	    Uprintf(idx, "Nobody is playing.\n");
	    break;
	case SS_OPEN:
	    if (sp->player_count == 1)
		Uprintf(idx, "There is 1 person playing here.\n");
	    else
		Uprintf(idx, "There are %d people playing here.\n",
		    sp->player_count);
	    print_players(idx, sorted[srv]);
	    break;
	case SS_QUEUE:
	    if (sp->queue_size == 1)
		Uprintf(idx, "There is a wait queue with 1 person on it.\n");
	    else
		Uprintf(idx, "There is a wait queue with %d people on it.\n",
		    sp->queue_size);
	    print_players(idx, sorted[srv]);
	    break;

	default:
	    /* huh? */
	    fprintf(stderr, "Internal ERROR: strange state in disp_verbose\n");
	    break;
	}

	Uprintf(idx, "\n\n");
    }

    /* print footer */
    Uprintf(idx, "\nThat's it!\n\n");
    return (0);
}


#define NOBODY 0x0
#define IND 0x0
#define FED 0x1
#define ROM 0x2
#define KLI 0x4
#define ORI 0x8
#define ALLTEAM (FED|ROM|KLI|ORI)
#define MAXTEAM (ORI)
#define NUMTEAM 4

#define NUMRANKS 9
#define PNUMRANKS 18
struct rank {
    char *name;
};
static struct rank ranks[NUMRANKS+1] = {
    { "Ensign"},
    { "Lieutenant"},
    { "Lt. Cmdr."},
    { "Commander"},
    { "Captain"},
    { "Flt. Capt."},
    { "Commodore"},
    { "Rear Adm."},
    { "Admiral"},
    { "(unknown)"}
};

static struct rank    pranks[PNUMRANKS] =
{
	{ "Recruit"},
	{ "Specialist"},
	{ "Cadet"},
	{ "Midshipman"},
	{ "Ensn., J.G."},
	{ "Ensign"},
	{ "Lt., J.G."},
	{ "Lieutenant"},
	{ "Lt. Cmdr."},
	{ "Commander"},
	{ "Captain"},
	{ "Fleet Capt."},
	{ "Commodore"},
	{ "Moff"},
	{ "Grand Moff"},
	{ "Rear Adml."},
	{ "Admiral"},
	{ "Grand Adml."}
};

/*static*/ int
print_players(idx, srv)
int idx, srv;
{
    SERVER *sp;
    PLAYER *j;
    int i, k, t, ci;
    int plcount, fromm;
    char namebuf[18];

    Uprintf(idx, "\n");
    sp = &servers[srv];

    /* first, scan list */
    plcount = 0;
    for (i = 0; i < sp->max_players; i++)
	if (sp->players[i].p_status!=PFREE && sp->players[i].p_status!=POUTFIT)
		plcount++;

    if (!plcount) {
	Uprintf(idx, "(Player list not available.)\n");
	return (0);
    }
    fromm = (sp->status == SS_QUEUE);
    if (fromm)
	Uprintf(idx, "(player list from port %d; may not be 100%% accurate)\n",
	    sp->port-1);

    /* this was adapted from ck_players */
    Uprintf(idx, "  Type Rank       Name              Login\n");
/*    for (k = IND; k <= ORI; k<<=1) {*/
    for (t = 0; t <= NUMTEAM; t++) {
	if (!t) k = 0;
	else    k = 1 << (t-1);
	for (i = 0, j = &sp->players[i]; i < sp->max_players; i++, j++) {
	    if ((j->p_status != PFREE && j->p_status != POUTFIT)
							&& j->p_team == k) {
		/* ugly way to ferret out trailing spaces */
		strncpy(namebuf+1, j->p_name, 16);
		namebuf[0] = ' ';
		namebuf[16] = namebuf[17] = '\0';
		if (namebuf[strlen(namebuf)-1] == ' ') {
		    namebuf[0] = '"';
		    namebuf[strlen(namebuf)] = '"';
		}
		for (ci = 0; ci < 18; ci++)	/* weed out control chars */
		    if (namebuf[ci] && ((namebuf[ci] & 0x7f) < 0x20))
			namebuf[ci] = '^';
		Uprintf(idx, "%c%c %s  %-9.9s %-18.18s %s@%s\n",
		    teamlet[j->p_team],
		    shipnos[i],
		    /*classes[j->ship_type],*/
		    (sp->type[0] != 'P')
			? ((j->ship_type<NUM_TYPES) ?
			   classes[j->ship_type]:"!!")
			: ((j->ship_type<PNUM_TYPES) ?
			   pclasses[j->ship_type]:"!!"),
		    (sp->type[0] != 'P')
			? ((j->p_rank<NUMRANKS) ?
			   ranks[j->p_rank].name:"(unknown)")
			: ((j->p_rank<PNUMRANKS) ?
			   pranks[j->p_rank].name:"(unknown)"),
		    namebuf,	/*j->p_name,*/
		    j->p_login, j->p_monitor );
	    }
	}
    }
}

--- NEW FILE: packets.h ---
/* 
 * Include file for socket I/O xtrek.
 *
 * Kevin P. Smith 1/29/89
 */

/* the following typedefs allow portability to machines without the
   ubiquitous 32-bit architecture (KSR1, Cray, DEC Alpha) */

typedef unsigned int CARD32;
typedef unsigned short CARD16;
typedef unsigned char CARD8;
typedef int LONG;

typedef int INT32;
typedef short INT16;
#if __STDC__ || defined(sgi) || defined(AIXV3)
typedef signed char INT8;
#else
/* stupid compilers */
typedef char INT8;
#endif

/* packets sent from xtrek server to remote client */
#define SP_MESSAGE 	1
#define SP_PLAYER_INFO 	2		/* general player info not elsewhere */
#define SP_KILLS	3 		/* # kills a player has */
#define SP_PLAYER	4		/* x,y for player */
#define SP_TORP_INFO	5		/* torp status */
#define SP_TORP		6		/* torp location */
#define SP_PHASER	7		/* phaser status and direction */
#define SP_PLASMA_INFO	8		/* player login information */
#define SP_PLASMA	9		/* like SP_TORP */
#define SP_WARNING	10		/* like SP_MESG */
#define SP_MOTD		11		/* line from .motd screen */
#define SP_YOU		12		/* info on you? */
#define SP_QUEUE	13		/* estimated loc in queue? */
#define SP_STATUS	14		/* galaxy status numbers */
#define SP_PLANET 	15		/* planet armies & facilities */
#define SP_PICKOK	16		/* your team & ship was accepted */
#define SP_LOGIN	17		/* login response */
#define SP_FLAGS	18		/* give flags for a player */
#define SP_MASK		19		/* tournament mode mask */
#define SP_PSTATUS	20		/* give status for a player */
#define SP_BADVERSION   21		/* invalid version number */
#define SP_HOSTILE	22		/* hostility settings for a player */
#define SP_STATS	23		/* a player's statistics */
#define SP_PL_LOGIN	24		/* new player logs in */
#define SP_RESERVED	25		/* for future use */
#define SP_PLANET_LOC	26		/* planet name, x, y */

/* paradise packets */
#define SP_SCAN		27		/*  scan packet */
#define SP_UDP_REPLY	28		/* notify client of UDP status */
#define SP_SEQUENCE	29		/* sequence # packet */
#define SP_SC_SEQUENCE	30		/* this trans is semi-critical info */
#define SP_RSA_KEY	31		/* RSA data packet */

#define SP_MOTD_PIC     32              /* motd bitmap pictures */
#define SP_STATS2	33		/*  new stats packet*/
#define SP_STATUS2	34		/*  new status packet*/
#define SP_PLANET2	35		/*  new planet packet*/
#define SP_NEW_MOTD     36              /* New MOTD info notification uses */
#define SP_THINGY	37	/* thingy location */
#define SP_THINGY_INFO	38	/* thingy status */
#define SP_SHIP_CAP	39		/* ship capabilities */

/* packets sent from remote client to xtrek server */
#define CP_MESSAGE      1		/* send a message */
#define CP_SPEED	2		/* set speed */
#define CP_DIRECTION	3		/* change direction */
#define CP_PHASER	4		/* phaser in a direction */
#define CP_PLASMA	5		/* plasma (in a direction) */
#define CP_TORP		6		/* fire torp in a direction */
#define CP_QUIT		7		/* self destruct */
#define CP_LOGIN	8		/* log in (name, password) */
#define CP_OUTFIT	9		/* outfit to new ship */
#define CP_WAR		10		/* change war status */
#define CP_PRACTR	11		/* create practice robot? */
#define CP_SHIELD	12		/* raise/lower sheilds */
#define CP_REPAIR	13		/* enter repair mode */
#define CP_ORBIT	14		/* orbit planet/starbase */
#define CP_PLANLOCK	15		/* lock on planet */
#define CP_PLAYLOCK	16		/* lock on player */
#define CP_BOMB		17		/* bomb a planet */
#define CP_BEAM		18		/* beam armies up/down */
#define CP_CLOAK	19		/* cloak on/off */
#define CP_DET_TORPS	20		/* detonate enemy torps */
#define CP_DET_MYTORP	21		/* detonate one of my torps */
#define CP_COPILOT	22		/* toggle copilot mode */
#define CP_REFIT	23		/* refit to different ship type */
#define CP_TRACTOR	24		/* tractor on/off */
#define CP_REPRESS	25		/* pressor on/off */
#define CP_COUP		26		/* coup home planet */
#define CP_SOCKET	27		/* new socket for reconnection */
#define CP_OPTIONS	28		/* send my options to be saved */
#define CP_BYE		29		/* I'm done! */
#define CP_DOCKPERM	30		/* set docking permissions */
#define CP_UPDATES	31		/* set number of usecs per update */
#define CP_RESETSTATS	32		/* reset my stats packet */
#define CP_RESERVED	33		/* for future use */

#define SOCKVERSION 	4
struct packet_handler {
    int size;
    int (*handler)();
};

struct mesg_spacket {
    char type;		/* SP_MESSAGE */
    unsigned char m_flags;
    unsigned char m_recpt;
    unsigned char m_from;
    char mesg[80];
};

struct plyr_info_spacket {
    char type;		/* SP_PLAYER_INFO */
    char pnum;
    char shiptype;	
    char team;
};

struct plyr_login_spacket {
    char type;		/* SP_PL_LOGIN */
    char pnum;
    char rank;
    char pad1;
    char name[16];
    char monitor[16];
    char login[16];
};

struct hostile_spacket {
    char type;		/* SP_HOSTILE */
    char pnum;
    char war;
    char hostile;
};

struct stats_spacket {
    char type;		/* SP_STATS */
    char pnum;
    char pad1;
    char pad2;
    long tkills;	/* Tournament kills */
    long tlosses;	/* Tournament losses */
    long kills;		/* overall */
    long losses;	/* overall */
    long tticks;	/* ticks of tournament play time */
    long tplanets;	/* Tournament planets */
    long tarmies;	/* Tournament armies */
    long sbkills;	/* Starbase kills */
    long sblosses;	/* Starbase losses */
    long armies;	/* non-tourn armies */
    long planets;	/* non-tourn planets */
    long maxkills;	/* max kills as player * 100 */
    long sbmaxkills;	/* max kills as sb * 100 */
};

struct flags_spacket {
    char type;		/* SP_FLAGS */
    char pnum;		/* whose flags are they? */
    char pad1;
    char pad2;
    unsigned flags;
};

struct kills_spacket {
    char type;		/* SP_KILLS */
    char pnum;
    char pad1;
    char pad2;
    unsigned kills;	/* where 1234=12.34 kills and 0=0.00 kills */
};

struct player_spacket {
    char type;		/* SP_PLAYER */
    char pnum;		
    unsigned char dir;
    char speed;
    LONG x,y;
};

struct torp_info_spacket {
    char  type;		/* SP_TORP_INFO */
    char  war;		
    char  status;	/* TFREE, TDET, etc... */
    char  pad1;		/* pad needed for cross cpu compatibility */
    short tnum;		
    short pad2;
};

struct torp_spacket {
    char  type;		/* SP_TORP */
    unsigned char dir;
    short tnum;
    long  x,y;
};

struct phaser_spacket {
    char type;		/* SP_PHASER */
    char pnum;
    char status;	/* PH_HIT, etc... */
    unsigned char dir;
    long x,y;
    long target;
};

struct you_spacket {
    char type;		/* SP_YOU */
    char pnum;		/* Guy needs to know this... */
    char hostile;
    char swar;
    char armies;
    char pad1;
    char pad2;
    char pad3;
    unsigned flags;
    LONG damage;
    LONG shield;
    LONG fuel;
    short etemp;
    short wtemp;
    short whydead;
    short whodead;
};

struct status_spacket {
    char type;		/* SP_STATUS */
    char tourn;
    char pad1;
    char pad2;
    unsigned armsbomb;
    unsigned planets;
    unsigned kills;
    unsigned losses;
    unsigned time;
    unsigned long timeprod;
};

struct warning_spacket {
    char type;		/* SP_WARNING */
    char pad1;
    char pad2;
    char pad3;
    char mesg[80];
};

struct planet_spacket {
    char  type;		/* SP_PLANET */
    char  pnum;
    char  owner;
    char  info;		
    short flags;
    short pad2;
    long  armies;
};

struct torp_cpacket {
    char type;		/* CP_TORP */
    unsigned char dir;		/* direction to fire torp */
    char pad1;
    char pad2;
};

struct phaser_cpacket {
    char type;		/* CP_PHASER */
    unsigned char dir;
    char pad1;
    char pad2;
};

struct speed_cpacket {
    char type;		/* CP_SPEED */
    char speed;		
    char pad1;
    char pad2;
};

struct dir_cpacket {
    char type;		/* CP_DIRECTION */
    unsigned char dir;
    char pad1;
    char pad2;
};

struct shield_cpacket {
    char type;		/* CP_SHIELD */
    char state;		/* up/down */
    char pad1;
    char pad2;
};

struct repair_cpacket {
    char type;		/* CP_REPAIR */
    char state;		/* on/off */
    char pad1;
    char pad2;
};

struct orbit_cpacket {
    char type;		/* CP_ORBIT */
    char state;		/* on/off */
    char pad1;
    char pad2;
};

struct practr_cpacket {
    char type;		/* CP_PRACTR */
    char pad1;
    char pad2;
    char pad3;
};

struct bomb_cpacket {
    char type;		/* CP_BOMB */
    char state;
    char pad1;
    char pad2;
};

struct beam_cpacket {
    char type;		/* CP_BEAM */
    char state;
    char pad1; 
    char pad2;
};

struct cloak_cpacket {
    char type;		/* CP_CLOAK */
    char state;		
    char pad1;
    char pad2;
};

struct det_torps_cpacket {
    char type;		/* CP_DET_TORPS */
    char pad1;
    char pad2;
    char pad3;
};

struct copilot_cpacket {
    char type;		/* CP_COPLIOT */
    char state;
    char pad1;
    char pad2;
};

struct queue_spacket {
    char type;		/* SP_QUEUE */
    char pad1;
    short pos;
};

struct outfit_cpacket {
    char type;		/* CP_OUTFIT */
    char team;
    char ship;
    char pad1;
};

struct pickok_spacket {
    char type;		/* SP_PICKOK */
    char state;
    char pad2;
    char pad3;
};

struct login_cpacket {
    char type;		/* CP_LOGIN */
    char query;
    char pad2;
    char pad3;
    char name[16];
    char password[16];
    char login[16];
};

struct login_spacket {
    char type;		/* SP_LOGIN */
    char accept;	/* 1/0 */
    char pad2;
    char pad3;
    long flags;
    char keymap[96];
};

struct tractor_cpacket {
    char type;		/* CP_TRACTOR */
    char state;
    char pnum;
    char pad2;
};

struct repress_cpacket {
    char type;		/* CP_REPRESS */
    char state;
    char pnum;
    char pad2;
};

struct det_mytorp_cpacket {
    char type;		/* CP_DET_MYTORP */
    char pad1;
    short tnum;
};

struct war_cpacket {
    char type;		/* CP_WAR */
    char newmask;
    char pad1;
    char pad2;
};

struct refit_cpacket {
    char type;		/* CP_REFIT */
    char ship;
    char pad1;
    char pad2;
};

struct plasma_cpacket {
    char type;		/* CP_PLASMA */
    unsigned char dir;
    char pad1;
    char pad2;
};

struct plasma_info_spacket {
    char  type;		/* SP_PLASMA_INFO */
    char  war;		
    char  status;	/* TFREE, TDET, etc... */
    char  pad1;		/* pad needed for cross cpu compatibility */
    short pnum;		
    short pad2;
};

struct plasma_spacket {
    char  type;		/* SP_PLASMA */
    char  pad1;
    short pnum;
    long  x,y;
};

struct playlock_cpacket {
    char type;		/* CP_PLAYLOCK */
    char pnum;
    char pad1;
    char pad2;
};

struct planlock_cpacket {
    char type;		/* CP_PLANLOCK */
    char pnum;
    char pad1;
    char pad2;
};

struct coup_cpacket {
    char type;		/* CP_COUP */
    char pad1;
    char pad2;
    char pad3;
};

struct pstatus_spacket {
    char type;		/* SP_PSTATUS */
    char pnum;
    char status;
    char pad1;
};

struct motd_spacket {
    char type;		/* SP_MOTD */
    char pad1;
    char pad2;
    char pad3;
    char line[80];
};

struct quit_cpacket {
    char type;		/* CP_QUIT */
    char pad1;
    char pad2;
    char pad3;
};

struct mesg_cpacket {
    char type;		/* CP_MESSAGE */
    char group;
    char indiv;
    char pad1;
    char mesg[80];
};

struct mask_spacket {
    char type;		/* SP_MASK */
    char mask;
    char pad1;
    char pad2;
};

struct socket_cpacket {
    char type;		/* CP_SOCKET */
    char version;
    char pad2;
    char pad3;
    unsigned socket;
};

struct options_cpacket {
    char type;		/* CP_OPTIONS */
    char pad1;
    char pad2;
    char pad3;
    unsigned flags;
    char keymap[96];
};

struct bye_cpacket {
    char type;		/* CP_BYE */
    char pad1;
    char pad2;
    char pad3;
};

struct badversion_spacket {
    char type;		/* SP_BADVERSION */
    char why;
    char pad2;
    char pad3;
};

struct dockperm_cpacket {
    char type;		/* CP_DOCKPERM */
    char state;
    char pad2;
    char pad3;
};

struct updates_cpacket {
    char type;		/* CP_UPDATES */
    char pad1;
    char pad2;
    char pad3;
    unsigned usecs;
};

struct resetstats_cpacket {
    char type;		/* CP_RESETSTATS */
    char verify;	/* 'Y' - just to make sure he meant it */
    char pad2;
    char pad3;
};

struct reserved_spacket {
    char type;		/* SP_RESERVED */
    char pad1;
    char pad2;
    char pad3;
    char data[16];
};

struct reserved_cpacket {
    char type;		/* CP_RESERVED */
    char pad1;
    char pad2;
    char pad3;
    char data[16];
    char resp[16];
};

struct planet_loc_spacket {
    char type;		/* SP_PLANET_LOC */
    char pnum;
    char pad2;
    char pad3;
    long x;
    long y;
    char name[16];
};

/* paradise packets */

struct scan_spacket {		/* ATM */
  INT8 type;			/* SP_SCAN */
  INT8 pnum;
  INT8 success;
  INT8 pad1;
  INT32 p_fuel;
  INT32 p_armies;
  INT32 p_shield;
  INT32 p_damage;
  INT32 p_etemp;
  INT32 p_wtemp;
};

struct udp_reply_spacket {	/* UDP */
  INT8 type;			/* SP_UDP_REPLY */
  INT8 reply;
  INT8 pad1;
  INT8 pad2;
  INT32 port;
};

struct sequence_spacket {	/* UDP */
  INT8 type;			/* SP_SEQUENCE */
  INT8 pad1;
  CARD16 sequence;
};
struct sc_sequence_spacket {	/* UDP */
  INT8 type;			/* SP_CP_SEQUENCE */
  INT8 pad1;
  CARD16 sequence;
};

/*
 * Game configuration.
 * KAO 1/23/93
 */

struct ship_cap_spacket {   /* Server configuration of client */
  INT8 type;			/* screw motd method */
  INT8 operation;		/* 0 = add/change a ship, 1 = remove a ship */
  INT16 s_type;			/* SP_SHIP_CAP */
  INT16 s_torpspeed;
  INT16 s_phaserrange;
  INT32 s_maxspeed;
  INT32 s_maxfuel;
  INT32 s_maxshield;
  INT32 s_maxdamage;
  INT32 s_maxwpntemp;
  INT32 s_maxegntemp;
  INT16 s_width;
  INT16 s_height;
  INT16 s_maxarmies;
  INT8 s_letter;
  INT8 pad2;
  INT8 s_name[16];
  INT8 s_desig1;
  INT8 s_desig2;
  INT16 s_bitmap;
};

struct motd_pic_spacket {
  INT8 type;			/* SP_MOTD_PIC */
  INT8 pad1;
  INT16 x, y, page;
  INT16 width, height;
  INT8 bits[1016];
};


	/*  This is used to send paradise style stats */
struct stats_spacket2 {
  INT8 type;			/* SP_STATS2 */
  INT8 pnum;
  INT8 pad1;
  INT8 pad2;

  INT32 genocides;		/* number of genocides participated in */
  INT32 maxkills;		/* max kills ever * 100  */
  INT32 di;			/* destruction inflicted for all time * 100 */
  INT32 kills;			/* Kills in tournament play */
  INT32 losses;			/* Losses in tournament play */
  INT32 armsbomb;		/* Tournament armies bombed */
  INT32 resbomb;		/* resources bombed off */
  INT32 dooshes;		/* armies killed while being carried */
  INT32 planets;		/* Tournament planets conquered */
  INT32 tticks;			/* Tournament ticks */
				/* SB/WB/JS stats are entirely separate */
  INT32 sbkills;		/* Kills as starbase */
  INT32 sblosses;		/* Losses as starbase */
  INT32 sbticks;		/* Time as starbase */
  INT32 sbmaxkills;		/* Max kills as starbase * 100 */
  INT32 wbkills;		/* Kills as warbase */
  INT32 wblosses;		/* Losses as warbase */
  INT32 wbticks;		/* Time as warbase */
  INT32 wbmaxkills;		/* Max kills as warbase * 100 */
  INT32 jsplanets;		/* planets assisted with in JS */
  INT32 jsticks;		/* ticks played as a JS */
  INT32 rank;			/* Ranking of the player */
  INT32 royal;			/* royaly, specialty, rank */
};

	/*status info for paradise stats */
struct status_spacket2 {
  INT8 type;			/* SP_STATUS2 */
  INT8 tourn;
  INT8 pad1;
  INT8 pad2;
  CARD32 dooshes;		/* total number of armies dooshed */
  CARD32 armsbomb;		/* all t-mode armies bombed */
  CARD32 resbomb;		/* resources bombed */
  CARD32 planets;		/* all t-mode planets taken */
  CARD32 kills;			/* all t-mode kills made */
  CARD32 losses;		/* all t-mode losses */
  CARD32 sbkills;		/* total kills in SB's */
  CARD32 sblosses;		/* total losses in Sb's */
  CARD32 sbtime;		/* total time in SB's */
  CARD32 wbkills;		/* kills in warbases */
  CARD32 wblosses;		/* losses in warbases */
  CARD32 wbtime;		/* total time played in wb's */
  CARD32 jsplanets;		/* total planets taken by jump ships */
  CARD32 jstime;		/* total time in a jump ship */
  CARD32 time;			/* t mode time in this game */
  CARD32 timeprod;		/* t-mode ship ticks--sort of like */
};


	/*planet info for a paradise planet*/
struct planet_spacket2 {
  INT8 type;			/* SP_PLANET2 */
  INT8 pnum;			/* planet number */
  INT8 owner;			/* owner of the planet */
  INT8 info;			/* who has touched planet */
  INT32 flags;			/* planet's flags */
  INT32 timestamp;		/* timestamp for info on planet */
  INT32 armies;			/* armies on the planet */
};

struct obvious_packet {
  INT8 type;			/* SP_NEW_MOTD */
  INT8 pad1;			/* CP_ASK_MOTD */
};

struct thingy_info_spacket {
  INT8 type;			/* SP_TORP_INFO */
  INT8 war;
  INT16 shape;			/* a thingy_types */
  INT16 tnum;
  INT16 owner;
};

struct thingy_spacket {
  INT8 type;			/* SP_TORP */
  CARD8 dir;
  INT16 tnum;
  INT32 x, y;
};

struct rsa_key_spacket {
  INT8 type;			/* SP_RSA_KEY */
  INT8 pad1;
  INT8 pad2;
  INT8 pad3;
  CARD8 data[32];
};


struct ping_spacket {
  INT8 type;			/* SP_PING */
  CARD8 number;			/* id (ok to wrap) */
  CARD16 lag;			/* delay of last ping in ms */

  CARD8 tloss_sc;		/* total loss server-client 0-100% */
  CARD8 tloss_cs;		/* total loss client-server 0-100% */

  CARD8 iloss_sc;		/* inc. loss server-client 0-100% */
  CARD8 iloss_cs;		/* inc. loss client-server 0-100% */
};

--- NEW FILE: meta.h ---
/*
 * meta.h - global vars and configuration stuff
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 */
/*#define DEBUG*/

/* files - adjust to taste */
#define LOGFILE		"metalog"
#define CONFIGFILE	"metarc"
#define CHECKFILE	"metackp"

/* some time constants, in seconds */
/* MAX_BUSY 20	BUSY_EXTEN 20 */
#define MAX_BUSY	40		/* max time to wait for a server */
#define BUSY_EXTEN	40		/* extension time for distant servers */
#define TIME_RES	40		/* how often do we wake up */
/*#define START_DIST	40		/* space initial checks by this much */
#define START_DIST	5
#define MAX_USER	10		/* after this long, bye bye user */
#define MAX_CKP_AGE	60*10		/* reject ckp files after 10 mins */

/* flood controls */
#define MAX_CALLS	3		/* you get this many calls... */
#define FLOOD_TIME	10		/* ...over this many seconds. */

/* server flood controls */
#define MIN_UREAD_TIME  60

/*
 * end of configurable stuff
 */

/* what version we are, and date for old format */
#define VERSION	"1.0.3"
#define VDATE	"6/1/93"

/* display flags */
typedef struct {
    int  bit;
    char chr;
    char *word;
} FLAG;
#define MAX_FLAG	8	/* increase to 32 as needed */
extern FLAG flags[MAX_FLAG];
#define DF_HAD_TMODE	0x0001
#define DF_ALWAYS_DEAD	0x0002
#define DF_RSA		0x0004
#define DF_TYPE		0x0040	/* not really a flag */

#define TRUE	1
#define FALSE	0

/* deal with funky SVR3 signal mechanism */
#ifdef _UTS
# define SYSV
#endif
#if defined (SYSV) && !defined(hpux)
# define SIGNAL(sig,action) sigset(sig, action)
#else
# define SIGNAL(sig,action) signal(sig, action)
#endif

/* assorted library routines */
extern char *malloc(),
	    *realloc();

/* player info */
typedef struct {
    int  p_status;
    int  p_rank;
    int  p_team;
    char p_mapchars[2];
    char p_name[16];
    char p_login[16];
    char p_monitor[16];
    int  ship_type;
} PLAYER;
#define MAX_PLAYER	32		/* must be >= server MAXPLAYER */
#define NUM_TYPES	8		/* #of different ship types */
#define PNUM_TYPES	16		/* #of different ship types */

/* global structure declarations */
typedef enum { SS_WORKING, SS_QUEUE, SS_OPEN, SS_EMPTY, SS_NOCONN, SS_INIT }
    SERVER_STATUS;	/* note: this affects sorting order */
typedef enum { WD_UNKNOWN, WD_CONN, WD_READ, WD_GARBAGE, WD_TIMEOUT, 
    WD_FLOODING } WHY_DEAD;
typedef enum { PS_PORTM, PS_PORT } PROBE_STATE;
typedef enum { CS_CLOSED, CS_CONNECTING, CS_CONNECTED } CONN_STATE;
typedef struct {
    SERVER_STATUS status;	/* status of the server */
    PROBE_STATE pstate;		/* what we are trying to do (port or port-1) */
    CONN_STATE cstate;		/* what our connection status is */
    WHY_DEAD why_dead;		/* if status==SS_NOCONN, why? */
    char hostname[64];	/* DNS */
    char ip_addr[32];	/* ASCII IP */
    long addr;		/* actual address */
    int  port;
    int  count;
    time_t down_time;
    time_t last_update;
    time_t next_update;
    long flags;			/* internal use */
    long display_flags;		/* for "flags" column */
    char comment[40];
    char mask;			/* tournament mode mask */
    char type[1];		/* may expand in the future */
    int  max_players;

    int  player_count;
    int  queue_size;
    PLAYER players[MAX_PLAYER];
} SERVER;		/* (if you add pointers here, update checkpointing) */

typedef struct {
    char *buf;
    int  pos;
    int  buf_size;
    int  data_size;
    time_t start_time;		/* if nonzero, then user is active */
} USER;

/* port stuff */
typedef enum { DI_OLD, DI_NEW, DI_WEB, DI_VERBOSE, DI_INFO } DISPLAY;
typedef struct {
    DISPLAY port_kind;
    char *port_name;
} PORT_DEF;
    
typedef struct {
    int  port;
    DISPLAY kind;
    char desc[64];
    char extra[64];
} PORT;

typedef struct {
    long addr;
} EXCLUDE;


/* some stuff from struct.h */
#define PFREE 0
#define POUTFIT 1
#define PALIVE 2
#define PEXPLODE 3
#define PDEAD 4
#define MAX_STATUS 5

#define PTQUEUE 5	/* Paradise servers only */

/* function prototypes */
extern void init_connections(/*void*/),
	    main_loop(/*void*/),
	    Uprintf(/*pseudo-var*/);
extern char *ctime(),
	    *n_to_a(/*long*/);
extern long lookup(/*char *host, char *iphost*/);
extern int wakeup(/*signal handler*/);
extern int *sort_servers(/*void*/);

/* global variables */
extern int verbose,
	   wait_noconn,
	   wait_empty,
	   wait_open,
	   wait_queue,
	   try_alt_port,
	   server_count,
	   port_count,
	   exclude_count,
	   log_dns_names,
	   new_conn,
	   mnew_conn,
	   busy,
	   busy_time;
extern char *prog,
	    location[],
	    admin[];
extern char *classes[], *pclasses[],
	    teamlet[],
	    shipnos[];
extern SERVER *servers,
	      prev_info;
extern USER *users;
extern PORT_DEF port_defs[];
extern PORT *ports;
extern EXCLUDE *excludes;
extern time_t uptime,
	      checktime;


--- NEW FILE: scan.c ---
/*
 * scan.c - scan the various servers
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 */
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "meta.h"
[...1208 lines suppressed...]
		cc--;
	    } else if (FD_ISSET(i, &writefds)) {
		if (i == busy_sock) {
		    /* finally got ahold of the darn server */
		    server_connected(i);
		    handle_server(i);
		} else {
		    /* looks like a user is writeable */
		    handle_user(i);
		}
		cc--;
	    }

	}

	if (verbose) fflush(stdout);
    }

    /*NOTREACHED*/
}

--- NEW FILE: server.c ---
/*
 * server.c - interact with a Netrek server
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 *
 * This is sorta messed up.  Things would be much simpler if we could just
 * sit and block on the Netrek socket, but that could take several seconds
 * to complete.  We need something which works the same way, but deals with
 * partial packets, etc, etc, without blowing away the CPU like we are now.
 */
#include <stdio.h>
#include <fcntl.h>
#include <string.h>		/* or strings.h */
#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
/*
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
*/
#include <netinet/in.h>
#include <errno.h>
#include "meta.h"
#include "packets.h"

int new_conn;		/* scan.c sets to TRUE when server conn is opened */
int mnew_conn;		/* whoops, need one for read_minus too */


/*
 * Private stuff
 */
#define NOBODY_PLAYING	"No one playing"
#define SERVER_IN_USE	"Server is in use"

#define BUFSIZE	85000	/* was 65000 */
static char rdbuf[BUFSIZE+1];

static int ssock;

/* from struct.h */
char *classes[NUM_TYPES] = {
    "SC", "DD", "CA", "BB", "AS", "SB", "GA", "??"
};
char *pclasses[PNUM_TYPES] = {
    "SC", "DD", "CA", "BB", "AS", "SB", "AT",
    "JS", "FR", "WB", "CL", "CV", "UT", "PT"
};
char teamlet[] = { 'I', 'F', 'R', 'X', 'K', 'X', 'X', 'X', 'O' };
char shipnos[] = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'
};

#define NUMRANKS 9
#define PNUMRANKS 18
static struct rankstrings {
    char *name;
    int len;
} rank[NUMRANKS] = {
    { "Ensign", 6 },
    { "Lieutenant", 9 },	/* one short in case it got cut off */
    { "Lt. Cmdr.", 9 },
    { "Commander", 9 },
    { "Captain", 7 },
    { "Flt. Capt.", 9 },	/* (ditto) */
    { "Commodore", 9 },
    { "Rear Adm.", 9 },
    { "Admiral", 7 },
    /* "(unknown)" */
}, prank[PNUMRANKS] = {
	{ "Recruit",     7 },
	{ "Specialist",  9 },
	{ "Cadet",       5 },
	{ "Midshipman",  9 },
	{ "Ensn., J.G.", 9 },
	{ "Ensign",      6 },
	{ "Lt., J.G.",   9 },
	{ "Lieutenant",  9 },
	{ "Lt. Cmdr.",   9 },
	{ "Commander",   9 },
	{ "Captain",     7 },
	{ "Fleet Capt.", 9 },
	{ "Commodore",   9 },
	{ "Moff",        4 },
	{ "Grand Moff",  9 },
	{ "Rear Adml.",  9 },
	{ "Admiral",     7 },
	{ "Grand Adml.", 9 }
};



/*
 * ---------------------------------------------------------------------------
 *	Stuff adapted from netrek/socket.c
 * ---------------------------------------------------------------------------
 */
static foo() {}
struct packet_handler handlers[] = {
    { 0, NULL },	/* record 0 */
    { sizeof(struct mesg_spacket), foo }, 	/* SP_MESSAGE */
    { sizeof(struct plyr_info_spacket), foo },	/* SP_PLAYER_INFO */
    { sizeof(struct kills_spacket), foo },	/* SP_KILLS */
    { sizeof(struct player_spacket), foo },	/* SP_PLAYER */
    { sizeof(struct torp_info_spacket), foo },	/* SP_TORP_INFO */
    { sizeof(struct torp_spacket), foo }, 	/* SP_TORP */
    { sizeof(struct phaser_spacket), foo },	/* SP_PHASER */
    { sizeof(struct plasma_info_spacket), foo},	/* SP_PLASMA_INFO */
    { sizeof(struct plasma_spacket), foo},	/* SP_PLASMA */
    { sizeof(struct warning_spacket), foo },	/* SP_WARNING */
    { sizeof(struct motd_spacket), foo },	/* SP_MOTD */
    { sizeof(struct you_spacket), foo },	/* SP_YOU */
    { sizeof(struct queue_spacket), foo },	/* SP_QUEUE */
    { sizeof(struct status_spacket), foo },	/* SP_STATUS */
    { sizeof(struct planet_spacket), foo }, 	/* SP_PLANET */
    { sizeof(struct pickok_spacket), foo },	/* SP_PICKOK */
    { sizeof(struct login_spacket), foo }, 	/* SP_LOGIN */
    { sizeof(struct flags_spacket), foo },	/* SP_FLAGS */
    { sizeof(struct mask_spacket), foo },	/* SP_MASK */
    { sizeof(struct pstatus_spacket), foo },	/* SP_PSTATUS */
    { sizeof(struct badversion_spacket), foo },	/* SP_BADVERSION */
    { sizeof(struct hostile_spacket), foo },	/* SP_HOSTILE */
    { sizeof(struct stats_spacket), foo },	/* SP_STATS */
    { sizeof(struct plyr_login_spacket), foo },	/* SP_PL_LOGIN */
    { sizeof(struct reserved_spacket), foo },	/* SP_RESERVED */
    { sizeof(struct planet_loc_spacket), foo },	/* SP_PLANET_LOC */
/* paradise packets */
    { sizeof(struct scan_spacket), foo },	/* SP_SCAN (ATM) */
    { sizeof(struct udp_reply_spacket), foo },	/* SP_UDP_STAT */
    { sizeof(struct sequence_spacket), foo },   /* SP_SEQUENCE */
    { sizeof(struct sc_sequence_spacket), foo },/*SP_SC_SEQUENCE*/
    { sizeof(struct rsa_key_spacket), foo },	/* SP_RSA_KEY */
    { sizeof(struct motd_pic_spacket), foo },   /* SP_MOTD_PIC */
    { sizeof(struct stats_spacket2), foo },	/* SP_STATS2 */
    { sizeof(struct status_spacket2), foo },	/* SP_STATUS2 */
    { sizeof(struct planet_spacket2), foo },	/* SP_PLANET2 */
    { sizeof(struct obvious_packet), foo },     /* SP_TEMP_5 */
    { sizeof(struct thingy_spacket), foo },	/* SP_THINGY */
    { sizeof(struct thingy_info_spacket), foo },/* SP_THINGY_INFO*/
    { sizeof(struct ship_cap_spacket), foo }    /* SP_SHIP_CAP */
};

int sizes[] = {
    0,	/* record 0 */
    sizeof(struct mesg_cpacket), 		/* CP_MESSAGE */
    sizeof(struct speed_cpacket),		/* CP_SPEED */
    sizeof(struct dir_cpacket),			/* CP_DIRECTION */
    sizeof(struct phaser_cpacket),		/* CP_PHASER */
    sizeof(struct plasma_cpacket),		/* CP_PLASMA */
    sizeof(struct torp_cpacket),		/* CP_TORP */
    sizeof(struct quit_cpacket), 		/* CP_QUIT */
    sizeof(struct login_cpacket),		/* CP_LOGIN */
    sizeof(struct outfit_cpacket),		/* CP_OUTFIT */
    sizeof(struct war_cpacket),			/* CP_WAR */
    sizeof(struct practr_cpacket),		/* CP_PRACTR */
    sizeof(struct shield_cpacket),		/* CP_SHIELD */
    sizeof(struct repair_cpacket),		/* CP_REPAIR */
    sizeof(struct orbit_cpacket),		/* CP_ORBIT */
    sizeof(struct planlock_cpacket),		/* CP_PLANLOCK */
    sizeof(struct playlock_cpacket),		/* CP_PLAYLOCK */
    sizeof(struct bomb_cpacket),		/* CP_BOMB */
    sizeof(struct beam_cpacket),		/* CP_BEAM */
    sizeof(struct cloak_cpacket),		/* CP_CLOAK */
    sizeof(struct det_torps_cpacket),		/* CP_DET_TORPS */
    sizeof(struct det_mytorp_cpacket),		/* CP_DET_MYTORP */
    sizeof(struct copilot_cpacket),		/* CP_COPILOT */
    sizeof(struct refit_cpacket),		/* CP_REFIT */
    sizeof(struct tractor_cpacket),		/* CP_TRACTOR */
    sizeof(struct repress_cpacket),		/* CP_REPRESS */
    sizeof(struct coup_cpacket),		/* CP_COUP */
    sizeof(struct socket_cpacket),		/* CP_SOCKET */
    sizeof(struct options_cpacket),		/* CP_OPTIONS */
    sizeof(struct bye_cpacket),			/* CP_BYE */
    sizeof(struct dockperm_cpacket),		/* CP_DOCKPERM */
    sizeof(struct updates_cpacket),		/* CP_UPDATES */
    sizeof(struct resetstats_cpacket),		/* CP_RESETSTATS */
    sizeof(struct reserved_cpacket)		/* CP_RESERVED */
};

#define NUM_PACKETS (sizeof(handlers) / sizeof(handlers[0]) - 1)
#define NUM_SIZES (sizeof(sizes) / sizeof(sizes[0]) - 1)


int
sendServerPacket(packet)
struct player_spacket *packet;
{
    int size;

#ifdef DEBUG
    printf("- Sending %d\n", packet->type);
#endif
	
    if (packet->type<1 || packet->type>NUM_SIZES || sizes[packet->type]==0) {
	fprintf(stderr, "Attempt to send strange packet %d!\n", packet->type);
        return (-1);
    }
    size=sizes[packet->type];
    if (gwrite(ssock, packet, size) != size) {
	return (-1);
    }

    return (0);
}


/*static*/ int
gwrite(fd, buf, bytes)
int fd;
char *buf;
register int bytes;
{
    long orig = bytes;
    register long n;

    while (bytes) {
        n = write(fd, buf, bytes);
        if (n < 0)
            return(-1);
        bytes -= n;
        buf += n;
    }
    return(orig);
}


/*
 * ---------------------------------------------------------------------------
 *	New stuff
 * ---------------------------------------------------------------------------
 */

/*static*/ char *
scan_buf(buf, str, count)
register char *buf, *str;
int count;
{
    register int i;
    int len, max;

    len = strlen(str);
    max = count - len;
    if (max <= 0) return (NULL);

    for (i = 0; i < max; i++, buf++) {
	if (*buf == *str && !strncmp(buf, str, len))
	    return (buf);
    }

    return (NULL);
}


/*
 * Parse a "players" output (port 2591 stuff).  Returns the #of players found.
 *
 * Note that the data here will be overwritten if we get better info from
 * the normal port (this is useful because it will get player info even
 * if there's a wait queue).
 *
 * Note that we want to do this BEFORE trying the other port, so that we
 * don't wake up the server if nobody is playing.
 *
 * New strategy: look for "standard keywords" in a header line, which is
 * expected to immediately follow after "<>==".  Gather a collection of
 * line offsets, and use those when scanning the "info" lines.
 *
 * Info line strategy: find a line which starts with F/K/R/O/I and a number
 * or small letter, and grab info until we hit something which doesn't match
 * (like "<>==").  Then we're done.
 *
 * We do have something of a dilemma here: we want to allow the display
 * to be off by a char or two, but it's possible that one or more of the
 * fields could be blank (for example, if the user is just logging in).
 * Until somebody defines the exact format to use (I did ask!), this will
 * be satisfactory.
 */
#define PL_SEP	"<>=="
#ifdef FUBAR
#define POS_MAPCHARS	2
#define POS_PSEUDO	POS_MAPCHARS+4
#define POS_LOGIN	POS_PSEUDO+17
#define POS_DISPLAY	POS_LOGIN+12
#define POS_SHIP	POS_DISPLAY+18
#endif
#define POSx_MAPCHARS	0
#define POSx_RANK	1
#define POSx_PSEUDO	2
#define POSx_LOGIN	3
#define POSx_DISPLAY	4
#define POSx_TYPE	5
#define MAX_POSx	6
#define TEAMCHARS	"IFRKO"
#define PLAYERCHARS	"0123456789abcdefghiABCDEFGHI"

struct {
    int offset;
    char *title;
    int len;
} offsets[MAX_POSx] = {
    { 0, "Pl:", 3 },
    { 0, "Rank", 4 },
    { 0, "Name", 4 },
    { 0, "Login", 5 },
    { 0, "Display", 7 },
    { 0, "Type", 4 },
};

/*static*/ int
parse_playerlist(buf, size)
register char *buf;
int size;
{
    SERVER *sp = &servers[busy];
    PLAYER *pp;
    int pl_count = 0;
    register char *end = buf+size;
    char ship_type[4], *cp, *brkp;
    char c;
    int i, j, pl, len, tmpo;

#ifdef DEBUG
    printf("parsing playerlist, buf = 0x%.8lx, end = 0x%.8lx\n",
	buf, end);	/* DEBUG */
    printf("%s\n",buf);
#endif
    for (i = 0; i < MAX_POSx; i++)
	offsets[i].offset = -1;

    /* find start of interesting stuff */
    while (buf < end) {
	if (!strncmp(buf, PL_SEP, strlen(PL_SEP))) break;

	while (*buf != '\n' && buf < end)
	    buf++;
	buf++;
    }
    /* title line is expected next */
    while (*buf != '\n' && buf < end)
	buf++;
    buf++;
    if (buf >= end) return (-1);

    for (cp = buf; *cp != '\n' && cp < end; cp++)
	;
    if (cp >= end) return (-1);
    brkp = cp;
    *brkp = '\0';

    /* title line is at *buf; figure out where each of the fields begins */
    if ((len = strlen(buf)) < 4) return (-1);

    for (i = 0, cp = buf; i < len; i++, cp++) {
	for (j = 0; j < MAX_POSx; j++) {
	    if (*cp == *(offsets[j].title)) {
		if (!strncmp(cp, offsets[j].title, offsets[j].len)) {
		    /*printf("offset %d (%s) = %d\n", j, offsets[j].title, i);*/
		    offsets[j].offset = i;
		    i += offsets[j].len;
		    cp += offsets[j].len;
		    break;	/* out of j-loop */
		}
	    }
	}
    }

    /* resume past the '\0' */
    buf = brkp+1;

    while (buf < end) {
	tmpo = offsets[POSx_MAPCHARS].offset;
	if (strchr(TEAMCHARS,*(buf+tmpo)) && strchr(PLAYERCHARS,*(buf+tmpo+1))){
	    /* found the start */

	    while (buf < end) {
		tmpo = offsets[POSx_MAPCHARS].offset;
		c = *(buf+tmpo+1);
		if (c >= '0' && c <= '9') pl = c - '0';
		else {
		    if (islower(c)) c = toupper(c);
		    pl = (c - 'A') + 10;
		}
		if (pl < 0 || pl >= sp->max_players) {
		    /*printf("illegal player #%d\n", pl);*/
		    return (-1);
		}
		/*printf("Player %d\n", pl);*/
		pp = &(sp->players[pl]);
		pp->p_mapchars[0] = c = *(buf+tmpo);
		pp->p_mapchars[1] = *(buf+tmpo+1);

		switch (c) {
		case 'I': pp->p_team = 0; break;
		case 'F': pp->p_team = 1; break;
		case 'R': pp->p_team = 2; break;
		case 'K': pp->p_team = 4; break;
		case 'O': pp->p_team = 8; break;
		default:
		    /*printf("Bad Team %s\n", buf);*/
		    return (-1);
		}

		if ((tmpo = offsets[POSx_RANK].offset) == -1 ||
		    *(buf+tmpo) == ' ') {
		    pp->p_rank = NUMRANKS;
		} else {
		    struct rankstrings	*ranklist;
		    int	nranks;
		    if (sp->type[0]=='P') {
		      nranks = PNUMRANKS;
		      ranklist = prank;
		    } else {
		      nranks = NUMRANKS;
		      ranklist = rank;
		    }
		    pp->p_rank = nranks;
		    for (i = 0; i < nranks; i++) {
			if (!strncmp(buf+tmpo, ranklist[i].name, ranklist[i].len)) {
			    pp->p_rank = i;
			    break;
			}
		    }
		}

		if ((tmpo = offsets[POSx_PSEUDO].offset) == -1 ||
		    *(buf+tmpo) == ' ') {
		    strcpy(pp->p_name, "(none)");
		} else {
		    strncpy(pp->p_name, buf+tmpo, 15);
		    cp = pp->p_name + strlen(pp->p_name) -1;
		    if (cp > pp->p_name) {
			while (*cp == ' ') cp--;
			*(cp+1) = '\0';
		    }
		    /*printf("name = '%s' ", pp->p_name);*/
		}

		if ((tmpo = offsets[POSx_LOGIN].offset) == -1 ||
		    *(buf+tmpo) == ' ') {
		    strcpy(pp->p_login, "(none)");
		} else {
		    if (strtok(buf+tmpo, " ") == NULL) {
			/*printf("Bad login %s\n", buf);*/
			return (-1);
		    }
		    strncpy(pp->p_login, buf+tmpo, 15);
		    *(pp->p_login+15) = '\0';
		    /*printf("login = '%s' ", pp->p_login);*/
		}

		if ((tmpo = offsets[POSx_DISPLAY].offset) == -1 ||
		    *(buf+tmpo) == ' ') {
		    strcpy(pp->p_name, "(none)");
		} else {
		    if (strtok(buf+tmpo, " ") == NULL) {
			/*printf("Bad display %s\n", buf);*/
			return (-1);
		    }
		    strncpy(pp->p_monitor, buf+tmpo, 15);
		    *(pp->p_name+15) = '\0';
		    /*printf("disp = '%s'\n", pp->p_monitor);*/
		}

		if ((tmpo = offsets[POSx_TYPE].offset) == -1) {
		    strcpy(ship_type, "??");
		    pp->ship_type = NUM_TYPES-1;
		    goto skip_type;
		}
		if (*(buf+tmpo) == ' ') tmpo++;		/* allow a blank here */
		if (*(buf+tmpo) == ' ' || *(buf+tmpo+1) == ' ') {
		    /*printf("Bad ship type %s\n", buf);*/
		    return (-1);
		}
		ship_type[0] = *(buf+tmpo);
		ship_type[1] = *(buf+tmpo+1);
		ship_type[2] = '\0';
		pp->ship_type = NUM_TYPES-1;	/* default is "??" */
		for (i = 0; i < NUM_TYPES; i++) {
		    if (!strcmp(ship_type, classes[i])) {
			pp->ship_type = i;
			break;
		    }
		}
skip_type:
		/*printf("ship type = %d (%s)\n", pp->ship_type,
		    classes[pp->ship_type]);*/

		pp->p_status = PALIVE;
		pl_count++;


		/* move to start of next line */
		while (*buf != '\n' && buf < end)
		    buf++;
		buf++;

		/* done yet? */
		if (*buf == *PL_SEP) goto done;

	    }
	}

	while (*buf != '\n' && buf < end)
	    buf++;
	buf++;
    }
done:
    sp->player_count = pl_count;
    return (pl_count);
}


/*
 * Read & interpret data from port-1 (sends a player listing).  Uses blocking
 * reads; the amount of data should be small enough that this isn't a problem.
 *
 * Returns -1 on error, -2 if not done yet, status otherwise.
 *
 * NOTE: if the output of port 2591 was garbage, we would get "0 players
 * playing" from parse_playerlist().  However, if nobody was playing, then
 * we should've seen "No one is playing" earlier.  So, if we get 0 back from
 * parse_playerlist(), assume something went wrong and return (-1).
 */
SERVER_STATUS
read_minus(sock)
int sock;
{
    static int count = 0;
    int cc, pc;

#ifdef DEBUG
    printf("Reading fd %d\n", sock);
#endif
    ssock = sock;

    if (mnew_conn) {
	count = 0;
	mnew_conn = FALSE;

#ifdef CONNECT 
	/* see if we're really connected */
	if ((cc = write(sock, " ", 1)) < 0) {
	    if (errno != EPIPE) {
		if (verbose) perror("strange read_minus error");
		log_msg("strange read_minus write error %d with %s", errno,
		    servers[busy].hostname);
	    }
	    /* no change to why_dead for read_minus */
	    return (-1);
	}
#endif
	return (-2);
    }

    /* if we get an error, assume it was the server hanging up on us */
    if ((cc = read(sock, rdbuf+count, BUFSIZE-count)) < 0) {
	if (errno != ECONNREFUSED) {
	    if (verbose) perror("read minus");
	    log_msg("read error %d in read_minus with %s", errno,
	        servers[busy].hostname);
	}
	return (-1);
    }
#ifdef DEBUG
    printf ("read() of 2591 yielded %d bytes\n",cc);
#endif
    count += cc;
    if (cc) return (-2);	/* more to go (ok if buf fills) */
    if (!count) return (-1);	/* no info! */
    rdbuf[count] = '\0';

    /* see if nobody is playing */
    if (scan_buf(rdbuf, NOBODY_PLAYING, count) != NULL) {
#ifdef DEBUG
	printf("2591 no one is playing!\n");
#endif
	return (SS_EMPTY);
    }

    /* see if server is up, but no verbose info */
    if (scan_buf(rdbuf, SERVER_IN_USE, count) != NULL)
	return (SS_OPEN);

    /* okay, we got valid stuff, now parse it and stuff it into SERVER struct */
    if (!(pc = parse_playerlist(rdbuf, count)))
	return (-1);		/* was SS_EMPTY */
    else if (pc < 0)
	return (-1);
    else
	return (SS_OPEN);
}


/*
 * See if we have the info for MAX_PLAYERS players in rdbuf.  Returns (-1)
 * if we don't have all the info yet.  Returns 0 otherwise.  Exits with 0
 * if it encounters an SP_QUEUE packet.  Returns (-2) if the data is corrupted.
 *
 * We count SP_PLAYER because it's the last packet in the group sent for
 * each player.  If the server sends them in a different order than we could
 * have bogus data for the last player.  Since this is what ck_players does,
 * we break iff everything else breaks.
 *
 * To alleviate the CPU load, this keeps track of the byte offset of the
 * first interesting item in the buffer.  That way we don't have to seek
 * past the motd every time.  Whenever the data in the buffer changes (other
 * than appending new data), call this with buflen=0 to reset the stuff.
 *
 * Here's what the profiler showed after half a day of activity:
 *
 *	%time  cumsecs  #call  ms/call  name
 *	 24.9     8.58  17371     0.49  _read
 *	 23.3    16.62  19318     0.42  _select
 *	  5.5    18.51                  mcount
 *	  4.8    20.18  24272     0.07  __doprnt
 *	  4.8    21.84   1484     1.12  _connect
 *	  3.6    23.08  17371     0.07  _scan_packets	[]
 *	  3.3    24.21      1  1130.00  _main_loop	[]
 *	  3.0    25.23   3042     0.34  _close
 *	  2.9    26.23   1472     0.68  _write
 *	  2.0    26.91 176515     0.00  .rem
 *	  1.8    27.54  13274     0.05  _strpbrk
 *	  1.7    28.14    472     1.27  _parse_server	[]
 *
 * So this routine is still the most CPU-intensive in the program, but it's
 * relatively insignificant.
 */
int
scan_packets(buflen)
int buflen;
{
    register unsigned char *buf;
    register int pos, type, size;
    int plcount = 0;
    static int first_off = 0;		/* opt */
    int neat;				/* opt */

    if (!buflen) {
	/* reset */
	first_off = 0;
	return;
    }
    pos = first_off;
    buf = (unsigned char *) (rdbuf + pos);

    if (pos > buflen) {
	log_msg("something whacked in scan_packets");
#ifdef DEBUG
	printf("scan_packets has initial pos = %d, buflen = %d\n", pos,buflen);
#endif
	return (-1);
    }

    neat = 0;
    while (pos < buflen) {
#ifdef DEBUG
	printf("%02.2x ",(char) *(buf));
	printf("%02.2x ",(char) *(buf+1));
	printf("%02.2x ",(char) *(buf+2));
	printf("%02.2x \n",(char) *(buf+3));
#endif
	type = (short int) *buf;
	if (type < 1 || type > NUM_PACKETS) {
	    printf("rcvd bad packet %d (0x%x) from %s (%d)\n", type, type,
		servers[busy].hostname, servers[busy].port);
	    return (-2);
	}
	size = handlers[type].size;
	if (size <= 0) {
	    /* got bogus size, headed for infinite spin */
	    printf("zero size in scan_packets from %s\n",
		servers[busy].hostname);
	    return (-2);
	}

	if (size+pos > buflen)
	    return (-1);

	if (type != SP_MOTD)
	    neat++;			/* don't advance first_off anymore */
	if (type == SP_PLAYER) {
	    SERVER *serv = &servers[busy];
	    {
		struct player_spacket *sp;
		sp = (struct player_spacket *) buf;
	    }
	    plcount++;
	    if (verbose) printf("Got player # %d\n",plcount);
	    if (plcount >= serv->max_players)
		return (0);
	}
	if (type == SP_QUEUE)
	    return (0);

	buf += size;
	pos += size;
	if (!neat) first_off = pos;	/* start here next time */
    }

    return (-1);
}


/*
 * Parse the info we got from the server.  Returns status.
 */
SERVER_STATUS
parse_server(buflen)
int buflen;
{
    SERVER *sp = &servers[busy];
    PLAYER *pp;
    struct queue_spacket *qp;
    struct plyr_login_spacket *plp;
    struct plyr_info_spacket *pip;
    struct pstatus_spacket *psp;
    register char *buf;
    register int pos;
    int type, size;
    int pl_seen = 0;

    buf = rdbuf, pos = 0;

    sp->player_count = 0;
    pp = sp->players;
    while (pos < buflen && pl_seen < sp->max_players) {
	type = (int) *buf;
	size = handlers[type].size;

	if (size+pos > buflen) {
#ifdef DEBUG
	    printf("type = %d, size = %d, pos = %d, buflen = %d\n",
		type, size, pos, buflen);
#endif
	    return (-1);
	}

	switch (type) {
	case SP_QUEUE:
	    qp = (struct queue_spacket *) buf;
	    sp->queue_size = qp->pos;
	    return (SS_QUEUE);
	case SP_PL_LOGIN:
	    plp = (struct plyr_login_spacket *) buf;
	    if (plp->pnum < 0 || plp->pnum >= sp->max_players) break;
	    strncpy(pp[plp->pnum].p_name, plp->name, 16);
	    strncpy(pp[plp->pnum].p_monitor, plp->monitor, 16);
	    strncpy(pp[plp->pnum].p_login, plp->login, 16);
	    pp[plp->pnum].p_rank = plp->rank;
	    break;
	case SP_PLAYER_INFO:
	    pip = (struct plyr_info_spacket *) buf;
	    if (pip->pnum < 0 || pip->pnum >= sp->max_players) break;
	    pp[pip->pnum].ship_type = pip->shiptype;
	    pp[pip->pnum].p_team = pip->team;
	    pp[pip->pnum].p_mapchars[0] = teamlet[pip->team];
	    break;
	case SP_PSTATUS:
	    psp = (struct pstatus_spacket *) buf;
	    if (psp->pnum < 0 || psp->pnum >= sp->max_players) break;
	    pp[psp->pnum].p_mapchars[1] = shipnos[psp->pnum];
	    if (psp->status >=0 && psp->status < MAX_STATUS) {
		pp[psp->pnum].p_status = psp->status;
	    } else if (psp->status == PTQUEUE) {
		/* Paradise server tournament queue */
		pp[psp->pnum].p_status = PALIVE;
	    } else {
		log_msg("rcvd bad status (%d) from %s", psp->status,
			servers[busy].hostname);
		pp[psp->pnum].p_status = PFREE;
	    }
	    /* this is kinda weak, but *we* show up as POUTFIT... */
	    if (psp->status != PFREE && psp->status != POUTFIT)
		sp->player_count++;
	    pl_seen++;
	    break;
#ifdef GET_MASK		/* can't be done w/o full login */
	case SP_MASK:
	    mp = (struct mask_spacket *) buf;
	    sp->mask = mp->mask;
	    mask_seen++;
	    printf("mask = 0x%x\n", sp->mask);
	    break;
#endif

	case SP_STATS:
	case SP_PLAYER:
	case SP_FLAGS:
	case SP_KILLS:
	case SP_HOSTILE:
	default:
	    /* do nothing */
	    size = size;	/* silly compiler */
	}

	buf += size;
	pos += size;
    }

    /* all done */
    if (!sp->player_count)
	return (SS_EMPTY);
    else
	return (SS_OPEN);
}


/*
 * Read & interpret data from the standard Netrek port.  This is a whole lot
 * of fun, because we don't want to block on the read() calls.
 *
 * Basic algorithm is:
 *   while (still hungry && room in buffer) {
 *       read more data
 *       if (we have all of the information for each player)
 *	     analyze it and return
 *	 else {
 *	     if we've got more room
 *		 return with -2 so we come back later
 *	     else
 *		 return with -1 (hopefully this won't happen!)
 *	 }
 *   }
 *
 * Two possible adjustments: one is to interpret the data as we go along
 * (but that quickly turns to spaghetti), the other is to filter out the
 * motd lines and scrunch the buffer up (which should work, but will raise
 * the CPU load because of all the bcopy() calls).  The latter approach will
 * be implemented, just in case some bozo has a 60K motd.  (idea: wait until
 * the buffer is >50% full before doing any scrunching.)
 *
 * Returns -1 on error, -2 if not yet done, status otherwise.
 */
SERVER_STATUS
read_server(sock)
int sock;
{
    struct socket_cpacket sockPack;
    static char *buf;			/* buf & count stay across calls */
    static int count, extend_time;
    int cc, res;


    ssock = sock;
    if (new_conn) {
	new_conn = FALSE;
	buf = rdbuf;
	extend_time = 0;
	count = 0;
	scan_packets(0);		/* reset */

	sockPack.type=CP_SOCKET;
	sockPack.socket=htonl(65535);
	sockPack.version=(char) SOCKVERSION;
	if (sendServerPacket(&sockPack) < 0) {
	    /* see if server really exists */
	    if (errno != EPIPE) {
		perror("strange error on sendServerPacket");
		log_msg("strange server write 27 error %d with %s", errno,
		    servers[busy].hostname);
	    }
	    servers[busy].why_dead = WD_CONN;	/* connection really failed */
	    return (-1);
	}

	/* the server might not have data to send just yet */
	/* (minor improvement: put a select() call here)   */
	return (-2);
    }

    if ((cc = read(sock, buf, BUFSIZE-count)) < 0) {
	log_msg("server read error %d from %s", errno,
	    servers[busy].hostname);
#ifdef DEBUG
	perror("read_server read");
#endif
	servers[busy].why_dead = WD_READ;
	return (-1);
    }
#ifdef DEBUG
    printf("read, cc=%d\n", cc);
#endif
    if (!cc) {
	/* we've hit EOF, but we don't have all the data; assume server dead */
#ifdef DEBUG
	fprintf(stderr, "EOF hit early\n");
#endif
	if (verbose)
	    log_msg("+ early EOF reading from %s", servers[busy].hostname);
	servers[busy].why_dead = WD_GARBAGE;
	return (-1);
    }
    buf += cc, count += cc;

    if (!extend_time && count > 1024) {
	busy_time += BUSY_EXTEN;	/* this could be in the future... */
	extend_time++;
    }

    /* FIX: add motd scrunch code here */

    if ((res = scan_packets(count)) < 0) {
	if (res == (-2)) {		/* corruption */
	    servers[busy].why_dead = WD_GARBAGE;
#ifdef DEBUG
	    printf ( "scan_packets returned garbage status\n" );
#endif
	    return (-1);
	}
	if (count < BUFSIZE)
	    return (-2);		/* get more */
	else {
	    if (verbose)fprintf(stderr,"Internal ERROR: read buffer flooded\n");
	    log_msg("ERROR: read buffer flooded by server %s\n",
		servers[busy].hostname);
	    return (-1);		/* ran out of room! */
	}
    }

    /* hooray, we have all the info in the buffer! */
    return (parse_server(count));
}

--- NEW FILE: disp_info.c ---
/*
 * disp_info.c - send a file to the client
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include "meta.h"


/*
 * display the info file
 */
int
display_info(idx, port_idx)
int idx, port_idx;
{
    if (verbose) printf("%s: sending info '%s'\n", prog, ports[port_idx].extra);
    send_info(idx, ports[port_idx].extra);
    return (0);
}



static int
send_info(idx, file)
int idx;
char *file;
{
    USER *up;
    FILE *fp;
    char buf[128];

    up = &users[idx];
    up->data_size = up->buf_size = up->pos = 0;

    if ((fp = fopen(file, "r")) == NULL) {
	if (errno == EMFILE)
	    /* I don't see how this could be happening... weird */
	    Uprintf(idx,
		"\nToo many files open here; wait a bit and try again\n\n");
	else
	    Uprintf(idx,"\nUnable to open %s (err %d); tell the admin (%s)\n\n",
		file, errno, admin);
	log_msg("Unable to open %s (errno=%d)", file, errno);
	return (0);
    }

    while (1) {
	fgets(buf, 128, fp);
	if (ferror(fp))
	    log_msg("error reading %s: %d", file, errno);
	if (ferror(fp) || feof(fp))
	    break;

	Uprintf(idx, "%s", buf);
    }

    fclose(fp);

    return (0);
}

--- NEW FILE: BecomeDaemon.c ---
/*
 * BecomeDaemon.c
 * shamelessly swiped from xdm source code.
 * Appropriate copyrights kept, and hereby follow
 * ERic mehlhaff, 3/11/92
 *
 * xdm - display manager daemon
 *
 * $XConsortium: daemon.c,v 1.5 89/01/20 10:43:49 jim Exp $
 *
 * Copyright 1988 Massachusetts Institute of Technology
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  M.I.T. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * Author:  Keith Packard, MIT X Consortium
 */


#include <sys/ioctl.h>
#ifdef hpux
#include <sys/ptyio.h>
#endif

#include <errno.h>
#include <stdio.h>
#include <sys/file.h>


extern void exit ();

void BecomeDaemon ()
{
    register int i;
    int forkresult;

    /*
     * fork so that the process goes into the background automatically. Also
     * has a nice side effect of having the child process get inherited by
     * init (pid 1).
     */

    if ( (forkresult = fork ()) ){	/* if parent */
	  if(forkresult < 0 ){
		perror("Fork error!");
	  }
	  exit (0);			/* then no more work to do */
      }

    /*
     * Close standard file descriptors and get rid of controlling tty
     */

    close (0); 
    close (1);
    close (2);

    /*
     * Set up the standard file descriptors.
     */
    (void) open ("/", O_RDONLY);	/* root inode already in core */
    (void) open ("/tmp/metaout", O_WRONLY | O_APPEND | O_CREAT, 0644);
    (void) dup2 (1, 2);

    if ((i = open ("/dev/tty", O_RDWR)) >= 0) {	/* did open succeed? */
#if defined(SYSV) && defined(TIOCTTY)
	int zero = 0;
	(void) ioctl (i, TIOCTTY, &zero);
#else
	(void) ioctl (i, TIOCNOTTY, (char *) 0);    /* detach, BSD style */
#endif
	(void) close (i);
    }


#ifdef SYSV
#ifdef hpux
    setsid();
#else
    setpgrp (0, 0);
#endif
#else
    setpgrp (0, getpid());
#endif

}

--- NEW FILE: main.c ---
/*
 * main.c - args, configuration, logging
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 */
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/param.h>
#include <errno.h>
#include <unistd.h>
#include "meta.h"

/*
 * Globals
 */
int verbose = FALSE;		/* if TRUE, print extra info on terminal */
char *prog = NULL;		/* our program name */
SERVER *servers = NULL;		/* list of servers to check */
int server_count = 0;		/* #of servers in list */
PORT *ports = NULL;		/* list of ports to listen on */
int port_count = 0;		/* #of ports in list */
EXCLUDE *excludes = NULL;	/* list of clients to exclude */
int exclude_count = 0;
int try_alt_port = TRUE;	/* try port-1 first? */
int log_dns_names = TRUE;	/* do DNS lookup on hostnames for log file? */
int save_ckp = TRUE;		/* save checkpoint file? */
int wait_noconn = 30;		/* wait 30 mins if connection failed */
int wait_empty = 15;		/* wait 15 mins if server is empty */
int wait_open = 15;		/* wait 15 mins if server is open */
int wait_queue = 15;		/* wait 15 mins if server has a wait queue */
time_t uptime = 0;		/* when we started */
time_t checktime = 0;		/* when we started collecting data */

char info_file[MAXPATHLEN];	/* pathname of info file */
char faq_file[MAXPATHLEN];	/* pathmame of faq file */
char location[80];		/* city/state/country */
char admin[80];			/* e-mail addr of MSII maintainer */

FLAG flags[MAX_FLAG] = {
    { DF_HAD_TMODE,	'T',	"HAD-TMODE" },
    { DF_ALWAYS_DEAD,	'D',	"ALWAYS-DEAD" },
    { DF_RSA,		'R',	"RSA-RESERVED" },
};

PORT_DEF port_defs[] = {
    { DI_OLD, "old" },
    { DI_NEW, "new" },
    { DI_WEB, "web" },
    { DI_VERBOSE, "verbose" },
    { DI_INFO, "info" },
};
#define MAX_PORT_DEF	(sizeof(port_defs) / sizeof(PORT_DEF))

#define PIDFILE			"meta.pid"

/*
 * Private variables
 */
static FILE *logfp = NULL;	/* log file */
static int do_log = TRUE;	/* set from config file */
static int trun = 0;	/* truncate log file on startup? */


/*
 * Configuration stuff
 */
typedef enum {
    STMT_BOOL, STMT_INT, STMT_STRING, STMT_SERVER, STMT_PORT, STMT_EXCLUDE
} STMT_KIND;
struct keyword_t {
    STMT_KIND kind;
    char *keyword;
    int *var;
} keywords[] = {
    { STMT_INT,		"wait_noconn",	&wait_noconn },
    { STMT_INT,		"wait_empty",	&wait_empty },
    { STMT_INT,		"wait_open",	&wait_open },
    { STMT_INT,		"wait_queue",	&wait_queue },
    { STMT_BOOL,	"do_logging",	&do_log },
    { STMT_BOOL,	"log_dns_names", &log_dns_names },
    { STMT_BOOL,	"try_alt_port",	&try_alt_port },
    { STMT_BOOL,	"save_ckp",	&save_ckp },
    { STMT_STRING,	"info_file",	(int *) info_file },
    { STMT_STRING,	"faq_file",	(int *) faq_file },
    { STMT_STRING,	"location",	(int *) location },
    { STMT_STRING,	"admin",	(int *) admin },
    { STMT_SERVER,	"servers",	NULL },
    { STMT_PORT,	"port",		NULL },
    { STMT_EXCLUDE,	"exclude",	NULL },
};
#define MAX_KEYWORDS	(sizeof(keywords) / sizeof(struct keyword_t))


/*
 * print a message to the log
 *
 * (I'd use varargs, but vfprintf() doesn't exist everywhere, so why bother?)
 */
void
log_msg(format, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
char *format;
int arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7;
{
    char buf[256];

    if (!do_log) return;
    if (logfp == NULL) {
	fprintf(stderr, "WARNING: attempt to write message to closed log\n");
	return;
    }

    /* date is in hex to keep it compact */
    sprintf(buf, "%.8lx: ", time(0));
    sprintf(buf+10, format, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
    if (*(buf + strlen(buf)-1) != '\n')
	strcat(buf, "\n");

    /* since there should only be one metaserver process, we probably don't */
    /* need to seek to the end every time (but it allows truncation...) */
    fseek(logfp, (off_t) 0, 2);
    fprintf(logfp, buf);
}


/* add an entry to the "server" list */
static void
add_server(type, host, iphost, port, players, comment)
char *type, *host, *iphost, *port, *players, *comment;
{
    int portnum = atoi(port);
    int playernum = atoi(players);
    int rsa;

    if (servers == NULL) {
	servers = (SERVER *) malloc(sizeof(SERVER));
    } else {
	servers = (SERVER *) realloc(servers,
		sizeof(SERVER) * (server_count+1));
    }
    if (servers == NULL) {
	fprintf(stderr, "ERROR: malloc/realloc failure (servers)\n");
	exit(1);
    }
    servers[server_count].status = SS_INIT;
    servers[server_count].cstate = CS_CLOSED;
    if (!strncmp(host, "RSA", 3)) {
	rsa = 1;
	strcpy(servers[server_count].hostname, host+3);
    } else {
	rsa = 0;
	strcpy(servers[server_count].hostname, host);
    }
    strcpy(servers[server_count].ip_addr, iphost);
    if ((servers[server_count].addr = lookup(host, iphost)) == -1)
	return;
    servers[server_count].type[0] = *type;
    servers[server_count].port = portnum;
    servers[server_count].max_players = playernum;
    servers[server_count].count = 0;
    servers[server_count].last_update = (time_t) 0;
    servers[server_count].down_time = 0;
    /* set the next_update time to space the checks 40 seconds apart */
    servers[server_count].next_update = (time_t) (time(0) +
						    server_count*START_DIST);
    servers[server_count].flags = 0L;
    servers[server_count].display_flags = (DF_ALWAYS_DEAD) | (DF_RSA * rsa);
    if (comment == NULL)
	strcpy(servers[server_count].comment, "");
    else
	strcpy(servers[server_count].comment, comment);
    servers[server_count].player_count = 0;
    server_count++;
}

/* add an entry to the "port" list */
static void
add_port(port, type, description, extra)
char *port, *type, *description, *extra;
{
    DISPLAY kind;
    int portnum = atoi(port);
    int i;

    /* see if portnum is valid and port kind is known */
    if (portnum < 1024) {
	fprintf(stderr, "WARNING: port %d (%s) reserved; stmt ignored\n",
		portnum, port);
	return;
    }
    for (i = 0; i < MAX_PORT_DEF; i++) {
	if (!strcmp(type, port_defs[i].port_name)) {
	    kind = port_defs[i].port_kind;
	    break;
	}
    }
    if (i == MAX_PORT_DEF) {
	fprintf(stderr, "WARNING: unknown port kind '%s'\n", type);
	return;
    }

    /* create a new entry, and add it to the list */
    if (ports == NULL) {
	ports = (PORT *) malloc(sizeof(PORT));
    } else {
	ports = (PORT *) realloc(ports,
		sizeof(PORT) * (port_count+1));
    }
    if (ports == NULL) {
	fprintf(stderr, "ERROR: malloc/realloc failure (ports)\n");
	exit(1);
    }
    ports[port_count].port = portnum;
    ports[port_count].kind = kind;
    strcpy(ports[port_count].desc, description);
    if (extra != NULL)
	strcpy(ports[port_count].extra, extra);
    else
	ports[port_count].extra[0] = '\0';
#ifdef DEBUG
    printf("port=%d, kind=%d, desc='%s', extra='%s'\n",
	ports[port_count].port, ports[port_count].kind,
	ports[port_count].desc, ports[port_count].extra);
#endif
    port_count++;
}

/* add an entry to the "exclude" list */
static void
add_exclude(host)
char *host;
{
    if (excludes == NULL) {
	excludes = (EXCLUDE *) malloc(sizeof(EXCLUDE));
    } else {
	excludes = (EXCLUDE *) realloc(excludes,
		sizeof(EXCLUDE) * (exclude_count+1));
    }
    if (excludes == NULL) {
	fprintf(stderr, "ERROR: malloc/realloc failure (excludes)\n");
	exit(1);
    }
    if ((excludes[exclude_count].addr = lookup(host, host)) == -1)
	return;
#ifdef DEBUG
    printf("exclude %s (0x%lx)\n", host, excludes[exclude_count].addr);
#endif

    exclude_count++;
}

/* for some stupid reason my inet_ntoa() is crashing (bad shared libs??) */
char *
my_ntoa(val)
unsigned long val;
{
    static char buf[16];
    unsigned long foo;
    char *cp;
    int i;

    cp = buf;
    for (i = 3; i >= 0; i--) {
	/* sadly, sprintf() doesn't return #of chars on BSD systems */
	foo = val >> (8*i);
	sprintf(cp, "%d.", foo & 0xff);
	cp += strlen(cp);
    }
    *(cp-1) = '\0';	/* trim the last '.' off */
    return (buf);
}

/* print the list of excludes after the log file is open */
static void
print_excludes()
{
    int i;

    for (i = 0; i < exclude_count; i++)
	log_msg("exclude #%d is %s (0x%.8lx)", i, my_ntoa(excludes[i].addr),
		excludes[i].addr);
}

/* find the next token */
static char *
next_token(str)
char *str;
{
    /* skip leading whitespace */
    while (*str && (*str == ' ' || *str == '\t' || *str == '\n'))
	str++;
    if (*str)
	return (str);
    else
	return (NULL);
}


/*
 * read the configuration in (may be called from a signal handler)
 */
static void
load_config()
{
    FILE *fp;
    char buf[128], *cp, *cp2, *cp3, *cp4, *cp5, *cp6;
    int i, len, line;
    int serv_mode = FALSE;

    /* clear out any existing configuration stuff */
    if (server_count) {
	free(servers);
	server_count = 0;
    }
    *info_file = *faq_file = *admin = *location = '\0';

    if ((fp = fopen(CONFIGFILE, "r")) == NULL) {
	perror("ERROR: can't open config file");
	exit(1);
    }

    line = 0;
    while (1) {
	fgets(buf, 128, fp);
	line++;
	if (feof(fp)) break;
	if (ferror(fp)) {
	    perror("ERROR: error reading config file");
	    exit(1);
	}
	if (buf[strlen(buf)-1] == '\n')
	    buf[strlen(buf)-1] = '\0';

	/* skip comments and blank lines */
	cp = next_token(buf);
	if (cp == NULL || *cp == '#')
	    continue;

	/* once serv_mode is TRUE, we don't do anything but snag server names */
	if (serv_mode) {
	    cp = strtok(cp, " \t");
	    if ((cp2 = strtok(NULL, " \t")) == NULL) {	    /* Server name */
		fprintf(stderr,
			"WARNING: missing server name (line %d); ignored\n",
				line);
		break;
	    }
	    if ((cp3 = strtok(NULL, " \t")) == NULL) {	    /* IP address */
		fprintf(stderr,
			"WARNING: missing IP host (line %d); ignored\n", line);
		break;
	    }
	    if ((cp4 = strtok(NULL, " \t")) == NULL) {	    /* Port */
		fprintf(stderr,
			"WARNING: missing port (line %d); ignored\n", line);
		break;
	    }
	    if ((cp5 = strtok(NULL, " \t")) == NULL) {      /* Player # */
                fprintf(stderr,
                        "WARNING: missing player number (line %d); ignored\n", line);
                break;
            }
	    cp6 = next_token(cp4+strlen(cp5)+1);
	    if (verbose) printf("Line is: %s %s %s %s %s %s\n",
				cp, cp2, cp3, cp4, cp5, cp6);
	    add_server(cp, cp2, cp3, cp4, cp5, cp6);

	    continue;
	}

	/* find a token in "keywords" which matches */
	for (i = 0; i < MAX_KEYWORDS; i++) {
	    len = strlen(keywords[i].keyword);	/* use table for speed? */
	    if (!strncmp(cp, keywords[i].keyword, len)) {
		cp2 = next_token(cp+len);
		if (cp2 != NULL && *cp2 == ':') {
		    break;
		}
	    }
	}
	if (i == MAX_KEYWORDS) {
	    fprintf(stderr, "WARNING: unknown statement (line %d): %s\n",
		line, buf);
	    continue;
	}

	/* parse the right-hand side with strtok (unless it's a string) */
	if (keywords[i].kind == STMT_STRING || keywords[i].kind == STMT_SERVER){
	    cp = next_token(cp2+1);
	} else if ((cp = strtok(cp2+1, " \t")) == NULL) {
	    fprintf(stderr, "WARNING: missing rhs (line %d); ignored\n", line);
	    continue;
	}

	switch (keywords[i].kind) {
	case STMT_BOOL:		/* boolean on/off */
	    if (!strcasecmp(cp, "on"))
		*(keywords[i].var) = 1;
	    else if (!strcasecmp(cp, "off"))
		*(keywords[i].var) = 0;
	    else
		fprintf(stderr,
		    "WARNING: boolean expr must be 'on' or 'off' (line %d)\n",
		    line);
	    break;
	case STMT_INT:		/* integer value */
	    *((int *) keywords[i].var) = atoi(cp);
	    break;
	case STMT_STRING:	/* string; dest should be allocated */
	    if (keywords[i].var == NULL) {
		fprintf(stderr, "WARNING: no storage\n");
	    } else {
		strcpy((char *) keywords[i].var, cp);
	    }
	    break;
	case STMT_PORT:		/* "port" line */
	    if ((cp2 = strtok(NULL, " \t")) == NULL) {
		fprintf(stderr, "WARNING: port missing port kind; ignored\n");
		break;
	    }
	    if ((cp3 = strtok(NULL, "\"\t")) == NULL) {
		fprintf(stderr, "WARNING: port missing description; ignored\n");
		break;
	    }
	    cp4 = strtok(NULL, " \t");
	    add_port(cp, cp2, cp3, cp4);
	    break;
	case STMT_EXCLUDE:	/* "exclude" line */
	    add_exclude(cp);
	    break;
	case STMT_SERVER:	/* "server" line */
	    serv_mode = TRUE;
	    break;
	default:
	    /* "shouldn't happen" */
	    fprintf(stderr, "Internal error: bad keyword kind %d\n",
		keywords[i].kind);
	}
    }

    /* sanity check: make sure we have something to do! */
    if (!server_count) {
	fprintf(stderr, "ERROR: no servers in list!\n");
	exit(1);
    }
    if (!port_count) {
	fprintf(stderr, "ERROR: no ports to listen on!\n");
	exit(1);
    }

#ifdef DEBUG2
    for (i = 0; i < server_count; i++) {
	printf("%d: %s\n", i, servers[i].hostname);
    }
#endif
}


/*
 * open the log file and print a friendly message
 */
static void
open_log()
{
    time_t now;
    char *ostr;

    if (!do_log) return;

    /* prepare the log file */
    if (!trun)
	ostr = "a";	/* just append */
    else
	ostr = "w";	/* truncate */

    if ((logfp = fopen(LOGFILE, ostr)) == NULL) {
	perror("ERROR: can't open log file for writing");
	exit(1);
    } else {
	setvbuf(logfp, NULL, _IONBF, 0);   /* no buffering */

	log_msg("\n");
	now = time(0);
	log_msg("** MetaServerII v%s started on %s", VERSION, ctime(&now));
    }
}


/*
 * Save status to checkpoint file
 *
 * Just does a dump of server structs.  Changing the struct makes existing
 * checkpoint files worthless.  Puts the current time and the start time at
 * the head of the file.
 */
void
save_checkpoint()
{
    FILE *fp;
    time_t now;

    if (!save_ckp) {
	log_msg("checkpoint NOT saved");
	return;
    }

    if ((fp = fopen(CHECKFILE, "w")) == NULL) {
	if (verbose) perror("fopen(checkfile)");
	log_msg("unable to open checkpoint file (err=%d)", errno);
	return;
    }

    now = time(0);
    if (fwrite((char *)&now, sizeof(time_t), 1, fp) <= 0) {
	if (verbose) perror("fwrite(checkfile) time-now");
	log_msg("unable to write to checkpoint file (t)(err=%d)", errno);
	fclose(fp);
	return;
    }
    if (fwrite((char *)&checktime, sizeof(time_t), 1, fp) <= 0) {
	if (verbose) perror("fwrite(checkfile) time-begin");
	log_msg("unable to write to checkpoint file (t2)(err=%d)", errno);
	fclose(fp);
	return;
    }

    if (fwrite((char *)servers, sizeof(SERVER), server_count, fp) <= 0) {
	if (verbose) perror("fwrite(checkfile)");
	log_msg("unable to write to checkpoint file (err=%d)", errno);
	fclose(fp);
	return;
    }

    fclose(fp);
    log_msg("checkpoint saved");
}


/*
 * Restore status from checkpoint file
 *
 * The server list may have changed, so we need to match each item from the
 * checkpoint file with an item in the server list.  This is not trapped
 * automatically.
 *
 * If the checkpoint file is corrupted, we can get pretty messed up.
 */
void
restore_checkpoint()
{
    SERVER *sp, srvbuf;
    FILE *fp;
    time_t filetime, now;
    int i, j;

    if ((fp = fopen(CHECKFILE, "r")) == NULL) {
	if (verbose) perror("fopen(checkfile)");
	log_msg("unable to open checkpoint file (r)(err=%d)", errno);
	return;
    }
    if (fread((char *)&filetime, sizeof(time_t), 1, fp) <= 0) {
	if (verbose) perror("fread(checkfile) time");
	log_msg("unable to read checkpoint file (t)(err=%d)", errno);
	fclose(fp);
	return;
    }
    if (uptime - filetime > MAX_CKP_AGE) {
	if (verbose) fprintf(stderr,
		    "Warning: checkpoint file too old (ignored)\n");
	log_msg("checkpoint file ignored (%d seconds old, max is %d)",
		uptime - filetime, MAX_CKP_AGE);
	goto dodel;
    }
    if (verbose) printf("Checkpoint file is %d seconds old\n",
	uptime - filetime);

    if (fread((char *)&checktime, sizeof(time_t), 1, fp) <= 0) {
	if (verbose) perror("fread(checkfile) time");
	log_msg("unable to read checkpoint file (t)(err=%d)", errno);
	fclose(fp);
	return;
    }

    /* for each entry in the checkpoint file, find the matching SERVER */
    /* (if config info has changed, ignore the checkpoint entry) */
    while (1) {
	if (fread((char *)(&srvbuf), sizeof(SERVER), 1, fp) <= 0)
	    break;
#ifdef DEBUG
	printf("Got '%s'\n", srvbuf.hostname);
#endif

	/* shouldn't compare against already-matched servers... */
	for (i = 0, sp = servers; i < server_count; i++, sp++) {
	    if (!strcmp(srvbuf.hostname, sp->hostname) &&
		!strcmp(srvbuf.ip_addr, sp->ip_addr) &&
		!strcmp(srvbuf.comment, sp->comment) &&
		(srvbuf.type[0] == sp->type[0]) &&
		srvbuf.port == sp->port) {
		bcopy(&srvbuf, sp, sizeof(SERVER));
	    }
	}
    }

    /* do some minor cleaning in case we interrupted it last time */
    /* while we're here, bump up "next check" times for new servers */
    now = time(0);
    for (i = j = 0, sp = servers; i < server_count; i++, sp++) {
	sp->pstate = PS_PORTM;
	sp->cstate = CS_CLOSED;
	if (sp->status == SS_WORKING) sp->status = SS_INIT;

	if (sp->status == SS_INIT)
	    sp->next_update = (time_t) (now + (j++) * START_DIST);
    }

    log_msg("checkpoint restored");

    /* delete the checkpoint file */
dodel:
    if (unlink(CHECKFILE) < 0) {
	if (verbose) perror("unlink(checkfile)");
	log_msg("unable to unlink checkfile (err=%d)", errno);
    }
}


/* fatal signal received */
static int
sig_death(sig, code, scp, addr)
int sig, code;
struct sigcontext *scp;
char *addr;
{
    if (verbose) printf("%s: killed by signal %d\n", prog, sig);
    log_msg("exit: killed by signal %d", sig);

    save_checkpoint();

#ifdef ABORT
#ifndef PROF	/* always exit cleanly if we're doing profiling */
    log_msg("sig = %d, code = %d, scp = 0x%.8lx, addr = 0x%.8lx\n",
	sig, code, scp, addr);
    log_msg("pc = 0x%.8lx\n", scp->sc_pc);
    log_msg("Dumping core...\n");
    abort();
#endif
#endif /*ABORT*/

    exit(2);
}


/* reconfigure signal receieved */
static int
sig_reconfig(sig)
int sig;
{
    if (verbose) printf("%s: received reconfigure signal %d\n", prog, sig);
    /*log_msg("rereading configuration file (signal %d)", sig);*/
    log_msg("ignoring config signal %d", sig);

    /*load_config();*/
}


/*
 * initialize signal handling stuff
 */
static void
init_signals()
{
    SIGNAL(SIGINT, sig_death);
    SIGNAL(SIGQUIT, sig_death);
    SIGNAL(SIGTERM, sig_death);
    SIGNAL(SIGHUP, sig_reconfig);
    SIGNAL(SIGPIPE, SIG_IGN);
    /*SIGNAL(SIGSEGV, sig_crash);*/

    SIGNAL(SIGALRM, wakeup);
}


/*
 * Process args
 */
int
main(argc, argv)
int argc;
char *argv[];
{
    extern char *optarg;
    extern int optind;
    int c;
    FILE *fp;
    int pid = getpid();
    if ((fp = fopen(PIDFILE,"w"))==NULL) {
	perror("pid file");
	exit(0);
    }

    fprintf(fp,"%d\n", pid);
    fclose(fp);

    /* parse args */
    while ((c = getopt(argc, argv, "vtd")) != EOF)
	switch (c) {
	case 'v':
	    verbose++;
	    break;
	case 't':
	    trun++;
	    break;
	case 'd':
	    BecomeDaemon();
	    break;
	case '?':
	default:
	    fprintf(stderr, "Netrek MetaServerII v%s\n", VERSION);
	    fprintf(stderr, "usage: metaserverII [-d] [-v] [-t]\n\n");
	    exit(2);
	}

    if ((prog = strrchr(argv[0], '/')) == NULL)
	prog = argv[0];
    else
	prog++;

    /* read the configuration file */
    load_config();

    /* initialize internal data structures */
    init_connections();

    /* open the log and print a friendly message */
    if (do_log) open_log();

    /* init the signal handling stuff */
    init_signals();

    uptime = checktime = time(0);

    /* if there's a checkpoint file, load the old data */
    restore_checkpoint();

    /* print the list of excluded hosts, for reference */
    print_excludes();

    /* nothing has failed yet, so go wait for connections */
    main_loop();

    exit(0);
    /*NOTREACHED*/
}