Merged from silc_1_0_branch.
[silc.git] / apps / irssi / src / core / servers.c
index f74e7f58906f4db6b39ff122e280ae620653265b..f9ab791aabb1f6e1666c1584aa1dcf47dc5d2691 100644 (file)
@@ -31,7 +31,6 @@
 #include "chat-protocols.h"
 #include "servers.h"
 #include "servers-reconnect.h"
-#include "servers-redirect.h"
 #include "servers-setup.h"
 #include "channels.h"
 #include "queries.h"
@@ -46,23 +45,26 @@ void server_connect_failed(SERVER_REC *server, const char *msg)
        lookup_servers = g_slist_remove(lookup_servers, server);
 
        signal_emit("server connect failed", 2, server, msg);
-       if (server->connect_tag != -1)
+
+       if (server->connect_tag != -1) {
                g_source_remove(server->connect_tag);
-       if (server->handle != NULL)
+               server->connect_tag = -1;
+       }
+       if (server->handle != NULL) {
                net_sendbuffer_destroy(server->handle, TRUE);
+               server->handle = NULL;
+       }
 
        if (server->connect_pipe[0] != NULL) {
                g_io_channel_close(server->connect_pipe[0]);
                g_io_channel_unref(server->connect_pipe[0]);
                g_io_channel_close(server->connect_pipe[1]);
                g_io_channel_unref(server->connect_pipe[1]);
+               server->connect_pipe[0] = NULL;
+               server->connect_pipe[1] = NULL;
        }
 
-       MODULE_DATA_DEINIT(server);
-       server_connect_free(server->connrec);
-       g_free_not_null(server->nick);
-       g_free(server->tag);
-       g_free(server);
+       server_unref(server);
 }
 
 /* generate tag from server's address */
@@ -101,16 +103,34 @@ static char *server_create_tag(SERVER_CONNECT_REC *conn)
        char *tag;
        int num;
 
-        g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
+       g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
 
        tag = conn->chatnet != NULL && *conn->chatnet != '\0' ?
                g_strdup(conn->chatnet) :
                server_create_address_tag(conn->address);
 
+       if (conn->tag != NULL && server_find_tag(conn->tag) == NULL &&
+            server_find_lookup_tag(conn->tag) == NULL &&
+           strncmp(conn->tag, tag, strlen(tag)) == 0) {
+               /* use the existing tag if it begins with the same ID -
+                  this is useful when you have several connections to
+                  same server and you want to keep the same tags with
+                  the servers (or it would cause problems when rejoining
+                  /LAYOUT SAVEd channels). */
+               g_free(tag);
+               return g_strdup(conn->tag);
+       }
+
+
        /* then just append numbers after tag until unused is found.. */
        str = g_string_new(tag);
-       for (num = 2; server_find_tag(str->str) != NULL; num++)
+
+       num = 2;
+       while (server_find_tag(str->str) != NULL ||
+              server_find_lookup_tag(str->str) != NULL) {
                g_string_sprintf(str, "%s%d", tag, num);
+               num++;
+       }
        g_free(tag);
 
        tag = str->str;
@@ -122,11 +142,6 @@ static char *server_create_tag(SERVER_CONNECT_REC *conn)
 void server_connect_finished(SERVER_REC *server)
 {
        server->connect_time = time(NULL);
-       server->rawlog = rawlog_create();
-
-       server->eventtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
-       server->eventgrouptable = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
-       server->cmdtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
 
        servers = g_slist_append(servers, server);
        signal_emit("server connected", 1, server);
@@ -152,16 +167,53 @@ static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
        server_connect_finished(server);
 }
 
-static void server_connect_callback_readpipe(SERVER_REC *server)
+static void server_real_connect(SERVER_REC *server, IPADDR *ip,
+                               const char *unix_socket)
 {
-       SERVER_CONNECT_REC *conn;
-       RESOLVED_IP_REC iprec;
        GIOChannel *handle;
-        IPADDR *ip, *own_ip;
-       const char *errormsg;
+        IPADDR *own_ip;
         int port;
 
-       g_return_if_fail(IS_SERVER(server));
+       g_return_if_fail(ip != NULL || unix_socket != NULL);
+
+       signal_emit("server connecting", 2, server, ip);
+
+       if (ip != NULL) {
+               own_ip = ip == NULL ? NULL :
+                       (IPADDR_IS_V6(ip) ? server->connrec->own_ip6 :
+                        server->connrec->own_ip4);
+               port = server->connrec->proxy != NULL ?
+                       server->connrec->proxy_port : server->connrec->port;
+               handle = server->connrec->use_ssl ?
+                       net_connect_ip_ssl(ip, port, own_ip) :
+                       net_connect_ip(ip, port, own_ip);
+       } else {
+               handle = net_connect_unix(unix_socket);
+       }
+
+       if (handle == NULL) {
+               /* failed */
+               if (server->connrec->use_ssl && errno == ENOSYS)
+                       server->no_reconnect = TRUE;
+
+               server->connection_lost = TRUE;
+               server_connect_failed(server, g_strerror(errno));
+       } else {
+               server->handle = net_sendbuffer_create(handle, 0);
+               server->connect_tag =
+                       g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
+                                   (GInputFunction)
+                                   server_connect_callback_init,
+                                   server);
+       }
+}
+
+static void server_connect_callback_readpipe(SERVER_REC *server)
+{
+       RESOLVED_IP_REC iprec;
+        IPADDR *ip;
+       const char *errormsg;
+       char *servername = NULL;
 
        g_source_remove(server->connect_tag);
        server->connect_tag = -1;
@@ -177,76 +229,107 @@ static void server_connect_callback_readpipe(SERVER_REC *server)
        server->connect_pipe[1] = NULL;
 
        /* figure out if we should use IPv4 or v6 address */
-       ip = iprec.error != 0 ? NULL : iprec.ip6.family == 0 ||
-               (server->connrec->family == AF_INET && iprec.ip4.family != 0) ?
-               &iprec.ip4 : &iprec.ip6;
-       if (iprec.ip4.family != 0 && server->connrec->family == 0 &&
-           !settings_get_bool("resolve_prefer_ipv6"))
-                ip = &iprec.ip4;
-
-        conn = server->connrec;
-       port = conn->proxy != NULL ? conn->proxy_port : conn->port;
-       own_ip = ip == NULL ? NULL :
-               (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
-
-       if (ip != NULL)
-               signal_emit("server connecting", 2, server, ip);
-
-       handle = ip == NULL ? NULL : net_connect_ip(ip, port, own_ip);
-       if (handle == NULL) {
-               /* failed */
-               if (iprec.error != 0 && net_hosterror_notfound(iprec.error)) {
-                       /* IP wasn't found for the host, don't try to reconnect
-                          back to this server */
+       if (iprec.error != 0) {
+                /* error */
+               ip = NULL;
+       } else if (server->connrec->family == AF_INET) {
+               /* force IPv4 connection */
+               ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
+               servername = iprec.host4;
+       } else if (server->connrec->family == AF_INET6) {
+               /* force IPv6 connection */
+               ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
+               servername = iprec.host6;
+       } else {
+               /* pick the one that was found, or if both do it like
+                  /SET resolve_prefer_ipv6 says. */
+               if (iprec.ip4.family == 0 ||
+                   (iprec.ip6.family != 0 &&
+                    settings_get_bool("resolve_prefer_ipv6"))) {
+                       ip = &iprec.ip6;
+                       servername = iprec.host6;
+               } else {
+                       ip = &iprec.ip4;
+                       servername = iprec.host4;
+               }
+       }
+
+       if (ip != NULL) {
+               /* host lookup ok */
+               if (servername) {
+                       g_free(server->connrec->address);
+                       server->connrec->address = g_strdup(servername);
+               }
+               server_real_connect(server, ip, NULL);
+               errormsg = NULL;
+       } else {
+               if (iprec.error == 0 || net_hosterror_notfound(iprec.error)) {
+                       /* IP wasn't found for the host, don't try to
+                          reconnect back to this server */
                        server->dns_error = TRUE;
                }
 
                if (iprec.error == 0) {
-                       /* connect() failed */
-                       errormsg = g_strerror(errno);
+                       /* forced IPv4 or IPv6 address but it wasn't found */
+                       errormsg = server->connrec->family == AF_INET ?
+                               "IPv4 address not found for host" :
+                               "IPv6 address not found for host";
                } else {
                        /* gethostbyname() failed */
                        errormsg = iprec.errorstr != NULL ? iprec.errorstr :
                                "Host lookup failed";
                }
+
                server->connection_lost = TRUE;
                server_connect_failed(server, errormsg);
-               g_free_not_null(iprec.errorstr);
-               return;
        }
 
-       server->handle = net_sendbuffer_create(handle, 0);
-       server->connect_tag =
-               g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
-                           (GInputFunction) server_connect_callback_init,
-                           server);
+       g_free(iprec.errorstr);
+       g_free(iprec.host4);
+       g_free(iprec.host6);
+}
+
+SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
+{
+       CHAT_PROTOCOL_REC *proto;
+       SERVER_REC *server;
+
+       proto = CHAT_PROTOCOL(conn);
+       server = proto->server_init_connect(conn);
+       proto->server_connect(server);
+
+       return server;
 }
 
 /* initializes server record but doesn't start connecting */
 void server_connect_init(SERVER_REC *server)
 {
+       const char *str;
+
        g_return_if_fail(server != NULL);
 
        MODULE_DATA_INIT(server);
        server->type = module_get_uniq_id("SERVER", 0);
+       server_ref(server);
 
        server->nick = g_strdup(server->connrec->nick);
        if (server->connrec->username == NULL || *server->connrec->username == '\0') {
                g_free_not_null(server->connrec->username);
 
-               server->connrec->username = g_get_user_name();
-               if (*server->connrec->username == '\0') server->connrec->username = "-";
-               server->connrec->username = g_strdup(server->connrec->username);
+               str = g_get_user_name();
+               if (*str == '\0') str = "unknown";
+               server->connrec->username = g_strdup(str);
        }
        if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
                g_free_not_null(server->connrec->realname);
 
-               server->connrec->realname = g_get_real_name();
-               if (*server->connrec->realname == '\0') server->connrec->realname = "-";
-               server->connrec->realname = g_strdup(server->connrec->realname);
+               str = g_get_real_name();
+               if (*str == '\0') str = server->connrec->username;
+               server->connrec->realname = g_strdup(str);
        }
 
        server->tag = server_create_tag(server->connrec);
+       server->connect_tag = -1;
 }
 
 /* starts connecting to server */
@@ -256,48 +339,64 @@ int server_start_connect(SERVER_REC *server)
         int fd[2];
 
        g_return_val_if_fail(server != NULL, FALSE);
-       if (server->connrec->port <= 0) return FALSE;
+       if (!server->connrec->unix_socket && server->connrec->port <= 0)
+               return FALSE;
 
-       server_connect_init(server);
+       server->rawlog = rawlog_create();
 
-       if (pipe(fd) != 0) {
-               g_warning("server_connect(): pipe() failed.");
-                g_free(server->tag);
-               g_free(server->nick);
-               return FALSE;
-       }
+       if (server->connrec->connect_handle != NULL) {
+               /* already connected */
+               GIOChannel *handle = server->connrec->connect_handle;
+
+               server->connrec->connect_handle = NULL;
+               server->handle = net_sendbuffer_create(handle, 0);
+               server_connect_finished(server);
+       } else if (server->connrec->unix_socket) {
+               /* connect with unix socket */
+               server_real_connect(server, NULL, server->connrec->address);
+       } else {
+               /* resolve host name */
+               if (pipe(fd) != 0) {
+                       g_warning("server_connect(): pipe() failed.");
+                       g_free(server->tag);
+                       g_free(server->nick);
+                       return FALSE;
+               }
 
-        server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
-       server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
+               server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
+               server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
 
-       connect_address = server->connrec->proxy != NULL ?
-               server->connrec->proxy : server->connrec->address;
-       server->connect_pid =
-               net_gethostbyname_nonblock(connect_address,
-                                          server->connect_pipe[1]);
-       server->connect_tag =
-               g_input_add(server->connect_pipe[0], G_INPUT_READ,
-                           (GInputFunction) server_connect_callback_readpipe,
-                           server);
+               connect_address = server->connrec->proxy != NULL ?
+                       server->connrec->proxy : server->connrec->address;
+               server->connect_pid =
+                       net_gethostbyname_nonblock(connect_address,
+                                                  server->connect_pipe[1],
+                                                  settings_get_bool("resolve_reverse_lookup"));
+               server->connect_tag =
+                       g_input_add(server->connect_pipe[0], G_INPUT_READ,
+                                   (GInputFunction)
+                                   server_connect_callback_readpipe,
+                                   server);
 
-       lookup_servers = g_slist_append(lookup_servers, server);
+               lookup_servers = g_slist_append(lookup_servers, server);
 
-       signal_emit("server looking", 1, server);
+               signal_emit("server looking", 1, server);
+       }
        return TRUE;
 }
 
 static int server_remove_channels(SERVER_REC *server)
 {
-       GSList *tmp;
+       GSList *tmp, *next;
        int found;
 
        g_return_val_if_fail(server != NULL, FALSE);
 
        found = FALSE;
-       for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+       for (tmp = server->channels; tmp != NULL; tmp = next) {
                CHANNEL_REC *channel = tmp->data;
 
-               channel->server = NULL;
+               next = tmp->next;
                channel_destroy(channel);
                found = TRUE;
        }
@@ -317,6 +416,9 @@ void server_disconnect(SERVER_REC *server)
 
        g_return_if_fail(IS_SERVER(server));
 
+       if (server->disconnected)
+               return;
+
        if (server->connect_tag != -1) {
                /* still connecting to server.. */
                if (server->connect_pid != -1)
@@ -327,6 +429,7 @@ void server_disconnect(SERVER_REC *server)
 
        servers = g_slist_remove(servers, server);
 
+       server->disconnected = TRUE;
        signal_emit("server disconnected", 1, server);
 
        /* close all channels */
@@ -345,18 +448,46 @@ void server_disconnect(SERVER_REC *server)
                server->handle = NULL;
        }
 
-       if (server->readtag > 0)
+       if (server->readtag > 0) {
                g_source_remove(server->readtag);
+               server->readtag = -1;
+       }
+
+       server_unref(server);
+}
+
+void server_ref(SERVER_REC *server)
+{
+       g_return_if_fail(IS_SERVER(server));
+
+       server->refcount++;
+}
+
+int server_unref(SERVER_REC *server)
+{
+       g_return_val_if_fail(IS_SERVER(server), FALSE);
+
+       if (--server->refcount > 0)
+               return TRUE;
+
+       if (g_slist_find(servers, server) != NULL) {
+               g_warning("Non-referenced server wasn't disconnected");
+               server_disconnect(server);
+               return TRUE;
+       }
 
         MODULE_DATA_DEINIT(server);
-       server_connect_free(server->connrec);
-       rawlog_destroy(server->rawlog);
-       line_split_free(server->buffer);
-       g_free_not_null(server->version);
-       g_free_not_null(server->away_reason);
+       server_connect_unref(server->connrec);
+       if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
+       if (server->buffer != NULL) line_split_free(server->buffer);
+       g_free(server->version);
+       g_free(server->away_reason);
        g_free(server->nick);
        g_free(server->tag);
+
+       server->type = 0;
        g_free(server);
+        return FALSE;
 }
 
 SERVER_REC *server_find_tag(const char *tag)
@@ -373,6 +504,16 @@ SERVER_REC *server_find_tag(const char *tag)
                        return server;
        }
 
+       return NULL;
+}
+
+SERVER_REC *server_find_lookup_tag(const char *tag)
+{
+       GSList *tmp;
+
+       g_return_val_if_fail(tag != NULL, NULL);
+       if (*tag == '\0') return NULL;
+
        for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
                SERVER_REC *server = tmp->data;
 
@@ -401,15 +542,33 @@ SERVER_REC *server_find_chatnet(const char *chatnet)
        return NULL;
 }
 
-void server_connect_free(SERVER_CONNECT_REC *conn)
+void server_connect_ref(SERVER_CONNECT_REC *conn)
+{
+        conn->refcount++;
+}
+
+void server_connect_unref(SERVER_CONNECT_REC *conn)
 {
        g_return_if_fail(IS_SERVER_CONNECT(conn));
 
-       signal_emit("server connect free", 1, conn);
-        g_free_not_null(conn->proxy);
+       if (--conn->refcount > 0)
+               return;
+       if (conn->refcount < 0) {
+               g_warning("Connection '%s' refcount = %d",
+                         conn->tag, conn->refcount);
+       }
+
+        CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
+
+       if (conn->connect_handle != NULL)
+               net_disconnect(conn->connect_handle);
+
+       g_free_not_null(conn->proxy);
        g_free_not_null(conn->proxy_string);
+       g_free_not_null(conn->proxy_string_after);
        g_free_not_null(conn->proxy_password);
 
+       g_free_not_null(conn->tag);
        g_free_not_null(conn->address);
        g_free_not_null(conn->chatnet);
 
@@ -423,14 +582,14 @@ void server_connect_free(SERVER_CONNECT_REC *conn)
 
        g_free_not_null(conn->channels);
         g_free_not_null(conn->away_reason);
-        g_free(conn);
+
+        conn->type = 0;
+       g_free(conn);
 }
 
 void server_change_nick(SERVER_REC *server, const char *nick)
 {
-       g_free(server->connrec->nick);
        g_free(server->nick);
-       server->connrec->nick = g_strdup(nick);
        server->nick = g_strdup(nick);
 
        signal_emit("server nick changed", 1, server);
@@ -522,12 +681,12 @@ static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
 void servers_init(void)
 {
        settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
+       settings_add_bool("server", "resolve_reverse_lookup", FALSE);
        lookup_servers = servers = NULL;
 
        signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
 
        servers_reconnect_init();
-       servers_redirect_init();
        servers_setup_init();
 }
 
@@ -536,7 +695,6 @@ void servers_deinit(void)
        signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
 
        servers_setup_deinit();
-       servers_redirect_deinit();
        servers_reconnect_deinit();
 
        module_uniq_destroy("SERVER");