4e1d475fe04cbd60d129a1e7197a507402f3cf7a
[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 static gboolean send_line_timed(struct line *l, time_t t, void *_client)
244 {
245         struct client *c = _client;
246
247         if ((!g_strcasecmp(l->args[0], "PRIVMSG") ||
248                 !g_strcasecmp(l->args[0], "NOTICE")) &&
249                 l->argc > 2) {
250                 gboolean ret;
251                 struct line *nl = linedup(l);
252                 char stime[512];
253                 char *tmp;
254
255                 strftime(stime, sizeof(stime), "%H:%M:%S", localtime(&t));
256                 tmp = g_strdup_printf("[%s] %s", stime, nl->args[2]);
257                 g_free(nl->args[2]);
258                 nl->args[2] = tmp;
259                 ret = client_send_line(c, nl);
260                 free_line(nl);
261                 return ret;
262         } else {
263                 return client_send_line(c, l);
264         }
265 }
266
267 gboolean linestack_send(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
268 {
269         return linestack_traverse(ctx, mf, mt, send_line, c);
270 }
271
272 gboolean linestack_send_timed(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
273 {
274         return linestack_traverse(ctx, mf, mt, send_line_timed, c);
275 }
276
277 gboolean linestack_send_object(struct linestack_context *ctx, const char *obj, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
278 {
279         return linestack_traverse_object(ctx, obj, mf, mt, send_line, c);
280 }
281
282 gboolean linestack_send_object_timed(struct linestack_context *ctx, const char *obj, struct linestack_marker *mf, struct linestack_marker *mt, struct client *c)
283 {
284         return linestack_traverse_object(ctx, obj, mf, mt, send_line_timed, c);
285 }
286
287 static gboolean replay_line(struct line *l, time_t t, void *state)
288 {
289         struct network_state *st = state;
290         state_handle_data(st, l);
291         return TRUE;
292 }
293
294 gboolean linestack_replay(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct network_state *st)
295 {
296         return linestack_traverse(ctx, mf, mt, replay_line, st);
297 }
298
299 struct linestack_ops *linestack_find_ops(const char *name)
300 {
301         GSList *gl;
302         for (gl = linestack_backends; gl ; gl = gl->next) {
303                 struct linestack_ops *ops = gl->data;
304                 if (!strcmp(ops->name, name))
305                         return ops;
306         }
307
308         return NULL;
309 }
310
311 struct linestack_context *new_linestack(struct network *n)
312 {
313         const struct linestack_ops *current_backend = NULL;
314         struct ctrlproxy_config *cfg = NULL;
315         
316         if (n->global != NULL)
317                 cfg = n->global->config;
318
319         register_linestack(&linestack_file);
320
321         if (cfg && cfg->linestack_backend) {
322                 current_backend = linestack_find_ops(cfg->linestack_backend);
323
324                 if (!current_backend) 
325                         log_global(LOG_WARNING, "Unable to find linestack backend %s: falling back to default", cfg->linestack_backend);
326         }
327
328         if (!current_backend) {
329                 current_backend = &linestack_file;
330         }
331
332         return create_linestack(current_backend, n->info.name, cfg, n->state);
333 }
334
335