2 ctrlproxy: A modular IRC proxy
3 (c) 2002-2007 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"
23 #include "transport.h"
25 #define AF_LOCAL AF_UNIX
28 static GHashTable *virtual_network_ops = NULL;
30 static gboolean delayed_connect_server(struct irc_network *s);
31 static gboolean connect_server(struct irc_network *s);
32 static gboolean close_server(struct irc_network *s);
33 static void reconnect(struct irc_network *server);
34 static gboolean server_finish_connect(GIOChannel *ioc, GIOCondition cond,
37 static void state_log_helper(enum log_level l, void *userdata, const char *msg)
39 network_log(l, (const struct irc_network *)userdata, "%s", msg);
42 static void server_send_login (struct irc_network *s)
44 struct irc_login_details *login_details = s->callbacks->get_login_details(s);
47 s->connection.state = NETWORK_CONNECTION_STATE_LOGIN_SENT;
49 network_log(LOG_TRACE, s, "Sending login details");
51 s->external_state = network_state_init(login_details->nick, login_details->username,
53 network_state_set_log_fn(s->external_state, state_log_helper, s);
54 if (s->callbacks->state_set)
55 s->callbacks->state_set(s);
56 g_assert(s->linestack != NULL);
58 if (login_details->password != NULL) {
59 network_send_args(s, "PASS", login_details->password, NULL);
61 g_assert(login_details->nick != NULL && strlen(login_details->nick) > 0);
62 network_send_args(s, "NICK", login_details->nick, NULL);
63 g_assert(login_details->username != NULL && strlen(login_details->username) > 0);
64 g_assert(login_details->mode != NULL && strlen(login_details->mode) > 0);
65 g_assert(login_details->unused != NULL && strlen(login_details->unused) > 0);
66 g_assert(login_details->realname != NULL && strlen(login_details->realname) > 0);
67 network_send_args(s, "USER", login_details->username, login_details->mode,
68 login_details->unused, login_details->realname, NULL);
70 free_login_details(login_details);
74 * Change the character set used to communicate with the server.
76 * @param n network to change the character set for
77 * @param name name of the character set to use
78 * @return true if setting the charset worked
80 gboolean network_set_charset(struct irc_network *n, const char *name)
82 if (!transport_set_charset(n->connection.transport, name)) {
83 network_log(LOG_WARNING, n, "Unable to find charset `%s'", name);
89 static void network_report_disconnect(struct irc_network *n, const char *fmt, ...)
94 tmp = g_strdup_vprintf(fmt, ap);
97 g_free(n->connection.data.tcp.last_disconnect_reason);
98 n->connection.data.tcp.last_disconnect_reason = tmp;
100 network_log(LOG_WARNING, n, "%s", tmp);
103 static void on_transport_disconnect(struct irc_transport *transport)
108 static void on_transport_hup(struct irc_transport *transport)
110 struct irc_network *network = transport->userdata;
112 network_report_disconnect(network, "Hangup from server, scheduling reconnect");
116 static gboolean on_transport_error(struct irc_transport *transport, const char *error_msg)
118 struct irc_network *network = transport->userdata;
120 network_report_disconnect(network,
121 "Error from server: %s, scheduling reconnect", error_msg);
128 static void on_transport_charset_error(struct irc_transport *transport, const char *error_msg)
130 struct irc_network *network = transport->userdata;
132 network_log(LOG_WARNING, network, "Error while sending line with charset: %s",
136 static gboolean on_transport_recv(struct irc_transport *transport,
137 const struct irc_line *line)
139 struct irc_network *server = transport->userdata;
140 return server->callbacks->process_from_server(server, line);
143 static void on_transport_log(struct irc_transport *transport, const struct irc_line *l, const GError *error)
145 struct irc_network *network = transport->userdata;
147 network_log(LOG_WARNING, network, "Error while sending line '%s': %s",
148 l->args[0], error->message);
151 static const struct irc_transport_callbacks network_callbacks = {
152 .disconnect = on_transport_disconnect,
153 .hangup = on_transport_hup,
154 .error = on_transport_error,
155 .charset_error = on_transport_charset_error,
156 .recv = on_transport_recv,
157 .log = on_transport_log,
160 static struct tcp_server_config *network_get_next_tcp_server(struct irc_network *n)
162 struct network_config *nc = n->private_data;
167 cur = g_list_find(nc->type_settings.tcp.servers, n->connection.data.tcp.current_server);
169 /* Get next available server */
170 if (cur != NULL && cur->next != NULL)
173 cur = nc->type_settings.tcp.servers;
181 static gboolean network_send_line_direct(struct irc_network *s, struct irc_client *c,
182 const struct irc_line *ol)
184 struct irc_line nl, *l;
185 struct network_config *nc = s->private_data;
187 g_assert(nc != NULL);
189 g_assert(nc->type == NETWORK_TCP ||
190 nc->type == NETWORK_PROGRAM ||
191 nc->type == NETWORK_IOCHANNEL ||
192 nc->type == NETWORK_VIRTUAL);
195 memcpy(l, ol, sizeof(struct irc_line));
198 /* origin lines should never be sent to the server */
199 g_assert(l->origin == NULL);
201 if (nc->type == NETWORK_VIRTUAL) {
202 if (s->connection.data.virtual.ops == NULL)
204 return s->connection.data.virtual.ops->to_server(s, c, l);
206 if (s->connection.transport == NULL)
208 return transport_send_line(s->connection.transport, l);
213 * Send a line to the network.
214 * @param s Network to send to.
215 * @param c Client the line was sent by originally.
216 * @param ol Line to send to the network
218 gboolean network_send_line(struct irc_network *s, struct irc_client *c,
219 const struct irc_line *ol)
227 if (l.origin == NULL && s->external_state != NULL) {
228 l.origin = s->external_state->me.hostmask;
231 if (l.origin != NULL) {
232 if (!s->callbacks->process_to_server(s, &l)) {
237 return network_send_line_direct(s, c, ol);
241 * Indicate that a response is received by a virtual network.
243 * @param n Network to receive data
244 * @param num Number of the response to receive
246 gboolean virtual_network_recv_response(struct irc_network *n, int num, ...)
251 struct network_config *nc;
255 nc = n->private_data;
257 g_assert(nc->type == NETWORK_VIRTUAL);
260 l = virc_parse_line(n->name, ap);
263 l->args = g_realloc(l->args, sizeof(char *) * (l->argc+4));
264 memmove(&l->args[2], &l->args[0], l->argc * sizeof(char *));
266 l->args[0] = g_strdup_printf("%03d", num);
268 if (n->external_state != NULL && n->external_state->me.nick != NULL)
269 l->args[1] = g_strdup(n->external_state->me.nick);
271 l->args[1] = g_strdup("*");
274 l->args[l->argc] = NULL;
276 ret = virtual_network_recv_line(n, l);
284 * Indicate that a line is received by a virtual network.
286 * @param s Network to send to.
287 * @param l Line to receive.
289 gboolean virtual_network_recv_line(struct irc_network *s, struct irc_line *l)
294 if (l->origin == NULL)
295 l->origin = g_strdup(get_my_hostname());
297 return s->callbacks->process_from_server(s, l);
301 * Indicate that a line has been received.
303 * @param s Network to use.
304 * @param origin Origin to make the data originate from
306 gboolean virtual_network_recv_args(struct irc_network *s, const char *origin, ...)
314 va_start(ap, origin);
315 l = virc_parse_line(origin, ap);
318 ret = virtual_network_recv_line(s, l);
326 * Send a new line to the network.
329 * @param ... Arguments terminated by NULL
331 gboolean network_send_args(struct irc_network *s, ...)
340 l = virc_parse_line(NULL, ap);
343 ret = network_send_line(s, NULL, l);
350 static gboolean bindsock(struct irc_network *s,
351 int sock, struct addrinfo *res,
355 struct addrinfo hints_bind;
357 struct addrinfo *res_bind, *addrinfo_bind;
359 memset(&hints_bind, 0, sizeof(hints_bind));
360 hints_bind.ai_family = res->ai_family;
363 hints_bind.ai_flags = AI_ADDRCONFIG;
366 hints_bind.ai_socktype = res->ai_socktype;
367 hints_bind.ai_protocol = res->ai_protocol;
369 error = getaddrinfo(address, service, &hints_bind, &addrinfo_bind);
371 network_log(LOG_ERROR, s,
372 "Unable to lookup %s:%s %s", address, service,
373 gai_strerror(error));
377 for (res_bind = addrinfo_bind;
378 res_bind; res_bind = res_bind->ai_next) {
379 if (bind(sock, res_bind->ai_addr, res_bind->ai_addrlen) < 0) {
380 network_log(LOG_ERROR, s, "Unable to bind to %s:%s %s",
381 address, service, strerror(errno));
385 freeaddrinfo(addrinfo_bind);
387 return (res_bind != NULL);
393 * @param server network to ping
394 * @param ping_source GSource id of the ping event
396 static void ping_server(struct irc_network *server, gboolean ping_source)
398 gint silent_time = time(NULL) - server->connection.last_line_recvd;
399 if (silent_time > MAX_SILENT_TIME) {
400 network_report_disconnect(server, "Ping timeout (%d seconds)",
403 } else if (silent_time > MIN_SILENT_TIME) {
404 network_send_args(server, "PING", "ctrlproxy", NULL);
408 static gboolean connect_current_tcp_server(struct irc_network *s)
410 struct addrinfo *res;
412 struct tcp_server_config *cs;
413 GIOChannel *ioc = NULL;
414 struct addrinfo hints;
415 struct addrinfo *addrinfo = NULL;
418 gboolean connect_finished = TRUE;
419 struct network_config *nc;
423 nc = s->private_data;
425 if (!s->connection.data.tcp.current_server) {
426 s->connection.data.tcp.current_server = network_get_next_tcp_server(s);
429 network_log(LOG_TRACE, s, "connect_current_tcp_server");
431 cs = s->connection.data.tcp.current_server;
433 nc->autoconnect = FALSE;
434 network_log(LOG_WARNING, s, "No servers listed, not connecting");
438 network_log(LOG_INFO, s, "Connecting with %s:%s", cs->host, cs->port);
440 memset(&hints, 0, sizeof(hints));
441 hints.ai_family = PF_UNSPEC;
442 hints.ai_socktype = SOCK_STREAM;
445 hints.ai_flags = AI_ADDRCONFIG;
449 error = getaddrinfo(cs->host, cs->port, &hints, &addrinfo);
451 network_log(LOG_ERROR, s, "Unable to lookup %s:%s %s",
452 cs->host, cs->port, gai_strerror(error));
453 if (addrinfo != NULL)
454 freeaddrinfo(addrinfo);
460 for (res = addrinfo; res; res = res->ai_next) {
462 sock = socket(res->ai_family, res->ai_socktype,
468 if (cs->bind_address)
469 bindsock(s, sock, res, cs->bind_address, NULL);
470 else if (nc->type_settings.tcp.default_bind_address)
471 bindsock(s, sock, res, nc->type_settings.tcp.default_bind_address, NULL);
473 ioc = g_io_channel_unix_new(sock);
474 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
476 if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
477 if (errno == EINPROGRESS) {
478 connect_finished = FALSE;
481 g_io_channel_unref(ioc);
489 size = sizeof(struct sockaddr_storage);
490 g_assert(s->connection.data.tcp.local_name == NULL);
491 g_assert(s->connection.data.tcp.remote_name == NULL);
494 network_log(LOG_ERROR, s, "Unable to connect: %s", strerror(errno));
498 s->connection.data.tcp.remote_name = g_memdup(res->ai_addr,
500 s->connection.data.tcp.local_name = g_malloc(size);
501 s->connection.data.tcp.namelen = getsockname(sock, s->connection.data.tcp.local_name, &size);
503 freeaddrinfo(addrinfo);
505 g_io_channel_set_close_on_unref(ioc, TRUE);
507 cs = s->connection.data.tcp.current_server;
510 g_io_channel_set_close_on_unref(ioc, TRUE);
511 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
513 ioc = ssl_wrap_iochannel (ioc, SSL_TYPE_CLIENT,
514 s->connection.data.tcp.current_server->host,
518 network_report_disconnect(s, "Couldn't connect via server %s:%s", cs->host, cs->port);
523 network_log(LOG_WARNING, s, "SSL enabled for %s:%s, but no SSL support loaded", cs->host, cs->port);
527 s->connection.state = NETWORK_CONNECTION_STATE_CONNECTING;
529 if (!connect_finished) {
530 s->connection.data.tcp.connect_id = g_io_add_watch(ioc,
531 G_IO_OUT|G_IO_ERR|G_IO_HUP,
532 server_finish_connect, s);
534 server_finish_connect(ioc, G_IO_OUT, s);
537 g_io_channel_unref(ioc);
542 static void reconnect(struct irc_network *server)
544 struct network_config *nc = server->private_data;
547 g_assert(server->connection.state != NETWORK_CONNECTION_STATE_RECONNECT_PENDING);
549 close_server(server);
553 if (nc->type == NETWORK_TCP)
554 server->connection.data.tcp.current_server = network_get_next_tcp_server(server);
556 if (nc->type == NETWORK_TCP ||
557 nc->type == NETWORK_IOCHANNEL ||
558 nc->type == NETWORK_PROGRAM) {
559 server->connection.state = NETWORK_CONNECTION_STATE_RECONNECT_PENDING;
560 network_log(LOG_INFO, server, "Reconnecting in %d seconds",
561 server->reconnect_interval);
562 server->reconnect_id = g_timeout_add(1000 *
563 server->reconnect_interval,
564 (GSourceFunc) delayed_connect_server, server);
566 connect_server(server);
570 static void free_tcp_names(struct irc_network *n)
572 g_free(n->connection.data.tcp.local_name);
573 g_free(n->connection.data.tcp.remote_name);
574 n->connection.data.tcp.local_name = NULL;
575 n->connection.data.tcp.remote_name = NULL;
578 static gboolean close_server(struct irc_network *n)
580 struct network_config *nc = n->private_data;
584 if (n->connection.state == NETWORK_CONNECTION_STATE_RECONNECT_PENDING) {
585 g_source_remove(n->reconnect_id);
587 n->connection.state = NETWORK_CONNECTION_STATE_NOT_CONNECTED;
590 if (n->connection.state == NETWORK_CONNECTION_STATE_CONNECTING) {
591 g_source_remove(n->connection.data.tcp.connect_id);
592 n->connection.data.tcp.connect_id = 0;
593 n->connection.state = NETWORK_CONNECTION_STATE_NOT_CONNECTED;
594 if (nc->type == NETWORK_TCP)
598 if (n->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
602 network_send_args(n, "QUIT", NULL);
604 if (n->callbacks->disconnect != NULL)
605 n->callbacks->disconnect(n);
607 if (n->external_state) {
609 free_network_state(n->external_state);
610 n->external_state = NULL;
617 case NETWORK_PROGRAM:
618 case NETWORK_IOCHANNEL:
619 irc_transport_disconnect(n->connection.transport);
620 if (n->connection.data.tcp.ping_id > 0) {
621 g_source_remove(n->connection.data.tcp.ping_id);
622 n->connection.data.tcp.ping_id = 0;
625 free_irc_transport(n->connection.transport);
627 case NETWORK_VIRTUAL:
628 if (n->connection.data.virtual.ops &&
629 n->connection.data.virtual.ops->fini) {
630 n->connection.data.virtual.ops->fini(n);
633 default: g_assert_not_reached();
636 n->connection.state = NETWORK_CONNECTION_STATE_NOT_CONNECTED;
641 static pid_t piped_child(struct irc_network *s, char* const command[], int *f_in)
646 if (socketpair(PF_UNIX, SOCK_STREAM, AF_LOCAL, sock) == -1) {
647 network_log(LOG_ERROR, s, "socketpair: %s", strerror(errno));
653 fcntl(sock[0], F_SETFL, O_NONBLOCK);
658 network_log(LOG_ERROR, s, "fork: %s", strerror(errno));
670 execvp(command[0], command);
679 static gboolean server_finish_connect(GIOChannel *ioc, GIOCondition cond,
682 struct irc_network *s = data;
684 if (cond & G_IO_ERR) {
685 network_report_disconnect(s, "Error connecting: %s",
686 g_io_channel_unix_get_sock_error(ioc));
691 if (cond & G_IO_OUT) {
692 s->connection.state = NETWORK_CONNECTION_STATE_CONNECTED;
694 s->connection.data.tcp.connect_id = 0; /* Otherwise data will be queued */
695 network_set_iochannel(s, ioc);
697 s->connection.last_line_recvd = time(NULL);
698 s->connection.data.tcp.ping_id = g_timeout_add(5000,
699 (GSourceFunc) ping_server, s);
704 if (cond & G_IO_HUP) {
705 network_report_disconnect(s, "Server closed connection");
714 * Change the IO channel used to communicate with a network.
715 * @param s Network to set the IO channel for.
716 * @param ioc IO channel to use
718 gboolean network_set_iochannel(struct irc_network *s, GIOChannel *ioc)
720 GError *error = NULL;
721 struct network_config *nc = s->private_data;
722 g_assert(nc->type != NETWORK_VIRTUAL);
723 if (g_io_channel_set_encoding(ioc, NULL, &error) != G_IO_STATUS_NORMAL) {
724 network_log(LOG_ERROR, s, "Unable to change encoding: %s",
725 error?error->message:"unknown");
730 g_io_channel_set_close_on_unref(ioc, TRUE);
731 if (g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, &error) != G_IO_STATUS_NORMAL) {
732 network_log(LOG_ERROR, s, "Unable to change flags: %s",
733 error?error->message:"unknown");
739 s->connection.transport = irc_transport_new_iochannel(ioc);
741 irc_transport_set_callbacks(s->connection.transport,
745 transport_parse_buffer(s->connection.transport);
747 server_send_login(s);
752 static gboolean connect_program(struct irc_network *s)
754 struct network_config *nc = s->private_data;
762 g_assert(nc->type == NETWORK_PROGRAM);
764 cmd[0] = nc->type_settings.program_location;
766 pid = piped_child(s, cmd, &sock);
768 if (pid == -1) return FALSE;
770 ioc = g_io_channel_unix_new(sock);
771 network_set_iochannel(s, ioc);
773 g_io_channel_unref(ioc);
775 if (s->name == NULL) {
776 if (strchr(nc->type_settings.program_location, '/')) {
777 s->name = g_strdup(strrchr(nc->type_settings.program_location, '/')+1);
779 s->name = g_strdup(nc->type_settings.program_location);
786 static gboolean connect_virtual(struct irc_network *s)
788 struct irc_login_details *details;
789 struct network_config *nc = s->private_data;
791 if (nc->type_settings.virtual.ops == NULL)
794 details = s->callbacks->get_login_details(s);
796 s->external_state = network_state_init(details->nick, details->username,
799 free_login_details(details);
800 s->external_state->userdata = s;
801 s->external_state->log = state_log_helper;
802 if (s->callbacks->state_set)
803 s->callbacks->state_set(s);
804 s->connection.state = NETWORK_CONNECTION_STATE_MOTD_RECVD;
806 if (nc->type_settings.virtual.ops->init)
807 return nc->type_settings.virtual.ops->init(s);
812 static gboolean connect_server(struct irc_network *s)
814 struct network_config *nc = s->private_data;
821 return connect_current_tcp_server(s);
823 case NETWORK_PROGRAM:
824 return connect_program(s);
826 case NETWORK_VIRTUAL:
827 return connect_virtual(s);
828 default: g_assert_not_reached();
834 static gboolean delayed_connect_server(struct irc_network *s)
838 return (s->connection.state == NETWORK_CONNECTION_STATE_RECONNECT_PENDING);
841 struct irc_network *irc_network_new(const struct irc_network_callbacks *callbacks, void *private_data)
843 struct irc_network *s;
845 s = g_new0(struct irc_network, 1);
846 s->callbacks = callbacks;
848 s->private_data = private_data;
849 s->reconnect_interval = ((struct network_config *)private_data)->reconnect_interval == -1?DEFAULT_RECONNECT_INTERVAL:((struct network_config *)private_data)->reconnect_interval;
850 s->info = network_info_init();
851 s->name = g_strdup(((struct network_config *)private_data)->name);
852 s->info->ircd = g_strdup("ctrlproxy");
853 s->info->forced_nick_changes = TRUE; /* Forced nick changes are done by ctrlproxy */
856 s->ssl_credentials = ssl_get_client_credentials(NULL);
863 * Connect to a network, returns TRUE if connection was successful
864 * (or startup of connection was successful)
866 * @param s Network to connect to
868 gboolean connect_network(struct irc_network *s)
871 g_assert(s->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED ||
872 s->connection.state == NETWORK_CONNECTION_STATE_RECONNECT_PENDING);
874 return connect_server(s);
877 static void free_network(struct irc_network *s)
879 struct network_config *nc;
881 nc = s->private_data;
883 free_network_info(s->info);
884 if (nc->type == NETWORK_TCP)
885 g_free(s->connection.data.tcp.last_disconnect_reason);
888 ssl_free_client_credentials(s->ssl_credentials);
897 * Disconnect from a network. The network will still be kept in memory,
898 * but all socket connections associated with it will be dropped.
900 * @param s Network to disconnect from
901 * @return Whether disconnecting succeeded.
903 gboolean disconnect_network(struct irc_network *s)
906 if (s->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
910 network_log(LOG_INFO, s, "Disconnecting");
911 return close_server(s);
915 * Register a new virtual network type.
917 * @param ops Callback functions for the virtual network type.
919 void register_virtual_network(struct virtual_network_ops *ops)
921 if (virtual_network_ops == NULL)
922 virtual_network_ops = g_hash_table_new(g_str_hash, g_str_equal);
924 g_hash_table_insert(virtual_network_ops, ops->name, ops);
927 struct virtual_network_ops *find_virtual_network(const char *name)
929 if (virtual_network_ops == NULL)
931 return g_hash_table_lookup(virtual_network_ops, name);
935 * Autoconnect to all the networks in a list.
937 * @param networks GList with networks
940 gboolean autoconnect_networks(GList *networks)
943 for (gl = networks; gl; gl = gl->next)
945 struct irc_network *n = gl->data;
946 struct network_config *nc = n->private_data;
957 * Find a network by name.
959 * @param networks GList with possible networks
960 * @param name Name of the network to search for.
961 * @return first network found or NULL
963 struct irc_network *find_network(GList *networks, const char *name)
966 for (gl = networks; gl; gl = gl->next) {
967 struct irc_network *n = gl->data;
968 if (n->name && !g_strcasecmp(n->name, name))
976 * Switch to the next server listed for a network.
980 void irc_network_select_next_server(struct irc_network *n)
982 struct network_config *nc = n->private_data;
987 if (nc->type != NETWORK_TCP)
990 network_log(LOG_INFO, n, "Trying next server");
991 n->connection.data.tcp.current_server = network_get_next_tcp_server(n);
995 * Generate 005-response string to send to client connected to a network.
997 * @param n Network to generate for
998 * @return An 005 string, newly allocated
1000 char *network_generate_feature_string(struct irc_network *n)
1004 return network_info_string(n->info);
1008 * Increase the reference count for a network
1010 struct irc_network *network_ref(struct irc_network *n)
1017 void irc_network_unref(struct irc_network *n)
1022 if (n->references == 0)
1026 void network_log(enum log_level l, const struct irc_network *s,
1027 const char *fmt, ...)
1032 if (s->callbacks->log == NULL)
1039 ret = g_strdup_vprintf(fmt, ap);
1042 s->callbacks->log(l, s, ret);
1047 void unregister_virtual_networks(void)
1049 if (virtual_network_ops != NULL)
1050 g_hash_table_destroy(virtual_network_ops);
1051 virtual_network_ops = NULL;