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 ip = iprec.error != 0 ? NULL : iprec.ip6.family == 0 ||
190 (server->connrec->family == AF_INET && iprec.ip4.family != 0) ?
191 &iprec.ip4 : &iprec.ip6;
192 if (iprec.ip4.family != 0 && server->connrec->family == 0 &&
193 !settings_get_bool("resolve_prefer_ipv6"))
196 conn = server->connrec;
197 port = conn->proxy != NULL ? conn->proxy_port : conn->port;
198 own_ip = ip == NULL ? NULL :
199 (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
203 signal_emit("server connecting", 2, server, ip);
204 if (server->handle == NULL)
205 handle = net_connect_ip(ip, port, own_ip);
207 handle = net_sendbuffer_handle(server->handle);
210 if (handle == NULL) {
212 if (iprec.error != 0 && net_hosterror_notfound(iprec.error)) {
213 /* IP wasn't found for the host, don't try to reconnect
214 back to this server */
215 server->dns_error = TRUE;
218 if (iprec.error == 0) {
219 /* connect() failed */
220 errormsg = g_strerror(errno);
222 /* gethostbyname() failed */
223 errormsg = iprec.errorstr != NULL ? iprec.errorstr :
224 "Host lookup failed";
226 server->connection_lost = TRUE;
227 server_connect_failed(server, errormsg);
228 g_free_not_null(iprec.errorstr);
232 if (server->handle == NULL)
233 server->handle = net_sendbuffer_create(handle, 0);
234 server->connect_tag =
235 g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
236 (GInputFunction) server_connect_callback_init,
240 /* initializes server record but doesn't start connecting */
241 void server_connect_init(SERVER_REC *server)
243 g_return_if_fail(server != NULL);
245 MODULE_DATA_INIT(server);
246 server->type = module_get_uniq_id("SERVER", 0);
249 server->nick = g_strdup(server->connrec->nick);
250 if (server->connrec->username == NULL || *server->connrec->username == '\0') {
251 g_free_not_null(server->connrec->username);
253 server->connrec->username = g_get_user_name();
254 if (*server->connrec->username == '\0') server->connrec->username = "-";
255 server->connrec->username = g_strdup(server->connrec->username);
257 if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
258 g_free_not_null(server->connrec->realname);
260 server->connrec->realname = g_get_real_name();
261 if (*server->connrec->realname == '\0') server->connrec->realname = "-";
262 server->connrec->realname = g_strdup(server->connrec->realname);
265 server->tag = server_create_tag(server->connrec);
268 /* starts connecting to server */
269 int server_start_connect(SERVER_REC *server)
271 const char *connect_address;
274 g_return_val_if_fail(server != NULL, FALSE);
275 if (server->connrec->port <= 0) return FALSE;
277 server_connect_init(server);
280 g_warning("server_connect(): pipe() failed.");
282 g_free(server->nick);
286 server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
287 server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
289 connect_address = server->connrec->proxy != NULL ?
290 server->connrec->proxy : server->connrec->address;
291 server->connect_pid =
292 net_gethostbyname_nonblock(connect_address,
293 server->connect_pipe[1]);
294 server->connect_tag =
295 g_input_add(server->connect_pipe[0], G_INPUT_READ,
296 (GInputFunction) server_connect_callback_readpipe,
298 server->rawlog = rawlog_create();
300 lookup_servers = g_slist_append(lookup_servers, server);
302 signal_emit("server looking", 1, server);
306 static int server_remove_channels(SERVER_REC *server)
311 g_return_val_if_fail(server != NULL, FALSE);
314 for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
315 CHANNEL_REC *channel = tmp->data;
317 channel->server = NULL;
318 channel_destroy(channel);
322 while (server->queries != NULL)
323 query_change_server(server->queries->data, NULL);
325 g_slist_free(server->channels);
326 g_slist_free(server->queries);
331 void server_disconnect(SERVER_REC *server)
335 g_return_if_fail(IS_SERVER(server));
337 if (server->disconnected)
340 if (server->connect_tag != -1) {
341 /* still connecting to server.. */
342 if (server->connect_pid != -1)
343 net_disconnect_nonblock(server->connect_pid);
344 server_connect_failed(server, NULL);
348 servers = g_slist_remove(servers, server);
350 server->disconnected = TRUE;
351 signal_emit("server disconnected", 1, server);
353 /* close all channels */
354 chans = server_remove_channels(server);
356 if (server->handle != NULL) {
357 if (!chans || server->connection_lost)
358 net_sendbuffer_destroy(server->handle, TRUE);
360 /* we were on some channels, try to let the server
361 disconnect so that our quit message is guaranteed
363 net_disconnect_later(net_sendbuffer_handle(server->handle));
364 net_sendbuffer_destroy(server->handle, FALSE);
366 server->handle = NULL;
369 if (server->readtag > 0) {
370 g_source_remove(server->readtag);
371 server->readtag = -1;
374 server_unref(server);
377 void server_ref(SERVER_REC *server)
379 g_return_if_fail(IS_SERVER(server));
384 int server_unref(SERVER_REC *server)
386 g_return_val_if_fail(IS_SERVER(server), FALSE);
388 if (--server->refcount > 0)
391 if (g_slist_find(servers, server) != NULL) {
392 g_warning("Non-referenced server wasn't disconnected");
393 server_disconnect(server);
397 MODULE_DATA_DEINIT(server);
398 server_connect_unref(server->connrec);
399 if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
400 if (server->buffer != NULL) line_split_free(server->buffer);
401 g_free(server->version);
402 g_free(server->away_reason);
403 g_free(server->nick);
411 SERVER_REC *server_find_tag(const char *tag)
415 g_return_val_if_fail(tag != NULL, NULL);
416 if (*tag == '\0') return NULL;
418 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
419 SERVER_REC *server = tmp->data;
421 if (g_strcasecmp(server->tag, tag) == 0)
425 for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
426 SERVER_REC *server = tmp->data;
428 if (g_strcasecmp(server->tag, tag) == 0)
435 SERVER_REC *server_find_chatnet(const char *chatnet)
439 g_return_val_if_fail(chatnet != NULL, NULL);
440 if (*chatnet == '\0') return NULL;
442 for (tmp = servers; tmp != NULL; tmp = tmp->next) {
443 SERVER_REC *server = tmp->data;
445 if (server->connrec->chatnet != NULL &&
446 g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
453 void server_connect_ref(SERVER_CONNECT_REC *conn)
458 void server_connect_unref(SERVER_CONNECT_REC *conn)
460 g_return_if_fail(IS_SERVER_CONNECT(conn));
462 if (--conn->refcount > 0)
464 if (conn->refcount < 0) {
465 g_warning("Connection '%s' refcount = %d",
466 conn->tag, conn->refcount);
469 CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
471 g_free_not_null(conn->proxy);
472 g_free_not_null(conn->proxy_string);
473 g_free_not_null(conn->proxy_string_after);
474 g_free_not_null(conn->proxy_password);
476 g_free_not_null(conn->tag);
477 g_free_not_null(conn->address);
478 g_free_not_null(conn->chatnet);
480 g_free_not_null(conn->own_ip4);
481 g_free_not_null(conn->own_ip6);
483 g_free_not_null(conn->password);
484 g_free_not_null(conn->nick);
485 g_free_not_null(conn->username);
486 g_free_not_null(conn->realname);
488 g_free_not_null(conn->channels);
489 g_free_not_null(conn->away_reason);
495 void server_change_nick(SERVER_REC *server, const char *nick)
497 g_free(server->nick);
498 server->nick = g_strdup(nick);
500 signal_emit("server nick changed", 1, server);
503 /* Update own IPv4 and IPv6 records */
504 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
505 IPADDR *ip4, IPADDR *ip6)
507 if (ip4 == NULL || ip4->family == 0)
508 g_free_and_null(conn->own_ip4);
509 if (ip6 == NULL || ip6->family == 0)
510 g_free_and_null(conn->own_ip6);
512 if (ip4 != NULL && ip4->family != 0) {
513 /* IPv4 address was found */
514 if (conn->own_ip4 == NULL)
515 conn->own_ip4 = g_new0(IPADDR, 1);
516 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
519 if (ip6 != NULL && ip6->family != 0) {
520 /* IPv6 address was found */
521 if (conn->own_ip6 == NULL)
522 conn->own_ip6 = g_new0(IPADDR, 1);
523 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
527 /* `optlist' should contain only one unknown key - the server tag.
528 returns NULL if there was unknown -option */
529 SERVER_REC *cmd_options_get_server(const char *cmd,
531 SERVER_REC *defserver)
534 GSList *list, *tmp, *next;
536 /* get all the options, then remove the known ones. there should
537 be only one left - the server tag. */
538 list = hashtable_get_keys(optlist);
540 for (tmp = list; tmp != NULL; tmp = next) {
541 char *option = tmp->data;
544 if (command_have_option(cmd, option))
545 list = g_slist_remove(list, option);
552 server = server_find_tag(list->data);
553 if (server == NULL || list->next != NULL) {
554 /* unknown option (not server tag) */
555 signal_emit("error command", 2,
556 GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
557 server == NULL ? list->data : list->next->data);
567 static void disconnect_servers(GSList *servers, int chat_type)
571 for (tmp = servers; tmp != NULL; tmp = next) {
572 SERVER_REC *rec = tmp->data;
575 if (rec->chat_type == chat_type)
576 server_disconnect(rec);
580 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
582 disconnect_servers(servers, proto->id);
583 disconnect_servers(lookup_servers, proto->id);
586 void servers_init(void)
588 settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
589 lookup_servers = servers = NULL;
591 signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
593 servers_reconnect_init();
594 servers_setup_init();
597 void servers_deinit(void)
599 signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
601 servers_setup_deinit();
602 servers_reconnect_deinit();
604 module_uniq_destroy("SERVER");
605 module_uniq_destroy("SERVER CONNECT");