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.ip4.family == 0 ||
202 (iprec.ip6.family != 0 &&
203 settings_get_bool("resolve_prefer_ipv6")) ?
204 &iprec.ip6 : &iprec.ip4;
207 conn = server->connrec;
208 port = conn->proxy != NULL ? conn->proxy_port : conn->port;
209 own_ip = ip == NULL ? NULL :
210 (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
214 signal_emit("server connecting", 2, server, ip);
215 if (server->handle == NULL)
216 handle = net_connect_ip(ip, port, own_ip);
218 handle = net_sendbuffer_handle(server->handle);
221 if (handle == NULL) {
223 if (ip == NULL && (iprec.error == 0 ||
224 net_hosterror_notfound(iprec.error))) {
225 /* IP wasn't found for the host, don't try to reconnect
226 back to this server */
227 server->dns_error = TRUE;
231 /* connect() failed */
232 errormsg = g_strerror(errno);
233 } else if (iprec.error == 0) {
234 /* forced IPv4 or IPv6 address but it wasn't found */
235 errormsg = server->connrec->family == AF_INET ?
236 "IPv4 address not found for host" :
237 "IPv6 address not found for host";
239 /* gethostbyname() failed */
240 errormsg = iprec.errorstr != NULL ? iprec.errorstr :
241 "Host lookup failed";
243 server->connection_lost = TRUE;
244 server_connect_failed(server, errormsg);
245 g_free_not_null(iprec.errorstr);
249 if (server->handle == NULL)
250 server->handle = net_sendbuffer_create(handle, 0);
251 server->connect_tag =
252 g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
253 (GInputFunction) server_connect_callback_init,
257 /* initializes server record but doesn't start connecting */
258 void server_connect_init(SERVER_REC *server)
262 g_return_if_fail(server != NULL);
264 MODULE_DATA_INIT(server);
265 server->type = module_get_uniq_id("SERVER", 0);
268 server->nick = g_strdup(server->connrec->nick);
269 if (server->connrec->username == NULL || *server->connrec->username == '\0') {
270 g_free_not_null(server->connrec->username);
272 str = g_get_user_name();
273 if (*str == '\0') str = "-";
274 server->connrec->username = g_strdup(str);
276 if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
277 g_free_not_null(server->connrec->realname);
279 str = g_get_real_name();
280 if (*str == '\0') str = "-";
281 server->connrec->realname = g_strdup(str);
284 server->tag = server_create_tag(server->connrec);
287 /* starts connecting to server */
288 int server_start_connect(SERVER_REC *server)
290 const char *connect_address;
293 g_return_val_if_fail(server != NULL, FALSE);
294 if (server->connrec->port <= 0) return FALSE;
296 server_connect_init(server);
299 g_warning("server_connect(): pipe() failed.");
301 g_free(server->nick);
305 server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
306 server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
308 connect_address = server->connrec->proxy != NULL ?
309 server->connrec->proxy : server->connrec->address;
310 server->connect_pid =
311 net_gethostbyname_nonblock(connect_address,
312 server->connect_pipe[1]);
313 server->connect_tag =
314 g_input_add(server->connect_pipe[0], G_INPUT_READ,
315 (GInputFunction) server_connect_callback_readpipe,
317 server->rawlog = rawlog_create();
319 lookup_servers = g_slist_append(lookup_servers, server);
321 signal_emit("server looking", 1, server);
325 static int server_remove_channels(SERVER_REC *server)
330 g_return_val_if_fail(server != NULL, FALSE);
333 for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
334 CHANNEL_REC *channel = tmp->data;
336 channel_destroy(channel);
340 while (server->queries != NULL)
341 query_change_server(server->queries->data, NULL);
343 g_slist_free(server->channels);
344 g_slist_free(server->queries);
349 void server_disconnect(SERVER_REC *server)
353 g_return_if_fail(IS_SERVER(server));
355 if (server->disconnected)
358 if (server->connect_tag != -1) {
359 /* still connecting to server.. */
360 if (server->connect_pid != -1)
361 net_disconnect_nonblock(server->connect_pid);
362 server_connect_failed(server, NULL);
366 servers = g_slist_remove(servers, server);
368 server->disconnected = TRUE;
369 signal_emit("server disconnected", 1, server);
371 /* close all channels */
372 chans = server_remove_channels(server);
374 if (server->handle != NULL) {
375 if (!chans || server->connection_lost)
376 net_sendbuffer_destroy(server->handle, TRUE);
378 /* we were on some channels, try to let the server
379 disconnect so that our quit message is guaranteed
381 net_disconnect_later(net_sendbuffer_handle(server->handle));
382 net_sendbuffer_destroy(server->handle, FALSE);
384 server->handle = NULL;
387 if (server->readtag > 0) {
388 g_source_remove(server->readtag);
389 server->readtag = -1;
392 server_unref(server);
395 void server_ref(SERVER_REC *server)
397 g_return_if_fail(IS_SERVER(server));
402 int server_unref(SERVER_REC *server)
404 g_return_val_if_fail(IS_SERVER(server), FALSE);
406 if (--server->refcount > 0)
409 if (g_slist_find(servers, server) != NULL) {
410 g_warning("Non-referenced server wasn't disconnected");
411 server_disconnect(server);
415 MODULE_DATA_DEINIT(server);
416 server_connect_unref(server->connrec);
417 if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
418 if (server->buffer != NULL) line_split_free(server->buffer);
419 g_free(server->version);
420 g_free(server->away_reason);
421 g_free(server->nick);
429 SERVER_REC *server_find_tag(const char *tag)
433 g_return_val_if_fail(tag != NULL, NULL);
434 if (*tag == '\0') return NULL;
436 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
437 SERVER_REC *server = tmp->data;
439 if (g_strcasecmp(server->tag, tag) == 0)
443 for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
444 SERVER_REC *server = tmp->data;
446 if (g_strcasecmp(server->tag, tag) == 0)
453 SERVER_REC *server_find_chatnet(const char *chatnet)
457 g_return_val_if_fail(chatnet != NULL, NULL);
458 if (*chatnet == '\0') return NULL;
460 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
461 SERVER_REC *server = tmp->data;
463 if (server->connrec->chatnet != NULL &&
464 g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
471 void server_connect_ref(SERVER_CONNECT_REC *conn)
476 void server_connect_unref(SERVER_CONNECT_REC *conn)
478 g_return_if_fail(IS_SERVER_CONNECT(conn));
480 if (--conn->refcount > 0)
482 if (conn->refcount < 0) {
483 g_warning("Connection '%s' refcount = %d",
484 conn->tag, conn->refcount);
487 CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
489 g_free_not_null(conn->proxy);
490 g_free_not_null(conn->proxy_string);
491 g_free_not_null(conn->proxy_string_after);
492 g_free_not_null(conn->proxy_password);
494 g_free_not_null(conn->tag);
495 g_free_not_null(conn->address);
496 g_free_not_null(conn->chatnet);
498 g_free_not_null(conn->own_ip4);
499 g_free_not_null(conn->own_ip6);
501 g_free_not_null(conn->password);
502 g_free_not_null(conn->nick);
503 g_free_not_null(conn->username);
504 g_free_not_null(conn->realname);
506 g_free_not_null(conn->channels);
507 g_free_not_null(conn->away_reason);
513 void server_change_nick(SERVER_REC *server, const char *nick)
515 g_free(server->nick);
516 server->nick = g_strdup(nick);
518 signal_emit("server nick changed", 1, server);
521 /* Update own IPv4 and IPv6 records */
522 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
523 IPADDR *ip4, IPADDR *ip6)
525 if (ip4 == NULL || ip4->family == 0)
526 g_free_and_null(conn->own_ip4);
527 if (ip6 == NULL || ip6->family == 0)
528 g_free_and_null(conn->own_ip6);
530 if (ip4 != NULL && ip4->family != 0) {
531 /* IPv4 address was found */
532 if (conn->own_ip4 == NULL)
533 conn->own_ip4 = g_new0(IPADDR, 1);
534 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
537 if (ip6 != NULL && ip6->family != 0) {
538 /* IPv6 address was found */
539 if (conn->own_ip6 == NULL)
540 conn->own_ip6 = g_new0(IPADDR, 1);
541 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
545 /* `optlist' should contain only one unknown key - the server tag.
546 returns NULL if there was unknown -option */
547 SERVER_REC *cmd_options_get_server(const char *cmd,
549 SERVER_REC *defserver)
552 GSList *list, *tmp, *next;
554 /* get all the options, then remove the known ones. there should
555 be only one left - the server tag. */
556 list = hashtable_get_keys(optlist);
558 for (tmp = list; tmp != NULL; tmp = next) {
559 char *option = tmp->data;
562 if (command_have_option(cmd, option))
563 list = g_slist_remove(list, option);
570 server = server_find_tag(list->data);
571 if (server == NULL || list->next != NULL) {
572 /* unknown option (not server tag) */
573 signal_emit("error command", 2,
574 GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
575 server == NULL ? list->data : list->next->data);
585 static void disconnect_servers(GSList *servers, int chat_type)
589 for (tmp = servers; tmp != NULL; tmp = next) {
590 SERVER_REC *rec = tmp->data;
593 if (rec->chat_type == chat_type)
594 server_disconnect(rec);
598 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
600 disconnect_servers(servers, proto->id);
601 disconnect_servers(lookup_servers, proto->id);
604 void servers_init(void)
606 settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
607 lookup_servers = servers = NULL;
609 signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
611 servers_reconnect_init();
612 servers_setup_init();
615 void servers_deinit(void)
617 signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
619 servers_setup_deinit();
620 servers_reconnect_deinit();
622 module_uniq_destroy("SERVER");
623 module_uniq_destroy("SERVER CONNECT");