Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-hiredis for openSUSE:Factory 
checked in at 2026-10-26 10:00:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-hiredis (Old)
 and      /work/SRC/openSUSE:Factory/.python-hiredis.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-hiredis"

Mon Oct 26 10:00:44 2026 rev:19 rq:1392438 version:3.5.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-hiredis/python-hiredis.changes    
2026-10-06 19:16:07.266769297 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-hiredis.new.2252680/python-hiredis.changes   
    2026-10-26 10:01:57.198623240 +0100
@@ -1,0 +2,13 @@
+Sun Oct 25 14:27:18 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 3.5.0:
+  * Drop Python 3.8 support
+  * Bump hiredis to v1.5.0
+    The C parser picks up upstream fixes, including protection
+    against overflow of map elements (redis/hiredis#1323). The
+    nesting limit is now configurable upstream, but it still
+    defaults to 1024, so parsing behavior has not changed.
+  * Fix benchmarking for hiredis-py
+  * Remove outdated spell check workflow
+
+-------------------------------------------------------------------

Old:
----
  hiredis-3.4.2.tar.gz

New:
----
  hiredis-3.5.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-hiredis.spec ++++++
--- /var/tmp/diff_new_pack.Rejj9u/_old  2026-10-26 10:01:57.968655532 +0100
+++ /var/tmp/diff_new_pack.Rejj9u/_new  2026-10-26 10:01:57.971655658 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-hiredis
-Version:        3.4.2
+Version:        3.5.0
 Release:        0
 Summary:        Python wrapper for hiredis
 License:        BSD-3-Clause

++++++ hiredis-3.4.2.tar.gz -> hiredis-3.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/PKG-INFO new/hiredis-3.5.0/PKG-INFO
--- old/hiredis-3.4.2/PKG-INFO  2026-09-22 13:27:39.637731800 +0200
+++ new/hiredis-3.5.0/PKG-INFO  2026-10-15 18:50:54.015393000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: hiredis
-Version: 3.4.2
+Version: 3.5.0
 Summary: Python wrapper for hiredis
 Home-page: https://github.com/redis/hiredis-py
 Author: Jan-Erik Rediger, Pieter Noordhuis
@@ -17,16 +17,16 @@
 Classifier: Programming Language :: C
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: 3.15
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Topic :: Software Development
-Requires-Python: >=3.8
+Requires-Python: >=3.9
 Description-Content-Type: text/markdown
 License-File: LICENSE
 Dynamic: author
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/hiredis/version.py 
new/hiredis-3.5.0/hiredis/version.py
--- old/hiredis-3.4.2/hiredis/version.py        2026-09-22 13:27:32.000000000 
+0200
+++ new/hiredis-3.5.0/hiredis/version.py        2026-10-15 18:50:49.000000000 
+0200
@@ -1 +1 @@
-__version__ = "3.4.2"
+__version__ = "3.5.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/hiredis.egg-info/PKG-INFO 
new/hiredis-3.5.0/hiredis.egg-info/PKG-INFO
--- old/hiredis-3.4.2/hiredis.egg-info/PKG-INFO 2026-09-22 13:27:39.000000000 
+0200
+++ new/hiredis-3.5.0/hiredis.egg-info/PKG-INFO 2026-10-15 18:50:53.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: hiredis
-Version: 3.4.2
+Version: 3.5.0
 Summary: Python wrapper for hiredis
 Home-page: https://github.com/redis/hiredis-py
 Author: Jan-Erik Rediger, Pieter Noordhuis
@@ -17,16 +17,16 @@
 Classifier: Programming Language :: C
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: 3.15
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Topic :: Software Development
-Requires-Python: >=3.8
+Requires-Python: >=3.9
 Description-Content-Type: text/markdown
 License-File: LICENSE
 Dynamic: author
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/setup.py new/hiredis-3.5.0/setup.py
--- old/hiredis-3.4.2/setup.py  2026-09-22 13:27:32.000000000 +0200
+++ new/hiredis-3.5.0/setup.py  2026-10-15 18:50:49.000000000 +0200
@@ -61,7 +61,7 @@
     packages=["hiredis"],
     package_data={"hiredis": ["hiredis.pyi", "py.typed"]},
     ext_modules=[ext],
-    python_requires=">=3.8",
+    python_requires=">=3.9",
     project_urls={
         "Changes": "https://github.com/redis/hiredis-py/releases";,
         "Issue tracker": "https://github.com/redis/hiredis-py/issues";,
@@ -75,13 +75,13 @@
         'Programming Language :: C',
         'Programming Language :: Python :: 3',
         'Programming Language :: Python :: 3 :: Only',
-        'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3.10',
         'Programming Language :: Python :: 3.11',
         'Programming Language :: Python :: 3.12',
         'Programming Language :: Python :: 3.13',
         'Programming Language :: Python :: 3.14',
+        'Programming Language :: Python :: 3.15',
         'Programming Language :: Python :: Implementation :: CPython',
         'Topic :: Software Development',
     ],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/vendor/hiredis/hiredis.h 
new/hiredis-3.5.0/vendor/hiredis/hiredis.h
--- old/hiredis-3.4.2/vendor/hiredis/hiredis.h  2026-09-22 13:27:33.000000000 
+0200
+++ new/hiredis-3.5.0/vendor/hiredis/hiredis.h  2026-10-15 18:50:51.000000000 
+0200
@@ -46,9 +46,9 @@
 #include "alloc.h" /* for allocation wrappers */
 
 #define HIREDIS_MAJOR 1
-#define HIREDIS_MINOR 4
+#define HIREDIS_MINOR 5
 #define HIREDIS_PATCH 0
-#define HIREDIS_SONAME 1.4.0
+#define HIREDIS_SONAME 1.5.0
 
 /* Connection type can be blocking or non-blocking and is set in the
  * least significant bit of the flags field in redisContext. */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/vendor/hiredis/hiredis_ssl.h 
new/hiredis-3.5.0/vendor/hiredis/hiredis_ssl.h
--- old/hiredis-3.4.2/vendor/hiredis/hiredis_ssl.h      2026-09-22 
13:27:33.000000000 +0200
+++ new/hiredis-3.5.0/vendor/hiredis/hiredis_ssl.h      2026-10-15 
18:50:51.000000000 +0200
@@ -59,7 +59,8 @@
     REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED,   /* Failed to set client 
default certificate directory */
     REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED,      /* Failed to load private key 
*/
     REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED,     /* Failed to open system 
certificate store */
-    REDIS_SSL_CTX_OS_CERT_ADD_FAILED            /* Failed to add CA 
certificates obtained from system to the SSL context */
+    REDIS_SSL_CTX_OS_CERT_ADD_FAILED,           /* Failed to add CA 
certificates obtained from system to the SSL context */
+    REDIS_SSL_CTX_VERIFY_NAME_FAILED            /* Failed to set the expected 
peer certificate name for verification */
 } redisSSLContextError;
 
 /* Constants that mirror OpenSSL's verify modes. By default,
@@ -81,6 +82,21 @@
     const char *private_key_filename;
     const char *server_name;
     int verify_mode;
+    /* Expected peer certificate identity: a single DNS name (SAN, with CN as a
+     * fallback per OpenSSL rules) or IP address literal that the peer
+     * certificate must carry, verified as part of the TLS handshake. When
+     * NULL, only CA chain validation is performed and ANY certificate issued
+     * by a trusted CA is accepted, regardless of the identity it carries.
+     * Requires verify_mode to include REDIS_SSL_VERIFY_PEER: without it the
+     * handshake completes regardless of the verification result, so a name
+     * could not be enforced. Use NULL, not an empty string, to skip
+     * verification. Anything that would leave the name unenforced -- an empty
+     * name, peer verification disabled, or a build without name verification
+     * support -- is rejected with REDIS_SSL_CTX_VERIFY_NAME_FAILED rather than
+     * silently accepting any identity.
+     * Note that server_name is used for SNI routing only and does NOT verify
+     * the peer's identity. */
+    const char *verify_name;
 } redisSSLOptions;
 
 /**
@@ -113,7 +129,9 @@
  * be both specified or omitted.
  *
  * server_name is an optional and will be used as a server name indication
- * (SNI) TLS extension.
+ * (SNI) TLS extension. Note that it is NOT matched against the certificate
+ * the peer presents: to bind the connection to a specific peer identity, use
+ * redisCreateSSLContextWithOptions() with the verify_name option.
  *
  * If error is non-null, it will be populated in case the context creation 
fails
  * (returning a NULL).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/vendor/hiredis/net.c 
new/hiredis-3.5.0/vendor/hiredis/net.c
--- old/hiredis-3.4.2/vendor/hiredis/net.c      2026-09-22 13:27:33.000000000 
+0200
+++ new/hiredis-3.5.0/vendor/hiredis/net.c      2026-10-15 18:50:51.000000000 
+0200
@@ -53,6 +53,26 @@
 
 int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval 
*timeout);
 
+#ifdef __linux__
+static void redisDrainSocketErrorQueue(redisFD fd) {
+    int saved_errno = errno;
+
+    for (;;) {
+        char buf[1];
+        struct iovec iov = {.iov_base = buf, .iov_len = sizeof(buf)};
+        struct msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1};
+
+        if (recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT) >= 0)
+            continue;
+        if (errno == EINTR)
+            continue;
+        break;
+    }
+
+    errno = saved_errno;
+}
+#endif
+
 void redisNetClose(redisContext *c) {
     if (c && c->fd != REDIS_INVALID_FD) {
         close(c->fd);
@@ -312,35 +332,50 @@
     wfd.events = POLLOUT;
     end = msec >= 0 ? redisPollMillis() + msec : 0;
 
-    while ((res = poll(&wfd, 1, msec)) <= 0) {
+    for (;;) {
+        int completed = 0;
+
+        res = poll(&wfd, 1, msec);
         if (res < 0 && errno != EINTR) {
             __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
             redisNetClose(c);
             return REDIS_ERR;
-        } else if (res == 0 || (msec >= 0 && redisPollMillis() >= end)) {
+        }
+
+        if (res > 0) {
+            if (redisCheckConnectDone(c, &completed) != REDIS_OK) {
+                redisCheckSocketError(c);
+                return REDIS_ERR;
+            }
+            if (completed)
+                return REDIS_OK;
+        }
+
+        if (res == 0) {
             errno = ETIMEDOUT;
             __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
             redisNetClose(c);
             return REDIS_ERR;
-        } else {
-            /* res < 0 && errno == EINTR, try again */
         }
-    }
 
-    if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
-        redisCheckSocketError(c);
-        return REDIS_ERR;
-    }
+        if (msec >= 0) {
+            long now = redisPollMillis();
 
-    return REDIS_OK;
+            if (now >= end) {
+                errno = ETIMEDOUT;
+                __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
+                redisNetClose(c);
+                return REDIS_ERR;
+            }
+            msec = end - now;
+        }
+    }
 }
 
 int redisCheckConnectDone(redisContext *c, int *completed) {
     int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
-    if (rc == 0) {
-        *completed = 1;
-        return REDIS_OK;
-    }
+    if (rc == 0)
+        goto connected;
     int error = errno;
     if (error == EINPROGRESS) {
         /* must check error to see if connect failed.  Get the socket error */
@@ -350,8 +385,7 @@
         if (fail == 0) {
             if (so_error == 0) {
                 /* Socket is connected! */
-                *completed = 1;
-                return REDIS_OK;
+                goto connected;
             }
             /* connection error; */
             errno = so_error;
@@ -360,15 +394,26 @@
     }
     switch (error) {
     case EISCONN:
-        *completed = 1;
-        return REDIS_OK;
+        goto connected;
     case EALREADY:
     case EWOULDBLOCK:
+#ifdef __linux__
+        if (c->connection_type == REDIS_CONN_TCP)
+            redisDrainSocketErrorQueue(c->fd);
+#endif
         *completed = 0;
         return REDIS_OK;
     default:
         return REDIS_ERR;
     }
+
+connected:
+    *completed = 1;
+#ifdef __linux__
+    if (c->connection_type == REDIS_CONN_TCP)
+        redisDrainSocketErrorQueue(c->fd);
+#endif
+    return REDIS_OK;
 }
 
 int redisCheckSocketError(redisContext *c) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/vendor/hiredis/read.c 
new/hiredis-3.5.0/vendor/hiredis/read.c
--- old/hiredis-3.4.2/vendor/hiredis/read.c     2026-09-22 13:27:33.000000000 
+0200
+++ new/hiredis-3.5.0/vendor/hiredis/read.c     2026-10-15 18:50:51.000000000 
+0200
@@ -71,9 +71,6 @@
 /* Initial size of our nested reply stack and how much we grow it when needd */
 #define REDIS_READER_STACK_SIZE 9
 
-/* Maximum depth of nested aggregate replies. */
-#define REDIS_READER_MAX_REPLY_DEPTH 1024
-
 static void __redisReaderSetError(redisReader *r, int type, const char *str) {
     size_t len;
 
@@ -560,7 +557,7 @@
     long long elements;
     int root = 0, len;
 
-    if (r->ridx >= REDIS_READER_MAX_REPLY_DEPTH) {
+    if (r->maxdepth > 0 && r->ridx >= r->maxdepth) {
         __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
                 "Max nesting depth exceeded");
         return REDIS_ERR;
@@ -760,6 +757,7 @@
     r->fn = fn;
     r->maxbuf = REDIS_READER_MAX_BUF;
     r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS;
+    r->maxdepth = REDIS_READER_MAX_REPLY_DEPTH;
     r->ridx = -1;
 
     return r;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/vendor/hiredis/read.h 
new/hiredis-3.5.0/vendor/hiredis/read.h
--- old/hiredis-3.4.2/vendor/hiredis/read.h     2026-09-22 13:27:33.000000000 
+0200
+++ new/hiredis-3.5.0/vendor/hiredis/read.h     2026-10-15 18:50:51.000000000 
+0200
@@ -69,6 +69,9 @@
 /* Default multi-bulk element limit */
 #define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1)
 
+/* Default maximum depth of nested aggregate replies. */
+#define REDIS_READER_MAX_REPLY_DEPTH 1024
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -110,6 +113,8 @@
 
     redisReplyObjectFunctions *fn;
     void *privdata;
+
+    int maxdepth; /* Max nested aggregate reply depth */
 } redisReader;
 
 /* Public API for the protocol parser. */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/vendor/hiredis/ssl.c 
new/hiredis-3.5.0/vendor/hiredis/ssl.c
--- old/hiredis-3.4.2/vendor/hiredis/ssl.c      2026-09-22 13:27:33.000000000 
+0200
+++ new/hiredis-3.5.0/vendor/hiredis/ssl.c      2026-10-15 18:50:51.000000000 
+0200
@@ -36,6 +36,7 @@
 
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#include <openssl/x509v3.h>
 
 #include <assert.h>
 #include <errno.h>
@@ -60,6 +61,25 @@
 #include "hiredis_ssl.h"
 
 #define OPENSSL_1_1_0 0x10100000L
+#define OPENSSL_1_0_2 0x10002000L
+
+/* Peer certificate name verification (redisSSLOptions.verify_name) relies on
+ * the X509_VERIFY_PARAM host/IP API, available since OpenSSL 1.0.2. Building
+ * against an older OpenSSL is a hard error by default, so a build cannot
+ * silently lack the check. Defining HIREDIS_NO_PEER_NAME_VERIFICATION compiles
+ * the feature out: it is both the required opt-out for building against an
+ * older OpenSSL and a way to disable the feature on any OpenSSL version. When
+ * compiled out, requesting verify_name fails context creation (fail closed)
+ * rather than silently skipping the check. */
+#if defined(HIREDIS_NO_PEER_NAME_VERIFICATION)
+#define HIREDIS_SSL_SUPPORTS_VERIFY_NAME 0
+#elif OPENSSL_VERSION_NUMBER >= OPENSSL_1_0_2
+#define HIREDIS_SSL_SUPPORTS_VERIFY_NAME 1
+#else
+#error "redisSSLOptions.verify_name requires OpenSSL 1.0.2 or newer. Build 
against a \
+newer OpenSSL, or define HIREDIS_NO_PEER_NAME_VERIFICATION to build without 
peer \
+certificate name verification."
+#endif
 
 void __redisSetError(redisContext *c, int type, const char *str);
 
@@ -198,6 +218,8 @@
             return "Failed to open system certificate store";
         case REDIS_SSL_CTX_OS_CERT_ADD_FAILED:
             return "Failed to add CA certificates obtained from system to the 
SSL context";
+        case REDIS_SSL_CTX_VERIFY_NAME_FAILED:
+            return "Failed to set the expected peer certificate name for 
verification";
         default:
             return "Unknown error code";
     }
@@ -242,6 +264,51 @@
     return redisCreateSSLContextWithOptions(&options, error);
 }
 
+#if HIREDIS_SSL_SUPPORTS_VERIFY_NAME
+/* Bind the SSL_CTX to an expected peer identity: every certificate accepted
+ * through this context must carry `name` (a DNS name matched against the SAN
+ * entries, with CN as a fallback per OpenSSL rules, or an IP address literal
+ * matched against IP SAN entries) in addition to chaining to a trusted CA.
+ * Returns 1 on success, 0 on failure. */
+static int redisSSLContextSetVerifyName(SSL_CTX *ssl_ctx, const char *name) {
+    /* No certificate can carry an empty identity, and OpenSSL reads an empty
+     * name as "clear the expected hosts" and reports success, which would 
leave
+     * the context with CA-only validation while the caller believes an 
identity
+     * was bound. A verification request that cannot be honored must fail, as 
it
+     * does when the feature is compiled out; pass NULL to skip verification. 
*/
+    if (name[0] == '\0')
+        return 0;
+
+    /* A name can only be enforced if the peer certificate is verified at all.
+     * Under SSL_VERIFY_NONE OpenSSL still records a mismatch as the verify
+     * result but completes the handshake anyway, and this path never inspects
+     * that result, so a wrong-name certificate would be accepted while the
+     * caller believes the name is protecting them. Note REDIS_SSL_VERIFY_NONE
+     * is 0, so a zero-initialized redisSSLOptions lands here. Refuse rather
+     * than silently not enforce. */
+    if (!(SSL_CTX_get_verify_mode(ssl_ctx) & SSL_VERIFY_PEER))
+        return 0;
+
+    X509_VERIFY_PARAM *param = SSL_CTX_get0_param(ssl_ctx);
+
+    /* Reject partial-label wildcards (e.g. "f*.example.com"); a full-label
+     * "*.example.com" still matches. */
+    X509_VERIFY_PARAM_set_hostflags(param, 
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+
+    /* An IP address literal must be matched against IP SAN entries, which
+     * X509_VERIFY_PARAM_set1_host() does not do. set1_ip_asc() doubles as the
+     * IP literal detector: it fails on anything that does not parse as an
+     * IPv4/IPv6 address, in which case `name` is treated as a DNS name. */
+    if (X509_VERIFY_PARAM_set1_ip_asc(param, name) == 1)
+        return 1;
+
+    /* Pass the length explicitly rather than 0: OpenSSL reads 0 as "call
+     * strlen", but BoringSSL rejects a zero length outright, which would fail
+     * every DNS name. */
+    return X509_VERIFY_PARAM_set1_host(param, name, strlen(name));
+}
+#endif
+
 redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, 
redisSSLContextError *error) {
     const char *cacert_filename = options->cacert_filename;
     const char *capath = options->capath;
@@ -279,6 +346,22 @@
 
     SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL);
 
+    if (options->verify_name) {
+#if HIREDIS_SSL_SUPPORTS_VERIFY_NAME
+        if (!redisSSLContextSetVerifyName(ctx->ssl_ctx, options->verify_name)) 
{
+            if (error) *error = REDIS_SSL_CTX_VERIFY_NAME_FAILED;
+            goto error;
+        }
+#else
+        /* Peer name verification is compiled out
+         * (HIREDIS_NO_PEER_NAME_VERIFICATION): an explicit verification
+         * request that cannot be honored must fail, not silently degrade to
+         * CA-only validation. */
+        if (error) *error = REDIS_SSL_CTX_VERIFY_NAME_FAILED;
+        goto error;
+#endif
+    }
+
     if ((cert_filename != NULL && private_key_filename == NULL) ||
             (private_key_filename != NULL && cert_filename == NULL)) {
         if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
@@ -287,7 +370,8 @@
 
     if (capath || cacert_filename) {
 #ifdef _WIN32
-        if (0 == strcmp(cacert_filename, "wincert")) {
+        /* wincert is a filename sentinel. capath only callers pass NULL here. 
*/
+        if (cacert_filename && 0 == strcmp(cacert_filename, "wincert")) {
             win_store = CertOpenSystemStore(NULL, "Root");
             if (!win_store) {
                 if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED;
@@ -394,8 +478,11 @@
             snprintf(err,sizeof(err)-1,"SSL_connect failed: 
%s",strerror(errno));
         else {
             unsigned long e = ERR_peek_last_error();
+            const char *reason = ERR_reason_error_string(e);
+            /* The error queue may be empty, e.g. when the handshake fails on
+             * a socket read that timed out; don't print "(null)". */
             snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
-                    ERR_reason_error_string(e));
+                    reason ? reason : "unknown error");
         }
         __redisSetError(c, REDIS_ERR_IO, err);
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hiredis-3.4.2/vendor/hiredis/test.c 
new/hiredis-3.5.0/vendor/hiredis/test.c
--- old/hiredis-3.4.2/vendor/hiredis/test.c     2026-09-22 13:27:33.000000000 
+0200
+++ new/hiredis-3.5.0/vendor/hiredis/test.c     2026-10-15 18:50:51.000000000 
+0200
@@ -13,6 +13,16 @@
 #include <limits.h>
 #include <math.h>
 
+#ifdef __linux__
+#include <arpa/inet.h>
+#include <linux/errqueue.h>
+#include <linux/net_tstamp.h>
+#include <netinet/in.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
 #include "hiredis.h"
 #include "async.h"
 #include "adapters/poll.h"
@@ -52,6 +62,7 @@
         const char *ca_cert;
         const char *cert;
         const char *key;
+        const char *verify_name;
     } ssl;
 };
 
@@ -476,7 +487,7 @@
     redisReaderFree(reader);
 
     reader = redisReaderCreate();
-    test("Can handle deeply nested multi-bulks: ");
+    test("Can handle arbitrarily nested multi-bulks: ");
     for (i = 0; i < 128; i++) {
         redisReaderFeed(reader,(char*)"*1\r\n", 4);
     }
@@ -487,7 +498,7 @@
         ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
         ((redisReply*)reply)->elements == 1);
 
-    test("Can parse deeply nested multi-bulks correctly: ");
+    test("Can parse arbitrarily nested multi-bulks correctly: ");
     while(i--) {
         assert(reply != NULL && ((redisReply*)reply)->type == 
REDIS_REPLY_ARRAY);
         reply = ((redisReply*)reply)->element[0];
@@ -497,10 +508,15 @@
     freeReplyObject(root);
     redisReaderFree(reader);
 
+    test("Reader sets a default maximum nesting depth: ");
+    reader = redisReaderCreate();
+    test_cond(reader->maxdepth == REDIS_READER_MAX_REPLY_DEPTH);
+    redisReaderFree(reader);
+
     test("Can parse a reply at the maximum nesting depth: ");
     reader = redisReaderCreate();
     reader->fn = NULL;
-    for (i = 0; i < 1024; i++) {
+    for (i = 0; i < REDIS_READER_MAX_REPLY_DEPTH; i++) {
         redisReaderFeed(reader,(char*)"*1\r\n",4);
     }
     redisReaderFeed(reader,(char*)"+OK\r\n",5);
@@ -508,9 +524,9 @@
     test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_ARRAY);
     redisReaderFree(reader);
 
-    test("Set error when nesting depth exceeds the maximum: ");
+    test("Set error when nesting depth exceeds the configured maximum: ");
     reader = redisReaderCreate();
-    for (i = 0; i <= 1024; i++) {
+    for (i = 0; i <= REDIS_READER_MAX_REPLY_DEPTH; i++) {
         redisReaderFeed(reader,(char*)"*1\r\n",4);
     }
     redisReaderFeed(reader,(char*)"+OK\r\n",5);
@@ -520,6 +536,18 @@
     freeReplyObject(reply);
     redisReaderFree(reader);
 
+    test("Can disable the maximum nesting depth: ");
+    reader = redisReaderCreate();
+    reader->maxdepth = 0;
+    reader->fn = NULL;
+    for (i = 0; i <= REDIS_READER_MAX_REPLY_DEPTH; i++) {
+        redisReaderFeed(reader,(char*)"*1\r\n",4);
+    }
+    redisReaderFeed(reader,(char*)"+OK\r\n",5);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_ARRAY);
+    redisReaderFree(reader);
+
     test("Correctly parses LLONG_MAX: ");
     reader = redisReaderCreate();
     redisReaderFeed(reader, ":9223372036854775807\r\n",22);
@@ -1064,6 +1092,158 @@
 #endif
 }
 
+#ifdef __linux__
+static uint32_t wait_for_epollerr(int epoll_fd) {
+    struct epoll_event event;
+    int count;
+
+    do {
+        count = epoll_wait(epoll_fd, &event, 1, 2000);
+    } while (count == -1 && errno == EINTR);
+
+    assert(count == 1);
+    assert(event.events & EPOLLERR);
+    return event.events;
+}
+
+static void consume_timestamp_error(redisFD fd) {
+    union {
+        struct cmsghdr align;
+        unsigned char data[512];
+    } control;
+    struct sock_extended_err *socket_error = NULL;
+    struct msghdr message = {0};
+    struct iovec iov;
+    struct cmsghdr *cmsg;
+    char byte;
+
+    iov.iov_base = &byte;
+    iov.iov_len = sizeof(byte);
+    message.msg_iov = &iov;
+    message.msg_iovlen = 1;
+    message.msg_control = control.data;
+    message.msg_controllen = sizeof(control.data);
+
+    assert(recvmsg(fd, &message, MSG_ERRQUEUE | MSG_DONTWAIT) >= 0);
+    assert(!(message.msg_flags & MSG_CTRUNC));
+    for (cmsg = CMSG_FIRSTHDR(&message); cmsg != NULL;
+         cmsg = CMSG_NXTHDR(&message, cmsg)) {
+        if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) {
+            socket_error = (struct sock_extended_err *)CMSG_DATA(cmsg);
+            break;
+        }
+    }
+    assert(socket_error != NULL);
+    assert(socket_error->ee_origin == SO_EE_ORIGIN_TIMESTAMPING);
+}
+
+static void test_nonblocking_connect_error_queue(void) {
+    const int timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
+                             SOF_TIMESTAMPING_SOFTWARE |
+                             SOF_TIMESTAMPING_OPT_TSONLY;
+    const char pong[] = "+PONG\r\n";
+    struct sockaddr_in address = {0};
+    struct epoll_event event = {0};
+    redisContext *context;
+    redisReply *reply = NULL;
+    socklen_t address_length = sizeof(address);
+    socklen_t option_length;
+    char byte = 'x';
+    int listener_fd;
+    int peer_fd;
+    int epoll_fd;
+    int completed = 0;
+    int option;
+    int count;
+    ssize_t result;
+
+    listener_fd = socket(AF_INET, SOCK_STREAM, 0);
+    assert(listener_fd != -1);
+    address.sin_family = AF_INET;
+    address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+    address.sin_port = 0;
+    assert(bind(listener_fd, (struct sockaddr *)&address, sizeof(address)) == 
0);
+    assert(listen(listener_fd, 1) == 0);
+    assert(getsockname(listener_fd, (struct sockaddr *)&address,
+                       &address_length) == 0);
+
+    context = redisConnectNonBlock("127.0.0.1", ntohs(address.sin_port));
+    assert(context != NULL && context->err == 0);
+    peer_fd = accept(listener_fd, NULL, NULL);
+    assert(peer_fd != -1);
+
+    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+    assert(epoll_fd != -1);
+    event.events = EPOLLIN;
+    event.data.fd = context->fd;
+    assert(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, context->fd, &event) == 0);
+    count = setsockopt(context->fd, SOL_SOCKET, SO_TIMESTAMPING,
+                       &timestamping, sizeof(timestamping));
+    if (count == -1 && errno == ENOPROTOOPT) {
+        test("Nonblocking connect error queue (SO_TIMESTAMPING unavailable): 
");
+        test_skipped();
+        goto cleanup;
+    }
+    assert(count == 0);
+
+    /* Timestamp records use the same sk_error_queue occupancy predicate as
+     * queued ICMP errors, without requiring raw sockets or a network race. */
+    assert(send(context->fd, &byte, sizeof(byte), 0) == (ssize_t)sizeof(byte));
+    assert(recv(peer_fd, &byte, sizeof(byte), 0) == (ssize_t)sizeof(byte));
+    (void)wait_for_epollerr(epoll_fd);
+    consume_timestamp_error(context->fd);
+    assert(epoll_wait(epoll_fd, &event, 1, 0) == 0);
+
+    assert(send(context->fd, &byte, sizeof(byte), 0) == (ssize_t)sizeof(byte));
+    assert(recv(peer_fd, &byte, sizeof(byte), 0) == (ssize_t)sizeof(byte));
+    (void)wait_for_epollerr(epoll_fd);
+
+    option = 0;
+    assert(setsockopt(context->fd, SOL_SOCKET, SO_TIMESTAMPING,
+                      &option, sizeof(option)) == 0);
+    option_length = sizeof(option);
+    assert(getsockopt(context->fd, IPPROTO_IP, IP_RECVERR,
+                      &option, &option_length) == 0 && option == 0);
+    option_length = sizeof(option);
+    assert(getsockopt(context->fd, SOL_SOCKET, SO_TIMESTAMPING,
+                      &option, &option_length) == 0 && option == 0);
+    option_length = sizeof(option);
+    assert(getsockopt(context->fd, SOL_SOCKET, SO_ERROR,
+                      &option, &option_length) == 0 && option == 0);
+    errno = 0;
+    result = recv(context->fd, &byte, sizeof(byte), MSG_DONTWAIT);
+    assert(result == -1 && (errno == EAGAIN || errno == EWOULDBLOCK));
+    assert(wait_for_epollerr(epoll_fd) & EPOLLERR);
+
+    context->flags |= REDIS_BLOCK;
+    assert(redisCheckConnectDone(context, &completed) == REDIS_OK);
+    context->flags &= ~REDIS_BLOCK;
+    assert(completed == 1);
+    count = epoll_wait(epoll_fd, &event, 1, 0);
+    test("Successful connect drains the error queue in blocking mode: ");
+    test_cond(count == 0);
+
+    assert(send(peer_fd, pong, sizeof(pong) - 1, 0) ==
+           (ssize_t)(sizeof(pong) - 1));
+    do {
+        count = epoll_wait(epoll_fd, &event, 1, 2000);
+    } while (count == -1 && errno == EINTR);
+    assert(count == 1 && (event.events & EPOLLIN));
+    assert(redisBufferRead(context) == REDIS_OK);
+    assert(redisGetReplyFromReader(context, (void **)&reply) == REDIS_OK);
+    test("Connection with a queued error remains usable: ");
+    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS &&
+              strcmp(reply->str, "PONG") == 0);
+
+cleanup:
+    freeReplyObject(reply);
+    close(epoll_fd);
+    close(peer_fd);
+    close(listener_fd);
+    redisFree(context);
+}
+#endif
+
 /* Test push handler */
 void push_handler(void *privdata, void *r) {
     struct pushCounters *pcounts = privdata;
@@ -1390,6 +1570,14 @@
         test_skipped();
     }
 
+    /* The 10ms timeout is only needed by the timeout tests above; if it stays
+     * on the context, redisReconnect() re-applies it to the new socket and the
+     * TLS handshake in do_reconnect() must then finish every read within it,
+     * which is flaky on slow machines. */
+    tv.tv_sec = 1;
+    tv.tv_usec = 0;
+    redisSetTimeout(c, tv);
+
     test("Reconnect properly reconnects after a timeout: ");
     do_reconnect(c, config);
     reply = redisCommand(c, "PING");
@@ -2377,6 +2565,111 @@
 }
 /* End of Async polling_adapter driven tests */
 
+#ifdef HIREDIS_TEST_SSL
+/* Connect to the SSL port with a context bound to an expected peer name.
+ * Returns the (possibly errored) context; the caller inspects c->err. */
+static redisContext *do_ssl_connect_with_verify_name(struct config config, 
const char *verify_name) {
+    redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
+    redisSSLOptions options = {
+        .cacert_filename = config.ssl.ca_cert,
+        .cert_filename = config.ssl.cert,
+        .private_key_filename = config.ssl.key,
+        .verify_mode = REDIS_SSL_VERIFY_PEER,
+        .verify_name = verify_name,
+    };
+
+    redisSSLContext *ssl_ctx = redisCreateSSLContextWithOptions(&options, 
&ssl_error);
+    assert(ssl_ctx != NULL && ssl_error == REDIS_SSL_CTX_NONE);
+
+    redisContext *c = redisConnect(config.ssl.host, config.ssl.port);
+    assert(c != NULL && c->err == 0);
+    redisInitiateSSLWithContext(c, ssl_ctx);
+
+    redisFreeSSLContext(ssl_ctx);
+    return c;
+}
+
+/* Peer-name verification (redisSSLOptions.verify_name) tests. The server
+ * certificate generated by test.sh carries subjectAltName entries for
+ * config.ssl.verify_name (a DNS name) and config.ssl.host (an IP address), so
+ * a context bound to either must connect, while a context bound to a name the
+ * certificate does not carry must fail its handshake even though the
+ * certificate chains to the trusted CA. The mismatch cases are the
+ * impersonation scenario this option exists to close: without verify_name, ANY
+ * certificate from the trusted CA is accepted regardless of the identity it
+ * carries.
+ *
+ * Both polarities are covered for each kind of name, because DNS names and IP
+ * addresses are matched by different OpenSSL machinery (X509_check_host()
+ * against DNS SANs versus X509_check_ip() against IP SANs). The positive IP
+ * case proves the IP path is taken at all -- an IP handed to
+ * X509_VERIFY_PARAM_set1_host() would be compared against DNS entries and
+ * fail -- while the negative IP case proves that path actually compares the
+ * address rather than merely accepting any certificate that happens to carry
+ * an IP SAN. */
+static void test_ssl_verify_name(struct config config) {
+    redisContext *c;
+    redisReply *reply;
+
+    test("SSL verify_name accepts a certificate carrying the expected DNS 
name: ");
+    c = do_ssl_connect_with_verify_name(config, config.ssl.verify_name);
+    reply = redisCommand(c, "PING");
+    test_cond(c->err == 0 && reply != NULL && reply->type == 
REDIS_REPLY_STATUS &&
+              strcasecmp(reply->str, "pong") == 0);
+    freeReplyObject(reply);
+    redisFree(c);
+
+    test("SSL verify_name accepts a certificate carrying the expected IP 
address: ");
+    c = do_ssl_connect_with_verify_name(config, config.ssl.host);
+    reply = redisCommand(c, "PING");
+    test_cond(c->err == 0 && reply != NULL && reply->type == 
REDIS_REPLY_STATUS &&
+              strcasecmp(reply->str, "pong") == 0);
+    freeReplyObject(reply);
+    redisFree(c);
+
+    test("SSL verify_name rejects a trusted certificate lacking the expected 
name: ");
+    c = do_ssl_connect_with_verify_name(config, "wrong.example.com");
+    test_cond(c->err != 0);
+    redisFree(c);
+
+    /* A well-formed address that the certificate does not carry: the IP SAN
+     * present in the certificate must not satisfy a different expected IP. */
+    test("SSL verify_name rejects a trusted certificate lacking the expected 
IP address: ");
+    c = do_ssl_connect_with_verify_name(config, "10.99.99.99");
+    test_cond(c->err != 0);
+    redisFree(c);
+
+    /* An empty name is not a usable identity, and OpenSSL reads it as "clear
+     * the expected hosts" while reporting success. Context creation must 
reject
+     * it rather than hand back a context that silently validates only the CA
+     * chain; callers skip verification by passing NULL. */
+    test("SSL verify_name rejects an empty expected name: ");
+    redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
+    redisSSLOptions options = {
+        .cacert_filename = config.ssl.ca_cert,
+        .cert_filename = config.ssl.cert,
+        .private_key_filename = config.ssl.key,
+        .verify_mode = REDIS_SSL_VERIFY_PEER,
+        .verify_name = "",
+    };
+    redisSSLContext *ssl_ctx = redisCreateSSLContextWithOptions(&options, 
&ssl_error);
+    test_cond(ssl_ctx == NULL && ssl_error == 
REDIS_SSL_CTX_VERIFY_NAME_FAILED);
+    redisFreeSSLContext(ssl_ctx);
+
+    /* A name cannot be enforced while peer verification is disabled: OpenSSL
+     * completes the handshake regardless of the verification result, so a
+     * wrong-name certificate would be accepted. REDIS_SSL_VERIFY_NONE is 0, so
+     * this is also what a zero-initialized redisSSLOptions asks for. */
+    test("SSL verify_name is rejected when peer verification is disabled: ");
+    ssl_error = REDIS_SSL_CTX_NONE;
+    options.verify_mode = REDIS_SSL_VERIFY_NONE;
+    options.verify_name = config.ssl.verify_name;
+    ssl_ctx = redisCreateSSLContextWithOptions(&options, &ssl_error);
+    test_cond(ssl_ctx == NULL && ssl_error == 
REDIS_SSL_CTX_VERIFY_NAME_FAILED);
+    redisFreeSSLContext(ssl_ctx);
+}
+#endif
+
 int main(int argc, char **argv) {
     struct config cfg = {
         .tcp = {
@@ -2426,6 +2719,9 @@
         } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
             argv++; argc--;
             cfg.ssl.key = argv[0];
+        } else if (argc >= 2 && !strcmp(argv[0],"--ssl-verify-name")) {
+            argv++; argc--;
+            cfg.ssl.verify_name = argv[0];
 #endif
         } else {
             fprintf(stderr, "Invalid argument: %s\n", argv[0]);
@@ -2451,6 +2747,9 @@
     test_reply_reader();
     test_blocking_connection_errors();
     test_free_null();
+#ifdef __linux__
+    test_nonblocking_connect_error_queue();
+#endif
 
     printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, 
cfg.tcp.port);
     cfg.type = CONN_TCP;
@@ -2493,6 +2792,9 @@
         test_append_formatted_commands(cfg);
         if (throughput) test_throughput(cfg);
 
+        if (cfg.ssl.verify_name)
+            test_ssl_verify_name(cfg);
+
         redisFreeSSLContext(_ssl_ctx);
         _ssl_ctx = NULL;
     }

Reply via email to