]> jfr.im git - irc/quakenet/newserv.git/blame - localuser/localuserchannel.c
GLINES: fix null pointer deref in trustgline / trustungline
[irc/quakenet/newserv.git] / localuser / localuserchannel.c
CommitLineData
c86edd1d
Q
1/* Functions for manipulating local users on channels */
2
3#include "localuser.h"
4#include "localuserchannel.h"
5#include "../nick/nick.h"
6#include "../channel/channel.h"
7#include "../irc/irc.h"
87698d77 8#include "../lib/version.h"
526e7c1d 9#include "../lib/sstring.h"
c86edd1d
Q
10
11#include <stdarg.h>
12#include <stdio.h>
13#include <assert.h>
4ad1cf7a 14#include <string.h>
8bab42e7 15#include <stdint.h>
c86edd1d 16
70b0a4e5 17MODULE_VERSION("");
87698d77 18
526e7c1d
P
19typedef struct pendingkick {
20 nick *source, *target;
21 channel *chan;
22 sstring *reason;
23 struct pendingkick *next;
24} pendingkick;
25
26pendingkick *pendingkicklist;
27
c86edd1d 28int handlechannelmsgcmd(void *source, int cargc, char **cargv);
cc7f7ce9 29int handlechannelnoticecmd(void *source, int cargc, char **cargv);
c86edd1d 30int handleinvitecmd(void *source, int cargc, char **cargv);
526e7c1d
P
31void clearpendingkicks(int hooknum, void *arg);
32void checkpendingkicknicks(int hooknum, void *arg);
33void checkpendingkickchannels(int hooknum, void *arg);
34void _localkickuser(nick *np, channel *cp, nick *target, const char *message);
c86edd1d
Q
35void luc_handlekick(int hooknum, void *arg);
36
37void _init() {
526e7c1d 38 pendingkicklist=NULL;
c86edd1d 39 registerserverhandler("P",&handlechannelmsgcmd,2);
cc7f7ce9 40 registerserverhandler("O",&handlechannelnoticecmd,2);
c86edd1d
Q
41 registerserverhandler("I",&handleinvitecmd,2);
42 registerhook(HOOK_CHANNEL_KICK, luc_handlekick);
526e7c1d
P
43 registerhook(HOOK_NICK_LOSTNICK, checkpendingkicknicks);
44 registerhook(HOOK_CHANNEL_LOSTCHANNEL, checkpendingkickchannels);
45 registerhook(HOOK_CORE_ENDOFHOOKSQUEUE,&clearpendingkicks);
c86edd1d
Q
46}
47
48void _fini() {
526e7c1d
P
49 pendingkick *pk;
50
c86edd1d 51 deregisterserverhandler("P",&handlechannelmsgcmd);
cc7f7ce9 52 deregisterserverhandler("O",&handlechannelnoticecmd);
c86edd1d
Q
53 deregisterserverhandler("I",&handleinvitecmd);
54 deregisterhook(HOOK_CHANNEL_KICK, luc_handlekick);
526e7c1d
P
55 deregisterhook(HOOK_NICK_LOSTNICK, checkpendingkicknicks);
56 deregisterhook(HOOK_CHANNEL_LOSTCHANNEL, checkpendingkickchannels);
57 deregisterhook(HOOK_CORE_ENDOFHOOKSQUEUE,&clearpendingkicks);
58
df3bf970 59 for (pk=pendingkicklist;pk;pk=pendingkicklist) {
526e7c1d
P
60 pendingkicklist = pk->next;
61 freesstring(pk->reason);
62 free(pk);
526e7c1d 63 }
c86edd1d
Q
64}
65
66void luc_handlekick(int hooknum, void *arg) {
67 void **args=arg;
68 nick *target=args[1];
69 void *myargs[3];
70
71 if (homeserver(target->numeric)!=mylongnum)
72 return;
73
74 if (umhandlers[target->numeric & MAXLOCALUSER]) {
75 myargs[0]=args[2];
76 myargs[1]=args[0];
77 myargs[2]=args[3];
78
79 (umhandlers[target->numeric & MAXLOCALUSER])(target, LU_KICKED, myargs);
80 }
81}
82
83/* invites look something like:
84 * XXyyy I TargetNick :#channel
85 */
86
87int handleinvitecmd(void *source, int cargc, char **cargv) {
88 void *nargs[2];
89 nick *sender;
90 channel *cp;
91 nick *target;
92
93 if (cargc<2) {
94 return CMD_OK;
95 }
96
97 if (!(sender=getnickbynumericstr(source))) {
16739dbe 98 Error("localuserchannel",ERR_WARNING,"Got invite from unknown numeric %s.",(char *)source);
c86edd1d
Q
99 return CMD_OK;
100 }
101
102 if (!(target=getnickbynick(cargv[0]))) {
103 Error("localuserchannel",ERR_WARNING,"Got invite for unknown local user %s.",cargv[0]);
104 return CMD_OK;
105 }
106
107 if (!(cp=findchannel(cargv[1]))) {
108 Error("localuserchannel",ERR_WARNING,"Got invite for non-existent channel %s.",cargv[1]);
109 return CMD_OK;
110 }
111
112 if (homeserver(target->numeric) != mylongnum) {
113 Error("localuserchannel",ERR_WARNING,"Got invite for non-local user %s.",target->nick);
114 return CMD_OK;
115 }
116
117 /* This is a valid race condition.. */
118 if (getnumerichandlefromchanhash(cp->users, target->numeric)) {
119 Error("localuserchannel",ERR_DEBUG,"Got invite for user %s already on %s.",target->nick, cp->index->name->content);
120 return CMD_OK;
121 }
122
123 nargs[0]=(void *)sender;
124 nargs[1]=(void *)cp;
125
126 if (umhandlers[target->numeric&MAXLOCALUSER]) {
127 (umhandlers[target->numeric&MAXLOCALUSER])(target, LU_INVITE, nargs);
128 }
129
130 return CMD_OK;
131}
132
d561c7db 133/* PRIVMSG/NOTICE to channel handling is identical up to the point where the hook is called. */
134static int handlechannelmsgornotice(void *source, int cargc, char **cargv, int isnotice) {
c86edd1d
Q
135 void *nargs[3];
136 nick *sender;
137 channel *target;
138 nick *np;
139 unsigned long numeric;
140 int i;
141 int found=0;
142
143 if (cargc<2) {
144 return CMD_OK;
145 }
146
147 if (cargv[0][0]!='#' && cargv[0][0]!='+') {
148 /* Not a channel message */
149 return CMD_OK;
150 }
151
152 if ((sender=getnickbynumericstr((char *)source))==NULL) {
d561c7db 153 Error("localuserchannel",ERR_DEBUG,"PRIVMSG/NOTICE from non existant user %s",(char *)source);
c86edd1d
Q
154 return CMD_OK;
155 }
156
157 if ((target=findchannel(cargv[0]))==NULL) {
d561c7db 158 Error("localuserchannel",ERR_DEBUG,"PRIVMSG/NOTICE to non existant channel %s",cargv[0]);
c86edd1d
Q
159 return CMD_OK;
160 }
161
162 /* OK, we have a valid channel the message was sent to. Let's look to see
163 * if we have any local users on there. Set up the arguments first as they are
164 * always going to be the same. */
165
166 nargs[0]=(void *)sender;
167 nargs[1]=(void *)target;
168 nargs[2]=(void *)cargv[1];
169
170 for (found=0,i=0;i<target->users->hashsize;i++) {
171 numeric=target->users->content[i];
172 if (numeric!=nouser && homeserver(numeric&CU_NUMERICMASK)==mylongnum) {
173 /* OK, it's one of our users.. we need to deal with it */
526e7c1d
P
174 if (found && ((getnickbynumericstr((char *)source)==NULL) || ((findchannel(cargv[0]))==NULL) || !(getnumerichandlefromchanhash(target->users, sender->numeric)))) {
175 Error("localuserchannel", ERR_INFO, "Nick or channel lost, or user no longer on channel in LU_CHANMSG");
176 break;
177 }
c86edd1d
Q
178 found++;
179 if (umhandlers[numeric&MAXLOCALUSER]) {
180 if ((np=getnickbynumeric(numeric))==NULL) {
d561c7db 181 Error("localuserchannel",ERR_ERROR,"PRIVMSG/NOTICE to channel user who doesn't exist (?) on %s",cargv[0]);
c86edd1d
Q
182 continue;
183 }
184 if (!IsDeaf(np)) {
d561c7db 185 (umhandlers[numeric&MAXLOCALUSER])(np,(isnotice?LU_CHANNOTICE:LU_CHANMSG),nargs);
c86edd1d
Q
186 } else {
187 found--;
188 }
189 }
190 }
191 }
192
193 if (!found) {
d561c7db 194 Error("localuserchannel",ERR_DEBUG,"Couldn't find any local targets for PRIVMSG/NOTICE to %s",cargv[0]);
c86edd1d
Q
195 }
196
197 return CMD_OK;
198}
199
d561c7db 200/* Wrapper functions to call the above code */
c5d235d0 201int handlechannelmsgcmd(void *source, int cargc, char **cargv) {
d561c7db 202 return handlechannelmsgornotice(source, cargc, cargv, 0);
203}
cc7f7ce9 204
d561c7db 205int handlechannelnoticecmd(void *source, int cargc, char **cargv) {
206 return handlechannelmsgornotice(source, cargc, cargv, 1);
cc7f7ce9
C
207}
208
c85feb15 209/* Burst onto channel. This replaces the timestamp and modes
210 * with the provided ones. Keys and limits use the provided ones
211 * if needed. nick * is optional, but joins the channel opped if
212 * provided.
213 *
214 * Due to the way ircu works, this only works if the provided timestamp is
215 * older than the one currently on the channel. If the timestamps are
216 * equal, the modes are ignored, but the user (if any) is still allowed to
217 * join with op. If the provided timestamp is newer than the exsting one we
218 * just do a join instead - if you try to replace an old timestamp with a
219 * newer one ircu will just laugh at you (and you will be desynced).
220 */
221int localburstontochannel(channel *cp, nick *np, time_t timestamp, flag_t modes, unsigned int limit, char *key) {
222 unsigned int i;
223 char extramodebuf[512];
224 char nickbuf[512];
225
226 if (cp==NULL)
227 return 1;
228
229 if (timestamp > cp->timestamp) {
230 return localjoinchannel(np, cp);
231 }
232
233 if (timestamp < cp->timestamp) {
234 cp->timestamp=timestamp;
235 cp->flags=modes;
236
237 /* deal with key - if we need one use the provided one if set, otherwise
238 * the existing one, but if there is no existing one clear +k */
239 if (IsKey(cp)) {
934fe9ac 240 /* Sanitise the provided key - this might invalidate it.. */
241 if (key)
242 clean_key(key);
243
244 if (key && *key) {
c85feb15 245 /* Free old key, if any */
246 if (cp->key)
247 freesstring(cp->key);
248
249 cp->key=getsstring(key,KEYLEN);
250 } else {
251 if (!cp->key)
252 ClearKey(cp);
253 }
254 } else {
255 /* Not +k - free the existing key, if any */
256 freesstring(cp->key);
257 cp->key=NULL;
258 }
259
260 if (IsLimit(cp)) {
261 if (limit) {
262 cp->limit=limit;
263 } else {
264 if (!cp->limit)
265 ClearLimit(cp);
266 }
267 } else {
268 if (cp->limit)
269 cp->limit=0;
270 }
271
272 /* We also need to blow away all other op/voice and bans on the
273 * channel. This is the same code we use when someone else does
274 * it to us. */
275 clearallbans(cp);
276 for (i=0;i<cp->users->hashsize;i++) {
277 if (cp->users->content[i]!=nouser) {
278 cp->users->content[i]&=CU_NUMERICMASK;
279 }
280 }
281 }
282
283 /* Actually add the nick to the channel. Make sure it's a local nick and actually exists first. */
284 if (np && (homeserver(np->numeric) == mylongnum) &&
285 !(getnumerichandlefromchanhash(cp->users,np->numeric))) {
286 addnicktochannel(cp,(np->numeric)|CUMODE_OP);
287 } else {
288 np=NULL; /* If we're not adding it here, don't send it later in the burst msg either */
289 }
290
291 if (connected) {
292 /* actual burst message */
293 if (np) {
294 sprintf(nickbuf," %s:o", longtonumeric(np->numeric,5));
295 } else {
296 nickbuf[0]='\0';
297 }
298
299 if (IsLimit(cp)) {
300 sprintf(extramodebuf," %d",cp->limit);
301 } else {
302 extramodebuf[0]='\0';
303 }
304
305 /* XX B #channel <timestamp> +modes <limit> <key> <user> */
5d889c62 306 irc_send("%s B %s %lu %s%s%s%s%s",
c85feb15 307 mynumeric->content,cp->index->name->content,cp->timestamp,
308 printflags(cp->flags,cmodeflags),extramodebuf,
309 IsKey(cp)?" ":"",IsKey(cp)?cp->key->content:"", nickbuf);
310 }
311
312 /* Tell the world something happened... */
313 triggerhook(HOOK_CHANNEL_BURST,cp);
314
315 return 0;
316}
317
c86edd1d
Q
318int localjoinchannel(nick *np, channel *cp) {
319 void *harg[2];
320
321 if (cp==NULL || np==NULL) {
322 return 1;
323 }
324
325 /* Check that the user _is_ a local one.. */
326 if (homeserver(np->numeric)!=mylongnum) {
327 return 1;
328 }
329
330 /* Check that the user isn't on the channel already */
331 if ((getnumerichandlefromchanhash(cp->users,np->numeric))!=NULL) {
332 return 1;
333 }
334
335 /* OK, join the channel.. */
336 addnicktochannel(cp,np->numeric);
337
338 /* Trigger the event */
339 harg[0]=cp;
340 harg[1]=np;
341
c86edd1d
Q
342 if (connected) {
343 irc_send("%s J %s %lu",longtonumeric(np->numeric,5),cp->index->name->content,cp->timestamp);
344 }
9408ae5d 345
346 triggerhook(HOOK_CHANNEL_JOIN, harg);
347
c86edd1d
Q
348 return 0;
349}
350
334b567e 351int localpartchannel(nick *np, channel *cp, char *reason) {
c86edd1d
Q
352 void *harg[3];
353
354 /* Check pointers are valid.. */
355 if (cp==NULL || np==NULL) {
16739dbe 356 Error("localuserchannel",ERR_WARNING,"Trying to part NULL channel or NULL nick (cp=%p,np=%p)",cp,np);
c86edd1d
Q
357 return 1;
358 }
359
360 /* And that user is local.. */
361 if (homeserver(np->numeric)!=mylongnum) {
362 Error("localuserchannel",ERR_WARNING,"Trying to part remote user %s",np->nick);
363 return 1;
364 }
365
366 /* Check that user is on channel */
367 if (getnumerichandlefromchanhash(cp->users,np->numeric)==NULL) {
368 Error("localuserchannel",ERR_WARNING,"Trying to part user %s from channel %s it is not on",np->nick,cp->index->name->content);
369 return 1;
370 }
371
372 if (connected) {
334b567e
P
373 if (reason != NULL)
374 irc_send("%s L %s :%s",longtonumeric(np->numeric,5),cp->index->name->content, reason);
375 else
376 irc_send("%s L %s",longtonumeric(np->numeric,5),cp->index->name->content);
c86edd1d
Q
377 }
378
379 harg[0]=cp;
380 harg[1]=np;
381 harg[2]=NULL;
382 triggerhook(HOOK_CHANNEL_PART,harg);
383
384 /* Now leave the channel */
385 delnickfromchannel(cp,np->numeric,1);
386
387 return 0;
388}
389
390int localcreatechannel(nick *np, char *channame) {
391 channel *cp;
392
393 /* Check that the user _is_ a local one.. */
394 if (homeserver(np->numeric)!=mylongnum) {
395 return 1;
396 }
397
398 if ((cp=findchannel(channame))!=NULL) {
399 /* Already exists */
400 return 1;
401 }
402
403 cp=createchannel(channame);
404 cp->timestamp=getnettime();
405
406 /* Add the local user to the channel, preopped */
407 addnicktochannel(cp,(np->numeric)|CUMODE_OP);
408
409 if (connected) {
410 irc_send("%s C %s %ld",longtonumeric(np->numeric,5),cp->index->name->content,cp->timestamp);
411 }
412
413 triggerhook(HOOK_CHANNEL_NEWCHANNEL,(void *)cp);
414 return 0;
415}
416
417int localgetops(nick *np, channel *cp) {
418 unsigned long *lp;
419
420 /* Check that the user _is_ a local one.. */
421 if (homeserver(np->numeric)!=mylongnum) {
422 return 1;
423 }
424
425 if ((lp=getnumerichandlefromchanhash(cp->users,np->numeric))==NULL) {
426 return 1;
427 }
428
429 if (*lp & CUMODE_OP) {
430 /* already opped */
431 return 1;
432 }
433
434 /* Op the user */
435 (*lp)|=CUMODE_OP;
436
437 if (connected) {
438 irc_send("%s M %s +o %s",mynumeric->content,cp->index->name->content,longtonumeric(np->numeric,5));
439 }
440
441 return 0;
442}
443
444int localgetvoice(nick *np, channel *cp) {
445 unsigned long *lp;
446
447 /* Check that the user _is_ a local one.. */
448 if (homeserver(np->numeric)!=mylongnum) {
449 return 1;
450 }
451
452 if ((lp=getnumerichandlefromchanhash(cp->users,np->numeric))==NULL) {
453 return 1;
454 }
455
456 if (*lp & CUMODE_VOICE) {
457 /* already opped */
458 return 1;
459 }
460
461 /* Voice the user */
462 (*lp)|=CUMODE_VOICE;
463
464 if (connected) {
465 irc_send("%s M %s +v %s",mynumeric->content,cp->index->name->content,longtonumeric(np->numeric,5));
466 }
467
468 return 0;
469}
470
471/*
472 * localsetmodeinit:
473 * Initialises the modechanges structure.
474 */
475
476void localsetmodeinit (modechanges *changes, channel *cp, nick *np) {
477 changes->cp=cp;
478 changes->source=np;
479 changes->changecount=0;
480 changes->addflags=0;
481 changes->delflags=0;
482}
483
484/*
485 * localdosetmode_ban:
486 * Set or clear a ban on the channel
487 */
488
489void localdosetmode_ban (modechanges *changes, const char *ban, short dir) {
490 sstring *bansstr=getsstring(ban,HOSTLEN+NICKLEN+USERLEN+5);
491
492 /* If we're told to clear a ban that isn't here, do nothing. */
493 if (dir==MCB_DEL && !clearban(changes->cp, bansstr->content, 1))
494 return;
0524ca09 495
496 /* Similarly if someone is trying to add a completely overlapped ban, do
497 * nothing */
498 if (dir==MCB_ADD && !setban(changes->cp, bansstr->content))
499 return;
c86edd1d
Q
500
501 if (changes->changecount >= MAXMODEARGS)
502 localsetmodeflush(changes, 0);
503
504 changes->changes[changes->changecount].str=bansstr;
505 changes->changes[changes->changecount].flag='b';
506 changes->changes[changes->changecount++].dir=dir;
507
508 if (dir==MCB_ADD) {
509 setban(changes->cp, bansstr->content);
510 }
511}
512
513/*
514 * localdosetmode_key:
515 * Set or clear a key on the channel
516 */
517
934fe9ac 518void localdosetmode_key (modechanges *changes, char *key, short dir) {
c86edd1d
Q
519 int i,j;
520 sstring *keysstr;
521
522 /* Check we have space in the buffer */
523 if (changes->changecount >= MAXMODEARGS)
524 localsetmodeflush(changes,0);
525
526 if (dir==MCB_ADD) {
934fe9ac 527 /* Sanitise the key. If this nullifies it then we give up */
528 clean_key(key);
529 if (!*key)
530 return;
531
c86edd1d
Q
532 /* Get a copy of the key for use later */
533 keysstr=getsstring(key, KEYLEN);
534
535 /* Check there isn't a key set/clear in the pipeline already.. */
536 for (i=0;i<changes->changecount;i++) {
537 if (changes->changes[i].flag=='k') {
538 /* There's a change already.. */
539 if (changes->changes[i].dir==MCB_ADD) {
540 /* Already an add key change. Here, we just replace the key
541 * we were going to add with this one. Note that we need to
542 * free the current cp->key, and the changes.str */
543 freesstring(changes->changes[i].str);
544 changes->changes[i].str=keysstr;
545 freesstring(changes->cp->key);
546 changes->cp->key=getsstring(key, KEYLEN);
547 /* That's it, we're done */
934fe9ac 548 freesstring(keysstr);
c86edd1d
Q
549 return;
550 } else {
551 /* There was a command to delete key.. we need to flush
552 * this out then add the new key (-k+k isn't valid).
553 * Make sure this gets flushed, then drop through to common code */
554 localsetmodeflush(changes, 1);
555 }
556 }
557 }
558
559 /* We got here, so there's no change pending already .. check for key on chan */
560 if (IsKey(changes->cp)) {
561 if (sstringcompare(changes->cp->key, keysstr)) {
562 /* Key is set and different. Need to put -k in and flush changes now */
563 changes->changes[changes->changecount].str=changes->cp->key; /* implicit free */
564 changes->changes[changes->changecount].dir=MCB_DEL;
565 changes->changes[changes->changecount++].flag='k';
566 localsetmodeflush(changes, 1); /* Note that this will free the sstring on the channel */
567 } else {
568 /* Key is set and the same: do nothing. */
569 freesstring(keysstr); /* Don't need this after all */
570 return;
571 }
572 }
573
574 /* If we get here, there's no key on the channel right now and nothing in the buffer to
575 * add or remove one */
576 SetKey(changes->cp);
577 changes->cp->key=getsstring(key, KEYLEN);
578
579 changes->changes[changes->changecount].str=keysstr;
580 changes->changes[changes->changecount].dir=MCB_ADD;
581 changes->changes[changes->changecount++].flag='k';
582 } else {
583 /* We're removing a key.. */
584 /* Only bother if key is set atm */
585 if (IsKey(changes->cp)) {
586 ClearKey(changes->cp);
587 for(i=0;i<changes->changecount;i++) {
588 if (changes->changes[i].flag=='k') {
589 /* We were already doing something with a key..
590 * it MUST be adding one */
591 assert(changes->changes[i].dir==MCB_ADD);
592 /* Just forget the earlier change */
593 freesstring(changes->changes[i].str);
594 changes->changecount--;
595 for (j=i;j<changes->changecount;j++) {
596 changes->changes[j]=changes->changes[j+1];
597 }
598 /* Explicitly free key on chan */
599 freesstring(changes->cp->key);
600 changes->cp->key=NULL;
601 return;
602 }
603 }
604
605 /* We didn't hit a key change, so put a remove command in */
606 changes->changes[changes->changecount].str=changes->cp->key; /* implicit free */
607 changes->cp->key=NULL;
608 changes->changes[changes->changecount].dir=MCB_DEL;
609 changes->changes[changes->changecount++].flag='k';
610 }
611 }
612}
613
614void localdosetmode_limit (modechanges *changes, unsigned int limit, short dir) {
615 int i;
616 char buf[20];
617
618 if (dir==MCB_DEL) {
619 localdosetmode_simple(changes, 0, CHANMODE_LIMIT);
620 return;
621 }
622
623 /* Kill redundant changes */
624 if ((IsLimit(changes->cp) && changes->cp->limit==limit) || !limit)
625 return;
626
627 SetLimit(changes->cp);
628 changes->cp->limit=limit;
629 changes->delflags &= ~CHANMODE_LIMIT;
630
631 /* Check for existing limit add */
632 for (i=0;i<changes->changecount;i++) {
633 if (changes->changes[i].flag=='l') {
634 /* must be add */
635 freesstring(changes->changes[i].str);
636 sprintf(buf,"%u",limit);
637 changes->changes[i].str=getsstring(buf,20);
638 return;
639 }
640 }
641
642 /* None, add new one. Note that we can do +l even if a limit is already set */
643 if (changes->changecount >= MAXMODEARGS)
644 localsetmodeflush(changes,0);
645
646 changes->changes[changes->changecount].flag='l';
647 sprintf(buf,"%u",limit);
648 changes->changes[changes->changecount].str=getsstring(buf,20);
649 changes->changes[changes->changecount++].dir=MCB_ADD;
650}
651
652void localdosetmode_simple (modechanges *changes, flag_t addmodes, flag_t delmodes) {
653 int i,j;
654
655 /* We can't add a mode we're deleting, a key, limit, or mode that's already set */
656 addmodes &= ~(delmodes | CHANMODE_KEY | CHANMODE_LIMIT | changes->cp->flags);
657
658 /* We can't delete a key or a mode that's not set */
659 delmodes &= (~(CHANMODE_KEY) & changes->cp->flags);
660
661 /* If we're doing +p, do -s as well, and vice versa */
662 /* Also disallow +ps */
663 if (addmodes & CHANMODE_SECRET) {
664 addmodes &= ~(CHANMODE_PRIVATE);
665 delmodes |= (CHANMODE_PRIVATE & changes->cp->flags);
666 }
667
668 if (addmodes & CHANMODE_PRIVATE) {
669 delmodes |= (CHANMODE_SECRET & changes->cp->flags);
670 }
671
672 /* Fold changes into channel */
673 changes->cp->flags |= addmodes;
674 changes->cp->flags &= ~delmodes;
675
676 if (delmodes & CHANMODE_LIMIT) {
677 /* Check for +l in the parametered changes */
678 for (i=0;i<changes->changecount;i++) {
679 if (changes->changes[i].flag=='l') {
680 freesstring(changes->changes[i].str);
681 changes->changecount--;
682 for (j=0;j<changes->changecount;j++) {
683 changes->changes[j]=changes->changes[j+1];
684 }
685 }
686 break;
687 }
688 changes->cp->limit=0;
689 }
690
691 /* And into the changes buffer */
692 changes->addflags &= ~delmodes;
693 changes->addflags |= addmodes;
694
695 changes->delflags &= ~addmodes;
696 changes->delflags |= delmodes;
697}
698
699/*
700 * localdosetmode:
701 * Applies a mode change.
702 */
703
704void localdosetmode_nick (modechanges *changes, nick *target, short modes) {
705 unsigned long *lp;
706
707 if ((lp=getnumerichandlefromchanhash(changes->cp->users,target->numeric))==NULL) {
708 /* Target isn't on channel, abort */
709 return;
710 }
711
b69a2845
GB
712 if (IsCloaked(target)) {
713 /* Target is cloaked, never set channel modes for cloaked users */
714 return;
715 }
716
c86edd1d
Q
717 if ((modes & MC_DEOP) && (*lp & CUMODE_OP)) {
718 (*lp) &= ~CUMODE_OP;
719 if (changes->changecount >= MAXMODEARGS)
720 localsetmodeflush(changes, 0);
721 changes->changes[changes->changecount].str=getsstring(longtonumeric(target->numeric,5),5);
722 changes->changes[changes->changecount].dir=MCB_DEL;
723 changes->changes[changes->changecount++].flag='o';
724 }
725
726 if ((modes & MC_DEVOICE) && (*lp & CUMODE_VOICE)) {
727 (*lp) &= ~CUMODE_VOICE;
728 if (changes->changecount >= MAXMODEARGS)
729 localsetmodeflush(changes, 0);
730 changes->changes[changes->changecount].str=getsstring(longtonumeric(target->numeric,5),5);
731 changes->changes[changes->changecount].dir=MCB_DEL;
732 changes->changes[changes->changecount++].flag='v';
733 }
734
735 if ((modes & MC_OP) && !(modes & MC_DEOP) && !(*lp & CUMODE_OP)) {
736 (*lp) |= CUMODE_OP;
737 if (changes->changecount >= MAXMODEARGS)
738 localsetmodeflush(changes, 0);
739 changes->changes[changes->changecount].str=getsstring(longtonumeric(target->numeric,5),5);
740 changes->changes[changes->changecount].dir=MCB_ADD;
741 changes->changes[changes->changecount++].flag='o';
742 }
743
744 if ((modes & MC_VOICE) && !(modes & MC_DEVOICE) && !(*lp & CUMODE_VOICE)) {
745 (*lp) |= CUMODE_VOICE;
746 if (changes->changecount >= MAXMODEARGS)
747 localsetmodeflush(changes, 0);
748 changes->changes[changes->changecount].str=getsstring(longtonumeric(target->numeric,5),5);
749 changes->changes[changes->changecount].dir=MCB_ADD;
750 changes->changes[changes->changecount++].flag='v';
751 }
752
753}
754
755/*
756 * localsetmodeflush:
757 * Sends out mode changes to the network.
758 */
759
760void localsetmodeflush (modechanges *changes, int flushall) {
761 int i,j=0;
762 unsigned long *lp;
763 char source[6];
764
765 char addmodes[26];
766 int ampos=0;
767 char remmodes[26];
768 int rmpos=0;
769
770 char addargs[500];
771 int aapos=0;
772 char remargs[500];
773 int rapos=0;
774
775 strcpy(addmodes, printflags_noprefix(changes->addflags, cmodeflags));
776 ampos=strlen(addmodes);
777
778 strcpy(remmodes, printflags_noprefix(changes->delflags, cmodeflags));
779 rmpos=strlen(remmodes);
780
781 changes->addflags=changes->delflags=0;
782
783 for (i=0;i<changes->changecount;i++) {
784 /* Don't overflow the string, kinda nasty to work out.. format is: */
785 /* AAAA M #chan +add-rem (addstr) (remstr) */
786 if ((changes->cp->index->name->length + aapos + rapos +
787 ampos + rmpos + changes->changes[i].str->length + 20) > BUFSIZE)
788 break;
789
790 switch (changes->changes[i].dir) {
791 case MCB_ADD:
792 addmodes[ampos++]=changes->changes[i].flag;
793 aapos+=sprintf(addargs+aapos, "%s ", changes->changes[i].str->content);
794 break;
795
796 case MCB_DEL:
797 remmodes[rmpos++]=changes->changes[i].flag;
798 rapos+=sprintf(remargs+rapos, "%s ", changes->changes[i].str->content);
799 break;
800 }
801 freesstring(changes->changes[i].str);
802 }
803
804 if (i<changes->changecount) {
805 for (j=i;j<changes->changecount;j++)
806 changes->changes[j-i]=changes->changes[j];
807 }
808
809 changes->changecount -= i;
810
811 if ((ampos+rmpos)==0) {
812 /* No changes */
813 return;
814 }
815
816 addmodes[ampos]='\0';
817 remmodes[rmpos]='\0';
818 addargs[aapos]='\0';
819 remargs[rapos]='\0';
820
821 if (changes->source==NULL ||
822 (lp=getnumerichandlefromchanhash(changes->cp->users,changes->source->numeric))==NULL) {
823 /* User isn't on channel, hack mode */
824 strcpy(source,mynumeric->content);
825 } else {
826 /* Check the user is local */
827 if (homeserver(changes->source->numeric)!=mylongnum) {
828 return;
829 }
830 if ((*lp&CUMODE_OP)==0) {
831 localgetops(changes->source,changes->cp);
832 }
833 strcpy(source,longtonumeric(changes->source->numeric,5));
834 }
835
836 if (connected) {
837 irc_send("%s M %s %s%s%s%s %s%s",source,changes->cp->index->name->content,
838 rmpos ? "-" : "", remmodes,
839 ampos ? "+" : "", addmodes, remargs, addargs);
840 }
841
842 /* If we have to flush everything out but didn't finish, go round again */
843 if (changes->changecount && flushall)
844 localsetmodeflush(changes, 1);
845}
846
847/*
848 * localsetmodes:
849 * Sets modes for the user on the channel. This is now a stub routine that
850 * uses the new functions.
851 */
852
853int localsetmodes(nick *np, channel *cp, nick *target, short modes) {
854 modechanges changes;
855
856 localsetmodeinit (&changes, cp, np);
857 localdosetmode_nick (&changes, target, modes);
858 localsetmodeflush (&changes, 1);
859
860 return 0;
861}
862
863/* This function just sends the actual mode change,
864 * it assumes that the actual channel modes have been changed appropriately.
865 * This is unfortunately inconsistent with the rest of the localuser API..
866 */
867
868void localusermodechange(nick *np, channel *cp, char *modes) {
869 char source[10];
870 unsigned long *lp;
871
872 if (np==NULL || (lp=getnumerichandlefromchanhash(cp->users,np->numeric))==NULL) {
873 /* User isn't on channel, hack mode */
874 strcpy(source,mynumeric->content);
875 } else {
876 /* Check the user is local */
877 if (homeserver(np->numeric)!=mylongnum) {
878 return;
879 }
880 if ((*lp&CUMODE_OP)==0) {
881 localgetops(np,cp);
882 }
883 strcpy(source,longtonumeric(np->numeric,5));
884 }
885
886 if (connected) {
887 irc_send("%s M %s %s",source,cp->index->name->content,modes);
888 }
889}
890
891/* This function actually sets the topic itself though.. a bit inconsistent :/ */
892
893void localsettopic(nick *np, channel *cp, char *topic) {
894 unsigned long *lp;
895 char source[10];
1e1bd194 896 time_t now=getnettime();
c86edd1d
Q
897
898 if (np==NULL || (lp=getnumerichandlefromchanhash(cp->users,np->numeric))==NULL) {
899 /* User isn't on channel, hack mode */
900 strcpy(source,mynumeric->content);
901 } else {
902 /* Check the user is local */
903 if (homeserver(np->numeric)!=mylongnum) {
904 return;
905 }
906 if ((*lp&CUMODE_OP)==0 && IsTopicLimit(cp)) {
907 localgetops(np,cp);
908 }
909 strcpy(source,longtonumeric(np->numeric,5));
910 }
911
912 if (cp->topic) {
913 freesstring(cp->topic);
914 }
915
916 cp->topic=getsstring(topic,TOPICLEN);
1e1bd194 917
13199536 918 /* Update the topic time iff the old time was in the past. This
919 * means if we are bouncing a topic with a TS 1sec newer than ours
920 * we won't use an old timestamp */
921 if (cp->topictime < now) {
922 cp->topictime=now;
1e1bd194 923 }
c86edd1d
Q
924
925 if (connected) {
58a4da4a 926 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:"");
c86edd1d
Q
927 }
928}
929
930void localkickuser(nick *np, channel *cp, nick *target, const char *message) {
526e7c1d
P
931 pendingkick *pk;
932
933 if (hookqueuelength) {
934 for (pk = pendingkicklist; pk; pk = pk->next)
935 if (pk->target == target && pk->chan == cp)
936 return;
937
a77bd764 938 Error("localuserchannel", ERR_DEBUG, "Adding pending kick for %s on %s", target->nick, cp->index->name->content);
526e7c1d
P
939 pk = (pendingkick *)malloc(sizeof(pendingkick));
940 pk->source = np;
941 pk->chan = cp;
942 pk->target = target;
943 pk->reason = getsstring(message, BUFSIZE);
944 pk->next = pendingkicklist;
945 pendingkicklist = pk;
946 } else {
947 _localkickuser(np, cp, target, message);
948 }
949}
950
951void _localkickuser(nick *np, channel *cp, nick *target, const char *message) {
c86edd1d
Q
952 unsigned long *lp;
953 char source[10];
954
955 if (np==NULL || (lp=getnumerichandlefromchanhash(cp->users,np->numeric))==NULL) {
956 /* User isn't on channel, hack mode */
957 strcpy(source,mynumeric->content);
958 } else {
959 /* Check the user is local */
960 if (homeserver(np->numeric)!=mylongnum) {
961 return;
962 }
526e7c1d 963 if ((*lp&CUMODE_OP)==0) {
c86edd1d
Q
964 localgetops(np,cp);
965 }
966 strcpy(source,longtonumeric(np->numeric,5));
967 }
968
969 if ((lp=getnumerichandlefromchanhash(cp->users,target->numeric))==NULL)
970 return;
971
972 /* Send the message to the network first in case delnickfromchannel()
973 * destroys the channel.. */
974 if (connected) {
975 irc_send("%s K %s %s :%s",source,cp->index->name->content,
976 longtonumeric(target->numeric,5), message);
977 }
978
979 delnickfromchannel(cp, target->numeric, 1);
980}
981
526e7c1d
P
982void clearpendingkicks(int hooknum, void *arg) {
983 pendingkick *pk;
984
985 pk = pendingkicklist;
986 while (pk) {
987 pendingkicklist = pk->next;
988
989 if (pk->target && pk->chan) {
a77bd764 990 Error("localuserchannel", ERR_DEBUG, "Processing pending kick for %s on %s", pk->target->nick, pk->chan->index->name->content);
526e7c1d
P
991 _localkickuser(pk->source, pk->chan, pk->target, pk->reason->content);
992 }
993
994 freesstring(pk->reason);
995 free(pk);
996 pk = pendingkicklist;
997 }
998}
999
1000void checkpendingkicknicks(int hooknum, void *arg) {
1001 nick *np = (nick *)arg;
1002 pendingkick *pk;
1003
1004 for (pk=pendingkicklist; pk; pk = pk->next) {
1005 if (pk->source == np) {
1006 Error("localuserchannel", ERR_INFO, "Pending kick source %s got deleted, NULL'ing source for pending kick", np->nick);
1007 pk->source = NULL;
1008 }
1009 if (pk->target == np) {
1010 Error("localuserchannel", ERR_INFO, "Pending kick target %s got deleted, NULL'ing target for pending kick", np->nick);
1011 pk->target = NULL;
1012 }
1013 }
1014}
1015
1016void checkpendingkickchannels(int hooknum, void *arg) {
1017 channel *cp = (channel *)arg;
1018 pendingkick *pk;
1019
1020 for (pk=pendingkicklist; pk; pk = pk->next) {
1021 if (pk->chan == cp) {
1022 Error("localuserchannel", ERR_INFO, "Pending kick channel %s got deleted, NULL'ing channel for pending kick", cp->index->name->content);
1023 pk->chan = NULL;
1024 }
1025 }
1026}
1027
c86edd1d
Q
1028void sendmessagetochannel(nick *source, channel *cp, char *format, ... ) {
1029 char buf[BUFSIZE];
1030 char senderstr[6];
1031 va_list va;
1032
1033 if (!source)
1034 return;
1035
1036 longtonumeric2(source->numeric,5,senderstr);
1037
1038 va_start(va,format);
1039 /* 10 bytes of numeric, 5 bytes of fixed format + terminator = 17 bytes */
1040 /* So max sendable message is 495 bytes. Of course, a client won't be able
1041 * to receive this.. */
1042
1043 vsnprintf(buf,BUFSIZE-17,format,va);
1044 va_end(va);
1045
1046 if (connected) {
1047 irc_send("%s P %s :%s",senderstr,cp->index->name->content,buf);
1048 }
1049}
1050
9408ae5d 1051void sendopnoticetochannel(nick *source, channel *cp, char *format, ... ) {
1052 char buf[BUFSIZE];
1053 char senderstr[6];
1054 va_list va;
1055
1056 if (!source)
1057 return;
1058
1059 longtonumeric2(source->numeric,5,senderstr);
1060
1061 va_start(va,format);
1062 /* 10 bytes of numeric, 5 bytes of fixed format + terminator = 17 bytes */
1063 /* So max sendable message is 495 bytes. Of course, a client won't be able
1064 * to receive this.. */
1065
1066 vsnprintf(buf,BUFSIZE-17,format,va);
1067 va_end(va);
1068
1069 if (connected) {
1070 irc_send("%s WC %s :%s",senderstr,cp->index->name->content,buf);
1071 }
1072}
1073
620cc49d 1074void localinvite(nick *source, chanindex *cip, nick *target) {
c86edd1d
Q
1075
1076 /* Servers can't send invites */
1077 if (!source)
1078 return;
1079
1080 /* CHECK: Does the sender have to be on the relevant channel? */
1081
1082 /* For some reason invites are sent with the target nick as
1083 * argument */
1084 if (connected) {
1085 irc_send("%s I %s :%s",longtonumeric(source->numeric,5),
620cc49d 1086 target->nick, cip->name->content);
c86edd1d
Q
1087 }
1088}
1089