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 strncmp(conn->tag, tag, strlen(tag)) == 0) {
114 /* use the existing tag if it begins with the same ID -
115 this is useful when you have several connections to
116 same server and you want to keep the same tags with
117 the servers (or it would cause problems when rejoining
118 /LAYOUT SAVEd channels). */
120 return g_strdup(conn->tag);
124 /* then just append numbers after tag until unused is found.. */
125 str = g_string_new(tag);
126 for (num = 2; server_find_tag(str->str) != NULL; num++)
127 g_string_sprintf(str, "%s%d", tag, num);
131 g_string_free(str, FALSE);
135 /* Connection to server finished, fill the rest of the fields */
136 void server_connect_finished(SERVER_REC *server)
138 server->connect_time = time(NULL);
140 servers = g_slist_append(servers, server);
141 signal_emit("server connected", 1, server);
144 static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
148 g_return_if_fail(IS_SERVER(server));
150 error = net_geterror(handle);
152 server->connection_lost = TRUE;
153 server_connect_failed(server, g_strerror(error));
157 lookup_servers = g_slist_remove(lookup_servers, server);
158 g_source_remove(server->connect_tag);
159 server->connect_tag = -1;
161 server_connect_finished(server);
164 static void server_connect_callback_readpipe(SERVER_REC *server)
166 SERVER_CONNECT_REC *conn;
167 RESOLVED_IP_REC iprec;
170 const char *errormsg;
173 g_return_if_fail(IS_SERVER(server));
175 g_source_remove(server->connect_tag);
176 server->connect_tag = -1;
178 net_gethostbyname_return(server->connect_pipe[0], &iprec);
180 g_io_channel_close(server->connect_pipe[0]);
181 g_io_channel_unref(server->connect_pipe[0]);
182 g_io_channel_close(server->connect_pipe[1]);
183 g_io_channel_unref(server->connect_pipe[1]);
185 server->connect_pipe[0] = NULL;
186 server->connect_pipe[1] = NULL;
188 /* figure out if we should use IPv4 or v6 address */
189 if (iprec.error != 0) {
192 } else if (server->connrec->family == AF_INET) {
193 /* force IPv4 connection */
194 ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
195 } else if (server->connrec->family == AF_INET6) {
196 /* force IPv6 connection */
197 ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
199 /* pick the one that was found, or if both do it like
200 /SET resolve_prefer_ipv6 says. */
201 ip = iprec.ip6.family != 0 &&
202 settings_get_bool("resolve_prefer_ipv6") ?
203 &iprec.ip6 : &iprec.ip4;
206 conn = server->connrec;
207 port = conn->proxy != NULL ? conn->proxy_port : conn->port;
208 own_ip = ip == NULL ? NULL :
209 (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
213 signal_emit("server connecting", 2, server, ip);
214 if (server->handle == NULL)
215 handle = net_connect_ip(ip, port, own_ip);
217 handle = net_sendbuffer_handle(server->handle);
220 if (handle == NULL) {
222 if (ip == NULL && (iprec.error == 0 ||
223 net_hosterror_notfound(iprec.error))) {
224 /* IP wasn't found for the host, don't try to reconnect
225 back to this server */
226 server->dns_error = TRUE;
230 /* connect() failed */
231 errormsg = g_strerror(errno);
232 } else if (iprec.error == 0) {
233 /* forced IPv4 or IPv6 address but it wasn't found */
234 errormsg = server->connrec->family == AF_INET ?
235 "IPv4 address not found for host" :
236 "IPv6 address not found for host";
238 /* gethostbyname() failed */
239 errormsg = iprec.errorstr != NULL ? iprec.errorstr :
240 "Host lookup failed";
242 server->connection_lost = TRUE;
243 server_connect_failed(server, errormsg);
244 g_free_not_null(iprec.errorstr);
248 if (server->handle == NULL)
249 server->handle = net_sendbuffer_create(handle, 0);
250 server->connect_tag =
251 g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
252 (GInputFunction) server_connect_callback_init,
256 /* initializes server record but doesn't start connecting */
257 void server_connect_init(SERVER_REC *server)
259 g_return_if_fail(server != NULL);
261 MODULE_DATA_INIT(server);
262 server->type = module_get_uniq_id("SERVER", 0);
265 server->nick = g_strdup(server->connrec->nick);
266 if (server->connrec->username == NULL || *server->connrec->username == '\0') {
267 g_free_not_null(server->connrec->username);
269 server->connrec->username = g_get_user_name();
270 if (*server->connrec->username == '\0') server->connrec->username = "-";
271 server->connrec->username = g_strdup(server->connrec->username);
273 if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
274 g_free_not_null(server->connrec->realname);
276 server->connrec->realname = g_get_real_name();
277 if (*server->connrec->realname == '\0') server->connrec->realname = "-";
278 server->connrec->realname = g_strdup(server->connrec->realname);
281 server->tag = server_create_tag(server->connrec);
284 /* starts connecting to server */
285 int server_start_connect(SERVER_REC *server)
287 const char *connect_address;
290 g_return_val_if_fail(server != NULL, FALSE);
291 if (server->connrec->port <= 0) return FALSE;
293 server_connect_init(server);
296 g_warning("server_connect(): pipe() failed.");
298 g_free(server->nick);
302 server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
303 server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
305 connect_address = server->connrec->proxy != NULL ?
306 server->connrec->proxy : server->connrec->address;
307 server->connect_pid =
308 net_gethostbyname_nonblock(connect_address,
309 server->connect_pipe[1]);
310 server->connect_tag =
311 g_input_add(server->connect_pipe[0], G_INPUT_READ,
312 (GInputFunction) server_connect_callback_readpipe,
314 server->rawlog = rawlog_create();
316 lookup_servers = g_slist_append(lookup_servers, server);
318 signal_emit("server looking", 1, server);
322 static int server_remove_channels(SERVER_REC *server)
327 g_return_val_if_fail(server != NULL, FALSE);
330 for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
331 CHANNEL_REC *channel = tmp->data;
333 channel->server = NULL;
334 channel_destroy(channel);
338 while (server->queries != NULL)
339 query_change_server(server->queries->data, NULL);
341 g_slist_free(server->channels);
342 g_slist_free(server->queries);
347 void server_disconnect(SERVER_REC *server)
351 g_return_if_fail(IS_SERVER(server));
353 if (server->disconnected)
356 if (server->connect_tag != -1) {
357 /* still connecting to server.. */
358 if (server->connect_pid != -1)
359 net_disconnect_nonblock(server->connect_pid);
360 server_connect_failed(server, NULL);
364 servers = g_slist_remove(servers, server);
366 server->disconnected = TRUE;
367 signal_emit("server disconnected", 1, server);
369 /* close all channels */
370 chans = server_remove_channels(server);
372 if (server->handle != NULL) {
373 if (!chans || server->connection_lost)
374 net_sendbuffer_destroy(server->handle, TRUE);
376 /* we were on some channels, try to let the server
377 disconnect so that our quit message is guaranteed
379 net_disconnect_later(net_sendbuffer_handle(server->handle));
380 net_sendbuffer_destroy(server->handle, FALSE);
382 server->handle = NULL;
385 if (server->readtag > 0) {
386 g_source_remove(server->readtag);
387 server->readtag = -1;
390 server_unref(server);
393 void server_ref(SERVER_REC *server)
395 g_return_if_fail(IS_SERVER(server));
400 int server_unref(SERVER_REC *server)
402 g_return_val_if_fail(IS_SERVER(server), FALSE);
404 if (--server->refcount > 0)
407 if (g_slist_find(servers, server) != NULL) {
408 g_warning("Non-referenced server wasn't disconnected");
409 server_disconnect(server);
413 MODULE_DATA_DEINIT(server);
414 server_connect_unref(server->connrec);
415 if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
416 if (server->buffer != NULL) line_split_free(server->buffer);
417 g_free(server->version);
418 g_free(server->away_reason);
419 g_free(server->nick);
427 SERVER_REC *server_find_tag(const char *tag)
431 g_return_val_if_fail(tag != NULL, NULL);
432 if (*tag == '\0') return NULL;
434 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
435 SERVER_REC *server = tmp->data;
437 if (g_strcasecmp(server->tag, tag) == 0)
441 for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
442 SERVER_REC *server = tmp->data;
444 if (g_strcasecmp(server->tag, tag) == 0)
451 SERVER_REC *server_find_chatnet(const char *chatnet)
455 g_return_val_if_fail(chatnet != NULL, NULL);
456 if (*chatnet == '\0') return NULL;
458 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
459 SERVER_REC *server = tmp->data;
461 if (server->connrec->chatnet != NULL &&
462 g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
469 void server_connect_ref(SERVER_CONNECT_REC *conn)
474 void server_connect_unref(SERVER_CONNECT_REC *conn)
476 g_return_if_fail(IS_SERVER_CONNECT(conn));
478 if (--conn->refcount > 0)
480 if (conn->refcount < 0) {
481 g_warning("Connection '%s' refcount = %d",
482 conn->tag, conn->refcount);
485 CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
487 g_free_not_null(conn->proxy);
488 g_free_not_null(conn->proxy_string);
489 g_free_not_null(conn->proxy_string_after);
490 g_free_not_null(conn->proxy_password);
492 g_free_not_null(conn->tag);
493 g_free_not_null(conn->address);
494 g_free_not_null(conn->chatnet);
496 g_free_not_null(conn->own_ip4);
497 g_free_not_null(conn->own_ip6);
499 g_free_not_null(conn->password);
500 g_free_not_null(conn->nick);
501 g_free_not_null(conn->username);
502 g_free_not_null(conn->realname);
504 g_free_not_null(conn->channels);
505 g_free_not_null(conn->away_reason);
511 void server_change_nick(SERVER_REC *server, const char *nick)
513 g_free(server->nick);
514 server->nick = g_strdup(nick);
516 signal_emit("server nick changed", 1, server);
519 /* Update own IPv4 and IPv6 records */
520 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
521 IPADDR *ip4, IPADDR *ip6)
523 if (ip4 == NULL || ip4->family == 0)
524 g_free_and_null(conn->own_ip4);
525 if (ip6 == NULL || ip6->family == 0)
526 g_free_and_null(conn->own_ip6);
528 if (ip4 != NULL && ip4->family != 0) {
529 /* IPv4 address was found */
530 if (conn->own_ip4 == NULL)
531 conn->own_ip4 = g_new0(IPADDR, 1);
532 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
535 if (ip6 != NULL && ip6->family != 0) {
536 /* IPv6 address was found */
537 if (conn->own_ip6 == NULL)
538 conn->own_ip6 = g_new0(IPADDR, 1);
539 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
543 /* `optlist' should contain only one unknown key - the server tag.
544 returns NULL if there was unknown -option */
545 SERVER_REC *cmd_options_get_server(const char *cmd,
547 SERVER_REC *defserver)
550 GSList *list, *tmp, *next;
552 /* get all the options, then remove the known ones. there should
553 be only one left - the server tag. */
554 list = hashtable_get_keys(optlist);
556 for (tmp = list; tmp != NULL; tmp = next) {
557 char *option = tmp->data;
560 if (command_have_option(cmd, option))
561 list = g_slist_remove(list, option);
568 server = server_find_tag(list->data);
569 if (server == NULL || list->next != NULL) {
570 /* unknown option (not server tag) */
571 signal_emit("error command", 2,
572 GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
573 server == NULL ? list->data : list->next->data);
583 static void disconnect_servers(GSList *servers, int chat_type)
587 for (tmp = servers; tmp != NULL; tmp = next) {
588 SERVER_REC *rec = tmp->data;
591 if (rec->chat_type == chat_type)
592 server_disconnect(rec);
596 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
598 disconnect_servers(servers, proto->id);
599 disconnect_servers(lookup_servers, proto->id);
602 void servers_init(void)
604 settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
605 lookup_servers = servers = NULL;
607 signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
609 servers_reconnect_init();
610 servers_setup_init();
613 void servers_deinit(void)
615 signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
617 servers_setup_deinit();
618 servers_reconnect_deinit();
620 module_uniq_destroy("SERVER");
621 module_uniq_destroy("SERVER CONNECT");