swr2.0/
swr2.0/area/
swr2.0/boards/
swr2.0/clans/
swr2.0/doc/
swr2.0/planets/
swr2.0/spacecraft/
/***************************************************************************
 * Mud Telopt Handler 1.3 by Igor van den Hoven.               06 Apr 2009 *
 ***************************************************************************/

#include <string.h>
#include <stdarg.h>

#include "mud.h"
#include "telnet.h"

/* Externals */
bool write_to_descriptor( DESCRIPTOR_DATA *d, const char *txt, int length );
size_t skill_package_table_size( void );
extern int top_area;
extern int top_help;
extern int top_mob_index;
extern int top_obj_index;
extern int top_room;

#define TELOPT_DEBUG 1
#define ANNOUNCE_WILL   BV01
#define ANNOUNCE_DO     BV02

const char iac_will_ttype[]       = { IAC, WILL, TELOPT_TTYPE, 0 };
const char iac_sb_ttype_is[]      = { IAC, SB,   TELOPT_TTYPE, ENV_IS, 0 };
const char iac_sb_naws[]          = { IAC, SB,   TELOPT_NAWS, 0 };
const char iac_will_new_environ[] = { IAC, WILL, TELOPT_NEW_ENVIRON, 0 };
const char iac_sb_new_environ[]   = { IAC, SB,   TELOPT_NEW_ENVIRON, ENV_IS,0 };
const char iac_do_mssp[]          = { IAC, DO,   TELOPT_MSSP, 0 };
const char iac_do_mccp[]          = { IAC, DO,   TELOPT_MCCP, 0 };
const char iac_dont_mccp[]        = { IAC, DONT, TELOPT_MCCP, 0 };

/* Exported functions */
int translate_telopts( DESCRIPTOR_DATA *d, char *src, int srclen, char *out );
void announce_support( DESCRIPTOR_DATA *d );
int start_compress( DESCRIPTOR_DATA *d );
void end_compress( DESCRIPTOR_DATA *d );
void write_compressed( DESCRIPTOR_DATA *d );
void send_echo_on( DESCRIPTOR_DATA *d );
void send_echo_off( DESCRIPTOR_DATA *d );

/* Private functions */
static void debug_telopts( DESCRIPTOR_DATA *d, unsigned char *src,
			   int srclen );
static int process_will_ttype( DESCRIPTOR_DATA *d, unsigned char *src,
			       int srclen );
static int process_sb_ttype_is( DESCRIPTOR_DATA *d, unsigned char *src,
				int srclen );
static int process_sb_naws( DESCRIPTOR_DATA *d, unsigned char *src,
			    int srclen );
static int process_will_new_environ( DESCRIPTOR_DATA *d, unsigned char *src,
				     int srclen );
static int process_sb_new_environ( DESCRIPTOR_DATA *d, unsigned char *src,
				   int srclen );
static int process_do_mssp( DESCRIPTOR_DATA *d, unsigned char *src,
			    int srclen );
static int process_do_mccp( DESCRIPTOR_DATA *d, unsigned char *src,
			    int srclen );
static int process_dont_mccp( DESCRIPTOR_DATA *d, unsigned char *src,
			      int srclen );
static int skip_sb( DESCRIPTOR_DATA *d, unsigned char *src, int srclen );
static void descriptor_printf( DESCRIPTOR_DATA *d, const char *fmt, ...);
static void process_compressed( DESCRIPTOR_DATA *d );

struct telopt_type
{
  int      size;
  const char   * code;
  int   (* func) (DESCRIPTOR_DATA *d, unsigned char *src, int srclen);
};

const struct telopt_type telopt_table [] =
{
  { 3, iac_will_ttype,       &process_will_ttype},
  { 4, iac_sb_ttype_is,      &process_sb_ttype_is},

  { 3, iac_sb_naws,          &process_sb_naws},

  { 3, iac_will_new_environ, &process_will_new_environ},
  { 4, iac_sb_new_environ,   &process_sb_new_environ},

  { 3, iac_do_mssp,          &process_do_mssp},

  { 3, iac_do_mccp,          &process_do_mccp},
  { 3, iac_dont_mccp,        &process_dont_mccp},

  { 0, NULL,                 NULL}
};

const char *telcmds[] =
  {
    "EOF",   "SUSP",  "ABORT", "EOR",   "SE",
    "NOP",   "DMARK", "BRK",   "IP",    "AO",
    "AYT",   "EC",    "EL",    "GA",    "SB",
    "WILL",  "WONT",  "DO",    "DONT",  "IAC"
  };

struct telnet_type
{
  const char      *name;
  int       flags;
};

struct telnet_type telnet_table[] =
  {
    {    "BINARY",                0 },
    {    "ECHO",                  0 }, /* Local echo */
    {    "RCP",                   0 },
    {    "SUPPRESS GA",           0 }, /* Character mode */
    {    "NAME",                  0 },
    {    "STATUS",                0 },
    {    "TIMING MARK",           0 },
    {    "RCTE",                  0 },
    {    "NAOL",                  0 },
    {    "NAOP",                  0 },
    {    "NAORCD",                0 },
    {    "NAOHTS",                0 },
    {    "NAOHTD",                0 },
    {    "NAOFFD",                0 },
    {    "NAOVTS",                0 },
    {    "NAOVTD",                0 },
    {    "NAOLFD",                0 },
    {    "EXTEND ASCII",          0 },
    {    "LOGOUT",                0 },
    {    "BYTE MACRO",            0 },
    {    "DATA ENTRY TERML",      0 },
    {    "SUPDUP",                0 },
    {    "SUPDUP OUTPUT",         0 },
    {    "SEND LOCATION",         0 },
    {    "TERMINAL TYPE",         ANNOUNCE_DO }, /* Terminal Type */
    {    "EOR",                   0 }, /* End of Record */
    {    "TACACS UID",            0 },
    {    "OUTPUT MARKING",        0 },
    {    "TTYLOC",                0 },
    {    "3270 REGIME",           0 },
    {    "X.3 PAD",               0 },
    {    "NAWS",                  ANNOUNCE_DO }, /* Negotiate About Window Size */
    {    "TSPEED",                0 },
    {    "LFLOW",                 0 },
    {    "LINEMODE",              0 },
    {    "XDISPLOC",              0 },
    {    "OLD_ENVIRON",           0 },
    {    "AUTH",                  0 },
    {    "ENCRYPT",               0 },
    {    "NEW_ENVIRON",           ANNOUNCE_DO }, /* Used to detect Win32 telnet */
    {    "TN3270E",               0 },
    {    "XAUTH",                 0 },
    {    "CHARSET",               0 },
    {    "RSP",                   0 },
    {    "COM PORT",              0 },
    {    "SLE",                   0 },
    {    "STARTTLS",              0 },
    {    "KERMIT",                0 },
    {    "SEND-URL",              0 },
    {    "FORWARD_X",             0 },
    {    "50",                    0 },
    {    "51",                    0 },
    {    "52",                    0 },
    {    "53",                    0 },
    {    "54",                    0 },
    {    "55",                    0 },
    {    "56",                    0 },
    {    "57",                    0 },
    {    "58",                    0 },
    {    "59",                    0 },
    {    "60",                    0 },
    {    "61",                    0 },
    {    "62",                    0 },
    {    "63",                    0 },
    {    "64",                    0 },
    {    "65",                    0 },
    {    "66",                    0 },
    {    "67",                    0 },
    {    "68",                    0 },
    {    "69",                    0 },
    {    "MSSP",                  ANNOUNCE_WILL },
    {    "71",                    0 },
    {    "72",                    0 },
    {    "73",                    0 },
    {    "74",                    0 },
    {    "75",                    0 },
    {    "76",                    0 },
    {    "77",                    0 },
    {    "78",                    0 },
    {    "79",                    0 },
    {    "80",                    0 },
    {    "81",                    0 },
    {    "82",                    0 },
    {    "83",                    0 },
    {    "84",                    0 },
    {    "MCCP1",                 0 }, /* Obsolete - don't use. */
    {    "MCCP2",                 ANNOUNCE_WILL }, /* Mud Client Compression Protocol */
    {    "87",                    0 },
    {    "88",                    0 },
    {    "89",                    0 },
    {    "MSP",                   0 },
    {    "MXP",                   0 },
    {    "MSP2",                  0 }, /* Unadopted */
    {    "ZMP",                   0 }, /* Unadopted */
    {    "94",                    0 },
    {    "95",                    0 },
    {    "96",                    0 },
    {    "97",                    0 },
    {    "98",                    0 },
    {    "99",                    0 },
    {    "100",                   0 },
    {    "101",                   0 },
    {    "102",                   0 }, /* Aardwolf */
    {    "103",                   0 },
    {    "104",                   0 },
    {    "105",                   0 },
    {    "106",                   0 },
    {    "107",                   0 },
    {    "108",                   0 },
    {    "109",                   0 },
    {    "110",                   0 },
    {    "111",                   0 },
    {    "112",                   0 },
    {    "113",                   0 },
    {    "114",                   0 },
    {    "115",                   0 },
    {    "116",                   0 },
    {    "117",                   0 },
    {    "118",                   0 },
    {    "119",                   0 },
    {    "120",                   0 },
    {    "121",                   0 },
    {    "122",                   0 },
    {    "123",                   0 },
    {    "124",                   0 },
    {    "125",                   0 },
    {    "126",                   0 },
    {    "127",                   0 },
    {    "128",                   0 },
    {    "129",                   0 },
    {    "130",                   0 },
    {    "131",                   0 },
    {    "132",                   0 },
    {    "133",                   0 },
    {    "134",                   0 },
    {    "135",                   0 },
    {    "136",                   0 },
    {    "137",                   0 },
    {    "138",                   0 },
    {    "139",                   0 },
    {    "140",                   0 },
    {    "141",                   0 },
    {    "142",                   0 },
    {    "143",                   0 },
    {    "144",                   0 },
    {    "145",                   0 },
    {    "146",                   0 },
    {    "147",                   0 },
    {    "148",                   0 },
    {    "149",                   0 },
    {    "150",                   0 },
    {    "151",                   0 },
    {    "152",                   0 },
    {    "153",                   0 },
    {    "154",                   0 },
    {    "155",                   0 },
    {    "156",                   0 },
    {    "157",                   0 },
    {    "158",                   0 },
    {    "159",                   0 },
    {    "160",                   0 },
    {    "161",                   0 },
    {    "162",                   0 },
    {    "163",                   0 },
    {    "164",                   0 },
    {    "165",                   0 },
    {    "166",                   0 },
    {    "167",                   0 },
    {    "168",                   0 },
    {    "169",                   0 },
    {    "170",                   0 },
    {    "171",                   0 },
    {    "172",                   0 },
    {    "173",                   0 },
    {    "174",                   0 },
    {    "175",                   0 },
    {    "176",                   0 },
    {    "177",                   0 },
    {    "178",                   0 },
    {    "179",                   0 },
    {    "180",                   0 },
    {    "181",                   0 },
    {    "182",                   0 },
    {    "183",                   0 },
    {    "184",                   0 },
    {    "185",                   0 },
    {    "186",                   0 },
    {    "187",                   0 },
    {    "188",                   0 },
    {    "189",                   0 },
    {    "190",                   0 },
    {    "191",                   0 },
    {    "192",                   0 },
    {    "193",                   0 },
    {    "194",                   0 },
    {    "195",                   0 },
    {    "196",                   0 },
    {    "197",                   0 },
    {    "198",                   0 },
    {    "199",                   0 },
    {    "200",                   0 }, /* Achaea */
    {    "201",                   0 },
    {    "202",                   0 },
    {    "203",                   0 },
    {    "204",                   0 },
    {    "205",                   0 },
    {    "206",                   0 },
    {    "207",                   0 },
    {    "208",                   0 },
    {    "209",                   0 },
    {    "210",                   0 },
    {    "211",                   0 },
    {    "212",                   0 },
    {    "213",                   0 },
    {    "214",                   0 },
    {    "215",                   0 },
    {    "216",                   0 },
    {    "217",                   0 },
    {    "218",                   0 },
    {    "219",                   0 },
    {    "220",                   0 },
    {    "221",                   0 },
    {    "222",                   0 },
    {    "223",                   0 },
    {    "224",                   0 },
    {    "225",                   0 },
    {    "226",                   0 },
    {    "227",                   0 },
    {    "228",                   0 },
    {    "229",                   0 },
    {    "230",                   0 },
    {    "231",                   0 },
    {    "232",                   0 },
    {    "233",                   0 },
    {    "234",                   0 },
    {    "235",                   0 },
    {    "236",                   0 },
    {    "237",                   0 },
    {    "238",                   0 },
    {    "239",                   0 },
    {    "240",                   0 },
    {    "241",                   0 },
    {    "242",                   0 },
    {    "243",                   0 },
    {    "244",                   0 },
    {    "245",                   0 },
    {    "246",                   0 },
    {    "247",                   0 },
    {    "248",                   0 },
    {    "249",                   0 },
    {    "250",                   0 },
    {    "251",                   0 },
    {    "252",                   0 },
    {    "253",                   0 },
    {    "254",                   0 },
    {    "255",                   0 }
  };

/*
  Call this to announce support for telopts marked as such in tables.c
*/

void announce_support( DESCRIPTOR_DATA *d)
{
  int i = 0;

  for (i = 0 ; i < 255 ; i++)
    {
      if (telnet_table[i].flags)
	{
	  if (IS_SET(telnet_table[i].flags, ANNOUNCE_WILL))
	    {
	      descriptor_printf(d, "%c%c%c", IAC, WILL, i);
	    }

	  if (IS_SET(telnet_table[i].flags, ANNOUNCE_DO))
	    {
	      descriptor_printf(d, "%c%c%c", IAC, DO, i);
	    }
	}
    }
}

/*
  This is the main routine that strips out and handles telopt negotiations.
  It also deals with \r and \0 so commands are separated by a single \n.
*/

int translate_telopts(DESCRIPTOR_DATA *d, char *src, int srclen, char *out)
{
  int cnt = 0, skip = 0;
  unsigned char *pti = NULL, *pto = NULL;

  pti = (unsigned char*) src;
  pto = (unsigned char*) out;

  if (d->teltop)
    {
      if (d->teltop + srclen + 1 < MAX_INPUT_LENGTH)
	{
	  memcpy(d->telbuf + d->teltop, src, srclen);
	  srclen += d->teltop;
	  pti = (unsigned char*) d->telbuf;
	}
      d->teltop = 0;
    }

  while (srclen > 0)
    {
      switch (*pti)
	{
	case IAC:
	  skip = 2;
	  debug_telopts(d, pti, srclen);

	  for (cnt = 0 ; telopt_table[cnt].code ; cnt++)
	    {
	      if (srclen < telopt_table[cnt].size)
		{
		  if (!memcmp(pti, telopt_table[cnt].code, srclen))
		    {
		      skip = telopt_table[cnt].size;
		      break;
		    }
		}
	      else
		{
		  if (!memcmp(pti, telopt_table[cnt].code, telopt_table[cnt].size))
		    {
		      skip = telopt_table[cnt].func(d, pti, srclen);
		      break;
		    }
		}
	    }

	  if (telopt_table[cnt].code == NULL && srclen > 1)
	    {
	      switch (pti[1])
		{
		case WILL:
		case DO:
		case WONT:
		case DONT:
		  skip = 3;
		  break;

		case SB:
		  skip = skip_sb(d, pti, srclen);
		  break;

		case IAC:
		  *pto++ = *pti++;
		  srclen--;
		  skip = 1;
		  break;

		default:
		  if (TELCMD_OK(pti[1]))
		    {
		      skip = 2;
		    }
		  else
		    {
		      skip = 1;
		    }
		  break;
		}
	    }

	  if (skip <= srclen)
	    {
	      pti += skip;
	      srclen -= skip;
	    }
	  else
	    {
	      memcpy(d->telbuf, pti, srclen);
	      d->teltop = srclen;

	      *pto = 0;
	      return strlen(out);
	    }
	  break;

	case '\r':
	  if (srclen > 1 && pti[1] == '\0')
	    {
	      *pto++ = '\n';
	    }
	  pti++;
	  srclen--;
	  break;

	case '\0':
	  pti++;
	  srclen--;
	  break;

	default:
	  *pto++ = *pti++;
	  srclen--;
	  break;
	}
    }
  *pto = 0;

  return strlen(out);
}

void debug_telopts( DESCRIPTOR_DATA *d, unsigned char *src, int srclen )
{
#if SWR2_DEBUG_TELNET == 1
  if (srclen > 1 && TELOPT_DEBUG)
    {
      switch(src[1])
	{
	case IAC:
	  log_printf("D%d@%s RCVD IAC IAC", d->descriptor, d->host);
	  break;

	case DO:
	case DONT:
	case WILL:
	case WONT:
	case SB:
	  if (srclen > 2)
	    {
	      log_printf("D%d@%s RCVD IAC %s %s", d->descriptor, d->host, TELCMD(src[1]), TELOPT(src[2]));
	    }
	  else
	    {
	      log_printf("D%d@%s RCVD IAC %s ?", d->descriptor, d->host, TELCMD(src[1]));
	    }
	  break;

	default:
	  if (TELCMD_OK(src[1]))
	    {
	      log_printf("D%d@%s RCVD IAC %s", d->descriptor, d->host, TELCMD(src[1]));
	    }
	  else
	    {
	      log_printf("D%d@%s RCVD IAC %d", d->descriptor, d->host, src[1]);
	    }
	  break;
	}
    }
  else
    {
      log_printf("D%d@%s RCVD IAC ?", d->descriptor, d->host);
    }
#endif
}

/*
  Send to client to have it disable local echo
*/
void send_echo_off( DESCRIPTOR_DATA *d )
{
  descriptor_printf(d, "%c%c%c", IAC, WILL, TELOPT_ECHO);
}

/*
  Send to client to have it enable local echo
*/
void send_echo_on( DESCRIPTOR_DATA *d )
{
  descriptor_printf(d, "%c%c%c", IAC, WONT, TELOPT_ECHO);
}

/*
  Terminal Type negotiation - make sure d->terminal_type is initialized.
*/
int process_will_ttype( DESCRIPTOR_DATA *d, unsigned char *src, int srclen )
{
  if (*d->terminal_type == 0)
    {
      descriptor_printf(d, "%c%c%c%c%c%c", IAC, SB, TELOPT_TTYPE, ENV_SEND, IAC, SE);
    }
  return 3;
}

int process_sb_ttype_is( DESCRIPTOR_DATA *d, unsigned char *src, int srclen )
{
  char val[MAX_INPUT_LENGTH];
  char *pto = val;
  int i = 0;

  if (skip_sb(d, src, srclen) > srclen)
    {
      return srclen + 1;
    }

  for (i = 4 ; i < srclen && src[i] != SE ; i++)
    {
      switch (src[i])
	{
	default:			
	  *pto++ = src[i];
	  break;

	case IAC:
	  *pto = 0;
	  STRFREE( d->terminal_type );
	  d->terminal_type = STRALLOC( val );
	  break;
	}
    }
  return i + 1;
}

/*
  NAWS: Negotiate About Window Size
*/
int process_sb_naws( DESCRIPTOR_DATA *d, unsigned char *src, int srclen )
{
  int i, j;

  d->cols = d->rows = 0;

  if (skip_sb(d, src, srclen) > srclen)
    {
      return srclen + 1;
    }

  for (i = 3, j = 0 ; i < srclen && j < 4 ; i++, j++)
    {
      switch (j)
	{
	case 0:
	  d->cols += (src[i] == IAC) ? src[i++] * 256 : src[i] * 256;
	  break;
	case 1:
	  d->cols += (src[i] == IAC) ? src[i++] : src[i];
	  break;
	case 2:
	  d->rows += (src[i] == IAC) ? src[i++] * 256 : src[i] * 256;
	  break;
	case 3:
	  d->rows += (src[i] == IAC) ? src[i++] : src[i];
	  break;
	}
    }

  return skip_sb(d, src, srclen);
}

/*
  NEW ENVIRON, used here to discover Windows telnet.
*/
int process_will_new_environ( DESCRIPTOR_DATA *d, unsigned char *src,
			      int srclen )
{
  descriptor_printf(d, "%c%c%c%c%c%s%c%c", IAC, SB, TELOPT_NEW_ENVIRON,
		    ENV_SEND, ENV_VAR, "SYSTEMTYPE", IAC, SE);

  return 3;
}

int process_sb_new_environ( DESCRIPTOR_DATA *d, unsigned char *src,
			    int srclen )
{
  char var[MAX_INPUT_LENGTH], val[MAX_INPUT_LENGTH];
  char *pto = NULL;
  int i = 0;

  if (skip_sb(d, src, srclen) > srclen)
    {
      return srclen + 1;
    }

  var[0] = val[0] = 0;
  i = 4;

  while (i < srclen && src[i] != SE)
    {
      switch (src[i])
	{
	case ENV_VAR:
	case ENV_USR:
	  i++;
	  pto = var;

	  while (i < srclen && src[i] >= 32 && src[i] != IAC)
	    {
	      *pto++ = src[i++];
	    }
	  *pto = 0;
	  break;

	case ENV_VAL:
	  i++;
	  pto = val;

	  while (i < srclen && src[i] >= 32 && src[i] != IAC)
	    {
	      *pto++ = src[i++];
	    }
	  *pto = 0;

	  if (!str_cmp(var, "SYSTEMTYPE") && !str_cmp(val, "WIN32"))
	    {
	      STRFREE( d->terminal_type );
	      d->terminal_type = STRALLOC( "WIN32" );
	    }
	  break;

	default:
	  i++;
	  break;
	}
    }
  return i + 1;
}

/*
 * Data for MUD Server Status Protocol (MSSP).
 *
 * Please try following the established conventions as
 * outlined here: http://www.mudbytes.net/index.php?a=articles&s=MSSP_Fields
 */
static const char *MSSP_FIELD_MUD_NAME        = "SWR2 Refactor Devel";
static const char *MSSP_FIELD_HOSTNAME        = "swr2.totj.net";
static const char *MSSP_FIELD_IP              = "97.107.139.29";
static const char *MSSP_FIELD_CODEBASE        = "SWR2 Refactor";
static const char *MSSP_FIELD_CONTACT         = "kai@swr2.totj.net";
static const char *MSSP_FIELD_CREATED         = "2009";
static const char *MSSP_FIELD_ICON            = "";
static const char *MSSP_FIELD_LANGUAGE        = "English";
static const char *MSSP_FIELD_LOCATION        = "United States";
static const char *MSSP_FIELD_MINIMUM_AGE     = "13";
static const char *MSSP_FIELD_WEBSITE         = "http://swr2.totj.net";
static const char *MSSP_FIELD_FAMILY          = "DikuMUD";
static const char *MSSP_FIELD_GENRE           = "Science Fiction";
static const char *MSSP_FIELD_SUBGENRE        = "None";
static const char *MSSP_FIELD_GAMEPLAY        = "Player versus Player";
static const char *MSSP_FIELD_GAMESYSTEM      = "Custom";
static const char *MSSP_FIELD_INTERMUD        = "IMC2";
static const char *MSSP_FIELD_STATUS          = "Open Beta";
static const int   MSSP_FIELD_LEVELS          = 0;
static const int   MSSP_FIELD_RACES           = 1;
static const int   MSSP_FIELD_ANSI            = 1;
static const int   MSSP_FIELD_MCCP            = 1;
static const int   MSSP_FIELD_MCP             = 0;
static const int   MSSP_FIELD_MSP             = 0;
static const int   MSSP_FIELD_MXP             = 0;
static const int   MSSP_FIELD_PUEBLO          = 0;
static const int   MSSP_FIELD_VT100           = 0;
static const int   MSSP_FIELD_XTERM256        = 0;
static const int   MSSP_FIELD_PAY_TO_PLAY     = 0;
static const int   MSSP_FIELD_PAY_FOR_PERKS   = 0;
static const int   MSSP_FIELD_HIRING_BUILDERS = 0;
static const int   MSSP_FIELD_HIRING_CODERS   = 0;

extern int port;

/*
  MSSP: Mud Server Status Protocol
*/
int process_do_mssp( DESCRIPTOR_DATA *d, unsigned char *src, int srclen )
{
  char buffer[MAX_STRING_LENGTH] = { 0 };

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "NAME",
              MSSP_VAL, MSSP_FIELD_MUD_NAME);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PLAYERS",
	      MSSP_VAL, num_descriptors);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "UPTIME",
	      MSSP_VAL, boot_time);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "HOSTNAME",
	      MSSP_VAL, MSSP_FIELD_HOSTNAME);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PORT",
              MSSP_VAL, port);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CODEBASE",
	      MSSP_VAL, MSSP_FIELD_CODEBASE);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CONTACT",
	      MSSP_VAL, MSSP_FIELD_CONTACT);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CREATED",
	      MSSP_VAL, MSSP_FIELD_CREATED);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "ICON",
              MSSP_VAL, MSSP_FIELD_ICON);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "IP",
	      MSSP_VAL, MSSP_FIELD_IP);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "LANGUAGE",
	      MSSP_VAL, MSSP_FIELD_LANGUAGE);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "LOCATION",
	      MSSP_VAL, MSSP_FIELD_LOCATION);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "MINIMUM AGE",
	      MSSP_VAL, MSSP_FIELD_MINIMUM_AGE);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "WEBSITE",
	      MSSP_VAL, MSSP_FIELD_WEBSITE);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "FAMILY",
	      MSSP_VAL, MSSP_FIELD_FAMILY);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GENRE",
	      MSSP_VAL, MSSP_FIELD_GENRE);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GAMEPLAY",
	      MSSP_VAL, MSSP_FIELD_GAMEPLAY);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GAMESYSTEM",
	      MSSP_VAL, MSSP_FIELD_GAMESYSTEM);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "INTERMUD",
	      MSSP_VAL, MSSP_FIELD_INTERMUD);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "STATUS",
	      MSSP_VAL, MSSP_FIELD_STATUS);

  cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "SUBGENRE",
	      MSSP_VAL, MSSP_FIELD_SUBGENRE);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "AREAS",
	      MSSP_VAL, top_area);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "HELPFILES",
	      MSSP_VAL, top_help);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MOBILES",
	      MSSP_VAL, top_mob_index);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "OBJECTS",
	      MSSP_VAL, top_obj_index);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "ROOMS",
	      MSSP_VAL, top_room);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "CLASSES",
	      MSSP_VAL, skill_package_table_size() );

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "LEVELS",
	      MSSP_VAL, MSSP_FIELD_LEVELS );

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "RACES",
	      MSSP_VAL, MSSP_FIELD_RACES );

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "SKILLS",
	      MSSP_VAL, top_sn);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "ANSI",
              MSSP_VAL, MSSP_FIELD_ANSI);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MCCP",
              MSSP_VAL, MSSP_FIELD_MCCP);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MCP",
	      MSSP_VAL, MSSP_FIELD_MCP);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MSP",
	      MSSP_VAL, MSSP_FIELD_MSP);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MXP",
	      MSSP_VAL, MSSP_FIELD_MXP);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PUEBLO",
	      MSSP_VAL, MSSP_FIELD_PUEBLO);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "VT100",
	      MSSP_VAL, MSSP_FIELD_VT100);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "XTERM 256 COLORS",
	      MSSP_VAL, MSSP_FIELD_XTERM256);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PAY TO PLAY",
	      MSSP_VAL, MSSP_FIELD_PAY_TO_PLAY);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PAY FOR PERKS",
	      MSSP_VAL, MSSP_FIELD_PAY_FOR_PERKS);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "HIRING BUILDERS",
	      MSSP_VAL, MSSP_FIELD_HIRING_BUILDERS);

  cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "HIRING CODERS",
	      MSSP_VAL, MSSP_FIELD_HIRING_CODERS);

  descriptor_printf(d, "%c%c%c%s%c%c", IAC, SB, TELOPT_MSSP, buffer, IAC, SE);

  return 3;
}

/*
  MCCP: Mud Client Compression Protocol
*/
void *zlib_alloc( void *opaque, unsigned int items, unsigned int size )
{
  return calloc(items, size);
}

void zlib_free( void *opaque, void *address ) 
{
  free(address);
}

int start_compress( DESCRIPTOR_DATA *d )
{
  const char start_mccp[] = { IAC, SB, TELOPT_MCCP, IAC, SE, 0 };
  z_stream *stream = NULL;

  if (d->mccp)
    {
      return TRUE;
    }

  CREATE(stream, z_stream, 1);

  stream->next_in	    = NULL;
  stream->avail_in    = 0;

  stream->next_out    = sysdata.mccp_buf;
  stream->avail_out   = COMPRESS_BUF_SIZE;

  stream->data_type   = Z_ASCII;
  stream->zalloc      = zlib_alloc;
  stream->zfree       = zlib_free;
  stream->opaque      = Z_NULL;

  /*
    12, 5 = 32K of memory, more than enough
  */

  if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED, 12, 5, Z_DEFAULT_STRATEGY) != Z_OK)
    {
      log_printf("start_compress: failed deflateInit2 D%d@%s", d->descriptor, d->host);
      DISPOSE(stream);

      return FALSE;
    }

  write_to_descriptor(d, start_mccp, 0);

  /*
    The above call must send all pending output to the descriptor, since from now on we'll be compressing.
  */

  d->mccp = stream;

  return TRUE;
}

void end_compress( DESCRIPTOR_DATA *d )
{
  if (d->mccp == NULL)
    {
      return;
    }

  d->mccp->next_in	= NULL;
  d->mccp->avail_in	= 0;

  d->mccp->next_out	= sysdata.mccp_buf;
  d->mccp->avail_out	= COMPRESS_BUF_SIZE;

  if (deflate(d->mccp, Z_FINISH) != Z_STREAM_END)
    {
      log_printf("end_compress: failed to deflate D%d@%s", d->descriptor, d->host);
    }

  process_compressed(d);

  if (deflateEnd(d->mccp) != Z_OK)
    {
      log_printf("end_compress: failed to deflateEnd D%d@%s", d->descriptor, d->host);
    }

  DISPOSE(d->mccp);
  d->mccp = NULL;
}

void write_compressed( DESCRIPTOR_DATA *d )
{
  d->mccp->next_in    = (unsigned char*) d->outbuf;
  d->mccp->avail_in   = d->outtop;

  d->mccp->next_out   = sysdata.mccp_buf;
  d->mccp->avail_out  = COMPRESS_BUF_SIZE;

  d->outtop           = 0;

  if (deflate(d->mccp, Z_SYNC_FLUSH) != Z_OK)
    {
      return;
    }

  process_compressed(d);
}

void process_compressed( DESCRIPTOR_DATA *d )
{
  int length = COMPRESS_BUF_SIZE - d->mccp->avail_out;

  if (send(d->descriptor, sysdata.mccp_buf, length, 0) < 1)
    {
      log_printf("process_compressed D%d@%s", d->descriptor, d->host);
      return;
    }
}

int process_do_mccp( DESCRIPTOR_DATA *d, unsigned char *src, int srclen )
{
  start_compress(d);

  return 3;
}

int process_dont_mccp( DESCRIPTOR_DATA *d, unsigned char *src, int srclen )
{
  end_compress(d);

  return 3;
}

/*
  Returns the length of a telnet subnegotiation, return srclen + 1 for incomplete state.
*/
int skip_sb( DESCRIPTOR_DATA *d, unsigned char *src, int srclen )
{
  int i;

  for (i = 1 ; i < srclen ; i++)
    {
      if (src[i] == SE && src[i-1] == IAC)
	{
	  return i + 1;
	}
    }

  return srclen + 1;
}

/*
  Utility function
*/
void descriptor_printf( DESCRIPTOR_DATA *d, const char *fmt, ... )
{
  char buf[MAX_STRING_LENGTH];
  int size = 0;
  va_list args;

  va_start(args, fmt);

  size = vsprintf(buf, fmt, args);

  va_end(args);

  write_to_descriptor(d, buf, size);
}