]> jfr.im git - irc/evilnet/x3.git/blame - src/log.c
srvx arch sync
[irc/evilnet/x3.git] / src / log.c
CommitLineData
d76ed9a9 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
26struct logDestination;
27
28struct 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
38struct logDestination {
39 struct logDest_vtable *vtbl;
40 char *name;
41 int refcnt;
42};
43
44DECLARE_LIST(logList, struct logDestination*);
45
46struct 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
57static 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
70static struct dict *log_dest_types;
71static struct dict *log_dests;
72static struct dict *log_types;
73static struct log_type *log_default;
74static int log_inited, log_debugged;
75
76DEFINE_LIST(logList, struct logDestination*);
77static void log_format_audit(struct logEntry *entry);
78static 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
84static struct logDestination *
85log_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
114static void
115logList_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
138static void
139logList_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
167static void
168logList_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
180static void
181close_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(&lt->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
199static void
200log_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
213static void
214log_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
223static void
224cleanup_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
233static enum log_severity
234find_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
253static void
254log_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
266static void
267log_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
318static void
319log_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
331static void
332log_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
343static void
344log_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
395void
396log_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
413void
414log_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
423struct log_type *
424log_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
461void
462log_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
548void
549log_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
563void
564log_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 }
0d16e639 590 if (sev == LOG_FATAL) {
591 assert(0 && "fatal message logged");
592 _exit(1);
593 }
d76ed9a9 594}
595
596/* audit log searching */
597
598struct logSearch *
599log_discrim_create(struct userNode *service, struct userNode *user, unsigned int argc, char *argv[])
600{
601 unsigned int ii;
602 struct logSearch *discrim;
603
604 /* Assume all criteria require arguments. */
605 if((argc - 1) % 2)
606 {
607 send_message(user, service, "MSG_MISSING_PARAMS", argv[0]);
608 return NULL;
609 }
610
611 discrim = malloc(sizeof(struct logSearch));
612 memset(discrim, 0, sizeof(*discrim));
613 discrim->limit = 25;
614 discrim->max_time = INT_MAX;
615 discrim->severities = ~0;
616
617 for (ii=1; ii<argc-1; ii++) {
618 if (!irccasecmp(argv[ii], "bot")) {
619 struct userNode *bot = GetUserH(argv[++ii]);
620 if (!bot) {
621 send_message(user, service, "MSG_NICK_UNKNOWN", argv[ii]);
622 goto fail;
623 } else if (!IsLocal(bot)) {
624 send_message(user, service, "MSG_NOT_A_SERVICE", argv[ii]);
625 goto fail;
626 }
627 discrim->masks.bot = bot;
628 } else if (!irccasecmp(argv[ii], "channel")) {
629 discrim->masks.channel_name = argv[++ii];
630 } else if (!irccasecmp(argv[ii], "nick")) {
631 discrim->masks.user_nick = argv[++ii];
632 } else if (!irccasecmp(argv[ii], "account")) {
633 discrim->masks.user_account = argv[++ii];
634 } else if (!irccasecmp(argv[ii], "hostmask")) {
635 discrim->masks.user_hostmask = argv[++ii];
636 } else if (!irccasecmp(argv[ii], "command")) {
637 discrim->masks.command = argv[++ii];
638 } else if (!irccasecmp(argv[ii], "age")) {
639 const char *cmp = argv[++ii];
640 if (cmp[0] == '<') {
641 if (cmp[1] == '=')
642 discrim->min_time = now - ParseInterval(cmp+2);
643 else
644 discrim->min_time = now - (ParseInterval(cmp+1) - 1);
645 } else if (cmp[0] == '>') {
646 if (cmp[1] == '=')
647 discrim->max_time = now - ParseInterval(cmp+2);
648 else
649 discrim->max_time = now - (ParseInterval(cmp+1) - 1);
650 } else {
651 discrim->min_time = now - ParseInterval(cmp+2);
652 }
653 } else if (!irccasecmp(argv[ii], "limit")) {
654 discrim->limit = strtoul(argv[++ii], NULL, 10);
655 } else if (!irccasecmp(argv[ii], "level")) {
656 char *severity = argv[++ii];
657 discrim->severities = 0;
658 while (1) {
659 enum log_severity sev = find_severity(severity);
660 if (sev == LOG_NUM_SEVERITIES) {
661 send_message(user, service, "MSG_INVALID_SEVERITY", severity);
662 goto fail;
663 }
664 discrim->severities |= 1 << sev;
665 severity = strchr(severity, ',');
666 if (!severity)
667 break;
668 severity++;
669 }
670 } else if (!irccasecmp(argv[ii], "type")) {
671 if (!(discrim->type = dict_find(log_types, argv[++ii], NULL))) {
672 send_message(user, service, "MSG_INVALID_FACILITY", argv[ii]);
673 goto fail;
674 }
675 } else {
676 send_message(user, service, "MSG_INVALID_CRITERIA", argv[ii]);
677 goto fail;
678 }
679 }
680
681 return discrim;
682 fail:
683 free(discrim);
684 return NULL;
685}
686
687static int
688entry_match(struct logSearch *discrim, struct logEntry *entry)
689{
690 if ((entry->time < discrim->min_time)
691 || (entry->time > discrim->max_time)
692 || !(discrim->severities & (1 << entry->slvl))
693 || (discrim->masks.bot && (discrim->masks.bot != entry->bot))
694 /* don't do glob matching, so that !events #a*b does not match #acb */
695 || (discrim->masks.channel_name
696 && (!entry->channel_name
697 || irccasecmp(entry->channel_name, discrim->masks.channel_name)))
698 || (discrim->masks.user_nick
699 && !match_ircglob(entry->user_nick, discrim->masks.user_nick))
700 || (discrim->masks.user_account
701 && (!entry->user_account
702 || !match_ircglob(entry->user_account, discrim->masks.user_account)))
703 || (discrim->masks.user_hostmask
704 && entry->user_hostmask
705 && !match_ircglob(entry->user_hostmask, discrim->masks.user_hostmask))
706 || (discrim->masks.command
707 && !match_ircglob(entry->command, discrim->masks.command))) {
708 return 0;
709 }
710 return 1;
711}
712
713void
714log_report_entry(struct logEntry *match, void *extra)
715{
716 struct logReport *rpt = extra;
717 send_message_type(4, rpt->user, rpt->reporter, "%s", match->default_desc);
718}
719
720unsigned int
721log_entry_search(struct logSearch *discrim, entry_search_func esf, void *data)
722{
723 unsigned int matched = 0;
724
725 if (discrim->type) {
0d16e639 726 static volatile struct logEntry *last;
727 struct logEntry *entry;
d76ed9a9 728
ec1a68c8 729 for (entry = discrim->type->log_oldest, last = NULL;
730 entry;
731 last = entry, entry = entry->next) {
732 verify(entry);
d76ed9a9 733 if (entry_match(discrim, entry)) {
734 esf(entry, data);
735 if (++matched >= discrim->limit)
736 break;
737 }
738 }
739 } else {
740 dict_iterator_t it;
741
742 for (it = dict_first(log_types); it; it = iter_next(it)) {
743 discrim->type = iter_data(it);
744 matched += log_entry_search(discrim, esf, data);
745 }
746 }
747
748 return matched;
749}
750
751/* generic helper functions */
752
753static void
754log_format_timestamp(time_t when, struct string_buffer *sbuf)
755{
756 struct tm local;
757 localtime_r(&when, &local);
758 if (sbuf->size < 24) {
759 sbuf->size = 24;
760 free(sbuf->list);
761 sbuf->list = calloc(1, 24);
762 }
763 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);
764}
765
766static void
767log_format_audit(struct logEntry *entry)
768{
769 struct string_buffer sbuf;
770 memset(&sbuf, 0, sizeof(sbuf));
771 log_format_timestamp(entry->time, &sbuf);
772 string_buffer_append_string(&sbuf, " (");
773 string_buffer_append_string(&sbuf, entry->bot->nick);
774 if (entry->channel_name) {
775 string_buffer_append(&sbuf, ':');
776 string_buffer_append_string(&sbuf, entry->channel_name);
777 }
778 string_buffer_append_string(&sbuf, ") [");
779 string_buffer_append_string(&sbuf, entry->user_nick);
780 if (entry->user_hostmask) {
781 string_buffer_append(&sbuf, '!');
782 string_buffer_append_string(&sbuf, entry->user_hostmask);
783 }
784 if (entry->user_account) {
785 string_buffer_append(&sbuf, ':');
786 string_buffer_append_string(&sbuf, entry->user_account);
787 }
788 string_buffer_append_string(&sbuf, "]: ");
789 string_buffer_append_string(&sbuf, entry->command);
790 entry->default_desc = strdup(sbuf.list);
791 free(sbuf.list);
792}
793
794/* shared stub log operations act as a noop */
795
796static void
797ldNop_reopen(UNUSED_ARG(struct logDestination *self_)) {
798 /* no operation necessary */
799}
800
801static void
802ldNop_replay(UNUSED_ARG(struct logDestination *self_), UNUSED_ARG(struct log_type *type), UNUSED_ARG(int is_write), UNUSED_ARG(const char *line)) {
803 /* no operation necessary */
804}
805
806/* file: log type */
807
808struct logDest_file {
809 struct logDestination base;
810 char *fname;
811 FILE *output;
812};
813static struct logDest_vtable ldFile_vtbl;
814
815static struct logDestination *
816ldFile_open(const char *args) {
817 struct logDest_file *ld;
818 ld = calloc(1, sizeof(*ld));
819 ld->base.vtbl = &ldFile_vtbl;
820 ld->fname = strdup(args);
821 ld->output = fopen(ld->fname, "a");
822 return &ld->base;
823}
824
825static void
826ldFile_reopen(struct logDestination *self_) {
827 struct logDest_file *self = (struct logDest_file*)self_;
828 fclose(self->output);
829 self->output = fopen(self->fname, "a");
830}
831
832static void
833ldFile_close(struct logDestination *self_) {
834 struct logDest_file *self = (struct logDest_file*)self_;
835 fclose(self->output);
836 free(self->fname);
837 free(self);
838}
839
840static void
841ldFile_audit(struct logDestination *self_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) {
842 struct logDest_file *self = (struct logDest_file*)self_;
843 fputs(entry->default_desc, self->output);
844 fputc('\n', self->output);
845 fflush(self->output);
846}
847
848static void
849ldFile_replay(struct logDestination *self_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) {
850 struct logDest_file *self = (struct logDest_file*)self_;
851 struct string_buffer sbuf;
852 memset(&sbuf, 0, sizeof(sbuf));
853 log_format_timestamp(now, &sbuf);
854 string_buffer_append_string(&sbuf, is_write ? "W: " : " ");
855 string_buffer_append_string(&sbuf, line);
856 fputs(sbuf.list, self->output);
857 fputc('\n', self->output);
858 free(sbuf.list);
859 fflush(self->output);
860}
861
862static void
863ldFile_module(struct logDestination *self_, struct log_type *type, enum log_severity sev, const char *message) {
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 fprintf(self->output, "%s (%s:%s) %s\n", sbuf.list, type->name, log_severity_names[sev], message);
869 free(sbuf.list);
870 fflush(self->output);
871}
872
873static struct logDest_vtable ldFile_vtbl = {
874 "file",
875 ldFile_open,
876 ldFile_reopen,
877 ldFile_close,
878 ldFile_audit,
879 ldFile_replay,
880 ldFile_module
881};
882
883/* std: log type */
884
885static struct logDest_vtable ldStd_vtbl;
886
887static struct logDestination *
888ldStd_open(const char *args) {
889 struct logDest_file *ld;
890 ld = calloc(1, sizeof(*ld));
891 ld->base.vtbl = &ldStd_vtbl;
892 ld->fname = strdup(args);
893
894 /* Print to stderr if given "err" and default to stdout otherwise. */
895 if (atoi(args))
896 ld->output = fdopen(atoi(args), "a");
897 else if (!strcasecmp(args, "err"))
898 ld->output = stdout;
899 else
900 ld->output = stderr;
901
902 return &ld->base;
903}
904
905static void
906ldStd_close(struct logDestination *self_) {
907 struct logDest_file *self = (struct logDest_file*)self_;
908 free(self->fname);
909 free(self);
910}
911
912static void
913ldStd_replay(struct logDestination *self_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) {
914 struct logDest_file *self = (struct logDest_file*)self_;
915 fprintf(self->output, "%s%s\n", is_write ? "W: " : " ", line);
916}
917
918static void
919ldStd_module(struct logDestination *self_, UNUSED_ARG(struct log_type *type), enum log_severity sev, const char *message) {
920 struct logDest_file *self = (struct logDest_file*)self_;
921 fprintf(self->output, "%s: %s\n", log_severity_names[sev], message);
922}
923
924static struct logDest_vtable ldStd_vtbl = {
925 "std",
926 ldStd_open,
927 ldNop_reopen,
928 ldStd_close,
929 ldFile_audit,
930 ldStd_replay,
931 ldStd_module
932};
933
934/* irc: log type */
935
936struct logDest_irc {
937 struct logDestination base;
938 char *target;
939};
940static struct logDest_vtable ldIrc_vtbl;
941
942static struct logDestination *
943ldIrc_open(const char *args) {
944 struct logDest_irc *ld;
945 ld = calloc(1, sizeof(*ld));
946 ld->base.vtbl = &ldIrc_vtbl;
947 ld->target = strdup(args);
948 return &ld->base;
949}
950
951static void
952ldIrc_close(struct logDestination *self_) {
953 struct logDest_irc *self = (struct logDest_irc*)self_;
954 free(self->target);
955 free(self);
956}
957
958static void
959ldIrc_audit(struct logDestination *self_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) {
960 struct logDest_irc *self = (struct logDest_irc*)self_;
961
962 if (entry->channel_name) {
963 send_target_message(4, self->target, entry->bot, "(%s", strchr(strchr(entry->default_desc, ' '), ':')+1);
964 } else {
965 send_target_message(4, self->target, entry->bot, "%s", strchr(entry->default_desc, ')')+2);
966 }
967}
968
969static void
970ldIrc_module(struct logDestination *self_, struct log_type *type, enum log_severity sev, const char *message) {
971 struct logDest_irc *self = (struct logDest_irc*)self_;
972 extern struct userNode *opserv;
973
974 send_target_message(4, self->target, opserv, "%s %s: %s\n", type->name, log_severity_names[sev], message);
975}
976
977static struct logDest_vtable ldIrc_vtbl = {
978 "irc",
979 ldIrc_open,
980 ldNop_reopen,
981 ldIrc_close,
982 ldIrc_audit,
983 ldNop_replay, /* totally ignore this - it would be a recipe for disaster */
984 ldIrc_module
985};
986
987void
988log_init(void)
989{
990 log_types = dict_new();
991 dict_set_free_keys(log_types, free);
992 dict_set_free_data(log_types, log_type_free);
993 log_dest_types = dict_new();
994 /* register log types */
995 dict_insert(log_dest_types, ldFile_vtbl.type_name, &ldFile_vtbl);
996 dict_insert(log_dest_types, ldStd_vtbl.type_name, &ldStd_vtbl);
997 dict_insert(log_dest_types, ldIrc_vtbl.type_name, &ldIrc_vtbl);
998 conf_register_reload(log_conf_read);
999 log_default = log_register_type("*", NULL);
1000 reg_exit_func(cleanup_logs);
1001 message_register_table(msgtab);
1002 log_inited = 1;
1003}
eb5d6b73 1004
1005void SyncLog(char *fmt,...)
1006{
1007 va_list args;
1008 char buff[MAXLEN*4];
1009 char *tmp;
1010 FILE *LogFile;
1011
1012 va_start(args, fmt);
1013 vsnprintf(buff, MAXLEN, fmt, args);
1014 buff[MAXLEN - 1] = '\0';
1015 va_end(args);
1016
1017 for (tmp = buff; *tmp; tmp++)
1018 {
1019 if ((*tmp == '\n') || (*tmp == '\r'))
1020 *tmp = '\0';
1021 else if (*tmp == '\001')
1022 *tmp = ' ';
1023 }
1024
1025 if((LogFile = fopen("sync.log", "a")))
1026 {
1027 fprintf(LogFile, "%s: %s\n", time2str(time(NULL)), buff);
1028 fclose(LogFile);
1029 }
1030
1031}