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),
openSUSE Build Service is sponsored by