/*
 * channels.c -- part of channels.mod
 *   support for channels within the bot
 *
 * $Id: channels.c,v 1.93 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.
 */

#define MODULE_NAME "channels"
#define MAKING_CHANNELS

#include <sys/stat.h>
#include "src/mod/module.h"

static Function *global = NULL;

static char chanfile[121], glob_chanmode[64];
static char *lastdeletedmask;

static struct udef_struct *udef;

static int setstatic, use_info, chan_hack, quiet_save, global_revenge_mode,
           global_stopnethack_mode, global_idle_kick, global_aop_min,
           global_aop_max, global_ban_time, global_exempt_time,
           global_invite_time;

/* Global channel settings (drummer/dw) */
static char glob_chanset[512];

/* Global flood settings */
static int gfld_chan_thr, gfld_chan_time, gfld_deop_thr, gfld_deop_time,
           gfld_kick_thr, gfld_kick_time, gfld_join_thr, gfld_join_time,
           gfld_ctcp_thr, gfld_ctcp_time, gfld_nick_thr, gfld_nick_time;

#include "channels.h"
#include "cmdschan.c"
#include "tclchan.c"
#include "userchan.c"
#include "udefchan.c"


static void *channel_malloc(int size, char *file, int line)
{
  char *p;

#ifdef DEBUG_MEM
  p = ((void *) (global[0] (size, MODULE_NAME, file, line)));
#else
  p = nmalloc(size);
#endif
  egg_bzero(p, size);
  return p;
}

static void set_mode_protect(struct chanset_t *chan, char *set)
{
  int i, pos = 1;
  char *s, *s1;

  /* Clear old modes */
  chan->mode_mns_prot = chan->mode_pls_prot = 0;
  chan->limit_prot = 0;
  chan->key_prot[0] = 0;
  for (s = newsplit(&set); *s; s++) {
    i = 0;
    switch (*s) {
    case '+':
      pos = 1;
      break;
    case '-':
      pos = 0;
      break;
    case 'i':
      i = CHANINV;
      break;
    case 'p':
      i = CHANPRIV;
      break;
    case 's':
      i = CHANSEC;
      break;
    case 'm':
      i = CHANMODER;
      break;
    case 'c':
      i = CHANNOCLR;
      break;
    case 'C':
      i = CHANNOCTCP;
      break;
    case 'R':
      i = CHANREGON;
      break;
    case 'M':
      i = CHANMODREG;
      break;
    case 'r':
      i = CHANLONLY;
      break;
    case 'D':
      i = CHANDELJN;
      break;
    case 'u':
      i = CHANSTRIP;
      break;
    case 'N':
      i = CHANNONOTC;
      break;
    case 't':
      i = CHANTOPIC;
      break;
    case 'n':
      i = CHANNOMSG;
      break;
    case 'a':
      i = CHANANON;
      break;
    case 'q':
      i = CHANQUIET;
      break;
    case 'l':
      i = CHANLIMIT;
      chan->limit_prot = 0;
      if (pos) {
        s1 = newsplit(&set);
        if (s1[0])
          chan->limit_prot = atoi(s1);
      }
      break;
    case 'k':
      i = CHANKEY;
      chan->key_prot[0] = 0;
      if (pos) {
        s1 = newsplit(&set);
        if (s1[0])
          strcpy(chan->key_prot, s1);
      }
      break;
    }
    if (i) {
      if (pos) {
        chan->mode_pls_prot |= i;
        chan->mode_mns_prot &= ~i;
      } else {
        chan->mode_pls_prot &= ~i;
        chan->mode_mns_prot |= i;
      }
    }
  }
  /* Prevents a +s-p +p-s flood  (fixed by drummer) */
  if (chan->mode_pls_prot & CHANSEC)
    chan->mode_pls_prot &= ~CHANPRIV;
}

static void get_mode_protect(struct chanset_t *chan, char *s)
{
  char *p = s, s1[121];
  int i, tst;

  s1[0] = 0;
  for (i = 0; i < 2; i++) {
    if (i == 0) {
      tst = chan->mode_pls_prot;
      if ((tst) || (chan->limit_prot != 0) || (chan->key_prot[0]))
        *p++ = '+';
      if (chan->limit_prot != 0) {
        *p++ = 'l';
        sprintf(&s1[strlen(s1)], "%d ", chan->limit_prot);
      }
      if (chan->key_prot[0]) {
        *p++ = 'k';
        sprintf(&s1[strlen(s1)], "%s ", chan->key_prot);
      }
    } else {
      tst = chan->mode_mns_prot;
      if (tst)
        *p++ = '-';
      if (tst & CHANKEY)
        *p++ = 'k';
      if (tst & CHANLIMIT)
        *p++ = 'l';
    }
    if (tst & CHANINV)
      *p++ = 'i';
    if (tst & CHANPRIV)
      *p++ = 'p';
    if (tst & CHANSEC)
      *p++ = 's';
    if (tst & CHANMODER)
      *p++ = 'm';
    if (tst & CHANNOCLR)
      *p++ = 'c';
    if (tst & CHANNOCTCP)
      *p++ = 'C';
    if (tst & CHANREGON)
      *p++ = 'R';
    if (tst & CHANMODREG)
      *p++ = 'M';
    if (tst & CHANLONLY)
      *p++ = 'r';
    if (tst & CHANDELJN)
      *p++ = 'D';
    if (tst & CHANSTRIP)
      *p++ = 'u';
    if (tst & CHANNONOTC)
      *p++ = 'N';
    if (tst & CHANTOPIC)
      *p++ = 't';
    if (tst & CHANNOMSG)
      *p++ = 'n';
    if (tst & CHANANON)
      *p++ = 'a';
    if (tst & CHANQUIET)
      *p++ = 'q';
  }
  *p = 0;
  if (s1[0]) {
    s1[strlen(s1) - 1] = 0;
    strcat(s, " ");
    strcat(s, s1);
  }
}

/* Returns true if this is one of the channel masks
 */
static int ismodeline(masklist *m, char *user)
{
  for (; m && m->mask[0]; m = m->next)
    if (!rfc_casecmp(m->mask, user))
      return 1;
  return 0;
}

/* Returns true if user matches one of the masklist -- drummer
 */
static int ismasked(masklist *m, char *user)
{
  for (; m && m->mask[0]; m = m->next)
    if (wild_match(m->mask, user))
      return 1;
  return 0;
}

/* Unlink chanset element from chanset list.
 */
inline static int chanset_unlink(struct chanset_t *chan)
{
  struct chanset_t *c, *c_old = NULL;

  for (c = chanset; c; c_old = c, c = c->next) {
    if (c == chan) {
      if (c_old)
        c_old->next = c->next;
      else
        chanset = c->next;
      return 1;
    }
  }
  return 0;
}

/* Completely removes a channel.
 *
 * This includes the removal of all channel-bans, -exempts and -invites, as
 * well as all user flags related to the channel.
 */
static void remove_channel(struct chanset_t *chan)
{
  int i;
  module_entry *me;

  /* Remove the channel from the list, so that noone can pull it
   * away from under our feet during the check_tcl_part() call. */
  (void) chanset_unlink(chan);

  if ((me = module_find("irc", 1, 3)) != NULL)
    (me->funcs[IRC_DO_CHANNEL_PART]) (chan);

  clear_channel(chan, 0);
  noshare = 1;
  /* Remove channel-bans */
  while (chan->bans)
    u_delban(chan, chan->bans->mask, 1);
  /* Remove channel-exempts */
  while (chan->exempts)
    u_delexempt(chan, chan->exempts->mask, 1);
  /* Remove channel-invites */
  while (chan->invites)
    u_delinvite(chan, chan->invites->mask, 1);
  /* Remove channel specific user flags */
  user_del_chan(chan->dname);
  noshare = 0;
  nfree(chan->channel.key);
  for (i = 0; i < MODES_PER_LINE_MAX && chan->cmode[i].op; i++)
    nfree(chan->cmode[i].op);
  if (chan->key)
    nfree(chan->key);
  if (chan->rmkey)
    nfree(chan->rmkey);
  nfree(chan);
}

/* Bind this to chon and *if* the users console channel == ***
 * then set it to a specific channel
 */
static int channels_chon(char *handle, int idx)
{
  struct flag_record fr = { FR_CHAN | FR_ANYWH | FR_GLOBAL, 0, 0, 0, 0, 0 };
  int find, found = 0;
  struct chanset_t *chan = chanset;

  if (dcc[idx].type == &DCC_CHAT) {
    if (!findchan_by_dname(dcc[idx].u.chat->con_chan) &&
        ((dcc[idx].u.chat->con_chan[0] != '*') ||
         (dcc[idx].u.chat->con_chan[1] != 0))) {
      get_user_flagrec(dcc[idx].user, &fr, NULL);
      if (glob_op(fr))
        found = 1;
      if (chan_owner(fr))
        find = USER_OWNER;
      else if (chan_master(fr))
        find = USER_MASTER;
      else
        find = USER_OP;
      fr.match = FR_CHAN;
      while (chan && !found) {
        get_user_flagrec(dcc[idx].user, &fr, chan->dname);
        if (fr.chan & find)
          found = 1;
        else
          chan = chan->next;
      }
      if (!chan)
        chan = chanset;
      if (chan)
        strcpy(dcc[idx].u.chat->con_chan, chan->dname);
      else
        strcpy(dcc[idx].u.chat->con_chan, "*");
    }
  }
  return 0;
}

static char *convert_element(char *src, char *dst)
{
  int flags;

  Tcl_ScanElement(src, &flags);
  Tcl_ConvertElement(src, dst, flags);
  return dst;
}

#define PLSMNS(x) (x ? '+' : '-')

/*
 * Note:
 *  - We write chanmode "" too, so that the bot won't use default-chanmode
 *    instead of ""
 *  - We will write empty need-xxxx too, why not? (less code + lazyness)
 */
static void write_channels()
{
  FILE *f;
  char s[121], w[1024], w2[1024], name[163];
  char need1[242], need2[242], need3[242], need4[242], need5[242];
  struct chanset_t *chan;
  struct udef_struct *ul;

  if (!chanfile[0])
    return;
  sprintf(s, "%s~new", chanfile);
  f = fopen(s, "w");
  chmod(s, userfile_perm);
  if (f == NULL) {
    putlog(LOG_MISC, "*", "ERROR writing channel file.");
    return;
  }
  if (!quiet_save)
    putlog(LOG_MISC, "*", "Writing channel file...");
  fprintf(f, "#Dynamic Channel File for %s (%s) -- written %s\n",
          botnetnick, ver, ctime(&now));
  for (chan = chanset; chan; chan = chan->next) {
    convert_element(chan->dname, name);
    get_mode_protect(chan, w);
    convert_element(w, w2);
    convert_element(chan->need_op, need1);
    convert_element(chan->need_invite, need2);
    convert_element(chan->need_key, need3);
    convert_element(chan->need_unban, need4);
    convert_element(chan->need_limit, need5);
    /* Do not indent me (adds extra spaces to chan file). */
    fprintf(f,
"channel %s %s%schanmode %s idle-kick %d stopnethack-mode %d revenge-mode %d \
need-op %s need-invite %s need-key %s need-unban %s need-limit %s \
flood-chan %d:%d flood-ctcp %d:%d flood-join %d:%d flood-kick %d:%d \
flood-deop %d:%d flood-nick %d:%d aop-delay %d:%d ban-time %d exempt-time %d \
invite-time %d %cenforcebans %cdynamicbans %cuserbans %cautoop %cautohalfop \
%cbitch %cgreet %cprotectops %cprotecthalfops %cprotectfriends %cdontkickops \
%cstatuslog %crevenge %crevengebot %cautovoice %csecret %cshared %ccycle \
%cseen %cinactive %cdynamicexempts %cuserexempts %cdynamicinvites \
%cuserinvites %cnodesynch ",
            channel_static(chan) ? "set" : "add", name, channel_static(chan) ?
            " " : " { ", w2, chan->idle_kick, chan->stopnethack_mode,
            chan->revenge_mode, need1, need2, need3, need4, need5,
            chan->flood_pub_thr, chan->flood_pub_time,
            chan->flood_ctcp_thr, chan->flood_ctcp_time,
            chan->flood_join_thr, chan->flood_join_time,
            chan->flood_kick_thr, chan->flood_kick_time,
            chan->flood_deop_thr, chan->flood_deop_time,
            chan->flood_nick_thr, chan->flood_nick_time,
            chan->aop_min, chan->aop_max, chan->ban_time,
            chan->exempt_time, chan->invite_time,
            PLSMNS(channel_enforcebans(chan)),
            PLSMNS(channel_dynamicbans(chan)),
            PLSMNS(!channel_nouserbans(chan)),
            PLSMNS(channel_autoop(chan)),
            PLSMNS(channel_autohalfop(chan)),
            PLSMNS(channel_bitch(chan)),
            PLSMNS(channel_greet(chan)),
            PLSMNS(channel_protectops(chan)),
            PLSMNS(channel_protecthalfops(chan)),
            PLSMNS(channel_protectfriends(chan)),
            PLSMNS(channel_dontkickops(chan)),
            PLSMNS(channel_logstatus(chan)),
            PLSMNS(channel_revenge(chan)),
            PLSMNS(channel_revengebot(chan)),
            PLSMNS(channel_autovoice(chan)),
            PLSMNS(channel_secret(chan)),
            PLSMNS(channel_shared(chan)),
            PLSMNS(channel_cycle(chan)),
            PLSMNS(channel_seen(chan)),
            PLSMNS(channel_inactive(chan)),
            PLSMNS(channel_dynamicexempts(chan)),
            PLSMNS(!channel_nouserexempts(chan)),
            PLSMNS(channel_dynamicinvites(chan)),
            PLSMNS(!channel_nouserinvites(chan)),
            PLSMNS(channel_nodesynch(chan)));
    fprintf(f, "%s\n", channel_static(chan) ? "" : "}");
    for (ul = udef; ul; ul = ul->next) {
      if (ul->defined && ul->name) {
        if (ul->type == UDEF_FLAG)
          fprintf(f, "channel set %s %c%s%s\n", name, getudef(ul->values, chan->dname) ? '+' : '-',
                  "udef-flag-", ul->name);
        else if (ul->type == UDEF_INT)
          fprintf(f, "channel set %s %s%s %d\n", name, "udef-int-", ul->name, getudef(ul->values,
                  chan->dname));
        else if (ul->type == UDEF_STR) {
          char *p = (char *) getudef(ul->values, chan->dname);

          if (!p)
            p = "{}";

          fprintf(f, "channel set %s udef-str-%s %s\n", name, ul->name, p);
        } else
          debug1("UDEF-ERROR: unknown type %d", ul->type);
      }
    }
    if (fflush(f)) {
      putlog(LOG_MISC, "*", "ERROR writing channel file.");
      fclose(f);
      return;
    }
  }
  fclose(f);
  unlink(chanfile);
  movefile(s, chanfile);
}

static void read_channels(int create)
{
  struct chanset_t *chan, *chan_next;

  if (!chanfile[0])
    return;
  for (chan = chanset; chan; chan = chan->next)
    if (!channel_static(chan))
      chan->status |= CHAN_FLAGGED;
  chan_hack = 1;
  if (!readtclprog(chanfile) && create) {
    FILE *f;

    /* Assume file isnt there & therfore make it */
    putlog(LOG_MISC, "*", "Creating channel file");
    f = fopen(chanfile, "w");
    if (!f)
      putlog(LOG_MISC, "*", "Couldn't create channel file: %s.  Dropping",
             chanfile);
    else
      fclose(f);
  }
  chan_hack = 0;
  for (chan = chanset; chan; chan = chan_next) {
    chan_next = chan->next;
    if (chan->status & CHAN_FLAGGED) {
      putlog(LOG_MISC, "*", "No longer supporting channel %s", chan->dname);
      remove_channel(chan);
    }
  }
}

static void backup_chanfile()
{
  char s[125];

  putlog(LOG_MISC, "*", "Backing up channel file...");
  egg_snprintf(s, sizeof s, "%s~bak", chanfile);
  copyfile(chanfile, s);
}

static void channels_prerehash()
{
  struct chanset_t *chan;

  /* Flag will be cleared as the channels are re-added by the
   * config file. Any still flagged afterwards will be removed.
   */
  for (chan = chanset; chan; chan = chan->next) {
    chan->status |= CHAN_FLAGGED;
    /* Flag is only added to channels read from config file */
    if (chan->status & CHAN_STATIC)
      chan->status &= ~CHAN_STATIC;
  }
  setstatic = 1;
}

static void channels_rehash()
{
  struct chanset_t *chan;

  setstatic = 0;
  read_channels(1);
  /* Remove any extra channels, by checking the flag. */
  chan = chanset;
  for (chan = chanset; chan;) {
    if (chan->status & CHAN_FLAGGED) {
      putlog(LOG_MISC, "*", "No longer supporting channel %s", chan->dname);
      remove_channel(chan);
      chan = chanset;
    } else
      chan = chan->next;
  }
}

static cmd_t my_chon[] = {
  {"*",  "",   (Function) channels_chon, "channels:chon"},
  {NULL, NULL, NULL,                                NULL}
};

static void channels_report(int idx, int details)
{
  struct chanset_t *chan;
  int i;
  char s[1024], s2[100];
  struct flag_record fr = { FR_CHAN | FR_GLOBAL, 0, 0, 0, 0, 0 };

  for (chan = chanset; chan; chan = chan->next) {
    if (idx != DP_STDOUT)
      get_user_flagrec(dcc[idx].user, &fr, chan->dname);
    if ((idx == DP_STDOUT) || glob_master(fr) || chan_master(fr)) {
      s[0] = 0;
      if (channel_greet(chan))
        strcat(s, "greet, ");
      if (channel_autoop(chan))
        strcat(s, "auto-op, ");
      if (channel_bitch(chan))
        strcat(s, "bitch, ");
      if (s[0])
        s[strlen(s) - 2] = 0;
      if (!s[0])
        strcpy(s, MISC_LURKING);
      get_mode_protect(chan, s2);
      if (!channel_inactive(chan)) {
        if (channel_active(chan)) {
          /* If it's a !chan, we want to display it's unique name too <cybah> */
          if (chan->dname[0] == '!') {
            dprintf(idx, "    %-20s: %3d member%s enforcing \"%s\" (%s), "
                    "unique name %s\n", chan->dname, chan->channel.members,
                    (chan->channel.members == 1) ? "," : "s,", s2, s,
                    chan->name);
          } else {
            dprintf(idx, "    %-20s: %3d member%s enforcing \"%s\" (%s)\n",
                    chan->dname, chan->channel.members,
                    chan->channel.members == 1 ? "," : "s,", s2, s);
          }
        } else {
          dprintf(idx, "    %-20s: (%s), enforcing \"%s\"  (%s)\n", chan->dname,
                  channel_pending(chan) ? "pending" : "not on channel", s2, s);
        }
      } else {
        dprintf(idx, "    %-20s: channel is set +inactive\n", chan->dname);
      }
      if (details) {
        s[0] = 0;
        i = 0;
        if (channel_enforcebans(chan))
          i += my_strcpy(s + i, "enforcebans ");
        if (channel_dynamicbans(chan))
          i += my_strcpy(s + i, "dynamicbans ");
        if (!channel_nouserbans(chan))
          i += my_strcpy(s + i, "userbans ");
        if (channel_autoop(chan))
          i += my_strcpy(s + i, "autoop ");
        if (channel_bitch(chan))
          i += my_strcpy(s + i, "bitch ");
        if (channel_greet(chan))
          i += my_strcpy(s + i, "greet ");
        if (channel_protectops(chan))
          i += my_strcpy(s + i, "protectops ");
        if (channel_protecthalfops(chan))
          i += my_strcpy(s + i, "protecthalfops ");
        if (channel_protectfriends(chan))
          i += my_strcpy(s + i, "protectfriends ");
        if (channel_dontkickops(chan))
          i += my_strcpy(s + i, "dontkickops ");
        if (channel_logstatus(chan))
          i += my_strcpy(s + i, "statuslog ");
        if (channel_revenge(chan))
          i += my_strcpy(s + i, "revenge ");
        if (channel_revenge(chan))
          i += my_strcpy(s + i, "revengebot ");
        if (channel_secret(chan))
          i += my_strcpy(s + i, "secret ");
        if (channel_shared(chan))
          i += my_strcpy(s + i, "shared ");
        if (!channel_static(chan))
          i += my_strcpy(s + i, "dynamic ");
        if (channel_autovoice(chan))
          i += my_strcpy(s + i, "autovoice ");
        if (channel_autohalfop(chan))
          i += my_strcpy(s + i, "autohalfop ");
        if (channel_cycle(chan))
          i += my_strcpy(s + i, "cycle ");
        if (channel_seen(chan))
          i += my_strcpy(s + i, "seen ");
        if (channel_dynamicexempts(chan))
          i += my_strcpy(s + i, "dynamicexempts ");
        if (!channel_nouserexempts(chan))
          i += my_strcpy(s + i, "userexempts ");
        if (channel_dynamicinvites(chan))
          i += my_strcpy(s + i, "dynamicinvites ");
        if (!channel_nouserinvites(chan))
          i += my_strcpy(s + i, "userinvites ");
        if (channel_inactive(chan))
          i += my_strcpy(s + i, "inactive ");
        if (channel_nodesynch(chan))
          i += my_strcpy(s + i, "nodesynch ");
        dprintf(idx, "      Options: %s\n", s);
        if (chan->need_op[0])
          dprintf(idx, "      To get ops, I do: %s\n", chan->need_op);
        if (chan->need_invite[0])
          dprintf(idx, "      To get invited, I do: %s\n", chan->need_invite);
        if (chan->need_limit[0])
          dprintf(idx, "      To get the channel limit raised, I do: %s\n",
                  chan->need_limit);
        if (chan->need_unban[0])
          dprintf(idx, "      To get unbanned, I do: %s\n", chan->need_unban);
        if (chan->need_key[0])
          dprintf(idx, "      To get the channel key, I do: %s\n",
                  chan->need_key);
        if (chan->idle_kick)
          dprintf(idx, "      Kicking idle users after %d minute%s\n",
                  chan->idle_kick, (chan->idle_kick != 1) ? "s" : "");
        if (chan->stopnethack_mode)
          dprintf(idx, "      stopnethack-mode: %d\n", chan->stopnethack_mode);
        if (chan->revenge_mode)
          dprintf(idx, "      revenge-mode: %d\n", chan->revenge_mode);
        dprintf(idx, "      Bans last %d minute%s.\n", chan->ban_time,
                 (chan->ban_time != 1) ? "s" : "");
        dprintf(idx, "      Exemptions last %d minute%s.\n", chan->exempt_time,
                 (chan->exempt_time != 1) ? "s" : "");
        dprintf(idx, "      Invitations last %d minute%s.\n", chan->invite_time,
                 (chan->invite_time != 1) ? "s" : "");
      }
    }
  }
}

static int expmem_masklist(masklist *m)
{
  int result = 0;

  for (; m; m = m->next) {
    result += sizeof(masklist);
    if (m->mask)
      result += strlen(m->mask) + 1;
    if (m->who)
      result += strlen(m->who) + 1;
  }
  return result;
}

static int channels_expmem()
{
  int tot = 0, i;
  struct chanset_t *chan;

  for (chan = chanset; chan; chan = chan->next) {
    tot += sizeof(struct chanset_t);

    tot += strlen(chan->channel.key) + 1;
    if (chan->channel.topic)
      tot += strlen(chan->channel.topic) + 1;
    tot += (sizeof(struct memstruct) * (chan->channel.members + 1));

    tot += expmem_masklist(chan->channel.ban);
    tot += expmem_masklist(chan->channel.exempt);
    tot += expmem_masklist(chan->channel.invite);

    for (i = 0; i < MODES_PER_LINE_MAX && chan->cmode[i].op; i++)
      tot += strlen(chan->cmode[i].op) + 1;
    if (chan->key)
      tot += strlen(chan->key) + 1;
    if (chan->rmkey)
      tot += strlen(chan->rmkey) + 1;
  }
  tot += expmem_udef(udef);
  if (lastdeletedmask)
    tot += strlen(lastdeletedmask) + 1;
  return tot;
}

static char *traced_globchanset(ClientData cdata, Tcl_Interp *irp,
                                EGG_CONST char *name1,
                                EGG_CONST char *name2, int flags)
{
  int i, items;
  char *t, *s;
  EGG_CONST char **item, *s2;

  if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) {
    Tcl_SetVar2(interp, name1, name2, glob_chanset, TCL_GLOBAL_ONLY);
    if (flags & TCL_TRACE_UNSETS)
      Tcl_TraceVar(interp, "global-chanset",
                   TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                   traced_globchanset, NULL);
  } else {                        /* Write */
    s2 = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY);
    Tcl_SplitList(interp, s2, &items, &item);
    for (i = 0; i < items; i++) {
      if (!(item[i]) || (strlen(item[i]) < 2))
        continue;
      s = glob_chanset;
      while (s[0]) {
        t = strchr(s, ' ');     /* Can't be NULL coz of the extra space */
        t[0] = 0;
        if (!strcmp(s + 1, item[i] + 1)) {
          s[0] = item[i][0];    /* +- */
          t[0] = ' ';
          break;
        }
        t[0] = ' ';
        s = t + 1;
      }
    }
    if (item)                   /* hmm it cant be 0 */
      Tcl_Free((char *) item);
    Tcl_SetVar2(interp, name1, name2, glob_chanset, TCL_GLOBAL_ONLY);
  }
  return NULL;
}

static tcl_ints my_tcl_ints[] = {
  {"share-greet",             NULL,                     0},
  {"use-info",                &use_info,                0},
  {"quiet-save",              &quiet_save,              0},
  {"global-stopnethack-mode", &global_stopnethack_mode, 0},
  {"global-revenge-mode",     &global_revenge_mode,     0},
  {"global-idle-kick",        &global_idle_kick,        0},
  {"global-ban-time",         &global_ban_time,         0},
  {"global-exempt-time",      &global_exempt_time,      0},
  {"global-invite-time",      &global_invite_time,      0},
  /* keeping [ban|exempt|invite]-time for compatability <Wcc[07/20/02]> */
  {"ban-time",                &global_ban_time,         0},
  {"exempt-time",             &global_exempt_time,      0},
  {"invite-time",             &global_invite_time,      0},
  {NULL,                      NULL,                     0}
};

static tcl_coups mychan_tcl_coups[] = {
  {"global-flood-chan", &gfld_chan_thr,  &gfld_chan_time},
  {"global-flood-deop", &gfld_deop_thr,  &gfld_deop_time},
  {"global-flood-kick", &gfld_kick_thr,  &gfld_kick_time},
  {"global-flood-join", &gfld_join_thr,  &gfld_join_time},
  {"global-flood-ctcp", &gfld_ctcp_thr,  &gfld_ctcp_time},
  {"global-flood-nick", &gfld_nick_thr,  &gfld_nick_time},
  {"global-aop-delay",  &global_aop_min, &global_aop_max},
  {NULL,                NULL,                       NULL}
};

static tcl_strings my_tcl_strings[] = {
  {"chanfile",        chanfile,      120, STR_PROTECT},
  {"global-chanmode", glob_chanmode, 64,            0},
  {NULL,              NULL,          0,             0}
};

static char *channels_close()
{
  write_channels();
  free_udef(udef);
  if (lastdeletedmask)
    nfree(lastdeletedmask);
  rem_builtins(H_chon, my_chon);
  rem_builtins(H_dcc, C_dcc_irc);
  rem_tcl_commands(channels_cmds);
  rem_tcl_strings(my_tcl_strings);
  rem_tcl_ints(my_tcl_ints);
  rem_tcl_coups(mychan_tcl_coups);
  del_hook(HOOK_USERFILE, (Function) channels_writeuserfile);
  del_hook(HOOK_BACKUP, (Function) backup_chanfile);
  del_hook(HOOK_REHASH, (Function) channels_rehash);
  del_hook(HOOK_PRE_REHASH, (Function) channels_prerehash);
  del_hook(HOOK_MINUTELY, (Function) check_expired_bans);
  del_hook(HOOK_MINUTELY, (Function) check_expired_exempts);
  del_hook(HOOK_MINUTELY, (Function) check_expired_invites);
  Tcl_UntraceVar(interp, "global-chanset",
                 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                 traced_globchanset, NULL);
  rem_help_reference("channels.help");
  rem_help_reference("chaninfo.help");
  module_undepend(MODULE_NAME);
  return NULL;
}

EXPORT_SCOPE char *channels_start();

static Function channels_table[] = {
  /* 0 - 3 */
  (Function) channels_start,
  (Function) channels_close,
  (Function) channels_expmem,
  (Function) channels_report,
  /* 4 - 7 */
  (Function) u_setsticky_mask,
  (Function) u_delban,
  (Function) u_addban,
  (Function) write_bans,
  /* 8 - 11 */
  (Function) get_chanrec,
  (Function) add_chanrec,
  (Function) del_chanrec,
  (Function) set_handle_chaninfo,
  /* 12 - 15 */
  (Function) channel_malloc,
  (Function) u_match_mask,
  (Function) u_equals_mask,
  (Function) clear_channel,
  /* 16 - 19 */
  (Function) set_handle_laston,
  (Function) NULL,           /* [17] used to be ban_time <Wcc[07/19/02]>    */
  (Function) & use_info,
  (Function) get_handle_chaninfo,
  /* 20 - 23 */
  (Function) u_sticky_mask,
  (Function) ismasked,
  (Function) add_chanrec_by_handle,
  (Function) NULL,           /* [23] used to be isexempted() <cybah>         */
  /* 24 - 27 */
  (Function) NULL,           /* [24] used to be exempt_time <Wcc[07/19/02]>  */
  (Function) NULL,           /* [25] used to be isinvited() <cybah>          */
  (Function) NULL,           /* [26] used to be ban_time <Wcc[07/19/02]>     */
  (Function) NULL,
  /* 28 - 31 */
  (Function) NULL,           /* [28] used to be u_setsticky_exempt() <cybah> */
  (Function) u_delexempt,
  (Function) u_addexempt,
  (Function) NULL,
  /* 32 - 35 */
  (Function) NULL,           /* [32] used to be u_sticky_exempt() <cybah>    */
  (Function) NULL,
  (Function) NULL,           /* [34] used to be killchanset().               */
  (Function) u_delinvite,
  /* 36 - 39 */
  (Function) u_addinvite,
  (Function) tcl_channel_add,
  (Function) tcl_channel_modify,
  (Function) write_exempts,
  /* 40 - 43 */
  (Function) write_invites,
  (Function) ismodeline,
  (Function) initudef,
  (Function) ngetudef,
  /* 44 - 47 */
  (Function) expired_mask,
  (Function) remove_channel,
  (Function) & global_ban_time,
  (Function) & global_exempt_time,
  /* 48 - 51 */
  (Function) & global_invite_time,
};

char *channels_start(Function *global_funcs)
{
  global = global_funcs;

  gfld_chan_thr = 10;
  gfld_chan_time = 60;
  gfld_deop_thr = 3;
  gfld_deop_time = 10;
  gfld_kick_thr = 3;
  gfld_kick_time = 10;
  gfld_join_thr = 5;
  gfld_join_time = 60;
  gfld_ctcp_thr = 5;
  gfld_ctcp_time = 60;
  global_idle_kick = 0;
  global_aop_min = 5;
  global_aop_max = 30;
  setstatic = 0;
  lastdeletedmask = 0;
  use_info = 1;
  strcpy(chanfile, "chanfile");
  chan_hack = 0;
  quiet_save = 0;
  strcpy(glob_chanmode, "nt");
  udef = NULL;
  global_stopnethack_mode = 0;
  global_revenge_mode = 0;
  global_ban_time = 120;
  global_exempt_time = 60;
  global_invite_time = 60;
  strcpy(glob_chanset,
         "-enforcebans "
         "+dynamicbans "
         "+userbans "
         "-autoop "
         "-bitch "
         "+greet "
         "+protectops "
         "+statuslog "
         "-revenge "
         "-secret "
         "-autovoice "
         "+cycle "
         "+dontkickops "
         "-inactive "
         "-protectfriends "
         "+shared "
         "-seen "
         "+userexempts "
         "+dynamicexempts "
         "+userinvites "
         "+dynamicinvites "
         "-revengebot "
         "-protecthalfops "
         "-autohalfop "
         "-nodesynch ");
  module_register(MODULE_NAME, channels_table, 1, 0);
  if (!module_depend(MODULE_NAME, "eggdrop", 106, 7)) {
    module_undepend(MODULE_NAME);
    return "This module requires Eggdrop 1.6.7 or later.";
  }
  add_hook(HOOK_MINUTELY, (Function) check_expired_bans);
  add_hook(HOOK_MINUTELY, (Function) check_expired_exempts);
  add_hook(HOOK_MINUTELY, (Function) check_expired_invites);
  add_hook(HOOK_USERFILE, (Function) channels_writeuserfile);
  add_hook(HOOK_BACKUP, (Function) backup_chanfile);
  add_hook(HOOK_REHASH, (Function) channels_rehash);
  add_hook(HOOK_PRE_REHASH, (Function) channels_prerehash);
  Tcl_TraceVar(interp, "global-chanset",
               TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
               traced_globchanset, NULL);
  add_builtins(H_chon, my_chon);
  add_builtins(H_dcc, C_dcc_irc);
  add_tcl_commands(channels_cmds);
  add_tcl_strings(my_tcl_strings);
  add_help_reference("channels.help");
  add_help_reference("chaninfo.help");
  my_tcl_ints[0].val = &share_greet;
  add_tcl_ints(my_tcl_ints);
  add_tcl_coups(mychan_tcl_coups);
  read_channels(0);
  setstatic = 1;
  return NULL;
}
