#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) {
}
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;
}
return CMD_OK;
}
-int handlechannelmsgcmd(void *source, int cargc, char **cargv) {
+/* PRIVMSG/NOTICE to channel handling is identical up to the point where the hook is called. */
+static int handlechannelmsgornotice(void *source, int cargc, char **cargv, int isnotice) {
void *nargs[3];
nick *sender;
channel *target;
}
if ((sender=getnickbynumericstr((char *)source))==NULL) {
- Error("localuserchannel",ERR_DEBUG,"PRIVMSG from non existant user %s",(char *)source);
+ Error("localuserchannel",ERR_DEBUG,"PRIVMSG/NOTICE from non existant user %s",(char *)source);
return CMD_OK;
}
if ((target=findchannel(cargv[0]))==NULL) {
- Error("localuserchannel",ERR_DEBUG,"PRIVMSG to non existant channel %s",cargv[0]);
+ Error("localuserchannel",ERR_DEBUG,"PRIVMSG/NOTICE to non existant channel %s",cargv[0]);
return CMD_OK;
}
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) {
- Error("localuserchannel",ERR_ERROR,"PRIVMSG to channel user who doesn't exist (?) on %s",cargv[0]);
+ Error("localuserchannel",ERR_ERROR,"PRIVMSG/NOTICE to channel user who doesn't exist (?) on %s",cargv[0]);
continue;
}
if (!IsDeaf(np)) {
- (umhandlers[numeric&MAXLOCALUSER])(np,LU_CHANMSG,nargs);
+ (umhandlers[numeric&MAXLOCALUSER])(np,(isnotice?LU_CHANNOTICE:LU_CHANMSG),nargs);
} else {
found--;
}
}
if (!found) {
- Error("localuserchannel",ERR_DEBUG,"Couldn't find any local targets for PRIVMSG to %s",cargv[0]);
+ Error("localuserchannel",ERR_DEBUG,"Couldn't find any local targets for PRIVMSG/NOTICE to %s",cargv[0]);
}
return CMD_OK;
}
-/*
- * Added by Cruicky so S2 can receive channel notices
- * Shameless rip of the above with s/privmsg/notice
- */
+/* Wrapper functions to call the above code */
+int handlechannelmsgcmd(void *source, int cargc, char **cargv) {
+ return handlechannelmsgornotice(source, cargc, cargv, 0);
+}
+
int handlechannelnoticecmd(void *source, int cargc, char **cargv) {
- void *nargs[3];
- nick *sender;
- channel *target;
- nick *np;
- unsigned long numeric;
- int i;
- int found=0;
+ return handlechannelmsgornotice(source, cargc, cargv, 1);
+}
- if (cargc<2) {
- return CMD_OK;
- }
+/* 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 (cargv[0][0]!='#' && cargv[0][0]!='+') {
- /* Not a channel notice */
- return CMD_OK;
+ if (cp==NULL)
+ return 1;
+
+ if (timestamp > cp->timestamp) {
+ return localjoinchannel(np, cp);
}
- if ((sender=getnickbynumericstr((char *)source))==NULL) {
- Error("localuserchannel",ERR_DEBUG,"NOTICE from non existant user %s",(char *)source);
- return CMD_OK;
+ 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;
+ }
+ }
}
- if ((target=findchannel(cargv[0]))==NULL) {
- Error("localuserchannel",ERR_DEBUG,"NOTICE to non existant channel %s",cargv[0]);
- return CMD_OK;
+ /* 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 */
}
-
- /* OK, we have a valid channel the notice was sent to. Let's look to see
- * if we have any local users on there. Set up the arguments first as they are
- * always going to be the same. */
-
- nargs[0]=(void *)sender;
- nargs[1]=(void *)target;
- nargs[2]=(void *)cargv[1];
-
- for (found=0,i=0;i<target->users->hashsize;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 */
- found++;
- if (umhandlers[numeric&MAXLOCALUSER]) {
- if ((np=getnickbynumeric(numeric))==NULL) {
- Error("localuserchannel",ERR_ERROR,"NOTICE to channel user who doesn't exist (?) on %s",cargv[0]);
- continue;
- }
- if (!IsDeaf(np)) {
- (umhandlers[numeric&MAXLOCALUSER])(np,LU_CHANNOTICE,nargs);
- } else {
- found--;
- }
- }
+
+ 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);
}
- if (!found) {
- Error("localuserchannel",ERR_DEBUG,"Couldn't find any local targets for NOTICE to %s",cargv[0]);
- }
+ /* Tell the world something happened... */
+ triggerhook(HOOK_CHANNEL_BURST,cp);
- return CMD_OK;
+ return 0;
}
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;
}
}
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;
/* 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);
* 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;
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);
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
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)
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 */
}
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];
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));
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];
}
}
-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)
* argument */
if (connected) {
irc_send("%s I %s :%s",longtonumeric(source->numeric,5),
- target->nick, cp->index->name->content);
+ target->nick, cip->name->content);
}
}