1 /*
2 * -------------------------------------------------------
3 * Copyright (C) 2003-2007 Tommi Saviranta <wnd@iki.fi>
4 * -------------------------------------------------------
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
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
16 #ifdef HAVE_CONFIG_H
17 #include <config.h>
18 #endif /* ifdef HAVE_CONFIG_H */
19
20 #ifdef QUICKLOG
21
22 #include "qlog.h"
23 #include "client.h"
24 #include "irc.h"
25 #include "list.h"
26 #include "llist.h"
27 #include "miau.h"
28 #include "tools.h"
29 #include "commands.h"
30 #include "error.h"
31 #include "common.h"
32 #include "messages.h"
33
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <string.h>
37
38
39
40 static list_type *qlog = NULL;
41
42
43
44 #ifdef QLOGSTAMP
45 static const char *qlog_add_timestamp(qlogentry *entry, char *buf,
46 size_t size);
47 #endif /* QLOGSTAMP */
48
49 static channel_type *qlog_get_channel(const char *msg);
50
51
52
53 /*
54 * Walk thru qlog to see if any of active channels has some log to be replayed.
55 * Basically this could be done while writing qlog, but this would be waste of
56 * CPU-time as qlog can be checked just as well before replaying.
57 */
58 void
qlog_check(int age)59 qlog_check(int age)
60 {
61 llist_node *iterl;
62 list_type *iter;
63 time_t oldest;
64
65 if (age == 0) {
66 oldest = 0;
67 } else {
68 oldest = time(NULL) - age;
69 }
70
71 /* First set each channel not to have qlog. */
72 for (iterl = active_channels.head; iterl != NULL; iterl = iterl->next) {
73 channel_type *chan;
74 chan = (channel_type *) iterl->data;
75 chan->hasqlog = 0;
76 }
77 for (iterl = old_channels.head; iterl != NULL; iterl = iterl->next) {
78 channel_type *chan;
79 chan = (channel_type *) iterl->data;
80 chan->hasqlog = 0;
81 }
82
83 for (iter = qlog; iter != NULL; iter = iter->next) {
84 qlogentry *entry;
85 entry = (qlogentry *) iter->data;
86 if (entry->timestamp < oldest) {
87 continue;
88 }
89 /* Don't waste time looking for channel if line's privmsg. */
90 #ifdef INBOX
91 if (entry->privmsg == 0)
92 #endif /* ifdef else INBOX */
93 {
94 channel_type *chan;
95 chan = qlog_get_channel(entry->text);
96 if (chan != NULL) {
97 chan->hasqlog = 1;
98 }
99 }
100 }
101 } /* void qlog_check(int age) */
102
103
104
105 void
qlog_replay_header(connection_type * client)106 qlog_replay_header(connection_type *client)
107 {
108 channel_type *chan;
109 llist_node *iter;
110
111 for (iter = active_channels.head; iter != NULL; iter = iter->next) {
112 chan = (channel_type *) iter->data;
113 if (chan->hasqlog) {
114 irc_write(client, ":%s NOTICE %s :%s",
115 status.nickname,
116 chan->name,
117 CLNT_QLOGSTART);
118 }
119 }
120 } /* void qlog_replay_header(connection_type *client) */
121
122
123
124 void
qlog_replay_footer(connection_type * client)125 qlog_replay_footer(connection_type *client)
126 {
127 channel_type *chan;
128 llist_node *iter;
129
130 for (iter = active_channels.head; iter != NULL; iter = iter->next) {
131 chan = (channel_type *) iter->data;
132 if (chan->hasqlog) {
133 irc_write(client, ":%s NOTICE %s :%s",
134 status.nickname,
135 chan->name,
136 CLNT_QLOGEND);
137 }
138 }
139 } /* void qlog_replay_footer(connection_type *client) */
140
141
142
143 static int
is_my_quit(const char * msg)144 is_my_quit(const char *msg)
145 {
146 const char *quit, *end;
147 int len;
148 #define TMP_LEN 64
149 char tmp[TMP_LEN + 1];
150 tmp[TMP_LEN] = '\0';
151
152 /* the cheapest test first... */
153
154 if (msg == NULL) {
155 return 0;
156 }
157
158 /* line must begin with a colon */
159 if (*msg != ':') {
160 return 0;
161 }
162
163 /* and it must be about me */
164 len = snprintf(tmp, TMP_LEN, "%s!", status.nickname);
165 if (strncmp(msg + 1, tmp, len) != 0) {
166 return 0;
167 }
168
169 /* and it must have "QUIT :" in it */
170 quit = strstr(msg, "QUIT :");
171 if (quit == NULL) {
172 return 0;
173 }
174
175 /* and "QUIT :" must come before the second colon in the message */
176 end = strchr(msg + 1, (int) ':');
177 if (end == NULL) {
178 return 0;
179 }
180 if (quit > end) {
181 return 0;
182 }
183
184 /*
185 * We're fairly sure it's about us now, but we still cannot be 100%
186 * sure. We could check username and hostname, but some servers have
187 * tendency to mutilate them beyond recognition.
188 */
189
190 return 1;
191 } /* static int is_my_quit(const char *msg) */
192
193
194
195 /*
196 * Replay quicklog data.
197 */
198 void
qlog_replay(connection_type * client,time_t oldest)199 qlog_replay(connection_type *client, time_t oldest)
200 {
201 list_type *iter;
202 #ifdef QLOGSTAMP
203 char qlogbuf[IRC_MSGLEN];
204 #endif /* QLOGSTAMP */
205
206 if (client == NULL) {
207 #ifdef ENDUSERDEBUG
208 enduserdebug("qlog_replay(NULL, oldest)");
209 #endif /* ifdef ENDUSERDEBUG */
210 return;
211 }
212
213 if (oldest != 0) {
214 oldest = time(NULL) - oldest;
215 }
216
217 /* Walk through quicklog. */
218 for (iter = qlog; iter != NULL; iter = iter->next) {
219 qlogentry *entry;
220 entry = (qlogentry *) iter->data;
221
222 /* also skip too old entries */
223 if (entry->timestamp < oldest) {
224 continue;
225 }
226
227 if (cfg.qlog_no_my_quit == 1) {
228 if (is_my_quit(entry->text) == 1) {
229 continue;
230 }
231 }
232
233 #ifdef QLOGSTAMP
234 if (cfg.timestamp != TS_NONE) {
235 const char *out;
236 out = qlog_add_timestamp(entry, qlogbuf, IRC_MSGLEN);
237 irc_write(client, "%s", out);
238 } else {
239 irc_write(client, "%s", entry->text);
240 }
241 #else /* ifdef QLOGSTAMP */
242 irc_write(client, "%s", entry->text);
243 #endif /* ifdef else QLOGSTAMP */
244 }
245 } /* void qlog_replay(connection_type *client, time_t oldest) */
246
247
248
249 #ifdef QLOGSTAMP
250 /*
251 * Add timestamp in qlogentry.
252 */
253 static const char *
qlog_add_timestamp(qlogentry * entry,char * buf,size_t size)254 qlog_add_timestamp(qlogentry *entry, char *buf, size_t size)
255 {
256 int cmd, i;
257 char *p;
258 /* TSLEN: "[HH:MM:SS]\0" == 11 */
259 #define TSLEN 11
260 char stamp[TSLEN];
261
262 /* attach tag only if we know what we're doing */
263 p = nextword(entry->text);
264 if (p == NULL) {
265 return entry->text;
266 }
267 /* Next find out what command it was. */
268 i = pos(p, ' ');
269 if (i != -1 && i < TSLEN) {
270 char tmp[18];
271 strncpy(tmp, p, i);
272 tmp[i] = '\0';
273 cmd = command_find(tmp);
274 } else {
275 return entry->text;
276 }
277
278 /* Is this something we can handle? */
279 if (cmd != CMD_PRIVMSG && cmd != CMD_NOTICE && cmd != CMD_QUIT &&
280 cmd != CMD_PART && cmd != CMD_KICK && cmd != CMD_KILL) {
281 return entry->text;
282 }
283
284 /*
285 * p now points to command. The next parameter is target, which can be
286 * either channel or user. Since channel name may contain a colon (':'),
287 * we need to skip it (and the command).
288 */
289 p = nextword(p);
290 if (p == NULL) {
291 return entry->text;
292 }
293 p = nextword(p);
294 if (p == NULL) {
295 return entry->text;
296 }
297
298 switch (cfg.timestamp) {
299 case TS_BEGINNING:
300 {
301 char rep;
302
303 p = strchr(p, (int) ':');
304 if (p != NULL) {
305 if (p[1] == '\0') {
306 p = NULL;
307 } else if (p[1] == '\1') {
308 p = strchr(p, (int) ' ');
309 }
310 }
311 /*
312 * Confused? Don't break already broken things any
313 * further.
314 */
315 if (p == NULL) {
316 return entry->text;
317 }
318 rep = *p;
319 *p = '\0'; /* ugly, but makes life easier */
320
321 strftime(stamp, TSLEN, "[%H:%M:%S]",
322 localtime(&entry->timestamp));
323
324 snprintf(buf, size, "%s%c%s %s",
325 entry->text, rep, stamp, p + 1);
326 buf[size - 1] = '\0';
327 *p = rep;
328
329 return buf;
330 }
331
332 case TS_END:
333 {
334 int add_one;
335 int len;
336
337 len = strlen(entry->text);
338 if (entry->text[len - 1] == '\1') {
339 entry->text[len - 1] = '\0';
340 add_one = 1;
341 } else {
342 add_one = 0;
343 }
344
345 strftime(stamp, TSLEN, "[%H:%M:%S]",
346 localtime(&entry->timestamp));
347
348 snprintf(buf, size, "%s %s%s", entry->text,
349 stamp, add_one == 1 ? "\1" : "");
350 buf[size - 1] = '\0';
351 return buf;
352 }
353
354 default:
355 return entry->text;
356 }
357 } /* static const char *qlog_add_timestamp(qlogentry *entry, char *buf,
358 size_t size) */
359 #endif /* ifdef QLOGSTAMP */
360
361
362
363 /*
364 * Remove old lines from quicklog.
365 */
366 void
qlog_flush(time_t oldest,int move_to_inbox)367 qlog_flush(time_t oldest, int move_to_inbox)
368 {
369 if (qlog == NULL) {
370 return;
371 }
372
373 while (qlog != NULL) { /* secondary exit condition */
374 qlogentry *entry;
375 entry = (qlogentry *) qlog->data;
376
377 /* primary exit condition */
378 if (entry->timestamp > oldest) {
379 break;
380 }
381
382 #ifdef INBOX
383 if (entry->privmsg == 1 && move_to_inbox == 1) {
384 char *message;
385
386 /* Get sender (split it) and beginning of payload. */
387 message = strstr(entry->text + 1, " :");
388
389 if (message == NULL) {
390 #ifdef ENDUSERDEBUG
391 enduserdebug("converting invalid qlog-line?");
392 enduserdebug("%s", entry->text);
393 #endif /* ifdef ENDUSERDEBUG */
394 goto drop_free;
395 }
396 strtok(entry->text, " ");
397
398 if (entry->text[0] == '\0' || message[0] == '\0') {
399 #ifdef ENDUSERDEBUG
400 enduserdebug("invalid stuff in qlog");
401 #endif /* ifdef else ENDUSERDEBUG */
402 goto drop_free;
403 }
404
405 /* termination and validity guaranteed */
406 if (inbox != NULL) {
407 fprintf(inbox, "%s <%s> %s\n",
408 get_timestamp(&entry->timestamp,
409 TIMESTAMP_SHORT),
410 entry->text + 1, message + 2);
411 fflush(inbox);
412 }
413 }
414 #endif /* INBOX */
415
416 #ifdef INBOX
417 drop_free:
418 #endif /* ifdef INBOX */
419 xfree(entry->text);
420 xfree(entry);
421 qlog = list_delete(qlog, qlog);
422 }
423 } /* void qlog_drop_old(time_t oldest, int move_to_inbox) */
424
425
426
427 /*
428 * Write lines to quick log.
429 */
430 void
qlog_write(const int privmsg,char * format,...)431 qlog_write(const int privmsg, char *format, ...)
432 {
433 qlogentry *line;
434 va_list va;
435 char buf[BUFFERSIZE];
436
437 /* First remove possible outdated lines. */
438 qlog_flush(time(NULL) - cfg.qloglength * 60, 1);
439
440 va_start(va, format);
441 vsnprintf(buf, IRC_MSGLEN - 2, format, va);
442 va_end(va);
443 buf[IRC_MSGLEN - 3] = '\0';
444
445 /* Create new line of quicklog. */
446 line = (qlogentry *) xmalloc(sizeof(qlogentry));
447 time(&line->timestamp);
448 line->text = xstrdup(buf);
449 #ifdef INBOX
450 line->privmsg = privmsg;
451 #endif /* ifdef INBOX */
452 qlog = list_add_tail(qlog, line);
453 } /* void qlog_write(const int privmsg, char *format, ...) */
454
455
456
457 static channel_type *
qlog_get_channel(const char * msg)458 qlog_get_channel(const char *msg)
459 {
460 channel_type *chan;
461 char *b, *t;
462 int l;
463
464 b = strchr(msg, (int) ' ');
465 if (b == NULL) {
466 return NULL;
467 }
468
469 b = strchr(b + 1, (int) ' ');
470 if (b == NULL) {
471 return NULL;
472 }
473
474 l = pos(b + 1, ' ');
475 if (l == -1) {
476 return NULL;
477 }
478
479 t = (char *) xmalloc(l + 1);
480 memcpy(t, b + 1, l);
481 t[l] = '\0';
482 /* Check active/old_channels. */
483 chan = channel_find(t, LIST_ACTIVE);
484 if (chan == NULL) {
485 chan = channel_find(t, LIST_OLD);
486 }
487 xfree(t);
488
489 return chan;
490 } /* static channel_type *qlog_get_channel(const char *msg) */
491
492
493
494 #endif /* ifdef QUICKLOG */
495