/* 
   chan.c -- handles:
     almost everything to do with channel manipulation
     telling channel status
     'who' response
     user kickban, kick, op, deop
     idle kicking

   dprintf'ized, 27oct95
   multi-channel, 8feb96
*/
/*
   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.
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "eggdrop.h"
#if HAVE_GETRUSAGE
#include <sys/resource.h>
# if HAVE_SYS_RUSAGE_H
# include <sys/rusage.h>
# endif
#endif
#include "users.h"
#include "chan.h"
#include "proto.h"

extern char botuserhost[];
extern char botuser[];
extern char version[];
extern int serv;
extern char botchan[];
extern char botserver[];
extern time_t online_since;
extern int backgrd;
extern int con_chan;
extern int term_z;
extern int botserverport;
extern int learn_users;
extern int mtot,htot;
extern int dcc_total;
extern struct dcc_t dcc[];
extern int use_info;
extern int share_users;
extern int passive;
extern char cx_file[];
extern int cx_line;
extern char ver[];
extern int ban_time;
extern struct userrec *userlist;
extern int cache_hit, cache_miss;
extern int default_flags;
extern int clearbans;
extern int waiting_for_awake;
extern char newbotname[];
extern time_t trying_server;
extern time_t server_online;
extern int server_lag;

/* bot's nickname */
char botname[10];
/* bot's intended nickname (might have had to switch if nick was in use) */
char origbotname[10];
/* if # servers on our side of a split falls below this, jump servers */
/* 0 = don't use this */
int min_servs=0;
/* admin info */
char admin[121];
/* strict hostname matching (don't strip ~) */
int strict_host=0;

extern struct chanset_t *chanset;


/* dump status info out to dcc */
void tell_verbose_status(idx,showchan)
int idx,showchan;
{
  char s[256],s1[121],s2[81]; int i,j; time_t now,hr,min;
#ifndef NO_IRC
  struct chanset_t *chan; char *p,*q;
#endif
#if HAVE_GETRUSAGE
  struct rusage ru;
#else
#if HAVE_CLOCK
  clock_t cl;
#endif
#endif
  context;
  i=count_users(userlist);
  dprintf(idx,"I am %s, running %s:  %d user%s (mem: %uk)\n",botname,ver,
	  i,i==1?"":"s",(int)(expected_memory()/1024));
  if (admin[0]) dprintf(idx,"Admin: %s\n",admin);
#ifdef NO_IRC
  dprintf(idx,"Floating in limbo (no IRC interaction)\n");
#else
  p=(char *)nmalloc(11); strcpy(p,"Channels: ");
  chan=chanset; while (chan!=NULL) {
    strcpy(s,chan->name);
    if (!(chan->stat & CHANACTIVE)) strcat(s," (trying)");
    else if (chan->stat & CHANPEND) strcat(s," (pending)");
    else if (!me_op(chan)) strcat(s," (want ops!)");
    strcat(s,", ");
    q=(char *)nmalloc(strlen(p)+strlen(s)+1);
    strcpy(q,p); strcat(q,s); nfree(p); p=q;
    chan=chan->next;
  }
  if (strlen(p)>10) {
    p[strlen(p)-2]=0;
    dprintf(idx,"%s\n",p);
  }
  nfree(p);
  if (showchan) {
    chan=chanset;
    while (chan!=NULL) {
      s[0]=0;
      if (chan->stat & CHAN_GREET) strcat(s,"greet, ");
      if (chan->stat & CHAN_OPONJOIN) strcat(s,"auto-op, ");
      if (chan->stat & CHAN_BITCH) strcat(s,"bitch, ");
      if (s[0]) s[strlen(s)-2]=0;
      if (!s[0]) strcpy(s,"lurking");
      get_mode_protect(chan,s2);
      if (chan->stat&CHANACTIVE)
	dprintf(idx,"%-10s: %2d member%c, enforcing \"%s\"  (%s)\n",chan->name,
		chan->channel.members,chan->channel.members==1?' ':'s',s2,s);
      else
	dprintf(idx,"%-10s: (inactive), enforcing \"%s\"  (%s)\n",chan->name,
		s2,s);
      chan=chan->next;
    }
  }
#endif  /* NO_IRC */
  for (i=0; i<dcc_total; i++) if (dcc[i].type==DCC_BOT) {
    if (dcc[i].u.bot->status&STAT_GETTING) {
      for (j=0; j<dcc_total; j++)
	if ((dcc[j].type==DCC_SEND) &&
	    (strcasecmp(dcc[j].host,dcc[i].nick)==0)) {
	  dprintf(idx,"Downloading userlist from %s (%d%% done)\n",
		  dcc[i].nick,(int)(100.0*((float)dcc[j].u.xfer->sent)/
				    ((float)dcc[j].u.xfer->length)));
	}
    }
    if (dcc[i].u.bot->status&STAT_SENDING) {
      for (j=0; j<dcc_total; j++) {
	if ((dcc[j].type==DCC_GET) &&
	    (strcasecmp(dcc[j].host,dcc[i].nick)==0)) {
	  dprintf(idx,"Sending userlist to %s (%d%% done)\n",
		  dcc[i].nick,(int)(100.0*((float)dcc[j].u.xfer->sent)/
				    ((float)dcc[j].u.xfer->length)));
	}
	if ((dcc[j].type==DCC_GET_PENDING) &&
	    (strcasecmp(dcc[j].host,dcc[i].nick)==0)) {
	  dprintf(idx,"Sending userlist to %s (waiting for connect)\n",
		  dcc[i].nick);
	}
      }
    }
  }
  status_tbufs(idx);
#ifndef NO_IRC
  s[0]=0;
  if (!trying_server) {
    daysdur(time(NULL),server_online,s1);
    sprintf(s,"(connected %s)",s1);
    if ((server_lag) && !(waiting_for_awake)) {
      sprintf(s1," (lag: %ds)",server_lag);
      if (server_lag==(-1)) sprintf(s1," (bad pong replies)");
      strcat(s,s1);
    }
  }
  dprintf(idx,"Server %s:%d %s\n",botserver,botserverport,trying_server?
	  "(trying)":s);
  if (mtot) dprintf(idx,"Server queue is %d%% full.\n",
		    (int)((float)(mtot*100.0)/(float)MAXQMSG));
  if (htot) dprintf(idx,"Help queue is %d%% full.\n",
		    (int)((float)(htot*100.0)/(float)MAXQMSG));
#endif
  now=time(NULL); now-=online_since; s[0]=0;
  if (now>86400) {
    /* days */
    sprintf(s,"%d day",(int)(now/86400));
    if ((int)(now/86400) >= 2) strcat(s,"s");
    strcat(s,", "); now-=(((int)(now/86400))*86400);
  }
  hr=(time_t)((int)now/3600); now-=(hr*3600);
  min=(time_t)((int)now/60);
  sprintf(&s[strlen(s)],"%02d:%02d",(int)hr,(int)min);
  s1[0]=0;
  if (backgrd) strcpy(s1,"background");
  else {
    if (term_z) strcpy(s1,"terminal mode");
    else if (con_chan) strcpy(s1,"status mode");
    else strcpy(s1,"logdump mode");
  }
#if HAVE_GETRUSAGE
  getrusage(RUSAGE_SELF,&ru);
  hr=(int)((ru.ru_utime.tv_sec+ru.ru_stime.tv_sec)/60);
  min=(int)((ru.ru_utime.tv_sec+ru.ru_stime.tv_sec)-(hr*60));
  sprintf(s2,"CPU %02d:%02d",(int)hr,(int)min);  /* actally min/sec */
#else
#if HAVE_CLOCK
  cl=(clock()/CLOCKS_PER_SEC);
  hr=(int)(cl/60); min=(int)(cl-(hr*60));
  sprintf(s2,"CPU %02d:%02d",(int)hr,(int)min);  /* actually min/sec */
#else
  sprintf(s2,"CPU ???");
#endif
#endif
  dprintf(idx,"Online for %s  (%s)  %s  cache hit %4.1f%%\n",s,s1,s2,
	  100.0*((float)cache_hit)/((float)(cache_hit+cache_miss)));
  s1[0]=0;
  if (learn_users) strcat(s1,"learn users, ");
  if (share_users) {
    if (passive) strcat(s1,"passive share, ");
    else strcat(s1,"aggressive share, ");
  }
  if (s1[0]) s1[strlen(s1)-2]=0; else strcpy(s1,"none");
  dprintf(idx,"Mode(s): %s.\n",s1);
}

/* what the channel's mode CURRENTLY is */
char *getchanmode(chan)
struct chanset_t *chan;
{
  static char s[121]; int atr,i;
  s[0]='+'; i=1; atr=chan->channel.mode;
  if (atr&CHANINV) s[i++]='i';
  if (atr&CHANPRIV) s[i++]='p';
  if (atr&CHANSEC) s[i++]='s';
  if (atr&CHANMODER) s[i++]='m';
  if (atr&CHANTOPIC) s[i++]='t';
  if (atr&CHANNOMSG) s[i++]='n';
  if (atr&CHANANON) s[i++]='a';
  if (chan->channel.key[0]) s[i++]='k';
  if (chan->channel.maxmembers>-1) s[i++]='l';
  s[i]=0;
  if (chan->channel.key[0]) sprintf(&s[strlen(s)]," %s",chan->channel.key);
  if (chan->channel.maxmembers>-1)
    sprintf(&s[strlen(s)]," %d",chan->channel.maxmembers);
  return s;
}

/* dump channel info out to dcc */
void tell_verbose_chan_info(idx,chname)
int idx; char *chname;
{
#ifndef NO_IRC
  char handle[20],s[121],s1[121],atrflag,chanflag;
  struct chanset_t *chan; int i,atr; time_t now; memberlist *m;
#endif
  context;
#ifdef NO_IRC
  dprintf(idx,"This bot runs without IRC interaction.\n");
#else
  if (!chname[0]) chan=findchan(dcc[idx].u.chat->con_chan);
  else chan=findchan(chname);
  if (chan==NULL) {
    dprintf(idx,"Not active on channel %s\n",chname);
    return;
  }
  now=time(NULL);
  strcpy(s,getchanmode(chan));
  if (chan->stat&CHANPEND) sprintf(s1,"Processing channel %s",chan->name);
  else if (chan->stat&CHANACTIVE) sprintf(s1,"Channel %s",chan->name);
  else sprintf(s1,"Desiring channel %s",chan->name);
  dprintf(idx,"%s, %d member%s, mode %s:\n",s1,chan->channel.members,
	  chan->channel.members==1?"":"s",s);
  m=chan->channel.member; i=0;
  if (chan->stat&CHANACTIVE) {
    dprintf(idx,"(m = master, o = op, d = deop, b = bot)\n");
    dprintf(idx," NICKNAME  HANDLE    JOIN   IDLE  USER@HOST\n");
    while (m->nick[0]) {
      if (m->joined > 0) {
	strcpy(s,ctime(&(m->joined)));
	if ((time(NULL)-(m->joined)) > 86400) {
	  strcpy(s1,&s[4]); strcpy(s,&s[8]); strcpy(&s[2],s1); s[5]=0;
	}
	else { strcpy(s,&s[11]); s[5]=0; }
      } else strcpy(s," --- ");
      if (m->user==NULL) { 
	sprintf(s1,"%s!%s",m->nick,m->userhost);
	get_handle_by_host(handle,s1);  /* <- automagically updates m->user */
      }
      if (m->user==NULL) atr=0;
      else { strcpy(handle,m->user->handle); atr=m->user->flags; }
      /* determine status char to use */
      if (atr&USER_BOT) atrflag='b';
      else if (atr&USER_MASTER) atrflag='m';
      else if (atr&USER_OP) atrflag='o';
      else if (atr&USER_DEOP) atrflag='d';
      else atrflag=' ';
      if (m->flags&CHANOP) chanflag='@';
      else if (m->flags&CHANVOICE) chanflag='+';
      else chanflag=' ';
      if (m->split > 0)
	dprintf(idx,"%c%-9s %-9s %s %c     <- netsplit, %lus\n",chanflag,
		m->nick,handle,s,atrflag,now-(m->split));
      else if (strcmp(m->nick,botname)==0)
	dprintf(idx,"%c%-9s %-9s %s %c     <- it's me!\n",chanflag,m->nick,
		handle,s,atrflag);
      else {
	/* determine idle time */
	if (now-(m->last) > 86400)
	  sprintf(s1,"%2lud",((now-(m->last))/86400));
	else if (now-(m->last) > 3600)
	  sprintf(s1,"%2luh",((now-(m->last))/3600));
	else if (now-(m->last) > 180)
	  sprintf(s1,"%2lum",((now-(m->last))/60));
	else strcpy(s1,"   ");
	dprintf(idx,"%c%-9s %-9s %s %c %s  %s\n",chanflag,m->nick,handle,
		s,atrflag,s1,m->userhost);
      }
      if (m->flags&FAKEOP)
	dprintf(idx,"    (FAKE CHANOP GIVEN BY SERVER)\n");
      if (m->flags&SENTOP)
	dprintf(idx,"    (pending +o -- I'm lagged)\n");
      if (m->flags&SENTDEOP)
	dprintf(idx,"    (pending -o -- I'm lagged)\n");
      if (m->flags&SENTKICK)
	dprintf(idx,"    (pending kick)\n");
      m=m->next;
    }
  }
  dprintf(idx,"End of channel info.\n");
#endif /* NO_IRC */
}

void tell_chanbans(chan,idx,k,match)
struct chanset_t *chan; int idx,k; char *match;
{
  banlist *b=chan->channel.ban; char s[UHOSTLEN],s1[UHOSTLEN],fill[256];
  time_t now; int min,sec;
  context;
  now=time(NULL);
  if (chan->stat&CHANACTIVE) {
    while (b->ban[0]) {
      if ((!equals_ban(b->ban)) && (!u_equals_ban(chan->bans,b->ban))) {
	strcpy(s,b->who); splitnick(s1,s);
	if (s1[0]) sprintf(fill,"%s (%s!%s)",b->ban,s1,s);
	else if (strcasecmp(s,"existent")==0) sprintf(fill,"%s (%s)",b->ban,s);
	else sprintf(fill,"%s (server %s)",b->ban,s);
	if (b->timer != 0) {
	  min=(now - b->timer)/60; sec=(now - b->timer)-(min*60);
	  sprintf(s," (active %02d:%02d)",min,sec);
	  strcat(fill,s);
	}
	if ((!match[0]) || (wild_match(match,b->ban)))
	  dprintf(idx,"* [%3d] %s\n",k,fill);
	k++;
      }
      b=b->next;
    }
  }
  if (k==1) dprintf(idx,"(There are no bans, permanent or otherwise.)\n");
}

int kill_chanban(chname,idx,k,which)
int idx,k,which; char *chname;
{
  banlist *b; struct chanset_t *chan;
  context; if (k==0) k=1;
  chan=findchan(chname); if (chan==NULL) return 0;
  b=chan->channel.ban;
  while (b->ban[0]) {
    if ((!equals_ban(b->ban)) && (!u_equals_ban(chan->bans,b->ban))) {
      if (k==which) {
	add_mode(chan,'-','b',b->ban);
	dprintf(idx,"Removed ban on '%s'.\n",b->ban);
      }
      k++;
    }
    b=b->next;
  }
  if (k<=which) { dprintf(idx,"No such ban.\n"); return 0; }
  else return 1;
}

int kill_chanban_name(chname,idx,which)
int idx; char *which,*chname;
{
  banlist *b; struct chanset_t *chan;
  context;
  chan=findchan(chname); if (chan==NULL) return 0;
  b=chan->channel.ban;
  while (b->ban[0]) {
    if (strcasecmp(b->ban,which)==0) {
      add_mode(chan,'-','b',b->ban);
      dprintf(idx,"Removed ban on '%s'.\n",b->ban);
      return 1;
    }
    b=b->next;
  }
  dprintf(idx,"No such ban.\n"); return 0;
}

/* log the channel members */
void log_chans()
{
  banlist *b; memberlist *m; struct chanset_t *chan;
  int chops,bans;
  context;
  chan=chanset; while (chan!=NULL) {
    if ((chan->stat&CHANACTIVE) && (chan->stat&CHAN_LOGSTATUS)) {
      m=chan->channel.member; chops=0;
      while (m->nick[0]) {
	if (m->flags&CHANOP) chops++;
	m=m->next;
      }
      b=chan->channel.ban; bans=0;
      while (b->ban[0]) { bans++; b=b->next; }
      putlog(LOG_MISC,chan->name,"%-10s: %2d member%c (%2d chop%c), %2d ban%c %s",
	  chan->name,chan->channel.members,chan->channel.members==1?' ':'s',
	  chops,chops==1?' ':'s',bans,bans==1?' ':'s',me_op(chan)?"":
	  "(not op'd)");
    }
    chan=chan->next;
  }
}

/* dump channel info out to nick */
void tell_chan_info(nick)
char *nick;
{
  char s[256],*p,*q; int i; struct chanset_t *chan;
  context;
  mprintf(serv,"NOTICE %s :I am %s, running %s.\n",nick,botname,version);
  if (admin[0]) mprintf(serv,"NOTICE %s :Admin: %s\n",nick,admin);
  p=(char *)nmalloc(11); strcpy(p,"Channels: ");
  chan=chanset; while (chan!=NULL) {
    strcpy(s,chan->name);
    if (!(chan->stat & CHANACTIVE)) strcat(s," (trying)");
    else if (chan->stat & CHANPEND) strcat(s," (pending)");
    else if (!me_op(chan)) strcat(s," (want ops!)");
    strcat(s,", ");
    q=(char *)nmalloc(strlen(p)+strlen(s)+1);
    strcpy(q,p); strcat(q,s); nfree(p); p=q;
    chan=chan->next;
  }
  if (strlen(p)>10) {
    p[strlen(p)-2]=0;
    mprintf(serv,"NOTICE %s :%s\n",nick,p);
  }
  nfree(p);
  i=count_users(userlist);
  mprintf(serv,"NOTICE %s :%d user%s  (mem: %uk)\n",nick,i,i==1?"":"s",
	  (int)(expected_memory()/1024));
}

/* got 324: mode status */
/* <server> 324 <to> <channel> <mode> */
void got324(from,msg)
char *from,*msg;
{
  int i=1; char *p,*q,chname[81]; struct chanset_t *chan;
  context;
  split(NULL,msg); split(chname,msg);
  chan=findchan(chname);
  if (chan==NULL) {
    putlog(LOG_MISC,"*","Hmm, mode info from a channel I'm not on: %s",
	chname);
    tprintf(serv,"PART %s\n",chname); return;
  }
  while (msg[i]!=0) {
    context;
    if (msg[i]=='i') chan->channel.mode |= CHANINV;
    if (msg[i]=='p') chan->channel.mode |= CHANPRIV;
    if (msg[i]=='s') chan->channel.mode |= CHANSEC;
    if (msg[i]=='m') chan->channel.mode |= CHANMODER;
    if (msg[i]=='t') chan->channel.mode |= CHANTOPIC;
    if (msg[i]=='n') chan->channel.mode |= CHANNOMSG;
    if (msg[i]=='a') chan->channel.mode |= CHANANON;
    if (msg[i]=='q') chan->channel.mode |= CHANQUIET;
    if (msg[i]=='k') {
      context;
      p=strchr(msg,' '); p++;
      q=strchr(p,' '); if (q!=NULL) {
	*q=0; chan->channel.key=(char *)nmalloc(strlen(p)+1);
	strcpy(chan->channel.key,p); strcpy(p,q+1);
      }
      else {
	chan->channel.key=(char *)nmalloc(strlen(p)+1);
	strcpy(chan->channel.key,p); *p=0;
      }
    }
    if (msg[i]=='l') {
      context;
      p=strchr(msg,' '); p++; q=strchr(p,' '); if (q!=NULL) {
	*q=0; chan->channel.maxmembers=atoi(p); strcpy(p,q+1);
      }
      else { chan->channel.maxmembers=atoi(p); *p=0; }
    }
    i++;
  }
  context;
}

/* got a 352: who info! */
void got352(from,msg)
char *from,*msg;
{
  char userhost[UHOSTLEN],nick[NICKLEN],s[UHOSTLEN],hand[10]; memberlist *m;
  int waschanop,atr; struct chanset_t *chan;
  context; split(NULL,msg); split(s,msg);
  chan=findchan(s); if (chan==NULL) return;
  split(userhost,msg); strcat(userhost,"@");
  split(&userhost[strlen(userhost)],msg);
  split(NULL,msg); split(nick,msg);
  sprintf(s,"%s!%s",nick,userhost); fixfrom(s);
  splitnick(nick,s); strcpy(userhost,s);
  split(s,msg); m=ismember(chan,nick);
  if (m==NULL) { 
    m=newmember(chan);
    m->joined=m->split=0L; m->flags=0;
    m->last=time(NULL);
  }
  strcpy(m->nick,nick); strcpy(m->userhost,userhost); m->user=NULL;
  if (strcasecmp(nick,botname)==0) strcpy(botuserhost,userhost);
  waschanop=me_op(chan);
  if (strchr(s,'@')!=NULL) m->flags |= CHANOP;
  else m->flags &= ~CHANOP;
  if (strchr(s,'+')!=NULL) m->flags |= CHANVOICE;
  else m->flags &= ~CHANVOICE;
  if ((strcasecmp(nick,botname)==0) && (!waschanop) && (me_op(chan)))
    newly_chanop(chan);
  if ((strcasecmp(nick,botname)==0) && (any_ops(chan)) && (!me_op(chan))) {
    if (chan->need_op[0]) do_tcl("need-op",chan->need_op);
  }
  sprintf(s,"%s!%s",nick,userhost); fixfrom(s);
  m->user=get_user_by_host(s);
  atr=get_attr_host(s);
  if ((m->flags&CHANOP) && (atr & USER_DEOP) && (me_op(chan)) &&
      (strcasecmp(nick,botname)!=0))
    add_mode(chan,'-','o',nick);
  if (((match_ban(s)) || (u_match_ban(chan->bans,s))) && 
      (strcasecmp(nick,botname)!=0) && (me_op(chan)) &&
      (chan->stat&CHAN_ENFORCEBANS))
    tprintf(serv,"KICK %s %s :banned\n",chan->name,nick);
  else if ((atr & USER_KICK) && (strcasecmp(nick,botname)!=0) &&
	   (me_op(chan))) {
    get_handle_by_host(hand,s);
    get_handle_comment(hand,userhost);
    if (userhost[0])
      tprintf(serv,"KICK %s %s :%s\n",chan->name,nick,userhost);
    else
      tprintf(serv,"KICK %s %s :...and thank you for playing.\n",chan->name,
	      nick);
  }
}

/* got 315: end of who */
/* <server> 315 <to> <chan> :End of /who */
void got315(from,msg)
char *from,*msg;
{
  char chname[81]; struct chanset_t *chan;
  split(NULL,msg); split(chname,msg);
  chan=findchan(chname); if (chan==NULL) return;
  /* may have left the channel before the who info came in */
  if (!(chan->stat & CHANPEND)) return;
  /* finished getting who list, can now be considered officially ON CHANNEL */
  chan->stat|=CHANACTIVE;
  chan->stat&=~CHANPEND;
  /* am *I* on the channel now? if not, well shit. */
  if (!ismember(chan,botname)) {
/*    putlog(LOG_MISC|LOG_JOIN,chan->name,"Oops, I'm not really on %s",
	   chan->name); */
    clear_channel(chan,1);
    chan->stat&=~CHANACTIVE;
    tprintf(serv,"JOIN %s %s\n",chan->name,chan->key_prot);
  }
  /* do not check for i-lines here. */
}

/* got 367: ban info */
/* <server> 367 <to> <chan> <ban> [placed-by] [timestamp] */
void got367(from,msg)
char *from,*msg;
{
  char s[UHOSTLEN],ban[81],who[UHOSTLEN],chname[81]; struct chanset_t *chan;
  context; split(NULL,msg); split(chname,msg);
  chan=findchan(chname); if (chan==NULL) return;
  nsplit(ban,msg); nsplit(who,msg);
  /* extended timestamp format? */
  if (who[0]) newban(chan,ban,who);
  else newban(chan,ban,"existent"); 
  sprintf(s,"%s!%s",botname,botuserhost);
  if ((wild_match(ban,s)) && (me_op(chan))) add_mode(chan,'-','b',ban);
  if ((get_attr_host(ban) & USER_OP) && (me_op(chan)))
    add_mode(chan,'-','b',ban);
  /* these will be flushed by 368: end of ban info */
}

/* got 368: end of ban list */
/* <server> 368 <to> <chan> :etc */
void got368(from,msg)
char *from,*msg;
{
  struct chanset_t *chan; char chname[81];
  /* ok, now add bans that i want, which aren't set yet */
  context;
  split(NULL,msg); split(chname,msg);
  chan=findchan(chname); if (chan==NULL) return;
  if (chan->stat&CHAN_CLEARBANS) resetbans(chan);
  else {
    kill_bogus_bans(chan);
    recheck_bans(chan);
  }
  /* if i sent a mode -b on myself (deban) in got367, either */
  /* resetbans() or recheck_bans() will flush that */
}

/* got 251: lusers */
/* <server> 251 <to> :there are 2258 users and 805 invisible on 127 servers */
void got251(from,msg)
char *from,*msg;
{
  int i; char servs[20];
  split(NULL,msg); fixcolon(msg);
  for (i=0; i<8; i++) split(NULL,msg);
  split(servs,msg);
  if (strncmp(msg,"servers",7)!=0) return;  /* was invalid format */
  i=atoi(servs); if (min_servs==0) return;  /* no minimum limit on servers */
  if (i<min_servs) {
    putlog(LOG_SERV,"*","Jumping servers (need %d servers, only have %d)",
	   min_servs,i);
    tprintf(serv,"QUIT :changing servers\n");
  }
}

/* too many channels */
void got405(from,msg)
char *from,*msg;
{
  context;
  putlog(LOG_MISC,"*","I'm on too many channels.");
}

/* got 471: can't join channel, full */
void got471(from,msg)
char *from,*msg;
{
  char chname[81]; struct chanset_t *chan;
  context; split(NULL,msg); split(chname,msg);
  putlog(LOG_JOIN,chname,"Can't join %s (full)",chname);
  chan=findchan(chname); if (chan==NULL) return;
  if (chan->need_invite[0]) do_tcl("need-invite",chan->need_invite);
}

/* got 473: can't join channel, invite only */
void got473(from,msg)
char *from,*msg;
{
  char chname[81]; struct chanset_t *chan;
  context; split(NULL,msg); split(chname,msg);
  putlog(LOG_JOIN,chname,"Can't join %s (+i)",chname);
  chan=findchan(chname); if (chan==NULL) return;
  if (chan->need_invite[0]) do_tcl("need-invite",chan->need_invite);
}

/* got 474: can't join channel, banned */
void got474(from,msg)
char *from,*msg;
{
  char chname[81]; struct chanset_t *chan;
  context; split(NULL,msg); split(chname,msg);
  putlog(LOG_JOIN,chname,"Can't join %s (banned)",chname);
  chan=findchan(chname); if (chan==NULL) return;
  if (chan->need_invite[0]) do_tcl("need-invite",chan->need_invite);
}

/* got 475: can't goin channel, bad key */
void got475(from,msg)
char *from,*msg;
{
  char chname[81]; struct chanset_t *chan;
  context; split(NULL,msg); split(chname,msg);
  putlog(LOG_JOIN,chname,"Can't join %s (bad key)",chname);
  chan=findchan(chname); if (chan==NULL) return;
  if (chan->need_invite[0]) do_tcl("need-invite",chan->need_invite);
}

/* got invitation */
void gotinvite(from,msg)
char *from,*msg;
{
  char nick[NICKLEN]; struct chanset_t *chan;
  static char lastnick[NICKLEN]="*";
  static time_t lastinv=(time_t)0L;
  context; split(NULL,msg); fixcolon(msg); splitnick(nick,from);
  /* come on.  more than one invite from someone within 30 seconds? */
  /* just ignore them -- don't even log it. */
  if ((strcasecmp(lastnick,nick)!=0) && (time(NULL) - lastinv < 30))
    putlog(LOG_MISC,"*","%s!%s invited me to %s",nick,from,msg);
  strcpy(lastnick,nick); lastinv=time(NULL);
  chan=findchan(msg);
  if (chan==NULL) return;  /* some channel i don't want */
  if (chan->stat & (CHANPEND|CHANACTIVE)) {
/*    mprintf(serv,"NOTICE %s :I'm already here.\n",nick);  */
    return;
  }
  tprintf(serv,"JOIN %s %s\n",chan->name,chan->key_prot);
}

/* topic change */
void gottopic(from,msg)
char *from,*msg;
{
  char nick[NICKLEN],handle[10],s[UHOSTLEN],chname[81]; memberlist *m;
  struct chanset_t *chan;
  context; split(chname,msg); fixcolon(msg); splitnick(nick,from);
  chan=findchan(chname); if (chan==NULL) return;
  putlog(LOG_JOIN,chname,"Topic changed on %s by %s!%s: %s",chname,nick,from,
	 msg);
  m=ismember(chan,nick); if (m!=NULL) m->last=time(NULL);
  sprintf(s,"%s!%s",nick,from);
  get_handle_by_host(handle,s);
  strcpy(chan->channel.topic,msg);
  check_tcl_topc(nick,from,handle,chname,msg);
}

/* 331: no current topic for this channel */
/* <server> 331 <to> <chname> :etc */
void got331(from,msg)
char *from,*msg;
{
  char chname[81]; struct chanset_t *chan;
  split(NULL,msg); split(chname,msg);
  chan=findchan(chname); if (chan==NULL) return;
  chan->channel.topic[0]=0;
  check_tcl_topc("*","*","*",chname,"");
}

/* 332: topic on a channel i've just joined */
/* <server> 332 <to> <chname> :topic goes here */
void got332(from,msg)
char *from,*msg;
{
  struct chanset_t *chan; char chname[81];
  context; split(NULL,msg); split(chname,msg);
  chan=findchan(chname); if (chan==NULL) return;
  fixcolon(msg);
  strcpy(chan->channel.topic,msg);
  check_tcl_topc("*","*","*",chname,msg);
}

void do_embedded_mode(chan,nick,m,mode)
struct chanset_t *chan; char *nick,*mode; memberlist *m;
{
  char s[81];
  while (*mode) {
    switch (*mode) {
    case 'o':
      sprintf(s,"+o %s",nick);
      check_tcl_mode(botserver,"","*",chan->name,s);
      got_op(chan,botserver,"",nick,0);
      break;
    case 'v':
      sprintf(s,"+v %s",nick);
      check_tcl_mode(botserver,"","*",chan->name,s);
      m->flags|=CHANVOICE;
      break;
    }
    mode++;
  }
}

/* join */
void gotjoin(from,chname)
char *from,*chname;
{
  char s[UHOSTLEN],s1[UHOSTLEN],handle[10],nick[NICKLEN],*p,*newmode;
  time_t tt,tnow; int i,j,atr; struct chanset_t *chan; memberlist *m;
  context;
  
  fixcolon(chname);
  /* ircd 2.9 sometimes does '#chname^Gmodes' when returning from splits */
  newmode=NULL;
  p=strchr(chname,7); if (p!=NULL) {
    *p=0; newmode=(++p);
  }
  chan=findchan(chname); if (chan==NULL) {
    putlog(LOG_MISC,"*","joined %s but didn't want to, dammit",chname);
    mprintf(serv,"PART %s\n",chname);
    return;
  }
  if (chan->stat & CHANPEND) return;
  detect_flood(from,chan,FLOOD_JOIN,1);
  /* grab last time joined before we updated it */
  get_handle_by_host(handle,from);
  get_handle_laston(handle,&tt); tnow=time(NULL);
  splitnick(nick,from);
  if ((!(chan->stat & CHANACTIVE)) && (strcasecmp(botname,nick)!=0) &&
      (strcasecmp(newbotname,nick)!=0)) {
    /* uh, what?!  i'm on the channel?! */
    putlog(LOG_MISC,chname,
	   "confused bot: guess I'm on %s and didn't realize it",chname);
    chan->stat|=CHANACTIVE; chan->stat&=~CHANPEND;
    reset_chan_info(chan);
    return;
  }
  m=ismember(chan,nick); if (m!=NULL) {
    /* already on channel?!? */
    if (m->split==0) killmember(chan,nick);
    else if (strcasecmp(m->userhost,from)!=0) killmember(chan,nick);
    else {
      check_tcl_rejn(nick,from,handle,chan->name);
      m->split=0; m->last=time(NULL);
      m->flags=0;  /* clean slate, let server refresh */
      set_handle_laston(handle,tnow);
      if ((m->flags&CHANOP) && (me_op(chan)) && (chan->stat & CHAN_OPONJOIN))
	add_mode(chan,'+','o',nick);
      if (newmode) {
	putlog(LOG_JOIN,chname,"%s (%s) returned to %s (with +%s).",nick,
	       from,chname,newmode);
	do_embedded_mode(chan,nick,m,newmode);
      }
      else putlog(LOG_JOIN,chname,"%s (%s) returned to %s.",nick,from,chname);
      return;
    }
  }
  m=newmember(chan);
  m->joined=tnow; m->split=0L;
  m->flags=0; m->last=time(NULL);
  strcpy(m->nick,nick); strcpy(m->userhost,from);
  sprintf(s,"%s!%s",nick,from); m->user=NULL;
  m->user=get_user_by_host(s);
  check_tcl_join(nick,from,handle,chname);
  set_handle_laston(handle,tnow);
  if (newmode) do_embedded_mode(chan,nick,m,newmode);
  if (strcasecmp(nick,botname)==0) {
    /* it was me joining! */
    if (newmode) putlog(LOG_JOIN|LOG_MISC,chname,"%s joined %s (with +%s).",
			nick,chname,newmode);
    else putlog(LOG_JOIN|LOG_MISC,chname,"%s joined %s.",nick,chname);
    chan->stat|=CHANPEND;
    tprintf(serv,"MODE %s\n",chname);
    tprintf(serv,"TOPIC %s\n",chname);
    tprintf(serv,"WHO %s\n",chname);
    /* can't get ban list till i'm opped :( */
    /* ^ this is no longer true, but it's harmless */
  }
  else {
    if (newmode) putlog(LOG_JOIN,chname,"%s (%s) joined %s (with +%s).",nick,
			from,chname,newmode);
    else putlog(LOG_JOIN,chname,"%s (%s) joined %s.",nick,from,chname);
    atr=get_attr_host(s);
    for (i=0; i<strlen(s); i++) if ((unsigned char)s[i]<32) {
      mprintf(serv,"KICK %s %s :bogus username\n",chname,nick);
      return;
    }
    if (me_op(chan)) {
      if ((atr & USER_OP) && (chan->stat & CHAN_OPONJOIN))
	add_mode(chan,'+','o',nick);
      if (strcasecmp(nick,botname)!=0) {
	if ((match_ban(s)) || (u_match_ban(chan->bans,s)))
	  refresh_ban_kick(chan,s,nick);
	else if (atr & USER_KICK) {
	  get_handle_comment(handle,s1);
	  if (s1[0])
	    mprintf(serv,"KICK %s %s :%s\n",chname,nick,s1);
	  else
	    mprintf(serv,"KICK %s %s :...and don't come back.\n",chname,nick);
	}
      }
    }
    /* don't re-display greeting if they've been on the channel recently */
    if ((chan->stat&CHAN_GREET) && (use_info) && (tnow-tt > WAIT_INFO))
      showinfo(chan,handle,nick);
    i=num_notes(handle);
    for (j=0; j<dcc_total; j++)
      if ((dcc[j].type==DCC_CHAT) && (strcasecmp(dcc[j].nick,handle)==0))
	i=0;    /* they already know they have notes */
    if (i) {
      hprintf(serv,"NOTICE %s :You have %d note%s waiting on %s.\n",
	      nick,i,i==1?"":"s",botname);
      hprintf(serv,"NOTICE %s :For a list, /MSG %s NOTES [pass] INDEX\n",
	      nick,botname);
    }
  }
}

/* part */
void gotpart(from,chname)
char *from,*chname;
{
  char nick[NICKLEN],hand[10],oldfrom[UHOSTLEN]; struct chanset_t *chan;
  context;
  fixcolon(chname);
  chan=findchan(chname); if (chan==NULL) return;
  if (chan->stat & CHANPEND) return;
  strcpy(oldfrom,from);
  get_handle_by_host(hand,from);
  splitnick(nick,from); fixcolon(chname);
  if (!(chan->stat & CHANACTIVE)) {
    /* whoa! */
    putlog(LOG_MISC,chname,
	   "confused bot: guess I'm on %s and didn't realize it",chname);
    chan->stat|=CHANACTIVE; chan->stat&=~CHANPEND;
    reset_chan_info(chan);
  }
  check_tcl_part(nick,from,hand,chname);
  update_laston(oldfrom);
  killmember(chan,nick);
  putlog(LOG_JOIN,chname,"%s (%s) left %s.",nick,from,chname);
  /* if it was me, all hell breaks loose: */
  if (strcasecmp(nick,botname)==0) {
    clear_channel(chan,1);
    chan->stat&=~(CHANACTIVE|CHANPEND);
    tprintf(serv,"JOIN %s %s\n",chan->name,chan->key_prot);
  }
  else check_lonely_channel(chan);
}

/* kick */
void gotkick(from,msg)
char *from,*msg;
{
  char nick[NICKLEN],whodid[NICKLEN],chname[81],s[UHOSTLEN],s1[UHOSTLEN];
  char hand[10]; memberlist *m; struct chanset_t *chan; int atr,atr1;
  time_t now=time(NULL);

  context;
  split(chname,msg);
  chan=findchan(chname); if (chan==NULL) return;
  if (chan->stat & CHANPEND) return;  /* not ready yet */
  split(nick,msg); fixcolon(msg); splitnick(whodid,from);
  sprintf(s,"%s!%s",whodid,from);
  m=ismember(chan,whodid); if (m!=NULL) m->last=time(NULL);
  get_handle_by_host(hand,s);
  check_tcl_kick(whodid,from,hand,chname,nick,msg);
  atr=get_attr_host(s);
  /* check for masskick */
  if (!(atr & (USER_FRIEND|USER_MASTER)) &&
      (strcasecmp(botname,whodid)!=0) &&
      (strcasecmp(chan->kicknick,whodid)==0)) {
    if (now - chan->kicktime <= 10) {
      chan->kicks++; if (chan->kicks>=3) {   /* masskick */
	tprintf(serv,"KICK %s %s :mass kick, go sit in a corner\n",chname,
		whodid);
	chan->kicks=0; chan->kicknick[0]=0; chan->kicktime=0L;
      }
    }
    else { chan->kicks=1; chan->kicktime=time(NULL); }
  }
  else {
    strcpy(chan->kicknick,whodid); chan->kicktime=time(NULL); chan->kicks=1;
  }
  /* kicking an oplisted person?  KICK THEM. */
  m=ismember(chan,nick); if (m!=NULL) {
    sprintf(s1,"%s!%s",m->nick,m->userhost);
    atr1=get_attr_host(s1);
    update_laston(s1);
    /* revenge on, kicker was not bot, kicker was not kickee, kickee was
       oplisted, and kicker was not (whew!) */
    if ((atr1 & USER_OP) && (chan->stat & CHAN_REVENGE) &&
	(strcasecmp(whodid,botname)!=0) && !(atr & USER_OP) &&
	(strcasecmp(whodid,nick)!=0))
      tprintf(serv,"KICK %s %s :don't kick my friends, bud\n",chname,whodid);
    putlog(LOG_MODES,chname,"%s kicked from %s by %s: %s",s1,chname,s,msg);
  }
  if (strcasecmp(nick,botname)==0) {
    chan->stat&=~(CHANACTIVE|CHANPEND);
    mprintf(serv,"JOIN %s %s\n",chan->name,chan->key_prot);
    clear_channel(chan,1);
    if ((chan->stat & CHAN_REVENGE) && !(atr & (USER_MASTER|USER_FRIEND))) {
      take_revenge(chan,s,"kicked me off the channel");
      /* ^put the kicker on the deop list : revenge */
    }
  }
  else killmember(chan,nick);
  if (strcasecmp(nick,botname)!=0) check_lonely_channel(chan);
}

/* nick change */
void gotnick(ffrom,msg)
char *ffrom, *msg;
{
  char nick[NICKLEN],hand[10],from[UHOSTLEN],s[UHOSTLEN],s1[UHOSTLEN];
  memberlist *m,*mm; struct chanset_t *chan;
  context;
  strcpy(from,ffrom); fixfrom(from);
  get_handle_by_host(hand,from); strcpy(s,from);
  splitnick(nick,from); fixcolon(msg);
  chan=chanset; while (chan!=NULL) {
    m=ismember(chan,nick);
    if (m!=NULL) {
      putlog(LOG_JOIN,chan->name,"Nick change: %s -> %s",nick,msg);
      m->last=time(NULL);
      if (strcasecmp(nick,msg)!=0) {
	/* not just a capitalization change */
	mm=ismember(chan,msg);
	if (mm!=NULL) {
	  /* someone on channel with old nick?! */
	  if (mm->split)
	    putlog(LOG_JOIN,chan->name,"Possible future nick collision: %s",
		   mm->nick);
	  else
	    putlog(LOG_MISC,chan->name,"* Bug: nick change to existing nick");
	  killmember(chan,mm->nick);
	}
      }
      check_tcl_nick(nick,from,hand,chan->name,msg);
      /* banned? */
      /* compose a nick!user@host for the new nick */
      sprintf(s1,"%s!%s",msg,from);
      if ((chan->stat & CHAN_ENFORCEBANS) && (!(m->flags & SENTKICK)) &&
	  ((match_ban(s1)) || (u_match_ban(chan->bans,s1))))
	refresh_ban_kick(chan,s1,nick);
      strcpy(m->nick,msg);
    }
    chan=chan->next;
  }
  if (strcasecmp(nick,botname)==0) strcpy(botname,msg);
  if (strcasecmp(nick,newbotname)==0) {
    /* regained nick! */
    strcpy(botname,msg);
    newbotname[0]=0; waiting_for_awake=0;
    putlog(LOG_SERV|LOG_MISC,"*","Regained nickname '%s'.",botname);
  }
  clear_chanlist();   /* cache is meaningless now */
}

/* WALLOPS: oper's nussance */
void gotwall(from,msg)
char *from,*msg;
{
  char nick[NICKLEN]; char *p; int r;
  fixcolon(msg);
  p=strchr(from,'!');
  if ((p!=NULL) && (p==strrchr(from,'!'))) {
    splitnick(nick,from);
    r = check_tcl_wall(nick,msg);
    if (r==0) putlog(LOG_WALL,"*","!%s(%s)! %s",nick,from,msg);
  } else {
    r = check_tcl_wall(from,msg);
    if (r==0) putlog(LOG_WALL,"*","!%s! %s",from,msg);
  }
  return;
}

/* signoff, similar to part */
void gotquit(from,msg)
char *from,*msg;
{
  char nick[NICKLEN],hand[10]; int split=0;
  memberlist *m; char *p; struct chanset_t *chan;
  context;
  get_handle_by_host(hand,from);
  splitnick(nick,from); fixcolon(msg);
  /* Fred1: instead of expensive wild_match on signoff, quicker method */
  /* determine if signoff string matches "%.% %.%", and only one space */
  p=strchr(msg,' ');
  if ((p!=NULL) && (p==strrchr(msg,' '))) {
    char *z1,*z2;
    *p=0; z1=strchr(p+1,'.'); z2=strchr(msg,'.');
    if ((z1!=NULL) && (z2!=NULL) && (*(z1+1)!=0) && (z1-1 != p) &&
	(z2+1 != p) && (z2 != msg)) {
      /* server split, or else it looked like it anyway */
      /* (no harm in assuming) */
      split=1;
    }
    else *p=' ';
  }
  chan=chanset; while (chan!=NULL) {
    m=ismember(chan,nick); if (m!=NULL) {
      if (split) {
	m->split=time(NULL);
	check_tcl_splt(nick,from,hand,chan->name);
	putlog(LOG_JOIN,chan->name,"%s (%s) got netsplit.",nick,from);
      }
      else {
	check_tcl_sign(nick,from,hand,chan->name,msg);
	context;
	putlog(LOG_JOIN,chan->name,"%s (%s) left irc: %s",nick,from,msg);
	killmember(chan,nick);
	context;
	check_lonely_channel(chan);
	context;
      }
    }
    chan=chan->next;
  }
  context;
  update_laston(from);
  context;
}

/* msg to xx everyone on the channel's info */
void show_all_info(chname,who)
char *chname,*who;
{
  memberlist *m; char s[UHOSTLEN],nick[NICKLEN],also[512]; int atr;
  struct chanset_t *chan;
  context; also[0]=0;
  chan=findchan(chname); if (chan==NULL) {
    hprintf(serv,"NOTICE %s :I'm not on channel %s\n",who,chname);
    return;
  }
  m=chan->channel.member; while (m->nick[0]) {
    sprintf(s,"%s!%s",m->nick,m->userhost);
    get_handle_by_host(nick,s);
    get_handle_info(nick,s);
    atr=get_attr_handle(nick);
    if (atr & USER_BOT) s[0]=0;
    if (s[0]=='@') strcpy(s,&s[1]);
    if (s[0]) hprintf(serv,"NOTICE %s :[%9s] %s\n",who,m->nick,s);
    else {
      if (strcasecmp(m->nick,botname)==0)
	hprintf(serv,"NOTICE %s :[%9s] <-- I'm the bot, of course.\n",
		who,m->nick);
      else if (atr & USER_BOT) {
	if (atr & BOT_SHARE)
	  hprintf(serv,"NOTICE %s :[%9s] <-- a twin of me\n",who,m->nick);
	else
	  hprintf(serv,"NOTICE %s :[%9s] <-- another bot\n",who,m->nick);
      }
      else { strcat(also,m->nick); strcat(also,", "); }
    }
    m=m->next;
  }
  if (also[0]) {
    also[strlen(also)-2]=0;
    hprintf(serv,"NOTICE %s :No info: %s\n",who,also);
  }
}

/* request from a user to kickban (over dcc) */
void user_kickban(idx,nick)
int idx; char *nick;
{
  memberlist *m; char s[512],s1[181],note[512],*pp; int atr,atr1,i;
  struct chanset_t *chan;
  context;
  note[0]=0;
  if (strchr(nick,' ')!=NULL) {
    split(s,nick); strncpy(note,nick,65); note[65]=0; strcpy(nick,s);
  }
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"error: invalid console channel\n");
    return;
  }
  if (!(chan->stat&CHANACTIVE)) {
    dprintf(idx,"I'm not on %s right now!\n",chan->name);
    return;
  }
  if (strcasecmp(nick,botname)==0) {
    dprintf(idx,"You're trying to pull a Run?\n");
    return;
  }
  m=ismember(chan,nick);
  if (m==NULL) {
    dprintf(idx,"%s is not on %s\n",nick,chan->name);
    return;
  }
  sprintf(s,"%s!%s",m->nick,m->userhost);
  atr=get_attr_host(s);
  atr1=get_attr_handle(dcc[idx].nick);
  if (!(atr1 & USER_OWNER)) {
    if ((atr & USER_OP) && (!(atr1 & USER_MASTER))) {
      dprintf(idx,"%s is a legal op.\n",nick);
      return;
    }
    if (atr & USER_MASTER) {
      dprintf(idx,"%s is a bot master.\n",nick);
      return;
    }
    if (atr & USER_BOT) {
      dprintf(idx,"%s is another channel bot!\n",nick);
      return;
    }
  }
  strcpy(s,m->userhost); maskhost(s,s1);
  /* detect long username (10-char username can't add the '*') */
  i=0; pp=m->userhost; while ((*pp) && (*pp!='@')) { pp++; i++; }
  if (i>9) strcpy(s,s1);
  else sprintf(s,"*!*%s",&s1[2]);  /* gotta add that extra '*' */
  if (m->flags&CHANOP) add_mode(chan,'-','o',m->nick);
  add_mode(chan,'+','b',s); flush_mode(chan,QUICK);
  if (note[0]) tprintf(serv,"KICK %s %s :request: %s\n",chan->name,m->nick,note);
  else {
    strcpy(note,"request");
    tprintf(serv,"KICK %s %s :requested\n",chan->name,m->nick);
  }
  u_addban(chan->bans,s,dcc[idx].nick,note,time(NULL)+(60*ban_time));
  dprintf(idx,"Okay, done.\n");
}

/* request from a user to kick (over dcc) */
void user_kick(idx,nick)
int idx; char *nick;
{
  memberlist *m; char s[512],note[512]; int atr;
  struct chanset_t *chan;
  context;
  if (strchr(nick,' ')!=NULL) {
    split(s,nick); strncpy(note,nick,65); note[65]=0; strcpy(nick,s);
  }
  else strcpy(note,"request");
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"error: invalid console channel\n");
    return;
  }
  if (!(chan->stat&CHANACTIVE)) {
    dprintf(idx,"I'm not on %s right now!\n",chan->name);
    return;
  }
  if (strcasecmp(nick,botname)==0) {
    dprintf(idx,"But I don't WANT to kick myself!\n");
    return;
  }
  m=ismember(chan,nick);
  if (m==NULL) {
    dprintf(idx,"%s is not on %s\n",nick,chan->name);
    return;
  }
  sprintf(s,"%s!%s",m->nick,m->userhost);
  atr=get_attr_host(s);
  if (atr & USER_MASTER) {
    dprintf(idx,"%s is a bot master.\n",nick);
    return;
  }
  if (atr & USER_BOT) {
    dprintf(idx,"%s is another channel bot!\n",nick);
    return;
  }
  mprintf(serv,"KICK %s %s :%s\n",chan->name,m->nick,note);
  dprintf(idx,"Okay, done.\n");
}

/* add a user who's on the channel */
int add_chan_user(nick,idx)
char *nick; int idx;
{
  memberlist *m; char s[121],s1[121],hand[10]; struct chanset_t *chan;
  context;
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"error: invalid console channel\n");
    return 0;
  }
  if (!(chan->stat&CHANACTIVE)) {
    dprintf(idx,"I'm not on %s!\n",chan->name);
    return 0;
  }
  m=ismember(chan,nick); if (m==NULL) {
    dprintf(idx,"%s is not on %s.\n",nick,chan->name);
    return 0;
  }
  strncpy(hand,m->nick,9); hand[9]=0;
  sprintf(s,"%s!%s",m->nick,m->userhost);
  get_handle_by_host(s1,s);
  if (s1[0]!='*') {
    dprintf(idx,"%s is already known as %s.\n",nick,s1);
    return 0;
  }
#ifdef OWNER
  if ((get_attr_handle(s1) & USER_OWNER) &&
      !(get_attr_handle(dcc[idx].nick) & USER_OWNER) &&
      (strcmp(dcc[idx].nick,s1)!=0)) {
    dprintf(idx,"You can't add hostmasks to the bot owner.\n");
    return 0;
  }
#endif
  maskhost(s,s1);
  if (!is_user(hand)) {
    dprintf(idx,"Added [%s]%s with no password.\n",hand,s1);
    userlist=adduser(userlist,hand,s1,"-",default_flags);
    return 1;
  }
  else {
    char h[10];
    get_handle_by_host(h,s);
    if (strcmp(h,"*")!=0) {
      dprintf(idx,"This user already matches for %s!\n",h);
      return 0;
    }
    dprintf(idx,"Added hostmask %s to %s.\n",s1,hand);
    addhost_by_handle(hand,s1);
    if ((get_attr_handle(hand) & USER_OP) && (chan->stat & CHAN_OPONJOIN))
      add_mode(chan,'+','o',m->nick);
    return 1;
  }
}

/* add hostmask to a bot's record if possible */
int add_bot_hostmask(idx,nick)
int idx; char *nick;
{
  struct chanset_t *chan; memberlist *m; char s[UHOSTLEN],s1[UHOSTLEN];
  chan=chanset; while (chan!=NULL) {
    if (chan->stat&CHANACTIVE) {
      m=ismember(chan,nick); if (m!=NULL) {
	sprintf(s,"%s!%s",m->nick,m->userhost);
	get_handle_by_host(s1,s);
	if (s1[0]!='*') {
	  dprintf(idx,"(Can't add userhost for %s because it matches %s)\n",
		  nick,s1);
	  return 0;
	}
	maskhost(s,s1);
	dprintf(idx,"(Added hostmask for %s from %s)\n",nick,chan->name);
	addhost_by_handle(m->nick,s1);
	return 1;
      }
    }
    chan=chan->next;
  }
  return 0;
}

/* op/deop on the fly per master's request */
void give_op(nick,chan,idx)
char *nick; int idx; struct chanset_t *chan;
{
  memberlist *m; char s[121]; int atr;
  context;
  if (!(chan->stat&CHANACTIVE)) {
    dprintf(idx,"I'm not on %s!\n",chan->name);
    return;
  }
  m=ismember(chan,nick); if (m==NULL) {
    dprintf(idx,"%s is not on %s.\n",nick,chan->name);
    return;
  }
  sprintf(s,"%s!%s",m->nick,m->userhost);
  atr=get_attr_host(s);
  if (atr & USER_DEOP) {
    dprintf(idx,"%s is currently being auto-deopped.\n",m->nick);
    return;
  }
  if ((chan->stat&CHAN_BITCH) && (!(atr & USER_OP))) {
    dprintf(idx,"%s is not a registered op.\n",m->nick);
    return;
  }
  add_mode(chan,'+','o',nick);
  dprintf(idx,"Gave op to %s on %s\n",nick,chan->name);
}

void give_deop(nick,chan,idx)
char *nick; int idx; struct chanset_t *chan;
{
  memberlist *m; char s[121];
  context;
  if (!(chan->stat&CHANACTIVE)) {
    dprintf(idx,"I'm not on %s!\n",chan->name);
    return;
  }
  m=ismember(chan,nick); if (m==NULL) {
    dprintf(idx,"%s is not on %s.\n",nick,chan->name);
    return;
  }
  if (strcasecmp(nick,botname)==0) {
    dprintf(idx,"I'm not going to deop myself.\n");
    return;
  }
  sprintf(s,"%s!%s",m->nick,m->userhost);
  if (get_attr_host(s) & USER_OP) {
    dprintf(idx,"%s is +o in the user list.\n",m->nick);
    return;
  }
  add_mode(chan,'-','o',nick);
  dprintf(idx,"Took op from %s on %s\n",nick,chan->name);
}
