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