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)
260 g_return_if_fail(server != NULL);
262 MODULE_DATA_INIT(server);
263 server->type = module_get_uniq_id("SERVER", 0);
266 server->nick = g_strdup(server->connrec->nick);
267 if (server->connrec->username == NULL || *server->connrec->username == '\0') {
268 g_free_not_null(server->connrec->username);
270 server->connrec->username = g_get_user_name();
271 if (*server->connrec->username == '\0') server->connrec->username = "-";
272 server->connrec->username = g_strdup(server->connrec->username);
274 if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
275 g_free_not_null(server->connrec->realname);
277 server->connrec->realname = g_get_real_name();
278 if (*server->connrec->realname == '\0') server->connrec->realname = "-";
279 server->connrec->realname = g_strdup(server->connrec->realname);
282 server->tag = server_create_tag(server->connrec);
285 /* starts connecting to server */
286 int server_start_connect(SERVER_REC *server)
288 const char *connect_address;
291 g_return_val_if_fail(server != NULL, FALSE);
292 if (server->connrec->port <= 0) return FALSE;
294 server_connect_init(server);
297 g_warning("server_connect(): pipe() failed.");
299 g_free(server->nick);
303 server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
304 server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
306 connect_address = server->connrec->proxy != NULL ?
307 server->connrec->proxy : server->connrec->address;
308 server->connect_pid =
309 net_gethostbyname_nonblock(connect_address,
310 server->connect_pipe[1]);
311 server->connect_tag =
312 g_input_add(server->connect_pipe[0], G_INPUT_READ,
313 (GInputFunction) server_connect_callback_readpipe,
315 server->rawlog = rawlog_create();
317 lookup_servers = g_slist_append(lookup_servers, server);
319 signal_emit("server looking", 1, server);
323 static int server_remove_channels(SERVER_REC *server)
328 g_return_val_if_fail(server != NULL, FALSE);
331 for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
332 CHANNEL_REC *channel = tmp->data;
334 channel->server = NULL;
335 channel_destroy(channel);
339 while (server->queries != NULL)
340 query_change_server(server->queries->data, NULL);
342 g_slist_free(server->channels);
343 g_slist_free(server->queries);
348 void server_disconnect(SERVER_REC *server)
352 g_return_if_fail(IS_SERVER(server));
354 if (server->disconnected)
357 if (server->connect_tag != -1) {
358 /* still connecting to server.. */
359 if (server->connect_pid != -1)
360 net_disconnect_nonblock(server->connect_pid);
361 server_connect_failed(server, NULL);
365 servers = g_slist_remove(servers, server);
367 server->disconnected = TRUE;
368 signal_emit("server disconnected", 1, server);
370 /* close all channels */
371 chans = server_remove_channels(server);
373 if (server->handle != NULL) {
374 if (!chans || server->connection_lost)
375 net_sendbuffer_destroy(server->handle, TRUE);
377 /* we were on some channels, try to let the server
378 disconnect so that our quit message is guaranteed
380 net_disconnect_later(net_sendbuffer_handle(server->handle));
381 net_sendbuffer_destroy(server->handle, FALSE);
383 server->handle = NULL;
386 if (server->readtag > 0) {
387 g_source_remove(server->readtag);
388 server->readtag = -1;
391 server_unref(server);
394 void server_ref(SERVER_REC *server)
396 g_return_if_fail(IS_SERVER(server));
401 int server_unref(SERVER_REC *server)
403 g_return_val_if_fail(IS_SERVER(server), FALSE);
405 if (--server->refcount > 0)
408 if (g_slist_find(servers, server) != NULL) {
409 g_warning("Non-referenced server wasn't disconnected");
410 server_disconnect(server);
414 MODULE_DATA_DEINIT(server);
415 server_connect_unref(server->connrec);
416 if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
417 if (server->buffer != NULL) line_split_free(server->buffer);
418 g_free(server->version);
419 g_free(server->away_reason);
420 g_free(server->nick);
428 SERVER_REC *server_find_tag(const char *tag)
432 g_return_val_if_fail(tag != NULL, NULL);
433 if (*tag == '\0') return NULL;
435 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
436 SERVER_REC *server = tmp->data;
438 if (g_strcasecmp(server->tag, tag) == 0)
442 for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
443 SERVER_REC *server = tmp->data;
445 if (g_strcasecmp(server->tag, tag) == 0)
452 SERVER_REC *server_find_chatnet(const char *chatnet)
456 g_return_val_if_fail(chatnet != NULL, NULL);
457 if (*chatnet == '\0') return NULL;
459 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
460 SERVER_REC *server = tmp->data;
462 if (server->connrec->chatnet != NULL &&
463 g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
470 void server_connect_ref(SERVER_CONNECT_REC *conn)
475 void server_connect_unref(SERVER_CONNECT_REC *conn)
477 g_return_if_fail(IS_SERVER_CONNECT(conn));
479 if (--conn->refcount > 0)
481 if (conn->refcount < 0) {
482 g_warning("Connection '%s' refcount = %d",
483 conn->tag, conn->refcount);
486 CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
488 g_free_not_null(conn->proxy);
489 g_free_not_null(conn->proxy_string);
490 g_free_not_null(conn->proxy_string_after);
491 g_free_not_null(conn->proxy_password);
493 g_free_not_null(conn->tag);
494 g_free_not_null(conn->address);
495 g_free_not_null(conn->chatnet);
497 g_free_not_null(conn->own_ip4);
498 g_free_not_null(conn->own_ip6);
500 g_free_not_null(conn->password);
501 g_free_not_null(conn->nick);
502 g_free_not_null(conn->username);
503 g_free_not_null(conn->realname);
505 g_free_not_null(conn->channels);
506 g_free_not_null(conn->away_reason);
512 void server_change_nick(SERVER_REC *server, const char *nick)
514 g_free(server->nick);
515 server->nick = g_strdup(nick);
517 signal_emit("server nick changed", 1, server);
520 /* Update own IPv4 and IPv6 records */
521 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
522 IPADDR *ip4, IPADDR *ip6)
524 if (ip4 == NULL || ip4->family == 0)
525 g_free_and_null(conn->own_ip4);
526 if (ip6 == NULL || ip6->family == 0)
527 g_free_and_null(conn->own_ip6);
529 if (ip4 != NULL && ip4->family != 0) {
530 /* IPv4 address was found */
531 if (conn->own_ip4 == NULL)
532 conn->own_ip4 = g_new0(IPADDR, 1);
533 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
536 if (ip6 != NULL && ip6->family != 0) {
537 /* IPv6 address was found */
538 if (conn->own_ip6 == NULL)
539 conn->own_ip6 = g_new0(IPADDR, 1);
540 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
544 /* `optlist' should contain only one unknown key - the server tag.
545 returns NULL if there was unknown -option */
546 SERVER_REC *cmd_options_get_server(const char *cmd,
548 SERVER_REC *defserver)
551 GSList *list, *tmp, *next;
553 /* get all the options, then remove the known ones. there should
554 be only one left - the server tag. */
555 list = hashtable_get_keys(optlist);
557 for (tmp = list; tmp != NULL; tmp = next) {
558 char *option = tmp->data;
561 if (command_have_option(cmd, option))
562 list = g_slist_remove(list, option);
569 server = server_find_tag(list->data);
570 if (server == NULL || list->next != NULL) {
571 /* unknown option (not server tag) */
572 signal_emit("error command", 2,
573 GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
574 server == NULL ? list->data : list->next->data);
584 static void disconnect_servers(GSList *servers, int chat_type)
588 for (tmp = servers; tmp != NULL; tmp = next) {
589 SERVER_REC *rec = tmp->data;
592 if (rec->chat_type == chat_type)
593 server_disconnect(rec);
597 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
599 disconnect_servers(servers, proto->id);
600 disconnect_servers(lookup_servers, proto->id);
603 void servers_init(void)
605 settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
606 lookup_servers = servers = NULL;
608 signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
610 servers_reconnect_init();
611 servers_setup_init();
614 void servers_deinit(void)
616 signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
618 servers_setup_deinit();
619 servers_reconnect_deinit();
621 module_uniq_destroy("SERVER");
622 module_uniq_destroy("SERVER CONNECT");