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