diff options
author | Gu1ll4um3r0m41n <aeroxteam@gmail.com> | 2010-11-18 17:35:08 +0100 |
---|---|---|
committer | Sebastien Helleu <flashcode@flashtux.org> | 2010-11-18 17:35:08 +0100 |
commit | f15f114e4d4a1b649248301fef5cda176ad2a3a3 (patch) | |
tree | fd561d261459bb336570a567d81f04cec2e37372 /src/core | |
parent | 4d1c9a831503a6cb2589266239c11368a12c8a5d (diff) | |
download | weechat-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.c | 6 | ||||
-rw-r--r-- | src/core/wee-config.h | 1 | ||||
-rw-r--r-- | src/core/wee-hook.c | 22 | ||||
-rw-r--r-- | src/core/wee-hook.h | 4 | ||||
-rw-r--r-- | src/core/wee-network.c | 135 |
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 } |