unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive |
timedban.c (14490B)
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->ban_type = b->ban_type; 150 newb->conv_options = b->conv_options; 151 newb->client = b->client; 152 newb->channel = b->channel; 153 ret = extban->conv_param(newb, extban); 154 ret = prefix_with_extban(ret, newb, extban, retbuf, sizeof(retbuf)); 155 safe_free(newb); 156 return ret; 157 } 158 /* else, do some basic sanity checks and cut it off at 80 bytes */ 159 if ((mask[1] != ':') || (mask[2] == '\0')) 160 return NULL; /* require a ":<char>" after extban type */ 161 if (strlen(mask) > 80) 162 mask[80] = '\0'; 163 return mask; 164 } 165 166 return convert_regular_ban(mask, NULL, 0); 167 } 168 169 /** Convert ban to an acceptable format (or return NULL to fully reject it) */ 170 const char *timedban_extban_conv_param(BanContext *b, Extban *extban) 171 { 172 static char retbuf[MAX_LENGTH+1]; 173 char para[MAX_LENGTH+1]; 174 char tmpmask[MAX_LENGTH+1]; 175 char *durationstr; /**< Duration, such as '5' */ 176 int duration; 177 char *matchby; /**< Matching method, such as 'n!u@h' */ 178 const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */ 179 static int timedban_extban_conv_param_recursion = 0; 180 181 if (timedban_extban_conv_param_recursion) 182 return NULL; /* reject: recursion detected! */ 183 184 strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */ 185 186 /* ~t:duration:n!u@h for direct matching 187 * ~t:duration:~x:.... when calling another bantype 188 */ 189 190 durationstr = para; 191 matchby = strchr(para, ':'); 192 if (!matchby || !matchby[1]) 193 return NULL; 194 *matchby++ = '\0'; 195 196 duration = atoi(durationstr); 197 198 if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME)) 199 return NULL; 200 201 strlcpy(tmpmask, matchby, sizeof(tmpmask)); 202 timedban_extban_conv_param_recursion++; 203 //newmask = extban_conv_param_nuh_or_extban(tmpmask); 204 b->banstr = matchby; // this was previously 'tmpmask' but then it's a copy-copy-copy.. :D 205 newmask = generic_clean_ban_mask(b, extban); 206 timedban_extban_conv_param_recursion--; 207 if (!newmask || (strlen(newmask) <= 1)) 208 return NULL; 209 210 //snprintf(retbuf, sizeof(retbuf), "~t:%d:%s", duration, newmask); 211 snprintf(retbuf, sizeof(retbuf), "%d:%s", duration, newmask); 212 return retbuf; 213 } 214 215 int timedban_extban_syntax(Client *client, int checkt, char *reason) 216 { 217 if (MyUser(client) && (checkt == EXBCHK_PARAM)) 218 { 219 sendnotice(client, "Error when setting timed ban: %s", reason); 220 sendnotice(client, " Syntax: +b ~t:duration:mask"); 221 sendnotice(client, "Example: +b ~t:5:nick!user@host"); 222 sendnotice(client, "Duration is the time in minutes after which the ban is removed (1-9999)"); 223 sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, .."); 224 } 225 return 0; /* FAIL: ban rejected */ 226 } 227 228 /** Generic helper for sub-bans, used by our "is this ban ok?" function */ 229 int generic_ban_is_ok(BanContext *b) 230 { 231 if ((b->banstr[0] == '~') && MyUser(b->client)) 232 { 233 Extban *extban; 234 const char *nextbanstr; 235 236 /* This portion is copied from clean_ban_mask() */ 237 if (is_extended_ban(b->banstr) && MyUser(b->client)) 238 { 239 if (RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",b->client,NULL,NULL,NULL)) 240 { 241 if (!strcmp(RESTRICT_EXTENDEDBANS, "*")) 242 { 243 if (b->is_ok_check == EXBCHK_ACCESS_ERR) 244 sendnotice(b->client, "Setting/removing of extended bans has been disabled"); 245 return 0; /* REJECT */ 246 } 247 if (strchr(RESTRICT_EXTENDEDBANS, b->banstr[1])) 248 { 249 if (b->is_ok_check == EXBCHK_ACCESS_ERR) 250 sendnotice(b->client, "Setting/removing of extended bantypes '%s' has been disabled", RESTRICT_EXTENDEDBANS); 251 return 0; /* REJECT */ 252 } 253 } 254 /* And next is inspired by cmd_mode */ 255 extban = findmod_by_bantype(b->banstr, &nextbanstr); 256 if (extban && extban->is_ok) 257 { 258 b->banstr = nextbanstr; 259 if ((b->is_ok_check == EXBCHK_ACCESS) || (b->is_ok_check == EXBCHK_ACCESS_ERR)) 260 { 261 if (!extban->is_ok(b) && 262 !ValidatePermissionsForPath("channel:override:mode:extban",b->client,NULL,b->channel,NULL)) 263 { 264 return 0; /* REJECT */ 265 } 266 } else 267 if (b->is_ok_check == EXBCHK_PARAM) 268 { 269 if (!extban->is_ok(b)) 270 { 271 return 0; /* REJECT */ 272 } 273 } 274 } 275 } 276 } 277 278 /* ACCEPT: 279 * - not an extban; OR 280 * - extban with NULL is_ok; OR 281 * - non-existing extban character (handled by conv_param?) 282 */ 283 return 1; 284 } 285 286 /** Validate ban ("is this ban ok?") */ 287 int timedban_extban_is_ok(BanContext *b) 288 { 289 char para[MAX_LENGTH+1]; 290 char tmpmask[MAX_LENGTH+1]; 291 char *durationstr; /**< Duration, such as '5' */ 292 int duration; 293 char *matchby; /**< Matching method, such as 'n!u@h' */ 294 char *newmask; /**< Cleaned matching method, such as 'n!u@h' */ 295 static int timedban_extban_is_ok_recursion = 0; 296 int res; 297 298 /* Always permit deletion */ 299 if (b->what == MODE_DEL) 300 return 1; 301 302 if (timedban_extban_is_ok_recursion) 303 return 0; /* Recursion detected (~t:1:~t:....) */ 304 305 strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */ 306 307 /* ~t:duration:n!u@h for direct matching 308 * ~t:duration:~x:.... when calling another bantype 309 */ 310 311 durationstr = para; 312 matchby = strchr(para, ':'); 313 if (!matchby || !matchby[1]) 314 return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid syntax"); 315 *matchby++ = '\0'; 316 317 duration = atoi(durationstr); 318 319 if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME)) 320 return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid duration time"); 321 322 strlcpy(tmpmask, matchby, sizeof(tmpmask)); 323 timedban_extban_is_ok_recursion++; 324 //res = extban_is_ok_nuh_extban(b->client, b->channel, tmpmask, b->is_ok_check, b->what, b->ban_type); 325 b->banstr = tmpmask; 326 res = generic_ban_is_ok(b); 327 timedban_extban_is_ok_recursion--; 328 if (res == 0) 329 { 330 /* This could be anything ranging from: 331 * invalid n!u@h syntax, unknown (sub)extbantype, 332 * disabled extban type in conf, too much recursion, etc. 333 */ 334 return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid matcher"); 335 } 336 337 return 1; /* OK */ 338 } 339 340 /** Check if the user is currently banned */ 341 int timedban_is_banned(BanContext *b) 342 { 343 b->banstr = strchr(b->banstr, ':'); /* skip time argument */ 344 if (!b->banstr) 345 return 0; /* invalid fmt */ 346 b->banstr++; /* skip over final semicolon */ 347 348 return ban_check_mask(b); 349 } 350 351 /** Helper to check if the ban has been expired. 352 */ 353 int timedban_has_ban_expired(Ban *ban) 354 { 355 char *banstr = ban->banstr; 356 char *p1, *p2; 357 int t; 358 time_t expire_on; 359 360 /* The caller has only performed a very light check (string starting 361 * with ~t, in the interest of performance), so we don't know yet if 362 * it REALLY is a timed ban. We check that first here... 363 */ 364 if (!strncmp(banstr, "~t:", 3)) 365 p1 = banstr + 3; 366 else if (!strncmp(banstr, "~time:", 6)) 367 p1 = banstr + 6; 368 else 369 return 0; /* not for us */ 370 p2 = strchr(p1+1, ':'); /* skip time argument */ 371 if (!p2) 372 return 0; /* invalid fmt */ 373 *p2 = '\0'; /* danger.. must restore!! */ 374 t = atoi(p1); 375 *p2 = ':'; /* restored.. */ 376 377 expire_on = ban->when + (t * 60) - TIMEDBAN_TIMER_DELTA; 378 379 if (expire_on < TStime()) 380 return 1; 381 return 0; 382 } 383 384 static char mbuf[512]; 385 static char pbuf[512]; 386 387 /** This removes any expired timedbans */ 388 EVENT(timedban_timeout) 389 { 390 Channel *channel; 391 Ban *ban, *nextban; 392 static int current_iteration = 0; 393 394 if (++current_iteration >= TIMEDBAN_TIMER_ITERATION_SPLIT) 395 current_iteration = 0; 396 397 for (channel = channels; channel; channel = channel->nextch) 398 { 399 /* This is a very quick check, at the cost of it being 400 * biased since there's always a tendency of more channel 401 * names to start with one specific letter. But hashing 402 * is too costly. So we stick with this. It should be 403 * good enough. Alternative would be some channel->id value. 404 */ 405 if (((unsigned int)channel->name[1] % TIMEDBAN_TIMER_ITERATION_SPLIT) != current_iteration) 406 continue; /* not this time, maybe next */ 407 408 *mbuf = *pbuf = '\0'; 409 for (ban = channel->banlist; ban; ban=nextban) 410 { 411 nextban = ban->next; 412 if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban)) 413 { 414 add_send_mode_param(channel, &me, '-', 'b', ban->banstr); 415 del_listmode(&channel->banlist, channel, ban->banstr); 416 } 417 } 418 for (ban = channel->exlist; ban; ban=nextban) 419 { 420 nextban = ban->next; 421 if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban)) 422 { 423 add_send_mode_param(channel, &me, '-', 'e', ban->banstr); 424 del_listmode(&channel->exlist, channel, ban->banstr); 425 } 426 } 427 for (ban = channel->invexlist; ban; ban=nextban) 428 { 429 nextban = ban->next; 430 if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban)) 431 { 432 add_send_mode_param(channel, &me, '-', 'I', ban->banstr); 433 del_listmode(&channel->invexlist, channel, ban->banstr); 434 } 435 } 436 if (*pbuf) 437 { 438 MessageTag *mtags = NULL; 439 new_message(&me, NULL, &mtags); 440 sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf); 441 sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf); 442 free_message_tags(mtags); 443 *pbuf = 0; 444 } 445 } 446 } 447 448 #if MODEBUFLEN > 512 449 #error "add_send_mode_param() is not made for MODEBUFLEN > 512" 450 #endif 451 452 void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param) { 453 static char *modes = NULL, lastwhat; 454 static short count = 0; 455 short send = 0; 456 457 if (!modes) modes = mbuf; 458 459 if (!mbuf[0]) { 460 modes = mbuf; 461 *modes++ = what; 462 *modes = 0; 463 lastwhat = what; 464 *pbuf = 0; 465 count = 0; 466 } 467 if (lastwhat != what) { 468 *modes++ = what; 469 *modes = 0; 470 lastwhat = what; 471 } 472 if (strlen(pbuf) + strlen(param) + 11 < MODEBUFLEN) { 473 if (*pbuf) 474 strcat(pbuf, " "); 475 strcat(pbuf, param); 476 *modes++ = mode; 477 *modes = 0; 478 count++; 479 } 480 else if (*pbuf) 481 send = 1; 482 483 if (count == MAXMODEPARAMS) 484 send = 1; 485 486 if (send) 487 { 488 MessageTag *mtags = NULL; 489 490 new_message(&me, NULL, &mtags); 491 sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf); 492 sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf); 493 free_message_tags(mtags); 494 send = 0; 495 *pbuf = 0; 496 modes = mbuf; 497 *modes++ = what; 498 lastwhat = what; 499 if (count != MAXMODEPARAMS) 500 { 501 strlcpy(pbuf, param, sizeof(pbuf)); 502 *modes++ = mode; 503 count = 1; 504 } else { 505 count = 0; 506 } 507 *modes = 0; 508 } 509 }