Browse Source

[Feature] Perform clean SSL shutdown

tags/2.0
Vsevolod Stakhov 4 years ago
parent
commit
6a7437c48a
1 changed files with 80 additions and 38 deletions
  1. 80
    38
      src/libutil/ssl_util.c

+ 80
- 38
src/libutil/ssl_util.c View File

@@ -18,6 +18,7 @@
#include "libutil/util.h"
#include "libutil/logger.h"
#include "ssl_util.h"
#include "unix-std.h"

#include <openssl/ssl.h>
#include <openssl/err.h>
@@ -30,7 +31,8 @@ enum rspamd_ssl_state {
ssl_conn_init,
ssl_conn_connected,
ssl_next_read,
ssl_next_write
ssl_next_write,
ssl_next_shutdown,
};

enum rspamd_ssl_shutdown {
@@ -399,6 +401,53 @@ rspamd_tls_set_error (gint retcode, const gchar *stage, GError **err)
g_string_free (reason, TRUE);
}

static void
rspamd_ssl_connection_dtor (struct rspamd_ssl_connection *conn)
{
SSL_free (conn->ssl);

if (conn->hostname) {
g_free (conn->hostname);
}

close (conn->fd);
g_free (conn);
}

static void
rspamd_ssl_shutdown (struct rspamd_ssl_connection *conn)
{
gint ret;

if ((ret = SSL_shutdown (conn->ssl)) == 1) {
/* All done */
rspamd_ssl_connection_dtor (conn);
}
else {
short what;

ret = SSL_get_error (conn->ssl, ret);
conn->state = ssl_next_shutdown;

if (ret == SSL_ERROR_WANT_READ) {
what = EV_READ;
}
else if (ret == SSL_ERROR_WANT_WRITE) {
what = EV_WRITE;
}
else {
/* Cannot do anything else, fatal error */
rspamd_ssl_connection_dtor (conn);

return;
}

/* As we own fd, we can try to perform shutdown one more time */
rspamd_ev_watcher_reschedule (conn->event_loop, conn->ev, what);
conn->state = ssl_next_shutdown;
}
}

static void
rspamd_ssl_event_handler (gint fd, short what, gpointer ud)
{
@@ -407,12 +456,18 @@ rspamd_ssl_event_handler (gint fd, short what, gpointer ud)
GError *err = NULL;

if (what == EV_TIMER) {
c->shut = ssl_shut_unclean;
rspamd_ev_watcher_stop (c->event_loop, c->ev);
g_set_error (&err, rspamd_ssl_quark (), ETIMEDOUT,
"ssl connection timed out");
c->err_handler (c->handler_data, err);
g_error_free (err);
if (c->state == ssl_next_shutdown) {
/* No way to restore, just terminate */
rspamd_ssl_connection_dtor (c);
}
else {
c->shut = ssl_shut_unclean;
rspamd_ev_watcher_stop (c->event_loop, c->ev);
g_set_error (&err, rspamd_ssl_quark (), ETIMEDOUT,
"ssl connection timed out");
c->err_handler (c->handler_data, err);
g_error_free (err);
}

return;
}
@@ -471,6 +526,9 @@ rspamd_ssl_event_handler (gint fd, short what, gpointer ud)
c->state = ssl_conn_connected;
c->handler (fd, what, c->handler_data);
break;
case ssl_next_shutdown:
rspamd_ssl_shutdown (c);
break;
default:
rspamd_ev_watcher_stop (c->event_loop, c->ev);
g_set_error (&err, rspamd_ssl_quark (), EINVAL,
@@ -512,13 +570,22 @@ rspamd_ssl_connect_fd (struct rspamd_ssl_connection *conn, gint fd,
return FALSE;
}

conn->fd = fd;
/* We dup fd to allow graceful closing */
gint nfd = dup (fd);

if (nfd == -1) {
return FALSE;
}

conn->fd = nfd;
conn->ev = ev;
conn->handler = handler;
conn->err_handler = err_handler;
conn->handler_data = handler_data;

if (SSL_set_fd (conn->ssl, fd) != 1) {
if (SSL_set_fd (conn->ssl, conn->fd) != 1) {
close (conn->fd);

return FALSE;
}

@@ -747,38 +814,13 @@ void
rspamd_ssl_connection_free (struct rspamd_ssl_connection *conn)
{
if (conn) {
/*
* SSL_RECEIVED_SHUTDOWN tells SSL_shutdown to act as if we had already
* received a close notify from the other end. SSL_shutdown will then
* send the final close notify in reply. The other end will receive the
* close notify and send theirs. By this time, we will have already
* closed the socket and the other end's real close notify will never be
* received. In effect, both sides will think that they have completed a
* clean shutdown and keep their sessions valid. This strategy will fail
* if the socket is not ready for writing, in which case this hack will
* lead to an unclean shutdown and lost session on the other end.
*/
if (conn->shut == ssl_shut_unclean) {
SSL_set_shutdown (conn->ssl, SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN);
SSL_set_quiet_shutdown (conn->ssl, 1);
/* Ignore return result and close socket */
(void)SSL_shutdown (conn->ssl);
rspamd_ssl_connection_dtor (conn);
}
else {
SSL_set_shutdown (conn->ssl, SSL_RECEIVED_SHUTDOWN);
rspamd_ssl_shutdown (conn);
}

/* Stupid hack to enforce SSL to do shutdown sequence */
for (guint i = 0; i < 4; i++) {
if (SSL_shutdown (conn->ssl)) {
break;
}
}

SSL_free (conn->ssl);

if (conn->hostname) {
g_free (conn->hostname);
}

g_free (conn);
}
}

Loading…
Cancel
Save