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