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)
177 g_return_if_fail(ip != NULL || unix_socket != NULL);
179 signal_emit("server connecting", 2, server, ip);
182 own_ip = ip == NULL ? NULL :
183 (IPADDR_IS_V6(ip) ? server->connrec->own_ip6 :
184 server->connrec->own_ip4);
185 port = server->connrec->proxy != NULL ?
186 server->connrec->proxy_port : server->connrec->port;
187 handle = server->connrec->use_ssl ?
188 net_connect_ip_ssl(ip, port, own_ip) :
189 net_connect_ip(ip, port, own_ip);
191 handle = net_connect_unix(unix_socket);
194 if (handle == NULL) {
196 if (server->connrec->use_ssl && errno == ENOSYS)
197 server->no_reconnect = TRUE;
199 server->connection_lost = TRUE;
200 server_connect_failed(server, g_strerror(errno));
202 server->handle = net_sendbuffer_create(handle, 0);
203 server->connect_tag =
204 g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
206 server_connect_callback_init,
211 static void server_connect_callback_readpipe(SERVER_REC *server)
213 RESOLVED_IP_REC iprec;
215 const char *errormsg;
216 char *servername = NULL;
218 g_source_remove(server->connect_tag);
219 server->connect_tag = -1;
221 net_gethostbyname_return(server->connect_pipe[0], &iprec);
223 g_io_channel_close(server->connect_pipe[0]);
224 g_io_channel_unref(server->connect_pipe[0]);
225 g_io_channel_close(server->connect_pipe[1]);
226 g_io_channel_unref(server->connect_pipe[1]);
228 server->connect_pipe[0] = NULL;
229 server->connect_pipe[1] = NULL;
231 /* figure out if we should use IPv4 or v6 address */
232 if (iprec.error != 0) {
235 } else if (server->connrec->family == AF_INET) {
236 /* force IPv4 connection */
237 ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
238 servername = iprec.host4;
239 } else if (server->connrec->family == AF_INET6) {
240 /* force IPv6 connection */
241 ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
242 servername = iprec.host6;
244 /* pick the one that was found, or if both do it like
245 /SET resolve_prefer_ipv6 says. */
246 if (iprec.ip4.family == 0 ||
247 (iprec.ip6.family != 0 &&
248 settings_get_bool("resolve_prefer_ipv6"))) {
250 servername = iprec.host6;
253 servername = iprec.host4;
260 g_free(server->connrec->address);
261 server->connrec->address = g_strdup(servername);
263 server_real_connect(server, ip, NULL);
266 if (iprec.error == 0 || net_hosterror_notfound(iprec.error)) {
267 /* IP wasn't found for the host, don't try to
268 reconnect back to this server */
269 server->dns_error = TRUE;
272 if (iprec.error == 0) {
273 /* forced IPv4 or IPv6 address but it wasn't found */
274 errormsg = server->connrec->family == AF_INET ?
275 "IPv4 address not found for host" :
276 "IPv6 address not found for host";
278 /* gethostbyname() failed */
279 errormsg = iprec.errorstr != NULL ? iprec.errorstr :
280 "Host lookup failed";
283 server->connection_lost = TRUE;
284 server_connect_failed(server, errormsg);
287 g_free(iprec.errorstr);
292 SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
294 CHAT_PROTOCOL_REC *proto;
297 proto = CHAT_PROTOCOL(conn);
298 server = proto->server_init_connect(conn);
299 proto->server_connect(server);
304 /* initializes server record but doesn't start connecting */
305 void server_connect_init(SERVER_REC *server)
309 g_return_if_fail(server != NULL);
311 MODULE_DATA_INIT(server);
312 server->type = module_get_uniq_id("SERVER", 0);
315 server->nick = g_strdup(server->connrec->nick);
316 if (server->connrec->username == NULL || *server->connrec->username == '\0') {
317 g_free_not_null(server->connrec->username);
319 str = g_get_user_name();
320 if (*str == '\0') str = "unknown";
321 server->connrec->username = g_strdup(str);
323 if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
324 g_free_not_null(server->connrec->realname);
326 str = g_get_real_name();
327 if (*str == '\0') str = server->connrec->username;
328 server->connrec->realname = g_strdup(str);
331 server->tag = server_create_tag(server->connrec);
332 server->connect_tag = -1;
335 /* starts connecting to server */
336 int server_start_connect(SERVER_REC *server)
338 const char *connect_address;
341 g_return_val_if_fail(server != NULL, FALSE);
342 if (!server->connrec->unix_socket && server->connrec->port <= 0)
345 server->rawlog = rawlog_create();
347 if (server->connrec->connect_handle != NULL) {
348 /* already connected */
349 GIOChannel *handle = server->connrec->connect_handle;
351 server->connrec->connect_handle = NULL;
352 server->handle = net_sendbuffer_create(handle, 0);
353 server_connect_finished(server);
354 } else if (server->connrec->unix_socket) {
355 /* connect with unix socket */
356 server_real_connect(server, NULL, server->connrec->address);
358 /* resolve host name */
360 g_warning("server_connect(): pipe() failed.");
362 g_free(server->nick);
366 server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
367 server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
369 connect_address = server->connrec->proxy != NULL ?
370 server->connrec->proxy : server->connrec->address;
371 server->connect_pid =
372 net_gethostbyname_nonblock(connect_address,
373 server->connect_pipe[1],
374 settings_get_bool("resolve_reverse_lookup"));
375 server->connect_tag =
376 g_input_add(server->connect_pipe[0], G_INPUT_READ,
378 server_connect_callback_readpipe,
381 lookup_servers = g_slist_append(lookup_servers, server);
383 signal_emit("server looking", 1, server);
388 static int server_remove_channels(SERVER_REC *server)
393 g_return_val_if_fail(server != NULL, FALSE);
396 for (tmp = server->channels; tmp != NULL; tmp = next) {
397 CHANNEL_REC *channel = tmp->data;
400 channel_destroy(channel);
404 while (server->queries != NULL)
405 query_change_server(server->queries->data, NULL);
407 g_slist_free(server->channels);
408 g_slist_free(server->queries);
413 void server_disconnect(SERVER_REC *server)
417 g_return_if_fail(IS_SERVER(server));
419 if (server->disconnected)
422 if (server->connect_tag != -1) {
423 /* still connecting to server.. */
424 if (server->connect_pid != -1)
425 net_disconnect_nonblock(server->connect_pid);
426 server_connect_failed(server, NULL);
430 servers = g_slist_remove(servers, server);
432 server->disconnected = TRUE;
433 signal_emit("server disconnected", 1, server);
435 /* close all channels */
436 chans = server_remove_channels(server);
438 if (server->handle != NULL) {
439 if (!chans || server->connection_lost)
440 net_sendbuffer_destroy(server->handle, TRUE);
442 /* we were on some channels, try to let the server
443 disconnect so that our quit message is guaranteed
445 net_disconnect_later(net_sendbuffer_handle(server->handle));
446 net_sendbuffer_destroy(server->handle, FALSE);
448 server->handle = NULL;
451 if (server->readtag > 0) {
452 g_source_remove(server->readtag);
453 server->readtag = -1;
456 server_unref(server);
459 void server_ref(SERVER_REC *server)
461 g_return_if_fail(IS_SERVER(server));
466 int server_unref(SERVER_REC *server)
468 g_return_val_if_fail(IS_SERVER(server), FALSE);
470 if (--server->refcount > 0)
473 if (g_slist_find(servers, server) != NULL) {
474 g_warning("Non-referenced server wasn't disconnected");
475 server_disconnect(server);
479 MODULE_DATA_DEINIT(server);
480 server_connect_unref(server->connrec);
481 if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
482 if (server->buffer != NULL) line_split_free(server->buffer);
483 g_free(server->version);
484 g_free(server->away_reason);
485 g_free(server->nick);
493 SERVER_REC *server_find_tag(const char *tag)
497 g_return_val_if_fail(tag != NULL, NULL);
498 if (*tag == '\0') return NULL;
500 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
501 SERVER_REC *server = tmp->data;
503 if (g_strcasecmp(server->tag, tag) == 0)
510 SERVER_REC *server_find_lookup_tag(const char *tag)
514 g_return_val_if_fail(tag != NULL, NULL);
515 if (*tag == '\0') return NULL;
517 for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
518 SERVER_REC *server = tmp->data;
520 if (g_strcasecmp(server->tag, tag) == 0)
527 SERVER_REC *server_find_chatnet(const char *chatnet)
531 g_return_val_if_fail(chatnet != NULL, NULL);
532 if (*chatnet == '\0') return NULL;
534 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
535 SERVER_REC *server = tmp->data;
537 if (server->connrec->chatnet != NULL &&
538 g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
545 void server_connect_ref(SERVER_CONNECT_REC *conn)
550 void server_connect_unref(SERVER_CONNECT_REC *conn)
552 g_return_if_fail(IS_SERVER_CONNECT(conn));
554 if (--conn->refcount > 0)
556 if (conn->refcount < 0) {
557 g_warning("Connection '%s' refcount = %d",
558 conn->tag, conn->refcount);
561 CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
563 if (conn->connect_handle != NULL)
564 net_disconnect(conn->connect_handle);
566 g_free_not_null(conn->proxy);
567 g_free_not_null(conn->proxy_string);
568 g_free_not_null(conn->proxy_string_after);
569 g_free_not_null(conn->proxy_password);
571 g_free_not_null(conn->tag);
572 g_free_not_null(conn->address);
573 g_free_not_null(conn->chatnet);
575 g_free_not_null(conn->own_ip4);
576 g_free_not_null(conn->own_ip6);
578 g_free_not_null(conn->password);
579 g_free_not_null(conn->nick);
580 g_free_not_null(conn->username);
581 g_free_not_null(conn->realname);
583 g_free_not_null(conn->channels);
584 g_free_not_null(conn->away_reason);
590 void server_change_nick(SERVER_REC *server, const char *nick)
592 g_free(server->nick);
593 server->nick = g_strdup(nick);
595 signal_emit("server nick changed", 1, server);
598 /* Update own IPv4 and IPv6 records */
599 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
600 IPADDR *ip4, IPADDR *ip6)
602 if (ip4 == NULL || ip4->family == 0)
603 g_free_and_null(conn->own_ip4);
604 if (ip6 == NULL || ip6->family == 0)
605 g_free_and_null(conn->own_ip6);
607 if (ip4 != NULL && ip4->family != 0) {
608 /* IPv4 address was found */
609 if (conn->own_ip4 == NULL)
610 conn->own_ip4 = g_new0(IPADDR, 1);
611 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
614 if (ip6 != NULL && ip6->family != 0) {
615 /* IPv6 address was found */
616 if (conn->own_ip6 == NULL)
617 conn->own_ip6 = g_new0(IPADDR, 1);
618 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
622 /* `optlist' should contain only one unknown key - the server tag.
623 returns NULL if there was unknown -option */
624 SERVER_REC *cmd_options_get_server(const char *cmd,
626 SERVER_REC *defserver)
629 GSList *list, *tmp, *next;
631 /* get all the options, then remove the known ones. there should
632 be only one left - the server tag. */
633 list = hashtable_get_keys(optlist);
635 for (tmp = list; tmp != NULL; tmp = next) {
636 char *option = tmp->data;
639 if (command_have_option(cmd, option))
640 list = g_slist_remove(list, option);
647 server = server_find_tag(list->data);
648 if (server == NULL || list->next != NULL) {
649 /* unknown option (not server tag) */
650 signal_emit("error command", 2,
651 GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
652 server == NULL ? list->data : list->next->data);
662 static void disconnect_servers(GSList *servers, int chat_type)
666 for (tmp = servers; tmp != NULL; tmp = next) {
667 SERVER_REC *rec = tmp->data;
670 if (rec->chat_type == chat_type)
671 server_disconnect(rec);
675 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
677 disconnect_servers(servers, proto->id);
678 disconnect_servers(lookup_servers, proto->id);
681 void servers_init(void)
683 settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
684 settings_add_bool("server", "resolve_reverse_lookup", FALSE);
685 lookup_servers = servers = NULL;
687 signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
689 servers_reconnect_init();
690 servers_setup_init();
693 void servers_deinit(void)
695 signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
697 servers_setup_deinit();
698 servers_reconnect_deinit();
700 module_uniq_destroy("SERVER");
701 module_uniq_destroy("SERVER CONNECT");