/*
   mode.c -- handles:
     queueing and flushing mode changes made by the bot
     channel mode changes and the bot's reaction to them
     setting and getting the current wanted channel modes

   dprintf'ized, 12dec95
   multi-channel, 6feb96
*/
/*
   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 "eggdrop.h"
#include "users.h"
#include "chan.h"
#include "proto.h"


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

extern int serv;
extern char botname[];
extern char botuserhost[];
extern char botuser[];
extern char botserver[];
extern char cx_file[];
extern int cx_line;
extern char origbotname[];
extern char SBUF[];
extern struct chanset_t *chanset;

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


void flush_mode(chan,pri)
struct chanset_t *chan; int pri;
{
  char *p,*post; int i,ok=0;
  p=SBUF; post=&SBUF[512]; *p=0; *post=0;
  if (chan->pls[0]) *p++='+';
  for (i=0; i<strlen(chan->pls); i++) *p++=chan->pls[i];
  if (chan->mns[0]) *p++='-';
  for (i=0; i<strlen(chan->mns); i++) *p++=chan->mns[i];
  chan->pls[0]=0; chan->mns[0]=0; ok=0;
  /* +k or +l ? */
  if (chan->key[0]) {
    if (!ok) { *p++='+'; ok=1; }
    *p++='k'; strcat(post,chan->key); strcat(post," ");
  }
  if (chan->limit != -1) {
    if (!ok) { *p++='+'; ok=1; }
    *p++='l'; sprintf(&post[strlen(post)],"%d ",chan->limit);
  }
  chan->limit=(-1); chan->key[0]=0;
  for (i=0; i<MODES_PER_LINE; i++) if (chan->cmode[i].type&PLUS) {
    if (!ok) { *p++='+'; ok=1; }
    *p++=(chan->cmode[i].type&BAN?'b':(chan->cmode[i].type&CHOP?'o':'v'));
    strcat(post,chan->cmode[i].op); strcat(post," ");
    nfree(chan->cmode[i].op); chan->cmode[i].op=NULL;
  } 
  ok=0;
  /* -k ? */
  if (chan->rmkey[0]) {
    if (!ok) { *p++='-'; ok=1; }
    *p++='k'; strcat(post,chan->rmkey); strcat(post," ");
  }
  chan->rmkey[0]=0;
  for (i=0; i<MODES_PER_LINE; i++) if (chan->cmode[i].type&MINUS) {
    if (!ok) { *p++='-'; ok=1; }
    *p++=(chan->cmode[i].type&BAN?'b':(chan->cmode[i].type&CHOP?'o':'v'));
    strcat(post,chan->cmode[i].op); strcat(post," ");
    nfree(chan->cmode[i].op); chan->cmode[i].op=NULL;
  }
  *p=0;
  for (i=0; i<MODES_PER_LINE; i++) chan->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]) {
    if (pri==QUICK) tprintf(serv,"MODE %s %s\n",chan->name,SBUF);
    else mprintf(serv,"MODE %s %s\n",chan->name,SBUF);
  }
}

/* for EVERY channel */
void flush_modes()
{
  struct chanset_t *chan;
  chan=chanset; while (chan!=NULL) {
    flush_mode(chan,NORMAL);
    chan=chan->next;
  }
}

/* queue a channel mode change */
void add_mode(chan,plus,mode,op)
struct chanset_t *chan; char plus,mode,*op;
{
  int i,type,ok; char s[21];
  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<MODES_PER_LINE; i++) 
      if ((chan->cmode[i].type==type) && (chan->cmode[i].op!=NULL) &&
	  (strcasecmp(chan->cmode[i].op,op)==0))
	return;   /* already in there :- duplicate */
    ok=0;  /* add mode to buffer */
    for (i=0; i<MODES_PER_LINE; i++) if ((chan->cmode[i].type==0) && (!ok)) {
      chan->cmode[i].type=type;
      chan->cmode[i].op=(char *)nmalloc(strlen(op)+1);
      strcpy(chan->cmode[i].op,op); ok=1;
    }
    ok=0;  /* check for full buffer */
    for (i=0; i<MODES_PER_LINE; i++) if (chan->cmode[i].type==0) ok=1;
    if (!ok) flush_mode(chan,NORMAL);      /* full buffer!  flush modes */
    return;
  }
  /* +k ? store key */
  if ((plus=='+') && (mode=='k')) {
    strcpy(chan->key,op); return;
  }
  /* -k ? store removed key */
  if ((plus=='-') && (mode=='k')) {
    strcpy(chan->rmkey,op); return;
  }
  /* +l ? store limit */
  if ((plus=='+') && (mode=='l')) {
    chan->limit=atoi(op); return;
  }
  /* typical mode changes */
  if (plus=='+') strcpy(s,chan->pls); else strcpy(s,chan->mns);
  if (strchr(s,mode)!=NULL) return;   /* duplicate */
  if (plus=='+') {
    chan->pls[strlen(chan->pls)+1]=0; chan->pls[strlen(chan->pls)]=mode;
  }
  else {
    chan->mns[strlen(chan->mns)+1]=0; chan->mns[strlen(chan->mns)]=mode;
  }
}

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

void got_key(chan,nick,from,key,atr)
struct chanset_t *chan; char *nick,*from,*key; int atr;
{
  int bogus=0,i;
  context; set_key(chan,key);
  for (i=0; i<strlen(key); i++)
    if (((key[i]<32) || (key[i]==127)) &&
	(key[i]!=2) && (key[i]!=31) && (key[i]!=22))
      bogus=1;
  if ((bogus) && (strcasecmp(nick,botname)!=0)) {
    putlog(LOG_MODES,chan->name,"Bogus channel key on %s!",chan->name);
    mprintf(serv,"KICK %s %s :bogus channel key\n",chan->name,nick);
  }
  if ((reversing) || (bogus) || ((chan->mode_mns_prot&CHANKEY) && 
				 (!(atr&(USER_MASTER|USER_BOT)))))
    add_mode(chan,'-','k',key);
}

void got_op(chan,nick,from,who,atr1)
struct chanset_t *chan; char *nick,*from,*who; int atr1;
{
  memberlist *m; char s[UHOSTLEN]; int atr;
  context;
  m=ismember(chan,who); if (m==NULL) {
    putlog(LOG_MISC,chan->name,"* Mode change on %s for nonexistent %s!",
	   chan->name,who);
    mprintf(serv,"WHO %s\n",who);
    return;
  }
  sprintf(s,"%s!%s",m->nick,m->userhost);
  atr=get_attr_host(s);
  if ((!me_op(chan)) && (strcasecmp(who,botname)==0)) newly_chanop(chan);
  else if ((me_op(chan)) && (strcasecmp(who,botname)!=0) &&
	   (!(m->flags & SENTDEOP))) {
    if ((chan->stat&CHAN_BITCH) && (!(atr & USER_OP)) && 
	(!(atr1 & (USER_MASTER|USER_BOT)))) {
      add_mode(chan,'-','o',who); m->flags|=SENTDEOP;
    }
    else if ((atr&USER_DEOP) && (!(atr&USER_OP)) &&
	     (!(atr1 & (USER_MASTER|USER_BOT)))) {
      add_mode(chan,'-','o',who); m->flags|=SENTDEOP;
    }
    else if (reversing) {
      add_mode(chan,'-','o',who); m->flags|=SENTDEOP;
    }
  }
  else if ((reversing) && (!(m->flags & SENTDEOP)) &&
	   (strcasecmp(who,botname)!=0)) {
    add_mode(chan,'-','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(chan)) && 
	(chan->stat&CHAN_STOPNETHACK)) {
      add_mode(chan,'-','o',who); m->flags|=(FAKEOP|SENTDEOP);
    }
  }
  else m->flags&=~FAKEOP;
  m->flags|=CHANOP;
  m->flags&=~SENTOP;
}

void got_deop(chan,nick,from,who,atr1)
struct chanset_t *chan; char *nick,*from,*who; int atr1;
{
  memberlist *m; char s[UHOSTLEN],s1[UHOSTLEN],s2[UHOSTLEN]; int atr;
  context;
  m=ismember(chan,who); if (m==NULL) {
    putlog(LOG_MISC,chan->name,"* Mode change on %s for nonexistent %s!",
	   chan->name,who);
    mprintf(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);
  /* deop'd someone on my oplist? */
  if ((me_op(chan)) && (atr & (USER_OP|USER_FRIEND)) && !(atr & USER_DEOP) &&
      (strcasecmp(nick,botname)!=0) && (strcasecmp(who,nick)!=0) &&
      (m->flags & CHANOP) &&
      (strcasecmp(who,botname)!=0)) {        /* added 25mar96, robey */
    /* reop? */
    if (!(atr1 & (USER_MASTER|USER_BOT)) && (chan->stat&CHAN_PROTECTOPS) &&
	(!(chan->stat&CHAN_BITCH) || (atr & (USER_OP|USER_MASTER))) &&
	/* ^ must be +o or +m to get re-op'd when +bitch is on */
	!(m->flags & SENTOP)) {
      add_mode(chan,'+','o',who); m->flags|=SENTOP;
    }
    else if (reversing) {
      add_mode(chan,'+','o',who); m->flags|=SENTOP;
    }
    if (!(atr1 & (USER_MASTER|USER_FRIEND|USER_BOT)) && 
	(chan->stat & CHAN_REVENGE)) {
      if (!nick[0])
	putlog(LOG_MODES,chan->name,"TS resync (%s): %s deopped by %s",
	       chan->name,who,from);
      else {
	sprintf(s2,"deopped %s",s);
	take_revenge(chan,s1,s2);    /* punish bad guy */
      }
    }
  }
  /* check for mass deop */
  if (!(atr1 & (USER_MASTER|USER_FRIEND|USER_BOT)) && (nick[0]) &&
      (strcasecmp(nick,botname)!=0)) {
    if ((strcasecmp(nick,chan->deopnick)==0) &&
        (strcasecmp(who,chan->deopd)!=0)) {
      time_t tx=time(NULL);
      strcpy(chan->deopd,who);
      if (tx - chan->deoptime <= 10) {
	chan->deops++; if (chan->deops >= 3) {
	  putlog(LOG_MODES,chan->name,"Mass deop on %s by %s",chan->name,s1);
	  tprintf(serv,"KICK %s %s :mass deop, go sit in a corner\n",
		  chan->name,nick);
	  chan->deopnick[0]=0; chan->deoptime=0L; chan->deops=0;
	  chan->deopd[0]=0;
	}
      }
      else {
	chan->deoptime=time(NULL); chan->deops=1; strcpy(chan->deopd,who);
      }
    }
    else {
      strcpy(chan->deopnick,nick); chan->deoptime=time(NULL); chan->deops=1;
      strcpy(chan->deopd,who);
    }
  }
  /* having op hides your +v status -- so now that someone's lost ops,
     check to see if they have +v */
  if (!(m->flags&CHANVOICE)) mprintf(serv,"WHO %s\n",m->nick);
  if ((strcasecmp(who,botname)==0) && (chan->stat & CHAN_REVENGE)) {
    /* deopped ME!  take revenge */
    if ((!(atr1 & (USER_MASTER|USER_FRIEND))) && (nick[0]) &&
	(strcasecmp(nick,botname)!=0))
      take_revenge(chan,s1,"deopped me");
    if (!nick[0])
      putlog(LOG_MODES,chan->name,"TS resync deopped me on %s :(",chan->name);
    if (chan->need_op[0]) do_tcl("need-op",chan->need_op);
  }
  m->flags&=~(FAKEOP|CHANOP|SENTDEOP);
}

void got_ban(chan,nick,from,who,atr)
struct chanset_t *chan; char *nick,*from,*who; int atr;
{
  char me[UHOSTLEN],s[UHOSTLEN],s1[UHOSTLEN]; int check,i,bogus;
  memberlist *m;
  context;
  sprintf(me,"%s!%s",botname,botuserhost);
  sprintf(s,"%s!%s",nick,from);
  newban(chan,who,s); bogus=0; check=1;
  if (strcasecmp(nick,botname)!=0) {    /* it's not my ban */
    if ((chan->stat&CHAN_NOUSERBANS) && (!(atr&USER_BOT)) && 
	(!(atr&USER_MASTER))) {
/*      mprintf(serv,"PRIVMSG %s :You may not place bans directly.\n",nick); */
      add_mode(chan,'-','b',who); return;
    }
    for (i=0; i<strlen(who); i++) 
      if (((who[i]<32) || (who[i]==127)) &&
	  (who[i]!=2) && (who[i]!=22) && (who[i]!=31))
	bogus=1;
    if (bogus) {
      if (atr & (USER_MASTER|USER_FRIEND|USER_BOT)) {
	/* fix their bogus ban */
	int ok=0;
	strcpy(s1,who);
	for (i=0; i<strlen(s1); i++) {
	  if (((s1[i]<32) || (s1[i]==127)) &&
	      (s1[i]!=2) && (s1[i]!=22) && (s1[i]!=31)) s1[i]='?';
	  if ((s1[i]!='?') && (s1[i]!='*') && (s1[i]!='!') && (s1[i]!='@'))
	    ok=1;
	}
	add_mode(chan,'-','b',who); flush_mode(chan,NORMAL);
	/* only re-add it if it has something besides wildcards */
	if (ok) add_mode(chan,'+','b',s1);
      }
      else {
	add_mode(chan,'-','b',who);
	mprintf(serv,"KICK %s %s :bogus ban\n",chan->name,nick);
      }
      return;
    }
    /* don't enforce a server ban right away -- give channel users a chance */
    /* to remove it, in case it's fake */
    if (!nick[0]) check=0;
    /* does this remotely match against any of our hostmasks? */
    /* just an off-chance... */
    if (get_attr_host(who) & (USER_OP|USER_FRIEND|USER_MASTER)) {
      if (!(atr & (USER_MASTER|USER_BOT))) {
	reversing=1; check=0;
      }
      if (get_attr_host(who) & USER_MASTER) check=0;
    }
    else if ((wild_match(who,me)) && (me_op(chan))) {
      reversing=1; check=0;
    }
    /* ^ don't really feel like being banned today, thank you! */
    else {
      /* banning an oplisted person who's on the channel? */
      m=chan->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)) {
	    /* remove ban on +o/f/m user, unless placed by another +m/b */
	    if (!(atr & (USER_MASTER|USER_BOT))) {
	      add_mode(chan,'-','b',who); check=0;
	    }
	    if (tatr&USER_MASTER) check=0;
	  }
	}
	m=m->next;
      }
    }
    if (check) kick_match_ban(chan,who);
  }
  else kick_match_ban(chan,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(chan,'-','b',who); 
  else */
  if (reversing) add_mode(chan,'-','b',who);
}

void got_unban(chan,nick,from,who,atr)
struct chanset_t *chan; char *nick,*from,*who; int atr;
{
  int i,bogus;
  killban(chan,who); bogus=0;
  for (i=0; i<strlen(who); i++)
    if (((who[i]<32) || (who[i]==127)) &&
	(who[i]!=2) && (who[i]!=22) && (who[i]!=31))
      bogus=1;
  if ((bogus) && (strcasecmp(nick,botname)!=0) && (!isbanned(chan,who)) &&
      !(atr & (USER_OP|USER_FRIEND|USER_MASTER|USER_BOT))) {
    tprintf(serv,"KICK %s %s :bogus ban\n",chan->name,nick);
    return;
  }
  if (((equals_ban(who)) || (u_equals_ban(chan->bans,who))) &&
      (me_op(chan)) && (!(chan->stat&CHAN_DYNAMICBANS))) {
    /* that's a permban! */
    if ((atr & (USER_BOT|BOT_SHARE)) == (USER_BOT|BOT_SHARE)) {
      /* sharebot -- do nothing */
    }
    else if (atr & (USER_MASTER|USER_OP)) {
      hprintf(serv,"NOTICE %s :%s is in my permban list.  You need to %s\n",
	      nick,who,"use '-ban' in dcc chat if you want it gone for good.");
    }
    else add_mode(chan,'+','b',who);
  }
  /* there's no need to reverse a -b mode unless it was something i wanted: */
  /* if (reversing) add_mode(chan,'+','b',who); */
}


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

  context; split(ch,msg); nsplit(chg,msg); reversing=0;
  /* usermode changes? */
  if ((ch[0]!='#') && (ch[0]!='&')) {
    if (strcasecmp(ch,botname)==0) {
      /* umode +r? */
      if ((chg[0]=='+') && (strchr(chg,'r')!=NULL)) {
	putlog(LOG_MISC|LOG_JOIN,"*","%s has me i-lined (jumping)",botserver);
	tprintf(serv,"QUIT :i-lines suck\n"); killsock(serv); serv=(-1);
	return;
      }
    }
    return;
  }
  chan=findchan(ch); if (chan==NULL) {
    putlog(LOG_MISC,"*","Oops, someone joined me to %s ... leaving ...",ch);
    tprintf(serv,"PART %s\n",ch); return;
  }
  if (chan->stat & CHANPEND) return;  /* not yet! */
  putlog(LOG_MODES,chan->name,"%s: mode change '%s %s' by %s",ch,chg,msg,from);
  atr=get_attr_host(from);
  get_handle_by_host(hand,from);
  splitnick(nick,from); i=0;
  m=ismember(chan,nick); if ((m!=NULL) && (me_op(chan))) {
    if (m->flags&FAKEOP) {
      putlog(LOG_MODES,ch,"Mode change by fake op on %s!  Reversing...",ch);
      mprintf(serv,"KICK %s %s :abusing ill-gained server ops\n",ch,nick);
      reversing=1;
    }
    else if (!(m->flags&CHANOP)) {
      putlog(LOG_MODES,ch,"Mode change by non-chanop on %s!  Reversing...",ch);
      mprintf(serv,"KICK %s %s :abusing desync\n",ch,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]=='q') modechg(CHANQUIET,'q');
    if (chg[i]=='l') {
      if (pos==-1) {
	check_tcl_mode(nick,from,hand,chan->name,"-l");
	if ((reversing) && (chan->channel.maxmembers!=(-1))) {
	  sprintf(s,"%d",chan->channel.maxmembers);
	  add_mode(chan,'+','l',s);
	}
	else if ((chan->limit_prot!=(-1)) && (!(atr&USER_MASTER))) {
	  sprintf(s,"%d",chan->limit_prot);
	  add_mode(chan,'+','l',s);
	}
	chan->channel.maxmembers=(-1);
      }
      else {
	nsplit(op,msg); chan->channel.maxmembers=atoi(op);
	sprintf(ms,"+l %d",chan->channel.maxmembers);
	check_tcl_mode(nick,from,hand,chan->name,ms);
	if ((reversing) ||
	    ((chan->mode_mns_prot&CHANLIMIT) && !(atr&USER_MASTER))) {
	  if (chan->channel.maxmembers==0) add_mode(chan,'+','l',"23");
	  add_mode(chan,'-','l',"");
	}
	if ((chan->limit_prot != chan->channel.maxmembers) &&
	    !(atr&USER_MASTER)) {
	  sprintf(s,"%d",chan->limit_prot);
	  add_mode(chan,'+','l',s);
	}
      }
    }
    if (chg[i]=='k') {
      nsplit(op,msg);
      sprintf(ms,"%ck %s",(pos==1)?'+':'-',op);
      check_tcl_mode(nick,from,hand,chan->name,ms);
      if (pos==1) got_key(chan,nick,from,op,atr);
      else {
	if ((reversing) && (chan->channel.key[0]))
	  add_mode(chan,'+','k',chan->channel.key);
	else if ((chan->key_prot[0]) && (!(atr&USER_MASTER)))
	  add_mode(chan,'+','k',chan->key_prot);
	set_key(chan,NULL);
      }
    }
    if (chg[i]=='o') {
      nsplit(op,msg);
      sprintf(ms,"%co %s",(pos==1)?'+':'-',op);
      check_tcl_mode(nick,from,hand,chan->name,ms);
      if (pos==1) got_op(chan,nick,from,op,atr);
      else got_deop(chan,nick,from,op,atr);
    }
    if (chg[i]=='v') {
      nsplit(op,msg); m=ismember(chan,op);
      if (m==NULL) {
	putlog(LOG_MISC,chan->name,"* Mode change on %s for nonexistent %s!",
	       chan->name,op);
	tprintf(serv,"WHO %s\n",op);
      }
      else {
	sprintf(ms,"%cv %s",(pos==1)?'+':'-',op);
	check_tcl_mode(nick,from,hand,chan->name,ms);
	if (pos==1) {
	  m->flags|=CHANVOICE;
	  if (reversing) add_mode(chan,'-','v',op);
	}
	else {
	  m->flags&=~CHANVOICE;
	  if (reversing) add_mode(chan,'+','v',op);
	}
      }
    }
    if (chg[i]=='b') {
      nsplit(op,msg);
      sprintf(ms,"%cb %s",(pos==1)?'+':'-',op);
      check_tcl_mode(nick,from,hand,chan->name,ms);
      if (pos==1) got_ban(chan,nick,from,op,atr);
      else got_unban(chan,nick,from,op,atr);
    }
    i++;
  }
}

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

/* interpret configfile setting for modes to protect */
#define protmode(x) { \
  chan->mode_pls_prot&=(~(x)); chan->mode_mns_prot&=(~(x)); \
  if (pos==1) chan->mode_pls_prot|=(x); else chan->mode_mns_prot|=(x); \
}
void set_mode_protect(chan,set)
struct chanset_t *chan; char *set;
{
  int i,pos=1; char s[121],s1[121];
  context; nsplit(s,set);
  /* clear old modes */
  chan->mode_mns_prot=chan->mode_pls_prot=0;
  chan->limit_prot=(-1); chan->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]=='q') protmode(CHANQUIET);
    if (s[i]=='l') {
      chan->mode_mns_prot&=(~CHANLIMIT); chan->limit_prot=(-1);
      if (pos==-1) chan->mode_mns_prot|=CHANLIMIT;
      else {
	nsplit(s1,set); if (s1[0]) chan->limit_prot=atoi(s1);
      }
    }
    if (s[i]=='k') {
      chan->mode_mns_prot&=(~CHANKEY); chan->key_prot[0]=0;
      if (pos==-1) chan->mode_mns_prot|=CHANKEY;
      else {
	nsplit(s1,set); if (s1[0]) strcpy(chan->key_prot,s1);
      }
    }
  }
  if (chan->stat&CHANACTIVE) recheck_chanmode(chan);
}

void get_mode_protect(chan,s)
struct chanset_t *chan; 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=chan->mode_pls_prot;
      if ((tst) || (chan->limit_prot!=(-1)) || (chan->key_prot[0])) *p++='+';
      if (chan->limit_prot!=(-1)) {
	*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&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';
    if (tst&CHANQUIET) *p++='q';
  }
  *p=0;
  if (s1[0]) {
    s1[strlen(s1)-1]=0; strcat(s," "); strcat(s,s1);
  }
}

