Support delaying disconnects until all lines are sent.
[jelmer/ctrlproxy.git] / libirc / transport_ioc.c
1 /*
2         ctrlproxy: A modular IRC proxy
3         (c) 2002-2008 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 3 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 #include "transport.h"
22 #include "line.h"
23 #include "util.h"
24 #include <glib.h>
25 #include <sys/socket.h>
26 #include <ctype.h>
27 #include <fcntl.h>
28 #include <netdb.h>
29
30 struct irc_transport_data_iochannel {
31         GIOChannel *incoming;
32         gboolean pending_disconnect;
33         gint incoming_id;
34         gint outgoing_id;
35         GIConv incoming_iconv;
36         GIConv outgoing_iconv;
37         GQueue *pending_lines;
38 };
39
40
41 static gboolean handle_transport_receive(GIOChannel *c, GIOCondition cond, 
42                                                                           void *_transport);
43
44 static void free_pending_line(void *_line, void *userdata)
45 {
46         free_line((struct irc_line *)_line);
47 }
48
49 static void irc_transport_iochannel_free_data(void *data)
50 {
51         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)data;
52
53         g_assert(backend_data->pending_disconnect);
54
55         if (backend_data->outgoing_iconv != (GIConv)-1)
56                 g_iconv_close(backend_data->outgoing_iconv);
57         if (backend_data->incoming_iconv != (GIConv)-1)
58                 g_iconv_close(backend_data->incoming_iconv);
59
60         g_queue_foreach(backend_data->pending_lines, free_pending_line, NULL);
61         g_queue_free(backend_data->pending_lines);
62
63         g_free(backend_data);
64 }
65
66 static char *irc_transport_iochannel_get_peer_name(void *data)
67 {
68         int fd;
69         socklen_t len = sizeof(struct sockaddr_storage);
70         struct sockaddr_storage sa;
71         char hostname[NI_MAXHOST];
72         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)data;
73
74         fd = g_io_channel_unix_get_fd(backend_data->incoming);
75
76         if (getpeername (fd, (struct sockaddr *)&sa, &len) < 0) {
77                 return NULL;
78         }
79
80         if (sa.ss_family == AF_INET || sa.ss_family == AF_INET6) {
81                 if (getnameinfo((struct sockaddr *)&sa, len, hostname, sizeof(hostname),
82                                                 NULL, 0, 0) == 0) {
83                         return g_strdup(hostname);
84                 } 
85         } else if (sa.ss_family == AF_UNIX) {
86                 return g_strdup("localhost");
87         }
88
89         return NULL;
90 }
91
92 static gboolean irc_transport_iochannel_set_charset(struct irc_transport *transport, const char *name)
93 {
94         GIConv tmp;
95         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)transport->backend_data;
96
97         if (name != NULL) {
98                 tmp = g_iconv_open(name, "UTF-8");
99
100                 if (tmp == (GIConv)-1) {
101                         return FALSE;
102                 }
103         } else {
104                 tmp = (GIConv)-1;
105         }
106         
107         if (backend_data->outgoing_iconv != (GIConv)-1)
108                 g_iconv_close(backend_data->outgoing_iconv);
109
110         backend_data->outgoing_iconv = tmp;
111
112         if (name != NULL) {
113                 tmp = g_iconv_open("UTF-8", name);
114
115                 if (tmp == (GIConv)-1) {
116                         return FALSE;
117                 }
118         } else {
119                 tmp = (GIConv)-1;
120         }
121
122         if (backend_data->incoming_iconv != (GIConv)-1)
123                 g_iconv_close(backend_data->incoming_iconv);
124
125         backend_data->incoming_iconv = tmp;
126
127         return TRUE;
128 }
129
130 static void really_disconnect(struct irc_transport_data_iochannel *backend_data)
131 {
132         g_io_channel_unref(backend_data->incoming);
133
134         g_source_remove(backend_data->incoming_id);
135         if (backend_data->outgoing_id)
136                 g_source_remove(backend_data->outgoing_id);
137
138         backend_data->incoming = NULL;
139 }
140
141 static void irc_transport_iochannel_disconnect(void *data)
142 {
143         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)data;
144
145         backend_data->pending_disconnect = TRUE;
146
147         if (backend_data->outgoing_id == 0) 
148                 really_disconnect(backend_data);
149 }
150
151 static gboolean transport_send_queue(GIOChannel *ioc, GIOCondition cond, 
152                                                                           void *_transport);
153
154 static gboolean irc_transport_iochannel_send_line(struct irc_transport *transport, const struct irc_line *l, GError **error)
155 {
156         GIOStatus status;
157         GError *tmp = NULL;
158
159         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)transport->backend_data;
160
161         if (error == NULL) {
162                 error = &tmp;
163         }
164
165         if (backend_data->outgoing_id != 0) {
166                 g_queue_push_tail(backend_data->pending_lines, linedup(l));
167                 return TRUE;
168         }
169
170         status = irc_send_line(backend_data->incoming, backend_data->outgoing_iconv, l, error);
171
172         switch (status) {
173         case G_IO_STATUS_AGAIN:
174                 backend_data->outgoing_id = g_io_add_watch(backend_data->incoming, G_IO_OUT, 
175                                                                                 transport_send_queue, transport);
176                 g_queue_push_tail(backend_data->pending_lines, linedup(l));
177                 break;
178         case G_IO_STATUS_EOF:
179                 transport->callbacks->hangup(transport);
180                 return FALSE;
181         case G_IO_STATUS_ERROR:
182                 transport->callbacks->log(transport, l, *error);
183                 if (error == &tmp)
184                         g_error_free(tmp);
185                 return FALSE;
186         case G_IO_STATUS_NORMAL:
187                 transport->last_line_sent = time(NULL);
188                 break;
189         }
190
191         status = g_io_channel_flush(backend_data->incoming, error);
192
193         switch (status) {
194         case G_IO_STATUS_EOF:
195                 g_assert_not_reached();
196         case G_IO_STATUS_NORMAL:
197                 break;
198         case G_IO_STATUS_AGAIN:
199                 backend_data->outgoing_id = g_io_add_watch(backend_data->incoming, G_IO_OUT, 
200                                                                                 transport_send_queue, transport);
201                 break;
202         case G_IO_STATUS_ERROR:
203                 transport->callbacks->log(transport, l, *error);
204                 if (error == &tmp)
205                         g_error_free(tmp);
206                 return FALSE;
207         }
208
209         return TRUE;
210
211 }
212
213 static void irc_transport_iochannel_activate(struct irc_transport *transport)
214 {
215         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)transport->backend_data;
216         backend_data->incoming_id = g_io_add_watch(
217                                                         backend_data->incoming, G_IO_IN | G_IO_HUP, 
218                                                         handle_transport_receive, transport);
219
220         handle_transport_receive(backend_data->incoming, 
221                           g_io_channel_get_buffer_condition(backend_data->incoming) & G_IO_IN,
222                           transport);
223 }
224
225
226
227 static gboolean handle_recv_status(struct irc_transport *transport, GIOStatus status, GError *error)
228 {
229         if (status == G_IO_STATUS_EOF) {
230                 transport->callbacks->hangup(transport);
231                 return FALSE;
232         }
233
234         if (status != G_IO_STATUS_AGAIN) {
235                 if (error->domain == G_CONVERT_ERROR &&
236                         error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) {
237                         transport->callbacks->charset_error(transport, 
238                                                                                                 error->message);
239                 } else {
240                         return transport->callbacks->error(transport, error?error->message:NULL);
241                 }
242         }
243
244         return TRUE;
245 }
246
247
248
249 static gboolean handle_transport_receive(GIOChannel *c, GIOCondition cond, 
250                                                                           void *_transport)
251 {
252         struct irc_transport *transport = _transport;
253         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)transport->backend_data;
254         struct irc_line *l;
255
256         g_assert(transport);
257
258         if (cond & G_IO_ERR) {
259                 char *tmp = g_strdup_printf("Error reading from client: %s", 
260                                                   g_io_channel_unix_get_sock_error(c));
261                 transport->callbacks->error(transport, tmp);
262                 g_free(tmp);
263                 return FALSE;
264         }
265
266
267         if (cond & G_IO_IN) {
268                 GError *error = NULL;
269                 GIOStatus status;
270                 gboolean ret = TRUE;
271                 
272                 while ((status = irc_recv_line(c, backend_data->incoming_iconv, &error, 
273                                                                            &l)) == G_IO_STATUS_NORMAL) {
274
275                         ret &= transport->callbacks->recv(transport, l);
276                         free_line(l);
277
278                         if (!ret)
279                                 return FALSE;
280                 }
281
282                 ret &= handle_recv_status(transport, status, error);
283                 if (error != NULL)
284                         g_error_free(error);
285                 return ret;
286         }
287
288         if (cond & G_IO_HUP) {
289                 transport->callbacks->hangup(transport);
290                 return FALSE;
291         }
292
293
294         return TRUE;
295 }
296
297
298
299 static gboolean irc_transport_iochannel_is_connected(void *data)
300 {
301         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)data;
302
303         return backend_data->incoming != NULL;
304 }
305
306 static const struct irc_transport_ops irc_transport_iochannel_ops = {
307         .free_data = irc_transport_iochannel_free_data,
308         .is_connected = irc_transport_iochannel_is_connected,
309         .disconnect = irc_transport_iochannel_disconnect,
310         .send_line = irc_transport_iochannel_send_line,
311         .get_peer_name = irc_transport_iochannel_get_peer_name,
312         .activate = irc_transport_iochannel_activate,
313         .set_charset = irc_transport_iochannel_set_charset,
314 };
315
316 /* GIOChannels passed into this function 
317  * should preferably:
318  *  - have no encoding set
319  *  - work asynchronously
320  *
321  * @param iochannel Channel to talk over 
322  */
323 struct irc_transport *irc_transport_new_iochannel(GIOChannel *iochannel)
324 {
325         struct irc_transport *ret = g_new0(struct irc_transport, 1);
326         struct irc_transport_data_iochannel *backend_data = g_new0(struct irc_transport_data_iochannel, 1);
327         
328         ret->backend_ops = &irc_transport_iochannel_ops;
329         ret->backend_data = backend_data;
330         backend_data->incoming = iochannel;
331         backend_data->pending_lines = g_queue_new();
332         backend_data->outgoing_iconv = backend_data->incoming_iconv = (GIConv)-1;
333         g_io_channel_ref(backend_data->incoming);
334
335         return ret;
336 }
337
338 static gboolean transport_send_queue(GIOChannel *ioc, GIOCondition cond, 
339                                                                           void *_transport)
340 {
341         gboolean ret = FALSE;
342         struct irc_transport *transport = _transport;
343         struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)transport->backend_data;
344         GIOStatus status;
345
346         g_assert(ioc == backend_data->incoming);
347
348         status = g_io_channel_flush(backend_data->incoming, NULL);
349         if (status == G_IO_STATUS_AGAIN)
350                 ret = TRUE;
351
352         g_assert(backend_data->pending_lines != NULL);
353
354         while (!g_queue_is_empty(backend_data->pending_lines)) {
355                 GError *error = NULL;
356                 struct irc_line *l = g_queue_pop_head(backend_data->pending_lines);
357
358                 g_assert(backend_data->incoming != NULL);
359                 status = irc_send_line(backend_data->incoming, 
360                                                            backend_data->outgoing_iconv, l, &error);
361
362                 switch (status) {
363                 case G_IO_STATUS_AGAIN:
364                         g_queue_push_head(backend_data->pending_lines, l);
365                         return TRUE;
366                 case G_IO_STATUS_ERROR:
367                         transport->callbacks->log(transport, l, error);
368                         g_error_free(error);
369                         break;
370                 case G_IO_STATUS_EOF:
371                         backend_data->outgoing_id = 0;
372
373                         transport->callbacks->hangup(transport);
374
375                         free_line(l);
376
377                         return FALSE;
378                 case G_IO_STATUS_NORMAL:
379                         transport->last_line_sent = time(NULL);
380                         break;
381                 }
382
383                 status = g_io_channel_flush(backend_data->incoming, &error);
384                 switch (status) {
385                 case G_IO_STATUS_EOF:
386                         g_assert_not_reached();
387                 case G_IO_STATUS_AGAIN:
388                         free_line(l);
389                         return TRUE;
390                 case G_IO_STATUS_NORMAL:
391                         break;
392                 case G_IO_STATUS_ERROR:
393                         transport->callbacks->log(transport, l, error);
394                         g_error_free(error);
395                         break;
396                 }
397                 free_line(l);
398         }
399
400         if (!ret)
401                 backend_data->outgoing_id = 0;
402
403         if (!ret && backend_data->pending_disconnect)
404                 really_disconnect(backend_data);
405
406         return ret;
407 }