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.
20 #include "ctrlproxy.h"
29 #include <sys/types.h>
33 #define mkdir(s,t) _mkdir(s)
39 #define G_LOG_DOMAIN "log_custom"
41 const char *logfilename = NULL;
42 GHashTable *fmts = NULL;
44 /* Translation table */
50 char *(*callback) (struct network *, struct line *l, gboolean case_sensitive);
53 static char *get_hours(struct network *n, struct line *l, gboolean case_sensitive) {
54 time_t ti = time(NULL);
55 struct tm *t = localtime(&ti);
56 return g_strdup_printf("%02d", t->tm_hour);
59 static char *get_minutes(struct network *n, struct line *l, gboolean case_sensitive) {
60 time_t ti = time(NULL);
61 struct tm *t = localtime(&ti);
62 return g_strdup_printf("%02d", t->tm_min);
65 static char *get_seconds(struct network *n, struct line *l, gboolean case_sensitive) {
66 time_t ti = time(NULL);
67 struct tm *t = localtime(&ti);
68 return g_strdup_printf("%02d", t->tm_sec);
71 static char *get_seconds_since_1970(struct network *n, struct line *l, gboolean case_sensitive) {
72 time_t ti = time(NULL);
73 return g_strdup_printf("%ld", ti);
76 static char *get_day(struct network *n, struct line *l, gboolean case_sensitive) {
77 time_t ti = time(NULL);
78 struct tm *t = localtime(&ti);
79 return g_strdup_printf("%02d", t->tm_mday);
82 static char *get_user(struct network *n, struct line *l, gboolean case_sensitive) {
86 if(l->origin)nick = g_strdup(l->origin);
87 if(nick)user = strchr(nick, '!');
88 if(user){ *user = '\0';user++; }
90 if(case_sensitive) return g_ascii_strdown(user, -1);
91 else return g_strdup(user);
94 static char *get_monthname(struct network *n, struct line *l, gboolean case_sensitive) {
96 time_t ti = time(NULL);
97 strftime(stime, sizeof(stime), "%b", localtime(&ti));
98 return g_strdup_printf("%s", stime);
101 static char *get_nick(struct network *n, struct line *l, gboolean case_sensitive) {
102 if(line_get_nick(l)) {
103 if(case_sensitive) return g_ascii_strdown(line_get_nick(l), -1);
104 else return g_strdup(line_get_nick(l));
110 static char *get_network(struct network *n, struct line *l, gboolean case_sensitive)
111 { return g_strdup(n->name); }
112 static char *get_server(struct network *n, struct line *l, gboolean case_sensitive)
113 { return g_strdup(n->connection.data.tcp.current_server->name); }
115 static char *get_percent(struct network *n, struct line *l, gboolean case_sensitive) { return g_strdup("%"); }
117 static const char *identifier = NULL;
119 static char *get_identifier(struct network *n, struct line *l, gboolean case_sensitive) {
120 if(case_sensitive) return g_ascii_strdown(identifier, -1);
121 else return g_strdup(identifier);
124 static char *get_modechanges(struct network *n, struct line *l, gboolean case_sensitive) {
128 for( i = 3 ; l->args[i+1] != NULL; i++ )
129 if(i == 3) sprintf(buf, "%s", l->args[i]);
130 else sprintf(buf, "%s %s", buf, l->args[i]);
132 return g_strdup(buf);
135 static struct log_mapping mappings[] = {
136 {NULL, '@', -1, get_identifier },
137 {NULL, 'h', -1, get_hours },
138 {NULL, 'M', -1, get_minutes },
139 {NULL, 's', -1, get_seconds },
140 {NULL, 'd', -1, get_day },
141 {NULL, 'e', -1, get_seconds_since_1970 },
142 {NULL, 'b', -1, get_monthname },
143 {NULL, 'n', -1, get_nick },
144 {NULL, 'u', -1, get_user },
145 {NULL, 'N', -1, get_network },
146 {NULL, 'S', -1, get_server },
147 {NULL, '%', -1, get_percent },
148 {"JOIN", 'c', 1, NULL },
149 {"PART", 'c', 1, NULL },
150 {"PART", 'm', 2, NULL },
151 {"KICK", 'c', 1, NULL },
152 {"KICK", 't', 2, NULL },
153 {"KICK", 'r', 3, NULL },
154 {"QUIT", 'm', 1, NULL },
155 {"NOTICE", 't', 1, NULL },
156 {"NOTICE", 'm', 2, NULL },
157 {"PRIVMSG", 't', 1, NULL },
158 {"PRIVMSG", 'm', 2, NULL },
159 {"MSG", 't', 1, NULL },
160 {"MSG", 'm', 2, NULL },
161 {"TOPIC", 'c', 1, NULL },
162 {"TOPIC", 't', 2, NULL },
163 {"MODE", 't', 1, NULL },
164 {"MODE", 'p', 2, NULL },
165 {"MODE", 'c', -1, get_modechanges },
166 {"NICK", 'r', 1, NULL },
167 {NULL, '0', 0, NULL },
168 {NULL, '1', 1, NULL },
169 {NULL, '2', 2, NULL },
170 {NULL, '3', 3, NULL },
171 {NULL, '4', 4, NULL },
172 {NULL, '5', 5, NULL },
173 {NULL, '6', 6, NULL },
174 {NULL, '7', 7, NULL },
175 {NULL, '8', 8, NULL },
176 {NULL, '9', 9, NULL },
180 static char *find_mapping(struct network *network, struct line *l, char c, gboolean case_sensitive)
183 for(i = 0; mappings[i].subst; i++) {
184 if(mappings[i].command &&
185 strcmp(mappings[i].command, l->args[0])) continue;
186 if(mappings[i].subst != c)continue;
187 if(mappings[i].index == -1) return mappings[i].callback(network, l, case_sensitive);
188 if(mappings[i].index < l->argc) {
189 if(case_sensitive) return g_ascii_strdown(l->args[mappings[i].index], -1);
190 else return g_strdup(l->args[mappings[i].index]);
196 static void custom_subst(struct network *network, char **_new, const char *fmt, struct line *l, const char *_identifier, gboolean case_sensitive)
198 char *subst[MAX_SUBST];
200 size_t len, curpos = 0;
203 identifier = _identifier;
206 memset(subst, 0, sizeof(char *) * MAX_SUBST);
207 for(i = 0; i < strlen(fmt); i++) {
209 subst[(int)fmt[i+1]] = find_mapping(network, l, fmt[i+1], case_sensitive);
210 if (subst[(int)fmt[i+1]] == NULL) subst[(int)fmt[i+1]] = g_strdup("");
211 len += strlen(subst[(int)fmt[i+1]]);
215 len++; /* newline! */
217 new = g_new(char, len);
218 for(i = 0; i < strlen(fmt); i++) {
221 strncat(new, subst[(int)fmt[i+1]], len);
222 curpos+=strlen(subst[(int)fmt[i+1]]);
225 new[curpos] = fmt[i];
231 for(i = 0; i < MAX_SUBST; i++) { if(subst[i])g_free(subst[i]); }
241 * %b -> locale month name
242 * %n -> initiating nick
243 * %u -> initiating user
253 -- KICK: %t (target nick), %r (reason)
255 -- NOTICE/PRIVMSG: %t (target nick/channel), %m
256 -- MODE: %p(mode change), %t, %c (target nicks)
261 static GHashTable *files = NULL;
263 static FILE *find_channel_file(struct network *network, struct line *l, char *identifier) {
266 if(!logfilename)return NULL;
267 custom_subst(network, &n, logfilename, l, identifier, TRUE);
268 f = g_hash_table_lookup(files, n);
273 static FILE *find_add_channel_file(struct network *network, struct line *l, const char *identifier)
275 char *n = NULL, *dn, *p;
277 if(!logfilename) return NULL;
278 custom_subst(network, &n, logfilename, l, identifier, TRUE);
279 f = g_hash_table_lookup(files, n);
283 /* Only include directory-part */
284 p = strrchr(dn, '/');
287 /* Check if directory needs to be created */
288 if(!g_file_test(dn, G_FILE_TEST_IS_DIR) && mkdir(dn, 0700) == -1) {
289 log_network("log_custom", network, "Couldn't create directory %s for logging!", dn);
296 /* Then open the correct filename */
297 custom_subst(network, &n, logfilename, l, identifier, TRUE);
300 log_network("log_custom", network, "Couldn't open file %s for logging!", n);
304 g_hash_table_insert(files, n, f);
310 static void file_write_target(struct network *network, const char *n, struct line *l)
315 fmt = g_hash_table_lookup(fmts, n);
318 if(!irccmp(&network->state->info, network->state->me.nick, l->args[1])) {
319 if(line_get_nick(l)) { t = g_strdup(line_get_nick(l)); }
320 else { t = g_strdup("_messages_"); }
322 t = g_strdup(l->args[1]);
325 f = find_add_channel_file(network, l, t);
326 if(!f) { g_free(t); return; }
328 custom_subst(network, &s, fmt, l, t, FALSE);
338 static void file_write_channel_only(struct network *network, const char *n, struct line *l)
343 fmt = g_hash_table_lookup(fmts, n);
346 f = find_add_channel_file(network, l, l->args[1]);
349 custom_subst(network, &s, fmt, l, l->args[1], FALSE);
351 fputs(s, f); fputc('\n', f);
357 static void file_write_channel_query(struct network *network, const char *n, struct line *l)
364 nick = line_get_nick(l);
367 fmt = g_hash_table_lookup(fmts, n);
370 /* check for the query first */
371 f = find_channel_file(network, l, nick);
374 custom_subst(network, &s, fmt, l, nick, FALSE);
375 fputs(s, f); fputc('\n', f);
380 /* now, loop thru the channels and check if the user is there */
381 gl = network->state->channels;
383 struct channel_state *c = (struct channel_state *)gl->data;
384 if(find_nick(c, nick)) {
385 f = find_add_channel_file(network, l, c->name);
387 custom_subst(network, &s, fmt, l, c->name, FALSE);
388 fputs(s, f); fputc('\n', f);
397 static gboolean log_custom_data(struct network *network, struct line *l, enum data_direction dir, void *userdata)
399 const char *nick = NULL;
402 if(!l->args || !l->args[0] || l->options & LINE_NO_LOGGING)return TRUE;
403 nick = line_get_nick(l);
404 if(user){ *user = '\0';user++; }
406 /* Loop thru possible values for %@ */
408 /* There are a few possibilities:
409 * - log to line_get_nick(l) or l->args[1] file depending on which
410 * was the current user (PRIVMSG, NOTICE, ACTION, MODE) (target)
411 * - log to all channels line_get_nick(l) was on, and to query, if applicable (NICK, QUIT) (channel_query)
412 * - log to channel only (KICK, PART, JOIN, TOPIC) (channel_only)
415 if(dir == FROM_SERVER && !g_strcasecmp(l->args[0], "JOIN")) {
416 file_write_target(network, "join", l);
417 } else if(dir == FROM_SERVER && !g_strcasecmp(l->args[0], "PART")) {
418 file_write_channel_only(network, "part", l);
419 } else if(!g_strcasecmp(l->args[0], "PRIVMSG")) {
420 if(l->args[2][0] == '
\ 1') {
421 l->args[2][strlen(l->args[2])-1] = '\0';
422 if(!g_ascii_strncasecmp(l->args[2], "
\ 1ACTION ", 8)) {
424 file_write_target(network, "action", l);
427 l->args[2][strlen(l->args[2])] = '
\ 1';
428 /* Ignore all other ctcp messages */
430 file_write_target(network, "msg", l);
432 } else if(!g_strcasecmp(l->args[0], "NOTICE")) {
433 file_write_target(network, "notice", l);
434 } else if(!g_strcasecmp(l->args[0], "MODE") && l->args[1] &&
435 is_channelname(l->args[1], &network->state->info) && dir == FROM_SERVER) {
436 file_write_target(network, "mode", l);
437 } else if(!g_strcasecmp(l->args[0], "QUIT")) {
438 file_write_channel_query(network, "quit", l);
439 } else if(!g_strcasecmp(l->args[0], "KICK") && l->args[1] && l->args[2] && dir == FROM_SERVER) {
440 if(!strchr(l->args[1], ',')) {
441 file_write_channel_only(network, "kick", l);
443 char *channels = g_strdup(l->args[1]);
444 char *nicks = g_strdup(l->args[1]);
445 char *p,*n; char cont = 1;
456 file_write_channel_only(network, "kick", l);
459 _nick = strchr(_nick, ',');
467 } else if(!g_strcasecmp(l->args[0], "TOPIC") && dir == FROM_SERVER && l->args[1]) {
468 if(l->args[2]) file_write_channel_only(network, "topic", l);
469 else file_write_channel_only(network, "notopic", l);
470 } else if(!g_strcasecmp(l->args[0], "NICK") && dir == FROM_SERVER && l->args[1]) {
471 file_write_channel_query(network, "nickchange", l);
479 static gboolean fini_plugin(struct plugin *p)
481 del_log_filter("log_custom");
485 static void savefmt(gpointer key, gpointer value, gpointer udata)
487 xmlNodePtr node = udata;
489 xmlNewTextChild(node, NULL, key, value);
492 static gboolean save_config(struct plugin *p, xmlNodePtr node)
494 xmlNewTextChild(node, NULL, "logfilename", logfilename);
496 g_hash_table_foreach(fmts, savefmt, node);
501 static gboolean load_config(struct plugin *p, xmlNodePtr node)
505 for (cur = node->children; cur; cur = cur->next)
507 if (cur->type != XML_ELEMENT_NODE) continue;
509 if (!strcmp(cur->name, "logfilename")) {
510 logfilename = xmlNodeGetContent(cur);
512 g_hash_table_insert(fmts, g_strdup(cur->name), xmlNodeGetContent(cur));
519 static gboolean init_plugin(struct plugin *p)
521 files = g_hash_table_new(g_str_hash, g_str_equal);
522 fmts = g_hash_table_new(g_str_hash, g_str_equal);
523 add_log_filter("log_custom", log_custom_data, NULL, 1000);
527 struct plugin_ops plugin = {
528 .name = "log_custom",
532 .load_config = load_config,
533 .save_config = save_config