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,
+ ×tamping, 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;
}