#ifdef RCS
static char rcsid[]="$Id: seen.c,v 1.1.1.1 2000/11/13 02:42:47 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/seen.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 2000/11/13 02:42:47 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "function.h"
#include "user.h"
#include "transfer.h"
#include "seen.h"

/* --- Global ----------------------------------------------------- */

extern time_t now;

extern char seenfile[];
extern int seenmonths;
extern itemident *current;

int numSeenNicks = 0; /* This is not actually very truthful */
int numSeenHosts = 0;

struct Seenstruct SeenHead = {
  NULL, NULL, NULL,
  NULL, NULL,
  0, 0,
  NULL, NULL,
  0
};

itemseen *seenHead = &SeenHead;

itemaux **hashnick = NULL;
itemseen **hashsite = NULL;


static long changedseenitem = 0;

/* --- SeenReport ------------------------------------------------- */

static void SeenReport(char *from, char *nick, time_t departure, itemseen *ps)
{
  snapshot;
  switch (ps->type) {

    case SEENLEAVE:
      Sendf(from, GetText(msg_seenleave),
            nick, ps->host, TimeAgo(departure),
            ps->msg ? " (" : "", ps->msg ? ps->msg : "", ps->msg ? ")" : "");
      break;

    case SEENQUIT:
      Sendf(from, GetText(msg_seenquit),
            nick, ps->host, TimeAgo(departure), ps->msg ? ps->msg : "");
      break;

    case SEENKICK:
      Sendf(from, GetText(msg_seenkick), nick, ps->host,
            TimeAgo(departure), ps->by ? ps->by : "", ps->msg ? ps->msg : "");
      break;

    case SEENLEFT:
      Sendf(from, GetText(msg_seenleft), nick, ps->host, TimeAgo(departure));
      break;

    case SEENQUITED:
    case SEENERROR:
      Sendf(from, GetText(msg_seenquited), nick, ps->host, TimeAgo(departure));
      break;

    case SEENKICKED:
      Sendf(from, GetText(msg_seenkicked),
            nick, ps->host, TimeAgo(departure), ps->by ? ps->by : "");
      break;

    case SEENCHANGE:
      Sendf(from, GetText(msg_seenchange), nick, ps->host, TimeAgo(departure));

    default:
      break;

  }
}

/* --- SeenMostNick ----------------------------------------------- */

itemaux *SeenMostNick(char *nick)
{
  long max = 0;
  itemaux *p, *pp = NULL;

  snapshot;
  for (p = hashnick[HashU(nick)]; p; p = p->next) {
    if (IRCEqual(p->nick, nick)) {
      if (p->count >= max) {
        max = p->count;
        pp = p;
      }
    }
  }
  return pp;
}

/* --- SeenRecentNick --------------------------------------------- */

itemaux *SeenRecentNick(char *nick)
{
  time_t recent = 0;
  itemaux *p, *pp = NULL;

  snapshot;
  for (p = hashnick[HashU(nick)]; p; p = p->next) {
    if (IRCEqual(p->nick, nick)) {
      if (p->departure > recent) {
        recent = p->departure;
        pp = p;
      }
    }
  }
  return pp;
}

/* --- SeenRecentRootNick ----------------------------------------- */

itemaux *SeenRecentRootNick(char *nick)
{
  time_t recent = 0;
  itemaux *p, *pp = NULL;

  snapshot;
  for (p = hashnick[HashU(nick)]; p; p = p->next) {
    if (IRCEqual(p->nick, nick)) {
      if (p->root->departure > recent) {
        recent = p->root->departure;
        pp = p;
      }
    }
  }
  return pp;
}

/* --- SeenNick --------------------------------------------------- */

void SeenNick(char *from, char *nick, char opt)
{
  extern char nickname[];
  bool hit = FALSE;
  itemaux *pa = NULL;

  snapshot;
  if (0 == opt) {
    if (FindNick(nick)) {
      if (IRCEqual(nickname, nick)) {
        Send(from, GetText(msg_yes_i_am_here));
        return;
      }
      else if (IRCEqual(current->nick, nick)) {
        Send(from, GetText(msg_yes_i_see_you));
        return;
      }
      else {
        Send(from, GetText(msg_seen_already_here));
        return;
      }
    }
    else if (FindSplitNick(nick)) {
      Sendf(from, GetText(msg_seensplit), nick);
      return;
    }
  }

  switch (opt) {

    case 'A': /* All */
      for (pa = hashnick[HashU(nick)]; pa; pa = pa->next) {
        if (IRCEqual(pa->nick, nick)) {
          hit = TRUE;
          SeenReport(from, nick, pa->root->departure, pa->root);
        }
      }
      break;

    case 'M': /* Most often used */
      pa = SeenMostNick(nick);
      break;

    default:  /* Most recent */
      /*
       * gr8ron January 24th, 1999:
       * After a little discussion with Bagder we came to the conclusion
       * that it was time to drop checks for X->root->departure here unless
       * explicitly requested. SeenRecentNick() uses X->departure now.
       * SeenRecentRootNick() uses X->root->departure instead.
       * Also, SeenAddNick() sets the departure time for nick to root's
       * departure time when there is none supplied or when it's 0.
       */
      pa = SeenRecentNick(nick);
      break;

  }

  if (pa)
    SeenReport(from, nick, pa->root->departure, pa->root);
  else if (!hit)
    Sendf(from, GetText(msg_never_seen), nick);
}

/* --- SeenMatch -------------------------------------------------- */

#define SEEN_MAX_DISPLAYED 15

void SeenMatch(char *from, char *pattern)
{
  char nickpattern[NICKLEN+1] = "";
  char *hostpattern;
  int count = 0;
  ulong nicksig, hostsig, max;
  itemseen *ps;
  itemaux *pa, *pam;

  snapshot;
  hostpattern = StrIndex(pattern, '!');
  if (hostpattern) {
    StrScan(pattern, "%"NICKLENTXT"[^!]", nickpattern);
    nicksig = HashSignatureU(nickpattern);
    hostpattern++;
  }
  else {
    hostpattern = pattern;
  }

  hostsig = HashSignatureU(hostpattern);

  for (ps = seenHead->link; ps; ps = ps->link) {
    if (((ps->signature & hostsig) == hostsig) && Match(ps->host, hostpattern)) {
      if (nickpattern[0]) {
        for (pa = ps->first; pa; pa = pa->link) {
          if (((pa->signature & nicksig) == nicksig) && Match(pa->nick, nickpattern)) {
            SeenReport(from, pa->nick, pa->departure, ps);
            if (SEEN_MAX_DISPLAYED <= ++count)
              break;
          }
        }
      }
      else {
        max = 0;
        for (pa = pam = ps->first; pa; pa = pa->link) {
          if (pa->count > max) {
            pam = pa;
            max = pa->count;
          }
        }

        SeenReport(from, pam->nick, pam->root->departure, ps);
        count++;
      }

      if (SEEN_MAX_DISPLAYED <= count) {
        Send(from, GetText(msg_seen_bad_pattern));
        break;
      }
    }
  }

  if (0 == count)
    Sendf(from, GetText(msg_never_seen_match), pattern);
}

/* --- Seen ------------------------------------------------------- */

void Seen(char *from, char *line)
{
  extern char *errfrom;
  extern bool public;
  extern long levels[];
  extern itemopt option;
  char *who;
  char opt = (char)0;
  bool illegalpub = FALSE;

  snapshot;
  if (GetOption(line)) {
    switch (toupper(option.copt)) {

      case 'A': /* All */
        illegalpub = TRUE;
        break;

      case 'M': /* Most used */
      case 'R': /* Recent */
        break;

      default:
        break;

    }
    opt = toupper(option.copt);
    line = option.newpos;
  }

  who = StrTokenize(line, " ");
  if (who && *who) {
    if (public && illegalpub) {
      Send(errfrom, GetText(msg_no_public_options));
    }
    else if (public && StrIndex(line, '*')) {
      Send(errfrom, GetText(msg_no_public_wildcards));
    }
    else {
      if (strpbrk(who, "@*")) {
        if (current->level >= LEVELPUB) {
          SeenMatch(from, who);
        }
        else {
          /* Pretend to be stupid */
          Sendf(errfrom, GetText(msg_never_seen), who);
        }
      }
      else
        SeenNick(from, who, opt);
    }
  }
  else
    CmdSyntax(errfrom, "SEEN");
}

/* --- FreeSeen --------------------------------------------------- */

void FreeSeen(itemseen *ps)
{
  snapshot;
  if (ps) {
    if (ps->usite)
      StrFree(ps->usite);
    if (ps->host)
      StrFree(ps->host);
    if (ps->by)
      StrFree(ps->by);
    if (ps->msg)
      StrFree(ps->msg);
    free(ps);
  }
}

/* --- SeenNewSeen ------------------------------------------------ */

itemseen *SeenNewSeen(char *usite, char *host)
{
  itemseen *ps;

  snapshot;
  /* Allocate a structure for the new site */
  ps = NewEntry(itemseen);
  if (ps) {
    ps->usite = StrDuplicate(usite);
    if (ps->usite) {
      ps->host = StrDuplicate(host);
      if (ps->host) {
        ps->signature = HashSignatureU(ps->host);
        return ps;
      }
      StrFree(ps->usite);
    }
    free(ps);
  }
  return NULL;
}

/* --- SeenLinkSeen ----------------------------------------------- */

static void SeenLinkSeen(itemseen *ps, unsigned int x)
{
  snapshot;
  ps->link       = seenHead->link;
  seenHead->link = ps;
  ps->next       = hashsite[x];
  hashsite[x]    = ps;
  numSeenHosts++;
}

/* --- SeenNewAux ------------------------------------------------- */

itemaux *SeenNewAux(char *nick)
{
  itemaux *pa;

  snapshot;
  /* Allocate a structure for the new aux */
  pa = NewEntry(itemaux);
  if (pa) {
    pa->nick = StrDuplicate(nick);
    if (pa->nick) {
      pa->signature = HashSignatureU(pa->nick);
      return pa;
    }
    free(pa);
  }
  return NULL;
}

/* --- SeenLinkAux ------------------------------------------------ */

static void SeenLinkAux(itemseen *ps, itemaux *pa, unsigned int x)
{
  snapshot;
  pa->link    = ps->first;
  ps->first   = pa;
  pa->next    = hashnick[x];
  hashnick[x] = pa;
  pa->root    = ps;
  numSeenNicks++;
}

/* --- SeenInsert ------------------------------------------------- */

void SeenInsert(itemguest *g, int type, char *by, char *msg)
{
  unsigned int xsite;
  itemseen *ps;
  itemaux *pa = NULL;

  snapshot;

  /* Check whether usite already exists */
  xsite = Hash(g->ident->userdomain);
  for (ps = hashsite[xsite]; ps; ps = ps->next) {
    if (StrEqual(ps->usite, g->ident->userdomain))
      break;
  }

  /* If the usite exists */
  if (ps) {
    /* Check whether nick has been used from this usite before */
    for (pa = ps->first; pa; pa = pa->link) {
      if (IRCEqual(pa->nick, g->ident->nick))
        break;
    }

    /* Keep the last seen host in ps->host */
    if (!StrEqualCase(ps->host, g->ident->host)) {
      char *pointer;

      pointer = StrDuplicate(g->ident->host);
      if (pointer) {
        StrFree(ps->host);
        ps->host = pointer;
        ps->signature = HashSignatureU(ps->host);
      }
    }
  }

  if (NULL == pa) {
    /* We need a new aux structure */
    pa = SeenNewAux(g->ident->nick);
    if (pa) {
      if (NULL == ps) {
        /* We need a new usite */
        ps = SeenNewSeen(g->ident->userdomain, g->ident->host);
        if (ps)
          SeenLinkSeen(ps, xsite);
      }

      if (ps) {
        SeenLinkAux(ps, pa, HashU(g->ident->nick));
      }
      else {
        StrFree(pa->nick);
        free(pa);
        pa = NULL;
      }
    }
  }

  /* Finally update the new seen information */
  if (ps) {
    if (pa) {
      pa->count++;
      pa->departure = now;
    }

    ps->departure = now;
    ps->type = type;
    if (ps->by)
      StrFree(ps->by);
    ps->by = (by && by[0]) ? StrDuplicate(by) : NULL;
    if (ps->msg)
      StrFree(ps->msg);
    ps->msg = (msg && msg[0]) ? StrDuplicate(msg) : NULL;

    changedseenitem++;
  }
}

/* --- SeenInsertAll ---------------------------------------------- */

void SeenInsertAll(int type, char *by, char *msg)
{
  extern itemguest *guestHead;
  itemguest *g;

  snapshot;
  for (g = First(guestHead); g; g = Next(g)) {
    SeenInsert(g, type, by, msg);
  }
}

/* --- SeenAdd ---------------------------------------------------- */

itemseen *SeenAdd(char *line)
{
  char host[MIDBUFFER];
  char by[MIDBUFFER]  = "";
  char msg[BIGBUFFER] = "";
  char *usite;
  ulong departure;
  int type;
  itemseen *ps = NULL;

  snapshot;
  if (3 <= StrScan(line, "%"MIDBUFFERTXT"s %lu %d %"MIDBUFFERTXT"s :%"BIGBUFFERTXT"[^\n]",
                   host, &departure, &type, by, msg)) {

    /* Simple sanity check, a host name must include the @-letter */
    if (NULL == StrIndex(host, '@'))
      return NULL;

    usite = Userdomain(host);
    if (usite) {
      ps = SeenNewSeen(usite, host);
      if (ps) {
        ps->departure = departure;
        ps->type = type;
        ps->by = (by[0] && ('.' != by[0])) ? StrDuplicate(by) : NULL;
        ps->msg = msg[0] ? StrDuplicate(msg) : NULL;
      }
      StrFree(usite);
    }
  }
  return ps;
}

/* --- SeenAddNick ------------------------------------------------ */

void SeenAddNick(itemseen *ps, char *line)
{
  char nick[NICKLEN+1], tmp[NICKLEN+1];
  time_t departure = 0;
  ulong count = 1;
  itemaux *pa;

  snapshot;
  if (1 <= StrScan(line, "%"NICKLENTXT"s %d %d", nick, &count, &departure)) {
    /*
     * Only add nicknames that use legal characters, as other names are
     * likely to be a proof of a broken seen file or similar
     */
    if (StrScan(nick, "%[0-9A-~-]", tmp) && StrEqualCase(nick, tmp)) {
      pa = SeenNewAux(nick);
      if (pa) {
        /*
         * We can now link the allocated seen structure to seenlist
         * if it has not been linked before.
         */
        if (NULL == ps->first)
          SeenLinkSeen(ps, Hash(ps->usite));

        SeenLinkAux(ps, pa, HashU(pa->nick));
        pa->count = count; /* Might be 1 if field wasn't set */
        /*
         * pa->departure is now set to departure time of the root if the
         * corresponding field isn't set or when its set to 0.
         */
        pa->departure = departure ? departure : ps->departure;
      }
    }
  }
}

/* --- SeenInit --------------------------------------------------- */

bool SeenInit(void)
{
  char line[MAXLINE];
  itemseen *ps = NULL;
  FILE *f;

  snapshot;
  hashnick = (itemaux  **)calloc(HASHSIZE, sizeof(itemaux *));
  hashsite = (itemseen **)calloc(HASHSIZE, sizeof(itemseen *));

  if (hashnick && hashsite && seenfile[0]) {
    f = fopen(seenfile, "r");
    if (f) {
      while (fgets(line, sizeof(line), f)) {
        switch (line[0]) {

          case ' ':
            if (ps)
              SeenAddNick(ps, &line[1]);
            break;

          default:
            /* We have to free sites that have no nicks connected */
            if (ps && (NULL == ps->first))
              FreeSeen(ps);
            ps = SeenAdd(line);
            break;

        }
      }
      fclose(f);

      if (ps && (NULL == ps->first))
        FreeSeen(ps);

      return TRUE;
    }
  }

  return FALSE;
}

/* --- SeenSave --------------------------------------------------- */

void SeenSave(void)
{
  char tempfile[MIDBUFFER];
  bool ok = TRUE;
  itemseen *ps;
  itemaux *pa;
  FILE *f;

  snapshot;
  if ((char)0 == seenfile[0])
    return;

  if (0 == changedseenitem) {
    Logf(LOG, "No seen item changed, no save performed.");
    return;
  }

  Logf(LOG, "Saving seen data \"%s\"", seenfile);

  StrFormatMax(tempfile, sizeof(tempfile), "%s~", seenfile);

  f = fopen(tempfile, "w");
  if (f) {
    for (ps = seenHead->link; ps && ok; ps = ps->link) {
      if ((ps->departure + seenmonths*SECINMONTH) >= now) {
        /*
         * Only save if the user departed within 'seenmonths' months ago!
         */
        if (0 > fprintf(f, "%s %ld %d %s :%s\n",
                        ps->host, ps->departure, ps->type,
                        ps->by ? ps->by : ".",
                        ps->msg ? ps->msg : "")) {
          ok = FALSE;
          break;
        }
        for (pa = ps->first; pa; pa = pa->link) {
          if ((pa->departure + seenmonths*SECINMONTH) >= now) {
            /*
             * Here too. We only save the nicks this person has used within
             * the 'seenmonths' period. We shouldn't let old sins live on...
             */
            if (0 > fprintf(f, " %s %ld %ld\n",
                            pa->nick, pa->count, pa->departure)) {
              ok = FALSE;
              break;
            }
          }
        }
      }
    }
    fclose(f);

    if (ok) {
      rename(tempfile, seenfile);
      changedseenitem = 0;
    }
  }
}

/* --- SeenCleanup ------------------------------------------------ */

void SeenCleanup(void)
{
  itemseen *ps, *tps;
  itemaux *pa, *tpa;

  snapshot;
  SeenSave();

  /* Remember to free all allocated memory in structures */
  for (ps = seenHead->link; ps; ps = tps) {
    tps = ps->link;
    for (pa = ps->first; pa; pa = tpa) {
      tpa = pa->link;
      if (pa->nick)
        StrFree(pa->nick);
      free(pa);
    }
    FreeSeen(ps);
  }

  if (hashnick)
    free(hashnick);
  if (hashsite)
    free(hashsite);
}


syntax highlighted by Code2HTML, v. 0.9.1