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:
committed by
Sebastien Helleu
parent
4d1c9a8315
commit
f15f114e4d
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user