imcserver/config/
imcserver/src/
/*
 * IMC2 Liberty Server - Developed by Mud Domain.
 * Copyright (C)2004-2008 Roger Libiez(Samson)
 * Additional code Copyright (C)2004-2006 by Jonathan Walker (Xorith)
 * Comments and suggestions welcome: imc@intermud.us
 * Registered with the United States Copyright Office: TX 5-953-618
 *
 * IMC2 MUD-Net versions 3.20 through 3.31 developed by Mud Domain.
 * Copyright (C) 2003-2004 Roger Libiez (Samson)
 * Additional code Copyright (C) 2003-2004 by Jonathan Walker (Xorith)
 *
 * Some portions of code from Hermes 3.17 Copyright (C)2003 Rogel
 *
 * IMC2 Hermes version 3.15 is developed by Rogel
 * Copyright (C) 2003 Rogel
 *
 * IMC2 MUD-Net versions 3.00 through 3.10 developed by Alsherok and Crimson Oracles
 * Copyright (C) 2002-2003 Roger Libiez (Samson)
 * Additional code Copyright (C) 2002 Orion Elder
 * Registered with the United States Copyright Office: TX 5-555-584
 *
 * IMC2 Gold versions 1.00 through 2.00 developed by MudWorld.
 * Copyright (C) 1999 - 2002
 * Scion Altera, Shogar, Kratas, Tagith, Noplex,
 * Senir, Trax, Samson, and Ntanel StormBlade( Anthony R. Haslage )
 *
 * IMC2 version 0.10 - an inter-mud communications protocol
 * Copyright (C) 1996 & 1997 Oliver Jowett <oliver@randomly.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file COPYING); if not, write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

using namespace std;

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <zlib.h>
#include <cstdarg>
#include <cerrno>
#include <csignal>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <sstream>
#include <fstream>
#include <iomanip>
#include "imc.h"
#include "md5.h"
#include "sha256.h"

StreamWrapper logfile;
ofstream logstream;

bool imcpacketdebug = false;
time_t imc_now;                     /* current time */
time_t imc_boot;                    /* startup time */
static int event_freecount;
static int control;
static int memory_head;             /* next entry in memory table to use, wrapping */
imc_siteinfo *siteinfo;
imc_statistics imc_stats;
imc_event *imc_event_list, *imc_event_free;

imc_memory imc_memory[IMC_MEMORY]; /* sequence memory */
unsigned long imc_sequencenumber;   /* sequence# for outgoing packets */

list<imc_channel*> chanlist;
list<connection*> connlist;
list<imc_info*> infolist;
list<imc_reminfo*> reminfolist;
list<imc_laston*> lastonlist;
list<packet_handler*> phanlderlist;
list<string> banlist;

char *const perm_names[] =
{
   "Mort", "Imm", "Admin", "Imp"
};

/********************************
 * Low level utility functions. *
 ********************************/

// Compare: astr ><= bstr.
// <0 = astr < bstr
//  0 = astr == bstr
// >0 = astr > bstr
// Case insensitive.
// -- Justice
int str_cmp( const string& astr, const string& bstr )
{
   string::const_iterator a1, a2, b1, b2;

   a1 = astr.begin();
   a2 = astr.end();

   b1 = bstr.begin();
   b2 = bstr.end();

   while( a1 != a2 && b1 != b2 )
   {
      if( std::toupper(*a1) != std::toupper(*b1) )
         return( std::toupper(*a1) < std::toupper(*b1) ? -1 : 1 );
      ++a1;
      ++b1;
   }
   return( bstr.size() == astr.size() ? 0 : ( astr.size() < bstr.size() ? -1 : 1 ) );
}

/* Borrowed strlcpy and strlcat from OpenSSH, copyright notice and license is in STRLCPY-STRLCAT-LICENSE file */
#ifndef HAVE_STRLCPY
/*
 * Copy src to string dst of size siz.  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz == 0).
 * Returns strlen(src); if retval >= siz, truncation occurred.
 */
size_t strlcpy( char *dst, const char *src, size_t siz )
{
   register char *d = dst;
   register const char *s = src;
   register size_t n = siz;

   /* Copy as many bytes as will fit */
   if( n != 0 && --n != 0 )
   {
      do
      {
         if( ( *d++ = *s++ ) == 0 )
            break;
      }
      while( --n != 0 );
   }

   /* Not enough room in dst, add NUL and traverse rest of src */
   if( n == 0 )
   {
      if( siz != 0 )
         *d = '\0'; /* NUL-terminate dst */

      while( *s++ )
         ;
   }
   return( s - src - 1 ); /* count does not include NUL */
}
#endif /* !HAVE_STRLCPY */

#ifndef HAVE_STRLCAT
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(initial dst) + strlen(src); if retval >= siz,
 * truncation occurred.
 */
size_t strlcat( char *dst, const char *src, size_t siz )
{
   register char *d = dst;
   register const char *s = src;
   register size_t n = siz;
   size_t dlen;

   /* Find the end of dst and adjust bytes left but don't go past end */
   while( n-- != 0 && *d != '\0' )
      ++d;

   dlen = d - dst;
   n = siz - dlen;

   if( n == 0 )
      return( dlen + strlen(s) );

   while( *s != '\0' )
   {
      if( n != 1 )
      {
         *d++ = *s;
         --n;
      }
      ++s;
   }
   *d = '\0';
   return( dlen + (s - src) ); /* count does not include NUL */
}
#endif /* !HAVE_STRLCAT */

char *md5_crypt( const char *pwd )
{
   md5_state_t state;
   static md5_byte_t digest[17];
   static char passwd[17];

   md5_init( &state );
   md5_append( &state, (const md5_byte_t *)pwd, strlen( pwd ) );
   md5_finish( &state, digest );
   strlcpy( passwd, (const char *)digest, 16 );

   /* The listed exceptions below will fubar the MD5 authentication packets, so change them */
   for( size_t x = 0; x < strlen( passwd ); ++x )
   {
      if( passwd[x] == '\n' )
         passwd[x] = 'n';
      if( passwd[x] == '\r' )
         passwd[x] = 'r';
      if( passwd[x] == '\t' )
         passwd[x] = 't';
      if( passwd[x] == ' ' )
         passwd[x] = 's';
      if( (int)passwd[x] == 11 )
         passwd[x] = 'x';
      if( (int)passwd[x] == 12 )
         passwd[x] = 'X';
   }
   return( passwd );
}

bool is_number( const string& arg )
{
   size_t x;
   bool first = true;

   if( arg.empty(  ) )
      return false;

   for( x = 0; x < arg.length(  ); ++x )
   {
      if( first && arg[x] == '-' )
      {
         first = false;
         continue;
      }

      if( !isdigit( arg[x] ) )
         return false;
      first = false;
   }
   return true;
}

/* Strips off leading spaces and tabs in strings.
 * Useful mainly for input file streams because they suck so much.
 * Samson 10-16-04
 */
void strip_lspace( string & line )
{
   string::size_type space;

   space = line.find_first_not_of( ' ' );
   if( space == string::npos )
      space = 0;
   line = line.substr( space, line.length(  ) );

   space = line.find_first_not_of( '\t' );
   if( space == string::npos )
      space = 0;
   line = line.substr( space, line.length(  ) );
}

/* Strips off trailing spaces in strings. */
void strip_tspace( string & line )
{
   string::size_type space;

   space = line.find_last_not_of( ' ' );
   if( space != string::npos )
      line = line.substr( 0, space + 1 );
}

/* Strip both leading and trailing spaces from a string */
void strip_spaces( string & line )
{
   strip_lspace( line );
   strip_tspace( line );
}

// Pick off one argument from a string and return the rest.
string one_argument( const string& argument, string& first )
{
   string::size_type start, stop, stop2;
   char find;

   // Init
   start = 0;

   // Make sure first is clean
   first.clear();

   // Empty?
   if( argument.empty() )
      return "";

   // Strip leading spaces
   if( argument[0] == ' ' )
   {
      start = argument.find_first_not_of( ' ' );

      // Empty?
      if( start == argument.npos )
         return "";
   }

   // Quotes or space?
   switch( argument[start] )
   {
      case '\'':
         find = '\'';
         ++start;
         break;

      case '\"':
         find = '\"';
         ++start;
         break;

      default:
         find = ' ';
   }

   // Find end of argument.
   stop = argument.find_first_of( find, start );

   // Empty leftovers?
   if( stop == argument.npos )
   {
      first = argument.substr( start );
      return "";
   }

   // Update first
   first = argument.substr( start, (stop-start) );

   // Strip leading spaces from leftovers
   stop2 = argument.find_first_not_of( ' ', stop+1 );

   // Empty leftovers?
   if( stop2 == argument.npos )
      return "";

   // Return leftovers.
   return argument.substr( stop2 );
}

/* Does the list have the member in it? */
bool hasname( const string& list, const string& member )
{
   string::size_type x;

   if( list.empty() )
      return false;

   if( ( x = list.find( member ) ) != string::npos )
      return true;

   return false;
}

/* Add a new member to the list, provided it's not already there */
void addname( string& list, const string& member )
{
   if( hasname( list, member ) )
      return;

   if( list.empty(  ) )
      list = member;
   else
      list.append( " " + member );
   strip_lspace( list );
}

/* Remove a member from a list, provided it's there. */
void removename( string& list, const string& member )
{
   if( !hasname( list, member ) )
      return;

   // Implies the list has more than just this name.
   if( list.length() > member.length() )
   {
      string die = " " + member;
      string::size_type pos = list.find( die );

      list.erase( pos, die.length() );
   }
   else
      list.clear();
   strip_lspace( list );
}

string imc_nameof( const string& src )
{
   string::size_type x;

   if( ( x = src.find( '@' ) ) != string::npos && x > 0 )
      return src.substr( 0, x );

   return src;
}

string imc_mudof( const string& src )
{
   string::size_type x;

   if( ( x = src.find( '@' ) ) != string::npos && x > 0 )
      return src.substr( x+1, src.length() );

   return src;
}

string ice_mudof( const string& fullname )
{
   string::size_type x;

   if( ( x = fullname.find_first_of( ':' ) ) == string::npos )
      return fullname;
   return fullname.substr( 0, x );
}

string imc_makename( const string& person, const string& mud )
{
   ostringstream name;

   name << person << "@" << mud;
   return name.str();
}

string escape_string( const string& sData )
{
   string sDataReturn = "";
   unsigned int i = 0;
   bool quote = false;

   if( sData.find( ' ' ) != string::npos )
      quote = true;

   for( i = 0; i < sData.length(); ++i )
   {
      if( sData[i] == '"' )
         sDataReturn += "\\\"";
      else if( sData[i] == '\n' )
         sDataReturn += "\\n";
      else if( sData[i] == '\r' )
         sDataReturn += "\\r";
      else
         sDataReturn += sData[i];
   }

   if( quote )
      sDataReturn = '"' + sDataReturn + '"';

   return sDataReturn;
}

string unescape_string( const string& sData )
{
   string sDataReturn = "";

   for( size_t i = 0; i < sData.length(); ++i )
   {
      if( sData[i] == '\\' && sData[i+1] == '\"' )
      {
         sDataReturn += '"';
         ++i;
      }
      else if( sData[i] == '\\' && sData[i+1] == 'n' )
      {
         sDataReturn += "\n";
         ++i;
      }
      else if( sData[i] == '\\' && sData[i+1] == 'r' )
      {
         sDataReturn += "\r";
         ++i;
      }
      else
         sDataReturn += sData[i];
   }
   return sDataReturn;
}

string parseword( string& line )
{
   if( line.empty() )
      return "";

   // check for spaces; if there is no space check for line breaking; if all 
   // else fails go directly to the end of the string ; length ; 
   string::size_type iSpace = line.find( ' ' );

   if( iSpace == string::npos )
   {
      iSpace = line.find( '\n' );
      if( iSpace == string::npos )
      {
         iSpace = line.find( '\r' );
         if( iSpace == string::npos )
            iSpace = line.length();
      }
   }

   // added the sequence to check for quotes; no packet should have a quote 
   // character unless it has strings with spaces inside of it
   string::size_type iQuote = line.find( '"' );

   if( iQuote >= 0 && iQuote < iSpace && line[iQuote-1] != '\\' )
   {
      line = line.substr( iQuote+1, line.length() );
      while( ( iQuote = line.find( '"', iSpace+1 ) ) != string::npos )
      {
         iSpace = iQuote;

         if( iQuote > 0 && line[iQuote-1] == '\\' )
            continue;
         else
            break;
      }
   }

   string sWord = line.substr( 0, iSpace );
   if( iSpace < ( line.length()-1 ) )
      line = line.substr( iSpace+1, line.length() );

   return sWord;
}

/* return true if 'name' is a part of 'path'  (internal) */
bool inpath( const string& path, const string& name )
{
   char buf[LSS];

   if( path == name )
      return true;

   snprintf( buf, LSS, "%s!", name.c_str() );
   if( STRN_CEQL( path.c_str(), buf, strlen(buf) ) )
      return true;

   snprintf( buf, LSS, "!%s", name.c_str() );
   if( strlen(buf) < path.length() && STR_CEQL( path.c_str() + path.length() - strlen(buf), buf ) )
      return true;

   snprintf( buf, LSS, "!%s!", name.c_str() );
   if( strcasestr( path.c_str(), buf ) ) /* Ntanel */
      return true;

   return false;
}

/* return 'e' from 'a!b!c!d!e' */
string imc_lastinpath( const string& path )
{
   string::size_type x;

   if( ( x = path.find_last_of( '!' ) ) != string::npos && x > 0 )
      return path.substr( x+1, path.length() );
   return path;
}

/* return 'b' from 'a!b!c!d!e' */
string imc_serverinpath( const string& path )
{
   string::size_type x, y;

   if( path.empty() )
      return "";

   if( ( x = path.find_first_of( '!' ) ) == string::npos )
      return siteinfo->name;

   string piece = path.substr( x+1, path.length() );

   if( ( y = piece.find_first_of( '!' ) ) == string::npos )
      return piece;

   return piece.substr( 0, y );
}

/* return 'a' from 'a!b!c!d!e' */
string imc_firstinpath( const string& path )
{
   string::size_type x;

   if( ( x = path.find_first_of( '!' ) ) != string::npos && x > 0 )
      return path.substr( 0, x );
   return path;
}

int get_permvalue( const char *flag )
{
   for( size_t x = 0; x < ( sizeof(perm_names) / sizeof(perm_names[0]) ); ++x )
      if( STR_CEQL( flag, perm_names[x] ) )
         return x;
   return -1;
}

bool exists_file( const string& name )
{
   struct stat fst;

   /* Stands to reason that if there ain't a name to look at, it damn well don't exist! */
   if( name.empty() )
	return false;

   if( stat( name.c_str(), &fst ) != -1 )
	return true;
   else
	return false;
}

/* delete the info entry "p" */
void imc_delete_reminfo( imc_reminfo *p )
{
   imc_cancel_event( NULL, p );
   reminfolist.remove( p );
   deleteptr( p );
}

/* create a new info entry, insert into list */
imc_reminfo *imc_new_reminfo( const string& mud )
{
   imc_reminfo *p = new imc_reminfo;

   p->name    = mud;
   p->version = "Unknown";
   p->path.clear();
   p->top_sequence = 0;
   p->expired = false;
   p->url = "Unknown";
   p->network = "Unknown";
   p->sha256 = false;

   list<imc_reminfo*>::iterator rin;
   for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin )
   {
      imc_reminfo *r = *rin;

      if( strcasecmp( r->name.c_str(), mud.c_str() ) >= 0 )
      {
         reminfolist.insert( rin, p );
         return p;
      }
   }
   reminfolist.push_back( p );
   return p;
}

/*
 *  imc_reminfo handling
 */

/* find an info entry for "name" */
imc_reminfo *imc_find_reminfo( const string& name )
{
   list<imc_reminfo*>::iterator rin;
   for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin )
   {
      imc_reminfo *r = *rin;

      if( !str_cmp( r->name, name ) )
         return r;
   }
   return NULL;
}

/* get info struct for given mud */
imc_info *imc_getinfo( const string& mud )
{
   list<imc_info*>::iterator inf;
   for( inf = infolist.begin(); inf != infolist.end(); ++inf )
   {
      imc_info *i = *inf;

      if( !str_cmp( i->name, mud ) )
         return i;
   }
   return NULL;
}

imc_info *imc_new_info( void )
{
   imc_info *i = new imc_info;

   i->port = 0;
   i->last_connected = 0;
   i->sha256 = false;
   i->server = false;
   i->conn = NULL;
   i->url = "Unknown";
   infolist.push_back( i );

   return i;
}

void imc_delete_info( imc_info *i )
{
   list<connection*>::iterator conn;
   for( conn = connlist.begin(); conn != connlist.end(); )
   {
      connection *c = *conn;
      ++conn;

      if( c->info == i )
         do_close( c );
   }
   imc_cancel_event( NULL, i );
   infolist.remove( i );
   deleteptr( i );
   --siteinfo->connects;
}

imc_channel *iced_findchannel( const string& name )
{
   list<imc_channel*>::iterator chn;

   for( chn = chanlist.begin(); chn != chanlist.end(); ++chn )
   {
      imc_channel *c = *chn;

      if( !str_cmp( c->name, name ) )
         return c;
   }
   return NULL;
}

/* get name of a connection */
string imc_getconnectname( const connection *c )
{
   ostringstream buf;
   string n;

   if( c->info )
      n = c->info->name;
   else if( !c->host.empty() )
      n = c->host;
   else
      n = "Unknown";

   buf << n << "[" << c->desc << "]";
   return buf.str();
}

/* set up a new imc_connect struct, and link it into imc_connect_list */
connection *imc_new_connect( void )
{
   connection *c = new connection;

   c->info     = NULL;
   c->host     = "Unknown";
   c->desc     = -1;
   c->disconnect = false;
   c->state    = CONN_NONE;
   c->version  = 2;
   c->auth_value = 0;
   c->newoutput = false;
   c->is_compressing = false;
   c->insize   = IMC_MINBUF;
   c->outsize  = IMC_MINBUF;
   CREATE( c->inbuf, char, c->insize );
   CREATE( c->outbuf, char, c->outsize );
   c->inbuf[0] = c->outbuf[0] = '\0';
   c->info     = NULL;

   connlist.push_back( c );
   return c;
}

/*  free buffers and extract 'c' from imc_connect_list
 *  called from imc_idle_select when we're done with a connection with
 *  c->state==CONN_NONE
 */
void imc_extract_connect( connection *c )
{
   if( c->state != CONN_NONE )
   {
      logfile << __FUNCTION__ << ": non-closed connection" << endl;
      return;
   }

   DISPOSE( c->inbuf );
   DISPOSE( c->outbuf );
   imc_cancel_event( NULL, c );
   connlist.remove( c );
   deleteptr( c );
}

/******************************************
 * Packet handling and routing functions. *
 ******************************************/

/* accept a connection on the control port */
void do_accept( void )
{
   connection *c;
   struct sockaddr_storage ss;
   socklen_t ss_len = sizeof(ss);
   char host[NI_MAXHOST];
   char serv[NI_MAXSERV];
   int n, r, fd;

   fd = accept( control, (struct sockaddr *) &ss, &ss_len );
   if( fd < 0 )
   {
      logfile << __FUNCTION__ << ": accept failed: " << strerror(errno) << endl;
      return;
   }

   n = getnameinfo( (struct sockaddr *) &ss, ss_len, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST );
   if( n )
   {
      strlcpy( host, "Unknown", NI_MAXHOST );
      strlcpy( serv, "Unknwon", NI_MAXSERV );
   }

   r = fcntl( fd, F_GETFL, 0 );
   if( r < 0 || fcntl( fd, F_SETFL, O_NONBLOCK | r ) < 0 )
   {
      logfile << __FUNCTION__ << ": fcntl call failed" << endl;
      close(fd);
      return;
   }

   c = imc_new_connect();
   c->state = CONN_WAITCLIENTPWD;
   c->desc  = fd;
   c->host  = host;
   c->disconnect = false;

   imc_add_event( IMC_LOGIN_TIMEOUT, ev_login_timeout, c );
   logfile << "Connection from " << host << ":" << serv << " on descriptor " << fd << endl;
}

/* close given connection */
void do_close( connection *c )
{
   imc_reminfo *r;

   if( c->state == CONN_NONE )
      return;

   c->state = CONN_NONE;
   c->is_compressing = false;

   if( c->desc )
   {
      logfile << imc_getconnectname( c ) << ": closing link" << endl;
      close( c->desc );
      c->desc = 0;
   }

   if( c->info )
   {
      c->info->conn = NULL;

      /* dont announce a simple reboot - shogar - 2/2/2000 */ 
      imc_add_event( 60, ev_close_notify, c->info->name.c_str() );

      if( ( r = imc_find_reminfo( c->info->name ) ) != NULL )
         imc_delete_reminfo( r );

      c->info = NULL;
   }
   c->inbuf[0] = '\0';
   c->outbuf[0] = '\0';
}

/* char *string_compress( char *buf )
{
   int len = strlen(buf)+1, comprLen = IMC_MAXBUF-1;
   int status;
   static char compr[IMC_MAXBUF-1];

   memset( compr, 0, IMC_MAXBUF-1 );
   status = compress( (Bytef*)compr, (uLong *)&comprLen, (const Bytef*)buf, len );
   if( status != Z_OK )
   {
      switch( status )
      {
         default:
            logfile << "Unknown error state" << endl;
            break;
         case Z_MEM_ERROR:
            logfile << "Error state was Z_MEM_ERROR" << endl;
            break;
         case Z_BUF_ERROR:
            logfile << "Error state was Z_BUF_ERROR" << endl;
            break;
         case Z_DATA_ERROR:
            logfile << "Error state was Z_DATA_ERROR" << endl;
            break;
      }
      return "ERROR";
   }
   compr[comprLen] = '\0';
   return compr;
}

char *string_uncompress( char *buf )
{
   int comprLen = IMC_MAXBUF;
   int status;
   static char uncompr[IMC_MAXBUF];
   int uncomprLen = IMC_MAXBUF;

   memset( uncompr, 0, IMC_MAXBUF );
   status = uncompress( (Bytef*) uncompr, (uLong*)&uncomprLen, (Bytef*)buf, comprLen );

   if( status != Z_OK )
   {
      switch( status )
      {
         case Z_MEM_ERROR:
            logfile << "Error state was Z_MEM_ERROR" << endl;
         case Z_BUF_ERROR:
            logfile << "Error state was Z_BUF_ERROR" << endl;
         case Z_DATA_ERROR:
            logfile << "Error state was Z_DATA_ERROR" << endl;
      }
      logfile << "Compressed data  : " << buf << endl;
      logfile << "Uncompressed data: " << uncompr << endl;
      return "ERROR";
   }

   uncompr[uncomprLen] = '\0';
   return uncompr;
}
*/

/* read waiting data from descriptor.
 * read to a temp buffer to avoid repeated allocations
 */
void do_read( connection *c )
{
   char temp[IMC_MAXBUF];
   char *newbuf;

   int r = read( c->desc, temp, IMC_MAXBUF - 1 );
   if( !r || ( r < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) )
   {
      if( !c->info )
      {
         if( r < 0 )                    /* read error */
            logfile << imc_getconnectname( c ) << ": read error" << endl;
         else                        /* socket was closed */
            logfile << imc_getconnectname( c ) << ": EOF" << endl;
      }
      do_close( c );
      return;
   }
  
   if( r < 0 )			/* EAGAIN error */
      return;

   temp[r] = '\0';

//   logfile << "RAW DATA READ : " << temp << endl;
//   logfile << "RAW BYTES READ: " << strlen(temp) << endl;

/*   if( c->is_compressing )
   {
      c->inflate_buf = string_uncompress( temp );
      if( STR_CEQL( c->inflate_buf, "ERROR" ) )
      {
         logfile << __FUNCTION__ << ": Decompression error on socket: " << imc_getconnectname(c) << endl;
         do_close(c);
         return;
      }
      logfile << "Decompressed data: " << c->inflate_buf << endl;
   }
*/
   int size = strlen( c->inbuf ) + r + 1;
   int newsize;

   if( size >= c->insize )
   {
      newsize = c->insize;
      while( newsize < size )
         newsize *= 2;

      CREATE( newbuf, char, newsize );
      strlcpy( newbuf, c->inbuf, newsize );
      DISPOSE( c->inbuf );
      c->inbuf = newbuf;
      c->insize = newsize;
   }

   if( size < c->insize/2 && size >= IMC_MINBUF )
   {
      newsize = c->insize;
      newsize /= 2;

      CREATE( newbuf, char, newsize );
      strlcpy( newbuf, c->inbuf, newsize );
      DISPOSE( c->inbuf );
      c->inbuf = newbuf;
      c->insize = newsize;
   }
   if( c->is_compressing )
   {
      strlcat( c->inbuf, c->inflate_buf, c->insize );
      imc_stats.rx_cmpbytes += r;
      imc_stats.rx_bytessaved += ( strlen( c->inbuf ) - r );
   }
   else
   {
      strlcat( c->inbuf, temp, c->insize );
      imc_stats.rx_bytes += r;
   }
//   logfile << "INPUT BUFFER: " << c->inbuf << endl;
}

/* write to descriptor */
void do_write( connection *c )
{
   int w;

   if( c->state == CONN_SERVERCONNECT )
   {
      /* Wait for server password */
      c->state = CONN_WAITSERVERPWD;
      return;
   }

   int size = strlen( c->outbuf );
   if( !size ) /* nothing to write */
      return;

/*   if( c->is_compressing )
   {
      c->deflate_buf = string_compress( c->outbuf );
      if( STR_CEQL( c->deflate_buf, "ERROR" ) )
      {
         logfile << __FUNCTION__ << "Compression error on socket: " << imc_getconnectname(c) << endl;
         do_close(c);
         return;
      }
      w = write( c->desc, c->deflate_buf, strlen( c->deflate_buf ) );
      if( !w || ( w < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) )
      {
         if( !c->info )
         {
            if( w < 0 )			// write error
               logfile << __FUNCTION__<< ": " << imc_getconnectname(c) << ": write error" << endl;
            else			// socket was closed
               logfile << __FUNCTION__<< ": " << imc_getconnectname(c) << ": EOF" << endl;
         }
         do_close( c );
         return;
      }
   }

   else */
   {
      w = write( c->desc, c->outbuf, strlen( c->outbuf ) );
      if( !w || ( w < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) )
      {
         if( !c->info )
         {
            if( w < 0 )			/* write error */
               logfile << __FUNCTION__<< ": " << imc_getconnectname(c) << ": write error" << endl;
            else			/* socket was closed */
               logfile << __FUNCTION__<< ": " << imc_getconnectname(c) << ": EOF" << endl;
         }
         do_close( c );
         return;
      }
   }

   if( w < 0 )			/* EAGAIN */
      return;

   if( imcpacketdebug )
      logfile << "Packet sent: " << c->outbuf << endl;

   if( c->is_compressing )
   {
      imc_stats.tx_cmpbytes += w;
      imc_stats.tx_bytessaved += ( size - w );
      logfile << "Compressed buffer out: " << c->deflate_buf << endl;
   }
   else
      imc_stats.tx_bytes += w;
   strlcpy( c->outbuf, c->outbuf + size, c->outsize );
}

/* put a line onto descriptors output buffer */
void write_buffer( connection *c, const string& line )
{
   char *newbuf;
   int newsize = c->outsize;

   if( c->state == CONN_NONE )
      return;

   if( !c->outbuf[0] )
      c->newoutput = true;

   int len = strlen( c->outbuf ) + line.length() + 3;

   if( len > c->outsize )
   {
      while( newsize < len )
         newsize *= 2;

      CREATE( newbuf, char, newsize );
      strlcpy( newbuf, c->outbuf, newsize );
      DISPOSE( c->outbuf );
      c->outbuf = newbuf;
      c->outsize = newsize;
   }
   if( len < c->outsize/2 && len >= IMC_MINBUF )
   {
      newsize = c->outsize/2;

      CREATE( newbuf, char, newsize );
      strlcpy( newbuf, c->outbuf, newsize );
      DISPOSE( c->outbuf );
      c->outbuf = newbuf;
      c->outsize = newsize;
   }
   strlcat( c->outbuf, line.c_str(), c->outsize );
   strlcat( c->outbuf, "\n\r", c->outsize );
}

/*
 * Convert a packet to text to then send to the buffer
 */
void imc_packet::writepacket( connection *c )
{
   ostringstream txt;
   string newpath;

   if( i.path.empty() )
      newpath = siteinfo->name;
   else
      newpath = i.path + "!" + siteinfo->name;

   /* Assemble your buffer, and at the same time disassemble the packet struct to free the memory */
   txt << i.from << " " << i.sequence << " " << newpath << " " << type << " " << i.to;
   txt << " " << data.str();

   ++imc_stats.tx_pkts;

   if( txt.str().length() > imc_stats.max_pkt )
      imc_stats.max_pkt = txt.str().length();

   write_buffer( c, txt.str() );
   return;
}

imc_packet::imc_packet( const string& pfrom, const string& ptype, const string& pto )
{
   if( ptype.empty() )
   {
      logfile << __FUNCTION__ << ": Attempt to build packet with no type field." << endl;
   }

   if( pfrom.empty() )
   {
      logfile << __FUNCTION__ << ": Attempt to build " << ptype << " packet with no from field." << endl;
   }

   if( pto.empty() )
   {
      logfile << __FUNCTION__ << ": Attempt to build " << ptype << " packet with no to field." << endl;
   }

   from = pfrom.empty() ? "BORKED" : pfrom + "@" + siteinfo->name;
   type = ptype.empty() ? "BORKED" : ptype;
   to = pto.empty() ? "BORKED" : pto;
   stop = false;
   data.str("");

   i.path.clear();
   i.sequence = imc_sequencenumber++;
   if( !imc_sequencenumber )
      ++imc_sequencenumber;

   i.to = to;
   i.from = from;
}

imc_packet::imc_packet()
{
   from.clear();
   type.clear();
   to.clear();
   stop = false;
   data.str("");
   i.path.clear();

   i.to = to;
   i.from = from;
}

/*  try to read a line from the input buffer, NULL if none ready
 *  all lines are \n\r terminated in theory, but take other combinations
 */
static const char *imc_getline( char *buffer, int len )
{
   int i;
   static char buf[IMC_PACKET_LENGTH];

   /* copy until \n, \r, end of buffer, or out of space */
   for( i = 0; buffer[i] && buffer[i] != '\n' && buffer[i] != '\r' && i+1 < IMC_PACKET_LENGTH; ++i )
      buf[i] = buffer[i];

   /* end of buffer and we haven't hit the maximum line length */
   if( !buffer[i] && i+1 < IMC_PACKET_LENGTH )
   {
      buf[0] = '\0';
      return NULL; /* so no line available */
   }

   /* terminate return string */
   buf[i] = 0;

   /* strip off extra control codes */
   while( buffer[i] && ( buffer[i] == '\n' || buffer[i] == '\r' ) )
      ++i;

   /* remove the line from the input buffer */
   strlcpy( buffer, buffer+i, len );

   return buf;
}

/* checkrepeat: check for repeats in the memory table */
bool checkrepeat( const string& mud, unsigned long seq )
{
   for( int i = 0; i < IMC_MEMORY; ++i )
      if( !imc_memory[i].from.empty() && !str_cmp( mud, imc_memory[i].from ) && seq == imc_memory[i].sequence )
         return true;

   /* not a repeat, so log it */
   imc_memory[memory_head].from = mud;
   imc_memory[memory_head].sequence = seq;

   ++memory_head;
   if( memory_head == IMC_MEMORY )
      memory_head = 0;

   return false;
}

bool can_forward( const imc_packet *p )
{
   if( p->type == "chat" || p->type == "emote" )
   {
      /* The only valid one left these days is 15, don't forward others */
      if( p->data.str().find( "channel=15" ) == string::npos )
         return false;
   }

   /* Contain these only to the server a mud is connected to */
   if( p->type == "imc-laston" )
      return false;

   // Temporary hack to kill processing of channel notification packets.
   if( p->type == "channel-notify" )
      return false;

   /* Stop forwarding packets if they've been flagged */
   if( p->stop )
      return false;

   return true;
}

/* update our routing table based on a packet received with path "path" */
void updateroutes( const string& path )
{
   imc_reminfo *r;
   string sender, temp;
   string::size_type x;

   /* loop through each item in the path, and update routes to there */
   temp = path;
   while( !temp.empty() )
   {
      sender = imc_firstinpath( temp );

      if( sender != siteinfo->name )
      {
         /* not from us */
         /* check if its in the list already */

         if( !( r = imc_find_reminfo( sender ) ) ) /* not in list yet, create a new entry */
            r = imc_new_reminfo( sender );
         r->expired = false;
         r->path = temp;
      }
      /* get the next item in the path */
      if( ( x = temp.find( '!' ) ) != string::npos )
         temp = temp.substr( x+1, temp.length() );
      else
         break;
   }
}

/* forward a packet - main routing function, all packets pass through here */
void imc_packet::forward()
{
   imc_reminfo *route = NULL;
   imc_info *in = NULL, *direct = NULL;
   string pto;
   bool isbroadcast = false;
   bool ismulticast = false;

   /* check for duplication, and register the packet in the sequence memory */
   if( i.sequence && checkrepeat( imc_mudof( i.from ), i.sequence ) )
   {
      delete this;
      return;
   }

   /* check for packets we've already forwarded */
   if( inpath( i.path, siteinfo->name ) )
   {
      delete this;
      return;
   }

   /* check for really old packets */
   if( ( route = imc_find_reminfo( imc_mudof( i.from ) ) ) != NULL )
   {
      if( ( i.sequence + IMC_PACKET_LIFETIME ) < route->top_sequence )
      {
         ++imc_stats.sequence_drops;
         delete this;
         return;
      }
      if( i.sequence > route->top_sequence )
         route->top_sequence = i.sequence;
   }

   /* update our routing info */
   updateroutes( i.path );

   pto = imc_mudof( i.to );
   isbroadcast = ( pto == "*" ); /* broadcasts are, well, broadcasts */
   ismulticast = ( pto == "$" ); /* Limited broadcast for servers only */

   /* forward to our mud if it's for us */
   if( isbroadcast || ismulticast || !str_cmp( pto, siteinfo->name ) )
   {
      to = imc_nameof( i.to );    /* strip the name from the 'pto' */
      from = i.from;

      imc_recv( this );

      /* if its only to us (ie. not broadcast) don't forward it */
      if( !isbroadcast && !ismulticast )
      {
         delete this;
	   return;
      }
   }

   /* check if we should just drop it (policy rules) */
   if( !can_forward( this ) )
   {
      delete this;
      return;
   }

   /* convert 'to' fields that we have a route for to a hop along the route */
   if( !isbroadcast && !ismulticast && ( route = imc_find_reminfo( pto ) ) != NULL 
     && !route->path.empty() && !inpath( i.path, imc_lastinpath( route->path ) ) )
   /* avoid circular routing */
   {
      /*  check for a direct connection: if we find it, and the route isn't
       *  to it, then the route is a little suspect.. also send it direct
       */
      if( pto != imc_lastinpath( route->path ) && ( in = imc_getinfo( pto ) ) != NULL && in->conn )
         direct = in;

      pto = imc_lastinpath( route->path );
   }

   /* check for a direct connection */
   if( !isbroadcast && !ismulticast 
    && ( ( in = imc_getinfo( pto ) ) == NULL || !in->conn )
    && ( !direct || !direct->conn ) )
   {
	logfile << "Unable to forward packet sent from " << imc_mudof( i.from ) << ": No path available." << endl;
      logfile << "Packet: " << i.from << " " << i.sequence << " " << i.path << " " << type << " " << i.to << " : Alledgedly destined for: " << pto << endl;
      delete this;
	return;
   }

   if( isbroadcast )
   {
      list<connection*>::iterator conn;
      for( conn = connlist.begin(); conn != connlist.end(); ++conn )
      {
         connection *c = (*conn);

         if( c->state == CONN_COMPLETE )
         {
            if( !c->info )
               continue;

	      /* don't forward to sites that have already received it, or sites that don't need this packet */
	      if( inpath( i.path, c->info->name ) )
	         continue;
	      /* end SPAM fix */
	      writepacket( c );
         }
      }
   }
   /* Multicasts go only to flagged servers in the info list */
   else if( ismulticast )
   {
      list<connection*>::iterator conn;
      for( conn = connlist.begin(); conn != connlist.end(); ++conn )
      {
         connection *c = (*conn);

         if( c->state == CONN_COMPLETE )
         {
            if( !c->info )
               continue;

            if( !c->info->server )
               continue;

            if( inpath( i.path, c->info->name ) )
               continue;

            writepacket( c );
         }
      }
   }
   /* forwarding to a specific connection */
   else
   {
      /* but only if they haven't seen it (sanity check) */
      if( in && in->conn && !inpath( i.path, in->name ) )
         writepacket( in->conn );

      /* send on direct connection, if we have one */
      if( direct && direct != in && direct->conn && !inpath( i.path, direct->name ) )
         writepacket( direct->conn );
   }
   delete this;
}

/* notify everyone of the closure - shogar */
void imc_close_notify( const string& host )
{
   imc_packet *p = new imc_packet( "*", "close-notify", "*@*" );
   p->data << "host=" << host;
   p->forward();
}

/* send a keepalive to everyone */
PFUN( imc_send_keepalive )
{
   imc_packet *p;

   if( q )
      p = new imc_packet( "*", "is-alive", q->from );
   else
      p = new imc_packet( "*", "is-alive", packet );
   p->data << "versionid=" << escape_string( IMC_VERSIONID );
   p->data << " networkname=" << escape_string( siteinfo->network );
   p->data << " url=" << escape_string( siteinfo->url );
   p->data << " sha256=1";
   p->forward();
}

void iced_gannounce( const char *fmt, ... )
{
   char buf[IMC_DATA_LENGTH];
   va_list ap;

   strlcpy( buf, "announces that ", IMC_DATA_LENGTH );

   va_start( ap, fmt );
   vsnprintf( buf, IMC_DATA_LENGTH, fmt, ap );
   va_end( ap );

   imc_send_emote( 15, buf, "*" );
}

void iced_privmsg( imc_channel *c, imc_packet *p, const string& exclude )
{
   string active = c->active, v2;
   imc_packet *q;

   while( !active.empty() )
   {
      active = one_argument( active, v2 );

      if( ( exclude.empty() || str_cmp( v2, exclude ) ) && imc_find_reminfo( v2 ) )
      {
         q = new imc_packet( p->from, p->type, "*@" + v2 );
         q->from = p->from;
         q->i.from = p->i.from;
         q->data << p->data.str();
         q->forward();
      }
   }
   deleteptr( p );
}

void iced_announce( imc_channel *c, const char *fmt, ... )
{
   string txt;
   va_list ap;
   char buf[IMC_DATA_LENGTH];

   txt = "announces that ";
  
   va_start( ap, fmt );
   vsnprintf( buf, IMC_DATA_LENGTH, fmt, ap );
   va_end( ap );

   txt += buf;

   if( !c->open )
   {
      imc_packet *p = new imc_packet( "ICE", "ice-msg-r", "*" );
      p->data << "realfrom=ICE@" << siteinfo->name;
      p->data << " text=" << escape_string( txt );
      p->data << " channel=" << c->name;
      p->data << " emote=1";

      iced_privmsg( c, p, "" );
   }
   else
   {
      imc_packet *p = new imc_packet( "ICE", "ice-msg-b", "*" );
      p->data << "text=" << escape_string( txt );
      p->data << " channel=" << c->name;
      p->data << " emote=1";

      p->forward();
   }
}

/* send a keepalive request to everyone - shogar */
void imc_request_keepalive( void )
{
   imc_packet *p = new imc_packet( "*", "keepalive-request", "*@*" );
   p->forward();
}

void fixactive( imc_channel *c )
{
   string invited = c->invited;
   string operators = c->operators;
   string buf, v3;

   if( c->open )
   {
      c->active = "";
      return;
   }

   buf = "";
   while( !invited.empty() )
   {
      invited = one_argument( invited, v3 );

      if( !hasname( buf, imc_mudof( v3 ) ) )
         addname( buf, imc_mudof( v3 ) );
   }

   while( !operators.empty() )
   {
      operators = one_argument( operators, v3 );

      if( !hasname( buf, imc_mudof( v3 ) ) )
         addname( buf, imc_mudof( v3 ) );
   }

   if( !hasname( buf, imc_mudof( c->owner ) ) )
      addname( buf, imc_mudof( c->owner ) );

   c->active = buf;
}

/* update a channel */
void iced_update( imc_channel *c, const string& to )
{
   imc_packet *p;

   fixactive( c );

   if( !c->open )
   {
      string active = c->active;

      if( !to.empty() )
      {
         if( c->active.find( imc_mudof( to ) ) != string::npos )
         {
            p = new imc_packet( "ICE", "ice-update", to );
            p->data << "channel=" << c->name;
            p->data << " owner=" << c->owner;
            p->data << " policy=" << ( c->open ? "open" : "private" );
            if( !c->operators.empty() )
               p->data << " operators=" << escape_string( c->operators );
            if( !c->invited.empty() )
               p->data << " invited=" << escape_string( c->invited );
            if( !c->excluded.empty() )
               p->data << " excluded=" << escape_string( c->excluded );
            if( !c->level.empty() )
               p->data << " level=" << c->level;
            if( !c->lname.empty() )
               p->data << " localname=" << c->lname;
            p->forward();
         }
      }
      else
      {
         string v2;
         while( !active.empty() )
         {
            active = one_argument( active, v2 );

            if( imc_find_reminfo( v2 ) )
            {
               p = new imc_packet( "ICE", "ice-update", "*@" + v2 );
               p->data << "channel=" << c->name;
               p->data << " owner=" << c->owner;
               p->data << " policy=" << ( c->open ? "open" : "private" );
               if( !c->operators.empty() )
                  p->data << " operators=" << escape_string( c->operators );
               if( !c->invited.empty() )
                  p->data << " invited=" << escape_string( c->invited );
               if( !c->excluded.empty() )
                  p->data << " excluded=" << escape_string( c->excluded );
               if( !c->level.empty() )
                  p->data << " level=" << c->level;
               if( !c->lname.empty() )
                  p->data << " localname=" << c->lname;
               p->forward();
            }
         }
      }
   }

   else
   {
      p = new imc_packet( "ICE", "ice-update", !to.empty() ? to : "*@*" );
      p->data << "channel=" << c->name;
      p->data << " owner=" << c->owner;
      p->data << " policy=" << ( c->open ? "open" : "private" );
      if( !c->operators.empty() )
         p->data << " operators=" << escape_string( c->operators );
      if( !c->invited.empty() )
         p->data << " invited=" << escape_string( c->invited );
      if( !c->excluded.empty() )
         p->data << " excluded=" << escape_string( c->excluded );
      if( !c->level.empty() )
         p->data << " level=" << c->level;
      if( !c->lname.empty() )
         p->data << " localname=" << c->lname;
      p->forward();
   }
   logfile << "Channel update for " << c->name << " sent to " << ( !to.empty() ? to : "entire network" ) << endl;
}

void iced_send_destroy( const string& cname, const string& to )
{
   imc_packet *p = new imc_packet( "ICE", "ice-destroy", !to.empty() ? to : "*" );
   p->data << "channel=" << cname;
   p->forward();
}

void send_reminfo_destroy( const string& mudname )
{
   imc_packet *p = new imc_packet( "ICE", "reminfo-destroy", "*@$" );
   p->data << "mudname=" << mudname;
   p->forward();
}

/* send an emote out on a channel */
void imc_send_emote( int channel, const string& argument, const string& to )
{
   imc_packet *p = new imc_packet( "ICE", "emote", "*@*" );
   p->data << "channel=" << channel;
   p->data << " text=" << escape_string( argument );
   p->forward();
}

/* send a tell to a remote player */
void imc_send_tell( const string& to, const string& argument )
{
   if( imc_mudof(to) == "*" )
      return; /* don't let them do this */

   imc_packet *p = new imc_packet( "ICE", "tell", to );
   p->data << "text=" << escape_string( argument );
   p->data << " isreply=1";
   p->forward();
}

/* respond to a who request with the given data */
void imc_send_whoreply( const string& to, const string& data )
{
   if( imc_mudof(to) == "*" )
      return; /* don't let them do this */

   imc_packet *p = new imc_packet( "*", "who-reply", to );
   p->data << "text=" << escape_string( data );
   p->forward();
}

map<string,string> imc_getData( string packet )
{
   map<string,string> dataMap;
   string::size_type iEqual = 0;

   dataMap.clear();

   while( ( iEqual = packet.find( '=' ) ) != string::npos )
   {
      string sKey = packet.substr( 0, iEqual );

      if( iEqual < ( packet.length() -1 ) )
         packet = packet.substr( iEqual+1, packet.length() );
      else
      {
         sKey.clear();
         break;
      }
      strip_lspace( sKey );
      strip_lspace( packet );
      string sValue = parseword( packet );
      dataMap[sKey] = unescape_string( sValue );
   }
   return dataMap;
}

void imc_addnewmud( connection *c, const string& name, const string& pw, const string& spw, const string& encrypt )
{
   imc_info *i;
   ostringstream response;
   list<imc_reminfo*>::iterator rin;

   /* Check the rest of the remote connections to be sure they aren't on another server already. */
   for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin )
   {
      imc_reminfo *r = *rin;

      if( r->name == name )
      {
         logfile << name << " is already on the network. Autosetup failed." << endl;
         response << "autosetup " << siteinfo->name << " reject connected";
         write_buffer( c, response.str() );
         c->disconnect = true;
         return;
      }
   }

   /* Reject immediately if this server is set private */
   if( siteinfo->priv == true )
   {
      logfile << "Privacy: Rejecting autosetup from " << name << endl;
      response << "autosetup " << siteinfo->name << " reject private";
      write_buffer( c, response.str() );
      c->disconnect = true;
      return;
   }

   /* Reject if the server has reached it's connection limit.
    * Have it bounce to a new one it knows about at some point.
    */
   if( siteinfo->connects >= siteinfo->maxconnects )
   {
      logfile << "Rejecting autosetup from " << name << " - Server is full" << endl;
      response << "autosetup " << siteinfo->name << " reject full";
      write_buffer( c, response.str() );
      c->disconnect = true;
      return;
   }

   /* Check for a banned connection here */
   list<string>::iterator ban;
   for( ban = banlist.begin(); ban != banlist.end(); ++ban )
   {
      string b = *ban;

      if( b == c->host )
      {
         logfile << name << ": Banned connection attempt." << endl;
         response << "autosetup " << siteinfo->name << " reject ban";
         write_buffer( c, response.str() );
         c->disconnect = true;
         return;
      }
   }

   /* Autosetup was accepted */
   if( !encrypt.empty() )
   {
      if( encrypt == "MD5" )
         response << "autosetup " << siteinfo->name << " accept " << siteinfo->network << " MD5-SET";
      else
         response << "autosetup " << siteinfo->name << " accept " << siteinfo->network << " SHA256-SET";
   }
   else
      response << "autosetup " << siteinfo->name << " accept " << siteinfo->network;
   write_buffer( c, response.str() );

   i = imc_new_info();
   i->name      = name;
   i->clientpw  = pw;
   i->serverpw  = spw;

   if( !encrypt.empty() )
   {
      if( encrypt == "MD5" )
      {
         i->md5 = true;
         i->sha256 = false;
      }
      else
      {
         i->sha256 = true;
         i->md5 = false;
      }
   }
   else
   {
      i->md5 = false;
      i->sha256 = false;
   }

   /* register them */
   i->conn = c;
   i->login_time = imc_now;
   i->last_connected = imc_now;
   c->state      = CONN_COMPLETE;
   c->info       = i;
   ++siteinfo->connects;
   imc_saveconfig();

   imc_cancel_event( ev_login_timeout, c );
   imc_cancel_event( ev_close_notify, i->name.c_str() );
   return;
}

void send_sha256_auth( connection *c, const string& name )
{
   ostringstream auth;

   c->auth_value = (unsigned long)imc_now;
   auth << "SHA256-AUTH-INIT " << siteinfo->name << " " << (unsigned long)imc_now;
   write_buffer( c, auth.str() );
   logfile << imc_getconnectname(c) << ": Sending SHA-256 Authentication request to " << name << endl;
   return;
}

void send_md5_auth( connection *c, const string& name )
{
   ostringstream auth;

   c->auth_value = (unsigned long)imc_now;
   auth << "MD5-AUTH-INIT " << siteinfo->name << " " << (unsigned long)imc_now;
   write_buffer( c, auth.str() );
   logfile << imc_getconnectname(c) << ": Sending MD5 Authentication request to " << name << endl;
   return;
}

/* handle a password from a client */
void clientpassword( connection *c, string& argument )
{
   string type, mudname, password, version, command, data, encryption;
   imc_info *i;
   ostringstream response;

   argument = one_argument( argument, type );
   argument = one_argument( argument, mudname );

   if( type.empty() || mudname.empty() )
   {
      logfile << "Invalid packet format from " << c->host << endl;
      write_buffer( c, "Missing required packet data." );
      do_write( c );
      c->disconnect = true;
      return;
   }

   argument = one_argument( argument, password );
   argument = one_argument( argument, version );
   argument = one_argument( argument, command );
   argument = one_argument( argument, data );
   argument = one_argument( argument, encryption );

   /* Verify the version.
    * Only version 2 is valid, so toss anything else.
    * There is no plan to actually raise this anyway.
    */
   if( !version.empty() )
   {
      if( str_cmp( version, "version=2" ) )
      {
         logfile << c->host << ": Unsupported version: " << version << endl;
         write_buffer( c, "Client version is unsupported." );
         do_write( c );
         c->disconnect = true;
         return;
      }
   }

   /* Why do I get the feeling this is going to be a VERY BAD IDEA(tm) */
   if( type == "COMPRESSME" )
   {
      if( !siteinfo->can_compress )
      {
         write_buffer( c, "COMPRESSION UNAVAILABLE" );
         do_write( c );
         return;
      }
      write_buffer( c, "COMPRESSION ENABLED" );
      do_write( c );

      c->is_compressing = true;
      return;
   }

   /* Check for a banned connection here */
   list<string>::iterator ban;
   for( ban = banlist.begin(); ban != banlist.end(); ++ban )
   {
      string b = *ban;
      if( b == c->host )
      {
         logfile << c->host << ": Banned mud attempting to connect: " << mudname << endl;
         write_buffer( c, "Your mud is banned." );
         do_write( c );
         c->disconnect = true;
         return;
      }
   }

   i = imc_getinfo( mudname );

   if( type == "MD5-AUTH-REQ" )
   {
      if( !i )
      {
         logfile << imc_getconnectname(c) << ": Unregistered MD5 connection attempt by " << mudname << endl;
         write_buffer( c, "Unregistered MD5 connection attempt." );
         do_write( c );
         c->disconnect = true;
         return;
      }
      send_md5_auth( c, i->name );
      return;
   }
   else if( type == "MD5-AUTH-RESP" )
   {
      char pwd[IMC_DATA_LENGTH];
      char *cryptpwd;

      if( !i )
      {
         logfile << imc_getconnectname(c) << ": Bloody hell! How is " << mudname << " not already registered?" << endl;
         write_buffer( c, "Possible malformed packet sent." );
         do_write( c );
         c->disconnect = true;
         return;
      }

      if( !i->md5 )
      {
         logfile << imc_getconnectname(c) << ": " << i->name << " attempted to use MD5 authentication but is not configured for it." << endl;
         write_buffer( c, "MD5 is not configured." );
         do_write( c );
         c->disconnect = true;
         return;
      }

      if( password.empty() )
      {
         logfile << imc_getconnectname(c) << ": MD5 Authentication failure: No password was returned by " << i->name << endl;
         write_buffer( c, "No MD5 password provided." );
         do_write( c );
         c->disconnect = true;
         return;
      }

      /* Lets encrypt this bastard now! */
      snprintf( pwd, IMC_DATA_LENGTH, "%ld%s%s", c->auth_value, i->clientpw.c_str(), i->serverpw.c_str() );
      cryptpwd = md5_crypt( pwd );

      if( !STR_CEQL( cryptpwd, password.c_str() ) )
      {
         logfile << imc_getconnectname(c) << ": MD5 Authentication failure: Invalid password response by " << i->name << endl;
         write_buffer( c, "Wrong MD5 password provided." );
         do_write( c );
         c->disconnect = true;
         return;
      }

      /* Well, ok. Lets at least see if they're calling from the same IP. If not, bounce it. */
      if( i->conn )
      {
         if( i->conn->host != c->host )
         {
            logfile << i->name << ": Already connected." << endl;
            write_buffer( c, "Mud is already connected." );
            do_write( c );
            c->disconnect = true;
            return;
         }
         else
            do_close( i->conn );
      }

      /* register them */
      i->conn  = c;
      c->state = CONN_COMPLETE;
      c->info  = i;

      /* send our response */
      response << "MD5-AUTH-APPR " << siteinfo->name << " " << siteinfo->network << " version=2";
      write_buffer( c, response.str() );

      logfile << imc_getconnectname(c) << ": MD5 Authentication complete." << endl;

      c->info->login_time = imc_now;
      c->info->last_connected = imc_now;
      imc_cancel_event( ev_login_timeout, c );
      imc_cancel_event( ev_close_notify, i->name.c_str() );
      return;
   }
   else if( type == "SHA256-AUTH-REQ" )
   {
      if( !i )
      {
         logfile << imc_getconnectname(c) << ": Unregistered SHA-256 connection attempt by " << mudname << endl;
         write_buffer( c, "Unregistered SHA-256 connection attempt." );
         do_write( c );
         c->disconnect = true;
         return;
      }
      send_sha256_auth( c, i->name );
      return;
   }
   else if( type == "SHA256-AUTH-RESP" )
   {
      char pwd[IMC_DATA_LENGTH];
      char *cryptpwd;

      if( !i )
      {
         logfile << imc_getconnectname(c) << ": Bloody hell! How is " << mudname << " not already registered?" << endl;
         write_buffer( c, "Possible malformed packet sent." );
         do_write( c );
         c->disconnect = true;
         return;
      }

      if( !i->sha256 && !i->md5 ) // Hack to accomadate upgraders from MD5
      {
         logfile << imc_getconnectname(c) << ": " << i->name << " attempted to use SHA-256 authentication but is not configured for it." << endl;
         write_buffer( c, "SHA-256 is not configured." );
         do_write( c );
         c->disconnect = true;
         return;
      }

      if( password.empty() )
      {
         logfile << imc_getconnectname(c) << ": SHA-256 Authentication failure: No password was returned by " << i->name << endl;
         write_buffer( c, "No SHA-256 password provided." );
         do_write( c );
         c->disconnect = true;
         return;
      }

      /* Lets encrypt this bastard now! */
      snprintf( pwd, IMC_DATA_LENGTH, "%ld%s%s", c->auth_value, i->clientpw.c_str(), i->serverpw.c_str() );
      cryptpwd = sha256_crypt( pwd );

      if( !STR_CEQL( cryptpwd, password.c_str() ) )
      {
         logfile << imc_getconnectname(c) << ": SHA-256 Authentication failure: Invalid password response by " << i->name << endl;
         write_buffer( c, "Wrong SHA-256 password provided." );
         do_write( c );
         c->disconnect = true;
         return;
      }

      /* Well, ok. Lets at least see if they're calling from the same IP. If not, bounce it. */
      if( i->conn )
      {
         if( i->conn->host != c->host )
         {
            logfile << i->name << ": Already connected." << endl;
            write_buffer( c, "Mud is already connected." );
            do_write( c );
            c->disconnect = true;
            return;
         }
         else
            do_close( i->conn );
      }

      /* register them */
      i->conn  = c;
      c->state = CONN_COMPLETE;
      c->info  = i;

      /* send our response */
      response << "SHA256-AUTH-APPR " << siteinfo->name << " " << siteinfo->network << " version=2";
      write_buffer( c, response.str() );

      logfile << imc_getconnectname(c) << ": SHA-256 Authentication complete." << endl;

      c->info->login_time = imc_now;
      c->info->last_connected = imc_now;
      imc_cancel_event( ev_login_timeout, c );
      imc_cancel_event( ev_close_notify, i->name.c_str() );
      if( !i->sha256 && i->md5 ) // Another hack to accomadate MD5 upgrade
      {
         i->sha256 = true;
         i->md5 = false;
         imc_saveconfig();
      }
      return;
   }
   else if( type != "PW" )
   {
      logfile << imc_getconnectname(c) << ": Invalid authentication packet received from " << mudname << endl;
      write_buffer( c, "Invalid authentication packet sent." );
      do_write( c );
      c->disconnect = true;
      return;
   }

   /* They don't exist - lets add them to the server config if they supplied all the needed data */
   if( !i && !command.empty() && command == "autosetup" )
   {
      if( !data.empty() )
      {
         if( !encryption.empty() )
            imc_addnewmud( c, mudname, password, data, encryption );
         else
            imc_addnewmud( c, mudname, password, data, "" );
         return;
      }
      logfile << imc_getconnectname(c) << ": malformed autosetup command received from " << mudname << endl;
      response << "autosetup " << siteinfo->name << " reject data format invalid";
      write_buffer( c, response.str() );
      do_write( c );
      c->disconnect = true;
      return;
   }

   if( !i )
   {
      logfile << imc_getconnectname(c) << ": Unregistered connection attempt for " << mudname << endl;
      write_buffer( c, "No autosetup. Consider upgrading." );
      do_write( c );
      c->disconnect = true;
      return;
   }

   /* They're in the list already. Authenticate them. */
   if( i->md5 )
   {
      logfile << imc_getconnectname(c) << ": MD5 authentication failure for " << i->name << ": Required authorization not provided." << endl;
      write_buffer( c, "MD5 authentication is required." );
      do_write( c );
      c->disconnect = true;
      return;
   }
   if( i->sha256 )
   {
      logfile << imc_getconnectname(c) << ": SHA-256 authentication failure for " << i->name << ": Required authorization not provided." << endl;
      write_buffer( c, "SHA-256 authentication is required." );
      do_write( c );
      c->disconnect = true;
      return;
   }

   if( i->clientpw != password )
   {
      logfile << imc_getconnectname(c) << ": Plain text password failure for " << i->name << endl;
      write_buffer( c, "Invalid client password sent." );
      do_write( c );
      c->disconnect = true;
      return;
   }

   /* Well, ok. Lets at least see if they're calling from the same IP. If not, bounce it. */
   if( i->conn )
   {
      if( i->conn->host != c->host )
      {
         logfile << i->name << ": Already connected." << endl;
         write_buffer( c, "Mud is already connected." );
         do_write( c );
         c->disconnect = true;
         return;
      }
      else
         do_close( i->conn );
   }

   /* register them */
   i->conn     = c;
   c->state    = CONN_COMPLETE;
   c->info     = i;
   /* Once MD5 is set, it will be an enforced requirement from then on unless the server is told otherwise. */
   if( !encryption.empty() && encryption == "MD5" )
   {
      i->md5 = true;
      i->sha256 = false;
      imc_saveconfig();
   }
   else if( !encryption.empty() && encryption == "SHA256" )
   {
      i->sha256 = true;
      i->md5 = false;
      imc_saveconfig();
   }

   /* send our response */
   if( i->md5 )
      response << "PW " << siteinfo->name << " " << i->serverpw << " version=2 " << siteinfo->network << " MD5-SET";
   else if( i->sha256 )
      response << "PW " << siteinfo->name << " " << i->serverpw << " version=2 " << siteinfo->network << " SHA256-SET";
   else
      response << "PW " << siteinfo->name << " " << i->serverpw << " version=2 " << siteinfo->network;
   write_buffer( c, response.str() );

   logfile << imc_getconnectname(c) << ": Standard authentication complete." << endl;

   c->info->login_time = imc_now;
   c->info->last_connected = imc_now;
   imc_cancel_event( ev_login_timeout, c );
   imc_cancel_event( ev_close_notify, i->name.c_str() );
}

void finalize_connection( connection *c, imc_info *i )
{
   if( i->conn )	/* kill old connections */
      do_close( i->conn );

   i->conn = c;

   c->state = CONN_COMPLETE;

   logfile << imc_getconnectname(c) << ": connected." << endl;

   c->info->login_time = imc_now;
   c->info->last_connected = imc_now;
   imc_cancel_event( ev_login_timeout, c );
   imc_cancel_event( ev_reconnect, c->info );
   imc_cancel_event( ev_close_notify, i->name.c_str() );
   imc_request_keepalive();
   return;
}

/* 
 * Handles authentication for 2.x and 1.x clients the server connected out to.
 */
void serverpassword( connection *c, string& packet )
{
   string type, servername, password, version;
   ostringstream response;
   imc_info *i;

   packet = one_argument( packet, type );
   packet = one_argument( packet, servername );
   packet = one_argument( packet, password );
   packet = one_argument( packet, version );

   if( type.empty() || servername.empty() || password.empty() )
   {
      logfile << "Invalid packet format from " << c->host << endl;
      write_buffer( c, "Missing required packet data." );
      c->disconnect = true;
      return;
   }

   /* Verify the version.
    * Only version 2 is valid, so toss anything else.
    * There is no plan to actually raise this anyway.
    */
   if( !version.empty() )
   {
      if( str_cmp( version, "version=2" ) )
      {
         logfile << c->host << ": unsupported version!" << endl;
         write_buffer( c, "Client version is unsupported." );
         c->disconnect = true;
         return;
      }
   }

   if( !( i = imc_getinfo( servername ) ) )
   {
      logfile << imc_getconnectname(c) << ": Unresgistered connection attempt: " << servername << endl;
      write_buffer( c, "Your connection isn't registered." );
      c->disconnect = true;
      return;
   }

   if( type == "MD5-AUTH-INIT" )
   {
      char pwd[IMC_DATA_LENGTH];
      char *cryptpwd;
      long auth_value = 0;

      if( password.empty() )
      {
         logfile << "MD5 Authentication failure: No auth_value was returned by " << servername << endl;
         write_buffer( c, "Missing MD5 auth value." );
         c->disconnect = true;
         return;
      }

      /* Lets encrypt this bastard now! */
      auth_value = atol( password.c_str() );
      snprintf( pwd, IMC_DATA_LENGTH, "%ld%s%s", auth_value, i->clientpw.c_str(), i->serverpw.c_str() );
      cryptpwd = md5_crypt( pwd );

      response << "MD5-AUTH-RESP " << siteinfo->name << " " << cryptpwd << " version=2";
      write_buffer( c, response.str() );
      return;
   }

   /* MD5 response is pretty simple. */
   if( type == "MD5-AUTH-APPR" )
   {
      logfile << imc_getconnectname(c) << ": MD5 Authentication completed." << endl;
      finalize_connection( c, i );
      return;
   }

   if( type == "SHA256-AUTH-INIT" )
   {
      char pwd[IMC_DATA_LENGTH];
      char *cryptpwd;
      long auth_value = 0;

      if( password.empty() )
      {
         logfile << "SHA256 Authentication failure: No auth_value was returned by " << servername << endl;
         write_buffer( c, "Missing SHA256 auth value." );
         c->disconnect = true;
         return;
      }

      /* Lets encrypt this bastard now! */
      auth_value = atol( password.c_str() );
      snprintf( pwd, IMC_DATA_LENGTH, "%ld%s%s", auth_value, i->clientpw.c_str(), i->serverpw.c_str() );
      cryptpwd = sha256_crypt( pwd );

      response << "SHA256-AUTH-RESP " << siteinfo->name << " " << cryptpwd << " version=2";
      write_buffer( c, response.str() );
      return;
   }

   /* SHA256 response is pretty simple. */
   if( type == "SHA256-AUTH-APPR" )
   {
      logfile << imc_getconnectname(c) << ": SHA256 Authentication completed." << endl;
      finalize_connection( c, i );
      return;
   }

   if( type != "PW" )
   {
      logfile << imc_getconnectname(c) << ": non-PW password packet in serverpassword" << endl;
      write_buffer( c, "Invalid authentication packet sent." );
      c->disconnect = true;
      return;
   }

   if( password != i->serverpw )
   {
      logfile << imc_getconnectname(c) << ": Invalid plain text authentication for " << servername << endl;
      write_buffer( c, "Invalid authentication password sent." );
      c->disconnect = true;
      return;
   }
   finalize_connection( c, i );
}

/* get some IMC stats, return a string describing them */
string imc_getstats( const string& choice )
{
   ostringstream buf;
   int evcount = 0;
   imc_event *ev = imc_event_list;

   if( choice.empty() || choice == "network" )
   {
      while( ev )
      {
         ++evcount;
         ev = ev->next;
      }

      buf << "~WGeneral IMC Statistics" << endl;
      buf << "~cReceived packets       : ~W" << imc_stats.rx_pkts << endl;
      buf << "~cReceived bytes         : ~W" << imc_stats.rx_bytes << endl;
      buf << "~cCompressed bytes recvd : ~W" << imc_stats.rx_cmpbytes << endl;
      buf << "~cBytes saved            : ~W" << imc_stats.rx_bytessaved << endl;
      buf << "~cTransmitted packets    : ~W" << imc_stats.tx_pkts << endl;
      buf << "~cTransmitted bytes      : ~W" << imc_stats.tx_bytes << endl;
      buf << "~cCompressed bytes sent  : ~W" << imc_stats.tx_cmpbytes << endl;
      buf << "~cBytes saved            : ~W" << imc_stats.tx_bytessaved << endl;
      buf << "~cMaximum packet size    : ~W" << imc_stats.max_pkt << endl;
      buf << "~cPending events         : ~W" << evcount << endl;
      buf << "~cSequence drops         : ~W" << imc_stats.sequence_drops << endl;
      buf << "~cLast IMC Boot          : ~W" << ctime( &imc_boot ) << endl;
      return buf.str();
   }

   if( choice == "general" )
   {
      buf << "~WSite Information:" << endl;
      buf << "~cName       :~W " << siteinfo->name << endl;
      buf << "~cIMC Version:~W " << IMC_VERSIONID << endl;
      buf << "~cAddress    :~W " << siteinfo->url << endl;
      return buf.str();
   }
   return "Bad invocation of imc_getstats."; 
}

string process_directconnection_list( void )
{
   ostringstream buf;
   string strtime;

   buf << setiosflags( ios::left );
   buf << "~CDirect Connections to " << siteinfo->name << ":" << endl;
   buf << "~W" << setw(20) << "Name" << setw(15) << "Conn Type" << setw(14) << "Conn State" << "Last Connected" << endl;

   list<imc_info*>::iterator inf;
   for( inf = infolist.begin(); inf != infolist.end(); ++inf )
   {
      imc_info *i = *inf;

      if( i->conn )
         strtime = ctime( &(i->login_time) );
      else
         strtime = ctime( &(i->last_connected) );
      strtime = strtime.substr( 0, strtime.length()-1 );
      buf << endl << "~c" << setw(20) << i->name << setw(17) << ( i->server ? "~CServer" : "~BClient" );
      buf << setw(16) << ( i->conn ? "~GOnline" : "~ROffline" );
      buf << "~c" << strtime;
   }
   buf << endl;
   return buf.str();
}

PFUN( imc_recv_who )
{
   map<string,string> keymap = imc_getData( packet );

   if( keymap["type"] == "who" )
      imc_send_whoreply( q->from, process_directconnection_list() );
   else if( keymap["type"] == "info" )
      imc_send_whoreply( q->from, imc_getstats( "general" ) );
   else if( keymap["type"] == "istats" )
      imc_send_whoreply( q->from, imc_getstats( "network" ) );
   else if( keymap["type"] == "help" || keymap["type"] == "services" )
      imc_send_whoreply( q->from,
	   "~WAvailable imcminfo types:\r\n"
	   "~chelp       ~W- ~Cthis list\r\n"
         "~cwho        ~W- ~Clist directly connected muds\r\n"
	   "~cinfo       ~W- ~Cserver information\r\n"
	   "~cistats     ~W- ~Cnetwork traffic statistics\r\n" );
   else
      imc_send_whoreply( q->from, "Sorry, no information of that type is available." );
}

/* called when a keepalive has been received */
PFUN( imc_recv_isalive )
{
   imc_reminfo *p;
   map<string,string> keymap = imc_getData( packet );

   if( imc_mudof( q->from ) == siteinfo->name )
      return;

   /* this should never fail, imc.c should create an entry if one doesn't exist (in the path update code) */
   if( !( p = imc_find_reminfo( imc_mudof( q->from ) ) ) )
      return;

   if( keymap["versionid"] != p->version )
      p->version = keymap["versionid"];

   /* Not sure how or why this might change, but just in case it does */
   if( keymap["networkname"] != p->network )
      p->network = keymap["networkname"];

   imc_info *i = imc_getinfo( p->name );

   if( keymap["url"] != p->url )
   {
      p->url = keymap["url"];
      if( i )
         i->url = keymap["url"];
   }
   /* If this causes a breakage.... */
   p->sha256 = atoi( keymap["sha256"].c_str() );

   if( !keymap["host"].empty(  ) )
      p->host = keymap["host"];

   if( !keymap["port"].empty(  ) )
      p->port = keymap["port"];
}

PFUN( imc_recv_reminfo_destroy )
{
   imc_reminfo *p;
   map<string,string> keymap = imc_getData( packet );

   /* Do nothing if it's not in the list */
   if( !( p = imc_find_reminfo( keymap["mudname"] ) ) )
      return;

   logfile << "Destroying remote info for " << p->name << " by request of " << q->from << endl;
   imc_delete_reminfo( p );
}

PFUN( imc_recv_laston )
{
   map<string,string> keymap = imc_getData( packet );
   ostringstream buf;
   string strtime;
   bool found = false;

   if( keymap["username"].empty() )
   {
      imc_send_tell( q->from, "You must specify a username, or all, to get a listing." );
      return;
   }

   list<imc_laston*>::iterator lst;
   for( lst = lastonlist.begin(); lst != lastonlist.end(); ++lst )
   {
      imc_laston *l = *lst;

      if( !str_cmp( l->name, keymap["username"] ) )
      {
         strtime = ctime( &(l->last_time) );
         strtime = strtime.substr( 0, strtime.length()-1 );
         buf << l->name << " was last seen using " << l->chan << " on " << strtime;
         imc_send_tell( q->from, buf.str() );
         return;
      }
   }

   for( lst = lastonlist.begin(); lst != lastonlist.end(); ++lst )
   {
      imc_laston *l = *lst;

      if( !str_cmp( keymap["username"], "all" ) || !str_cmp( imc_nameof( l->name ), keymap["username"] ) )
      {
         strtime = ctime( &(l->last_time) );
         strtime = strtime.substr( 0, strtime.length()-1 );
         buf << l->name << " was last seen using " << l->chan << " on " << strtime;
         imc_send_tell( q->from, buf.str() );
         buf.str("");
         found = true;
      }
   }

   if( !found )
   {
      buf << keymap["username"] << " has not been seen online recently.";
      imc_send_tell( q->from, buf.str() );
   }
}

/* Only updated by broadcast channel messages */
void imc_update_laston( const string& from, const string& chan )
{
   imc_laston *laston;
   list<imc_laston*>::iterator lst;

   for( lst = lastonlist.begin(); lst != lastonlist.end(); ++lst )
   {
      imc_laston *l = *lst;

      if( !str_cmp( from, l->name ) )
      {
         l->name = from;
         l->chan = chan;
         l->last_time = imc_now;
         return;
      }
   }
   laston = new imc_laston;
   laston->name = from;
   laston->chan = chan;
   laston->last_time = imc_now;
   lastonlist.push_back( laston );
}

/* see if someone can talk on a channel - lots of string stuff here! */
bool ice_audible( imc_channel *c, const string& who )
{
   if( !c || who.empty() )
      return false;

   /* owners and operators always can */
   if( c->owner == who || hasname( c->operators, who ) )
      return true;

   /* ICE locally can use any channel */
   if( imc_nameof(who) == "ICE" && imc_mudof(who) == siteinfo->name )
      return true;

   if( c->open )
   {
      /* open policy. default yes. override with excludes, then invites */
      if( ( hasname( c->excluded, who ) || hasname( c->excluded, imc_mudof(who) ) )
       && !hasname( c->invited, who ) && !hasname( c->invited, imc_mudof(who) ) )
         return false;
      else
         return true;
   }

   /* closed or private. default no, override with invites, then excludes */
   if( ( hasname( c->invited, who ) || hasname( c->invited, imc_mudof(who) ) )
    && !hasname( c->excluded, who ) && !hasname( c->excluded, imc_mudof(who) ) )
      return true;
   else
      return false;
}

/* broadcast message - complain if its a private channel */
PFUN( iced_recv_broadcast )
{
   imc_channel *c;
   imc_packet *p;
   map<string,string> keymap = imc_getData( packet );

   if( keymap["channel"].find( ':' ) == string::npos )
      return;

   /* If this server isn't the channel owner... well... we'll just have to assume this is a legal move */
   if( str_cmp( ice_mudof( keymap["channel"] ), siteinfo->name ) )
   {
      imc_update_laston( q->from, keymap["channel"] );
      return;
   }

   if( !( c = iced_findchannel( keymap["channel"] ) ) )
   {
      imc_send_tell( q->from, "You're trying to talk on a nonexistant channel." );
      iced_send_destroy( keymap["channel"], imc_mudof( q->from ) );
      q->stop = true;
      return;
   }

   if( !ice_audible( c, q->from ) )
   {
      imc_send_tell( q->from, "You're trying to talk on a channel that you don't have access to." );
      iced_send_destroy( c->name, imc_mudof( q->from ) );
      q->stop = true;
      return;
   }

   if( !c->open )
   {
      imc_send_tell( q->from, "Misconfiguration, sending broadcast message on private channel. Try again." );
      iced_update( c, imc_mudof( q->from ) );
      q->stop = true;
      return;
   }

   imc_update_laston( q->from, keymap["channel"] );

   if( keymap["echo"] == "0" )
      return;

   p = new imc_packet( imc_nameof( q->from ) + "-" + imc_mudof( q->from ), "ice-msg-b", "*@" + imc_mudof( q->from ) );
   p->data << "text=" << escape_string( keymap["text"] );
   p->data << " channel=" << keymap["channel"];
   p->data << " sender=" << q->from;
   p->data << " emote=" << keymap["emote"];
   p->forward();
}

PFUN( iced_recv_icerefresh )
{
   list<imc_channel*>::iterator chn;

   for( chn = chanlist.begin(); chn != chanlist.end(); ++chn )
   {
      imc_channel *c = (*chn);

      iced_update( c, q->from );
   }
}

PFUN( imc_recv_closenotify )
{
   imc_reminfo *r;
   imc_info *i;
   map<string,string> keymap = imc_getData( packet );

   // More or less: Bullshit, I'm not closed you dumbass, I'm right here! But give it about 2 seconds or so.
   if( !str_cmp( keymap["host"], siteinfo->name ) )
   {
      imc_add_event( 2, ev_keepalive, NULL );
      return;
   }

   if( !( r = imc_find_reminfo( keymap["host"] ) ) )
      return;

   r->expired = true;

   if( !( i = imc_getinfo( r->name ) ) )
      return;

   /* Give it one shot at a 30 second reconnect, don't pursue it if this fails though. */
   imc_add_event( 30, ev_reconnect, i );
}

const char *channel_accesstring[4] = { "none", "creator", "operator", "owner" };

int iced_getaccess( imc_channel *c, const string& from )
{
   if( !c )
   {
      if( hasname( siteinfo->chancreators, "*" ) || hasname( siteinfo->chancreators, from )
       || hasname( siteinfo->chancreators, imc_mudof(from) ) )
         return ACCESS_CREATOR;

      // If you're a serveradmin, you have automatic owner rights now.
      // No person strictly a channel admin should trump server admin abilities.
      if( hasname( siteinfo->serveradmins, from ) || hasname( siteinfo->serveradmins, imc_mudof(from) ) )
         return ACCESS_CREATOR;

      return ACCESS_NONE;
   }
   // If you're a serveradmin, you have automatic owner rights now.
   // No person strictly a channel admin should trump server admin abilities.
   else if( from == c->owner 
    || ( hasname( siteinfo->serveradmins, from ) || hasname( siteinfo->serveradmins, imc_mudof(from) ) ) )
      return ACCESS_OWNER;
   else if( hasname( c->operators, from ) )
      return ACCESS_OPERATOR;
   else
      return ACCESS_NONE;
}

/* list commands */
void run_iced_list( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;
   int i;

   buf << endl << "~WAvailable Channel Administration Commands:" << endl;
   buf << "~c" << setw(10) << "Name" << " Access Level" << endl;

   for( i = 0; i < MAX_ICED_COMMAND; ++i )
   {
      buf << "~C" << setw(10) << iced_cmdtable[i].name << " " << channel_accesstring[iced_cmdtable[i].level] << endl;
   }

   buf << "~cYour access level for this channel: ~W" << channel_accesstring[iced_getaccess( c, from )] << endl;
   imc_send_tell( from, buf.str() );
}

/* destroy a channel */
void run_iced_destroy( imc_channel *c, const string& cname, const string& from, const string& data )
{
   logfile << "Channel named " << c->name << " destroyed by " << from << endl;
   iced_gannounce( "the channel called %s has been destroyed by %s.", c->name.c_str(), from.c_str() );

   /* Moved to up here to fix a bug -- Kratas */
   /* send destroy notification */
   iced_send_destroy( c->name, "" );
   imc_send_tell( from, "The channel has been destroyed successfully." );

   /* remove/free the channel from our list */
   chanlist.remove( c );
   deleteptr( c );
}

/* Set default localname */
void run_iced_perm( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;
   int value;

   value = get_permvalue( data.c_str() );
   if( value < PERM_MORT || value > PERM_IMP )
   {
      buf << data << " is not a valid permission setting.";
      imc_send_tell( from, buf.str() );
      return;
   }

   buf << "Default permission level for " << c->name << " has been changed to " << perm_names[value] << ".";
   imc_send_tell( from, buf.str() );

   c->level = perm_names[value];
   iced_update( c, "" );
}

/* Set default localname */
void run_iced_localname( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;

   buf << "Default local name for " << c->name << " has been changed to " << data << ".";
   imc_send_tell( from, buf.str() );

   c->lname = data;
   iced_update( c, "" );
}

/* set channel policy */
void run_iced_policy( imc_channel *c, const string& cname, const string& from, const string& data )
{
   if( data == "open" )
   {
      if( c->open )
      {
         imc_send_tell( from, "This channel already has an open policy." );
         return;
      }

      c->open = true;
      iced_gannounce( "The channel called %s now has an open policy.", c->name.c_str() );
      iced_update( c, "" );
   }
   else if( data == "private" )
   {
      if( !c->open )
      {
         imc_send_tell( from, "This channel already has a private policy." );
         return;
      }

      c->open = false;
      iced_gannounce( "The channel called %s now has a private policy.", c->name.c_str() );
      iced_update( c, "" );
   }
   else
      imc_send_tell( from, "The currently available channel policies are open and private." );
}

/* add operator */
void run_iced_addop( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;

   if( data.find( '@' ) == string::npos )
   {
      imc_send_tell( from, "The name of the operator must be in the format of person@mud." );
      return;
   }

   if( hasname( c->operators, data ) )
   {
      imc_send_tell( from, "That person is already an operator." );
      return;
   }

   addname( c->operators, data );
   fixactive( c );
   buf << data << " is now an operator of " << c->name << ".";
   imc_send_tell( from, buf.str() );

   iced_announce( c, "%s is now an operator for this channel.", data.c_str() );
   iced_update( c, "" );
}

/* remove operator */
void run_iced_removeop( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;

   if( data.find( '@' ) == string::npos )
   {
      imc_send_tell( from, "The name of the operator must be in the format of person@mud." );
      return;
   }

   if( !hasname( c->operators, data ) )
   {
      imc_send_tell( from, "That person is not an operator." );
      return;
   }

   removename( c->operators, data );
   fixactive( c );
   buf << data << " is no longer an operator of " << c->name << ".";
   imc_send_tell( from, buf.str() );
   iced_announce( c, "%s is no longer an operator for this channel.", data.c_str() );
   iced_update( c, "" );
}

/* invite mud or player */
void run_iced_invite( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;

   if( hasname( c->invited, data ) )
   {
      imc_send_tell( from, "That person is already on the invite list for that channel." );
      return;
   }

   addname( c->invited, data );
   fixactive( c );
   buf << data << " has now been invited to " << c->name << ".";
   imc_send_tell( from, buf.str() );
   iced_announce( c, "%s has invited %s to this channel.", from.c_str(), data.c_str() );
   iced_update( c, "" );
}

/* uninvite mud or player */
void run_iced_uninvite( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;

   if( !hasname( c->invited, data ) )
   {
      imc_send_tell( from, "That person is not currently on the invite list for that channel." );
      return;
   }

   removename( c->invited, data );
   fixactive( c );
   buf << data << " is no longer invited on " << c->name << ".";
   imc_send_tell( from, buf.str() );
   iced_announce( c, "%s has uninvited %s from this channel.", from.c_str(), data.c_str() );
   iced_update( c, "" );
}

/* exclude mud or player */
void run_iced_exclude( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;

   if( hasname( c->excluded, data ) )
   {
      imc_send_tell( from, "That person is already on the exclude list for that channel." );
      return;
   }

   addname( c->excluded, data );

   buf << data << " is now excluded from " << c->name << ".";
   imc_send_tell( from, buf.str() );
   iced_announce( c, "%s has excluded %s from this channel.", from.c_str(), data.c_str() );
   iced_update( c, "" );
}

/* unexclude mud or player */
void run_iced_unexclude( imc_channel *c, const string& cname, const string& from, const string& data )
{
   ostringstream buf;

   if( !hasname( c->excluded, data ) )
   {
      imc_send_tell( from, "That person is not on the exclude list for that channel." );
      return;
   }

   removename( c->excluded, data );

   buf << data << " is no longer excluded from " << c->name << ".";
   imc_send_tell( from, buf.str() );
   iced_announce( c, "%s has unexcluded %s from this channel.", from.c_str(), data.c_str() );
   iced_update( c, "" );
}

/* create a channel */
void run_iced_create( imc_channel *c, const string& cname, const string& from, const string& data )
{
   imc_channel *p;
   string lname;

   if( cname.empty() || cname.find( ':' ) == string::npos || str_cmp( ice_mudof(cname), siteinfo->name ) )
   {
      imc_send_tell( from, "You must supply a new channel name in the format of servername:channame" );
      return;
   }

   string::size_type x = cname.find_first_of( ':' );
   lname = cname.substr( x+1, cname.length() );

   p = new imc_channel;
   p->name = cname;
   p->owner = from;
   p->operators.clear();
   p->invited.clear();
   p->excluded.clear();
   p->active.clear();
   p->level = "Admin";
   p->lname = lname;
   p->open = false; /* Channels should be private in the beginning */

   chanlist.push_back( p );
   iced_update( p, "" );

   logfile << "Channel named " << p->name << " created by " << from << " with localname " << lname << endl;
   iced_gannounce( "a channel named %s has been created by %s.", p->name.c_str(), from.c_str() );
   imc_send_tell( from, "The channel has been created successfully." );
}

PFUN( iced_recv_command )
{
   imc_channel *c;
   map<string,string> keymap = imc_getData( packet );
   int i;
   bool foundCmd = false;

   for( i = 0; i < MAX_ICED_COMMAND; ++i )
      if( !str_cmp( iced_cmdtable[i].name, keymap["command"] ) )
      {
         foundCmd = true;
         break;
      }

   c = iced_findchannel( keymap["channel"] );

   if( !foundCmd )
   {
      run_iced_list( c, keymap["channel"], q->from, keymap["data"] );
      return;
   }

   if( !c && iced_cmdtable[i].level > ACCESS_CREATOR )
   {
      imc_send_tell( q->from, "You need to input a valid channel name to use this command." );
      return;
   }

   if( c && iced_cmdtable[i].level == ACCESS_CREATOR )
   {
      imc_send_tell( q->from, "You must make up a new channel for this command." );
      return;
   }

   if( iced_getaccess( c, q->from ) < iced_cmdtable[i].level ) 
   {
      if( iced_cmdtable[i].level > ACCESS_CREATOR )
         imc_send_tell( q->from, "You do not have the access level to use this command with that channel." );
      else if( iced_cmdtable[i].level == ACCESS_CREATOR )
         imc_send_tell( q->from, "You do not have the access level to create a new channel." );

      return;
   }

   if( keymap["data"].empty() && iced_cmdtable[i].need_data )
   {
      imc_send_tell( q->from, "This command requires additional data." );
      return;
   }

   logfile << "Channel command issued by " << q->from << ". Command: " << keymap["command"] << " on channel " << keymap["channel"] << " " << keymap["data"] << endl;
   (*iced_cmdtable[i].cmdfn)( c, keymap["channel"], q->from, keymap["data"] );
   iced_save_channels();
}

const struct iced_cmd_table iced_cmdtable[MAX_ICED_COMMAND] =
{
   { "list",       ACCESS_NONE,     run_iced_list,      false  },
   { "create",     ACCESS_CREATOR,  run_iced_create,    false  },
   { "destroy",    ACCESS_OWNER,    run_iced_destroy,   false  },
   { "invite",     ACCESS_OPERATOR, run_iced_invite,    true   },
   { "uninvite",   ACCESS_OPERATOR, run_iced_uninvite,  true   },
   { "exclude",    ACCESS_OPERATOR, run_iced_exclude,   true   },
   { "unexclude",  ACCESS_OPERATOR, run_iced_unexclude, true   },
   { "policy",     ACCESS_OWNER,    run_iced_policy,    true   },
   { "addop",      ACCESS_OWNER,    run_iced_addop,     true   },
   { "removeop",   ACCESS_OWNER,    run_iced_removeop,  true   },
   { "localname",  ACCESS_OWNER,    run_iced_localname, true   },
   { "perm",       ACCESS_OWNER,    run_iced_perm,      true   }
};

/* private message - for forwarding */
PFUN( iced_recv_msg_p )
{
   imc_channel *c;
   imc_packet *p;
   map<string,string> keymap = imc_getData( packet );

   c = iced_findchannel( keymap["channel"] );

   if( !c )
   {
      imc_send_tell( q->from, "You're trying to talk on a nonexistant channel." );
      iced_send_destroy( keymap["channel"], imc_mudof( q->from ) );
      q->stop = true;
      return;
   }

   if( !ice_audible( c, q->from ) )
   {
      imc_send_tell( q->from, "You're trying to talk on a channel that you don't have access to." );
      iced_send_destroy( c->name, imc_mudof( q->from ) );
      q->stop = true;
      return;
   }

   if( c->open )
   {
      imc_send_tell( q->from, "Misconfiguration, sending private message on nonprivate channel. Try again." );
      iced_update( c, imc_mudof( q->from ) );
      q->stop = true;
      return;
   }

   p = new imc_packet( "ICE", "ice-msg-r", imc_mudof( q->from ) );
   p->data << "realfrom=" << q->from;
   p->data << " channel=" << keymap["channel"];
   p->data << " text=" << escape_string( keymap["text"] );
   p->data << " emote=" << keymap["emote"];

   if( keymap["echo"] == "1" )
      iced_privmsg( c, p, "" );
   else
      iced_privmsg( c, p, imc_mudof( q->from ) );
}

void imc_set_maxconnect( const string& from, const string& data )
{
   ostringstream buf;

   if( !str_cmp( data, "show" ) )
   {
      buf << "The server currently allows up to " << siteinfo->maxconnects << " connections.";
      imc_send_tell( from, buf.str() );
      return;
   }

   if( !is_number( data ) )
   {
      imc_send_tell( from, "Maxconnections must be a numeric value." );
      return;
   }
   siteinfo->maxconnects = atoi( data.c_str() );
   buf << "~cMaximum allowed connections has been changed to: ~W" << siteinfo->maxconnects;
   imc_send_tell( from, buf.str() );
   imc_saveconfig();
}

void imc_set_infourl( const string& from, const string& data )
{
   ostringstream buf;

   if( !str_cmp( data, "show" ) )
   {
      if( siteinfo->url.empty() )
         buf << "There is no server URL defined.";
      else
         buf << "~cCurrent server URL: ~W" << siteinfo->url;
      imc_send_tell( from, buf.str() );
      return;
   }
   siteinfo->url = data;
   buf << "~cServer URL has been changed to: ~W" << siteinfo->url;
   imc_send_tell( from, buf.str() );
   imc_saveconfig();
   imc_send_tell( from, "Issuing is-alive packet to update network listings." );
   imc_send_keepalive( NULL, "*@*" );
}

void imc_set_infohost( const string& from, const string& data )
{
   ostringstream buf;

   if( !str_cmp( data, "show" ) )
   {
      if( siteinfo->host.empty() )
         buf << "There is no network URL defined.";
      else
         buf << "~cCurrent network URL: ~W" << siteinfo->host;
      imc_send_tell( from, buf.str() );
      return;
   }
   siteinfo->host = data;
   buf << "~cNetwork URL has been changed to: ~W" << siteinfo->host;
   imc_send_tell( from, buf.str() );
   imc_saveconfig();
}

void imc_set_webfile( const string& from, const string& data )
{
   ostringstream buf;

   if( !str_cmp( data, "show" ) )
   {
      if( siteinfo->statsfile.empty() )
         buf << "There is no webstats file path defined.";
      else
         buf << "~cCurrent webstats file path: ~W" << siteinfo->statsfile;
      imc_send_tell( from, buf.str() );
      return;
   }
   siteinfo->statsfile = data;
   buf << "~cWebstats file path has been changed to: ~W" << siteinfo->statsfile;
   imc_send_tell( from, buf.str() );
   imc_saveconfig();
}

void imc_toggle_webstats( const string& from, const string& data )
{
   siteinfo->webstats = !siteinfo->webstats;

   if( siteinfo->webstats )
   {
      if( siteinfo->statsfile.empty() )
      {
         siteinfo->webstats = false;
         imc_send_tell( from, "There is no webstats file path defined. Cannot enable statdump." );
         return;
      }
      imc_send_tell( from, "Webstats information will now be updated every 15 seconds." );
      imc_add_event( 1, ev_statdump, NULL );
   }
   else
   {
      imc_send_tell( from, "Webstats information will no longer be updated." );
      imc_cancel_event( ev_statdump, NULL );
   }
   imc_saveconfig();
}

void imc_toggle_autoupdate( const string& from, const string& data )
{
   siteinfo->autoupdate = !siteinfo->autoupdate;

   if( siteinfo->autoupdate )
   {
      imc_send_tell( from, "Autoupdate file will now be checked every 15 seconds." );
      imc_add_event( 15, ev_mudconnections, NULL );
   }
   else
   {
      imc_send_tell( from, "Autoupdate file will no longer be checked." );
      imc_cancel_event( ev_mudconnections, NULL );
   }
   imc_saveconfig();
}

void imc_toggle_compression( const string& from, const string& data )
{
   siteinfo->can_compress = !siteinfo->can_compress;

   if( siteinfo->can_compress )
      imc_send_tell( from, "Packet compression is now enabled." );
   else
      imc_send_tell( from, "Packet compression is now disabled." );
   imc_saveconfig();
}

void imc_toggle_prune( const string& from, const string& data )
{
   siteinfo->prune = !siteinfo->prune;

   if( siteinfo->prune )
   {
      imc_send_tell( from, "Outdated connections will now be self-pruned every 14 days." );
      imc_add_event( 900, ev_purgeinfo, NULL );
   }
   else
   {
      imc_send_tell( from, "Outdated connections will no longer be self-pruned." );
      imc_cancel_event( ev_purgeinfo, NULL );
   }
   imc_saveconfig();
}

void imc_toggle_autosetup( const string& from, const string& data )
{
   siteinfo->priv = !siteinfo->priv;

   if( siteinfo->priv )
      imc_send_tell( from, "Autosetup connections will no longer be accepted." );
   else
      imc_send_tell( from, "Autosetup connections will now be allowed." );
   imc_saveconfig();
}

void imc_toggle_debug( const string& from, const string& data )
{
   imcpacketdebug = !imcpacketdebug;

   if( imcpacketdebug )
      imc_send_tell( from, "~cPacket debugging has been `WENABLED~!~c. Don't leave this on for too long." );
   else
      imc_send_tell( from, "Packet debugging has been disabled." );
}

void imc_disable_remote( const string& from, const string& data )
{
   siteinfo->remoteadmin = false;

   imc_send_tell( from, "Remote administration functions have been disabled." );
   imc_saveconfig();
}

void imc_ban_ip( const string& from, const string& data )
{
   ostringstream buf;
   list<string>::iterator ban;

   if( !str_cmp( data, "list" ) || !str_cmp( data, "show" ) )
   {
      buf << endl << "~cThe following IPs are banned:" << endl;
      buf << "~C-----------------------------" << endl << endl;
      for( ban = banlist.begin(); ban != banlist.end(); ++ban )
      {
         string b = *ban;
         buf << "~W" << b << endl;
      }
      imc_send_tell( from, buf.str() );
      return;
   }

   for( ban = banlist.begin(); ban != banlist.end(); ++ban )
   {
      string b = *ban;

      if( !str_cmp( b, data ) )
      {
         buf << "~cIP ~W" << data << " ~chas been unbanned.";
         banlist.remove( data );
         imc_savebans();
         imc_send_tell( from, buf.str() );
         return;
      }
   }

   banlist.push_back( data );
   imc_savebans();

   list<connection*>::iterator conn;
   for( conn = connlist.begin(); conn != connlist.end(); )
   {
      connection *c = *conn;
      ++conn;

      if( c->host == data )
      {
         if( c->info )
            imc_delete_info( c->info );
         else
            do_close( c );
      }
   }

   buf << "~cIP ~W" << data << " ~chas been banned.";
   imc_send_tell( from, buf.str() );
}

void imc_destroy_info( const string& from, const string& data )
{
   imc_info *i;
   ostringstream buf;

   if( !( i = imc_getinfo( data ) ) )
   {
      buf << "~cThere is no connection named ~W" << data << " ~cregistered here.";
      imc_send_tell( from, buf.str() );
      return;
   }

   buf << "~W" << i->name << " ~chas been removed from listings.";
   imc_send_tell( from, buf.str() );
   send_reminfo_destroy( i->name );
   imc_delete_info( i );
   imc_saveconfig();
}

void imc_discon_mud( const string& from, const string& data )
{
   imc_info *i;
   ostringstream buf;

   if( !( i = imc_getinfo( data ) ) )
   {
      buf << "~cThere is no connection named ~W" << data << " ~cregistered here.";
      imc_send_tell( from, buf.str() );
      return;
   }

   if( !i->conn )
   {
      buf << "~W" << i->name << " ~cis not currently connected.";
      imc_send_tell( from, buf.str() );
      return;
   }

   buf << "~W" << i->name << " ~chas been disconnected.";
   imc_send_tell( from, buf.str() );
   do_close( i->conn );
}

void imc_list_remcommands( const string& from, const string& data )
{
   ostringstream buf;
   int i;

   buf << endl << "~cAvailable remote commands:" << endl;
   buf << "~C--------------------------" << endl << endl;
   for( i = 0; i < MAX_REMOTE_COMMAND; ++i )
      buf << "~W" << imc_remote_cmdtable[i].name << endl;

   imc_send_tell( from, buf.str() );
}

void imc_serverreboot( const string& from, const string& data )
{
   imc_send_tell( from, "The server is rebooting." );
   logfile << "Reboot called by remote admin command." << endl;
   imc_shutdown();
}

void imc_serverhotboot( const string& from, const string& data )
{
   imc_send_tell( from, "The server is executing a hotboot." );
   logfile << "Hotboot called via remote amdin command." << endl;
   imc_hotboot();
}

void imc_servershutdown( const string& from, const string& data )
{
   ofstream fp;

   imc_send_tell( from, "The server is shutting down and will not reboot." );
   logfile << "Shutdown called by remote admin command." << endl;
   fp.open( "shutdown" );
   if( !fp.is_open() )
   {
      logfile << "Unable to drop shutdown file! Server will reboot instead." << endl;
      imc_send_tell( from, "Unable to drop shutdown file! Server will reboot instead." );
      imc_shutdown();
   }
   fp << "SHUTDOWN" << endl;
   fp.close();
   imc_shutdown();
}

void imc_serveradmins( const string& from, const string& data )
{
   ostringstream buf;

   if( data.find( '@' ) == string::npos )
   {
      imc_send_tell( from, "Server administrators must be specified in the form of user@mud." );
      return;
   }
   if( hasname( siteinfo->serveradmins, data ) )
   {
      removename( siteinfo->serveradmins, data );
      buf << "~W" << data << " ~chas been removed as a server administrator.";
      imc_send_tell( from, buf.str() );
   }
   else
   {
      addname( siteinfo->serveradmins, data );
      buf << "~W" << data << " ~chas been added as a server administrator.";
      imc_send_tell( from, buf.str() );
   }
   imc_saveconfig();
}

void imc_chancreators( const string& from, const string& data )
{
   ostringstream buf;

   if( data.find( '@' ) == string::npos )
   {
      imc_send_tell( from, "Channel creators must be specified in the form of user@mud." );
      return;
   }
   if( hasname( siteinfo->chancreators, data ) )
   {
      removename( siteinfo->chancreators, data );
      buf << "~W" << data << " ~chas been removed as a channel creator.";
      imc_send_tell( from, buf.str() );
   }
   else
   {
      addname( siteinfo->chancreators, data );
      buf << "~W" << data << " ~chas been added as a channel creator.";
      imc_send_tell( from, buf.str() );
   }
   imc_saveconfig();
}

void imc_set_netname( const string& from, const string& data )
{
   ostringstream buf;

   if( !str_cmp( data, "show" ) )
   {
      if( siteinfo->network.empty()  )
         buf << "There is no network name defined.";
      else
         buf << "~cCurrent network name: ~W" << siteinfo->network;
      imc_send_tell( from, buf.str() );
      return;
   }
   siteinfo->network = data;
   buf << "~cNetwork name has been changed to: ~W" << siteinfo->network;
   imc_send_tell( from, buf.str() );
   imc_saveconfig();
   imc_send_tell( from, "Issuing is-alive packet to update network listings." );
   imc_send_keepalive( NULL, "*@*" );
}

void imc_show_serverconfig( const string& from, const string& data )
{
   ostringstream buf;

   buf << endl << "~W" << siteinfo->name << " ~chas the following configuration:" << endl;
   buf << "~C-------------------------------------" << endl << endl;
   buf << "~cNetname: ~W" << siteinfo->network << endl;
   buf << "~cMax Connections: ~W" << siteinfo->maxconnects << endl;
   buf << "~cChannel Creators: ~W" << siteinfo->chancreators << endl;
   buf << "~cServer Administrators: ~W" << siteinfo->serveradmins << endl;
   buf << "~cNetwork URL: ~W" << siteinfo->host << endl;
   buf << "~cServer URL: ~W" << siteinfo->url << endl;
   buf << "~cAutosetup: ~W" << ( siteinfo->priv ? "Disabled" : "Enabled" ) << endl;
   buf << "~cAutoupdate File: ~W" << ( siteinfo->autoupdate ? "Enabled" : "Disabled" ) << endl;
   buf << "~cInfo Pruning: ~W" << ( siteinfo->prune ? "Enabled" : "Disabled" ) << endl;
   buf << "~cWebstats Dump: ~W" << ( siteinfo->webstats ? "Enabled" : "Disabled" ) << endl;
   buf << "~cWebstats File: ~W" << siteinfo->statsfile << endl;
   buf << "~cPacket Compression: ~W" << ( siteinfo->can_compress ? "Enabled" : "Disabled" ) << endl;
   buf << "~cPacket Debugging: " << ( imcpacketdebug ? "`WENABLED!" : "~WDisabled" ) << endl;
   imc_send_tell( from, buf.str() );
}

/* The remote administration handler */
PFUN( imc_remote_admin )
{
   imc_reminfo *r;
   map<string,string> keymap = imc_getData( packet );
   ostringstream buf;
   char pwd[IMC_DATA_LENGTH];
   char *cryptpwd;
   int i;
   bool foundCmd = false;

   if( !( r = imc_find_reminfo( imc_mudof( q->from ) ) ) )
   {
      logfile << "Invalid source mud sending remote admin command: " << q->from << endl;
      return;
   }

   if( !r->sha256 ) /* Let them eat static - don't return a tell */
   {
      logfile << r->name << " does not support SHA256 - rejecting remote admin command from " << q->from << endl;
      return;
   }

   if( !siteinfo->remoteadmin )
   {
      logfile << "Remote admin command from " << q->from << " with remote admin disabled." << endl;
      imc_send_tell( q->from, "Remote admin commands are not enabled here." );
      return;
   }

   if( !hasname( siteinfo->serveradmins, q->from ) )
   {
      logfile << "Non permitted user attempting to issue remote admin command: " << q->from << endl;
      imc_send_tell( q->from, "You do not have permission to use remote commands here." );
      return;
   }

   if( keymap["hash"].empty() )
   {
      logfile << "Required password for remote admin not provided by " << q->from << endl;
      imc_send_tell( q->from, "Required password was not provided." );
      return;
   }

   snprintf( pwd, IMC_DATA_LENGTH, "%ld%s", q->i.sequence, siteinfo->serveradminpwd.c_str() );
   cryptpwd = sha256_crypt( pwd );

   if( !STR_CEQL( cryptpwd, keymap["hash"].c_str() ) )
   {
      logfile << "Invalid remote admin password from " << q->from << endl;
      logfile << "Hash received: " << q->i.sequence << ", " << keymap["hash"] << endl;
      imc_send_tell( q->from, "Invalid password." );
      return;
   }

   if( keymap["command"].empty() )
   {
      imc_send_tell( q->from, "You must specify some sort of command." );
      return;
   }

   for( i = 0; i < MAX_REMOTE_COMMAND; ++i )
      if( !str_cmp( imc_remote_cmdtable[i].name, keymap["command"] ) )
      {
         foundCmd = true;
         break;
      }

   if( !foundCmd )
   {
      buf << keymap["command"] << " ~cis not a valid remote command.";
      imc_send_tell( q->from, buf.str() );
      imc_list_remcommands( q->from, keymap["data"] );
      return;
   }

   if( keymap["data"].empty() && imc_remote_cmdtable[i].need_data )
   {
      imc_send_tell( q->from, "This command requires additional data." );
      return;
   }

   logfile << "Remote command issued by " << q->from << ". Command: " << keymap["command"] << " " << keymap["data"] << endl;
   (*imc_remote_cmdtable[i].cmdfn)( q->from, keymap["data"] );
}

const	struct imc_rcmd_table imc_remote_cmdtable[MAX_REMOTE_COMMAND] =
{
   { "list",            imc_list_remcommands,   false },
   { "reboot",          imc_serverreboot,       false },
   { "hotboot",         imc_serverhotboot,      false },
   { "shutdown",        imc_servershutdown,     false },
   { "disconnect",      imc_discon_mud,         true  },
   { "destroy",         imc_destroy_info,       true  },
   { "ban",             imc_ban_ip,             true  },
   { "remoteadmin",     imc_disable_remote,     false },
   { "autosetup",       imc_toggle_autosetup,   false },
   { "prune",           imc_toggle_prune,       false },
   { "autoupdate",      imc_toggle_autoupdate,  false },
   { "webstats",        imc_toggle_webstats,    false },
   { "debug",           imc_toggle_debug,       false },
   { "webstatsfile",    imc_set_webfile,        true  },
   { "networkurl",      imc_set_infohost,       true  },
   { "serverurl",       imc_set_infourl,        true  },
   { "maxconnect",      imc_set_maxconnect,     true  },
   { "chancreator",     imc_chancreators,       true  },
   { "serveradmin",     imc_serveradmins,       true  },
   { "netname",         imc_set_netname,        true  },
   { "compression",     imc_toggle_compression, false },
   { "config",          imc_show_serverconfig,  false }
};

void register_packet_handler( const string& name, PACKET_FUN *func )
{
   packet_handler *ph;
   list<packet_handler*>::iterator phn;

   for( phn = phanlderlist.begin(); phn != phanlderlist.end(); ++phn )
   {
      ph = *phn;

      if( ph->name == name ) // No str_cmp here, we want an exact name match
      {
         logfile << "Unable to register packet type " << name << ". Another module has already registered it." << endl;
         return;
      }
   }

   ph = new packet_handler;

   ph->name = name;
   ph->func = func;

   phanlderlist.push_back( ph );
}

void imc_register_default_packets( void )
{
   register_packet_handler( "who", imc_recv_who );
   register_packet_handler( "wHo", imc_recv_who );
   register_packet_handler( "is-alive", imc_recv_isalive );
   register_packet_handler( "keepalive-request", imc_send_keepalive );
   register_packet_handler( "reminfo-destroy", imc_recv_reminfo_destroy );
   register_packet_handler( "close-notify", imc_recv_closenotify );
   register_packet_handler( "imc-laston", imc_recv_laston );
   register_packet_handler( "ice-cmd", iced_recv_command );
   register_packet_handler( "ice-msg-p", iced_recv_msg_p );
   register_packet_handler( "ice-msg-b", iced_recv_broadcast );
   register_packet_handler( "ice-refresh", iced_recv_icerefresh );
   register_packet_handler( "remote-admin", imc_remote_admin );
}

PACKET_FUN *pfun_lookup( const string& type )
{
   list<packet_handler*>::iterator phn;

   for( phn = phanlderlist.begin(); phn != phanlderlist.end(); ++phn )
   {
      packet_handler *ph = *phn;

      if( type == ph->name ) // No str_cmp here, we need an exact match
         return ph->func;
   }
   return NULL;
}

/* handle a packet destined for us, or a broadcast */
void imc_recv( imc_packet *p )
{
   PACKET_FUN *pfun;

   if( imcpacketdebug )
      logfile << "ALTERED Packet received: " << p->from << " " << p->i.sequence << " " << p->i.path << " " << p->type << " " << p->to << " " << p->data.str() << endl;

   pfun = pfun_lookup( p->type );

   if( !pfun )
   {
      logfile << "No packet handler function has been defined for " << p->type << endl;
      return;
   }
   try
   {
      (*pfun)( p, p->data.str() );
   }
   catch( exception& e )
   {
      logfile << "EXCEPTION GENERATED: " << e.what() << ". Caused by packet: " << p->from << " " << p->i.sequence << " " << p->i.path << " " << p->type << " " << p->to << " " << p->data.str() << endl;
   }
   catch(...)
   {
      logfile << "UNKNOWN EXCEPTION: Generated by packet: " << p->from << " " << p->i.sequence << " " << p->i.path << " " << p->type << " " << p->to << " " << p->data.str() << endl;
   }
}

imc_packet *disassemble_packet( string& packet )
{
   imc_packet *p;
   string from, sequence, path, type, to, data;

   try
   {
      packet = one_argument( packet, from );
      packet = one_argument( packet, sequence );
      packet = one_argument( packet, path );
      packet = one_argument( packet, type );
      packet = one_argument( packet, to );

      if( from.empty() || sequence.empty() || path.empty() || type.empty() || to.empty() )
      {
         logfile << __FUNCTION__ << ": Invalid packet format!" << endl;
         return NULL;
      }
   }
   catch( exception& e )
   {
      logfile << "EXCEPTION GENERATED: " << e.what() << " by packet!" << endl;
      return NULL;
   }
   catch(...)
   {
      logfile << "UNKNOWN EXCEPTION: Generated by packet!" << endl;
      return NULL;
   }

   if( imcpacketdebug )
      logfile << "PACKET BREAKDOWN: From=" << from << " Seq=" << sequence << " Path=" << path << " Type=" << type << " To=" << to << " Data=" << packet << endl;

   p = new imc_packet;
   p->i.from = from;
   p->i.sequence = atol( sequence.c_str() );
   p->i.path = path;
   p->type = type;
   p->i.to = to;

   if( !packet.empty() )
      p->data << packet;
   else p->data.clear();

   return p;
}

/* low-level idle function: read/write buffers as needed, etc */
void imc_idle_select( fd_set *iread, fd_set *iwrite, fd_set *exc, time_t now )
{
   const char *command;

   if( imc_sequencenumber < (unsigned long)imc_now )
      imc_sequencenumber = (unsigned long)imc_now;

   imc_run_events( now );

   /* handle results of the select */
   if( FD_ISSET( control, iread ) )
      do_accept();

   list<connection*>::iterator conn;
   for( conn = connlist.begin(); conn != connlist.end(); )
   {
      connection *c = *conn;
      ++conn;

      if( c->state != CONN_NONE && FD_ISSET( c->desc, exc ) )
         do_close( c );

      if( c->state != CONN_NONE && FD_ISSET( c->desc, iread ) )
         do_read( c );

      while( c->state != CONN_NONE && ( command = imc_getline( c->inbuf, c->insize ) ) != NULL )
      {
         string packet = command;

         if( packet.length() > imc_stats.max_pkt )
	      imc_stats.max_pkt = packet.length();

         if( imcpacketdebug )
            logfile << "UNPROCESSED PACKET RECEIVED: " << command << endl;

         switch( c->state )
         {
            default:
               break;

            case CONN_WAITCLIENTPWD:
	         clientpassword( c, packet );
	         break;

            case CONN_WAITSERVERPWD:
	         serverpassword( c, packet );
	         break;

            case CONN_COMPLETE:
               imc_packet *p = disassemble_packet( packet );
               if( p ) 
               {
                  ++imc_stats.rx_pkts;

                  /* Direct connection is not a server.
                   * Check for ! in path and verify source is where it claims to be from.
                   * Also inserts network name into is-alives from clients who don't send one.
                   * Place any server sensative packets to check in here.
                   * Muds should never have any reason to use them.
                   * Samson 2-13-04
                   */
                  if( !c->info->server )
                  {
                     if( p->i.path.find( '!' ) != string::npos )
                        logfile << "ALERT: Packet from " << c->info->name << " forged to look like it was from " << imc_mudof( p->i.from ) << ". Possible spoof attempt." << endl;

                     else if( imc_mudof( p->i.from ) != c->info->name )
                        logfile << "ALERT: Packet from " << c->info->name << " forged to look like it was from " << imc_mudof( p->i.from ) << endl;

                     /* Packets only a server should send go here */
                     else if( p->type == "reminfo-destroy" )
                        logfile << "ALERT: " << c->info->name << " attempted to send reminfo-destroy packet." << endl;
                     else if( p->type == "close-notify" )
                        logfile << "ALERT: " << c->info->name << " attempted to send close-notify packet." << endl;
                     else if( p->type == "ice-update" )
                        logfile << "ALERT: " << c->info->name << " attempted to send ice-update packet." << endl;

                     /* Go ahead and forward it now */
                     else
                     {
                        /* Oh, we're really going to town on this shit now */
                        if( p->type == "is-alive" )
                        {
                           ostringstream Nsha256;
                           string::size_type x = p->data.str().find( "sha256=" );

                           if( x != string::npos )
                              p->data.str() = p->data.str().erase( x, 5 ); 

                           Nsha256 << c->info->sha256;
                           p->data << " sha256=" << Nsha256.str();

                           if( p->data.str().find( "networkname" ) == string::npos )
                              p->data << " networkname=" << siteinfo->network;
                        }
                        if( p->type == "ice-refresh" )
                        {
                           /* Modify incoming ice-refresh packets to use multicast if they're still using broadcast */
                           if( imc_mudof( p->i.to ) == "*" )
                              p->i.to = "IMC@$";
                        }
                        p->forward();
                     }
                  }
                  else
                  {
                     /* Check the last entry in the path is the same as the sending mud.
                      * Also check the first entry to see that it matches the sender. 
                      */ 
                     if( c->info->name != imc_lastinpath( p->i.path ) )
                        logfile << "ALERT: Packet passed through " << c->info->name << " forged to look like it was passed through " << imc_lastinpath( p->i.path ) << endl;

                     else if( imc_mudof( p->i.from ) != imc_firstinpath( p->i.path ) )
                        logfile << "ALERT: Packet originally from " << p->i.from << " forged to look like it was originally from " << imc_firstinpath( p->i.path ) << endl;

                     else
                        p->forward(); /* only forward if its a valid packet! */
                  }
               }
               break;
         }
      }
   }

   for( conn = connlist.begin(); conn != connlist.end(); )
   {
      connection *c = (*conn);
      ++conn;

      if( c->state != CONN_NONE && ( FD_ISSET( c->desc, iwrite ) || c->newoutput ) )
      {
         do_write( c );
         if( c->outbuf[0] != '\0' )
            c->newoutput = true;
         else
            c->newoutput = false;
         if( c->disconnect )
            do_close( c );
      }
   }

   for( conn = connlist.begin(); conn != connlist.end(); )
   {
      connection *c = (*conn);
      ++conn;

      if( c->state == CONN_NONE )
         imc_extract_connect( c );
   }
}

int imc_fill_fdsets( int maxfd, fd_set *iread, fd_set *iwrite, fd_set *exc )
{
   /* set up fd_sets for select */
   if( maxfd < control )
       maxfd = control;
      
   FD_SET( control, iread );

   list<connection*>::iterator conn;
   for( conn = connlist.begin(); conn != connlist.end(); ++conn )
   {
      connection *c = (*conn);

      if( maxfd < c->desc )
         maxfd = c->desc;

      if( c->state == CONN_NONE )
         continue;

      if( c->state == CONN_SERVERCONNECT )
      {
         FD_SET( c->desc, iwrite );
         continue;
      }

      FD_SET( c->desc, iread );

      if( c->outbuf[0] )
         FD_SET( c->desc, iwrite );
   }
   return maxfd;
}

/* shell around imc_idle_select */
void imc_idle( int s )
{
   fd_set iread, iwrite, exc;
   int maxfd;
   struct timeval timeout;
   int i;

   FD_ZERO( &iread );
   FD_ZERO( &iwrite );
   FD_ZERO( &exc );

   maxfd = imc_fill_fdsets( 0, &iread, &iwrite, &exc );
   timeout.tv_sec = s;
   timeout.tv_usec = 0;

   if( maxfd )
      while( ( i = select( maxfd+1, &iread, &iwrite, &exc, &timeout ) ) < 0 && errno == EINTR )
         ;
   else
      while( ( i = select( 0, NULL, NULL, NULL, &timeout ) ) < 0 && errno == EINTR )
         ;

   if( i < 0 )
   {
      logfile << __FUNCTION__ << ": select stalled" << endl;
      imc_shutdown();
      return;
   }
   imc_idle_select( &iread, &iwrite, &exc, time( NULL ) );
}

void server_loop( struct timeval last_time )
{
   struct timeval now_time;
   long secDelta;
   long usecDelta;

   imc_idle( imc_get_event_timeout() );

   /*
    * Synchronize to a clock - make sure that it takes at least a second plus a 1/4 second
    * delay time to go through the loop. Keeps sequence numbers from increasing too fast.
    * Sleep( last_time + 1/PULSE_PER_SECOND - now ).
    * Careful here of signed versus unsigned arithmetic.
    */
   gettimeofday( &now_time, NULL );

   usecDelta = ((int) last_time.tv_usec) - ((int) now_time.tv_usec) + 1000000 / 4;
   secDelta  = ((int) last_time.tv_sec ) - ((int) now_time.tv_sec );

   while( usecDelta < 0 )
   {
      usecDelta += 1000000;
      secDelta  -= 1;
   }

   while( usecDelta >= 1000000 )
   {
      usecDelta -= 1000000;
      secDelta  += 1;
   }

   if( secDelta >= 0 && usecDelta >= 0 )
   {
      struct timeval stall_time;

      stall_time.tv_usec = usecDelta;
      stall_time.tv_sec  = secDelta;

      if( select( 0, NULL, NULL, NULL, &stall_time ) < 0 && errno != EINTR )
      {
         perror( "server_loop: select: stall" );
         exit( 1 );
      }
   }
   gettimeofday( &last_time, NULL );
   server_loop( last_time );
   return;
}

/********************
 * Event Management *
 ********************/

/* time out a login */
void ev_login_timeout( const void *data )
{
   connection *c = (connection *)data;

   logfile << imc_getconnectname(c) << ": login timeout" << endl;
   if( c->info && c->info->port > 0 )
      imc_add_event( 60, ev_reconnect, c->info );
   do_close( c );
}

/* make it an event, dont announce a simple reboot - shogar - 2/1/2000 */
void ev_close_notify( const void *data )
{
   const char *host = (const char *)data;

   imc_cancel_event( ev_close_notify, host );
   imc_close_notify( host );
}

/* try a reconnect to the given imc_info */
void ev_reconnect( const void *data )
{
   imc_info *info = (imc_info *)data;

   if( !info->conn && info->port > 0 )
	imc_connect_to( info->name ); 
}

/* generate a channel listing */
void ev_iced_refresh( const void *data )
{
   list<imc_channel*>::iterator chn;

   for( chn = chanlist.begin(); chn != chanlist.end(); ++chn )
   {
      imc_channel *c = *chn;

      iced_update( c, "" );
   }
}

void ev_keepalive( const void *data )
{
   imc_send_keepalive( NULL, "*@*" );
}

/* Check all type 1 connections to see if they are up.
 * If not, try to connect. Will trigger every 30 minutes.
 */
void ev_autoconnects( const void *data )
{
   list<imc_info*>::iterator inf;
   for( inf = infolist.begin(); inf != infolist.end(); ++inf )
   {
      imc_info *i = *inf;

      if( i->port > 0 && i->conn == NULL )
         imc_connect_to( i->name );
   }
   imc_add_event( 900, ev_autoconnects, NULL );
}

/* Dump our stats into a file that a webscript can use -- Xorith */
void imc_statdump( )
{
   ofstream stream;
   string netname;
   char urldump[LSS];
   int evcount = 0;
   imc_event *ev = imc_event_list;
   long secondspast;

   if( !siteinfo->webstats )
      return;

   stream.open( siteinfo->statsfile.c_str() );
   if( !stream.is_open() )
   {
      logfile << "Error opening stat dump file: " << siteinfo->statsfile << endl;
      return;
   }

   while( ev )
   {
      ++evcount;
      ev = ev->next;
   }

   if( ( imc_now - imc_stats.start ) > 0 )
      secondspast = imc_now - imc_stats.start;
   else
      secondspast = 1;

   stream << "RXPKTS " << imc_stats.rx_pkts << endl;
   stream << "RXBYTES " << imc_stats.rx_bytes << endl;
   stream << "RXBPS " << ( imc_stats.rx_bytes / secondspast ) << endl;
   stream << "TXPKTS " << imc_stats.tx_pkts << endl;
   stream << "TXBYTES " << imc_stats.tx_bytes << endl;
   stream << "TXBPS " << ( imc_stats.tx_bytes / secondspast ) << endl;
   if( siteinfo->can_compress )
   {
      stream << "TXCMPBYTES " << imc_stats.tx_cmpbytes << endl;
      stream << "RXCMPBYTES " << imc_stats.rx_cmpbytes << endl;
      stream << "RXBYTESSAVED " << imc_stats.rx_bytessaved << endl;
      stream << "TXBYTESSAVED " << imc_stats.tx_bytessaved << endl;
   }
   stream << "MAXPKTSZ " << imc_stats.max_pkt << endl;
   stream << "EVENTS " << evcount << endl;
   stream << "SEQDROPS " << imc_stats.sequence_drops << endl;
   stream << "IMCBOOT " << imc_boot << endl;
   stream << "LASTDUMP " << imc_now << endl;
   stream << "IMCUPFOR " << ( imc_now - imc_boot ) << endl;
   stream << "NAME " << siteinfo->name << endl;
   stream << "VERS " << IMC_VERSIONID << endl;
   stream << "ADDR " << siteinfo->host << endl;
   stream << "NETWK " << siteinfo->network << endl;

   list<imc_info*>::iterator inf;
   for( inf = infolist.begin(); inf != infolist.end(); ++inf )
   {
      imc_info *i = *inf;

      if( i->url.empty() || i->url == "??" || i->url == "Unknown"
       || i->url.find( "http://" ) == string::npos )
         strlcpy( urldump, "", LSS );
      else
         strlcpy( urldump, i->url.c_str(), LSS );
      stream << "CONN " << i->name << " " << ( i->conn ? "1" : "0" ) << " " << ( i->server ? "1" : "0" ) << " " << urldump << endl;
   }
   stream.close();

   stream.open( IMC_DUMP_FILE );
   if( !stream.is_open() )
   {
      logfile << "Error opening info dump file: " << IMC_DUMP_FILE << endl;
      return;
   }

   if( siteinfo->url.empty() 
    || siteinfo->url == "??" || siteinfo->url == "Unknown"
    || siteinfo->url.find( "http://" ) == string::npos )
      snprintf( urldump, LSS, "~c%-30.30s~!", siteinfo->name.c_str() );
   else
   {
      char nspaces[30];
      unsigned int x, namelen;

      namelen = 30 - siteinfo->name.length();
      for( x = 0; x < namelen; ++x )
         nspaces[x] = ' ';
      nspaces[x] = '\0';
      snprintf( urldump, LSS, "~c<a href=\"%s\">%s</a>~!%s", siteinfo->url.c_str(), siteinfo->name.c_str(), nspaces );
   }

   stream << urldump << " ~B" << setw(50) << IMC_VERSIONID << "~! ~g" << setw(15) << siteinfo->network << "~! ~G" << siteinfo->name << "~!" << endl;

   list<imc_reminfo*>::iterator rin;
   for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin )
   {
      imc_reminfo *r = *rin;

      if( r->expired )
         continue;

      if( r->url.empty() || r->url == "??" || r->url =="Unknown"
       || r->url.find( "http://" ) == string::npos )
         snprintf( urldump, LSS, "~c%-30.30s~!", r->name.c_str() );
      else
      {
         char nspaces[30];
         int x, namelen;

         namelen = 30 - r->name.length();
         for( x = 0; x < namelen; ++x )
            nspaces[x] = ' ';
         nspaces[x] = '\0';
         snprintf( urldump, LSS, "~c<a href=\"%s\">%s</a>~!%s", r->url.c_str(), r->name.c_str(), nspaces );
      }

      if( !str_cmp( r->network, "unknown" ) )
         netname = siteinfo->network;
      else
         netname = r->network;
      stream << urldump << " ~B" << setw(40) << r->version << "~! ~g" << setw(15) << netname << "~! ~G" << imc_serverinpath( r->path ) << "~!" << endl;
   }
   stream.close();
}

void ev_statdump( const void *data )
{
   imc_statdump();
   imc_add_event( 15, ev_statdump, NULL );
}

void check_mudsfile( void )
{
   ifstream stream;
   string key;
   bool found = false;

   if( !siteinfo->autoupdate )
      return;

   stream.open( IMC_UPDATE_FILE );
   if( !stream.is_open() )
      return;

   unlink( IMC_UPDATE_FILE ); // In case something crashes during reading
   do
   {
      stream >> key;
      if( key == "\n" || key == "\r" || key.empty() )
         break;
      else if( key == "#" )
      {
         string host;
         bool ifound = false;

         stream >> host;

         list<string>::iterator ban;
         for( ban = banlist.begin(); ban != banlist.end(); ++ban )
         {
            string b = (*ban);

            if( b == host )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            logfile << "Ban listed in a \'#\' line in the update file already exists." << endl;
            continue;
         }
         logfile << "Adding ban for " << host << " via autoupdate file." << endl;
         banlist.push_back( host );
         imc_savebans();

         list<connection*>::iterator conn;
         for( conn = connlist.begin(); conn != connlist.end(); )
         {
            connection *c = (*conn);
            ++conn;

            if( c->host == host )
            {
               if( c->info )
                  imc_delete_info( c->info );
               else
                  do_close( c );
            }
         }
         continue;
      }
      else if( key == "$" )
      {
         string host;
         bool ifound = false;

         stream >> host;

         list<string>::iterator ban;
         for( ban = banlist.begin(); ban != banlist.end(); ++ban )
         {
            string b = *ban;

            if( b == host )
            {
               ifound = true;
               break;
            }
         }

         if( !ifound )
         {
            logfile << "Ban listed in a \'$\' line in the update file could not be found." << endl;
            continue;
         }
         logfile << "Removing ban for " << host << " via autoupdate file." << endl;
         banlist.remove( host );
         imc_savebans();
         continue;
      }
      else if( key == "-" )
      {
         string name;
         imc_info *i = NULL;
         bool ifound = false;

         stream >> name;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( !ifound )
         {
            logfile << "Connection referenced in a \'-\' line in the update file could not be found." << endl;
            continue;
         }
         logfile << "Removing connection for " << name << " via autoupdate file." << endl;
         send_reminfo_destroy( name );
         imc_delete_info( i );
         found = true;
         continue;
      }
      else if( key == "=" )
      {
         imc_info *i;
         string name, host, pw1, pw2;
         unsigned short port;
         bool ifound = false;

         stream >> name >> host >> port >> pw1 >> pw2;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            logfile << "Connection listed in a \'=\' line in the update file already exists." << endl;
            continue;
         }
         i = imc_new_info();
         i->name       = name;
         i->host       = host;
         i->clientpw   = pw1;
         i->serverpw   = pw2;
         i->port       = port;
         i->md5        = false;
         i->sha256     = false;
         i->last_connected = imc_now;
         i->server = true;
         logfile << "Adding Server-Type1 connection for " << i->name << " via autoupdate file. Using Standard Authentication." << endl;
         found = true;
         continue;
      }
      else if( key == "&" )
      {
         imc_info *i;
         string name, pw1, pw2;
         bool ifound = false;

         stream >> name >> pw1 >> pw2;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            logfile << "Connection listed in a \'&\' line in the update file already exists." << endl;
            continue;
         }

         i = imc_new_info();
         i->name       = name;
         i->clientpw   = pw1;
         i->serverpw   = pw2;
         i->server     = true;
         i->md5        = false;
         i->sha256     = false;
         i->last_connected = imc_now;
         logfile << "Adding Server-Type2 connection for " << i->name << " via autoupdate file. Using Standard Authentication." << endl;
         found = true;
         continue;
      }
      else if( key == "%" )
      {
         imc_info *i = NULL;
         string name, host, pw1, pw2;
         unsigned short port;
         bool ifound = false;

         stream >> name >> host >> port >> pw1 >> pw2;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            if( !i->md5 )
            {
               i->md5 = true;
               i->sha256 = false;
               logfile << "Updating connection for " << i->name << " to use MD5 Authentication via autoupdate file." << endl;
               if( i->conn )
                  imc_send_keepalive( NULL, "*@" + i->name );
               found = true;
               continue;
            }
            logfile << "Connection listed in a \'%\' line in the update file already exists." << endl;
            continue;
         }
         i = imc_new_info();
         i->name       = name;
         i->host       = host;
         i->clientpw   = pw1;
         i->serverpw   = pw2;
         i->port       = port;
         i->md5        = true;
         i->last_connected = imc_now;
         i->server    = true;
         logfile << "Adding Server-Type1 connection for " << i->name << " via autoupdate file. Using MD5 Authentication." << endl;
         found = true;
         continue;
      }
      else if( key == "@" )
      {
         imc_info *i = NULL;
         string name, pw1, pw2;
         bool ifound = false;

         stream >> name >> pw1 >> pw2;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            if( !i->md5 )
            {
               i->md5 = true;
               i->sha256 = false;
               logfile << "Updating connection for " << i->name << " to use MD5 Authentication via autoupdate file." << endl;
               if( i->conn )
                  imc_send_keepalive( NULL, "*@" + i->name );
               found = true;
               continue;
            }
            logfile << "Connection listed in a \'@\' line in the update file already exists." << endl;
            continue;
         }

         i = imc_new_info();
         i->name       = name;
         i->clientpw   = pw1;
         i->serverpw   = pw2;
         i->server     = true;
         i->md5        = true;
         i->last_connected = imc_now;
         logfile << "Adding Server-Type2 connection for " << i->name << " via autoupdate file. Using MD5 Authentication." << endl;
         found = true;
         continue;
      }
      else if( key == "^" )
      {
         imc_info *i = NULL;
         string name, host, pw1, pw2;
         unsigned short port;
         bool ifound = false;

         stream >> name >> host >> port >> pw1 >> pw2;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            if( !i->sha256 )
            {
               i->sha256 = true;
               i->md5 = false;
               logfile << "Updating connection for " << i->name << " to use SHA-256 Authentication via autoupdate file." << endl;
               if( i->conn )
                  imc_send_keepalive( NULL, "*@" + i->name );
               found = true;
               continue;
            }
            logfile << "Connection listed in a \'^\' line in the update file already exists." << endl;
            continue;
         }
         i = imc_new_info();
         i->name       = name;
         i->host       = host;
         i->clientpw   = pw1;
         i->serverpw   = pw2;
         i->port       = port;
         i->sha256     = true;
         i->last_connected = imc_now;
         i->server    = true;
         logfile << "Adding Server-Type1 connection for " << i->name << " via autoupdate file. Using SHA-256 Authentication." << endl;
         found = true;
         continue;
      }
      else if( key == "!" )
      {
         imc_info *i = NULL;
         string name, pw1, pw2;
         bool ifound = false;

         stream >> name >> pw1 >> pw2;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            if( !i->sha256 )
            {
               i->sha256 = true;
               i->md5 = false;
               logfile << "Updating connection for " << i->name << " to use SHA-256 Authentication via autoupdate file." << endl;
               if( i->conn )
                  imc_send_keepalive( NULL, "*@" + i->name );
               found = true;
               continue;
            }
            logfile << "Connection listed in a \'@\' line in the update file already exists." << endl;
            continue;
         }

         i = imc_new_info();
         i->name       = name;
         i->clientpw   = pw1;
         i->serverpw   = pw2;
         i->server     = true;
         i->sha256     = true;
         i->last_connected = imc_now;
         logfile << "Adding Server-Type2 connection for " << i->name << " via autoupdate file. Using SHA-256 Authentication." << endl;
         found = true;
         continue;
      }
      else if( key == "+" )
      {
         imc_info *i;
         string name, host, pw1, pw2;
         unsigned short port;
         bool ifound = false;

         stream >> name >> host >> port >> pw1 >> pw2;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            logfile << "Connection listed in a \'+\' line in the update file already exists." << endl;
            continue;
         }
         i = imc_new_info();
         i->name       = name;
         i->host       = host;
         i->clientpw   = pw1;
         i->serverpw   = pw2;
         i->port       = port;
         i->md5        = false;
         i->sha256     = false;
         i->last_connected = imc_now;
         logfile << "Adding Mud-Type1 connection for " << i->name << " via autoupdate file. Using Standard Authentication." << endl;
         found = true;
         continue;
      }
      else if( key == "*" )
      {
         imc_info *i;
         string name, pw1, pw2;
         bool ifound = false;

         stream >> name >> pw1 >> pw2;

         list<imc_info*>::iterator inf;
         for( inf = infolist.begin(); inf != infolist.end(); ++inf )
         {
            i = *inf;
            if( !str_cmp( name, i->name ) )
            {
               ifound = true;
               break;
            }
         }

         if( ifound )
         {
            logfile << "Connection listed in a \'*\' line in the update file already exists." << endl;
            continue;
         }

         i = imc_new_info();
         i->name       = name;
         i->clientpw   = pw1;
         i->serverpw   = pw2;
         i->md5        = false;
         i->sha256     = false;
         i->last_connected = imc_now;
         logfile << "Adding Mud-Type2 connection for " << i->name << " via autoupdate file. Using Standard Authentication." << endl;
         found = true;
         continue;
      }
      else if( key == "~" )
      {
         string name;

         stream >> name;

         siteinfo->serveradminpwd = name;
         logfile << "Remote administration password has been changed via autoupdate file." << endl;
         found = true;
         continue;
      }
      else
      {
         char buf[LSS];
         stream.getline( buf, LSS );
         logfile << "Bad \'start of line\' character found in the update file." << endl;
      }
   }
   while( !stream.eof() );
   stream.close();
   if( found )
      imc_saveconfig();
}

void ev_mudconnections( const void *data )
{
   check_mudsfile();
   imc_add_event( 15, ev_mudconnections, NULL );
}

void ev_bootcheck( const void *data )
{
   if( exists_file( IMC_BOOT_FILE ) )
   {
      unlink( IMC_BOOT_FILE );
      logfile << "Reboot called from boot file." << endl;
      imc_shutdown();
   }

   if( exists_file( IMC_HBOOT_FILE ) )
   {
      unlink( IMC_HBOOT_FILE );
      logfile << "Hotboot called from boot file." << endl;
      imc_hotboot();
   }
   imc_add_event( 15, ev_bootcheck, NULL );
}

void imc_free_event( imc_event *p )
{
   if( p != imc_event_list )
   {
      imc_event *c = NULL;

      for( c = imc_event_list; c; c = c->next )
         if( c->next == p )
         {
            c->next = p->next;
            break;
         }
   }
   else
      imc_event_list = NULL;

   if( event_freecount > 10 ) /* pick a number, any number */
      deleteptr( p );
   else
   {
      p->next = imc_event_free;
      imc_event_free = p;
      ++event_freecount;
   }
}

void imc_add_event( int when, void (*callback)( const void *), const void *data )
{
   imc_event *p, *cur = NULL, *last = NULL;

   if( imc_event_free )
   {
      p = imc_event_free;
      imc_event_free = p->next;
      --event_freecount;
   }
   else
      p = new imc_event;

   p->when = imc_now + when;
   p->callback = callback;
   p->data = data;

   for( cur = imc_event_list; cur; cur = cur->next )
   {
      if( cur->when > p->when )
         break;
      last = cur;
   }
   p->next = cur;

   if( !last )
      imc_event_list = p;
   else
      last->next = p;
}

void imc_cancel_event( void (*callback)( const void *), const void *data )
{
   imc_event *p, *p_next, **last;

   for( last = &imc_event_list, p = *last; p; p = p_next )
   {
      p_next = p->next;

      if( (!callback) && p->data == data )
      {
         *last = p_next;
         imc_free_event( p );
      }
      else if( (callback) && p->data == data && data != NULL )
      {
         *last = p_next;
         imc_free_event( p );
      }
      else if( p->callback == callback && data == NULL )
      {
         *last = p_next;
         imc_free_event( p );
      }
      else
         last = &p->next;
   }
}

/* 
   added this to help control open and close announcements, tie them to
   the 60 second grace period on close-notify 
   shogar 2/26/2000
*/
imc_event *imc_find_event( void (*callback)( const void *), const void *data )
{
   imc_event *p;

   for( p = imc_event_list; p; p = p->next )
      if( p->callback == callback && p->data == data )
         return p;

   return NULL;
}

int imc_next_event( void (*callback)( const void *), const void *data )
{
   imc_event *p;

   for( p = imc_event_list; p; p = p->next )
      if( p->callback == callback && p->data == data )
         return p->when - imc_now;

   return -1;
}

int imc_get_event_timeout( void )
{
   if( imc_event_list != NULL )
      return( imc_event_list->when - imc_now );

   /* make sure we don't get too backlogged with events */
   return 60;
}

void imc_run_events( time_t newtime )
{
   imc_event *p;
   void (*callback)( const void *);
   const void *data;

   while( imc_event_list )
   {
      p = imc_event_list;

      if( p->when > newtime )
         break;

      imc_event_list = p->next;
      callback = p->callback;
      data = p->data;
      imc_now = p->when;

      imc_free_event(p);

      if( callback )
         (*callback)(data);
      else
         logfile << __FUNCTION__ << ": NULL callback" << endl;
   }
   imc_now = newtime;
}

/* Junks inactive connections after 14 days and informs other servers to update their reminfo lists */
void ev_purgeinfo( const void *data )
{
   imc_reminfo *p;
   list<imc_info*>::iterator inf;

   for( inf = infolist.begin(); inf != infolist.end(); )
   {
      imc_info *i = *inf;
      ++inf;

      if( i->conn )
      {
         i->last_connected = imc_now;
         continue;
      }
      if( i->last_connected < 1 )
      {
         i->last_connected = imc_now;
         continue;
      }
      if( i->server )
         continue;
      if( i->last_connected + 1209600 < imc_now && !i->conn )
      {
         if( ( p = imc_find_reminfo( i->name ) ) )
            imc_delete_reminfo( p );

         send_reminfo_destroy( i->name );
         logfile << "Deleting configuration for " << i->name << ". Expired for non-connection." << endl;
         imc_delete_info( i );
      }
   }
   imc_saveconfig();
   siteinfo->connects = infolist.size(); // This is a hackish fix since the servers seem to be maintaining bad count data elsewhere.
   imc_add_event( 900, ev_purgeinfo, NULL );
}

/*******************************************
 * Network Startup and Shutdown functions. *
 *******************************************/

/* connect to given mud */
bool imc_connect_to( const string& mud )
{
   imc_info *i;
   connection *c;
   struct addrinfo hints, *ai_list, *ai;
   char rport[SSS];
   int n, r, desc = -1;
   ostringstream buf;

   if( !( i = imc_getinfo( mud ) ) )
   {
      logfile << __FUNCTION__ << ": " << mud << " - unknown mud name" << endl;
      return false;
   }

   if( i->conn )
   {
      logfile << __FUNCTION__ << ": " << mud << " - already connected" << endl;
      return false;
   }

   snprintf( rport, SSS, "%hu", i->port );
   memset( &hints, 0, sizeof(struct addrinfo) );
   hints.ai_family = AF_UNSPEC;
   hints.ai_socktype = SOCK_STREAM;
   hints.ai_protocol = IPPROTO_TCP;
   n = getaddrinfo( i->host.c_str(), rport, &hints, &ai_list );

   if( n )
   {
      logfile << __FUNCTION__ << ": getaddrinfo: " << gai_strerror(n) << endl;
      return false;
   }

   for( ai = ai_list; ai; ai = ai->ai_next )
   {
      desc = socket( ai->ai_family, ai->ai_socktype, ai->ai_protocol );
      if( desc < 0 )
         continue;

      if( connect( desc, ai->ai_addr, ai->ai_addrlen ) == 0 )
         break;
      close( desc );
   }
   freeaddrinfo( ai_list );
   if( ai == NULL )
   {
      logfile << __FUNCTION__ << ": socket or connect: failed for " << i->host << " port " << rport << endl;
      return false;
   }

   r = fcntl( desc, F_GETFL, 0 );
   if( r < 0 || fcntl( desc, F_SETFL, O_NONBLOCK | r ) < 0 )
   {
      perror( "imc_connect: fcntl" );
      close( desc );
      return false;
   }
   logfile << __FUNCTION__ << ": Connecting to " << mud << endl;
   c = imc_new_connect();

   c->desc     = desc;
   c->state    = CONN_SERVERCONNECT;
   c->info     = i;

   imc_add_event( IMC_LOGIN_TIMEOUT, ev_login_timeout, c );

   if( i->md5 && !i->sha256 )
      buf << "MD5-AUTH-REQ " << siteinfo->name;
   else if( i->sha256 )
      buf << "SHA256-AUTH-REQ " << siteinfo->name;
   else
      buf << "PW " << siteinfo->name << " " << i->clientpw << " version=2";
   write_buffer( c, buf.str() );

   return true;
}

/* Save current ban list. Short, simple. */
void imc_savebans( void )
{
   ofstream stream;

   stream.open( IMC_BAN_FILE );
   if( !stream.is_open() )
   {
      logfile << "Error opening ban file for write" << endl;
      return;
   }

   stream << "#BANLIST" << endl;

   list<string>::iterator ban;
   for( ban = banlist.begin(); ban != banlist.end(); ++ban )
   {
      string b = *ban;

      stream << b << endl;
   }
   stream.close();
}

/* read an IMC ban file */
void imc_readbans( void )
{
   ifstream stream;
   string word;

   stream.open( IMC_BAN_FILE );
   if( !stream.is_open() )
   {
      logfile << "Couldn't open ban file for read" << endl;
      return;
   }

   banlist.clear();

   do
   {
      stream >> word;

      if( word[0] == '#' )
         continue;
      banlist.push_back( word );
   }
   while( !stream.eof() );
   stream.close();
}

void imc_save_reminfo_table( void )
{
   ofstream stream;

   stream.open( REMINFO_FILE );
   if( !stream.is_open() )
   {
      logfile << "Could not write to reminfo file" << endl;
      return;
   }
   logfile << "Saving remote information table..." << endl;

   list<imc_reminfo*>::iterator rin;
   for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin )
   {
      imc_reminfo *r = *rin;

      stream << "#REMINFO" << endl;
      stream << "Name    " << r->name << endl;
      if( !(*rin)->path.empty() )
         stream << "Path    " << r->path << endl;
      stream << "Expired " << r->expired << endl;
      stream << "SHA256  " << r->sha256 << endl;
      stream << "SeqNum  " << r->top_sequence << endl;
      stream << "End" << endl << endl;
   }
   stream.close();
}

void imc_load_reminfo_table( void )
{
   imc_reminfo *r;
   ifstream stream;

   reminfolist.clear();

   logfile << "Loading remote information table..." << endl;
   stream.open( REMINFO_FILE );
   if( !stream.is_open() )
   {
	logfile << "No remote information table found." << endl;
	return;
   }

   /* Limit crash damage potential in case file is corrupted */
   unlink( REMINFO_FILE );

   do
   {
      string line, key, value;
      char buf[MSS];

      stream.getline( buf, MSS );
      line = buf;
      if( line.empty() )
         continue;

      istringstream lstream( line );
      lstream >> key >> value;
      strip_lspace( key );
      strip_lspace( value );

	if( key == "#REMINFO" )
         r = new imc_reminfo;
      else if( key == "Name" )
         r->name = value;
      else if( key == "Path" )
         r->path = value;
      else if( key == "Expired" )
         r->expired = atoi( value.c_str() );
      else if( key == "SHA256" )
         r->sha256 = atoi( value.c_str() );
      else if( key == "SeqNum" )
         r->top_sequence = atol( value.c_str() );
      else if( key == "End" )
         reminfolist.push_back( r );
      else
         logfile << "Bad data in reminfo table." << endl;
   }
   while( !stream.eof() );
   stream.close();
}

void imc_save_laston_table( void )
{
   ofstream stream;

   stream.open( LASTON_FILE );
   if( !stream.is_open() )
   {
      logfile << "Could not write to laston file" << endl;
      return;
   }
   logfile << "Saving last_on information table..." << endl;

   list<imc_laston*>::iterator lst;
   for( lst = lastonlist.begin(); lst != lastonlist.end(); ++lst )
   {
      imc_laston *l = *lst;

      stream << "#LINFO" << endl;
      stream << "Name    " << l->name << endl;
      stream << "Channel " << l->chan << endl;
      stream << "Time    " << l->last_time << endl;
      stream << "End" << endl << endl;
   }
   stream.close();
}

void imc_load_laston_table( void )
{
   imc_laston *r;
   ifstream stream;

   lastonlist.clear();

   logfile << "Loading last_on information table..." << endl;

   stream.open( LASTON_FILE );
   if( !stream.is_open() )
   {
	logfile << "No last_on information table found." << endl;
	return;
   }

   /* Limit crash damage potential in case file is corrupted */
   unlink( LASTON_FILE );

   do
   {
      string line, key, value;
      char buf[MSS];

      stream.getline( buf, MSS );
      line = buf;
      if( line.empty() )
         continue;

      istringstream lstream( line );
      lstream >> key >> value;
      strip_lspace( key );
      strip_lspace( value );

	if( key == "#LINFO" )
         r = new imc_laston;
      else if( key == "Name" )
         r->name = value;
      else if( key == "Channel" )
         r->chan = value;
      else if( key == "Time" )
         r->last_time = atol( value.c_str() );
      else if( key == "End" )
         lastonlist.push_back( r );
      else
         logfile << "Bad data in laston table." << endl;
   }
   while( !stream.eof() );
   stream.close();
}

void iced_save_channels( void )
{
   ofstream stream;

   stream.open( IMC_CHANNEL_FILE );
   if( !stream.is_open() )
   {
      logfile << "Can't write to channel file" << endl;
      return;
   }

   list<imc_channel*>::iterator chn;
   for( chn = chanlist.begin(); chn != chanlist.end(); ++chn )
   {
      imc_channel *c = *chn;

      stream << "#IMCCHAN" << endl;
	stream << "ChanName     " << c->name << endl;
      stream << "ChanPolicy   " << c->open << endl;
      if( !c->lname.empty() )
         stream << "ChanLocal    " << c->lname << endl;
      if( !c->level.empty() )
         stream << "ChanLevel    " << c->level << endl;
      if( !c->owner.empty() )
         stream << "ChanOwner    " << c->owner << endl;
      if( !c->operators.empty() )
         stream << "ChanOps      " << c->operators << endl;
      if( !c->invited.empty() )
         stream << "ChanInvited  " << c->invited << endl;
      if( !c->excluded.empty() )
         stream << "ChanExcluded " << c->excluded << endl;
	stream << "End" << endl << endl;
   }
   stream.close();
}

void iced_loadchannels( void )
{
   ifstream stream;
   imc_channel *c;

   logfile << "Loading iced channel configuration..." << endl;

   chanlist.clear();

   stream.open( IMC_CHANNEL_FILE );
   if( !stream.is_open() )
   {
      logfile << "Can't open iced channel file" << endl;
      return;
   }

   do
   {
      string key, value;
      char buf[LSS];

      stream >> key;
      stream.getline( buf, LSS );
      value = buf;

      strip_lspace( key );
      strip_lspace( value );

	if( key == "#IMCCHAN" )
	   c = new imc_channel;
      else if( key == "ChanName" )
         c->name = value;
      else if( key == "ChanPolicy" )
         c->open = atoi( value.c_str() );
      else if( key == "ChanLocal" )
         c->lname = value;
      else if( key == "ChanLevel" )
         c->level = value;
      else if( key == "ChanOwner" )
         c->owner = value;
      else if( key == "ChanOps" )
         c->operators = value;
      else if( key == "ChanInvited" )
         c->invited = value;
      else if( key == "ChanExcluded" )
         c->excluded = value;
      else if( key == "End" )
      {
         chanlist.push_back( c );
         logfile << "Channel " << c->name << " loaded. Localname: " << ( !c->lname.empty() ? c->lname : "not set" ) << ". Permission: " << c->level << ". Policy: " << ( c->open ? "open" : "private" ) << endl;
         fixactive( c );
      }
      else
         logfile << "Bad data in channel file." << endl;
   }
   while( !stream.eof() );
   stream.close();
}

/* Save config file - So nonrebooted updates save */
bool imc_saveconfig( void )
{
   ofstream stream;

   stream.open( IMC_CONFIG_FILE );
   if( !stream.is_open() )
   {
      logfile << "Configuration file could not be opened for writing." << endl;
      return false;
   }

   stream << "#" << IMC_VERSIONID << " config file" << endl;
   stream << "FileVersion     4" << endl;
   stream << "ServerName      " << siteinfo->name << endl;
   stream << "ServerPort      " << siteinfo->port << endl;
   stream << "Network         " << siteinfo->network << endl;
   stream << "MaxConnect      " << siteinfo->maxconnects << endl;
   if( !siteinfo->chancreators.empty() )
      stream << "ChannelCreators " << siteinfo->chancreators << endl;
   if( !siteinfo->serveradmins.empty() )
      stream << "ServerAdmins    " << siteinfo->serveradmins << endl;
   if( !siteinfo->serveradminpwd.empty() )
      stream << "ServerAdminPwd  " << siteinfo->serveradminpwd << endl;
   if( siteinfo->bind > 0 )
   {
      struct sockaddr_in binds;

      binds.sin_addr.s_addr = siteinfo->bind;
      stream << "BindIP          " << inet_ntoa( binds.sin_addr ) << endl;
   }
   if( !siteinfo->host.empty() )
      stream << "InfoHost        " << siteinfo->host << endl;
   if( !siteinfo->url.empty() )
      stream << "InfoURL         " << siteinfo->url << endl;

   stream << endl << "#Optional flags for the server. Usage details in the install.txt file." << endl;
   stream << "#These can also be toggled via remote admin if it is enabled." << endl;
   stream << ( siteinfo->remoteadmin ? "RemoteAdmin" : "#RemoteAdmin" ) << endl;
   stream << ( siteinfo->priv ? "Private" : "#Private" ) << endl;
   stream << ( siteinfo->autoupdate ? "Autoupdate" : "#Autoupdate" ) << endl;
   stream << ( siteinfo->prune ? "PruneLinks" : "#PruneLinks" ) << endl;
   stream << ( siteinfo->can_compress ? "Zlib" : "#Zlib" ) << endl;
   stream << ( siteinfo->webstats ? "Webstats" : "#Webstats" ) << endl;
   if( !siteinfo->statsfile.empty() )
      stream << "WebstatsFile " << siteinfo->statsfile << endl;

   stream << endl << "#Network connection listings." << endl;
   stream << "#Server-Type1 connections will attempt to connect at every server reboot and at set intervals while running." << endl;

   list<imc_info*>::iterator inf;
   for( inf = infolist.begin(); inf != infolist.end(); ++inf )
   {
      imc_info *i = *inf;

      if( i->name.empty() || i->clientpw.empty() || i->serverpw.empty()
       || i->host.empty() || i->port == 0 || i->server == false )
         continue;

      stream << "Server-Type1 " << i->name << " " << i->host << " " << i->port << " ";
      stream << i->clientpw << " " << i->serverpw << " " << i->md5 << " " << i->sha256 << " ";
      stream << i->last_connected << endl;
   }

   stream << endl <<"#Server-Type2 connections will be left to their own means to connect." << endl;
   for( inf = infolist.begin(); inf != infolist.end(); ++inf )
   {
      imc_info *i = *inf;

      if( i->name.empty() || i->clientpw.empty() || i->serverpw.empty()
       || i->port > 0 || i->server == false )
         continue;

      stream << "Server-Type2 " << i->name << " " << i->clientpw << " ";
      stream << i->serverpw << " " << i->md5 << " " << i->sha256 << " " << i->last_connected << endl;
   }

   stream << endl << "#Mud-Type1 connections will attempt to connect at every server reboot and at set intervals while running." << endl;

   for( inf = infolist.begin(); inf != infolist.end(); ++inf )
   {
      imc_info *i = *inf;

      if( i->name.empty() || i->clientpw.empty() || i->serverpw.empty()
       || i->host.empty() || i->port == 0 || i->server == true )
         continue;

      stream << "Mud-Type1 " << i->name << " " << i->host << " " << i->port;
      stream << " " << i->clientpw << " " << i->serverpw << " " << i->md5 << " " << i->sha256 << " ";
      stream << i->last_connected << endl;
   }

   stream << endl << "#Mud-Type2 connections will be left to their own means to connect." << endl;

   for( inf = infolist.begin(); inf != infolist.end(); ++inf )
   {
      imc_info *i = *inf;

      if( i->name.empty() || i->clientpw.empty() || i->serverpw.empty()
       || i->port > 0 || i->server == true )
         continue;

      stream << "Mud-Type2 " << i->name << " " << i->clientpw << " ";
      stream << i->serverpw << " " << i->md5 << " " << i->sha256 << " " << i->last_connected << endl;
   }
   stream << endl;
   stream.close();
   return true;
}

/* read config file */
bool imc_readconfig( void )
{
   imc_info *i;
   ifstream stream;
   char buf[LSS];
   time_t last_conn = 0;
   int clcount = 0, rtcount = 0, fileversion = 0;

   stream.open( IMC_CONFIG_FILE );
   if( !stream.is_open() )
   {
      logfile << "Configuration file could not be opened for reading." << endl;
      return false;
   }

   siteinfo = new imc_siteinfo;

   /* Default value unless specified in the config file */
   siteinfo->maxconnects = 40;
   siteinfo->bind = 0;
   siteinfo->prune = false;
   siteinfo->remoteadmin = false;
   siteinfo->autoupdate = false;
   siteinfo->can_compress = false;
   siteinfo->priv = false;
   siteinfo->webstats = false;
   do
   {
      string key, value;

      stream >> key;
      stream.getline( buf, LSS );
      value = buf;
      if( key.empty() || key[0] == '#' )
         continue;

      strip_lspace( key );
      strip_lspace( value );

      if( key == "FileVersion" )
         fileversion = atoi( value.c_str() );
      else if( key == "ServerName" || key == "RouterName" ) 
         siteinfo->name = value;
      else if( key == "BindIP" )
         siteinfo->bind = inet_addr( value.c_str() );
      else if( key == "ServerPort" || key == "RouterPort" )
         siteinfo->port = atoi( value.c_str() );
      else if( key == "MaxConnect" )
         siteinfo->maxconnects = atoi( value.c_str() );
      else if( key == "RemoteAdmin" )
         siteinfo->remoteadmin = true;
      else if( key == "PruneLinks" )
         siteinfo->prune = true;
      else if( key == "Private" )
         siteinfo->priv = true;
      else if( key == "Autoupdate" )
         siteinfo->autoupdate = true;
      else if( key == "Zlib" )
         siteinfo->can_compress = true;
      else if( key == "Webstats" )
         siteinfo->webstats = true;
      else if( key == "WebstatsFile" )
         siteinfo->statsfile = value;
      else if( key == "Network" )
         siteinfo->network = value;
      else if( key == "ChannelCreators" )
         siteinfo->chancreators = value;
      else if( key == "ServerAdmins" || key == "RouterAdmins" )
         siteinfo->serveradmins = value;
      else if( key == "ServerAdminPwd" || key == "RouterAdminPwd" )
         siteinfo->serveradminpwd = value;
      else if( key == "InfoHost" )
         siteinfo->host = value;
      else if( key == "InfoURL" )
         siteinfo->url = value;
      else if( key == "Server-Type1" || key == "Router-Type1" )
      {
         istringstream line( value );
         string name;
         last_conn = 0;

         line >> name;
         if( ( i = imc_getinfo( name ) ) )
         {
            logfile << "Warning: duplicate '" << key << "' lines in configuration file - discarding new one." << endl;
            continue;
         }
         i = imc_new_info();
         i->name = name;
         i->server = true;
         if( fileversion < 3 )
            line >> i->host >> i->port >> i->clientpw >> i->serverpw >> last_conn;
         else if( fileversion == 3 )
            line >> i->host >> i->port >> i->clientpw >> i->serverpw >> i->md5 >> last_conn;
         else
            line >> i->host >> i->port >> i->clientpw >> i->serverpw >> i->md5 >> i->sha256 >> last_conn;

         if( last_conn < 0 )
            last_conn = imc_now;
         i->last_connected = last_conn;
         ++rtcount;
      }
      else if( key == "Server-Type2" || key == "Router-Type2" )
      {
         istringstream line( value );
         string name;
         last_conn = 0;

         line >> name;
         if( ( i = imc_getinfo( name ) ) )
         {
            logfile << "Warning: duplicate '" << key << "' lines in configuration file - discarding new one." << endl;
            continue;
         }
         i = imc_new_info();
         i->name = name;
         i->server = true;
         if( fileversion < 3 )
            line >> i->clientpw >> i->serverpw >> last_conn;
         else if( fileversion == 3 )
            line >> i->clientpw >> i->serverpw >> i->md5 >> last_conn;
         else
            line >> i->clientpw >> i->serverpw >> i->md5 >> i->sha256 >> last_conn;

         if( last_conn < 0 )
            last_conn = imc_now;
         i->last_connected = last_conn;
         ++rtcount;
      }
      else if( key == "Mud-Type1" )
      {
         istringstream line( value );
         string name;
         last_conn = 0;

         line >> name;
         if( ( i = imc_getinfo( name ) ) )
         {
            logfile << "Warning: duplicate '" << key << "' lines in configuration file - discarding new one." << endl;
            continue;
         }
         i = imc_new_info();
         i->name = name;
         i->server = false;

         if( fileversion < 3 )
            line >> i->host >> i->port >> i->clientpw >> i->serverpw >> last_conn;
         else if( fileversion == 3 )
            line >> i->host >> i->port >> i->clientpw >> i->serverpw >> i->md5 >> last_conn;
         else
            line >> i->host >> i->port >> i->clientpw >> i->serverpw >> i->md5 >> i->sha256 >> last_conn;

         if( last_conn < 0 )
            last_conn = imc_now;
         i->last_connected = last_conn;
         ++clcount;
      }
      else if( key == "Mud-Type2" )
      {
         istringstream line( value );
         string name;
         last_conn = 0;

         line >> name;
         if( ( i = imc_getinfo( name ) ) )
         {
            logfile << "Warning: duplicate '" << key << "' lines in configuration file - discarding new one." << endl;
            continue;
         }
         i = imc_new_info();
         i->name = name;
         i->server = false;

         if( fileversion < 3 )
            line >> i->clientpw >> i->serverpw >> last_conn;
         else if( fileversion == 3 )
            line >> i->clientpw >> i->serverpw >> i->md5 >> last_conn;
         else
            line >> i->clientpw >> i->serverpw >> i->md5 >> i->sha256 >> last_conn;

         if( last_conn < 0 )
            last_conn = imc_now;
         i->last_connected = last_conn;
         ++clcount;
      }
      else
         logfile << "Bad line in configuration file: " << key << endl;
   }
   while( !stream.eof() );
   stream.close();
   siteinfo->connects = infolist.size();
   logfile << "Configured " << rtcount << " server" << ( rtcount == 1 ? "" : "s" ) << " and " << clcount << " client" << ( clcount == 1 ? "" : "s" ) << endl;
   return true;
}

void imc_shutdown_network( void )
{
   imc_event *ev, *ev_next;

   logfile << "Closing listening port." << endl;
   close( control );

   imc_save_laston_table();

   list<imc_laston*>::iterator lst;
   for( lst = lastonlist.begin(); lst != lastonlist.end(); )
   {
      imc_laston *l = (*lst);
      ++lst;

      deleteptr( l );
   }
   lastonlist.clear();

   list<connection*>::iterator conn;
   for( conn = connlist.begin(); conn != connlist.end(); )
   {
      connection *c = (*conn);
      ++conn;

      do_close( c );
      imc_extract_connect( c );
   }
   connlist.clear();

   list<imc_reminfo*>::iterator rin;
   for( rin = reminfolist.begin(); rin != reminfolist.end(); )
   {
      imc_reminfo *r = (*rin);
      ++rin;

      imc_delete_reminfo( r );
   }
   reminfolist.clear();

   for( ev = imc_event_list; ev; ev = ev_next )
   {
      ev_next = ev->next;
      deleteptr( ev );
   }
   for( ev = imc_event_free; ev; ev = ev_next )
   {
      ev_next = ev->next;
      deleteptr( ev );
   }
   imc_event_list = imc_event_free = NULL;
}

/* close down imc */
void imc_shutdown( void )
{
   logfile << "Shutting down " << IMC_VERSIONID << endl;

   list<imc_info*>::iterator inf;
   for( inf = infolist.begin(); inf != infolist.end(); )
   {
      imc_info *i = *inf;
      ++inf;

      imc_delete_info( i );
   }
   infolist.clear();

   imc_shutdown_network();

   deleteptr( siteinfo );
   logfile << IMC_VERSIONID << " shutdown completed." << endl;
   logstream.close();
   exit(0);
}

void imc_hotboot( void )
{
   ofstream stream;
   char buf[100], buf2[100];

   stream.open( HOTBOOT_FILE );
   if( !stream.is_open() )
   {
	logfile << "Could not write to hotboot file. Hotboot aborted." << endl;
      return;
   }

   logfile << "Saving connection states...." << endl;

   /* For each connection, save its state */
   list<connection*>::iterator conn;
   for( conn = connlist.begin(); conn != connlist.end(); ++conn )
   {
      connection *c = *conn;

      if( c->state != CONN_COMPLETE ) /* drop those logging on */
         do_close( c ); /* throw'em out */
      else
         stream << c->desc << " " << c->version << " " << c->info->name << " " << c->host << endl;
   }
   stream << "-1" << endl;
   stream.close();

   imc_save_reminfo_table();
   imc_save_laston_table();

   logfile << "Executing hotboot...." << endl;

   /* exec - descriptors are inherited */
   snprintf( buf, 100, "%hu", siteinfo->port );
   snprintf( buf2, 100, "%d", control );

   logstream.close();
   execl( EXE_FILE, "server", buf, "hotboot", buf2, (char *)NULL );
    
   /* Failed - sucessful exec will not return */
   perror( "imc_hotboot: execl" );
   imc_shutdown();
}

void imc_hotboot_recover( void )
{
   connection *c = NULL;
   imc_info *i;
   ifstream stream;

   imc_load_reminfo_table();

   stream.open( HOTBOOT_FILE );
   if( !stream.is_open() ) /* there are some descriptors open which will hang forever then ? */
   {
      logfile << "Hotboot file not found. Exitting." << endl;
      exit( 1 );
   }

   unlink( HOTBOOT_FILE ); /* In case something crashes - doesn't prevent reading */
   do
   {
      string line, desc, version, name, host;
      char buf[MSS];

      stream.getline( buf, MSS );
      line = buf;
      if( line.empty() )
         continue;

      istringstream lstream( line );
      lstream >> desc >> version >> name >> host;
      strip_lspace( desc );
      strip_lspace( version );
      strip_lspace( name );
      strip_lspace( host );
        
      if( desc == "-1" )
         break;

      /* Write something, and check if it goes error-free 
      if( !write_to_descriptor_old( desc, "\r\nThe ether swirls in chaos.\r\n", 0 ) )
      {
         close( desc );
         continue;
      } */
        
      c = imc_new_connect();

      if( desc == "0" )
      {
         logfile << __FUNCTION__ << ": ALERT! Assigning socket 0! BAD BAD BAD! Name: " << name << " Host: " << host << endl;
         imc_extract_connect( c );
         continue;
      }

      c->state = CONN_COMPLETE;
      c->desc = atoi( desc.c_str() );
      c->version = atoi( version.c_str() );
      c->host = host;

      if( !( i = imc_getinfo( name ) ) )
      {
         logfile << "Unable to find imc_info listing for " << name << endl;
         do_close( c );
         imc_extract_connect( c );
         continue;
      }
      c->info = i;
      i->login_time = imc_now;
      i->last_connected = imc_now;
      i->conn = c;
   }
   while( !stream.eof() );
   stream.close();
   logfile << "Hotboot recovery complete." << endl;
}

/* start up listening port */
bool imc_startup_port( void )
{
   int i;
   struct sockaddr_in sa;

   control = -1;

   logfile << "Binding port " << siteinfo->port << " for incoming connections." << endl;

   control = socket( AF_INET, SOCK_STREAM, 0 );
   if( control < 0 )
   {
      logfile << __FUNCTION__ << ": socket call failure" << endl;
      return false;
   }

   i = 1;
   if( setsockopt( control, SOL_SOCKET, SO_REUSEADDR, (void *)&i, sizeof(i) ) < 0 )
   {
      logfile << __FUNCTION__ << ": SO_REUSEADDR unable to be set" << endl;
      close( control );
      return false;
   }

   if( ( i = fcntl( control, F_GETFL, 0 ) ) < 0 )
   {
      logfile << __FUNCTION__ << ": fcntl(F_GETFL) call failure" << endl;
      close( control );
      return false;
   }

   if( fcntl( control, F_SETFL, i | O_NONBLOCK ) < 0 )
   {
      logfile << __FUNCTION__ << ": fcntl(F_SETFL) call failure" << endl;
      close( control );
      return false;
   }

   sa.sin_family      = AF_INET;
   sa.sin_port        = htons( siteinfo->port );
   sa.sin_addr.s_addr = siteinfo->bind; /* already in network order */

   if( bind( control, (struct sockaddr *)&sa, sizeof(sa) ) < 0 )
   {
      logfile << __FUNCTION__ << ": bind call failure" << endl;
      close( control );
      return false;
   }

   if( listen( control, 5 ) < 0 )
   {
      logfile << __FUNCTION__ << ": listen call failure" << endl;
      close( control );
      return false;
   }
   return true;
}

/* start up IMC */
bool imc_startup_network( bool hotboot )
{
   if( hotboot )
      imc_hotboot_recover();
   else if( !imc_startup_port() )
      return false;

   imc_stats.start    = imc_now;
   imc_stats.rx_pkts  = 0;
   imc_stats.tx_pkts  = 0;
   imc_stats.rx_bytes = 0;
   imc_stats.tx_bytes = 0;
   imc_stats.rx_cmpbytes = 0;
   imc_stats.tx_cmpbytes = 0;
   imc_stats.rx_bytessaved = 0;
   imc_stats.tx_bytessaved = 0;
   imc_stats.sequence_drops = 0;

   /* Scan for reboot file */
   imc_add_event( 15, ev_bootcheck, NULL );

   /* Allow updating mud connections list without reboot */
   if( siteinfo->autoupdate )
      imc_add_event( 15, ev_mudconnections, NULL );

   /* Run the initial statdump for the web script -- Xorith */
   if( siteinfo->webstats )
      imc_add_event( 1, ev_statdump, NULL );

   /* Begin the regular purge cycle for mud info listings - every 15 minutes */
   if( siteinfo->prune )
      imc_add_event( 900, ev_purgeinfo, NULL );

   /* Setup autoconnection check event */
   if( !hotboot )
   {
      list<imc_info*>::iterator inf;
      for( inf = infolist.begin(); inf != infolist.end(); ++inf )
      {
         imc_info *i = (*inf);

         if( i->port > 0 && i->conn == NULL )
            imc_connect_to( i->name );
      }
   }
   imc_add_event( 900, ev_autoconnects, NULL );

   /* Send out a keepalive */
   imc_add_event( 2, ev_keepalive, NULL );

   /* Make sure any channel changes get sent out if the server reboots */
   imc_add_event( 4, ev_iced_refresh, NULL );
   return true;
}

void imc_startup( bool hotboot )
{
   imc_now = time( NULL );                  /* start our clock */
   imc_boot = imc_now;

   logfile << IMC_VERSIONID << " initializing" << endl;

   imc_sequencenumber = imc_now;

   if( !imc_readconfig() || !imc_startup_network( hotboot ) )
   {
      logfile << "Unable to start server. Giving up. Sorry." << endl;
      exit( 1 );
   }

   /* Lets register all the default packet handlers */
   imc_register_default_packets();

   imc_load_laston_table();
   imc_readbans();
}

void open_new_log( void )
{
   ostringstream buf;
   int logindex;    

   for( logindex = 1000; ; ++logindex )
   {
      buf.str("");
      buf << "../logs/" << logindex << ".log";
	if( exists_file( buf.str() ) )
	   continue;
      else
	   break;
   }

   logstream.open( buf.str().c_str() );
   if( !logstream.is_open() )
   {
      fprintf( stderr, "Unable to open logfile %s.", buf.str().c_str() );
      exit( 1 );
   }
   logfile.setStream( logstream );
}

static void graceful_exit( int sig )
{
   ofstream stream;

   logfile << "Shutdown called by kill signal." << endl;
   stream.open( "shutdown" );
   if( !stream.is_open() )
   {
      logfile << __FUNCTION__ << ": Unable to drop shutdown file! Server will reboot instead." << endl;
      imc_shutdown();
   }
   stream << "SHUTDOWN" << endl;
   stream.close();
   imc_shutdown();
}

int main( int argc, char **argv )
{
   struct timeval	  last_time;
   bool hotboot = false;

   gettimeofday( &last_time, NULL );

   signal( SIGPIPE, SIG_IGN );
   signal( SIGTERM, graceful_exit );

   if( argc > 1 )
   {
      if( argv[2] && argv[2][0] )
      {
         hotboot = true;
         control = atoi( argv[3] );
      }
      else
         hotboot = false;
   }

   open_new_log();

   imc_startup( hotboot );
   iced_loadchannels();
   if( hotboot )
   {
      imc_request_keepalive();
   }
   server_loop( last_time );

   /* Never reaches this point - server_loop is recursive */
   exit(0);
}