/*
 * IMC2 - an inter-mud communications protocol
 *
 * imc.c: the core protocol code
 *
 * Copyright (C) 1996,1997 Oliver Jowett <oliver@jowett.manawatu.planet.co.nz>
 *
 * 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 <ctype.h>
#include <unistd.h>
#include <sys/time.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>

/* if freeBSD , use flock instead of lockf - shogar */
#ifndef F_TLOCK
#define F_TLOCK LOCK_EX
#define F_ULOCK LOCK_UN
#define lockf(a,b,c) flock(a,b)
#endif

#include "imc.h"
#include "icec.h"

/*
 *  Local declarations + some global stuff from imc.h
 */

/* decls of vars from imc.h */

imc_connect *imc_connect_list;
imc_info    *imc_info_list;
imc_reminfo *imc_reminfo_list;

imc_statistics imc_stats;

/* imc_active now has more states:
 *
 * 0:  nothing done yet
 * 1:  configuration loaded, but IMC not active. imc_name is not set.
 * 2:  configuration loaded, but IMC not active. imc_name is valid.
 * 3:  imc_name and configuration loaded, network active, port disabled.
 * 4:  everything active.
 *
 */
int imc_active;
time_t imc_now;          /* current time */
time_t imc_boot;          /* current time */
int imc_lock;
int imc_is_router;       /* run as a router (ie. ping stuff) */
int imc_lock_file=-1;

char global_hubname[IMC_MNAME_LENGTH] = "NULL";
int global_directnum=-1;
extern int imc_hubswitch;

/* control socket for accepting connections */
static int control;

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

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

char *imc_name;			      /* our IMC name */
unsigned short imc_port;              /* our port; 0=disabled */
unsigned long imc_bind;               /* IP to bind to */

/* imc flag/state tables */

/* flags for connections */
const imc_flag_type imc_connection_flags[] =
{
  { "noauto",    IMC_NOAUTO    },
  { "client",    IMC_CLIENT    },
  { "reconnect", IMC_RECONNECT },
  { "broadcast", IMC_BROADCAST },
  { "deny",      IMC_DENY      },
  { "quiet",     IMC_QUIET     },
  { "hub",       IMC_HUB       }, /* SPAM fix - shogar && currently used hub (unless main hub.. confused yet?) ;) - Scion */
  { "main_hub",  IMC_MAIN_HUB  }, /* main hub, same as hub, but first to try - Scion */
  { "old_hub",   IMC_OLD_HUB   }, /* backup hub just in case - Scion */
  { "dead_hub",  IMC_DEAD_HUB  }, /* defunct hub, 3 connection attempts - Scion */
  { "new",       IMC_NEW_HUB  }, /* mark new hubs so dont save - shogar */
  { "noswitch",  IMC_NOSWITCH  }, /* might be reasons to never switch to them - shogar */
  { NULL, 0 },
};

/* flags for rignore entries */
const imc_flag_type imc_ignore_types[] =
{
  { "ignore",    IMC_IGNORE               },
  { "notrust",   IMC_NOTRUST              },
  { "trust",     IMC_TRUST                },
  
  /* for old config files */
  { "1",         IMC_IGNORE               },
  { "2",         IMC_NOTRUST              },
  { NULL, 0 }
};

/* states that state in imc_connect can take */
const imc_flag_type imc_state_names[] =
{
  { "closed",      IMC_CLOSED     },
  { "connecting",  IMC_CONNECTING },
  { "wait1",       IMC_WAIT1      },
  { "wait2",       IMC_WAIT2      },
  { "connected",   IMC_CONNECTED  },
  { NULL, 0 }
};

/* states that imc_active can take */
const imc_flag_type imc_active_names[] =
{
  { "inactive - not initialized",                         IA_NONE      },
  { "inactive - config loaded, local name not set",       IA_CONFIG1   },
  { "inactive - config loaded, local name set",           IA_CONFIG2   },
  { "active - not accepting connections",                 IA_UP        },
  { "active - accepting connections",                     IA_LISTENING },
  { NULL, 0 }
};

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

  c=imc_malloc(sizeof(*c));
  c->state    = IMC_CLOSED;
  c->desc     = -1;
  c->insize   = IMC_MINBUF;
  c->inbuf    = imc_malloc(c->insize);
  c->outsize  = IMC_MINBUF;
  c->outbuf   = imc_malloc(c->outsize);
  c->inbuf[0] = c->outbuf[0] = 0;
  c->info     = NULL;
  c->spamcounter1=0;
  c->spamcounter2=0;
  c->spamtime1=0;
  c->spamtime2=0;
  c->newoutput=0;

  c->next=imc_connect_list;
  imc_connect_list=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==IMC_CLOSED
 */
void imc_extract_connect(imc_connect *c)
{
  imc_connect *c_find;

  if (c->state!=IMC_CLOSED)
  {
    imc_logerror("imc_extract_connect: non-closed connection");
    return;
  }
  
  imc_free(c->inbuf, c->insize);
  imc_free(c->outbuf, c->outsize);

  if (c==imc_connect_list)
    imc_connect_list=c->next;
  else
  {
    for (c_find=imc_connect_list; c_find && c_find->next!=c;
	 c_find=c_find->next)
      ;
    
    if (!c_find)
      imc_logerror("imc_extract_connect: not in imc_connect_list");
    else
      c_find->next=c->next;
  }

  imc_cancel_event(NULL, c);

  imc_free(c, sizeof(*c));
}

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

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

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

    if (strcasecmp(sender, imc_name))
    {
      /* not from us */
      /* check if its in the list already */

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

	p->name    = imc_strdup(sender);
	p->ping    = 0;
	p->alive   = imc_now;
	p->route   = imc_strdup(last);
	p->version = imc_strdup("unknown");
	p->type    = IMC_REMINFO_NORMAL;
      }
      else
      {				/* already in list, update the entry */
	if (strcasecmp(last, p->route))
	{
	  imc_strfree(p->route);
	  p->route=imc_strdup(last);
	}
	p->alive=imc_now;
	p->type = IMC_REMINFO_NORMAL;
      }
    }

    /* 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) */
static int inpath(const char *path, const char *name)
{
  char buf[IMC_MNAME_LENGTH+3];
  char tempn[IMC_MNAME_LENGTH], tempp[IMC_PATH_LENGTH];

  imc_sncpy(tempn, name, IMC_MNAME_LENGTH);
  imc_sncpy(tempp, path, IMC_PATH_LENGTH);
  imc_slower(tempn);
  imc_slower(tempp);

  if (!strcmp(tempp, tempn))
    return 1;

  sprintf(buf, "%s!", tempn);
  if (!strncmp(tempp, buf, strlen(buf)))
    return 1;

  sprintf(buf, "!%s", tempn);
  if (strlen(buf) < strlen(tempp) &&
      !strcmp(tempp + strlen(tempp) - strlen(buf), buf))
    return 1;

  sprintf(buf, "!%s!", tempn);
  if (strstr(tempp, buf))
    return 1;

  return 0;
}

/*
 *  Core functions (all internal)
 */

/* accept a connection on the control port */
static void do_accept(void)
{
  int d;
  imc_connect *c;
  struct sockaddr_in sa;
  int size = sizeof(sa);
  int r;

  d=accept(control, (struct sockaddr *) &sa, &size);
  if (d<0)
  {
    imc_lerror("accept");
    return;
  }

  r=fcntl(d, F_GETFL, 0);
  if (r<0 || fcntl(d, F_SETFL, O_NONBLOCK | r)<0)
  {
    imc_lerror("do_accept: fcntl");
    close(d);
    return;
  }

  c=imc_new_connect();
  c->state    = IMC_WAIT1;
  c->desc     = d;

  imc_add_event(IMC_LOGIN_TIMEOUT, ev_login_timeout, c, 1);
  imc_logstring("connection from %s:%d on descriptor %d",
		inet_ntoa(sa.sin_addr), ntohs(sa.sin_port), d);
}
/* notify everyone of the closure - shogar */
void imc_close_notify(const char *host)
{
  imc_packet out;
  char shorthost[80];

  if (imc_active<IA_UP)
    return;

  strcpy(shorthost,host);
  if(strchr(shorthost,'['))
    *(strchr(shorthost,'['))=0;

  imc_initdata(&out.data);
  strcpy(out.type, "close-notify");
  strcpy(out.from, "*");
  strcpy(out.to, "*@*");
  imc_addkey(&out.data, "versionid", IMC_VERSIONID);
  if (imc_siteinfo.flags[0])
    imc_addkey(&out.data, "flags", imc_siteinfo.flags);
  imc_addkey(&out.data, "host", shorthost);
  imc_send(&out);
  imc_freedata(&out.data);
}

/* close given connection */
static void do_close(imc_connect *c)
{
  const char *name;
  imc_reminfo *r;

  if (c->info) /* if our switched hub, get a new one right away - shogar */
  {
    if (!imc_is_router && (c->info->flags & IMC_NEW_HUB) && c->info == imc_info_list)
    {
	imc_cancel_event(ev_imc_pollforhub,NULL);
	imc_cancel_event(ev_imc_optimize,NULL);
  	imc_add_event(10, ev_imc_pollforhub, NULL, 1);
  	imc_add_event(70, ev_imc_optimize, NULL, 1);
    }
  }
  
  if (c->state==IMC_CLOSED)
    return;

  name=imc_getconnectname(c);
  if(name && c->state == IMC_CONNECTED) /* dont send if never connected */
  {
  	imc_close_notify(name);
        close(c->desc); /* dont close if closed */
  }
  if (c->state == IMC_CONNECTED)
    c->info->connection=NULL;

  /* handle reconnects */
  if (c->info)
    if ((c->info->flags & IMC_RECONNECT) &&
	!(c->info->flags & IMC_DENY) &&
	!(c->info->flags & IMC_CLIENT))
    {
      imc_setup_reconnect(c->info);
    }

  c->state=IMC_CLOSED;

  /* only log after we've set the state, in case imc_logstring
   * sends packets itself (problems with eg. output buffer overflow).
   */
  if (!c->info || !(c->info->flags & IMC_QUIET))
  {
  	name=imc_getconnectname(c);
    	if(name) imc_logstring("%s: closing link", name);
  }

  if (c->info)
  {
    r=imc_find_reminfo(c->info->name, 1);
    if (r)
      imc_delete_reminfo(r);
  }
  if(c->desc)
	close(c->desc);
  c->desc=0;
  c->inbuf[0]=0;
  c->outbuf[0]=0;
  
}

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

  if (!c->info || !(c->info->flags & IMC_QUIET))
    imc_logstring("%s: login timeout", imc_getconnectname(c));
  do_close(c);
}

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

  r=read(c->desc, temp, IMC_MAXBUF-1);
  if (!r || (r<0 && errno != EAGAIN && errno != EWOULDBLOCK))
  {
    if (!c->info || !(c->info->flags & IMC_QUIET))
    {
      if (r<0)                    /* read error */
      {
        imc_lerror("%s: read", imc_getconnectname(c));
      }
      else                        /* socket was closed */
      {
        imc_logerror("%s: read: EOF", 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)
  {

#ifdef SHOW_OVERFLOW
	/* not an error anymore, expected and handled - shogar */
    if (size>IMC_MAXBUF)
    {
        imc_logerror("%s: input buffer overflow", imc_getconnectname(c));
        imc_logerror("%d: was allocated", c->insize);
//      do_close(c);
//      imc_free(c->inbuf,c->insize);
//      c->insize=IMC_MINBUF;
//      c->inbuf= imc_malloc(c->insize);
//      size = r + 1;
//      return;
    }
      
#endif
    newsize=c->insize;
    while(newsize<size)
      newsize*=2;

    newbuf=imc_malloc(newsize);
    strcpy(newbuf, c->inbuf);
    imc_free(c->inbuf, c->insize);
    c->inbuf=newbuf;
    c->insize=newsize;
  }
  
  if (size>c->insize/2)
  {
    imc_cancel_event(ev_shrink_input, c);
    imc_add_event(IMC_SHRINKTIME, ev_shrink_input, c, 0);
  }
  if (size<c->insize/2 && size >= IMC_MINBUF)
  {
    newsize=c->insize;
    newsize/=2;

    newbuf=imc_malloc(newsize);
    strcpy(newbuf, c->inbuf);
    imc_free(c->inbuf, c->insize);
    c->inbuf=newbuf;
    c->insize=newsize;
  }

  strcat(c->inbuf, temp);

  imc_stats.rx_bytes += r;
}

/* write to descriptor */
static void do_write(imc_connect *c)
{
  int size, w;

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

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

  w=write(c->desc, c->outbuf, size);
  if (!w || (w<0 && errno != EAGAIN && errno != EWOULDBLOCK))
  {
    if (!c->info || !(c->info->flags & IMC_QUIET))
    {
      if (w<0)			/* write error */
      {
        imc_lerror("%s: write", imc_getconnectname(c));
      }
      else			/* socket was closed */
      {
        imc_logerror("%s: write: EOF", imc_getconnectname(c));
      }
    }
    do_close(c);
    return;
  }

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

  /* throw away data we wrote */
//  memmove(c->outbuf, c->outbuf+w, size-w+1);
  strcpy(c->outbuf,c->outbuf+w);

  imc_stats.tx_bytes += w;
}

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

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

//  imc_debug(c, 1, line);	/* log outgoing traffic */

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

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

  if (len > c->outsize)
  {

#ifdef SHOW_OVERFLOW
	/* not an error anymore, expected and handled - shogar */
    if (len > IMC_MAXBUF)
    {
      if (!c->info || !(c->info->flags & IMC_QUIET))
      imc_logerror("%s: output buffer overflow", imc_getconnectname(c));
      imc_logerror("%d: was allocated", c->outsize);
//      imc_logerror("current buf: %s", c->outbuf);
//    do_close(c);
//      imc_free(c->outbuf,c->outsize);
//      c->outsize=IMC_MINBUF;
//      c->outbuf= imc_malloc(c->outsize);
//      len=strlen(line)+3;
//    return;
  }
#endif
    while(newsize < len)
      newsize*=2;

    newbuf=imc_malloc(newsize);
    strcpy(newbuf, c->outbuf);
    imc_free(c->outbuf, c->outsize);
    c->outbuf=newbuf;
    c->outsize=newsize;
  }
  if (len<c->outsize/2 && len >= IMC_MINBUF)
  {
    newsize=c->outsize/2;

    newbuf=imc_malloc(newsize);
    strcpy(newbuf, c->outbuf);
    imc_free(c->outbuf, c->outsize);
    c->outbuf=newbuf;
    c->outsize=newsize;
  }

  strcat(c->outbuf, line);
  strcat(c->outbuf, "\n\r");

  if (strlen(c->outbuf)>=c->outsize/2)
  {
    imc_cancel_event(ev_shrink_output, c);
    imc_add_event(IMC_SHRINKTIME, ev_shrink_output, c, 0);
  }
}

/*  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)
{
  int i;
  char *buf=imc_getsbuf(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;
    imc_shrinksbuf(buf);
    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 */
//  memmove(buffer, buffer+i, strlen(buffer+i) + 1);
  strcpy(buffer,buffer+i);

  imc_shrinksbuf(buf);
  return buf;
}

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

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

  for (i=0; i<IMC_MEMORY; i++)
    if (imc_memory[i].from && 
        !strcasecmp(mud, imc_memory[i].from) &&
        seq == imc_memory[i].sequence)
      return 1;

  /* not a repeat, so log it */

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

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

  return 0;
}

#ifdef IMC_NOTIFY
static void do_notify(void)
{
  /* tell the central server that we're using IMC.
   *
   * This isn't related to your IMC connections, it's just so I can keep track
   * of how many people are using IMC, with what versions, and where.
   *
   * This gets done once a day (and on startup), but only when packets are
   * actually being forwarded. This means that muds not connected
   * to anything won't notify. The notification is a single UDP packet to a
   * hardcoded IP, containing the version ID of IMC being used, and your IMC
   * name.
   *
   * If it bugs you, comment out the #define IMC_NOTIFY in imc.h and it won't
   * notify the central server.
   */

  struct sockaddr_in sa;
  int s;
  char buf[100];

  sprintf(buf, "name=%s\nversion=%s\nemail=%s\n",
	  imc_name ? imc_name : "unset",
	  IMC_VERSIONID,
	  imc_siteinfo.email);

  s=socket(AF_INET, SOCK_DGRAM, 0);
  if (s<0)
    return;

  /* we won't do a lookup here.. if the IP changes, such is life.
   * 209.51.169.2 is toof.net
   */
  sa.sin_family=AF_INET;
  sa.sin_addr.s_addr=inet_addr("209.51.169.2");
  sa.sin_port=htons(9000);

  sendto(s, buf, 100, 0, (struct sockaddr *)&sa, sizeof(sa));

  close(s);
}
#endif  

/* send a packet to a mud using the right version */
static void do_send_packet(imc_connect *c, const imc_packet *p)
{
  const char *output;
  int v;
#ifdef IMC_NOTIFY
  static time_t last_notify;

  if (last_notify + 3600*24 < imc_now)
  {
    last_notify = imc_now;
    do_notify();
  }
#endif

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

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

  if (output)
  {
    imc_stats.tx_pkts++;
    if (strlen(output) > imc_stats.max_pkt)
      imc_stats.max_pkt=strlen(output);
    do_send(c, output);
  }
}

static int can_forward(const imc_packet *p)
{
  if (!strcasecmp(p->type, "chat") ||
      !strcasecmp(p->type, "emote"))
  {
    int chan=imc_getkeyi(&p->data, "channel", 0);
    
    if (chan==0 || chan==1 || chan==3)
      return 0;
  }

  return 1;
}

/* forward a packet - main routing function, all packets pass through here */
static void forward(imc_packet *p)
{
  imc_info *i;
  int broadcast, isbroadcast;
  const char *to;
  imc_reminfo *route;
  imc_info *direct;
  imc_connect *c;
  char recievedfrom[IMC_MAXBUF]; /* SPAM fix - shogar */
  imc_connect *rf; /* SPAM fix - shogar */

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

  /* check for packets we've already forwarded */

  if (inpath(p->i.path, imc_name))
    return;

  /* check for really old packets */

  route=imc_find_reminfo(imc_mudof(p->i.from), 1);
  if (route)
  {
    if ((p->i.sequence+IMC_PACKET_LIFETIME) < route->top_sequence)
    {
      imc_stats.sequence_drops++;
#ifdef LOG_LATE_PACKETS 
	/* kind of spammy, but late packets are natural when the path
           is broken between sender and reciever
      if(imc_is_router) /* spare the muds from seeing this - shogar */
         imc_logstring("sequence drop: %s (seq=%ld, top=%ld)",
		    p->i.path, p->i.sequence, route->top_sequence);
#endif
      return;
    }
    if (p->i.sequence > route->top_sequence)
      route->top_sequence=p->i.sequence;
  }

  /* update our routing info */

  updateroutes(p->i.path);

  /* forward to our mud if it's for us */

  if (!strcmp(imc_mudof(p->i.to), "*") ||
      !strcasecmp(imc_mudof(p->i.to), imc_name))
  {
    strcpy(p->to, imc_nameof(p->i.to));    /* strip the name from the 'to' */
    strcpy(p->from, p->i.from);

    imc_recv(p);
  }

  /* if its only to us (ie. not broadcast) don't forward it */
  if (!strcasecmp(imc_mudof(p->to), imc_name))
    return;

  /* check if we should just drop it (policy rules) */
  if (!can_forward(p))
    return;
  
  /* convert a specific destination to a broadcast in some cases */

  to=imc_mudof(p->i.to);

  isbroadcast=!strcmp(to, "*");	  /* broadcasts are, well, broadcasts */
  broadcast=1;		          /* unless we know better, flood packets */
  i=0;  			  /* make gcc happy */
  direct=NULL;			  /* no direct connection to send on */
  
  /* convert 'to' fields that we have a route for to a hop along the route */
  
  if (!isbroadcast &&
      (route=imc_find_reminfo(to, 0)) != NULL &&
      route->route != NULL &&
      !inpath(p->i.path, route->route))	/* 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 (strcasecmp(to, route->route) &&
	(i=imc_getinfo(to))!=NULL &&
	i->connection)
      direct=i;
    to=route->route;
  }
  
  /* check for a direct connection */
  
  if (!isbroadcast &&
      (i=imc_getinfo(to)) != NULL &&
      i->connection &&
      !(i->flags & IMC_BROADCAST))
    broadcast=0;

  if (broadcast)
  {				/* need to forward a packet */
    /* SPAM fix - hubcnt and who just gave us the packet- shogar */
    int hubcnt,fromhub;
    hubcnt=0;
    fromhub=0;
    strcpy(recievedfrom,imc_lastinpath(p->i.path)); 
    for (rf=imc_connect_list; rf; rf=rf->next)
    {
	if(rf->info && rf->info->name && !strcmp(recievedfrom,rf->info->name))
        {
        	if(rf->info->flags & IMC_HUB) 
			fromhub=1;
	}
    }
    /* end SPAM fix */

    for (c=imc_connect_list; c; c=c->next)
      if (c->state==IMC_CONNECTED)
      {
	/* don't forward to sites that have already received it,
	 * or sites that don't need this packet
	 */
	if (inpath(p->i.path, c->info->name) ||
	    (p->i.stamp & c->info->noforward)!=0)
	  continue;
        /* SPAM fix - shogar */
        if(c->info->flags & IMC_HUB) 
        {
	    	if(!imc_is_router)
            	{
			if (fromhub)
				continue;
			if(hubcnt)
			{
				continue;
			}
			else
			{
				hubcnt=1;
			}
		}
/* if for imc3 we need to do this - shogar */
/*
        	if (imc_getkeyi(&p->data,"channel",0) == 2)
			continue;
*/
	}
	/* end SPAM fix */
	do_send_packet(c, p);
      }
  }
  else
    /* forwarding to a specific connection */
  {
    /* but only if they haven't seen it (sanity check) */
    if (i->connection && !inpath(p->i.path, i->name))
      do_send_packet(i->connection, p);

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

/* handle a password from a client */
static void clientpassword(imc_connect *c, const char *argument)
{
  char arg1[3], name[IMC_MNAME_LENGTH], pw[IMC_PW_LENGTH], version[20];
  imc_info *i;
  char response[IMC_PACKET_LENGTH];

  argument=imc_getarg(argument, arg1, 4);      /* packet type (has to be PW) */
  argument=imc_getarg(argument, name, IMC_MNAME_LENGTH);  /* remote mud name */
  argument=imc_getarg(argument, pw, IMC_PW_LENGTH);	         /* password */
  argument=imc_getarg(argument, version, 20);	/* optional version=n string */

  if (strcasecmp(arg1, "PW"))
  {
    imc_logstring("%s: non-PW password packet", imc_getconnectname(c));
    do_close(c);
    return;
  }

  /* do we know them, and do they have the right password? */
  i=imc_getinfo(name);
  if (!i || strcmp(i->clientpw, pw))
  {
    if (!i || !(i->flags & IMC_QUIET))
    imc_logstring("%s: password failure for %s", imc_getconnectname(c), name);
    do_close(c);
    return;
  }

  /* deny access if deny flag is set (good for eg. muds that start crashing
   * on rwho)
   */
  if (i->flags & IMC_DENY)
  {
    if (!(i->flags & IMC_QUIET))
      imc_logstring("%s: denying connection", name);
    do_close(c);
    return;
  }

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

  /* register them */
  i->connection     = c;

  c->state          = IMC_CONNECTED;
  c->info           = i;
  c->spamcounter1   = 0;
  c->spamcounter2   = 0;

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

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

  /* send our response */

  sprintf(response, "PW %s %s version=%d",
	  imc_name, i->serverpw, IMC_VERSION);
  do_send(c, response);

  if (!(i->flags & IMC_QUIET))
    imc_logstring("%s: connected (version %d)",
		  imc_getconnectname(c), c->version);

  c->info->timer_duration=IMC_MIN_RECONNECT_TIME;
  c->info->last_connected=imc_now;
  imc_cancel_event(ev_login_timeout, c);
  imc_cancel_event(ev_reconnect, c->info);
}

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

  argument=imc_getarg(argument, arg1, 4);	/* has to be PW */
  argument=imc_getarg(argument, name, IMC_MNAME_LENGTH);
  argument=imc_getarg(argument, pw, IMC_PW_LENGTH);
  argument=imc_getarg(argument, version, 20);

  if (strcasecmp(arg1, "PW"))
  {
    imc_logstring("%s: non-PW password packet", imc_getconnectname(c));
    do_close(c);
    return;
  }

  i=imc_getinfo(name);
  if (!i || strcmp(i->serverpw, pw) ||
      i != c->info)
  {
    if ((!i || !(i->flags & IMC_QUIET)) && !(c->info->flags & IMC_QUIET))
    imc_logstring("%s: password failure for %s", imc_getconnectname(c), name);
    do_close(c);
    return;
  }

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

  i->connection         = c;

  c->state              = IMC_CONNECTED;
  c->spamcounter1       = 0;
  c->spamcounter2       = 0;

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

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

  if (!(i->flags & IMC_QUIET))
    imc_logstring("%s: connected (version %d)",
		  imc_getconnectname(c), c->version);
  c->info->timer_duration=IMC_MIN_RECONNECT_TIME;
  c->info->last_connected=imc_now;
  imc_cancel_event(ev_login_timeout, c);
  imc_cancel_event(ev_reconnect, c->info);
}

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

  if (imc_active!=IA_UP)
  {
    imc_logerror("imc_startup_port: called with imc_active=%d", imc_active);
    return;
  }

  if (imc_port==0)
  {
    imc_logerror("imc_startup_port: called with imc_port=0");
    return;
  }
  
  imc_logstring("binding port %d for incoming connections", imc_port);
      
  control = socket(AF_INET, SOCK_STREAM, 0);
  if (control<0)
  {
    imc_lerror("imc_startup_port: socket");
    return;
  }
    
  i=1;
  if (setsockopt(control, SOL_SOCKET, SO_REUSEADDR, (void *)&i,
		 sizeof(i))<0)
  {
    imc_lerror("imc_startup_port: SO_REUSEADDR");
    close(control);
    return;
  }
  
  if ((i=fcntl(control, F_GETFL, 0))<0)
  {
    imc_lerror("imc_startup_port: fcntl(F_GETFL)");
    close(control);
    return;
  }

  if (fcntl(control, F_SETFL, i | O_NONBLOCK)<0)
  {
    imc_lerror("imc_startup_port: fcntl(F_SETFL)");
    close(control);
    return;
  }

  sa.sin_family      = AF_INET;
  sa.sin_port        = htons(imc_port);
  sa.sin_addr.s_addr = imc_bind; /* already in network order */
  
  if (bind(control, (struct sockaddr *)&sa, sizeof(sa))<0)
  {
    imc_lerror("imc_startup_port: bind");
    close(control);
    return;
  }
  
  if (listen(control, 1)<0)
  {
    imc_lerror("imc_startup_port: listen");
    close(control);
    return;
  }
  
  imc_active=IA_LISTENING;
}

/* shut down listening port */
void imc_shutdown_port(void)
{
  if (imc_active!=IA_LISTENING)
  {
    imc_logerror("imc_shutdown_port: called with imc_active=%d", imc_active);
    return;
  }

  imc_logstring("closing listen port");
  close(control);
  imc_active=IA_UP;
}

#ifdef USEIOCTL
/*  this is an ugly hack to generate the send-queue size for an empty queue.
 *  SO_SNDBUF is only supported in some places, and seems to cause problems
 *  under SunOS
 */

/*  connect to the local discard server, and look at the queue size for an
 *  empty socket.
 */
static int getsndbuf(void)
{
  struct sockaddr_in sa;
  int s, queue;

  if ((s=socket(AF_INET, SOCK_STREAM, 0))<0)
    return 0;

  sa.sin_family      = AF_INET;
  sa.sin_addr.s_addr = inet_addr("127.0.0.1");	/* connect to localhost */
  sa.sin_port        = htons(9);                /* 'discard' service */

  if (connect(s, (struct sockaddr *)&sa, sizeof(sa))<0)
  {
    close(s);
    return 0;
  }

  if (ioctl(s, TIOCOUTQ, &queue)<0)
  {
    close(s);
    return 0;
  }

  close(s);
  return queue;
}
#endif

static int lock_prefix(void)
{
  char lockfile[1000];

  sprintf(lockfile, "%slock", imc_prefix);
  imc_lock_file=open(lockfile, O_CREAT|O_EXCL|O_RDWR, 0644);
  if (imc_lock_file<0)
    imc_lock_file=open(lockfile, O_RDWR, 0644);
  if (imc_lock_file<0)
  {
    imc_lerror("lock_prefix: open %s", lockfile);
    return 0;
  }

  //if (lockf(imc_lock_file, F_TLOCK, 1)<0)
  if (imc_lock_file<0) {
    close(imc_lock_file);
    imc_lock_file=-1;
    return 0;
  }

  return 1;
}

static void unlock_prefix(void)
{
  if (imc_lock_file<0)
    return;

  //lockf(imc_lock_file, F_ULOCK, 1);
  close(imc_lock_file);
  imc_lock_file=-1;
}

/* start up IMC */
void imc_startup_network(void)
{
    imc_info *info;
	int toggle=0; /* This is to tell after we've done reconnects if we've connected to a hub or not -- Scion */

  if (imc_active != IA_CONFIG2)
  {
    imc_logerror("imc_startup_network: called with imc_active==%d",
		 imc_active);
    return;
  }

  if (!imc_siteinfo.name[0])
  {
    imc_logerror("InfoName not set, not initializing");
    return;
  }

  if (!imc_siteinfo.email[0])
  {
    imc_logerror("InfoEmail not set, not initializing");
    return;
  }
  
  imc_logstring("network initializing");

  imc_active=IA_UP;

  control=-1;

  if (imc_port)
    imc_startup_port();
  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.sequence_drops = 0;

  imc_add_event(20, ev_keepalive, NULL, 1);
  /* fill my imclist please - shogar */
  imc_add_event(30, ev_request_keepalive, NULL, 1);

  imc_mail_startup();		/* start up the mailer */

  if (!lock_prefix())
  {
    imc_logstring("another process is using the same config prefix, not autoconnecting.");
    return;
  }

  /* do autoconnects */
  for (info=imc_info_list; info; info=info->next)
    if (!(info->flags & IMC_NOAUTO) && !(info->flags & IMC_CLIENT) 
		&& !(info->flags & IMC_DENY) 
 /*               && !(info->flags & IMC_OLD_HUB)  not used anymore - shogar
                && !(info->flags & IMC_DEAD_HUB)
*/
		&& !(info->flags & IMC_HUB)) {
      if (imc_connect_to(info->name) && ((info->flags & IMC_MAIN_HUB) || (info->flags & IMC_HUB)))
		  toggle=1;
	}

  
	/* Setup optimization to run, it won't if we're a hub -- Scion */
   if(imc_hubswitch && !imc_is_router)
   {
   	imc_add_event(360, ev_imc_pollforhub, NULL, 1);
   	imc_add_event(420, ev_imc_optimize, NULL, 1);

   	imc_logstring("Setting auto-optimize to run in 6 minutes.");
   }

   /* Are we connected to any hubs, btw? -- Scion */

   if (!toggle) { /* No hubs connected! */
	   for (info=imc_info_list; info; info=info->next)
		   if ((info->flags & IMC_HUB) && (!toggle))
			   if (imc_connect_to(info->name))
				   toggle=0;
   }
   /* We're out of ideas, notify the admins -- Scion */
/*
   if (!toggle)
	   imc_logstring("No hubs connected! Check the configuration!");
*/
}

void imc_startup(const char *prefix)
{
  if (imc_active!=IA_NONE)
  {
    imc_logstring("imc_startup: called with imc_active=%d", imc_active);
    return;
  }

  imc_now=time(NULL);                  /* start our clock */
  imc_boot=imc_now;

  imc_logstring("%s initializing", IMC_VERSIONID);

#ifdef USEIOCTL
  outqsize = getsndbuf();
  imc_logstring("found TIOCOUTQ=%d", outqsize);
#endif

  imc_prefix=imc_strdup(prefix);

  imc_sequencenumber=imc_now;
  strcpy(imc_lasterror, "no error");

  imc_readconfig();
  imc_readignores();

  imc_active = imc_name ? IA_CONFIG2 : IA_CONFIG1;

  if (imc_active==IA_CONFIG2)
    imc_startup_network();
}

void imc_shutdown_network(void)
{
  imc_event *ev, *ev_next;
  imc_connect *c, *c_next;
  imc_reminfo *p, *pnext;

  if (imc_active < IA_UP)
  {
    imc_logerror("imc_shutdown_network: called with imc_active==%d",
		 imc_active);
    return;
  }

  if (imc_lock)
  {
    imc_logerror("imc_shutdown_network: called from within imc_idle_select");
    return;
  }

  imc_logstring("shutting down network");

  if (imc_active == IA_LISTENING)
    imc_shutdown_port();

  imc_logstring("rx %ld packets, %ld bytes (%ld/second)",
		imc_stats.rx_pkts,
		imc_stats.rx_bytes,
		(imc_now == imc_stats.start) ? 0 :
		imc_stats.rx_bytes / (imc_now - imc_stats.start));
  imc_logstring("tx %ld packets, %ld bytes (%ld/second)",
		imc_stats.tx_pkts,
		imc_stats.tx_bytes,
		(imc_now == imc_stats.start) ? 0 :
		imc_stats.tx_bytes / (imc_now - imc_stats.start));
  imc_logstring("largest packet %d bytes", imc_stats.max_pkt);
  imc_logstring("dropped %d packets by sequence number",
                imc_stats.sequence_drops);

  imc_mail_shutdown();

  for (c=imc_connect_list; c; c=c_next)
  {
    c_next=c->next;
    do_close(c);
    imc_extract_connect(c);
  }
  imc_connect_list=NULL; 
  if(!imc_is_router)
  	icec_shutdown();
  for (p=imc_reminfo_list; p; p=pnext)
  {
    pnext=p->next;
    imc_strfree(p->version);
    imc_strfree(p->name);
    if(p->path) imc_strfree(p->path); 
    imc_free(p, sizeof(imc_reminfo));
  }
  imc_reminfo_list=NULL;

  for (ev=imc_event_list; ev; ev=ev_next)
  {
    ev_next=ev->next;
    imc_free(ev, sizeof(imc_event));
  }
  for (ev=imc_event_free; ev; ev=ev_next)
  {
    ev_next=ev->next;
    imc_free(ev, sizeof(imc_event));
  }
  imc_event_list=imc_event_free=NULL;

  unlock_prefix();

  imc_active=IA_CONFIG2;
}

/* close down imc */
void imc_shutdown(void)
{
  imc_ignore_data *ign, *ign_next;
  imc_info *info, *info_next;

  if (imc_active==IA_NONE)
  {
    imc_logerror("imc_shutdown: called with imc_active==0");
    return;
  }

  if (imc_active>=IA_UP)
    imc_shutdown_network();

  for (ign=imc_ignore_list; ign; ign=ign_next)
  {
    ign_next=ign->next;
    imc_freeignore(ign);
  }
  imc_ignore_list=NULL;

  for (info=imc_info_list; info; info=info_next)
  {
    info_next=info->next;
    imc_delete_info(info);
  }
  imc_info_list=NULL;

  if (imc_active >= IA_UP)
    imc_shutdown_network();

  imc_strfree(imc_prefix);
  imc_prefix=NULL;

  if (imc_active >= IA_CONFIG2)
    imc_strfree(imc_name);

  imc_name=NULL;
  imc_active=IA_NONE;
}

/* interpret an incoming packet using the right version */
static imc_packet *do_interpret_packet(imc_connect *c, const char *line)
{
  int v;
  imc_packet *p;

  if (!line[0])
    return NULL;

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

  p=(*imc_vinfo[v].interpret)(line);
  if (p)
  {
    if (c->info)
    {
      p->i.stamp=c->info->rcvstamp;
    }
    else
    {
      p->i.stamp=0;
    }
  }

  return p;
}

int imc_fill_fdsets(int maxfd, fd_set *read, fd_set *write, fd_set *exc)
{
  imc_connect *c;

  if (imc_active<IA_UP)
    return maxfd;

  /* set up fd_sets for select */

  if (imc_active>=IA_LISTENING)
  {
    if (maxfd < control)
      maxfd = control;
    FD_SET(control, read);
  }

  for (c=imc_connect_list; c; c=c->next)
  {
    if (maxfd < c->desc)
      maxfd = c->desc;

    switch (c->state)
    {
    case IMC_CONNECTING:	/* connected/error when writable */
      FD_SET(c->desc, write);
      break;
    case IMC_CONNECTED:
    case IMC_WAIT1:
    case IMC_WAIT2:
      FD_SET(c->desc, read);
      if (c->outbuf[0])
	FD_SET(c->desc, write);
      break;
    }
  }

  return maxfd;
}

int imc_get_max_timeout(void)
{
  imc_event *p;

  for (p=imc_event_list; p; p=p->next)
    if (p->timed)
      return p->when - imc_now;

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

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

  FD_ZERO(&read);
  FD_ZERO(&write);
  FD_ZERO(&exc);

  maxfd=imc_fill_fdsets(0, &read, &write, &exc);
  timeout.tv_sec = s;
  timeout.tv_usec = us;

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

  if (i<0)
  {
    imc_lerror("imc_idle: select");
    imc_shutdown_network();
    return;
  }

  imc_idle_select(&read, &write, &exc, time(NULL));
}

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

  if (imc_active<IA_CONFIG1)
    return;

  if (imc_lock)
  {
    imc_logerror("imc_idle_select: recursive call");
    return;
  }

  imc_lock=1;

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

  imc_run_events(now);

  if (imc_active<IA_UP)
  {
    imc_lock=0;
    return;
  }

  /* handle results of the select */

  if (imc_active >= IA_LISTENING &&
      FD_ISSET(control, read))
    do_accept();

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

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

    if (c->state!=IMC_CLOSED && FD_ISSET(c->desc, read))
      do_read(c);

    while (c->state!=IMC_CLOSED &&
//	   (c->spamtime1>=0 || c->spamcounter1<=IMC_SPAM1MAX) &&
//	   (c->spamtime2>=0 || c->spamcounter2<=IMC_SPAM2MAX) &&
	   (command = getline(c->inbuf)) != NULL)
    {
      if (strlen(command) > imc_stats.max_pkt)
	imc_stats.max_pkt=strlen(command);

//      imc_debug(c, 0, command);	/* log incoming packets */

      switch (c->state)
      {
      case IMC_CLOSED:
	break;
      case IMC_WAIT1:
	clientpassword(c, command);
	break;
      case IMC_WAIT2:
	serverpassword(c, command);
	break;
      case IMC_CONNECTED:
	p = do_interpret_packet(c, command);
	if (p)
	{
#ifdef IMC_PARANOIA
	  /* paranoia: 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.
	   */

	  imc_stats.rx_pkts++;

	  if (strcasecmp(c->info->name,
			 imc_lastinpath(p->i.path)))
	    imc_logerror("PARANOIA: packet from %s allegedly from %s",
			 c->info->name,
			 imc_lastinpath(p->i.path));
	  else if (strcasecmp(imc_mudof(p->i.from), imc_firstinpath(p->i.path)))
	    imc_logerror("PARANOIA: packet from %s has firstinpath %s",
			 p->i.from,
			 imc_firstinpath(p->i.path));
	  else
	    forward(p);		/* only forward if its a valid packet! */
#else
	  imc_stats.rx_pkts++;
	  forward(p);
#endif
#ifdef SPAMPROT
  if (!strcasecmp(p->type, "chat") ||
      !strcasecmp(p->type, "tell") ||
      !strcasecmp(p->type, "emote") || 1)
  {
      if (!c->spamcounter1 && !c->spamtime1)
	imc_add_event(IMC_SPAM1INTERVAL, ev_spam1, c, 0);
      c->spamcounter1++;

      if (!c->spamcounter2 && !c->spamtime2)
	imc_add_event(IMC_SPAM2INTERVAL, ev_spam2, c, 0);
      c->spamcounter2++;
  }
#endif
	  imc_freedata(&p->data);
	}
	break;
      }
    }
  }

  for (c=imc_connect_list; c; c=c_next)
  {
    c_next=c->next;
    
    if (c->state!=IMC_CLOSED &&
	(FD_ISSET(c->desc, write) || c->newoutput))
    {
//      c->newoutput=0;
      do_write(c);
      c->newoutput=c->outbuf[0];
    }
  }

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

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

  imc_lock=0;
}

/* connect to given mud */
int imc_connect_to(const char *mud)
{
  imc_info *i;
  imc_connect *c;
  int desc;
  struct sockaddr_in sa;
  char buf[IMC_DATA_LENGTH];
  int r;

  if (imc_active == IA_NONE)
  {
    imc_qerror("IMC is not active");
    return 0;
  }
    
  i=imc_getinfo(mud);
  if (!i)
  {
    imc_qerror("%s: unknown mud name", mud);
    return 0;
  }

  if (i->connection)
  {
    imc_qerror("%s: already connected", mud);
    return 0;
  }

  if (i->flags & IMC_CLIENT)
  {
    imc_qerror("%s: client-only flag is set", mud);
    return 0;
  }

  if (i->flags & IMC_DENY)
  {
    imc_qerror("%s: deny flag is set", mud);
    return 0;
  }

  if (!(i->flags & IMC_QUIET))
    imc_logstring("connect to %s", mud);

  /*  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 ((sa.sin_addr.s_addr=inet_addr(i->host)) == -1UL)
  {
    struct hostent *hostinfo;

    if (NULL == (hostinfo=gethostbyname(i->host)))
    {
      imc_logerror("imc_connect: couldn't resolve hostname");
      return 0;
    }

    sa.sin_addr.s_addr = *(unsigned long *) hostinfo->h_addr;
  }

  sa.sin_port   = htons(i->port);
  sa.sin_family = AF_INET;

  desc=socket(AF_INET, SOCK_STREAM, 0);
  if (desc<0)
  {
    imc_lerror("socket");
    return 0;
  }

  r=fcntl(desc, F_GETFL, 0);
  if (r<0 || fcntl(desc, F_SETFL, O_NONBLOCK | r)<0)
  {
    imc_lerror("imc_connect: fcntl");
    close(desc);
    return 0;
  }

  if (connect(desc, (struct sockaddr *)&sa, sizeof(sa))<0)
    if (errno != EINPROGRESS)
    {
      imc_lerror("connect");
      close(desc);
      return 0;
    }

  c=imc_new_connect();

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

  imc_add_event(IMC_LOGIN_TIMEOUT, ev_login_timeout, c, 1);

  sprintf(buf, "PW %s %s version=%d",
	  imc_name,
	  i->clientpw,
	  IMC_VERSION);
  do_send(c, buf);

  return 1;
}

int imc_disconnect(const char *mud)
{
  imc_connect *c;
  imc_info *i;
  int d;

  if (imc_active == IA_NONE)
  {
    imc_qerror("IMC is not active");
    return 0;
  }

  if ((d=atoi(mud))!=0)
  {
    /* disconnect a specific descriptor */

    for (c=imc_connect_list; c; c=c->next)
      if (c->desc==d)
      {
        imc_logstring("disconnect descriptor %s", imc_getconnectname(c));
	do_close(c);
	return 1;
      }

    imc_qerror("%d: no matching descriptor", d);
    return 0;
  }
    
  i=imc_getinfo(mud);
  if (!i)
  {
    if (strcasecmp(mud, "unknown")) /* disconnect all unknown muds */
    {
      imc_qerror("%s: unknown mud", mud);
      return 0;
    }
  }

  imc_logstring("disconnect %s", mud);

  for (c=imc_connect_list; c; c=c->next)
    if (c->info==i)
      do_close(c);

  return 1;
}

void imc_send(imc_packet *p)
{
  if (imc_active < IA_UP)
  {
    imc_logerror("imc_send when not active!");
    return;
  }
  
  /* initialize packet fields that the caller shouldn't/doesn't set */

  p->i.stamp = 0;
  p->i.path[0]  = 0;
  
  p->i.sequence = imc_sequencenumber++;
  if (!imc_sequencenumber)
    imc_sequencenumber++;
  
  imc_sncpy(p->i.to, p->to, IMC_NAME_LENGTH);
  
  imc_sncpy(p->i.from, p->from, IMC_NAME_LENGTH);
  strcat(p->i.from, "@");
  imc_sncpy(p->i.from + strlen(p->i.from), imc_name,
	    IMC_NAME_LENGTH - strlen(p->i.from));

  forward(p);
}

imc_info *imc_new_info()
{
  imc_info *i, *p;

  i=imc_malloc(sizeof(*i));

  i->name       = NULL;
  i->host       = NULL;
  i->port       = 0;
  i->connection = NULL;
  i->clientpw   = NULL;
  i->serverpw   = NULL;
  i->timer_duration = IMC_MIN_RECONNECT_TIME;
  i->rcvstamp   = 0;
  i->noforward  = 0;
  i->flags      = 0;
  i->last_connected = 0;

  /* ugly hack, but Too Bad, I don't want another global floating around */
  i->next=NULL;

  for (p=imc_info_list; p && p->next; p=p->next)
    ;

  if (!p)
    imc_info_list=i;
  else
    p->next=i;

  return i;
}
imc_info *imc_insert_info()
{
  imc_info *i;

  i=imc_malloc(sizeof(*i));

  i->name       = NULL;
  i->host       = NULL;
  i->port       = 0;
  i->connection = NULL;
  i->clientpw   = NULL;
  i->serverpw   = NULL;
  i->timer_duration = IMC_MIN_RECONNECT_TIME;
  i->rcvstamp   = 0;
  i->noforward  = 0;
  i->flags      = 0;
  i->last_connected = 0;

  /* ugly hack, but Too Bad, I don't want another global floating around */
  i->next=imc_info_list;
  imc_info_list=i;
  return i;
}

void imc_delete_info(imc_info *i)
{
  imc_connect *c;
  imc_info *last;

  for (c=imc_connect_list; c; c=c->next)
    if (c->info==i)
      do_close(c);

  if (i==imc_info_list)
    imc_info_list=i->next;
  else
  {
    for (last=imc_info_list; last && last->next!=i; last=last->next)
      ;

    if (!last)
      imc_logerror("imc_delete_info: not in list");
    else
      last->next=i->next;
  }

  if (i->name)
    imc_strfree(i->name);
  if (i->host)
    imc_strfree(i->host);
  if (i->clientpw)
    imc_strfree(i->clientpw);
  if (i->serverpw)
    imc_strfree(i->serverpw);

  imc_cancel_event(NULL, i);

  imc_free(i, sizeof(*i));
}
void imc_cancel_info(imc_info *i)
{
  imc_info *last;

  if (i==imc_info_list)
    imc_info_list=i->next;
  else
  {
    for (last=imc_info_list; last && last->next!=i; last=last->next)
      ;

    if (!last)
      imc_logerror("imc_delete_info: not in list");
    else
      last->next=i->next;
  }

  if (i->name)
    imc_strfree(i->name);
  if (i->host)
    imc_strfree(i->host);
  if (i->clientpw)
    imc_strfree(i->clientpw);
  if (i->serverpw)
    imc_strfree(i->serverpw);

  imc_free(i, sizeof(*i));
}
