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