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