/*
   chanprog.c -- handles:
     rmspace()
     maintaining the server list
     revenge punishment
     timers, utimers
     telling the current programmed settings
     initializing a lot of stuff and loading the tcl scripts

   dprintf'ized, 1nov95
*/
/*
   This file is part of the eggdrop source code
   copyright (c) 1997 Robey Pointer
   and is distributed according to the GNU general public license.
   For full details, read the top of 'main.c' or the file called
   COPYING that was distributed with this code.
*/

/* config file format changed 27jan94 (Tcl outdates that) */

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>   /* for mips */
#include "../lush.h"
#include "eggdrop.h"
#include "users.h"
#include "chan.h"
#include "proto.h"
#include "tclegg.h"

extern int serv;
extern int shtime;
extern int op_on_join;
extern int telnet_port;
extern int min_servs;
extern int curserv;
extern char botname[];
extern char origbotname[];
extern char botuser[];
extern char bothost[];
extern char botrealname[];
extern char botserver[];
extern char configfile[];
extern char dccdir[];
extern char dccin[];
extern char motdfile[];
extern char admin[];
extern char userfile[];
extern char helpdir[];
extern char initserver[];
extern char notify_new[];
extern char tempdir[];
extern char ctcp_version[];
extern char ctcp_finger[];
extern char ctcp_userinfo[];
extern char textdir[];
extern char owner[];
extern char firewall[];
extern char altnick[];
extern int botserverport;
extern int dcc_total;
extern int use_stderr;
extern int learn_users;
extern int flood_thr;
extern int flood_time;
extern int flood_pub_thr;
extern int flood_pub_time;
extern int flood_join_thr;
extern int flood_join_time;
extern int flood_ctcp_thr;
extern int flood_ctcp_time;
extern int require_x;
extern int share_users;
extern int use_info;
extern int passive;
extern int strict_host;
extern int noshare;
extern int require_p;
extern int isolate;
extern int conmask;
extern int default_flags;
extern int keep_all_logs;
extern int ban_time;
extern int ignore_time;
extern int make_userfile;
extern int upload_to_cd;
extern int dcc_limit;
extern int never_give_up;
extern int allow_new_telnets;
extern int keepnick;
extern int dcc_block;
extern int dcc_maxsize;
extern int dcc_users;
extern int firewallport;
extern int telnet_bots_only;
extern struct dcc_t dcc[];
extern char *logfile[];
extern char *logchan[];
extern int logmask[];
extern char cx_file[];
extern int cx_line;
extern struct userrec *userlist;
extern char SBUF[];
extern struct chanset_t *chanset;
extern Tcl_Interp *interp;


/* used when rehash-ing */
char oldnick[NICKLEN]="";
int nulluser=0;
/* where to store notes */
char notefile[121];
/* old-style queue, still used by server list */
struct eggqueue *serverlist=NULL;
/* timers (minutely) and utimers (secondly) */
tcl_timer_t *timer=NULL,*utimer=NULL;
/* next timer of any sort will have this number */
unsigned long timer_id=1;

/* remove space characters from beginning and end of string */
/* (more efficent by Fred1) */
void rmspace(s)
char *s;
{
#define whitespace(c) ( ((c)==32) || ((c)==9) || ((c)==13) || ((c)==10) )
  char *p;
  /* wipe end of string */
  for (p=s+strlen(s)-1; ((whitespace(*p))&&(p>=s)); p--); 
  if (p!=s+strlen(s)-1) *(p+1)=0;
  for (p=s; ((whitespace(*p)) && (*p)); p++); if (p!=s) strcpy(s,p);
}

/* memory we should be using */
int expmem_chanprog()
{
  int tot; struct eggqueue *s=serverlist; tcl_timer_t *t;
  tot=0;
  while (s!=NULL) {
    tot+=strlen(s->item)+1;
    tot+=sizeof(struct eggqueue);
    s=s->next;
  }
  t=timer; while (t!=NULL) {
    tot+=sizeof(tcl_timer_t); tot+=strlen(t->cmd)+1;
    t=t->next;
  }
  t=utimer; while (t!=NULL) {
    tot+=sizeof(tcl_timer_t); tot+=strlen(t->cmd)+1;
    t=t->next;
  }
  return tot;
}

/* add someone to a queue */
struct eggqueue *addq(ss,q)
char *ss; struct eggqueue *q;
{
  char s[512]; struct eggqueue *x,*z; char s1[121],*p;
  strcpy(s,ss);
  do {
    p=strchr(s,','); if (p!=NULL) { *p=0; p++; strcpy(s1,p); } else s1[0]=0;
    rmspace(s); rmspace(s1);
    x=(struct eggqueue *)nmalloc(sizeof(struct eggqueue));
    x->next=NULL;
    x->item=(char *)nmalloc(strlen(s)+1);
    strcpy(x->item,s);
    if (q==NULL) q=x;
    else {
      z=q; while (z->next!=NULL) z=z->next;
      z->next=x;
    }
    s[0]=0; strcpy(s,s1);
  } while (s[0]);
  return q;
}

/* remove someone from a queue */
struct eggqueue *delq(s,q,ok)
char *s; struct eggqueue *q; int *ok;
{
  struct eggqueue *x,*ret,*old;
  x=q; ret=q; old=q; *ok=0;
  while (x!=NULL) {
    if (strcasecmp(x->item,s)==0) {
      if (x==ret) {
	ret=(x->next);
	nfree(x->item); nfree(x);
	x=ret;
      }
      else {
	old->next=x->next;
	nfree(x->item); nfree(x); x=old->next;
      }
      *ok=1;
    }
    else { old=x; x=x->next; }
  }
  return ret;
}

/* clear out a list */
void clearq(xx)
struct eggqueue *xx;
{
  struct eggqueue *x,*x1;
  x=xx; while (x!=NULL) {
    x1=x->next; nfree(x->item); nfree(x);
    x=x1;
  }
}

/* new server to the list */
void add_server(s) char *s; { serverlist=addq(s,serverlist); }

/* set botserver to the next available server */
/* -> if (*ptr == -1) then jump to that particular server */
void next_server(ptr,serv,port,pass)
int *ptr,*port; char *serv,*pass;
{
  struct eggqueue *x=serverlist; int ok=1,i; char s[121];
  context;
  if (x==NULL) return;
  /* -1  -->  go to specified server */
  if (*ptr == -1) {
    char sv[121]; int p;
    ok=0; i=0;
    while ((x!=NULL) && (!ok)) {
      strcpy(s,x->item); splitc(sv,s,':');
      if (!sv[0]) { strcpy(sv,s); p=DEFAULT_PORT; }
      else { p=atoi(s); }
      if ((strcasecmp(sv,serv)==0) && (p==*port)) ok=1;
      else { x=x->next; i++; }
    }
    if (ok) { *ptr=i; return; }  /* requested server is valid */
    /* gotta add it : */
    sprintf(s,"%s:%d",serv,*port);
    if (pass[0]) { strcat(s,":"); strcat(s,pass); }
    add_server(s); *ptr=i;
    return;
  }
  /* find where i am and boogey */
  i=(*ptr);
  while ((i>0) && (x!=NULL)) { x=x->next; i--; }
  if (x!=NULL) { x=x->next; (*ptr)++; }     /* go to next server */
  if (x==NULL) { x=serverlist; *ptr=0; }    /* start over at the beginning */
  pass[0]=0; strcpy(s,x->item); splitc(serv,s,':');
  if (!serv[0]) { strcpy(serv,s); *port=DEFAULT_PORT; }
  else {
    char xs[121];
    *port=atoi(s); splitc(xs,s,':');
    if (xs[0]) strcpy(pass,s);
  }
  return;
}

/* 001: welcome to IRC (use it to fix the server name) */
void got001(from,msg)
char *from,*msg;
{
  struct eggqueue *x; int i; char s[121],s1[121],srv[121];
  context;
  /* init-server */
  if (initserver[0]) do_tcl("init-server",initserver);
  x=serverlist;
  if (x==NULL) return;   /* uh, no server list */
  if (strcasecmp(from,botserver)!=0) {
    putlog(LOG_MISC,"*","(%s is really %s; updating server list)",
	   botserver,from);
    for(i=curserv; i>0 && x!=NULL; i--) x=x->next;
    if (x==NULL) {
      putlog(LOG_MISC,"*","Invalid server list!");
      return;
    }
    strcpy(s,x->item); splitc(srv,s,':');
    if (!srv[0]) { strcpy(srv,s); sprintf(s,"%d",DEFAULT_PORT); }
    sprintf(s1,"%s:%s",from,s);
    nfree(x->item); x->item=(char *)nmalloc(strlen(s1)+1);
    strcpy(x->item,s1);
    strcpy(botserver,from);
  }
}

/* show server list, and point out which one the bot is on */
void tell_servers(idx)
int idx;
{
  struct eggqueue *x=serverlist; int i,sp; char s[141],sv[121];
  context;
  if (x==NULL) {
    dprintf(idx,"No servers.\n"); return;
  }
  dprintf(idx,"My server list:\n"); i=0;
  while (x!=NULL) {
    strcpy(s,x->item); splitc(sv,s,':'); 
    if (!sv[0]) { strcpy(sv,s); sp=DEFAULT_PORT; }
    else sp=atoi(s);
    sprintf(s,"  %s:%d",sv,sp);
    if (i==curserv) strcat(s,"   <- I am here.");
    dprintf(idx,"%s\n",s); x=x->next; i++;
  }
}

void wipe_serverlist()
{
  if (serverlist==NULL) return;
  clearq(serverlist); serverlist=NULL;
}

/* revenge tactic: person did something bad */
/* if they're oplisted, remove them from the op list */
/* otherwise, deop them */
void take_revenge(chan,who,reason)
struct chanset_t *chan; char *who; char *reason;
{
  char nick[NICKLEN],s[UHOSTLEN],s1[UHOSTLEN],ct[81]; int i,atr; time_t tm;
  context;
  atr=get_attr_host(who);
  if (atr & USER_FRIEND) {
    putlog(LOG_MISC,"*","%s is a friend (%s)",who,reason);
    return;  /* argh! */
  }
  if (change_attr(who,'-',USER_OP)) {
    putlog(LOG_MISC,"*","No longer opping %s (%s)",who,reason);
    recheck_channels();
    return;
  }
  if ((match_ban(who)) || (u_match_ban(chan->bans,who))) {
    /* what more can we do? */
    return;
  }
  /* get current time into a string */
  tm=time(NULL); strcpy(ct,ctime(&tm));
  ct[10]=0; ct[16]=0; strcpy(ct,&ct[8]);
  strcpy(&ct[2],&ct[3]); strcpy(&ct[7],&ct[10]);
  if (atr & USER_DEOP) {
    /* this is out of control: BAN THEM */
    putlog(LOG_MISC,"*","Now banning %s (%s)",who,reason);
    strcpy(s1,who); splitnick(nick,s1); maskhost(s1,s);
    strcpy(s1,"*!*"); strcat(s1,&s[2]);   /* add extra * for ban */
    sprintf(s,"(%s) %s",ct,reason);
    u_addban(chan->bans,s1,origbotname,s,time(NULL)+(60*ban_time));
    if (me_op(chan)) add_mode(chan,'+','b',s1);
    recheck_channels();
    return;
  }
  if (change_attr(who,'+',USER_DEOP)) {
    /* in the user list already, cool :) */
    sprintf(s,"(%s) %s",ct,reason);
    putlog(LOG_MISC,"*","Now deopping %s (%s)",who,s);
    recheck_channels();
    return;
  }
  strcpy(s1,who); splitnick(nick,s1);
  maskhost(s1,s);
  while (is_user(nick)) {
    if (strncmp(nick,"bad",3)==0) {
      i=atoi(&nick[3]); sprintf(nick,"bad%d",i+1);
    }
    else strcpy(nick,"bad1");
  }
  userlist=adduser(userlist,nick,s,"-",USER_DEOP);
  sprintf(s,"(%s) %s (%s)",ct,reason,who);
  set_handle_comment(userlist,nick,s);
  putlog(LOG_MISC,"*","Now deopping %s (%s)",who,reason);
  recheck_channels();
}

/* clear out the programming */
void clearprog()
{
  context;
  clear_userlist(userlist); userlist=NULL;
  clearq(serverlist); serverlist=NULL;
  strcpy(oldnick,botname);
}

int logmodes(s)
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;
#ifdef USE_CONSOLE_R
  case 'r': case 'R': res|=LOG_RAW; break;
#endif
  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 '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(x)
int x;
{
  static char s[20]; 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';
#ifdef USE_CONSOLE_R
  if (x&LOG_RAW) *p++='r';
#endif
  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_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';
  *p=0;
  return s;
}

char *maskname(x)
int x;
{
  static char s[161];
  s[0]=0;
  if (x&LOG_MSGS) strcat(s,"msgs, ");
  if (x&LOG_PUBLIC) strcat(s,"public, ");
  if (x&LOG_JOIN) strcat(s,"joins, ");
  if (x&LOG_MODES) strcat(s,"kicks/modes, ");
  if (x&LOG_CMDS) strcat(s,"cmds, ");
  if (x&LOG_MISC) strcat(s,"misc, ");
  if (x&LOG_BOTS) strcat(s,"bots, ");
#ifdef USE_CONSOLE_R
  if (x&LOG_RAW) strcat(s,"raw, ");
#endif
  if (x&LOG_FILES) strcat(s,"files, ");
  if (x&LOG_SERV) strcat(s,"server, ");
  if (x&LOG_DEBUG) strcat(s,"debug, ");
  if (x&LOG_WALL) strcat(s,"wallops, ");
  if (x&LOG_LEV1) strcat(s,"level 1, ");
  if (x&LOG_LEV2) strcat(s,"level 2, ");
  if (x&LOG_LEV3) strcat(s,"level 3, ");
  if (x&LOG_LEV4) strcat(s,"level 4, ");
  if (x&LOG_LEV5) strcat(s,"level 5, ");
  if (x&LOG_LEV6) strcat(s,"level 6, ");
  if (x&LOG_LEV7) strcat(s,"level 7, ");
  if (x&LOG_LEV8) strcat(s,"level 8, ");
  if (!s[0]) strcpy(s,"none, ");
  s[strlen(s)-2]=0;
  return s;
}

/* show all internal state variables */
void tell_settings(idx)
int idx;
{
  char s[256]; int i;
#ifndef NO_IRC
  struct chanset_t *chan;
#endif
  dprintf(idx,"%s!%s@%s (%s)\n",origbotname,botuser,bothost,botrealname);
  if (firewall[0])
    dprintf(idx,"Firewall: %s, port %d\n",firewall,firewallport);
#ifndef NO_IRC
  /* channels */
  chan=chanset; while (chan!=NULL) {
    dprintf(idx,"Channel %s:  ",chan->name);
    get_mode_protect(chan,s);
    if (s[0]) dprintf(idx," (forcing mode: %s)",s);
    s[0]=0;
    if (chan->stat&CHAN_CLEARBANS) strcat(s,"clear-bans ");
    if (chan->stat&CHAN_ENFORCEBANS) strcat(s,"enforce-bans ");
    if (chan->stat&CHAN_DYNAMICBANS) strcat(s,"dynamic-bans ");
    if (chan->stat&CHAN_NOUSERBANS) strcat(s,"forbid-user-bans ");
    if (chan->stat&CHAN_OPONJOIN) strcat(s,"op-on-join ");
    if (chan->stat&CHAN_BITCH) strcat(s,"bitch ");
    if (chan->stat&CHAN_GREET) strcat(s,"greet ");
    if (chan->stat&CHAN_PROTECTOPS) strcat(s,"protect-ops ");
    if (chan->stat&CHAN_LOGSTATUS) strcat(s,"log-status ");
    if (chan->stat&CHAN_REVENGE) strcat(s,"revenge ");
    if (chan->stat&CHAN_STOPNETHACK) strcat(s,"stopnethack ");
    if (chan->stat&CHAN_SECRET) strcat(s,"secret ");
    dprintf(idx,"\n   Options: %s\n",s); s[0]=0;
    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->idle_kick)
      dprintf(idx,"   Kicking idle users after %d min\n",chan->idle_kick);
    chan=chan->next;
  }
#endif
  dprintf(idx,"Userfile: %s   Motd: %s\n",userfile,motdfile);
  if (notefile[0])
    dprintf(idx,"Notes can be stored, in: %s\n",notefile);
  else dprintf(idx,"Notes can not be stored.\n");
#ifndef NO_IRC
  if (dccdir[0]) {
    dprintf(idx,"DCC file path: %s",dccdir);
    if (upload_to_cd) dprintf(idx,"\n     incoming: (go to the current dir)\n");
    else if (dccin[0]) dprintf(idx,"\n     incoming: %s\n",dccin);
    else dprintf(idx," (no uploads)\n");
    dprintf(idx,"DCC block is %d, max concurrent d/ls is %d\n",dcc_block,
	    dcc_limit);
    if (dcc_users) dprintf(idx,"    max users is %d\n",dcc_users);
    if ((upload_to_cd) || (dccin[0]))
      dprintf(idx,"DCC max file size: %dk\n",dcc_maxsize);
  }
  else dprintf(idx,"(No active file transfer path defined.)\n");
#endif
  if (telnet_port) 
    dprintf(idx,"Listening at telnet port %d\n",telnet_port);
#ifndef NO_IRC
  if (min_servs)
    dprintf(idx,"Requiring a net of at least %d server(s)\n",min_servs);
  if (initserver[0])
    dprintf(idx,"On connect, I do: %s\n",initserver);
  dprintf(idx,"Flood is: %d msg/%ds, %d pub/%ds, %d join(nick)/%ds, %d ctcp/%ds\n",
	  flood_thr,flood_time,flood_pub_thr,flood_pub_time,flood_join_thr,
	  flood_join_time,flood_ctcp_thr,flood_ctcp_time);
  dprintf(idx,"Bans last %d mins, ignores last %d mins\n",ban_time,
	  ignore_time);
#endif
  dprintf(idx,"Help dir (%s), temp dir (%s), text dir (%s)\n",helpdir,tempdir,
	  textdir);
  flags2str(default_flags,s);
  dprintf(idx,"New users get flags [%s], notify: %s\n",s,notify_new);
#ifdef OWNER
  if (owner[0])
    dprintf(idx,"Permanent owner(s): %s\n",owner);
#endif
  for (i=0; i<MAXLOGS; i++) if (logfile[i]!=NULL) {
    dprintf(idx,"Logfile #%d: %s on %s (%s: %s)\n",i+1,logfile[i],logchan[i],
	    masktype(logmask[i]),maskname(logmask[i]));
  }
}

void reaffirm_owners()
{
#ifdef OWNER
  char *p,s[121];
  /* make sure default owners are +n */
  if (owner[0]) {
    strcpy(s,owner); p=strchr(s,','); while (p!=NULL) {
      *p=0; rmspace(s);
      set_attr_handle(s,get_attr_handle(s)|USER_OWNER);
      strcpy(s,p+1); p=strchr(s,',');
    }
    rmspace(s);
    if (s[0]) set_attr_handle(s,get_attr_handle(s)|USER_OWNER);
  }
#endif
}

void chanprog()
{
  int i;
  context;
  strcpy(botrealname,"eggdrop bot");
  admin[0]=0; helpdir[0]=0; initserver[0]=0; textdir[0]=0;
  notefile[0]=0; dccdir[0]=0; dccin[0]=0; tempdir[0]=0;
  for (i=0; i<MAXLOGS; i++) {
    if (logfile[i]!=NULL) { nfree(logfile[i]); logfile[i]=NULL; }
    if (logchan[i]!=NULL) { nfree(logchan[i]); logchan[i]=NULL; }
    logmask[i]=0;
  }
  conmask=0;
  /* turn off read-only variables (make them write-able) for rehash */
  unprotect_tcl();
  /* let's make sure when adding channels to not shareout a null user */
  nulluser=1;
  /* now read it */
  if (!readtclprog(configfile)) fatal("CONFIG FILE NOT FOUND",0);
  /* We should be safe now */
  nulluser=0;
  protect_tcl();
  strcpy(botname,origbotname);
#ifndef NO_IRC
  if (chanset==NULL) fatal("NO CHANNELS DEFINED.",0);
#endif
  if (!botname[0]) fatal("NO BOT NAME.",0);
  if (!userfile[0]) fatal("NO USER FILE.",0);

  if (!botuser[0]) {
    /* get this user's real username */
    int uid=getuid(); struct passwd *pwd;
    pwd=getpwuid(uid);
    if (pwd==NULL) strcpy(botuser,"UNKNOWN");
    else strcpy(botuser,pwd->pw_name);
  }

  if (!readuserfile(userfile,&userlist)) {
    if (!make_userfile)
      fatal("USER FILE NOT FOUND!  (try 'eggdrop -m' to make one)",0);
    printf("\n\nSTARTING BOT IN USERFILE CREATION MODE.\n");
#ifdef NO_IRC
    printf("Telnet to the bot and enter 'NEW' as your nickname.\n");
#else
    printf("Go to IRC and:  /msg %s hello\n",botname);
#endif
    printf("This will make the bot recognize you as the master.\n\n");
  }
  else if (make_userfile) fatal("USERFILE ALREADY EXISTS (drop the '-m')",0);

  context;
#ifndef NO_IRC
  if (serverlist==NULL) fatal("NO SERVER.",0);
  if (dccdir[0]) if (dccdir[strlen(dccdir)-1]!='/') strcat(dccdir,"/");
  if (dccin[0]) if (dccin[strlen(dccin)-1]!='/') strcat(dccin,"/");
#else
  dccdir[0]=0;
#endif
  if (helpdir[0]) if (helpdir[strlen(helpdir)-1]!='/') strcat(helpdir,"/");
  if (tempdir[0]) if (tempdir[strlen(tempdir)-1]!='/') strcat(tempdir,"/");
  if (textdir[0]) {
    if (textdir[strlen(textdir)-1]!='/') strcat(textdir,"/");
  }
  else strcpy(textdir,helpdir);

  context;
  /* test tempdir: it's vital */
  {
    FILE *f; char s[161];
    sprintf(s,"%s.test.file",tempdir);
    f=fopen(s,"w");
    if (f==NULL) fatal("CAN'T WRITE TO TEMP DIR.",0);
    fclose(f); unlink(s);
  }

#ifdef NO_IRC
  /* sanity check */
  if (telnet_bots_only)
    putlog(LOG_MISC,"*","* Drone mode  (no user access; only bots)");
#endif

  reaffirm_owners();
}

/* reload the user file from disk */
void reload()
{
  FILE *f;
  context;
  f=fopen(userfile,"r");
  if (f==NULL) {
    putlog(LOG_MISC,"*","Can't reload user file!");
    return;
  }
  fclose(f);
  clear_userlist(userlist); userlist=NULL;
  if (!readuserfile(userfile,&userlist))
    fatal("USER FILE IS MISSING!",0);
  context;
  reaffirm_owners();
}

void rehash()
{
  int old_tp=telnet_port;   /* save old telnet port value */
  struct chanset_t *chan;
  context;
  chan=chanset; while (chan!=NULL) {
    chan->stat |= CHANFLAGGED;
    /* flag will be cleared as the channels are re-added by the config file */
    /* any still flagged afterwards will be removed */
    chan=chan->next;
  }
  clearprog(); chanprog();
  telnet_port=old_tp;
#ifdef NO_IRC
  dccdir[0]=0;
#else
  if ((strcasecmp(oldnick,botname)!=0) && (strcasecmp(oldnick,altnick)!=0) &&
  (oldnick[0])) {
    /* change botname back, don't be premature */
    strcpy(botname,oldnick);
    tprintf(serv,"NICK %s\n",origbotname);
  }
  /* change botname back incase we were using altnick previous to rehash */
  else if (oldnick[0]) strcpy(botname,oldnick);
  if (initserver[0]) do_tcl("init-server",initserver);
  /* remove any extra channels */
  chan=chanset; while (chan!=NULL) {
    if (chan->stat & CHANFLAGGED) {
      putlog(LOG_MISC,"*","No longer supporting channel %s",chan->name);
      if (serv>=0) mprintf(serv,"PART %s\n",chan->name);
      clear_channel(chan,0);
      freeuser(chan->bans);
      killchanset(chan->name);
      chan=chanset;
    }
    else chan=chan->next;
  }
  /* update stuff, as if we just got control of a channel again: */
  chan=chanset; while (chan!=NULL) {
    recheck_channel(chan);
    chan=chan->next;
  }
#endif
}

void get_first_server()
{
  curserv=999;
  /* silly, no? */
}

/* brief venture into timers */

/* add a timer */
unsigned long add_timer(stack,elapse,cmd,prev_id)
tcl_timer_t **stack; int elapse; char *cmd; unsigned long prev_id;
{
  tcl_timer_t *old=(*stack);
  *stack=(tcl_timer_t *)nmalloc(sizeof(tcl_timer_t));
  (*stack)->next=old; (*stack)->mins=elapse;
  (*stack)->cmd=(char *)nmalloc(strlen(cmd)+1);
  strcpy((*stack)->cmd,cmd);
  /* if it's just being added back and already had an id, */
  /* don't create a new one */
  if (prev_id>0) (*stack)->id=prev_id;
  else (*stack)->id=timer_id++;
  return (*stack)->id;
}

/* remove a timer, by id */
int remove_timer(stack,id)
tcl_timer_t **stack; unsigned long id;
{
  tcl_timer_t *mark=*stack,*old; int ok=0;
  context;
  *stack=NULL; while (mark!=NULL) {
    if (mark->id != id) add_timer(stack,mark->mins,mark->cmd,mark->id);
    else ok++;
    old=mark; mark=mark->next;
    nfree(old->cmd); nfree(old);
  }
  return ok;
}

/* check timers, execute the ones that have expired */
void do_check_timers(stack)
tcl_timer_t **stack;
{
  tcl_timer_t *mark=*stack,*old; Tcl_DString ds; int argc,i; char **argv;
  context;
  /* new timers could be added by a Tcl script inside a current timer */
  /* so i'll just clear out the timer list completely, and add any */
  /* unexpired timers back on */
  *stack=NULL;
  while (mark!=NULL) {
    mark->mins--;
    if (mark->mins == 0) {
      int code;
      set_tcl_vars();
      Tcl_DStringInit(&ds);
      if (Tcl_SplitList(interp,mark->cmd,&argc,&argv) != TCL_OK) {
	putlog(LOG_MISC,"*","(Timer) Error for '%s': %s",mark->cmd,
	       interp->result);
      }
      else {
	for (i=0; i<argc; i++) Tcl_DStringAppendElement(&ds,argv[i]);
	n_free(argv,"",0);
	code=Tcl_Eval(interp,Tcl_DStringValue(&ds));
	/* code=Tcl_Eval(interp,mark->cmd); */
	Tcl_DStringFree(&ds);
	if (code!=TCL_OK)
	  putlog(LOG_MISC,"*","(Timer) Error for '%s': %s",mark->cmd,
		 interp->result);
      }
      get_tcl_vars();
    }
    else add_timer(stack,mark->mins,mark->cmd,mark->id);
    context;
    old=mark; mark=mark->next;
    nfree(old->cmd); nfree(old);
  }
}

void check_timers()
{
  do_check_timers(&timer);
}

void check_utimers()
{
  do_check_timers(&utimer);
}

/* return list of timers */
void list_timers(irp,stack)
Tcl_Interp *irp; tcl_timer_t *stack;
{
  tcl_timer_t *mark=stack; char mins[10],id[20],*argv[3],*x;
  while (mark!=NULL) {
    sprintf(mins,"%u",mark->mins);
    sprintf(id,"timer%lu",mark->id);
    argv[0]=mins; argv[1]=mark->cmd; argv[2]=id;
    x=Tcl_Merge(3,argv);
    Tcl_AppendElement(irp,x);
    n_free(x,"",0);
    mark=mark->next;
  }
}

/* wipe all timers */
void wipe_timers(irp,stack)
Tcl_Interp *irp; tcl_timer_t **stack;
{
  tcl_timer_t *mark=*stack,*old;
  while (mark!=NULL) {
    old=mark; mark=mark->next;
    remove_timer(stack,old->id);
  }
  *stack=NULL;
}
