]> jfr.im git - irc/quakenet/newserv.git/blobdiff - localuser/localuserchannel.c
CHANSERV: better batcher error handling for expired accounts/accounts with no email.
[irc/quakenet/newserv.git] / localuser / localuserchannel.c
index 9c6951ae6aefd601536df9a97e79590480b4fa40..d770f877a22a22075eb4df957ab6d723f77e9cea 100644 (file)
@@ -6,31 +6,61 @@
 #include "../channel/channel.h"
 #include "../irc/irc.h"
 #include "../lib/version.h"
+#include "../lib/sstring.h"
 
 #include <stdarg.h>
 #include <stdio.h>
 #include <assert.h>
 #include <string.h>
+#include <stdint.h>
 
 MODULE_VERSION("");
 
+typedef struct pendingkick {
+  nick *source, *target;
+  channel *chan;
+  sstring *reason;
+  struct pendingkick *next;
+} pendingkick;
+
+pendingkick *pendingkicklist;
+
 int handlechannelmsgcmd(void *source, int cargc, char **cargv);
 int handlechannelnoticecmd(void *source, int cargc, char **cargv);
 int handleinvitecmd(void *source, int cargc, char **cargv);
+void clearpendingkicks(int hooknum, void *arg);
+void checkpendingkicknicks(int hooknum, void *arg);
+void checkpendingkickchannels(int hooknum, void *arg);
+void _localkickuser(nick *np, channel *cp, nick *target, const char *message);
 void luc_handlekick(int hooknum, void *arg);
 
 void _init() {
+  pendingkicklist=NULL;
   registerserverhandler("P",&handlechannelmsgcmd,2);
   registerserverhandler("O",&handlechannelnoticecmd,2);
   registerserverhandler("I",&handleinvitecmd,2);
   registerhook(HOOK_CHANNEL_KICK, luc_handlekick);
+  registerhook(HOOK_NICK_LOSTNICK, checkpendingkicknicks);
+  registerhook(HOOK_CHANNEL_LOSTCHANNEL, checkpendingkickchannels);
+  registerhook(HOOK_CORE_ENDOFHOOKSQUEUE,&clearpendingkicks);
 }
 
 void _fini() {
+  pendingkick *pk;
+
   deregisterserverhandler("P",&handlechannelmsgcmd);
   deregisterserverhandler("O",&handlechannelnoticecmd);
   deregisterserverhandler("I",&handleinvitecmd);
   deregisterhook(HOOK_CHANNEL_KICK, luc_handlekick);
+  deregisterhook(HOOK_NICK_LOSTNICK, checkpendingkicknicks);
+  deregisterhook(HOOK_CHANNEL_LOSTCHANNEL, checkpendingkickchannels);
+  deregisterhook(HOOK_CORE_ENDOFHOOKSQUEUE,&clearpendingkicks);
+
+  for (pk=pendingkicklist;pk;pk=pendingkicklist) {
+    pendingkicklist = pk->next;
+    freesstring(pk->reason);
+    free(pk);
+  }
 }
 
 void luc_handlekick(int hooknum, void *arg) {
@@ -65,7 +95,7 @@ int handleinvitecmd(void *source, int cargc, char **cargv) {
   }
   
   if (!(sender=getnickbynumericstr(source))) {
-    Error("localuserchannel",ERR_WARNING,"Got invite from unknown numeric %s.",source);
+    Error("localuserchannel",ERR_WARNING,"Got invite from unknown numeric %s.",(char *)source);
     return CMD_OK;
   }
   
@@ -141,6 +171,10 @@ static int handlechannelmsgornotice(void *source, int cargc, char **cargv, int i
     numeric=target->users->content[i];
     if (numeric!=nouser && homeserver(numeric&CU_NUMERICMASK)==mylongnum) {
       /* OK, it's one of our users.. we need to deal with it */
+      if (found && ((getnickbynumericstr((char *)source)==NULL) || ((findchannel(cargv[0]))==NULL) || !(getnumerichandlefromchanhash(target->users, sender->numeric)))) {
+        Error("localuserchannel", ERR_INFO, "Nick or channel lost, or user no longer on channel in LU_CHANMSG");
+        break;
+      }
       found++;
       if (umhandlers[numeric&MAXLOCALUSER]) {
        if ((np=getnickbynumeric(numeric))==NULL) {
@@ -164,7 +198,7 @@ static int handlechannelmsgornotice(void *source, int cargc, char **cargv, int i
 }
 
 /* Wrapper functions to call the above code */
-int handlechannelmessagecmd(void *source, int cargc, char **cargv) {
+int handlechannelmsgcmd(void *source, int cargc, char **cargv) {
   return handlechannelmsgornotice(source, cargc, cargv, 0);
 }
 
@@ -172,6 +206,115 @@ int handlechannelnoticecmd(void *source, int cargc, char **cargv) {
   return handlechannelmsgornotice(source, cargc, cargv, 1);
 }
 
+/* Burst onto channel.  This replaces the timestamp and modes
+ * with the provided ones.  Keys and limits use the provided ones 
+ * if needed.  nick * is optional, but joins the channel opped if 
+ * provided. 
+ * 
+ * Due to the way ircu works, this only works if the provided timestamp is
+ * older than the one currently on the channel.  If the timestamps are
+ * equal, the modes are ignored, but the user (if any) is still allowed to
+ * join with op.  If the provided timestamp is newer than the exsting one we
+ * just do a join instead - if you try to replace an old timestamp with a
+ * newer one ircu will just laugh at you (and you will be desynced).
+ */
+int localburstontochannel(channel *cp, nick *np, time_t timestamp, flag_t modes, unsigned int limit, char *key) {
+  unsigned int i;
+  char extramodebuf[512];
+  char nickbuf[512];
+  
+  if (cp==NULL)
+    return 1;
+    
+  if (timestamp > cp->timestamp) {
+    return localjoinchannel(np, cp);
+  }
+  
+  if (timestamp < cp->timestamp) {
+    cp->timestamp=timestamp;
+    cp->flags=modes;
+    
+    /* deal with key - if we need one use the provided one if set, otherwise
+     * the existing one, but if there is no existing one clear +k */
+    if (IsKey(cp)) {
+      /* Sanitise the provided key - this might invalidate it.. */
+      if (key)
+        clean_key(key);
+        
+      if (key && *key) {
+        /* Free old key, if any */
+        if (cp->key)
+          freesstring(cp->key);
+
+        cp->key=getsstring(key,KEYLEN);
+      } else {
+        if (!cp->key)
+          ClearKey(cp);
+      }
+    } else {
+      /* Not +k - free the existing key, if any */
+      freesstring(cp->key);
+      cp->key=NULL;
+    }
+    
+    if (IsLimit(cp)) {
+      if (limit) {
+        cp->limit=limit;
+      } else {
+        if (!cp->limit)
+          ClearLimit(cp);
+      } 
+    } else {
+      if (cp->limit)
+        cp->limit=0;
+    }
+    
+    /* We also need to blow away all other op/voice and bans on the
+     * channel.  This is the same code we use when someone else does 
+     * it to us. */ 
+    clearallbans(cp); 
+    for (i=0;i<cp->users->hashsize;i++) {
+      if (cp->users->content[i]!=nouser) {
+        cp->users->content[i]&=CU_NUMERICMASK;
+      }
+    }
+  }
+
+  /* Actually add the nick to the channel.  Make sure it's a local nick and actually exists first. */
+  if (np && (homeserver(np->numeric) == mylongnum) &&
+      !(getnumerichandlefromchanhash(cp->users,np->numeric))) {
+    addnicktochannel(cp,(np->numeric)|CUMODE_OP);
+  } else {
+    np=NULL; /* If we're not adding it here, don't send it later in the burst msg either */
+  }
+  
+  if (connected) {
+    /* actual burst message */
+    if (np) {
+      sprintf(nickbuf," %s:o", longtonumeric(np->numeric,5));
+    } else {
+      nickbuf[0]='\0';
+    }
+    
+    if (IsLimit(cp)) {
+      sprintf(extramodebuf," %d",cp->limit);
+    } else {
+      extramodebuf[0]='\0';
+    }
+    
+    /* XX B #channel <timestamp> +modes <limit> <key> <user> */
+    irc_send("%s B %s %lu %s%s%s%s%s",
+              mynumeric->content,cp->index->name->content,cp->timestamp,
+              printflags(cp->flags,cmodeflags),extramodebuf,
+              IsKey(cp)?" ":"",IsKey(cp)?cp->key->content:"", nickbuf);
+  }
+
+  /* Tell the world something happened... */
+  triggerhook(HOOK_CHANNEL_BURST,cp);
+
+  return 0;
+}
+
 int localjoinchannel(nick *np, channel *cp) {
   void *harg[2];
   
@@ -196,20 +339,21 @@ int localjoinchannel(nick *np, channel *cp) {
   harg[0]=cp;
   harg[1]=np;
     
-  triggerhook(HOOK_CHANNEL_JOIN, harg);
-    
   if (connected) {
     irc_send("%s J %s %lu",longtonumeric(np->numeric,5),cp->index->name->content,cp->timestamp);
   }
+
+  triggerhook(HOOK_CHANNEL_JOIN, harg);
+    
   return 0;
 }
 
-int localpartchannel(nick *np, channel *cp) {
+int localpartchannel(nick *np, channel *cp, char *reason) {
   void *harg[3];
   
   /* Check pointers are valid.. */
   if (cp==NULL || np==NULL) {
-    Error("localuserchannel",ERR_WARNING,"Trying to part NULL channel or NULL nick (cp=%x,np=%x)",cp,np);
+    Error("localuserchannel",ERR_WARNING,"Trying to part NULL channel or NULL nick (cp=%p,np=%p)",cp,np);
     return 1;
   }    
   
@@ -226,7 +370,10 @@ int localpartchannel(nick *np, channel *cp) {
   }
 
   if (connected) {
-    irc_send("%s L %s",longtonumeric(np->numeric,5),cp->index->name->content);
+    if (reason != NULL)
+      irc_send("%s L %s :%s",longtonumeric(np->numeric,5),cp->index->name->content, reason);
+    else
+      irc_send("%s L %s",longtonumeric(np->numeric,5),cp->index->name->content);
   }
   
   harg[0]=cp;
@@ -345,6 +492,11 @@ void localdosetmode_ban (modechanges *changes, const char *ban, short dir) {
   /* If we're told to clear a ban that isn't here, do nothing. */
   if (dir==MCB_DEL && !clearban(changes->cp, bansstr->content, 1))
     return;
+    
+  /* Similarly if someone is trying to add a completely overlapped ban, do
+   * nothing */
+  if (dir==MCB_ADD && !setban(changes->cp, bansstr->content))
+    return;
 
   if (changes->changecount >= MAXMODEARGS)
     localsetmodeflush(changes, 0);
@@ -363,7 +515,7 @@ void localdosetmode_ban (modechanges *changes, const char *ban, short dir) {
  *  Set or clear a key on the channel
  */
 
-void localdosetmode_key (modechanges *changes, const char *key, short dir) {
+void localdosetmode_key (modechanges *changes, char *key, short dir) {
   int i,j;
   sstring *keysstr;
 
@@ -372,6 +524,11 @@ void localdosetmode_key (modechanges *changes, const char *key, short dir) {
     localsetmodeflush(changes,0);
 
   if (dir==MCB_ADD) {
+    /* Sanitise the key.  If this nullifies it then we give up */
+    clean_key(key);
+    if (!*key)
+      return;
+
     /* Get a copy of the key for use later */
     keysstr=getsstring(key, KEYLEN);
     
@@ -388,6 +545,7 @@ void localdosetmode_key (modechanges *changes, const char *key, short dir) {
          freesstring(changes->cp->key);
          changes->cp->key=getsstring(key, KEYLEN);
          /* That's it, we're done */
+         freesstring(keysstr);
          return;
        } else {
          /* There was a command to delete key.. we need to flush 
@@ -551,6 +709,11 @@ void localdosetmode_nick (modechanges *changes, nick *target, short modes) {
     return;
   }
 
+  if (IsCloaked(target)) {
+    /* Target is cloaked, never set channel modes for cloaked users */
+    return;
+  }
+
   if ((modes & MC_DEOP) && (*lp & CUMODE_OP)) {
     (*lp) &= ~CUMODE_OP;
     if (changes->changecount >= MAXMODEARGS)
@@ -730,6 +893,7 @@ void localusermodechange(nick *np, channel *cp, char *modes) {
 void localsettopic(nick *np, channel *cp, char *topic) {
   unsigned long *lp;
   char source[10];
+  time_t now=getnettime();
 
   if (np==NULL || (lp=getnumerichandlefromchanhash(cp->users,np->numeric))==NULL) {
     /* User isn't on channel, hack mode */
@@ -750,14 +914,41 @@ void localsettopic(nick *np, channel *cp, char *topic) {
   }
 
   cp->topic=getsstring(topic,TOPICLEN);
-  cp->topictime=getnettime();
+
+  /* Update the topic time iff the old time was in the past.  This 
+   * means if we are bouncing a topic with a TS 1sec newer than ours 
+   * we won't use an old timestamp */
+  if (cp->topictime < now) {
+    cp->topictime=now;
+  }
   
   if (connected) {
-    irc_send("%s T %s %u %u :%s",source,cp->index->name->content,cp->timestamp,cp->topictime,(cp->topic)?cp->topic->content:"");
+    irc_send("%s T %s %jd %jd :%s",source,cp->index->name->content,(intmax_t)cp->timestamp,(intmax_t)cp->topictime,(cp->topic)?cp->topic->content:"");
   }
 }
 
 void localkickuser(nick *np, channel *cp, nick *target, const char *message) {
+  pendingkick *pk;
+
+  if (hookqueuelength) {
+    for (pk = pendingkicklist; pk; pk = pk->next)
+      if (pk->target == target && pk->chan == cp)
+        return;
+
+    Error("localuserchannel", ERR_DEBUG, "Adding pending kick for %s on %s", target->nick, cp->index->name->content);
+    pk = (pendingkick *)malloc(sizeof(pendingkick));
+    pk->source = np;
+    pk->chan = cp;
+    pk->target = target;
+    pk->reason = getsstring(message, BUFSIZE);
+    pk->next = pendingkicklist;
+    pendingkicklist = pk;
+  } else {
+    _localkickuser(np, cp, target, message);
+  }
+}
+
+void _localkickuser(nick *np, channel *cp, nick *target, const char *message) {
   unsigned long *lp;
   char source[10];
 
@@ -769,7 +960,7 @@ void localkickuser(nick *np, channel *cp, nick *target, const char *message) {
     if (homeserver(np->numeric)!=mylongnum) {
       return;
     }
-    if ((*lp&CUMODE_OP)==0 && IsTopicLimit(cp)) {
+    if ((*lp&CUMODE_OP)==0) {
       localgetops(np,cp);
     }
     strcpy(source,longtonumeric(np->numeric,5));
@@ -788,6 +979,52 @@ void localkickuser(nick *np, channel *cp, nick *target, const char *message) {
   delnickfromchannel(cp, target->numeric, 1);
 }
 
+void clearpendingkicks(int hooknum, void *arg) {
+  pendingkick *pk;
+
+  pk = pendingkicklist;
+  while (pk) {
+    pendingkicklist = pk->next;
+
+    if (pk->target && pk->chan) {
+      Error("localuserchannel", ERR_DEBUG, "Processing pending kick for %s on %s", pk->target->nick, pk->chan->index->name->content);
+      _localkickuser(pk->source, pk->chan, pk->target, pk->reason->content);
+    }
+
+    freesstring(pk->reason);
+    free(pk);
+    pk = pendingkicklist;
+  }
+}
+
+void checkpendingkicknicks(int hooknum, void *arg) {
+  nick *np = (nick *)arg;
+  pendingkick *pk;
+
+  for (pk=pendingkicklist; pk; pk = pk->next) {
+    if (pk->source == np) {
+      Error("localuserchannel", ERR_INFO, "Pending kick source %s got deleted, NULL'ing source for pending kick", np->nick);
+      pk->source = NULL;
+    }
+    if (pk->target == np) {
+      Error("localuserchannel", ERR_INFO, "Pending kick target %s got deleted, NULL'ing target for pending kick", np->nick);
+      pk->target = NULL;
+    }
+  }
+}
+
+void checkpendingkickchannels(int hooknum, void *arg) {
+  channel *cp = (channel *)arg;
+  pendingkick *pk;
+
+  for (pk=pendingkicklist; pk; pk = pk->next) {
+    if (pk->chan == cp) {
+      Error("localuserchannel", ERR_INFO, "Pending kick channel %s got deleted, NULL'ing channel for pending kick", cp->index->name->content);
+      pk->chan = NULL;
+    }
+  }
+}
+
 void sendmessagetochannel(nick *source, channel *cp, char *format, ... ) {
   char buf[BUFSIZE];
   char senderstr[6];
@@ -811,7 +1048,30 @@ void sendmessagetochannel(nick *source, channel *cp, char *format, ... ) {
   }
 }
 
-void localinvite(nick *source, channel *cp, nick *target) {
+void sendopnoticetochannel(nick *source, channel *cp, char *format, ... ) {
+  char buf[BUFSIZE];
+  char senderstr[6];
+  va_list va;
+  
+  if (!source)
+    return;
+  longtonumeric2(source->numeric,5,senderstr);
+     
+  va_start(va,format);
+  /* 10 bytes of numeric, 5 bytes of fixed format + terminator = 17 bytes */
+  /* So max sendable message is 495 bytes.  Of course, a client won't be able
+   * to receive this.. */
+
+  vsnprintf(buf,BUFSIZE-17,format,va);
+  va_end(va);
+
+  if (connected) {
+    irc_send("%s WC %s :%s",senderstr,cp->index->name->content,buf);
+  }
+}
+
+void localinvite(nick *source, chanindex *cip, nick *target) {
 
   /* Servers can't send invites */
   if (!source) 
@@ -823,7 +1083,7 @@ void localinvite(nick *source, channel *cp, nick *target) {
    * argument */
   if (connected) {
     irc_send("%s I %s :%s",longtonumeric(source->numeric,5),
-            target->nick, cp->index->name->content);
+            target->nick, cip->name->content);
   }
 }