4 Copyright (C) 1999-2000 Timo Sirainen
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "net-disconnect.h"
25 #include "net-nonblock.h"
26 #include "net-sendbuffer.h"
31 #include "chat-protocols.h"
33 #include "servers-reconnect.h"
34 #include "servers-setup.h"
38 GSList *servers, *lookup_servers;
40 /* connection to server failed */
41 void server_connect_failed(SERVER_REC *server, const char *msg)
43 g_return_if_fail(IS_SERVER(server));
45 lookup_servers = g_slist_remove(lookup_servers, server);
47 signal_emit("server connect failed", 2, server, msg);
49 if (server->connect_tag != -1) {
50 g_source_remove(server->connect_tag);
51 server->connect_tag = -1;
53 if (server->handle != NULL) {
54 net_sendbuffer_destroy(server->handle, TRUE);
55 server->handle = NULL;
58 if (server->connect_pipe[0] != NULL) {
59 g_io_channel_close(server->connect_pipe[0]);
60 g_io_channel_unref(server->connect_pipe[0]);
61 g_io_channel_close(server->connect_pipe[1]);
62 g_io_channel_unref(server->connect_pipe[1]);
63 server->connect_pipe[0] = NULL;
64 server->connect_pipe[1] = NULL;
70 /* generate tag from server's address */
71 static char *server_create_address_tag(const char *address)
73 const char *start, *end;
75 g_return_val_if_fail(address != NULL, NULL);
77 /* try to generate a reasonable server tag */
78 if (strchr(address, '.') == NULL) {
80 } else if (g_ascii_strncasecmp(address, "irc", 3) == 0 ||
81 g_ascii_strncasecmp(address, "chat", 4) == 0) {
82 /* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */
83 end = strrchr(address, '.');
85 while (start > address && *start != '.') start--;
87 /* efnet.cs.hut.fi -> efnet */
88 end = strchr(address, '.');
92 if (start == end) start = address; else start++;
93 if (end == NULL) end = address + strlen(address);
95 return g_strndup(start, (int) (end-start));
98 /* create unique tag for server. prefer ircnet's name or
99 generate it from server's address */
100 static char *server_create_tag(SERVER_CONNECT_REC *conn)
106 g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
108 tag = conn->chatnet != NULL && *conn->chatnet != '\0' ?
109 g_strdup(conn->chatnet) :
110 server_create_address_tag(conn->address);
112 if (conn->tag != NULL && server_find_tag(conn->tag) == NULL &&
113 server_find_lookup_tag(conn->tag) == NULL &&
114 strncmp(conn->tag, tag, strlen(tag)) == 0) {
115 /* use the existing tag if it begins with the same ID -
116 this is useful when you have several connections to
117 same server and you want to keep the same tags with
118 the servers (or it would cause problems when rejoining
119 /LAYOUT SAVEd channels). */
121 return g_strdup(conn->tag);
125 /* then just append numbers after tag until unused is found.. */
126 str = g_string_new(tag);
129 while (server_find_tag(str->str) != NULL ||
130 server_find_lookup_tag(str->str) != NULL) {
131 g_string_printf(str, "%s%d", tag, num);
137 g_string_free(str, FALSE);
141 /* Connection to server finished, fill the rest of the fields */
142 void server_connect_finished(SERVER_REC *server)
144 server->connect_time = time(NULL);
146 servers = g_slist_append(servers, server);
147 signal_emit("server connected", 1, server);
150 static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
154 g_return_if_fail(IS_SERVER(server));
156 error = net_geterror(handle);
158 server->connection_lost = TRUE;
159 server_connect_failed(server, g_strerror(error));
163 lookup_servers = g_slist_remove(lookup_servers, server);
164 g_source_remove(server->connect_tag);
165 server->connect_tag = -1;
167 server_connect_finished(server);
171 static void server_connect_callback_init_ssl(SERVER_REC *server, GIOChannel *handle)
175 g_return_if_fail(IS_SERVER(server));
177 error = irssi_ssl_handshake(handle);
179 server->connection_lost = TRUE;
180 server_connect_failed(server, NULL);
184 if (server->connect_tag != -1)
185 g_source_remove(server->connect_tag);
186 server->connect_tag = g_input_add(handle, error == 1 ? G_INPUT_READ : G_INPUT_WRITE,
188 server_connect_callback_init_ssl,
193 lookup_servers = g_slist_remove(lookup_servers, server);
194 if (server->connect_tag != -1) {
195 g_source_remove(server->connect_tag);
196 server->connect_tag = -1;
199 server_connect_finished(server);
203 static void server_real_connect(SERVER_REC *server, IPADDR *ip,
204 const char *unix_socket)
207 IPADDR *own_ip = NULL;
210 char ipaddr[MAX_IP_LEN];
213 g_return_if_fail(ip != NULL || unix_socket != NULL);
215 signal_emit("server connecting", 2, server, ip);
217 if (server->connrec->no_connect)
221 own_ip = ip == NULL ? NULL :
222 (IPADDR_IS_V6(ip) ? server->connrec->own_ip6 :
223 server->connrec->own_ip4);
224 port = server->connrec->proxy != NULL ?
225 server->connrec->proxy_port : server->connrec->port;
226 handle = server->connrec->use_ssl ?
227 net_connect_ip_ssl(ip, port, own_ip, server) : net_connect_ip(ip, port, own_ip);
229 handle = net_connect_unix(unix_socket);
232 if (handle == NULL) {
234 errmsg = g_strerror(errno);
236 if (errno == EADDRNOTAVAIL) {
237 if (own_ip != NULL) {
238 /* show the IP which is causing the error */
239 net_ip2host(own_ip, ipaddr);
240 errmsg2 = g_strconcat(errmsg, ": ", ipaddr, NULL);
242 server->no_reconnect = TRUE;
244 if (server->connrec->use_ssl && errno == ENOSYS)
245 server->no_reconnect = TRUE;
247 server->connection_lost = TRUE;
248 server_connect_failed(server, errmsg2 ? errmsg2 : errmsg);
251 server->handle = net_sendbuffer_create(handle, 0);
253 if (server->connrec->use_ssl)
254 server_connect_callback_init_ssl(server, handle);
257 server->connect_tag =
258 g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
260 server_connect_callback_init,
265 static void server_connect_callback_readpipe(SERVER_REC *server)
267 RESOLVED_IP_REC iprec;
269 const char *errormsg;
270 char *servername = NULL;
272 g_source_remove(server->connect_tag);
273 server->connect_tag = -1;
275 net_gethostbyname_return(server->connect_pipe[0], &iprec);
277 g_io_channel_close(server->connect_pipe[0]);
278 g_io_channel_unref(server->connect_pipe[0]);
279 g_io_channel_close(server->connect_pipe[1]);
280 g_io_channel_unref(server->connect_pipe[1]);
282 server->connect_pipe[0] = NULL;
283 server->connect_pipe[1] = NULL;
285 /* figure out if we should use IPv4 or v6 address */
286 if (iprec.error != 0) {
289 } else if (server->connrec->family == AF_INET) {
290 /* force IPv4 connection */
291 ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
292 servername = iprec.host4;
293 } else if (server->connrec->family == AF_INET6) {
294 /* force IPv6 connection */
295 ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
296 servername = iprec.host6;
298 /* pick the one that was found, or if both do it like
299 /SET resolve_prefer_ipv6 says. */
300 if (iprec.ip4.family == 0 ||
301 (iprec.ip6.family != 0 &&
302 settings_get_bool("resolve_prefer_ipv6"))) {
304 servername = iprec.host6;
307 servername = iprec.host4;
314 g_free(server->connrec->address);
315 server->connrec->address = g_strdup(servername);
317 server_real_connect(server, ip, NULL);
320 if (iprec.error == 0 || net_hosterror_notfound(iprec.error)) {
321 /* IP wasn't found for the host, don't try to
322 reconnect back to this server */
323 server->dns_error = TRUE;
326 if (iprec.error == 0) {
327 /* forced IPv4 or IPv6 address but it wasn't found */
328 errormsg = server->connrec->family == AF_INET ?
329 "IPv4 address not found for host" :
330 "IPv6 address not found for host";
332 /* gethostbyname() failed */
333 errormsg = iprec.errorstr != NULL ? iprec.errorstr :
334 "Host lookup failed";
337 server->connection_lost = TRUE;
338 server_connect_failed(server, errormsg);
341 g_free(iprec.errorstr);
346 SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
348 CHAT_PROTOCOL_REC *proto;
351 proto = CHAT_PROTOCOL(conn);
352 server = proto->server_init_connect(conn);
353 proto->server_connect(server);
358 /* initializes server record but doesn't start connecting */
359 void server_connect_init(SERVER_REC *server)
363 g_return_if_fail(server != NULL);
365 MODULE_DATA_INIT(server);
366 server->type = module_get_uniq_id("SERVER", 0);
369 server->nick = g_strdup(server->connrec->nick);
370 if (server->connrec->username == NULL || *server->connrec->username == '\0') {
371 g_free_not_null(server->connrec->username);
373 str = g_get_user_name();
374 if (*str == '\0') str = "unknown";
375 server->connrec->username = g_strdup(str);
377 if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
378 g_free_not_null(server->connrec->realname);
380 str = g_get_real_name();
381 if (*str == '\0') str = server->connrec->username;
382 server->connrec->realname = g_strdup(str);
385 server->tag = server_create_tag(server->connrec);
386 server->connect_tag = -1;
389 /* starts connecting to server */
390 int server_start_connect(SERVER_REC *server)
392 const char *connect_address;
395 g_return_val_if_fail(server != NULL, FALSE);
396 if (!server->connrec->unix_socket && server->connrec->port <= 0)
399 server->rawlog = rawlog_create();
401 if (server->connrec->connect_handle != NULL) {
402 /* already connected */
403 GIOChannel *handle = server->connrec->connect_handle;
405 server->connrec->connect_handle = NULL;
406 server->handle = net_sendbuffer_create(handle, 0);
407 server_connect_finished(server);
408 } else if (server->connrec->unix_socket) {
409 /* connect with unix socket */
410 server_real_connect(server, NULL, server->connrec->address);
412 /* resolve host name */
414 g_warning("server_connect(): pipe() failed.");
416 g_free(server->nick);
420 server->connect_pipe[0] = g_io_channel_new(fd[0]);
421 server->connect_pipe[1] = g_io_channel_new(fd[1]);
423 connect_address = server->connrec->proxy != NULL ?
424 server->connrec->proxy : server->connrec->address;
425 server->connect_pid =
426 net_gethostbyname_nonblock(connect_address,
427 server->connect_pipe[1],
428 settings_get_bool("resolve_reverse_lookup"));
429 server->connect_tag =
430 g_input_add(server->connect_pipe[0], G_INPUT_READ,
432 server_connect_callback_readpipe,
435 lookup_servers = g_slist_append(lookup_servers, server);
437 signal_emit("server looking", 1, server);
442 static int server_remove_channels(SERVER_REC *server)
447 g_return_val_if_fail(server != NULL, FALSE);
450 for (tmp = server->channels; tmp != NULL; tmp = next) {
451 CHANNEL_REC *channel = tmp->data;
454 channel_destroy(channel);
458 while (server->queries != NULL)
459 query_change_server(server->queries->data, NULL);
461 g_slist_free(server->channels);
462 g_slist_free(server->queries);
467 void server_disconnect(SERVER_REC *server)
471 g_return_if_fail(IS_SERVER(server));
473 if (server->disconnected)
476 if (server->connect_tag != -1) {
477 /* still connecting to server.. */
478 if (server->connect_pid != -1)
479 net_disconnect_nonblock(server->connect_pid);
480 server_connect_failed(server, NULL);
484 servers = g_slist_remove(servers, server);
486 server->disconnected = TRUE;
487 signal_emit("server disconnected", 1, server);
489 /* close all channels */
490 chans = server_remove_channels(server);
492 if (server->handle != NULL) {
493 if (!chans || server->connection_lost)
494 net_sendbuffer_destroy(server->handle, TRUE);
496 /* we were on some channels, try to let the server
497 disconnect so that our quit message is guaranteed
499 net_disconnect_later(net_sendbuffer_handle(server->handle));
500 net_sendbuffer_destroy(server->handle, FALSE);
502 server->handle = NULL;
505 if (server->readtag > 0) {
506 g_source_remove(server->readtag);
507 server->readtag = -1;
510 server_unref(server);
513 void server_ref(SERVER_REC *server)
515 g_return_if_fail(IS_SERVER(server));
520 int server_unref(SERVER_REC *server)
522 g_return_val_if_fail(IS_SERVER(server), FALSE);
524 if (--server->refcount > 0)
527 if (g_slist_find(servers, server) != NULL) {
528 g_warning("Non-referenced server wasn't disconnected");
529 server_disconnect(server);
533 MODULE_DATA_DEINIT(server);
534 server_connect_unref(server->connrec);
535 if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
536 g_free(server->version);
537 g_free(server->away_reason);
538 g_free(server->nick);
546 SERVER_REC *server_find_tag(const char *tag)
550 g_return_val_if_fail(tag != NULL, NULL);
551 if (*tag == '\0') return NULL;
553 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
554 SERVER_REC *server = tmp->data;
556 if (g_strcasecmp(server->tag, tag) == 0)
563 SERVER_REC *server_find_lookup_tag(const char *tag)
567 g_return_val_if_fail(tag != NULL, NULL);
568 if (*tag == '\0') return NULL;
570 for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
571 SERVER_REC *server = tmp->data;
573 if (g_strcasecmp(server->tag, tag) == 0)
580 SERVER_REC *server_find_chatnet(const char *chatnet)
584 g_return_val_if_fail(chatnet != NULL, NULL);
585 if (*chatnet == '\0') return NULL;
587 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
588 SERVER_REC *server = tmp->data;
590 if (server->connrec->chatnet != NULL &&
591 g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
598 void server_connect_ref(SERVER_CONNECT_REC *conn)
603 void server_connect_unref(SERVER_CONNECT_REC *conn)
605 g_return_if_fail(IS_SERVER_CONNECT(conn));
607 if (--conn->refcount > 0)
609 if (conn->refcount < 0) {
610 g_warning("Connection '%s' refcount = %d",
611 conn->tag, conn->refcount);
614 CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
616 if (conn->connect_handle != NULL)
617 net_disconnect(conn->connect_handle);
619 g_free_not_null(conn->proxy);
620 g_free_not_null(conn->proxy_string);
621 g_free_not_null(conn->proxy_string_after);
622 g_free_not_null(conn->proxy_password);
624 g_free_not_null(conn->tag);
625 g_free_not_null(conn->address);
626 g_free_not_null(conn->chatnet);
628 g_free_not_null(conn->own_ip4);
629 g_free_not_null(conn->own_ip6);
631 g_free_not_null(conn->password);
632 g_free_not_null(conn->nick);
633 g_free_not_null(conn->username);
634 g_free_not_null(conn->realname);
636 g_free_not_null(conn->ssl_cert);
637 g_free_not_null(conn->ssl_pkey);
638 g_free_not_null(conn->ssl_cafile);
639 g_free_not_null(conn->ssl_capath);
641 g_free_not_null(conn->channels);
642 g_free_not_null(conn->away_reason);
648 void server_change_nick(SERVER_REC *server, const char *nick)
650 g_free(server->nick);
651 server->nick = g_strdup(nick);
653 signal_emit("server nick changed", 1, server);
656 /* Update own IPv4 and IPv6 records */
657 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
658 IPADDR *ip4, IPADDR *ip6)
660 if (ip4 == NULL || ip4->family == 0)
661 g_free_and_null(conn->own_ip4);
662 if (ip6 == NULL || ip6->family == 0)
663 g_free_and_null(conn->own_ip6);
665 if (ip4 != NULL && ip4->family != 0) {
666 /* IPv4 address was found */
667 if (conn->own_ip4 == NULL)
668 conn->own_ip4 = g_new0(IPADDR, 1);
669 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
672 if (ip6 != NULL && ip6->family != 0) {
673 /* IPv6 address was found */
674 if (conn->own_ip6 == NULL)
675 conn->own_ip6 = g_new0(IPADDR, 1);
676 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
680 /* `optlist' should contain only one unknown key - the server tag.
681 returns NULL if there was unknown -option */
682 SERVER_REC *cmd_options_get_server(const char *cmd,
684 SERVER_REC *defserver)
687 GSList *list, *tmp, *next;
689 /* get all the options, then remove the known ones. there should
690 be only one left - the server tag. */
691 list = hashtable_get_keys(optlist);
693 for (tmp = list; tmp != NULL; tmp = next) {
694 char *option = tmp->data;
697 if (command_have_option(cmd, option))
698 list = g_slist_remove(list, option);
705 server = server_find_tag(list->data);
706 if (server == NULL || list->next != NULL) {
707 /* unknown option (not server tag) */
708 signal_emit("error command", 2,
709 GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
710 server == NULL ? list->data : list->next->data);
720 static void disconnect_servers(GSList *servers, int chat_type)
724 for (tmp = servers; tmp != NULL; tmp = next) {
725 SERVER_REC *rec = tmp->data;
728 if (rec->chat_type == chat_type)
729 server_disconnect(rec);
733 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
735 disconnect_servers(servers, proto->id);
736 disconnect_servers(lookup_servers, proto->id);
739 void servers_init(void)
741 settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
742 settings_add_bool("server", "resolve_reverse_lookup", FALSE);
743 lookup_servers = servers = NULL;
745 signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
747 servers_reconnect_init();
748 servers_setup_init();
751 void servers_deinit(void)
753 signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
755 servers_setup_deinit();
756 servers_reconnect_deinit();
758 module_uniq_destroy("SERVER");
759 module_uniq_destroy("SERVER CONNECT");