/*
 * flags.c -- handles:
 *   all the flag matching/conversion functions in one neat package :)
 *
 * $Id: flags.c,v 1.32 2006-03-28 02:35:50 wcc Exp $
 */
/*
 * Copyright (C) 1997 Robey Pointer
 * Copyright (C) 1999 - 2006 Eggheads Development Team
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "main.h"


extern int raw_log, require_p, noshare, allow_dk_cmds;
extern struct dcc_t *dcc;


int logmodes(char *s)
{
  int i;
  int res = 0;

  for (i = 0; i < strlen(s); i++)
    switch (s[i]) {
    case 'm':
    case 'M':
      res |= LOG_MSGS;
      break;
    case 'p':
    case 'P':
      res |= LOG_PUBLIC;
      break;
    case 'j':
    case 'J':
      res |= LOG_JOIN;
      break;
    case 'k':
    case 'K':
      res |= LOG_MODES;
      break;
    case 'c':
    case 'C':
      res |= LOG_CMDS;
      break;
    case 'o':
    case 'O':
      res |= LOG_MISC;
      break;
    case 'b':
    case 'B':
      res |= LOG_BOTS;
      break;
    case 'r':
    case 'R':
      res |= raw_log ? LOG_RAW : 0;
      break;
    case 'w':
    case 'W':
      res |= LOG_WALL;
      break;
    case 'x':
    case 'X':
      res |= LOG_FILES;
      break;
    case 's':
    case 'S':
      res |= LOG_SERV;
      break;
    case 'd':
    case 'D':
      res |= LOG_DEBUG;
      break;
    case 'v':
    case 'V':
      res |= raw_log ? LOG_SRVOUT : 0;
      break;
    case 't':
    case 'T':
      res |= raw_log ? LOG_BOTNET : 0;
      break;
    case 'h':
    case 'H':
      res |= raw_log ? LOG_BOTSHARE : 0;
      break;
    case '1':
      res |= LOG_LEV1;
      break;
    case '2':
      res |= LOG_LEV2;
      break;
    case '3':
      res |= LOG_LEV3;
      break;
    case '4':
      res |= LOG_LEV4;
      break;
    case '5':
      res |= LOG_LEV5;
      break;
    case '6':
      res |= LOG_LEV6;
      break;
    case '7':
      res |= LOG_LEV7;
      break;
    case '8':
      res |= LOG_LEV8;
      break;
    case '*':
      res |= LOG_ALL;
      break;
    }
  return res;
}

char *masktype(int x)
{
  static char s[24];            /* Change this if you change the levels */
  char *p = s;

  if (x & LOG_MSGS)
    *p++ = 'm';
  if (x & LOG_PUBLIC)
    *p++ = 'p';
  if (x & LOG_JOIN)
    *p++ = 'j';
  if (x & LOG_MODES)
    *p++ = 'k';
  if (x & LOG_CMDS)
    *p++ = 'c';
  if (x & LOG_MISC)
    *p++ = 'o';
  if (x & LOG_BOTS)
    *p++ = 'b';
  if ((x & LOG_RAW) && raw_log)
    *p++ = 'r';
  if (x & LOG_FILES)
    *p++ = 'x';
  if (x & LOG_SERV)
    *p++ = 's';
  if (x & LOG_DEBUG)
    *p++ = 'd';
  if (x & LOG_WALL)
    *p++ = 'w';
  if ((x & LOG_SRVOUT) && raw_log)
    *p++ = 'v';
  if ((x & LOG_BOTNET) && raw_log)
    *p++ = 't';
  if ((x & LOG_BOTSHARE) && raw_log)
    *p++ = 'h';
  if (x & LOG_LEV1)
    *p++ = '1';
  if (x & LOG_LEV2)
    *p++ = '2';
  if (x & LOG_LEV3)
    *p++ = '3';
  if (x & LOG_LEV4)
    *p++ = '4';
  if (x & LOG_LEV5)
    *p++ = '5';
  if (x & LOG_LEV6)
    *p++ = '6';
  if (x & LOG_LEV7)
    *p++ = '7';
  if (x & LOG_LEV8)
    *p++ = '8';
  if (p == s)
    *p++ = '-';
  *p = 0;
  return s;
}

char *maskname(int x)
{
  static char s[207];           /* Change this if you change the levels */
  int i = 0;

  s[0] = 0;
  if (x & LOG_MSGS)
    i += my_strcpy(s, "msgs, ");
  if (x & LOG_PUBLIC)
    i += my_strcpy(s + i, "public, ");
  if (x & LOG_JOIN)
    i += my_strcpy(s + i, "joins, ");
  if (x & LOG_MODES)
    i += my_strcpy(s + i, "kicks/modes, ");
  if (x & LOG_CMDS)
    i += my_strcpy(s + i, "cmds, ");
  if (x & LOG_MISC)
    i += my_strcpy(s + i, "misc, ");
  if (x & LOG_BOTS)
    i += my_strcpy(s + i, "bots, ");
  if ((x & LOG_RAW) && raw_log)
    i += my_strcpy(s + i, "raw, ");
  if (x & LOG_FILES)
    i += my_strcpy(s + i, "files, ");
  if (x & LOG_SERV)
    i += my_strcpy(s + i, "server, ");
  if (x & LOG_DEBUG)
    i += my_strcpy(s + i, "debug, ");
  if (x & LOG_WALL)
    i += my_strcpy(s + i, "wallops, ");
  if ((x & LOG_SRVOUT) && raw_log)
    i += my_strcpy(s + i, "server output, ");
  if ((x & LOG_BOTNET) && raw_log)
    i += my_strcpy(s + i, "botnet traffic, ");
  if ((x & LOG_BOTSHARE) && raw_log)
    i += my_strcpy(s + i, "share traffic, ");
  if (x & LOG_LEV1)
    i += my_strcpy(s + i, "level 1, ");
  if (x & LOG_LEV2)
    i += my_strcpy(s + i, "level 2, ");
  if (x & LOG_LEV3)
    i += my_strcpy(s + i, "level 3, ");
  if (x & LOG_LEV4)
    i += my_strcpy(s + i, "level 4, ");
  if (x & LOG_LEV5)
    i += my_strcpy(s + i, "level 5, ");
  if (x & LOG_LEV6)
    i += my_strcpy(s + i, "level 6, ");
  if (x & LOG_LEV7)
    i += my_strcpy(s + i, "level 7, ");
  if (x & LOG_LEV8)
    i += my_strcpy(s + i, "level 8, ");
  if (i)
    s[i - 2] = 0;
  else
    strcpy(s, "none");
  return s;
}

/* Some flags are mutually exclusive -- this roots them out
 */
int sanity_check(int atr)
{
  if ((atr & USER_BOT) &&
      (atr & (USER_PARTY | USER_MASTER | USER_COMMON | USER_OWNER)))
    atr &= ~(USER_PARTY | USER_MASTER | USER_COMMON | USER_OWNER);
  if ((atr & USER_OP) && (atr & USER_DEOP))
    atr &= ~(USER_OP | USER_DEOP);
  if ((atr & USER_HALFOP) && (atr & USER_DEHALFOP))
    atr &= ~(USER_HALFOP | USER_DEHALFOP);
  if ((atr & USER_AUTOOP) && (atr & USER_DEOP))
    atr &= ~(USER_AUTOOP | USER_DEOP);
  if ((atr & USER_AUTOHALFOP) && (atr & USER_DEHALFOP))
    atr &= ~(USER_AUTOHALFOP | USER_DEHALFOP);
  if ((atr & USER_VOICE) && (atr & USER_QUIET))
    atr &= ~(USER_VOICE | USER_QUIET);
  if ((atr & USER_GVOICE) && (atr & USER_QUIET))
    atr &= ~(USER_GVOICE | USER_QUIET);
  /* Can't be owner without also being master */
  if (atr & USER_OWNER)
    atr |= USER_MASTER;
  /* Master implies botmaster, op and janitor */
  if (atr & USER_MASTER)
    atr |= USER_BOTMAST | USER_OP | USER_JANITOR;
  /* Can't be botnet master without party-line access */
  if (atr & USER_BOTMAST)
    atr |= USER_PARTY;
  /* Janitors can use the file area */
  if (atr & USER_JANITOR)
    atr |= USER_XFER;
  /* Ops should be halfops */
  if (atr & USER_OP)
    atr |= USER_HALFOP;
  return atr;
}

/* Sanity check on channel attributes
 */
int chan_sanity_check(int chatr, int atr)
{
  if ((chatr & USER_OP) && (chatr & USER_DEOP))
    chatr &= ~(USER_OP | USER_DEOP);
  if ((chatr & USER_HALFOP) && (chatr & USER_DEHALFOP))
    chatr &= ~(USER_HALFOP | USER_DEHALFOP);
  if ((chatr & USER_AUTOOP) && (chatr & USER_DEOP))
    chatr &= ~(USER_AUTOOP | USER_DEOP);
  if ((chatr & USER_AUTOHALFOP) && (chatr & USER_DEHALFOP))
    chatr &= ~(USER_AUTOHALFOP | USER_DEHALFOP);
  if ((chatr & USER_VOICE) && (chatr & USER_QUIET))
    chatr &= ~(USER_VOICE | USER_QUIET);
  if ((chatr & USER_GVOICE) && (chatr & USER_QUIET))
    chatr &= ~(USER_GVOICE | USER_QUIET);
  /* Can't be channel owner without also being channel master */
  if (chatr & USER_OWNER)
    chatr |= USER_MASTER;
  /* Master implies op */
  if (chatr & USER_MASTER)
    chatr |= USER_OP;
  /* Op implies halfop */
  if (chatr & USER_OP)
    chatr |= USER_HALFOP;
  /* Can't be +s on chan unless you're a bot */
  if (!(atr & USER_BOT))
    chatr &= ~BOT_SHARE;
  return chatr;
}

/* Get icon symbol for a user (depending on access level)
 *
 * (*) owner on any channel
 * (+) master on any channel
 * (%) botnet master
 * (@) op on any channel
 * (^) halfop on any channel
 * (-) other
 */
char geticon(int idx)
{
  struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 };

  if (!dcc[idx].user)
    return '-';
  get_user_flagrec(dcc[idx].user, &fr, 0);
  if (glob_owner(fr) || chan_owner(fr))
    return '*';
  if (glob_master(fr) || chan_master(fr))
    return '+';
  if (glob_botmast(fr))
    return '%';
  if (glob_op(fr) || chan_op(fr))
    return '@';
  if (glob_halfop(fr) || chan_halfop(fr))
    return '^';
  return '-';
}

void break_down_flags(const char *string, struct flag_record *plus,
                      struct flag_record *minus)
{
  struct flag_record *which = plus;
  int mode = 0;                 /* 0 = glob, 1 = chan, 2 = bot */
  int flags = plus->match;

  if (!(flags & FR_GLOBAL)) {
    if (flags & FR_BOT)
      mode = 2;
    else if (flags & FR_CHAN)
      mode = 1;
    else
      return;                   /* We dont actually want any..huh? */
  }
  egg_bzero(plus, sizeof(struct flag_record));

  if (minus)
    egg_bzero(minus, sizeof(struct flag_record));

  plus->match = FR_OR;          /* Default binding type OR */
  while (*string) {
    switch (*string) {
    case '+':
      which = plus;
      break;
    case '-':
      which = minus ? minus : plus;
      break;
    case '|':
    case '&':
      if (!mode) {
        if (*string == '|')
          plus->match = FR_OR;
        else
          plus->match = FR_AND;
      }
      which = plus;
      mode++;
      if ((mode == 2) && !(flags & (FR_CHAN | FR_BOT)))
        string = "";
      else if (mode == 3)
        mode = 1;
      break;
    default:
      if ((*string >= 'a') && (*string <= 'z')) {
        switch (mode) {
        case 0:
          which->global |=1 << (*string - 'a');

          break;
        case 1:
          which->chan |= 1 << (*string - 'a');
          break;
        case 2:
          which->bot |= 1 << (*string - 'a');
        }
      } else if ((*string >= 'A') && (*string <= 'Z')) {
        switch (mode) {
        case 0:
          which->udef_global |= 1 << (*string - 'A');
          break;
        case 1:
          which->udef_chan |= 1 << (*string - 'A');
          break;
        }
      } else if ((*string >= '0') && (*string <= '9')) {
        switch (mode) {
          /* Map 0->9 to A->K for glob/chan so they are not lost */
        case 0:
          which->udef_global |= 1 << (*string - '0');
          break;
        case 1:
          which->udef_chan |= 1 << (*string - '0');
          break;
        case 2:
          which->bot |= BOT_FLAG0 << (*string - '0');
          break;
        }
      }
    }
    string++;
  }
  for (which = plus; which; which = (which == plus ? minus : 0)) {
    which->global &=USER_VALID;

    which->udef_global &= 0x03ffffff;
    which->chan &= CHAN_VALID;
    which->udef_chan &= 0x03ffffff;
    which->bot &= BOT_VALID;
  }
  plus->match |= flags;
  if (minus) {
    minus->match |= flags;
    if (!(plus->match & (FR_AND | FR_OR)))
      plus->match |= FR_OR;
  }
}

static int flag2str(char *string, int bot, int udef)
{
  char x = 'a', *old = string;

  while (bot && (x <= 'z')) {
    if (bot & 1)
      *string++ = x;
    x++;
    bot = bot >> 1;
  }
  x = 'A';
  while (udef && (x <= 'Z')) {
    if (udef & 1)
      *string++ = x;
    udef = udef >> 1;
    x++;
  }
  if (string == old)
    *string++ = '-';
  return string - old;
}

static int bot2str(char *string, int bot)
{
  char x = 'a', *old = string;

  while (x < 'v') {
    if (bot & 1)
      *string++ = x;
    x++;
    bot >>= 1;
  }
  x = '0';
  while (x <= '9') {
    if (bot & 1)
      *string++ = x;
    x++;
    bot >>= 1;
  }
  return string - old;
}

int build_flags(char *string, struct flag_record *plus,
                struct flag_record *minus)
{
  char *old = string;

  if (plus->match & FR_GLOBAL) {
    if (minus && (plus->global ||plus->udef_global))
      *string++ = '+';
    string += flag2str(string, plus->global, plus->udef_global);

    if (minus && (minus->global ||minus->udef_global)) {
      *string++ = '-';
      string += flag2str(string, minus->global, minus->udef_global);
    }
  } else if (plus->match & FR_BOT) {
    if (minus && plus->bot)
      *string++ = '+';
    string += bot2str(string, plus->bot);
    if (minus && minus->bot) {
      *string++ = '-';
      string += bot2str(string, minus->bot);
    }
  }
  if (plus->match & FR_CHAN) {
    if (plus->match & (FR_GLOBAL | FR_BOT))
      *string++ = (plus->match & FR_AND) ? '&' : '|';
    if (minus && (plus->chan || plus->udef_chan))
      *string++ = '+';
    string += flag2str(string, plus->chan, plus->udef_chan);
    if (minus && (minus->chan || minus->udef_chan)) {
      *string++ = '-';
      string += flag2str(string, minus->global, minus->udef_chan);
    }
  }
  if ((plus->match & (FR_BOT | FR_CHAN)) == (FR_BOT | FR_CHAN)) {
    *string++ = (plus->match & FR_AND) ? '&' : '|';
    if (minus && plus->bot)
      *string++ = '+';
    string += bot2str(string, plus->bot);
    if (minus && minus->bot) {
      *string++ = '-';
      string += bot2str(string, minus->bot);
    }
  }
  if (string == old) {
    *string++ = '-';
    *string = 0;
    return 0;
  }
  *string = 0;
  return string - old;
}

int flagrec_ok(struct flag_record *req, struct flag_record *have)
{
  /* FIXME: flag masks with '&' in them won't be subject to
   *        further tests below. Example: 'o&j'
   */
  if (req->match & FR_AND)
    return flagrec_eq(req, have);
  else if (req->match & FR_OR) {
    int hav = have->global;

    /* Exception 1 - global +d/+k cant use -|-, unless they are +p */
    if (!req->chan && !req->global && !req->udef_global && !req->udef_chan) {
      if (!allow_dk_cmds) {
        if (glob_party(*have))
          return 1;
        if (glob_kick(*have) || chan_kick(*have))
          return 0;             /* +k cant use -|- commands */
        if (glob_deop(*have) || chan_deop(*have))
          return 0;             /* neither can +d's */
      }
      return 1;
    }
    /* The +n/+m checks aren't needed anymore because +n/+m
     * automatically adds lower flags
     */
    if (!require_p && ((hav & USER_OP) || (have->chan & USER_OWNER)))
      hav |= USER_PARTY;
    if (hav & req->global)
      return 1;
    if (have->chan & req->chan)
      return 1;
    if (have->udef_global & req->udef_global)
      return 1;
    if (have->udef_chan & req->udef_chan)
      return 1;
    return 0;
  }
  return 0;                     /* fr0k3 binding, dont pass it */
}

int flagrec_eq(struct flag_record *req, struct flag_record *have)
{
  if (req->match & FR_AND) {
    if (req->match & FR_GLOBAL) {
      if ((req->global & have->global) !=req->global)
        return 0;
      if ((req->udef_global & have->udef_global) != req->udef_global)
        return 0;
    }
    if (req->match & FR_BOT)
      if ((req->bot & have->bot) != req->bot)
        return 0;
    if (req->match & FR_CHAN) {
      if ((req->chan & have->chan) != req->chan)
        return 0;
      if ((req->udef_chan & have->udef_chan) != req->udef_chan)
        return 0;
    }
    return 1;
  } else if (req->match & FR_OR) {
    if (!req->chan && !req->global && !req->udef_chan &&
        !req->udef_global && !req->bot)
      return 1;
    if (req->match & FR_GLOBAL) {
      if (have->global & req->global)
        return 1;
      if (have->udef_global & req->udef_global)
        return 1;
    }
    if (req->match & FR_BOT)
      if (have->bot & req->bot)
        return 1;
    if (req->match & FR_CHAN) {
      if (have->chan & req->chan)
        return 1;
      if (have->udef_chan & req->udef_chan)
        return 1;
    }
    return 0;
  }
  return 0;                     /* fr0k3 binding, dont pass it */
}

void set_user_flagrec(struct userrec *u, struct flag_record *fr,
                      const char *chname)
{
  struct chanuserrec *cr = NULL;
  int oldflags = fr->match;
  char buffer[100];
  struct chanset_t *ch;

  if (!u)
    return;
  if (oldflags & FR_GLOBAL) {
    u->flags = fr->global;

    u->flags_udef = fr->udef_global;
    if (!noshare && !(u->flags & USER_UNSHARED)) {
      fr->match = FR_GLOBAL;
      build_flags(buffer, fr, NULL);
      shareout(NULL, "a %s %s\n", u->handle, buffer);
    }
  }
  if ((oldflags & FR_BOT) && (u->flags & USER_BOT))
    set_user(&USERENTRY_BOTFL, u, (void *) fr->bot);
  /* Don't share bot attrs */
  if ((oldflags & FR_CHAN) && chname) {
    for (cr = u->chanrec; cr; cr = cr->next)
      if (!rfc_casecmp(chname, cr->channel))
        break;
    ch = findchan_by_dname(chname);
    if (!cr && ch) {
      cr = user_malloc(sizeof(struct chanuserrec));
      egg_bzero(cr, sizeof(struct chanuserrec));

      cr->next = u->chanrec;
      u->chanrec = cr;
      strncpyz(cr->channel, chname, sizeof cr->channel);
    }
    if (cr && ch) {
      cr->flags = fr->chan;
      cr->flags_udef = fr->udef_chan;
      if (!noshare && !(u->flags & USER_UNSHARED) && channel_shared(ch)) {
        fr->match = FR_CHAN;
        build_flags(buffer, fr, NULL);
        shareout(ch, "a %s %s %s\n", u->handle, buffer, chname);
      }
    }
  }
  fr->match = oldflags;
}

/* Always pass the dname (display name) to this function for chname <cybah>
 */
void get_user_flagrec(struct userrec *u, struct flag_record *fr,
                      const char *chname)
{
  struct chanuserrec *cr = NULL;

  if (!u) {
    fr->global = fr->udef_global = fr->chan = fr->udef_chan = fr->bot = 0;

    return;
  }
  if (fr->match & FR_GLOBAL) {
    fr->global = u->flags;

    fr->udef_global = u->flags_udef;
  } else {
    fr->global = 0;

    fr->udef_global = 0;
  }
  if (fr->match & FR_BOT) {
    fr->bot = (long) get_user(&USERENTRY_BOTFL, u);
  } else
    fr->bot = 0;
  if (fr->match & FR_CHAN) {
    if (fr->match & FR_ANYWH) {
      fr->chan = u->flags;
      fr->udef_chan = u->flags_udef;
      for (cr = u->chanrec; cr; cr = cr->next)
        if (findchan_by_dname(cr->channel)) {
          fr->chan |= cr->flags;
          fr->udef_chan |= cr->flags_udef;
        }
    } else {
      if (chname)
        for (cr = u->chanrec; cr; cr = cr->next)
          if (!rfc_casecmp(chname, cr->channel))
            break;
      if (cr) {
        fr->chan = cr->flags;
        fr->udef_chan = cr->flags_udef;
      } else {
        fr->chan = 0;
        fr->udef_chan = 0;
      }
    }
  }
}

static int botfl_unpack(struct userrec *u, struct user_entry *e)
{
  struct flag_record fr = { FR_BOT, 0, 0, 0, 0, 0 };

  break_down_flags(e->u.list->extra, &fr, NULL);
  list_type_kill(e->u.list);
  e->u.ulong = fr.bot;
  return 1;
}

static int botfl_pack(struct userrec *u, struct user_entry *e)
{
  char x[100];
  struct flag_record fr = { FR_BOT, 0, 0, 0, 0, 0 };

  fr.bot = e->u.ulong;
  e->u.list = user_malloc(sizeof(struct list_type));
  e->u.list->next = NULL;
  e->u.list->extra = user_malloc(build_flags(x, &fr, NULL) + 1);
  strcpy(e->u.list->extra, x);
  return 1;
}

static int botfl_kill(struct user_entry *e)
{
  nfree(e);
  return 1;
}

static int botfl_write_userfile(FILE *f, struct userrec *u,
                                struct user_entry *e)
{
  char x[100];
  struct flag_record fr = { FR_BOT, 0, 0, 0, 0, 0 };

  fr.bot = e->u.ulong;
  build_flags(x, &fr, NULL);
  if (fprintf(f, "--%s %s\n", e->type->name, x) == EOF)
    return 0;
  return 1;
}

static int botfl_set(struct userrec *u, struct user_entry *e, void *buf)
{
  register long atr = ((long) buf & BOT_VALID);

  if (!(u->flags & USER_BOT))
    return 1;                   /* Don't even bother trying to set the
                                 * flags for a non-bot */

  if ((atr & BOT_HUB) && (atr & BOT_ALT))
    atr &= ~BOT_ALT;
  if (atr & BOT_REJECT) {
    if (atr & BOT_SHARE)
      atr &= ~(BOT_SHARE | BOT_REJECT);
    if (atr & BOT_HUB)
      atr &= ~(BOT_HUB | BOT_REJECT);
    if (atr & BOT_ALT)
      atr &= ~(BOT_ALT | BOT_REJECT);
  }
  if (!(atr & BOT_SHARE))
    atr &= ~BOT_GLOBAL;
  e->u.ulong = atr;
  return 1;
}

static int botfl_tcl_get(Tcl_Interp *interp, struct userrec *u,
                         struct user_entry *e, int argc, char **argv)
{
  char x[100];
  struct flag_record fr = { FR_BOT, 0, 0, 0, 0, 0 };

  fr.bot = e->u.ulong;
  build_flags(x, &fr, NULL);
  Tcl_AppendResult(interp, x, NULL);
  return TCL_OK;
}

static int botfl_tcl_set(Tcl_Interp *irp, struct userrec *u,
                         struct user_entry *e, int argc, char **argv)
{
  struct flag_record fr = { FR_BOT, 0, 0, 0, 0, 0 };

  BADARGS(4, 4, " handle BOTFL flags");

  if (u->flags & USER_BOT) {
    /* Silently ignore for users */
    break_down_flags(argv[3], &fr, NULL);
    botfl_set(u, e, (void *) fr.bot);
  }
  return TCL_OK;
}

static int botfl_expmem(struct user_entry *e)
{
  return 0;
}

static void botfl_display(int idx, struct user_entry *e)
{
  struct flag_record fr = { FR_BOT, 0, 0, 0, 0, 0 };
  char x[100];

  fr.bot = e->u.ulong;
  build_flags(x, &fr, NULL);
  dprintf(idx, "  BOT FLAGS: %s\n", x);
}

struct user_entry_type USERENTRY_BOTFL = {
  0,                            /* always 0 ;) */
  0,
  def_dupuser,
  botfl_unpack,
  botfl_pack,
  botfl_write_userfile,
  botfl_kill,
  def_get,
  botfl_set,
  botfl_tcl_get,
  botfl_tcl_set,
  botfl_expmem,
  botfl_display,
  "BOTFL"
};
