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