2 ctrlproxy: A modular IRC proxy
3 (c) 2002-2008 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 3 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 "internals.h"
21 #include "transport.h"
25 #include <sys/socket.h>
30 struct irc_transport_data_iochannel {
32 gboolean pending_disconnect;
35 GIConv incoming_iconv;
36 GIConv outgoing_iconv;
37 GQueue *pending_lines;
41 static gboolean handle_transport_receive(GIOChannel *c, GIOCondition cond,
44 static void free_pending_line(void *_line, void *userdata)
46 free_line((struct irc_line *)_line);
49 static void irc_transport_iochannel_free_data(void *data)
51 struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)data;
53 g_assert(backend_data->pending_disconnect);
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);
60 g_queue_foreach(backend_data->pending_lines, free_pending_line, NULL);
61 g_queue_free(backend_data->pending_lines);
66 static char *irc_transport_iochannel_get_peer_name(void *data)
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;
74 fd = g_io_channel_unix_get_fd(backend_data->incoming);
76 if (getpeername (fd, (struct sockaddr *)&sa, &len) < 0) {
80 if (sa.ss_family == AF_INET || sa.ss_family == AF_INET6) {
81 if (getnameinfo((struct sockaddr *)&sa, len, hostname, sizeof(hostname),
83 return g_strdup(hostname);
85 } else if (sa.ss_family == AF_UNIX) {
86 return g_strdup("localhost");
92 static gboolean irc_transport_iochannel_set_charset(struct irc_transport *transport, const char *name)
95 struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)transport->backend_data;
98 tmp = g_iconv_open(name, "UTF-8");
100 if (tmp == (GIConv)-1) {
107 if (backend_data->outgoing_iconv != (GIConv)-1)
108 g_iconv_close(backend_data->outgoing_iconv);
110 backend_data->outgoing_iconv = tmp;
113 tmp = g_iconv_open("UTF-8", name);
115 if (tmp == (GIConv)-1) {
122 if (backend_data->incoming_iconv != (GIConv)-1)
123 g_iconv_close(backend_data->incoming_iconv);
125 backend_data->incoming_iconv = tmp;
130 static void really_disconnect(struct irc_transport_data_iochannel *backend_data)
132 g_io_channel_unref(backend_data->incoming);
134 g_source_remove(backend_data->incoming_id);
135 if (backend_data->outgoing_id)
136 g_source_remove(backend_data->outgoing_id);
138 backend_data->incoming = NULL;
141 static void irc_transport_iochannel_disconnect(void *data)
143 struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)data;
145 backend_data->pending_disconnect = TRUE;
147 if (backend_data->outgoing_id == 0)
148 really_disconnect(backend_data);
151 static gboolean transport_send_queue(GIOChannel *ioc, GIOCondition cond,
154 static gboolean irc_transport_iochannel_send_line(struct irc_transport *transport, const struct irc_line *l, GError **error)
159 struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)transport->backend_data;
165 if (backend_data->outgoing_id != 0) {
166 g_queue_push_tail(backend_data->pending_lines, linedup(l));
170 status = irc_send_line(backend_data->incoming, backend_data->outgoing_iconv, l, error);
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));
178 case G_IO_STATUS_EOF:
179 transport->callbacks->hangup(transport);
181 case G_IO_STATUS_ERROR:
182 transport->callbacks->log(transport, l, *error);
186 case G_IO_STATUS_NORMAL:
187 transport->last_line_sent = time(NULL);
191 status = g_io_channel_flush(backend_data->incoming, error);
194 case G_IO_STATUS_EOF:
195 g_assert_not_reached();
196 case G_IO_STATUS_NORMAL:
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);
202 case G_IO_STATUS_ERROR:
203 transport->callbacks->log(transport, l, *error);
213 static void irc_transport_iochannel_activate(struct irc_transport *transport)
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);
220 handle_transport_receive(backend_data->incoming,
221 g_io_channel_get_buffer_condition(backend_data->incoming) & G_IO_IN,
227 static gboolean handle_recv_status(struct irc_transport *transport, GIOStatus status, GError *error)
229 if (status == G_IO_STATUS_EOF) {
230 transport->callbacks->hangup(transport);
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,
240 return transport->callbacks->error(transport, error?error->message:NULL);
249 static gboolean handle_transport_receive(GIOChannel *c, GIOCondition cond,
252 struct irc_transport *transport = _transport;
253 struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)transport->backend_data;
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);
267 if (cond & G_IO_IN) {
268 GError *error = NULL;
272 while ((status = irc_recv_line(c, backend_data->incoming_iconv, &error,
273 &l)) == G_IO_STATUS_NORMAL) {
275 ret &= transport->callbacks->recv(transport, l);
282 ret &= handle_recv_status(transport, status, error);
288 if (cond & G_IO_HUP) {
289 transport->callbacks->hangup(transport);
299 static gboolean irc_transport_iochannel_is_connected(void *data)
301 struct irc_transport_data_iochannel *backend_data = (struct irc_transport_data_iochannel *)data;
303 return backend_data->incoming != NULL;
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,
316 /* GIOChannels passed into this function
318 * - have no encoding set
319 * - work asynchronously
321 * @param iochannel Channel to talk over
323 struct irc_transport *irc_transport_new_iochannel(GIOChannel *iochannel)
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);
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);
338 static gboolean transport_send_queue(GIOChannel *ioc, GIOCondition cond,
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;
346 g_assert(ioc == backend_data->incoming);
348 status = g_io_channel_flush(backend_data->incoming, NULL);
349 if (status == G_IO_STATUS_AGAIN)
352 g_assert(backend_data->pending_lines != NULL);
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);
358 g_assert(backend_data->incoming != NULL);
359 status = irc_send_line(backend_data->incoming,
360 backend_data->outgoing_iconv, l, &error);
363 case G_IO_STATUS_AGAIN:
364 g_queue_push_head(backend_data->pending_lines, l);
366 case G_IO_STATUS_ERROR:
367 transport->callbacks->log(transport, l, error);
370 case G_IO_STATUS_EOF:
371 backend_data->outgoing_id = 0;
373 transport->callbacks->hangup(transport);
378 case G_IO_STATUS_NORMAL:
379 transport->last_line_sent = time(NULL);
383 status = g_io_channel_flush(backend_data->incoming, &error);
385 case G_IO_STATUS_EOF:
386 g_assert_not_reached();
387 case G_IO_STATUS_AGAIN:
390 case G_IO_STATUS_NORMAL:
392 case G_IO_STATUS_ERROR:
393 transport->callbacks->log(transport, l, error);
401 backend_data->outgoing_id = 0;
403 if (!ret && backend_data->pending_disconnect)
404 really_disconnect(backend_data);