From 0c1454490669f3c95e3c8b0ac6a83582d14e30e0 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 27 Nov 2017 13:10:21 +0000 Subject: Initial SSL (sync) implementation --- Makefile | 16 +++-- hiredis.c | 73 +++++++++++++++-------- hiredis.h | 15 +++++ sslio.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sslio.h | 48 +++++++++++++++ 5 files changed, 322 insertions(+), 27 deletions(-) create mode 100644 sslio.c create mode 100644 sslio.h diff --git a/Makefile b/Makefile index 07b8a83..ea96419 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ # Copyright (C) 2010-2011 Pieter Noordhuis # This file is released under the BSD license, see the COPYING file -OBJ=net.o hiredis.o sds.o async.o read.o -EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib +OBJ=net.o hiredis.o sds.o async.o read.o sslio.o +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-ssl TESTS=hiredis-test LIBNAME=libhiredis PKGCONFNAME=hiredis.pc @@ -53,6 +53,10 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) +OPENSSL_PREFIX=/usr/local/opt/openssl +CFLAGS+=-I$(OPENSSL_PREFIX)/include +LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ifeq ($(uname_S),SunOS) @@ -70,10 +74,11 @@ all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) # Deps (use make dep to generate this) async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h dict.o: dict.c fmacros.h dict.h -hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h sslio.h net.o: net.c fmacros.h net.h hiredis.h read.h sds.h read.o: read.c fmacros.h read.h sds.h sds.o: sds.c sds.h +sslio.o: sslio.c sslio.h hiredis.h test.o: test.c fmacros.h hiredis.h read.h sds.h $(DYLIBNAME): $(OBJ) @@ -101,6 +106,9 @@ hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) +hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + ifndef AE_DIR hiredis-example-ae: @echo "Please specify AE_DIR (e.g. /src)" @@ -158,7 +166,7 @@ clean: rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov dep: - $(CC) -MM *.c + $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c INSTALL?= cp -pPR diff --git a/hiredis.c b/hiredis.c index bfbf483..fae8094 100644 --- a/hiredis.c +++ b/hiredis.c @@ -42,6 +42,7 @@ #include "hiredis.h" #include "net.h" #include "sds.h" +#include "sslio.h" static redisReply *createReplyObject(int type); static void *createStringObject(const redisReadTask *task, char *str, size_t len); @@ -614,7 +615,9 @@ void redisFree(redisContext *c) { free(c->unix_sock.path); free(c->timeout); free(c->saddr); - free(c); + if (c->ssl) { + redisFreeSsl(c->ssl); + } } int redisFreeKeepFd(redisContext *c) { @@ -760,6 +763,11 @@ redisContext *redisConnectFd(int fd) { return c; } +int redisSecureConnection(redisContext *c, const char *caPath, + const char *certPath, const char *keyPath) { + return redisSslCreate(c, caPath, certPath, keyPath); +} + /* Set read/write timeout on a blocking socket. */ int redisSetTimeout(redisContext *c, const struct timeval tv) { if (c->flags & REDIS_BLOCK) @@ -774,6 +782,24 @@ int redisEnableKeepAlive(redisContext *c) { return REDIS_OK; } +static int rawRead(redisContext *c, char *buf, size_t bufcap) { + int nread = read(c->fd, buf, bufcap); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } else if (nread == 0) { + __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); + return -1; + } else { + return nread; + } +} + /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * @@ -787,26 +813,33 @@ int redisBufferRead(redisContext *c) { if (c->err) return REDIS_ERR; - nread = read(c->fd,buf,sizeof(buf)); - if (nread == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); + nread = c->flags & REDIS_SSL ? + redisSslRead(c, buf, sizeof(buf)) : rawRead(c, buf, sizeof(buf)); + if (nread > 0) { + if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { + __redisSetError(c, c->reader->err, c->reader->errstr); return REDIS_ERR; + } else { } - } else if (nread == 0) { - __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + } else if (nread < 0) { return REDIS_ERR; - } else { - if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } } return REDIS_OK; } +static int rawWrite(redisContext *c) { + int nwritten = write(c->fd, c->obuf, sdslen(c->obuf)); + if (nwritten < 0) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } + return nwritten; +} + /* Write the output buffer to the socket. * * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was @@ -817,21 +850,15 @@ int redisBufferRead(redisContext *c) { * c->errstr to hold the appropriate error string. */ int redisBufferWrite(redisContext *c, int *done) { - int nwritten; /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; if (sdslen(c->obuf) > 0) { - nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); - if (nwritten == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } + int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : rawWrite(c); + if (nwritten < 0) { + return REDIS_ERR; } else if (nwritten > 0) { if (nwritten == (signed)sdslen(c->obuf)) { sdsfree(c->obuf); diff --git a/hiredis.h b/hiredis.h index 1b0d5e6..29c0253 100644 --- a/hiredis.h +++ b/hiredis.h @@ -74,6 +74,9 @@ /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDIS_REUSEADDR 0x80 +/* Flag that is set when this connection is done through SSL */ +#define REDIS_SSL 0x100 + #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and @@ -112,6 +115,8 @@ enum redisConnectionType { REDIS_CONN_UNIX }; +struct redisSsl; + /* Context for a connection to Redis */ typedef struct redisContext { int err; /* Error flags, 0 when there is no error */ @@ -137,6 +142,9 @@ typedef struct redisContext { /* For non-blocking connect */ struct sockadr *saddr; size_t addrlen; + /* For SSL communication */ + struct redisSsl *ssl; + } redisContext; redisContext *redisConnect(const char *ip, int port); @@ -151,6 +159,13 @@ redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectFd(int fd); +/** + * Secure the connection using SSL. This should be done before any command is + * executed on the connection. + */ +int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, + const char *keypath); + /** * Reconnect the given context using the saved information. * diff --git a/sslio.c b/sslio.c new file mode 100644 index 0000000..9ee015b --- /dev/null +++ b/sslio.c @@ -0,0 +1,197 @@ +#include "hiredis.h" +#include "sslio.h" + +#include +#ifndef HIREDIS_NOSSL +#include + +void __redisSetError(redisContext *c, int type, const char *str); + +static void sslLogCallback(const SSL *ssl, int where, int ret) { + const char *retstr = ""; + int should_log = 1; + /* Ignore low-level SSL stuff */ + + if (where & SSL_CB_ALERT) { + should_log = 1; + } + if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) { + should_log = 1; + } + if ((where & SSL_CB_EXIT) && ret == 0) { + should_log = 1; + } + + if (!should_log) { + return; + } + + retstr = SSL_alert_type_string(ret); + printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr); + + if (where == SSL_CB_HANDSHAKE_DONE) { + printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl)); + } +} + +typedef pthread_mutex_t sslLockType; +static void sslLockInit(sslLockType *l) { + pthread_mutex_init(l, NULL); +} +static void sslLockAcquire(sslLockType *l) { + pthread_mutex_lock(l); +} +static void sslLockRelease(sslLockType *l) { + pthread_mutex_unlock(l); +} +static pthread_mutex_t *ossl_locks; + +static void opensslDoLock(int mode, int lkid, const char *f, int line) { + sslLockType *l = ossl_locks + lkid; + + if (mode & CRYPTO_LOCK) { + sslLockAcquire(l); + } else { + sslLockRelease(l); + } + + (void)f; + (void)line; +} + +static void initOpensslLocks(void) { + unsigned ii, nlocks; + if (CRYPTO_get_locking_callback() != NULL) { + /* Someone already set the callback before us. Don't destroy it! */ + return; + } + nlocks = CRYPTO_num_locks(); + ossl_locks = malloc(sizeof(*ossl_locks) * nlocks); + for (ii = 0; ii < nlocks; ii++) { + sslLockInit(ossl_locks + ii); + } + CRYPTO_set_locking_callback(opensslDoLock); +} + +void redisFreeSsl(redisSsl *ssl){ + if (ssl->ctx) { + SSL_CTX_free(ssl->ctx); + } + if (ssl->ssl) { + SSL_free(ssl->ssl); + } + free(ssl); +} + +int redisSslCreate(redisContext *c, const char *capath, const char *certpath, + const char *keypath) { + assert(!c->ssl); + c->ssl = calloc(1, sizeof(*c->ssl)); + static int isInit = 0; + if (!isInit) { + isInit = 1; + SSL_library_init(); + initOpensslLocks(); + } + + redisSsl *s = c->ssl; + s->ctx = SSL_CTX_new(SSLv23_client_method()); + SSL_CTX_set_info_callback(s->ctx, sslLogCallback); + SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL); + + if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) { + __redisSetError(c, REDIS_ERR, "certpath and keypath must be specified together"); + return REDIS_ERR; + } + + if (capath) { + if (!SSL_CTX_load_verify_locations(s->ctx, capath, NULL)) { + __redisSetError(c, REDIS_ERR, "Invalid CA certificate"); + return REDIS_ERR; + } + } + if (certpath) { + if (!SSL_CTX_use_certificate_chain_file(s->ctx, certpath)) { + __redisSetError(c, REDIS_ERR, "Invalid client certificate"); + return REDIS_ERR; + } + if (!SSL_CTX_use_PrivateKey_file(s->ctx, keypath, SSL_FILETYPE_PEM)) { + __redisSetError(c, REDIS_ERR, "Invalid client key"); + return REDIS_ERR; + } + printf("Loaded certificate!\n"); + } + + s->ssl = SSL_new(s->ctx); + if (!s->ssl) { + __redisSetError(c, REDIS_ERR, "Couldn't create new SSL instance"); + return REDIS_ERR; + } + + SSL_set_fd(s->ssl, c->fd); + SSL_set_connect_state(s->ssl); + + c->flags |= REDIS_SSL; + printf("Before SSL_connect()\n"); + int rv = SSL_connect(c->ssl->ssl); + if (rv == 1) { + printf("SSL_connect() success!\n"); + return REDIS_OK; + } + printf("ConnectRV: %d\n", rv); + + rv = SSL_get_error(s->ssl, rv); + if (((c->flags & REDIS_BLOCK) == 0) && + (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { + return REDIS_OK; + } + + if (c->err == 0) { + __redisSetError(c, REDIS_ERR_IO, "SSL_connect() failed"); + printf("rv: %d\n", rv); + } + printf("ERROR!!!\n"); + return REDIS_ERR; +} + +int redisSslRead(redisContext *c, char *buf, size_t bufcap) { + int nread = SSL_read(c->ssl->ssl, buf, bufcap); + if (nread > 0) { + return nread; + } else if (nread == 0) { + __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); + return -1; + } else { + int err = SSL_get_error(c->ssl->ssl, nread); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } +} + +int redisSslWrite(redisContext *c) { + size_t len = c->ssl->lastLen ? c->ssl->lastLen : sdslen(c->obuf); + int rv = SSL_write(c->ssl->ssl, c->obuf, len); + + if (rv > 0) { + c->ssl->lastLen = 0; + } else if (rv < 0) { + c->ssl->lastLen = len; + + int err = SSL_get_error(c->ssl->ssl, rv); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } + return rv; +} + +#endif diff --git a/sslio.h b/sslio.h new file mode 100644 index 0000000..a410cb3 --- /dev/null +++ b/sslio.h @@ -0,0 +1,48 @@ +#ifndef REDIS_SSLIO_H +#define REDIS_SSLIO_H + + +#ifdef HIREDIS_NOSSL +typedef struct redisSsl { + int dummy; +} redisSsl; +static void redisFreeSsl(redisSsl *) { +} +static int redisSslCreate(struct redisContext *c) { + return REDIS_ERR; +} +static int redisSslRead(struct redisContect *c, char *s, size_t, n) { + return -1; +} +static int redisSslWrite(struct redisContext *c) { + return -1; +} +#else +#include + +/** + * This file contains routines for HIREDIS' SSL + */ + +typedef struct redisSsl { + SSL *ssl; + SSL_CTX *ctx; + + /** + * SSL_write() requires to be called again with the same arguments it was + * previously called with in the event of an SSL_read/SSL_write situation + */ + size_t lastLen; +} redisSsl; + +struct redisContext; + +void redisFreeSsl(redisSsl *); +int redisSslCreate(struct redisContext *c, const char *caPath, + const char *certPath, const char *keyPath); + +int redisSslRead(struct redisContext *c, char *buf, size_t bufcap); +int redisSslWrite(struct redisContext *c); + +#endif /* !HIREDIS_NOSSL */ +#endif /* HIREDIS_SSLIO_H */ -- cgit v1.2.3 From 5f50eb4131c98cb7c418c107317373292ad6ae6c Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 27 Nov 2017 09:24:44 -0500 Subject: Remove extra printfs --- sslio.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sslio.c b/sslio.c index 9ee015b..6958d37 100644 --- a/sslio.c +++ b/sslio.c @@ -7,6 +7,9 @@ void __redisSetError(redisContext *c, int type, const char *str); +/** + * Callback used for debugging + */ static void sslLogCallback(const SSL *ssl, int where, int ret) { const char *retstr = ""; int should_log = 1; @@ -96,7 +99,7 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, redisSsl *s = c->ssl; s->ctx = SSL_CTX_new(SSLv23_client_method()); - SSL_CTX_set_info_callback(s->ctx, sslLogCallback); + /* SSL_CTX_set_info_callback(s->ctx, sslLogCallback); */ SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL); @@ -121,7 +124,6 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, __redisSetError(c, REDIS_ERR, "Invalid client key"); return REDIS_ERR; } - printf("Loaded certificate!\n"); } s->ssl = SSL_new(s->ctx); @@ -134,13 +136,10 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, SSL_set_connect_state(s->ssl); c->flags |= REDIS_SSL; - printf("Before SSL_connect()\n"); int rv = SSL_connect(c->ssl->ssl); if (rv == 1) { - printf("SSL_connect() success!\n"); return REDIS_OK; } - printf("ConnectRV: %d\n", rv); rv = SSL_get_error(s->ssl, rv); if (((c->flags & REDIS_BLOCK) == 0) && @@ -150,9 +149,7 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, if (c->err == 0) { __redisSetError(c, REDIS_ERR_IO, "SSL_connect() failed"); - printf("rv: %d\n", rv); } - printf("ERROR!!!\n"); return REDIS_ERR; } -- cgit v1.2.3 From ba947bc93cc262c37db181f4684db1c37e803d6a Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 27 Nov 2017 09:24:53 -0500 Subject: Add SSL example --- examples/example-ssl.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 examples/example-ssl.c diff --git a/examples/example-ssl.c b/examples/example-ssl.c new file mode 100644 index 0000000..28489e4 --- /dev/null +++ b/examples/example-ssl.c @@ -0,0 +1,92 @@ +#include +#include +#include + +#include + +int main(int argc, char **argv) { + unsigned int j; + redisContext *c; + redisReply *reply; + if (argc < 4) { + printf("Usage: %s [ca]\n", argv[0]); + exit(1); + } + const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = atoi(argv[2]); + const char *cert = argv[3]; + const char *key = argv[4]; + const char *ca = argc > 4 ? argv[5] : NULL; + + struct timeval timeout = { 1, 500000 }; // 1.5 seconds + c = redisConnectWithTimeout(hostname, port, timeout); + if (c == NULL || c->err) { + if (c) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + } else { + printf("Connection error: can't allocate redis context\n"); + } + exit(1); + } + + if (redisSecureConnection(c, ca, cert, key) != REDIS_OK) { + printf("Couldn't initialize SSL!\n"); + printf("Error: %s\n", c->errstr); + redisFree(c); + exit(1); + } + + /* PING server */ + reply = redisCommand(c,"PING"); + printf("PING: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key */ + reply = redisCommand(c,"SET %s %s", "foo", "hello world"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key using binary safe API */ + reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); + printf("SET (binary API): %s\n", reply->str); + freeReplyObject(reply); + + /* Try a GET and two INCR */ + reply = redisCommand(c,"GET foo"); + printf("GET foo: %s\n", reply->str); + freeReplyObject(reply); + + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + /* again ... */ + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + + /* Create a list of numbers, from 0 to 9 */ + reply = redisCommand(c,"DEL mylist"); + freeReplyObject(reply); + for (j = 0; j < 10; j++) { + char buf[64]; + + snprintf(buf,64,"%u",j); + reply = redisCommand(c,"LPUSH mylist element-%s", buf); + freeReplyObject(reply); + } + + /* Let's check what we have inside the list */ + reply = redisCommand(c,"LRANGE mylist 0 -1"); + if (reply->type == REDIS_REPLY_ARRAY) { + for (j = 0; j < reply->elements; j++) { + printf("%u) %s\n", j, reply->element[j]->str); + } + } + freeReplyObject(reply); + + /* Disconnects and frees the context */ + redisFree(c); + + return 0; +} -- cgit v1.2.3 From 08efa46599410e6b56ab19ed1c9a72c67476db9c Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 27 Nov 2017 15:49:28 -0500 Subject: SSL for async I/O --- Makefile | 6 ++- async.c | 92 +++++++++++++++++++++++++++++++++++++++++ examples/example-libevent-ssl.c | 72 ++++++++++++++++++++++++++++++++ sslio.c | 22 ++++++++-- sslio.h | 9 ++++ 5 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 examples/example-libevent-ssl.c diff --git a/Makefile b/Makefile index ea96419..b723245 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,8 @@ # This file is released under the BSD license, see the COPYING file OBJ=net.o hiredis.o sds.o async.o read.o sslio.o -EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-ssl +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib \ + hiredis-example-ssl hiredis-example-libevent-ssl TESTS=hiredis-test LIBNAME=libhiredis PKGCONFNAME=hiredis.pc @@ -94,6 +95,9 @@ static: $(STLIBNAME) hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) +hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) diff --git a/async.c b/async.c index 0cecd30..5a14d45 100644 --- a/async.c +++ b/async.c @@ -40,6 +40,7 @@ #include "net.h" #include "dict.c" #include "sds.h" +#include "sslio.h" #define _EL_ADD_READ(ctx) do { \ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ @@ -524,6 +525,87 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { } } +#ifndef HIREDIS_NOSSL +/** + * Handle SSL when socket becomes available for reading. This also handles + * read-while-write and write-while-read + */ +static void asyncSslRead(redisAsyncContext *ac) { + int rv; + redisSsl *ssl = ac->c.ssl; + redisContext *c = &ac->c; + + ssl->wantRead = 0; + + if (ssl->pendingWrite) { + int done; + + /* This is probably just a write event */ + ssl->pendingWrite = 0; + rv = redisBufferWrite(c, &done); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + return; + } else if (!done) { + _EL_ADD_WRITE(ac); + } + } + + rv = redisBufferRead(c); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +/** + * Handle SSL when socket becomes available for writing + */ +static void asyncSslWrite(redisAsyncContext *ac) { + int rv, done = 0; + redisSsl *ssl = ac->c.ssl; + redisContext *c = &ac->c; + + ssl->pendingWrite = 0; + rv = redisBufferWrite(c, &done); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + return; + } + + if (!done) { + if (ssl->wantRead) { + /* Need to read-before-write */ + ssl->pendingWrite = 1; + _EL_DEL_WRITE(ac); + } else { + /* No extra reads needed, just need to write more */ + _EL_ADD_WRITE(ac); + } + } else { + /* Already done! */ + _EL_DEL_WRITE(ac); + } + + /* Always reschedule a read */ + _EL_ADD_READ(ac); +} +#else + +/* Just so we're able to compile */ +static void asyncSslRead(redisAsyncContext *ac) { + abort(); + (void)ac; +} +static void asyncSslWrite(redisAsyncContext *ac) { + abort(); + (void)ac; +} + +#endif + /* This function should be called when the socket is readable. * It processes all replies that can be read and executes their callbacks. */ @@ -539,6 +621,11 @@ void redisAsyncHandleRead(redisAsyncContext *ac) { return; } + if (c->flags & REDIS_SSL) { + asyncSslRead(ac); + return; + } + if (redisBufferRead(c) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { @@ -561,6 +648,11 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { return; } + if (c->flags & REDIS_SSL) { + asyncSslWrite(ac); + return; + } + if (redisBufferWrite(c,&done) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { diff --git a/examples/example-libevent-ssl.c b/examples/example-libevent-ssl.c new file mode 100644 index 0000000..f780e3e --- /dev/null +++ b/examples/example-libevent-ssl.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + struct event_base *base = event_base_new(); + if (argc < 5) { + fprintf(stderr, + "Usage: %s [ca]\n", argv[0]); + exit(1); + } + + const char *value = argv[1]; + size_t nvalue = strlen(value); + + const char *hostname = argv[2]; + int port = atoi(argv[3]); + + const char *cert = argv[4]; + const char *certKey = argv[5]; + const char *caCert = argc > 5 ? argv[6] : NULL; + + redisAsyncContext *c = redisAsyncConnect(hostname, port); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + if (redisSecureConnection(&c->c, caCert, cert, certKey) != REDIS_OK) { + printf("SSL Error!\n"); + exit(1); + } + + redisLibeventAttach(c,base); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + event_base_dispatch(base); + return 0; +} diff --git a/sslio.c b/sslio.c index 6958d37..3b08140 100644 --- a/sslio.c +++ b/sslio.c @@ -99,7 +99,7 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, redisSsl *s = c->ssl; s->ctx = SSL_CTX_new(SSLv23_client_method()); - /* SSL_CTX_set_info_callback(s->ctx, sslLogCallback); */ + SSL_CTX_set_info_callback(s->ctx, sslLogCallback); SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL); @@ -153,6 +153,22 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, return REDIS_ERR; } +static int maybeCheckWant(redisSsl *rssl, int rv) { + /** + * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set + * and true is returned. False is returned otherwise + */ + if (rv == SSL_ERROR_WANT_READ) { + rssl->wantRead = 1; + return 1; + } else if (rv == SSL_ERROR_WANT_WRITE) { + rssl->pendingWrite = 1; + return 1; + } else { + return 0; + } +} + int redisSslRead(redisContext *c, char *buf, size_t bufcap) { int nread = SSL_read(c->ssl->ssl, buf, bufcap); if (nread > 0) { @@ -162,7 +178,7 @@ int redisSslRead(redisContext *c, char *buf, size_t bufcap) { return -1; } else { int err = SSL_get_error(c->ssl->ssl, nread); - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + if (maybeCheckWant(c->ssl, err)) { return 0; } else { __redisSetError(c, REDIS_ERR_IO, NULL); @@ -181,7 +197,7 @@ int redisSslWrite(redisContext *c) { c->ssl->lastLen = len; int err = SSL_get_error(c->ssl->ssl, rv); - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + if (maybeCheckWant(c->ssl, err)) { return 0; } else { __redisSetError(c, REDIS_ERR_IO, NULL); diff --git a/sslio.h b/sslio.h index a410cb3..1f46b03 100644 --- a/sslio.h +++ b/sslio.h @@ -33,6 +33,15 @@ typedef struct redisSsl { * previously called with in the event of an SSL_read/SSL_write situation */ size_t lastLen; + + /** Whether the SSL layer requires read (possibly before a write) */ + int wantRead; + + /** + * Whether a write was requested prior to a read. If set, the write() + * should resume whenever a read takes place, if possible + */ + int pendingWrite; } redisSsl; struct redisContext; -- cgit v1.2.3 From 82549a53de4483b368008485d74028f302fd0e60 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 8 Jan 2018 16:07:10 -0500 Subject: Disable SSL by default --- Makefile | 8 +++++--- sslio.c | 2 +- sslio.h | 21 ++++++++++++++------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index b723245..f7ed675 100644 --- a/Makefile +++ b/Makefile @@ -54,9 +54,11 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) -OPENSSL_PREFIX=/usr/local/opt/openssl -CFLAGS+=-I$(OPENSSL_PREFIX)/include -LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto +ifdef USE_SSL + OPENSSL_PREFIX=/usr/local/opt/openssl + CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL + LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto +endif # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') diff --git a/sslio.c b/sslio.c index 3b08140..721a63a 100644 --- a/sslio.c +++ b/sslio.c @@ -2,7 +2,7 @@ #include "sslio.h" #include -#ifndef HIREDIS_NOSSL +#ifdef HIREDIS_SSL #include void __redisSetError(redisContext *c, int type, const char *str); diff --git a/sslio.h b/sslio.h index 1f46b03..9edac87 100644 --- a/sslio.h +++ b/sslio.h @@ -2,19 +2,26 @@ #define REDIS_SSLIO_H -#ifdef HIREDIS_NOSSL +#ifndef HIREDIS_SSL typedef struct redisSsl { - int dummy; + size_t lastLen; + int wantRead; + int pendingWrite; } redisSsl; -static void redisFreeSsl(redisSsl *) { +static inline void redisFreeSsl(redisSsl *ssl) { + (void)ssl; } -static int redisSslCreate(struct redisContext *c) { +static inline int redisSslCreate(struct redisContext *c, const char *ca, + const char *cert, const char *key) { + (void)c;(void)ca;(void)cert;(void)key; return REDIS_ERR; } -static int redisSslRead(struct redisContect *c, char *s, size_t, n) { +static inline int redisSslRead(struct redisContext *c, char *s, size_t n) { + (void)c;(void)s;(void)n; return -1; } -static int redisSslWrite(struct redisContext *c) { +static inline int redisSslWrite(struct redisContext *c) { + (void)c; return -1; } #else @@ -53,5 +60,5 @@ int redisSslCreate(struct redisContext *c, const char *caPath, int redisSslRead(struct redisContext *c, char *buf, size_t bufcap); int redisSslWrite(struct redisContext *c); -#endif /* !HIREDIS_NOSSL */ +#endif /* HIREDIS_SSL */ #endif /* HIREDIS_SSLIO_H */ -- cgit v1.2.3 From bc2a8f372a9f073e2fc00c34ba11cb3cb671f252 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 8 Jan 2018 16:09:53 -0500 Subject: Minor SSL-related fixes --- Makefile | 4 +++- sslio.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f7ed675..7e3e2bf 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,9 @@ STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) ifdef USE_SSL - OPENSSL_PREFIX=/usr/local/opt/openssl + # This is the prefix of openssl on my system. This should be the sane default + # based on the platform + OPENSSL_PREFIX?=/usr/local/opt/openssl CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto endif diff --git a/sslio.c b/sslio.c index 721a63a..8933f95 100644 --- a/sslio.c +++ b/sslio.c @@ -57,7 +57,7 @@ static void opensslDoLock(int mode, int lkid, const char *f, int line) { } else { sslLockRelease(l); } - + (void)f; (void)line; } -- cgit v1.2.3 From 4127e4488bcebceac59aefe6013e3f854b192394 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Tue, 9 Jan 2018 06:57:17 -0500 Subject: Don't add dead code for HIREDIS_NOSSL We changed this to `HIREDIS_SSL` --- async.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/async.c b/async.c index 5a14d45..db59036 100644 --- a/async.c +++ b/async.c @@ -525,10 +525,12 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { } } -#ifndef HIREDIS_NOSSL /** * Handle SSL when socket becomes available for reading. This also handles - * read-while-write and write-while-read + * read-while-write and write-while-read. + * + * These functions will not work properly unless `HIREDIS_SSL` is defined + * (however, they will compile) */ static void asyncSslRead(redisAsyncContext *ac) { int rv; @@ -592,19 +594,6 @@ static void asyncSslWrite(redisAsyncContext *ac) { /* Always reschedule a read */ _EL_ADD_READ(ac); } -#else - -/* Just so we're able to compile */ -static void asyncSslRead(redisAsyncContext *ac) { - abort(); - (void)ac; -} -static void asyncSslWrite(redisAsyncContext *ac) { - abort(); - (void)ac; -} - -#endif /* This function should be called when the socket is readable. * It processes all replies that can be read and executes their callbacks. -- cgit v1.2.3 From d329cc952a61fc937c2bfe6887ed6d64d763f4f3 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 21 May 2018 10:35:16 -0400 Subject: Use SSL by default --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 7e3e2bf..b2a9fb5 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,8 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) +USE_SSL:=1 + ifdef USE_SSL # This is the prefix of openssl on my system. This should be the sane default # based on the platform -- cgit v1.2.3 From e4a78006e73fada8084b15618b7ba269fdda9237 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 5 Dec 2018 20:28:13 -0500 Subject: Provide option-struct initialization This reduces the boilerplate of all the redisConnectXXX functions, and allows us to provide more connection options in the future. --- hiredis.c | 151 ++++++++++++++++++++++++++++++-------------------------------- hiredis.h | 45 ++++++++++++++++++- 2 files changed, 117 insertions(+), 79 deletions(-) diff --git a/hiredis.c b/hiredis.c index fae8094..d33e393 100644 --- a/hiredis.c +++ b/hiredis.c @@ -584,14 +584,21 @@ redisReader *redisReaderCreate(void) { return redisReaderCreateWithFunctions(&defaultFunctions); } -static redisContext *redisContextInit(void) { +static redisContext *redisContextInit(const redisOptions *options) { redisContext *c; - c = calloc(1,sizeof(redisContext)); + c = calloc(1, sizeof(*c)); if (c == NULL) return NULL; + c->err = 0; + c->errstr[0] = '\0'; c->obuf = sdsempty(); + c->flags = 0; + c->tcp.host = NULL; + c->tcp.source_addr = NULL; + c->unix_sock.path = NULL; + c->timeout = NULL; c->reader = redisReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { @@ -655,112 +662,100 @@ int redisReconnect(redisContext *c) { return REDIS_ERR; } +redisContext *redisConnectWithOptions(const redisOptions *options) { + redisContext *c = redisContextInit(options); + if (c == NULL) { + return NULL; + } + if (!(options->options & REDIS_OPT_NONBLOCK)) { + c->flags |= REDIS_BLOCK; + } + if (options->options & REDIS_OPT_REUSEADDR) { + c->flags |= REDIS_REUSEADDR; + } + + if (options->type == REDIS_CONN_TCP) { + redisContextConnectBindTcp(c, options->endpoint.tcp.ip, + options->endpoint.tcp.port, options->timeout, + options->endpoint.tcp.source_addr); + } else if (options->type == REDIS_CONN_UNIX) { + redisContextConnectUnix(c, options->endpoint.unix_socket, + options->timeout); + } else if (options->type == REDIS_CONN_USERFD) { + c->fd = options->endpoint.fd; + c->flags |= REDIS_CONNECTED; + } else { + // Unknown type - FIXME - FREE + return NULL; + } + return c; +} + /* Connect to a Redis instance. On error the field error in the returned * context will be set to the return value of the error function. * When no set of reply functions is given, the default set will be used. */ redisContext *redisConnect(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + return redisConnectWithOptions(&options); } redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,&tv); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.timeout = &tv; + return redisConnectWithOptions(&options); } redisContext *redisConnectNonBlock(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr) { - redisContext *c = redisContextInit(); - if (c == NULL) - return NULL; - c->flags &= ~REDIS_BLOCK; - redisContextConnectBindTcp(c,ip,port,NULL,source_addr); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr) { - redisContext *c = redisContextInit(); - if (c == NULL) - return NULL; - c->flags &= ~REDIS_BLOCK; - c->flags |= REDIS_REUSEADDR; - redisContextConnectBindTcp(c,ip,port,NULL,source_addr); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR; + return redisConnectWithOptions(&options); } redisContext *redisConnectUnix(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + return redisConnectWithOptions(&options); } redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,&tv); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + options.timeout = &tv; + return redisConnectWithOptions(&options); } redisContext *redisConnectUnixNonBlock(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } redisContext *redisConnectFd(int fd) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->fd = fd; - c->flags |= REDIS_BLOCK | REDIS_CONNECTED; - return c; + redisOptions options = {REDIS_CONN_USERFD}; + options.endpoint.fd = fd; + return redisConnectWithOptions(&options); } int redisSecureConnection(redisContext *c, const char *caPath, diff --git a/hiredis.h b/hiredis.h index 29c0253..621ea5b 100644 --- a/hiredis.h +++ b/hiredis.h @@ -112,11 +112,53 @@ void redisFreeSdsCommand(sds cmd); enum redisConnectionType { REDIS_CONN_TCP, - REDIS_CONN_UNIX + REDIS_CONN_UNIX, + REDIS_CONN_USERFD }; struct redisSsl; +#define REDIS_OPT_NONBLOCK 0x01 +#define REDIS_OPT_REUSEADDR 0x02 + +typedef struct { + /* + * the type of connection to use. This also indicates which + * `endpoint` member field to use + */ + int type; + /* bit field of REDIS_OPT_xxx */ + int options; + /* timeout value. if NULL, no timeout is used */ + const struct timeval *timeout; + union { + /** use this field for tcp/ip connections */ + struct { + const char *source_addr; + const char *ip; + int port; + } tcp; + /** use this field for unix domain sockets */ + const char *unix_socket; + /** + * use this field to have hiredis operate an already-open + * file descriptor */ + int fd; + } endpoint; +} redisOptions; + +/** + * Helper macros to initialize options to their specified fields. + */ +#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ + (opts)->type = REDIS_CONN_TCP; \ + (opts)->endpoint.tcp.ip = ip_; \ + (opts)->endpoint.tcp.port = port; + +#define REDIS_OPTIONS_SET_UNIX(opts, path) \ + (opts)->type = REDIS_CONN_UNIX; \ + (opts)->endpoint.unix_socket = path; + /* Context for a connection to Redis */ typedef struct redisContext { int err; /* Error flags, 0 when there is no error */ @@ -147,6 +189,7 @@ typedef struct redisContext { } redisContext; +redisContext *redisConnectWithOptions(const redisOptions *options); redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectNonBlock(const char *ip, int port); -- cgit v1.2.3 From 35a0a1f369cdeb8d7d93fda8278099d7aa60aee6 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 8 Mar 2018 11:21:07 +0200 Subject: read/write timeouts --- adapters/libevent.h | 79 ++++++++++++++++++++++++++++++++++++++--------------- async.c | 62 ++++++++++++++++++++++++++++++++++++++--- async.h | 5 ++++ read.h | 1 + 4 files changed, 121 insertions(+), 26 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 7d2bef1..9843696 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -36,48 +36,81 @@ typedef struct redisLibeventEvents { redisAsyncContext *context; - struct event *rev, *wev; + struct event *ev, *tmr; + struct event_base *base; + struct timeval tv; + short flags; } redisLibeventEvents; -static void redisLibeventReadEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); +static void redisLibeventHandler(int fd, short event, void *arg) { + ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleRead(e->context); + if (event & EV_TIMEOUT) { + redisAsyncHandleTimeout(e->context); + } + if (e->context && (event & EV_READ)) { + redisAsyncHandleRead(e->context); + } + if (e->context && (event & EV_WRITE)) { + redisAsyncHandleWrite(e->context); + } } -static void redisLibeventWriteEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); - redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleWrite(e->context); +static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { + redisLibeventEvents *e = (redisLibeventEvents *)privdata; + const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL; + + if (isRemove) { + if ((e->flags & flag) == 0) { + return; + } else { + e->flags &= ~flag; + } + } else { + if (e->flags & flag) { + return; + } else { + e->flags |= flag; + } + } + + event_del(e->ev); + event_assign(e->ev, e->base, e->context->c.fd, e->flags, + redisLibeventHandler, privdata); + event_add(e->ev, tv); } static void redisLibeventAddRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(e->rev,NULL); + redisLibeventUpdate(privdata, EV_READ, 0); } static void redisLibeventDelRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(e->rev); + redisLibeventUpdate(privdata, EV_READ, 1); } static void redisLibeventAddWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(e->wev,NULL); + redisLibeventUpdate(privdata, EV_WRITE, 0); } static void redisLibeventDelWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(e->wev); + redisLibeventUpdate(privdata, EV_WRITE, 1); } static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_free(e->rev); - event_free(e->wev); + event_free(e->ev); free(e); } +static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { + redisLibeventEvents *e = (redisLibeventEvents *)privdata; + short flags = e->flags; + e->flags = 0; + e->tv = tv; + event_del(e->ev); + redisLibeventUpdate(e, flags, 0); +} + static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { redisContext *c = &(ac->c); redisLibeventEvents *e; @@ -96,13 +129,15 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { ac->ev.addWrite = redisLibeventAddWrite; ac->ev.delWrite = redisLibeventDelWrite; ac->ev.cleanup = redisLibeventCleanup; + ac->ev.scheduleTimer = redisLibeventSetTimeout; ac->ev.data = e; /* Initialize and install read/write events */ - e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e); - e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e); - event_add(e->rev, NULL); - event_add(e->wev, NULL); + e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); + e->flags = 0; + e->base = base; + e->tv.tv_sec = 0; + e->tv.tv_usec = 0; return REDIS_OK; } #endif diff --git a/async.c b/async.c index db59036..b789f93 100644 --- a/async.c +++ b/async.c @@ -42,15 +42,19 @@ #include "sds.h" #include "sslio.h" -#define _EL_ADD_READ(ctx) do { \ +#define _EL_ADD_READ(ctx) \ + do { \ + refreshTimeout(ctx); \ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ - } while(0) + } while (0) #define _EL_DEL_READ(ctx) do { \ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ } while(0) -#define _EL_ADD_WRITE(ctx) do { \ +#define _EL_ADD_WRITE(ctx) \ + do { \ + refreshTimeout(ctx); \ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ - } while(0) + } while (0) #define _EL_DEL_WRITE(ctx) do { \ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ } while(0) @@ -58,6 +62,19 @@ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ } while(0); +static void refreshTimeout(redisAsyncContext *ctx) { + if (ctx->c.timeout && ctx->ev.scheduleTimer && + (ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) { + ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout); + // } else { + // printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout); + // if (ctx->c.timeout){ + // printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec, + // ctx->c.timeout->tv_usec); + // } + } +} + /* Forward declaration of function in hiredis.c */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); @@ -656,6 +673,30 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { } } +void __redisSetError(redisContext *c, int type, const char *str); + +void redisAsyncHandleTimeout(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + + if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) { + /* Nothing to do - just an idle timeout */ + return; + } + + if (!c->err) { + __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); + } + + if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { + ac->onConnect(ac, REDIS_ERR); + } + + while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { + __redisRunCallback(ac, &cb, NULL); + } +} + /* Sets a pointer to the first argument and its length starting at p. Returns * the number of bytes to skip to get to the following argument. */ static const char *nextArgument(const char *start, const char **str, size_t *len) { @@ -795,3 +836,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); return status; } + +void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { + if (!ac->c.timeout) { + ac->c.timeout = calloc(1, sizeof(tv)); + } + + if (tv.tv_sec == ac->c.timeout->tv_sec && + tv.tv_usec == ac->c.timeout->tv_usec) { + return; + } + + *ac->c.timeout = tv; +} \ No newline at end of file diff --git a/async.h b/async.h index 740555c..3d51a35 100644 --- a/async.h +++ b/async.h @@ -57,6 +57,7 @@ typedef struct redisCallbackList { /* Connection callback prototypes */ typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); +typedef void(redisTimerCallback)(void *timer, void *privdata); /* Context for an async connection to Redis */ typedef struct redisAsyncContext { @@ -81,6 +82,7 @@ typedef struct redisAsyncContext { void (*addWrite)(void *privdata); void (*delWrite)(void *privdata); void (*cleanup)(void *privdata); + void (*scheduleTimer)(void *privdata, struct timeval tv); } ev; /* Called when either the connection is terminated due to an error or per @@ -113,12 +115,15 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectUnix(const char *path); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); + +void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac); /* Handle read/write events */ void redisAsyncHandleRead(redisAsyncContext *ac); void redisAsyncHandleWrite(redisAsyncContext *ac); +void redisAsyncHandleTimeout(redisAsyncContext *ac); /* Command functions for an async context. Write the command to the * output buffer and register the provided callback. */ diff --git a/read.h b/read.h index 2988aa4..0894b78 100644 --- a/read.h +++ b/read.h @@ -45,6 +45,7 @@ #define REDIS_ERR_EOF 3 /* End of file */ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */ #define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_TIMEOUT 6 /* Timed out */ #define REDIS_ERR_OTHER 2 /* Everything else... */ #define REDIS_REPLY_STRING 1 -- cgit v1.2.3 From deba8d956d578547319919adedda4714f779dbb9 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 6 Dec 2018 04:54:05 -0500 Subject: Allow connectWithOptions for async as well --- async.c | 52 ++++++++++++++++++++++++---------------------------- async.h | 1 + 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/async.c b/async.c index b789f93..a32d528 100644 --- a/async.c +++ b/async.c @@ -168,56 +168,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) { ac->errstr = c->errstr; } -redisAsyncContext *redisAsyncConnect(const char *ip, int port) { +redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) { + redisOptions myOptions = *options; redisContext *c; redisAsyncContext *ac; - c = redisConnectNonBlock(ip,port); - if (c == NULL) + myOptions.options |= REDIS_OPT_NONBLOCK; + c = redisConnectWithOptions(&myOptions); + if (c == NULL) { return NULL; - + } ac = redisAsyncInitialize(c); if (ac == NULL) { redisFree(c); return NULL; } - __redisAsyncCopyError(ac); return ac; } +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + return redisAsyncConnectWithOptions(&options); +} + redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr) { - redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); - redisAsyncContext *ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + return redisAsyncConnectWithOptions(&options); } redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, const char *source_addr) { - redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); - redisAsyncContext *ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.options |= REDIS_OPT_REUSEADDR; + options.endpoint.tcp.source_addr = source_addr; + return redisAsyncConnectWithOptions(&options); } redisAsyncContext *redisAsyncConnectUnix(const char *path) { - redisContext *c; - redisAsyncContext *ac; - - c = redisConnectUnixNonBlock(path); - if (c == NULL) - return NULL; - - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - - __redisAsyncCopyError(ac); - return ac; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + return redisAsyncConnectWithOptions(&options); } int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { diff --git a/async.h b/async.h index 3d51a35..40a1819 100644 --- a/async.h +++ b/async.h @@ -108,6 +108,7 @@ typedef struct redisAsyncContext { } redisAsyncContext; /* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options); redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, -- cgit v1.2.3 From 53d9b12b76858bb236113628ad8585abbccd9f87 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 6 Dec 2018 04:54:29 -0500 Subject: Fix bug in options macro --- hiredis.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiredis.h b/hiredis.h index 621ea5b..bb9b6a7 100644 --- a/hiredis.h +++ b/hiredis.h @@ -153,7 +153,7 @@ typedef struct { #define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ (opts)->type = REDIS_CONN_TCP; \ (opts)->endpoint.tcp.ip = ip_; \ - (opts)->endpoint.tcp.port = port; + (opts)->endpoint.tcp.port = port_; #define REDIS_OPTIONS_SET_UNIX(opts, path) \ (opts)->type = REDIS_CONN_UNIX; \ -- cgit v1.2.3 From 7b705936f66f34f79ec0bc45c6c7cf4ab40296e7 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 6 Dec 2018 04:55:47 -0500 Subject: libevent-example: Use timeout --- examples/example-libevent.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/examples/example-libevent.c b/examples/example-libevent.c index d333c22..1fe71ae 100644 --- a/examples/example-libevent.c +++ b/examples/example-libevent.c @@ -9,7 +9,12 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; - if (reply == NULL) return; + if (reply == NULL) { + if (c->errstr) { + printf("errstr: %s\n", c->errstr); + } + return; + } printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ @@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) { int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); struct event_base *base = event_base_new(); + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); + struct timeval tv = {0}; + tv.tv_sec = 1; + options.timeout = &tv; + - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + redisAsyncContext *c = redisAsyncConnectWithOptions(&options); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); -- cgit v1.2.3 From f4f6b6d65c83df53ac4e7a6730440ea7af368fcd Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 12 Dec 2018 11:36:52 -0500 Subject: minor fixes: initialize options struct with 0 always also, clean up redisContextInit -- we're just zeoring the struct --- hiredis.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/hiredis.c b/hiredis.c index d33e393..7ba51f6 100644 --- a/hiredis.c +++ b/hiredis.c @@ -591,21 +591,14 @@ static redisContext *redisContextInit(const redisOptions *options) { if (c == NULL) return NULL; - c->err = 0; - c->errstr[0] = '\0'; c->obuf = sdsempty(); - c->flags = 0; - c->tcp.host = NULL; - c->tcp.source_addr = NULL; - c->unix_sock.path = NULL; - c->timeout = NULL; c->reader = redisReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { redisFree(c); return NULL; } - + (void)options; /* options are used in other functions */ return c; } @@ -753,7 +746,8 @@ redisContext *redisConnectUnixNonBlock(const char *path) { } redisContext *redisConnectFd(int fd) { - redisOptions options = {REDIS_CONN_USERFD}; + redisOptions options = {0}; + options.type = REDIS_CONN_USERFD; options.endpoint.fd = fd; return redisConnectWithOptions(&options); } -- cgit v1.2.3 From f51363a70cdee179ed30b580f1e47b715cde06fb Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 12 Dec 2018 11:39:41 -0500 Subject: Don't warn on missing field initializers --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b2a9fb5..3532812 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ export REDIS_TEST_CONFIG CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') OPTIMIZATION?=-O3 -WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings +WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers DEBUG_FLAGS?= -g -ggdb REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) REAL_LDFLAGS=$(LDFLAGS) -- cgit v1.2.3 From 3a547b8ec045704bf4836d0c5c9657fa311698f0 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 13 Dec 2018 11:24:40 -0500 Subject: Unix: set addrlen so async reconnect uses proper size --- net.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net.c b/net.c index f09183e..45dca7a 100644 --- a/net.c +++ b/net.c @@ -502,7 +502,8 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); c->addrlen = sizeof(struct sockaddr_un); sa->sun_family = AF_UNIX; - strncpy(sa->sun_path,path,sizeof(sa->sun_path)-1); + c->addrlen = sizeof(*sa); + strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (errno == EINPROGRESS && !blocking) { /* This is ok. */ -- cgit v1.2.3 From 389e694abe6bb9ed6e5a44674d89a866c5231422 Mon Sep 17 00:00:00 2001 From: valentino Date: Sun, 9 Dec 2018 12:33:56 +0200 Subject: Fix compilation on Ubuntu --- Makefile | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 3532812..111dee8 100644 --- a/Makefile +++ b/Makefile @@ -50,22 +50,28 @@ STLIBSUFFIX=a DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) -DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) -USE_SSL:=1 +# Platform-specific overrides +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') + +USE_SSL?=1 ifdef USE_SSL # This is the prefix of openssl on my system. This should be the sane default # based on the platform - OPENSSL_PREFIX?=/usr/local/opt/openssl - CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL - LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + ifeq ($(uname_S),Linux) + CFLAGS+=-DHIREDIS_SSL + LDFLAGS+=-lssl -lcrypto + else + OPENSSL_PREFIX?=/usr/local/opt/openssl + CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL + LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + endif endif -# Platform-specific overrides -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ifeq ($(uname_S),SunOS) REAL_LDFLAGS+= -ldl -lnsl -lsocket DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) @@ -89,7 +95,7 @@ sslio.o: sslio.c sslio.h hiredis.h test.o: test.c fmacros.h hiredis.h read.h sds.h $(DYLIBNAME): $(OBJ) - $(DYLIB_MAKE_CMD) $(OBJ) + $(DYLIB_MAKE_CMD) $(OBJ) $(REAL_LDFLAGS) $(STLIBNAME): $(OBJ) $(STLIB_MAKE_CMD) $(OBJ) @@ -99,25 +105,25 @@ static: $(STLIBNAME) # Binaries: hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) ifndef AE_DIR hiredis-example-ae: @@ -134,7 +140,7 @@ hiredis-example-libuv: @false else hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) endif ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) @@ -151,14 +157,14 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) endif hiredis-example: examples/example.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) examples: $(EXAMPLES) hiredis-test: test.o $(STLIBNAME) hiredis-%: %.o $(STLIBNAME) - $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) + $(CC) $(REAL_CFLAGS) -o $@ $< $(STLIBNAME) $(REAL_LDFLAGS) test: hiredis-test ./hiredis-test @@ -191,11 +197,14 @@ $(PKGCONFNAME): hiredis.h @echo Description: Minimalistic C client library for Redis. >> $@ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@ +ifdef USE_SSL + @echo Libs.private: lssl -lcrypto >> $@ +endif @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) - $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH) + $(INSTALL) hiredis.h async.h read.h sds.h sslio.h $(INSTALL_INCLUDE_PATH) $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) -- cgit v1.2.3 From 58222c26f4889c3f83c453bd7ec87e387459fd0c Mon Sep 17 00:00:00 2001 From: valentino Date: Thu, 20 Dec 2018 16:26:24 +0200 Subject: Support SNI --- examples/example-libevent-ssl.c | 2 +- examples/example-ssl.c | 2 +- hiredis.c | 4 ++-- hiredis.h | 2 +- sslio.c | 8 +++++++- sslio.h | 6 +++--- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/example-libevent-ssl.c b/examples/example-libevent-ssl.c index f780e3e..562e1a1 100644 --- a/examples/example-libevent-ssl.c +++ b/examples/example-libevent-ssl.c @@ -57,7 +57,7 @@ int main (int argc, char **argv) { printf("Error: %s\n", c->errstr); return 1; } - if (redisSecureConnection(&c->c, caCert, cert, certKey) != REDIS_OK) { + if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) { printf("SSL Error!\n"); exit(1); } diff --git a/examples/example-ssl.c b/examples/example-ssl.c index 28489e4..a90b78a 100644 --- a/examples/example-ssl.c +++ b/examples/example-ssl.c @@ -30,7 +30,7 @@ int main(int argc, char **argv) { exit(1); } - if (redisSecureConnection(c, ca, cert, key) != REDIS_OK) { + if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) { printf("Couldn't initialize SSL!\n"); printf("Error: %s\n", c->errstr); redisFree(c); diff --git a/hiredis.c b/hiredis.c index 7ba51f6..dd499b8 100644 --- a/hiredis.c +++ b/hiredis.c @@ -753,8 +753,8 @@ redisContext *redisConnectFd(int fd) { } int redisSecureConnection(redisContext *c, const char *caPath, - const char *certPath, const char *keyPath) { - return redisSslCreate(c, caPath, certPath, keyPath); + const char *certPath, const char *keyPath, const char *servername) { + return redisSslCreate(c, caPath, certPath, keyPath, servername); } /* Set read/write timeout on a blocking socket. */ diff --git a/hiredis.h b/hiredis.h index bb9b6a7..0d646f8 100644 --- a/hiredis.h +++ b/hiredis.h @@ -207,7 +207,7 @@ redisContext *redisConnectFd(int fd); * executed on the connection. */ int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, - const char *keypath); + const char *keypath, const char *servername); /** * Reconnect the given context using the saved information. diff --git a/sslio.c b/sslio.c index 8933f95..efbf06e 100644 --- a/sslio.c +++ b/sslio.c @@ -87,7 +87,7 @@ void redisFreeSsl(redisSsl *ssl){ } int redisSslCreate(redisContext *c, const char *capath, const char *certpath, - const char *keypath) { + const char *keypath, const char *servername) { assert(!c->ssl); c->ssl = calloc(1, sizeof(*c->ssl)); static int isInit = 0; @@ -131,6 +131,12 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, __redisSetError(c, REDIS_ERR, "Couldn't create new SSL instance"); return REDIS_ERR; } + if (servername) { + if (!SSL_set_tlsext_host_name(s->ssl, servername)) { + __redisSetError(c, REDIS_ERR, "Couldn't set server name indication"); + return REDIS_ERR; + } + } SSL_set_fd(s->ssl, c->fd); SSL_set_connect_state(s->ssl); diff --git a/sslio.h b/sslio.h index 9edac87..e5493b7 100644 --- a/sslio.h +++ b/sslio.h @@ -12,8 +12,8 @@ static inline void redisFreeSsl(redisSsl *ssl) { (void)ssl; } static inline int redisSslCreate(struct redisContext *c, const char *ca, - const char *cert, const char *key) { - (void)c;(void)ca;(void)cert;(void)key; + const char *cert, const char *key, const char *servername) { + (void)c;(void)ca;(void)cert;(void)key;(void)servername; return REDIS_ERR; } static inline int redisSslRead(struct redisContext *c, char *s, size_t n) { @@ -55,7 +55,7 @@ struct redisContext; void redisFreeSsl(redisSsl *); int redisSslCreate(struct redisContext *c, const char *caPath, - const char *certPath, const char *keyPath); + const char *certPath, const char *keyPath, const char *servername); int redisSslRead(struct redisContext *c, char *buf, size_t bufcap); int redisSslWrite(struct redisContext *c); -- cgit v1.2.3 From 847a20122f3e3c6e69179943f2d397da6712de80 Mon Sep 17 00:00:00 2001 From: valentino Date: Sun, 20 Jan 2019 16:07:55 +0200 Subject: Fix memory leaks --- adapters/libevent.h | 5 ++--- hiredis.c | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 9843696..5959e89 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -48,10 +48,10 @@ static void redisLibeventHandler(int fd, short event, void *arg) { if (event & EV_TIMEOUT) { redisAsyncHandleTimeout(e->context); } - if (e->context && (event & EV_READ)) { + if ((event & EV_READ) && e->context) { redisAsyncHandleRead(e->context); } - if (e->context && (event & EV_WRITE)) { + if ((event & EV_WRITE) && e->context) { redisAsyncHandleWrite(e->context); } } @@ -107,7 +107,6 @@ static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { short flags = e->flags; e->flags = 0; e->tv = tv; - event_del(e->ev); redisLibeventUpdate(e, flags, 0); } diff --git a/hiredis.c b/hiredis.c index dd499b8..9c2788a 100644 --- a/hiredis.c +++ b/hiredis.c @@ -618,6 +618,7 @@ void redisFree(redisContext *c) { if (c->ssl) { redisFreeSsl(c->ssl); } + free(c); } int redisFreeKeepFd(redisContext *c) { -- cgit v1.2.3 From 0bc2356ed5b587f4b4e41878a51b741e12901016 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 6 Feb 2019 14:14:46 -0500 Subject: CMake: update for SSL --- CMakeLists.txt | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 89ae962..4952083 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,8 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) INCLUDE(GNUInstallDirs) +PROJECT(hiredis) + +OPTION(HIREDIS_SSL "Link against OpenSSL" ON) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") @@ -27,7 +30,8 @@ ADD_LIBRARY(hiredis SHARED hiredis.c net.c read.c - sds.c) + sds.c + sslio.c) SET_TARGET_PROPERTIES(hiredis PROPERTIES @@ -44,9 +48,22 @@ INSTALL(FILES hiredis.h read.h sds.h async.h INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +IF(HIREDIS_SSL) + IF (NOT OPENSSL_ROOT_DIR) + IF (APPLE) + SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") + ENDIF() + ENDIF() + FIND_PACKAGE(OpenSSL REQUIRED) + ADD_DEFINITIONS(-DHIREDIS_SSL) + INCLUDE_DIRECTORIES("${OPENSSL_INCLUDE_DIR}") + TARGET_LINK_LIBRARIES(hiredis ${OPENSSL_LIBRARIES}) +ENDIF() + ENABLE_TESTING() -ADD_EXECUTABLE(hiredis-test - test.c) +ADD_EXECUTABLE(hiredis-test test.c) + + TARGET_LINK_LIBRARIES(hiredis-test hiredis) ADD_TEST(NAME hiredis-test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) -- cgit v1.2.3 From 5f633ac4ec0ee818fb9785e751fbbdfab48f9542 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 11:26:15 -0500 Subject: fix potential uninitialized read If callback was set before scheduleTimer was set (i..e before one of the attach()) calls. --- async.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/async.c b/async.c index a32d528..0ed973e 100644 --- a/async.c +++ b/async.c @@ -144,6 +144,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ac->ev.addWrite = NULL; ac->ev.delWrite = NULL; ac->ev.cleanup = NULL; + ac->ev.scheduleTimer = NULL; ac->onConnect = NULL; ac->onDisconnect = NULL; @@ -844,4 +845,4 @@ void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { } *ac->c.timeout = tv; -} \ No newline at end of file +} -- cgit v1.2.3 From 24e6166fedbd76a47db2ca0b4f7ab52edc0044e8 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 14:52:37 -0500 Subject: libevent: fix invalid mem access on delete within callback enter --- adapters/libevent.h | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 5959e89..58bab4b 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -34,26 +34,49 @@ #include "../hiredis.h" #include "../async.h" +#define REDIS_LIBEVENT_DELETED 0x01 +#define REDIS_LIBEVENT_ENTERED 0x02 + typedef struct redisLibeventEvents { redisAsyncContext *context; struct event *ev, *tmr; struct event_base *base; struct timeval tv; short flags; + short state; } redisLibeventEvents; +static void redisLibeventDestroy(redisLibeventEvents *e) { + free(e); +} + static void redisLibeventHandler(int fd, short event, void *arg) { ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; - if (event & EV_TIMEOUT) { + e->state |= REDIS_LIBEVENT_ENTERED; + + #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ + redisLibeventDestroy(e);\ + return; \ + } + + if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleTimeout(e->context); + CHECK_DELETED(); } - if ((event & EV_READ) && e->context) { + + if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleRead(e->context); + CHECK_DELETED(); } - if ((event & EV_WRITE) && e->context) { + + if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleWrite(e->context); + CHECK_DELETED(); } + + e->state &= ~REDIS_LIBEVENT_ENTERED; + #undef CHECK_DELETED } static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { @@ -98,8 +121,18 @@ static void redisLibeventDelWrite(void *privdata) { static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; + if (!e) { + return; + } + event_del(e->ev); event_free(e->ev); - free(e); + e->ev = NULL; + + if (e->state & REDIS_LIBEVENT_ENTERED) { + e->state |= REDIS_LIBEVENT_DELETED; + } else { + free(e); + } } static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { -- cgit v1.2.3 From f60c5506feab92f2b6185661cce16a303d28a6a3 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 14:53:42 -0500 Subject: Add EV_PERSIST flag to read events This will avoid the need to constantly reschedule the event --- adapters/libevent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 58bab4b..fb5558e 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -98,7 +98,7 @@ static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { } event_del(e->ev); - event_assign(e->ev, e->base, e->context->c.fd, e->flags, + event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST, redisLibeventHandler, privdata); event_add(e->ev, tv); } -- cgit v1.2.3 From 1eb44cf0c4e004dda6d8b9c7500cd55e367cc87b Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 14:55:07 -0500 Subject: scrub redisContext before freeing This helps us detect use-after-free --- hiredis.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hiredis.c b/hiredis.c index 9c2788a..d28fcb9 100644 --- a/hiredis.c +++ b/hiredis.c @@ -618,6 +618,7 @@ void redisFree(redisContext *c) { if (c->ssl) { redisFreeSsl(c->ssl); } + memset(0xff, c, sizeof(*c)); free(c); } -- cgit v1.2.3 From f0a7595056d00383701fcb2a6247ea1ec91c2ae9 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 14:56:56 -0500 Subject: libevent: call destroy from cleanup also, indentation fix --- adapters/libevent.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index fb5558e..1c86c15 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -47,7 +47,7 @@ typedef struct redisLibeventEvents { } redisLibeventEvents; static void redisLibeventDestroy(redisLibeventEvents *e) { - free(e); + free(e); } static void redisLibeventHandler(int fd, short event, void *arg) { @@ -56,8 +56,8 @@ static void redisLibeventHandler(int fd, short event, void *arg) { e->state |= REDIS_LIBEVENT_ENTERED; #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ - redisLibeventDestroy(e);\ - return; \ + redisLibeventDestroy(e);\ + return; \ } if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) { @@ -122,16 +122,16 @@ static void redisLibeventDelWrite(void *privdata) { static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; if (!e) { - return; + return; } event_del(e->ev); event_free(e->ev); e->ev = NULL; if (e->state & REDIS_LIBEVENT_ENTERED) { - e->state |= REDIS_LIBEVENT_DELETED; + e->state |= REDIS_LIBEVENT_DELETED; } else { - free(e); + redisLibeventDestroy(e); } } -- cgit v1.2.3 From ea9f9d2515081d191507ea055cd0e614a078d914 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Tue, 12 Feb 2019 09:30:43 -0500 Subject: fixed wrong memset args --- hiredis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiredis.c b/hiredis.c index d28fcb9..ba8d316 100644 --- a/hiredis.c +++ b/hiredis.c @@ -618,7 +618,7 @@ void redisFree(redisContext *c) { if (c->ssl) { redisFreeSsl(c->ssl); } - memset(0xff, c, sizeof(*c)); + memset(c, 0xff, sizeof(*c)); free(c); } -- cgit v1.2.3 From 271f33953ff75f32d100714ae9ea2a666bca6469 Mon Sep 17 00:00:00 2001 From: valentino Date: Sun, 17 Feb 2019 18:19:21 +0200 Subject: fix pkg config --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 111dee8..815c9d8 100644 --- a/Makefile +++ b/Makefile @@ -198,7 +198,7 @@ $(PKGCONFNAME): hiredis.h @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@ ifdef USE_SSL - @echo Libs.private: lssl -lcrypto >> $@ + @echo Libs.private: -lssl -lcrypto >> $@ endif @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ -- cgit v1.2.3 From d9e0299f1cf923ebba0e2027a2044954c5a22a38 Mon Sep 17 00:00:00 2001 From: valentino Date: Tue, 19 Feb 2019 19:17:41 +0200 Subject: fix redisLibeventEvents init --- adapters/libevent.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 1c86c15..a495277 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -39,7 +39,7 @@ typedef struct redisLibeventEvents { redisAsyncContext *context; - struct event *ev, *tmr; + struct event *ev; struct event_base *base; struct timeval tv; short flags; @@ -152,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { return REDIS_ERR; /* Create container for context and r/w events */ - e = (redisLibeventEvents*)malloc(sizeof(*e)); + e = (redisLibeventEvents*)calloc(1, sizeof(*e)); e->context = ac; /* Register functions to start/stop listening for events */ @@ -166,10 +166,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { /* Initialize and install read/write events */ e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); - e->flags = 0; e->base = base; - e->tv.tv_sec = 0; - e->tv.tv_usec = 0; return REDIS_OK; } #endif -- cgit v1.2.3 From 4b904296405c888dde38c4e10818714f8165bced Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 20 Feb 2019 04:50:02 -0500 Subject: Remove redundant line after rebase --- net.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net.c b/net.c index 45dca7a..3cd0402 100644 --- a/net.c +++ b/net.c @@ -502,7 +502,6 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); c->addrlen = sizeof(struct sockaddr_un); sa->sun_family = AF_UNIX; - c->addrlen = sizeof(*sa); strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (errno == EINPROGRESS && !blocking) { -- cgit v1.2.3 From 3511c8df68b8ff640a8e1748f96197cab71311bb Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 20 Feb 2019 04:53:21 -0500 Subject: gitignore: dSYM --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c44b5c5..8e50b54 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /*.dylib /*.a /*.pc +*.dSYM -- cgit v1.2.3 From 3949c8a1536bf67293817315f616ec6d1462290e Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 20 Feb 2019 07:23:37 -0500 Subject: Disable SSL by default --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 815c9d8..9ca40ee 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -USE_SSL?=1 +USE_SSL?=0 ifdef USE_SSL # This is the prefix of openssl on my system. This should be the sane default -- cgit v1.2.3 From 1ec4aefba6d3690c461ea870eab0984a2cb597f0 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 21 Feb 2019 06:21:53 -0500 Subject: Fix ifeq condition (thanks @regae) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9ca40ee..b253904 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') USE_SSL?=0 -ifdef USE_SSL +ifeq ($(USE_SSL),1) # This is the prefix of openssl on my system. This should be the sane default # based on the platform ifeq ($(uname_S),Linux) -- cgit v1.2.3 From 5eb6958870b71d30e3ed63e65e9f1d546dc419ec Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 21 Feb 2019 11:49:25 -0500 Subject: Allow option for async connections to not automatically free --- async.c | 7 ++++++- hiredis.c | 3 +++ hiredis.h | 12 ++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/async.c b/async.c index 0ed973e..0a2659c 100644 --- a/async.c +++ b/async.c @@ -361,7 +361,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { /* For non-clean disconnects, __redisAsyncFree() will execute pending * callbacks with a NULL-reply. */ - __redisAsyncFree(ac); + if (!(c->flags & REDIS_NO_AUTO_FREE)) { + __redisAsyncFree(ac); + } } /* Tries to do a clean disconnect from Redis, meaning it stops new commands @@ -373,6 +375,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { void redisAsyncDisconnect(redisAsyncContext *ac) { redisContext *c = &(ac->c); c->flags |= REDIS_DISCONNECTING; + + /** unset the auto-free flag here, because disconnect undoes this */ + c->flags &= ~REDIS_NO_AUTO_FREE; if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) __redisAsyncDisconnect(ac); } diff --git a/hiredis.c b/hiredis.c index ba8d316..2506d51 100644 --- a/hiredis.c +++ b/hiredis.c @@ -668,6 +668,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { if (options->options & REDIS_OPT_REUSEADDR) { c->flags |= REDIS_REUSEADDR; } + if (options->options & REDIS_OPT_NOAUTOFREE) { + c->flags |= REDIS_NO_AUTO_FREE; + } if (options->type == REDIS_CONN_TCP) { redisContextConnectBindTcp(c, options->endpoint.tcp.ip, diff --git a/hiredis.h b/hiredis.h index 0d646f8..4025869 100644 --- a/hiredis.h +++ b/hiredis.h @@ -77,6 +77,12 @@ /* Flag that is set when this connection is done through SSL */ #define REDIS_SSL 0x100 +/** + * Flag that indicates the user does not want the context to + * be automatically freed upon error + */ +#define REDIS_NO_AUTO_FREE 0x200 + #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and @@ -121,6 +127,12 @@ struct redisSsl; #define REDIS_OPT_NONBLOCK 0x01 #define REDIS_OPT_REUSEADDR 0x02 +/** + * Don't automatically free the async object on a connection failure, + * or other implicit conditions. Only free on an explicit call to disconnect() or free() + */ +#define REDIS_OPT_NOAUTOFREE 0x04 + typedef struct { /* * the type of connection to use. This also indicates which -- cgit v1.2.3 From 792bdbab7e781d1391adf242cb5fda33364ffeae Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 21 Feb 2019 12:04:53 -0500 Subject: cmake: ssl disabled by default --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4952083..cce2c61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) INCLUDE(GNUInstallDirs) PROJECT(hiredis) -OPTION(HIREDIS_SSL "Link against OpenSSL" ON) +OPTION(HIREDIS_SSL "Link against OpenSSL" OFF) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") -- cgit v1.2.3 From ffceb87ec3aa8504aea933ee4584ff3bc540622f Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 21 Feb 2019 12:09:17 -0500 Subject: SSL: build in travis --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4da8186..51171c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,13 +28,14 @@ env: - BITS="64" script: - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then + - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON"; + if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$BITS" == "32" ]; then CFLAGS="-m32 -Werror"; CXXFLAGS="-m32 -Werror"; LDFLAGS="-m32"; + EXTRA_CMAKE_OPTS=; else - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON"; CFLAGS="-Werror"; CXXFLAGS="-Werror"; fi; @@ -44,8 +45,8 @@ script: CFLAGS="-m32 -Werror"; CXXFLAGS="-m32 -Werror"; LDFLAGS="-m32"; + EXTRA_CMAKE_OPTS=; else - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON"; CFLAGS="-Werror"; CXXFLAGS="-Werror"; fi; -- cgit v1.2.3