#ifdef RCS
static char rcsid[]="$Id: link.c,v 1.1.1.1 2000/11/13 02:42:45 holsta Exp $";
#endif
/******************************************************************************
 *                    Internetting Cooperating Programmers
 * ----------------------------------------------------------------------------
 *
 *  ____    PROJECT
 * |  _ \  __ _ _ __   ___ ___ _ __ 
 * | | | |/ _` | '_ \ / __/ _ \ '__|
 * | |_| | (_| | | | | (_|  __/ |   
 * |____/ \__,_|_| |_|\___\___|_|   the IRC bot
 *
 * All files in this archive are subject to the GNU General Public License.
 *
 * $Source: /cvsroot/dancer/dancer/src/link.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 2000/11/13 02:42:45 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

/*
 * - Exchange of userlist not implemented.
 * - Problems when linking more than two clusters of linkbots
 *   together (only one cluster gets to know about the other
 *   cluster)
 */

/*
 * Define to test reliability. If defined it will skip acks
 * and send duplicates at random. Don't do this at home, kids.
 */
#undef UNRELIABLE

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "list.h"
#include "user.h"
#include "transfer.h"
#include "link.h"
#include "command.h"
#include "function.h"

#include <stdarg.h>
#include <sys/socket.h>

#define TIMEREQUEST (-1)

extern time_t now;
extern int linkSocket;

extern struct sockaddr_in fromaddr;
extern itemuser *userHead;
extern itemlink *linkHead;
extern char nickname[];
extern ulong linkvalue;
extern long levels[];

time_t commontime = 0; /* Reference zero time */


/* ----------------------------------------------------------------
 *
 *  Level 1 routines
 *
 * ---------------------------------------------------------------- */

/*
 * Various routines to provide reliable message passing. The rtt_XXX
 * routines are taken from chapter 8.4 of "Unix Network Programming"
 * by Stevens. Didn't bother to add the comments.
 */

#ifdef UNRELIABLE
int exp_backoff[] = {1, 1, 1, 1, 1};
#else
int exp_backoff[] = {1, 2, 4, 8, 16};
#endif
int whatever[] = {1, 1};

#define RTT_RXTMIN 2
#define RTT_RXTMAX 120
#define RTT_MAXNREXMT (sizeof(exp_backoff)/sizeof(exp_backoff[0]))

void rtt_init(itemlink *r)
{
  snapshot;
  r->rtt_rtt = r->rtt_srtt = 0;
  r->rtt_rttdev = 1.5;
  r->rtt_nxtrto = 0;
}

inline void rtt_newpack(itemlink *r)
{
  snapshot;
  r->rtt_nrexmt = 0;
}

/* Returns timeout value */
int rtt_start(itemlink *r)
{
  int rexmt;

  snapshot;
  if (r->rtt_nrexmt > 0) {
    r->rtt_currto *= exp_backoff[r->rtt_nrexmt];
    return (r->rtt_currto);
  }
  gettimeofday(&r->time_start, NULL);
  if (r->rtt_nxtrto > 0) {
    r->rtt_currto = r->rtt_nxtrto;
    r->rtt_nxtrto = 0;
    return (r->rtt_currto);
  }
  rexmt = (int)(r->rtt_srtt + (2.0 * r->rtt_rttdev) + 0.5);
  if (rexmt < RTT_RXTMIN)
    rexmt = RTT_RXTMIN;
  else if (rexmt > RTT_RXTMAX)
    rexmt = RTT_RXTMAX;
  return (r->rtt_currto = rexmt);
}

void rtt_stop(itemlink *r)
{
  long diff;
  float err;

  snapshot;
  if (r->rtt_nrexmt > 0)
    r->rtt_nxtrto = r->rtt_currto;
  else {
    r->rtt_nxtrto = 0;
    gettimeofday(&r->time_stop, NULL);
    diff = r->time_stop.tv_sec - r->time_start.tv_sec;
    if (r->time_stop.tv_usec < r->time_start.tv_usec)
      diff--;
    r->rtt_rtt = (float)diff;
    /* Use integer version instead */
    err = r->rtt_rtt - r->rtt_srtt;
    r->rtt_srtt += err/8;
    if (err < 0.0)
      err = -err;
    r->rtt_rttdev += (err - r->rtt_rttdev) / 4;
  }
}

int rtt_timeout(itemlink *r)
{
  snapshot;
  rtt_stop(r);
  return (++r->rtt_nrexmt >= RTT_MAXNREXMT);
}

/*
 * msgid, type, msg, \n
 */

#if 0
int L1_recv(struct Socket *self, char *buf, int len, int flags)
{
  int size = sizeof(self->fromaddr);

  snapshot;
  size = recvfrom(self->handle, buf, len, flags,
		  (struct sockaddr *)&(self->fromaddr), &size);
  buf[size] = NIL;
#ifdef DBUG
  fprintf(stderr, "L1_recv: %d (%d) << %s",
	  self->handle, ntohs(self->fromaddr.sin_port), buf);
#endif
  return size;
}

int L1_send(itemlink *self, char *msg, int len, int flags)
{
  snapshot;
  sendto(self->handle, msg, len, flags,
	 (struct sockaddr *)&self->addr, sizeof(struct sockaddr_in));
#ifdef DBUG
  fprintf(stderr, "%d (%d) >> %s",
          self->handle, ntohs(self->addr.sin_port), rawmsg);
#endif
}
#endif

/* --- ReadLink --------------------------------------------------- */

int ReadLink(char *inbuf)
{
  int size = sizeof(fromaddr);

  snapshot;
  size = recvfrom(linkSocket, inbuf, MAXLINE, 0,
                  (struct sockaddr *)&fromaddr, &size);
  inbuf[size] = NIL;
#ifdef DBUG
  fprintf(stderr, "%d (%d) << %s", linkSocket, ntohs(fromaddr.sin_port), inbuf);
#endif
  return size;
}

/* --- WriteLink -------------------------------------------------- */

#define UDPBUFFER 1024

void WriteLinkRaw(itemlink *r, char *rawmsg)
{
  snapshot;

#ifdef DBUG
  fprintf(stderr, "%d (%d) >> %s",
          linkSocket, ntohs(r->addr.sin_port), rawmsg);
#endif

  sendto(linkSocket, rawmsg, StrLength(rawmsg), 0,
	 (struct sockaddr *)&r->addr, sizeof(struct sockaddr_in));
}

void WriteLink(itemlink *r, int type, char *msg)
{
  unsigned char buffer[UDPBUFFER];

  snapshot;
  StrFormatMax(buffer, sizeof(buffer), "%d %d %s\n", ++r->mymsgid, type, msg);
  WriteLinkRaw(r, buffer);
}

void WriteLinkf(itemlink *r, int type, char *format, ...)
{
  char buffer[MAXLINE];
  va_list args;

  snapshot;
  va_start(args, format);  
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  WriteLink(r, type, buffer);
}

/* --- SendLink --------------------------------------------------- */
/* If queue is empty send directly, else queue it. Don't remove
 * from queue until ACK is received. The message on top of the
 * list is always the one being sent, the rest are stalled to
 * ensure casuality.
 */

void QueueLink(itemlink *r, int type, char *msg)
{
  char buffer[BIGGERBUFFER];
  itemmsg *m;

  snapshot;
  StrFormatMax(buffer, sizeof(buffer), "%d %lu %s\n", ++r->mymsgid, type, msg);

  m = NewEntry(itemmsg);
  if (m) {
    m->msg = StrDuplicate(buffer);

    /* If first in queue ship it now */
    if (EmptyList(&r->RemoteHead)) {
      rtt_newpack(r);
      WriteLinkRaw(r, buffer);

#ifdef UNRELIABLE
      if (Rnd() < 0.2) /* Send duplicate */
        WriteLinkRaw(r, buffer);
#endif

      r->expires = now + rtt_start(r);
    }

    InsertLast(&r->RemoteHead, m);
  }
}

void SendLink(itemlink *r, int type, char *format, ...)
{
  char buffer[BIGGERBUFFER];
  va_list args;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  QueueLink(r, type, buffer);
}

void SendLinkAll(int type, char *format, ...)
{
  char buffer[BIGGERBUFFER];
  va_list args;
  itemlink *r;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  for (r = First(linkHead); r; r = Next(r)) {
    if (!r->new)
      QueueLink(r, type, buffer);
  }
}

void SendLinkBut(itemlink *rin, int type, char *format, ...)
{
  char buffer[BIGGERBUFFER];
  va_list args;
  itemlink *r;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  for (r = First(linkHead); r; r = Next(r)) {
    if (!r->new && (r != rin))
      QueueLink(r, type, buffer);
  }
}

/* --- CheckLinkQueue --------------------------------------------- */

/*
 * Whenever a message is sent it must wait for acknowledgement.
 * When messages are sent they are put in a queue. Each linked
 * bot has its own queue. When an ack is received the next message
 * from the queue is sent.
 */

void CheckLinkQueue(void)
{
  itemlink *r;
  itemmsg *m;

  snapshot;

  /* Called each second */
  for (r = First(linkHead); r; r = Next(r)) {
    m = First(&r->RemoteHead);
    if (m) {
      if (r->expires <= now) {
        if (rtt_timeout(r)) {
          /* Link is dead, remove it */
          r = RemoveLink(r);
          continue; /* for */
        }
        else {
          /* Retransmit message */
          WriteLinkRaw(r, m->msg);
          r->expires = now + rtt_start(r);
        }
      }
    }
  }
}

void FlushLinkQueue(itemlink *r)
{
  snapshot;
  FlushList(&r->RemoteHead, FreeMessage);
}

/* --- LinkDequeue ------------------------------------------------ */

bool LinkDequeue(itemlink *r, unsigned long msgid)
{
  unsigned long mid;
  int type;
  itemmsg *m;

  snapshot;

  /* Remove from top of queue */
  if ((m = First(&r->RemoteHead)) &&
      (2 == StrScan(m->msg, "%d %lu", &mid, &type))) {

    /* Duplicates of previously acknowledged message may exist */
    if (mid == msgid) {
      /* Wait to remove the rejected link until it acknowledges */
      if (IBCP_REJECT == type) {
        RemoveLink(r);
        return TRUE;
      }
      else {
        DeleteEntry(&r->RemoteHead, m, FreeMessage);

        m = First(&r->RemoteHead);
        if (m) {
          /* Ship the next */
          rtt_newpack(r);
          WriteLinkRaw(r, m->msg);
          r->expires = now + rtt_start(r);
        }
      }
    }
  }
  return FALSE;
}


/* ----------------------------------------------------------------
 *
 *  Level 2 routines
 *
 * ---------------------------------------------------------------- */

/* --- LinkQuit --------------------------------------------------- */

/*
 * The QUIT message surpasses the queue. We don't care about
 * acknowledgement - if the recipient received it fine; if not
 * the connection will timeout eventually.
 */

void LinkQuit(void)
{
  itemlink *r;

  snapshot;

  /* Equivalent to SendLinkAll(IBCP_QUIT, "Bored"); */
  for (r = First(linkHead); r; r = Next(r))
    WriteLink(r, IBCP_QUIT, "Bored");
}

/* --- LinkHello -------------------------------------------------- */

itemlink *LinkHello(char *msg)
{
  int ver, bottype;
  ulong key;
  itemlink *r = NULL;
  char name[MINIBUFFER];

  snapshot;
  if (4 == StrScan(msg, "%d %d %s %lu", &ver, &bottype, name, &key))
    {
      /*
       * We initiated the link. Now that we've got the info
       * of the other end we add it and reply with our info.
       */
      r = AddLink(name, NULL, ntohl(fromaddr.sin_addr.s_addr),
		  ntohs(fromaddr.sin_port));
      if (r)
	{
	  r->bottype = bottype;
	  r->protoversion = ver;
	  /* We asked to link so we assume that the other end is authenticated */
	  r->passed = TRUE;
	  SendLink(r, IBCP_HELLO_REPLY, "%d %d %s %lu",
		   IBCP_VERSION, IBCP_BOT_DANCER, nickname, key ^ linkvalue);
	}
    }
  return r;
}


/* --- LinkHelloReply --------------------------------------------- */

void LinkHelloReply(itemlink *r, char *msg)
{
  int ver, bottype;
  ulong key;
  char name[MINIBUFFER];

  snapshot;
  if (4 == StrScan(msg, "%d %d %s %lu", &ver, &bottype, name, &key)) {
    if (r)
      {
	/*
	 * The other end initiated the link. We have already
	 * sent our info (in the HELLO message in ctcp.c) and
	 * now get the info of the initiater in return
	 */
	r->bottype = bottype;
	r->protoversion = ver;
	if (name)
	  r->name = StrDuplicate(name);

	if ((r->linkvalue ^ key) == r->tmpvalue)
	  {
	    r->passed = TRUE;
#if 0
	    SendLinkBut(r, IBCP_AUTH, "%ld %d %d %d %s",
			ntohl(fromaddr.sin_addr.s_addr), ntohs(fromaddr.sin_port),
			ver, bottype, name);
	    r->new = FALSE;
#endif
	    SendLink(r, IBCP_ACCEPT, "");

#if 0
	    /* Tell newcomer about everybody else in net */
	    for (rr = First(linkHead); rr; rr = Next(rr))
	      {
		if (rr != r)
		  SendLink(r, IBCP_AUTH_LIST, "%ld %d %d %d %s",
			   ntohl(rr->addr.sin_addr.s_addr), ntohs(rr->addr.sin_port),
			   rr->protoversion, rr->bottype, rr->name);
	      }
	    SendLink(r, IBCP_AUTH_END, "1");
	    /* Logf(LINK, "Added %s from %s %d", r->name,
	       ntohl(r->addr.sin_addr.s_addr), ntons(r->addr.sin_port));
	       */
#endif
	  }
	else
	  {
	    r->passed = FALSE;
	    SendLink(r, IBCP_REJECT, "No authentication");
	    /* Link will be removed in LinkDequeue() */
	    /* Logf(LINK, "Rejected..."); */
	    return;
	  }
      }
  }
}


/* --- LinkAuth --------------------------------------------------- */

void LinkAuth(itemlink *r, char *msg)
{
  itemlink *rr;
  long addr;
  int port, ver, bottype;
  char name[MINIBUFFER];

  snapshot;
  if (5 == StrScan(msg, "%ld %d %d %d %s", &addr, &port, &ver, &bottype, name))
    {
      rr = AddLink(name, NULL, addr, port);
      if (rr)
	{
	  rr->bottype = bottype;
	  rr->protoversion = ver;
	  rr->passed = TRUE;
	  rr->new = FALSE;  /* Add immediately */
	}
    }
}

void LinkAuthList(itemlink *r, char *msg)
{
  itemlink *rr;
  long addr;
  int port, ver, bottype;
  char name[MINIBUFFER];
/*   char naddr[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:256.256.256.256"]; */

  snapshot;
  if (5 == StrScan(msg, "%ld %d %d %d %s", &addr, &port, &ver, &bottype, name))
    {
      rr = AddLink(name, NULL, addr, port);
      if (rr)
	{
	  rr->bottype = bottype;
	  rr->protoversion = ver;
	  rr->passed = TRUE;
	  rr->new = TRUE;
	}
    }
}

void LinkAuthEnd(itemlink *r, char *msg)
{
  itemlink *rr, *xrr;

  snapshot;
  if (*msg ^ '0') {
    /* Return our cluster */
    for (rr = First(linkHead); rr; rr = Next(rr)) {
      if ( !rr->new && (rr != r) )
	SendLink(r, IBCP_AUTH_LIST, "%ld %d %d %d %s",
		 ntohl(rr->addr.sin_addr.s_addr), ntohs(rr->addr.sin_port),
		 rr->protoversion, rr->bottype, rr->name);
    }
    SendLink(r, IBCP_AUTH_END, "0");
  }

  /* Commit newly received links */
  for (rr = First(linkHead); rr; rr = Next(rr)) {
    if (rr->new) {
      for (xrr = rr; xrr; xrr = Next(xrr)) {
	if ( !xrr->new )
	  SendLink(xrr, IBCP_AUTH, "%ld %d %d %d %s",
		   ntohl(rr->addr.sin_addr.s_addr), ntohs(rr->addr.sin_port),
		   rr->protoversion, rr->bottype, rr->name);
      }
      rr->new = FALSE;
    }
  }
}

/* --- LinkAccept ------------------------------------------------- */

void LinkAccept(itemlink *r)
{
  snapshot;
  SendLink(r, IBCP_SYNC, "%ld %ld", TIMEREQUEST, now);
}

/* --- LinkSync --------------------------------------------------- */

void LinkSync(itemlink *r, char *msg)
{
  long ct, then;

  snapshot;
  if (2 == StrScan(msg, "%ld %ld", &ct, &then)) {
    if (ct == TIMEREQUEST) {
      /* It is a request for common time */
      SendLink(r, IBCP_SYNC, "%ld %ld", now - commontime, then);
    }
    else {
      /* T_common_ref = T_now - (T_remote + T_round-trip/2) */
      long cnow = now;
      commontime = cnow - ct - (cnow - then) / 2;
      SendLink(r, IBCP_USERS_REQUEST, "%lu", LatestUserUpdate());
    }
  }
}

/* --- LinkNickname ----------------------------------------------- */

void LinkNickname(itemlink *r, char *msg)
{
  snapshot;
  if (r->name)
    StrFree(r->name);
  r->name = StrDuplicate(msg);
}

/* --- LinkMulticast ---------------------------------------------- */

void LinkMulticast(itemlink *r, char *msg)
{
  char *text;

  snapshot;
  text = StrIndex(msg, ' ');
  if (text) {
    /*
     * Multicasting from remote link is only forwarded
     * to local listening clients
     */
    MulticastLocalf(atoi(msg), "[%s]%s", r->name, text);
  }
}

/* --- LinkSendUsers ---------------------------------------------- */

#if 0
char *LevelType(int level)
{
  snapshot;
  if (level < LEVELRECOG)
    return "UNKNOWN";
  else if (level < LEVELCHANOP)
    return "RECOGNIZED";
  else if (level < LEVELTRUST)
    return "CHANOP";
  else if (level < LEVELEXPERT)
    return "TRUSTED";
  else if (level < LEVELBOT)
    return "EXPERT";
  else if (level < LEVELOWNER)
    return "MAINTAINER";
  else
    return "OWNER";
}

void LinkSendUser(itemlink *r, itemuser *u)
{
  char buffer[BIGGERBUFFER];
  itemlist *t;

  snapshot;
  StrFormatMax(buffer, sizeof(buffer), "%s", LevelType(u->level));
  if (u->flags.autoop)
    StrAppendMax(buffer, sizeof(buffer), ",AUTO");
  if (u->flags.banprotect)
    StrAppendMax(buffer, sizeof(buffer), ",PROTECT");
  /* u->flags.linkbot is local data and not shared */

  SendLink(r, IBCP_DATA_USER, "BEGIN: NICK:%s PASS:%s PRIV:%s CTIME:%ld MTIME:%ld",
           u->nick, u->passwd, buffer, u->created - commontime, u->modified - commontime);
  SendLink(r, IBCP_DATA_USER, "EMAIL:\"%s\" COMMENT:\"%s\"",
           u->realname, u->comment);

  buffer[0] = (char)0;
  for (t = First(u->domainhead); t; t = Next(t))
/* What is this? */
    StrFormatMax(buffer, sizeof(buffer), "%s%s,",
                 (NULL == StrIndex((char *)t->pointer, '!')) ? "*!" : "",
                 (char *)t->pointer);
  SendLink(r, IBCP_DATA_USER, "HOST:%s END:", buffer);
}

void LinkSendUserlist(itemlink *r)
{
  itemuser *u;

  snapshot;
  for (u = First(userHead); u; u = Next(u))
    LinkSendUser(r, u);
}

/* --- LinkUpdateUser --------------------------------------------- */

void LinkUpdateUser(itemlink *r, char *msg)
{
  static itemuser user;
  char buffer[BIGGERBUFFER];
  char strtype[MINIBUFFER];
  char strargs[BIGBUFFER];
  int index = 0;

  snapshot;
#ifdef HAVE_N_IN_SCANF
  StrScan(&msg[index], "%s %n", buffer, &index);
#else
  StrScan(&msg[index], "%s", buffer);
  index = StrLength(buffer);
#endif

  StrScan(buffer, "[^:]*1[:][^\n]", strtype, strargs);

  if (StrEqual(strtype, "BEGIN"))
    memset(&user, NIL, sizeof(user));
  else if (StrEqual(strtype, "NICK"))
    user.nick = StrDuplicate(strargs);
  else if (StrEqual(strtype, "PASS"))
    user.passwd = StrDuplicate(strargs);
/* Remember that time is relative to commontime */
  else if (StrEqual(strtype, "END")) {
    /* Insert user entry */
#if 0
    AddUser(&user, buffer);  /* <--- Must be UpdateUser() not AddUser() */
    StrFormatMax(buffer, sizeof(buffer), "%s 0 %d %d 0", user.passwd, NULL,
                 user.modified, user.created);
    AddNewData(&user, buffer);
    AddDomain(&user, whatever);
#endif
    if (user.nick)
      StrFree(user.nick);
    if (user.passwd)
      StrFree(user.passwd);
  }
}
#endif

/* --- LinkUsers -------------------------------------------------- */

char *SetUserFlags(uFlags *);

void LinkUsersRequest(itemlink *r, char *msg)
{
  char buffer[3*BIGBUFFER];
  itemuser *u;
  itemlist *l;
  time_t t;

  snapshot;
  if (1 == StrScan(msg, "%lu", &t) ) {
    printf("update since %lu\n", t);

    /* Ship all entries that are newer than 't' */
    for (u = First(userHead); u; u = Next(u)) {
      if (u->modified > t - 400) {  /* !!! remove - 400 !!! */

        /* Should send the same data as UserSave() */
        SendLink(r, IBCP_USERS_LIST, "%s", WriteUserHead(buffer, sizeof(buffer), u));
	for (l = First(u->domainhead); l; l = Next(l))
	  SendLink(r, IBCP_USERS_LIST, "!%s\n", (char *)l->pointer);
      }
    }
    SendLink(r, IBCP_USERS_END, "");
  }
}

void LinkUsersUpdate(itemlink *r, char *msg)
{
  snapshot;
  printf("Should do the update here\n");
}

void LinkUsersList(itemlink *r, char *msg)
{
  snapshot;
  LinkUsersUpdate(r, msg);
  /* Forward to rest of local cluster */
  SendLinkBut(r, IBCP_USERS_UPDATE, msg);
}

void LinkUsersEnd(itemlink *r, char *msg)
{
  snapshot;
/*  SendLink(r, IBCP_AUTH_REQUEST, "");*/
  printf("initiate AUTH... when installed\n");
}


/* --- LinkRemote ------------------------------------------------- */

void LinkRemoteCmd(itemlink *r, char *msg)
{
  char name[MINIBUFFER];
  char cmd[MIDBUFFER];
  char buffer[BIGBUFFER] = "";

  snapshot;
  if (2 <= StrScan(msg, "%s %s %[^\n]", name, cmd, buffer)) {
    printf("remote '%s %s' from %s", cmd, buffer[0] ? buffer : "", name);
  }
}

void LinkRemoteAnswer(itemlink *r, char *msg)
{
  extern bool chat;
  extern itemclient *client;
  char name[MINIBUFFER];
  char buffer[BIGBUFFER];
  bool chit;
  itemclient *klient;

  snapshot;
  if (2 == StrScan(msg, "%"MINIBUFFERTXT"s %"BIGBUFFERTXT"[^\n]", name, buffer)) {
    printf("remote answer '%s' to %s", buffer, name);
    chit = chat;
    klient = client;
    chat = (client = FindClientByNick(name)) ? TRUE : FALSE;
    Send(name, buffer);
    chat = chit;
    client = klient;
  }
}

/* --- LinkParse -------------------------------------------------- */

#define IDBACKTRACK 1000u

bool IsDuplicate(unsigned long this, unsigned long last)
{
  snapshot;

  /* This strange looking code works because we are dealing
   * with unsigned arithmetic. Hopefully the compiler doesn't
   * choose to optimize it away.
   */
  if (this <= last)
    return (this - IDBACKTRACK <= last - IDBACKTRACK);
  return FALSE;
}

void LinkParse(char *line)
{
  itemlink *r;
  char buffer[BIGBUFFER];
  char out[BIGBUFFER];
  char *msg;
  int type, rc;
  unsigned long msgid;

  snapshot;
#if 1

  if (2 <= StrScan(line, "%d %d %[^\n]", &msgid, &type, buffer)) {
    msg = buffer;
    r = FindLink(fromaddr.sin_addr.s_addr, fromaddr.sin_port);
    if (NULL == r) {
      if (type == IBCP_HELLO) {
        /* We will only accept a HELLO from unknown destinations. */
        /* And only if it contains data */
        if (StrLength(msg) > 0) {
          r = LinkHello(msg);
          if (r) {
            /* Acknowledge directly */
            StrFormatMax(out, sizeof(out), "%d %d\n", msgid, IBCP_ACK);
            WriteLinkRaw(r, out);
            r->yourmsgid = msgid;
          }
        }
      }

      if (NULL == r)
        return;
    }

    /* r is always set at this point */

    if (IBCP_ACK == type) {
      /* We got an acknowledgement. Dequeue message and send next */
      LinkDequeue(r, msgid);
      /* Don't acknowledge acknowledgements... bad karma */
      return;
    }

    if (IsDuplicate(msgid, r->yourmsgid))
      /* Ignore duplicates */
      return;

    rtt_stop(r);

    if (StrLength(msg) > 0) {
      /* If zero it's only an ack */

#if 1
#ifdef UNRELIABLE
      if (Rnd() > 0.2) /* Don't acknowledge */
#endif
	    StrFormatMax(out, sizeof(out), "%d %d\n", msgid, IBCP_ACK);
	  WriteLinkRaw(r, out); /* &fromaddr */
	  /* bool wantpiggy; ulong piggymsg; */
#endif

	  /*
	   * Check if from authenticated bot. There are two ways to become
	   * authenticated: either by authentication at connect-time, or
	   * through validation by another bot.
	   */

	  if (r->passed)
	    {
	      switch (type)
		{
		case IBCP_QUIT:  /* Link that decided to quit */
		  RemoveLink(r);
		  r = NULL;
		  break;
		case IBCP_ACCEPT:
		  LinkAccept(r);
		  break;
		case IBCP_REJECT:  /* I was rejected by r */
		  RemoveLink(r);
		  r = NULL;
		  break;
		case IBCP_SYNC:  /* Establish or request common time */
		  LinkSync(r, msg);
		  break;
		case IBCP_AUTH:
		  LinkAuth(r, msg);
		  break;
		case IBCP_AUTH_LIST:
		  /* Not broadcasted until AUTH_END is received */
		  LinkAuthList(r, msg);
		  break;
		case IBCP_AUTH_END:
		  LinkAuthEnd(r, msg);
		  break;

		  /* Handle userlist */
		case IBCP_USERS_REQUEST:
		  LinkUsersRequest(r, msg);
		  break;
		case IBCP_USERS_UPDATE:
		  LinkUsersUpdate(r, msg);
		  break;
		case IBCP_USERS_LIST:
		  LinkUsersList(r, msg);
		  break;
		case IBCP_USERS_END:
		  LinkUsersEnd(r, msg);
		  break;

		case IBCP_REMOTE_CMD:
		  LinkRemoteCmd(r, msg);
		  break;
		case IBCP_REMOTE_ANSWER:
		  LinkRemoteAnswer(r, msg);
		  break;

		case IBCP_NICKNAME:
		  LinkNickname(r, msg);
		  break;

		case IBCP_MULTICAST:
		  LinkMulticast(r, msg);
		  break;

		default:
		  break;
		}
	    }
	  else if (type == IBCP_HELLO_REPLY)
	    {
	      /*
	       * HELLO_REPLY is part of the authentication scheme and
	       * therefor we will only accept it if the link isn't
	       * authenticated yet.
	       */
	      LinkHelloReply(r, msg);
	    }
	  r->yourmsgid = msgid;
	}
      r->lasttime = now;
    }

#else

  unsigned long msgid;
  int type;

  /* Skip escape code if present */
  if (*line == IBCP_ESC) line++;

  msg = buffer;
  if ( 2 <= StrScan(line, "%d %lu %[^\n]", &type, &msgid, msg) ) {

    r = FindLink(fromaddr.sin_addr.s_addr, fromaddr.sin_port);

    if (type == IBCP_ACK) {
      /* We got an acknowledgement. Dequeue message and send next */
      if (r)
        LinkDequeue(r, msgid);
      /* Don't acknowledge acknowledgements... bad karma */
      return;
    }

    /* Ignore duplicates */
    if (r && IsDuplicate(msgid, r->yourmsgid)) {
      printf("Ignored\n");
      return;
    }

#ifdef UNRELIABLE
    if (Rnd() > 0.2) /* Don't acknowledge */
#endif
      WriteLink(&fromaddr, IBCP_ACK, "");

    if (type == IBCP_HELLO) {
      LinkHello(r, msg);
      return;
    } else if (type == IBCP_HELLO_REPLY) {
      LinkHelloReply(r, msg);
      return;
    }

    if (r) {

      rtt_stop(r);

      /*
       * Check if from authenticated bot. There are two ways to become
       * authenticated: either by authentication at connect-time, or
       * through validation by another bot
       */

      /*if (r->passed)*/ {
        switch (type) {

        case IBCP_QUIT:  /* Link that decided to quit */
          RemoveLink(r);
          break;

        case IBCP_ACCEPT:
          LinkAccept(r);
          break;
        case IBCP_REJECT:  /* I was rejected by r */
          RemoveLink(r);
          break;

        case IBCP_AUTH:
          LinkAuth(r, msg);
          break;
        case IBCP_AUTH_LIST:
	  /* Not passed until AUTH_END is received */
          LinkAuthList(r, msg);
          break;
	case IBCP_AUTH_END:
	  LinkAuthEnd(r, msg);
	  break;

        case IBCP_SYNC:  /* Establish or request common time */
          LinkSync(r, msg);
          break;

	  /* Handle userlist */
        case IBCP_USERS_REQUEST:
          LinkUsersRequest(r, msg);
          break;
        case IBCP_USERS_UPDATE:
          LinkUsersUpdate(r, msg);
          break;
        case IBCP_USERS_LIST:
          LinkUsersList(r, msg);
          break;
        case IBCP_USERS_END:
          LinkUsersEnd(r, msg);
          break;

        case IBCP_REMOTE_CMD:
          LinkRemoteCmd(r, msg);
          break;
        case IBCP_REMOTE_ANSWER:
          LinkRemoteAnswer(r, msg);
          break;

        case IBCP_NICKNAME:
          LinkNickname(r, msg);
          break;

        case IBCP_MULTICAST:
          LinkMulticast(r, msg);
          break;

        default:
          break;
        }
      }
      r->yourmsgid = msgid;
      r->lasttime = now;
    }
  }

#endif
}


syntax highlighted by Code2HTML, v. 0.9.1