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