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

Added Files:
	BecomeDaemon.c Makefile PROJECTS checkalive disp_info.c 
	disp_new.c disp_old.c disp_udp.c disp_web.c features 
	goaway.txt main.c meta.h metaII_info metacheck.c metalist 
	metarc packets.h rsa_keys scan.c server.c server_faq 
	serverlist 
Log Message:
Importing the metaserver into the sourceforge CVS.  Unfortunately,
some revision history was lost from the old Tanner server.

This version is the active version as of 2006-Feb-13.

--Carlos V.




--- NEW FILE: goaway.txt ---
Go away.


--- NEW FILE: features ---
#
# Syntax:
# FEATURE_NAME                <S/C>   <ON/OFF>        [int-arg] [int-arg]
#
# Description:
# FEATURE_NAME: is a unique string of up to 80 characters describing
# a client or server feature.
# <S/C>: 'S' indicates that the feature requires server support.  Servers
# that do not support the feature, regardless of how it is listed in the
# .features file, will return value UNKNOWN to clients requesting that
# feature.  'C' indicates that the feature is a client option, requiring no
# special code support from the server.
# <ON/OFF>: As stated above, if the server does not support a given feature,
# this value is ignored and UNKNOWN will be sent to the client.  Otherwise
# this value is returned and will be honored by the client.
# [int-args]: two optional integer args may be used for future extensions.
#
# feature packets support
FEATURE_PACKETS       S       ON
# why-dead message extra args (see vanilla server)
WHY_DEAD              S       ON
# receiver-configurable distress (see vanilla server)
RC_DISTRESS           S       ON
# multi-line macros (see vanilla server)
MULTIMACROS           C       ON
# first 8 flags of my ship sent in SP_S_YOU_SS (see vanilla server)
SELF_8FLAGS           S       ON
# warp 15 means player is cloaked (see vanilla server)
CLOAK_MAXWARP         S       ON
# SB hours (?)
SBHOURS               S       ON
# client macro switch (old clients)
NEWMACRO              C       ON
# client macro switch (old clients)
SMARTMACRO            C       ON
# continuous mouse
CONTINUOUS_MOUSE      C       OFF
# continuous steering
CONTINUOUS_STEER      C       ON
# info mode
INFO_MODE1            C       ON
# beeplite options. These get or'ed together to produce the BEEPLITE value.
#       LITE_PLAYERS_MAP       1
#       LITE_PLAYERS_LOCAL     2
#       LITE_SELF              4
#       LITE_PLANETS           8
#       LITE_SOUNDS            16
#       LITE_COLOR             32
#       LITE_TTS               64
#
BEEPLITE              C       ON      95
# lite players map
LITE_PLAYERS_MAP      C       ON
# lite players local
LITE_PLAYERS_LOCAL    C       ON
# lite self
LITE_SELF             C       ON
# lite planets        
LITE_PLANETS          C       ON
# lite sounds
LITE_SOUNDS           C       ON
# ship stat. packets
SHIP_CAP              S       ON
#
#
# OLD FORMAT FOR CLIENTS WITHOUT FEATURE_PACKETS SUPPORT.
#
#	INL SERVERS READ: The inl servers are using a broken version of 
#		feature.c and no one seems to want to fix it. So ALL the
#		features without confirm strings WILL NOT work. YOU WILL
#		HAVE TO PUT A CONFIRM ON THEM. To do this for example change:
#			Client Of Win: WHY_DEAD  	to
#			Client Of Win*confirm:0.00.0: WHY_DEAD
#
#
#
#	BRM FEATURES
#
# NEWMACRO and SMARTMACRO are intelligent message macro sending. For more info
# look at the NEWMACRO.DOC that comes with the BRM source.
# These should be disabled soon since they are now on by default and BRM2.99
# doesn't use these anymore.
BerkRick Moo!confirm:2.99.0: NEWMACRO
BerkRick Moo!confirm:2.99.0: SMARTMACRO
#
# WHY_DEAD will allow the server/client to communicate why a player died while
# using Short Packets.
BerkRick Moo*confirm:2.00.15: WHY_DEAD
#
ricksmoo 3.0*confirm:moo 3.0.99: WHY_DEAD
#
#
# Starting with 2.99 the BRM will have SMART_MACROS on unless told not to by
# the server. NOSMARTMACROS turns off smart-macros.
#BerkRick Moo*confirm:2.99.0: NO_SMARTMACROS 
#
# NO_SMARTDISTRESS is new to BRM 2.99. It disables the smart distresses when
# the client recieves the string.
#BerkRick Moo*confirm:2.99.0: NO_SMARTDISTRESS
#
# RC_DISTRESS is new to BRM 2.99pl2. It enables variable distress calls. If the
# server has it then it should be enabled.
BerkRick Moo*confirm:2.99.3: RC_DISTRESS
#
# NO_CONTINUOUS_MOUSE is to disable the ability to have auto-repeat with the
# mouse.
BerkRick Moo*confirm:3.00.0: NO_CONTINUOUS_MOUSE
#
#
#	BRM INFO CLIENT FEATURES
#
# Disable all of BRMi's enhancements
#BerkRick Moo (i): NO_INFO_MODE
#
# Enable the first level of enhanced information.
BerkRick Moo (i): INFO_MODE1
#
# INFO_MODE2-6 enable more and more until 6 is a full blown info-borg
#
#
# Enable BRMi's code which provides minimal information about
# touching planets, bombing etcetera.
#
#BerkRick Moo (i):0.00.0: TRAINER_MODE
#
#
#	BRM SOUND CLIENT FEATURES
#
BRM-Sound: WHY_DEAD
BRM-Sound*confirm:0.99.0: INFO Welcome to the world of Sounds.
#BRM-Sound: NO_SMARTMACROS
#BRM-Sound: NO_SMARTDISTRESS
#
#
#       BRM - HADLEY  FEATURES
#
BRM-Hadley*confirm:1.4.0: WHY_DEAD
#
#
#	Paradise CLIENT FEATURES
#
paradise 2*confirm:2.2 patch 11au: WHY_DEAD
paradise 2*confirm:2.2 patch 11au: NO_CONTINUOUS_MOUSE
#paradise 2*confirm:2.2 patch 11au: NO_NEWMACRO
#paradise 2*confirm:2.2 patch 11au: NO_SMARTMACRO
#paradise 2*confirm:2.2 patch 11au: RC_DISTRESS

--- NEW FILE: Makefile ---
#
# Makefile for MetaServerII
#
#CC = purify -log-file="/home/netrek/metaserver/purify.log" -show-directory=yes -show-pc=yes -user-path="/home/netrek/metaserver" gcc
CC = gcc
CFILES	= main.c scan.c server.c disp_old.c disp_new.c disp_web.c disp_udp.c disp_info.c BecomeDaemon.c
OFILES	= $(CFILES:.c=.o)
HDRS	= meta.h packets.h copyright2.h
TARGET	= metaserverII
UTSLIBS	= -lbsd -lsocket

#CFLAGS	= -O -s -DSYSV  -DDEBUG
#CFLAGS	= -O -s -DDEBUG
#CFLAGS	= -O -s
#CFLAGS	= -O -s -DSYSV
CFLAGS	= -ggdb -DSYSV 
#CFLAGS	= -g -DDEBUG
#CFLAGS	= -g -p -DPROF
LIBS	= 

# Amdahl UTS stuff
#LIBS	= $(UTSLIBS)
#LIBS	= -lnsl -lsocket
# hpux stuff
#LIBS	= -lBSD
# Solaris stuff
#LIBS	= -lsocket -lnsl

all: $(TARGET)

$(TARGET): $(OFILES)
	$(CC) $(CFLAGS) -o $(TARGET) $(OFILES) $(LIBS)

$(OFILES):	meta.h

tar:
	tar cvf metaII.tar metarc rsa_keys Makefile features \
			   metaII_info server_faq $(CFILES) $(HDRS)
#			   sample.metarc Customers $(CFILES) $(HDRS)

clean:
	-rm -f $(OFILES)
clobber: clean
	-rm -f $(TARGET)

--- NEW FILE: rsa_keys ---
###############################################################################
# RSA key list (for server administrators only; you don't need this to play)
# Last update: Tue May 18 10:50 PDT 1999
#
# Contact metaserver at us.netrek.org for information and
# administrative requests (e.g., if you want to have a key added, e-mail
# it to clientkeys at clientkeys.netrek.org ).
#
# When submitting keys, don't forget to include (1) if it's reserved.c
# blessed, and (2) if it'll be available for FTP, specify the site.
#
# You need a program to convert from this format to the binary format used
# be the server itself; look for keycomp.tar.Z on pittslug.sug.org.  Please
# use the newer key generation tools that create keys in this format.
#
################################################################################
#
#	Bronco Clients	- ick
#
[...1161 lines suppressed...]
   :pk=c3915350005759ed5f831e63a5358547de04282c187d8874b0457ea979b88b0d:
#
#
#
NetrekXP-RSA-Key-Win32:ct=NetrekXP:cr=ssheldon at sodablue.org:\
   :cd=July 2001:ar=Win32:cl=inl,standard2:\
   :cm=Netrek XP for Windows 9x/NT/2000/XP:\
   :gk=0ff451661e2c8af6d79450af271617d38c0322e77195fea59fae0592139d3306:\
   :pk=d74853b1837ab65914ab90aa563e4c0cab47698b0a86a290cb2bded894983704:
#
NetrekXP-Mod-RSA-Key-Win32:ct=NetrekXP Mod:cr=keyos at keyos.org:\
   :cd=January 2003:ar=Win32:cl=inl,standard2:\
   :cm=NetrekXP Modification for Windows 9x/NT/2000/XP:\
   :gk=b160145c4205bba8815db0a908fcac9b99a0303ee44c204469374109e09bb782:\
   :pk=8b0f3d3f1b1b8528d86545d3be9d8900f2e3130cb52248df9a5ec63ff9a15e11:
#
#
# Temp development keys
#
# --- end of list ---

--- NEW FILE: scan.c ---
/*
 * scan.c - scan the various servers
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 * 
 * $Id: scan.c,v 1.1 2006/02/14 06:43:11 unbelver Exp $
 * 
 */
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
[...1406 lines suppressed...]

/* 
 * wrapper for the web stuff in case I want to limit # of simultaneous
 * connections.
 *
 * Yup. it looks like I do.
 */
void do_web(int idx, int port_idx)
{
  if (numweb > MAX_NUM_WEB)
    {
      log_msg("do_web: too many active web processes");
      return;
    }
  else
    {                           /* count number of processes and fork */
      numweb++;
      web_fork(idx, port_idx);
    }
}

--- NEW FILE: disp_udp.c ---
/*
 * disp_udp.c - output to client via returned buffer
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "meta.h"

char *
display_udp(int *length)
{
    SERVER *sp;
    int srv, *sorted, count;
    char *buffer, *bp;
    time_t now = time(NULL);
    int extendedstatus;

    /* sort and count the servers that will be returned */
    sorted = sort_servers();
    count = 0;
    for (srv = 0; srv < server_count; srv++) {
	sp = &servers[sorted[srv]];
	if (sp->status == SS_WORKING) sp = &prev_info;
	if (sp->status == SS_INIT) continue;
	if (sp->status == SS_WORKING) continue;
	count++;
    }

    /* estimate memory required and allocate it, match with sprintf()'s */
    buffer = malloc((MAX_HOSTNAME+1 +6+1 +3+1 +6+1 +3+1 +3+1 +1+1 +1)*count+6);
    /*			host        port stat age  play que  type \n	*/

    /* out of memory? */
    if ( buffer == NULL ) return "";
    bp = buffer;

    /* include the count at the top to ease the client's coding */
    sprintf(bp,"r,%d\n",count);
    bp += strlen(bp);

    for (srv = 0; srv < server_count; srv++) {
	sp = &servers[sorted[srv]];
	if (sp->status == SS_WORKING) sp = &prev_info;
	if (sp->status == SS_INIT) continue;
	if (sp->status == SS_WORKING) continue;

        /* this if structure is to satisfy COW's server filtering based on */
        /* the TCP output of the metaserver. TCP was text, this is only #s */
        if (sp->status == SS_NOCONN )
          extendedstatus = (sp->why_dead == WD_TIMEOUT) ? 6 : 4;
        else
          extendedstatus = sp->status;

        sprintf(bp,"%s,%d,%d,%d,%d,%d,%c\n",
	    sp->hostname,		/* host name of server		*/
	    sp->port,			/* port number of server	*/
	    extendedstatus,		/* metaserver status code	*/
	    now - sp->last_update,	/* age of data in seconds	*/
	    sp->player_count,		/* count of players on server	*/
	    sp->queue_size,		/* length of wait queue		*/
	    sp->type[0] );		/* type code, B, P, etc		*/
        bp += strlen(bp);
    }

    *length = strlen(buffer);		/* for future optimisation	*/

    return buffer;			/* caller must free buffer!	*/
}

--- NEW FILE: serverlist ---
[brain] ~/netrek/metaserver/src/metaserver%telnet pickled.fox.cs.cmu.edu 2591
Trying 128.2.181.129...
Connected to pickled.fox.cs.cmu.edu.
Escape character is '^]'.
<>=======================================================================<>
  Pl: Rank       Name             Login      Display                  Type
<>=======================================================================<>
  F1: Lieutenant guest            peterm     #S.Berkeley.EDU           SC
  F2: Captain    Quayle_for_'99   Bozo       141.117.90.228            CA
  O3: Commodore  Mojo_Riser       tom        #o.Berkeley.EDU           CA
  O4: Lt. Cmdr.  spiderman        netrek     #0.nycap.rr.com           CA
  F5: Commander  Funky_Buddha     Bozo       #0.sympatico.ca           CA
  O6: Lt. Cmdr.  guest            puckett    208.197.220.173           CA
  F7: Flt. Capt. Jan_Brady        SGO        #4.dial.umd.edu           CA
  O8: Commodore  _____            psychos    fusion.blm.net            CA
  F9: Ensign     redford          OEM        #A8.ipt.aol.com           CA
  Fa: Ensign                                 #d1.tx.home.com           CA
  Oc: Ensign     burger_king      netrek     #llatlantic.net           CA
  Fe: Lieutenant KickButtMan!     KickButtMa #78.peganet.com           CA
  Of: Lt. Cmdr.  peskylilw00bie   jivey      206.147.83.29             SC
  Fg: Ensign     guest            Lab1       #mmons.ucla.edu           CA
  Oh: Ensign     dui-na__         g8vinny    #df.toronto.edu           CA
<>=======================================================================<>
  Feds: 7   Roms: 0   Kli: 0   Ori: 7
  No wait queue.
<>=======================================================================<>

--- NEW FILE: PROJECTS ---
List of things to be done.

- port 1080 web display is blank for most browsers unless they are a long
way from the metaserver.  [rec.games.netrek, "Server List", May 1999]
[fixed by forking off a different process to handle web requests -c.v]


--- 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
 * 
 * $Id: disp_old.c,v 1.1 2006/02/14 06:43:11 unbelver Exp $
 * 
 */
#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
 * 
 * $Id: disp_new.c,v 1.1 2006/02/14 06:43:11 unbelver Exp $
 * 
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/fcntl.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;
  /*
    int     n, hackfd;
    char    hackbuf[80];
    */

  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;
    }
  }

  /* quick hack to print US metaserver info */
  /*
    if ((hackfd = open ( "usfile", O_RDONLY )) < 0 )
    Uprintf(idx, "\nNo US-metaserver info now, sorry.\n");
    else
    {
    lseek( hackfd, 0L, SEEK_SET );
    while ((n = read (hackfd, hackbuf, 80)) > 0)
    {
    write(idx, hackbuf, n);
    }
    close(hackfd);
    }
    */
   

  /* 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
 * 
 * $Id: packets.h,v 1.1 2006/02/14 06:43:11 unbelver Exp $
 */

/* 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 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
 * 
 * $Id: meta.h,v 1.1 2006/02/14 06:43:11 unbelver Exp $
 * 
 */
/*#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	60		/* 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  58

#define MAX_WEB_TIME    30              /* maximum amount of time a web */
                                        /* request may last in seconds  */
#define MAX_WEB_BYTES 1024              /* biggest packet I'm expecting */
#define MAX_NUM_WEB     20              /* eh. 20 processes seems ok    */
/*
 * 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	36		/* must be >= server MAXPLAYER */
#define NUM_TYPES	8		/* #of different ship types */
#define PNUM_TYPES	16		/* #of different ship types */
#define MAX_HOSTNAME	64

/* 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[MAX_HOSTNAME];	/* 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;
  int  solicit;         /* we got this as a solicit packet */
  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*/);
void sig_child(/*signal handler*/);  /* the child reaper */

/* 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: metacheck.c ---


/* meta.c
 * 
 * - Nick Trown         May 1993        Original Version. - Andy McFadden
 * May 1993 ?   Connect to Metaserver. - BDyess (Paradise)      ???
 * ug Fixes.  Add server type field. - Michael Kellen   Jan 1995        Don't
 * list Paradise Servers. List empty servers. - James Soutter   Jan 1995
 * Big Parsemeta changes.  Included some Paradise Code.  Added Known Servers
 * Option.  Added option for metaStatusLevel.  Bug Fixes.
 * - Jonathan Shekter Aug 1995 -- changed to read into buffer in all cases,
 * use findfile() interface for cachefiles.
 */

#undef DEBUG

#include <limits.h>
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdlib.h>
#include <sys/select.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>



/* Constants */

#define BUF     6144
#define LINE    80                       /* Width of a meta-server
                                                  * line */
/* Local Types */

#define LONG long

struct servers
  {
    char    address[LINE];
    int     port;
    int     age;
    int     players;
    int     status;
    char    typeflag;
  };

struct servers *serverlist = NULL;               /* The record for each
                                                  * server.      */
int     num_servers = 0;                     /* The number of servers.       */


char   *metaWindowName;                      /* The window's name.           */

char   metaserver[256] = "metaserver.netrek.org";
int    metaport = 3521;

/* The status strings:  The order of the strings up until statusNull is
 * important because the meta-client will display all the strings up to a
 * particular point.
 * 
 * The strings after statusNull are internal status types and are formatted
 * separatly from the other strings.
 * 
 * The string corresponding to "statusNull" is assigned to thoes servers which
 * have "statusNobody" or earlier strings in old, cached, meta-server data. */

char   *statusStrings[] =
{"OPEN:", "Wait queue:", "Nobody", "Timed out", "No connection",
 "Active", "CANNOT CONNECT", "DEFAULT SERVER"};

enum statusTypes
  {
    statusOpen = 0, statusWait, statusNobody, statusTout, statusNoConnect,
    statusNull, statusCantConnect, statusDefault
  };

const int defaultStatLevel = statusTout;


/* Functions */

extern void terminate(int error);

static int open_port(char *host, int port, int verbose)
/* The connection to the metaserver is by Andy McFadden. This calls the
 * metaserver and parses the output into something useful */
{
  struct sockaddr_in addr;
  struct hostent *hp;
  int     sock;


  /* Connect to the metaserver */
  /* get numeric form */
  if ((addr.sin_addr.s_addr = inet_addr(host)) == -1)
    {
      if ((hp = gethostbyname(host)) == NULL)
        {
          if (verbose)
            fprintf(stderr, "unknown host '%s'\n", host);
          return (-1);
        }
      else
        {
          addr.sin_addr.s_addr = *(LONG *) hp->h_addr;
        }
    }
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      if (verbose)
        perror("socket");
      return (-1);
    }
  if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0)
    {
      if (verbose)
        perror("connect");
      close(sock);
      return (-1);
    }
  return (sock);
}


static void parseInput(char *in, FILE * out, int statusLevel)
/* Read the information we need from the meta-server. */
{
  char    line[LINE + 1], *numstr, *point, **statStr;
  struct servers *slist;
  int     rtn, max_servers;
  int     count;

#ifdef DEBUG
   printf("In parseInput\n");
#endif   
   
  /* Create some space to hold the entries that will be read.  More space can
   * be added later */

  serverlist = (struct servers *) malloc(sizeof(struct servers) * 10);

  max_servers = 10;
  num_servers = 0;


  /* Add the default server */

  while (1)
    {
      /* Read a line */
      point = line;
      count = LINE + 1;

      do
        {
          if (!(--count))
            {
              fputs("Warning: Line from meta server was too long!!!\n", stderr);
              ++point;                           /* Pretend we read a '\n' */
              break;
            }

          rtn = *in++;
          if (!rtn)                              /* use a zero to mark end of buffer */
            return;
            
          *(point++) = rtn;
        }
      while (rtn != EOF && rtn != '\n');

      *(--point) = '\0';

      if (out != NULL)                           /* Save a copy of the stuff
                                                  * we read */
        {
          fputs(line, out);
          putc('\n', out);
        }


      /* Find somewhere to put the information that is just about to be
       * parsed */

      if (num_servers >= max_servers)
        {
          max_servers += 5;
          serverlist = (struct servers *) realloc(serverlist,
                                      sizeof(struct servers) * max_servers);
        }

      slist = serverlist + num_servers;



      /* Is this a line we want? */

      if (sscanf(line, "-h %s -p %d %d %d",
                 slist->address, &(slist->port),
                 &(slist->age)) != 3)
        {
          continue;
        }

      /* Find the status of the server, eg "Not responding". */

      for (statStr = statusStrings + statusLevel
           ; statStr >= statusStrings
           ; --statStr)
        {
          if ((numstr = strstr(line, *statStr)) != NULL)
            {
              (slist->status) = statStr - statusStrings;
              (slist->players) = 0;
              sscanf(numstr, "%*[^0123456789] %d", &(slist->players));
              break;
            }
        }

      if (statStr < statusStrings)               /* No status was allocated */
        continue;


      /* Read the flags */

      slist->typeflag = *(point - 1);


      /* Don't list Paradise Servers  */

      if (slist->typeflag != 'P')
        {

#ifdef DEBUG 
          printf("HOST:%-30s PORT:%-6d %12s %-5d %d %c\n",
                  serverlist[num_servers].address,
                  serverlist[num_servers].port,
                  statusStrings[serverlist[num_servers].status],
                  serverlist[num_servers].players,
                  serverlist[num_servers].typeflag);
#endif

          ++num_servers;
        }
    }
}

#define MAXMETABYTES 2048
static int sock;		/* the socket to talk to the metaservers */
static int sent = 0;		/* number of solicitations sent		 */
static int seen = 0;		/* number of replies seen		 */

static int ReadMetasSend()
{
  struct sockaddr_in address;	/* the address of the metaservers	 */
  int length;			/* length of the address		 */
  int bytes;			/* number of bytes received from meta'   */
  fd_set readfds;		/* the file descriptor set for select()	 */
  struct timeval timeout;	/* timeout for select() call		 */
  char packet[MAXMETABYTES];	/* buffer for packet returned by meta'	 */
  int max_servers;		/* number of servers we have defined	 */
  

  sock = socket(AF_INET, SOCK_DGRAM, 0);
  if (sock < 0) { perror("ReadMetasSend: socket"); return 0; }

  address.sin_addr.s_addr = INADDR_ANY;
  address.sin_family      = AF_INET;
  address.sin_port        = 0;
  if (bind(sock,(struct sockaddr *)&address, sizeof(address)) < 0) {
    perror("ReadMetasSend: bind");
    return 0;
  }
    
  address.sin_family = AF_INET;
  address.sin_port = htons(metaport);
  /* attempt numeric translation first */
  if ((address.sin_addr.s_addr = inet_addr(metaserver)) == -1) {
    struct hostent *hp;
        
    /* then translation by name */
    if ((hp = gethostbyname(metaserver)) == NULL) {
      /* if it didn't work, return failure and warning */
      fprintf(stderr,
	"Cannot resolve metaserver address of %s, check for DNS problems?\n",
	metaserver);
      return 0;
    } else {
      int i;

      /* resolution worked, send a query to every metaserver listed */
      for(i=0;;i++) {

	/* check for end of list of addresses */
	if (hp->h_addr_list[i] == NULL) break;
	address.sin_addr.s_addr = *(long *) hp->h_addr_list[i];
	printf("Requesting player list from metaserver at %s\n",
		inet_ntoa(address.sin_addr));
	if (sendto(sock, "?", 1, 0, (struct sockaddr *)&address,
		sizeof(address)) < 0) {
	  perror("ReadMetasSend: sendto");
	  return 0;
	}
	sent++;
      }
    }
  } else {
    /* send only to the metaserver specified by numeric IP address */
    if (sendto(sock, "?", 1, 0, (struct sockaddr *)&address,
	sizeof(address)) < 0) {
      perror("ReadMetasSend: sendto");
      return 0;
    }
    sent++;
  }
  return 1;
}

static int ReadMetasRecv()
{
  struct sockaddr_in address;	/* the address of the metaservers	 */
  int length;			/* length of the address		 */
  int bytes;			/* number of bytes received from meta'   */
  fd_set readfds;		/* the file descriptor set for select()	 */
  struct timeval timeout;	/* timeout for select() call		 */
  char packet[MAXMETABYTES];	/* buffer for packet returned by meta'	 */
  int max_servers;		/* number of servers we have defined	 */
  
  /* now await and process replies */
  while(1) {
    char *p;
    int servers, i, j;

    FD_ZERO(&readfds);
    FD_SET(sock, &readfds);
    timeout.tv_sec=0;
    timeout.tv_usec=1000;

    if (select(FD_SETSIZE, &readfds, NULL, NULL, &timeout) < 0) {
      perror("ReadMetasRecv: select");
      return 0;
    }

    /* if the wait timed out, then we give up */
    if (!FD_ISSET(sock, &readfds)) {
      /* here placeth logic that sees time for reply is up and asks again */
      return 0;
    }

    /* so we have data back from a metaserver */
    length = sizeof(address);
    bytes = recvfrom(sock, packet, MAXMETABYTES, 0, (struct sockaddr *)&address,
	&length );
    if (bytes < 0) {
      perror("ReadMetasRecv: recvfrom");
      fprintf(stderr, "sock=%d packet=%08.8x length=%d\n", sock, packet,
	MAXMETABYTES );
      return 0;
    }

    /* terminate the packet received */
    packet[bytes] = 0;
    printf("\n%s\n", packet);

    /* process the packet, updating our server list */

    /* version identifier */
    p = strtok(packet,",");
    if (p == NULL) continue;
    if (p[0] != 'r') continue;

    /* number of servers */
    p = strtok(NULL,"\n");
    if (p == NULL) continue;
    servers = atoi(p);

    /* sanity check on number of servers */
    if (servers > 2048) continue;
    if (servers < 0) continue;

    printf("\nMetaserver at %s responded with %d server%s\n",
	inet_ntoa(address.sin_addr),
	servers,
	servers == 1 ? "" : "s" );

    /* allocate or extend a server list */
    if (serverlist == NULL) {
      serverlist = (struct servers *) malloc(
	sizeof(struct servers) * servers);
    } else {
      serverlist = (struct servers *) realloc(serverlist, 
	sizeof(struct servers) * ( servers + num_servers ));
    }

    /* for each server listed by this metaserver packet */
    for(i=0;i<servers;i++) {
      struct servers *sp = serverlist;
      char *address, type;
      int port, status, age, players, queue;

      address = strtok(NULL,",");	/* hostname */
      if (address == NULL) continue;

      p = strtok(NULL,",");		/* port */
      if (p == NULL) continue;
      port = atoi(p);

      p = strtok(NULL,",");		/* status */
      if (p == NULL) continue;
      status = atoi(p);

      /* ignore servers here based on status */

      p = strtok(NULL,",");		/* age (of data in seconds) */
      if (p == NULL) continue;
      age = atoi(p);

      p = strtok(NULL,",");		/* players */
      if (p == NULL) continue;
      players = atoi(p);

      p = strtok(NULL,",");		/* queue size */
      if (p == NULL) continue;
      queue = atoi(p);

      p = strtok(NULL,"\n");		/* server type */
      if (p == NULL) continue;
      type = p[0];

      /* find in current server list? */
      for(j=0;j<num_servers;j++) {
        sp = serverlist + j;
        if ((!strcmp(sp->address,address)) && (sp->port == port)) {
          fprintf(stderr, "%s:%d already listed\n", address, port);
          break;
        }
      }

      /* if it was found, check age */
      if (j != num_servers) {
	if (age > sp->age) continue;
      } else {
        /* not found, store it at the end of the list */
        sp = serverlist + j;
	strncpy(sp->address,address,LINE);
	sp->port = port;
	sp->age = age;
	num_servers++;
      }

/* from meta.h of metaserver code */
#define SS_WORKING 0
#define SS_QUEUE 1
#define SS_OPEN 2
#define SS_EMPTY 3
#define SS_NOCONN 4
#define SS_INIT 5

      switch (status) {
        case SS_QUEUE:
	  sp->status = statusWait;
	  sp->players = queue;
	  break;
	case SS_OPEN:
	  sp->status = statusOpen;
	  sp->players = players;
	  break;
	case SS_EMPTY:
	  sp->status = statusNobody;
	  sp->players = 0;
	  break;
	case SS_NOCONN:			/* no connection */
	case SS_WORKING:		/* metaserver should not return this */
	case SS_INIT:			/* nor this */
	default:
	  sp->status = statusNoConnect;
	  sp->players = 0;
	  break;
      }
      sp->typeflag = type;
    }
    /* finished processing the packet */

    /* count the number of replies we have received */
    seen++;

    /* if we have seen the same number of replies to what we sent, end */
    if (sent == seen) return 1;
  }
}

static int ReadFromMeta(int statusLevel, int parse)
/* Read from the meta-server.  Return TRUE on success and FALSE on failure. */
{
  FILE   *in, *out;
  char   *cacheName;
  char   cacheFileName[PATH_MAX];
  char   tmpFileName[PATH_MAX];
  char   *sockbuf, *buf;
  int     bufleft=BUF-1;
  int     len;
  int     sock;

  if ((sock = open_port(metaserver, metaport, 1)) <= 0)
    {
      fprintf(stderr, "Cannot connect to MetaServer (%s , %d)\n",
              metaserver, metaport);
      return 0;
    }

  /* Allocate a buffer and read until full */
  buf = sockbuf = (char *)malloc(BUF);
  while (bufleft > 0 && (len = read(sock, buf, bufleft)) > 0)
    {
    bufleft-=len;
    buf += len;
    printf("Read %d bytes from Metaserver\n", len);
    }
  close (sock);
  *buf = 0;                   /* End of buffer marker */

  if (len<0)
    {
    perror ("read");
    free(sockbuf);
    return 0;
    }


  printf("Got this from a TCP request from metaserver (unparsed)\n%s", sockbuf);

  if(parse)
    parseInput(sockbuf, out, statusLevel);

  free(sockbuf);
  metaWindowName = "MetaServer List";

  return 1;
}

void    parsemeta(int metaType)
/* Read and Parse the metaserver information, either from the metaservers
 * by UDP (1), from a single metaserver by TCP (3), or from the cache (2).
 * 
 * NOTE: This function sets the variable "num_servers" which is later used to
 * decide the height of the metaserver window. */
{
  int     statusLevel;

  statusLevel = 0;

  if (statusLevel < 0)
    statusLevel = 0;
  else if (statusLevel >= statusNull)
    statusLevel = statusNull - 1;

  switch (metaType)
    {
      case 1:
        ReadMetasSend();
	ReadMetasRecv();
        return;
	break;
      case 2:
	if (ReadFromMeta(statusLevel,1)) return;
	break;
      case 3:
	if (ReadFromMeta(statusLevel,1)) return;
	break;
    }
}


static void metarefresh(int i)
/* Refresh line i in the list */
{
  char    buf[LINE + 1];
  struct servers *slist;

  slist = serverlist + i;

  sprintf(buf, "%-40s %14s ",
          slist->address,
          statusStrings[slist->status]);

  if (slist->status <= statusNull)
    {
      if (slist->status == statusOpen || slist->status == statusWait)
        {
          /* Don't print the number of players if nobody is playing */
          sprintf(buf + strlen(buf), "%-5d  ", serverlist[i].players);
        }
      else
        {
          strcat(buf, "       ");
        }

      switch (serverlist[i].typeflag)
        {
        case 'P':
          strcat(buf, "Paradise");
          break;
        case 'B':
          strcat(buf, "Bronco");
          break;
        case 'C':
          strcat(buf, "Chaos");
          break;
        case 'I':
          strcat(buf, "INL");
          break;
        case 'S':
          strcat(buf, "Sturgeon");
          break;
        case 'H':
          strcat(buf, "Hockey");
          break;
        case 'F':
          strcat(buf, "Dogfight");
          break;
        default:
          strcat(buf, "Unknown");
          break;
        }
    }
  printf("%s\n", buf);
}


static void metadone(void)
/* Unmap the metaWindow */
{
  /* Unmap window */
  free(serverlist);
}

int main(int argc, char *argv)
{  
  int i;
  
   printf("Doing a TCP request (only one server) to %s\n",metaserver);
   ReadFromMeta(0, 0);

   printf("\nNow doing UDP request (possibly more than one server) to %s\n",metaserver);
   printf("\nWhat you will see is the IP address(es) of the server(s),\n");
   printf("       followed by the raw packet(s) received, \n");
   printf("       followed by the parsed (and merged) output\n\n");
   ReadMetasSend();
   sleep(5);
   ReadMetasRecv();

   for(i=0;i<num_servers;i++)
     metarefresh(i);
}

--- NEW FILE: checkalive ---
#!/usr/bin/perl

open(PS, "/bin/ps ax|grep metaserverII |/bin/grep -v grep|") || die "Error starting ps!\n";

if($ps = <PS>)
{
   exit;
}
else
{
  print("Starting metaserver\n");
  chdir("/home/unbelver/src/metaserver");
  exec("./metaserverII -d");
}

--- NEW FILE: metarc ---
#
# Sample configuration for MetaServerII
#
# $Id: metarc,v 1.1 2006/02/14 06:43:11 unbelver Exp $
#

# ports to listen on for user connections
#
# ----- num --- kind -- "Description" ------------------------- extra-stuff ---
#port:	3520	old	"Original METASERVER format"
port:	3521	new	"New and improved format"
port:	3522	verbose	"Verbose format, with player lists"
port:	1080	web	"Web browser HTML format"
#port:	3523	info	"News & information"			metaII_info
#port:	3524	info	"Server FAQ posting (long)"		server_faq
#port:	3525	info	"MetaServerII config file"		metarc
#port:	3530	info	"RSA key list"				rsa_keys
#port:	3531	info	"Server Feature List"			features


# couldn't connect to server, wait xx minutes
wait_noconn:		30
# server is empty, wait xx minutes
wait_empty:		15
# server is open (1-15 players), wait xx minutes
wait_open:		5
# server has a wait queue, wait xx minutes
wait_queue:		10


# check port-1 (player list port) first?
try_alt_port:		on

# shall we do logging?
do_logging:		on

# log DNS hostnames instead of numeric IP addresses?  (DNS lookups can cause
# pauses during client connections)
log_dns_names:		off

# where are we?
location:		Earth

# who maintains me?
admin:			metaserver at us.netrek.org

# should we write to a checkpoint file before exiting?
save_ckp:		on


#
# List of sites on our "bad" list.  They get a standard "go away" message
# if they try to connect.  [actually, they just get dropped]
#
# Future enhancement, for entire subdomains, allowing tailored messages:
# exclude: ip-addr ip-mask message-file
#

#
# List of servers; runs until end of file.  MetaServerII will use the IP
# address if one is given; if you use 0.0.0.0 it will resolve the domain
# name at startup time.
#
# Note that this format is identical to the FAQ server list; just cut & paste.
# If you plan to take MetaServerII up and down frequently, put the most
# popular servers near the top, so that they can be scanned right away when
# the program comes up.
#
servers:

#				Number of Players ---vv
# server name ----------------- IP address ---- Port -- Notes (optional) ------
#
# BRONCO/VANILLA Servers (B):

B RSAnetrek.unh.edu		0.0.0.0		2592 20 New Hampshire.
B RSAsoda.csua.berkeley.edu	0.0.0.0		2592 20 Berkeley, CA
B RSAspamburger.openface.ca	0.0.0.0		2592 20 Montreal, Canada
B RSAse.netrek.org              0.0.0.0		2592 20 Sweden
B RSAkirk.hal-pc.org		0.0.0.0		2592 20

# 
# CHAOS Servers (C):
# 

#
# HOCKEY Servers (H):
#
# H hockey.psychosis.net             140.186.18.198  2592    36

#
# PARADISE Servers (P):
#
P bode.ee.ualberta.ca		0.0.0.0		2592 20 Alberta, Canada.
P tanya.ucsd.edu 		0.0.0.0		2592 20 San Diego, Ca.
P paradise.games.uk.demon.net   0.0.0.0         2592 32
P europa.informatik.uni-frankfurt.de 141.2.20.4 2592 32
P paradise-lost.kulua.org       0.0.0.0         2592 32

#
# DOGFIGHT/BASEPRACTICE Servers (F):
#

#
# STURGEON Servers (S):
#


--- NEW FILE: server_faq ---
This is a list of all known public Netrek servers.  If you know of any 
others, or if any of my information is wrong, please mail
tom at csua.Berkeley.EDU.

Note: All servers are UDP unless noted otherwise.


Subject: Vanilla servers in America (short listing)

Description: These servers are based on the orignal code, Terence Chang's
	     bronco code, or Nick Trown's New Vanilla release.  They are
	     not all identical but they have minimal modifications.


Server name			INET address	Port	Notes

bronco.ece.cmu.edu		128.2.210.65	2592	Now RSA only.

calvin.usc.edu			128.125.62.143	2592	USC, RSA.

netrek.cs.mcgill.ca		132.206.51.198	2592	Canada.

garnet.cdf.toronto.edu		128.100.31.36	5555	Canada.

wormhole.ecst.csuchico.edu	132.241.1.117	5858	Replaces guzzler, RSA.

bigbang.astro.indiana.edu	129.79.157.41	2592	Indiana, RSA.

rosebud.umiacs.umd.edu		128.8.120.103	2592	Maryland.

mean.mu.caltech.edu		131.215.143.6	2592	Caltech, borgish.

bradbert.ugcs.caltech.edu	131.215.134.140	2592	Caltech.


Subject: Vanilla servers outside America (short listing)

Description: Same as above but not in America.


peanuts.informatik.uni-tuebingen.de 134.2.14.1	2592	Germany.

bayes.ibr.cs.tu-bs.de		134.169.34.33	5855	Germany.

legend.cma.fr			192.33.149.2	2592	France, RSA only.

netrek.chemietechnik.uni-dortmund.de 129.217.174.20  592  Germany.

ghost.dsi.unimi.it		149.132.1.2	2592	Italy, RSA.

netrek.cs.bham.ac.uk		147.188.192.10	2592	U.K. RSA only.

megalag.risc.uni-linz.ac.at	193.170.38.158	2592	Replaces melmac.

fisher.psy.vu.nl		130.37.96.2	2592	Amsterdam.

rsls6.sprachlit.uni-regensburg.de 132.199.136.36  2592	Germany.

gilean.solace.mh.se		193.10.118.132	2592	Sweeden, experimental.


Subject: Sturgeon-style upgrade servers (short listing)

Description: These servers are based on Donald Tsang's Sturgeon source.
	     They include ship upgrades, a modified combat system,
	     ridiculous starbases, and more.

rosebud.umiacs.umd.edu		128.8.120.103	7654	U Maryland.

netrek.engg.ksu.edu		129.130.41.86	3333	Kansas State.


Subject: Paradise-style servers (short listing)

Description: These servers use the NetrekII/Paradise source.  Paradise 
	     servers can be configured to many settings, including that
	     simulating a regular bronco server.  The source code release
	     includes an expanded galaxy, impluse/warp differentiation, 
	     suns, extra ships, and more.


netrek.cis.ufl.edu		128.227.224.254	2592	UFL, RSA only.

defiant.theo-physik.uni-kiel.de	134.245.67.1	2592	Germany.

aedile.icaen.uiowa.edu		128.255.17.38	2592	Iowa.

eelpout.micro.umn.edu		128.101.245.6	2592	UMN.

fantasia.eng.clemson.edu	130.127.152.72	2592	Clemson

netrek.skypoint.net		199.86.32.2	2592	?

netrek.skypoint.net		199.86.32.2	3333	No warp.

fred.cs.city.au.uk		138.40.91.2	2592	UK.

netrek.engg.ksu.edu		129.130.41.86	2592	Kansas State.

europa.informatik.uni-frankfurt.de  141.2.5.3	2592	Germany.

i12.msi.umn.edu			128.101.27.62	2592	Minnisota.

cassius.cs.uiuc.edu 		128.174.240.3	2592	Illinois.


Subject: Hockey servers (short listing)

Description: Netrek hockey mode.  Most fun you can have on ice without
             actually being on it.  Teams of players try to shoot (pressor)
             the Puck (a robot) into the other teams goal.


hockey.ksu.edu 			129.130.6.10	2592	Kansas State.

rosebud.umiacs.umd.edu		128.8.120.103	6666	U Maryland.

hot.caltech.edu			131.215.9.49	2592	Caltech.


Subject: KSU Chaos servers (short listing)

Description: These are servers based on the original KSU Chaos mods.  They
	     include galaxy-class ships, high refuel rates, free plasmas,
	     wrap-around walls, and much more.  

netrek.engg.ksu.edu		129.130.41.86	5855	Kansas State.


Subject: Dogfight servers (short listing)

Description: These are servers designed with dogfighting tournaments in mind.
	     The code is still rather buggy.

rosebud.umiacs.umd.edu		128.8.120.103	3333	U Maryland.


Comments and server configurations:

NOTE: It is an excellent idea to read the MOTD when you first log
on to a server (use 'f' and 'b' to move forwards and backwards through
the news).


Subject: Vanilla servers in America (long listing)

bronco.ece.cmu.edu:
Server source: Guzzler source.
UDP: version 1.0.
Hours: 5PM-9AM Eastern time Monday-Friday, all day on weekends.
T-mode: 5 on 5.
Maintainer: netrek at bronco.ece.cmu.edu
Comments: This server is down most of the time.

calvin.usc.edu
Server source: Scam source.
Hours: Open 24 hours.
Cyborgs: Never allowed, blessed binary required.
Maintainer: hadley at ics.uci.edu
Comments: Jacked-up AS's and transwarp.

netrek.cs.mcgill.ca
Server source: Guzzler source.
Hours: Open 24 hours.
Cyborgs: Never allowed, RSA client required.
Maintainer: kent at cs.mcgill.ca
Comments: This server requires RSA.  Suppots short packets, ping reporting.
	Clue checking 3PM-10PM weekdays, all day on weekends.

garnet.cdf.toronto.edu
Server source: Guzzler source.
Hours: Open 24 hours.

wormhole.ecst.csuchico.edu
Server source: Guzzler source.
Hours: Open 24 hours.
Cyborgs: Never allowed, RSA client required.
Maintainer: trown at ecst.csuchico.edu.
Comments: This server requires RSA.  Now has the calvin database.
	  Supports short packets, ping reporting, and RCD.
	  Clue checking 5PM-6AM weeknights, all day on weekends.

bigbang.astro.indiana.edu
Server source: Guzzler source.
Hours: Open 24 hours.
Cyborgs: Never allowed, RSA client required.
Maintainer: netrek at bigbang.astro.indiana.edu
Comments: Playerlist on port 2591.  finger netrek at bigbang.astro.indiana.edu
	  gives a high score list.  Ship stats modified to weaken cruisers.

rosebud.umiacs.umd.edu (2592)
Server source: Guzzler source.
Hours: Open 24 hours.
Maintainer: ctso at umiacs.umd.edu
Comments: Has the UML player database.  Note port number; port 6666 has 
	  a hockey server.

mean.mu.caltech.edu
Server source: Guzzler source.
Hours: Open 5PM to 8AM weekdays, all day on weekends.
Cyborgs: Encouraged, no binary checking.
Maintainer: kantner at hot.caltech.edu

tolstoy.afit.af.mil: This server is apparently down.

smurfland.cit.buffalo.edu: This server is apparently down.

bradbert.ugcs.caltech.edu
Server source: Guzzler source.
Hours: Open 24 hours.


Subject: Vanilla servers outside America (long listing)

peanuts.informatik.uni-tuebingen.de
Server source: Bronco source.
Hours: 5PM-7AM Monday-Friday, all day on weekends.
Cyborgs: Allowed.
Maintainer: jw at peanuts.informatik.uni-tuebingen.de.

bayes.ibr.cs.tu-bs.de
Server source: Bronco source.
Hours: Open 24 hours.
Cyborgs: Always allowed. 
Maintainer: schrei at ibr.cs.tu-bs.de.

legend.cma.fr
Server source: Guzzler source.
Hours: Open 24 hours.
Cyborgs: Never allowed, RSA client required.
Maintainer: jmt at cma.cma.fr.
Comments: Requires message reading.
	  Supports short packets and RC_DISTRESS.

netrek.chemietechnik.uni-dortmund.de
Server source: Bronco source.
Hours: Closed 8 AM to noon Monday-Friday.
Maintainer: hw at plato.Chemietechnik.Uni-Dortmund.DE.
Comments: No Iggy.

ghost.dsi.unimi.it
Server source: Bronco source.
Hours: Open 5PM-10AM Monday-Friday, all day on weekends.
Cyborgs: Always allowed.
Maintainer: carlo at ghost.dsi.unimi.it.

netrek.cs.bham.ac.uk
Server source: Bronco source.
Hours: 5PM to 8AM Monday-Friday, all day weekends.
T-mode: 4 on 4.
Cyborgs: Never allowed, RSA client required.
Maintainer: A.Willams at cs.bham.ac.uk.

megalag.risc.uni-linz.ac.at
Server source: Guzzler source.
Hours: Open 24 hours.
T-mode: 3 on 3.
Cyborgs: Never allowed, RSA client required.
Maintainer: siegl at risc.uni-linz.ac.at.
Comments: Usually runs in base practice server mode.  Suppots short packets  
	  and RC_DISTRESS.  Replaces melmac.

fisher.psy.vu.nl
Server source: Guzzler source.
Hours: Open 24 hours.
T-mode: 4 on 4.
Cyborgs: Never allowed, RSA client required.
Maintainer: rob at psy.vu.nl.
Comments: Clue hours 5PM to 8PM local time.

gilean.solace.mh.se
Server source: Guzzler source.
Hours: Open 24 hours.
T-mode: 3 on 3.
Cyborgs: Never allowed, RSA client required.
Maintainer: netrek at solace.mh.se.
Comments: Experimental server based on Sturgeon mods.
This server was down at the time of testing.


Subject: Sturgeon-style upgrade servers (long listing)

rosebud.umiacs.umd.edu: This server is apparently down.

netrek.engg.ksu.edu: This server is apparently down.


Subject: Paradise-style servers (long listing)

netrek.cis.ufl.edu
Server source: Paradise source.
Hours: Open 24 hours.
Maintainer: thoth at aviator.cis.ufl.edu.
Comments: This is Arctica, another branch of the Paradise line.

defiant.theo-physik.uni-kiel.de
Server source: Paradise source.
Hours: Open 24 hours.
Maintainer: trek at theo-physik.uni-kiel.de.
Comments: Good round-trip times.  Replaces ravel.

aedile.icaen.uiowa.edu
Server source: Paradise source.
Hours: Open 24 hours.

eelpout.micro.umn.edu: This server is apparently down.

fantasia.eng.clemson.edu
Server source: Paradise source.
Hours: Open 24 hours.
Maintainer: ttatum at harley.eng.clemson.edu, dcrane at eng.clemson.edu.
This server was down at the time of testing.

netrek.skypoint.net: This server is apparently down.

fred.cs.city.ac.uk
Server source: Paradise source.
Hours: Open 24 hours.
Maintainer: deorth at city.ac.uk.
This server was down at the time of testing.

netrek.engg.ksu.edu (2592)
Server source: Paradise source.
Hours: Open 24 hours.
Maintainer: tundra at cis.ksu.edu.
Comments: This is Armageddon, a mixture of Paradise and KSU Chaos.

europa.informatik.uni-frankfurt.de
Server source: Paradise source.
Hours: Open 24 hours.

i12.msi.umn.edu
Server source: Paradise source.
Hours: Open 24 hours.
This server was down at the time of testing.

cassius.cs.uiuc.edu 
Server source: Paradise source.
Hours: Open 24 hours.


Subject: Hockey servers (long listing)

hockey.ksu.edu
Server source: Guzzler source, hockey mode.
Hours: Open 24 hours.
Cyborgs: Allowed.
Maintainer: bav at hobbes.ksu.ksu.edu.
Comments: Hockey mode all the time, send 'help' to player g for details.

rosebud.umiacs.umd.edu (6666)
Server source: Guzzler source, hockey mode.
Hours: Open 24 hours.
Cyborgs: Never allowed, RSA client required.
Maintainer: ctso at umiacs.umd.edu.
Comments: Note port number, normal netrek server runing on port 2592.

hot.caltech.edu
Server source: Guzzler source, hockey mode.
Hours: Open 5PM-8AM weekdays, all day weekends.
Maintainer: kantner at hot.caltech.edu

rsls6.sprachlit.uni-regensburg.de
Server source: Guzzler source, hockey mode.
Hours: Open 24 hours.


Subject: KSU Chaos servers (long listing)

netrek.engg.ksu.edu (2592)
Server source: KSU Chaos.
Hours: Open 24 hours.
Cyborgs: Allowed.
Maintainer: greyhelm at engg.ksu.edu.
Comments: KSU Chaos with grit-style robots.


Subject: Dogfight servers (long listing)

rosebud.umiacs.umd.edu (3333)
Server source: Guzzler source, dogfight mode.
Hours: Open 24 hours.
Cyborgs: Not allowed, RSA client required.
Maintainer: ctso at umiacs.umd.edu.
Comments: Note port number; normal netrek on port 2592, hockey on port 6666.
This server was down at the time of testing.

--- 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 is 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 */
#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);
  }
  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) {
    type = (int) *buf;
    if (type < 1 || type > NUM_PACKETS) {
      log_msg("rcvd bad packet %d (0x%x) from %s (%d)", type, type,
	      servers[busy].hostname, servers[busy].port);
      return (-2);
    }
    size = handlers[type].size;
    if (size <= 0) {
      /* got bogus size, headed for infinite spin */
      log_msg("zero size in scan_packets from %s",
	      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 = ntohs(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;
      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
 *
 * $Id: disp_info.c,v 1.1 2006/02/14 06:43:11 unbelver Exp $
 *
 */
#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: main.c ---
/*
 * main.c - args, configuration, logging
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 * 
 * $Id: main.c,v 1.1 2006/02/14 06:43:11 unbelver Exp $
 * 
 */
#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;
  servers[server_count].solicit = FALSE;
  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) {
	memcpy(sp, &srvbuf, 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);

  SIGNAL(SIGCHLD, sig_child);
}


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


--- NEW FILE: disp_web.c ---
/*
 * disp_web.c - output in fancy new format
 *
 * MetaServerII
 * Copyright (c) 1993 by Andy McFadden
 * 
 * $Id: disp_web.c,v 1.1 2006/02/14 06:43:11 unbelver Exp $
 * 
 */
#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];

  if(up->buf)
    {
      free(up->buf);
      up->buf = NULL;
    }

  up->data_size = up->buf_size = up->pos = 0;

  now = time(0);

  /* print server 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");

  /* print page */
  Uprintf(idx, "\
<html><title>MetaServer II Server List</title>\n\
<body><center><h1>MetaServer II Server List</h1></center>\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);
  Uprintf(idx, "<p>Link to <a href=\"http://metaserver.netrek.org:1080/\">US metaserver</a><p>\n");
  Uprintf(idx, "<p>Link to <a href=\"http://metaserver.eu.netrek.org:1080/\">European metaserver</a></body></html>\n");

  return(0);
}

--- NEW FILE: metaII_info ---

Welcome to MetaServerII's News & Information port!


METASERVER-II SERVICES:

Port 3520 has the "look & feel" of the original METASERVER.  It's fully
    compatible with programs like XNetrekM.
Port 3521 is a new and improved format.  It incorporates some of the
    suggestions seen on rec.games.netrek.
Port 3522 provides verbose output, giving player lists (when available) as
    well as all of the information available from the above two ports.
Port 3523 is what you are reading now.
Port 3524 is the server list posted to rec.games.netrek once per month.  It
    has a complete list of known public servers, with some detailed info
    about each.
Port 3525 is the MetaServerII config file.  Server admins may want to look
    at this to see if/how their server is listed.
Port 3530 is the list of RSA public keys.  This is only useful for server
    administrators; you don't need this just to play.

You can save a copy of the info with:

	telnet metaserver.netrek.org <port> > file

(replace <port> with one of the numbers above.)


EXPLANATION OF "FLAGS":

 T  Means that at some point in the lifetime of the MetaServerII process there
    were >= 8 players on this server ("T-mode").
 D  Means that this server hasn't responded since MetaServerII started
    watching ("always Dead").
 R  Means that this server supports (and may REQUIRE) RSA validation.

The server types are as follows.  These are broad categories, indicating a
general "feel" of play.
 B  Bronco - standard scam/bronco sources.
 P  Paradise - paradise-style server.
 S  Sturgeon - upgrades and weird weapons, planets, etc.
 C  Chaos - lots of really whacked out mods.
 H  Hockey - works like hockey.
 F  Dogfighting - dogfighting server.


I CAN'T SEEM TO GET THE ENTIRE LISTING

Try sending it to a file:
("telnet metaserver.netrek.org 3521 > /tmp/mlist; cat /tmp/mlist").
If that doesn't work, I have a short (70 line) program which WILL work.
Send mail and I'll forward it.


MASS RETRIEVALS:

DO NOT run scripts or MS-II clients that automatically read in every port
every time.  If everybody ran one of these the extra load would be difficult
to bear.  Ask for all the information you want, but don't load up the network
with requests for information you're not interested in (the clients usually
get both ports 3520 and 3521, which contains essentially identical
information, and the faq from port 3524, which most people don't need to read
more than once or twice).

Repeat offenders will be added to the "exclude" list, which blocks a host
from all access.  Currently ALL of richsunN.gatech.edu is blocked for
exactly this reason.


WHERE CAN I GET MORE INFORMATION?

Try rec.games.netrek.  Use "rn", "readnews", "vnews", "nn", "notes", or
whatever else you have to read Usenet news.  If you can't quite figure it
out, ask someone on your system.

There's a Frequently Asked Questions list for rec.games.netrek.  I would
include it here except that you really ought to be reading the newsgroup
anyway.