unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
reputation.c (38555B)
1 /* 2 * reputation - Provides a scoring system for "known users". 3 * (C) Copyright 2015-2019 Bram Matthys (Syzop) and the UnrealIRCd team. 4 * License: GPLv2 or later 5 * 6 * How this works is simple: 7 * Every 5 minutes the IP address of all the connected users receive 8 * a point. Registered users receive 2 points every 5 minutes. 9 * The total reputation score is then later used, by other modules, for 10 * example to make decisions such as to reject or allow a user if the 11 * server is under attack. 12 * The reputation scores are saved in a database. By default this file 13 * is data/reputation.db (often ~/unrealircd/data/reputation.db). 14 * 15 * See also https://www.unrealircd.org/docs/Connthrottle 16 */ 17 18 #include "unrealircd.h" 19 20 #define REPUTATION_VERSION "1.2" 21 22 /* Change to #define to benchmark. Note that this will add random 23 * reputation entries so should never be used on production servers!!! 24 */ 25 #undef BENCHMARK 26 #undef TEST 27 28 /* Benchmark results (2GHz Xeon Skylake, compiled with -O2, Linux): 29 * 10k random IP's with various expire times: 30 * - load db: 23 ms 31 * - expiry: 1 ms 32 * - save db: 7 ms 33 * 100k random IP's with various expire times: 34 * - load db: 103 ms 35 * - expiry: 10 ms 36 * - save db: 32 ms 37 * So, even for 100,000 unique IP's, the initial load of the database 38 * would delay the UnrealIRCd boot process only for 0.1 second. 39 * The writing of the db, which happens every 5 minutes, for such 40 * amount of IP's takes 32ms (0.03 second). 41 * Of course, exact figures will depend on the storage and cache. 42 * That being said, the file for 100k random IP's is slightly under 43 * 3MB, so not big, which likely means the timing will be similar 44 * for a broad number of (storage) systems. 45 */ 46 47 #ifndef TEST 48 #define BUMP_SCORE_EVERY 300 49 #define DELETE_OLD_EVERY 605 50 #define SAVE_DB_EVERY 902 51 #else 52 #define BUMP_SCORE_EVERY 3 53 #define DELETE_OLD_EVERY 3 54 #define SAVE_DB_EVERY 3 55 #endif 56 57 #ifndef CALLBACKTYPE_REPUTATION_STARTTIME 58 #define CALLBACKTYPE_REPUTATION_STARTTIME 5 59 #endif 60 61 ModuleHeader MOD_HEADER 62 = { 63 "reputation", 64 REPUTATION_VERSION, 65 "Known IP's scoring system", 66 "UnrealIRCd Team", 67 "unrealircd-6", 68 }; 69 70 /* Defines */ 71 72 #define MAXEXPIRES 10 73 74 #define REPUTATION_SCORE_CAP 10000 75 76 #define UPDATE_SCORE_MARGIN 1 77 78 #define REPUTATION_HASH_TABLE_SIZE 2048 79 80 #define Reputation(client) moddata_client(client, reputation_md).l 81 82 #define WARN_WRITE_ERROR(fname) \ 83 do { \ 84 unreal_log(ULOG_ERROR, "reputation", "REPUTATION_FILE_WRITE_ERROR", NULL, \ 85 "[reputation] Error writing to temporary database file $filename: $system_error", \ 86 log_data_string("filename", fname), \ 87 log_data_string("system_error", unrealdb_get_error_string())); \ 88 } while(0) 89 90 #define W_SAFE(x) \ 91 do { \ 92 if (!(x)) { \ 93 WARN_WRITE_ERROR(tmpfname); \ 94 unrealdb_close(db); \ 95 return 0; \ 96 } \ 97 } while(0) 98 99 100 /* Definitions (structs, etc.) */ 101 102 struct cfgstruct { 103 int expire_score[MAXEXPIRES]; 104 long expire_time[MAXEXPIRES]; 105 char *database; 106 char *db_secret; 107 }; 108 109 typedef struct ReputationEntry ReputationEntry; 110 111 struct ReputationEntry { 112 ReputationEntry *prev, *next; 113 unsigned short score; /**< score for the user */ 114 long last_seen; /**< user last seen (unix timestamp) */ 115 int marker; /**< internal marker, not written to db */ 116 char ip[1]; /*< ip address */ 117 }; 118 119 /* Global variables */ 120 121 static struct cfgstruct cfg; /**< Current configuration */ 122 static struct cfgstruct test; /**< Testing configuration (not active yet) */ 123 long reputation_starttime = 0; 124 long reputation_writtentime = 0; 125 126 static ReputationEntry *ReputationHashTable[REPUTATION_HASH_TABLE_SIZE]; 127 static char siphashkey_reputation[SIPHASH_KEY_LENGTH]; 128 129 static ModuleInfo ModInf; 130 131 ModDataInfo *reputation_md; /* Module Data structure which we acquire */ 132 133 /* Forward declarations */ 134 void reputation_md_free(ModData *m); 135 const char *reputation_md_serialize(ModData *m); 136 void reputation_md_unserialize(const char *str, ModData *m); 137 void reputation_config_setdefaults(struct cfgstruct *cfg); 138 void reputation_free_config(struct cfgstruct *cfg); 139 CMD_FUNC(reputation_cmd); 140 CMD_FUNC(reputationunperm); 141 int reputation_whois(Client *client, Client *target, NameValuePrioList **list); 142 int reputation_set_on_connect(Client *client); 143 int reputation_pre_lconnect(Client *client); 144 int reputation_ip_change(Client *client, const char *oldip); 145 int reputation_connect_extinfo(Client *client, NameValuePrioList **list); 146 int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs); 147 int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type); 148 int reputation_config_posttest(int *errs); 149 static uint64_t hash_reputation_entry(const char *ip); 150 ReputationEntry *find_reputation_entry(const char *ip); 151 void add_reputation_entry(ReputationEntry *e); 152 EVENT(delete_old_records); 153 EVENT(add_scores); 154 EVENT(reputation_save_db_evt); 155 int reputation_load_db(void); 156 int reputation_save_db(void); 157 int reputation_starttime_callback(void); 158 159 MOD_TEST() 160 { 161 memcpy(&ModInf, modinfo, modinfo->size); 162 memset(&cfg, 0, sizeof(cfg)); 163 memset(&test, 0, sizeof(cfg)); 164 reputation_config_setdefaults(&test); 165 HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, reputation_config_test); 166 HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, reputation_config_posttest); 167 CallbackAdd(modinfo->handle, CALLBACKTYPE_REPUTATION_STARTTIME, reputation_starttime_callback); 168 return MOD_SUCCESS; 169 } 170 171 MOD_INIT() 172 { 173 ModDataInfo mreq; 174 175 MARK_AS_OFFICIAL_MODULE(modinfo); 176 ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1); 177 178 memset(&ReputationHashTable, 0, sizeof(ReputationHashTable)); 179 siphash_generate_key(siphashkey_reputation); 180 181 memset(&mreq, 0, sizeof(mreq)); 182 mreq.name = "reputation"; 183 mreq.free = reputation_md_free; 184 mreq.serialize = reputation_md_serialize; 185 mreq.unserialize = reputation_md_unserialize; 186 mreq.sync = 0; /* local! */ 187 mreq.type = MODDATATYPE_CLIENT; 188 reputation_md = ModDataAdd(modinfo->handle, mreq); 189 if (!reputation_md) 190 abort(); 191 192 reputation_config_setdefaults(&cfg); 193 HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, reputation_config_run); 194 HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, reputation_whois); 195 HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, reputation_set_on_connect); 196 HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 0, reputation_ip_change); 197 HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 2000000000, reputation_pre_lconnect); /* (prio: last) */ 198 HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, -1000000000, reputation_set_on_connect); /* (prio: near-first) */ 199 HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 0, reputation_connect_extinfo); /* (prio: near-first) */ 200 CommandAdd(ModInf.handle, "REPUTATION", reputation_cmd, MAXPARA, CMD_USER|CMD_SERVER); 201 CommandAdd(ModInf.handle, "REPUTATIONUNPERM", reputationunperm, MAXPARA, CMD_USER|CMD_SERVER); 202 return MOD_SUCCESS; 203 } 204 205 #ifdef BENCHMARK 206 void reputation_benchmark(int entries) 207 { 208 char ip[64]; 209 int i; 210 ReputationEntry *e; 211 212 srand(1234); // fixed seed 213 214 for (i = 0; i < entries; i++) 215 { 216 ReputationEntry *e = safe_alloc(sizeof(ReputationEntry) + 64); 217 snprintf(e->ip, 63, "%d.%d.%d.%d", rand()%255, rand()%255, rand()%255, rand()%255); 218 e->score = rand()%255 + 1; 219 e->last_seen = TStime(); 220 if (find_reputation_entry(e->ip)) 221 { 222 safe_free(e); 223 continue; 224 } 225 add_reputation_entry(e); 226 } 227 } 228 #endif 229 MOD_LOAD() 230 { 231 reputation_load_db(); 232 if (reputation_starttime == 0) 233 reputation_starttime = TStime(); 234 EventAdd(ModInf.handle, "delete_old_records", delete_old_records, NULL, DELETE_OLD_EVERY*1000, 0); 235 EventAdd(ModInf.handle, "add_scores", add_scores, NULL, BUMP_SCORE_EVERY*1000, 0); 236 EventAdd(ModInf.handle, "reputation_save_db", reputation_save_db_evt, NULL, SAVE_DB_EVERY*1000, 0); 237 #ifdef BENCHMARK 238 reputation_benchmark(10000); 239 #endif 240 return MOD_SUCCESS; 241 } 242 243 MOD_UNLOAD() 244 { 245 if (loop.terminating) 246 reputation_save_db(); 247 reputation_free_config(&test); 248 reputation_free_config(&cfg); 249 return MOD_SUCCESS; 250 } 251 252 void reputation_config_setdefaults(struct cfgstruct *cfg) 253 { 254 /* data/reputation.db */ 255 safe_strdup(cfg->database, "reputation.db"); 256 convert_to_absolute_path(&cfg->database, PERMDATADIR); 257 258 /* EXPIRES the following entries if the IP does appear for some time: */ 259 /* <=2 points after 1 hour */ 260 cfg->expire_score[0] = 2; 261 #ifndef TEST 262 cfg->expire_time[0] = 3600; 263 #else 264 cfg->expire_time[0] = 36; 265 #endif 266 /* <=6 points after 7 days */ 267 cfg->expire_score[1] = 6; 268 cfg->expire_time[1] = 86400*7; 269 /* ANY result that has not been seen for 30 days */ 270 cfg->expire_score[2] = -1; 271 cfg->expire_time[2] = 86400*30; 272 } 273 274 void reputation_free_config(struct cfgstruct *cfg) 275 { 276 safe_free(cfg->database); 277 safe_free(cfg->db_secret); 278 } 279 280 int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) 281 { 282 int errors = 0; 283 ConfigEntry *cep; 284 285 if (type != CONFIG_SET) 286 return 0; 287 288 /* We are only interrested in set::reputation.. */ 289 if (!ce || strcmp(ce->name, "reputation")) 290 return 0; 291 292 for (cep = ce->items; cep; cep = cep->next) 293 { 294 if (!cep->value) 295 { 296 config_error("%s:%i: blank set::reputation::%s without value", 297 cep->file->filename, cep->line_number, cep->name); 298 errors++; 299 continue; 300 } else 301 if (!strcmp(cep->name, "database")) 302 { 303 convert_to_absolute_path(&cep->value, PERMDATADIR); 304 safe_strdup(test.database, cep->value); 305 } else 306 if (!strcmp(cep->name, "db-secret")) 307 { 308 const char *err; 309 if ((err = unrealdb_test_secret(cep->value))) 310 { 311 config_error("%s:%i: set::channeldb::db-secret: %s", cep->file->filename, cep->line_number, err); 312 errors++; 313 continue; 314 } 315 safe_strdup(test.db_secret, cep->value); 316 } else 317 { 318 config_error("%s:%i: unknown directive set::reputation::%s", 319 cep->file->filename, cep->line_number, cep->name); 320 errors++; 321 continue; 322 } 323 } 324 325 *errs = errors; 326 return errors ? -1 : 1; 327 } 328 329 int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type) 330 { 331 ConfigEntry *cep; 332 333 if (type != CONFIG_SET) 334 return 0; 335 336 /* We are only interrested in set::reputation.. */ 337 if (!ce || strcmp(ce->name, "reputation")) 338 return 0; 339 340 for (cep = ce->items; cep; cep = cep->next) 341 { 342 if (!strcmp(cep->name, "database")) 343 { 344 safe_strdup(cfg.database, cep->value); 345 } else 346 if (!strcmp(cep->name, "db-secret")) 347 { 348 safe_strdup(cfg.db_secret, cep->value); 349 } 350 } 351 return 1; 352 } 353 354 int reputation_config_posttest(int *errs) 355 { 356 int errors = 0; 357 char *errstr; 358 359 if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret)))) 360 { 361 config_error("[reputation] %s", errstr); 362 errors++; 363 } 364 365 *errs = errors; 366 return errors ? -1 : 1; 367 } 368 369 /** Parse database header and set variables appropriately */ 370 int parse_db_header_old(char *buf) 371 { 372 char *header=NULL, *version=NULL, *starttime=NULL, *writtentime=NULL; 373 char *p=NULL; 374 375 if (strncmp(buf, "REPDB", 5)) 376 return 0; 377 378 header = strtoken(&p, buf, " "); 379 if (!header) 380 return 0; 381 382 version = strtoken(&p, NULL, " "); 383 if (!version || (atoi(version) != 1)) 384 return 0; 385 386 starttime = strtoken(&p, NULL, " "); 387 if (!starttime) 388 return 0; 389 390 writtentime = strtoken(&p, NULL, " "); 391 if (!writtentime) 392 return 0; 393 394 reputation_starttime = atol(starttime); 395 reputation_writtentime = atol(writtentime); 396 397 return 1; 398 } 399 400 void reputation_load_db_old(void) 401 { 402 FILE *fd; 403 char buf[512], *p; 404 #ifdef BENCHMARK 405 struct timeval tv_alpha, tv_beta; 406 407 gettimeofday(&tv_alpha, NULL); 408 #endif 409 410 fd = fopen(cfg.database, "r"); 411 if (!fd) 412 { 413 config_warn("WARNING: Could not open/read database '%s': %s", cfg.database, strerror(ERRNO)); 414 return; 415 } 416 417 memset(buf, 0, sizeof(buf)); 418 if (fgets(buf, 512, fd) == NULL) 419 { 420 config_error("WARNING: Database file corrupt ('%s')", cfg.database); 421 fclose(fd); 422 return; 423 } 424 425 /* Header contains: REPDB <version> <starttime> <writtentime> 426 * Where: 427 * REPDB: Literally the string "REPDB". 428 * <version> This is version 1 at the time of this writing. 429 * <starttime> The time that recording of reputation started, 430 * in other words: when this module was first loaded, ever. 431 * <writtentime> Time that the database was last written. 432 */ 433 if (!parse_db_header_old(buf)) 434 { 435 config_error("WARNING: Cannot load database %s. Error reading header. " 436 "Database corrupt? Or are you downgrading from a newer " 437 "UnrealIRCd version perhaps? This is not supported.", 438 cfg.database); 439 fclose(fd); 440 return; 441 } 442 443 while(fgets(buf, 512, fd) != NULL) 444 { 445 char *ip = NULL, *score = NULL, *last_seen = NULL; 446 ReputationEntry *e; 447 448 stripcrlf(buf); 449 /* Format: <ip> <score> <last seen> */ 450 ip = strtoken(&p, buf, " "); 451 if (!ip) 452 continue; 453 score = strtoken(&p, NULL, " "); 454 if (!score) 455 continue; 456 last_seen = strtoken(&p, NULL, " "); 457 if (!last_seen) 458 continue; 459 460 e = safe_alloc(sizeof(ReputationEntry)+strlen(ip)); 461 strcpy(e->ip, ip); /* safe, see alloc above */ 462 e->score = atoi(score); 463 e->last_seen = atol(last_seen); 464 465 add_reputation_entry(e); 466 } 467 fclose(fd); 468 469 #ifdef BENCHMARK 470 gettimeofday(&tv_beta, NULL); 471 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL, 472 "[reputation] Benchmark: LOAD DB: $time_msec microseconds", 473 log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec))); 474 #endif 475 } 476 477 #define R_SAFE(x) \ 478 do { \ 479 if (!(x)) { \ 480 config_warn("[reputation] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \ 481 unrealdb_close(db); \ 482 safe_free(ip); \ 483 return 0; \ 484 } \ 485 } while(0) 486 487 int reputation_load_db_new(UnrealDB *db) 488 { 489 uint64_t l_db_version = 0; 490 uint64_t l_starttime = 0; 491 uint64_t l_writtentime = 0; 492 uint64_t count = 0; 493 uint64_t i; 494 char *ip = NULL; 495 uint16_t score; 496 uint64_t last_seen; 497 ReputationEntry *e; 498 #ifdef BENCHMARK 499 struct timeval tv_alpha, tv_beta; 500 501 gettimeofday(&tv_alpha, NULL); 502 #endif 503 504 R_SAFE(unrealdb_read_int64(db, &l_db_version)); /* reputation db version */ 505 if (l_db_version > 2) 506 { 507 config_error("[reputation] Reputation DB is of a newer version (%ld) than supported by us (%ld). " 508 "Did you perhaps downgrade your UnrealIRCd?", 509 (long)l_db_version, (long)2); 510 unrealdb_close(db); 511 return 0; 512 } 513 R_SAFE(unrealdb_read_int64(db, &l_starttime)); /* starttime of data gathering */ 514 R_SAFE(unrealdb_read_int64(db, &l_writtentime)); /* current time */ 515 R_SAFE(unrealdb_read_int64(db, &count)); /* number of entries */ 516 517 reputation_starttime = l_starttime; 518 reputation_writtentime = l_writtentime; 519 520 for (i=0; i < count; i++) 521 { 522 R_SAFE(unrealdb_read_str(db, &ip)); 523 R_SAFE(unrealdb_read_int16(db, &score)); 524 R_SAFE(unrealdb_read_int64(db, &last_seen)); 525 526 e = safe_alloc(sizeof(ReputationEntry)+strlen(ip)); 527 strcpy(e->ip, ip); /* safe, see alloc above */ 528 e->score = score; 529 e->last_seen = last_seen; 530 add_reputation_entry(e); 531 safe_free(ip); 532 } 533 unrealdb_close(db); 534 #ifdef BENCHMARK 535 gettimeofday(&tv_beta, NULL); 536 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL, 537 "Reputation benchmark: LOAD DB: $time_msec microseconds", 538 log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec))); 539 #endif 540 return 1; 541 } 542 543 /** Load the reputation DB. 544 * Strategy is: 545 * 1) Check for the old header "REPDB 1", if so then call reputation_load_db_old(). 546 * 2) Otherwise, open with unrealdb routine 547 * 3) If that fails due to a password provided but the file is unrealdb without password 548 * then fallback to open without a password (so users can easily upgrade to encrypted) 549 */ 550 int reputation_load_db(void) 551 { 552 FILE *fd; 553 UnrealDB *db; 554 char buf[512]; 555 556 fd = fopen(cfg.database, "r"); 557 if (!fd) 558 { 559 /* Database does not exist. Could be first boot */ 560 config_warn("[reputation] No database present at '%s', will start a new one", cfg.database); 561 return 1; 562 } 563 564 *buf = '\0'; 565 if (fgets(buf, sizeof(buf), fd) == NULL) 566 { 567 fclose(fd); 568 config_warn("[reputation] Database at '%s' is 0 bytes", cfg.database); 569 return 1; 570 } 571 fclose(fd); 572 if (!strncmp(buf, "REPDB 1 ", 8)) 573 { 574 reputation_load_db_old(); 575 return 1; /* not so good to always pretend succes */ 576 } 577 578 /* Otherwise, it is an unrealdb, crypted or not */ 579 db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret); 580 if (!db) 581 { 582 if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND) 583 { 584 /* Database does not exist. Could be first boot */ 585 config_warn("[reputation] No database present at '%s', will start a new one", cfg.database); 586 return 1; 587 } else 588 if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED) 589 { 590 db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL); 591 } 592 if (!db) 593 { 594 config_error("[reputation] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string()); 595 return 0; 596 } 597 } 598 return reputation_load_db_new(db); 599 } 600 601 int reputation_save_db_old(void) 602 { 603 FILE *fd; 604 char tmpfname[512]; 605 int i; 606 ReputationEntry *e; 607 #ifdef BENCHMARK 608 struct timeval tv_alpha, tv_beta; 609 610 gettimeofday(&tv_alpha, NULL); 611 #endif 612 613 /* We write to a temporary file. Only to rename it later if everything was ok */ 614 snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32()); 615 616 fd = fopen(tmpfname, "w"); 617 if (!fd) 618 { 619 config_error("ERROR: Could not open/write database '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO)); 620 return 0; 621 } 622 623 if (fprintf(fd, "REPDB 1 %lld %lld\n", (long long)reputation_starttime, (long long)TStime()) < 0) 624 goto write_fail; 625 626 for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++) 627 { 628 for (e = ReputationHashTable[i]; e; e = e->next) 629 { 630 if (fprintf(fd, "%s %d %lld\n", e->ip, (int)e->score, (long long)e->last_seen) < 0) 631 { 632 write_fail: 633 config_error("ERROR writing to '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO)); 634 fclose(fd); 635 return 0; 636 } 637 } 638 } 639 640 if (fclose(fd) < 0) 641 { 642 config_error("ERROR writing to '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO)); 643 return 0; 644 } 645 646 /* Everything went fine. We rename our temporary file to the existing 647 * DB file (will overwrite), which is more or less an atomic operation. 648 */ 649 #ifdef _WIN32 650 /* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */ 651 unlink(cfg.database); 652 #endif 653 if (rename(tmpfname, cfg.database) < 0) 654 { 655 config_error("ERROR renaming '%s' to '%s': %s -- DATABASE *NOT* SAVED!!!", 656 tmpfname, cfg.database, strerror(ERRNO)); 657 return 0; 658 } 659 660 reputation_writtentime = TStime(); 661 662 #ifdef BENCHMARK 663 gettimeofday(&tv_beta, NULL); 664 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL, 665 "Reputation benchmark: SAVE DB: $time_msec microseconds", 666 log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec))); 667 #endif 668 669 return 1; 670 } 671 672 int reputation_save_db(void) 673 { 674 UnrealDB *db; 675 char tmpfname[512]; 676 int i; 677 uint64_t count; 678 ReputationEntry *e; 679 #ifdef BENCHMARK 680 struct timeval tv_alpha, tv_beta; 681 682 gettimeofday(&tv_alpha, NULL); 683 #endif 684 685 #ifdef TEST 686 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_TEST", NULL, "Reputation in running in test mode. Saving DB's...."); 687 #endif 688 689 /* Comment this out after one or more releases (means you cannot downgrade to <=5.0.9.1 anymore) */ 690 if (cfg.db_secret == NULL) 691 return reputation_save_db_old(); 692 693 /* We write to a temporary file. Only to rename it later if everything was ok */ 694 snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32()); 695 696 db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret); 697 if (!db) 698 { 699 WARN_WRITE_ERROR(tmpfname); 700 return 0; 701 } 702 703 /* Write header */ 704 W_SAFE(unrealdb_write_int64(db, 2)); /* reputation db version */ 705 W_SAFE(unrealdb_write_int64(db, reputation_starttime)); /* starttime of data gathering */ 706 W_SAFE(unrealdb_write_int64(db, TStime())); /* current time */ 707 708 /* Count entries */ 709 count = 0; 710 for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++) 711 for (e = ReputationHashTable[i]; e; e = e->next) 712 count++; 713 W_SAFE(unrealdb_write_int64(db, count)); /* Number of DB entries */ 714 715 /* Now write the actual individual entries: */ 716 for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++) 717 { 718 for (e = ReputationHashTable[i]; e; e = e->next) 719 { 720 W_SAFE(unrealdb_write_str(db, e->ip)); 721 W_SAFE(unrealdb_write_int16(db, e->score)); 722 W_SAFE(unrealdb_write_int64(db, e->last_seen)); 723 } 724 } 725 726 if (!unrealdb_close(db)) 727 { 728 WARN_WRITE_ERROR(tmpfname); 729 return 0; 730 } 731 732 /* Everything went fine. We rename our temporary file to the existing 733 * DB file (will overwrite), which is more or less an atomic operation. 734 */ 735 #ifdef _WIN32 736 /* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */ 737 unlink(cfg.database); 738 #endif 739 if (rename(tmpfname, cfg.database) < 0) 740 { 741 config_error("ERROR renaming '%s' to '%s': %s -- DATABASE *NOT* SAVED!!!", 742 tmpfname, cfg.database, strerror(ERRNO)); 743 return 0; 744 } 745 746 reputation_writtentime = TStime(); 747 748 #ifdef BENCHMARK 749 gettimeofday(&tv_beta, NULL); 750 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL, 751 "Reputation benchmark: SAVE DB: $time_msec microseconds", 752 log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec))); 753 #endif 754 return 1; 755 } 756 757 static uint64_t hash_reputation_entry(const char *ip) 758 { 759 return siphash(ip, siphashkey_reputation) % REPUTATION_HASH_TABLE_SIZE; 760 } 761 762 void add_reputation_entry(ReputationEntry *e) 763 { 764 int hashv = hash_reputation_entry(e->ip); 765 766 AddListItem(e, ReputationHashTable[hashv]); 767 } 768 769 ReputationEntry *find_reputation_entry(const char *ip) 770 { 771 ReputationEntry *e; 772 int hashv = hash_reputation_entry(ip); 773 774 for (e = ReputationHashTable[hashv]; e; e = e->next) 775 if (!strcmp(e->ip, ip)) 776 return e; 777 778 return NULL; 779 } 780 781 int reputation_lookup_score_and_set(Client *client) 782 { 783 char *ip = client->ip; 784 ReputationEntry *e; 785 786 Reputation(client) = 0; /* (re-)set to zero (yes, important!) */ 787 if (ip) 788 { 789 e = find_reputation_entry(ip); 790 if (e) 791 { 792 Reputation(client) = e->score; /* SET MODDATA */ 793 } 794 } 795 return Reputation(client); 796 } 797 798 /** Called when the user connects. 799 * Locally: very early, just after the TCP/IP connection has 800 * been established, before any data. 801 * Remote user: early in the HOOKTYPE_REMOTE_CONNECT hook. 802 */ 803 int reputation_set_on_connect(Client *client) 804 { 805 reputation_lookup_score_and_set(client); 806 return 0; 807 } 808 809 int reputation_ip_change(Client *client, const char *oldip) 810 { 811 reputation_lookup_score_and_set(client); 812 return 0; 813 } 814 815 int reputation_pre_lconnect(Client *client) 816 { 817 /* User will likely be accepted. Inform other servers about the score 818 * we have for this user. For more information about this type of 819 * server to server traffic, see the reputation_server_cmd function. 820 * 821 * Note that we use reputation_lookup_score_and_set() here 822 * and not Reputation(client) because we want to RE-LOOKUP 823 * the score for the IP in the database. We do this because 824 * between reputation_set_on_connect() and reputation_pre_lconnect() 825 * the IP of the user may have been changed due to IP-spoofing 826 * (WEBIRC). 827 */ 828 int score = reputation_lookup_score_and_set(client); 829 830 sendto_server(NULL, 0, 0, NULL, ":%s REPUTATION %s %d", me.id, GetIP(client), score); 831 832 return 0; 833 } 834 835 EVENT(add_scores) 836 { 837 static int marker = 0; 838 char *ip; 839 Client *client; 840 ReputationEntry *e; 841 842 /* This marker is used so we only bump score for an IP entry 843 * once and not twice (or more) if there are multiple users 844 * with the same IP address. 845 */ 846 marker += 2; 847 848 /* These macros make the code below easier to read. Also, 849 * this explains why we just did marker+=2 and not marker++. 850 */ 851 #define MARKER_UNREGISTERED_USER (marker) 852 #define MARKER_REGISTERED_USER (marker+1) 853 854 list_for_each_entry(client, &client_list, client_node) 855 { 856 if (!IsUser(client)) 857 continue; /* skip servers, unknowns, etc.. */ 858 859 ip = client->ip; 860 if (!ip) 861 continue; 862 863 e = find_reputation_entry(ip); 864 if (!e) 865 { 866 /* Create */ 867 e = safe_alloc(sizeof(ReputationEntry)+strlen(ip)); 868 strcpy(e->ip, ip); /* safe, allocated above */ 869 add_reputation_entry(e); 870 } 871 872 /* If this is not a duplicate entry, then bump the score.. */ 873 if ((e->marker != MARKER_UNREGISTERED_USER) && (e->marker != MARKER_REGISTERED_USER)) 874 { 875 e->marker = MARKER_UNREGISTERED_USER; 876 if (e->score < REPUTATION_SCORE_CAP) 877 { 878 /* Regular users receive a point. */ 879 e->score++; 880 /* Registered users receive an additional point */ 881 if (IsLoggedIn(client) && (e->score < REPUTATION_SCORE_CAP)) 882 { 883 e->score++; 884 e->marker = MARKER_REGISTERED_USER; 885 } 886 } 887 } else 888 if ((e->marker == MARKER_UNREGISTERED_USER) && IsLoggedIn(client) && (e->score < REPUTATION_SCORE_CAP)) 889 { 890 /* This is to catch a special case: 891 * If there are 2 or more users with the same IP 892 * address and the first user was not registered 893 * then the IP entry only received a score bump of +1. 894 * If the 2nd user (with same IP) is a registered 895 * user then the IP should actually receive a 896 * score bump of +2 (in total). 897 */ 898 e->score++; 899 e->marker = MARKER_REGISTERED_USER; 900 } 901 902 e->last_seen = TStime(); 903 Reputation(client) = e->score; /* update moddata */ 904 } 905 } 906 907 /** Is this entry expired? */ 908 static inline int is_reputation_expired(ReputationEntry *e) 909 { 910 int i; 911 for (i = 0; i < MAXEXPIRES; i++) 912 { 913 if (cfg.expire_time[i] == 0) 914 break; /* end of all entries */ 915 if ((e->score <= cfg.expire_score[i]) && (TStime() - e->last_seen > cfg.expire_time[i])) 916 return 1; 917 } 918 return 0; 919 } 920 921 /** If the reputation changed (due to server syncing) then update the 922 * individual users reputation score as well. 923 */ 924 void reputation_changed_update_users(ReputationEntry *e) 925 { 926 Client *client; 927 928 list_for_each_entry(client, &client_list, client_node) 929 { 930 if (client->ip && !strcmp(e->ip, client->ip)) 931 { 932 /* With some (possibly unneeded) care to only go forward */ 933 if (Reputation(client) < e->score) 934 Reputation(client) = e->score; 935 } 936 } 937 } 938 939 EVENT(delete_old_records) 940 { 941 int i; 942 ReputationEntry *e, *e_next; 943 #ifdef BENCHMARK 944 struct timeval tv_alpha, tv_beta; 945 946 gettimeofday(&tv_alpha, NULL); 947 #endif 948 949 for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++) 950 { 951 for (e = ReputationHashTable[i]; e; e = e_next) 952 { 953 e_next = e->next; 954 955 if (is_reputation_expired(e)) 956 { 957 #ifdef DEBUGMODE 958 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_EXPIRY", NULL, 959 "Deleting expired entry for $ip (score $score, last seen $time_delta seconds ago)", 960 log_data_string("ip", e->ip), 961 log_data_integer("score", e->score), 962 log_data_integer("time_delta", TStime() - e->last_seen)); 963 #endif 964 DelListItem(e, ReputationHashTable[i]); 965 safe_free(e); 966 } 967 } 968 } 969 970 #ifdef BENCHMARK 971 gettimeofday(&tv_beta, NULL); 972 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL, 973 "Reputation benchmark: EXPIRY IN MEM: $time_msec microseconds", 974 log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec))); 975 #endif 976 } 977 978 EVENT(reputation_save_db_evt) 979 { 980 reputation_save_db(); 981 } 982 983 CMD_FUNC(reputationunperm) 984 { 985 if (!IsOper(client)) 986 { 987 sendnumeric(client, ERR_NOPRIVILEGES); 988 return; 989 } 990 991 ModuleSetOptions(ModInf.handle, MOD_OPT_PERM, 0); 992 993 unreal_log(ULOG_INFO, "reputation", "REPUTATIONUNPERM_COMMAND", client, 994 "$client used /REPUTATIONUNPERM. On next REHASH the module can be RELOADED or UNLOADED. " 995 "Note however that for a few minutes the scoring may be skipped, so don't do this too often."); 996 } 997 998 int reputation_connect_extinfo(Client *client, NameValuePrioList **list) 999 { 1000 add_fmt_nvplist(list, 0, "reputation", "%d", GetReputation(client)); 1001 return 0; 1002 } 1003 1004 int count_reputation_records(void) 1005 { 1006 int i; 1007 ReputationEntry *e; 1008 int total = 0; 1009 1010 for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++) 1011 for (e = ReputationHashTable[i]; e; e = e->next) 1012 total++; 1013 1014 return total; 1015 } 1016 1017 void reputation_channel_query(Client *client, Channel *channel) 1018 { 1019 Member *m; 1020 char buf[512]; 1021 char tbuf[256]; 1022 char **nicks; 1023 int *scores; 1024 int cnt = 0, i, j; 1025 ReputationEntry *e; 1026 1027 sendtxtnumeric(client, "Users and reputation scores for %s:", channel->name); 1028 1029 /* Step 1: build a list of nicks and their reputation */ 1030 nicks = safe_alloc((channel->users+1) * sizeof(char *)); 1031 scores = safe_alloc((channel->users+1) * sizeof(int)); 1032 for (m = channel->members; m; m = m->next) 1033 { 1034 nicks[cnt] = m->client->name; 1035 if (m->client->ip) 1036 { 1037 e = find_reputation_entry(m->client->ip); 1038 if (e) 1039 scores[cnt] = e->score; 1040 } 1041 if (++cnt > channel->users) 1042 { 1043 unreal_log(ULOG_WARNING, "bug", "REPUTATION_CHANNEL_QUERY_BUG", client, 1044 "[BUG] reputation_channel_query() expected $expected_users users, but $found_users (or more) users were present in $channel", 1045 log_data_integer("expected_users", channel->users), 1046 log_data_integer("found_users", cnt), 1047 log_data_string("channel", channel->name)); 1048 #ifdef DEBUGMODE 1049 abort(); 1050 #endif 1051 break; /* safety net */ 1052 } 1053 } 1054 1055 /* Step 2: lazy selection sort */ 1056 for (i = 0; i < cnt && nicks[i]; i++) 1057 { 1058 for (j = i+1; j < cnt && nicks[j]; j++) 1059 { 1060 if (scores[i] < scores[j]) 1061 { 1062 char *nick_tmp; 1063 int score_tmp; 1064 nick_tmp = nicks[i]; 1065 score_tmp = scores[i]; 1066 nicks[i] = nicks[j]; 1067 scores[i] = scores[j]; 1068 nicks[j] = nick_tmp; 1069 scores[j] = score_tmp; 1070 } 1071 } 1072 } 1073 1074 /* Step 3: send the (ordered) list to the user */ 1075 *buf = '\0'; 1076 for (i = 0; i < cnt && nicks[i]; i++) 1077 { 1078 snprintf(tbuf, sizeof(tbuf), "%s\00314(%d)\003 ", nicks[i], scores[i]); 1079 if ((strlen(tbuf)+strlen(buf) > 400) || !nicks[i+1]) 1080 { 1081 sendtxtnumeric(client, "%s%s", buf, tbuf); 1082 *buf = '\0'; 1083 } else { 1084 strlcat(buf, tbuf, sizeof(buf)); 1085 } 1086 } 1087 sendtxtnumeric(client, "End of list."); 1088 safe_free(nicks); 1089 safe_free(scores); 1090 } 1091 1092 void reputation_list_query(Client *client, int maxscore) 1093 { 1094 Client *target; 1095 ReputationEntry *e; 1096 1097 sendtxtnumeric(client, "Users and reputation scores <%d:", maxscore); 1098 1099 list_for_each_entry(target, &client_list, client_node) 1100 { 1101 int score = 0; 1102 1103 if (!IsUser(target) || IsULine(target) || !target->ip) 1104 continue; 1105 1106 e = find_reputation_entry(target->ip); 1107 if (e) 1108 score = e->score; 1109 if (score >= maxscore) 1110 continue; 1111 sendtxtnumeric(client, "%s!%s@%s [%s] \017(score: %d)", 1112 target->name, 1113 target->user->username, 1114 target->user->realhost, 1115 target->ip, 1116 score); 1117 } 1118 sendtxtnumeric(client, "End of list."); 1119 } 1120 1121 CMD_FUNC(reputation_user_cmd) 1122 { 1123 ReputationEntry *e; 1124 const char *ip; 1125 1126 if (!IsOper(client)) 1127 { 1128 sendnumeric(client, ERR_NOPRIVILEGES); 1129 return; 1130 } 1131 1132 if ((parc < 2) || BadPtr(parv[1])) 1133 { 1134 sendnotice(client, "Reputation module statistics:"); 1135 sendnotice(client, "Recording for: %lld seconds (since unixtime %lld)", 1136 (long long)(TStime() - reputation_starttime), 1137 (long long)reputation_starttime); 1138 if (reputation_writtentime) 1139 { 1140 sendnotice(client, "Last successful db write: %lld seconds ago (unixtime %lld)", 1141 (long long)(TStime() - reputation_writtentime), 1142 (long long)reputation_writtentime); 1143 } else { 1144 sendnotice(client, "Last successful db write: never"); 1145 } 1146 sendnotice(client, "Current number of records (IP's): %d", count_reputation_records()); 1147 sendnotice(client, "-"); 1148 sendnotice(client, "Available commands:"); 1149 sendnotice(client, "/REPUTATION [nick] Show reputation info about nick name"); 1150 sendnotice(client, "/REPUTATION [ip] Show reputation info about IP address"); 1151 sendnotice(client, "/REPUTATION [channel] List users in channel along with their reputation score"); 1152 sendnotice(client, "/REPUTATION <NN List users with reputation score below value NN"); 1153 return; 1154 } 1155 1156 if (strchr(parv[1], '.') || strchr(parv[1], ':')) 1157 { 1158 ip = parv[1]; 1159 } else 1160 if (parv[1][0] == '#') 1161 { 1162 Channel *channel = find_channel(parv[1]); 1163 if (!channel) 1164 { 1165 sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]); 1166 return; 1167 } 1168 /* corner case: ircop without proper permissions and not in channel */ 1169 if (!ValidatePermissionsForPath("channel:see:names:invisible",client,NULL,NULL,NULL) && !IsMember(client,channel)) 1170 { 1171 sendnumeric(client, ERR_NOTONCHANNEL, channel->name); 1172 return; 1173 } 1174 reputation_channel_query(client, channel); 1175 return; 1176 } else 1177 if (parv[1][0] == '<') 1178 { 1179 int max = atoi(parv[1] + 1); 1180 if (max < 1) 1181 { 1182 sendnotice(client, "REPUTATION: Invalid search value specified. Use for example '/REPUTATION <5' to search on less-than-five"); 1183 return; 1184 } 1185 reputation_list_query(client, max); 1186 return; 1187 } else { 1188 Client *target = find_user(parv[1], NULL); 1189 if (!target) 1190 { 1191 sendnumeric(client, ERR_NOSUCHNICK, parv[1]); 1192 return; 1193 } 1194 ip = target->ip; 1195 if (!ip) 1196 { 1197 sendnotice(client, "No IP address information available for user '%s'.", parv[1]); /* e.g. services */ 1198 return; 1199 } 1200 } 1201 1202 e = find_reputation_entry(ip); 1203 if (!e) 1204 { 1205 sendnotice(client, "No reputation record found for IP %s", ip); 1206 return; 1207 } 1208 1209 sendnotice(client, "****************************************************"); 1210 sendnotice(client, "Reputation record for IP %s:", ip); 1211 sendnotice(client, " Score: %hd", e->score); 1212 sendnotice(client, "Last seen: %lld seconds ago (unixtime: %lld)", 1213 (long long)(TStime() - e->last_seen), 1214 (long long)e->last_seen); 1215 sendnotice(client, "****************************************************"); 1216 } 1217 1218 /** The REPUTATION server command handler. 1219 * Syntax: :server REPUTATION <ip> <score> 1220 * Where the <score> may be prefixed by an asterisk (*). 1221 * 1222 * The best way to explain this command is to illustrate by example: 1223 * :servera REPUTATION 1.2.3.4 0 1224 * Then serverb, which might have a score of 2 for this IP, will: 1225 * - Send back to the servera direction: :serverb REPUTATION 1.2.3.4 *2 1226 * So the original server (and direction) receive a score update. 1227 * - Propagate to non-servera direction: :servera REPUTATION 1.2.3.4 2 1228 * So use the new higher score (2 rather than 0). 1229 * Then the next server may do the same. It MUST propagate to non-serverb 1230 * direction and MAY (again) update the score even higher. 1231 * 1232 * If the score is not prefixed by * then the server may do as above and 1233 * send back to the uplink an "update" of the score. If, however, the 1234 * score is prefixed by * then the server will NEVER send back to the 1235 * uplink, it may only propagate. This is to prevent loops. 1236 * 1237 * Note that some margin is used when deciding if the server should send 1238 * back score updates. This is defined by UPDATE_SCORE_MARGIN. 1239 * If this is for example set to 1 then a point difference of 1 will not 1240 * yield a score update since such a minor score update is not worth the 1241 * server to server traffic. Also, due to timing differences a score 1242 * difference of 1 is quite likely to hapen in normal circumstances. 1243 */ 1244 CMD_FUNC(reputation_server_cmd) 1245 { 1246 ReputationEntry *e; 1247 const char *ip; 1248 int score; 1249 int allow_reply; 1250 1251 /* :server REPUTATION <ip> <score> */ 1252 if ((parc < 3) || BadPtr(parv[2])) 1253 { 1254 sendnumeric(client, ERR_NEEDMOREPARAMS, "REPUTATION"); 1255 return; 1256 } 1257 1258 ip = parv[1]; 1259 1260 if (parv[2][0] == '*') 1261 { 1262 allow_reply = 0; 1263 score = atoi(parv[2]+1); 1264 } else { 1265 allow_reply = 1; 1266 score = atoi(parv[2]); 1267 } 1268 1269 if (score > REPUTATION_SCORE_CAP) 1270 score = REPUTATION_SCORE_CAP; 1271 1272 e = find_reputation_entry(ip); 1273 if (allow_reply && e && (e->score > score) && (e->score - score > UPDATE_SCORE_MARGIN)) 1274 { 1275 /* We have a higher score, inform the client direction about it. 1276 * This will prefix the score with a * so servers will never reply to it. 1277 */ 1278 sendto_one(client, NULL, ":%s REPUTATION %s *%d", me.id, parv[1], e->score); 1279 #ifdef DEBUGMODE 1280 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_DIFFERS", client, 1281 "Reputation score for for $ip from $client is $their_score, but we have $score, sending back $score", 1282 log_data_string("ip", ip), 1283 log_data_integer("their_score", score), 1284 log_data_integer("score", e->score)); 1285 #endif 1286 score = e->score; /* Update for propagation in the non-client direction */ 1287 } 1288 1289 /* Update our score if sender has a higher score */ 1290 if (e && (score > e->score)) 1291 { 1292 #ifdef DEBUGMODE 1293 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_DIFFERS", client, 1294 "Reputation score for for $ip from $client is $their_score, but we have $score, updating our score to $score", 1295 log_data_string("ip", ip), 1296 log_data_integer("their_score", score), 1297 log_data_integer("score", e->score)); 1298 #endif 1299 e->score = score; 1300 reputation_changed_update_users(e); 1301 } 1302 1303 /* If we don't have any entry for this IP, add it now. */ 1304 if (!e && (score > 0)) 1305 { 1306 #ifdef DEBUGMODE 1307 unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_NEW", client, 1308 "Reputation score for for $ip from $client is $their_score, we had no entry, adding it", 1309 log_data_string("ip", ip), 1310 log_data_integer("their_score", score), 1311 log_data_integer("score", 0)); 1312 #endif 1313 e = safe_alloc(sizeof(ReputationEntry)+strlen(ip)); 1314 strcpy(e->ip, ip); /* safe, see alloc above */ 1315 e->score = score; 1316 e->last_seen = TStime(); 1317 add_reputation_entry(e); 1318 reputation_changed_update_users(e); 1319 } 1320 1321 /* Propagate to the non-client direction (score may be updated) */ 1322 sendto_server(client, 0, 0, NULL, 1323 ":%s REPUTATION %s %s%d", 1324 client->id, 1325 parv[1], 1326 allow_reply ? "" : "*", 1327 score); 1328 } 1329 1330 CMD_FUNC(reputation_cmd) 1331 { 1332 if (MyUser(client)) 1333 CALL_CMD_FUNC(reputation_user_cmd); 1334 else if (IsServer(client) || IsMe(client)) 1335 CALL_CMD_FUNC(reputation_server_cmd); 1336 } 1337 1338 int reputation_whois(Client *client, Client *target, NameValuePrioList **list) 1339 { 1340 int reputation; 1341 1342 if (whois_get_policy(client, target, "reputation") != WHOIS_CONFIG_DETAILS_FULL) 1343 return 0; 1344 1345 reputation = Reputation(target); 1346 if (reputation > 0) 1347 { 1348 add_nvplist_numeric_fmt(list, 0, "reputation", client, RPL_WHOISSPECIAL, 1349 "%s :is using an IP with a reputation score of %d", 1350 target->name, reputation); 1351 } 1352 return 0; 1353 } 1354 1355 void reputation_md_free(ModData *m) 1356 { 1357 /* we have nothing to free actually, but we must set to zero */ 1358 m->l = 0; 1359 } 1360 1361 const char *reputation_md_serialize(ModData *m) 1362 { 1363 static char buf[32]; 1364 if (m->i == 0) 1365 return NULL; /* not set (reputation always starts at 1) */ 1366 snprintf(buf, sizeof(buf), "%d", m->i); 1367 return buf; 1368 } 1369 1370 void reputation_md_unserialize(const char *str, ModData *m) 1371 { 1372 m->i = atoi(str); 1373 } 1374 1375 int reputation_starttime_callback(void) 1376 { 1377 /* NB: fix this by 2038 */ 1378 return (int)reputation_starttime; 1379 }