]>
Commit | Line | Data |
---|---|---|
d76ed9a9 AS |
1 | /* log.c - Diagnostic and error logging |
2 | * Copyright 2000-2004 srvx Development Team | |
3 | * | |
4 | * This file is part of srvx. | |
5 | * | |
6 | * srvx is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with srvx; if not, write to the Free Software Foundation, | |
18 | * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
19 | */ | |
20 | ||
21 | #include "conf.h" | |
22 | #include "log.h" | |
23 | #include "helpfile.h" /* send_message, message_register, etc */ | |
24 | #include "nickserv.h" | |
25 | ||
26 | struct logDestination; | |
27 | ||
28 | struct logDest_vtable { | |
29 | const char *type_name; | |
30 | struct logDestination* (*open)(const char *args); | |
31 | void (*reopen)(struct logDestination *self); | |
32 | void (*close)(struct logDestination *self); | |
33 | void (*log_audit)(struct logDestination *self, struct log_type *type, struct logEntry *entry); | |
34 | void (*log_replay)(struct logDestination *self, struct log_type *type, int is_write, const char *line); | |
35 | void (*log_module)(struct logDestination *self, struct log_type *type, enum log_severity sev, const char *message); | |
36 | }; | |
37 | ||
38 | struct logDestination { | |
39 | struct logDest_vtable *vtbl; | |
40 | char *name; | |
41 | int refcnt; | |
42 | }; | |
43 | ||
44 | DECLARE_LIST(logList, struct logDestination*); | |
45 | ||
46 | struct log_type { | |
47 | char *name; | |
48 | struct logList logs[LOG_NUM_SEVERITIES]; | |
49 | struct logEntry *log_oldest; | |
50 | struct logEntry *log_newest; | |
51 | unsigned int log_count; | |
52 | unsigned int max_age; | |
53 | unsigned int max_count; | |
54 | unsigned int default_set : 1; | |
55 | }; | |
56 | ||
57 | static const char *log_severity_names[] = { | |
58 | "replay", /* 0 */ | |
59 | "debug", | |
60 | "command", | |
61 | "info", | |
62 | "override", | |
63 | "staff", /* 5 */ | |
64 | "warning", | |
65 | "error", | |
66 | "fatal", | |
67 | 0 | |
68 | }; | |
69 | ||
70 | static struct dict *log_dest_types; | |
71 | static struct dict *log_dests; | |
72 | static struct dict *log_types; | |
73 | static struct log_type *log_default; | |
74 | static int log_inited, log_debugged; | |
75 | ||
76 | DEFINE_LIST(logList, struct logDestination*); | |
77 | static void log_format_audit(struct logEntry *entry); | |
78 | static const struct message_entry msgtab[] = { | |
79 | { "MSG_INVALID_FACILITY", "$b%s$b is an invalid log facility." }, | |
80 | { "MSG_INVALID_SEVERITY", "$b%s$b is an invalid severity level." }, | |
81 | { NULL, NULL } | |
82 | }; | |
83 | ||
84 | static struct logDestination * | |
85 | log_open(const char *name) | |
86 | { | |
87 | struct logDest_vtable *vtbl; | |
88 | struct logDestination *ld; | |
89 | char *sep; | |
90 | char type_name[32]; | |
91 | ||
92 | if ((ld = dict_find(log_dests, name, NULL))) { | |
93 | ld->refcnt++; | |
94 | return ld; | |
95 | } | |
96 | if ((sep = strchr(name, ':'))) { | |
97 | memcpy(type_name, name, sep-name); | |
98 | type_name[sep-name] = 0; | |
99 | } else { | |
100 | strcpy(type_name, name); | |
101 | } | |
102 | if (!(vtbl = dict_find(log_dest_types, type_name, NULL))) { | |
103 | log_module(MAIN_LOG, LOG_ERROR, "Invalid log type for log '%s'.", name); | |
104 | return 0; | |
105 | } | |
106 | if (!(ld = vtbl->open(sep ? sep+1 : 0))) | |
107 | return 0; | |
108 | ld->name = strdup(name); | |
109 | dict_insert(log_dests, ld->name, ld); | |
110 | ld->refcnt = 1; | |
111 | return ld; | |
112 | } | |
113 | ||
114 | static void | |
115 | logList_open(struct logList *ll, struct record_data *rd) | |
116 | { | |
117 | struct logDestination *ld; | |
118 | unsigned int ii; | |
119 | ||
120 | if (!ll->size) | |
121 | logList_init(ll); | |
122 | switch (rd->type) { | |
123 | case RECDB_QSTRING: | |
124 | if ((ld = log_open(rd->d.qstring))) | |
125 | logList_append(ll, ld); | |
126 | break; | |
127 | case RECDB_STRING_LIST: | |
128 | for (ii=0; ii<rd->d.slist->used; ++ii) { | |
129 | if ((ld = log_open(rd->d.slist->list[ii]))) | |
130 | logList_append(ll, ld); | |
131 | } | |
132 | break; | |
133 | default: | |
134 | break; | |
135 | } | |
136 | } | |
137 | ||
138 | static void | |
139 | logList_join(struct logList *target, const struct logList *source) | |
140 | { | |
141 | unsigned int ii, jj, kk; | |
142 | ||
143 | if (!source->used) | |
144 | return; | |
145 | jj = target->used; | |
146 | target->used += source->used; | |
147 | target->size += source->used; | |
148 | target->list = realloc(target->list, target->size * sizeof(target->list[0])); | |
149 | for (ii = 0; ii < source->used; ++ii, ++jj) { | |
150 | int dup; | |
151 | for (dup = 0, kk = 0; kk < jj; kk++) { | |
152 | if (target->list[kk] == source->list[ii]) { | |
153 | dup = 1; | |
154 | break; | |
155 | } | |
156 | } | |
157 | if (dup) { | |
158 | jj--; | |
159 | target->used--; | |
160 | continue; | |
161 | } | |
162 | target->list[jj] = source->list[ii]; | |
163 | target->list[jj]->refcnt++; | |
164 | } | |
165 | } | |
166 | ||
167 | static void | |
168 | logList_close(struct logList *ll) | |
169 | { | |
170 | unsigned int ii; | |
171 | for (ii=0; ii<ll->used; ++ii) { | |
172 | if (!--ll->list[ii]->refcnt) { | |
173 | struct logDestination *ld = ll->list[ii]; | |
174 | ld->vtbl->close(ld); | |
175 | } | |
176 | } | |
177 | logList_clean(ll); | |
178 | } | |
179 | ||
180 | static void | |
181 | close_logs(void) | |
182 | { | |
183 | dict_iterator_t it; | |
184 | struct log_type *lt; | |
185 | enum log_severity ls; | |
186 | ||
187 | for (it = dict_first(log_types); it; it = iter_next(it)) { | |
188 | lt = iter_data(it); | |
189 | for (ls = 0; ls < LOG_NUM_SEVERITIES; ls++) { | |
190 | logList_close(<->logs[ls]); | |
191 | ||
192 | lt->logs[ls].size = 0; | |
193 | lt->logs[ls].used = 0; | |
194 | lt->logs[ls].list = 0; | |
195 | } | |
196 | } | |
197 | } | |
198 | ||
199 | static void | |
200 | log_type_free_oldest(struct log_type *lt) | |
201 | { | |
202 | struct logEntry *next; | |
203 | ||
204 | if (!lt->log_oldest) | |
205 | return; | |
206 | next = lt->log_oldest->next; | |
207 | free(lt->log_oldest->default_desc); | |
208 | free(lt->log_oldest); | |
209 | lt->log_oldest = next; | |
210 | lt->log_count--; | |
211 | } | |
212 | ||
213 | static void | |
214 | log_type_free(void *ptr) | |
215 | { | |
216 | struct log_type *lt = ptr; | |
217 | ||
218 | while (lt->log_oldest) | |
219 | log_type_free_oldest(lt); | |
220 | free(lt); | |
221 | } | |
222 | ||
223 | static void | |
224 | cleanup_logs(void) | |
225 | { | |
226 | ||
227 | close_logs(); | |
228 | dict_delete(log_types); | |
229 | dict_delete(log_dests); | |
230 | dict_delete(log_dest_types); | |
231 | } | |
232 | ||
233 | static enum log_severity | |
234 | find_severity(const char *text) | |
235 | { | |
236 | enum log_severity ls; | |
237 | for (ls = 0; ls < LOG_NUM_SEVERITIES; ++ls) | |
238 | if (!ircncasecmp(text, log_severity_names[ls], strlen(log_severity_names[ls]))) | |
239 | return ls; | |
240 | return LOG_NUM_SEVERITIES; | |
241 | } | |
242 | ||
243 | /* Log keys are based on syslog.conf syntax: | |
244 | * KEY := LOGSET '.' SEVSET | |
245 | * LOGSET := LOGLIT | LOGLIT ',' LOGSET | |
246 | * LOGLIT := a registered log type | |
247 | * SEVSET := '*' | SEVLIT | '<' SEVLIT | '<=' SEVLIT | '>' SEVLIT | '>=' SEVLIT | SEVLIT ',' SEVSET | |
248 | * SEVLIT := one of log_severity_names | |
249 | * A KEY contains the Cartesian product of the logs in its LOGSET | |
250 | * and the severities in its SEVSET. | |
251 | */ | |
252 | ||
253 | static void | |
254 | log_parse_logset(char *buffer, struct string_list *slist) | |
255 | { | |
256 | slist->used = 0; | |
257 | while (buffer) { | |
258 | char *cont = strchr(buffer, ','); | |
259 | if (cont) | |
260 | *cont++ = 0; | |
261 | string_list_append(slist, strdup(buffer)); | |
262 | buffer = cont; | |
263 | } | |
264 | } | |
265 | ||
266 | static void | |
267 | log_parse_sevset(char *buffer, char targets[LOG_NUM_SEVERITIES]) | |
268 | { | |
269 | memset(targets, 0, LOG_NUM_SEVERITIES); | |
270 | while (buffer) { | |
271 | char *cont; | |
272 | enum log_severity bound; | |
273 | int first; | |
274 | ||
275 | cont = strchr(buffer, ','); | |
276 | if (cont) | |
277 | *cont++ = 0; | |
278 | if (buffer[0] == '*' && buffer[1] == 0) { | |
279 | for (bound = 0; bound < LOG_NUM_SEVERITIES; bound++) { | |
280 | /* make people explicitly specify replay targets */ | |
281 | if (bound != LOG_REPLAY) | |
282 | targets[bound] = 1; | |
283 | } | |
284 | } else if (buffer[0] == '<') { | |
285 | if (buffer[1] == '=') | |
286 | bound = find_severity(buffer+2) + 1; | |
287 | else | |
288 | bound = find_severity(buffer+1); | |
289 | for (first = 1; bound > 0; bound--) { | |
290 | /* make people explicitly specify replay targets */ | |
291 | if (bound != LOG_REPLAY || first) { | |
292 | targets[bound] = 1; | |
293 | first = 0; | |
294 | } | |
295 | } | |
296 | } else if (buffer[0] == '>') { | |
297 | if (buffer[1] == '=') | |
298 | bound = find_severity(buffer+2); | |
299 | else | |
300 | bound = find_severity(buffer+1) + 1; | |
301 | for (first = 1; bound < LOG_NUM_SEVERITIES; bound++) { | |
302 | /* make people explicitly specify replay targets */ | |
303 | if (bound != LOG_REPLAY || first) { | |
304 | targets[bound] = 1; | |
305 | first = 0; | |
306 | } | |
307 | } | |
308 | } else { | |
309 | if (buffer[0] == '=') | |
310 | buffer++; | |
311 | bound = find_severity(buffer); | |
312 | targets[bound] = 1; | |
313 | } | |
314 | buffer = cont; | |
315 | } | |
316 | } | |
317 | ||
318 | static void | |
319 | log_parse_cross(const char *buffer, struct string_list *types, char sevset[LOG_NUM_SEVERITIES]) | |
320 | { | |
321 | char *dup, *sep; | |
322 | ||
323 | dup = strdup(buffer); | |
324 | sep = strchr(dup, '.'); | |
325 | *sep++ = 0; | |
326 | log_parse_logset(dup, types); | |
327 | log_parse_sevset(sep, sevset); | |
328 | free(dup); | |
329 | } | |
330 | ||
331 | static void | |
332 | log_parse_options(struct log_type *type, struct dict *conf) | |
333 | { | |
334 | const char *opt; | |
335 | opt = database_get_data(conf, "max_age", RECDB_QSTRING); | |
336 | if (opt) | |
337 | type->max_age = ParseInterval(opt); | |
338 | opt = database_get_data(conf, "max_count", RECDB_QSTRING); | |
339 | if (opt) | |
340 | type->max_count = strtoul(opt, NULL, 10); | |
341 | } | |
342 | ||
343 | static void | |
344 | log_conf_read(void) | |
345 | { | |
346 | struct record_data *rd, *rd2; | |
347 | dict_iterator_t it; | |
348 | const char *sep; | |
349 | struct log_type *type; | |
350 | enum log_severity sev; | |
351 | unsigned int ii; | |
352 | ||
353 | close_logs(); | |
354 | dict_delete(log_dests); | |
355 | ||
356 | log_dests = dict_new(); | |
357 | dict_set_free_keys(log_dests, free); | |
358 | ||
359 | rd = conf_get_node("logs"); | |
360 | if (rd && (rd->type == RECDB_OBJECT)) { | |
361 | for (it = dict_first(rd->d.object); it; it = iter_next(it)) { | |
362 | if ((sep = strchr(iter_key(it), '.'))) { | |
363 | struct logList logList; | |
364 | char sevset[LOG_NUM_SEVERITIES]; | |
365 | struct string_list *slist; | |
366 | ||
367 | /* It looks like a <type>.<severity> record. Try to parse it. */ | |
368 | slist = alloc_string_list(4); | |
369 | log_parse_cross(iter_key(it), slist, sevset); | |
370 | logList.size = 0; | |
371 | logList_open(&logList, iter_data(it)); | |
372 | for (ii = 0; ii < slist->used; ++ii) { | |
373 | type = log_register_type(slist->list[ii], NULL); | |
374 | for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) { | |
375 | if (!sevset[sev]) | |
376 | continue; | |
377 | logList_join(&type->logs[sev], &logList); | |
378 | } | |
379 | } | |
380 | logList_close(&logList); | |
381 | free_string_list(slist); | |
382 | } else if ((rd2 = iter_data(it)) | |
383 | && (rd2->type == RECDB_OBJECT) | |
384 | && (type = log_register_type(iter_key(it), NULL))) { | |
385 | log_parse_options(type, rd2->d.object); | |
386 | } else { | |
387 | log_module(MAIN_LOG, LOG_ERROR, "Unknown logs subkey '%s'.", iter_key(it)); | |
388 | } | |
389 | } | |
390 | } | |
391 | if (log_debugged) | |
392 | log_debug(); | |
393 | } | |
394 | ||
395 | void | |
396 | log_debug(void) | |
397 | { | |
398 | enum log_severity sev; | |
399 | struct logDestination *log_stdout; | |
400 | struct logList target; | |
401 | ||
402 | log_stdout = log_open("std:out"); | |
403 | logList_init(&target); | |
404 | logList_append(&target, log_stdout); | |
405 | ||
406 | for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) | |
407 | logList_join(&log_default->logs[sev], &target); | |
408 | ||
409 | logList_close(&target); | |
410 | log_debugged = 1; | |
411 | } | |
412 | ||
413 | void | |
414 | log_reopen(void) | |
415 | { | |
416 | dict_iterator_t it; | |
417 | for (it = dict_first(log_dests); it; it = iter_next(it)) { | |
418 | struct logDestination *ld = iter_data(it); | |
419 | ld->vtbl->reopen(ld); | |
420 | } | |
421 | } | |
422 | ||
423 | struct log_type * | |
424 | log_register_type(const char *name, const char *default_log) | |
425 | { | |
426 | struct log_type *type; | |
427 | struct logDestination *dest; | |
428 | enum log_severity sev; | |
429 | ||
430 | if (!(type = dict_find(log_types, name, NULL))) { | |
431 | type = calloc(1, sizeof(*type)); | |
432 | type->name = strdup(name); | |
433 | type->max_age = 600; | |
434 | type->max_count = 1024; | |
435 | dict_insert(log_types, type->name, type); | |
436 | } | |
437 | if (default_log && !type->default_set) { | |
438 | /* If any severity level was unspecified in the config, use the default. */ | |
439 | dest = NULL; | |
440 | for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) { | |
441 | if (sev == LOG_REPLAY) | |
442 | continue; /* never default LOG_REPLAY */ | |
443 | if (!type->logs[sev].size) { | |
444 | logList_init(&type->logs[sev]); | |
445 | if (!dest) { | |
446 | if (!(dest = log_open(default_log))) | |
447 | break; | |
448 | dest->refcnt--; | |
449 | } | |
450 | logList_append(&type->logs[sev], dest); | |
451 | dest->refcnt++; | |
452 | } | |
453 | } | |
454 | type->default_set = 1; | |
455 | } | |
456 | return type; | |
457 | } | |
458 | ||
459 | /* logging functions */ | |
460 | ||
461 | void | |
462 | log_audit(struct log_type *type, enum log_severity sev, struct userNode *user, struct userNode *bot, const char *channel_name, unsigned int flags, const char *command) | |
463 | { | |
464 | struct logEntry *entry; | |
465 | unsigned int size, ii; | |
466 | char *str_next; | |
467 | ||
468 | /* First make sure severity is appropriate */ | |
469 | if ((sev != LOG_COMMAND) && (sev != LOG_OVERRIDE) && (sev != LOG_STAFF)) { | |
470 | log_module(MAIN_LOG, LOG_ERROR, "Illegal audit severity %d", sev); | |
471 | return; | |
472 | } | |
473 | /* Allocate and fill in the log entry */ | |
474 | size = sizeof(*entry) + strlen(user->nick) + strlen(command) + 2; | |
475 | if (user->handle_info) | |
476 | size += strlen(user->handle_info->handle) + 1; | |
477 | if (channel_name) | |
478 | size += strlen(channel_name) + 1; | |
479 | if (flags & AUDIT_HOSTMASK) | |
480 | size += strlen(user->ident) + strlen(user->hostname) + 2; | |
481 | entry = calloc(1, size); | |
482 | str_next = (char*)(entry + 1); | |
483 | entry->time = now; | |
484 | entry->slvl = sev; | |
485 | entry->bot = bot; | |
486 | if (channel_name) { | |
487 | size = strlen(channel_name) + 1; | |
488 | entry->channel_name = memcpy(str_next, channel_name, size); | |
489 | str_next += size; | |
490 | } | |
491 | if (true) { | |
492 | size = strlen(user->nick) + 1; | |
493 | entry->user_nick = memcpy(str_next, user->nick, size); | |
494 | str_next += size; | |
495 | } | |
496 | if (user->handle_info) { | |
497 | size = strlen(user->handle_info->handle) + 1; | |
498 | entry->user_account = memcpy(str_next, user->handle_info->handle, size); | |
499 | str_next += size; | |
500 | } | |
501 | if (flags & AUDIT_HOSTMASK) { | |
502 | size = sprintf(str_next, "%s@%s", user->ident, user->hostname) + 1; | |
503 | entry->user_hostmask = str_next; | |
504 | str_next += size; | |
505 | } else { | |
506 | entry->user_hostmask = 0; | |
507 | } | |
508 | if (true) { | |
509 | size = strlen(command) + 1; | |
510 | entry->command = memcpy(str_next, command, size); | |
511 | str_next += size; | |
512 | } | |
513 | ||
514 | /* fill in the default text for the event */ | |
515 | log_format_audit(entry); | |
516 | ||
517 | /* insert into the linked list */ | |
518 | entry->next = 0; | |
519 | entry->prev = type->log_newest; | |
520 | if (type->log_newest) | |
521 | type->log_newest->next = entry; | |
522 | else | |
523 | type->log_oldest = entry; | |
524 | type->log_newest = entry; | |
525 | type->log_count++; | |
526 | ||
527 | /* remove old elements from the linked list */ | |
528 | while (type->log_count > type->max_count) | |
529 | log_type_free_oldest(type); | |
530 | while (type->log_oldest && (type->log_oldest->time + type->max_age < (unsigned long)now)) | |
531 | log_type_free_oldest(type); | |
532 | if (type->log_oldest) | |
533 | type->log_oldest->prev = 0; | |
534 | else | |
535 | type->log_newest = 0; | |
536 | ||
537 | /* call the destination logs */ | |
538 | for (ii=0; ii<type->logs[sev].used; ++ii) { | |
539 | struct logDestination *ld = type->logs[sev].list[ii]; | |
540 | ld->vtbl->log_audit(ld, type, entry); | |
541 | } | |
542 | for (ii=0; ii<log_default->logs[sev].used; ++ii) { | |
543 | struct logDestination *ld = log_default->logs[sev].list[ii]; | |
544 | ld->vtbl->log_audit(ld, type, entry); | |
545 | } | |
546 | } | |
547 | ||
548 | void | |
549 | log_replay(struct log_type *type, int is_write, const char *line) | |
550 | { | |
551 | unsigned int ii; | |
552 | ||
553 | for (ii=0; ii<type->logs[LOG_REPLAY].used; ++ii) { | |
554 | struct logDestination *ld = type->logs[LOG_REPLAY].list[ii]; | |
555 | ld->vtbl->log_replay(ld, type, is_write, line); | |
556 | } | |
557 | for (ii=0; ii<log_default->logs[LOG_REPLAY].used; ++ii) { | |
558 | struct logDestination *ld = log_default->logs[LOG_REPLAY].list[ii]; | |
559 | ld->vtbl->log_replay(ld, type, is_write, line); | |
560 | } | |
561 | } | |
562 | ||
563 | void | |
564 | log_module(struct log_type *type, enum log_severity sev, const char *format, ...) | |
565 | { | |
566 | char msgbuf[1024]; | |
567 | unsigned int ii; | |
568 | va_list args; | |
569 | ||
570 | if (sev > LOG_FATAL) { | |
571 | log_module(MAIN_LOG, LOG_ERROR, "Illegal log_module severity %d", sev); | |
572 | return; | |
573 | } | |
574 | va_start(args, format); | |
575 | vsnprintf(msgbuf, sizeof(msgbuf), format, args); | |
576 | va_end(args); | |
577 | if (log_inited) { | |
578 | for (ii=0; ii<type->logs[sev].used; ++ii) { | |
579 | struct logDestination *ld = type->logs[sev].list[ii]; | |
580 | ld->vtbl->log_module(ld, type, sev, msgbuf); | |
581 | } | |
582 | for (ii=0; ii<log_default->logs[sev].used; ++ii) { | |
583 | struct logDestination *ld = log_default->logs[sev].list[ii]; | |
584 | ld->vtbl->log_module(ld, type, sev, msgbuf); | |
585 | } | |
586 | } else { | |
587 | /* Special behavior before we start full operation */ | |
588 | fprintf(stderr, "%s: %s\n", log_severity_names[sev], msgbuf); | |
589 | } | |
590 | } | |
591 | ||
592 | /* audit log searching */ | |
593 | ||
594 | struct logSearch * | |
595 | log_discrim_create(struct userNode *service, struct userNode *user, unsigned int argc, char *argv[]) | |
596 | { | |
597 | unsigned int ii; | |
598 | struct logSearch *discrim; | |
599 | ||
600 | /* Assume all criteria require arguments. */ | |
601 | if((argc - 1) % 2) | |
602 | { | |
603 | send_message(user, service, "MSG_MISSING_PARAMS", argv[0]); | |
604 | return NULL; | |
605 | } | |
606 | ||
607 | discrim = malloc(sizeof(struct logSearch)); | |
608 | memset(discrim, 0, sizeof(*discrim)); | |
609 | discrim->limit = 25; | |
610 | discrim->max_time = INT_MAX; | |
611 | discrim->severities = ~0; | |
612 | ||
613 | for (ii=1; ii<argc-1; ii++) { | |
614 | if (!irccasecmp(argv[ii], "bot")) { | |
615 | struct userNode *bot = GetUserH(argv[++ii]); | |
616 | if (!bot) { | |
617 | send_message(user, service, "MSG_NICK_UNKNOWN", argv[ii]); | |
618 | goto fail; | |
619 | } else if (!IsLocal(bot)) { | |
620 | send_message(user, service, "MSG_NOT_A_SERVICE", argv[ii]); | |
621 | goto fail; | |
622 | } | |
623 | discrim->masks.bot = bot; | |
624 | } else if (!irccasecmp(argv[ii], "channel")) { | |
625 | discrim->masks.channel_name = argv[++ii]; | |
626 | } else if (!irccasecmp(argv[ii], "nick")) { | |
627 | discrim->masks.user_nick = argv[++ii]; | |
628 | } else if (!irccasecmp(argv[ii], "account")) { | |
629 | discrim->masks.user_account = argv[++ii]; | |
630 | } else if (!irccasecmp(argv[ii], "hostmask")) { | |
631 | discrim->masks.user_hostmask = argv[++ii]; | |
632 | } else if (!irccasecmp(argv[ii], "command")) { | |
633 | discrim->masks.command = argv[++ii]; | |
634 | } else if (!irccasecmp(argv[ii], "age")) { | |
635 | const char *cmp = argv[++ii]; | |
636 | if (cmp[0] == '<') { | |
637 | if (cmp[1] == '=') | |
638 | discrim->min_time = now - ParseInterval(cmp+2); | |
639 | else | |
640 | discrim->min_time = now - (ParseInterval(cmp+1) - 1); | |
641 | } else if (cmp[0] == '>') { | |
642 | if (cmp[1] == '=') | |
643 | discrim->max_time = now - ParseInterval(cmp+2); | |
644 | else | |
645 | discrim->max_time = now - (ParseInterval(cmp+1) - 1); | |
646 | } else { | |
647 | discrim->min_time = now - ParseInterval(cmp+2); | |
648 | } | |
649 | } else if (!irccasecmp(argv[ii], "limit")) { | |
650 | discrim->limit = strtoul(argv[++ii], NULL, 10); | |
651 | } else if (!irccasecmp(argv[ii], "level")) { | |
652 | char *severity = argv[++ii]; | |
653 | discrim->severities = 0; | |
654 | while (1) { | |
655 | enum log_severity sev = find_severity(severity); | |
656 | if (sev == LOG_NUM_SEVERITIES) { | |
657 | send_message(user, service, "MSG_INVALID_SEVERITY", severity); | |
658 | goto fail; | |
659 | } | |
660 | discrim->severities |= 1 << sev; | |
661 | severity = strchr(severity, ','); | |
662 | if (!severity) | |
663 | break; | |
664 | severity++; | |
665 | } | |
666 | } else if (!irccasecmp(argv[ii], "type")) { | |
667 | if (!(discrim->type = dict_find(log_types, argv[++ii], NULL))) { | |
668 | send_message(user, service, "MSG_INVALID_FACILITY", argv[ii]); | |
669 | goto fail; | |
670 | } | |
671 | } else { | |
672 | send_message(user, service, "MSG_INVALID_CRITERIA", argv[ii]); | |
673 | goto fail; | |
674 | } | |
675 | } | |
676 | ||
677 | return discrim; | |
678 | fail: | |
679 | free(discrim); | |
680 | return NULL; | |
681 | } | |
682 | ||
683 | static int | |
684 | entry_match(struct logSearch *discrim, struct logEntry *entry) | |
685 | { | |
686 | if ((entry->time < discrim->min_time) | |
687 | || (entry->time > discrim->max_time) | |
688 | || !(discrim->severities & (1 << entry->slvl)) | |
689 | || (discrim->masks.bot && (discrim->masks.bot != entry->bot)) | |
690 | /* don't do glob matching, so that !events #a*b does not match #acb */ | |
691 | || (discrim->masks.channel_name | |
692 | && (!entry->channel_name | |
693 | || irccasecmp(entry->channel_name, discrim->masks.channel_name))) | |
694 | || (discrim->masks.user_nick | |
695 | && !match_ircglob(entry->user_nick, discrim->masks.user_nick)) | |
696 | || (discrim->masks.user_account | |
697 | && (!entry->user_account | |
698 | || !match_ircglob(entry->user_account, discrim->masks.user_account))) | |
699 | || (discrim->masks.user_hostmask | |
700 | && entry->user_hostmask | |
701 | && !match_ircglob(entry->user_hostmask, discrim->masks.user_hostmask)) | |
702 | || (discrim->masks.command | |
703 | && !match_ircglob(entry->command, discrim->masks.command))) { | |
704 | return 0; | |
705 | } | |
706 | return 1; | |
707 | } | |
708 | ||
709 | void | |
710 | log_report_entry(struct logEntry *match, void *extra) | |
711 | { | |
712 | struct logReport *rpt = extra; | |
713 | send_message_type(4, rpt->user, rpt->reporter, "%s", match->default_desc); | |
714 | } | |
715 | ||
716 | unsigned int | |
717 | log_entry_search(struct logSearch *discrim, entry_search_func esf, void *data) | |
718 | { | |
719 | unsigned int matched = 0; | |
720 | ||
721 | if (discrim->type) { | |
722 | struct logEntry *entry; | |
723 | ||
724 | for (entry = discrim->type->log_oldest; entry; entry = entry->next) { | |
725 | if (entry_match(discrim, entry)) { | |
726 | esf(entry, data); | |
727 | if (++matched >= discrim->limit) | |
728 | break; | |
729 | } | |
730 | } | |
731 | } else { | |
732 | dict_iterator_t it; | |
733 | ||
734 | for (it = dict_first(log_types); it; it = iter_next(it)) { | |
735 | discrim->type = iter_data(it); | |
736 | matched += log_entry_search(discrim, esf, data); | |
737 | } | |
738 | } | |
739 | ||
740 | return matched; | |
741 | } | |
742 | ||
743 | /* generic helper functions */ | |
744 | ||
745 | static void | |
746 | log_format_timestamp(time_t when, struct string_buffer *sbuf) | |
747 | { | |
748 | struct tm local; | |
749 | localtime_r(&when, &local); | |
750 | if (sbuf->size < 24) { | |
751 | sbuf->size = 24; | |
752 | free(sbuf->list); | |
753 | sbuf->list = calloc(1, 24); | |
754 | } | |
755 | sbuf->used = sprintf(sbuf->list, "[%02d:%02d:%02d %02d/%02d/%04d]", local.tm_hour, local.tm_min, local.tm_sec, local.tm_mon+1, local.tm_mday, local.tm_year+1900); | |
756 | } | |
757 | ||
758 | static void | |
759 | log_format_audit(struct logEntry *entry) | |
760 | { | |
761 | struct string_buffer sbuf; | |
762 | memset(&sbuf, 0, sizeof(sbuf)); | |
763 | log_format_timestamp(entry->time, &sbuf); | |
764 | string_buffer_append_string(&sbuf, " ("); | |
765 | string_buffer_append_string(&sbuf, entry->bot->nick); | |
766 | if (entry->channel_name) { | |
767 | string_buffer_append(&sbuf, ':'); | |
768 | string_buffer_append_string(&sbuf, entry->channel_name); | |
769 | } | |
770 | string_buffer_append_string(&sbuf, ") ["); | |
771 | string_buffer_append_string(&sbuf, entry->user_nick); | |
772 | if (entry->user_hostmask) { | |
773 | string_buffer_append(&sbuf, '!'); | |
774 | string_buffer_append_string(&sbuf, entry->user_hostmask); | |
775 | } | |
776 | if (entry->user_account) { | |
777 | string_buffer_append(&sbuf, ':'); | |
778 | string_buffer_append_string(&sbuf, entry->user_account); | |
779 | } | |
780 | string_buffer_append_string(&sbuf, "]: "); | |
781 | string_buffer_append_string(&sbuf, entry->command); | |
782 | entry->default_desc = strdup(sbuf.list); | |
783 | free(sbuf.list); | |
784 | } | |
785 | ||
786 | /* shared stub log operations act as a noop */ | |
787 | ||
788 | static void | |
789 | ldNop_reopen(UNUSED_ARG(struct logDestination *self_)) { | |
790 | /* no operation necessary */ | |
791 | } | |
792 | ||
793 | static void | |
794 | ldNop_replay(UNUSED_ARG(struct logDestination *self_), UNUSED_ARG(struct log_type *type), UNUSED_ARG(int is_write), UNUSED_ARG(const char *line)) { | |
795 | /* no operation necessary */ | |
796 | } | |
797 | ||
798 | /* file: log type */ | |
799 | ||
800 | struct logDest_file { | |
801 | struct logDestination base; | |
802 | char *fname; | |
803 | FILE *output; | |
804 | }; | |
805 | static struct logDest_vtable ldFile_vtbl; | |
806 | ||
807 | static struct logDestination * | |
808 | ldFile_open(const char *args) { | |
809 | struct logDest_file *ld; | |
810 | ld = calloc(1, sizeof(*ld)); | |
811 | ld->base.vtbl = &ldFile_vtbl; | |
812 | ld->fname = strdup(args); | |
813 | ld->output = fopen(ld->fname, "a"); | |
814 | return &ld->base; | |
815 | } | |
816 | ||
817 | static void | |
818 | ldFile_reopen(struct logDestination *self_) { | |
819 | struct logDest_file *self = (struct logDest_file*)self_; | |
820 | fclose(self->output); | |
821 | self->output = fopen(self->fname, "a"); | |
822 | } | |
823 | ||
824 | static void | |
825 | ldFile_close(struct logDestination *self_) { | |
826 | struct logDest_file *self = (struct logDest_file*)self_; | |
827 | fclose(self->output); | |
828 | free(self->fname); | |
829 | free(self); | |
830 | } | |
831 | ||
832 | static void | |
833 | ldFile_audit(struct logDestination *self_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) { | |
834 | struct logDest_file *self = (struct logDest_file*)self_; | |
835 | fputs(entry->default_desc, self->output); | |
836 | fputc('\n', self->output); | |
837 | fflush(self->output); | |
838 | } | |
839 | ||
840 | static void | |
841 | ldFile_replay(struct logDestination *self_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) { | |
842 | struct logDest_file *self = (struct logDest_file*)self_; | |
843 | struct string_buffer sbuf; | |
844 | memset(&sbuf, 0, sizeof(sbuf)); | |
845 | log_format_timestamp(now, &sbuf); | |
846 | string_buffer_append_string(&sbuf, is_write ? "W: " : " "); | |
847 | string_buffer_append_string(&sbuf, line); | |
848 | fputs(sbuf.list, self->output); | |
849 | fputc('\n', self->output); | |
850 | free(sbuf.list); | |
851 | fflush(self->output); | |
852 | } | |
853 | ||
854 | static void | |
855 | ldFile_module(struct logDestination *self_, struct log_type *type, enum log_severity sev, const char *message) { | |
856 | struct logDest_file *self = (struct logDest_file*)self_; | |
857 | struct string_buffer sbuf; | |
858 | memset(&sbuf, 0, sizeof(sbuf)); | |
859 | log_format_timestamp(now, &sbuf); | |
860 | fprintf(self->output, "%s (%s:%s) %s\n", sbuf.list, type->name, log_severity_names[sev], message); | |
861 | free(sbuf.list); | |
862 | fflush(self->output); | |
863 | } | |
864 | ||
865 | static struct logDest_vtable ldFile_vtbl = { | |
866 | "file", | |
867 | ldFile_open, | |
868 | ldFile_reopen, | |
869 | ldFile_close, | |
870 | ldFile_audit, | |
871 | ldFile_replay, | |
872 | ldFile_module | |
873 | }; | |
874 | ||
875 | /* std: log type */ | |
876 | ||
877 | static struct logDest_vtable ldStd_vtbl; | |
878 | ||
879 | static struct logDestination * | |
880 | ldStd_open(const char *args) { | |
881 | struct logDest_file *ld; | |
882 | ld = calloc(1, sizeof(*ld)); | |
883 | ld->base.vtbl = &ldStd_vtbl; | |
884 | ld->fname = strdup(args); | |
885 | ||
886 | /* Print to stderr if given "err" and default to stdout otherwise. */ | |
887 | if (atoi(args)) | |
888 | ld->output = fdopen(atoi(args), "a"); | |
889 | else if (!strcasecmp(args, "err")) | |
890 | ld->output = stdout; | |
891 | else | |
892 | ld->output = stderr; | |
893 | ||
894 | return &ld->base; | |
895 | } | |
896 | ||
897 | static void | |
898 | ldStd_close(struct logDestination *self_) { | |
899 | struct logDest_file *self = (struct logDest_file*)self_; | |
900 | free(self->fname); | |
901 | free(self); | |
902 | } | |
903 | ||
904 | static void | |
905 | ldStd_replay(struct logDestination *self_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) { | |
906 | struct logDest_file *self = (struct logDest_file*)self_; | |
907 | fprintf(self->output, "%s%s\n", is_write ? "W: " : " ", line); | |
908 | } | |
909 | ||
910 | static void | |
911 | ldStd_module(struct logDestination *self_, UNUSED_ARG(struct log_type *type), enum log_severity sev, const char *message) { | |
912 | struct logDest_file *self = (struct logDest_file*)self_; | |
913 | fprintf(self->output, "%s: %s\n", log_severity_names[sev], message); | |
914 | } | |
915 | ||
916 | static struct logDest_vtable ldStd_vtbl = { | |
917 | "std", | |
918 | ldStd_open, | |
919 | ldNop_reopen, | |
920 | ldStd_close, | |
921 | ldFile_audit, | |
922 | ldStd_replay, | |
923 | ldStd_module | |
924 | }; | |
925 | ||
926 | /* irc: log type */ | |
927 | ||
928 | struct logDest_irc { | |
929 | struct logDestination base; | |
930 | char *target; | |
931 | }; | |
932 | static struct logDest_vtable ldIrc_vtbl; | |
933 | ||
934 | static struct logDestination * | |
935 | ldIrc_open(const char *args) { | |
936 | struct logDest_irc *ld; | |
937 | ld = calloc(1, sizeof(*ld)); | |
938 | ld->base.vtbl = &ldIrc_vtbl; | |
939 | ld->target = strdup(args); | |
940 | return &ld->base; | |
941 | } | |
942 | ||
943 | static void | |
944 | ldIrc_close(struct logDestination *self_) { | |
945 | struct logDest_irc *self = (struct logDest_irc*)self_; | |
946 | free(self->target); | |
947 | free(self); | |
948 | } | |
949 | ||
950 | static void | |
951 | ldIrc_audit(struct logDestination *self_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) { | |
952 | struct logDest_irc *self = (struct logDest_irc*)self_; | |
953 | ||
954 | if (entry->channel_name) { | |
955 | send_target_message(4, self->target, entry->bot, "(%s", strchr(strchr(entry->default_desc, ' '), ':')+1); | |
956 | } else { | |
957 | send_target_message(4, self->target, entry->bot, "%s", strchr(entry->default_desc, ')')+2); | |
958 | } | |
959 | } | |
960 | ||
961 | static void | |
962 | ldIrc_module(struct logDestination *self_, struct log_type *type, enum log_severity sev, const char *message) { | |
963 | struct logDest_irc *self = (struct logDest_irc*)self_; | |
964 | extern struct userNode *opserv; | |
965 | ||
966 | send_target_message(4, self->target, opserv, "%s %s: %s\n", type->name, log_severity_names[sev], message); | |
967 | } | |
968 | ||
969 | static struct logDest_vtable ldIrc_vtbl = { | |
970 | "irc", | |
971 | ldIrc_open, | |
972 | ldNop_reopen, | |
973 | ldIrc_close, | |
974 | ldIrc_audit, | |
975 | ldNop_replay, /* totally ignore this - it would be a recipe for disaster */ | |
976 | ldIrc_module | |
977 | }; | |
978 | ||
979 | void | |
980 | log_init(void) | |
981 | { | |
982 | log_types = dict_new(); | |
983 | dict_set_free_keys(log_types, free); | |
984 | dict_set_free_data(log_types, log_type_free); | |
985 | log_dest_types = dict_new(); | |
986 | /* register log types */ | |
987 | dict_insert(log_dest_types, ldFile_vtbl.type_name, &ldFile_vtbl); | |
988 | dict_insert(log_dest_types, ldStd_vtbl.type_name, &ldStd_vtbl); | |
989 | dict_insert(log_dest_types, ldIrc_vtbl.type_name, &ldIrc_vtbl); | |
990 | conf_register_reload(log_conf_read); | |
991 | log_default = log_register_type("*", NULL); | |
992 | reg_exit_func(cleanup_logs); | |
993 | message_register_table(msgtab); | |
994 | log_inited = 1; | |
995 | } |