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

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "function.h"
#include "user.h"
#include "tell.h"
#include "seen.h"
#include "transfer.h"
#include "flood.h"
#include "ourcrypt.h"
#include "link.h"
#include "bans.h"

#ifdef HAVE_LIBFPL
# include "fplrun.h"
#endif

#include <sys/socket.h>  /* AF_INET */


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

extern time_t now;

extern char nickname[];
extern char defaultpasswd[];
extern bool botop;
extern bool welcome;
extern bool warnmode;
extern bool mute;
extern bool nickflood;
extern bool masterflood;
extern int oplevel;
extern long levels[];
extern time_t mastertime;

char myaddr[MIDBUFFER];
char botmatch[MIDBUFFER];

int numUsers            = 0;
int numGuests           = 0;
int numSplitGuests      = 0;
int numClients          = 0;
int totalnumberofsplits = 0;

time_t lastsplittime    = 0;

itemuser *userHead     = NULL;
itemguest *guestHead   = NULL;
itemsplit *splitHead   = NULL;
itemclient *clientHead = NULL;
itemident *identHead   = NULL;
itemlink *linkHead     = NULL;


/* --- GetUserFlags ----------------------------------------------- */

static void GetUserFlags(uFlags *flags, char *pointer)
{
  if (pointer && ('-' == *pointer)) {
    for (pointer++; *pointer; pointer++) {
      switch (*pointer) {

        case FLAG_AUTOOP:
          flags->autoop = TRUE;
          break;

        case FLAG_BANPROTECT:
          flags->banprotect = TRUE;
          break;

        case FLAG_LINKBOT:
          flags->linkbot = TRUE;
          break;

        case FLAG_REGULARBOT:
          flags->regularbot = TRUE;
          break;

        case FLAG_DELETE:
          flags->delete = TRUE;
          break;

        case FLAG_STEALTH:
          flags->stealth = TRUE;
          break;

      }
    }
  }
}

/* --- AddUser ---------------------------------------------------- */

itemuser *AddUser(char *line)
{
  char nick[NICKLEN+1], flags[MINIBUFFER], realname[MIDBUFFER];
  char compare[NICKLEN+1];
  char *pointer;
  itemuser *u;

  snapshot;
  if (3 == StrScan(line, "%"NICKLENTXT"s %"MINIBUFFERTXT"s %"MIDBUFFERTXT"[^\n]",
                   nick, flags, realname)) {
    /*
     * Only add nicknames that use legal characters, as other names are
     * likely to be a proof of a broken userfile or similar
     */
    if ((1 == StrScan(nick, "%[0-9A-~-]", compare)) &&
        StrEqualCase(nick, compare) &&
        ('A' <= nick[0]) && ('~' >= nick[0])) {

      u = NewEntry(itemuser);
      if (u) {
        InsertLast(userHead, u);
        u->domainhead = NewList(itemlist);
        u->nick = StrDuplicate(nick);
        u->level = StrToLong(flags, &pointer, 10);
        GetUserFlags(&u->flags, pointer);
        u->realname = StrDuplicate(realname);
        numUsers++;
        return u;
      }
    }
  }
  return NULL;
}

void AddNewData(itemuser *u, char *line)
{
  char password[MINIBUFFER], lang[MINIBUFFER] = "";
  time_t passwdtime = 0;
  time_t modified = 0;
  time_t created = 0;
  long checksum = 0;
  long totaljoins = 0;
  long newsid = 0;

  snapshot;
  if (1 <= StrScan(line, "%"MINIBUFFERTXT"s %d %d %d %d %d %"MINIBUFFERTXT"s %d",
                   password, &passwdtime, &modified, &created, &checksum,
                   &totaljoins, lang, &newsid)) {
    if (u->passwd)
      StrFree(u->passwd);
    u->passwd = StrDuplicate(password);
    u->passwdtime = passwdtime;
    u->created = created;
    u->modified = modified;
    u->checksum = checksum;
    /*
     * New in 4.1.5. (Added to once and for all stop the complaints on
     * the join-count system that currently follows the hostpattern rather
     * than the user.) Do note that this data will get changed on each join
     * without that should count as a 'changed user' and thus the checksum
     * routine MUST NOT include this when calculating.
     * - Bagder
     */
    u->totaljoins = totaljoins;
    u->language = lang[0] ? LanguageNum(lang) : defaultlang;
    u->newsid = newsid;
  }
}

void AddComment(itemuser *u, char *line)
{
  char comment[MIDBUFFER];

  snapshot;
  if (1 == StrScan(line, "%"MIDBUFFERTXT"[^\n]", comment)) {
    if (u->comment)
      StrFree(u->comment);
    u->comment = StrDuplicate(comment);
  }
}

void AddMoreInfo(itemuser *u, char *line)
{
  char buffer[MIDBUFFER];
  time_t when;
  int flags;

  snapshot;
  if (3 == StrScan(line, "%"MIDBUFFERTXT"s %d %d", buffer, &when, &flags)) {
    if (u->stat_bywhom)
      StrFree(u->stat_bywhom);
    u->stat_bywhom = StrDuplicate(buffer);
    u->stat_lastchange = when;
    u->stat_lastdone = flags;
  }
}

void AddDomain(itemuser *u, char *line)
{
  char domain[MIDBUFFER];
  itemlist *l;

  snapshot;
  if (1 == StrScan(line, "%"MIDBUFFERTXT"[^ \n]", domain)) {
    l = NewEntry(itemlist);
    if (l) {
      l->pointer = StrDuplicate(domain);
      if (l->pointer) {
        InsertLast(u->domainhead, l);
      }
      else {
        free(l);
      }
    }
  }
}

void AddLabel(itemuser *u, char *line)
{
#ifdef HAVE_LIBFPL
  char labelname[BIGBUFFER], labelcontents[BIGBUFFER];

  snapshot;
  if (2 == StrScan(line, "%"BIGBUFFERTXT"s :%"BIGBUFFERTXT"[^\n]",
                   labelname, labelcontents)) {
    UserlabelSet(labelname, labelcontents, LAB_STRING,
                 (itemuserlabel **)&u->label);
  }
#endif
}

static long chksumstr(char *str)
{
  long checksum = 0;

  if (str) {
    while (*str) {
      checksum <<= 2;
      checksum += *str;
      str++;
    }
    return checksum;
  }
  return 0xC0CAC01A;
}

static long chksumint(int val)
{
  return (val + 0xDEAD);
}

long ChecksumUser(itemuser *u)
{
  long checksum = 0;
  itemlist *l;

  checksum = chksumstr(u->nick);
  checksum += chksumint(u->level);
  checksum += chksumstr(VerboseFlags(&u->flags));
  checksum += chksumstr(u->realname);
  checksum += chksumstr(u->passwd);
  checksum += chksumstr(u->comment);
  checksum += chksumint(u->passwdtime);
  checksum += chksumint(u->modified);
  checksum += chksumint(u->created);

  if (u->stat_bywhom) {
    checksum += chksumstr(u->stat_bywhom);
    checksum += chksumint(u->stat_lastchange);
    checksum += chksumint(u->stat_lastdone);
  }

  for (l = First(u->domainhead); l; l = Next(l)) {
    checksum += chksumstr((char *)l->pointer);
  }

  return checksum;
}

/* --- UserLoad --------------------------------------------------- */

bool UserLoad(char *filename)
{
  char line[MAXLINE];
  bool changed = FALSE;
  long checksum;
  itemuser *u = NULL;
  FILE *f;

  snapshot;
  if ((NULL == userHead) || (NULL == filename) || (NIL == filename[0]))
    return FALSE;

  f = fopen(filename, "r");
  if (f) {
    while (fgets(line, sizeof(line), f)) {
      switch (line[0]) {

        case '#':
        case '\n':
          break;

        case '-': /* password */
          if (u)
            AddNewData(u, &line[1]);
          break;

        case '?': /* comment */
          if (u)
            AddComment(u, &line[1]);
          break;

        case '*': /* change info */
          if (u)
            AddMoreInfo(u, &line[1]);
          break;

        case '!': /* account pattern */
          if (u)
            AddDomain(u, &line[1]);
          break;

        case '$': /* fpl-added labels in the format <label> :<value> */
          if (u)
            AddLabel(u, &line[1]);
          break;

        default: /* new user */
          u = AddUser(line);
          break;

      }
    }
    fclose(f);

    /*
     * Fill in default password if none is supplied and
     * check checksums to track manually modified users
     */
    for (u = First(userHead); u; u = Next(u)) {
      checksum = ChecksumUser(u);
      if (u->checksum && (u->checksum != checksum))
        changed = TRUE;
      else
        changed = FALSE;

      if (NULL == u->passwd) {
        u->passwd = MakePassword(defaultpasswd);
        u->passwdtime = now;
        changed = TRUE;
      }

      if (changed)
        u->modified = now;
    }

    return TRUE;
  }
  return FALSE;
}

/* --- UserReload ------------------------------------------------- */

void FreeUser(void *v)
{
  itemuser *u;

  u = (itemuser *)v;
  if (u) {
    if (u->nick)
      StrFree(u->nick);
    if (u->realname)
      StrFree(u->realname);
    if (u->passwd)
      StrFree(u->passwd);
    if (u->comment)
      StrFree(u->comment);
    if (u->stat_bywhom)
      StrFree(u->stat_bywhom);
    DeleteList(u->domainhead, FreeList);
#ifdef HAVE_LIBFPL
    DeleteList(u->label, FreeUserlabel);
#endif
    numUsers--;
  }
}

void UserReload(char *filename)
{
  itemident *p;

  snapshot;
  FlushList(userHead, FreeUser);
  UserLoad(filename);

  for (p = First(identHead); p; p = Next(p)) {
    p->user = FindUser(p->nick, p->host);
    if (p->user) {
      p->level = p->user->level;
    }
    else {
      p->level = 0;
      p->passed = FALSE;
    }
  }
}

char *VerboseFlags(uFlags *flags)
{
  static char buffer[MINIBUFFER];

  snapshot;
  if (flags->banprotect ||
      flags->autoop ||
      flags->linkbot ||
      flags->regularbot ||
      flags->delete ||
      flags->stealth) {
    StrFormatMax(buffer, sizeof(buffer), "Flags:%s%s%s%s%s%s",
                 flags->autoop ? " Autoop" : "",
                 flags->banprotect ? " Banprotected" : "",
                 flags->linkbot ? " Linkbot" : "",
                 flags->regularbot ? " Bot" : "",
                 flags->stealth ? " Stealth" : "",
                 flags->delete ? " DELETED" : "");
    return buffer;
  }
  return NULL;
}

char *SetUserFlags(uFlags *flags)
{
  static char buf[FLAG_MAXFLAGS+1];
  char *bufp = buf;

  if (flags->autoop)
    *bufp++ = FLAG_AUTOOP;
  if (flags->banprotect)
    *bufp++ = FLAG_BANPROTECT;
  if (flags->linkbot)
    *bufp++ = FLAG_LINKBOT;
  if (flags->regularbot)
    *bufp++ = FLAG_REGULARBOT;
  if (flags->delete)
    *bufp++ = FLAG_DELETE;
  if (flags->stealth)
    *bufp++ = FLAG_STEALTH;
  *bufp = (char)0;
  return buf;
}

char *WriteUserHead(char *buffer, size_t buffer_size, itemuser *u)
{
  int length, rc;

  snapshot;
  u->checksum = ChecksumUser(u);

  rc = StrFormatMax(buffer, buffer_size, " %s %d-%s %s\n" "-%s %d %d %d %d %d %s %d\n",
                    u->nick, u->level, SetUserFlags(&u->flags), u->realname,
                    u->passwd, u->passwdtime, u->modified, u->created,
                    u->checksum, u->totaljoins, LanguageShort(u->language),
                    u->newsid);

  if (rc > 0) {
    length = rc;

    if (u->comment) {
      rc = StrFormatMax(&buffer[length], buffer_size - length, "?%s\n",
                        u->comment);
      if (rc > 0)
        length += rc;
    }

    if (u->stat_bywhom) {
      rc = StrFormatMax(&buffer[length], buffer_size - length, "*%s %d %d\n",
                        u->stat_bywhom, u->stat_lastchange, u->stat_lastdone);
      if (rc > 0)
        length += rc;
    }
  }

  return buffer;
}

/* --- UserSave --------------------------------------------------- */
/* Returns TRUE on failure */

bool UserSave(void)
{
  extern char userfile[];
  char tempfile[MIDBUFFER], buffer[3*BIGBUFFER];
  bool ok = TRUE;
  itemuser *u;
  itemlist *l;
#ifdef HAVE_LIBFPL
  itemuserlabel *ul;
#endif
  FILE *f;

  snapshot;
  if (EmptyList(userHead))
    return TRUE;

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

  f = fopen(tempfile, "w");
  if (f) {
    if (0 > fprintf(f, "# Dancer userlist version: " VERSIONMSG "\n")) {
      ok = FALSE;
    }

    for (u = First(userHead); u && ok; u = Next(u)) {
      if (u->flags.delete && ((u->modified + USER_DELETE_TIMEOUT) < now))
        /*
         * This user has been marked for deletion and it has not been
         * changed for the USER_DELETE_TIMEOUT time. That means we should
         * no longer save it. It is now deleted for REAL. At least in the
         * next restart. - Bagder (added to 4.1)
         */
        continue;

      if (0 > fprintf(f, "%s", WriteUserHead(buffer, sizeof(buffer), u))) {
        ok = FALSE;
        break;
      }

      for (l = First(u->domainhead); l && ok; l = Next(l)) {
        if (0 > fprintf(f, "!%s\n", (char *)l->pointer)) {
          ok = FALSE;
          break;
        }
      }

#ifdef HAVE_LIBFPL
      for (ul = First((itemuserlabel *)u->label); ul && ok; ul = Next(ul)) {
        if (ul->label && (ul->label->flags & LAB_SAVE) && (ul->label->flags & LAB_DEFINED)) {
          if (ul->label->flags & LAB_STRING) {
            if (0 > fprintf(f, "$%s :%s\n", ul->label->name, ul->cont.str)) {
              ok = FALSE;
              break;
            }
          }
          else if (0 > fprintf(f, "$%s :%d\n", ul->label->name, ul->cont.val)) {
            ok = FALSE;
            break;
          }
        }
      }
#endif
    }
    fclose(f);

    if (ok)
      rename(tempfile, userfile);
  }
  return !ok;
}

/* --- LatestUserUpdate ------------------------------------------- */

time_t LatestUserUpdate(void)
{
  time_t time = 0;
  itemuser *u;

  for (u = First(userHead); u; u = Next(u)) {
    if (u->modified > time)
      time = u->modified;
  }
  return time;
}

/* --- FindUserByNick --------------------------------------------- */

itemuser *FindUserByNick(char *nick)
{
  itemuser *u;

  for (u = First(userHead); u; u = Next(u)) {
    if (IRCEqual(u->nick, nick))
      return u;
  }
  return NULL;
}

/* --- FindUser --------------------------------------------------- */

itemuser *FindUser(char *nick, char *userhost)
{
  char nickpattern[NICKLEN+1];
  char *hostpattern, *pointer;
  itemuser *u;
  itemlist *l;

  snapshot;
  if (userhost) {
    for (u = First(userHead); u; u = Next(u)) {
      for (l = First(u->domainhead); l; l = Next(l)) {
        hostpattern = (char *)l->pointer;

        if (nick) {
          pointer = StrIndex(hostpattern, '!');
          if (pointer) {
            nickpattern[0] = (char)0;
            StrScan(hostpattern, "%"NICKLENTXT"[^!]", nickpattern);

            /* If nick doesn't match, the whole pattern doesn't match */
            if (!Match(nick, nickpattern))
              continue;

            /* Make hostpattern point to userhost part of the pattern */
            hostpattern = &pointer[1];
          }
        }

        if (Match(userhost, hostpattern))
          return u;

        if (IsPrefix(userhost)) {
          if (Match(&userhost[1], hostpattern))
            return u;

          if (IsPrefix(hostpattern))
            if (Match(&userhost[1], &hostpattern[1]))
              return u;
        }
        else {
          if (IsPrefix(hostpattern))
            if (Match(userhost, &hostpattern[1]))
              return u;
        }
      }
    }
  }
  return NULL;
}

/* --- SplitHost -------------------------------------------------- */

bool SplitHost(char *userhost,     /* user@machine.host.domain */
               char *parsedhost,   /* should become 'machine.host.domain' */
               char *parseddomain, /* should become '*.host.domain' OR
                                      'machine.host.domain' if it is hostisp */
               char *parseduser)   /* should become 'user' */
{
  char *host, *xs, *ptr;
  int i;

  snapshot;
  host = StrIndexLast(userhost, '@');
  if (host) {
    StrCopyMax(parsedhost, MIDBUFFER, &host[1]);
    xs = Userdomain(userhost);
    if (xs) {
      ptr = StrIndex(xs, '@');
      StrCopyMax(parseddomain, MIDBUFFER, ptr ? &ptr[1] : xs);
      StrFree(xs);
    }
    else
      StrCopyMax(parseddomain, MIDBUFFER, &host[1]);
    for (i = 0; (i < USERLEN) && (&userhost[i] < host); i++)
      parseduser[i] = userhost[i];
    parseduser[i] = (char)0;
  }
  else
    return TRUE;

  return FALSE;
}

/* --- AddIdent --------------------------------------------------- */

itemident *AddIdent(char *nick,
                    char *userhost,
                    itemuser *u,
                    itemguest *g,
                    itemclient *k)
{
  char parsedhost[MIDBUFFER];
  char parseddomain[MIDBUFFER];
  char parseduser[USERLEN+1];
  itemident *p;

  snapshot;
  if (SplitHost(userhost, parsedhost, parseddomain, parseduser))
    return NULL;

  p = NewEntry(itemident);
  if (p) {
    InsertLast(identHead, p);
    p->level      = u ? u->level : 0;
    p->host       = StrDuplicate(userhost);
    p->userdomain = Userdomain(userhost);
    p->signature  = HashSignatureU(userhost);
    p->user       = u;
    p->guest      = g;
    p->client     = k;
    p->passed     = FALSE;
    p->phost       = StrDuplicate(parsedhost);
    p->puserdomain = StrDuplicate(parseddomain);
    p->pname       = StrDuplicate(parseduser);
#ifdef HAVE_LIBFPL
    p->label       = u ? u->label : NULL;
#endif
    StrCopyMax(p->nick, NICKLEN + 1, nick);
    p->illegalname = StrIndex(parsedhost, '*') ? TRUE : FALSE;
  }
  return p;
}

void FreeIdent(void *v)
{
  itemident *p;

  p = (itemident *)v;
  if (p) {
    if (p->host)
      StrFree(p->host);
    if (p->userdomain)
      StrFree(p->userdomain);
    if (p->phost)
      StrFree(p->phost);
    if (p->pname)
      StrFree(p->pname);
    if (p->puserdomain)
      StrFree(p->puserdomain);
  }
}

/* --- AutoOp ----------------------------------------------------- */

bool IsAutoOp(itemguest *g)
{
  return (g->ident->user && g->ident->user->flags.autoop);
}

void CheckForAutoOp(void)
{
  extern long autoopswaiting;

  snapshot;
  if (botop && autoopswaiting) {
    bool thereisone = FALSE;
    register itemguest *g;

    for (g = First(guestHead); g; g = Next(g)) {
      if (!g->flags.chanop && g->op_this_person) {
        if (g->op_this_person < now) {
          Mode("+o %s", g->ident->nick);
          g->op_this_person = 0; /* Don't do it again */
          return;
        }
        thereisone = TRUE; /* Not right now! */
      }
    }
    if (!thereisone)
      autoopswaiting = 0; /* There are no users [left] to make chanops */
  }
}

/* --- NetSplits -------------------------------------------------- */

/*
 * These functions keep a list with all users that quit as the result
 * of a split. If one of them joins within SPLITTIMEOUT seconds, we
 * consider the split to be healed, otherwise we remove them from this
 * list and consider them just quit.
 */

void FreeGuest(void *);

void FreeSplit(void *v)
{
  itemsplit *p;

  p = (itemsplit *)v;
  if (p) {
    if (p->servers)
      StrFree(p->servers);
    DeleteList(p->wholeft, FreeGuest);
  }
}

void AddSplitter(itemguest *g, char *servers)
{
  itemsplit *p;

  snapshot;
  g->flags.split = TRUE;

  totalnumberofsplits++; /* Never decrease */
  numSplitGuests++;      /* One more [amount in the split lists] */
  numGuests--;           /* One less [in the channel right now]  */

  for (p = First(splitHead); p; p = Next(p)) {
    if (StrEqual(p->servers, servers))
      break;
  }

  if (NULL == p) {  /* Make new if non-existant */
    p = NewEntry(itemsplit);
    if (p) {
      InsertFirst(splitHead, p);
      p->splittime = lastsplittime = now;
      p->servers = StrDuplicate(servers);
      p->wholeft = NewList(itemguest);
      Log(LOGSPLIT, servers);
    }
    else
      return; /* Error, no mem */
  }
  MoveLast(guestHead, g, p->wholeft);
}

itemguest *SplitterReturns(char *nick, char *host)
{
  itemguest *g;
  itemsplit *p;

  snapshot;
  for (p = First(splitHead); p; p = Next(p)) {
    for (g = First(p->wholeft); g; g = Next(g)) {
      if (Match(g->ident->nick, nick)) {
        if (StrEqualCase(g->ident->host, host)) {
          if (!p->heal) {  /* Not previously healed */
            p->heal = TRUE; /* This is now considered healed */
            Log(LOGNHEAL, p->servers);
          }
          MoveLast(p->wholeft, g, guestHead);
          if (g->flags.chanop)
            g->flags.splitop = TRUE;
          else
            g->flags.splitop = FALSE;
          g->flags.split = g->flags.chanop = g->flags.voice = FALSE;
          return g; /* The prodigal son */
        }
        else if (!mute) { /* Warn about a potential collision */
          SendNickf(nick, GetDefaultText(msg_warn_nick_collide),
                    g->ident->nick, TimeAgo(p->splittime), g->ident->host, p->servers);
        }
      }
    }
  }
  return NULL; /* No, not a netjoin */
}

/* --- ScanSplitServers ------------------------------------------- */

void ScanSplitServers(bool cleanall)
{
  itemsplit *p, *next;

  snapshot;
  for (p = First(splitHead); p; p = next) {
    next = Next(p);
    if (cleanall || EmptyList(p->wholeft) ||
        ((now - p->splittime) > SPLITTIMEOUT)) {
      if (!cleanall && !p->heal)
        Logf(LOGNHEAL, "(timeout) %s", p->servers);
      DeleteEntry(splitHead, p, FreeSplit);
    }
  }
}

/* --- DoCountJoins ----------------------------------------------- */

int DoCountJoins(itemguest *g)
{
  extern itemseen **hashsite;
  char *host, *usite;
  long joins = 1;
  ulong x;
  itemaux *p;
  itemseen *ps;

  snapshot;
  host = g->ident->host;
  if (IsPrefix(host))
    host++;
  x = Hash(g->ident->userdomain);
  for (ps = hashsite[x]; ps; ps = ps->next) {
    usite = ps->usite;
    if (IsPrefix(usite))
      usite++;
    if (Match(host, usite)) {
      for (p = ps->first; p; p = p->link)
        joins += p->count;  /* Accumulate all joins for user */
        break;
    }
  }
  g->joins = joins;
  return joins;
}

char *GetPrefix(char prefix)
{
  switch (prefix) {
  case PREFIX_HAT:
    return "^";
  case PREFIX_EQUAL:
    return "=";
  case PREFIX_PLUS:
    return "+";
  case PREFIX_TILDE:
    return "~";
  case PREFIX_DASH:
    return "-";
  default:
    return "";
  }
}

char IsPrefix(char *name)
{
  switch (name[0]) {
  case PREFIX_HAT:
  case PREFIX_TILDE:
  case PREFIX_PLUS:
  case PREFIX_EQUAL:
  case PREFIX_DASH:
    return name[0];
  default:
    return PREFIX_NONE;
  }
}

/* --- BadUsername ------------------------------------------------ */

/* Returns TRUE if the 'username' consists of any of the characters we
 * consider "illegal" and therefore enforces site bans/warns patterns.
 */

int BadUsername(char *name)
{
  while (*name) {
    switch (*name) {
    case '*':
    case '?':
#if 0
      /* caused major confusion, better leave them out for now */
    case '[':
    case ']':
    case '!':
#endif
    case '@':
      return TRUE;
    }
    name++;
  }
  return FALSE;
}

/* --- NewGuest --------------------------------------------------- */

void NewGuest_Who(char *line)
{
  char username[MIDBUFFER], host[MIDBUFFER], nick[NICKLEN+1],
       status[16];
  char buf[MIDBUFFER];
  bool chop, voice, ircop;
  itemguest *g;

  snapshot;
  if (4 <= StrScan(line, "%*s %*s %"MIDBUFFERTXT"s %"MIDBUFFERTXT"s %*s %"NICKLENTXT"s %15s",
                   username, host, nick, status)) {

    chop  = (StrIndex(status, '@') ? TRUE : FALSE);
    voice = (StrIndex(status, '+') ? TRUE : FALSE);
    ircop = (StrIndex(status, '*') ? TRUE : FALSE);

    /*
     * Further:
     * '*' means the person is an ircop
     * 'H' means the person is not away
     * 'G' means the person _is_ away
     *
     * But since we cannot get this info from joined users without making
     * additional /WHO calls on each joined person, we won't get this kind
     * of info on people not already joined when we join. - Bagder
     */

    StrFormatMax(buf, sizeof(buf), "%s@%s", username, host);
    AddGuest(nick, buf, chop, voice, ircop, &g);

    if (StrEqualCase(nick, nickname)) {
      /* this is us */
      botop = chop;
      if (g)
        g->flags.bot = TRUE; /* after all, we are a bot */

      StrFormatMax(myaddr, sizeof(myaddr), "%s@%s", username, host);
      StrFormatMax(botmatch, sizeof(botmatch), "%s!%s", nick, myaddr);
    }
  }
  else
    Debug("Strange format: %s", line);
}

/* --- RemoveGuest ------------------------------------------------ */

void FreeGuest(void *v)
{
  itemguest *g;

  g = (itemguest *)v;
  if (g) {
    if (g->ident->client)
      g->ident->guest = NULL;
    else
      DeleteEntry(identHead, g->ident, FreeIdent);
    if (g->kickmsg)
      StrFree(g->kickmsg);
    if (g->flags.split)
      numSplitGuests--;
    else
      numGuests--;
  }
}

void RemoveGuest(itemguest *g)
{
  snapshot;
  if (welcome && !mute) {  /* We are set to welcome / wave ! */
    if (!g->posts)
      Sayf("How rude, %s just left without saying *anything* to us!",
           g->ident->nick);
    else if ((g->jointime + SECINMIN) > now)
      Actionf("sighs, a %d seconds visit! Is that all we deserve? :-(",
              now - g->jointime);
  }

#ifdef HAVE_LIBFPL
  if (g->ident->user)
    g->ident->user->label = g->ident->label;
#endif

  DeleteEntry(guestHead, g, FreeGuest);
}

/* --- DeleteGuests ----------------------------------------------- */

void DeleteGuests(void)
{
  itemguest *g;

  snapshot;
  if (guestHead) {
#ifdef HAVE_LIBFPL
    for (g = First(guestHead); g; g = Next(g)) {
      if (g->ident->user)
        g->ident->user->label = g->ident->label;
    }
#endif

    /* Remove all splitted servers and the guests linked to them */
    ScanSplitServers(TRUE);
    FlushList(guestHead, FreeGuest);
  }
}

/* --- NetHeal ----------------------------------------------------- */

/* May be called several times each netheal! */

void NetHeal(void)
{
  extern bool possiblyfresh; /* this might be a restarted server!! */
  extern int possiblyfreshtime; /* timeout for refreshed server netheals */

  masterflood = TRUE;
  mastertime = now;
  if (possiblyfresh) {
    possiblyfreshtime = now;
    possiblyfresh = FALSE;
  }
}

/* --- AddGuest --------------------------------------------------- */

bool AddGuest(char *nick,
              char *host,
              bool chop,
              bool voice,
              bool ircop,
              itemguest **guest)
{
  bool realjoin = TRUE; /* standard join */
  itemguest *g;
  itemclient *k;

  snapshot;
  *guest = NULL;

  g = SplitterReturns(nick, host);
  if (g) {
    Logf(LOGNJOIN, "%s [%d] (%s)", nick, g->ident->level, host);
    NetHeal();
    numSplitGuests--; /* one less */
    numGuests++;      /* one more */
    realjoin = FALSE; /* no "real" join */
  }
  else {
    /*
     * At certain occasions, when the bot makes a /who call, and the list isn't
     * done yet, and a nick joins, we can get two entries with the same nick.
     * We prevent this by checking that the nick we're about to add isn't
     * already added.
     */
    g = FindNick(nick);
    if (NULL == g) {

      g = NewEntry(itemguest);
      if (g) {
        InsertLast(guestHead, g);

        /* Inherit privileges from client. If a user has made a client,
         * changed nick and joined, we cannot know if it's really him...
         * tough luck. If the user is spoofed, that's even tougher luck.
         */
        k = FindClientByNick(nick);
        if (k && StrEqualCase(k->ident->host, host)) {
          g->ident = k->ident; /* This is our ident struct */
          k->ident->guest = g; /* Make the ident point back to us */
        }
        else {
          /* Each itemguest _must_ have an ident structure attached! */
          g->ident = AddIdent(nick, host, FindUser(nick, host), g, NULL);
          if (NULL == g->ident) {
            DeleteEntry(guestHead, g, FreeGuest);
            Debug("Out of memory. Cannot add guest %s (%s)",
                  nick, host);
            return FALSE;
          }
        }
        /*
         * Check if the guest matched as a user that is some kind of
         * bot, and if so, set the guest-flag for BOT.
         */
        if (g->ident->user &&
            (g->ident->user->flags.linkbot ||
            g->ident->user->flags.regularbot))
          g->flags.bot = TRUE;
        else
          g->flags.bot = FALSE;
        g->flags.chanop = chop;
        g->flags.voice  = voice;
        g->flags.ircop  = ircop;
        g->flags.splitop = FALSE;
      }
      else {
        Debug("Out of memory");
        return FALSE;
      }
      numGuests++;
      Logf(LOGJOIN, "%s%s [%d] (%s)", chop ? "@" : (voice ? "+" : ""),
           g->ident->nick, g->ident->level, g->ident->host);

      g->jointime = now;
      /*
       * I think this might need some explanation. The joincount system has
       * always been based on the seen data. When the user joins, we check
       * the hostpattern and then sums the number of joins all the different
       * nick names have done with that hostpattern.
       * Now, since I intend to improve this system while there are already
       * lots of systems running and many many joins counted, I need a way
       * to get the old joins somehow read into the new system. I will
       * therefore make the ->totalcount of the user struct count the total
       * number of joins (for regged users) but I will continue doing the old
       * count. If the old count shows a higher count than the new, the new
       * will be raised to the old one. That way, the new should seen get at
       * least the value of the largest old one for the users hostpatterns.
       * - Bagder
       */

      DoCountJoins(g);

      if (g->ident->user) {
        g->ident->user->totaljoins++; /* This user did join right now */
        if (g->joins > g->ident->user->totaljoins)
          /* Old system contains more joins, copy them to the new system */
          g->ident->user->totaljoins = g->joins;
        else
          /* This is the total amount we know of */
          g->joins = g->ident->user->totaljoins;
      }
    }
  }

  /* Don't send messages for the bot owner to the bot */
  if (!StrEqualCase(g->ident->nick, nickname))
    TellNotify(g);

  *guest = g;
  return realjoin;
}

/* --- FindNick --------------------------------------------------- */

/* Used very often! Make hashed nicks instead? */

itemguest *FindNick(char *nick)
{
  register itemguest *g;

  for (g = First(guestHead); g; g = Next(g)) {
    if (IRCEqual(g->ident->nick, nick))
      return g;
  }
  return NULL;
}

itemguest *FindSplitNick(char *nick)
{
  itemsplit *p;
  itemguest *g;

  for (p = First(splitHead); p; p = Next(p)) {
    for (g = First(p->wholeft); g; g = Next(g)) {
      if (IRCEqual(g->ident->nick, nick))
        return g;
    }
  }
  return NULL;
}

/* --- FindHost --------------------------------------------------- */

itemguest *FindHost(char *pattern)
{
  itemguest *g;

  snapshot;
  if (StrIndex(pattern, '!')) {
    /* Full *!*@* pattern expected */
    char nickpattern[NICKLEN+1];
    char hostpattern[MIDBUFFER];

    if (2 == StrScan(pattern, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                     nickpattern, hostpattern)) {
      for (g = First(guestHead); g; g = Next(g)) {
        if (Match(g->ident->nick, nickpattern) &&
            Match(g->ident->host, hostpattern))
          return g;
      }
    }
  }
  else {
    for (g = First(guestHead); g; g = Next(g)) {
      if (Match(g->ident->host, pattern))
        return g;
    }
  }
  return NULL;
}

/* --- ChangeGuest ------------------------------------------------ */

void ChangeGuest(char *oldnick, char *newnick)
{
  itemguest *g, *w;
  itemclient *k;
  itemsplit *p;

  snapshot;
  g = FindNick(oldnick);
  if (g) {
    StrCopy(g->ident->nick, newnick);

    g->flags.kicked = FALSE;
    if (g->flags.kick) {
      Kick(newnick, g->kickmsg ? g->kickmsg : "Gotcha!");
      AddKick(g->ident, nickname, "Nick flooder!", KICK_BOT);
    }
    else if (nickflood) {
      if ((g->nicktime + 10) >= now) {
        if (++g->nickchanges > 2)
          Warning(g, "nick-flooders", "Nick flooder!");
      }
      else
        g->nickchanges = 0;
    }
    g->nicktime = now;

    if (warnmode && !g->flags.kick)
      WarnCheck(g->ident->nick, g->ident->host);

    /* Try to unify structures */
    if (!g->ident->client &&
        (k = FindClientByNick(g->ident->nick)) &&
        (g->ident->user == k->ident->user) &&
        StrEqualCase(g->ident->host, k->ident->host)) {
      g->ident->passed |= k->ident->passed;
      k->ident->client = NULL;
      DeleteEntry(identHead, k->ident, FreeIdent);
      k->ident = g->ident;

      /*
       * Hm, we should let the ident know it is used to identify a client
       * too.
       * - Bagder
       */
      g->ident->client = k;
    }

    if (!mute) {
      for (p = First(splitHead); p; p = Next(p)) {
        for (w = First(p->wholeft); w; w = Next(w)) {
          if (Match(w->ident->nick, newnick) &&
              !StrEqualCase(g->ident->userdomain, w->ident->userdomain))
            SendNickf(newnick, GetDefaultText(msg_warn_nick_collide),
                      w->ident->nick, TimeAgo(p->splittime), w->ident->host,
                      p->servers);
        }
      }
    }
  }
  else
    Debug("Internal confusion. Unknown user \"%s\" changed nick.", oldnick);
}

/* --- FindClientByNick ------------------------------------------- */

itemclient *FindClientByNick(char *nick)
{
  itemclient *k;

  for (k = First(clientHead); k; k = Next(k)) {
    if (k->ident && IRCEqual(k->ident->nick, nick))
      return k;
  }
  return NULL;
}

/* --- RemoveClient ----------------------------------------------- */

void FreeClient(void *v)
{
  itemclient *k;

  k = (itemclient *)v;
  if (k) {
    if (k->ident->guest) {
      k->ident->client = NULL;
    }
    else {
      DeleteEntry(identHead, k->ident, FreeIdent);
    }
    if (k->filebuffer)
      free(k->filebuffer);
  }
}

void RemoveClient(itemclient *k)
{
  char buffer[MIDBUFFER];

  snapshot;
  StrFormatMax(buffer, sizeof(buffer), "%s removed at socket %d",
               k->ident->nick, k->socket);
  CloseConnection(k->socket);
  DeleteEntry(clientHead, k, FreeClient);
  Log(LOGCLIENT, buffer);
  Multicast(SPYCAST, buffer);
  numClients--;
}

/* --- AddClient -------------------------------------------------- */

itemclient *AddClient(char *nick, char *uhost, itemuser *u, int sock)
{
  itemguest *g;
  itemclient *k;

  snapshot;
  k = NewEntry(itemclient);
  if (k) {
    InsertLast(clientHead, k);

    /* Find nick on channel, and inherit it's privileges */
    g = FindNick(nick);
    if (g) {
      if (g->ident->client) {
        DeleteEntry(clientHead, k, FreeClient); /* Remove again */
        return NULL; /* Client already exist */
      }
      k->ident = g->ident;
      g->ident->client = k;
    }
    else {
      k->ident = AddIdent(nick, uhost, u, NULL, k);
      if (NULL == k->ident) {
        DeleteEntry(clientHead, k, FreeClient);
        Debug("Out of memory. Cannot add client %s (%s)",
              nick, uhost);
        return NULL;
      }
    }
    k->socket = sock;
    k->status = CL_NONE;
    k->buildtime = k->lasttime = now;
    k->flags = WALLCAST;
    k->chatecho = TRUE; /* we start in echomode by default */
    if (u->level >= LEVELPUB)
      k->flags |= REPORTCAST;
    if (u->level >= LEVELOWNER)
      k->flags |= DEBUGCAST;
    numClients++;
    Logf(LOGCLIENT, "%s (%s) added", k->ident->nick, u->nick);
    Multicastf(SPYCAST, "client %s has connected", k->ident->nick);
  }
  return k;
}

/* --- DeleteClients ---------------------------------------------- */

void DeleteClients(void)
{
  FlushList(clientHead, FreeClient);
  Log(LOGCLIENT, "All clients removed");
}

/* --- Links ------------------------------------------------------ */

/* addr and port must be in network order */
itemlink *FindLink(ulong addr, ushort port)
{
  itemlink *r;

  for (r = First(linkHead); r; r = Next(r)) {
    if ((r->addr.sin_addr.s_addr == addr) && (r->addr.sin_port == port))
      return r;
  }
  return NULL;
}

itemlink *FindLinkByNick(char *nick)
{
  itemlink *r;

  for (r = First(linkHead); r; r = Next(r)) {
    if (IRCEqual(r->name, nick))
      return r;
  }
  return NULL;
}

itemlink *AddLink(char *name, itemuser *u, ulong num, ushort port)
{
  itemlink *r;

  snapshot;
  r = FindLink(htonl(num), htons(port));
  if (r)
    return NULL;

  r = NewEntry(itemlink);
  if (r) {
    InsertLast(linkHead, r);
    r->user = u;
    r->name = StrDuplicate(name);
    r->addr.sin_family = AF_INET;
    r->addr.sin_port = htons(port);
    r->addr.sin_addr.s_addr = htonl(num);
    r->passed = FALSE;
    r->buildtime = r->lasttime = now;
    r->mymsgid = 0;
    r->yourmsgid = -1;
    rtt_init(r);
  }
  return r;
}

void FreeLink(void *v)
{
  itemlink *r;

  r = (itemlink *)v;
  if (r) {
    FlushLinkQueue(r);
    if (r->name)
      StrFree(r->name);
  }
}

itemlink *RemoveLink(itemlink *r)
{
  itemlink *next;

  next = Next(r);
  DeleteEntry(linkHead, r, FreeLink);
  return next;
}

/* --- UserInit --------------------------------------------------- */

void UserInit(void)
{
  userHead   = NewList(itemuser);
  identHead  = NewList(itemident);
  linkHead   = NewList(itemlink);
  clientHead = NewList(itemclient);
  guestHead  = NewList(itemguest);
  splitHead  = NewList(itemsplit);
}

/* --- UserCleanup ------------------------------------------------ */

void UserCleanup(void)
{
  UserSave();

  DeleteList(splitHead, FreeSplit);
  DeleteList(guestHead, FreeGuest);
  DeleteList(clientHead, FreeClient);
  DeleteList(linkHead, FreeLink);
  DeleteList(identHead, FreeIdent);
  DeleteList(userHead, FreeUser);
}

/* --- MakePassword ----------------------------------------------- */

#define PASSLEN 32
#define PASSLENTXT "31"

char *MakePassword(char *plain)
{
  char passwd[PASSLEN] = "";
  unsigned char salt[3];
  int rnd;

  StrScan(plain, "%"PASSLENTXT"s", passwd);

  rnd = (int)(Rnd() * 4096);
  to64(salt, rnd, 2);
  salt[2] = (char)0;
  return StrDuplicate(mycrypt(passwd, salt));
}

/* --- CheckPassword ---------------------------------------------- */

#define SALT "k9"

bool CheckPassword(char *plain, char *crypted)
{
  char passwd[PASSLEN] = "";
  char salt[3];

  StrScan(plain, "%"PASSLENTXT"s", passwd);

  if (StrEqualCaseMax(crypted, 3, "$1$")) {
    salt[0] = crypted[3];
    salt[1] = crypted[4];
    salt[2] = (char)0;

    return StrEqualCase(mycrypt(passwd, salt), crypted); /*NEW*/
  }
  else {
#if defined(HAVE__CRYPT)
    return StrEqualCase((char *)_crypt(passwd, SALT), crypted);
#elif (!defined(AMIGA) || defined(__GNUC__)) && !defined(__CYGWIN32__) /* amiga version has no crypt() of its own at all */
    return StrEqualCase((char *)crypt(passwd, SALT), crypted); 
#endif
  }
}

int User2ID(char *nick, char *flags)
{
  itemguest *g;
  itemuser *u;
  itemclient *c;

  g = FindNick(nick);
  if (NULL == g)
    g = FindSplitNick(nick);
  u = FindUserByNick(nick);
  c = FindClientByNick(nick);

  if (StrContains(flags, "join"))
    return (int)g;

  if (StrContains(flags, "reg"))
    return (int)u;

  if (g)
    return (int)g;
  if (u)
    return (int)u;
  if (c)
    return (int)c;
  return 0;
}

#define ID_NONE 0
#define ID_GUEST 1
#define ID_SPLIT 2
#define ID_CLIENT 4
#define ID_USER  8

int ID2User(int id, itemuser **user, itemguest **guest, itemclient **client)
{
  itemguest *g;
  itemuser *u;
  itemclient *c;
  itemsplit *p;

  for (g = First(guestHead); g; g = Next(g)) {
    if (g == (itemguest *)id) {
      *guest = g;
      return ID_GUEST;
    }
  }

  for (p = First(splitHead); p; p = Next(p)) {
    for (g = First(p->wholeft); g; g = Next(g)) {
      if (g == (itemguest *)id) {
        *guest = g;
        return ID_SPLIT;
      }
    }
  }

  for (u = First(userHead); u; u = Next(u)) {
    if (u == (itemuser *)id) {
      *user = u;
      return ID_USER;
    }
  }

  for (c = First(clientHead); c; c = Next(c)) {
    if (c == (itemclient *)id) {
      *client = c;
      return ID_CLIENT;
    }
  }

  return ID_NONE;
}

itemident *ID2Ident(int id)
{
  itemguest *g;
  itemuser *u;
  itemclient *c;

  switch (ID2User(id, &u, &g, &c)) {
  case ID_NONE:
    return NULL; /* nada */
  case ID_GUEST:
  case ID_SPLIT:
    return g->ident;
  case ID_USER:
    return NULL;
  case ID_CLIENT:
    return c->ident;
  }
  return NULL;
}

#ifdef HAVE_LIBFPL
itemuserlabel **ID2Label(int id)
{
  itemguest *g;
  itemuser *u;
  itemclient *c;

  switch (ID2User(id, &u, &g, &c)) {
  case ID_NONE:
    return NULL; /* nada */
  case ID_GUEST:
  case ID_SPLIT:
    return (itemuserlabel **)&g->ident->label;
  case ID_USER:
    return (itemuserlabel **)&u->label;
  case ID_CLIENT:
    return (itemuserlabel **)&c->ident->label;
  }
  return NULL;
}
#endif

/* =========================================================================

   NickReport() experiments, please have patience!

   ========================================================================= */

/*****************

GENERAL-SEEN-NICK (no options)

  A  SEEN as usual. Say the usual. If joined, abort.

  B  Get the host pattern and search the ppl currently joined,
     any match? If yes, say:
    = "A person matching that pattern is currently joined as 'nick'"

  -  Check for the registered nick. If none of the reg'ed nick's hosts
     match the already found pattern, check the channel for anyone
     matching. Anyone joined?
     1- If yes, say and abort.
        = "The nick is registered to the user now joined as 'nick'."
     2- If no, say and abort
        = "The nick is registered to another user."

  C  Perform a seen -MOST- on the nick. If that gives us another
     host, check the channel for it. Anyone joined?
     1- If yes, say and abort
        = "Another hostpattern 'host' has used the nick more often and is
           currently joined as 'nick'."
     2- If no. Say and abort
        = "Another hostpattern 'host' has used the nick more often."

  D  Get the host pattern and search for a more frequently used nick.
     Any match? If yes, say and abort.
     = "The nick 'nick' has been used more often with that hostpattern."

*****************/

void NickReport(char *from, char *nick)
{
  extern itemaux **hashnick; /* from seen */

  long max = 0;
  long hits = 0;
  time_t nicktime = 0;
  time_t roottime = 0;
  itemuser *u;
  itemguest *g, *w;
  itemaux *p;
  itemaux *maxnick = NULL;
  itemaux *recentnick = NULL;
  itemaux *recentroot = NULL;

  snapshot;
  g = FindNick(nick);
  u = FindUserByNick(nick);

  if (g) {
    Sendf(from, "%s (%s) %s.", g->ident->nick, g->ident->userdomain,
          GetText(msg_is_joined_now));
    if (u) {
      if (u == g->ident->user)
        Send(from, GetText(msg_regged_user_with_nick));
      else {
        itemlist *l;

        Send(from, GetText(msg_not_his_nick));
        for (l = First(u->domainhead); l; l = Next(l)) {
          w = FindHost((char *)l->pointer);
          if (w) {
            Sendf(from, GetText(msg_who_is_now), w->ident->nick);
            break;
          }
        }
      }
    }
  }

  /*
   * We won't use SeenMostNick(), SeenRecentNick() or SeenRecentRootNick()
   * here cause this way we can get more information, and faster.
   */
  for (p = hashnick[HashU(nick)]; p; p = p->next) {
    if (IRCEqual(p->nick, nick)) {
      hits++;
      if (p->count > max) {
        max = p->count;
        maxnick = p;
      }
      if (p->departure > nicktime) {
        nicktime = p->departure;
        recentnick = p;
      }
      if (p->root->departure > roottime) {
        roottime = p->root->departure;
        recentroot = p;
      }
    }
  }

  if (recentnick) {
    Sendf(from, GetText(msg_most_recently_used_by), recentnick->root->host);
    w = FindHost(recentnick->root->host);
    if (w)
      Sendf(from, GetText(msg_from_which_host), w->ident->nick);

    if (maxnick) {
      if (recentnick == maxnick)
        Send(from, GetText(msg_and_also_most_often_used));
      else {
        Sendf(from, GetText(msg_most_often_used_by), maxnick->root->host);
        w = FindHost(maxnick->root->host);
        if (w)
          Sendf(from, GetText(msg_from_which_host), w->ident->nick);
      }
    }

    if (recentroot) {
      if (recentroot->root->departure > recentnick->departure) {
        for (p = recentroot->root->first; p; p = p->link) {
          if (p->departure > recentroot->departure)
            recentroot = p;
        }
        Sendf(from, "%s (%s) who has also used this nick in the past, has been seen most recently",
              recentroot->nick, recentroot->root->host);
        w = FindHost(recentroot->root->host);
        if (w)
          Sendf(from, GetText(msg_from_which_host), w->ident->nick);
      }
    }
  }

  if ((NULL == g) && (NULL == recentnick))
    Sendf(from, GetText(msg_never_seen), nick);
}

/*
 * NonHostISP()
 *
 * Returns TRUE if the input parameter is listed as a non-host-ISP in
 * the .config file.
 */

bool NonHostISP(char *checkthis)
{
  extern itemlist *nonhostispHead;
  itemlist *l;

  for (l = First(nonhostispHead); l; l = Next(l)) {
    if (Match(checkthis, l->pointer))
      return TRUE;
  }
  return FALSE;
}

/*
 * HostISP()
 *
 * Returns TRUE if the input parameter is listed as a host-ban-only ISP in
 * the .config file.
 */

bool HostISP(char *checkthis)
{
  extern itemlist *hostispHead;
  itemlist *l;

  for (l = First(hostispHead); l; l = Next(l)) {
    if (Match(checkthis, l->pointer)) {
      if (NonHostISP(checkthis))
        return FALSE; /* This is not considered to be a hostisp */
      else
        return TRUE; /* Straight and clean hostisp */
    }
  }
  return FALSE;
}

/*
 * DontSiteBan()
 *
 * Returns TRUE if the input parameter is listed as a dontsiteban ISP in the
 * .config file.
 */

bool DontSiteBan(char *checkthis)
{
  extern itemlist *dontsitebanHead;
  itemlist *l;

  for (l = First(dontsitebanHead); l; l = Next(l)) {
    if (Match(checkthis, l->pointer))
      return TRUE; /* Prevent sitebans on this domain */
  }
  return FALSE;
}


syntax highlighted by Code2HTML, v. 0.9.1