]> jfr.im git - irc/quakenet/newserv.git/blobdiff - chanserv/chanservuser.c
CHANSERV: add ADDCHAN relay command.
[irc/quakenet/newserv.git] / chanserv / chanservuser.c
index 529b867de5febbc94e0eff7af6367ce67149a197..db70abd4ee388180be68820069c404652396e8ed 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "chanserv.h"
 
+#include "../core/hooks.h"
 #include "../core/schedule.h"
 #include "../core/config.h"
 #include "../localuser/localuser.h"
@@ -15,6 +16,7 @@
 #include "../nick/nick.h"
 #include "../parser/parser.h"
 #include "../lib/splitline.h"
+#include "../lib/irc_string.h"
 
 #include <string.h>
 #include <stdio.h>
@@ -29,7 +31,7 @@ CommandTree *csctcpcommands;
 void chanservuserhandler(nick *target, int message, void **params);
 
 void chanservreguser(void *arg) {
-  sstring *csnick,*csuser,*cshost,*csrealname;
+  sstring *csnick,*csuser,*cshost,*csrealname,*csaccount;
   chanindex *cip;
   regchan   *rcp;
   int i;
@@ -38,23 +40,27 @@ void chanservreguser(void *arg) {
   csuser=getcopyconfigitem("chanserv","user","TheQBot",USERLEN);
   cshost=getcopyconfigitem("chanserv","host","some.host",HOSTLEN);
   csrealname=getcopyconfigitem("chanserv","realname","ChannelService",REALLEN);
+  csaccount=getcopyconfigitem("chanserv","account",csnick&&csnick->content&&csnick->content[0]?csnick->content:"Q",ACCOUNTLEN);
+
+  Error("chanserv",ERR_INFO,"Connecting %s...",csnick->content);
 
   chanservnick=registerlocaluser(csnick->content,csuser->content,cshost->content,
-                                csrealname->content,NULL,
-                                UMODE_INV|UMODE_SERVICE|UMODE_DEAF,
+                                csrealname->content,csaccount->content,
+                                UMODE_INV|UMODE_SERVICE|UMODE_DEAF|UMODE_OPER|UMODE_ACCOUNT,
                                 &chanservuserhandler);
 
   freesstring(csnick);
   freesstring(csuser);
   freesstring(cshost);
   freesstring(csrealname);
-  
+  freesstring(csaccount);
+
   /* Now join channels */
   for (i=0;i<CHANNELHASHSIZE;i++) {
     for (cip=chantable[i];cip;cip=cip->next) {
       if (cip->channel && (rcp=cip->exts[chanservext]) && !CIsSuspended(rcp)) {
-        if (CIsJoined(rcp))
-          chanservjoinchan(cip->channel);
+        /* This will do timestamp faffing even if it won't actually join */
+        chanservjoinchan(cip->channel);
        /* Do a check at some future time.. */
        cs_schedupdate(cip, 1, 5);
        rcp->status |= (QCSTAT_OPCHECK | QCSTAT_MODECHECK | QCSTAT_BANCHECK);
@@ -64,6 +70,8 @@ void chanservreguser(void *arg) {
     }
   }
 
+  Error("chanserv",ERR_INFO,"Loaded and joined channels.");
+
   if (chanserv_init_status == CS_INIT_NOUSER) {
     /* If this is the first time, perform last init tasks */
     chanserv_finalinit();
@@ -103,6 +111,7 @@ void chanservuserhandler(nick *target, int message, void **params) {
       }
       cmd=findcommandintree(csctcpcommands, cargv[0]+1, 1);
       if (cmd) {
+       cmd->calls++;
        rejoinline(cargv[1],cargc-1);
        cmd->handler((void *)sender, cargc-1, &(cargv[1]));
       }      
@@ -135,6 +144,12 @@ void chanservuserhandler(nick *target, int message, void **params) {
        break;
       }
       
+      if ((cmd->level & QCMD_STAFF) && 
+         (!(rup=getreguserfromnick(sender)) || !UHasStaffPriv(rup))) {
+       chanservstdmessage(sender, QM_NOACCESS, cargv[0]);
+       break;
+      }
+      
       if ((cmd->level & QCMD_HELPER) && 
          (!(rup=getreguserfromnick(sender)) || !UHasHelperPriv(rup))) {
        chanservstdmessage(sender, QM_NOACCESS, cargv[0]);
@@ -159,12 +174,30 @@ void chanservuserhandler(nick *target, int message, void **params) {
        break;
       }
       
+      if ((cmd->level & QCMD_ACHIEVEMENTS) && !UIsDev(rup) &&
+          ((time(NULL) < ACHIEVEMENTS_START) || 
+           ((time(NULL) > ACHIEVEMENTS_END) && !UIsAchievements(rup)))) {
+        chanservstdmessage(sender, QM_UNKNOWNCMD, cargv[0]);
+        break;
+      } 
+    
+      if ((cmd->level & QCMD_TITLES) && !UIsDev(rup) &&
+          ((time(NULL) < ACHIEVEMENTS_START) ||
+           (time(NULL) > ACHIEVEMENTS_END))) {
+        chanservstdmessage(sender, QM_UNKNOWNCMD, cargv[0]);
+        break;
+      }
+      
+      cmd->calls++;
+      
       if (cmd->maxparams < (cargc-1)) {
        rejoinline(cargv[cmd->maxparams],cargc-(cmd->maxparams));
        cargc=(cmd->maxparams)+1;
       }
       
       cmd->handler((void *)sender, cargc-1, &(cargv[1]));
+      
+      triggerhook(HOOK_CHANSERV_CMD, sender);
     }
     break;
 
@@ -183,7 +216,7 @@ void chanservcommandclose() {
   destroycommandtree(csctcpcommands);
 }
 
-void chanservaddcommand(char *command, int flags, int maxparams, CommandHandler handler, char *description) {
+void chanservaddcommand(char *command, int flags, int maxparams, CommandHandler handler, char *description, const char *help) {
   Command *newcmd;
   cmdsummary *summary;
 
@@ -193,7 +226,8 @@ void chanservaddcommand(char *command, int flags, int maxparams, CommandHandler
   memset((void *)summary,0,sizeof(cmdsummary));
 
   summary->def=getsstring(description, 250);
-
+  summary->defhelp=(char *)help; /* Assume that help is a constant */
+  
   newcmd->ext=(void *)summary;
   loadcommandsummary(newcmd);
 }
@@ -228,15 +262,32 @@ void chanservremovecommand(char *command, CommandHandler handler) {
   deletecommandfromtree(cscommands, command, handler);
 }
 
+void chanservpartchan(channel *cp, char *reason) {
+  /* Sanity check that we exist and are on the channel.
+   *
+   * Note that we don't do any of the other usual sanity checks here; if
+   * this channel is unregged or suspended or whatever then all the more
+   * reason to get Q off it ASAP! */
+  if (!chanservnick || !cp || !cp->users || !getnumerichandlefromchanhash(cp->users, chanservnick->numeric))
+    return;  
+  
+  localpartchannel(chanservnick, cp, reason);
+}
+
 void chanservjoinchan(channel *cp) {
   regchan *rcp;
-  
+  unsigned int i;
+  nick *np;
+  reguser *rup;
+  regchanuser *rcup;
+  flag_t themodes = 0;
+
   /* Skip unregistered channels */
   if (!(rcp=cp->index->exts[chanservext]))
     return;
     
-  /* Check for a new timestamp */
-  if ((!rcp->ltimestamp) || (cp->timestamp < rcp->ltimestamp)) {
+  /* Check for a new timestamp.  But don't do anything stupid if the incoming timestamp is 0 */
+  if (cp->timestamp && ((!rcp->ltimestamp) || (cp->timestamp < rcp->ltimestamp))) {
     rcp->ltimestamp=cp->timestamp;
     csdb_updatechanneltimestamp(rcp);
   }
@@ -245,18 +296,62 @@ void chanservjoinchan(channel *cp) {
   if (!chanservnick)
     return;
 
+  /* In gerenal this function shouldn't be used any more as a reason to get
+   * Q to leave, but if it should be leaving then just part with no reason. */
   if ((CIsSuspended(rcp) || !CIsJoined(rcp)) && getnumerichandlefromchanhash(cp->users, chanservnick->numeric)) {
-    localpartchannel(chanservnick, cp);
+    localpartchannel(chanservnick, cp, NULL);
   }
-
-  /* OK, we're going to join the channel.  Since our timestamp must be less
-   * than or equal to the one already there it should be OK to burst on. 
-   * 
+  
+  /* Right, we are definately going to either join the channel or at least
+   * burst some modes onto it.
+   *
    * We will try and burst our view of the world; if the timestamps are
    * actually equal this will be mostly ignored and we will have to fix it
-   * up later */
+   * up later.  For modes we use the forced modes, plus the default channel
+   * modes (unless any of those are explicitly denied) */
+    
+  /* By default, we set the forcemodes and the default modes, but never denymodes..
+   * OK, actually we should only set the default modes if our timestamp is older.*/
+  if (cp->timestamp > rcp->ltimestamp)
+    themodes |= CHANMODE_DEFAULT;
+  else
+    themodes=0;
+    
+  themodes = (themodes | rcp->forcemodes) & ~rcp->denymodes;
+  
+  /* Now, if someone has just created a channel and we are going to set +i
+   * or +k on it, this will kick them off.  This could be construed as a
+   * bit rude if it's their channel, so if there is only one person on the
+   * channel and they have a right to be there, burst with default modes
+   * only to avoid them being netrider kicked.
+   */
+  if (cp->users->totalusers==1) {
+    for (i=0;i<cp->users->hashsize;i++) {
+      if (cp->users->content[i] != nouser) {
+        if ((np=getnickbynumeric(cp->users->content[i]&CU_NUMERICMASK)) &&
+            (rup=getreguserfromnick(np)) &&
+            (rcup=findreguseronchannel(rcp,rup)) &&
+            CUKnown(rcup)) {
+          /* OK, there was one user, and they are known on this channel. 
+           * Don't burst with +i or +k */
+          themodes &= ~(CHANMODE_INVITEONLY | CHANMODE_KEY);
+        }
+      }
+    }
+  }
+
+  /* We should be on the channel - join with our nick */
   if (!CIsSuspended(rcp) && CIsJoined(rcp) && !getnumerichandlefromchanhash(cp->users, chanservnick->numeric)) {
-    localburstontochannel(cp, chanservnick, rcp->ltimestamp, rcp->forcemodes, rcp->limit, (rcp->key)?rcp->key->content:NULL);
+    /* If we pass the key parameter here but are not setting +k (see above)
+     * then localburstontochannel() will ignore the key */
+    localburstontochannel(cp, chanservnick, rcp->ltimestamp, themodes, 
+                         rcp->limit, (rcp->key)?rcp->key->content:NULL);
+  }
+
+  /* We're not joining the channel - send the burst with no nick */  
+  if (!CIsSuspended(rcp) && !CIsJoined(rcp) && (cp->timestamp > rcp->ltimestamp)) {
+    localburstontochannel(cp, NULL, rcp->ltimestamp, themodes,
+                          rcp->limit, (rcp->key)?rcp->key->content:NULL);
   }
 }
 
@@ -266,8 +361,8 @@ void chanservstdmessage(nick *np, int messageid, ... ) {
   int notice;
   reguser *rup;
   int language;
-  va_list va;
-  char *message;
+  va_list va, va2;
+  char *message, *messageargs;
   char *bp2,*bp;
   int len;
 
@@ -289,11 +384,14 @@ void chanservstdmessage(nick *np, int messageid, ... ) {
   } else if (csmessages[0][messageid]) {
     message=csmessages[0][messageid]->content;
   } else {
-    message=defaultmessages[messageid];
+    message=defaultmessages[messageid*2];
   }
 
+  messageargs=defaultmessages[messageid*2+1];
+
   va_start(va,messageid);
-  vsnprintf(buf,5000,message,va);
+  va_copy(va2, va);
+  q9vsnprintf(buf,5000,message,messageargs,va);
   va_end(va);
 
   len=0;
@@ -326,9 +424,18 @@ void chanservstdmessage(nick *np, int messageid, ... ) {
       *bp2++=*bp;
     }
   }
+  
+  /* Special case: If it's a "not enough parameters" message, show the first line of help */
+  if (messageid==QM_NOTENOUGHPARAMS) {
+    char *command=va_arg(va2, char *);
+    cs_sendhelp(np, command, 1);
+    chanservstdmessage(np, QM_TYPEHELPFORHELP, command); 
+  }
+  
+  va_end(va2);
 }
 
-void chanservsendmessage(nick *np, char *message, ... ) {
+void chanservsendmessage_real(nick *np, int oneline, char *message, ... ) {
   char buf[5010]; /* Very large buffer.. */
   char buf2[512], *bp, *bp2;
   int notice;
@@ -372,7 +479,7 @@ void chanservsendmessage(nick *np, char *message, ... ) {
       }
 
       /* If we ran out of buffer, get out here */
-      if (!*bp)
+      if (!*bp || (*bp=='\n' && oneline))
        break;
       
       bp2=buf2;
@@ -389,22 +496,29 @@ void chanservwallmessage(char *message, ... ) {
   va_list va;
   nick *np;
   unsigned int i=0;
-  
+
   va_start(va,message);
   vsnprintf(buf,5000,message,va);
   va_end(va);
   
   /* Scan for users */
-  for (i=0;i<NICKHASHSIZE;i++)
-    for (np=nicktable[i];np;np=np->next)
-      if (IsOper(np))
-        chanservsendmessage(np, "%s", buf);
+  for (i=0;i<NICKHASHSIZE;i++) {
+    for (np=nicktable[i];np;np=np->next) {
+      if (!IsOper(np)) /* optimisation, if VIEWWALLMESSAGE changes change this */
+        continue;
+
+      if (!cs_privcheck(QPRIV_VIEWWALLMESSAGE, np))
+        continue;
+
+      chanservsendmessage(np, "%s", buf);
+    }
+  }
 }
 
 void chanservkillstdmessage(nick *target, int messageid, ... ) {
   char buf[512];
   int language;
-  char* message;
+  char *message, *messageargs;
   va_list va;
   reguser *rup;
   
@@ -418,30 +532,45 @@ void chanservkillstdmessage(nick *target, int messageid, ... ) {
   else if (csmessages[0][messageid])
     message=csmessages[0][messageid]->content;
   else
-    message=defaultmessages[messageid];
+    message=defaultmessages[messageid*2];
 
+  messageargs=defaultmessages[messageid*2+1];
   va_start(va, messageid);
-  vsnprintf(buf, 511, message, va);
+  q9vsnprintf(buf, 511, message, messageargs, va);
   va_end(va);
-  killuser(chanservnick, target, buf);
-}
-
-int checkmasterpassword(reguser *rup, const char *pass) {
-  if (!strncmp(rup->masterpass, pass, PASSLEN))
-    return 1;
-  return 0;
+  killuser(chanservnick, target, "%s", buf);
 }
 
 int checkpassword(reguser *rup, const char *pass) {
+  if (!(*rup->password))
+    return 0;
+
   if (!strncmp(rup->password, pass, PASSLEN))
     return 1;
   return 0;
 }
 
-int setmasterpassword(reguser *rup, const char *pass) {
-  strncpy(rup->masterpass, pass, PASSLEN);
-  rup->masterpass[PASSLEN]='\0';
-  return 1;
+int checkresponse(reguser *rup, const unsigned char *entropy, const char *response, CRAlgorithm algorithm) {
+  char usernamel[NICKLEN+1], *dp, *up;
+
+  if (!(*rup->password))
+    return 0;
+
+  for(up=rup->username,dp=usernamel;*up;)
+    *dp++ = ToLower(*up++);
+  *dp = '\0';
+
+  return algorithm(usernamel, rup->password, cs_calcchallenge(entropy), response);
+}
+
+int checkhashpass(reguser *rup, const char *junk, const char *hash) {
+  char usernamel[NICKLEN+1], *dp, *up;
+
+  for(up=rup->username,dp=usernamel;*up;)
+    *dp++ = ToLower(*up++);
+  *dp = '\0';
+
+  return cs_checkhashpass(usernamel, rup->password, junk, hash);
 }
 
 int setpassword(reguser *rup, const char *pass) {
@@ -453,7 +582,6 @@ int setpassword(reguser *rup, const char *pass) {
 void cs_checknick(nick *np) {
   activeuser* aup;
   reguser *rup;
-  nicklist *nlp;
   char userhost[USERLEN+HOSTLEN+3];
   
   if (!(aup=getactiveuserfromnick(np))) {
@@ -463,30 +591,49 @@ void cs_checknick(nick *np) {
   
   assert(getactiveuserfromnick(np));
 
-  if (IsAccount(np)) {
-    if ((rup=findreguserbynick(np->authname))!=NULL) {
-      aup->rup=rup;
-      nlp=getnicklist();
-      nlp->np=np;
-      nlp->next=rup->nicks;
-      rup->nicks=nlp;
+  if (IsAccount(np) && np->auth) {
+    if (np->auth->exts[chanservaext]) {
+      rup=getreguserfromnick(np);
+
+      /* safe? */
+      if(rup) {
+        if (UIsDelayedGline(rup)) {
+          /* delayed-gline - schedule the user's squelching */
+          deleteschedule(NULL, &chanservdgline, (void*)rup); /* icky, but necessary unless we stick more stuff in reguser structure */
+          scheduleoneshot(time(NULL)+rand()%900, &chanservdgline, (void*)rup);
+        } else if (UIsGline(rup)) {
+          /* instant-gline - lets be lazy and set a schedule expiring now :) */
+          deleteschedule(NULL, &chanservdgline, (void*)rup); /* icky, but necessary unless we stick more stuff in reguser structure */
+          scheduleoneshot(time(NULL), &chanservdgline, (void*)rup);
+        } else if(UHasSuspension(rup)) {
+          chanservkillstdmessage(np, QM_SUSPENDKILL);
+          return;
+        }
+      }
       cs_doallautomodes(np);
     } else {
-      aup->rup=NULL;
       /* Auto create user.. */
       rup=getreguser();
       rup->status=0;
-      rup->ID=++lastuserID;
+      rup->ID=np->auth->userid;
+      if (rup->ID > lastuserID)
+        lastuserID=rup->ID;
       strncpy(rup->username,np->authname,NICKLEN); rup->username[NICKLEN]='\0';
       rup->created=time(NULL);
-      rup->lastauth=time(NULL);
+      rup->lastauth=0;
       rup->lastemailchange=0;
+      rup->lastpasschange=0;
       rup->flags=QUFLAG_NOTICE;
       rup->languageid=0;
       rup->suspendby=0;
+      rup->suspendexp=0;
+      rup->suspendtime=0;
+      rup->lockuntil=0;
       rup->password[0]='\0';
-      rup->masterpass[0]='\0';
       rup->email=NULL;
+      rup->lastemail=NULL;
+      rup->localpart=NULL;
+      rup->domain=NULL;
       rup->info=NULL;
       sprintf(userhost,"%s@%s",np->ident,np->host->name->content);
       rup->lastuserhost=getsstring(userhost,USERLEN+HOSTLEN+1);
@@ -496,16 +643,10 @@ void cs_checknick(nick *np) {
       rup->checkshd=NULL;
       rup->stealcount=0;
       rup->fakeuser=NULL;
-      rup->nicks=getnicklist();
-      rup->nicks->np=np;
-      rup->nicks->next=NULL;
       addregusertohash(rup);
 
       csdb_createuser(rup);
-      aup->rup=rup;
     }
-  } else {
-    aup->rup=NULL;
   }
 
   cs_checknickbans(np);
@@ -554,6 +695,9 @@ void cs_docheckchanmodes(channel *cp, modechanges *changes) {
   }
 }
 
+/* Slightly misnamed function that checks each USER on the channel against channel
+ * settings.  Possible actions are opping/deopping, voicing/devoicing, and banning
+ * for chanflag +k or chanlev flag +b */
 void cs_docheckopvoice(channel *cp, modechanges *changes) {
   regchan *rcp;
   reguser *rup;
@@ -572,7 +716,7 @@ void cs_docheckopvoice(channel *cp, modechanges *changes) {
       continue;
       
     if ((np=getnickbynumeric(cp->users->content[i]))==NULL) {
-      Error("chanserv",ERR_ERROR,"Found non-existent numeric %d on channel %s",cp->users->content[i],
+      Error("chanserv",ERR_ERROR,"Found non-existent numeric %lu on channel %s",cp->users->content[i],
             cp->index->name->content);
       continue;
     }
@@ -582,23 +726,33 @@ void cs_docheckopvoice(channel *cp, modechanges *changes) {
     else
       rcup=NULL;
     
-    if (rcup && CUIsBanned(rcup) && !IsService(np)) {
-      cs_banuser(changes, cp->index, np, NULL);
-      continue;
-    }
-
-    if (!IsService(np) && CIsKnownOnly(rcp) && !(rcup && CUKnown(rcup))) {
-      cs_banuser(changes, cp->index, np, "Authorised users only.");
-      continue;
+    /* Various things that might ban the user - don't apply these to +o, +k or +X users */
+    if (!IsService(np) && !IsXOper(np) && !IsOper(np)) {
+      if (rcup && CUIsBanned(rcup)) {
+        cs_banuser(changes, cp->index, np, NULL);
+        continue;
+      }
+      
+      /* chanflag +k checks; kick them if they "obviously" can't rejoin without a ban */
+      if (CIsKnownOnly(rcp) && !(rcup && CUKnown(rcup))) {
+        if (IsInviteOnly(cp) || (IsRegOnly(cp) && !IsAccount(np))) {
+          localkickuser(chanservnick,cp,np,"Authorised users only.");
+        } else {
+          cs_banuser(NULL, cp->index, np, "Authorised users only.");
+        }
+        continue;
+      }
     }
-    
+                                      
     if ((cp->users->content[i] & CUMODE_OP) && !IsService(np)) {
       if ((CIsBitch(rcp) && (!rcup || !CUHasOpPriv(rcup))) ||
            (rcup && CUIsDeny(rcup)))
         localdosetmode_nick(changes, np, MC_DEOP);
     } else {
-      if (rcup && (CUIsProtect(rcup) || CIsProtect(rcp)) && CUIsOp(rcup) && !CUIsDeny(rcup))
+      if (rcup && (CUIsProtect(rcup) || CIsProtect(rcp)) && CUIsOp(rcup) && !CUIsDeny(rcup)) {
         localdosetmode_nick(changes, np, MC_OP);    
+        cs_logchanop(rcp, np->nick, rcup->user);
+      }
     }
     
     if (cp->users->content[i] & CUMODE_VOICE) {
@@ -621,19 +775,44 @@ void cs_doallautomodes(nick *np) {
     return;
   
   for (rcup=rup->knownon;rcup;rcup=rcup->nextbyuser) {
+    /* Skip suspended channels */
+    if (CIsSuspended(rcup->chan))
+      continue;
+      
     if (rcup->chan->index->channel) {
       /* Channel exists */
       if ((lp=getnumerichandlefromchanhash(rcup->chan->index->channel->users, np->numeric))) {
-       
-       /* User is on channel.. */
+        /* User is on channel.. */
+
+        /* Update last use time.  Do early in case of ban. */
+        rcup->usetime=getnettime();
+        
+        if (CUIsBanned(rcup)) {
+          cs_banuser(NULL, rcup->chan->index, np, NULL);
+          continue;
+        }
+
+        if (CUHasOpPriv(rcup) && cs_ischannelactive(rcup->chan->index->channel, NULL)) {
+          /* This meets the channel use criteria, update. */
+          rcup->chan->lastactive=time(NULL);
+          
+          /* Don't spam the DB though for channels with lots of joins */
+          if (rcup->chan->lastcountersync < (time(NULL) - COUNTERSYNCINTERVAL)) {
+            csdb_updatechannelcounters(rcup->chan);
+            rcup->chan->lastcountersync=time(NULL);
+          }
+        }
+
        localsetmodeinit(&changes, rcup->chan->index->channel, chanservnick);
        if (*lp & CUMODE_OP) {
          if (!IsService(np) && (CUIsDeny(rcup) || (CIsBitch(rcup->chan) && !CUHasOpPriv(rcup))))
            localdosetmode_nick(&changes, np, MC_DEOP);
        } else {
          if (!CUIsDeny(rcup) && CUIsOp(rcup) && 
-             (CUIsProtect(rcup) || CIsProtect(rcup->chan) || CUIsAutoOp(rcup)))
+             (CUIsProtect(rcup) || CIsProtect(rcup->chan) || CUIsAutoOp(rcup))) {
            localdosetmode_nick(&changes, np, MC_OP);
+           cs_logchanop(rcup->chan, np->nick, rup);
+          }
        }
        
        if (*lp & CUMODE_VOICE) {
@@ -648,11 +827,16 @@ void cs_doallautomodes(nick *np) {
       } else {
        /* Channel exists but user is not joined: invite if they are +j-b */
        if (CUIsAutoInvite(rcup) && CUKnown(rcup) && !CUIsBanned(rcup)) {
-         localinvite(chanservnick, rcup->chan->index->channel, np);
+         localinvite(chanservnick, rcup->chan->index, np);
        }
       }
-    }
-  }
+    } /* if (rcup->chan->index->channel) */ else {
+      /* Channel doesn't currently exist - send invite anyway for +j */
+      if (CUIsAutoInvite(rcup) && CUKnown(rcup) && !CUIsBanned(rcup)) {
+        localinvite(chanservnick, rcup->chan->index, np);
+      }
+    } 
+  } /* for */
 }
 
 void cs_checknickbans(nick *np) {
@@ -660,7 +844,7 @@ void cs_checknickbans(nick *np) {
   regchan *rcp;
   int i,j;
 
-  if (IsService(np))
+  if (IsService(np) || IsOper(np) || IsXOper(np))
     return;
 
   /* Avoid races: memcpy the channel array */
@@ -670,7 +854,7 @@ void cs_checknickbans(nick *np) {
 
   for (j=0;j<i;j++) {
     if ((rcp=ca[j]->index->exts[chanservext]) && !CIsSuspended(rcp) && 
-       CIsEnforce(rcp) && nickbanned(np, ca[j]))
+       CIsEnforce(rcp) && nickbanned(np, ca[j], 1))
       localkickuser(chanservnick, ca[j], np, "Banned.");
   }
 
@@ -701,18 +885,18 @@ void cs_checkbans(channel *cp) {
       continue;
     
     if ((np=getnickbynumeric(cp->users->content[i]))==NULL) {
-      Error("chanserv",ERR_ERROR,"Found numeric %d on channel %s who doesn't exist.",
+      Error("chanserv",ERR_ERROR,"Found numeric %lu on channel %s who doesn't exist.",
            cp->users->content[i], cp->index->name->content);
       continue;
     }
     
-    if (IsService(np))
+    if (IsService(np) || IsOper(np) || IsXOper(np))
       continue;
 
     for (rbp=rcp->bans;rbp;rbp=rbp->next) {
       if (((!rbp->expiry) || (rbp->expiry <= now)) &&
-         nickmatchban(np, rbp->cbp)) {
-       if (!nickbanned(np, cp)) {
+         nickmatchban(np, rbp->cbp, 1)) {
+       if (!nickbanned(np, cp, 1)) {
          localdosetmode_ban(&changes, bantostring(rbp->cbp), MCB_ADD);
        }
        localkickuser(chanservnick,cp,np,rbp->reason?rbp->reason->content:"Banned.");
@@ -725,7 +909,7 @@ void cs_checkbans(channel *cp) {
 
     if (CIsEnforce(rcp)) {
       for (cbp=cp->bans;cbp;cbp=cbp->next) {
-       if ((cbp->timeset>=rcp->lastbancheck) && nickmatchban(np, cbp))
+       if ((cbp->timeset>=rcp->lastbancheck) && nickmatchban(np, cbp, 1))
          localkickuser(chanservnick,cp,np,"Banned.");
       }
       rcp->lastbancheck=time(NULL);
@@ -797,7 +981,7 @@ void cs_timerfunc(void *arg) {
     /* Only chanserv left in this channel */
     if (now >= (rcp->lastpart + LINGERTIME)) {
       /* Time to go */
-      localpartchannel(chanservnick, cip->channel);
+      localpartchannel(chanservnick, cip->channel, "Empty Channel");
       return;
     } else {
       if (!nextsched || nextsched > (rcp->lastpart + LINGERTIME))
@@ -809,9 +993,14 @@ void cs_timerfunc(void *arg) {
   
   if (CIsAutoLimit(rcp)) {
     if (!rcp->limit || rcp->autoupdate <= now) {
-      /* Update limit.. */
-      rcp->limit=cp->users->totalusers+rcp->autolimit;
-      rcp->status |= QCSTAT_MODECHECK;
+      /* Only update the limit if there are <= (autolimit/2) or 
+       * >= (autolimit * 1.5) slots free */
+      
+      if ((cp->users->totalusers >= (rcp->limit - rcp->autolimit/2)) ||
+          (cp->users->totalusers <= (rcp->limit - (3 * rcp->autolimit)/2))) {
+        rcp->limit=cp->users->totalusers+rcp->autolimit;
+        rcp->status |= QCSTAT_MODECHECK;
+      }
       
       /* And set the schedule for the next update */
       rcp->autoupdate = now + AUTOLIMIT_INTERVAL;
@@ -885,7 +1074,7 @@ void cs_timerfunc(void *arg) {
   localsetmodeflush(&changes, 1);
 }
 
-void cs_removechannel(regchan *rcp) {
+void cs_removechannel(regchan *rcp, char *reason) {
   int i;
   chanindex *cip;
   regchanuser *rcup, *nrcup;
@@ -914,8 +1103,7 @@ void cs_removechannel(regchan *rcp) {
     deleteschedule(rcp->checksched, cs_timerfunc, rcp->index);
     
   if (cip->channel) {
-    rcp->flags=QCFLAG_SUSPENDED;
-    chanservjoinchan(cip->channel); /* Force off the channel */
+    chanservpartchan(cip->channel, reason);
   }
   
   csdb_deletechannel(rcp);
@@ -932,34 +1120,43 @@ void cs_removechannel(regchan *rcp) {
   releasechanindex(cip);
 }
 
+/* Sender is who the DELCHAN is attributed to.. */
+int cs_removechannelifempty(nick *sender, regchan *rcp) {
+  unsigned int i;
+  regchanuser *rcup;
+  
+  for (i=0;i<REGCHANUSERHASHSIZE;i++) {
+    for (rcup=rcp->regusers[i];rcup;rcup=rcup->nextbychan) {
+      if (rcup->flags & ~(QCUFLAGS_PUNISH))
+        return 0;
+    }
+  }
+  
+  cs_log(sender,"DELCHAN %s (Empty)",rcp->index->name->content);
+  cs_removechannel(rcp, "Last user removed - channel deleted.");
+  
+  return 1;
+}
+
 void cs_removeuser(reguser *rup) {
-  int i;
   regchanuser *rcup, *nrcup;
   regchan *rcp;
+  struct authname *anp;
   
   /* Remove the user from all its channels */
   for (rcup=rup->knownon;rcup;rcup=nrcup) {
     nrcup=rcup->nextbyuser;
-    freesstring(rcup->info);
     rcp=rcup->chan;
 
     delreguserfromchannel(rcp, rup);
-
-    for (i=0;i<REGCHANUSERHASHSIZE;i++) {
-      if (rcp->regusers[i])
-       break;
-    }
-
-    if (i==REGCHANUSERHASHSIZE) {
-      /* There are no users left on this channel! */
-      cs_log(NULL, "DELCHAN %s (last user removed)",rcp->index->name->content);
-      cs_removechannel(rcp);
-    }
-    
-    freeregchanuser(rcup);
+    cs_removechannelifempty(NULL, rcp);
   }
 
+  if(rup->domain)
+    delreguserfrommaildomain(rup,rup->domain);
+  freesstring(rup->localpart);
   freesstring(rup->email);
+  freesstring(rup->lastemail);
   freesstring(rup->lastuserhost);
   freesstring(rup->suspendreason);
   freesstring(rup->comment);
@@ -969,7 +1166,7 @@ void cs_removeuser(reguser *rup) {
 
   removereguserfromhash(rup);
   
-  if (rup->nicks) {
+  if ((anp=findauthname(rup->ID)) && anp->nicks) {
     rup->status |= QUSTAT_DEAD;
   } else {
     freereguser(rup);
@@ -995,9 +1192,9 @@ int cs_bancheck(nick *np, channel *cp) {
       freesstring(rbp->reason);
       freechanban(rbp->cbp);
       freeregban(rbp);
-    } else if (nickmatchban(np,(*rbh)->cbp)) {
+    } else if (nickmatchban(np,(*rbh)->cbp,1)) {
       /* This user matches this ban.. */
-      if (!nickbanned(np,cp)) {
+      if (!nickbanned(np,cp,1)) {
        /* Only bother putting the ban on the channel if they're not banned already */
        /* (might be covered by this ban or a different one.. doesn't really matter */
        localsetmodeinit(&changes, cp, chanservnick);
@@ -1022,7 +1219,7 @@ void cs_setregban(chanindex *cip, regban *rbp) {
 
   if (!cip->channel)
     return;
-
+    
   localsetmodeinit(&changes, cip->channel, chanservnick);
   localdosetmode_ban(&changes, bantostring(rbp->cbp), MCB_ADD);
   localsetmodeflush(&changes, 1);
@@ -1030,8 +1227,8 @@ void cs_setregban(chanindex *cip, regban *rbp) {
   for (i=0;(cip->channel) && i<cip->channel->users->hashsize;i++) {
     if (cip->channel->users->content[i]!=nouser &&
        (np=getnickbynumeric(cip->channel->users->content[i])) &&
-       !IsService(np) &&
-       nickmatchban(np, rbp->cbp))
+       !IsService(np) && !IsOper(np) && !IsXOper(np) &&
+       nickmatchban(np, rbp->cbp, 1))
       localkickuser(chanservnick, cip->channel, np, rbp->reason ? rbp->reason->content : "Banned.");
   }
 
@@ -1045,7 +1242,7 @@ void cs_banuser(modechanges *changes, chanindex *cip, nick *np, const char *reas
   if (!cip->channel)
     return;
 
-  if (nickbanned(np, cip->channel)) {
+  if (nickbanned(np, cip->channel, 1)) {
     localkickuser(chanservnick, cip->channel, np, reason?reason:"Banned.");
     return;
   }
@@ -1073,5 +1270,272 @@ void cs_banuser(modechanges *changes, chanindex *cip, nick *np, const char *reas
   
   localkickuser(chanservnick, cip->channel, np, reason?reason:"Banned.");
 }
+
+/*
+ * cs_sanitisechanlev: Removes impossible combinations from chanlev flags.
+ * chanservuser.c is probably not the right file for this, but nowhere better
+ * presented itself...
+ */
+flag_t cs_sanitisechanlev(flag_t flags) {
+  /* +m or +n cannot have any "punishment" flags */
+  if (flags & (QCUFLAG_MASTER | QCUFLAG_OWNER))
+    flags &= ~(QCUFLAGS_PUNISH);
+  
+  /* +d can't be +o */
+  if (flags & QCUFLAG_DENY)
+    flags &= ~QCUFLAG_OP;
+  
+  /* +q can't be +v */
+  if (flags & QCUFLAG_QUIET)
+    flags &= ~QCUFLAG_VOICE;
+  
+  /* +p trumps +a and +g */
+  if (flags & QCUFLAG_PROTECT)
+    flags &= ~(QCUFLAG_AUTOOP | QCUFLAG_AUTOVOICE);
+    
+  /* -o can't be +a */
+  if (!(flags & QCUFLAG_OP)) 
+    flags &= ~QCUFLAG_AUTOOP;
+  
+  /* +a or -v can't be +g.  +a implies +o at this stage (see above) */
+  if (!(flags & QCUFLAG_VOICE) || (flags & QCUFLAG_AUTOOP))
+    flags &= ~QCUFLAG_AUTOVOICE;
+  
+  /* +p requires +o or +v */
+  if (!(flags & (QCUFLAG_VOICE | QCUFLAG_OP)))
+    flags &= ~QCUFLAG_PROTECT;
+  
+  /* The personal flags require one of +mnovk, as does +t */
+  if (!(flags & (QCUFLAG_OWNER | QCUFLAG_MASTER | QCUFLAG_OP | QCUFLAG_VOICE | QCUFLAG_KNOWN)))
+    flags &= ~(QCUFLAGS_PERSONAL | QCUFLAG_TOPIC);
+  
+  return flags;
+}
              
+/*
+ * findreguser:
+ *  This function does the standard "nick or #user" lookup.
+ *  If "sender" is not NULL, a suitable error message will
+ *  be sent if the lookup fails.
+ *  "sender" MUST be sent when a user is requesting a lookup
+ *  as there is some policy code here.
+ */
+
+reguser *findreguser(nick *sender, const char *str) {
+  reguser *rup, *vrup = getreguserfromnick(sender);;
+  nick *np;
+
+  if (!str || !*str)
+    return NULL;
+
+  if (*str=='#') {
+    if (str[1]=='\0') {
+      if (sender)
+       chanservstdmessage(sender, QM_UNKNOWNUSER, str);
+      return NULL;
+    }
+    if (!(rup=findreguserbynick(str+1)) && sender)
+      chanservstdmessage(sender, QM_UNKNOWNUSER, str);
+  } else if (*str=='&' && vrup && UHasStaffPriv(vrup)) {
+    if (str[1]=='\0') {
+      if (sender)
+       chanservstdmessage(sender, QM_UNKNOWNUSER, str);
+      return NULL;
+    }
+    if (!(rup=findreguserbyID(atoi(str+1))) && sender)
+      chanservstdmessage(sender, QM_UNKNOWNUSER, str);
+  } else {
+    if (!(np=getnickbynick(str))) {
+      if (sender)
+       chanservstdmessage(sender, QM_UNKNOWNUSER, str);
+      return NULL;
+    }
+    if (!(rup=getreguserfromnick(np)) && sender)
+      chanservstdmessage(sender, QM_USERNOTAUTHED, str);
+  }
+
+  /* removed the suspended check from here, I don't see the point... */
+  if (rup && (rup->status & QUSTAT_DEAD)) {
+    chanservstdmessage(sender, QM_USERHASBADAUTH, rup->username);
+    return NULL;
+  }
+
+  return rup;
+}
+
+/*
+ * Unbans a mask from a channel, including permbans if user has correct privs.
+ *
+ * Return 0 if it works, 1 if it don't.
+ */
+int cs_unbanfn(nick *sender, chanindex *cip, int (*fn)(void *arg, struct chanban *ban), void *arg, int removepermbans, int abortonfailure) {
+  regban **rbh, *rbp;
+  chanban **cbh, *cbp;
+  regchan *rcp;
+  modechanges changes;
+  char *banstr;
+
+  rcp=cip->exts[chanservext];
+
+  if (cip->channel)
+    localsetmodeinit(&changes, cip->channel, chanservnick);
+
+  for (rbh=&(rcp->bans); *rbh; ) {
+    rbp=*rbh;
+    if (fn(arg, rbp->cbp)) {
+      banstr=bantostring(rbp->cbp);
+      /* Check perms and remove */
+      if(!removepermbans) {
+        chanservstdmessage(sender, QM_WARNNOTREMOVEDPERMBAN, banstr, cip->name->content);
+        rbh=&(rbp->next);
+      } else if (!cs_checkaccess(sender, NULL, CA_MASTERPRIV, cip, NULL, 0, 1)) {
+        chanservstdmessage(sender, QM_NOTREMOVEDPERMBAN, banstr, cip->name->content);
+        if (abortonfailure) return 1; /* Just give up... */
+        rbh=&(rbp->next);
+      } else {
+        chanservstdmessage(sender, QM_REMOVEDPERMBAN, banstr, cip->name->content);
+        if (cip->channel)
+          localdosetmode_ban(&changes, banstr, MCB_DEL);
+        /* Remove from database */
+        csdb_deleteban(rbp);
+        /* Remove from list */
+        (*rbh)=rbp->next;
+        /* Free ban/string and update setby refcount, and free actual regban */
+        freesstring(rbp->reason);
+        freechanban(rbp->cbp);
+        freeregban(rbp);
+      }
+    } else {
+      rbh=&(rbp->next);
+    }
+  }
+
+  if (cip->channel) {
+    for (cbh=&(cip->channel->bans); *cbh; ) {
+      cbp=*cbh;
+      if (fn(arg, cbp)) {
+        /* Remove */
+        banstr=bantostring(cbp);
+        chanservstdmessage(sender, QM_REMOVEDCHANBAN, banstr, cip->name->content);
+        localdosetmode_ban(&changes, banstr, MCB_DEL);
+      } else {
+        cbh=&(cbp->next);
+      }
+    }
+    localsetmodeflush(&changes,1);
+  }
   
+  return 0;
+}
+
+/* Add entry to channel op history when someone gets ops. */
+void cs_logchanop(regchan *rcp, char *nick, reguser *rup) {
+  strncpy(rcp->chanopnicks[rcp->chanoppos], nick, NICKLEN);
+  rcp->chanopnicks[rcp->chanoppos][NICKLEN]='\0';
+  rcp->chanopaccts[rcp->chanoppos]=rup->ID;
+  rcp->chanoppos=(rcp->chanoppos+1)%CHANOPHISTORY;
+}
+
+int checkreason(nick *np, char *reason) {
+  if((strlen(reason) < MIN_REASONLEN) || !strchr(reason, ' ')) {
+    chanservstdmessage(np, QM_REASONREQUIRED);
+    return 0;
+  }
+
+  return 1;
+}
+
+regchan *cs_addchan(chanindex *cip, nick *sender, reguser *addedby, reguser *founder, flag_t flags, short type) {
+  regchan *rcp;
+  regchanuser *rcup;
+  void *args[3];
+  int i;
+
+  if (cip->exts[chanservext])
+    return NULL;
+
+  /* Initialise the channel */
+  rcp=getregchan();
+
+  /* ID, index */
+  rcp->ID=++lastchannelID;
+  rcp->index=cip;
+  cip->exts[chanservext]=rcp;
+
+  rcp->chantype=type;
+  rcp->flags=flags;
+  rcp->status=0;
+  rcp->bans=NULL;
+  rcp->lastcountersync=0;
+
+  rcp->limit=0;
+  rcp->forcemodes=CHANMODE_DEFAULT;
+  rcp->denymodes=0;
+
+  if (CIsAutoLimit(rcp)) {
+    rcp->forcemodes |= CHANMODE_LIMIT;
+  }
+
+  rcp->autolimit=5;
+  rcp->banstyle=0;
+
+  rcp->created=rcp->lastactive=rcp->statsreset=rcp->ostatsreset=time(NULL);
+  rcp->banduration=0;
+  rcp->autoupdate=0;
+  rcp->lastbancheck=0;
+
+  /* Added by */
+  rcp->addedby=addedby->ID;
+
+  /* Founder */
+  rcp->founder=founder->ID;
+
+  /* Suspend by */
+  rcp->suspendby=0;
+  rcp->suspendtime=0;
+
+  rcp->totaljoins=rcp->tripjoins=rcp->otripjoins=rcp->maxusers=rcp->tripusers=rcp->otripusers=0;
+  rcp->welcome=rcp->topic=rcp->key=rcp->suspendreason=rcp->comment=NULL;
+
+  /* Users */
+  memset(rcp->regusers,0,REGCHANUSERHASHSIZE*sizeof(reguser *));
+
+  rcp->checksched=NULL;
+  rcp->ltimestamp=0;
+  for (i=0;i<CHANOPHISTORY;i++) {
+    rcp->chanopnicks[i][0]='\0';
+    rcp->chanopaccts[i]=0;
+  }
+  rcp->chanoppos=0;
+
+  /* Add new channel to db.. */
+  csdb_createchannel(rcp);
+
+  /* Add the founder as +ano */
+  rcup=getregchanuser();
+  rcup->chan=rcp;
+  rcup->user=founder;
+  rcup->flags=(QCUFLAG_OWNER | QCUFLAG_OP | QCUFLAG_AUTOOP);
+  rcup->usetime=0;
+  rcup->info=NULL;
+  rcup->changetime=time(NULL);
+
+  addregusertochannel(rcup);
+  csdb_createchanuser(rcup);
+  csdb_chanlevhistory_insert(rcp, sender, rcup->user, 0, rcup->flags);
+
+  args[0]=sender;
+  args[1]=rcup;
+  args[2]=(void *)0;
+
+  triggerhook(HOOK_CHANSERV_CHANLEVMOD, args);
+
+  /* If the channel exists, get the ball rolling */
+  if (cip->channel) {
+    chanservjoinchan(cip->channel);
+    rcp->status |= QCSTAT_MODECHECK | QCSTAT_OPCHECK | QCSTAT_BANCHECK;
+    cs_timerfunc(cip);
+  }
+
+  return rcp;
+}