File neon-openssl3-socket-shutdown.patch of Package neon
From 01fd5ca9f09ba6c123f5665f00a3cf55a22c7c37 Mon Sep 17 00:00:00 2001
From: Joe Orton <jorton@redhat.com>
Date: Wed, 4 Aug 2021 16:24:26 +0100
Subject: [PATCH] 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.
---
diff -urp neon-0.30.2.orig/src/neon.vers neon-0.30.2/src/neon.vers
--- neon-0.30.2.orig/src/neon.vers 2023-12-06 14:07:32.755459942 -0600
+++ neon-0.30.2/src/neon.vers 2023-12-06 16:18:59.590549350 -0600
@@ -19,4 +19,5 @@ NEON_0_30 {
ne_addr_canonical;
ne_ssl_context_get_flag;
ne_set_addrlist2;
+ ne_sock_shutdown;
};
diff -urp neon-0.30.2.orig/src/ne_socket.c neon-0.30.2/src/ne_socket.c
--- neon-0.30.2.orig/src/ne_socket.c 2023-12-06 16:18:47.893820758 -0600
+++ neon-0.30.2/src/ne_socket.c 2023-12-06 16:18:59.590549350 -0600
@@ -615,7 +615,8 @@ static int error_ossl(ne_socket *sock, i
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;
}
@@ -1938,23 +1939,89 @@ void ne_sock_set_error(ne_socket *sock,
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
@@ -1963,6 +2030,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 -urp neon-0.30.2.orig/src/ne_socket.h neon-0.30.2/src/ne_socket.h
--- neon-0.30.2.orig/src/ne_socket.h 2023-12-06 14:07:32.758793293 -0600
+++ neon-0.30.2/src/ne_socket.h 2023-12-06 16:18:59.590549350 -0600
@@ -42,6 +42,8 @@ NE_BEGIN_DECLS
#define NE_SOCK_RESET (-4)
/* Secure connection was closed without proper SSL shutdown. */
#define NE_SOCK_TRUNC (-5)
+/* Retry operation later. */
+#define NE_SOCK_RETRY (-6)
/* ne_socket represents a TCP socket. */
typedef struct ne_socket_s ne_socket;
@@ -226,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 -urp neon-0.30.2.orig/test/common/child.c neon-0.30.2/test/common/child.c
--- neon-0.30.2.orig/test/common/child.c 2023-12-06 14:07:32.758793293 -0600
+++ neon-0.30.2/test/common/child.c 2023-12-06 16:18:59.590549350 -0600
@@ -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 -urp neon-0.30.2.orig/test/socket.c neon-0.30.2/test/socket.c
--- neon-0.30.2.orig/test/socket.c 2023-12-06 14:07:32.762126644 -0600
+++ neon-0.30.2/test/socket.c 2023-12-06 16:18:59.590549350 -0600
@@ -506,6 +506,8 @@ static int finish(ne_socket *sock, int e
{
if (eof)
CALL(expect_close(sock));
+ else
+ ne_sock_shutdown(sock, NE_SOCK_SEND);
CALL(good_close(sock));
return await_server();
}
@@ -1025,14 +1027,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);
@@ -1159,6 +1197,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;
@@ -1509,11 +1573,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),