/* mode dumps */

#define _MODE
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "eggdrop.h"
#include "users.h"
#include "chan.h"
#include "proto.h"

/* buffer for storing mode changes by the bot */
char pls[41]="";
char mns[41]="";
char key[121]="";
char rmkey[121]="";
int limit=(-1);

/* data for detecting mass deops */
char deopnick[10]="";
time_t deoptime=0L;
int deops=0;

/* reversing this mode? */
int reversing=0;

/* modes the bot should protect positive */
int mode_pls_prot=0;
/* modes the bot should protect negative */
int mode_mns_prot=0;
/* channel limit to protect */
int limit_prot=(-1);
/* channel key to protect */
char key_prot[121]="";
/* reop a +o user if they get deopped? */
int protect_ops=1;
/* forbid bans to be made by users directly */
int forbid_bans=0;

extern int serv;
extern char curchan[];
extern struct chan_t channel;
extern int helpsock;
#ifdef TCL
extern char need_op[];
#else
extern char gainops[];
#endif
extern char botname[];
extern char botuserhost[];
extern char botuser[];
extern int revenge;
extern char cx_file[];
extern int cx_line;
extern int perm_bans;
extern int bitch;
extern char origbotname[];
extern char SBUF[];

#define PLUS    1
#define MINUS   2
#define CHOP    4
#define BAN     8
#define VOICE   16

/* mode changes that require parameters */
struct {
  char *op;
  char type;
} cmode[3]={ {NULL,0}, {NULL,0}, {NULL,0} };

void flush_mode()
{
  char *p,*post; int i,ok=0;
  context; p=SBUF; post=&SBUF[512]; *p=0; *post=0;
  if (pls[0]) *p++='+';
  for (i=0; i<strlen(pls); i++) *p++=pls[i];
  if (mns[0]) *p++='-';
  for (i=0; i<strlen(mns); i++) *p++=mns[i];
  pls[0]=0; mns[0]=0; ok=0;
  /* +k or +l ? */
  if (key[0]) {
    if (!ok) { *p++='+'; ok=1; }
    *p++='k'; strcat(post,key); strcat(post," ");
  }
  if (limit != -1) {
    if (!ok) { *p++='+'; ok=1; }
    *p++='l'; sprintf(&post[strlen(post)],"%d ",limit);
  }
  limit=(-1); key[0]=0;
  for (i=0; i<3; i++) if (cmode[i].type&PLUS) {
    if (!ok) { *p++='+'; ok=1; }
    *p++=(cmode[i].type&BAN?'b':(cmode[i].type&CHOP?'o':'v'));
    strcat(post,cmode[i].op); strcat(post," ");
    nfree(cmode[i].op); cmode[i].op=NULL;
  }
  ok=0;
  /* -k ? */
  if (rmkey[0]) {
    if (!ok) { *p++='-'; ok=1; }
    *p++='k'; strcat(post,rmkey); strcat(post," ");
  }
  rmkey[0]=0;
  for (i=0; i<3; i++) if (cmode[i].type&MINUS) {
    if (!ok) { *p++='-'; ok=1; }
    *p++=(cmode[i].type&BAN?'b':(cmode[i].type&CHOP?'o':'v'));
    strcat(post,cmode[i].op); strcat(post," ");
    nfree(cmode[i].op); cmode[i].op=NULL;
  }
  *p=0;
  for (i=0; i<3; i++) cmode[i].type=0;
  if (post[strlen(post)-1]==' ') post[strlen(post)-1]=0;
  if (post[0]) { strcat(SBUF," "); strcat(SBUF,post); }
  if (SBUF[0]) tprintf(serv,"MODE %s %s\n",curchan,SBUF);
}

void add_mode(plus,mode,op)
char plus,mode,*op;
{
  int i,type,ok; char s[21];
  context;
  if ((mode=='o') || (mode=='b') || (mode=='v')) {
    type=(plus=='+'?PLUS:MINUS)|(mode=='o'?CHOP:(mode=='b'?BAN:VOICE));
    /* op-type mode change */
    for (i=0; i<3; i++) 
      if ((cmode[i].type==type) && (cmode[i].op!=NULL) &&
	  (strcasecmp(cmode[i].op,op)==0))
	return;   /* already in there :- duplicate */
    ok=0;  /* add mode to buffer */
    for (i=0; i<3; i++) if ((cmode[i].type==0) && (!ok)) {
      cmode[i].type=type;
      cmode[i].op=(char *)nmalloc(strlen(op)+1);
      strcpy(cmode[i].op,op); ok=1;
    }
    ok=0;  /* check for full buffer */
    for (i=0; i<3; i++) if (cmode[i].type==0) ok=1;
    if (!ok) flush_mode();      /* full buffer!  flush modes */
    return;
  }
  /* +k ? store key */
  if ((plus=='+') && (mode=='k')) {
    strcpy(key,op); return;
  }
  /* -k ? store removed key */
  if ((plus=='-') && (mode=='k')) {
    strcpy(rmkey,op); return;
  }
  /* +l ? store limit */
  if ((plus=='+') && (mode=='l')) {
    limit=atoi(op); return;
  }
  /* typical mode changes */
  if (plus=='+') strcpy(s,pls); else strcpy(s,mns);
  if (strchr(s,mode)!=NULL) return;   /* duplicate */
  if (plus=='+') {
    pls[strlen(pls)+1]=0; pls[strlen(pls)]=mode;
  }
  else {
    mns[strlen(mns)+1]=0; mns[strlen(mns)]=mode;
  }
}

/**********************************************************************/
/* horrible code to parse mode changes */
/* no, it's not horrible, it just looks that way */

void got_key(nick,from,key,atr)
char *nick,*from,*key; int atr;
{
  int bogus=0,i;
  context; set_key(key);
  for (i=0; i<strlen(key); i++) if ((key[i]<32) || (key[i]>126)) bogus=1;
  if ((bogus) && (strcasecmp(nick,botname)!=0)) {
    log(LOG_MODES,"Bogus channel key!");
    tprintf(serv,"KICK %s %s :bogus channel key\n",curchan,nick);
  }
  if ((reversing) || (bogus) || ((mode_mns_prot&CHANKEY) && 
				 (!(atr&USER_MASTER))))
    add_mode('-','k',key);
}

void got_op(nick,from,who,atr1)
char *nick,*from,*who; int atr1;
{
  memberlist *m; char s[121]; int atr;
  context;
  m=ismember(who); if (m==NULL) {
    log(LOG_MISC,"* Mode change on nonexistant %s!",who);
    tprintf(serv,"WHO %s\n",who);
    return;
  }
  sprintf(s,"%s!%s",m->nick,m->userhost);
  atr=get_attr_host(s);
  if ((!me_op()) && (strcasecmp(who,botname)==0)) newly_chanop();
  else if ((me_op()) && (strcasecmp(who,botname)!=0) &&
	   (!(m->flags & SENTDEOP))) {
    if ((bitch) && (!(atr & USER_OP)) && (!(atr1 & USER_MASTER))) {
      add_mode('-','o',who); m->flags|=SENTDEOP;
    }
    else if ((atr&USER_DEOP) && (!(atr&USER_OP)) && (!(atr1&USER_MASTER))) {
      add_mode('-','o',who); m->flags|=SENTDEOP;
    }
    else if (reversing) {
      add_mode('-','o',who); m->flags|=SENTDEOP;
    }
  }
  else if ((reversing) && (!(m->flags & SENTDEOP)) &&
	   (strcasecmp(who,botname)!=0)) {
    add_mode('-','o',who); m->flags|=SENTDEOP;
  }
  if (nick[0]==0) {   /* server op! */
    if ((!(atr & (USER_OP|USER_FRIEND|USER_MASTER))) && (!(m->flags&CHANOP)) &&
	(!(m->flags&SENTDEOP)) && (me_op())) {
      add_mode('-','o',who); m->flags|=(FAKEOP|SENTDEOP);
    }
  }
  else m->flags&=~FAKEOP;
  m->flags|=CHANOP;
  m->flags&=~SENTOP;
}

void got_deop(nick,from,who,atr1)
char *nick,*from,*who; int atr1;
{
  memberlist *m; char s[161],s1[161],s2[161]; int atr;
  context;
  m=ismember(who); if (m==NULL) {
    log(LOG_MISC,"* Mode change on nonexistant %s!",who);
    tprintf(serv,"WHO %s\n",who);
    return;
  }
  sprintf(s,"%s!%s",m->nick,m->userhost); fixfrom(s);
  sprintf(s1,"%s!%s",nick,from);
  atr=get_attr_host(s);
  if ((me_op()) && (atr & (USER_OP|USER_FRIEND)) && (!(atr & USER_DEOP)) &&
      (strcasecmp(nick,botname)!=0) && (strcasecmp(who,nick)!=0) &&
      (!(m->flags & SENTOP)) && (protect_ops) && (m->flags & CHANOP)) {
    /* deop'd someone on my oplist */
    if (!(atr1 & USER_MASTER)) {    /* op her */
      add_mode('+','o',who); m->flags|=SENTOP;
    }
    else if (reversing) {
      add_mode('+','o',who); m->flags|=SENTOP;
    }
    if ((atr1 & (USER_MASTER|USER_FRIEND|USER_OP) == 0) && (revenge)) {
      sprintf(s2,"deopped %s",s);
      unprog_op_and_deop(s1,s2);    /* punish bad guy */
    }
  }
  /* check for mass deop */
  if ((atr1 & (USER_MASTER|USER_FRIEND|USER_BOT) == 0) &&
      (strcasecmp(nick,botname)!=0)) {
    if (strcasecmp(nick,deopnick)==0) {
      time_t tx=time(NULL);
      if (tx-deoptime <= 10) {
	deops++; if (deops>=3) {
	  log(LOG_MODES,"Mass deop by %s",s1);
	  tprintf(serv,"KICK %s %s :mass deop, go sit in a corner\n",curchan,
		  nick);
	  deopnick[0]=0; deoptime=0L; deops=0;
	}
      }
      else { deoptime=time(NULL); deops=1; }
    }
    else { strcpy(deopnick,nick); deoptime=time(NULL); deops=1; }
  }
  /* having op hides your +v status -- so now that someone's lost ops,
     check to see if they have +v */
  if (!(m->flags&CHANVOICE)) tprintf(serv,"WHO %s\n",m->nick);
  if ((strcasecmp(who,botname)==0) && (revenge)) {
    /* deopped ME!  take revenge */
    if (atr1 & (USER_MASTER|USER_FRIEND) == 0)
      unprog_op_and_deop(s1,"deopped me");
#ifdef TCL
    if (need_op[0]) do_tcl("need-op",need_op);
#else
    if (gainops[0]) tprintf(serv,"%s\n",gainops);
#endif
    ask_tandem_for_ops();
  }
  m->flags&=~(FAKEOP|CHANOP|SENTDEOP);
}

void got_ban(nick,from,who,atr)
char *nick,*from,*who; int atr;
{
  char me[161],s[161],s1[161]; int check=1,i,bogus; memberlist *m;
  context;
  sprintf(me,"%s!%s",botname,botuserhost);
  sprintf(s,"%s!%s",nick,from);
  newban(who,s); bogus=0;
  if (strcasecmp(nick,botname)!=0) {    /* it's not my ban */
    if ((forbid_bans) && (!(atr&USER_BOT)) && (!(atr&USER_MASTER))) {
      mprintf(serv,"PRIVMSG %s :You may not place bans directly.\n",nick);
      add_mode('-','b',who); return;
    }
    for (i=0; i<strlen(who); i++) if ((who[i]<32) || (who[i]>126)) bogus=1;
    if (bogus) {
      if (atr & (USER_MASTER|USER_FRIEND)) {
	/* fix their bogus ban */
	int ok=0;
	strcpy(s1,who);
	for (i=0; i<strlen(s1); i++) {
	  if ((s1[i]<32) || (s1[i]>126)) s1[i]='?';
	  if ((s1[i]!='?') && (s1[i]!='*') && (s1[i]!='!') && (s1[i]!='@'))
	    ok=1;
	}
	add_mode('-','b',who); flush_mode();
	/* only re-add it if it has something besides wildcards */
	if (ok) add_mode('+','b',s1);
      }
      else {
	add_mode('-','b',who);
	tprintf(serv,"KICK %s %s :bogus ban\n",curchan,nick);
      }
      return;
    }
    if ((wild_match(who,me)) && (me_op())) add_mode('-','b',who);
    /* ^ don't really feel like being banned today, thank you! */
    else if (!(atr & USER_MASTER)) {   /* don't contradict master bans */
      /* banning an oplisted person who's on the channel? */
      m=channel.member; while (m->nick[0]) {
	sprintf(s1,"%s!%s",m->nick,m->userhost);
	if (wild_match(who,s1)) {
	  int tatr=get_attr_host(s1);
	  if (tatr&(USER_OP|USER_FRIEND|USER_MASTER)) {
	    add_mode('-','b',who); check=0;
	  }
	}
	m=m->next;
      }
      if (check) kick_match_ban(who);
    }
    else kick_match_ban(who);
  }
  else kick_match_ban(who);
  /* is it a server ban from nowhere? */
  /* uncomment the following line to remove all server bans */
/*  if ((!nick[0]) && (!equals_ban(who)) && (check)) add_mode('-','b',who); 
  else */
  if (reversing) add_mode('-','b',who);
}

void got_unban(nick,from,who,atr)
char *nick,*from,*who; int atr;
{
  int i,bogus;
  killban(who); bogus=0;
  for (i=0; i<strlen(who); i++) if ((who[i]<32) || (who[i]>126)) bogus=1;
  if ((bogus) && (strcasecmp(nick,botname)!=0) && (!isbanned(who)) &&
      (atr & (USER_OP|USER_FRIEND|USER_MASTER) == 0)) {
    tprintf(serv,"KICK %s %s :bogus ban\n",curchan,nick);
    return;
  }
  if ((equals_ban(who)) && (me_op()) && (perm_bans)) {
    /* that's a permban! */
    if (atr & (USER_MASTER|USER_OP)) {
      hprintf(helpsock,"NOTICE %s :That's in my permban list.  You need %s\n",
	      nick,"to use '-ban' in dcc chat if you want it gone for good.");
    }
    else add_mode('+','b',who);
  }
  if (reversing) add_mode('+','b',who);
}


#ifdef TCL
#define modechg(x,y) { \
  char ms[3]; \
  ms[0]=(pos==1)?'+':'-'; ms[1]=y; ms[2]=0; \
  check_tcl_mode(nick,from,hand,ms); \
  if (pos==1) channel.mode|=(x); else channel.mode&=~(x); \
  if (((reversing) || ((pos==1)&&(mode_mns_prot&(x))) || \
      ((pos==-1)&&(mode_pls_prot&(x)))) && (!(atr&USER_MASTER))) \
    add_mode((pos==1)?'-':'+',y,""); \
}
#else
#define modechg(x,y) { \
  if (pos==1) channel.mode|=(x); else channel.mode&=~(x); \
  if (((reversing) || ((pos==1)&&(mode_mns_prot&(x))) || \
      ((pos==-1)&&(mode_pls_prot&(x)))) && (!(atr&USER_MASTER))) \
    add_mode((pos==1)?'-':'+',y,""); \
}
#endif /* TCL */
/* a pain in the ass: mode changes */
void gotmode(from,msg)
char *from,*msg;
{
  char nick[10],hand[10],chan[121],op[121],chg[81],s[161]; int pos=0,i,atr;
  memberlist *m;

  context; split(chan,msg); nsplit(chg,msg); reversing=0;
  /* discard usermode changes, and +channels don't have modes: */
  if ((chan[0]!='#') && (chan[0]!='&')) return;
  log(LOG_MODES,"Mode change '%s %s' by %s",chg,msg,from);
  atr=get_attr_host(from);
  get_handle_by_host(hand,from);
  splitnick(nick,from); i=0;
  m=ismember(nick); if (m!=NULL) if ((m->flags&FAKEOP) && (me_op())) {
    log(LOG_MODES,"Mode change by fake op!  Reversing...");
    tprintf(serv,"KICK %s %s :abusing ill-gained server ops\n",curchan,nick);
    reversing=1;
  }
  while (chg[i]!=0) {
    if (chg[i]=='+') pos=1;
    if (chg[i]=='-') pos=(-1);
    if (chg[i]=='i') modechg(CHANINV,'i');
    if (chg[i]=='p') modechg(CHANPRIV,'p');
    if (chg[i]=='s') modechg(CHANSEC,'s');
    if (chg[i]=='m') modechg(CHANMODER,'m');
    if (chg[i]=='t') modechg(CHANTOPIC,'t');
    if (chg[i]=='n') modechg(CHANNOMSG,'n');
    if (chg[i]=='a') modechg(CHANANON,'a');
    if (chg[i]=='l') {
      if (pos==-1) {
#ifdef TCL
	check_tcl_mode(nick,from,hand,"-l");
#endif
	if ((reversing) && (channel.maxmembers!=(-1))) {
	  sprintf(s,"%d",channel.maxmembers);
	  add_mode('+','l',s);
	}
	else if ((limit_prot!=(-1)) && (!(atr&USER_MASTER))) {
	  sprintf(s,"%d",limit_prot);
	  add_mode('+','l',s);
	}
	channel.maxmembers=(-1);
      }
      else {
	nsplit(op,msg); channel.maxmembers=atoi(op);
#ifdef TCL
	{
	  char ms[21];
	  sprintf(ms,"+l %d",channel.maxmembers);
	  check_tcl_mode(nick,from,hand,ms);
	}
#endif
	if ((reversing) || ((mode_mns_prot&CHANLIMIT)&&(!(atr&USER_MASTER)))) {
	  if (channel.maxmembers==0) add_mode('+','l',"23");
	  add_mode('-','l',"");
	}
      }
    }
    if (chg[i]=='k') {
      nsplit(op,msg);
#ifdef TCL
      {
	char ms[121];
	sprintf(ms,"%ck %s",(pos==1)?'+':'-',op);
	check_tcl_mode(nick,from,hand,ms);
      }
#endif
      if (pos==1) got_key(nick,from,op,atr);
      else {
	if ((reversing) && (channel.key[0])) add_mode('+','k',channel.key);
	else if ((key_prot[0]) && (!(atr&USER_MASTER)))
	  add_mode('+','k',key_prot);
	set_key(NULL);
      }
    }
    if (chg[i]=='o') {
      nsplit(op,msg);
#ifdef TCL
      {
	char ms[121];
	sprintf(ms,"%co %s",(pos==1)?'+':'-',op);
	check_tcl_mode(nick,from,hand,ms);
      }
#endif
      if (pos==1) got_op(nick,from,op,atr);
      else got_deop(nick,from,op,atr);
    }
    if (chg[i]=='v') {
      nsplit(op,msg); m=ismember(op);
      if (m==NULL) {
	log(LOG_MISC,"* Mode change on nonexistant %s!",op);
	tprintf(serv,"WHO %s\n",op);
      }
      else {
#ifdef TCL
	char ms[121];
	sprintf(ms,"%cv %s",(pos==1)?'+':'-',op);
	check_tcl_mode(nick,from,hand,ms);
#endif
	if (pos==1) {
	  m->flags|=CHANVOICE;
	  if (reversing) add_mode('-','v',op);
	}
	else {
	  m->flags&=~CHANVOICE;
	  if (reversing) add_mode('+','v',op);
	}
      }
    }
    if (chg[i]=='b') {
      nsplit(op,msg);
#ifdef TCL
      {
	char ms[121];
	sprintf(ms,"%cb %s",(pos==1)?'+':'-',op);
	check_tcl_mode(nick,from,hand,ms);
      }
#endif
      if (pos==1) got_ban(nick,from,op,atr);
      else got_unban(nick,from,op,atr);
    }
    i++;
  }
}

/* interpret configfile setting for modes to protect */
#define protmode(x) { \
  mode_pls_prot&=(~(x)); mode_mns_prot&=(~(x)); \
  if (pos==1) mode_pls_prot|=(x); else mode_mns_prot|=(x); \
}
void set_mode_protect(set)
char *set;
{
  int i,pos=1; char s[121],s1[121];
  context; nsplit(s,set);
  /* clear old modes */
  mode_mns_prot=mode_pls_prot=0;
  limit_prot=(-1); key_prot[0]=0;
  for (i=0; i<strlen(s); i++) {
    if (s[i]=='+') pos=1;
    if (s[i]=='-') pos=(-1);
    if (s[i]=='i') protmode(CHANINV);
    if (s[i]=='p') protmode(CHANPRIV);
    if (s[i]=='s') protmode(CHANSEC);
    if (s[i]=='m') protmode(CHANMODER);
    if (s[i]=='t') protmode(CHANTOPIC);
    if (s[i]=='n') protmode(CHANNOMSG);
    if (s[i]=='a') protmode(CHANANON);
    if (s[i]=='l') {
      mode_mns_prot&=(~CHANLIMIT); limit_prot=(-1);
      if (pos==-1) mode_mns_prot|=CHANLIMIT;
      else {
	nsplit(s1,set); if (s1[0]) limit_prot=atoi(s1);
      }
    }
    if (s[i]=='k') {
      mode_mns_prot&=(~CHANKEY); key_prot[0]=0;
      if (pos==-1) mode_mns_prot|=CHANKEY;
      else {
	nsplit(s1,set); if (s1[0]) strcpy(key_prot,s1);
      }
    }
  }
}

void get_mode_protect(s)
char *s;
{
  char *p=s,s1[121]; int ok=0,i,tst;
  s1[0]=0;
  for (i=0; i<2; i++) {
    ok=0;
    if (i==0) {
      tst=mode_pls_prot;
      if ((tst) || (limit_prot!=(-1)) || (key_prot[0])) *p++='+';
      if (limit_prot!=(-1)) {
	*p++='l'; sprintf(&s1[strlen(s1)],"%d ",limit_prot);
      }
      if (key_prot[0]) {
	*p++='k'; sprintf(&s1[strlen(s1)],"%s ",key_prot);
      }
    }
    else {
      tst=mode_mns_prot;
      if (tst) *p++='-';
    }
    if (tst&CHANINV) *p++='i';
    if (tst&CHANPRIV) *p++='p';
    if (tst&CHANSEC) *p++='s';
    if (tst&CHANMODER) *p++='m';
    if (tst&CHANTOPIC) *p++='t';
    if (tst&CHANNOMSG) *p++='n';
    if (tst&CHANLIMIT) *p++='l';
    if (tst&CHANKEY) *p++='k';
    if (tst&CHANANON) *p++='a';
  }
  *p=0;
  if (s1[0]) {
    s1[strlen(s1)-1]=0; strcat(s," "); strcat(s,s1);
  }
}

void recheck_chanmode()
{
  int i,chk; char plus,s[81];
  context;
  for (i=0; i<2; i++) {
    if (i==0) { chk=mode_pls_prot; plus='+'; }
    else { chk=mode_mns_prot; plus='-'; }
    if (chk&CHANINV) add_mode(plus,'i',"");
    if (chk&CHANPRIV) add_mode(plus,'p',"");
    if (chk&CHANSEC) add_mode(plus,'s',"");
    if (chk&CHANMODER) add_mode(plus,'m',"");
    if (chk&CHANTOPIC) add_mode(plus,'t',"");
    if (chk&CHANNOMSG) add_mode(plus,'n',"");
    if (chk&CHANANON) add_mode(plus,'a',"");
    if (chk&CHANLIMIT) {
      if (channel.maxmembers == 0) add_mode('+','l',"23");
      add_mode('-','l',"");
    }
    if (chk&CHANKEY) add_mode('-','k',channel.key);
  }
  if (limit_prot != channel.maxmembers) {
    sprintf(s,"%d",limit_prot); add_mode('+','l',s);
  }
  if (key_prot[0]) {
    if (channel.key[0]) if (strcasecmp(channel.key,key_prot)!=0)
      add_mode('-','k',channel.key);
    add_mode('+','k',key_prot);
  }
}
