summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorGu1ll4um3r0m41n <aeroxteam@gmail.com>2010-11-18 17:35:08 +0100
committerSebastien Helleu <flashcode@flashtux.org>2010-11-18 17:35:08 +0100
commitf15f114e4d4a1b649248301fef5cda176ad2a3a3 (patch)
treefd561d261459bb336570a567d81f04cec2e37372 /src/core
parent4d1c9a831503a6cb2589266239c11368a12c8a5d (diff)
downloadweechat-f15f114e4d4a1b649248301fef5cda176ad2a3a3.zip
Fix infinite loop on gnutls handshake when connecting with SSL to server on wrong port or server with SSL problems (bug #27487)
Diffstat (limited to 'src/core')
-rw-r--r--src/core/wee-config.c6
-rw-r--r--src/core/wee-config.h1
-rw-r--r--src/core/wee-hook.c22
-rw-r--r--src/core/wee-hook.h4
-rw-r--r--src/core/wee-network.c135
5 files changed, 159 insertions, 9 deletions
diff --git a/src/core/wee-config.c b/src/core/wee-config.c
index f0324af09..cb3526758 100644
--- a/src/core/wee-config.c
+++ b/src/core/wee-config.c
@@ -186,6 +186,7 @@ struct t_config_option *config_history_display_default;
/* config, network section */
struct t_config_option *config_network_gnutls_ca_file;
+struct t_config_option *config_network_gnutls_handshake_timeout;
/* config, plugin section */
@@ -2034,6 +2035,11 @@ config_weechat_init_options ()
N_("file containing the certificate authorities (\"%h\" will be "
"replaced by WeeChat home, \"~/.weechat\" by default)"),
NULL, 0, 0, "%h/ssl/CAs.pem", NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+ config_network_gnutls_handshake_timeout = config_file_new_option (
+ weechat_config_file, ptr_section,
+ "gnutls_handshake_timeout", "integer",
+ N_("timeout (in seconds) for gnutls handshake"),
+ NULL, 1, INT_MAX, "30", NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
/* plugin */
ptr_section = config_file_new_section (weechat_config_file, "plugin",
diff --git a/src/core/wee-config.h b/src/core/wee-config.h
index 99046f5a0..d84476384 100644
--- a/src/core/wee-config.h
+++ b/src/core/wee-config.h
@@ -207,6 +207,7 @@ extern struct t_config_option *config_history_max_visited_buffers;
extern struct t_config_option *config_history_display_default;
extern struct t_config_option *config_network_gnutls_ca_file;
+extern struct t_config_option *config_network_gnutls_handshake_timeout;
extern struct t_config_option *config_plugin_autoload;
extern struct t_config_option *config_plugin_debug;
diff --git a/src/core/wee-hook.c b/src/core/wee-hook.c
index d7c674a11..e1c852b17 100644
--- a/src/core/wee-hook.c
+++ b/src/core/wee-hook.c
@@ -1687,6 +1687,10 @@ hook_connect (struct t_weechat_plugin *plugin, const char *proxy, const char *ad
new_hook_connect->child_write = -1;
new_hook_connect->child_pid = 0;
new_hook_connect->hook_fd = NULL;
+ new_hook_connect->handshake_hook_fd = NULL;
+ new_hook_connect->handshake_hook_timer = NULL;
+ new_hook_connect->handshake_fd_flags = 0;
+ new_hook_connect->handshake_ip_address = NULL;
hook_add_to_list (new_hook);
@@ -2695,6 +2699,12 @@ unhook (struct t_hook *hook)
free (HOOK_CONNECT(hook, local_hostname));
if (HOOK_CONNECT(hook, hook_fd))
unhook (HOOK_CONNECT(hook, hook_fd));
+ if (HOOK_CONNECT(hook, handshake_hook_fd))
+ unhook (HOOK_CONNECT(hook, handshake_hook_fd));
+ if (HOOK_CONNECT(hook, handshake_hook_timer))
+ unhook (HOOK_CONNECT(hook, handshake_hook_timer));
+ if (HOOK_CONNECT(hook, handshake_ip_address))
+ free (HOOK_CONNECT(hook, handshake_ip_address));
if (HOOK_CONNECT(hook, child_pid) > 0)
{
kill (HOOK_CONNECT(hook, child_pid), SIGKILL);
@@ -3001,6 +3011,14 @@ hook_add_to_infolist_type (struct t_infolist *infolist,
return 0;
if (!infolist_new_var_pointer (ptr_item, "hook_fd", HOOK_CONNECT(ptr_hook, hook_fd)))
return 0;
+ if (!infolist_new_var_pointer (ptr_item, "handshake_hook_fd", HOOK_CONNECT(ptr_hook, handshake_hook_fd)))
+ return 0;
+ if (!infolist_new_var_pointer (ptr_item, "handshake_hook_timer", HOOK_CONNECT(ptr_hook, handshake_hook_timer)))
+ return 0;
+ if (!infolist_new_var_integer (ptr_item, "handshake_fd_flags", HOOK_CONNECT(ptr_hook, handshake_fd_flags)))
+ return 0;
+ if (!infolist_new_var_string (ptr_item, "handshake_ip_address", HOOK_CONNECT(ptr_hook, handshake_ip_address)))
+ return 0;
}
break;
case HOOK_TYPE_PRINT:
@@ -3332,6 +3350,10 @@ hook_print_log ()
log_printf (" child_write . . . . . : %d", HOOK_CONNECT(ptr_hook, child_write));
log_printf (" child_pid . . . . . . : %d", HOOK_CONNECT(ptr_hook, child_pid));
log_printf (" hook_fd . . . . . . . : 0x%lx", HOOK_CONNECT(ptr_hook, hook_fd));
+ log_printf (" handshake_hook_fd . . : 0x%lx", HOOK_CONNECT(ptr_hook, handshake_hook_fd));
+ log_printf (" handshake_hook_timer. : 0x%lx", HOOK_CONNECT(ptr_hook, handshake_hook_timer));
+ log_printf (" handshake_fd_flags. . : %d", HOOK_CONNECT(ptr_hook, handshake_fd_flags));
+ log_printf (" handshake_ip_address. : '%s'", HOOK_CONNECT(ptr_hook, handshake_ip_address));
}
break;
case HOOK_TYPE_PRINT:
diff --git a/src/core/wee-hook.h b/src/core/wee-hook.h
index e842865f3..2cd9fd760 100644
--- a/src/core/wee-hook.h
+++ b/src/core/wee-hook.h
@@ -226,6 +226,10 @@ struct t_hook_connect
int child_write; /* to write data in pipe for child */
pid_t child_pid; /* pid of child process (connecting) */
struct t_hook *hook_fd; /* pointer to fd hook */
+ struct t_hook *handshake_hook_fd; /* fd hook for handshake */
+ struct t_hook *handshake_hook_timer; /* timer for handshake timeout */
+ int handshake_fd_flags; /* socket flags saved for handshake */
+ char *handshake_ip_address; /* ip address (used for handshake) */
};
/* hook print */
diff --git a/src/core/wee-network.c b/src/core/wee-network.c
index cdb4bbe05..972cbfdac 100644
--- a/src/core/wee-network.c
+++ b/src/core/wee-network.c
@@ -27,6 +27,7 @@
#endif
#include <unistd.h>
+#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
@@ -726,6 +727,94 @@ network_connect_child (struct t_hook *hook_connect)
}
/*
+ * network_connect_gnutls_handshake_fd_cb: callback for gnutls handshake
+ * (used to not block WeeChat if
+ * handshake takes some time to finish)
+ */
+
+#ifdef HAVE_GNUTLS
+int
+network_connect_gnutls_handshake_fd_cb (void *arg_hook_connect, int fd)
+{
+ struct t_hook *hook_connect;
+ int rc, direction, flags;
+
+ /* make C compiler happy */
+ (void) fd;
+
+ hook_connect = (struct t_hook *)arg_hook_connect;
+
+ rc = gnutls_handshake (*HOOK_CONNECT(hook_connect, gnutls_sess));
+
+ if ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED))
+ {
+ direction = gnutls_record_get_direction (*HOOK_CONNECT(hook_connect, gnutls_sess));
+ flags = HOOK_FD(HOOK_CONNECT(hook_connect, handshake_hook_fd), flags);
+ if ((((flags & HOOK_FD_FLAG_READ) == HOOK_FD_FLAG_READ)
+ && (direction != 0))
+ || (((flags & HOOK_FD_FLAG_WRITE) == HOOK_FD_FLAG_WRITE)
+ && (direction != 1)))
+ {
+ HOOK_FD(HOOK_CONNECT(hook_connect, handshake_hook_fd), flags) =
+ (direction) ? HOOK_FD_FLAG_WRITE: HOOK_FD_FLAG_READ;
+ }
+ }
+ else if (rc != GNUTLS_E_SUCCESS)
+ {
+ (void) (HOOK_CONNECT(hook_connect, callback))
+ (hook_connect->callback_data,
+ WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR,
+ rc,
+ gnutls_strerror (rc),
+ HOOK_CONNECT(hook_connect, handshake_ip_address));
+ unhook (hook_connect);
+ }
+ else
+ {
+ fcntl (HOOK_CONNECT(hook_connect, sock), F_SETFL,
+ HOOK_CONNECT(hook_connect, handshake_fd_flags));
+ unhook (HOOK_CONNECT(hook_connect, handshake_hook_fd));
+ (void) (HOOK_CONNECT(hook_connect, callback))
+ (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_OK, 0, NULL,
+ HOOK_CONNECT(hook_connect, handshake_ip_address));
+ unhook (hook_connect);
+ }
+
+ return WEECHAT_RC_OK;
+}
+#endif
+
+/*
+ * network_connect_gnutls_handshake_timer_cb: timer for timeout on handshake
+ */
+
+#ifdef HAVE_GNUTLS
+int
+network_connect_gnutls_handshake_timer_cb (void *arg_hook_connect,
+ int remaining_calls)
+{
+ struct t_hook *hook_connect;
+
+ /* make C compiler happy */
+ (void) remaining_calls;
+
+ hook_connect = (struct t_hook *)arg_hook_connect;
+
+ HOOK_CONNECT(hook_connect, handshake_hook_timer) = NULL;
+
+ (void) (HOOK_CONNECT(hook_connect, callback))
+ (hook_connect->callback_data,
+ WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR,
+ GNUTLS_E_EXPIRED,
+ gnutls_strerror (GNUTLS_E_EXPIRED),
+ HOOK_CONNECT(hook_connect, handshake_ip_address));
+ unhook (hook_connect);
+
+ return WEECHAT_RC_OK;
+}
+#endif
+
+/*
* network_connect_child_read_cb: read connection progress from child process
*/
@@ -737,7 +826,7 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd)
int num_read;
long size_ip;
#ifdef HAVE_GNUTLS
- int rc;
+ int rc, direction;
#endif
/* make C compiler happy */
@@ -780,21 +869,47 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd)
#ifdef HAVE_GNUTLS
if (HOOK_CONNECT(hook_connect, gnutls_sess))
{
+ /*
+ * the socket needs to be non-blocking since the call to
+ * gnutls_handshake can block
+ */
+ HOOK_CONNECT(hook_connect, handshake_fd_flags) =
+ fcntl (HOOK_CONNECT(hook_connect, sock), F_GETFL);
+ fcntl (HOOK_CONNECT(hook_connect, sock), F_SETFL,
+ HOOK_CONNECT(hook_connect, handshake_fd_flags) | O_NONBLOCK);
gnutls_transport_set_ptr (*HOOK_CONNECT(hook_connect, gnutls_sess),
(gnutls_transport_ptr) ((unsigned long) HOOK_CONNECT(hook_connect, sock)));
- if (HOOK_CONNECT(hook_connect, gnutls_dhkey_size) > 0) {
+ if (HOOK_CONNECT (hook_connect, gnutls_dhkey_size) > 0)
+ {
gnutls_dh_set_prime_bits (*HOOK_CONNECT(hook_connect, gnutls_sess),
(unsigned int) HOOK_CONNECT(hook_connect, gnutls_dhkey_size));
}
- while (1)
+ rc = gnutls_handshake (*HOOK_CONNECT(hook_connect, gnutls_sess));
+ if ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED))
{
- rc = gnutls_handshake (*HOOK_CONNECT(hook_connect, gnutls_sess));
- if ((rc == GNUTLS_E_SUCCESS)
- || ((rc != GNUTLS_E_AGAIN) && (rc != GNUTLS_E_INTERRUPTED)))
- break;
- usleep (1000);
+ /*
+ * gnutls was unable to proceed with the handshake without
+ * blocking: non fatal error, we just have to wait for an
+ * event about handshake
+ */
+ direction = gnutls_record_get_direction (*HOOK_CONNECT(hook_connect, gnutls_sess));
+ HOOK_CONNECT(hook_connect, handshake_ip_address) = ip_address;
+ HOOK_CONNECT(hook_connect, handshake_hook_fd) =
+ hook_fd (hook_connect->plugin,
+ HOOK_CONNECT(hook_connect, sock),
+ (!direction ? 1 : 0), (direction ? 1 : 0), 0,
+ //1, 1, 0,
+ &network_connect_gnutls_handshake_fd_cb,
+ hook_connect);
+ HOOK_CONNECT(hook_connect, handshake_hook_timer) =
+ hook_timer (hook_connect->plugin,
+ CONFIG_INTEGER(config_network_gnutls_handshake_timeout) * 1000,
+ 0, 1,
+ &network_connect_gnutls_handshake_timer_cb,
+ hook_connect);
+ return WEECHAT_RC_OK;
}
- if (rc != GNUTLS_E_SUCCESS)
+ else if (rc != GNUTLS_E_SUCCESS)
{
(void) (HOOK_CONNECT(hook_connect, callback))
(hook_connect->callback_data,
@@ -807,6 +922,8 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd)
free (ip_address);
return WEECHAT_RC_OK;
}
+ fcntl (HOOK_CONNECT(hook_connect, sock), F_SETFL,
+ HOOK_CONNECT(hook_connect, handshake_fd_flags));
}
#endif
}