unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
log.c (47489B)
1 /************************************************************************ 2 * IRC - Internet Relay Chat, src/api-channelmode.c 3 * (C) 2021 Bram Matthys (Syzop) and the UnrealIRCd Team 4 * 5 * See file AUTHORS in IRC package for additional names of 6 * the programmers. 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 1, or (at your option) 11 * any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 */ 22 23 /** @file 24 * @brief The logging API 25 */ 26 27 #define UNREAL_LOGGER_CODE 28 #include "unrealircd.h" 29 30 // TODO: Make configurable at compile time (runtime won't do, as we haven't read the config file) 31 #define show_event_console 0 32 33 #define MAXLOGLENGTH 16384 /**< Maximum length of a log entry (which may be multiple lines) */ 34 35 /* Variables */ 36 Log *logs[NUM_LOG_DESTINATIONS] = { NULL, NULL, NULL, NULL, NULL }; 37 Log *temp_logs[NUM_LOG_DESTINATIONS] = { NULL, NULL, NULL, NULL, NULL }; 38 static int snomask_num_destinations = 0; 39 40 static char snomasks_in_use[257] = { '\0' }; 41 static char snomasks_in_use_testing[257] = { '\0' }; 42 43 /* Forward declarations */ 44 int log_sources_match(LogSource *logsource, LogLevel loglevel, const char *subsystem, const char *event_id, int matched_already); 45 void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char *event_id, Client *client, int expand_msg, const char *msg, va_list vl); 46 void log_blocks_switchover(void); 47 48 LogType log_type_stringtoval(const char *str) 49 { 50 if (!strcmp(str, "json")) 51 return LOG_TYPE_JSON; 52 if (!strcmp(str, "text")) 53 return LOG_TYPE_TEXT; 54 return LOG_TYPE_INVALID; 55 } 56 57 const char *log_type_valtostring(LogType v) 58 { 59 switch(v) 60 { 61 case LOG_TYPE_TEXT: 62 return "text"; 63 case LOG_TYPE_JSON: 64 return "json"; 65 default: 66 return NULL; 67 } 68 } 69 70 /** Checks if 'v' is a valid loglevel */ 71 int valid_loglevel(int v) 72 { 73 if ((v == ULOG_DEBUG) || (v == ULOG_INFO) || 74 (v == ULOG_WARNING) || (v == ULOG_ERROR) || 75 (v == ULOG_FATAL)) 76 { 77 return 1; 78 } 79 return 0; 80 } 81 82 /***** CONFIGURATION ******/ 83 84 LogSource *add_log_source(const char *str) 85 { 86 LogSource *ls; 87 char buf[256]; 88 char *p; 89 LogLevel loglevel = ULOG_INVALID; 90 char *subsystem = NULL; 91 char *event_id = NULL; 92 int negative = 0; 93 94 if (*str == '!') 95 { 96 negative = 1; 97 strlcpy(buf, str+1, sizeof(buf)); 98 } else 99 { 100 strlcpy(buf, str, sizeof(buf)); 101 } 102 103 p = strchr(buf, '.'); 104 if (p) 105 *p++ = '\0'; 106 107 loglevel = log_level_stringtoval(buf); 108 if (loglevel == ULOG_INVALID) 109 { 110 if (isupper(*buf)) 111 event_id = buf; 112 else 113 subsystem = buf; 114 } 115 if (p) 116 { 117 if (isupper(*p)) 118 { 119 event_id = p; 120 } else 121 if (loglevel == ULOG_INVALID) 122 { 123 loglevel = log_level_stringtoval(p); 124 if ((loglevel == ULOG_INVALID) && !subsystem) 125 subsystem = p; 126 } else if (!subsystem) 127 { 128 subsystem = p; 129 } 130 } 131 ls = safe_alloc(sizeof(LogSource)); 132 ls->loglevel = loglevel; 133 ls->negative = negative; 134 if (!BadPtr(subsystem)) 135 strlcpy(ls->subsystem, subsystem, sizeof(ls->subsystem)); 136 if (!BadPtr(event_id)) 137 strlcpy(ls->event_id, event_id, sizeof(ls->event_id)); 138 139 return ls; 140 } 141 142 void free_log_source(LogSource *l) 143 { 144 safe_free(l); 145 } 146 147 void free_log_sources(LogSource *l) 148 { 149 LogSource *l_next; 150 for (; l; l = l_next) 151 { 152 l_next = l->next; 153 free_log_source(l); 154 } 155 } 156 157 int config_test_log(ConfigFile *conf, ConfigEntry *block) 158 { 159 int errors = 0; 160 int any_sources = 0; 161 ConfigEntry *ce, *cep, *cepp; 162 int destinations = 0; 163 164 for (ce = block->items; ce; ce = ce->next) 165 { 166 if (!strcmp(ce->name, "source")) 167 { 168 for (cep = ce->items; cep; cep = cep->next) 169 { 170 /* TODO: Validate the sources lightly for formatting issues */ 171 any_sources = 1; 172 } 173 } else 174 if (!strcmp(ce->name, "destination")) 175 { 176 for (cep = ce->items; cep; cep = cep->next) 177 { 178 if (!strcmp(cep->name, "snomask")) 179 { 180 destinations++; 181 snomask_num_destinations++; 182 /* We need to validate the parameter here as well */ 183 if (!cep->value) 184 { 185 config_error_blank(cep->file->filename, cep->line_number, "set::logging::snomask"); 186 errors++; 187 } else 188 if ((strlen(cep->value) != 1) || !(islower(cep->value[0]) || isupper(cep->value[0]))) 189 { 190 config_error("%s:%d: snomask must be a single letter", 191 cep->file->filename, cep->line_number); 192 errors++; 193 } else { 194 strlcat(snomasks_in_use_testing, cep->value, sizeof(snomasks_in_use_testing)); 195 } 196 } else 197 if (!strcmp(cep->name, "channel")) 198 { 199 destinations++; 200 /* We need to validate the parameter here as well */ 201 if (!cep->value) 202 { 203 config_error_blank(cep->file->filename, cep->line_number, "set::logging::channel"); 204 errors++; 205 } else 206 if (!valid_channelname(cep->value)) 207 { 208 config_error("%s:%d: Invalid channel name '%s'", 209 cep->file->filename, cep->line_number, cep->value); 210 errors++; 211 } 212 for (cepp = cep->items; cepp; cepp = cepp->next) 213 { 214 if (!strcmp(cepp->name, "color")) 215 ; 216 else if (!strcmp(cepp->name, "show-event")) 217 ; 218 else if (!strcmp(cepp->name, "json-message-tag")) 219 ; 220 else if (!strcmp(cepp->name, "oper-only")) 221 ; 222 else 223 { 224 config_error_unknown(cepp->file->filename, cepp->line_number, "log::destination::channel", cepp->name); 225 errors++; 226 } 227 } 228 } else 229 if (!strcmp(cep->name, "file")) 230 { 231 destinations++; 232 if (!cep->value) 233 { 234 config_error_blank(cep->file->filename, cep->line_number, "set::logging::file"); 235 errors++; 236 continue; 237 } 238 convert_to_absolute_path(&cep->value, LOGDIR); 239 for (cepp = cep->items; cepp; cepp = cepp->next) 240 { 241 if (!strcmp(cepp->name, "type")) 242 { 243 if (!cepp->value) 244 { 245 config_error_empty(cepp->file->filename, 246 cepp->line_number, "log", cepp->name); 247 errors++; 248 continue; 249 } 250 if (!log_type_stringtoval(cepp->value)) 251 { 252 config_error("%s:%i: unknown log type '%s'", 253 cepp->file->filename, cepp->line_number, 254 cepp->value); 255 errors++; 256 } 257 } else 258 if (!strcmp(cepp->name, "maxsize")) 259 { 260 if (!cepp->value) 261 { 262 config_error_empty(cepp->file->filename, 263 cepp->line_number, "log", cepp->name); 264 errors++; 265 } 266 } else 267 { 268 config_error_unknown(cepp->file->filename, cepp->line_number, "log::destination::file", cepp->name); 269 errors++; 270 } 271 } 272 } else 273 if (!strcmp(cep->name, "remote")) 274 { 275 destinations++; 276 } else 277 if (!strcmp(cep->name, "syslog")) 278 { 279 destinations++; 280 for (cepp = cep->items; cepp; cepp = cepp->next) 281 { 282 if (!strcmp(cepp->name, "type")) 283 { 284 if (!cepp->value) 285 { 286 config_error_empty(cepp->file->filename, 287 cepp->line_number, "log", cepp->name); 288 errors++; 289 continue; 290 } 291 if (!log_type_stringtoval(cepp->value)) 292 { 293 config_error("%s:%i: unknown log type '%s'", 294 cepp->file->filename, cepp->line_number, 295 cepp->value); 296 errors++; 297 } 298 } else 299 { 300 config_error_unknown(cepp->file->filename, cepp->line_number, "log::destination::syslog", cepp->name); 301 errors++; 302 } 303 } 304 } else 305 { 306 config_error_unknownopt(cep->file->filename, cep->line_number, "log::destination", cep->name); 307 errors++; 308 continue; 309 } 310 } 311 } else 312 { 313 config_error_unknown(ce->file->filename, ce->line_number, "log", ce->name); 314 errors++; 315 } 316 } 317 318 if (!any_sources && !destinations) 319 { 320 unreal_log(ULOG_ERROR, "config", "CONFIG_OLD_LOG_BLOCK", NULL, 321 "$config_file:$line_number: Your log block contains no sources and no destinations.\n" 322 "The log block changed between UnrealIRCd 5 and UnrealIRCd 6, " 323 "see https://www.unrealircd.org/docs/FAQ#old-log-block on how " 324 "to convert it to the new syntax.", 325 log_data_string("config_file", block->file->filename), 326 log_data_integer("line_number", block->line_number)); 327 errors++; 328 return errors; 329 } 330 331 if (!any_sources) 332 { 333 config_error("%s:%d: log block contains no sources. Old log block perhaps?", 334 block->file->filename, block->line_number); 335 errors++; 336 } 337 if (destinations == 0) 338 { 339 config_error("%s:%d: log block contains no destinations. Old log block perhaps?", 340 block->file->filename, block->line_number); 341 errors++; 342 } 343 if (destinations > 1) 344 { 345 config_error("%s:%d: log block contains multiple destinations. This is not support... YET!", 346 block->file->filename, block->line_number); 347 errors++; 348 } 349 return errors; 350 } 351 352 int config_run_log(ConfigFile *conf, ConfigEntry *block) 353 { 354 ConfigEntry *ce, *cep, *cepp; 355 LogSource *sources = NULL; 356 int type; 357 358 /* If we later allow multiple destination entries later, 359 * then we need to 'clone' sources or work with reference counts. 360 */ 361 362 /* First, gather the source... */ 363 for (ce = block->items; ce; ce = ce->next) 364 { 365 if (!strcmp(ce->name, "source")) 366 { 367 LogSource *s; 368 for (cep = ce->items; cep; cep = cep->next) 369 { 370 s = add_log_source(cep->name); 371 AddListItem(s, sources); 372 } 373 } 374 } 375 376 /* Now deal with destinations... */ 377 for (ce = block->items; ce; ce = ce->next) 378 { 379 if (!strcmp(ce->name, "destination")) 380 { 381 for (cep = ce->items; cep; cep = cep->next) 382 { 383 if (!strcmp(cep->name, "snomask")) 384 { 385 Log *log = safe_alloc(sizeof(Log)); 386 strlcpy(log->destination, cep->value, sizeof(log->destination)); /* destination is the snomask */ 387 strlcat(snomasks_in_use, cep->value, sizeof(snomasks_in_use)); 388 log->sources = sources; 389 if (!strcmp(cep->value, "s")) 390 AddListItem(log, temp_logs[LOG_DEST_OPER]); 391 else 392 AddListItem(log, temp_logs[LOG_DEST_SNOMASK]); 393 } else 394 if (!strcmp(cep->name, "channel")) 395 { 396 Log *log = safe_alloc(sizeof(Log)); 397 strlcpy(log->destination, cep->value, sizeof(log->destination)); /* destination is the channel */ 398 log->sources = sources; 399 AddListItem(log, temp_logs[LOG_DEST_CHANNEL]); 400 /* set defaults */ 401 log->color = tempiConf.server_notice_colors; 402 log->show_event = tempiConf.server_notice_show_event; 403 log->json_message_tag = 1; 404 log->oper_only = 1; 405 /* now parse options (if any) */ 406 for (cepp = cep->items; cepp; cepp = cepp->next) 407 { 408 if (!strcmp(cepp->name, "color")) 409 log->color = config_checkval(cepp->value, CFG_YESNO); 410 else if (!strcmp(cepp->name, "show-event")) 411 log->show_event = config_checkval(cepp->value, CFG_YESNO); 412 else if (!strcmp(cepp->name, "json-message-tag")) 413 log->json_message_tag = config_checkval(cepp->value, CFG_YESNO); 414 else if (!strcmp(cepp->name, "oper-only")) 415 log->oper_only = config_checkval(cepp->value, CFG_YESNO); 416 } 417 } else 418 if (!strcmp(cep->name, "remote")) 419 { 420 Log *log = safe_alloc(sizeof(Log)); 421 /* destination stays empty */ 422 log->sources = sources; 423 AddListItem(log, temp_logs[LOG_DEST_REMOTE]); 424 } else 425 if (!strcmp(cep->name, "file") || !strcmp(cep->name, "syslog")) 426 { 427 Log *log; 428 /* First check if already exists... yeah this is a bit late 429 * and ideally would have been done in config_test but... 430 * that would have been lots of work for a (hopefully) rare case. 431 */ 432 for (log = temp_logs[LOG_DEST_DISK]; log; log = log->next) 433 { 434 if ((log->file && !strcmp(log->file, cep->value)) || 435 (log->filefmt && !strcmp(log->filefmt, cep->value))) 436 { 437 config_warn("%s:%d: Ignoring duplicate log block for file '%s'. " 438 "You cannot have multiple log blocks logging to the same file.", 439 cep->file->filename, cep->line_number, 440 cep->value); 441 free_log_sources(sources); 442 return 0; 443 } 444 } 445 log = safe_alloc(sizeof(Log)); 446 log->sources = sources; 447 log->logfd = -1; 448 log->type = LOG_TYPE_TEXT; /* default */ 449 if (!strcmp(cep->name, "syslog")) 450 safe_strdup(log->file, "syslog"); 451 else if (strchr(cep->value, '%')) 452 safe_strdup(log->filefmt, cep->value); 453 else 454 safe_strdup(log->file, cep->value); 455 for (cepp = cep->items; cepp; cepp = cepp->next) 456 { 457 if (!strcmp(cepp->name, "maxsize")) 458 { 459 log->maxsize = config_checkval(cepp->value,CFG_SIZE); 460 } 461 else if (!strcmp(cepp->name, "type")) 462 { 463 log->type = log_type_stringtoval(cepp->value); 464 } 465 } 466 AddListItem(log, temp_logs[LOG_DEST_DISK]); 467 } 468 } 469 } 470 } 471 472 return 0; 473 } 474 475 LogData *log_data_string(const char *key, const char *str) 476 { 477 LogData *d = safe_alloc(sizeof(LogData)); 478 d->type = LOG_FIELD_STRING; 479 safe_strdup(d->key, key); 480 safe_strdup(d->value.string, str); 481 return d; 482 } 483 484 LogData *log_data_char(const char *key, const char c) 485 { 486 LogData *d = safe_alloc(sizeof(LogData)); 487 d->type = LOG_FIELD_STRING; 488 safe_strdup(d->key, key); 489 d->value.string = safe_alloc(2); 490 d->value.string[0] = c; 491 d->value.string[1] = '\0'; 492 return d; 493 } 494 495 LogData *log_data_integer(const char *key, int64_t integer) 496 { 497 LogData *d = safe_alloc(sizeof(LogData)); 498 d->type = LOG_FIELD_INTEGER; 499 safe_strdup(d->key, key); 500 d->value.integer = integer; 501 return d; 502 } 503 504 LogData *log_data_timestamp(const char *key, time_t ts) 505 { 506 LogData *d = safe_alloc(sizeof(LogData)); 507 d->type = LOG_FIELD_STRING; 508 safe_strdup(d->key, key); 509 safe_strdup(d->value.string, timestamp_iso8601(ts)); 510 return d; 511 } 512 513 LogData *log_data_client(const char *key, Client *client) 514 { 515 LogData *d = safe_alloc(sizeof(LogData)); 516 d->type = LOG_FIELD_CLIENT; 517 safe_strdup(d->key, key); 518 d->value.client = client; 519 return d; 520 } 521 522 LogData *log_data_channel(const char *key, Channel *channel) 523 { 524 LogData *d = safe_alloc(sizeof(LogData)); 525 d->type = LOG_FIELD_CHANNEL; 526 safe_strdup(d->key, key); 527 d->value.channel = channel; 528 return d; 529 } 530 531 LogData *log_data_source(const char *file, int line, const char *function) 532 { 533 LogData *d = safe_alloc(sizeof(LogData)); 534 json_t *j; 535 536 d->type = LOG_FIELD_OBJECT; 537 safe_strdup(d->key, "source"); 538 d->value.object = j = json_object(); 539 json_object_set_new(j, "file", json_string_unreal(file)); 540 json_object_set_new(j, "line", json_integer(line)); 541 json_object_set_new(j, "function", json_string_unreal(function)); 542 return d; 543 } 544 545 LogData *log_data_socket_error(int fd) 546 { 547 /* First, grab the error number very early here: */ 548 #ifndef _WIN32 549 int sockerr = errno; 550 #else 551 int sockerr = WSAGetLastError(); 552 #endif 553 int v; 554 int len = sizeof(v); 555 LogData *d; 556 json_t *j; 557 558 #ifdef SO_ERROR 559 /* Try to get the "real" error from the underlying socket. 560 * If we succeed then we will override "sockerr" with it. 561 */ 562 if ((fd >= 0) && !getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&v, &len) && v) 563 sockerr = v; 564 #endif 565 566 d = safe_alloc(sizeof(LogData)); 567 d->type = LOG_FIELD_OBJECT; 568 safe_strdup(d->key, "socket_error"); 569 d->value.object = j = json_object(); 570 json_object_set_new(j, "error_code", json_integer(sockerr)); 571 json_object_set_new(j, "error_string", json_string_unreal(STRERROR(sockerr))); 572 return d; 573 } 574 575 /** Populate log with the TLS error(s) stack */ 576 LogData *log_data_tls_error(void) 577 { 578 LogData *d; 579 json_t *j; 580 json_t *error_stack; 581 json_t *name = NULL; 582 json_t *jt; 583 unsigned long e; 584 char buf[512]; 585 static char all_errors[8192]; 586 587 d = safe_alloc(sizeof(LogData)); 588 d->type = LOG_FIELD_OBJECT; 589 safe_strdup(d->key, "tls_error"); 590 d->value.object = j = json_object(); 591 592 error_stack = json_array(); 593 json_object_set_new(j, "error_stack", error_stack); 594 *all_errors = '\0'; 595 596 do { 597 json_t *obj; 598 599 e = ERR_get_error(); 600 if (e == 0) 601 break; 602 ERR_error_string_n(e, buf, sizeof(buf)); 603 604 obj = json_object(); 605 json_object_set_new(obj, "code", json_integer(e)); 606 json_object_set_new(obj, "string", json_string_unreal(buf)); 607 json_array_append_new(error_stack, obj); 608 609 if (name == NULL) 610 { 611 /* Set tls_error.name to the first error that was encountered */ 612 json_object_set_new(j, "name", json_string_unreal(buf)); 613 } 614 strlcat(all_errors, buf, sizeof(all_errors)); 615 strlcat(all_errors, "\n", sizeof(all_errors)); 616 } while(e); 617 618 json_object_set_new(j, "all", json_string_unreal(all_errors)); 619 620 return d; 621 } 622 623 LogData *log_data_link_block(ConfigItem_link *link) 624 { 625 LogData *d = safe_alloc(sizeof(LogData)); 626 json_t *j; 627 char *bind_ip; 628 629 d->type = LOG_FIELD_OBJECT; 630 safe_strdup(d->key, "link_block"); 631 d->value.object = j = json_object(); 632 json_object_set_new(j, "name", json_string_unreal(link->servername)); 633 json_object_set_new(j, "file", json_string_unreal(link->outgoing.file)); 634 json_object_set_new(j, "hostname", json_string_unreal(link->outgoing.hostname)); 635 json_object_set_new(j, "ip", json_string_unreal(link->connect_ip)); 636 json_object_set_new(j, "port", json_integer(link->outgoing.port)); 637 json_object_set_new(j, "class", json_string_unreal(link->class->name)); 638 639 if (!link->outgoing.bind_ip && iConf.link_bindip) 640 bind_ip = iConf.link_bindip; 641 else 642 bind_ip = link->outgoing.bind_ip; 643 if (!bind_ip) 644 bind_ip = "*"; 645 json_object_set_new(j, "bind_ip", json_string_unreal(bind_ip)); 646 647 return d; 648 } 649 650 LogData *log_data_tkl(const char *key, TKL *tkl) 651 { 652 char buf[BUFSIZE]; 653 LogData *d = safe_alloc(sizeof(LogData)); 654 json_t *j; 655 656 d->type = LOG_FIELD_OBJECT; 657 safe_strdup(d->key, key); 658 d->value.object = j = json_object(); 659 660 json_expand_tkl(j, NULL, tkl, 1); 661 662 return d; 663 } 664 665 void log_data_free(LogData *d) 666 { 667 if (d->type == LOG_FIELD_STRING) 668 safe_free(d->value.string); 669 else if ((d->type == LOG_FIELD_OBJECT) && d->value.object) 670 json_decref(d->value.object); 671 672 safe_free(d->key); 673 safe_free(d); 674 } 675 676 const char *log_level_valtostring(LogLevel loglevel) 677 { 678 switch(loglevel) 679 { 680 case ULOG_DEBUG: 681 return "debug"; 682 case ULOG_INFO: 683 return "info"; 684 case ULOG_WARNING: 685 return "warn"; 686 case ULOG_ERROR: 687 return "error"; 688 case ULOG_FATAL: 689 return "fatal"; 690 default: 691 return NULL; 692 } 693 } 694 695 static NameValue log_colors_irc[] = { 696 { ULOG_INVALID, "\0030,01" }, 697 { ULOG_DEBUG, "\0030,01" }, 698 { ULOG_INFO, "\00303" }, 699 { ULOG_WARNING, "\00307" }, 700 { ULOG_ERROR, "\00304" }, 701 { ULOG_FATAL, "\00313" }, 702 }; 703 704 static NameValue log_colors_terminal[] = { 705 { ULOG_INVALID, "\033[90m" }, 706 { ULOG_DEBUG, "\033[37m" }, 707 { ULOG_INFO, "\033[92m" }, 708 { ULOG_WARNING, "\033[93m" }, 709 { ULOG_ERROR, "\033[91m" }, 710 { ULOG_FATAL, "\033[95m" }, 711 }; 712 713 const char *log_level_irc_color(LogLevel loglevel) 714 { 715 return nv_find_by_value(log_colors_irc, loglevel); 716 } 717 718 const char *log_level_terminal_color(LogLevel loglevel) 719 { 720 return nv_find_by_value(log_colors_terminal, loglevel); 721 } 722 723 LogLevel log_level_stringtoval(const char *str) 724 { 725 if (!strcmp(str, "info")) 726 return ULOG_INFO; 727 if (!strcmp(str, "warn")) 728 return ULOG_WARNING; 729 if (!strcmp(str, "error")) 730 return ULOG_ERROR; 731 if (!strcmp(str, "fatal")) 732 return ULOG_FATAL; 733 if (!strcmp(str, "debug")) 734 return ULOG_DEBUG; 735 return ULOG_INVALID; 736 } 737 738 #define validvarcharacter(x) (isalnum((x)) || ((x) == '_')) 739 #define valideventidcharacter(x) (isupper((x)) || isdigit((x)) || ((x) == '_')) 740 #define validsubsystemcharacter(x) (islower((x)) || isdigit((x)) || ((x) == '_') || ((x) == '-')) 741 742 int valid_event_id(const char *s) 743 { 744 if (!*s) 745 return 0; 746 for (; *s; s++) 747 if (!valideventidcharacter(*s)) 748 return 0; 749 return 1; 750 } 751 752 int valid_subsystem(const char *s) 753 { 754 if (!*s) 755 return 0; 756 if (log_level_stringtoval(s) != ULOG_INVALID) 757 return 0; 758 for (; *s; s++) 759 if (!validsubsystemcharacter(*s)) 760 return 0; 761 return 1; 762 } 763 764 // TODO: if in the function below we keep adding auto expanshion shit, 765 // like we currently have $client automatically expanding to $client.name 766 // and $socket_error to $socket_error.error_string, 767 // if this gets more than we should use some kind of array for it, 768 // especially for the hardcoded name shit like $socket_error. 769 770 /** Build a string and replace $variables where needed. 771 * See src/modules/blacklist.c for an example. 772 * @param inbuf The input string 773 * @param outbuf The output string 774 * @param len The maximum size of the output string (including NUL) 775 * @param name Array of variables names 776 * @param value Array of variable values 777 */ 778 void buildlogstring(const char *inbuf, char *outbuf, size_t len, json_t *details) 779 { 780 const char *i, *p; 781 char *o; 782 int left = len - 1; 783 int cnt, found; 784 char varname[256], *varp, *varpp; 785 json_t *t; 786 787 #ifdef DEBUGMODE 788 if (len <= 0) 789 abort(); 790 #endif 791 792 for (i = inbuf, o = outbuf; *i; i++) 793 { 794 if (*i == '$') 795 { 796 i++; 797 798 /* $$ = literal $ */ 799 if (*i == '$') 800 goto literal; 801 802 if (!validvarcharacter(*i)) 803 { 804 /* What do we do with things like '$/' ? -- treat literal */ 805 i--; 806 goto literal; 807 } 808 809 /* find termination */ 810 for (p=i; validvarcharacter(*p) || ((*p == '.') && validvarcharacter(p[1])); p++); 811 812 /* find variable name in list */ 813 strlncpy(varname, i, sizeof(varname), p - i); 814 varp = strchr(varname, '.'); 815 if (varp) 816 *varp = '\0'; 817 t = json_object_get(details, varname); 818 if (t) 819 { 820 const char *output = NULL; 821 if (varp) 822 { 823 char *varpp; 824 do { 825 varpp = strchr(varp+1, '.'); 826 if (varpp) 827 *varpp = '\0'; 828 /* Fetch explicit object.key */ 829 t = json_object_get(t, varp+1); 830 varp = varpp; 831 } while(t && varpp); 832 if (t) 833 output = json_get_value(t); 834 } else 835 if (!strcmp(varname, "socket_error")) 836 { 837 /* Fetch socket_error.error_string */ 838 t = json_object_get(t, "error_string"); 839 if (t) 840 output = json_get_value(t); 841 } else 842 if (json_is_object(t)) 843 { 844 /* Fetch object.name */ 845 t = json_object_get(t, "name"); 846 if (t) 847 output = json_get_value(t); 848 } else 849 { 850 output = json_get_value(t); 851 } 852 if (output) 853 { 854 strlcpy(o, output, left); 855 left -= strlen(output); /* may become <0 */ 856 if (left <= 0) 857 return; /* return - don't write \0 to 'o'. ensured by strlcpy already */ 858 o += strlen(output); /* value entirely written */ 859 } 860 } else 861 { 862 /* variable name does not exist -- treat as literal string */ 863 i--; 864 goto literal; 865 } 866 867 /* value written. we're done. */ 868 i = p - 1; 869 continue; 870 } 871 literal: 872 if (!left) 873 break; 874 *o++ = *i; 875 left--; 876 if (!left) 877 break; 878 } 879 *o = '\0'; 880 } 881 882 /** Do the actual writing to log files */ 883 void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, Client *from_server) 884 { 885 static time_t last_log_file_warning = 0; 886 Log *l; 887 char timebuf[128]; 888 struct stat fstats; 889 int n; 890 int write_error; 891 long snomask; 892 MultiLine *m; 893 894 snprintf(timebuf, sizeof(timebuf), "[%s] ", myctime(TStime())); 895 896 RunHook(HOOKTYPE_LOG, loglevel, subsystem, event_id, msg, json, json_serialized, timebuf); 897 898 if (!loop.forked && (loglevel > ULOG_DEBUG)) 899 { 900 for (m = msg; m; m = m->next) 901 { 902 #ifdef _WIN32 903 if (show_event_console) 904 win_log("* %s.%s%s [%s] %s\n", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line); 905 else 906 win_log("* [%s] %s\n", log_level_valtostring(loglevel), m->line); 907 #else 908 if (terminal_supports_color()) 909 { 910 if (show_event_console) 911 { 912 fprintf(stderr, "%s%s.%s%s %s[%s]%s %s\n", 913 log_level_terminal_color(ULOG_INVALID), subsystem, event_id, TERMINAL_COLOR_RESET, 914 log_level_terminal_color(loglevel), log_level_valtostring(loglevel), TERMINAL_COLOR_RESET, 915 m->line); 916 } else { 917 fprintf(stderr, "%s[%s]%s %s\n", 918 log_level_terminal_color(loglevel), log_level_valtostring(loglevel), TERMINAL_COLOR_RESET, 919 m->line); 920 } 921 } else { 922 if (show_event_console) 923 fprintf(stderr, "%s.%s%s [%s] %s\n", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line); 924 else 925 fprintf(stderr, "[%s] %s\n", log_level_valtostring(loglevel), m->line); 926 } 927 #endif 928 } 929 } 930 931 /* In case of './unrealircd configtest': don't write to log file, only to stderr */ 932 if (loop.config_test) 933 return; 934 935 for (l = logs[LOG_DEST_DISK]; l; l = l->next) 936 { 937 if (!log_sources_match(l->sources, loglevel, subsystem, event_id, 0)) 938 continue; 939 940 #ifdef HAVE_SYSLOG 941 if (l->file && !strcasecmp(l->file, "syslog")) 942 { 943 if (l->type == LOG_TYPE_JSON) 944 { 945 syslog(LOG_INFO, "%s", json_serialized); 946 } else 947 if (l->type == LOG_TYPE_TEXT) 948 { 949 for (m = msg; m; m = m->next) 950 syslog(LOG_INFO, "%s.%s%s %s: %s", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line); 951 } 952 continue; 953 } 954 #endif 955 956 /* This deals with dynamic log file names, such as ircd.%Y-%m-%d.log */ 957 if (l->filefmt) 958 { 959 char *fname = unreal_strftime(l->filefmt); 960 if (l->file && (l->logfd != -1) && strcmp(l->file, fname)) 961 { 962 /* We are logging already and need to switch over */ 963 fd_close(l->logfd); 964 l->logfd = -1; 965 } 966 safe_strdup(l->file, fname); 967 } 968 969 /* log::maxsize code */ 970 if (l->maxsize && (stat(l->file, &fstats) != -1) && fstats.st_size >= l->maxsize) 971 { 972 char oldlog[512]; 973 if (l->logfd == -1) 974 { 975 /* Try to open, so we can write the 'Max file size reached' message. */ 976 l->logfd = fd_fileopen(l->file, O_CREAT|O_APPEND|O_WRONLY); 977 } 978 if (l->logfd != -1) 979 { 980 if (write(l->logfd, "Max file size reached, starting new log file\n", 45) < 0) 981 { 982 /* We already handle the unable to write to log file case for normal data. 983 * I think we can get away with not handling this one. 984 */ 985 ; 986 } 987 fd_close(l->logfd); 988 } 989 l->logfd = -1; 990 991 /* Rename log file to xxxxxx.old */ 992 snprintf(oldlog, sizeof(oldlog), "%s.old", l->file); 993 unlink(oldlog); /* windows rename cannot overwrite, so unlink here.. ;) */ 994 rename(l->file, oldlog); 995 } 996 997 /* generic code for opening log if not open yet.. */ 998 if (l->logfd == -1) 999 { 1000 l->logfd = fd_fileopen(l->file, O_CREAT|O_APPEND|O_WRONLY); 1001 if (l->logfd == -1) 1002 { 1003 if (errno == ENOENT) 1004 { 1005 /* Create directory structure and retry */ 1006 unreal_create_directory_structure_for_file(l->file, 0777); 1007 l->logfd = fd_fileopen(l->file, O_CREAT|O_APPEND|O_WRONLY); 1008 } 1009 if (l->logfd == -1) 1010 { 1011 /* Still failed! */ 1012 if (!loop.booted) 1013 { 1014 config_status("WARNING: Unable to write to '%s': %s", l->file, strerror(errno)); 1015 } else { 1016 if (last_log_file_warning + 300 < TStime()) 1017 { 1018 config_status("WARNING: Unable to write to '%s': %s. This warning will not re-appear for at least 5 minutes.", l->file, strerror(errno)); 1019 last_log_file_warning = TStime(); 1020 } 1021 } 1022 continue; 1023 } 1024 } 1025 } 1026 1027 /* Now actually WRITE to the log... */ 1028 write_error = 0; 1029 if ((l->type == LOG_TYPE_JSON) && strcmp(subsystem, "rawtraffic")) 1030 { 1031 n = write(l->logfd, json_serialized, strlen(json_serialized)); 1032 if (n < strlen(json_serialized)) 1033 { 1034 write_error = 1; 1035 } else { 1036 if (write(l->logfd, "\n", 1) < 1) // FIXME: no.. we should do it this way..... and why do we use direct I/O at all? 1037 write_error = 1; 1038 } 1039 } else 1040 if (l->type == LOG_TYPE_TEXT) 1041 { 1042 for (m = msg; m; m = m->next) 1043 { 1044 static char text_buf[MAXLOGLENGTH]; 1045 snprintf(text_buf, sizeof(text_buf), "%s%s %s.%s%s %s: %s\n", 1046 timebuf, from_server->name, 1047 subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line); 1048 n = write(l->logfd, text_buf, strlen(text_buf)); 1049 if (n < strlen(text_buf)) 1050 { 1051 write_error = 1; 1052 break; 1053 } 1054 } 1055 } 1056 1057 if (write_error) 1058 { 1059 if (!loop.booted) 1060 { 1061 config_status("WARNING: Unable to write to '%s': %s", l->file, strerror(errno)); 1062 } else { 1063 if (last_log_file_warning + 300 < TStime()) 1064 { 1065 config_status("WARNING: Unable to write to '%s': %s. This warning will not re-appear for at least 5 minutes.", l->file, strerror(errno)); 1066 last_log_file_warning = TStime(); 1067 } 1068 } 1069 } 1070 } 1071 } 1072 1073 int log_sources_match(LogSource *logsource, LogLevel loglevel, const char *subsystem, const char *event_id, int matched_already) 1074 { 1075 int retval = 0; 1076 LogSource *ls; 1077 1078 // NOTE: This routine works by exclusion, so a bad struct would 1079 // cause everything to match!! 1080 1081 for (ls = logsource; ls; ls = ls->next) 1082 { 1083 /* First deal with all positive matchers.. */ 1084 if (ls->negative) 1085 continue; 1086 if (!strcmp(ls->subsystem, "all")) 1087 { 1088 retval = 1; 1089 break; 1090 } 1091 if (!strcmp(ls->subsystem, "nomatch") && !matched_already) 1092 { 1093 /* catch-all */ 1094 retval = 1; 1095 break; 1096 } 1097 if (*ls->event_id && strcmp(ls->event_id, event_id)) 1098 continue; 1099 if (*ls->subsystem && strcmp(ls->subsystem, subsystem)) 1100 continue; 1101 if ((ls->loglevel != ULOG_INVALID) && (ls->loglevel != loglevel)) 1102 continue; 1103 /* MATCH */ 1104 retval = 1; 1105 break; 1106 } 1107 1108 /* No matches? Then we can stop here */ 1109 if (retval == 0) 1110 return 0; 1111 1112 /* There was a match, now check for exemptions, eg !operoverride */ 1113 for (ls = logsource; ls; ls = ls->next) 1114 { 1115 /* Only deal with negative matches... */ 1116 if (!ls->negative) 1117 continue; 1118 if (!strcmp(ls->subsystem, "nomatch") || !strcmp(ls->subsystem, "all")) 1119 continue; /* !nomatch and !all make no sense, so just ignore it */ 1120 if (*ls->event_id && strcmp(ls->event_id, event_id)) 1121 continue; 1122 if (*ls->subsystem && strcmp(ls->subsystem, subsystem)) 1123 continue; 1124 if ((ls->loglevel != ULOG_INVALID) && (ls->loglevel != loglevel)) 1125 continue; 1126 /* NEGATIVE MATCH */ 1127 return 0; 1128 } 1129 1130 return 1; 1131 } 1132 1133 /** Convert loglevel/subsystem/event_id to a snomask. 1134 * @returns The snomask letters (may be more than one), 1135 * an asterisk (for all ircops), or NULL (no delivery) 1136 */ 1137 const char *log_to_snomask(LogLevel loglevel, const char *subsystem, const char *event_id) 1138 { 1139 Log *ld; 1140 static char snomasks[64]; 1141 int matched = 0; 1142 1143 *snomasks = '\0'; 1144 for (ld = logs[LOG_DEST_SNOMASK]; ld; ld = ld->next) 1145 { 1146 if (log_sources_match(ld->sources, loglevel, subsystem, event_id, 0)) 1147 { 1148 strlcat(snomasks, ld->destination, sizeof(snomasks)); 1149 matched = 1; 1150 } 1151 } 1152 1153 if (logs[LOG_DEST_OPER] && log_sources_match(logs[LOG_DEST_OPER]->sources, loglevel, subsystem, event_id, matched)) 1154 strlcat(snomasks, "s", sizeof(snomasks)); 1155 1156 return *snomasks ? snomasks : NULL; 1157 } 1158 1159 #define COLOR_NONE "\xf" 1160 #define COLOR_DARKGREY "\00314" 1161 1162 /** Generic sendto function for logging to IRC. Used for notices to IRCOps and also for sending to individual users on channels */ 1163 void sendto_log(Client *client, const char *msgtype, const char *destination, int show_colors, int show_event, 1164 LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server) 1165 { 1166 MultiLine *m; 1167 1168 for (m = msg; m; m = m->next) 1169 { 1170 MessageTag *mtags = NULL; 1171 new_message(from_server, NULL, &mtags); 1172 1173 /* Add JSON data, but only if it is the first message (m == msg) */ 1174 if (json_serialized && (m == msg)) 1175 { 1176 MessageTag *json_mtag = safe_alloc(sizeof(MessageTag)); 1177 safe_strdup(json_mtag->name, "unrealircd.org/json-log"); 1178 safe_strdup(json_mtag->value, json_serialized); 1179 AddListItem(json_mtag, mtags); 1180 } 1181 1182 if (show_colors) 1183 { 1184 if (show_event) 1185 { 1186 sendto_one(client, mtags, ":%s %s %s :%s%s.%s%s%s %s[%s]%s %s", 1187 from_server->name, msgtype, destination, 1188 COLOR_DARKGREY, subsystem, event_id, m->next?"+":"", COLOR_NONE, 1189 log_level_irc_color(loglevel), log_level_valtostring(loglevel), COLOR_NONE, 1190 m->line); 1191 } else { 1192 sendto_one(client, mtags, ":%s %s %s :%s[%s]%s %s", 1193 from_server->name, msgtype, destination, 1194 log_level_irc_color(loglevel), log_level_valtostring(loglevel), COLOR_NONE, 1195 m->line); 1196 } 1197 } else { 1198 if (show_event) 1199 { 1200 sendto_one(client, mtags, ":%s %s %s :%s.%s%s [%s] %s", 1201 from_server->name, msgtype, destination, 1202 subsystem, event_id, m->next?"+":"", 1203 log_level_valtostring(loglevel), 1204 m->line); 1205 } else { 1206 sendto_one(client, mtags, ":%s %s %s :[%s] %s", 1207 from_server->name, msgtype, destination, 1208 log_level_valtostring(loglevel), 1209 m->line); 1210 } 1211 } 1212 safe_free_message_tags(mtags); 1213 } 1214 } 1215 1216 /** Send server notices to IRCOps */ 1217 void do_unreal_log_opers(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server) 1218 { 1219 Client *client; 1220 const char *snomask_destinations, *p; 1221 1222 /* If not fully booted then we don't have a logging to snomask mapping so can't do much.. */ 1223 if (!loop.booted) 1224 return; 1225 1226 /* Never send these */ 1227 if (!strcmp(subsystem, "rawtraffic")) 1228 return; 1229 1230 snomask_destinations = log_to_snomask(loglevel, subsystem, event_id); 1231 if (!snomask_destinations) 1232 return; 1233 1234 /* To specific snomasks... */ 1235 list_for_each_entry(client, &oper_list, special_node) 1236 { 1237 const char *operlogin; 1238 ConfigItem_oper *oper; 1239 int show_colors = iConf.server_notice_colors; 1240 int show_event = iConf.server_notice_show_event; 1241 1242 if (snomask_destinations) 1243 { 1244 char found = 0; 1245 if (!client->user->snomask) 1246 continue; /* None set, so will never match */ 1247 for (p = snomask_destinations; *p; p++) 1248 { 1249 if (strchr(client->user->snomask, *p)) 1250 { 1251 found = 1; 1252 break; 1253 } 1254 } 1255 if (!found) 1256 continue; 1257 } 1258 1259 operlogin = get_operlogin(client); 1260 if (operlogin && (oper = find_oper(operlogin))) 1261 { 1262 show_colors = oper->server_notice_colors; 1263 show_event = oper->server_notice_show_event; 1264 } 1265 1266 sendto_log(client, "NOTICE", client->name, show_colors, show_event, loglevel, subsystem, event_id, msg, json_serialized, from_server); 1267 } 1268 } 1269 1270 /** Send server notices to channels */ 1271 void do_unreal_log_channels(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server) 1272 { 1273 Log *l; 1274 Member *m; 1275 Client *client; 1276 1277 /* If not fully booted then we don't have a logging to snomask mapping so can't do much.. */ 1278 if (!loop.booted) 1279 return; 1280 1281 /* Never send these */ 1282 if (!strcmp(subsystem, "rawtraffic")) 1283 return; 1284 1285 for (l = logs[LOG_DEST_CHANNEL]; l; l = l->next) 1286 { 1287 const char *operlogin; 1288 ConfigItem_oper *oper; 1289 Channel *channel; 1290 1291 if (!log_sources_match(l->sources, loglevel, subsystem, event_id, 0)) 1292 continue; 1293 1294 channel = find_channel(l->destination); 1295 if (!channel) 1296 continue; 1297 1298 for (m = channel->members; m; m = m->next) 1299 { 1300 Client *client = m->client; 1301 if (!MyUser(client)) 1302 continue; 1303 if (l->oper_only && !IsOper(client)) 1304 continue; 1305 sendto_log(client, "PRIVMSG", channel->name, l->color, l->show_event, 1306 loglevel, subsystem, event_id, msg, 1307 l->json_message_tag ? json_serialized : NULL, 1308 from_server); 1309 } 1310 } 1311 } 1312 1313 void do_unreal_log_remote(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized) 1314 { 1315 Log *l; 1316 int found = 0; 1317 1318 for (l = logs[LOG_DEST_REMOTE]; l; l = l->next) 1319 { 1320 if (log_sources_match(l->sources, loglevel, subsystem, event_id, 0)) 1321 { 1322 found = 1; 1323 break; 1324 } 1325 } 1326 if (found == 0) 1327 return; 1328 1329 do_unreal_log_remote_deliver(loglevel, subsystem, event_id, msg, json_serialized); 1330 } 1331 1332 /** Send server notices to control channel */ 1333 void do_unreal_log_control(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *j, const char *json_serialized, Client *from_server) 1334 { 1335 Client *client; 1336 MultiLine *m; 1337 1338 if (!loop.booted) 1339 return; 1340 1341 /* Never send these */ 1342 if (!strcmp(subsystem, "rawtraffic")) 1343 return; 1344 1345 list_for_each_entry(client, &control_list, lclient_node) 1346 if (IsMonitorRehash(client) && IsControl(client)) 1347 for (m = msg; m; m = m->next) 1348 sendto_one(client, NULL, "REPLY [%s] %s", log_level_valtostring(loglevel), m->line); 1349 1350 if (json_rehash_log) 1351 { 1352 json_t *log = json_object_get(json_rehash_log, "log"); 1353 if (log) 1354 json_array_append(log, j); // not xxx_new, right? 1355 } 1356 } 1357 1358 void do_unreal_log_free_args(va_list vl) 1359 { 1360 LogData *d; 1361 1362 while ((d = va_arg(vl, LogData *))) 1363 { 1364 log_data_free(d); 1365 } 1366 } 1367 1368 static int unreal_log_recursion_trap = 0; 1369 1370 /* Logging function, called by the unreal_log() macro. */ 1371 void do_unreal_log(LogLevel loglevel, const char *subsystem, const char *event_id, 1372 Client *client, const char *msg, ...) 1373 { 1374 va_list vl; 1375 1376 if (unreal_log_recursion_trap) 1377 { 1378 va_start(vl, msg); 1379 do_unreal_log_free_args(vl); 1380 va_end(vl); 1381 return; 1382 } 1383 1384 unreal_log_recursion_trap = 1; 1385 va_start(vl, msg); 1386 do_unreal_log_internal(loglevel, subsystem, event_id, client, 1, msg, vl); 1387 va_end(vl); 1388 unreal_log_recursion_trap = 0; 1389 } 1390 1391 /* Logging function, called by the unreal_log_raw() macro. */ 1392 void do_unreal_log_raw(LogLevel loglevel, const char *subsystem, const char *event_id, 1393 Client *client, const char *msg, ...) 1394 { 1395 va_list vl; 1396 1397 if (unreal_log_recursion_trap) 1398 { 1399 va_start(vl, msg); 1400 do_unreal_log_free_args(vl); 1401 va_end(vl); 1402 return; 1403 } 1404 1405 unreal_log_recursion_trap = 1; 1406 va_start(vl, msg); 1407 do_unreal_log_internal(loglevel, subsystem, event_id, client, 0, msg, vl); 1408 va_end(vl); 1409 unreal_log_recursion_trap = 0; 1410 } 1411 1412 void do_unreal_log_norecursioncheck(LogLevel loglevel, const char *subsystem, const char *event_id, 1413 Client *client, const char *msg, ...) 1414 { 1415 va_list vl; 1416 1417 va_start(vl, msg); 1418 do_unreal_log_internal(loglevel, subsystem, event_id, client, 1, msg, vl); 1419 va_end(vl); 1420 } 1421 1422 void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char *event_id, 1423 Client *client, int expand_msg, const char *msg, va_list vl) 1424 { 1425 LogData *d; 1426 char *json_serialized; 1427 const char *str; 1428 json_t *j = NULL; 1429 json_t *j_details = NULL; 1430 json_t *t; 1431 char msgbuf[MAXLOGLENGTH]; 1432 const char *loglevel_string = log_level_valtostring(loglevel); 1433 MultiLine *mmsg; 1434 Client *from_server = NULL; 1435 1436 /* Set flag so json_string_unreal() uses more strict filter */ 1437 log_json_filter = 1; 1438 1439 if (loglevel_string == NULL) 1440 { 1441 do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_LOGLEVEL", NULL, 1442 "[BUG] Next log message had an invalid log level -- corrected to ULOG_ERROR", 1443 NULL); 1444 loglevel = ULOG_ERROR; 1445 loglevel_string = log_level_valtostring(loglevel); 1446 } 1447 if (!valid_subsystem(subsystem)) 1448 { 1449 do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_SUBSYSTEM", NULL, 1450 "[BUG] Next log message had an invalid subsystem -- changed to 'unknown'", 1451 NULL); 1452 subsystem = "unknown"; 1453 } 1454 if (!valid_event_id(event_id)) 1455 { 1456 do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_EVENT_ID", NULL, 1457 "[BUG] Next log message had an invalid event id -- changed to 'unknown'", 1458 NULL); 1459 event_id = "unknown"; 1460 } 1461 /* This one is probably temporary since it should not be a real error, actually (but often is) */ 1462 if (expand_msg && strchr(msg, '%')) 1463 { 1464 do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_MESSAGE_PERCENT", NULL, 1465 "[BUG] Next log message contains a percent sign -- possibly accidental format string!", 1466 NULL); 1467 } 1468 1469 j = json_object(); 1470 j_details = json_object(); 1471 1472 json_object_set_new(j, "timestamp", json_string_unreal(timestamp_iso8601_now())); 1473 json_object_set_new(j, "level", json_string_unreal(loglevel_string)); 1474 json_object_set_new(j, "subsystem", json_string_unreal(subsystem)); 1475 json_object_set_new(j, "event_id", json_string_unreal(event_id)); 1476 json_object_set_new(j, "log_source", json_string_unreal(*me.name ? me.name : "local")); 1477 1478 /* We put all the rest in j_details because we want to enforce 1479 * a certain ordering of the JSON output. We will merge these 1480 * details later on. 1481 */ 1482 if (client) 1483 json_expand_client(j_details, "client", client, 3); 1484 /* Additional details (if any) */ 1485 while ((d = va_arg(vl, LogData *))) 1486 { 1487 switch(d->type) 1488 { 1489 case LOG_FIELD_INTEGER: 1490 json_object_set_new(j_details, d->key, json_integer(d->value.integer)); 1491 break; 1492 case LOG_FIELD_STRING: 1493 if (d->value.string) 1494 json_object_set_new(j_details, d->key, json_string_unreal(d->value.string)); 1495 else 1496 json_object_set_new(j_details, d->key, json_null()); 1497 break; 1498 case LOG_FIELD_CLIENT: 1499 json_expand_client(j_details, d->key, d->value.client, 3); 1500 break; 1501 case LOG_FIELD_CHANNEL: 1502 json_expand_channel(j_details, d->key, d->value.channel, 1); 1503 break; 1504 case LOG_FIELD_OBJECT: 1505 json_object_set_new(j_details, d->key, d->value.object); 1506 d->value.object = NULL; /* don't let log_data_free() free it */ 1507 break; 1508 default: 1509 #ifdef DEBUGMODE 1510 abort(); 1511 #endif 1512 break; 1513 } 1514 log_data_free(d); 1515 } 1516 1517 if (expand_msg) 1518 buildlogstring(msg, msgbuf, sizeof(msgbuf), j_details); 1519 else 1520 strlcpy(msgbuf, msg, sizeof(msgbuf)); 1521 1522 json_object_set_new(j, "msg", json_string_unreal(msgbuf)); 1523 1524 /* Now merge the details into root object 'j': */ 1525 json_object_update_missing(j, j_details); 1526 /* Generate the JSON */ 1527 json_serialized = json_dumps(j, JSON_COMPACT); 1528 1529 /* Convert the message buffer to MultiLine */ 1530 mmsg = line2multiline(msgbuf); 1531 1532 /* Parse the "from server" info, if any */ 1533 t = json_object_get(j_details, "from_server_name"); 1534 if (t && (str = json_get_value(t))) 1535 from_server = find_server(str, NULL); 1536 if (from_server == NULL) 1537 from_server = &me; 1538 1539 /* Now call all the loggers: */ 1540 1541 do_unreal_log_disk(loglevel, subsystem, event_id, mmsg, j, json_serialized, from_server); 1542 1543 if ((loop.rehashing == 2) || !strcmp(subsystem, "config")) 1544 do_unreal_log_control(loglevel, subsystem, event_id, mmsg, j, json_serialized, from_server); 1545 1546 do_unreal_log_opers(loglevel, subsystem, event_id, mmsg, json_serialized, from_server); 1547 1548 do_unreal_log_channels(loglevel, subsystem, event_id, mmsg, json_serialized, from_server); 1549 1550 do_unreal_log_remote(loglevel, subsystem, event_id, mmsg, json_serialized); 1551 1552 // NOTE: code duplication further down! 1553 1554 /* This one should only be in do_unreal_log_internal() 1555 * and never in do_unreal_log_internal_from_remote() 1556 */ 1557 if (remote_rehash_client && 1558 ((!strcmp(event_id, "CONFIG_ERROR_GENERIC") || 1559 !strcmp(event_id, "CONFIG_WARNING_GENERIC") || 1560 !strcmp(event_id, "CONFIG_INFO_GENERIC")) 1561 || 1562 (loop.config_status >= CONFIG_STATUS_TEST)) && 1563 strcmp(subsystem, "rawtraffic")) 1564 { 1565 sendto_log(remote_rehash_client, "NOTICE", remote_rehash_client->name, 1566 iConf.server_notice_colors, iConf.server_notice_show_event, 1567 loglevel, subsystem, event_id, mmsg, json_serialized, from_server); 1568 } 1569 1570 /* Free everything */ 1571 safe_free(json_serialized); 1572 safe_free_multiline(mmsg); 1573 json_decref(j_details); 1574 json_decref(j); 1575 1576 /* Turn off flag again */ 1577 log_json_filter = 0; 1578 } 1579 1580 void do_unreal_log_internal_from_remote(LogLevel loglevel, const char *subsystem, const char *event_id, 1581 MultiLine *msg, json_t *json, const char *json_serialized, Client *from_server) 1582 { 1583 if (unreal_log_recursion_trap) 1584 return; 1585 unreal_log_recursion_trap = 1; 1586 1587 /* Set flag so json_string_unreal() uses more strict filter */ 1588 log_json_filter = 1; 1589 1590 /* Call the disk loggers */ 1591 do_unreal_log_disk(loglevel, subsystem, event_id, msg, json, json_serialized, from_server); 1592 1593 /* And to IRC */ 1594 do_unreal_log_opers(loglevel, subsystem, event_id, msg, json_serialized, from_server); 1595 do_unreal_log_channels(loglevel, subsystem, event_id, msg, json_serialized, from_server); 1596 1597 /* Turn off flag again */ 1598 log_json_filter = 0; 1599 1600 unreal_log_recursion_trap = 0; 1601 } 1602 1603 1604 void free_log_block(Log *l) 1605 { 1606 Log *l_next; 1607 LogSource *src, *src_next; 1608 for (; l; l = l_next) 1609 { 1610 l_next = l->next; 1611 if (l->logfd > 0) 1612 { 1613 fd_close(l->logfd); 1614 l->logfd = -1; 1615 } 1616 free_log_sources(l->sources); 1617 safe_free(l->file); 1618 safe_free(l->filefmt); 1619 safe_free(l); 1620 } 1621 } 1622 1623 int log_tests(void) 1624 { 1625 if (snomask_num_destinations == 0) 1626 { 1627 unreal_log(ULOG_ERROR, "config", "LOG_SNOMASK_BLOCK_MISSING", NULL, 1628 "Missing snomask logging configuration:\n" 1629 "Please add the following line to your unrealircd.conf: " 1630 "include \"snomasks.default.conf\";"); 1631 return 0; 1632 } 1633 return 1; 1634 } 1635 1636 void postconf_defaults_log_block(void) 1637 { 1638 Log *l; 1639 LogSource *ls; 1640 1641 /* Is there any log block to disk? Then nothing to do. */ 1642 if (logs[LOG_DEST_DISK]) 1643 return; 1644 1645 unreal_log(ULOG_WARNING, "log", "NO_DISK_LOG_BLOCK", NULL, 1646 "No log { } block found that logs to disk -- " 1647 "logging everything in text format to 'ircd.log'"); 1648 1649 /* Create a default log block */ 1650 l = safe_alloc(sizeof(Log)); 1651 l->logfd = -1; 1652 l->type = LOG_TYPE_TEXT; /* text */ 1653 l->maxsize = 100000000; /* maxsize 100M */ 1654 safe_strdup(l->file, "ircd.log"); 1655 convert_to_absolute_path(&l->file, LOGDIR); 1656 AddListItem(l, logs[LOG_DEST_DISK]); 1657 1658 /* And the source filter */ 1659 ls = add_log_source("all"); 1660 AppendListItem(ls, l->sources); 1661 ls = add_log_source("!debug"); 1662 AppendListItem(ls, l->sources); 1663 ls = add_log_source("!join.LOCAL_CLIENT_JOIN"); 1664 AppendListItem(ls, l->sources); 1665 ls = add_log_source("!join.REMOTE_CLIENT_JOIN"); 1666 AppendListItem(ls, l->sources); 1667 ls = add_log_source("!part.LOCAL_CLIENT_PART"); 1668 AppendListItem(ls, l->sources); 1669 ls = add_log_source("!part.REMOTE_CLIENT_PART"); 1670 AppendListItem(ls, l->sources); 1671 ls = add_log_source("!kick.LOCAL_CLIENT_KICK"); 1672 AppendListItem(ls, l->sources); 1673 ls = add_log_source("!kick.REMOTE_CLIENT_KICK"); 1674 AppendListItem(ls, l->sources); 1675 } 1676 1677 /* Called before CONFIG_TEST */ 1678 void log_pre_rehash(void) 1679 { 1680 *snomasks_in_use_testing = '\0'; 1681 snomask_num_destinations = 0; 1682 } 1683 1684 /* Called after CONFIG_TEST right before CONFIG_RUN */ 1685 void config_pre_run_log(void) 1686 { 1687 *snomasks_in_use = '\0'; 1688 } 1689 1690 /* Called after CONFIG_RUN is complete */ 1691 void log_blocks_switchover(void) 1692 { 1693 int i; 1694 for (i=0; i < NUM_LOG_DESTINATIONS; i++) 1695 free_log_block(logs[i]); 1696 memcpy(logs, temp_logs, sizeof(logs)); 1697 memset(temp_logs, 0, sizeof(temp_logs)); 1698 } 1699 1700 /** Check if a letter is a valid snomask (that is: 1701 * one that exists in the log block configuration). 1702 * @param c the snomask letter to check 1703 * @returns 1 if exists, 0 if not. 1704 */ 1705 int is_valid_snomask(char c) 1706 { 1707 return strchr(snomasks_in_use, c) ? 1 : 0; 1708 } 1709 1710 /** Check if a letter is a valid snomask during or after CONFIG_TEST 1711 * (the snomasks exist in the log block configuration read during config_test). 1712 * @param c the snomask letter to check 1713 * @returns 1 if exists, 0 if not. 1714 */ 1715 int is_valid_snomask_testing(char c) 1716 { 1717 return strchr(snomasks_in_use_testing, c) ? 1 : 0; 1718 } 1719 1720 /** Check if a string all consists of valid snomasks during or after CONFIG_TEST 1721 * (the snomasks exist in the log block configuration read during config_test). 1722 * @param str the snomask string to check 1723 * @param invalid_snomasks list of unknown snomask letters 1724 * @returns 1 if exists, 0 if not. 1725 */ 1726 int is_valid_snomask_string_testing(const char *str, char **invalid_snomasks) 1727 { 1728 static char invalid_snomasks_buf[256]; 1729 1730 *invalid_snomasks_buf = '\0'; 1731 for (; *str; str++) 1732 { 1733 if ((*str == '+') || (*str == '-')) 1734 continue; 1735 if (!strchr(snomasks_in_use_testing, *str)) 1736 strlcat_letter(invalid_snomasks_buf, *str, sizeof(invalid_snomasks_buf)); 1737 } 1738 *invalid_snomasks = invalid_snomasks_buf; 1739 return *invalid_snomasks_buf ? 0 : 1; 1740 }