]> jfr.im git - irc/evilnet/x3.git/blob - src/log.c
3b5d7fc046a02adc5283b6e1f3f483d93a3302b1
[irc/evilnet/x3.git] / src / log.c
1 /* log.c - Diagnostic and error logging
2 * Copyright 2000-2004 srvx Development Team
3 *
4 * This file is part of x3.
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 "modcmd.h"
24 #include "helpfile.h" /* send_message, message_register, etc */
25 #include "nickserv.h"
26
27 #define Block 4096
28 #define MAXLOGSEARCHLENGTH 10000
29
30 struct userNode *chanserv;
31 struct logDestination;
32
33 struct logDest_vtable {
34 const char *type_name;
35 struct logDestination* (*open)(const char *args);
36 void (*reopen)(struct logDestination *self);
37 void (*close)(struct logDestination *self);
38 void (*log_audit)(struct logDestination *self, struct log_type *type, struct logEntry *entry);
39 void (*log_replay)(struct logDestination *self, struct log_type *type, int is_write, const char *line);
40 void (*log_module)(struct logDestination *self, struct log_type *type, enum log_severity sev, const char *message);
41 };
42
43 struct logDestination {
44 struct logDest_vtable *vtbl;
45 char *name;
46 int refcnt;
47 };
48
49 DECLARE_LIST(logList, struct logDestination*);
50
51 struct log_type {
52 char *name;
53 struct logList logs[LOG_NUM_SEVERITIES];
54 struct logEntry *log_oldest;
55 struct logEntry *log_newest;
56 unsigned int log_count;
57 unsigned int max_age;
58 unsigned int max_count;
59 unsigned int default_set : 1;
60 };
61
62 static const char *log_severity_names[] = {
63 "replay", /* 0 */
64 "debug",
65 "command",
66 "info",
67 "override",
68 "staff", /* 5 */
69 "warning",
70 "error",
71 "fatal",
72 0
73 };
74
75 static struct dict *log_dest_types;
76 static struct dict *log_dests;
77 static struct dict *log_types;
78 static struct log_type *log_default;
79 static int log_inited, log_debugged;
80
81 DEFINE_LIST(logList, struct logDestination*);
82 static void log_format_audit(struct logEntry *entry);
83 static const struct message_entry msgtab[] = {
84 { "MSG_INVALID_FACILITY", "$b%s$b is an invalid log facility." },
85 { "MSG_INVALID_SEVERITY", "$b%s$b is an invalid severity level." },
86
87 { "LAST_RESULTS", "$b%s$b] %s %s $b%s$b %s" },
88 { "LAST_ERROR", "%s:%s" },
89 { "LAST_COMMAND_LOG", "Channel Events for %s" },
90 { "LAST_LINE", "----------------------------------------" },
91 { "LAST_STOPPING_AT", "--------- Stopping at %d lines ---------" },
92 { "LAST_MAX_AGE", "-------- Data age limit reached --------" },
93 { "LAST_END_OF_LOG", "---------- Found %d Matches ------------" },
94
95 { NULL, NULL }
96 };
97
98 static struct logDestination *
99 log_open(const char *name)
100 {
101 struct logDest_vtable *vtbl;
102 struct logDestination *ld;
103 char *sep;
104 char type_name[32];
105
106 if ((ld = dict_find(log_dests, name, NULL))) {
107 ld->refcnt++;
108 return ld;
109 }
110 if ((sep = strchr(name, ':'))) {
111 memcpy(type_name, name, sep-name);
112 type_name[sep-name] = 0;
113 } else {
114 strcpy(type_name, name);
115 }
116 if (!(vtbl = dict_find(log_dest_types, type_name, NULL))) {
117 log_module(MAIN_LOG, LOG_ERROR, "Invalid log type for log '%s'.", name);
118 return 0;
119 }
120 if (!(ld = vtbl->open(sep ? sep+1 : 0)))
121 return 0;
122 ld->name = strdup(name);
123 dict_insert(log_dests, ld->name, ld);
124 ld->refcnt = 1;
125 return ld;
126 }
127
128 static void
129 logList_open(struct logList *ll, struct record_data *rd)
130 {
131 struct logDestination *ld;
132 unsigned int ii;
133
134 if (!ll->size)
135 logList_init(ll);
136 switch (rd->type) {
137 case RECDB_QSTRING:
138 if ((ld = log_open(rd->d.qstring)))
139 logList_append(ll, ld);
140 break;
141 case RECDB_STRING_LIST:
142 for (ii=0; ii<rd->d.slist->used; ++ii) {
143 if ((ld = log_open(rd->d.slist->list[ii])))
144 logList_append(ll, ld);
145 }
146 break;
147 default:
148 break;
149 }
150 }
151
152 static void
153 logList_join(struct logList *target, const struct logList *source)
154 {
155 unsigned int ii, jj, kk;
156
157 if (!source->used)
158 return;
159 jj = target->used;
160 target->used += source->used;
161 target->size += source->used;
162 target->list = realloc(target->list, target->size * sizeof(target->list[0]));
163 for (ii = 0; ii < source->used; ++ii, ++jj) {
164 int dup;
165 for (dup = 0, kk = 0; kk < jj; kk++) {
166 if (target->list[kk] == source->list[ii]) {
167 dup = 1;
168 break;
169 }
170 }
171 if (dup) {
172 jj--;
173 target->used--;
174 continue;
175 }
176 target->list[jj] = source->list[ii];
177 target->list[jj]->refcnt++;
178 }
179 }
180
181 static void
182 logList_close(struct logList *ll)
183 {
184 unsigned int ii;
185 for (ii=0; ii<ll->used; ++ii) {
186 if (!--ll->list[ii]->refcnt) {
187 struct logDestination *ld = ll->list[ii];
188 ld->vtbl->close(ld);
189 }
190 }
191 logList_clean(ll);
192 }
193
194 static void
195 close_logs(void)
196 {
197 dict_iterator_t it;
198 struct log_type *lt;
199 enum log_severity ls;
200
201 for (it = dict_first(log_types); it; it = iter_next(it)) {
202 lt = iter_data(it);
203 for (ls = 0; ls < LOG_NUM_SEVERITIES; ls++) {
204 logList_close(&lt->logs[ls]);
205
206 lt->logs[ls].size = 0;
207 lt->logs[ls].used = 0;
208 lt->logs[ls].list = 0;
209 }
210 }
211 }
212
213 static void
214 log_type_free_oldest(struct log_type *lt)
215 {
216 struct logEntry *next;
217
218 if (!lt->log_oldest)
219 return;
220 next = lt->log_oldest->next;
221 free(lt->log_oldest->default_desc);
222 free(lt->log_oldest);
223 lt->log_oldest = next;
224 lt->log_count--;
225 }
226
227 static void
228 log_type_free(void *ptr)
229 {
230 struct log_type *lt = ptr;
231
232 while (lt->log_oldest)
233 log_type_free_oldest(lt);
234 free(lt);
235 }
236
237 static void
238 cleanup_logs(void)
239 {
240
241 close_logs();
242 dict_delete(log_types);
243 dict_delete(log_dests);
244 dict_delete(log_dest_types);
245 }
246
247 static enum log_severity
248 find_severity(const char *text)
249 {
250 enum log_severity ls;
251 for (ls = 0; ls < LOG_NUM_SEVERITIES; ++ls)
252 if (!ircncasecmp(text, log_severity_names[ls], strlen(log_severity_names[ls])))
253 return ls;
254 return LOG_NUM_SEVERITIES;
255 }
256
257 /* Log keys are based on syslog.conf syntax:
258 * KEY := LOGSET '.' SEVSET
259 * LOGSET := LOGLIT | LOGLIT ',' LOGSET
260 * LOGLIT := a registered log type
261 * SEVSET := '*' | SEVLIT | '<' SEVLIT | '<=' SEVLIT | '>' SEVLIT | '>=' SEVLIT | SEVLIT ',' SEVSET
262 * SEVLIT := one of log_severity_names
263 * A KEY contains the Cartesian product of the logs in its LOGSET
264 * and the severities in its SEVSET.
265 */
266
267 static void
268 log_parse_logset(char *buffer, struct string_list *slist)
269 {
270 slist->used = 0;
271 while (buffer) {
272 char *cont = strchr(buffer, ',');
273 if (cont)
274 *cont++ = 0;
275 string_list_append(slist, strdup(buffer));
276 buffer = cont;
277 }
278 }
279
280 static void
281 log_parse_sevset(char *buffer, char targets[LOG_NUM_SEVERITIES])
282 {
283 memset(targets, 0, LOG_NUM_SEVERITIES);
284 while (buffer) {
285 char *cont;
286 enum log_severity bound;
287 int first;
288
289 cont = strchr(buffer, ',');
290 if (cont)
291 *cont++ = 0;
292 if (buffer[0] == '*' && buffer[1] == 0) {
293 for (bound = 0; bound < LOG_NUM_SEVERITIES; bound++) {
294 /* make people explicitly specify replay targets */
295 if (bound != LOG_REPLAY)
296 targets[bound] = 1;
297 }
298 } else if (buffer[0] == '<') {
299 if (buffer[1] == '=')
300 bound = find_severity(buffer+2) + 1;
301 else
302 bound = find_severity(buffer+1);
303 for (first = 1; bound > 0; bound--) {
304 /* make people explicitly specify replay targets */
305 if (bound != LOG_REPLAY || first) {
306 targets[bound] = 1;
307 first = 0;
308 }
309 }
310 } else if (buffer[0] == '>') {
311 if (buffer[1] == '=')
312 bound = find_severity(buffer+2);
313 else
314 bound = find_severity(buffer+1) + 1;
315 for (first = 1; bound < LOG_NUM_SEVERITIES; bound++) {
316 /* make people explicitly specify replay targets */
317 if (bound != LOG_REPLAY || first) {
318 targets[bound] = 1;
319 first = 0;
320 }
321 }
322 } else {
323 if (buffer[0] == '=')
324 buffer++;
325 bound = find_severity(buffer);
326 targets[bound] = 1;
327 }
328 buffer = cont;
329 }
330 }
331
332 static void
333 log_parse_cross(const char *buffer, struct string_list *types, char sevset[LOG_NUM_SEVERITIES])
334 {
335 char *dup, *sep;
336
337 dup = strdup(buffer);
338 sep = strchr(dup, '.');
339 *sep++ = 0;
340 log_parse_logset(dup, types);
341 log_parse_sevset(sep, sevset);
342 free(dup);
343 }
344
345 static void
346 log_parse_options(struct log_type *type, struct dict *conf)
347 {
348 const char *opt;
349 opt = database_get_data(conf, "max_age", RECDB_QSTRING);
350 if (opt)
351 type->max_age = ParseInterval(opt);
352 opt = database_get_data(conf, "max_count", RECDB_QSTRING);
353 if (opt)
354 type->max_count = strtoul(opt, NULL, 10);
355 }
356
357 static void
358 log_conf_read(void)
359 {
360 struct record_data *rd, *rd2;
361 dict_iterator_t it;
362 const char *sep;
363 struct log_type *type;
364 enum log_severity sev;
365 unsigned int ii;
366
367 close_logs();
368 dict_delete(log_dests);
369
370 log_dests = dict_new();
371 dict_set_free_keys(log_dests, free);
372
373 rd = conf_get_node("logs");
374 if (rd && (rd->type == RECDB_OBJECT)) {
375 for (it = dict_first(rd->d.object); it; it = iter_next(it)) {
376 if ((sep = strchr(iter_key(it), '.'))) {
377 struct logList logList;
378 char sevset[LOG_NUM_SEVERITIES];
379 struct string_list *slist;
380
381 /* It looks like a <type>.<severity> record. Try to parse it. */
382 slist = alloc_string_list(4);
383 log_parse_cross(iter_key(it), slist, sevset);
384 logList.size = 0;
385 logList_open(&logList, iter_data(it));
386 for (ii = 0; ii < slist->used; ++ii) {
387 type = log_register_type(slist->list[ii], NULL);
388 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) {
389 if (!sevset[sev])
390 continue;
391 logList_join(&type->logs[sev], &logList);
392 }
393 }
394 logList_close(&logList);
395 free_string_list(slist);
396 } else if ((rd2 = iter_data(it))
397 && (rd2->type == RECDB_OBJECT)
398 && (type = log_register_type(iter_key(it), NULL))) {
399 log_parse_options(type, rd2->d.object);
400 } else {
401 log_module(MAIN_LOG, LOG_ERROR, "Unknown logs subkey '%s'.", iter_key(it));
402 }
403 }
404 }
405 if (log_debugged)
406 log_debug();
407 }
408
409 void
410 log_debug(void)
411 {
412 enum log_severity sev;
413 struct logDestination *log_stdout;
414 struct logList target;
415
416 log_stdout = log_open("std:out");
417 logList_init(&target);
418 logList_append(&target, log_stdout);
419
420 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev)
421 logList_join(&log_default->logs[sev], &target);
422
423 logList_close(&target);
424 log_debugged = 1;
425 }
426
427 void
428 log_reopen(void)
429 {
430 dict_iterator_t it;
431 for (it = dict_first(log_dests); it; it = iter_next(it)) {
432 struct logDestination *ld = iter_data(it);
433 ld->vtbl->reopen(ld);
434 }
435 }
436
437 struct log_type *
438 log_register_type(const char *name, const char *default_log)
439 {
440 struct log_type *type;
441 struct logDestination *dest;
442 enum log_severity sev;
443
444 if (!(type = dict_find(log_types, name, NULL))) {
445 type = calloc(1, sizeof(*type));
446 type->name = strdup(name);
447 type->max_age = 600;
448 type->max_count = 1024;
449 dict_insert(log_types, type->name, type);
450 }
451 if (default_log && !type->default_set) {
452 /* If any severity level was unspecified in the config, use the default. */
453 dest = NULL;
454 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) {
455 if (sev == LOG_REPLAY)
456 continue; /* never default LOG_REPLAY */
457 if (!type->logs[sev].size) {
458 logList_init(&type->logs[sev]);
459 if (!dest) {
460 if (!(dest = log_open(default_log)))
461 break;
462 dest->refcnt--;
463 }
464 logList_append(&type->logs[sev], dest);
465 dest->refcnt++;
466 }
467 }
468 type->default_set = 1;
469 }
470 return type;
471 }
472
473 /* logging functions */
474
475 void
476 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)
477 {
478 struct logEntry *entry;
479 unsigned int size, ii;
480 char *str_next;
481
482 /* First make sure severity is appropriate */
483 if ((sev != LOG_COMMAND) && (sev != LOG_OVERRIDE) && (sev != LOG_STAFF)) {
484 log_module(MAIN_LOG, LOG_ERROR, "Illegal audit severity %d", sev);
485 return;
486 }
487 /* Allocate and fill in the log entry */
488 size = sizeof(*entry) + strlen(user->nick) + strlen(command) + 2;
489 if (user->handle_info)
490 size += strlen(user->handle_info->handle) + 1;
491 if (channel_name)
492 size += strlen(channel_name) + 1;
493 if (flags & AUDIT_HOSTMASK)
494 size += strlen(user->ident) + strlen(user->hostname) + 2;
495 entry = calloc(1, size);
496 str_next = (char*)(entry + 1);
497 entry->time = now;
498 entry->slvl = sev;
499 entry->bot = bot;
500 if (channel_name) {
501 size = strlen(channel_name) + 1;
502 entry->channel_name = memcpy(str_next, channel_name, size);
503 str_next += size;
504 }
505 if (true) {
506 size = strlen(user->nick) + 1;
507 entry->user_nick = memcpy(str_next, user->nick, size);
508 str_next += size;
509 }
510 if (user->handle_info) {
511 size = strlen(user->handle_info->handle) + 1;
512 entry->user_account = memcpy(str_next, user->handle_info->handle, size);
513 str_next += size;
514 }
515 if (flags & AUDIT_HOSTMASK) {
516 size = sprintf(str_next, "%s@%s", user->ident, user->hostname) + 1;
517 entry->user_hostmask = str_next;
518 str_next += size;
519 } else {
520 entry->user_hostmask = 0;
521 }
522 if (true) {
523 size = strlen(command) + 1;
524 entry->command = memcpy(str_next, command, size);
525 str_next += size;
526 }
527
528 /* fill in the default text for the event */
529 log_format_audit(entry);
530
531 /* insert into the linked list */
532 entry->next = 0;
533 entry->prev = type->log_newest;
534 if (type->log_newest)
535 type->log_newest->next = entry;
536 else
537 type->log_oldest = entry;
538 type->log_newest = entry;
539 type->log_count++;
540
541 /* remove old elements from the linked list */
542 while (type->log_count > type->max_count)
543 log_type_free_oldest(type);
544 while (type->log_oldest && (type->log_oldest->time + type->max_age < (unsigned long)now))
545 log_type_free_oldest(type);
546 if (type->log_oldest)
547 type->log_oldest->prev = 0;
548 else
549 type->log_newest = 0;
550
551 /* call the destination logs */
552 for (ii=0; ii<type->logs[sev].used; ++ii) {
553 struct logDestination *ld = type->logs[sev].list[ii];
554 ld->vtbl->log_audit(ld, type, entry);
555 }
556 for (ii=0; ii<log_default->logs[sev].used; ++ii) {
557 struct logDestination *ld = log_default->logs[sev].list[ii];
558 ld->vtbl->log_audit(ld, type, entry);
559 }
560 }
561
562 void
563 log_replay(struct log_type *type, int is_write, const char *line)
564 {
565 unsigned int ii;
566
567 for (ii=0; ii<type->logs[LOG_REPLAY].used; ++ii) {
568 struct logDestination *ld = type->logs[LOG_REPLAY].list[ii];
569 ld->vtbl->log_replay(ld, type, is_write, line);
570 }
571 for (ii=0; ii<log_default->logs[LOG_REPLAY].used; ++ii) {
572 struct logDestination *ld = log_default->logs[LOG_REPLAY].list[ii];
573 ld->vtbl->log_replay(ld, type, is_write, line);
574 }
575 }
576
577 void
578 log_module(struct log_type *type, enum log_severity sev, const char *format, ...)
579 {
580 char msgbuf[1024];
581 unsigned int ii;
582 va_list args;
583
584 if (sev > LOG_FATAL) {
585 log_module(MAIN_LOG, LOG_ERROR, "Illegal log_module severity %d", sev);
586 return;
587 }
588 va_start(args, format);
589 vsnprintf(msgbuf, sizeof(msgbuf), format, args);
590 va_end(args);
591 if (log_inited) {
592 for (ii=0; ii<type->logs[sev].used; ++ii) {
593 struct logDestination *ld = type->logs[sev].list[ii];
594 ld->vtbl->log_module(ld, type, sev, msgbuf);
595 }
596 for (ii=0; ii<log_default->logs[sev].used; ++ii) {
597 struct logDestination *ld = log_default->logs[sev].list[ii];
598 ld->vtbl->log_module(ld, type, sev, msgbuf);
599 }
600 } else {
601 /* Special behavior before we start full operation */
602 fprintf(stderr, "%s: %s\n", log_severity_names[sev], msgbuf);
603 }
604 if (sev == LOG_FATAL) {
605 assert(0 && "fatal message logged");
606 _exit(1);
607 }
608 }
609
610 /* audit log searching */
611
612 struct logSearch *
613 log_discrim_create(struct userNode *service, struct userNode *user, unsigned int argc, char *argv[])
614 {
615 unsigned int ii;
616 struct logSearch *discrim;
617
618 /* Assume all criteria require arguments. */
619 if((argc - 1) % 2)
620 {
621 send_message(user, service, "MSG_MISSING_PARAMS", argv[0]);
622 return NULL;
623 }
624
625 discrim = malloc(sizeof(struct logSearch));
626 memset(discrim, 0, sizeof(*discrim));
627 discrim->limit = 25;
628 discrim->max_time = INT_MAX;
629 discrim->severities = ~0;
630
631 for (ii=1; ii<argc-1; ii++) {
632 if (!irccasecmp(argv[ii], "bot")) {
633 struct userNode *bot = GetUserH(argv[++ii]);
634 if (!bot) {
635 send_message(user, service, "MSG_NICK_UNKNOWN", argv[ii]);
636 goto fail;
637 } else if (!IsLocal(bot)) {
638 send_message(user, service, "MSG_NOT_A_SERVICE", argv[ii]);
639 goto fail;
640 }
641 discrim->masks.bot = bot;
642 } else if (!irccasecmp(argv[ii], "channel")) {
643 discrim->masks.channel_name = argv[++ii];
644 } else if (!irccasecmp(argv[ii], "nick")) {
645 discrim->masks.user_nick = argv[++ii];
646 } else if (!irccasecmp(argv[ii], "account")) {
647 discrim->masks.user_account = argv[++ii];
648 } else if (!irccasecmp(argv[ii], "hostmask")) {
649 discrim->masks.user_hostmask = argv[++ii];
650 } else if (!irccasecmp(argv[ii], "command")) {
651 discrim->masks.command = argv[++ii];
652 } else if (!irccasecmp(argv[ii], "age")) {
653 const char *cmp = argv[++ii];
654 if (cmp[0] == '<') {
655 if (cmp[1] == '=')
656 discrim->min_time = now - ParseInterval(cmp+2);
657 else
658 discrim->min_time = now - (ParseInterval(cmp+1) - 1);
659 } else if (cmp[0] == '>') {
660 if (cmp[1] == '=')
661 discrim->max_time = now - ParseInterval(cmp+2);
662 else
663 discrim->max_time = now - (ParseInterval(cmp+1) - 1);
664 } else {
665 discrim->min_time = now - ParseInterval(cmp+2);
666 }
667 } else if (!irccasecmp(argv[ii], "limit")) {
668 discrim->limit = strtoul(argv[++ii], NULL, 10);
669 } else if (!irccasecmp(argv[ii], "level")) {
670 char *severity = argv[++ii];
671 discrim->severities = 0;
672 while (1) {
673 enum log_severity sev = find_severity(severity);
674 if (sev == LOG_NUM_SEVERITIES) {
675 send_message(user, service, "MSG_INVALID_SEVERITY", severity);
676 goto fail;
677 }
678 discrim->severities |= 1 << sev;
679 severity = strchr(severity, ',');
680 if (!severity)
681 break;
682 severity++;
683 }
684 } else if (!irccasecmp(argv[ii], "type")) {
685 if (!(discrim->type = dict_find(log_types, argv[++ii], NULL))) {
686 send_message(user, service, "MSG_INVALID_FACILITY", argv[ii]);
687 goto fail;
688 }
689 } else {
690 send_message(user, service, "MSG_INVALID_CRITERIA", argv[ii]);
691 goto fail;
692 }
693 }
694
695 return discrim;
696 fail:
697 free(discrim);
698 return NULL;
699 }
700
701 static int
702 entry_match(struct logSearch *discrim, struct logEntry *entry)
703 {
704 if ((entry->time < discrim->min_time)
705 || (entry->time > discrim->max_time)
706 || !(discrim->severities & (1 << entry->slvl))
707 || (discrim->masks.bot && (discrim->masks.bot != entry->bot))
708 /* don't do glob matching, so that !events #a*b does not match #acb */
709 || (discrim->masks.channel_name
710 && (!entry->channel_name
711 || irccasecmp(entry->channel_name, discrim->masks.channel_name)))
712 || (discrim->masks.user_nick
713 && !match_ircglob(entry->user_nick, discrim->masks.user_nick))
714 || (discrim->masks.user_account
715 && (!entry->user_account
716 || !match_ircglob(entry->user_account, discrim->masks.user_account)))
717 || (discrim->masks.user_hostmask
718 && entry->user_hostmask
719 && !match_ircglob(entry->user_hostmask, discrim->masks.user_hostmask))
720 || (discrim->masks.command
721 && !match_ircglob(entry->command, discrim->masks.command))) {
722 return 0;
723 }
724 return 1;
725 }
726
727 void
728 log_report_entry(struct logEntry *match, void *extra)
729 {
730 struct logReport *rpt = extra;
731 send_message_type(4, rpt->user, rpt->reporter, "%s", match->default_desc);
732 }
733
734 unsigned int
735 log_entry_search(struct logSearch *discrim, entry_search_func esf, void *data)
736 {
737 unsigned int matched = 0;
738
739 if (discrim->type) {
740 static volatile struct logEntry *last;
741 struct logEntry *entry;
742
743 for (entry = discrim->type->log_oldest, last = NULL;
744 entry;
745 last = entry, entry = entry->next) {
746 verify(entry);
747 if (entry_match(discrim, entry)) {
748 esf(entry, data);
749 if (++matched >= discrim->limit)
750 break;
751 }
752 }
753 } else {
754 dict_iterator_t it;
755
756 for (it = dict_first(log_types); it; it = iter_next(it)) {
757 discrim->type = iter_data(it);
758 matched += log_entry_search(discrim, esf, data);
759 }
760 }
761
762 return matched;
763 }
764
765 /* generic helper functions */
766
767 static void
768 log_format_timestamp(time_t when, struct string_buffer *sbuf)
769 {
770 struct tm local;
771 localtime_r(&when, &local);
772 if (sbuf->size < 24) {
773 sbuf->size = 24;
774 free(sbuf->list);
775 sbuf->list = calloc(1, 24);
776 }
777 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);
778 }
779
780 static void
781 log_format_audit(struct logEntry *entry)
782 {
783 struct string_buffer sbuf;
784 memset(&sbuf, 0, sizeof(sbuf));
785 log_format_timestamp(entry->time, &sbuf);
786 string_buffer_append_string(&sbuf, " (");
787 string_buffer_append_string(&sbuf, entry->bot->nick);
788 if (entry->channel_name) {
789 string_buffer_append(&sbuf, ':');
790 string_buffer_append_string(&sbuf, entry->channel_name);
791 }
792 string_buffer_append_string(&sbuf, ") [");
793 string_buffer_append_string(&sbuf, entry->user_nick);
794 if (entry->user_hostmask) {
795 string_buffer_append(&sbuf, '!');
796 string_buffer_append_string(&sbuf, entry->user_hostmask);
797 }
798 if (entry->user_account) {
799 string_buffer_append(&sbuf, ':');
800 string_buffer_append_string(&sbuf, entry->user_account);
801 }
802 string_buffer_append_string(&sbuf, "]: ");
803 string_buffer_append_string(&sbuf, entry->command);
804 entry->default_desc = strdup(sbuf.list);
805 free(sbuf.list);
806 }
807
808 /* shared stub log operations act as a noop */
809
810 static void
811 ldNop_reopen(UNUSED_ARG(struct logDestination *self_)) {
812 /* no operation necessary */
813 }
814
815 static void
816 ldNop_replay(UNUSED_ARG(struct logDestination *self_), UNUSED_ARG(struct log_type *type), UNUSED_ARG(int is_write), UNUSED_ARG(const char *line)) {
817 /* no operation necessary */
818 }
819
820 /* file: log type */
821
822 struct logDest_file {
823 struct logDestination base;
824 char *fname;
825 FILE *output;
826 };
827 static struct logDest_vtable ldFile_vtbl;
828
829 static struct logDestination *
830 ldFile_open(const char *args) {
831 struct logDest_file *ld;
832 ld = calloc(1, sizeof(*ld));
833 ld->base.vtbl = &ldFile_vtbl;
834 ld->fname = strdup(args);
835 ld->output = fopen(ld->fname, "a");
836 return &ld->base;
837 }
838
839 static void
840 ldFile_reopen(struct logDestination *self_) {
841 struct logDest_file *self = (struct logDest_file*)self_;
842 fclose(self->output);
843 self->output = fopen(self->fname, "a");
844 }
845
846 static void
847 ldFile_close(struct logDestination *self_) {
848 struct logDest_file *self = (struct logDest_file*)self_;
849 fclose(self->output);
850 free(self->fname);
851 free(self);
852 }
853
854 static void
855 ldFile_audit(struct logDestination *self_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) {
856 struct logDest_file *self = (struct logDest_file*)self_;
857 fputs(entry->default_desc, self->output);
858 fputc('\n', self->output);
859 fflush(self->output);
860 }
861
862 static void
863 ldFile_replay(struct logDestination *self_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) {
864 struct logDest_file *self = (struct logDest_file*)self_;
865 struct string_buffer sbuf;
866 memset(&sbuf, 0, sizeof(sbuf));
867 log_format_timestamp(now, &sbuf);
868 string_buffer_append_string(&sbuf, is_write ? "W: " : " ");
869 string_buffer_append_string(&sbuf, line);
870 fputs(sbuf.list, self->output);
871 fputc('\n', self->output);
872 free(sbuf.list);
873 fflush(self->output);
874 }
875
876 static void
877 ldFile_module(struct logDestination *self_, struct log_type *type, enum log_severity sev, const char *message) {
878 struct logDest_file *self = (struct logDest_file*)self_;
879 struct string_buffer sbuf;
880 memset(&sbuf, 0, sizeof(sbuf));
881 log_format_timestamp(now, &sbuf);
882 fprintf(self->output, "%s (%s:%s) %s\n", sbuf.list, type->name, log_severity_names[sev], message);
883 free(sbuf.list);
884 fflush(self->output);
885 }
886
887 static struct logDest_vtable ldFile_vtbl = {
888 "file",
889 ldFile_open,
890 ldFile_reopen,
891 ldFile_close,
892 ldFile_audit,
893 ldFile_replay,
894 ldFile_module
895 };
896
897 /* std: log type */
898
899 static struct logDest_vtable ldStd_vtbl;
900
901 static struct logDestination *
902 ldStd_open(const char *args) {
903 struct logDest_file *ld;
904 ld = calloc(1, sizeof(*ld));
905 ld->base.vtbl = &ldStd_vtbl;
906 ld->fname = strdup(args);
907
908 /* Print to stderr if given "err" and default to stdout otherwise. */
909 if (atoi(args))
910 ld->output = fdopen(atoi(args), "a");
911 else if (!strcasecmp(args, "err"))
912 ld->output = stdout;
913 else
914 ld->output = stderr;
915
916 return &ld->base;
917 }
918
919 static void
920 ldStd_close(struct logDestination *self_) {
921 struct logDest_file *self = (struct logDest_file*)self_;
922 free(self->fname);
923 free(self);
924 }
925
926 static void
927 ldStd_replay(struct logDestination *self_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) {
928 struct logDest_file *self = (struct logDest_file*)self_;
929 fprintf(self->output, "%s%s\n", is_write ? "W: " : " ", line);
930 }
931
932 static void
933 ldStd_module(struct logDestination *self_, UNUSED_ARG(struct log_type *type), enum log_severity sev, const char *message) {
934 struct logDest_file *self = (struct logDest_file*)self_;
935 fprintf(self->output, "%s: %s\n", log_severity_names[sev], message);
936 }
937
938 static struct logDest_vtable ldStd_vtbl = {
939 "std",
940 ldStd_open,
941 ldNop_reopen,
942 ldStd_close,
943 ldFile_audit,
944 ldStd_replay,
945 ldStd_module
946 };
947
948 /* irc: log type */
949
950 struct logDest_irc {
951 struct logDestination base;
952 char *target;
953 };
954 static struct logDest_vtable ldIrc_vtbl;
955
956 static struct logDestination *
957 ldIrc_open(const char *args) {
958 struct logDest_irc *ld;
959 ld = calloc(1, sizeof(*ld));
960 ld->base.vtbl = &ldIrc_vtbl;
961 ld->target = strdup(args);
962 return &ld->base;
963 }
964
965 static void
966 ldIrc_close(struct logDestination *self_) {
967 struct logDest_irc *self = (struct logDest_irc*)self_;
968 free(self->target);
969 free(self);
970 }
971
972 /*
973 * ldIrc_audit and ldIrc_module send log messages targetted to an IRC channel, to the channel
974 */
975 static void
976 ldIrc_audit(struct logDestination *self_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) {
977 struct logDest_irc *self = (struct logDest_irc*)self_;
978
979 if (entry->channel_name) {
980 send_target_message(5, self->target, entry->bot, "(%s", strchr(strchr(entry->default_desc, ' '), ':')+1);
981 } else {
982 send_target_message(5, self->target, entry->bot, "%s", strchr(entry->default_desc, ')')+2);
983 }
984 }
985
986 static void
987 ldIrc_module(struct logDestination *self_, struct log_type *type, enum log_severity sev, const char *message) {
988 struct logDest_irc *self = (struct logDest_irc*)self_;
989 extern struct userNode *opserv;
990
991 send_target_message(5, self->target, opserv, "%s %s: %s\n", type->name, log_severity_names[sev], message);
992 }
993
994 static struct logDest_vtable ldIrc_vtbl = {
995 "irc",
996 ldIrc_open,
997 ldNop_reopen,
998 ldIrc_close,
999 ldIrc_audit,
1000 ldNop_replay, /* totally ignore this - it would be a recipe for disaster */
1001 ldIrc_module
1002 };
1003
1004 void
1005 log_init(void)
1006 {
1007 log_types = dict_new();
1008 dict_set_free_keys(log_types, free);
1009 dict_set_free_data(log_types, log_type_free);
1010 log_dest_types = dict_new();
1011 /* register log types */
1012 dict_insert(log_dest_types, ldFile_vtbl.type_name, &ldFile_vtbl);
1013 dict_insert(log_dest_types, ldStd_vtbl.type_name, &ldStd_vtbl);
1014 dict_insert(log_dest_types, ldIrc_vtbl.type_name, &ldIrc_vtbl);
1015 conf_register_reload(log_conf_read);
1016 log_default = log_register_type("*", NULL);
1017 reg_exit_func(cleanup_logs);
1018 message_register_table(msgtab);
1019 log_inited = 1;
1020 }
1021
1022 void SyncLog(char *fmt,...)
1023 {
1024 va_list args;
1025 char buff[MAXLEN*4];
1026 char *tmp;
1027 FILE *LogFile;
1028
1029 va_start(args, fmt);
1030 vsnprintf(buff, MAXLEN, fmt, args);
1031 buff[MAXLEN - 1] = '\0';
1032 va_end(args);
1033
1034 for (tmp = buff; *tmp; tmp++)
1035 {
1036 if ((*tmp == '\n') || (*tmp == '\r'))
1037 *tmp = '\0';
1038 else if (*tmp == '\001')
1039 *tmp = ' ';
1040 }
1041
1042 if((LogFile = fopen("sync.log", "a")))
1043 {
1044 fprintf(LogFile, "%s: %s\n", time2str(time(NULL)), buff);
1045 fclose(LogFile);
1046 }
1047
1048 }
1049
1050 int parselog(char *LogLine, struct userNode *user, struct chanNode *cptr, char *chan, char *nuh, char *command, char *rest)
1051 {
1052 struct svccmd *svccmd;
1053 struct svccmd *cmd;
1054 struct service *service;
1055 const char *info;
1056 char serv[NICKLEN+1];
1057 char buf[MAXLEN];
1058 char myservc[MAXLEN];
1059 char* mychan;
1060 char* mynuh;
1061 char* mycommand;
1062 char* myrest;
1063 char* datestr;
1064 char* mywho;
1065 char *myserv;
1066 char* myserva;
1067 char* mychana;
1068 unsigned int pos;
1069 int p = 0;
1070
1071 datestr = (char *) mysep(&LogLine, "]");
1072 mywho = (char *) mysep(&LogLine, " ");
1073 mynuh = (char *) mysep(&LogLine, " ");
1074 mycommand = (char *) mysep(&LogLine, " ");
1075 myrest = (char *) mysep(&LogLine, "\0");
1076 myserva = (char *) mysep(&mywho, ":");
1077 mychana = (char *) mysep(&mywho, ":");
1078 myserv = (char *) mysep(&myserva, "(");
1079 mychan = (char *) mysep(&mychana, ")");
1080
1081 if(!mycommand)
1082 return 0;
1083
1084 if(cptr)
1085 chan = cptr->name;
1086
1087 if (!mychan)
1088 mychan = "";
1089
1090 if(!chan)
1091 chan = "";
1092
1093 if(!nuh)
1094 nuh = "";
1095 if(!command)
1096 command = "";
1097 if(!rest)
1098 rest = "";
1099 if(*chan && strcasecmp(mychan, chan))
1100 return 0;
1101 if(!myrest)
1102 myrest = "";
1103
1104 info = conf_get_data("services/opserv/nick", RECDB_QSTRING);
1105
1106 if (!myserv)
1107 myserv = "";
1108 else
1109 strcpy(myservc, myserv);
1110
1111
1112 if (!strcmp(myserv, info)) {
1113 if (!IsOper(user))
1114 return 0;
1115 else {
1116 if ((service = service_find(myserv))) {
1117 if (!(cmd = dict_find(service->commands, mycommand, NULL)))
1118 return 0;
1119
1120 if (!(svccmd = svccmd_resolve_name(cmd, mycommand)))
1121 return 0;
1122
1123 pos = snprintf(buf, sizeof(buf), "%s.%s", svccmd->command->parent->name, svccmd->command->name);
1124
1125 if (svccmd->alias.used) {
1126 buf[pos++] = ' ';
1127 unsplit_string((char**)svccmd->alias.list+1, svccmd->alias.used-1, buf+pos);
1128 }
1129 }
1130 }
1131
1132 if (!(strcmp(buf+0, "OpServ.OP")))
1133 p = 1;
1134 if (!(strcmp(buf+0, "OpServ.DEOP")))
1135 p = 1;
1136 if (!(strcmp(buf+0, "OpServ.VOICE")))
1137 p = 1;
1138 if (!(strcmp(buf+0, "OpServ.DEVOICE")))
1139 p = 1;
1140 if (!(strcmp(buf+0, "OpServ.KICK")))
1141 p = 1;
1142 if (!(strcmp(buf+0, "OpServ.KICKBAN")))
1143 p = 1;
1144
1145 if (!(strcmp(buf+0, "OpServ.OPALL")))
1146 p = 1;
1147 if (!(strcmp(buf+0, "OpServ.DEOPALL")))
1148 p = 1;
1149 if (!(strcmp(buf+0, "OpServ.VOICEALL")))
1150 p = 1;
1151 if (!(strcmp(buf+0, "OpServ.DEVOICEALL")))
1152 p = 1;
1153 if (!(strcmp(buf+0, "OpServ.KICKALL")))
1154 p = 1;
1155 if (!(strcmp(buf+0, "OpServ.KICKBANALL")))
1156 p = 1;
1157
1158
1159 if (!(strcmp(buf+0, "OpServ.INVITE")))
1160 p = 1;
1161 if (!(strcmp(buf+0, "OpServ.INVITEME")))
1162 p = 1;
1163 if (!(strcmp(buf+0, "OpServ.CLEARBANS")))
1164 p = 1;
1165 if (!(strcmp(buf+0, "OpServ.CLEARMODES")))
1166 p = 1;
1167
1168 if (p == 1)
1169 send_message(user, chanserv, "LAST_RESULTS", datestr, myserv, mynuh, mycommand, myrest);
1170
1171 p = 0;
1172 } else {
1173 sprintf(serv, "%s", "");
1174 send_message(user, chanserv, "LAST_RESULTS", datestr, serv, mynuh, mycommand, myrest);
1175 }
1176
1177 return 1;
1178
1179 }
1180
1181 int ShowLog(struct userNode *user, struct chanNode *cptr, char *chan, char *nuh, char *command, char *rest, int maxlines)
1182 {
1183 FILE *TheFile;
1184 int i, s, Last = 0, filelen, first;
1185 int line = 0, searchline = 0;
1186 int FilePosition;
1187
1188 char Buff[Block+1] = "", PrevBuff[Block+1] = "";
1189 char LogLine[(Block+1)*2] = ""; /* To hold our exported results. */
1190
1191 if(!(TheFile = fopen(AccountingLog, "r")))
1192 {
1193 send_message(user, chanserv, "LAST_ERROR", AccountingLog, strerror(errno));
1194 return 0;
1195 }
1196 s = fseek(TheFile, 0, SEEK_END); /* Start at the end. */
1197 filelen = ftell(TheFile); /* Find out the length. */
1198 FilePosition = 0; /* (from the bottom) */
1199 send_message(user, chanserv, "LAST_COMMAND_LOG", cptr->name);
1200 send_message(user, chanserv, "LAST_LINE");
1201 while(Last == 0)
1202 {
1203 FilePosition += Block;
1204 if(FilePosition > filelen)
1205 {
1206 FilePosition = filelen;
1207 Last = 1;
1208 }
1209 if((s = fseek(TheFile, filelen-FilePosition, SEEK_SET)) < 0)
1210 {
1211 send_message(user, chanserv, "LAST_ERROR", AccountingLog, strerror(errno));
1212 fclose(TheFile);
1213 return 0;
1214 }
1215 s = fread(Buff, 1, Block, TheFile);
1216 Buff[Block] = '\0';
1217 if(ferror(TheFile))
1218 {
1219 send_message(user, chanserv, "LAST_ERROR", AccountingLog, strerror(errno));
1220 fclose(TheFile);
1221 return 0;
1222 }
1223 first = 1;
1224 for(i = s-1; i >= 0; i--)
1225 {
1226 if(Buff[i] == '\n')
1227 {
1228 Buff[i] = '\0';
1229 strcpy(LogLine, &Buff[i+1]);
1230 if(first)
1231 strcat(LogLine, PrevBuff);
1232 first = 0;
1233 searchline++;
1234 if(parselog(LogLine, user, cptr, chan, nuh, command, rest))
1235 line++;
1236 if(line >= maxlines)
1237 {
1238 send_message(user, chanserv, "LAST_STOPPING_AT", maxlines);
1239 return 1;
1240 }
1241 if( searchline >= MAXLOGSEARCHLENGTH )
1242 {
1243 send_message(user, chanserv, "LAST_MAX_AGE");
1244 fclose(TheFile);
1245 return 1;
1246 }
1247
1248 }
1249 }
1250 strcpy(PrevBuff, Buff); /* Save the remaining bit. */
1251 }
1252 send_message(user, chanserv, "LAST_END_OF_LOG", line);
1253 fclose(TheFile);
1254 return 1;
1255 }