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
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include "line-split.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_strncasecmp(address, "irc", 3) == 0 ||
81 g_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_sprintf(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);
170 static void server_real_connect(SERVER_REC *server, IPADDR *ip,
171 const char *unix_socket)
174 IPADDR *own_ip = NULL;
177 char ipaddr[MAX_IP_LEN];
180 g_return_if_fail(ip != NULL || unix_socket != NULL);
182 signal_emit("server connecting", 2, server, ip);
184 if (server->connrec->no_connect)
188 own_ip = ip == NULL ? NULL :
189 (IPADDR_IS_V6(ip) ? server->connrec->own_ip6 :
190 server->connrec->own_ip4);
191 port = server->connrec->proxy != NULL ?
192 server->connrec->proxy_port : server->connrec->port;
193 handle = server->connrec->use_ssl ?
194 net_connect_ip_ssl(ip, port, own_ip, server->connrec->ssl_cert, server->connrec->ssl_pkey,
195 server->connrec->ssl_cafile, server->connrec->ssl_capath, server->connrec->ssl_verify) :
196 net_connect_ip(ip, port, own_ip);
198 handle = net_connect_unix(unix_socket);
201 if (handle == NULL) {
203 errmsg = g_strerror(errno);
205 if (errno == EADDRNOTAVAIL) {
206 if (own_ip != NULL) {
207 /* show the IP which is causing the error */
208 net_ip2host(own_ip, ipaddr);
209 errmsg2 = g_strconcat(errmsg, ": ", ipaddr, NULL);
211 server->no_reconnect = TRUE;
213 if (server->connrec->use_ssl && errno == ENOSYS)
214 server->no_reconnect = TRUE;
216 server->connection_lost = TRUE;
217 server_connect_failed(server, errmsg2 ? errmsg2 : errmsg);
220 server->handle = net_sendbuffer_create(handle, 0);
221 server->connect_tag =
222 g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
224 server_connect_callback_init,
229 static void server_connect_callback_readpipe(SERVER_REC *server)
231 RESOLVED_IP_REC iprec;
233 const char *errormsg;
234 char *servername = NULL;
236 g_source_remove(server->connect_tag);
237 server->connect_tag = -1;
239 net_gethostbyname_return(server->connect_pipe[0], &iprec);
241 g_io_channel_close(server->connect_pipe[0]);
242 g_io_channel_unref(server->connect_pipe[0]);
243 g_io_channel_close(server->connect_pipe[1]);
244 g_io_channel_unref(server->connect_pipe[1]);
246 server->connect_pipe[0] = NULL;
247 server->connect_pipe[1] = NULL;
249 /* figure out if we should use IPv4 or v6 address */
250 if (iprec.error != 0) {
253 } else if (server->connrec->family == AF_INET) {
254 /* force IPv4 connection */
255 ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
256 servername = iprec.host4;
257 } else if (server->connrec->family == AF_INET6) {
258 /* force IPv6 connection */
259 ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
260 servername = iprec.host6;
262 /* pick the one that was found, or if both do it like
263 /SET resolve_prefer_ipv6 says. */
264 if (iprec.ip4.family == 0 ||
265 (iprec.ip6.family != 0 &&
266 settings_get_bool("resolve_prefer_ipv6"))) {
268 servername = iprec.host6;
271 servername = iprec.host4;
278 g_free(server->connrec->address);
279 server->connrec->address = g_strdup(servername);
281 server_real_connect(server, ip, NULL);
284 if (iprec.error == 0 || net_hosterror_notfound(iprec.error)) {
285 /* IP wasn't found for the host, don't try to
286 reconnect back to this server */
287 server->dns_error = TRUE;
290 if (iprec.error == 0) {
291 /* forced IPv4 or IPv6 address but it wasn't found */
292 errormsg = server->connrec->family == AF_INET ?
293 "IPv4 address not found for host" :
294 "IPv6 address not found for host";
296 /* gethostbyname() failed */
297 errormsg = iprec.errorstr != NULL ? iprec.errorstr :
298 "Host lookup failed";
301 server->connection_lost = TRUE;
302 server_connect_failed(server, errormsg);
305 g_free(iprec.errorstr);
310 SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
312 CHAT_PROTOCOL_REC *proto;
315 proto = CHAT_PROTOCOL(conn);
316 server = proto->server_init_connect(conn);
317 proto->server_connect(server);
322 /* initializes server record but doesn't start connecting */
323 void server_connect_init(SERVER_REC *server)
327 g_return_if_fail(server != NULL);
329 MODULE_DATA_INIT(server);
330 server->type = module_get_uniq_id("SERVER", 0);
333 server->nick = g_strdup(server->connrec->nick);
334 if (server->connrec->username == NULL || *server->connrec->username == '\0') {
335 g_free_not_null(server->connrec->username);
337 str = g_get_user_name();
338 if (*str == '\0') str = "unknown";
339 server->connrec->username = g_strdup(str);
341 if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
342 g_free_not_null(server->connrec->realname);
344 str = g_get_real_name();
345 if (*str == '\0') str = server->connrec->username;
346 server->connrec->realname = g_strdup(str);
349 server->tag = server_create_tag(server->connrec);
350 server->connect_tag = -1;
353 /* starts connecting to server */
354 int server_start_connect(SERVER_REC *server)
356 const char *connect_address;
359 g_return_val_if_fail(server != NULL, FALSE);
360 if (!server->connrec->unix_socket && server->connrec->port <= 0)
363 server->rawlog = rawlog_create();
365 if (server->connrec->connect_handle != NULL) {
366 /* already connected */
367 GIOChannel *handle = server->connrec->connect_handle;
369 server->connrec->connect_handle = NULL;
370 server->handle = net_sendbuffer_create(handle, 0);
371 server_connect_finished(server);
372 } else if (server->connrec->unix_socket) {
373 /* connect with unix socket */
374 server_real_connect(server, NULL, server->connrec->address);
376 /* resolve host name */
378 g_warning("server_connect(): pipe() failed.");
380 g_free(server->nick);
384 server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
385 server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
387 connect_address = server->connrec->proxy != NULL ?
388 server->connrec->proxy : server->connrec->address;
389 server->connect_pid =
390 net_gethostbyname_nonblock(connect_address,
391 server->connect_pipe[1],
392 settings_get_bool("resolve_reverse_lookup"));
393 server->connect_tag =
394 g_input_add(server->connect_pipe[0], G_INPUT_READ,
396 server_connect_callback_readpipe,
399 lookup_servers = g_slist_append(lookup_servers, server);
401 signal_emit("server looking", 1, server);
406 static int server_remove_channels(SERVER_REC *server)
411 g_return_val_if_fail(server != NULL, FALSE);
414 for (tmp = server->channels; tmp != NULL; tmp = next) {
415 CHANNEL_REC *channel = tmp->data;
418 channel_destroy(channel);
422 while (server->queries != NULL)
423 query_change_server(server->queries->data, NULL);
425 g_slist_free(server->channels);
426 g_slist_free(server->queries);
431 void server_disconnect(SERVER_REC *server)
435 g_return_if_fail(IS_SERVER(server));
437 if (server->disconnected)
440 if (server->connect_tag != -1) {
441 /* still connecting to server.. */
442 if (server->connect_pid != -1)
443 net_disconnect_nonblock(server->connect_pid);
444 server_connect_failed(server, NULL);
448 servers = g_slist_remove(servers, server);
450 server->disconnected = TRUE;
451 signal_emit("server disconnected", 1, server);
453 /* close all channels */
454 chans = server_remove_channels(server);
456 if (server->handle != NULL) {
457 if (!chans || server->connection_lost)
458 net_sendbuffer_destroy(server->handle, TRUE);
460 /* we were on some channels, try to let the server
461 disconnect so that our quit message is guaranteed
463 net_disconnect_later(net_sendbuffer_handle(server->handle));
464 net_sendbuffer_destroy(server->handle, FALSE);
466 server->handle = NULL;
469 if (server->readtag > 0) {
470 g_source_remove(server->readtag);
471 server->readtag = -1;
474 server_unref(server);
477 void server_ref(SERVER_REC *server)
479 g_return_if_fail(IS_SERVER(server));
484 int server_unref(SERVER_REC *server)
486 g_return_val_if_fail(IS_SERVER(server), FALSE);
488 if (--server->refcount > 0)
491 if (g_slist_find(servers, server) != NULL) {
492 g_warning("Non-referenced server wasn't disconnected");
493 server_disconnect(server);
497 MODULE_DATA_DEINIT(server);
498 server_connect_unref(server->connrec);
499 if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
500 if (server->buffer != NULL) line_split_free(server->buffer);
501 g_free(server->version);
502 g_free(server->away_reason);
503 g_free(server->nick);
511 SERVER_REC *server_find_tag(const char *tag)
515 g_return_val_if_fail(tag != NULL, NULL);
516 if (*tag == '\0') return NULL;
518 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
519 SERVER_REC *server = tmp->data;
521 if (g_strcasecmp(server->tag, tag) == 0)
528 SERVER_REC *server_find_lookup_tag(const char *tag)
532 g_return_val_if_fail(tag != NULL, NULL);
533 if (*tag == '\0') return NULL;
535 for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
536 SERVER_REC *server = tmp->data;
538 if (g_strcasecmp(server->tag, tag) == 0)
545 SERVER_REC *server_find_chatnet(const char *chatnet)
549 g_return_val_if_fail(chatnet != NULL, NULL);
550 if (*chatnet == '\0') return NULL;
552 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
553 SERVER_REC *server = tmp->data;
555 if (server->connrec->chatnet != NULL &&
556 g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
563 void server_connect_ref(SERVER_CONNECT_REC *conn)
568 void server_connect_unref(SERVER_CONNECT_REC *conn)
570 g_return_if_fail(IS_SERVER_CONNECT(conn));
572 if (--conn->refcount > 0)
574 if (conn->refcount < 0) {
575 g_warning("Connection '%s' refcount = %d",
576 conn->tag, conn->refcount);
579 CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
581 if (conn->connect_handle != NULL)
582 net_disconnect(conn->connect_handle);
584 g_free_not_null(conn->proxy);
585 g_free_not_null(conn->proxy_string);
586 g_free_not_null(conn->proxy_string_after);
587 g_free_not_null(conn->proxy_password);
589 g_free_not_null(conn->tag);
590 g_free_not_null(conn->address);
591 g_free_not_null(conn->chatnet);
593 g_free_not_null(conn->own_ip4);
594 g_free_not_null(conn->own_ip6);
596 g_free_not_null(conn->password);
597 g_free_not_null(conn->nick);
598 g_free_not_null(conn->username);
599 g_free_not_null(conn->realname);
601 g_free_not_null(conn->ssl_cert);
602 g_free_not_null(conn->ssl_pkey);
603 g_free_not_null(conn->ssl_cafile);
604 g_free_not_null(conn->ssl_capath);
606 g_free_not_null(conn->channels);
607 g_free_not_null(conn->away_reason);
613 void server_change_nick(SERVER_REC *server, const char *nick)
615 g_free(server->nick);
616 server->nick = g_strdup(nick);
618 signal_emit("server nick changed", 1, server);
621 /* Update own IPv4 and IPv6 records */
622 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
623 IPADDR *ip4, IPADDR *ip6)
625 if (ip4 == NULL || ip4->family == 0)
626 g_free_and_null(conn->own_ip4);
627 if (ip6 == NULL || ip6->family == 0)
628 g_free_and_null(conn->own_ip6);
630 if (ip4 != NULL && ip4->family != 0) {
631 /* IPv4 address was found */
632 if (conn->own_ip4 == NULL)
633 conn->own_ip4 = g_new0(IPADDR, 1);
634 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
637 if (ip6 != NULL && ip6->family != 0) {
638 /* IPv6 address was found */
639 if (conn->own_ip6 == NULL)
640 conn->own_ip6 = g_new0(IPADDR, 1);
641 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
645 /* `optlist' should contain only one unknown key - the server tag.
646 returns NULL if there was unknown -option */
647 SERVER_REC *cmd_options_get_server(const char *cmd,
649 SERVER_REC *defserver)
652 GSList *list, *tmp, *next;
654 /* get all the options, then remove the known ones. there should
655 be only one left - the server tag. */
656 list = hashtable_get_keys(optlist);
658 for (tmp = list; tmp != NULL; tmp = next) {
659 char *option = tmp->data;
662 if (command_have_option(cmd, option))
663 list = g_slist_remove(list, option);
670 server = server_find_tag(list->data);
671 if (server == NULL || list->next != NULL) {
672 /* unknown option (not server tag) */
673 signal_emit("error command", 2,
674 GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
675 server == NULL ? list->data : list->next->data);
685 static void disconnect_servers(GSList *servers, int chat_type)
689 for (tmp = servers; tmp != NULL; tmp = next) {
690 SERVER_REC *rec = tmp->data;
693 if (rec->chat_type == chat_type)
694 server_disconnect(rec);
698 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
700 disconnect_servers(servers, proto->id);
701 disconnect_servers(lookup_servers, proto->id);
704 void servers_init(void)
706 settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
707 settings_add_bool("server", "resolve_reverse_lookup", FALSE);
708 lookup_servers = servers = NULL;
710 signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
712 servers_reconnect_init();
713 servers_setup_init();
716 void servers_deinit(void)
718 signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
720 servers_setup_deinit();
721 servers_reconnect_deinit();
723 module_uniq_destroy("SERVER");
724 module_uniq_destroy("SERVER CONNECT");