2 ctrlproxy: A modular IRC proxy
3 (c) 2002-2003 Jelmer Vernooij <jelmer@nl.linux.org>
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.
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.
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.
21 #include "ctrlproxy.h"
30 #include <sys/types.h>
35 #define G_LOG_DOMAIN "log_custom"
37 static xmlNodePtr xmlConf = NULL;
39 /* Translation table */
45 char *(*callback) (struct line *l, gboolean case_sensitive);
48 static char *get_hours(struct line *l, gboolean case_sensitive) {
50 time_t ti = time(NULL);
51 struct tm *t = localtime(&ti);
52 asprintf(&ret, "%02d", t->tm_hour);
56 static char *get_minutes(struct line *l, gboolean case_sensitive) {
58 time_t ti = time(NULL);
59 struct tm *t = localtime(&ti);
60 asprintf(&ret, "%02d", t->tm_min);
64 static char *get_seconds(struct line *l, gboolean case_sensitive) {
66 time_t ti = time(NULL);
67 struct tm *t = localtime(&ti);
68 asprintf(&ret, "%02d", t->tm_sec);
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));
78 if(l->direction == TO_SERVER) return xmlGetProp(l->network->xmlConf, "nick");
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"); }
88 static char *get_percent(struct line *l, gboolean case_sensitive) { return strdup("%"); }
90 static char *identifier = NULL;
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);
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 },
135 static char *find_mapping(struct line *l, char c, gboolean case_sensitive)
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]);
151 static void custom_subst(char **_new, char *fmt, struct line *l, char *_identifier, gboolean case_sensitive)
153 char *subst[MAX_SUBST];
155 size_t len, curpos = 0;
158 identifier = _identifier;
161 memset(subst, 0, sizeof(char *) * MAX_SUBST);
162 for(i = 0; i < strlen(fmt); i++) {
164 subst[(int)fmt[i+1]] = find_mapping(l, fmt[i+1], case_sensitive);
165 len += strlen(subst[(int)fmt[i+1]]);
169 len++; /* newline! */
172 for(i = 0; i < strlen(fmt); i++) {
175 strncat(new, subst[(int)fmt[i+1]], len);
176 curpos+=strlen(subst[(int)fmt[i+1]]);
179 new[curpos] = fmt[i];
185 for(i = 0; i < MAX_SUBST; i++) { if(subst[i])free(subst[i]); }
194 * %n -> initiating nick
195 * %u -> initiating user
205 -- KICK: %t (target nick), %r (reason)
207 -- NOTICE/PRIVMSG: %t (target nick/channel), %m
208 -- MODE: %p(mode change), %c, %t (target nick)
213 static GHashTable *files = NULL;
215 static FILE *find_channel_file(struct line *l, char *identifier) {
217 xmlNodePtr cur = xmlFindChildByElementName(xmlConf, "logfilename");
220 if(!cur) return NULL;
221 logfilename = xmlNodeGetContent(cur);
222 if(!logfilename)return NULL;
223 custom_subst(&n, logfilename, l, identifier, TRUE);
225 f = g_hash_table_lookup(files, n);
230 static FILE *find_add_channel_file(struct line *l, char *identifier) {
231 char *n = NULL, *dn, *p;
233 xmlNodePtr cur = xmlFindChildByElementName(xmlConf, "logfilename");
235 if(!cur) return NULL;
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);
244 /* Only include directory-part */
245 p = strrchr(dn, '/');
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);
258 /* Then open the correct filename */
259 custom_subst(&n, logfilename, l, identifier, TRUE);
262 g_warning("Couldn't open file %s for logging!", n);
263 xmlFree(logfilename);
267 g_hash_table_insert(files, n, f);
270 xmlFree(logfilename);
274 static void file_write_target(const char *n, struct line *l)
279 char *own_nick = xmlGetProp(l->network->xmlConf, "nick");
281 cur = xmlFindChildByElementName(xmlConf, n);
284 fmt = xmlNodeGetContent(cur);
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_"); }
292 t = strdup(l->args[1]);
296 f = find_add_channel_file(l, t);
297 if(!f) { free(t); return; }
299 custom_subst(&s, fmt, l, t, FALSE);
310 static void file_write_channel_only(const char *n, struct line *l)
316 cur = xmlFindChildByElementName(xmlConf, n);
319 fmt = xmlNodeGetContent(cur);
322 f = find_add_channel_file(l, l->args[1]);
325 custom_subst(&s, fmt, l, l->args[1], FALSE);
328 fputs(s, f); fputc('\n', f);
334 static void file_write_channel_query(const char *n, struct line *l)
342 nick = line_get_nick(l);
345 cur = xmlFindChildByElementName(xmlConf, n);
347 fmt = xmlNodeGetContent(cur);
350 /* check for the query first */
351 f = find_channel_file(l, nick);
354 custom_subst(&s, fmt, l, nick, FALSE);
355 fputs(s, f); fputc('\n', f);
360 /* now, loop thru the channels and check if the user is there */
361 gl = l->network->channels;
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);
368 custom_subst(&s, fmt, l, channame, FALSE);
369 fputs(s, f); fputc('\n', f);
381 static gboolean log_custom_data(struct line *l)
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");
392 printf("Writing logs for line of %s\n", l->args[0]);
394 /* Loop thru possible values for %@ */
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)
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)) {
412 file_write_target("action", l);
415 l->args[2][strlen(l->args[2])] = '
\ 1';
416 /* Ignore all other ctcp messages */
418 file_write_target("msg", l);
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);
430 char *channels = strdup(l->args[1]);
431 char *nicks = strdup(l->args[1]);
432 char *p,*n; char cont = 1;
443 file_write_channel_only("kick", l);
446 _nick = strchr(_nick, ',');
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);
468 gboolean fini_plugin(struct plugin *p)
470 del_filter(log_custom_data);
474 gboolean init_plugin(struct plugin *p)
476 xmlConf = p->xmlConf;
477 g_assert(p->xmlConf);
478 if(!xmlFindChildByElementName(xmlConf, "logfilename")) {
479 g_warning("No <logfilename> tag for log_custom module");
482 files = g_hash_table_new(g_str_hash, g_str_equal);
483 add_filter("log_custom", log_custom_data);