Merge from STABLE
[jelmer/ctrlproxy.git] / mods / log_custom.c
1 /* 
2         ctrlproxy: A modular IRC proxy
3         (c) 2002-2003 Jelmer Vernooij <jelmer@nl.linux.org>
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         You should have received a copy of the GNU General Public License
16         along with this program; if not, write to the Free Software
17         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #define _GNU_SOURCE
21 #include "ctrlproxy.h"
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <time.h>
28 #include <glib.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32
33 #define MAX_SUBST 256
34 #undef G_LOG_DOMAIN
35 #define G_LOG_DOMAIN "log_custom"
36
37 static xmlNodePtr xmlConf = NULL;
38
39 /* Translation table */
40 struct log_mapping {
41         char *command;
42         char subst;
43         int index;
44         /* If index is -1 */
45         char *(*callback) (struct line *l, gboolean case_sensitive);
46 };
47
48 static char *get_hours(struct line *l, gboolean case_sensitive) { 
49         char *ret;
50         time_t ti = time(NULL);
51         struct tm *t = localtime(&ti);
52         asprintf(&ret, "%02d", t->tm_hour);
53         return ret;
54 }
55
56 static char *get_minutes(struct line *l, gboolean case_sensitive) { 
57         char *ret;
58         time_t ti = time(NULL);
59         struct tm *t = localtime(&ti);
60         asprintf(&ret, "%02d", t->tm_min);
61         return ret;
62 }
63
64 static char *get_seconds(struct line *l, gboolean case_sensitive) { 
65         char *ret;
66         time_t ti = time(NULL);
67         struct tm *t = localtime(&ti);
68         asprintf(&ret, "%02d", t->tm_sec);
69         return ret;
70 }
71
72 static char *get_nick(struct line *l, gboolean case_sensitive) {
73         if(line_get_nick(l)) {
74                 if(case_sensitive) return g_ascii_strdown(line_get_nick(l), -1);
75                 else return strdup(line_get_nick(l)); 
76         }
77         
78         if(l->direction == TO_SERVER) return xmlGetProp(l->network->xmlConf, "nick");
79         
80         return strdup("");
81 }
82
83 static char *get_network(struct line *l, gboolean case_sensitive) 
84 { return xmlGetProp(l->network->xmlConf, "name"); }
85 static char *get_server(struct line *l, gboolean case_sensitive)
86 { return xmlGetProp(l->network->current_server, "name"); }
87
88 static char *get_percent(struct line *l, gboolean case_sensitive) { return strdup("%"); }
89
90 static char *identifier = NULL;
91
92 static char *get_identifier(struct line *l, gboolean case_sensitive) { 
93         if(case_sensitive) return g_ascii_strdown(identifier, -1); 
94         else return strdup(identifier); 
95 }
96
97 static struct log_mapping mappings[] = {
98         {NULL, '@', -1, get_identifier },
99         {NULL, 'h', -1, get_hours },
100         {NULL, 'M', -1, get_minutes },
101         {NULL, 's', -1, get_seconds },
102         {NULL, 'n', -1, get_nick },
103         {NULL, 'N', -1, get_network },
104         {NULL, 'S', -1, get_server },
105         {NULL, '%', -1, get_percent },
106         {"JOIN", 'c', 1, NULL },
107         {"PART", 'c', 1, NULL },
108         {"PART", 'm', 2, NULL },
109         {"KICK", 'c', 1, NULL },
110         {"KICK", 't', 2, NULL },
111         {"KICK", 'r', 3, NULL },
112         {"QUIT", 'm', 1, NULL },
113         {"NOTICE", 't', 1, NULL },
114         {"NOTICE", 'm', 2, NULL },
115         {"PRIVMSG", 't', 1, NULL },
116         {"PRIVMSG", 'm', 2, NULL },
117         {"TOPIC", 'c', 1, NULL },
118         {"TOPIC", 't', 2, NULL },
119         {"MODE", 't', 1, NULL },
120         {"MODE", 'p', 2, NULL },
121         {"NICK", 'r', 1, NULL },
122         {NULL, '0', 0, NULL },
123         {NULL, '1', 1, NULL },
124         {NULL, '2', 2, NULL },
125         {NULL, '3', 3, NULL },
126         {NULL, '4', 4, NULL },
127         {NULL, '5', 5, NULL },
128         {NULL, '6', 6, NULL },
129         {NULL, '7', 7, NULL },
130         {NULL, '8', 8, NULL },
131         {NULL, '9', 9, NULL },
132         { NULL }
133 };
134
135 static char *find_mapping(struct line *l, char c, gboolean case_sensitive)
136 {
137         int i;
138         for(i = 0; mappings[i].subst; i++) {
139                 if(mappings[i].command && 
140                    strcmp(mappings[i].command, l->args[0])) continue;
141                 if(mappings[i].subst != c)continue;
142                 if(mappings[i].index == -1) return mappings[i].callback(l, case_sensitive);
143                 if(mappings[i].index < l->argc) {
144                         if(case_sensitive) return g_ascii_strdown(l->args[mappings[i].index], -1);
145                         else return strdup(l->args[mappings[i].index]);
146                 }
147         }
148         return strdup("");
149 }
150
151 static void custom_subst(char **_new, char *fmt, struct line *l, char *_identifier, gboolean case_sensitive)
152 {
153         char *subst[MAX_SUBST];
154         char *new;
155         size_t len, curpos = 0;
156         int i;
157
158         identifier = _identifier;
159
160         len = strlen(fmt);
161         memset(subst, 0, sizeof(char *) * MAX_SUBST);
162         for(i = 0; i < strlen(fmt); i++) {
163                 if(fmt[i] == '%') {
164                         subst[(int)fmt[i+1]] = find_mapping(l, fmt[i+1], case_sensitive);       
165                         len += strlen(subst[(int)fmt[i+1]]);
166                 }
167         }
168
169         len++; /* newline! */
170
171         new = malloc(len);
172         for(i = 0; i < strlen(fmt); i++) {
173                 if(fmt[i] == '%') {
174                         new[curpos] = '\0';
175                         strncat(new, subst[(int)fmt[i+1]], len);
176                         curpos+=strlen(subst[(int)fmt[i+1]]);
177                         i++;
178                 } else {
179                         new[curpos] = fmt[i];
180                         curpos++;
181                 }
182         }
183         new[curpos] = '\0';
184
185         for(i = 0; i < MAX_SUBST; i++) { if(subst[i])free(subst[i]); }
186         *_new = new;
187 }
188
189 /* Syntax:
190 Always:
191  * %h -> hours
192  * %m -> minutes
193  * %s -> seconds
194  * %n -> initiating nick
195  * %u -> initiating user
196  * %N -> network name
197  * %S -> server name
198  * %% -> percent sign
199 If appropriate:
200  * %c -> channel name
201  * %m -> message
202
203  -- JOIN: %c
204  -- PART: %c, %m
205  -- KICK: %t (target nick), %r (reason)
206  -- QUIT: %m
207  -- NOTICE/PRIVMSG: %t (target nick/channel), %m
208  -- MODE: %p(mode change), %c, %t (target nick)
209  -- TOPIC: %t
210  -- NICK: %r
211  */
212
213 static GHashTable *files = NULL;
214
215 static FILE *find_channel_file(struct line *l, char *identifier) {
216         char *n = NULL;
217         xmlNodePtr cur = xmlFindChildByElementName(xmlConf, "logfilename");
218         FILE *f;
219         char *logfilename;
220         if(!cur) return NULL;
221         logfilename = xmlNodeGetContent(cur);
222         if(!logfilename)return NULL;
223         custom_subst(&n, logfilename, l, identifier, TRUE);
224         free(logfilename);
225         f = g_hash_table_lookup(files, n);
226         free(n);
227         return f;
228 }
229
230 static FILE *find_add_channel_file(struct line *l, char *identifier) {
231         char *n = NULL, *dn, *p;
232         FILE *f;
233         xmlNodePtr cur = xmlFindChildByElementName(xmlConf, "logfilename");
234         char *logfilename;
235         if(!cur) return NULL;
236
237         logfilename = xmlNodeGetContent(cur);
238         if(!logfilename) return NULL;
239         custom_subst(&n, logfilename, l, identifier, TRUE);
240         f = g_hash_table_lookup(files, n);
241         if(!f) {
242                 dn = strdup(n);
243                 
244                 /* Only include directory-part */
245                 p = strrchr(dn, '/');
246                 if(p) *p = '\0';
247
248                 /* Check if directory needs to be created */
249                 if(access(dn, F_OK) != 0 && mkdir(dn, 0700) == -1) {
250                         g_warning("Couldn't create directory %s for logging!", dn);
251                         xmlFree(logfilename);
252                         free(dn);
253                         free(n);
254                         return NULL;
255                 }
256                 free(dn);
257                 
258                 /* Then open the correct filename */
259                 custom_subst(&n, logfilename, l, identifier, TRUE);
260                 f = fopen(n, "a+");
261                 if(!f) {
262                         g_warning("Couldn't open file %s for logging!", n);
263                         xmlFree(logfilename);
264                         free(n);
265                         return NULL;
266                 }
267                 g_hash_table_insert(files, n, f);
268                 free(n);
269         } else free(n);
270         xmlFree(logfilename);
271         return f;
272 }
273
274 static void file_write_target(const char *n, struct line *l) 
275 {
276         char *t, *s, *fmt;
277         xmlNodePtr cur;
278         FILE *f;
279         char *own_nick = xmlGetProp(l->network->xmlConf, "nick");
280
281         cur = xmlFindChildByElementName(xmlConf, n);
282         if(!cur) return;
283
284         fmt = xmlNodeGetContent(cur);
285         if(!fmt) return;
286
287
288         if(!strcasecmp(own_nick, l->args[1])) {
289                 if(line_get_nick(l)) { t = strdup(line_get_nick(l)); }
290                 else { t = strdup("_messages_"); }
291         } else {
292                 t = strdup(l->args[1]);
293         }
294         xmlFree(own_nick);
295
296         f = find_add_channel_file(l, t);
297         if(!f) { free(t); return; }
298         
299         custom_subst(&s, fmt, l, t, FALSE);
300         free(t);
301         xmlFree(fmt);
302
303     fputs(s, f);
304         fputc('\n', f);
305         fflush(f);
306
307         free(s);
308 }
309
310 static void file_write_channel_only(const char *n, struct line *l)
311 {
312         char *s, *fmt;
313         xmlNodePtr cur;
314         FILE *f;
315
316         cur = xmlFindChildByElementName(xmlConf, n);
317         if(!cur) return;
318
319         fmt = xmlNodeGetContent(cur);
320         if(!fmt)return;
321
322         f = find_add_channel_file(l, l->args[1]);
323         if(!f) return; 
324
325         custom_subst(&s, fmt, l, l->args[1], FALSE);
326         xmlFree(fmt);
327
328         fputs(s, f); fputc('\n', f);
329         fflush(f);
330
331         free(s);
332 }
333
334 static void file_write_channel_query(const char *n, struct line *l)
335 {
336         char *s, *fmt;
337         xmlNodePtr cur;
338         char *nick;
339         FILE *f;
340         GList *gl;
341
342         nick = line_get_nick(l);
343         if(!nick)return;
344
345         cur = xmlFindChildByElementName(xmlConf, n);
346         if(!cur)return;
347         fmt = xmlNodeGetContent(cur);
348         if(!fmt)return;
349
350         /* check for the query first */
351         f = find_channel_file(l, nick);
352
353         if(f) {
354                 custom_subst(&s, fmt, l, nick, FALSE);
355                 fputs(s, f); fputc('\n', f);
356                 fflush(f);
357                 free(s);
358         }
359         
360         /* now, loop thru the channels and check if the user is there */
361         gl = l->network->channels;
362         while(gl) {
363                 struct channel *c = (struct channel *)gl->data;
364                 if(find_nick(c, nick)) {
365                         char *channame = xmlGetProp(c->xmlConf, "name");
366                         f = find_add_channel_file(l, channame);
367                         if(f) {
368                                 custom_subst(&s, fmt, l, channame, FALSE);
369                                 fputs(s, f); fputc('\n', f);
370                                 fflush(f);
371                                 free(s);
372                         }
373                         xmlFree(channame);
374                 }
375                 gl = gl->next;
376         }
377         
378         xmlFree(fmt);
379 }
380
381 static gboolean log_custom_data(struct line *l)
382 {
383         char *nick = NULL;
384         char *user = NULL;
385         FILE *f = NULL;
386         if(!l->args || !l->args[0] || l->options & LINE_NO_LOGGING)return TRUE;
387         if(l->origin)nick = strdup(l->origin);
388         if(nick)user = strchr(nick, '!');
389         if(user){ *user = '\0';user++; }
390         if(!nick && xmlHasProp(l->network->xmlConf, "nick"))nick = xmlGetProp(l->network->xmlConf, "nick");
391
392         printf("Writing logs for line of %s\n", l->args[0]);
393         
394         /* Loop thru possible values for %@ */
395
396         /* There are a few possibilities:
397          * - log to line_get_nick(l) or l->args[1] file depending on which 
398          *   was the current user (PRIVMSG, NOTICE, ACTION, MODE) (target)
399          * - log to all channels line_get_nick(l) was on, and to query, if applicable (NICK, QUIT) (channel_query)
400          * - log to channel only (KICK, PART, JOIN, TOPIC) (channel_only)
401          */
402
403         if(l->direction == FROM_SERVER && !strcasecmp(l->args[0], "JOIN")) {
404                 file_write_target("join", l); 
405         } else if(l->direction == FROM_SERVER && !strcasecmp(l->args[0], "PART")) {
406                 file_write_channel_only("part", l);
407         } else if(!strcasecmp(l->args[0], "PRIVMSG")) {
408                 if(l->args[2][0] == '\ 1') { 
409                         l->args[2][strlen(l->args[2])-1] = '\0';
410                         if(!strncasecmp(l->args[2], "\ 1ACTION ", 8)) { 
411                                 l->args[2]+=8;
412                                 file_write_target("action", l);
413                                 l->args[2]-=8;
414                         }
415                         l->args[2][strlen(l->args[2])] = '\ 1';
416                         /* Ignore all other ctcp messages */
417                 } else {
418                         file_write_target("msg", l);
419                 }
420         } else if(!strcasecmp(l->args[0], "NOTICE")) {
421                 file_write_target("notice", l);
422         } else if(!strcasecmp(l->args[0], "MODE") && l->args[1] && is_channelname(l->args[1], l->network) && l->direction == FROM_SERVER) {
423                 file_write_target("mode", l);
424         } else if(!strcasecmp(l->args[0], "QUIT")) {
425                 file_write_channel_query("quit", l);
426         } else if(!strcasecmp(l->args[0], "KICK") && l->args[1] && l->args[2] && l->direction == FROM_SERVER) {
427                 if(!strchr(l->args[1], ',')) {
428                         file_write_channel_only("kick", l);
429                 } else { 
430                         char *channels = strdup(l->args[1]);
431                         char *nicks = strdup(l->args[1]);
432                         char *p,*n; char cont = 1;
433                         char *_nick;
434
435                         p = channels;
436                         _nick = nicks;
437                         while(cont) {
438                                 n = strchr(p, ',');
439
440                                 if(!n) cont = 0;
441                                 else *n = '\0';
442
443                                 file_write_channel_only("kick", l);
444
445                                 p = n+1;
446                                 _nick = strchr(_nick, ',');
447                                 if(!_nick)break;
448                                 _nick++;
449                         }
450                         
451                         free(channels);
452                         free(nicks);
453                 }
454         } else if(!strcasecmp(l->args[0], "TOPIC") && l->direction == FROM_SERVER && l->args[1]) {
455                 if(l->args[2]) file_write_channel_only("topic", l);
456                 else file_write_channel_only("notopic", l);
457         } else if(!strcasecmp(l->args[0], "NICK") && l->direction == FROM_SERVER && l->args[1]) {
458                 file_write_channel_query("nickchange", l);
459         }
460
461         if(f) fflush(f);
462
463         if(nick)free(nick);
464
465         return TRUE;
466 }
467
468 gboolean fini_plugin(struct plugin *p)
469 {
470         del_filter(log_custom_data);
471         return TRUE;
472 }
473
474 gboolean init_plugin(struct plugin *p)
475 {
476         xmlConf = p->xmlConf;
477         g_assert(p->xmlConf);
478         if(!xmlFindChildByElementName(xmlConf, "logfilename")) {
479                 g_warning("No <logfilename> tag for log_custom module");
480                 return FALSE;
481         }
482         files = g_hash_table_new(g_str_hash, g_str_equal);
483         add_filter("log_custom", log_custom_data);
484         return TRUE;
485 }