summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Orton <jorton@redhat.com>2021-08-04 16:24:26 +0100
committerJoe Orton <jorton@apache.org>2021-08-04 16:59:51 +0100
commit01fd5ca9f09ba6c123f5665f00a3cf55a22c7c37 (patch)
tree140965dcc915e6b4afc015c7de96bc894dd8b523
parent1c36f97d784ffe0efa65a2b9aa143662d4f8a457 (diff)
downloadneon-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.c90
-rw-r--r--src/ne_socket.h32
-rw-r--r--src/neon.vers1
-rw-r--r--test/common/child.c24
-rw-r--r--test/socket.c70
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),