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 }