1
0
mirror of https://github.com/weechat/weechat.git synced 2026-07-09 11:13:12 +02:00

Fix infinite loop on gnutls handshake when connecting with SSL to server on wrong port or server with SSL problems (bug #27487)

This commit is contained in:
Gu1ll4um3r0m41n
2010-11-18 17:35:08 +01:00
committed by Sebastien Helleu
parent 4d1c9a8315
commit f15f114e4d
20 changed files with 235 additions and 21 deletions
+6
View File
@@ -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",
+1
View File
@@ -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;
+22
View File
@@ -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:
+4
View File
@@ -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 */
+126 -9
View File
@@ -27,6 +27,7 @@
#endif
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
@@ -725,6 +726,94 @@ network_connect_child (struct t_hook *hook_connect)
freeaddrinfo (res_local);
}
/*
* 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
}