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