unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
timedban.c (14397B)
1 /* 2 * timedban - Timed bans that are automatically unset. 3 * (C) Copyright 2009-2017 Bram Matthys (Syzop) and the UnrealIRCd team. 4 * License: GPLv2 or later 5 * 6 * This module adds an extended ban ~t:time:mask 7 * Where 'time' is the time in minutes after which the ban will be removed. 8 * Where 'mask' is any banmask that is normally valid. 9 * 10 * Note that this extended ban is rather special in the sense that 11 * it permits (crazy) triple-extbans to be set, such as: 12 * +b ~t:1:~q:~a:Account 13 * (=a temporary 1min ban to mute a user with services account Account) 14 * +e ~t:1440:~m:moderated:*!*@host 15 * (=user with *!*@host may speak through +m for the next 1440m / 24h) 16 * 17 * The triple-extbans / double-stacking requires special routines that 18 * are based on parts of the core and special recursion checks. 19 * If you are looking for inspiration of coding your own extended ban 20 * then look at another extended ban * module as this module is not a 21 * good starting point ;) 22 */ 23 24 #include "unrealircd.h" 25 26 /* Maximum time (in minutes) for a ban */ 27 #define TIMEDBAN_MAX_TIME 9999 28 29 /* Maximum length of a ban */ 30 #define MAX_LENGTH 128 31 32 /* Split timeout event in <this> amount of iterations */ 33 #define TIMEDBAN_TIMER_ITERATION_SPLIT 4 34 35 /* Call timeout event every <this> seconds. 36 * NOTE: until all channels are processed it takes 37 * TIMEDBAN_TIMER_ITERATION_SPLIT * TIMEDBAN_TIMER. 38 */ 39 #define TIMEDBAN_TIMER 2 40 41 /* We allow a ban to (potentially) expire slightly before the deadline. 42 * For example with TIMEDBAN_TIMER_ITERATION_SPLIT=4 and TIMEDBAN_TIMER=2 43 * a 1 minute ban would expire at 56-63 seconds, rather than 60-67 seconds. 44 * This is usually preferred. 45 */ 46 #define TIMEDBAN_TIMER_DELTA ((TIMEDBAN_TIMER_ITERATION_SPLIT*TIMEDBAN_TIMER)/2) 47 48 ModuleHeader MOD_HEADER 49 = { 50 "extbans/timedban", 51 "1.0", 52 "ExtBan ~t: automatically removed timed bans", 53 "UnrealIRCd Team", 54 "unrealircd-6", 55 }; 56 57 /* Forward declarations */ 58 const char *timedban_extban_conv_param(BanContext *b, Extban *extban); 59 int timedban_extban_is_ok(BanContext *b); 60 int timedban_is_banned(BanContext *b); 61 void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param); 62 char *timedban_chanmsg(Client *, Client *, Channel *, char *, int); 63 64 EVENT(timedban_timeout); 65 66 MOD_TEST() 67 { 68 return MOD_SUCCESS; 69 } 70 71 MOD_INIT() 72 { 73 ExtbanInfo extban; 74 75 MARK_AS_OFFICIAL_MODULE(modinfo); 76 77 memset(&extban, 0, sizeof(ExtbanInfo)); 78 extban.letter = 't'; 79 extban.name = "time"; 80 extban.options |= EXTBOPT_ACTMODIFIER; /* not really, but ours shouldn't be stacked from group 1 */ 81 extban.options |= EXTBOPT_INVEX; /* also permit timed invite-only exceptions (+I) */ 82 extban.conv_param = timedban_extban_conv_param; 83 extban.is_ok = timedban_extban_is_ok; 84 extban.is_banned = timedban_is_banned; 85 extban.is_banned_events = BANCHK_ALL; 86 87 if (!ExtbanAdd(modinfo->handle, extban)) 88 { 89 config_error("timedban: unable to register 't' extban type!!"); 90 return MOD_FAILED; 91 } 92 93 EventAdd(modinfo->handle, "timedban_timeout", timedban_timeout, NULL, TIMEDBAN_TIMER*1000, 0); 94 95 return MOD_SUCCESS; 96 } 97 98 MOD_LOAD() 99 { 100 return MOD_SUCCESS; 101 } 102 103 MOD_UNLOAD() 104 { 105 return MOD_SUCCESS; 106 } 107 108 /** Generic helper for our conv_param extban function. 109 * Mostly copied from clean_ban_mask() 110 * FIXME: Figure out why we have this one at all and not use conv_param? ;) 111 */ 112 const char *generic_clean_ban_mask(BanContext *b, Extban *extban) 113 { 114 char *cp, *x; 115 static char maskbuf[512]; 116 char *mask; 117 118 /* Work on a copy */ 119 strlcpy(maskbuf, b->banstr, sizeof(maskbuf)); 120 mask = maskbuf; 121 122 cp = strchr(mask, ' '); 123 if (cp) 124 *cp = '\0'; 125 126 /* Strip any ':' at beginning since that would cause a desync */ 127 for (; (*mask && (*mask == ':')); mask++); 128 if (!*mask) 129 return NULL; 130 131 /* Forbid ASCII <= 32 in all bans */ 132 for (x = mask; *x; x++) 133 if (*x <= ' ') 134 return NULL; 135 136 /* Extended ban? */ 137 if (is_extended_ban(mask)) 138 { 139 const char *nextbanstr; 140 Extban *extban = findmod_by_bantype(mask, &nextbanstr); 141 if (!extban) 142 return NULL; /* reject unknown extban */ 143 if (extban->conv_param) 144 { 145 const char *ret; 146 static char retbuf[512]; 147 BanContext *newb = safe_alloc(sizeof(BanContext)); 148 newb->banstr = nextbanstr; 149 newb->conv_options = b->conv_options; 150 ret = extban->conv_param(newb, extban); 151 ret = prefix_with_extban(ret, newb, extban, retbuf, sizeof(retbuf)); 152 safe_free(newb); 153 return ret; 154 } 155 /* else, do some basic sanity checks and cut it off at 80 bytes */ 156 if ((mask[1] != ':') || (mask[2] == '\0')) 157 return NULL; /* require a ":<char>" after extban type */ 158 if (strlen(mask) > 80) 159 mask[80] = '\0'; 160 return mask; 161 } 162 163 return convert_regular_ban(mask, NULL, 0); 164 } 165 166 /** Convert ban to an acceptable format (or return NULL to fully reject it) */ 167 const char *timedban_extban_conv_param(BanContext *b, Extban *extban) 168 { 169 static char retbuf[MAX_LENGTH+1]; 170 char para[MAX_LENGTH+1]; 171 char tmpmask[MAX_LENGTH+1]; 172 char *durationstr; /**< Duration, such as '5' */ 173 int duration; 174 char *matchby; /**< Matching method, such as 'n!u@h' */ 175 const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */ 176 static int timedban_extban_conv_param_recursion = 0; 177 178 if (timedban_extban_conv_param_recursion) 179 return NULL; /* reject: recursion detected! */ 180 181 strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */ 182 183 /* ~t:duration:n!u@h for direct matching 184 * ~t:duration:~x:.... when calling another bantype 185 */ 186 187 durationstr = para; 188 matchby = strchr(para, ':'); 189 if (!matchby || !matchby[1]) 190 return NULL; 191 *matchby++ = '\0'; 192 193 duration = atoi(durationstr); 194 195 if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME)) 196 return NULL; 197 198 strlcpy(tmpmask, matchby, sizeof(tmpmask)); 199 timedban_extban_conv_param_recursion++; 200 //newmask = extban_conv_param_nuh_or_extban(tmpmask); 201 b->banstr = matchby; // this was previously 'tmpmask' but then it's a copy-copy-copy.. :D 202 newmask = generic_clean_ban_mask(b, extban); 203 timedban_extban_conv_param_recursion--; 204 if (!newmask || (strlen(newmask) <= 1)) 205 return NULL; 206 207 //snprintf(retbuf, sizeof(retbuf), "~t:%d:%s", duration, newmask); 208 snprintf(retbuf, sizeof(retbuf), "%d:%s", duration, newmask); 209 return retbuf; 210 } 211 212 int timedban_extban_syntax(Client *client, int checkt, char *reason) 213 { 214 if (MyUser(client) && (checkt == EXBCHK_PARAM)) 215 { 216 sendnotice(client, "Error when setting timed ban: %s", reason); 217 sendnotice(client, " Syntax: +b ~t:duration:mask"); 218 sendnotice(client, "Example: +b ~t:5:nick!user@host"); 219 sendnotice(client, "Duration is the time in minutes after which the ban is removed (1-9999)"); 220 sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, .."); 221 } 222 return 0; /* FAIL: ban rejected */ 223 } 224 225 /** Generic helper for sub-bans, used by our "is this ban ok?" function */ 226 int generic_ban_is_ok(BanContext *b) 227 { 228 if ((b->banstr[0] == '~') && MyUser(b->client)) 229 { 230 Extban *extban; 231 const char *nextbanstr; 232 233 /* This portion is copied from clean_ban_mask() */ 234 if (is_extended_ban(b->banstr) && MyUser(b->client)) 235 { 236 if (RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",b->client,NULL,NULL,NULL)) 237 { 238 if (!strcmp(RESTRICT_EXTENDEDBANS, "*")) 239 { 240 if (b->is_ok_check == EXBCHK_ACCESS_ERR) 241 sendnotice(b->client, "Setting/removing of extended bans has been disabled"); 242 return 0; /* REJECT */ 243 } 244 if (strchr(RESTRICT_EXTENDEDBANS, b->banstr[1])) 245 { 246 if (b->is_ok_check == EXBCHK_ACCESS_ERR) 247 sendnotice(b->client, "Setting/removing of extended bantypes '%s' has been disabled", RESTRICT_EXTENDEDBANS); 248 return 0; /* REJECT */ 249 } 250 } 251 /* And next is inspired by cmd_mode */ 252 extban = findmod_by_bantype(b->banstr, &nextbanstr); 253 if (extban && extban->is_ok) 254 { 255 b->banstr = nextbanstr; 256 if ((b->is_ok_check == EXBCHK_ACCESS) || (b->is_ok_check == EXBCHK_ACCESS_ERR)) 257 { 258 if (!extban->is_ok(b) && 259 !ValidatePermissionsForPath("channel:override:mode:extban",b->client,NULL,b->channel,NULL)) 260 { 261 return 0; /* REJECT */ 262 } 263 } else 264 if (b->is_ok_check == EXBCHK_PARAM) 265 { 266 if (!extban->is_ok(b)) 267 { 268 return 0; /* REJECT */ 269 } 270 } 271 } 272 } 273 } 274 275 /* ACCEPT: 276 * - not an extban; OR 277 * - extban with NULL is_ok; OR 278 * - non-existing extban character (handled by conv_param?) 279 */ 280 return 1; 281 } 282 283 /** Validate ban ("is this ban ok?") */ 284 int timedban_extban_is_ok(BanContext *b) 285 { 286 char para[MAX_LENGTH+1]; 287 char tmpmask[MAX_LENGTH+1]; 288 char *durationstr; /**< Duration, such as '5' */ 289 int duration; 290 char *matchby; /**< Matching method, such as 'n!u@h' */ 291 char *newmask; /**< Cleaned matching method, such as 'n!u@h' */ 292 static int timedban_extban_is_ok_recursion = 0; 293 int res; 294 295 /* Always permit deletion */ 296 if (b->what == MODE_DEL) 297 return 1; 298 299 if (timedban_extban_is_ok_recursion) 300 return 0; /* Recursion detected (~t:1:~t:....) */ 301 302 strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */ 303 304 /* ~t:duration:n!u@h for direct matching 305 * ~t:duration:~x:.... when calling another bantype 306 */ 307 308 durationstr = para; 309 matchby = strchr(para, ':'); 310 if (!matchby || !matchby[1]) 311 return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid syntax"); 312 *matchby++ = '\0'; 313 314 duration = atoi(durationstr); 315 316 if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME)) 317 return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid duration time"); 318 319 strlcpy(tmpmask, matchby, sizeof(tmpmask)); 320 timedban_extban_is_ok_recursion++; 321 //res = extban_is_ok_nuh_extban(b->client, b->channel, tmpmask, b->is_ok_check, b->what, b->ban_type); 322 b->banstr = tmpmask; 323 res = generic_ban_is_ok(b); 324 timedban_extban_is_ok_recursion--; 325 if (res == 0) 326 { 327 /* This could be anything ranging from: 328 * invalid n!u@h syntax, unknown (sub)extbantype, 329 * disabled extban type in conf, too much recursion, etc. 330 */ 331 return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid matcher"); 332 } 333 334 return 1; /* OK */ 335 } 336 337 /** Check if the user is currently banned */ 338 int timedban_is_banned(BanContext *b) 339 { 340 b->banstr = strchr(b->banstr, ':'); /* skip time argument */ 341 if (!b->banstr) 342 return 0; /* invalid fmt */ 343 b->banstr++; /* skip over final semicolon */ 344 345 return ban_check_mask(b); 346 } 347 348 /** Helper to check if the ban has been expired. 349 */ 350 int timedban_has_ban_expired(Ban *ban) 351 { 352 char *banstr = ban->banstr; 353 char *p1, *p2; 354 int t; 355 time_t expire_on; 356 357 /* The caller has only performed a very light check (string starting 358 * with ~t, in the interest of performance), so we don't know yet if 359 * it REALLY is a timed ban. We check that first here... 360 */ 361 if (!strncmp(banstr, "~t:", 3)) 362 p1 = banstr + 3; 363 else if (!strncmp(banstr, "~time:", 6)) 364 p1 = banstr + 6; 365 else 366 return 0; /* not for us */ 367 p2 = strchr(p1+1, ':'); /* skip time argument */ 368 if (!p2) 369 return 0; /* invalid fmt */ 370 *p2 = '\0'; /* danger.. must restore!! */ 371 t = atoi(p1); 372 *p2 = ':'; /* restored.. */ 373 374 expire_on = ban->when + (t * 60) - TIMEDBAN_TIMER_DELTA; 375 376 if (expire_on < TStime()) 377 return 1; 378 return 0; 379 } 380 381 static char mbuf[512]; 382 static char pbuf[512]; 383 384 /** This removes any expired timedbans */ 385 EVENT(timedban_timeout) 386 { 387 Channel *channel; 388 Ban *ban, *nextban; 389 static int current_iteration = 0; 390 391 if (++current_iteration >= TIMEDBAN_TIMER_ITERATION_SPLIT) 392 current_iteration = 0; 393 394 for (channel = channels; channel; channel = channel->nextch) 395 { 396 /* This is a very quick check, at the cost of it being 397 * biased since there's always a tendency of more channel 398 * names to start with one specific letter. But hashing 399 * is too costly. So we stick with this. It should be 400 * good enough. Alternative would be some channel->id value. 401 */ 402 if (((unsigned int)channel->name[1] % TIMEDBAN_TIMER_ITERATION_SPLIT) != current_iteration) 403 continue; /* not this time, maybe next */ 404 405 *mbuf = *pbuf = '\0'; 406 for (ban = channel->banlist; ban; ban=nextban) 407 { 408 nextban = ban->next; 409 if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban)) 410 { 411 add_send_mode_param(channel, &me, '-', 'b', ban->banstr); 412 del_listmode(&channel->banlist, channel, ban->banstr); 413 } 414 } 415 for (ban = channel->exlist; ban; ban=nextban) 416 { 417 nextban = ban->next; 418 if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban)) 419 { 420 add_send_mode_param(channel, &me, '-', 'e', ban->banstr); 421 del_listmode(&channel->exlist, channel, ban->banstr); 422 } 423 } 424 for (ban = channel->invexlist; ban; ban=nextban) 425 { 426 nextban = ban->next; 427 if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban)) 428 { 429 add_send_mode_param(channel, &me, '-', 'I', ban->banstr); 430 del_listmode(&channel->invexlist, channel, ban->banstr); 431 } 432 } 433 if (*pbuf) 434 { 435 MessageTag *mtags = NULL; 436 new_message(&me, NULL, &mtags); 437 sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf); 438 sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf); 439 free_message_tags(mtags); 440 *pbuf = 0; 441 } 442 } 443 } 444 445 #if MODEBUFLEN > 512 446 #error "add_send_mode_param() is not made for MODEBUFLEN > 512" 447 #endif 448 449 void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param) { 450 static char *modes = NULL, lastwhat; 451 static short count = 0; 452 short send = 0; 453 454 if (!modes) modes = mbuf; 455 456 if (!mbuf[0]) { 457 modes = mbuf; 458 *modes++ = what; 459 *modes = 0; 460 lastwhat = what; 461 *pbuf = 0; 462 count = 0; 463 } 464 if (lastwhat != what) { 465 *modes++ = what; 466 *modes = 0; 467 lastwhat = what; 468 } 469 if (strlen(pbuf) + strlen(param) + 11 < MODEBUFLEN) { 470 if (*pbuf) 471 strcat(pbuf, " "); 472 strcat(pbuf, param); 473 *modes++ = mode; 474 *modes = 0; 475 count++; 476 } 477 else if (*pbuf) 478 send = 1; 479 480 if (count == MAXMODEPARAMS) 481 send = 1; 482 483 if (send) 484 { 485 MessageTag *mtags = NULL; 486 487 new_message(&me, NULL, &mtags); 488 sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf); 489 sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf); 490 free_message_tags(mtags); 491 send = 0; 492 *pbuf = 0; 493 modes = mbuf; 494 *modes++ = what; 495 lastwhat = what; 496 if (count != MAXMODEPARAMS) 497 { 498 strlcpy(pbuf, param, sizeof(pbuf)); 499 *modes++ = mode; 500 count = 1; 501 } else { 502 count = 0; 503 } 504 *modes = 0; 505 } 506 }