]> jfr.im git - irc/quakenet/newserv.git/blame - channel/channelhandlers.c
LUA: add function for channel chanop notice
[irc/quakenet/newserv.git] / channel / channelhandlers.c
CommitLineData
c86edd1d
Q
1/* channel.c */
2
f2b36aa8
CP
3#include <stdio.h>
4#include <string.h>
5
c86edd1d
Q
6#include "channel.h"
7#include "../server/server.h"
8#include "../nick/nick.h"
9#include "../lib/irc_string.h"
10#include "../irc/irc_config.h"
11#include "../parser/parser.h"
12#include "../irc/irc.h"
13#include "../lib/base64.h"
f2b36aa8 14#include "../lib/strlfunc.h"
c86edd1d
Q
15
16int handleburstmsg(void *source, int cargc, char **cargv) {
17 channel *cp;
18 time_t timestamp;
19 int wipeout=0;
20 int i;
21 int arg=0;
22 char *charp;
23 int newlimit;
24 int waslimit,waskeyed;
25 char *nextnum;
26 unsigned long currentmode;
27 int isnewchan;
28
29 /* (we don't see the first 2 params in cargc) */
0dd93b00 30 /* AK B #+lod+ 1017561154 +tnk eits ATJWu:o,AiW1a,Ag3lV,AiWnl,AE6oI :%*!@123.example.net */
c86edd1d
Q
31
32 if (cargc<2) {
33 Error("channel",ERR_WARNING,"Burst message with only %d parameters",cargc);
34 return CMD_OK;
35 }
36
37 timestamp=strtol(cargv[1],NULL,10);
38
39 if ((cp=findchannel(cargv[0]))==NULL) {
40 /* We don't have this channel already */
41 cp=createchannel(cargv[0]);
42 cp->timestamp=timestamp;
43 isnewchan=1;
44 } else {
45 isnewchan=0;
46 if (timestamp<cp->timestamp) {
47 /* The incoming timestamp is older. Erase all our current channel modes, and the topic. */
48 cp->timestamp=timestamp;
49 freesstring(cp->topic);
50 cp->topic=NULL;
51 cp->topictime=0;
52 freesstring(cp->key);
53 cp->key=NULL;
54 cp->limit=0;
55 cp->flags=0;
56 clearallbans(cp);
57 /* Remove all +v, +o we currently have */
58 for(i=0;i<cp->users->hashsize;i++) {
59 if (cp->users->content[i]!=nouser) {
60 cp->users->content[i]&=CU_NUMERICMASK;
61 }
62 }
63 } else if (timestamp>cp->timestamp) {
64 /* The incoming timestamp is greater. Ignore any incoming modes they may happen to set */
65 wipeout=1;
66 }
67 }
68
69 /* OK, dealt with the name and timestamp.
70 * Loop over the remaining args */
71 for (arg=2;arg<cargc;arg++) {
72 if (cargv[arg][0]=='+') {
73 /* Channel modes */
74 if (wipeout) {
75 /* We ignore the modes, but we need to see if they include +l or +k
76 * so that we can ignore their corresponding values */
77 for (charp=cargv[arg];*charp;charp++) {
78 if (*charp=='k' || *charp=='l') {
79 arg++;
80 }
81 }
82 } else {
83 /* Clear off the limit and key flags before calling setflags so we can see if the burst tried to set them */
84 /* If the burst doesn't set them, we restore them afterwards */
85 waslimit=IsLimit(cp); ClearLimit(cp);
86 waskeyed=IsKey(cp); ClearKey(cp);
87 /* We can then use the flag function for these modes */
88 setflags(&(cp->flags),CHANMODE_ALL,cargv[arg],cmodeflags,REJECT_NONE);
89 /* Pick up the limit and key, if they were set. Note that the limit comes first */
90 if (IsLimit(cp)) { /* A limit was SET by the burst */
91 if (++arg>=cargc) {
92 /* Ran out of args -- damn ircd is spewing out crap again */
93 Error("channel",ERR_WARNING,"Burst +l with no argument");
94 break; /* "break" being the operative word */
95 } else {
96 newlimit=strtol(cargv[arg],NULL,10);
97 }
98 if (cp->limit>0 && waslimit) {
99 /* We had a limit before -- we now have the lowest one of the two */
100 if (newlimit<cp->limit) {
101 cp->limit=newlimit;
102 }
103 } else {
104 /* No limit before -- we just have the new one */
105 cp->limit=newlimit;
106 }
107 } else if (waslimit) {
108 SetLimit(cp); /* We had a limit before, but the burst didn't set one. Restore flag. */
109 }
110
111 if (IsKey(cp)) { /* A key was SET by the burst */
112 if (++arg>=cargc) {
113 /* Ran out of args -- oopsie! */
114 Error("channel",ERR_WARNING,"Burst +k with no argument");
115 break;
116 }
117 if (waskeyed) {
118 /* We had a key before -- alphabetically first wins */
119 if (ircd_strcmp(cargv[arg],cp->key->content)<0) {
120 /* Replace our key */
121 freesstring(cp->key);
122 cp->key=getsstring(cargv[arg],KEYLEN);
123 }
124 } else {
125 /* No key before -- just the new one */
126 cp->key=getsstring(cargv[arg],KEYLEN);
127 }
128 } else if (waskeyed) {
129 SetKey(cp); /* We had a key before, but the burst didn't set one. Restore flag. */
130 }
131 }
132 } else if (cargv[arg][0]=='%') {
133 /* We have one or more bans here */
134 nextnum=cargv[arg]+1;
135 while (*nextnum) {
136 /* Split off the next ban */
137 for (charp=nextnum;*charp;charp++) {
138 if (*charp==' ') {
139 *charp='\0';
140 charp++;
141 break;
142 }
143 }
144 setban(cp,nextnum);
145 nextnum=charp;
146 }
147 } else {
148 /* List of numerics */
149 nextnum=charp=cargv[arg];
150 currentmode=0;
151 while (*nextnum!='\0') {
152 /* Step over the next numeric */
153 for (i=0;i<5;i++) {
154 if (*charp++=='\0')
155 break;
156 }
157 if (i<5) {
158 break;
159 }
160 if (*charp==',') {
161 *charp='\0';
162 charp++;
163 } else if (*charp==':') {
164 *charp='\0';
165 charp++;
166 currentmode=0;
167 /* Look for modes */
168 for (;*charp;charp++) {
169 if (*charp=='v') {
170 currentmode|=CUMODE_VOICE;
171 } else if (*charp=='o') {
172 currentmode|=CUMODE_OP;
173 } else if (*charp==',') {
174 charp++;
175 break;
176 }
177 }
178 /* If we're ignore incoming modes, zap it to zero again */
179 if (wipeout) {
180 currentmode=0;
181 }
182 }
183 /* OK. At this point charp points to either '\0' if we're at the end,
184 * or the start of the next numeric otherwise. nextnum points at a valid numeric
185 * we need to add, and currentmode reflects the correct mode */
186 addnicktochannel(cp,(numerictolong(nextnum,5)|currentmode));
187 nextnum=charp;
188 }
189 }
190 }
191 if (cp->users->totalusers==0) {
192 /* Oh dear, the channel is now empty. Perhaps one of those
193 * charming empty burst messages you get sometimes.. */
194 if (!isnewchan) {
195 /* I really don't think this can happen, can it..? */
196 /* Only send the LOSTCHANNEL if the channel existed before */
197 triggerhook(HOOK_CHANNEL_LOSTCHANNEL,cp);
198 }
199 delchannel(cp);
200 } else {
201 /* If this is a new channel, we do the NEWCHANNEL hook also */
202 if (isnewchan) {
203 triggerhook(HOOK_CHANNEL_NEWCHANNEL,cp);
204 }
205 /* Just one hook to say "something happened to this channel" */
206 triggerhook(HOOK_CHANNEL_BURST,cp);
207 }
208
209 return CMD_OK;
210}
211
212int handlejoinmsg(void *source, int cargc, char **cargv) {
213 char *pos,*nextchan;
214 nick *np;
62f9b4e9 215 void *harg[3];
c86edd1d
Q
216 channel *cp,**ch;
217 long timestamp=0;
218 int i;
219 int newchan=0;
220
221 if (cargc<1) {
222 return CMD_OK;
223 }
224
225 if (cargc>1) {
226 /* We must have received a timestamp too */
227 timestamp=strtol(cargv[1],NULL,10);
228 }
229
230 /* Find out who we are talking about here */
231 np=getnickbynumericstr(source);
232 if (np==NULL) {
16739dbe 233 Error("channel",ERR_WARNING,"Channel join from non existent user %s",(char *)source);
c86edd1d
Q
234 return CMD_OK;
235 }
236
237 nextchan=pos=cargv[0];
238 while (*nextchan!='\0') {
239 /* Find the next chan position */
240 for (;*pos!='\0' && *pos!=',';pos++)
241 ; /* Empty loop */
242
243 if (*pos==',') {
244 *pos='\0';
245 pos++;
246 }
247
248 /* OK, pos now points at either null or the next chan
249 * and nextchan now points at the channel name we want to parse next */
250
251 if(nextchan[0]=='0' && nextchan[1]=='\0') {
252 /* Leave all channels
253 * We do this as if they were leaving the network,
254 * then vape their channels array and start again */
255 ch=(channel **)(np->channels->content);
256 for(i=0;i<np->channels->cursi;i++) {
257 /* Send hook */
258 harg[0]=ch[i];
259 harg[1]=np;
5328e616 260 harg[2]=NULL;
c86edd1d
Q
261 triggerhook(HOOK_CHANNEL_PART,harg);
262 delnickfromchannel(ch[i],np->numeric,0);
263 }
264 array_free(np->channels);
265 array_init(np->channels,sizeof(channel *));
266 } else {
267 /* It's an actual channel join */
268 newchan=0;
269 if ((cp=findchannel(nextchan))==NULL) {
270 /* User joined non-existent channel - create it. Note that createchannel automatically
271 * puts the right magic timestamp in for us */
272 Error("channel",ERR_DEBUG,"User %s joined non existent channel %s.",np->nick,nextchan);
273 cp=createchannel(nextchan);
274 newchan=1;
275 }
276 if (cp->timestamp==MAGIC_REMOTE_JOIN_TS && timestamp) {
277 /* No valid timestamp on the chan and we received one -- set */
278 cp->timestamp=timestamp;
279 }
280 /* OK, this is slightly inefficient since we turn the nick * into a numeric,
281 * and addnicktochannel then converts it back again. BUT it's fewer lines of code :) */
282 if (addnicktochannel(cp,np->numeric)) {
283 /* The user wasn't added */
284 if (newchan) {
285 delchannel(cp);
286 }
e2f3cad8 287 } else {
288 chanindex *cip=cp->index;
289
c86edd1d
Q
290 /* If we just created a channel, flag it */
291 if (newchan) {
292 triggerhook(HOOK_CHANNEL_NEWCHANNEL,cp);
293 }
e2f3cad8 294
295 /* Don't send HOOK_CHANNEL_JOIN if the channel doesn't exist any
296 * more (can happen if something destroys it in response to
297 * HOOK_CHANNEL_NEWCHANNEL) */
298 if (cp == cip->channel) {
299 /* send hook */
300 harg[0]=cp;
301 harg[1]=np;
302 triggerhook(HOOK_CHANNEL_JOIN,harg);
303 }
c86edd1d
Q
304 }
305 }
306 nextchan=pos;
307 }
308 return CMD_OK;
309}
310
311int handlecreatemsg(void *source, int cargc, char **cargv) {
312 char *pos,*nextchan;
313 nick *np;
314 channel *cp;
315 long timestamp=0;
316 int newchan=1;
317 void *harg[2];
318
319 if (cargc<2) {
320 return CMD_OK;
321 }
322
323 timestamp=strtol(cargv[1],NULL,10);
324
325 /* Find out who we are talking about here */
326 np=getnickbynumericstr(source);
327 if (np==NULL) {
16739dbe 328 Error("channel",ERR_WARNING,"Channel create from non existent user %s",(char *)source);
c86edd1d
Q
329 return CMD_OK;
330 }
331
332 nextchan=pos=cargv[0];
333 while (*nextchan!='\0') {
334 /* Find the next chan position */
335 for (;*pos!='\0' && *pos!=',';pos++)
336 ; /* Empty loop */
337
338 if (*pos==',') {
339 *pos='\0';
340 pos++;
341 }
342
343 /* OK, pos now points at either null or the next chan
344 * and nextchan now points at the channel name we want to parse next */
345
346 /* It's a channel create */
347 if ((cp=findchannel(nextchan))==NULL) {
348 /* This is the expected case -- the channel didn't exist before */
349 cp=createchannel(nextchan);
350 cp->timestamp=timestamp;
351 newchan=1;
352 } else {
353 Error("channel",ERR_DEBUG,"Received CREATE for already existing channel %s",cp->index->name->content);
354 if (cp->timestamp==MAGIC_REMOTE_JOIN_TS && timestamp) {
355 /* No valid timestamp on the chan and we received one -- set */
356 cp->timestamp=timestamp;
357 }
358 newchan=0;
359 }
360 /* Add the user to the channel, preopped */
361 if (addnicktochannel(cp,(np->numeric)|CUMODE_OP)) {
362 if (newchan) {
363 delchannel(cp);
364 }
e2f3cad8 365 } else {
366 chanindex *cip = cp->index;
367
c86edd1d
Q
368 /* Flag the channel as new if necessary */
369 if (newchan) {
370 triggerhook(HOOK_CHANNEL_NEWCHANNEL,cp);
371 }
372
e2f3cad8 373 /* If HOOK_CHANNEL_NEWCHANNEL has caused the channel to be deleted,
374 * don't trigger the CREATE hook. */
375 if (cip->channel == cp) {
376 /* Trigger hook */
377 harg[0]=cp;
378 harg[1]=np;
379 triggerhook(HOOK_CHANNEL_CREATE,harg);
380 }
c86edd1d
Q
381 }
382 nextchan=pos;
383 }
384
385 return CMD_OK;
386}
387
388int handlepartmsg(void *source, int cargc, char **cargv) {
389 char *pos,*nextchan;
390 nick *np;
391 channel *cp;
392 void *harg[3];
393
394 if (cargc<1) {
395 return CMD_OK;
396 }
397
398 if (cargc>2) {
399 Error("channel",ERR_WARNING,"PART with too many parameters (%d)",cargc);
400 }
401
402 if (cargc>1) {
403 harg[2]=(void *)cargv[1];
404 } else {
405 harg[2]=(void *)"";
406 }
407
408 /* Find out who we are talking about here */
409 np=getnickbynumericstr(source);
410 if (np==NULL) {
16739dbe 411 Error("channel",ERR_WARNING,"PART from non existent numeric %s",(char *)source);
c86edd1d
Q
412 return CMD_OK;
413 }
414
415 harg[1]=np;
416
417 nextchan=pos=cargv[0];
418 while (*nextchan!='\0') {
419 /* Find the next chan position */
420 for (;*pos!='\0' && *pos!=',';pos++)
421 ; /* Empty loop */
422
423 if (*pos==',') {
424 *pos='\0';
425 pos++;
426 }
427
428 /* OK, pos now points at either null or the next chan
429 * and nextchan now points at the channel name we want to parse next */
430
431 if ((cp=findchannel(nextchan))==NULL) {
432 /* Erm, parting a channel that's not there?? */
433 Error("channel",ERR_WARNING,"Nick %s left non-existent channel %s",np->nick,nextchan);
434 } else {
01522d37
TS
435 /* Skip ghost parts (confirmation for kick from target server). */
436 if (getnumerichandlefromchanhash(cp->users, np->numeric)) {
437 /* Trigger hook *FIRST* */
438 harg[0]=cp;
439 triggerhook(HOOK_CHANNEL_PART,harg);
440 delnickfromchannel(cp,np->numeric,1);
441 }
c86edd1d
Q
442 }
443 nextchan=pos;
444 }
445
446 return CMD_OK;
447}
448
449int handlekickmsg(void *source, int cargc, char **cargv) {
450 nick *np,*kicker;
451 channel *cp;
452 void *harg[4];
453
454 if (cargc<3) {
455 return CMD_OK;
456 }
457
458 /* Find out who we are talking about here */
459 if ((np=getnickbynumericstr(cargv[1]))==NULL) {
16739dbe 460 Error("channel",ERR_DEBUG,"Non-existant numeric %s kicked from channel %s",(char *)source,cargv[0]);
c86edd1d
Q
461 return CMD_OK;
462 }
463
464 /* And who did the kicking */
465 if (((char *)source)[2]=='\0') {
466 /* 'Twas a server.. */
467 kicker=NULL;
468 } else if ((kicker=getnickbynumericstr((char *)source))==NULL) {
469 /* It looks strange, but we let the kick go through anyway */
470 Error("channel",ERR_DEBUG,"Kick from non-existant nick %s",(char *)source);
471 }
472
473 /* And find out which channel */
474 if ((cp=findchannel(cargv[0]))==NULL) {
475 /* OK, not a channel that actually exists then.. */
476 Error("channel",ERR_DEBUG,"Nick %s kicked from non-existent channel %s",np->nick,cargv[0]);
477 } else {
478 /* Before we do anything else, we have to acknowledge the kick to the network */
479 if (homeserver(np->numeric)==mylongnum) {
480 irc_send("%s L %s",longtonumeric(np->numeric,5),cp->index->name->content);
481 }
482
483 /* Trigger hook *FIRST* */
484 harg[0]=cp;
485 harg[1]=np;
486 harg[2]=kicker;
487 harg[3]=cargv[2];
488 triggerhook(HOOK_CHANNEL_KICK,harg);
489
490 /* We let delnickfromchannel() worry about whether this nick is actually on this channel */
491 delnickfromchannel(cp,np->numeric,1);
492 }
493
494 return CMD_OK;
495}
496
497int handletopicmsg(void *source, int cargc, char **cargv) {
498 channel *cp;
499 nick *np;
500 void *harg[2];
501 time_t topictime=0, timestamp=0;
502
503 if (cargc<2) {
504 return CMD_OK;
505 }
506
507 if (cargc>2)
508 topictime=strtol(cargv[cargc-2], NULL, 10);
509
510 if (cargc>3)
511 timestamp=strtol(cargv[cargc-3], NULL, 10);
512
266bf9c1
CP
513 np=getnickbynumericstr((char *)source);
514
6404bd54 515 /* The following check removed because servers can set topics.. */
516#if 0
266bf9c1 517 if (np==NULL) {
c86edd1d
Q
518 /* We should check the sender exists, but we still change the topic even if it doesn't */
519 Error("channel",ERR_WARNING,"Topic change by non-existent user %s",(char *)source);
520 }
6404bd54 521#endif
c86edd1d
Q
522
523 /* Grab channel pointer */
524 if ((cp=findchannel(cargv[0]))==NULL) {
525 /* We're not going to create a channel for the sake of a topic.. */
526 return CMD_OK;
527 } else {
528 if (timestamp && (cp->timestamp < timestamp)) {
529 /* Ignore topic change for younger channel
530 * (note that topic change for OLDER channel should be impossible!) */
531 return CMD_OK;
532 }
533 if (topictime && (cp->topictime > topictime)) {
534 /* Ignore topic change with older topic */
535 return CMD_OK;
536 }
537 if (cp->topic!=NULL) {
538 freesstring(cp->topic);
539 }
540 if (cargv[cargc-1][0]=='\0') {
541 cp->topic=NULL;
542 cp->topictime=0;
543 } else {
544 cp->topic=getsstring(cargv[cargc-1],TOPICLEN);
545 cp->topictime=topictime?topictime:getnettime();
546 }
547 /* Trigger hook */
548 harg[0]=cp;
549 harg[1]=np;
550 triggerhook(HOOK_CHANNEL_TOPIC,harg);
551 }
552
553 return CMD_OK;
554}
555
556/*
557 * Note that this function is also used for processing OPMODE messages.
558 * There is no checking on the source etc. anyway, so this should be OK.
559 * (we are trusting ircd not to feed us bogus modes)
560 */
561
562int handlemodemsg(void *source, int cargc, char **cargv) {
563 channel *cp;
564 int dir=1;
565 int arg=2;
566 char *modestr;
567 unsigned long *lp;
d968d1fe 568 void *harg[4];
c86edd1d
Q
569 nick *np, *target;
570 int hooknum;
571 int changes=0;
d968d1fe 572
c86edd1d
Q
573 if (cargc<2) {
574 return CMD_OK;
575 }
576
577 if (cargv[0][0]!='#' && cargv[0][0]!='+') {
578 /* Not a channel, ignore */
579 return CMD_OK;
580 }
581
582 if ((cp=findchannel(cargv[0]))==NULL) {
583 /* No channel, abort */
584 Error("channel",ERR_WARNING,"Mode change on non-existent channel %s",cargv[0]);
585 return CMD_OK;
586 }
587
588 if (((char *)source)[2]=='\0') {
589 /* Server mode change, treat as divine intervention */
590 np=NULL;
591 } else if ((np=getnickbynumericstr((char *)source))==NULL) {
592 /* No sender, continue but moan */
593 Error("channel",ERR_WARNING,"Mode change by non-existent user %s on channel %s",(char *)source,cp->index->name->content);
594 }
595
596 /* Set up the hook data */
597 harg[0]=cp;
598 harg[1]=np;
d968d1fe 599 harg[3]=(void *)(long)(cp->flags);
c86edd1d
Q
600
601 /* Process the mode string one character at a time */
602 /* Maybe I'll write this more intelligently one day if I can comprehend the ircu code that does this */
603 for (modestr=cargv[1];*modestr;modestr++) {
604 switch(*modestr) {
605 /* Set whether we are adding or removing modes */
606
607 case '+':
608 dir=1;
609 break;
610
611 case '-':
612 dir=0;
613 break;
614
615 /* Simple modes: just set or clear based on value of dir */
616
617 case 'n':
618 if (dir) { SetNoExtMsg(cp); } else { ClearNoExtMsg(cp); }
619 changes |= MODECHANGE_MODES;
620 break;
621
622 case 't':
623 if (dir) { SetTopicLimit(cp); } else { ClearTopicLimit(cp); }
624 changes |= MODECHANGE_MODES;
625 break;
626
627 case 's':
628 if (dir) { SetSecret(cp); ClearPrivate(cp); } else { ClearSecret(cp); }
629 changes |= MODECHANGE_MODES;
630 break;
631
632 case 'p':
633 if (dir) { SetPrivate(cp); ClearSecret(cp); } else { ClearPrivate(cp); }
634 changes |= MODECHANGE_MODES;
635 break;
636
637 case 'i':
638 if (dir) { SetInviteOnly(cp); } else { ClearInviteOnly(cp); }
639 changes |= MODECHANGE_MODES;
640 break;
641
642 case 'm':
643 if (dir) { SetModerated(cp); } else { ClearModerated(cp); }
644 changes |= MODECHANGE_MODES;
645 break;
646
647 case 'c':
648 if (dir) { SetNoColour(cp); } else { ClearNoColour(cp); }
649 changes |= MODECHANGE_MODES;
650 break;
651
652 case 'C':
653 if (dir) { SetNoCTCP(cp); } else { ClearNoCTCP(cp); }
654 changes |= MODECHANGE_MODES;
655 break;
656
657 case 'r':
658 if (dir) { SetRegOnly(cp); } else { ClearRegOnly(cp); }
659 changes |= MODECHANGE_MODES;
660 break;
661
662 case 'D':
663 if (dir) { SetDelJoins(cp); } else { ClearDelJoins(cp); }
664 changes |= MODECHANGE_MODES;
665 break;
666
667 case 'u':
668 if (dir) { SetNoQuitMsg(cp); } else { ClearNoQuitMsg(cp); }
669 changes |= MODECHANGE_MODES;
670 break;
671
672 case 'N':
673 if (dir) { SetNoNotice(cp); } else { ClearNoNotice(cp); }
674 changes |= MODECHANGE_MODES;
675 break;
676
73a2c60a 677 case 'M':
678 if (dir) { SetModNoAuth(cp); } else { ClearModNoAuth(cp); }
679 changes |= MODECHANGE_MODES;
680 break;
681
682 case 'T':
683 if (dir) { SetSingleTarg(cp); } else { ClearSingleTarg(cp); }
684 changes |= MODECHANGE_MODES;
685 break;
686
c86edd1d
Q
687 /* Parameter modes: advance parameter and possibly read it in */
688
689 case 'l':
690 if (dir) {
691 /* +l uses a parameter, but -l does not.
692 * If there is no parameter, don't set the mode.
693 * I guess we should moan too in that case, but
694 * they might be even nastier to us if we do ;) */
695 if (arg<cargc) {
696 cp->limit=strtol(cargv[arg++],NULL,10);
697 SetLimit(cp);
698 }
699 } else {
700 ClearLimit(cp);
701 cp->limit=0;
702 }
703 changes |= MODECHANGE_MODES;
704 break;
705
706 case 'k':
707 if (dir) {
708 /* +k uses a parameter in both directions */
709 if (arg<cargc) {
710 freesstring(cp->key); /* It's probably NULL, but be safe */
711 cp->key=getsstring(cargv[arg++],KEYLEN);
712 SetKey(cp);
713 }
714 } else {
715 freesstring(cp->key);
716 cp->key=NULL;
717 ClearKey(cp);
718 arg++; /* Eat the arg without looking at it, even if it's not there */
719 }
720 changes |= MODECHANGE_MODES;
721 break;
722
723 /* Op/Voice */
724
725 case 'o':
726 case 'v':
727 if (arg<cargc) {
728 if((lp=getnumerichandlefromchanhash(cp->users,numerictolong(cargv[arg++],5)))==NULL) {
729 /* They're not on the channel; MODE crossed with part/kill/kick/blah */
730 Error("channel",ERR_DEBUG,"Mode change for user %s not on channel %s",cargv[arg-1],cp->index->name->content);
731 } else {
732 if ((target=getnickbynumeric(*lp))==NULL) {
733 /* This really is a fuckup, we found them on the channel but there isn't a user with that numeric */
734 /* This means there's a serious bug in the nick/channel tracking code */
735 Error("channel",ERR_ERROR,"Mode change for user %s on channel %s who doesn't exist",cargv[arg-1],cp->index->name->content);
736 } else { /* Do the mode change whilst admiring the beautiful code layout */
737 harg[2]=target;
738 if (*modestr=='o') { if (dir) { *lp |= CUMODE_OP; hooknum=HOOK_CHANNEL_OPPED; } else
739 { *lp &= ~CUMODE_OP; hooknum=HOOK_CHANNEL_DEOPPED; } }
740 else { if (dir) { *lp |= CUMODE_VOICE; hooknum=HOOK_CHANNEL_VOICED; } else
741 { *lp &= ~CUMODE_VOICE; hooknum=HOOK_CHANNEL_DEVOICED; } }
742 triggerhook(hooknum,harg);
743 }
744 }
745 }
746 changes |= MODECHANGE_USERS;
747 break;
748
749 case 'b':
750 if (arg<cargc) {
751 if (dir) {
752 setban(cp,cargv[arg++]);
753 triggerhook(HOOK_CHANNEL_BANSET,harg);
754 } else {
755 clearban(cp,cargv[arg++],0);
756 triggerhook(HOOK_CHANNEL_BANCLEAR,harg);
757 }
758 }
759 changes |= MODECHANGE_BANS;
760 break;
761
762 default:
763 Error("channel",ERR_DEBUG,"Unknown mode char '%c' %s on %s",*modestr,dir?"set":"cleared",cp->index->name->content);
764 break;
765 }
766 }
767
c3db6f7e 768 harg[2]=(void *)((long)changes);
c86edd1d
Q
769 triggerhook(HOOK_CHANNEL_MODECHANGE,(void *)harg);
770 return CMD_OK;
771}
772
773/*
774 * Deal with 2.10.11ism: CLEARMODE
775 *
776 * [hAAA CM #twilightzone ovpsmikbl
777 */
778
779int handleclearmodemsg(void *source, int cargc, char **cargv) {
780 channel *cp;
d968d1fe 781 void *harg[4];
c86edd1d
Q
782 nick *np, *target;
783 char *mcp;
784 unsigned long usermask=0;
785 int i;
786 int changes=0;
787
788 if (cargc<2) {
789 return CMD_OK;
790 }
791
792 if ((cp=findchannel(cargv[0]))==NULL) {
793 /* No channel, abort */
794 Error("channel",ERR_WARNING,"Mode change on non-existent channel %s",cargv[0]);
795 return CMD_OK;
796 }
797
798 if (((char *)source)[2]=='\0') {
799 /* Server mode change? (I don't think servers are allowed to do CLEARMODE) */
800 np=NULL;
801 } else if ((np=getnickbynumericstr((char *)source))==NULL) {
802 /* No sender, continue but moan */
803 Error("channel",ERR_WARNING,"Mode change by non-existent user %s on channel %s",(char *)source,cp->index->name->content);
804 }
805
806 harg[0]=cp;
807 harg[1]=np;
d968d1fe
CP
808 harg[3]=(void *)(long)(cp->flags);
809
c86edd1d
Q
810 for (mcp=cargv[1];*mcp;mcp++) {
811 switch (*mcp) {
812 case 'o':
813 usermask |= CUMODE_OP;
814 changes |= MODECHANGE_USERS;
815 break;
816
817 case 'v':
818 usermask |= CUMODE_VOICE;
819 changes |= MODECHANGE_USERS;
820 break;
821
822 case 'n':
823 ClearNoExtMsg(cp);
824 changes |= MODECHANGE_MODES;
825 break;
826
827 case 't':
828 ClearTopicLimit(cp);
829 changes |= MODECHANGE_MODES;
830 break;
831
832 case 's':
833 ClearSecret(cp);
834 changes |= MODECHANGE_MODES;
835 break;
836
837 case 'p':
838 ClearPrivate(cp);
839 changes |= MODECHANGE_MODES;
840 break;
841
842 case 'i':
843 ClearInviteOnly(cp);
844 changes |= MODECHANGE_MODES;
845 break;
846
847 case 'm':
848 ClearModerated(cp);
849 changes |= MODECHANGE_MODES;
850 break;
851
852 case 'c':
853 ClearNoColour(cp);
854 changes |= MODECHANGE_MODES;
855 break;
856
857 case 'C':
858 ClearNoCTCP(cp);
859 changes |= MODECHANGE_MODES;
860 break;
861
862 case 'r':
863 ClearRegOnly(cp);
864 changes |= MODECHANGE_MODES;
865 break;
866
867 case 'D':
868 ClearDelJoins(cp);
869 changes |= MODECHANGE_MODES;
870 break;
871
872 case 'u':
873 ClearNoQuitMsg(cp);
874 changes |= MODECHANGE_MODES;
875 break;
876
877 case 'N':
878 ClearNoNotice(cp);
879 changes |= MODECHANGE_MODES;
880 break;
73a2c60a 881
882 case 'M':
883 ClearModNoAuth(cp);
884 changes |= MODECHANGE_MODES;
885 break;
886
887 case 'T':
888 ClearSingleTarg(cp);
889 changes |= MODECHANGE_MODES;
890 break;
c86edd1d
Q
891
892 case 'b':
893 clearallbans(cp);
894 changes |= MODECHANGE_BANS;
895 break;
896
897 case 'k':
898 /* This is all safe even if there is no key atm */
899 freesstring(cp->key);
900 cp->key=NULL;
901 ClearKey(cp);
902 changes |= MODECHANGE_MODES;
903 break;
904
905 case 'l':
906 cp->limit=0;
907 ClearLimit(cp);
908 changes |= MODECHANGE_MODES;
909 break;
910 }
911 }
912
913 if (usermask) {
914 /* We have to strip something off each user */
915 for (i=0;i<cp->users->hashsize;i++) {
916 if (cp->users->content[i]!=nouser && (cp->users->content[i] & usermask)) {
917 /* This user exists and has at least one of the modes we're clearing */
918 if ((target=getnickbynumeric(cp->users->content[i]))==NULL) {
919 /* This really is a fuckup, we found them on the channel but there isn't a user with that numeric */
920 /* This means there's a serious bug in the nick/channel tracking code */
921 Error("channel",ERR_ERROR,"CLEARMODE failed: user on channel who doesn't exist?");
922 } else {
923 harg[2]=target;
924 /* Yes, these are deliberate three way bitwise ANDs.. */
925 if (cp->users->content[i] & usermask & CUMODE_OP)
926 triggerhook(HOOK_CHANNEL_DEOPPED, harg);
927 if (cp->users->content[i] & usermask & CUMODE_VOICE)
928 triggerhook(HOOK_CHANNEL_DEVOICED, harg);
929 cp->users->content[i] &= ~usermask;
930 }
931 }
932 }
933 }
934
c3db6f7e 935 harg[2]=(void *)((long)changes);
c86edd1d
Q
936 triggerhook(HOOK_CHANNEL_MODECHANGE, harg);
937 return CMD_OK;
938}
f2b36aa8
CP
939
940void handlewhoischannels(int hooknum, void *arg) {
e40b9754 941 channel **chans;
f2b36aa8 942 char buffer[1024];
e40b9754 943 unsigned int bufpos;
944 sstring *name;
945 unsigned long *num;
f2b36aa8 946 int i;
a7697869 947 char **args = (char **)arg;
948 nick *sender = (nick *)args[0]; /* sender nick */
949 nick *target = (nick *)args[1]; /* target nick */
950 char *sourcenum = args[2]; /* source numeric */
951
952 /* do not show channels for +k service clients or IRC Operators
953 * do not show channels for +n users
954 * unless they whois themselves
955 */
956 if ((IsService(target) || IsHideChan(target)) && sender != target)
f2b36aa8
CP
957 return;
958
959 chans = (channel **)(target->channels->content);
960
961 buffer[0] = '\0';
e40b9754 962 bufpos=0;
963
f2b36aa8 964 /* Not handling delayed joins. */
bfae7e88 965 for(i=target->channels->cursi-1;i>=0;i--) {
e40b9754 966 /* Secret / Private channels: only show if the sender is on the channel as well */
967 if(IsSecret(chans[i]) || IsPrivate(chans[i])) {
968 if (!getnumerichandlefromchanhash(chans[i]->users, sender->numeric))
f2b36aa8
CP
969 continue;
970 }
e40b9754 971
f2b36aa8 972 name = chans[i]->index->name;
e40b9754 973 if (bufpos + name->length > 508) { /* why 508? - need room for -@#channame\0 + 1 slack */
f2b36aa8
CP
974 irc_send("%s", buffer);
975 buffer[0] = '\0';
e40b9754 976 bufpos=0;
f2b36aa8
CP
977 }
978
7bec4aeb 979 /*
980 * 319 RPL_WHOISCHANNELS "source 319 target nick :channels"
981 * "irc.netsplit.net 319 foobar barfoo :@#chan1 +#chan2 #chan3"
982 * "irc.netsplit.net 319 foobar barfoo :-@#chan1 -+#chan2 -#chan3"
983 */
f2b36aa8 984 if(buffer[0] == '\0')
a7697869 985 bufpos=snprintf(buffer, sizeof(buffer), "%s 319 %s %s :", getmynumeric(), sourcenum, target->nick);
f2b36aa8
CP
986
987 num = getnumerichandlefromchanhash(chans[i]->users, target->numeric);
e40b9754 988
989 /* Adding these flags might make the string "unsafe" (without terminating \0). */
990 /* sprintf'ing the channel name afterwards is guaranteed to fix it though */
991 if (IsDeaf(target))
992 buffer[bufpos++]='-';
f2b36aa8 993 if (*num & CUMODE_OP)
e40b9754 994 buffer[bufpos++]='@';
f2b36aa8 995 else if (*num & CUMODE_VOICE)
e40b9754 996 buffer[bufpos++]='+';
f2b36aa8 997
e40b9754 998 bufpos += sprintf(buffer+bufpos, "%s ",name->content);
f2b36aa8
CP
999 }
1000
1001 if (buffer[0] != '\0')
1002 irc_send("%s", buffer);
1003}
1004