Hopefully fix #143 (only PRIVMSG+NOTICE for BACKLOG command).
[jelmer/ctrlproxy.git] / src / linestack.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 #include "internals.h"
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif /* HAVE_CONFIG_H */
25
26 #include "irc.h"
27
28 static GSList *linestack_backends = NULL;
29
30 void register_linestack(const struct linestack_ops *b)
31 {
32         linestack_backends = g_slist_append(linestack_backends, g_memdup(b, sizeof(*b)));
33 }
34
35 struct linestack_context *create_linestack(const struct linestack_ops *ops, 
36                                                                                    const char *name, 
37                                                                                    struct ctrlproxy_config *cfg,
38                                                                                    const struct network_state *state)
39 {
40         struct linestack_context *ctx;
41
42         g_assert(name);
43         g_assert(state);
44         g_assert(cfg);
45
46         ctx = g_new0(struct linestack_context, 1);
47         ctx->ops = ops;
48         ops->init(ctx, name, cfg, state);
49
50         return ctx;
51 }
52
53 void free_linestack_context(struct linestack_context *ctx)
54 {
55         if (ctx)
56                 ctx->ops->fini(ctx);
57
58         g_free(ctx);
59 }
60
61 static struct linestack_marker *wrap_linestack_marker(struct linestack_context *ctx, void *data)
62 {
63         struct linestack_marker *mrk;
64         if (data == NULL)
65                 return NULL;
66
67         mrk = g_new0(struct linestack_marker, 1);
68         mrk->free_fn = ctx->ops->free_marker;
69         mrk->data = data;
70         return mrk;
71 }
72
73 struct linestack_marker *linestack_get_marker_numlines (struct linestack_context *ctx, int lines)
74 {
75         g_assert(ctx != NULL);
76         if (ctx->ops == NULL) 
77                 return NULL;
78
79         if (ctx->ops->get_marker_numlines == NULL) 
80                 return NULL;
81
82         return wrap_linestack_marker(ctx, ctx->ops->get_marker_numlines(ctx, lines));
83 }
84
85 struct network_state *linestack_get_state(
86                 struct linestack_context *ctx,
87                 struct linestack_marker *lm)
88 {
89         struct network_state *st;
90         g_assert(ctx != NULL);
91
92         if (!ctx->ops) return NULL;
93         if (!ctx->ops->get_state) return NULL;
94
95         st = ctx->ops->get_state(ctx, lm?lm->data:NULL);
96         if (st == NULL)
97                 return NULL;
98
99         g_assert(st->me.nick);
100         g_assert(st->me.query);
101         return st;
102 }
103
104 gboolean linestack_traverse(struct linestack_context *ctx,                                      
105                 struct linestack_marker *lm_from, struct linestack_marker *lm_to,
106                 linestack_traverse_fn handler, void *userdata)
107 {
108         g_assert(ctx != NULL);
109         g_assert(ctx->ops != NULL);
110         g_assert(ctx->ops->traverse != NULL);
111
112         return ctx->ops->traverse(ctx, lm_from?lm_from->data:NULL, lm_to?lm_to->data:NULL, handler, userdata);
113 }
114
115 struct traverse_object_data {
116         linestack_traverse_fn handler;
117         const char *object;
118         void *userdata;
119 };
120
121 static gboolean traverse_object_handler(struct line *l, time_t t, void *state)
122 {
123         struct traverse_object_data *d = state;
124         gboolean ret = TRUE;
125
126         if (l->argc < 2) 
127                 return TRUE;
128
129         if (strchr(l->args[1], ',') == NULL) {
130                 if (!strcmp(l->args[1], d->object))
131                         ret &= d->handler(l, t, d->userdata);
132         } else {
133                 int i;
134                 char **channels = g_strsplit(l->args[1], ",", 0);
135                 for (i = 0; channels[i]; i++) {
136                         if (!strcmp(channels[i], d->object)) {
137                                 gboolean ret = d->handler(l, t, d->userdata);
138                                 g_strfreev(channels);
139                                 return ret;
140                         }
141                 }
142                 g_strfreev(channels);
143         }
144
145         return ret;
146 }
147
148 gboolean linestack_traverse_object(
149                         struct linestack_context *ctx,
150                         const char *obj, 
151                         struct linestack_marker *lm_from, 
152                         struct linestack_marker *lm_to, linestack_traverse_fn hl,
153                         void *userdata)
154 {
155         struct traverse_object_data d;
156         g_assert(ctx != NULL);
157         g_assert(ctx->ops != NULL);
158
159         d.object = obj;
160         d.userdata = userdata;
161         d.handler = hl;
162         
163         return linestack_traverse(ctx, lm_from, lm_to, traverse_object_handler, &d);
164 }
165
166 void linestack_free_marker(struct linestack_marker *lm)
167 {
168         return;
169         if (lm == NULL)
170                 return;
171
172         if (lm->free_fn != NULL) 
173                 lm->free_fn(lm->data);
174         g_free(lm);
175 }
176
177 struct linestack_marker *linestack_get_marker(struct linestack_context *ctx)
178 {
179         if (ctx == NULL)
180                 return NULL;
181
182         g_assert (ctx->ops != NULL);
183         g_assert (ctx->ops->get_marker != NULL);
184
185         return wrap_linestack_marker(ctx, ctx->ops->get_marker(ctx));
186 }
187
188 static const char *linestack_messages[] = { 
189         "NICK", "JOIN", "QUIT", "PART", "PRIVMSG", "NOTICE", "KICK", 
190         "MODE", "TOPIC", 
191         "353", /* RPL_NAMREPLY */
192         "366", /* RPL_ENDOFNAMES */
193         "331", /* RPL_NOTOPIC */
194         "333", /* RPL_TOPICWHOTIME */
195         "332", /* RPL_TOPIC */
196         "324", /* RPL_CHANNELMODEIS */
197         "329", /* RPL_CREATIONTIME */
198         NULL };
199
200 gboolean linestack_insert_line(struct linestack_context *ctx, const struct line *l, enum data_direction dir, const struct network_state *state)
201 {
202         int i;
203         gboolean needed = FALSE;
204
205         if (ctx == NULL) return FALSE;
206
207         if (l->argc == 0) return FALSE;
208
209         if (!ctx->ops) return FALSE;
210         g_assert(ctx->ops->insert_line);
211
212         /* Only need PRIVMSG and NOTICE messages we send ourselves */
213         if (dir == TO_SERVER && 
214                 g_strcasecmp(l->args[0], "PRIVMSG") && 
215                 g_strcasecmp(l->args[0], "NOTICE")) return FALSE;
216
217         /* No CTCP, please */
218         if ((!g_strcasecmp(l->args[0], "PRIVMSG") ||
219                 !g_strcasecmp(l->args[0], "NOTICE")) && 
220                 l->argc > 2 && l->args[2][0] == '\001')
221                 return FALSE;
222
223         for (i = 0; linestack_messages[i]; i++) 
224                 if (!g_strcasecmp(linestack_messages[i], l->args[0]))
225                         needed = TRUE;
226
227         if (!needed) return FALSE;
228
229         for (i = 0; i < l->argc; i++) {
230                 g_assert(strchr(l->args[i], '\n') == NULL);
231                 g_assert(strchr(l->args[i], '\r') == NULL);
232         }
233
234         return ctx->ops->insert_line(ctx, l, state);
235 }
236
237 static gboolean send_line(struct line *l, time_t t, void *_client)
238 {
239         struct client *c = _client;
240         return client_send_line(c, l);
241 }
242
243 struct line *line_prefix_time(struct line *l, time_t t)
244 {
245         struct line *nl = linedup(l);
246         char stime[512];
247         char *tmp;
248
249         strftime(stime, sizeof(stime), "%H:%M:%S", localtime(&t));
250         tmp = g_strdup_printf("[%s] %s", stime, nl->args[2]);
251         if (tmp == NULL)
252                 return NULL;
253         g_free(nl->args[2]);
254         nl->args[2] = tmp;
255
256         return nl;
257 }
258
259 static gboolean send_line_timed(struct line *l, time_t t, void *_client)
260 {
261         struct client *c = _client;
262
263         if ((!g_strcasecmp(l->args[0], "PRIVMSG") ||
264                 !g_strcasecmp(l->args[0], "NOTICE")) &&
265                 l->argc > 2) {
266                 struct line *nl = line_prefix_time(l, t);
267                 gboolean ret;
268                 ret = client_send_line(c, nl);
269                 free_line(nl);
270                 return ret;
271         } else {
272                 return client_send_line(c, l);
273         }
274 }
275
276 static gboolean send_line_timed_dataonly(struct line *l, time_t t, void *_client)
277 {
278         struct client *c = _client;
279         gboolean ret;
280         struct line *nl;
281
282         if (g_strcasecmp(l->args[0], "PRIVMSG") != 0 && 
283                 g_strcasecmp(l->args[0], "NOTICE") != 0)
284                 return TRUE;
285
286         if (l->argc <= 2)
287                 return TRUE;
288
289         nl = line_prefix_time(l, t);
290         ret = client_send_line(c, nl);
291         free_line(nl);
292         return ret;
293 }
294
295 static gboolean send_line_dataonly(struct line *l, time_t t, void *_client)
296 {
297         struct client *c = _client;
298
299         if (g_strcasecmp(l->args[0], "PRIVMSG") != 0 && 
300                 g_strcasecmp(l->args[0], "NOTICE") != 0)
301                 return TRUE;
302
303         if (l->argc <= 2)
304                 return TRUE;
305
306         return client_send_line(c, l);
307 }
308
309 gboolean linestack_send(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
310 {
311         return linestack_traverse(ctx, mf, mt, send_line, c);
312 }
313
314 gboolean linestack_send_timed(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
315 {
316         return linestack_traverse(ctx, mf, mt, send_line_timed, c);
317 }
318
319 gboolean linestack_send_dataonly(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
320 {
321         return linestack_traverse(ctx, mf, mt, send_line_dataonly, c);
322 }
323
324 gboolean linestack_send_timed_dataonly(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
325 {
326         return linestack_traverse(ctx, mf, mt, send_line_timed_dataonly, c);
327 }
328
329 gboolean linestack_send_object(struct linestack_context *ctx, const char *obj, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
330 {
331         return linestack_traverse_object(ctx, obj, mf, mt, send_line, c);
332 }
333
334 gboolean linestack_send_object_timed(struct linestack_context *ctx, const char *obj, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
335 {
336         return linestack_traverse_object(ctx, obj, mf, mt, send_line_timed, c);
337 }
338
339 gboolean linestack_send_object_dataonly(struct linestack_context *ctx, const char *obj, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
340 {
341         return linestack_traverse_object(ctx, obj, mf, mt, send_line_dataonly, c);
342 }
343
344 gboolean linestack_send_object_timed_dataonly(struct linestack_context *ctx, const char *obj, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
345 {
346         return linestack_traverse_object(ctx, obj, mf, mt, send_line_timed_dataonly, c);
347 }
348
349 static gboolean replay_line(struct line *l, time_t t, void *state)
350 {
351         struct network_state *st = state;
352         state_handle_data(st, l);
353         return TRUE;
354 }
355
356 gboolean linestack_replay(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct network_state *st)
357 {
358         return linestack_traverse(ctx, mf, mt, replay_line, st);
359 }
360
361 struct linestack_ops *linestack_find_ops(const char *name)
362 {
363         GSList *gl;
364         for (gl = linestack_backends; gl ; gl = gl->next) {
365                 struct linestack_ops *ops = gl->data;
366                 if (!strcmp(ops->name, name))
367                         return ops;
368         }
369
370         return NULL;
371 }
372
373 struct linestack_context *new_linestack(struct network *n)
374 {
375         const struct linestack_ops *current_backend = NULL;
376         struct ctrlproxy_config *cfg = NULL;
377         
378         if (n->global != NULL)
379                 cfg = n->global->config;
380
381         register_linestack(&linestack_file);
382
383         if (cfg && cfg->linestack_backend) {
384                 current_backend = linestack_find_ops(cfg->linestack_backend);
385
386                 if (!current_backend) 
387                         log_global(LOG_WARNING, "Unable to find linestack backend %s: falling back to default", cfg->linestack_backend);
388         }
389
390         if (!current_backend) {
391                 current_backend = &linestack_file;
392         }
393
394         return create_linestack(current_backend, n->info.name, cfg, n->state);
395 }
396
397