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