unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
connthrottle.c (17346B)
1 /* 2 * connthrottle - Connection throttler 3 * (C) Copyright 2004-2020 Bram Matthys (Syzop) and the UnrealIRCd team 4 * License: GPLv2 or later 5 * See https://www.unrealircd.org/docs/Connthrottle 6 */ 7 8 #include "unrealircd.h" 9 10 #define CONNTHROTTLE_VERSION "1.3" 11 12 #ifndef CALLBACKTYPE_REPUTATION_STARTTIME 13 #define CALLBACKTYPE_REPUTATION_STARTTIME 5 14 #endif 15 16 ModuleHeader MOD_HEADER 17 = { 18 "connthrottle", 19 CONNTHROTTLE_VERSION, 20 "Connection throttler - by Syzop", 21 "UnrealIRCd Team", 22 "unrealircd-6", 23 }; 24 25 typedef struct { 26 int count; 27 int period; 28 } ThrottleSetting; 29 30 struct cfgstruct { 31 /* set::connthrottle::known-users: */ 32 ThrottleSetting local; 33 ThrottleSetting global; 34 /* set::connthrottle::except: */ 35 SecurityGroup *except; 36 /* set::connthrottle::disabled-when: */ 37 long reputation_gathering; 38 int start_delay; 39 /* set::connthrottle (generic): */ 40 char *reason; 41 }; 42 static struct cfgstruct cfg; 43 44 typedef struct { 45 int count; 46 long t; 47 } ThrottleCounter; 48 49 typedef struct UCounter UCounter; 50 struct UCounter { 51 ThrottleCounter local; /**< Local counter */ 52 ThrottleCounter global; /**< Global counter */ 53 int rejected_clients; /**< Number of rejected clients this minute */ 54 int allowed_except; /**< Number of allowed clients - on except list */ 55 int allowed_unknown_users; /**< Number of allowed clients - not on except list */ 56 char disabled; /**< Module disabled by oper? */ 57 int throttling_this_minute; /**< Did we do any throttling this minute? */ 58 int throttling_previous_minute; /**< Did we do any throttling previous minute? */ 59 int throttling_banner_displayed;/**< Big we-are-now-throttling banner displayed? */ 60 time_t next_event; /**< When is next event? (for "last 60 seconds" stats) */ 61 }; 62 UCounter *ucounter = NULL; 63 64 #define MSG_THROTTLE "THROTTLE" 65 66 /* Forward declarations */ 67 int ct_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs); 68 int ct_config_posttest(int *errs); 69 int ct_config_run(ConfigFile *cf, ConfigEntry *ce, int type); 70 int ct_pre_lconnect(Client *client); 71 int ct_lconnect(Client *); 72 int ct_rconnect(Client *); 73 CMD_FUNC(ct_throttle); 74 EVENT(connthrottle_evt); 75 void ucounter_free(ModData *m); 76 77 MOD_TEST() 78 { 79 memset(&cfg, 0, sizeof(cfg)); 80 81 /* Defaults: */ 82 cfg.local.count = 20; cfg.local.period = 60; 83 cfg.global.count = 30; cfg.global.period = 60; 84 cfg.start_delay = 180; /* 3 minutes */ 85 safe_strdup(cfg.reason, "Throttled: Too many users trying to connect, please wait a while and try again"); 86 cfg.except = safe_alloc(sizeof(SecurityGroup)); 87 cfg.except->reputation_score = 24; 88 cfg.except->identified = 1; 89 cfg.except->webirc = 0; 90 91 HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, ct_config_test); 92 HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, ct_config_posttest); 93 return MOD_SUCCESS; 94 } 95 96 MOD_INIT() 97 { 98 MARK_AS_OFFICIAL_MODULE(modinfo); 99 LoadPersistentPointer(modinfo, ucounter, ucounter_free); 100 if (!ucounter) 101 ucounter = safe_alloc(sizeof(UCounter)); 102 HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, ct_config_run); 103 HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, ct_pre_lconnect); 104 HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, ct_lconnect); 105 HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, ct_rconnect); 106 CommandAdd(modinfo->handle, MSG_THROTTLE, ct_throttle, MAXPARA, CMD_USER|CMD_SERVER); 107 return MOD_SUCCESS; 108 } 109 110 MOD_LOAD() 111 { 112 EventAdd(modinfo->handle, "connthrottle_evt", connthrottle_evt, NULL, 1000, 0); 113 return MOD_SUCCESS; 114 } 115 116 MOD_UNLOAD() 117 { 118 SavePersistentPointer(modinfo, ucounter); 119 safe_free(cfg.reason); 120 free_security_group(cfg.except); 121 return MOD_SUCCESS; 122 } 123 124 /** This function checks if the reputation module is loaded. 125 * If not, then the module will error, since we depend on it. 126 */ 127 int ct_config_posttest(int *errs) 128 { 129 int errors = 0; 130 131 /* Note: we use Callbacks[] here, but this is only for checking. Don't 132 * let this confuse you. At any other place you must use RCallbacks[]. 133 */ 134 if (Callbacks[CALLBACKTYPE_REPUTATION_STARTTIME] == NULL) 135 { 136 config_error("The 'connthrottle' module requires the 'reputation' " 137 "module to be loaded as well."); 138 config_error("Add the following to your configuration file: " 139 "loadmodule \"reputation\";"); 140 errors++; 141 } 142 143 *errs = errors; 144 return errors ? -1 : 1; 145 } 146 147 /** Test the set::connthrottle configuration */ 148 int ct_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) 149 { 150 int errors = 0; 151 ConfigEntry *cep, *cepp; 152 153 if (type != CONFIG_SET) 154 return 0; 155 156 /* We are only interrested in set::connthrottle.. */ 157 if (!ce || !ce->name || strcmp(ce->name, "connthrottle")) 158 return 0; 159 160 for (cep = ce->items; cep; cep = cep->next) 161 { 162 if (!strcmp(cep->name, "except")) 163 { 164 test_match_block(cf, cep, &errors); 165 } else 166 if (!strcmp(cep->name, "known-users")) 167 { 168 for (cepp = cep->items; cepp; cepp = cepp->next) 169 { 170 CheckNull(cepp); 171 if (!strcmp(cepp->name, "minimum-reputation-score")) 172 { 173 int cnt = atoi(cepp->value); 174 if (cnt < 1) 175 { 176 config_error("%s:%i: set::connthrottle::known-users::minimum-reputation-score should be at least 1", 177 cepp->file->filename, cepp->line_number); 178 errors++; 179 continue; 180 } 181 } else 182 if (!strcmp(cepp->name, "sasl-bypass")) 183 { 184 } else 185 if (!strcmp(cepp->name, "webirc-bypass")) 186 { 187 } else 188 { 189 config_error_unknown(cepp->file->filename, cepp->line_number, 190 "set::connthrottle::known-users", cepp->name); 191 errors++; 192 } 193 } 194 } else 195 if (!strcmp(cep->name, "new-users")) 196 { 197 for (cepp = cep->items; cepp; cepp = cepp->next) 198 { 199 CheckNull(cepp); 200 if (!strcmp(cepp->name, "local-throttle")) 201 { 202 int cnt, period; 203 if (!config_parse_flood(cepp->value, &cnt, &period) || 204 (cnt < 1) || (cnt > 2000000000) || (period > 2000000000)) 205 { 206 config_error("%s:%i: set::connthrottle::new-users::local-throttle error. " 207 "Syntax is <count>:<period> (eg 6:60), " 208 "and count and period should be non-zero.", 209 cepp->file->filename, cepp->line_number); 210 errors++; 211 continue; 212 } 213 } else 214 if (!strcmp(cepp->name, "global-throttle")) 215 { 216 int cnt, period; 217 if (!config_parse_flood(cepp->value, &cnt, &period) || 218 (cnt < 1) || (cnt > 2000000000) || (period > 2000000000)) 219 { 220 config_error("%s:%i: set::connthrottle::new-users::global-throttle error. " 221 "Syntax is <count>:<period> (eg 6:60), " 222 "and count and period should be non-zero.", 223 cepp->file->filename, cepp->line_number); 224 errors++; 225 continue; 226 } 227 } else 228 { 229 config_error_unknown(cepp->file->filename, cepp->line_number, 230 "set::connthrottle::new-users", cepp->name); 231 errors++; 232 } 233 } 234 } else 235 if (!strcmp(cep->name, "disabled-when")) 236 { 237 for (cepp = cep->items; cepp; cepp = cepp->next) 238 { 239 CheckNull(cepp); 240 if (!strcmp(cepp->name, "start-delay")) 241 { 242 int cnt = config_checkval(cepp->value, CFG_TIME); 243 if ((cnt < 0) || (cnt > 3600)) 244 { 245 config_error("%s:%i: set::connthrottle::disabled-when::start-delay should be in range 0-3600", 246 cepp->file->filename, cepp->line_number); 247 errors++; 248 continue; 249 } 250 } else 251 if (!strcmp(cepp->name, "reputation-gathering")) 252 { 253 } else 254 { 255 config_error_unknown(cepp->file->filename, cepp->line_number, 256 "set::connthrottle::disabled-when", cepp->name); 257 errors++; 258 } 259 } 260 } else 261 if (!strcmp(cep->name, "reason")) 262 { 263 CheckNull(cep); 264 } else 265 { 266 config_error("%s:%i: unknown directive set::connthrottle::%s", 267 cep->file->filename, cep->line_number, cep->name); 268 errors++; 269 continue; 270 } 271 } 272 273 *errs = errors; 274 return errors ? -1 : 1; 275 } 276 277 /* Configure ourselves based on the set::connthrottle settings */ 278 int ct_config_run(ConfigFile *cf, ConfigEntry *ce, int type) 279 { 280 ConfigEntry *cep, *cepp; 281 282 if (type != CONFIG_SET) 283 return 0; 284 285 /* We are only interrested in set::connthrottle.. */ 286 if (!ce || !ce->name || strcmp(ce->name, "connthrottle")) 287 return 0; 288 289 for (cep = ce->items; cep; cep = cep->next) 290 { 291 if (!strcmp(cep->name, "except")) 292 { 293 conf_match_block(cf, cep, &cfg.except); 294 } else 295 if (!strcmp(cep->name, "known-users")) 296 { 297 for (cepp = cep->items; cepp; cepp = cepp->next) 298 { 299 if (!strcmp(cepp->name, "minimum-reputation-score")) 300 cfg.except->reputation_score = atoi(cepp->value); 301 else if (!strcmp(cepp->name, "sasl-bypass")) 302 cfg.except->identified = config_checkval(cepp->value, CFG_YESNO); 303 else if (!strcmp(cepp->name, "webirc-bypass")) 304 cfg.except->webirc = config_checkval(cepp->value, CFG_YESNO); 305 } 306 } else 307 if (!strcmp(cep->name, "new-users")) 308 { 309 for (cepp = cep->items; cepp; cepp = cepp->next) 310 { 311 if (!strcmp(cepp->name, "local-throttle")) 312 config_parse_flood(cepp->value, &cfg.local.count, &cfg.local.period); 313 else if (!strcmp(cepp->name, "global-throttle")) 314 config_parse_flood(cepp->value, &cfg.global.count, &cfg.global.period); 315 } 316 } else 317 if (!strcmp(cep->name, "disabled-when")) 318 { 319 for (cepp = cep->items; cepp; cepp = cepp->next) 320 { 321 if (!strcmp(cepp->name, "start-delay")) 322 cfg.start_delay = config_checkval(cepp->value, CFG_TIME); 323 else if (!strcmp(cepp->name, "reputation-gathering")) 324 cfg.reputation_gathering = config_checkval(cepp->value, CFG_TIME); 325 } 326 } else 327 if (!strcmp(cep->name, "reason")) 328 { 329 safe_free(cfg.reason); 330 cfg.reason = safe_alloc(strlen(cep->value)+16); 331 sprintf(cfg.reason, "Throttled: %s", cep->value); 332 } 333 } 334 return 1; 335 } 336 337 /** Returns 1 if the 'reputation' module is still gathering 338 * data, such as in the first week of when it is loaded. 339 * This behavior is configured via set::disabled-when::reputation-gathering 340 */ 341 int still_reputation_gathering(void) 342 { 343 int v; 344 345 if (RCallbacks[CALLBACKTYPE_REPUTATION_STARTTIME] == NULL) 346 return 1; /* Reputation module not loaded, disable us */ 347 348 v = RCallbacks[CALLBACKTYPE_REPUTATION_STARTTIME]->func.intfunc(); 349 350 if (TStime() - v < cfg.reputation_gathering) 351 return 1; /* Still gathering reputation data (eg: first week) */ 352 353 return 0; 354 } 355 356 EVENT(connthrottle_evt) 357 { 358 char buf[512]; 359 360 if (ucounter->next_event > TStime()) 361 return; 362 ucounter->next_event = TStime() + 60; 363 364 if (ucounter->rejected_clients) 365 { 366 unreal_log(ULOG_INFO, "connthrottle", "CONNTHROTLE_REPORT", NULL, 367 "ConnThrottle] Stats for this server past 60 secs: " 368 "Connections rejected: $num_rejected. " 369 "Accepted: $num_accepted_except except user(s) and " 370 "$num_accepted_unknown_users new user(s).", 371 log_data_integer("num_rejected", ucounter->rejected_clients), 372 log_data_integer("num_accepted_except", ucounter->allowed_except), 373 log_data_integer("num_accepted_unknown_users", ucounter->allowed_unknown_users)); 374 } 375 376 /* Reset stats for next message */ 377 ucounter->rejected_clients = 0; 378 ucounter->allowed_except = 0; 379 ucounter->allowed_unknown_users = 0; 380 381 ucounter->throttling_previous_minute = ucounter->throttling_this_minute; 382 ucounter->throttling_this_minute = 0; /* reset */ 383 ucounter->throttling_banner_displayed = 0; /* reset */ 384 } 385 386 #define THROT_LOCAL 1 387 #define THROT_GLOBAL 2 388 int ct_pre_lconnect(Client *client) 389 { 390 int throttle=0; 391 int score; 392 393 if (me.local->creationtime + cfg.start_delay > TStime()) 394 return HOOK_CONTINUE; /* no throttle: start delay */ 395 396 if (ucounter->disabled) 397 return HOOK_CONTINUE; /* protection disabled: allow user */ 398 399 if (still_reputation_gathering()) 400 return HOOK_CONTINUE; /* still gathering reputation data */ 401 402 if (user_allowed_by_security_group(client, cfg.except)) 403 return HOOK_CONTINUE; /* allowed: user is exempt (known user or otherwise) */ 404 405 /* If we reach this then the user is NEW */ 406 407 /* +1 global client would reach global limit? */ 408 if ((TStime() - ucounter->global.t < cfg.global.period) && (ucounter->global.count+1 > cfg.global.count)) 409 throttle |= THROT_GLOBAL; 410 411 /* +1 local client would reach local limit? */ 412 if ((TStime() - ucounter->local.t < cfg.local.period) && (ucounter->local.count+1 > cfg.local.count)) 413 throttle |= THROT_LOCAL; 414 415 if (throttle) 416 { 417 ucounter->throttling_this_minute = 1; 418 ucounter->rejected_clients++; 419 /* We send the LARGE banner if throttling was activated */ 420 if (!ucounter->throttling_previous_minute && !ucounter->throttling_banner_displayed) 421 { 422 unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_ACTIVATED", NULL, 423 "[ConnThrottle] Connection throttling has been ACTIVATED due to a HIGH CONNECTION RATE.\n" 424 "Users with IP addresses that have not been seen before will be rejected above the set connection rate. Known users can still get in.\n" 425 "or more information see https://www.unrealircd.org/docs/ConnThrottle"); 426 ucounter->throttling_banner_displayed = 1; 427 } 428 exit_client(client, NULL, cfg.reason); 429 return HOOK_DENY; 430 } 431 432 return HOOK_CONTINUE; 433 } 434 435 /** Increase the connect counter(s), nothing else. */ 436 void bump_connect_counter(int local_connect) 437 { 438 if (local_connect) 439 { 440 /* Bump local connect counter */ 441 if (TStime() - ucounter->local.t >= cfg.local.period) 442 { 443 ucounter->local.t = TStime(); 444 ucounter->local.count = 1; 445 } else { 446 ucounter->local.count++; 447 } 448 } 449 450 /* Bump global connect counter */ 451 if (TStime() - ucounter->global.t >= cfg.global.period) 452 { 453 ucounter->global.t = TStime(); 454 ucounter->global.count = 1; 455 } else { 456 ucounter->global.count++; 457 } 458 } 459 460 int ct_lconnect(Client *client) 461 { 462 int score; 463 464 if (me.local->creationtime + cfg.start_delay > TStime()) 465 return 0; /* no throttle: start delay */ 466 467 if (ucounter->disabled) 468 return 0; /* protection disabled: allow user */ 469 470 if (still_reputation_gathering()) 471 return 0; /* still gathering reputation data */ 472 473 if (user_allowed_by_security_group(client, cfg.except)) 474 { 475 ucounter->allowed_except++; 476 return HOOK_CONTINUE; /* allowed: user is exempt (known user or otherwise) */ 477 } 478 479 /* Allowed NEW user */ 480 ucounter->allowed_unknown_users++; 481 482 bump_connect_counter(1); 483 484 return 0; 485 } 486 487 int ct_rconnect(Client *client) 488 { 489 int score; 490 491 if (client->uplink && !IsSynched(client->uplink)) 492 return 0; /* Netmerge: skip */ 493 494 if (IsULine(client)) 495 return 0; /* U:lined, such as services: skip */ 496 497 #if UNREAL_VERSION_TIME >= 201915 498 /* On UnrealIRCd 4.2.3+ we can see the boot time (start time) 499 * of the remote server. This way we can apply the 500 * set::disabled-when::start-delay restriction on remote 501 * servers as well. 502 */ 503 if (client->uplink && client->uplink->server && client->uplink->server->boottime && 504 (TStime() - client->uplink->server->boottime < cfg.start_delay)) 505 { 506 return 0; 507 } 508 #endif 509 510 if (user_allowed_by_security_group(client, cfg.except)) 511 return 0; /* user is on except list (known user or otherwise) */ 512 513 bump_connect_counter(0); 514 515 return 0; 516 } 517 518 static void ct_throttle_usage(Client *client) 519 { 520 sendnotice(client, "Usage: /THROTTLE [ON|OFF|STATUS|RESET]"); 521 sendnotice(client, " ON: Enabled protection"); 522 sendnotice(client, " OFF: Disables protection"); 523 sendnotice(client, " STATUS: Status report"); 524 sendnotice(client, " RESET: Resets all counters(&more)"); 525 sendnotice(client, "NOTE: All commands only affect this server. Remote servers are not affected."); 526 } 527 528 CMD_FUNC(ct_throttle) 529 { 530 if (!IsOper(client)) 531 { 532 sendnumeric(client, ERR_NOPRIVILEGES); 533 return; 534 } 535 536 if ((parc < 2) || BadPtr(parv[1])) 537 { 538 ct_throttle_usage(client); 539 return; 540 } 541 542 if (!strcasecmp(parv[1], "STATS") || !strcasecmp(parv[1], "STATUS")) 543 { 544 sendnotice(client, "STATUS:"); 545 if (ucounter->disabled) 546 { 547 sendnotice(client, "Module DISABLED on oper request. To re-enable, type: /THROTTLE ON"); 548 } else { 549 if (still_reputation_gathering()) 550 { 551 sendnotice(client, "Module DISABLED because the 'reputation' module has not gathered enough data yet (set::connthrottle::disabled-when::reputation-gathering)."); 552 } else 553 if (me.local->creationtime + cfg.start_delay > TStime()) 554 { 555 sendnotice(client, "Module DISABLED due to start-delay (set::connthrottle::disabled-when::start-delay), will be enabled in %lld second(s).", 556 (long long)((me.local->creationtime + cfg.start_delay) - TStime())); 557 } else 558 { 559 sendnotice(client, "Module ENABLED"); 560 } 561 } 562 } else 563 if (!strcasecmp(parv[1], "OFF")) 564 { 565 if (ucounter->disabled == 1) 566 { 567 sendnotice(client, "Already OFF"); 568 return; 569 } 570 ucounter->disabled = 1; 571 unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_MODULE_DISABLED", client, 572 "[ConnThrottle] $client.details DISABLED the connthrottle module."); 573 } else 574 if (!strcasecmp(parv[1], "ON")) 575 { 576 if (ucounter->disabled == 0) 577 { 578 sendnotice(client, "Already ON"); 579 return; 580 } 581 unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_MODULE_ENABLED", client, 582 "[ConnThrottle] $client.details ENABLED the connthrottle module."); 583 ucounter->disabled = 0; 584 } else 585 if (!strcasecmp(parv[1], "RESET")) 586 { 587 memset(ucounter, 0, sizeof(UCounter)); 588 unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_RESET", client, 589 "[ConnThrottle] $client.details did a RESET on the statistics/counters."); 590 } else 591 { 592 sendnotice(client, "Unknown option '%s'", parv[1]); 593 ct_throttle_usage(client); 594 } 595 } 596 597 void ucounter_free(ModData *m) 598 { 599 safe_free(ucounter); 600 }