]> jfr.im git - irc/quakenet/newserv.git/blame - channel/channelbans.c
Added module support - UNTESTED, but I've got to commit before the Id tags update.
[irc/quakenet/newserv.git] / channel / channelbans.c
CommitLineData
c86edd1d
Q
1/* channelbans.c */
2
3#include <assert.h>
4#include <string.h>
5#include <stdio.h>
6
7#include "channel.h"
8#include "../nick/nick.h"
9#include "../lib/irc_string.h"
10#include "../irc/irc_config.h"
11#include "../irc/irc.h"
12
13/*
14 * makeban:
15 * Converts the specified ban into a ban structure
16 */
17
18chanban *makeban(const char *mask) {
19 int len;
20 int foundat=-1,foundbang=-1;
21 int dotcount=0;
22 int notip=0;
23 int foundwild=0;
24 int foundslash=0;
25 int i;
26 int checklen;
27 chanban *cbp;
28
29 cbp=getchanban();
30 len=strlen(mask);
31
32 cbp->flags=0;
33
34 /* Let's catch a silly case first */
35 if (!strcmp(mask,"*")) {
36 cbp->flags=CHANBAN_HOSTANY | CHANBAN_USERANY | CHANBAN_NICKANY;
37 cbp->nick=NULL;
38 cbp->user=NULL;
39 cbp->host=NULL;
40 cbp->timeset=time(NULL);
41 return cbp;
42 }
43
44 for (i=(len-1);i>=0;i--) {
45 if (mask[i]=='@') {
46 /* Got @ sign: everything after here is host */
47 if ((len-i)-1 > HOSTLEN) {
48 /* This is too long, we need to truncate it */
49 cbp->host=getsstring(&mask[len-HOSTLEN],HOSTLEN);
50 cbp->host->content[0]='*';
51 cbp->flags |= CHANBAN_HOSTMASK;
52 } else if (i==(len-1)) {
53 /* Ban ending with @, just mark it invalid */
54 /* Note that "moo@*" overrides "moo@", so mark it as having a host too */
55 cbp->flags |= (CHANBAN_INVALID | CHANBAN_HOSTNULL);
56 cbp->host=NULL;
57 } else if (i==(len-2) && mask[i+1]=='*') {
58 /* Special case: "@*" */
59 cbp->flags |= CHANBAN_HOSTANY;
60 cbp->host=NULL;
61 } else if (foundslash) {
62 /* If we found a slash (/), this can only be a CIDR ban */
63 /* However, it might be broken, so we need to retain the exact string
64 * to track it accurately */
65 cbp->host=getsstring(&mask[i+1],HOSTLEN);
66 if ((notip || dotcount!=3) && !foundwild) {
67 cbp->flags |= (CHANBAN_INVALID | CHANBAN_HOSTEXACT);
68 }
69 if (foundwild) {
70 cbp->flags |= (CHANBAN_INVALID | CHANBAN_HOSTMASK);
71 }
72 cbp->flags |= (CHANBAN_HOSTEXACT | CHANBAN_CIDR);
73 } else {
74 /* We have some string with between 1 and HOSTLEN characters.. */
75 cbp->host=getsstring(&mask[i+1],HOSTLEN);
76 if (foundwild) {
77 /* We check all characters after the last wildcard (if any).. if they match
78 * the corresponding bits of the hidden host string we mark it accordingly */
79 if (!(checklen=len-foundwild-1)) { /* Ban ends in *, mark it anyway.. */
80 cbp->flags |= CHANBAN_HIDDENHOST;
81 } else {
82 if (checklen>=strlen(HIS_HIDDENHOST)) {
83 if (!ircd_strcmp(cbp->host->content+(cbp->host->length-strlen(HIS_HIDDENHOST)), HIS_HIDDENHOST))
84 cbp->flags |= CHANBAN_HIDDENHOST;
85 } else {
86 if (!ircd_strcmp(cbp->host->content+(cbp->host->length-checklen),
87 (HIS_HIDDENHOST)+strlen(HIS_HIDDENHOST)-checklen))
88 cbp->flags |= CHANBAN_HIDDENHOST;
89 }
90 }
91 cbp->flags |= CHANBAN_HOSTMASK;
92 if (!notip && dotcount<=3)
93 cbp->flags |= CHANBAN_IP;
94 } else {
95 /* Exact host: see if it ends with the "hidden host" string */
96 cbp->flags |= CHANBAN_HOSTEXACT;
97 if ((cbp->host->length > (strlen(HIS_HIDDENHOST)+1)) &&
98 !ircd_strcmp(cbp->host->content+(cbp->host->length-strlen(HIS_HIDDENHOST)), HIS_HIDDENHOST))
99 cbp->flags |= CHANBAN_HIDDENHOST;
100 else if (!notip && dotcount==3)
101 cbp->flags |= CHANBAN_IP;
102 }
103 }
104 foundat=i;
105 break;
106 } else if (mask[i]=='/') {
107 foundslash=1;
108 } else if (mask[i]=='.') {
109 dotcount++;
110 } else if (mask[i]=='?' || mask[i]=='*') {
111 if (!foundwild) /* Mark last wildcard in string */
112 foundwild=i;
113 } else if (mask[i]<'0' || mask[i]>'9') {
114 notip=1;
115 }
116 }
117
118 if (foundat<0) {
119 /* If there wasn't an @, this ban matches any host */
120 cbp->host=NULL;
121 cbp->flags |= CHANBAN_HOSTANY;
122 }
123
124 foundwild=0;
125
126 for (i=0;i<foundat;i++) {
127 if (mask[i]=='!') {
128 if (i==0) {
129 /* Invalid mask: nick is empty */
130 cbp->flags |= CHANBAN_NICKNULL;
131 cbp->nick=NULL;
132 } else if (i==1 && mask[0]=='*') {
133 /* matches any nick */
134 cbp->flags |= CHANBAN_NICKANY;
135 cbp->nick=NULL;
136 } else {
137 if (i>NICKLEN) {
138 /* too long: just take the first NICKLEN chars */
139 cbp->nick=getsstring(mask,NICKLEN);
140 } else {
141 cbp->nick=getsstring(mask,i);
142 }
143 if (foundwild)
144 cbp->flags |= CHANBAN_NICKMASK;
145 else
146 cbp->flags |= CHANBAN_NICKEXACT;
147 }
148 foundbang=i;
149 break;
150 } else if (mask[i]=='?' || mask[i]=='*') {
151 if (i<NICKLEN) {
152 foundwild=1;
153 }
154 }
155 }
156
157 if (foundbang<0) {
158 /* We didn't find a !, what we do now depends on what happened
159 * with the @ */
160 if (foundat<0) {
161 /* A ban with no ! or @ is treated as a nick ban only */
162 /* Note that we've special-cased "*" at the top, so we can only
163 * hit the MASK or EXACT case here. */
164 if (len>NICKLEN)
165 cbp->nick=getsstring(mask,NICKLEN);
166 else
167 cbp->nick=getsstring(mask,len);
168
169 if (foundwild)
170 cbp->flags |= CHANBAN_NICKMASK;
171 else
172 cbp->flags |= CHANBAN_NICKEXACT;
173
174 cbp->flags |= (CHANBAN_USERANY | CHANBAN_HOSTANY);
175 cbp->host=NULL;
176 cbp->user=NULL;
177 } else {
178 /* A ban with @ only is treated as user@host */
179 cbp->nick=NULL;
180 cbp->flags |= CHANBAN_NICKANY;
181 }
182 }
183
184 if (foundat>=0) {
185 /* We found an @, so everything between foundbang+1 and foundat-1 is supposed to be ident */
186 /* This is true even if there was no !.. */
187 if (foundat==(foundbang+1)) {
188 /* empty ident matches nothing */
189 cbp->flags |= (CHANBAN_INVALID | CHANBAN_USERNULL);
190 cbp->user=NULL;
191 } else if (foundat - foundbang - 1 > USERLEN) {
192 /* It's too long.. */
193 cbp->user=getsstring(&mask[foundat-USERLEN],USERLEN);
194 cbp->user->content[0]='*';
195 cbp->flags |= CHANBAN_USERMASK;
196 } else if ((foundat - foundbang - 1 == 1) && mask[foundbang+1]=='*') {
197 cbp->user=NULL;
198 cbp->flags |= CHANBAN_USERANY;
199 } else {
200 cbp->user=getsstring(&mask[foundbang+1],(foundat-foundbang-1));
201 if (strchr(cbp->user->content,'*') || strchr(cbp->user->content,'?'))
202 cbp->flags |= CHANBAN_USERMASK;
203 else
204 cbp->flags |= CHANBAN_USEREXACT;
205 }
206 }
207
208 assert(cbp->flags & (CHANBAN_USEREXACT | CHANBAN_USERMASK | CHANBAN_USERANY | CHANBAN_USERNULL));
209 assert(cbp->flags & (CHANBAN_NICKEXACT | CHANBAN_NICKMASK | CHANBAN_NICKANY | CHANBAN_NICKNULL));
210 assert(cbp->flags & (CHANBAN_HOSTEXACT | CHANBAN_HOSTMASK | CHANBAN_HOSTANY | CHANBAN_HOSTNULL));
211
212 cbp->timeset=time(NULL);
213
214 return cbp;
215}
216
217/* banoverlap:
218 * Returns nonzero iff bana is a SUPERSET of banb
219 */
220
221int banoverlap(const chanban *bana, const chanban *banb) {
222 /* This function works by looking for cases where bana DOESN'T overlap banb */
223
224 /* NICK section */
225 /* If bana has CHANBAN_NICKANY then it clearly overlaps
226 * in the nick section so there are no checks */
227
228 if ((bana->flags & CHANBAN_NICKNULL) && !(banb->flags & CHANBAN_NICKNULL)) {
229 return 0;
230 }
231
232 if (bana->flags & CHANBAN_NICKMASK) {
233 if (banb->flags & (CHANBAN_NICKANY | CHANBAN_NICKNULL)) {
234 return 0;
235 }
236 if ((banb->flags & CHANBAN_NICKMASK) && !match2patterns(bana->nick->content, banb->nick->content)) {
237 return 0;
238 }
239 if ((banb->flags & CHANBAN_NICKEXACT) && !match2strings(bana->nick->content, banb->nick->content)) {
240 return 0;
241 }
242 }
243
244 if (bana->flags & CHANBAN_NICKEXACT) {
245 if (banb->flags & (CHANBAN_NICKANY | CHANBAN_NICKMASK | CHANBAN_NICKNULL)) {
246 return 0;
247 }
248 if ((!bana->nick && banb->nick) || (bana->nick && !banb->nick)) {
249 return 0;
250 }
251 if (bana->nick && ircd_strcmp(bana->nick->content,banb->nick->content)) {
252 return 0;
253 }
254 }
255
256 /* USER section */
257 /* If bana has CHANBAN_USERANY then it clearly overlaps
258 * in the user section so there are no checks */
259
260 if ((bana->flags & CHANBAN_USERNULL) && !(banb->flags & CHANBAN_USERNULL)) {
261 return 0;
262 }
263
264 if (bana->flags & CHANBAN_USERMASK) {
265 if (banb->flags & (CHANBAN_USERANY | CHANBAN_USERNULL)) {
266 return 0;
267 }
268 if ((banb->flags & CHANBAN_USERMASK) && !match2patterns(bana->user->content, banb->user->content)) {
269 return 0;
270 }
271 if ((banb->flags & CHANBAN_USEREXACT) && !match2strings(bana->user->content, banb->user->content)) {
272 return 0;
273 }
274 }
275
276 if (bana->flags & CHANBAN_USEREXACT) {
277 if (banb->flags & (CHANBAN_USERANY | CHANBAN_USERMASK | CHANBAN_USERNULL)) {
278 return 0;
279 }
280 if ((!bana->user && banb->user) || (bana->user && !banb->user)) {
281 return 0;
282 }
283 if (bana->user && ircd_strcmp(bana->user->content,banb->user->content)) {
284 return 0;
285 }
286 }
287
288 /* HOST section */
289 /* If bana has CHANBAN_HOSTANY then it clearly overlaps
290 * in the host section so there are no checks */
291
292 if ((bana->flags & CHANBAN_HOSTNULL) && !(banb->flags & CHANBAN_HOSTNULL)) {
293 return 0;
294 }
295
296 if (bana->flags & CHANBAN_HOSTMASK) {
297 if (banb->flags & (CHANBAN_HOSTANY | CHANBAN_HOSTNULL)) {
298 return 0;
299 }
300 if ((banb->flags & CHANBAN_HOSTMASK) && !match2patterns(bana->host->content, banb->host->content)) {
301 return 0;
302 }
303 if ((banb->flags & CHANBAN_HOSTEXACT) && !match2strings(bana->host->content, banb->host->content)) {
304 return 0;
305 }
306 }
307
308 if (bana->flags & CHANBAN_HOSTEXACT) {
309 if (banb->flags & (CHANBAN_HOSTANY | CHANBAN_HOSTMASK | CHANBAN_HOSTNULL)) {
310 return 0;
311 }
312 if ((!bana->host && banb->host) || (bana->host && !banb->host)) {
313 return 0;
314 }
315 if (bana->host && ircd_strcmp(bana->host->content,banb->host->content)) {
316 return 0;
317 }
318 }
319
320 return 1;
321}
322
323/*
324 * banequal:
325 * Returns nonzero iff the bans are EXACTLY the same
326 */
327
328int banequal(chanban *bana, chanban *banb) {
329 if (bana->flags != banb->flags)
330 return 0;
331
332 if ((!bana->nick && banb->nick) || (bana->nick && !banb->nick))
333 return 0;
334
335 if (bana->nick && ircd_strcmp(bana->nick->content,banb->nick->content))
336 return 0;
337
338 if ((!bana->user && banb->user) || (bana->user && !banb->user))
339 return 0;
340
341 if (bana->user && ircd_strcmp(bana->user->content,banb->user->content))
342 return 0;
343
344 if ((!bana->host && banb->host) || (bana->host && !banb->host))
345 return 0;
346
347 if (bana->host && ircd_strcmp(bana->host->content,banb->host->content))
348 return 0;
349
350 return 1;
351}
352
353/*
354 * bantostring:
355 * Convert the specified ban to a string
356 */
357
358char *bantostring(chanban *bp) {
359 static char outstring[NICKLEN+USERLEN+HOSTLEN+5];
360 int strpos=0;
361
362 if (bp->flags & CHANBAN_NICKANY) {
363 strpos += sprintf(outstring+strpos,"*");
364 } else if (bp->nick) {
365 strpos += sprintf(outstring+strpos,"%s",bp->nick->content);
366 }
367
368 strpos += sprintf(outstring+strpos,"!");
369
370 if (bp->flags & CHANBAN_USERANY) {
371 strpos += sprintf(outstring+strpos,"*");
372 } else if (bp->user) {
373 strpos += sprintf(outstring+strpos,"%s",bp->user->content);
374 }
375
376 strpos += sprintf(outstring+strpos,"@");
377
378 if (bp->flags & CHANBAN_HOSTANY) {
379 strpos += sprintf(outstring+strpos,"*");
380 } else if (bp->host) {
381 strpos += sprintf(outstring+strpos,"%s",bp->host->content);
382 }
383
384 return outstring;
385}
386
387/*
388 * bantostringdebug:
389 * Convert the specified ban to a string (debugging version)
390 */
391
392char *bantostringdebug(chanban *bp) {
393 static char outstring[NICKLEN+USERLEN+HOSTLEN+5];
394 int strpos=0;
395
396 strpos += sprintf(outstring+strpos, "flags=%04x ",bp->flags);
397
398 if (bp->nick) {
399 strpos += sprintf(outstring+strpos, "nick=%s ",bp->nick->content);
400 } else {
401 strpos += sprintf(outstring+strpos, "nonick ");
402 }
403
404 if (bp->user) {
405 strpos += sprintf(outstring+strpos, "user=%s ",bp->user->content);
406 } else {
407 strpos += sprintf(outstring+strpos, "nouser ");
408 }
409
410 if (bp->host) {
411 strpos += sprintf(outstring+strpos, "host=%s ",bp->host->content);
412 } else {
413 strpos += sprintf(outstring+strpos, "nohost ");
414 }
415
416
417 return outstring;
418}
419
420/*
421 * nickmatchban:
422 * Returns true iff the supplied nick* matches the supplied ban*
423 */
424
425int nickmatchban(nick *np, chanban *bp) {
426 const char *ipstring;
427 char fakehost[HOSTLEN+1];
428
429 /* nick/ident section: return 0 (no match) if they don't match */
430
431 if (bp->flags & CHANBAN_INVALID)
432 return 0;
433
434 if (bp->flags & CHANBAN_USEREXACT &&
435 ircd_strcmp(np->ident,bp->user->content) &&
436 (!IsSetHost(np) || !np->shident ||
437 ircd_strcmp(np->shident->content,bp->user->content)))
438 return 0;
439
440 if (bp->flags & CHANBAN_NICKEXACT && ircd_strcmp(np->nick,bp->nick->content))
441 return 0;
442
443 if (bp->flags & CHANBAN_USERMASK &&
444 !match2strings(bp->user->content,np->ident) &&
445 (!IsSetHost(np) || !np->shident ||
446 !match2strings(bp->user->content, np->shident->content)))
447 return 0;
448
449 if (bp->flags & CHANBAN_NICKMASK && !match2strings(bp->nick->content,np->nick))
450 return 0;
451
452 /* host section. Return 1 (match) if they do match
453 * Note that if user or ident was specified, they've already been checked
454 */
455
456 if (bp->flags & CHANBAN_HOSTANY)
457 return 1;
458
459 if (bp->flags & CHANBAN_IP) {
460 /* IP bans are checked against IP address only */
461 ipstring=IPtostr(np->ipaddress);
462
463 if (bp->flags & CHANBAN_HOSTEXACT && !ircd_strcmp(ipstring,bp->host->content))
464 return 1;
465
466 if (bp->flags & CHANBAN_HOSTMASK && match2strings(bp->host->content,ipstring))
467 return 1;
468 } else {
469 /* Hostname bans need to be checked against +x host, +h host (if set)
470 * and actual host. Note that the +x host is only generated (and checked) if it's
471 * possible for the ban to match a hidden host.. */
472
473 if ((bp->flags & CHANBAN_HIDDENHOST) && IsAccount(np)) {
474 sprintf(fakehost,"%s.%s",np->authname, HIS_HIDDENHOST);
475
476 if ((bp->flags & CHANBAN_HOSTEXACT) &&
477 !ircd_strcmp(fakehost, bp->host->content))
478 return 1;
479
480 if ((bp->flags & CHANBAN_HOSTMASK) &&
481 match2strings(bp->host->content, fakehost))
482 return 1;
483 }
484
485 if (IsSetHost(np)) {
486 if ((bp->flags & CHANBAN_HOSTEXACT) &&
487 !ircd_strcmp(np->sethost->content, bp->host->content))
488 return 1;
489
490 if ((bp->flags & CHANBAN_HOSTMASK) &&
491 match2strings(bp->host->content, np->sethost->content))
492 return 1;
493 }
494
495 if (bp->flags & CHANBAN_HOSTEXACT && !ircd_strcmp(np->host->name->content,bp->host->content))
496 return 1;
497
498 if (bp->flags & CHANBAN_HOSTMASK && match2strings(bp->host->content,np->host->name->content))
499 return 1;
500 }
501
502 return 0;
503}
504
505/*
506 * nickbanned:
507 * Returns true iff the supplied nick* is banned on the supplied chan*
508 */
509
510int nickbanned(nick *np, channel *cp) {
511 chanban *cbp;
512
513 for (cbp=cp->bans;cbp;cbp=cbp->next) {
514 if (nickmatchban(np,cbp))
515 return 1;
516 }
517
518 return 0;
519}
520
521/*
522 * setban:
523 * Set the specified ban; if it completely encloses any existing bans
524 * then remove them.
525 */
526
527void setban(channel *cp, const char *ban) {
528 chanban **cbh,*cbp,*cbp2;
529
530 cbp=makeban(ban);
531
532 /* Remove enclosed bans first */
533 for (cbh=&(cp->bans);*cbh;) {
534 if (banoverlap(cbp,*cbh)) {
535 cbp2=(*cbh);
536 (*cbh)=cbp2->next;
537 freechanban(cbp2);
538 /* Break out of the loop if we just deleted the last ban */
539 if ((*cbh)==NULL) {
540 break;
541 }
542 } else {
543 cbh=(chanban **)&((*cbh)->next);
544 }
545 }
546
547 /* Now set the new ban */
548 cbp->next=(struct chanban *)cp->bans;
549 cp->bans=cbp;
550}
551
552/*
553 * clearban:
554 * Remove the specified ban iff an exact match is found
555 * Returns 1 if the ban was cleared, 0 if the ban didn't exist.
556 * If "optional" is 0 and the ban didn't exist, flags an error
557 */
558
559int clearban(channel *cp, const char *ban, int optional) {
560 chanban **cbh,*cbp,*cbp2;
561 int found=0;
562 int i=0;
563
564 if (!cp)
565 return 0;
566
567 cbp=makeban(ban);
568
569 for (cbh=&(cp->bans);*cbh;cbh=(chanban **)&((*cbh)->next)) {
570 if (banequal(cbp,*cbh)) {
571 cbp2=(*cbh);
572 (*cbh)=cbp2->next;
573 freechanban(cbp2);
574 found=1;
575 break;
576 }
577 }
578
579 if (!found && !optional) {
580 Error("channel",ERR_DEBUG,"Couldn't remove ban %s from %s. Dumping banlist:",ban,cp->index->name->content);
581 for (cbp2=cp->bans;cbp2;cbp2=cbp2->next) {
582 Error("channel",ERR_DEBUG,"%s %d %s",cp->index->name->content, i++, bantostringdebug(cbp2));
583 }
584 }
585
586 freechanban(cbp);
587
588 return found;
589}
590
591/*
592 * clearallbans:
593 * Just free all the bans on the channel
594 */
595
596void clearallbans(channel *cp) {
597 chanban *cbp,*ncbp;
598
599 for (cbp=cp->bans;cbp;cbp=ncbp) {
600 ncbp=(chanban *)cbp->next;
601 freechanban(cbp);
602 }
603
604 cp->bans=NULL;
605}