Cyclone-R01-dist/
/*
 * IMC2 version 0.10 - an inter-mud communications protocol
 * Copyright (C) 1996 & 1997 Oliver Jowett <oliver@randomly.org>
 *
 * IMC2 Gold versions 1.00 though 2.00 were developed by MudWorld.
 * Copyright (C) 1999 - 2002 Haslage Net Electronics (Anthony R. Haslage)
 *
 * IMC2 MUD-Net version 3.10 was developed by Alsherok and Crimson Oracles
 * Copyright (C) 2002 Roger Libiez ( Samson )
 * Additional code Copyright (C) 2002 Orion Elder
 * Registered with the United States Copyright Office
 * TX 5-555-584
 *
 * IMC2 Hermes R01-R04 were developed by Rogel
 * Copyright (C) 2003-2004 by Rogel
 *
 * IMC2 Cyclone R01 was developed by Rogel
 * Copyright (C) 2004 by Rogel
 *
 * 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.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/file.h>
#include <signal.h>
#include "imc.h"

#ifndef __FreeBSD__
char *strcasestr( const char *haystack, const char *needle );
#endif

time_t imc_now;  /* current time */
time_t imc_boot; /* current time */
imc_event *imc_event_list, *imc_event_free;
static int event_freecount;

/* sequence memory */
_imc_memory imc_memory[IMC_MEMORY];

unsigned long imc_sequencenumber;	  /* sequence# for outgoing packets */

imc_statistics imc_stats;

CONNECTION *first_connection;
CONNECTION *last_connection;
NETWORK *first_network;
NETWORK *last_network;
REMINFO *first_reminfo;
REMINFO *last_reminfo;
CHANNEL *first_channel;
CHANNEL *last_channel;

static void graceful_exit( int sig )
{
   Log( "%s", "Bridge killed from shell." );
   imc_shutdown();
}

int main( void )
{
   struct timeval	  last_time;

   gettimeofday( &last_time, NULL );

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

   imc_startup( );

   bridge_loop(last_time);

   /* Never reaches this point - bridge_loop is recursive */
   exit(0);
}

void bridge_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( "main: select: stall" );
            exit( 1 );
        }
    }

	gettimeofday( &last_time, NULL );
    
    bridge_loop(last_time);
    return;
}

// License for strlcpy located at ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/strlcpy.c
#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 */

// License for strlcat located at ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/strlcat.c

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

/*
 *  Error logging
 */
void Log( const char *format, ... )
{
   char buf[LSS];
   struct timeval proper_time;
   time_t current_time;
   char *strtime;
   va_list ap;

   va_start( ap, format );
   vsnprintf( buf, LSS, format, ap );
   va_end( ap );

   gettimeofday( &proper_time, NULL );
   current_time = proper_time.tv_sec;

   strtime                    = ctime( &current_time );
   strtime[strlen(strtime)-1] = '\0';
   fprintf( stderr, "%s :: %s\n", strtime, buf );

   return;
}

/* escape2: escape " -> \", \ -> \\, CR -> \r, LF -> \n */

static const char *escape2(const char *data)
{
   static char buf[IMC_DATA_LENGTH];
   unsigned int i = 0, j = 0;

   buf[0] = '\0';

   if ( !data || data[0] == '\0' )
       return buf;

   for ( i = 0, j = 0; i < IMC_DATA_LENGTH && data[j] != '\0'; i++, j++ ) 
   {
       if ( data[j] == '\n' || data[j] == '\r' || data[j] == '\\' || data[j] == '"' )
       {
           if ( i + 1 == IMC_DATA_LENGTH )
               break;

           buf[i++] = '\\';

           if ( data[j] == '\n' )
               buf[i] = 'n';
           else if ( data[j] == '\r' )
               buf[i] = 'r';
           else if ( data[j] == '\\' )
               buf[i] = '\\';
           else
               buf[i] = '\"';
       }
       else
           buf[i] = data[j];
   }

   buf[i] = '\0';

   return buf;
}

/* printkeys: print key-value pairs, escaping values */
static const char *printkeys( PACKET *data )
{
   static char buf[IMC_DATA_LENGTH];
   char temp[IMC_DATA_LENGTH];
   unsigned char i;

   buf[0] = '\0';

   if ( !data )
       return buf;

   for( i = 0; i < IMC_MAX_KEYS; i++ )
   {
      if( !data->key[i] )
         continue;

      if( !strchr( data->value[i], ' ' ) )
         snprintf( temp, IMC_DATA_LENGTH, "%s=%s ", data->key[i], escape2( data->value[i] ) );
      else
         snprintf( temp, IMC_DATA_LENGTH, "%s=\"%s\" ", data->key[i], escape2( data->value[i] ) );

      strlcat( buf, temp, IMC_DATA_LENGTH );
   }

   return buf;
}

/* add "key=value" to "p" */
void imc_addkey( PACKET *p, const char *key, const char *value )
{
   unsigned char i;

   for( i = 0; i < IMC_MAX_KEYS; i++ )
   {
      if( p->key[i] && STR_CEQL( key, p->key[i] ) )
      {
         DISPOSE( p->key[i] );
         DISPOSE( p->value[i] );
         break;
      }
   }
   if( !value || value[0] == '\0' )
      return;

   for( i = 0; i < IMC_MAX_KEYS; i++ )
   {
      if( !p->key[i] )
      {
         p->key[i]   = strdup( key );
         p->value[i] = strdup( value );
         return;
      }
   }
}

/* add "key=value" for an integer value */
void imc_addkeyi( PACKET *p, const char *key, int value )
{
   char temp[20];

   snprintf( temp, 20, "%d", value );
   imc_addkey( p, key, temp );
}

/* parsekeys: extract keys from string */
static void parsekeys( const char *string, PACKET *data )
{
   const char *p1;
   char *p2;
   char k[IMC_DATA_LENGTH], v[IMC_DATA_LENGTH];
   bool quote;

   p1 = string;

   while (*p1)
   {
      while( *p1 && isspace(*p1) )
         p1++;

      p2 = k;
      while( *p1 && *p1 != '=' && p2-k < IMC_DATA_LENGTH-1 )
         *p2++=*p1++;
      *p2=0;

      if( !k[0] || !*p1 )		/* no more keys? */
         break;

      p1++;			/* skip the '=' */

      if( *p1 == '"' )
      {
         p1++;
         quote = TRUE;
      }
      else
         quote = FALSE;

      p2 = v;
      while( *p1 && (!quote || *p1 != '"') && (quote || *p1 != ' ') && p2-v < IMC_DATA_LENGTH+1 )
      {
         if( *p1 == '\\' )
         {
	      switch( *(++p1) )
	      {
	         case '\\':
	            *p2++='\\';
	         break;
	         case 'n':
	            *p2++='\n';
	         break;
	         case 'r':
	            *p2++='\r';
	         break;
	         case '"':
	            *p2++='"';
	         break;
	         default:
	            *p2++=*p1;
	         break;
	      }
	      if( *p1 )
	         p1++;
         }
         else
	      *p2++ = *p1++;
      }

      *p2 = 0;

      if( !v[0] )
         continue;

      imc_addkey( data, k, v );

      if( quote && *p1 )
         p1++;
   }
}

/* clear all keys in "p" */
void imc_initdata( PACKET *p )
{
   unsigned char i;

   if ( !p )
       return;

   for( i = 0; i < IMC_MAX_KEYS; i++ )
   {
      p->key[i]   = NULL;
      p->value[i] = NULL;
   }
}

/* free all the keys in "p" */
void imc_freedata( PACKET *p )
{
   unsigned char i;

   if ( !p )
       return;

   for( i = 0; i < IMC_MAX_KEYS; i++ )
   {
      if( p->key[i] )
         DISPOSE( p->key[i] );
      if( p->value[i] )
         DISPOSE( p->value[i] );
   }
}

/*  imc_getarg: extract a single argument (with given max length) from
 *  argument to arg; if arg==NULL, just skip an arg, don't copy it out
 */
const char *imc_getarg( const char *argument, char *arg, unsigned int length)
{
  unsigned int len = 0;

  if ( !argument || argument[0] == '\0' )
  {
      if ( arg )
          arg[0] = '\0';

      return argument;
  }

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

  if (arg)
    while (*argument && !isspace(*argument) && len < length-1)
      *arg++=*argument++, len++;
  else
    while (*argument && !isspace(*argument))
      argument++;

  while (*argument && !isspace(*argument))
    argument++;

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

  if (arg)
    *arg = '\0';

  return argument;
}

char *generate2( PACKET *p, AFFILIATE *a )
{
   static char temp[IMC_PACKET_LENGTH];
   char newpath[IMC_PATH_LENGTH];

   if( !p->type[0] || !p->i.from[0] || !p->i.to[0] )
   {
      Log( "generate2: bad packet - type: %s from: %s to: %s path: %s data: %s", p->type, p->i.from,
              p->i.to, p->i.path, printkeys(p) );
      return NULL;		/* catch bad packets here */
   }

   if( !p->i.path[0] )
      strlcpy( newpath, a->name, IMC_PATH_LENGTH );
   else
      snprintf( newpath, IMC_PATH_LENGTH, "%s!%s", p->i.path, a->name );

   snprintf( temp, IMC_PACKET_LENGTH, "%s %lu %s %s %s %s", 
           p->i.from, p->i.sequence, newpath, p->type, p->i.to, printkeys( p ) );
   return temp;
}

PACKET *interpret2( const char *argument )
{
   char seq[20];
   static PACKET out;

   imc_initdata( &out );
   argument = imc_getarg( argument, out.i.from, IMC_NAME_LENGTH );
   argument = imc_getarg( argument, seq, 20 );
   argument = imc_getarg( argument, out.i.path, IMC_PATH_LENGTH );
   argument = imc_getarg( argument, out.type, IMC_TYPE_LENGTH );
   argument = imc_getarg( argument, out.i.to, IMC_NAME_LENGTH );

   if( !out.i.from[0] || !seq[0] || !out.i.path[0] || !out.type[0] || !out.i.to[0] )
   {
      Log( "interpret2: bad packet - type: %s from: %s to: %s path: %s seq: %s", out.type, out.from,
              out.i.to, out.i.path, seq );
      return NULL;
   }

   parsekeys( argument, &out );

   out.i.sequence = strtoul( seq, NULL, 10 );
   return &out;
}

_imc_vinfo imc_vinfo[] =
{
  { 0, NULL, NULL },
  { 1, NULL, NULL },
  { 2, generate2, interpret2 }
};

/*
 * Key/value manipulation
 */

/* get the value of "key" from "p"; if it isn't present, return "def" */
const char *imc_getkey( const PACKET *p, const char *key, const char *def )
{
   unsigned char i;

   for( i = 0; i < IMC_MAX_KEYS; i++ )
      if( p->key[i] && STR_CEQL(p->key[i], key))
         return p->value[i];

   return def;
}

/* identical to imc_getkey, except get the integer value of the key */
int imc_getkeyi( const PACKET *p, const char *key, int def )
{
   unsigned char i;

   for( i = 0; i < IMC_MAX_KEYS; i++ )
      if( p->key[i] && STR_CEQL(p->key[i], key))
         return atoi( p->value[i] );

   return def;
}

/*
 *  String manipulation functions, mostly exported
 */

/* return 'mud' from 'player@mud' */
const char *imc_mudof(const char *fullname)
{
   static char buf[IMC_MNAME_LENGTH];
   char *where;

   buf[0] = '\0';

   if ( !fullname || fullname[0] == '\0' )
       return buf;

   snprintf( buf, IMC_MNAME_LENGTH, "%s", (where = strchr( fullname, '@' )) != NULL ? where + 1 : fullname );
   return buf;
}

/* return 'player' from 'player@mud' */
const char *imc_nameof(const char *fullname)
{
   static char buf[IMC_PNAME_LENGTH];
   unsigned char i = 0, j = 0;

   buf[0] = '\0';

   if ( !fullname || fullname[0] == '\0' )
       return buf;

   for ( i = 0, j = 0; i < IMC_PNAME_LENGTH && fullname[j] != '\0' && fullname[j] != '@'; i++, j++ )
       buf[i] = fullname[j];

   buf[i] = '\0';
   return buf;
}

/* Convert a string from internetwork format to local format */
char *imc_itol( const char *name )
{
    static char buf[IMC_NAME_LENGTH];
    unsigned char i = 0, j = 0;

    buf[0] = '\0';

    if ( !name || name[0] == '\0' ) 
        return buf;

    if ( STR_CEQL( imc_mudof( name ), "*" ) || !strchr( name, ':' ))
    {
        strlcpy( buf, name, IMC_NAME_LENGTH );
        return buf;
    }

    for ( i = 0; i < IMC_NAME_LENGTH && name && name[i] != '\0' && name[i] != '@'; i++ )
    {
        if ( name[i] == ':' )
            j = i;

        buf[i] = name[i];
    }

    buf[j] = '@';
    buf[i] = '\0';

    return buf;
}

/* convert a string from local format to internetwork format */
char *imc_ltoi( const char *name, AFFILIATE *a )
{
    char newstring[IMC_NAME_LENGTH];
    static char buf[IMC_NAME_LENGTH];
    unsigned char i = 0;

    buf[0] = '\0';

    if ( !a || !name || name[0] == '\0' )
        return buf;

    for( i = 0; name && name[i] != '\0' && i < IMC_NAME_LENGTH; i++ ) 
    {
        if ( name[i] == '@' )
            newstring[i] = ':';
        else
            newstring[i] = name[i];
    }

    newstring[i] = '\0';

    snprintf( buf, IMC_NAME_LENGTH, "%s@%s", newstring, a->name );

    return buf;
}

/* return 'e' from 'a!b!c!d!e' */
char *imc_lastinpath( const char *path )
{
   const char *where;
   static char buf[IMC_NAME_LENGTH];

   where = path + strlen(path)-1;
   while( *where != '!' && where >= path )
      where--;

   strlcpy( buf, where+1, IMC_NAME_LENGTH );
   return buf;
}

/* return 'b' from 'a!b!c!d!e' */
char * imc_hubinpath( char *path, AFFILIATE *a )
{
    const char *separator, *where;
    static char buf[IMC_NAME_LENGTH];
    unsigned int i = 1;

    buf[0] = '\0';

    if ( !path || path[0] == '\0')
        return buf;

    if ( (separator = strchr( path, '!' ) ) == NULL )
       return a->name;

    for ( where = separator + 1; *where != '!' && *where != '\0'; where++ )
        i++;

    strlcpy( buf, separator + 1, i );
    return buf;
}

/* return 'a' from 'a!b!c!d!e' */
char *imc_firstinpath( const char *path )
{
   static char buf[IMC_NAME_LENGTH];
   char *p;

   buf[0] = '\0';

   if ( !path || path[0] == '\0' )
       return buf;

   for( p = buf; *path && *path != '!'; *p++=*path++ )
    ;

   *p = 0;
   return buf;
}

/* get some IMC stats, return a string describing them */
char *imc_getstats( void )
{
   static char buf[IMC_DATA_LENGTH];
   unsigned int evcount = 0;
   imc_event *ev = imc_event_list;

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

       
   snprintf( buf, IMC_DATA_LENGTH,
           "~WGeneral Bridge Statistics\n\r"
           "~cReceived packets   : ~C%ld\n\r"
           "~cReceived bytes     : ~C%ld\n\r"
           "~cTransmitted packets: ~C%ld\n\r"
           "~cTransmitted bytes  : ~C%ld\n\r"
           "~cMaximum packet size: ~C%d\n\r"
           "~cPending events     : ~C%d\n\r"
           "~cSequence drops     : ~C%d\n\r"
           "~cLast Bridge Boot   : ~C%s\n\r",
           imc_stats.rx_pkts, imc_stats.rx_bytes, imc_stats.tx_pkts, imc_stats.tx_bytes,
           imc_stats.max_pkt, evcount, imc_stats.sequence_drops, ctime( &imc_boot ) );

           return buf;
}
/*
 *  imc_reminfo handling
 */

/* find an info entry for "name" */
REMINFO *imc_find_reminfo( const char *name, NETWORK *network )
{
   REMINFO *p;

   if ( !name || name[0] == '\0' )
       return NULL;

   for( p = first_reminfo; p; p = p->next )
   {
      if( STR_CEQL( name, p->name ) && network == p->network )
         return p;
   }
   return NULL;
}

/* create a new info entry, insert into list */
REMINFO *imc_new_reminfo( char *mud, NETWORK *network )
{
   REMINFO *p, *mud_prev;

   if ( !mud || mud[0] == '\0' || !network )
       return NULL;

   CREATE( p, REMINFO, 1 );

   p->name    = strdup( mud );
   p->netname = NULL;
   p->web = NULL;
   p->version = NULL;
   p->path   = NULL;
   p->top_sequence = 0;
   p->network = network;

   for( mud_prev = first_reminfo; mud_prev; mud_prev = mud_prev->next )
      if( strcasecmp( mud_prev->name, mud ) >= 0 )
         break;

   if( !mud_prev )
      LINK( p, first_reminfo, last_reminfo, next, prev );
   else
      INSERT( p, mud_prev, first_reminfo, next, prev );

   return p;
}

/* delete the info entry "p" */
void imc_delete_reminfo( REMINFO *p )
{
   if( !first_reminfo || !p )
      return;

   UNLINK( p, first_reminfo, last_reminfo, next, prev );

   p->network = NULL;

   if ( p->name )
      DISPOSE( p->name );
   if ( p->netname )
      DISPOSE( p->netname );
   if ( p->web )
      DISPOSE( p->web );
   if ( p->version )
      DISPOSE( p->version );
   if ( p->path )
      DISPOSE( p->path );

   imc_cancel_event( NULL, p );
   DISPOSE( p );
}

/* get info struct for given mud */
NETWORK *imc_getnetwork( const char *mud )
{
   NETWORK *p;

   for( p = first_network; p; p = p->next )
      if( STR_CEQL( mud, p->netname ) )
         return p;

   return NULL;
}

/* get name of a connection */
const char *imc_getconnectname( const CONNECTION *c )
{
   static char buf[IMC_NAME_LENGTH];

   if( c->affiliate && c->affiliate->network && c->affiliate->network->netname && c->affiliate->name )
       snprintf( buf, IMC_NAME_LENGTH, "%s(as %s)", c->affiliate->network->netname, c->affiliate->name );
   else
       snprintf( buf, IMC_NAME_LENGTH, "%s", c->ip );

   return buf;
}

/* set up for a reconnect in 20 seconds */
void imc_setup_reconnect( AFFILIATE *a )
{
   int t;

   t = imc_next_event( ev_reconnect, a );

   if( t >= 0 && t < 20)
       return;

   if ( t > 0 )
       imc_cancel_event( ev_reconnect, a );

   imc_add_event( 20, ev_reconnect, a );
   return;
}

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

   CREATE( c, CONNECTION, 1 );

   c->state    = CONN_NONE;
   c->desc     = -1;
   c->insize   = IMC_MINBUF;
   CREATE( c->inbuf, char, c->insize );
   c->outsize  = IMC_MINBUF;
   CREATE( c->outbuf, char, c->outsize );
   c->inbuf[0] = c->outbuf[0] = '\0';
   c->affiliate = NULL;
   c->ip       = NULL;
   c->newoutput = 0;

   LINK( c, first_connection, last_connection, next, prev );
   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 )
   {
      Log( "imc_extract_connect: non-closed connection" );
      return;
   }

   DISPOSE( c->inbuf );
   DISPOSE( c->outbuf );

   if ( c->ip )
       DISPOSE( c->ip );

   UNLINK( c, first_connection, last_connection, next, prev );
   imc_cancel_event( NULL, c );
   DISPOSE( c );
}

/* update our routing table based on a packet received with path "path" */
static void updateroutes( char *path, AFFILIATE *a )
{
   REMINFO *p;
   char *sender;
   char *temp;

   if ( !path || path[0] == '\0' || !a || !a->name || a->name[0] == '\0' )
       return;

   /* loop through each item in the path, and update routes to there */

   temp = path;
   while( temp && temp[0] )
   {
      sender = imc_firstinpath( temp );

      if( sender && sender[0] != '\0' && !STR_CEQL( sender, a->name ) )
      {
         /* not from us */
         /* check if its in the list already */

         p = imc_find_reminfo( sender, a->network );
         if( !p )			/* not in list yet, create a new entry */
         {
             p = imc_new_reminfo( sender, a->network );

             p->netname = strdup("unknown");
             p->web     = strdup("unknown");
             p->version = strdup("unknown");
         }

         if (p->path)
             DISPOSE(p->path);
         p->path = strdup( temp );
      }
      /* get the next item in the path */
      temp = strchr( temp, '!' );
      if( temp )
        temp++;			/* skip to just after the next '!' */
   }
}

/* return 1 if 'name' is a part of 'path'  (internal) */
bool inpath(const char *path, const char *name)
{
  char buf[IMC_MNAME_LENGTH+3];

  if ( !path || path[0] == '\0' || !name || name[0] == '\0' )
      return FALSE;

  if (STR_CEQL(path, name))
      return TRUE;

  snprintf(buf, IMC_MNAME_LENGTH+3, "%s!", name);
  if (STRN_CEQL(path, buf, strlen(buf)))
      return TRUE;

  snprintf(buf, IMC_MNAME_LENGTH+3, "!%s", name);
  if (strlen(buf) < strlen(path) &&
      STR_CEQL(path + strlen(path) - strlen(buf), buf))
      return TRUE;

  snprintf(buf, IMC_MNAME_LENGTH+3, "!%s!", name);
  if (strcasestr(path, buf))
      return TRUE;

  return FALSE;
}

/*
 *  Core functions (all internal)
 */

/* notify everyone of the closure - shogar */
void imc_close_notify( AFFILIATE *a )
{
   REMINFO *r = NULL, *r_next = NULL;

   if ( !a || !a->equiv )
       return;

   for ( r = first_reminfo; r; r = r->next )
   {
       PACKET out;

       if ( r->network != a->network )
           continue;
   
       imc_initdata( &out );
       strlcpy( out.type, "close-notify", IMC_TYPE_LENGTH );
       strlcpy( out.to, "*@*", IMC_NAME_LENGTH );
       imc_addkey( &out, "versionid", IMC_VERSIONID );
       imc_addkey( &out, "host", r->name );
       imc_send( &out, a->equiv );
       imc_freedata( &out );
   }

   for ( r = first_reminfo; r; r = r_next )
   {
       r_next = r->next;

       if ( r->network == a->network )
           imc_delete_reminfo( r );
   }

   /* log and announce if closed via event - shogar 2/26/2000 */
   Log( "Bridge has closed the connection to %s(as %s).", a->network->netname, a->name );
}

/* close given connection */
void do_close( CONNECTION *c )
{
   if( c->state == CONN_NONE )
      return;

   c->state = CONN_NONE;

   if ( c->desc )
   {
       close( c->desc );
       c->desc = 0;
   }

   if ( c->affiliate )
   {
       c->affiliate->connection = NULL;

       if ( !c->affiliate->full_bridge )
       {
           REMINFO *r = NULL, *r_next = NULL;

           Log( "Bridge has closed the connection to %s.", imc_getconnectname( c ) );

           for ( r = first_reminfo; r; r = r_next )
           {
               r_next = r->next;

               if ( r->network == c->affiliate->network )
                   imc_delete_reminfo( r );
           }
       }
       else
           imc_add_event( 60, ev_close_notify, c->affiliate );

       /* Handle reconnects */
       imc_setup_reconnect( c->affiliate );

       c->affiliate = NULL;
   }
   else
       Log( "Bridge has closed the connection to %s.", imc_getconnectname( c ) );

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

/* read waiting data from descriptor.
 * read to a temp buffer to avoid repeated allocations
 */
static void do_read( CONNECTION *c )
{
   unsigned int size;
   int r;
   char temp[IMC_MAXBUF];
   char *newbuf;
   unsigned int newsize;

   r = read( c->desc, temp, IMC_MAXBUF-1 );
   if( !r || ( r < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) )
   {
       if( r < 0 )                    /* read error */
           Log( "%s: do_read: %s", imc_getconnectname( c ), strerror(errno) );
       else                        /* socket was closed */
           Log( "%s: do_read: connection closed from other side", imc_getconnectname( c ) );

      do_close( c );
      return;
   }
  
   if( r < 0 )			/* EAGAIN error */
      return;

   temp[r] = '\0';

   size = strlen( c->inbuf ) + r + 1;

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

   strlcat( c->inbuf, temp, c->insize );

   imc_stats.rx_bytes += r;
}

/* write to descriptor */
static void do_write( CONNECTION *c )
{
   unsigned int size = 0;
   int w = 0;

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

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

   w = write( c->desc, c->outbuf, size );
   if( !w || ( w < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) )
   {
       if( w < 0 )			/* write error */
           Log( "%s: do_write: %s.", imc_getconnectname( c ), strerror(errno) );
       else			/* socket was closed */
           Log( "%s: do_write: connection closed from other side", imc_getconnectname( c ) );

      do_close( c );
      return;
   }

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

   strlcpy( c->outbuf, c->outbuf + w, c->outsize );

   imc_stats.tx_bytes += w;

}

/* put a line onto descriptors output buffer */
static void do_send( CONNECTION *c, const char *line )
{
   unsigned int len;
   char *newbuf;
   unsigned int newsize = c->outsize;

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

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

   len = strlen( c->outbuf ) + strlen( line ) + 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->outsize  );

   strlcat( c->outbuf, "\n\r", c->outsize );
}

/*  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 *getline(char *buffer, size_t len)
{
  unsigned 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;
}

static int memory_head; /* next entry in memory table to use, wrapping */

/* checkrepeat: check for repeats in the memory table */
bool checkrepeat(const char *mud, unsigned long seq, AFFILIATE *a )
{
  unsigned int i;

  for (i=0; i<IMC_MEMORY; i++)
    if (imc_memory[i].from && seq == imc_memory[i].sequence && STR_CEQL(mud, imc_memory[i].from)
            && a == imc_memory[i].affiliate )
      return TRUE;

  /* not a repeat, so log it */

  if (imc_memory[memory_head].from)
    DISPOSE(imc_memory[memory_head].from);

  imc_memory[memory_head].from     = strdup(mud);
  imc_memory[memory_head].sequence = seq;
  imc_memory[memory_head].affiliate = a;
  
  memory_head++;
  if (memory_head==IMC_MEMORY)
    memory_head=0;

  return FALSE;
}

/* send a packet to a mud using the right version */
static void do_send_packet( CONNECTION *c, PACKET *p, AFFILIATE *a )
{
   const char *output;
   unsigned char v;

   v = c->version;
   
   if( v > IMC_VERSION )
      v = IMC_VERSION;

   output = ( *imc_vinfo[v].generate )(p, a);

   if( output )
   {
      imc_stats.tx_pkts++;

      if( strlen( output ) > imc_stats.max_pkt )
         imc_stats.max_pkt = strlen( output );

      do_send( c, output );
   }
}

/* checks if a player and/or mud is on a network's blacklist */
bool is_blacklisted( char *name, NETWORK *network )
{
    char arg[SSS];
    const char *p;

    if ( !name || name[0] == '\0' || !network )
        return TRUE;

    if ( !network->blacklist  || network->blacklist[0] == '\0' )
        return FALSE;

    p = imc_getarg( network->blacklist, arg, SSS );

    while ( arg && arg[0] )
    {
        if ( strcasestr( name, arg ) )
            return TRUE;

        p = imc_getarg( p, arg, SSS );
    }

    return FALSE;
}

/* Checks if a packet is allowed to be forwarded to another network - default is reject */
bool can_forward( PACKET *p, AFFILIATE *a, bool sendlocal )
{

   if ( STR_CEQL( p->type, "keepalive-request" ) )
   {
       if ( sendlocal )
           return TRUE;
   }
   else if ( STR_CEQL( p->type, "is-alive" ) || STR_CEQL( p->type, "close-notify" ) )
   {
       if ( a->full_bridge || sendlocal )
           return TRUE;
   }
   else if ( STR_CEQL( p->type, "ice-msg-r" ) || STR_CEQL( p->type, "ice-msg-b" ) )
   {
       char channel_name[SSS];

       strlcpy( channel_name, find_channel_equivalent( imc_getkey( p, "channel", "" ),
               a->network, a->equiv->network ), SSS );

       /* if channel is bridged, convert sender and channel keys */
       if ( channel_name && channel_name[0] != '\0' )
       {
           char sender[IMC_NAME_LENGTH];

           if ( !a->full_bridge )
           {
               strlcpy( sender, imc_ltoi( imc_getkey( p, "sender", "" ), a->equiv ), 
                       IMC_NAME_LENGTH );

               imc_addkey( p, "sender", sender );
           }

           imc_addkey( p, "channel", channel_name );
           imc_addkeyi( p, "echo", 0 );

           return TRUE;
       }
   }
   else if ( STR_CEQL( p->type, "ping" ) )
   {
       if ( a->full_bridge )
           return TRUE;
   }
   else if ( STR_CEQL( p->type, "tell" ) || STR_CEQL( p->type, "beep" ) 
           || STR_CEQL( p->type, "who-reply" ) || STR_CEQL( p->type, "who" ) 
           || STR_CEQL( p->type, "ping-reply" ) || STR_CEQL( p->type, "whois" )
           || STR_CEQL( p->type, "whois-reply" ))
       return TRUE;

   return FALSE;
}

/* forward a packet - main routing function, all packets pass through here */
static void forward( PACKET *p, AFFILIATE *a, bool readlocal )
{
   REMINFO *route = NULL;
   AFFILIATE *direct = NULL;
   const char *to;
   bool isbroadcast = FALSE, sendlocal = FALSE;

   /* check for duplication, and register the packet in the sequence memory */
   if( p->i.sequence && checkrepeat( imc_mudof( p->i.from ), p->i.sequence, a ) )
      return;

   /* find the locally sent packets */
   if ( a->full_bridge )
       sendlocal = STR_CEQL( imc_mudof( p->i.from ), a->name );
   else
       sendlocal = STR_CEQL( p->i.from, "*" );

   /* check for packets we've already forwarded */
   if( inpath( p->i.path, a->name ) 
           || ( !a->full_bridge && STR_CEQL( imc_mudof( p->i.from ), a->name ) )
           || ( isbroadcast && strchr( p->i.from, ':' ) ) )
      return;

   /* update our routing info */
   updateroutes( p->i.path, a );

   /* check for really old packets */
   if( (route = imc_find_reminfo( imc_mudof( p->i.from ), a->network )) != NULL )
   {
      if( ( p->i.sequence+IMC_PACKET_LIFETIME ) < route->top_sequence )
      {
         imc_stats.sequence_drops++;
         return;
      }
      if( p->i.sequence > route->top_sequence )
         route->top_sequence = p->i.sequence;
   }
       
   /* if we received an is-alive, let it go through a full bridge */
   if ( readlocal && STR_CEQL( p->type, "is-alive" ) )
       strlcpy( p->i.to, "*@*", IMC_NAME_LENGTH );

   to = imc_mudof( p->i.to );
   isbroadcast = STR_EQL( to, "*" ); /* broadcasts are, well, broadcasts */

   /* Make sure they are not blacklisted */
   if ( a->equiv && is_blacklisted( p->i.from, a->equiv->network ) && !STR_CEQL( p->type, "is-alive" )
           && !STR_CEQL( p->type, "close-notify" ) && !STR_CEQL( p->type, "who-reply" ) 
           && !STR_CEQL( p->type, "whois-reply" ) && !STR_CEQL( p->type, "ping-reply" ) )
   {
       if ( !isbroadcast )
       {
           char newstring[SSS];

           /* Let's be polite and tell them if they are blacklisted */
           snprintf( newstring, SSS, "You have been blacklisted from the %s network.",
                   a->equiv->network->netname );
           imc_send_tell( p->i.from, newstring, a );

           Log( "%s was denied access to %s(packet type: %s to: %s).",
                   p->i.from, a->equiv->network->netname, p->type, p->i.to );
           return;
       }

       return;
   }

   /* forward to our bridge if it's for us */
   if( readlocal )
   {
      strlcpy( p->to, imc_nameof( p->i.to ), IMC_NAME_LENGTH );    /* strip the name from the 'to' */
      strlcpy( p->from, p->i.from, IMC_NAME_LENGTH );

      imc_recv( p, a );

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

   /* check if we should just drop it (policy rules) */
   if( !can_forward(p, a, sendlocal) )
      return;

   /* if it was not made by us, have it go to other side of the bridge */
   if ( !sendlocal )
       a = a->equiv;

   /* convert 'to' fields that we have a route for to a hop along the route */
   if( !isbroadcast && ( route = imc_find_reminfo( to, a->network ) ) != NULL 
           && route->path != NULL && !inpath( p->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( !STR_CEQL( to, imc_lastinpath(route->path) )  
              &&  a != NULL && a->connection )
         direct = a;

      to = imc_lastinpath(route->path);
   }

   /* check for a direct connection */
   if( ( !a || !a->connection ) && ( !direct || !direct->connection )  )
   {
	 Log( "No path available for packet sent from %s(%s) to %s(%s) of type %s.", 
             imc_mudof(p->i.from), a->network->netname, p->i.to, a->name, p->type );
	 return;
   }
   
   /* bridge should be only thing seen if just a relay */
   if ( !a->full_bridge )
   {
       strlcpy( p->from, imc_ltoi( p->i.from, a ), IMC_NAME_LENGTH );
       strlcpy( p->i.from, p->from, IMC_NAME_LENGTH );
   
       p->i.sequence = imc_sequencenumber++;

       if ( !imc_sequencenumber )
           imc_sequencenumber++;

       p->i.path[0] = 0;
   }

   /* but only if they haven't seen it (sanity check) */
   if( a && a->connection && !inpath( p->i.path, a->name ) )
       do_send_packet( a->connection, p, a );

   /* send on direct connection, if we have one */
   if( direct && direct != a && direct->connection && !inpath( p->i.path, direct->name ) )
       do_send_packet( direct->connection, p, a );
}

/* handle a password response from a server */
static void serverpassword( CONNECTION *c, const char *argument )
{
   const char *p;
   char type[3], name[IMC_MNAME_LENGTH], pw[IMC_PW_LENGTH], version[20];

   p = imc_getarg( argument, type, 3 );	/* has to be PW */
   p = imc_getarg( p, name, IMC_MNAME_LENGTH );
   p = imc_getarg( p, pw, IMC_PW_LENGTH );
   p = imc_getarg( p, version, 20 );

   if( !STR_CEQL( type, "PW" ) )
   {
      Log( "%s: non-PW password packet on connect: %s", imc_getconnectname( c ), argument );
      do_close( c );
      return;
   }

   if( !c->affiliate || !STR_EQL( c->affiliate->network->serverpw, pw ) )
   {
      Log( "%s: password failure for %s", imc_getconnectname( c ), name );
      do_close( c );
      return;
   }

   if( c->affiliate->connection )	/* kill old connections */
      do_close( c->affiliate->connection );

   c->affiliate->connection         = c;

   c->state              = CONN_COMPLETE;

   /* check for a version string (assume version 2 if not present) */
   if( sscanf( version, "version=%hu", &c->version ) != 1 )
      c->version = 2;

   /* check for generator/interpreter */
   if( !imc_vinfo[c->version].generate || !imc_vinfo[c->version].interpret )
   {
      Log( "%s: unsupported version %d on connect", imc_getconnectname(c), c->version );
      do_close( c );
      return;
   }

   Log( "%s: connected (version %d)", imc_getconnectname(c), c->version );

   imc_cancel_event( ev_login_timeout, c );
   imc_cancel_event( ev_reconnect, c->affiliate );
   imc_cancel_event( ev_close_notify, c->affiliate );

   imc_request_keepalive( c->affiliate );
   imc_send_keepalive( NULL, "*", c->affiliate );
}

/* connect to networks and clear stats */
bool imc_startup_network( void )
{
   NETWORK *network = NULL;

   imc_stats.rx_pkts  = 0;
   imc_stats.tx_pkts  = 0;
   imc_stats.rx_bytes = 0;
   imc_stats.tx_bytes = 0;
   imc_stats.sequence_drops = 0;

   Log( "%s", "Bridge will attempt to connect to the loaded networks." );

   /* do autoconnects */
   for( network = first_network; network; network = network->next )
   {
       AFFILIATE *af = NULL;

       for ( af = network->first_affiliate; af; af = af->next )
           imc_connect_to( af );
   }

   return TRUE;
}

/* begin bridge processes */
void imc_startup( void )
{
  imc_now=time(NULL);                  /* start our clock */
  imc_boot=imc_now;

  Log("%s initializing.", IMC_VERSIONID);

  imc_sequencenumber=imc_now;

  if (!imc_readconfig() || !imc_startup_network())
  {
      Log( "Unable to complete startup procedures - aborting." );
      exit(1);
  }

}

/* print stats and clear memory */
void imc_shutdown_network( void )
{
   imc_event *ev, *ev_next;
   CONNECTION *c, *c_next;
   REMINFO *p, *pnext;

   Log( "Received %ld bytes(%ld packets) total.", imc_stats.rx_bytes, imc_stats.rx_pkts );
   Log( "Received %d age-dropped packets.", imc_stats.sequence_drops );
   Log( "Sent %ld bytes(%ld packets) total.", imc_stats.tx_bytes, imc_stats.tx_pkts );
   Log( "The largest packet sent or received was %d bytes.", imc_stats.max_pkt );

   for( c = first_connection; c; c = c_next )
   {
      c_next = c->next;
      do_close( c );
      imc_extract_connect( c );
   }

   for( p = first_reminfo; p; p = pnext )
   {
      pnext = p->next;
      imc_delete_reminfo( p );
   }

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

/* delete a bridge channel struct */
void imc_delete_channel( CHANNEL *c )
{
    if ( !c )
        return;

    UNLINK( c, first_channel, last_channel, next, prev );

    if ( c->local_channels )
        DISPOSE( c->local_channels );

    DISPOSE( c );
    return;
}

/* delete a network struct */
void imc_delete_network( NETWORK *i )
{
   CONNECTION *c;
   AFFILIATE *d, *d_next;

   if ( !i )
       return;

   for( c = first_connection; c; c = c->next )
      if( c->affiliate && c->affiliate->network == i )
         do_close( c );

   UNLINK( i, first_network, last_network, next, prev );

   if( i->netname )
      DISPOSE( i->netname );
   if( i->host )
      DISPOSE( i->host );
   if( i->clientpw )
      DISPOSE( i->clientpw );
   if( i->serverpw )
      DISPOSE( i->serverpw );
   if( i->blacklist )
      DISPOSE( i->blacklist );

   for ( d = i->first_affiliate; d; d = d_next )
   {
       d_next = d->next;
       UNLINK( d, i->first_affiliate, i->last_affiliate, next, prev );

       if ( d->name )
           DISPOSE( d->name );

       d->equiv = NULL;
       d->network = NULL;

       imc_cancel_event( NULL, d );

       DISPOSE( d );
   }

   imc_cancel_event( NULL, i );
   DISPOSE( i );
   return;
}

/* announce shutdown and start clearing memory */
void imc_shutdown( void )
{
   NETWORK *network, *network_next;
   CHANNEL *channel, *channel_next;

   Log( "Shutting down %s.", IMC_VERSIONID );

   for( network = first_network; network; network = network_next )
   {
      network_next = network->next;
      imc_delete_network( network );
   }

   for( channel = first_channel; channel; channel = channel_next )
   {
      channel_next = channel->next;
      imc_delete_channel( channel );
   }

   imc_shutdown_network();

   exit(0);
}

/* interpret an incoming packet using the right version */
static PACKET *do_interpret_packet( CONNECTION *c, const char *line )
{
   unsigned char v;
   PACKET *p;

   if( !line[0] )
      return NULL;

   v = c->version;
   if( v > IMC_VERSION )
      v = IMC_VERSION;

   p = ( *imc_vinfo[v].interpret )( line );

   return p;
}

/* set up descriptors to be checked with select */
int imc_fill_fdsets( fd_set *sread, fd_set *swrite, fd_set *exc )
{
   CONNECTION *c;
   int maxfd = 0;

   /* set up fd_sets for select */
   for( c = first_connection; c; c = c->next )
   {
      if( maxfd < c->desc )
         maxfd = c->desc;

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

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

      FD_SET( c->desc, sread );

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

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

  FD_ZERO(&sread);
  FD_ZERO(&swrite);
  FD_ZERO(&exc);

  maxfd=imc_fill_fdsets(&sread, &swrite, &exc);
  timeout.tv_sec = s;
  timeout.tv_usec = 0;

  if (maxfd)
    while ((i=select(maxfd+1, &sread, &swrite, &exc, &timeout)) < 0 && errno == EINTR)	/* loop, ignoring signals */
      ;
  else
    while ((i=select(0, NULL, NULL, NULL, &timeout)) < 0 && errno == EINTR)
      ;

  if ( i < 0 )
  {
      Log( "%s", "imc_idle: select call failed" );
      imc_shutdown();
      return;
  }

  imc_idle_select(&sread, &swrite, &exc, time(NULL));
}

/*
 * Events
 */ 

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

   Log( "%s: login timeout", imc_getconnectname( c ) );
   do_close( c );
}

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

  imc_close_notify(a);
}

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

   if( !affiliate->connection )
	   imc_connect_to( affiliate ); 
}

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 */
  {
    DISPOSE( p );
  }
  else
  {
    p->next=imc_event_free;
    imc_event_free=p;
    event_freecount++;
  }
}

void imc_add_event(int when, void (*callback)(void *), 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
      CREATE( p, imc_event, 1 );

  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;

  return;
}

void imc_cancel_event(void (*callback)(void *), 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)(void *), 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)(void *), 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;
}

unsigned 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)(void *);
  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
      Log("imc_run_events: NULL callback");
  }

  imc_now=newtime;
}

/* low-level idle function: read/write buffers as needed, etc */
void imc_idle_select( fd_set *sread, fd_set *swrite, fd_set *exc, time_t now )
{
   const char *command;
   PACKET *p;
   CONNECTION *c, *c_next;

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

   imc_run_events(now);

   /* handle results of the select */
   for( c = first_connection; c; c = c_next )
   {
      c_next = c->next;

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

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

      while( c->state != CONN_NONE && ( command = getline( c->inbuf, c->insize ) ) != NULL )
      {
         if( strlen( command ) > imc_stats.max_pkt )
	      imc_stats.max_pkt = strlen( command );

         switch( c->state )
         {
            case CONN_WAITSERVERPWD:
	         serverpassword( c, command );
	         break;
            case CONN_COMPLETE:
	         p = do_interpret_packet( c, command );
	         if( p )
	         {
                 char mud[IMC_NAME_LENGTH];
                 char *po = NULL;

                 imc_stats.rx_pkts++;
                 mud[0] = '\0';

                 /* if a full bridge, don't do anything special */
                 if ( !c->affiliate->full_bridge )
                 {
                     /* convert a finger request to a regular who or a proper internetwork finger */
                     /* also allows for player searches */
                     if ( STR_CEQL( p->type, "who" )
                             && sscanf( imc_getkey( p, "type", "unknown" ), "finger %60s", mud ) == 1 )
                 
                     {
                         if ( (po = strchr( mud, ':' ))  == NULL )
                         {
                             imc_addkey( p, "type", "who" );
                             snprintf( p->i.to, IMC_NAME_LENGTH, "*@%s", mud );
                         }
                         else
                         {
                             char newstring[SSS];

                             *po = '@';

                             /* Send whois if it is formatted right */
                             if ( STR_CEQL( imc_mudof(mud), "*" ) )
                             {
                                 strlcpy( p->type, "whois", IMC_TYPE_LENGTH );
                                 imc_addkey( p, "type", "" );
                                 snprintf( p->i.to, IMC_NAME_LENGTH, "%s@*", imc_nameof(mud) );
                             }
                             else
                             {
                                 strlcpy( p->i.to, mud, IMC_NAME_LENGTH );

                                 snprintf( newstring, SSS, "finger %s", imc_nameof(p->i.to) );
                                 imc_addkey( p, "type", newstring );
                             }
                         }
                     }
                     /* if we have an entry with the same name as hub, make sure things
                      * don't break
                      */
                     else if ( strchr( p->i.to, ':' ) )
                         strlcpy( mud, "boo", IMC_NAME_LENGTH );

                     /* convert to from internetwork format to local format */
                     strlcpy( p->to, imc_itol( p->i.to ), IMC_NAME_LENGTH );
                     strlcpy( p->i.to, p->to, IMC_NAME_LENGTH );
                 }

                 /* decide if it was directly for the bridge or not */
                 if ( (!mud || mud[0] == '\0') 
                         && (STR_CEQL( imc_mudof(p->i.to), c->affiliate->name ) 
                             || STR_CEQL( imc_mudof( p->i.to ), "*" ) ) ) 
                     forward(p, c->affiliate, TRUE );
                 else
                     forward(p, c->affiliate, FALSE );

                 imc_freedata( p );
	         }
	      break;
         }
      }
   }

   for( c = first_connection; c; c = c_next )
   {
      c_next = c->next;
    
      if( c->state != CONN_NONE && ( FD_ISSET( c->desc, swrite ) || c->newoutput ) )
      {
         do_write( c );
         c->newoutput = c->outbuf[0];
      }
   }

   for( c = first_connection; c; c = c_next )
   {
      c_next = c->next;

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

/* connect to given mud */
bool imc_connect_to( AFFILIATE *i )
{
   CONNECTION *c;
   int desc;
   struct sockaddr_in sa;
   char namestring[SSS];
   char buf[IMC_DATA_LENGTH];
   int r;

   if ( !i )
   {
      Log( "%s", "imc_connect_to: No affiliate information provided" ); 
      return 0;
   }

   snprintf( namestring, SSS, "%s(as %s)", i->network->netname, i->name ); 

   if( i->connection )
   {
      Log( "imc_connect_to: %s - already connected", namestring );
      return 0;
   }

   Log( "imc_connect_to: connecting to %s", namestring );

   if ( !i->network || !i->network->host || i->network->host[0] == '\0' )
   {
       Log( "imc_connect_to: host field null to %s", namestring );
       return 0;
   }

   /*  warning: this blocks. It would be better to farm the query out to
    *  another process, but that is difficult to do without lots of changes
    *  to the core mud code. You may want to change this code if you have an
    *  existing resolver process running.
    */

   if( !inet_aton( i->network->host, &sa.sin_addr ) )
   {
      struct hostent *hostinfo;

      if( ( hostinfo = gethostbyname( i->network->host ) ) == NULL )
      {
         Log( "imc_connect_to: hostname could not be resolved" );
         return 0;
      }

      memcpy( &sa.sin_addr, hostinfo->h_addr, (size_t) hostinfo->h_length );
   }

   sa.sin_port   = htons( (unsigned short) i->network->port );
   sa.sin_family = AF_INET;
   memset(&(sa.sin_zero), '\0', 8); // zero the rest of the struct

   desc = socket( AF_INET, SOCK_STREAM, 0 );
   if( desc < 0 )
   {
      Log( "imc_connect_to: socket call failure" );
      return 0;
   }

   r = fcntl( desc, F_GETFL, 0 );
   if( r < 0 || fcntl( desc, F_SETFL, O_NONBLOCK | r ) < 0 )
   {
      Log( "imc_connect_to: fcntl call failure" );
      close( desc );
      return 0;
   }

   if( connect( desc, ( struct sockaddr * )&sa, sizeof( sa ) ) < 0 )
   {
      if( errno != EINPROGRESS )
      {
         Log( "imc_connect_to: connection could not be established" );
         close( desc );
         return 0;
      }
   }
   c = imc_new_connect();

   c->desc     = desc;
   c->state    = CONN_SERVERCONNECT;
   c->affiliate     = i;
   c->ip       = strdup( inet_ntoa( sa.sin_addr ) );

   imc_add_event( IMC_LOGIN_TIMEOUT, ev_login_timeout, c );

   snprintf( buf, IMC_DATA_LENGTH, "PW %s %s version=%d", i->name, i->network->clientpw, IMC_VERSION );
   do_send( c, buf );

   return 1;
}

void imc_send(PACKET *p, AFFILIATE *a)
{
  /* initialize packet fields that the caller shouldn't/doesn't set */

  p->i.path[0]  = 0;
  
  p->i.sequence = imc_sequencenumber++;
  if (!imc_sequencenumber)
    imc_sequencenumber++;
  
  strlcpy(p->i.to, p->to, IMC_NAME_LENGTH);
  strlcpy(p->from, "*", IMC_NAME_LENGTH );

  /* full bridge packets don't get converted to internetwork format */
  if ( a->full_bridge )
      snprintf( p->i.from, IMC_NAME_LENGTH, "%s@%s", p->from, a->name );
  else
      strlcpy(p->i.from, p->from, IMC_NAME_LENGTH );

  forward( p, a, FALSE );
}

/* make a new network struct */
NETWORK *imc_new_network( void )
{
   NETWORK *i;

   CREATE( i, NETWORK, 1 );

   i->netname    = NULL;
   i->host       = NULL;
   i->port       = 0;
   i->clientpw   = NULL;
   i->serverpw   = NULL;
   i->blacklist  = NULL;
   i->first_affiliate = NULL;
   i->last_affiliate = NULL;

   LINK( i, first_network, last_network, next, prev );
   return i;
}

/* make a new bridge channel struct */
CHANNEL *imc_new_channel( char *local_channels )
{
   CHANNEL *c = NULL;

   if ( !local_channels || local_channels[0] == '\0' )
       return NULL;

   CREATE( c, CHANNEL, 1 );

   c->local_channels = strdup( local_channels );

   LINK( c, first_channel, last_channel, next, prev );

   return c;
}

/* find an equivalent bridged channel for the one that was sent  */
char *find_channel_equivalent( const char *from, NETWORK *from_network, NETWORK *to_network )
{
    CHANNEL *c = NULL;
    const char *p = NULL;
    static char channame[SSS];
    char newstring[SSS];

    channame[0] = '\0';

    if ( !from || from[0] == '\0' || !from_network || !to_network )
        return channame;

    snprintf( newstring, SSS, "%s:%s", from_network->netname, from );
    
    for ( c = first_channel; c; c = c->next )
    {
        if ( strcasestr( c->local_channels, newstring ) )
            break;
    }

    if ( !c )
        return channame;

    p = imc_getarg( c->local_channels, newstring, SSS );

    while ( newstring && newstring[0] != '\0' )
    {
        char *d;

        if ( (d = strcasestr( newstring, to_network->netname )) != NULL && *d == newstring[0] )
            break;

        p = imc_getarg( p, newstring, SSS );
    }

    if ( !newstring || newstring[0] == '\0' || (p = strchr( newstring, ':' ) ) == NULL )
        return channame;

    strlcpy( channame, p + 1, SSS );
    return channame;
}

/* read in data for a bridge */
void read_bridge( char *affiliates, NETWORK *n, bool fullbridge )
{
    char arg[SSS];
    const char *p;

    if ( !affiliates || affiliates[0] == '\0' || !n )
        return;

    p = imc_getarg( affiliates, arg, SSS );

    while ( arg && arg[0] )
    {
        if ( !STR_CEQL( arg, n->netname ) )
        {
            AFFILIATE *a;

            CREATE( a, AFFILIATE, 1 );
            a->name = strdup(arg);
            a->network = n;
            a->equiv = NULL;
            a->connection = NULL;
            a->full_bridge = fullbridge;

            LINK( a, n->first_affiliate, n->last_affiliate, next, prev );
        }

        p = imc_getarg( p, arg, SSS );
    }

    return;
}

/* link up both sides of a bridge */
void complete_bridge( AFFILIATE *a )
{
    NETWORK *n = NULL;
    AFFILIATE *f = NULL;

    if ( !a )
        return;

    for ( n = first_network; n; n = n->next )
        if ( STR_CEQL( n->netname, a->name ) )
            break;

    if ( !n )
        return;

    for ( f = n->first_affiliate; f; f = f->next )
        if ( STR_CEQL( a->network->netname, f->name ) )
            break;

    if ( !f )
        return;

    a->equiv = f;
    
    if ( f->equiv == NULL )
        f->equiv = a;
}

/* read config file */
bool imc_readconfig( void )
{
   FILE *cf;
   NETWORK *n = NULL;
   AFFILIATE *a = NULL;
   unsigned char count = 0;
   char c = '\0';

   if ( !(cf = fopen( IMC_CONFIG_FILE, "r" )) )
   {
      Log( "Configuration file could not be opened for reading.");
      return FALSE;
   }

   while ( (c = fgetc( cf ) ) != EOF )
   {
       if ( ferror( cf ) )
       {
           Log( "%s", "An error occured on an attempted read of the configuration file." );
           SFCLOSE( cf );
           return FALSE;
       }

       ungetc( c, cf );

       if ( c == '#' || c == '\n' || c == '\r' )
       {
                     
           while ( c != '\n' && c != '\r' && c != EOF )
               c = fgetc(cf);
                      
           while ( c == '\n' || c == '\r' )
               c = fgetc(cf);

           ungetc( c, cf );
       }
       else if ( c == 'B' )
       {
           char local_channels[SSS];

           if ( fscanf( cf, "Bridged-Channel %255[^\n]\n", local_channels ) != 1 )
           {
               Log( "Bad bridged-channel entry encountered in configuration file." );
               continue;
           }

           imc_new_channel( local_channels );
       }
       else if ( c == 'F' )
       {
           char affiliates[LSS];
           char arg[SSS];
           const char *p;

           if ( fscanf( cf, "Full-Bridge %255[^\n]\n", affiliates ) != 1 )
           {
               Log( "Bad full-bridge entry encountered in the configuration file." );
               continue;
           }
              
           p = imc_getarg( affiliates, arg, SSS );
                                                                                                                  
           while ( arg && arg[0] )
           {
               for ( n = first_network; n; n = n->next )
               {
                   if ( STR_CEQL( n->netname, arg ) )
                   {
                       read_bridge( affiliates, n, TRUE );
                       break;
                   }
               }
                                               
               p = imc_getarg( p, arg, SSS );
           }
       }
       else if ( c == 'N' )
       {
           char netname[LSS], host[LSS], clientpwd[LSS], serverpwd[LSS], blacklist[LSS];
           unsigned short port = 0;

           if ( fscanf( cf, "Network %11[^\n]\n"
                            "Connection %255[^:]:%hu:%19[^:]:%19[^\n]\n"
                            "Blacklist %255[^\n]\n\n",
                       netname, host, &port, clientpwd, serverpwd, blacklist ) != 6 )
           {
               Log( "Bad network entry encountered in the configuration file." );
               continue;
           }
               
           n = imc_new_network();
           n->netname = strdup( netname );
           n->host = strdup(host);
           n->port = port;
           n->clientpw = strdup( clientpwd );
           n->serverpw = strdup( serverpwd );

           if ( STR_CEQL( blacklist, "none" ) )
               n->blacklist = strdup( "" );
           else
               n->blacklist = strdup( blacklist );
               
           count++;
       }
       else if ( c == 'R' )
       {
           char affiliates[LSS];
           char arg[SSS];
           const char *p;
                                                                                                    
           if ( fscanf( cf, "Relay-Bridge %255[^\n]\n", affiliates ) != 1 )
           {
               Log( "Bad relay-bridge entry encountered in the configuration file." );
               continue;
           }
                                                                                                    
           p = imc_getarg( affiliates, arg, SSS );
                                                                                                    
                                                                                                    
           while ( arg && arg[0] )
           {
               for ( n = first_network; n; n = n->next )
               {
                   if ( STR_CEQL( n->netname, arg ) )
                   {
                       read_bridge( affiliates, n, FALSE );
                       break;
                   }
               }
 
               p = imc_getarg( p, arg, SSS );
           }
       }
       else
       {
           Log( "Invalid line encountered in the configuration file." );
                      
           while ( c != '\n' && c != '\r' && c != EOF )
               c = fgetc(cf);
                                             
           while ( c == '\n' || c == '\r' )
               c = fgetc(cf);
                                            
           ungetc( c, cf );
       }
   }

   SFCLOSE(cf);

   for ( n = first_network; n; n = n->next )
       for ( a = n->first_affiliate; a; a = a->next )
           if ( a->equiv == NULL )
               complete_bridge( a );


   if ( count < 2 )
   {
       Log( "Bridge needs at least two loaded networks to function properly - aborting" );
       return FALSE;
   }
   
   return TRUE;
}

/* display a listing of network connections for a single network */
const char *imc_list( AFFILIATE *a )
{
   static char buf[IMC_DATA_LENGTH];
   REMINFO *p;
   unsigned int count = 1;

   snprintf( buf, IMC_DATA_LENGTH, "~WNetwork Connection Listings for %s:\n\r"
                "~g%-15.15s  ~c%-35.35s  ~G%-10.10s  ~g%-10.10s"
        "\n\r\n\r~g%-15.15s  ~c%-35.35s  ~G%-10.10s  ~g%-10.10s",
                a->name, "Name", "IMC2 Version", "Network", "Hub", 
                a->equiv->name, IMC_VERSIONID, a->equiv->name, "Bridge" );

   for( p = first_reminfo; p; p = p->next )
   {
           if ( p->network != a->equiv->network )
               continue;

           snprintf( buf + strlen(buf), IMC_DATA_LENGTH - strlen(buf), 
           "\n\r~g%-15.15s  ~c%-35.35s  ~G%-10.10s  ~g%-10.10s",
           p->name, p->version, 
           STR_CEQL( p->netname, "unknown" ) ? a->name : p->netname, 
           imc_hubinpath(p->path, a));

           count++;
   }

   snprintf( buf + strlen( buf ), IMC_DATA_LENGTH - strlen(buf),
           "\n\r~W%d active connections found.\n\r", count );

   return buf;
}

/* send a keepalive request to everyone - shogar */
void imc_request_keepalive( AFFILIATE *a )
{
   PACKET out;

   imc_initdata( &out );
   strlcpy( out.type, "keepalive-request", IMC_TYPE_LENGTH );
   strlcpy( out.to, "*@*", IMC_NAME_LENGTH );
   imc_addkey( &out, "versionid", IMC_VERSIONID );

   imc_send( &out, a );
   imc_freedata( &out );
}

/* called when a keepalive has been received */
void imc_recv_keepalive( const char *from, const char *version, const char *networkname, const char *web, AFFILIATE *a)
{
   REMINFO *p;

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

   if( !STR_CEQL( version, p->version ) )    /* remote version has changed? */
   {
      DISPOSE( p->version );              /* if so, update it */
      p->version = strdup( version );
   }

   if( !STR_CEQL( networkname, p->netname ) )
   {
      DISPOSE( p->netname );
      p->netname = strdup( networkname );
   }

   if( !STR_CEQL( web, p->web ) )
   {
      DISPOSE( p->web );
      p->web = strdup( web );
   }
}

/* called when a ping request is received */
void imc_recv_ping(const char *from, const char *path, AFFILIATE *a)
{
  /* ping 'em back */
  imc_send_pingreply(from, path, a);
}

/* called when a bridge information requesting packet is received */
void imc_recv_who( PACKET *p, const char *pname, const char *type, AFFILIATE *a )
{
   char arg[IMC_DATA_LENGTH];

   type = imc_getarg( type, arg, IMC_DATA_LENGTH );

   if( STR_CEQL( arg, "list" ) || STR_CEQL( arg, "who" ) )
     imc_send_whoreply( pname, imc_list(a), a );
   else if( STR_CEQL( arg, "info" ) )
     imc_send_whoreply( pname, imc_getstats( ), a );
   else if( STR_CEQL( arg, "help" ) || STR_CEQL( arg, "services" ) )
     imc_send_whoreply( pname,
		      "~WAvailable imcminfo types:\n\r"
		      "~chelp                 ~W- ~Cthis list\n\r"
		      "~cinfo                 ~W- ~Cbridge information\n\r"
		      "~cwho                  ~W- ~Clist known muds on IMC\n\r"
              "~cfinger <mud>         ~W- ~Cshow a mud's who listing\n\r"
              "~cfinger <player:mud>  ~W- ~Cshow a player's information\n\r"
              "~cfinger <player:*>    ~W- ~Csearch for a player\n\r", a );
   else
     imc_send_whoreply( pname, "Sorry, no information of that type is available.", a );
}


/* handle a packet destined for us, or a broadcast */
void imc_recv( PACKET *p, AFFILIATE *a )
{
   /* who: receive a who request */
   if( STR_CEQL( p->type, "who" ) )
   {
       imc_recv_who( p, p->from, imc_getkey( p, "type", "who" ), a );
   }

   /* is-alive: receive a keepalive (broadcast) */
   else if( STR_CEQL( p->type, "is-alive" ) )
   {
       Log( "%s: Received is-alive from %s.", imc_getconnectname( a->connection ), imc_mudof( p->from ) );
       imc_recv_keepalive( imc_mudof( p->from ), imc_getkey( p, "versionid", "unknown" ),
               imc_getkey( p, "networkname", "unknown"), imc_getkey( p, "url", "unknown"), a );
   }

   /* ping: receive a ping request */
   else if( STR_CEQL( p->type, "ping" ) )
   {
      imc_recv_ping( imc_mudof( p->from ), p->i.path, a );
   }
   /* handle keepalive requests - shogar */
   else if( STR_CEQL( p->type, "keepalive-request" ) )
   {
       struct imc_keepalive *k = NULL;

       Log( "%s: Keepalive request received from %s.", imc_getconnectname( a->connection ),
               imc_mudof(p->from) );

       /* send cached network listing if full bridge, otherwise just send bridge's is-alive */
       if ( a->full_bridge )
       {
           CREATE( k, struct imc_keepalive, 1 );
           strlcpy( k->destination, imc_mudof(p->from), IMC_MNAME_LENGTH );
           k->count = 0;
           k->remote = first_reminfo;
           k->affiliate = a;

           imc_add_event( 1, ev_send_isalive, k );
       }
       else
       {
           char newstring[IMC_NAME_LENGTH];

           strlcpy( newstring, imc_mudof( p->from ), IMC_NAME_LENGTH );
           imc_send_keepalive( NULL, newstring, a );
       }
   }

   /* expire closed hubs - shogar */
   else if( STR_CEQL( p->type, "close-notify" ) )
   {
      REMINFO *r = NULL;

      Log( "%s: Close-notify packet for %s received from %s.", 
              imc_getconnectname( a->connection ), imc_getkey( p, "host", "unknown" ), p->from );

      if ((r = imc_find_reminfo(imc_getkey(p, "host", ""), a->network )) != NULL )
      {
          REMINFO *o, *o_next;

          /* if it's a full bridge, inform the other side of the expire */
          if ( a->full_bridge )
          {
              PACKET out;

              imc_initdata( &out );
              strlcpy( out.type, "close-notify", IMC_TYPE_LENGTH );
              strlcpy( out.to, "*@*", IMC_NAME_LENGTH );
              imc_addkey( &out, "versionid", IMC_VERSIONID );
              imc_addkey( &out, "host", r->name );
              imc_send( &out, a->equiv );
              imc_freedata( &out );
          }

          /* if there is no path to them, take them off the list */
          for ( o = first_reminfo; o; o = o_next )
          {
              o_next = o->next;

              if ( STR_CEQL( r->name, imc_hubinpath( o->path, a ) ) )
                  imc_delete_reminfo( o );
          }

          imc_delete_reminfo( r );
      }
   }

}

/* Commands called by the interface layer */

/* send a tell to a remote player */
void imc_send_tell( const char *to, const char *argument, AFFILIATE *a )
{
   PACKET out;

   if( STR_EQL( imc_mudof(to), "*" ) )
      return; /* don't let them do this */

  
   imc_initdata(&out);
   strcpy( out.to, to  );
   strcpy( out.type, "tell"  );
   imc_addkey( &out, "text", argument );
   imc_addkeyi( &out, "isreply", 1 );
   imc_addkeyi( &out, "level", -1 );

   imc_send( &out, a );
   imc_freedata( &out );
}

/* respond to a who request with the given data */
void imc_send_whoreply( const char *to, const char *data, AFFILIATE *a )
{
   PACKET out;

   if( STR_EQL( imc_mudof(to), "*" ) )
      return; /* don't let them do this */

   imc_initdata( &out );

   strlcpy( out.to, to, IMC_NAME_LENGTH );

   strlcpy( out.type, "who-reply", IMC_TYPE_LENGTH );

   imc_addkey( &out, "text", data );

   imc_send( &out, a );
   imc_freedata( &out );
}

/* send a cached supply of is-alive packets for a network */
void ev_send_isalive( void *data )
{
    REMINFO *r = NULL;
    struct imc_keepalive *k = (struct imc_keepalive *) data;
    unsigned char count = 0;

    imc_cancel_event( ev_send_isalive, k);

    if ( !k )
        return;

    if ( k->count == 0 )
    {
        imc_send_keepalive( NULL, k->destination, k->affiliate );
        count++;
    }

    for ( r = k->remote; r; r = r->next )
    {
        if ( STR_CEQL( k->destination, r->name ) || inpath(r->path, k->destination)
                || r->network == k->affiliate->network )
                        continue;
            
        imc_send_keepalive( r, k->destination, k->affiliate );
        count++;

        /* Don't want to send more than 15 is-alive packets at a time */
        if ( count >= 15 && r->next != NULL )
        {
            k->count += count;
            k->remote = r->next;
            imc_add_event( 3, ev_send_isalive, k );
            return;
        }
    }

    k->destination[0] = '\0';
    k->count = 0;
    k->remote = NULL;
    k->affiliate = NULL;

    DISPOSE( k );
    return;
}

/* send a keepalive to a requestor */
void imc_send_keepalive( REMINFO *r, char *destination, AFFILIATE *a )
{
   PACKET out;

   if ( !destination || destination[0] == '\0' )
       return;

   imc_initdata( &out );

   strlcpy( out.from, "*", IMC_NAME_LENGTH );
   strlcpy( out.type, "is-alive", IMC_TYPE_LENGTH );
   snprintf( out.to, IMC_NAME_LENGTH, "*@%s", destination );
   strlcpy( out.i.to, out.to, IMC_NAME_LENGTH );

   /* send an is-alive for the bridge */
   if ( !r )
   {
       if ( a->full_bridge )
           snprintf( out.i.from, IMC_NAME_LENGTH, "%s@%s", out.from, a->name );
       else
           strlcpy( out.i.from, out.from, IMC_NAME_LENGTH );

       imc_addkey( &out, "versionid", IMC_VERSIONID );
       imc_addkey( &out, "networkname", a->name );

       out.i.sequence = imc_sequencenumber++;

       if ( !imc_sequencenumber )
           imc_sequencenumber++;
       
       out.i.path[0] = 0;
   }
   /* send an is-alive for a network connection */
   else
   {
       unsigned int used_sequence[IMC_MEMORY];
       unsigned int counter = 0, x = 0, new_sequence = r->top_sequence;

       snprintf( out.from, IMC_NAME_LENGTH, "*@%s", r->name );
       strlcpy( out.i.from, out.from, IMC_NAME_LENGTH );

       if ( r->version && r->version[0] != '\0' && !STR_CEQL( r->version, "unknown" ) )
           imc_addkey( &out, "versionid", r->version );

       if ( r->netname && r->netname[0] != '\0' && !STR_CEQL( r->netname, "unknown" ) )
           imc_addkey( &out, "networkname", r->netname );

       if ( r->web && r->web[0] != '\0' && !STR_CEQL( r->web, "unknown" ) )
           imc_addkey( &out, "url", r->web );

       for( counter = 0; counter < IMC_MEMORY; counter++ )
           if ( imc_memory[counter].from && STR_CEQL( r->name, imc_memory[counter].from ) )
               used_sequence[x++] = imc_memory[counter].sequence;
                                                                                                           
       counter = 0;
                                                                                                                  
       while ( counter < x )
       {
           new_sequence--;
                                                          
           for ( counter = 0; counter < x; counter++ )
               if ( new_sequence == used_sequence[counter] )
                   break;
       }

                            
       out.i.sequence = new_sequence;
                             
       strlcpy( out.i.path, r->path, IMC_PATH_LENGTH );
   }


   forward( &out, a, FALSE );
   imc_freedata( &out );
}

/* send a pingreply with the originating path */
void imc_send_pingreply( const char *to, const char *path, AFFILIATE *a )
{
   PACKET out;

   imc_initdata( &out );
   strlcpy( out.type, "ping-reply", IMC_TYPE_LENGTH );
   snprintf( out.to, IMC_NAME_LENGTH, "*@%s", to );
   imc_addkey( &out, "path", path );

   imc_send( &out, a );
   imc_freedata( &out );
}