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;
217 g_source_remove(server->connect_tag);
218 server->connect_tag = -1;
220 net_gethostbyname_return(server->connect_pipe[0], &iprec);
222 g_io_channel_close(server->connect_pipe[0]);
223 g_io_channel_unref(server->connect_pipe[0]);
224 g_io_channel_close(server->connect_pipe[1]);
225 g_io_channel_unref(server->connect_pipe[1]);
227 server->connect_pipe[0] = NULL;
228 server->connect_pipe[1] = NULL;
230 /* figure out if we should use IPv4 or v6 address */
231 if (iprec.error != 0) {
234 } else if (server->connrec->family == AF_INET) {
235 /* force IPv4 connection */
236 ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
237 } else if (server->connrec->family == AF_INET6) {
238 /* force IPv6 connection */
239 ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
241 /* pick the one that was found, or if both do it like
242 /SET resolve_prefer_ipv6 says. */
243 ip = iprec.ip4.family == 0 ||
244 (iprec.ip6.family != 0 &&
245 settings_get_bool("resolve_prefer_ipv6")) ?
246 &iprec.ip6 : &iprec.ip4;
251 server_real_connect(server, ip, NULL);
254 if (iprec.error == 0 || net_hosterror_notfound(iprec.error)) {
255 /* IP wasn't found for the host, don't try to
256 reconnect back to this server */
257 server->dns_error = TRUE;
260 if (iprec.error == 0) {
261 /* forced IPv4 or IPv6 address but it wasn't found */
262 errormsg = server->connrec->family == AF_INET ?
263 "IPv4 address not found for host" :
264 "IPv6 address not found for host";
266 /* gethostbyname() failed */
267 errormsg = iprec.errorstr != NULL ? iprec.errorstr :
268 "Host lookup failed";
271 server->connection_lost = TRUE;
272 server_connect_failed(server, errormsg);
275 g_free(iprec.errorstr);
278 SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
280 CHAT_PROTOCOL_REC *proto;
283 proto = CHAT_PROTOCOL(conn);
284 server = proto->server_init_connect(conn);
285 proto->server_connect(server);
290 /* initializes server record but doesn't start connecting */
291 void server_connect_init(SERVER_REC *server)
295 g_return_if_fail(server != NULL);
297 MODULE_DATA_INIT(server);
298 server->type = module_get_uniq_id("SERVER", 0);
301 server->nick = g_strdup(server->connrec->nick);
302 if (server->connrec->username == NULL || *server->connrec->username == '\0') {
303 g_free_not_null(server->connrec->username);
305 str = g_get_user_name();
306 if (*str == '\0') str = "unknown";
307 server->connrec->username = g_strdup(str);
309 if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
310 g_free_not_null(server->connrec->realname);
312 str = g_get_real_name();
313 if (*str == '\0') str = server->connrec->username;
314 server->connrec->realname = g_strdup(str);
317 server->tag = server_create_tag(server->connrec);
318 server->connect_tag = -1;
321 /* starts connecting to server */
322 int server_start_connect(SERVER_REC *server)
324 const char *connect_address;
327 g_return_val_if_fail(server != NULL, FALSE);
328 if (!server->connrec->unix_socket && server->connrec->port <= 0)
331 server->rawlog = rawlog_create();
333 if (server->connrec->connect_handle != NULL) {
334 /* already connected */
335 GIOChannel *handle = server->connrec->connect_handle;
337 server->connrec->connect_handle = NULL;
338 server->handle = net_sendbuffer_create(handle, 0);
339 server_connect_finished(server);
340 } else if (server->connrec->unix_socket) {
341 /* connect with unix socket */
342 server_real_connect(server, NULL, server->connrec->address);
344 /* resolve host name */
346 g_warning("server_connect(): pipe() failed.");
348 g_free(server->nick);
352 server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
353 server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
355 connect_address = server->connrec->proxy != NULL ?
356 server->connrec->proxy : server->connrec->address;
357 server->connect_pid =
358 net_gethostbyname_nonblock(connect_address,
359 server->connect_pipe[1]);
360 server->connect_tag =
361 g_input_add(server->connect_pipe[0], G_INPUT_READ,
363 server_connect_callback_readpipe,
366 lookup_servers = g_slist_append(lookup_servers, server);
368 signal_emit("server looking", 1, server);
373 static int server_remove_channels(SERVER_REC *server)
378 g_return_val_if_fail(server != NULL, FALSE);
381 for (tmp = server->channels; tmp != NULL; tmp = next) {
382 CHANNEL_REC *channel = tmp->data;
385 channel_destroy(channel);
389 while (server->queries != NULL)
390 query_change_server(server->queries->data, NULL);
392 g_slist_free(server->channels);
393 g_slist_free(server->queries);
398 void server_disconnect(SERVER_REC *server)
402 g_return_if_fail(IS_SERVER(server));
404 if (server->disconnected)
407 if (server->connect_tag != -1) {
408 /* still connecting to server.. */
409 if (server->connect_pid != -1)
410 net_disconnect_nonblock(server->connect_pid);
411 server_connect_failed(server, NULL);
415 servers = g_slist_remove(servers, server);
417 server->disconnected = TRUE;
418 signal_emit("server disconnected", 1, server);
420 /* close all channels */
421 chans = server_remove_channels(server);
423 if (server->handle != NULL) {
424 if (!chans || server->connection_lost)
425 net_sendbuffer_destroy(server->handle, TRUE);
427 /* we were on some channels, try to let the server
428 disconnect so that our quit message is guaranteed
430 net_disconnect_later(net_sendbuffer_handle(server->handle));
431 net_sendbuffer_destroy(server->handle, FALSE);
433 server->handle = NULL;
436 if (server->readtag > 0) {
437 g_source_remove(server->readtag);
438 server->readtag = -1;
441 server_unref(server);
444 void server_ref(SERVER_REC *server)
446 g_return_if_fail(IS_SERVER(server));
451 int server_unref(SERVER_REC *server)
453 g_return_val_if_fail(IS_SERVER(server), FALSE);
455 if (--server->refcount > 0)
458 if (g_slist_find(servers, server) != NULL) {
459 g_warning("Non-referenced server wasn't disconnected");
460 server_disconnect(server);
464 MODULE_DATA_DEINIT(server);
465 server_connect_unref(server->connrec);
466 if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
467 if (server->buffer != NULL) line_split_free(server->buffer);
468 g_free(server->version);
469 g_free(server->away_reason);
470 g_free(server->nick);
478 SERVER_REC *server_find_tag(const char *tag)
482 g_return_val_if_fail(tag != NULL, NULL);
483 if (*tag == '\0') return NULL;
485 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
486 SERVER_REC *server = tmp->data;
488 if (g_strcasecmp(server->tag, tag) == 0)
495 SERVER_REC *server_find_lookup_tag(const char *tag)
499 g_return_val_if_fail(tag != NULL, NULL);
500 if (*tag == '\0') return NULL;
502 for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
503 SERVER_REC *server = tmp->data;
505 if (g_strcasecmp(server->tag, tag) == 0)
512 SERVER_REC *server_find_chatnet(const char *chatnet)
516 g_return_val_if_fail(chatnet != NULL, NULL);
517 if (*chatnet == '\0') return NULL;
519 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
520 SERVER_REC *server = tmp->data;
522 if (server->connrec->chatnet != NULL &&
523 g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
530 void server_connect_ref(SERVER_CONNECT_REC *conn)
535 void server_connect_unref(SERVER_CONNECT_REC *conn)
537 g_return_if_fail(IS_SERVER_CONNECT(conn));
539 if (--conn->refcount > 0)
541 if (conn->refcount < 0) {
542 g_warning("Connection '%s' refcount = %d",
543 conn->tag, conn->refcount);
546 CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
548 if (conn->connect_handle != NULL)
549 net_disconnect(conn->connect_handle);
551 g_free_not_null(conn->proxy);
552 g_free_not_null(conn->proxy_string);
553 g_free_not_null(conn->proxy_string_after);
554 g_free_not_null(conn->proxy_password);
556 g_free_not_null(conn->tag);
557 g_free_not_null(conn->address);
558 g_free_not_null(conn->chatnet);
560 g_free_not_null(conn->own_ip4);
561 g_free_not_null(conn->own_ip6);
563 g_free_not_null(conn->password);
564 g_free_not_null(conn->nick);
565 g_free_not_null(conn->username);
566 g_free_not_null(conn->realname);
568 g_free_not_null(conn->channels);
569 g_free_not_null(conn->away_reason);
575 void server_change_nick(SERVER_REC *server, const char *nick)
577 g_free(server->nick);
578 server->nick = g_strdup(nick);
580 signal_emit("server nick changed", 1, server);
583 /* Update own IPv4 and IPv6 records */
584 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
585 IPADDR *ip4, IPADDR *ip6)
587 if (ip4 == NULL || ip4->family == 0)
588 g_free_and_null(conn->own_ip4);
589 if (ip6 == NULL || ip6->family == 0)
590 g_free_and_null(conn->own_ip6);
592 if (ip4 != NULL && ip4->family != 0) {
593 /* IPv4 address was found */
594 if (conn->own_ip4 == NULL)
595 conn->own_ip4 = g_new0(IPADDR, 1);
596 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
599 if (ip6 != NULL && ip6->family != 0) {
600 /* IPv6 address was found */
601 if (conn->own_ip6 == NULL)
602 conn->own_ip6 = g_new0(IPADDR, 1);
603 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
607 /* `optlist' should contain only one unknown key - the server tag.
608 returns NULL if there was unknown -option */
609 SERVER_REC *cmd_options_get_server(const char *cmd,
611 SERVER_REC *defserver)
614 GSList *list, *tmp, *next;
616 /* get all the options, then remove the known ones. there should
617 be only one left - the server tag. */
618 list = hashtable_get_keys(optlist);
620 for (tmp = list; tmp != NULL; tmp = next) {
621 char *option = tmp->data;
624 if (command_have_option(cmd, option))
625 list = g_slist_remove(list, option);
632 server = server_find_tag(list->data);
633 if (server == NULL || list->next != NULL) {
634 /* unknown option (not server tag) */
635 signal_emit("error command", 2,
636 GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
637 server == NULL ? list->data : list->next->data);
647 static void disconnect_servers(GSList *servers, int chat_type)
651 for (tmp = servers; tmp != NULL; tmp = next) {
652 SERVER_REC *rec = tmp->data;
655 if (rec->chat_type == chat_type)
656 server_disconnect(rec);
660 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
662 disconnect_servers(servers, proto->id);
663 disconnect_servers(lookup_servers, proto->id);
666 void servers_init(void)
668 settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
669 lookup_servers = servers = NULL;
671 signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
673 servers_reconnect_init();
674 servers_setup_init();
677 void servers_deinit(void)
679 signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
681 servers_setup_deinit();
682 servers_reconnect_deinit();
684 module_uniq_destroy("SERVER");
685 module_uniq_destroy("SERVER CONNECT");