summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Nunberg <mnunberg@users.noreply.github.com>2019-09-16 06:05:46 -0400
committerGitHub <noreply@github.com>2019-09-16 06:05:46 -0400
commitbd2c8fedf75f4e60225dfd6c735068a88666839a (patch)
treeab4bd11eebd2b8f0bb1c4b905d81570f86b9f7ee
parent1ac8fca35de6d9ecc1b6b94cbd17aa7499cb8821 (diff)
parentdd408e8e3f90ab9de61a1d7fac711d2b5d7e1592 (diff)
Merge pull request #708 from yossigo/wip/ssl-reorganization
SSL Reorganization
-rw-r--r--CMakeLists.txt24
-rw-r--r--Makefile85
-rw-r--r--async.c143
-rw-r--r--async.h2
-rw-r--r--async_private.h72
-rw-r--r--examples/CMakeLists.txt6
-rw-r--r--examples/example-libevent-ssl.c1
-rw-r--r--examples/example-ssl.c1
-rw-r--r--hiredis.c25
-rw-r--r--hiredis.h27
-rw-r--r--hiredis_ssl.h53
-rw-r--r--hiredis_ssl.pc.in12
-rw-r--r--ssl.c448
-rw-r--r--sslio.c255
-rw-r--r--sslio.h64
15 files changed, 714 insertions, 504 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 30140bf..9e78894 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
INCLUDE(GNUInstallDirs)
PROJECT(hiredis)
-OPTION(HIREDIS_SSL "Link against OpenSSL" OFF)
+OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
MACRO(getVersionBit name)
SET(VERSION_REGEX "^#define ${name} (.+)$")
@@ -29,8 +29,7 @@ ADD_LIBRARY(hiredis SHARED
net.c
read.c
sds.c
- sockcompat.c
- sslio.c)
+ sockcompat.c)
SET_TARGET_PROPERTIES(hiredis
PROPERTIES
@@ -54,16 +53,27 @@ INSTALL(DIRECTORY adapters
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
-IF(HIREDIS_SSL)
+IF(ENABLE_SSL)
IF (NOT OPENSSL_ROOT_DIR)
IF (APPLE)
SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
ENDIF()
ENDIF()
FIND_PACKAGE(OpenSSL REQUIRED)
- TARGET_COMPILE_DEFINITIONS(hiredis PRIVATE HIREDIS_SSL)
- TARGET_INCLUDE_DIRECTORIES(hiredis PRIVATE "${OPENSSL_INCLUDE_DIR}")
- TARGET_LINK_LIBRARIES(hiredis PRIVATE ${OPENSSL_LIBRARIES})
+ ADD_LIBRARY(hiredis_ssl SHARED
+ ssl.c)
+ TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
+ TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
+ CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
+
+ INSTALL(TARGETS hiredis_ssl
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+
+ INSTALL(FILES hiredis_ssl.h
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
+
+ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
ENDIF()
IF(NOT (WIN32 OR MINGW))
diff --git a/Makefile b/Makefile
index 7a55f41..f992639 100644
--- a/Makefile
+++ b/Makefile
@@ -3,12 +3,17 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file
-OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o sslio.o
-EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib \
- hiredis-example-ssl hiredis-example-libevent-ssl
+OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o
+SSL_OBJ=ssl.o
+EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
+ifeq ($(USE_SSL),1)
+EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
+endif
TESTS=hiredis-test
LIBNAME=libhiredis
+SSL_LIBNAME=libhiredis_ssl
PKGCONFNAME=hiredis.pc
+SSL_PKGCONFNAME=hiredis_ssl.pc
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
@@ -50,26 +55,22 @@ 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)
+SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
+DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
-STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME)
+SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
+STLIB_MAKE_CMD=$(AR) rcs
# Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
USE_SSL?=0
-
-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)
- 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
+ifeq ($(uname_S),Linux)
+ SSL_LDFLAGS=-lssl -lcrypto
+else
+ OPENSSL_PREFIX?=/usr/local/opt/openssl
+ CFLAGS+=-I$(OPENSSL_PREFIX)/include
+ SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
endif
ifeq ($(uname_S),SunOS)
@@ -83,33 +84,46 @@ ifeq ($(uname_S),Darwin)
endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
+ifeq ($(USE_SSL),1)
+all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
+endif
# 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 sslio.h win32.h
+hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c sds.h
sockcompat.o: sockcompat.c sockcompat.h
-sslio.o: sslio.c sslio.h hiredis.h
+ssl.o: ssl.c hiredis.h
test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ)
- $(DYLIB_MAKE_CMD) $(OBJ) $(REAL_LDFLAGS)
+ $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
$(STLIBNAME): $(OBJ)
- $(STLIB_MAKE_CMD) $(OBJ)
+ $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
+
+$(SSL_DYLIBNAME): $(SSL_OBJ)
+ $(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
+
+$(SSL_STLIBNAME): $(SSL_OBJ)
+ $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
dynamic: $(DYLIBNAME)
static: $(STLIBNAME)
+ifeq ($(USE_SSL),1)
+dynamic: $(SSL_DYLIBNAME)
+static: $(SSL_STLIBNAME)
+endif
# Binaries:
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(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) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
+hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
@@ -123,8 +137,8 @@ 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) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
-hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
+hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
ifndef AE_DIR
hiredis-example-ae:
@@ -180,7 +194,7 @@ check: hiredis-test
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
clean:
- rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
+ rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
dep:
$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
@@ -198,14 +212,25 @@ $(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 >> $@
+$(SSL_PKGCONFNAME): hiredis.h
+ @echo "Generating $@ for pkgconfig..."
+ @echo prefix=$(PREFIX) > $@
+ @echo exec_prefix=\$${prefix} >> $@
+ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
+ @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
+ @echo >> $@
+ @echo Name: hiredis_ssl >> $@
+ @echo Description: SSL Support for hiredis. >> $@
+ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
+ @echo Requires: hiredis >> $@
+ @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
+ @echo Libs.private: -lssl -lcrypto >> $@
+
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 sslio.h $(INSTALL_INCLUDE_PATH)
+ $(INSTALL) hiredis.h async.h read.h sds.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)
diff --git a/async.c b/async.c
index e46573f..4f422d5 100644
--- a/async.c
+++ b/async.c
@@ -42,42 +42,9 @@
#include "net.h"
#include "dict.c"
#include "sds.h"
-#include "sslio.h"
#include "win32.h"
-#define _EL_ADD_READ(ctx) \
- do { \
- refreshTimeout(ctx); \
- if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
- } 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 { \
- refreshTimeout(ctx); \
- if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
- } while (0)
-#define _EL_DEL_WRITE(ctx) do { \
- if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
- } while(0)
-#define _EL_CLEANUP(ctx) do { \
- if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
- ctx->ev.cleanup = NULL; \
- } 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);
- // }
- }
-}
+#include "async_private.h"
/* Forward declaration of function in hiredis.c */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
@@ -347,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) {
}
/* Helper function to make the disconnect happen and clean up. */
-static void __redisAsyncDisconnect(redisAsyncContext *ac) {
+void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */
@@ -552,76 +519,18 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
}
}
-/**
- * Handle SSL when socket becomes available for reading. This also handles
- * 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;
- 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);
- }
- }
+void redisAsyncRead(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
- rv = redisBufferRead(c);
- if (rv == REDIS_ERR) {
+ if (redisBufferRead(c) == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
+ /* Always re-schedule reads */
_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);
-}
-
/* This function should be called when the socket is readable.
* It processes all replies that can be read and executes their callbacks.
*/
@@ -637,23 +546,29 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
return;
}
- if (c->flags & REDIS_SSL) {
- asyncSslRead(ac);
- return;
- }
+ c->funcs->async_read(ac);
+}
- if (redisBufferRead(c) == REDIS_ERR) {
+void redisAsyncWrite(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ int done = 0;
+
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
- /* Always re-schedule reads */
+ /* Continue writing when not done, stop writing otherwise */
+ if (!done)
+ _EL_ADD_WRITE(ac);
+ else
+ _EL_DEL_WRITE(ac);
+
+ /* Always schedule reads after writes */
_EL_ADD_READ(ac);
- redisProcessCallbacks(ac);
}
}
void redisAsyncHandleWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
- int done = 0;
if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */
@@ -664,23 +579,7 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
return;
}
- if (c->flags & REDIS_SSL) {
- asyncSslWrite(ac);
- return;
- }
-
- if (redisBufferWrite(c,&done) == REDIS_ERR) {
- __redisAsyncDisconnect(ac);
- } else {
- /* Continue writing when not done, stop writing otherwise */
- if (!done)
- _EL_ADD_WRITE(ac);
- else
- _EL_DEL_WRITE(ac);
-
- /* Always schedule reads after writes */
- _EL_ADD_READ(ac);
- }
+ c->funcs->async_write(ac);
}
void __redisSetError(redisContext *c, int type, const char *str);
diff --git a/async.h b/async.h
index 40a1819..4f6b3b7 100644
--- a/async.h
+++ b/async.h
@@ -125,6 +125,8 @@ void redisAsyncFree(redisAsyncContext *ac);
void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(redisAsyncContext *ac);
void redisAsyncHandleTimeout(redisAsyncContext *ac);
+void redisAsyncRead(redisAsyncContext *ac);
+void redisAsyncWrite(redisAsyncContext *ac);
/* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */
diff --git a/async_private.h b/async_private.h
new file mode 100644
index 0000000..d0133ae
--- /dev/null
+++ b/async_private.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_ASYNC_PRIVATE_H
+#define __HIREDIS_ASYNC_PRIVATE_H
+
+#define _EL_ADD_READ(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
+ } 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 { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_WRITE(ctx) do { \
+ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
+ } while(0)
+#define _EL_CLEANUP(ctx) do { \
+ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
+ ctx->ev.cleanup = NULL; \
+ } while(0);
+
+static inline 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);
+ // }
+ }
+}
+
+void __redisAsyncDisconnect(redisAsyncContext *ac);
+void redisProcessCallbacks(redisAsyncContext *ac);
+
+#endif /* __HIREDIS_ASYNC_PRIVATE_H */
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 8ab4b42..dd3a313 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -37,10 +37,10 @@ IF (APPLE)
TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
ENDIF()
-IF (HIREDIS_SSL)
+IF (ENABLE_SSL)
ADD_EXECUTABLE(example-ssl example-ssl.c)
- TARGET_LINK_LIBRARIES(example-ssl hiredis)
+ TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
ENDIF()
ADD_EXECUTABLE(example example.c)
-TARGET_LINK_LIBRARIES(example hiredis) \ No newline at end of file
+TARGET_LINK_LIBRARIES(example hiredis)
diff --git a/examples/example-libevent-ssl.c b/examples/example-libevent-ssl.c
index 562e1a1..1021113 100644
--- a/examples/example-libevent-ssl.c
+++ b/examples/example-libevent-ssl.c
@@ -4,6 +4,7 @@
#include <signal.h>
#include <hiredis.h>
+#include <hiredis_ssl.h>
#include <async.h>
#include <adapters/libevent.h>
diff --git a/examples/example-ssl.c b/examples/example-ssl.c
index 156f524..81f4648 100644
--- a/examples/example-ssl.c
+++ b/examples/example-ssl.c
@@ -3,6 +3,7 @@
#include <string.h>
#include <hiredis.h>
+#include <hiredis_ssl.h>
int main(int argc, char **argv) {
unsigned int j;
diff --git a/hiredis.c b/hiredis.c
index 9627832..0658e34 100644
--- a/hiredis.c
+++ b/hiredis.c
@@ -41,9 +41,17 @@
#include "hiredis.h"
#include "net.h"
#include "sds.h"
-#include "sslio.h"
+#include "async.h"
#include "win32.h"
+static redisContextFuncs redisContextDefaultFuncs = {
+ .free_privdata = NULL,
+ .async_read = redisAsyncRead,
+ .async_write = redisAsyncWrite,
+ .read = redisNetRead,
+ .write = redisNetWrite
+};
+
static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
static void *createArrayObject(const redisReadTask *task, size_t elements);
@@ -657,6 +665,7 @@ static redisContext *redisContextInit(const redisOptions *options) {
if (c == NULL)
return NULL;
+ c->funcs = &redisContextDefaultFuncs;
c->obuf = sdsempty();
c->reader = redisReaderCreate();
c->fd = REDIS_INVALID_FD;
@@ -681,8 +690,8 @@ void redisFree(redisContext *c) {
free(c->unix_sock.path);
free(c->timeout);
free(c->saddr);
- if (c->ssl) {
- redisFreeSsl(c->ssl);
+ if (c->funcs->free_privdata) {
+ c->funcs->free_privdata(c->privdata);
}
memset(c, 0xff, sizeof(*c));
free(c);
@@ -824,11 +833,6 @@ redisContext *redisConnectFd(redisFD fd) {
return redisConnectWithOptions(&options);
}
-int redisSecureConnection(redisContext *c, const char *caPath,
- const char *certPath, const char *keyPath, const char *servername) {
- return redisSslCreate(c, caPath, certPath, keyPath, servername);
-}
-
/* Set read/write timeout on a blocking socket. */
int redisSetTimeout(redisContext *c, const struct timeval tv) {
if (c->flags & REDIS_BLOCK)
@@ -856,8 +860,7 @@ int redisBufferRead(redisContext *c) {
if (c->err)
return REDIS_ERR;
- nread = c->flags & REDIS_SSL ?
- redisSslRead(c, buf, sizeof(buf)) : redisNetRead(c, buf, sizeof(buf));
+ nread = c->funcs->read(c, buf, sizeof(buf));
if (nread > 0) {
if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
__redisSetError(c, c->reader->err, c->reader->errstr);
@@ -886,7 +889,7 @@ int redisBufferWrite(redisContext *c, int *done) {
return REDIS_ERR;
if (sdslen(c->obuf) > 0) {
- int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : redisNetWrite(c);
+ int nwritten = c->funcs->write(c);
if (nwritten < 0) {
return REDIS_ERR;
} else if (nwritten > 0) {
diff --git a/hiredis.h b/hiredis.h
index d76a9e3..f02678a 100644
--- a/hiredis.h
+++ b/hiredis.h
@@ -78,9 +78,6 @@ struct timeval; /* forward declaration */
/* 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
-
/**
* Flag that indicates the user does not want the context to
* be automatically freed upon error
@@ -193,8 +190,21 @@ typedef struct {
(opts)->type = REDIS_CONN_UNIX; \
(opts)->endpoint.unix_socket = path;
+struct redisAsyncContext;
+struct redisContext;
+
+typedef struct redisContextFuncs {
+ void (*free_privdata)(void *);
+ void (*async_read)(struct redisAsyncContext *);
+ void (*async_write)(struct redisAsyncContext *);
+ int (*read)(struct redisContext *, char *, size_t);
+ int (*write)(struct redisContext *);
+} redisContextFuncs;
+
/* Context for a connection to Redis */
typedef struct redisContext {
+ const redisContextFuncs *funcs; /* Function table */
+
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
redisFD fd;
@@ -218,9 +228,9 @@ typedef struct redisContext {
/* For non-blocking connect */
struct sockadr *saddr;
size_t addrlen;
- /* For SSL communication */
- struct redisSsl *ssl;
+ /* Additional private data for hiredis addons such as SSL */
+ void *privdata;
} redisContext;
redisContext *redisConnectWithOptions(const redisOptions *options);
@@ -237,13 +247,6 @@ redisContext *redisConnectUnixNonBlock(const char *path);
redisContext *redisConnectFd(redisFD 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, const char *servername);
-
-/**
* Reconnect the given context using the saved information.
*
* This re-uses the exact same connect options as in the initial connection.
diff --git a/hiredis_ssl.h b/hiredis_ssl.h
new file mode 100644
index 0000000..f844f95
--- /dev/null
+++ b/hiredis_ssl.h
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (c) 2019, Redis Labs
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_SSL_H
+#define __HIREDIS_SSL_H
+
+/* This is the underlying struct for SSL in ssl.h, which is not included to
+ * keep build dependencies short here.
+ */
+struct ssl_st;
+
+/**
+ * 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, const char *servername);
+
+/**
+ * Initiate SSL/TLS negotiation on a provided context.
+ */
+
+int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
+
+#endif /* __HIREDIS_SSL_H */
diff --git a/hiredis_ssl.pc.in b/hiredis_ssl.pc.in
new file mode 100644
index 0000000..588a978
--- /dev/null
+++ b/hiredis_ssl.pc.in
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+pkgincludedir=${includedir}/hiredis
+
+Name: hiredis_ssl
+Description: SSL Support for hiredis.
+Version: @PROJECT_VERSION@
+Requires: hiredis
+Libs: -L${libdir} -lhiredis_ssl
+Libs.private: -lssl -lcrypto
diff --git a/ssl.c b/ssl.c
new file mode 100644
index 0000000..418ece6
--- /dev/null
+++ b/ssl.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2019, Redis Labs
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "hiredis.h"
+#include "async.h"
+
+#include <assert.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "async_private.h"
+
+void __redisSetError(redisContext *c, int type, const char *str);
+
+/* The SSL context is attached to SSL/TLS connections as a privdata. */
+typedef struct redisSSLContext {
+ /**
+ * OpenSSL SSL_CTX; It is optional and will not be set when using
+ * user-supplied SSL.
+ */
+ SSL_CTX *ssl_ctx;
+
+ /**
+ * OpenSSL SSL object.
+ */
+ SSL *ssl;
+
+ /**
+ * 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;
+
+ /** 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;
+} redisSSLContext;
+
+/* Forward declaration */
+redisContextFuncs redisContextSSLFuncs;
+
+#ifdef HIREDIS_SSL_TRACE
+/**
+ * Callback used for debugging
+ */
+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));
+ }
+}
+#endif
+
+/**
+ * OpenSSL global initialization and locking handling callbacks.
+ * Note that this is only required for OpenSSL < 1.1.0.
+ */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#define HIREDIS_USE_CRYPTO_LOCKS
+#endif
+
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+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);
+}
+#endif /* HIREDIS_USE_CRYPTO_LOCKS */
+
+/**
+ * SSL Connection initialization.
+ */
+
+static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
+ if (c->privdata) {
+ __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
+ return REDIS_ERR;
+ }
+ c->privdata = calloc(1, sizeof(redisSSLContext));
+
+ c->funcs = &redisContextSSLFuncs;
+ redisSSLContext *rssl = c->privdata;
+
+ rssl->ssl_ctx = ssl_ctx;
+ rssl->ssl = ssl;
+
+ SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_set_fd(rssl->ssl, c->fd);
+ SSL_set_connect_state(rssl->ssl);
+
+ ERR_clear_error();
+ int rv = SSL_connect(rssl->ssl);
+ if (rv == 1) {
+ return REDIS_OK;
+ }
+
+ rv = SSL_get_error(rssl->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) {
+ char err[512];
+ if (rv == SSL_ERROR_SYSCALL)
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
+ else {
+ unsigned long e = ERR_peek_last_error();
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
+ ERR_reason_error_string(e));
+ }
+ __redisSetError(c, REDIS_ERR_IO, err);
+ }
+ return REDIS_ERR;
+}
+
+int redisInitiateSSL(redisContext *c, SSL *ssl) {
+ return redisSSLConnect(c, NULL, ssl);
+}
+
+int redisSecureConnection(redisContext *c, const char *capath,
+ const char *certpath, const char *keypath, const char *servername) {
+
+ SSL_CTX *ssl_ctx = NULL;
+ SSL *ssl = NULL;
+
+ /* Initialize global OpenSSL stuff */
+ static int isInit = 0;
+ if (!isInit) {
+ isInit = 1;
+ SSL_library_init();
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+ initOpensslLocks();
+#endif
+ }
+
+ ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if (!ssl_ctx) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
+ goto error;
+ }
+
+#ifdef HIREDIS_SSL_TRACE
+ SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
+#endif
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+ if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
+ goto error;
+ }
+
+ if (capath) {
+ if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
+ goto error;
+ }
+ }
+ if (certpath) {
+ if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
+ goto error;
+ }
+ if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
+ goto error;
+ }
+ }
+
+ ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
+ goto error;
+ }
+ if (servername) {
+ if (!SSL_set_tlsext_host_name(ssl, servername)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
+ goto error;
+ }
+ }
+
+ return redisSSLConnect(c, ssl_ctx, ssl);
+
+error:
+ if (ssl) SSL_free(ssl);
+ if (ssl_ctx) SSL_CTX_free(ssl_ctx);
+ return REDIS_ERR;
+}
+
+static int maybeCheckWant(redisSSLContext *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;
+ }
+}
+
+/**
+ * Implementation of redisContextFuncs for SSL connections.
+ */
+
+static void redisSSLFreeContext(void *privdata){
+ redisSSLContext *rsc = privdata;
+
+ if (!rsc) return;
+ if (rsc->ssl) {
+ SSL_free(rsc->ssl);
+ rsc->ssl = NULL;
+ }
+ if (rsc->ssl_ctx) {
+ SSL_CTX_free(rsc->ssl_ctx);
+ rsc->ssl_ctx = NULL;
+ }
+ free(rsc);
+}
+
+static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
+ redisSSLContext *rssl = c->privdata;
+
+ int nread = SSL_read(rssl->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(rssl->ssl, nread);
+ if (c->flags & REDIS_BLOCK) {
+ /**
+ * In blocking mode, we should never end up in a situation where
+ * we get an error without it being an actual error, except
+ * in the case of EINTR, which can be spuriously received from
+ * debuggers or whatever.
+ */
+ if (errno == EINTR) {
+ return 0;
+ } else {
+ const char *msg = NULL;
+ if (errno == EAGAIN) {
+ msg = "Timed out";
+ }
+ __redisSetError(c, REDIS_ERR_IO, msg);
+ return -1;
+ }
+ }
+
+ /**
+ * We can very well get an EWOULDBLOCK/EAGAIN, however
+ */
+ if (maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+}
+
+static int redisSSLWrite(redisContext *c) {
+ redisSSLContext *rssl = c->privdata;
+
+ size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
+ int rv = SSL_write(rssl->ssl, c->obuf, len);
+
+ if (rv > 0) {
+ rssl->lastLen = 0;
+ } else if (rv < 0) {
+ rssl->lastLen = len;
+
+ int err = SSL_get_error(rssl->ssl, rv);
+ if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return rv;
+}
+
+static void redisSSLAsyncRead(redisAsyncContext *ac) {
+ int rv;
+ redisSSLContext *rssl = ac->c.privdata;
+ redisContext *c = &ac->c;
+
+ rssl->wantRead = 0;
+
+ if (rssl->pendingWrite) {
+ int done;
+
+ /* This is probably just a write event */
+ rssl->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);
+ }
+}
+
+static void redisSSLAsyncWrite(redisAsyncContext *ac) {
+ int rv, done = 0;
+ redisSSLContext *rssl = ac->c.privdata;
+ redisContext *c = &ac->c;
+
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+
+ if (!done) {
+ if (rssl->wantRead) {
+ /* Need to read-before-write */
+ rssl->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);
+}
+
+redisContextFuncs redisContextSSLFuncs = {
+ .free_privdata = redisSSLFreeContext,
+ .async_read = redisSSLAsyncRead,
+ .async_write = redisSSLAsyncWrite,
+ .read = redisSSLRead,
+ .write = redisSSLWrite
+};
+
diff --git a/sslio.c b/sslio.c
deleted file mode 100644
index f2f50a8..0000000
--- a/sslio.c
+++ /dev/null
@@ -1,255 +0,0 @@
-#include "hiredis.h"
-#include "sslio.h"
-
-#include <assert.h>
-#ifdef HIREDIS_SSL
-#include <pthread.h>
-#include <errno.h>
-#include <string.h>
-
-#include <openssl/err.h>
-
-void __redisSetError(redisContext *c, int type, const char *str);
-
-#ifdef HIREDIS_SSL_TRACE
-/**
- * Callback used for debugging
- */
-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));
- }
-}
-#endif
-
-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, const char *servername) {
- 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());
-#ifdef HIREDIS_SSL_TRACE
- SSL_CTX_set_info_callback(s->ctx, sslLogCallback);
-#endif
- 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;
- }
- }
-
- s->ssl = SSL_new(s->ctx);
- if (!s->ssl) {
- __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);
-
- c->flags |= REDIS_SSL;
- ERR_clear_error();
- int rv = SSL_connect(c->ssl->ssl);
- if (rv == 1) {
- return REDIS_OK;
- }
-
- 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) {
- char err[512];
- if (rv == SSL_ERROR_SYSCALL)
- snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
- else {
- unsigned long e = ERR_peek_last_error();
- snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
- ERR_reason_error_string(e));
- }
- __redisSetError(c, REDIS_ERR_IO, err);
- }
- 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) {
- 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 (c->flags & REDIS_BLOCK) {
- /**
- * In blocking mode, we should never end up in a situation where
- * we get an error without it being an actual error, except
- * in the case of EINTR, which can be spuriously received from
- * debuggers or whatever.
- */
- if (errno == EINTR) {
- return 0;
- } else {
- const char *msg = NULL;
- if (errno == EAGAIN) {
- msg = "Timed out";
- }
- __redisSetError(c, REDIS_ERR_IO, msg);
- return -1;
- }
- }
-
- /**
- * We can very well get an EWOULDBLOCK/EAGAIN, however
- */
- if (maybeCheckWant(c->ssl, err)) {
- 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 ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(c->ssl, err)) {
- return 0;
- } else {
- __redisSetError(c, REDIS_ERR_IO, NULL);
- return -1;
- }
- }
- return rv;
-}
-
-#endif
diff --git a/sslio.h b/sslio.h
deleted file mode 100644
index e5493b7..0000000
--- a/sslio.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#ifndef REDIS_SSLIO_H
-#define REDIS_SSLIO_H
-
-
-#ifndef HIREDIS_SSL
-typedef struct redisSsl {
- size_t lastLen;
- int wantRead;
- int pendingWrite;
-} redisSsl;
-static inline void redisFreeSsl(redisSsl *ssl) {
- (void)ssl;
-}
-static inline int redisSslCreate(struct redisContext *c, const char *ca,
- 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) {
- (void)c;(void)s;(void)n;
- return -1;
-}
-static inline int redisSslWrite(struct redisContext *c) {
- (void)c;
- return -1;
-}
-#else
-#include <openssl/ssl.h>
-
-/**
- * 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;
-
- /** 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;
-
-void redisFreeSsl(redisSsl *);
-int redisSslCreate(struct redisContext *c, const char *caPath,
- const char *certPath, const char *keyPath, const char *servername);
-
-int redisSslRead(struct redisContext *c, char *buf, size_t bufcap);
-int redisSslWrite(struct redisContext *c);
-
-#endif /* HIREDIS_SSL */
-#endif /* HIREDIS_SSLIO_H */