diff options
author | Joe Orton <jorton@redhat.com> | 2021-08-04 16:24:26 +0100 |
---|---|---|
committer | Joe Orton <jorton@apache.org> | 2021-08-04 16:59:51 +0100 |
commit | 01fd5ca9f09ba6c123f5665f00a3cf55a22c7c37 (patch) | |
tree | 140965dcc915e6b4afc015c7de96bc894dd8b523 | |
parent | 1c36f97d784ffe0efa65a2b9aa143662d4f8a457 (diff) | |
download | neon-git-01fd5ca9f09ba6c123f5665f00a3cf55a22c7c37.tar.gz |
Add API for socket shutdown, and fix TLS closure with OpenSSL 3.
(closes #27, closes #50)
* src/ne_socket.c (ne_sock_shutdown): New function.
(ne_sock_close): Use it to shut down.
* test/common/child.c (close_socket): Use it to implement proper
lingering close.
* test/socket.c
(finish, ssl_closure): Close send-side if not expecting EOF.
(ssl_shutdown, serve_shutdown, bidi): New tests.
-rw-r--r-- | src/ne_socket.c | 90 | ||||
-rw-r--r-- | src/ne_socket.h | 32 | ||||
-rw-r--r-- | src/neon.vers | 1 | ||||
-rw-r--r-- | test/common/child.c | 24 | ||||
-rw-r--r-- | test/socket.c | 70 |
5 files changed, 192 insertions, 25 deletions
diff --git a/src/ne_socket.c b/src/ne_socket.c index 7332061..1f27b8e 100644 --- a/src/ne_socket.c +++ b/src/ne_socket.c @@ -179,9 +179,6 @@ typedef struct in_addr ne_inet_addr; /* Socket read timeout */ #define SOCKET_READ_TIMEOUT 120 -/* Internal read retry value */ -#define NE_SOCK_RETRY (-6) - /* Critical I/O functions on a socket: useful abstraction for easily * handling SSL I/O alongside raw socket I/O. */ struct iofns { @@ -674,7 +671,8 @@ static int error_ossl(ne_socket *sock, int sret) unsigned long err; if (errnum == SSL_ERROR_ZERO_RETURN) { - set_error(sock, _("Connection closed")); + set_error(sock, _("Connection closed")); + NE_DEBUG(NE_DBG_SSL, "ssl: Got TLS closure.\n"); return NE_SOCK_CLOSED; } else if (errnum == SSL_ERROR_WANT_READ) { @@ -2007,23 +2005,89 @@ void ne_sock_set_error(ne_socket *sock, const char *format, ...) va_end(params); } -int ne_sock_close(ne_socket *sock) +int ne_sock_shutdown(ne_socket *sock, unsigned int flags) { int ret; - /* Per API description - for an SSL connection, simply send the - * close_notify but do not wait for the peer's response. */ + if (!flags) { + set_error(sock, _("Missing flags for socket shutdown")); + return NE_SOCK_ERROR; + } + +#if defined(HAVE_OPENSSL) + if (sock->ssl) { + int state = SSL_get_shutdown(sock->ssl); + + NE_DEBUG(NE_DBG_SSL, "ssl: Shutdown state: %ssent | %sreceived.\n", + (state & SSL_SENT_SHUTDOWN) ? "" : "not ", + (state & SSL_RECEIVED_SHUTDOWN) ? "" : "not "); + + if ((flags == NE_SOCK_BOTH || flags == NE_SOCK_SEND) + && (state & SSL_SENT_SHUTDOWN) == 0) { + NE_DEBUG(NE_DBG_SSL, "ssl: Sending closure.\n"); + ret = SSL_shutdown(sock->ssl); + + if (ret == 0) { + set_error(sock, _("Incomplete TLS closure")); + return NE_SOCK_RETRY; + } + else if (ret != 1) { + return error_ossl(sock, ret); + } + } + + if (flags == NE_SOCK_RECV || flags == NE_SOCK_BOTH) { + /* Returns whether the receive side is shutdown or not yet. */ + if ((state & SSL_RECEIVED_SHUTDOWN) == 0) { + set_error(sock, _("Incomplete TLS closure")); + return NE_SOCK_RETRY; + } + + /* For recv-only shutdown, must not complete TCP-level + * shutdown until the TLS shutdown is complete. */ + if (flags == NE_SOCK_RECV) { + return 0; + } + } + } +#elif defined(HAVE_GNUTLS) + if (sock->ssl) { + if (flags == NE_SOCK_RECV) { + /* unclear how to handle */ + set_error(sock, _("Incomplete TLS closure")); + return NE_SOCK_RETRY; + } + + ret = gnutls_bye(sock->ssl, + flags == NE_SOCK_SEND ? GNUTLS_SHUT_WR :GNUTLS_SHUT_RDRW); + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return NE_SOCK_RETRY; + } + } +#endif + + ret = shutdown(sock->fd, + flags == NE_SOCK_RECV ? SHUT_RD : + (flags == NE_SOCK_SEND ? SHUT_WR : SHUT_RDWR)); + if (ret < 0) { + int errnum = ne_errno; + set_strerror(sock, errnum); + return MAP_ERR(errnum); + } + + return ret; +} + +int ne_sock_close(ne_socket *sock) +{ + int ret = ne_sock_shutdown(sock, NE_SOCK_SEND); + #if defined(HAVE_OPENSSL) if (sock->ssl) { - SSL_shutdown(sock->ssl); SSL_free(sock->ssl); } #elif defined(HAVE_GNUTLS) if (sock->ssl) { - do { - ret = gnutls_bye(sock->ssl, GNUTLS_SHUT_WR); - } while (ret < 0 - && (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN)); gnutls_deinit(sock->ssl); } #endif @@ -2032,6 +2096,8 @@ int ne_sock_close(ne_socket *sock) ret = 0; else ret = ne_close(sock->fd); + sock->fd = -1; + ne_free(sock); return ret; } diff --git a/src/ne_socket.h b/src/ne_socket.h index 23846bd..d9861f2 100644 --- a/src/ne_socket.h +++ b/src/ne_socket.h @@ -42,7 +42,8 @@ NE_BEGIN_DECLS #define NE_SOCK_RESET (-4) /* Secure connection was closed without proper SSL shutdown. */ #define NE_SOCK_TRUNC (-5) -/* Reserved value - (-6) */ +/* Retry operation later. */ +#define NE_SOCK_RETRY (-6) /* ne_socket represents a TCP socket. */ typedef struct ne_socket_s ne_socket; @@ -227,10 +228,31 @@ int ne_sock_fd(const ne_socket *sock); * must be destroyed by caller using ne_iaddr_free. */ ne_inet_addr *ne_sock_peer(ne_socket *sock, unsigned int *port); -/* Close the socket and destroy the socket object. If SSL is in use - * for the socket, a closure alert is sent to initiate a clean - * shutdown, but this function does not wait for the peer's response. - * Returns zero on success, or non-zero on failure. */ +/* Flags for ne_sock_shutdown(): */ +#define NE_SOCK_RECV (1) +#define NE_SOCK_SEND (2) +#define NE_SOCK_BOTH (3) + +/* Shut down the socket in one or both directions, without destroying + * the socket object. Flags must be one of NE_SOCK_RECV/SEND/BOTH. + * For a non-TLS socket, performs the directional shutdown according + * to flags. + * For a TLS socket: + * - if flags are NE_SOCK_SEND or NE_SOCK_BOTH, sends the TLS + * close_notify. Returns NE_SOCK_RETRY if the TLS connection has + * not been closed by the peer. + * - if flags are NE_SOCK_RECV, returns NE_SOCK_RETRY if the + * TLS close_notify has not been closed by the peer. + * In NE_SOCK_SEND or NE_SOCK_BOTH is specified, and the bidirectional + * TLS shutdown has completed, the TCP shutdown will also be completed + * as for a non-TLS socket. +*/ +int ne_sock_shutdown(ne_socket *sock, unsigned int flags); + +/* Close the socket if it is open, and destroy the socket object. If + * SSL is in use for the socket, a closure alert is sent to initiate a + * clean shutdown, but this function does not wait for the peer's + * response. Returns zero on success, or non-zero on failure. */ int ne_sock_close(ne_socket *sock); /* Return current error string for socket. */ diff --git a/src/neon.vers b/src/neon.vers index 45f99ee..96565b1 100644 --- a/src/neon.vers +++ b/src/neon.vers @@ -32,4 +32,5 @@ NEON_0_32 { ne_strparam; ne_add_auth; ne_ssl_cert_hdigest; + ne_sock_shutdown; }; diff --git a/test/common/child.c b/test/common/child.c index 7b64d81..872fbda 100644 --- a/test/common/child.c +++ b/test/common/child.c @@ -148,14 +148,26 @@ int reset_socket(ne_socket *sock) /* close 'sock', performing lingering close to avoid premature RST. */ static int close_socket(ne_socket *sock) { -#ifdef HAVE_SHUTDOWN + int ret; char buf[20]; - int fd = ne_sock_fd(sock); - - shutdown(fd, 0); + + ret = ne_sock_shutdown(sock, NE_SOCK_SEND); + if (ret == 0) { + NE_DEBUG(NE_DBG_SOCKET, "ssl: Socket cleanly closed.\n"); + } + else { + NE_DEBUG(NE_DBG_SOCKET, "sock: Socket closed uncleanly: %s\n", + ne_sock_error(sock)); + } + + NE_DEBUG(NE_DBG_SSL, "sock: Lingering close...\n"); + ne_sock_read_timeout(sock, 5); while (ne_sock_read(sock, buf, sizeof buf) > 0); -#endif - return ne_sock_close(sock); + + NE_DEBUG(NE_DBG_SSL, "sock: Closing socket.\n"); + ret = ne_sock_close(sock); + NE_DEBUG(NE_DBG_SSL, "sock: Socket closed (%d).\n", ret); + return ret; } /* This runs as the child process. */ diff --git a/test/socket.c b/test/socket.c index aaf87c2..d9be107 100644 --- a/test/socket.c +++ b/test/socket.c @@ -513,6 +513,8 @@ static int finish(ne_socket *sock, int eof) { if (eof) CALL(expect_close(sock)); + else + ne_sock_shutdown(sock, NE_SOCK_SEND); CALL(good_close(sock)); return await_server(); } @@ -1037,14 +1039,50 @@ static int echo_lines(void) } #ifdef SOCKET_SSL -/* harder to simulate closure cases for an SSL connection, since it - * may be doing multiple read()s or write()s per ne_sock_* call. */ +static int serve_wait_close(ne_socket *sock, void *ud) +{ + ONV(ne_sock_read(sock, buffer, 1) != NE_SOCK_CLOSED, + ("failed waiting for TLS closure: %s", ne_sock_error(sock))); + + return 0; +} + +static int ssl_shutdown(void) +{ + ne_socket *sock; + int ret; + + CALL(begin(&sock, serve_wait_close, NULL)); + + ONV(ne_sock_shutdown(sock, NE_SOCK_RECV) != NE_SOCK_RETRY, + ("TLS socket closed too early")); + + ret = ne_sock_shutdown(sock, NE_SOCK_SEND); + if (ret == NE_SOCK_RETRY) { + /* Wait for closure. */ + ret = ne_sock_read(sock, buffer, 0); + ONV(ret != NE_SOCK_CLOSED, + ("read for closure didn't get closure: %d/%s", + ret, ne_sock_error(sock))); + } + else { + ONV(ret, ("socket shutdown unexpected state: %d/%s", + ret, ne_sock_error(sock))); + } + + CALL(await_server()); + ne_sock_close(sock); + + return OK; +} + static int ssl_closure(void) { ne_socket *sock; ssize_t ret; CALL(begin(&sock, serve_close, NULL)); CALL(full_write(sock, "a", 1)); + ne_sock_shutdown(sock, NE_SOCK_SEND); CALL(await_server()); do { ret = ne_sock_fullwrite(sock, "a", 1); @@ -1175,6 +1213,32 @@ static int block_timeout(void) TO_FINISH; } +#ifndef SOCKET_SSL +/* Waits for EOF from read-side and then sends "abcd". */ +static int serve_shutdown(ne_socket *sock, void *userdata) +{ + ONV(ne_sock_read(sock, buffer, 1) != NE_SOCK_CLOSED, + ("expected to get closure")); + CALL(full_write(sock, "abcd", 4)); + return 0; +} + +static int bidi(void) +{ + ne_socket *sock; + + CALL(begin(&sock, serve_shutdown, NULL)); + + CALL(expect_block_timeout(sock, 1, "read should timeout before closure")); + + ONV(ne_sock_shutdown(sock, NE_SOCK_SEND) != 0, + ("shutdown failed: `%s'", ne_sock_error(sock))); + FULLREAD("abcd"); + + return finish(sock, 1); +} +#endif + static int ssl_session_id(void) { ne_socket *sock; @@ -1526,11 +1590,13 @@ ne_test tests[] = { T(prebind), T(error), #ifdef SOCKET_SSL + T(ssl_shutdown), T(ssl_closure), T(ssl_truncate), #else T(write_reset), T(read_reset), + T(bidi), #endif #if TEST_CONNECT_TIMEOUT T(connect_timeout), |