diff options
| author | Mark Nunberg <mnunberg@users.noreply.github.com> | 2019-02-21 13:05:58 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-02-21 13:05:58 -0500 | 
| commit | 297de989a647a6f96f25aabea267aa0d18c43328 (patch) | |
| tree | 5afda44b01aea8a0b71ab7d01021cc2af9d91dd5 | |
| parent | 4d00404b8fb47e618474d5538e4a720ac1c95d95 (diff) | |
| parent | ffceb87ec3aa8504aea933ee4584ff3bc540622f (diff) | |
| download | hiredict-297de989a647a6f96f25aabea267aa0d18c43328.tar.xz | |
Merge pull request #645 from redis/ssl
SSL Support
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | .travis.yml | 7 | ||||
| -rw-r--r-- | CMakeLists.txt | 23 | ||||
| -rw-r--r-- | Makefile | 59 | ||||
| -rw-r--r-- | adapters/libevent.h | 112 | ||||
| -rw-r--r-- | async.c | 203 | ||||
| -rw-r--r-- | async.h | 6 | ||||
| -rw-r--r-- | examples/example-libevent-ssl.c | 72 | ||||
| -rw-r--r-- | examples/example-libevent.c | 15 | ||||
| -rw-r--r-- | examples/example-ssl.c | 92 | ||||
| -rw-r--r-- | hiredis.c | 221 | ||||
| -rw-r--r-- | hiredis.h | 72 | ||||
| -rw-r--r-- | net.c | 2 | ||||
| -rw-r--r-- | read.h | 1 | ||||
| -rw-r--r-- | sslio.c | 216 | ||||
| -rw-r--r-- | sslio.h | 64 | 
16 files changed, 983 insertions, 183 deletions
| @@ -5,3 +5,4 @@  /*.dylib  /*.a  /*.pc +*.dSYM 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; diff --git a/CMakeLists.txt b/CMakeLists.txt index 89ae962..cce2c61 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" OFF)  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) @@ -3,8 +3,9 @@  # 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 -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 hiredis-example-libevent-ssl  TESTS=hiredis-test  LIBNAME=libhiredis  PKGCONFNAME=hiredis.pc @@ -39,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) @@ -49,12 +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)  # 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 +endif +  ifeq ($(uname_S),SunOS)    REAL_LDFLAGS+= -ldl -lnsl -lsocket    DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) @@ -70,14 +87,15 @@ 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) -	$(DYLIB_MAKE_CMD) $(OBJ) +	$(DYLIB_MAKE_CMD) $(OBJ) $(REAL_LDFLAGS)  $(STLIBNAME): $(OBJ)  	$(STLIB_MAKE_CMD) $(OBJ) @@ -87,19 +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) -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) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)  ifndef AE_DIR  hiredis-example-ae: @@ -116,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)),) @@ -133,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 @@ -158,7 +182,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 @@ -173,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) diff --git a/adapters/libevent.h b/adapters/libevent.h index 7d2bef1..a495277 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -34,48 +34,113 @@  #include "../hiredis.h"  #include "../async.h" +#define REDIS_LIBEVENT_DELETED 0x01 +#define REDIS_LIBEVENT_ENTERED 0x02 +  typedef struct redisLibeventEvents {      redisAsyncContext *context; -    struct event *rev, *wev; +    struct event *ev; +    struct event_base *base; +    struct timeval tv; +    short flags; +    short state;  } redisLibeventEvents; -static void redisLibeventReadEvent(int fd, short event, void *arg) { -    ((void)fd); ((void)event); -    redisLibeventEvents *e = (redisLibeventEvents*)arg; -    redisAsyncHandleRead(e->context); +static void redisLibeventDestroy(redisLibeventEvents *e) { +    free(e);  } -static void redisLibeventWriteEvent(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; -    redisAsyncHandleWrite(e->context); +    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 && (e->state & REDIS_LIBEVENT_DELETED) == 0) { +        redisAsyncHandleRead(e->context); +        CHECK_DELETED(); +    } + +    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) { +    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 | EV_PERSIST, +                 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); -    free(e); +    if (!e) { +        return; +    } +    event_del(e->ev); +    event_free(e->ev); +    e->ev = NULL; + +    if (e->state & REDIS_LIBEVENT_ENTERED) { +        e->state |= REDIS_LIBEVENT_DELETED; +    } else { +        redisLibeventDestroy(e); +    } +} + +static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { +    redisLibeventEvents *e = (redisLibeventEvents *)privdata; +    short flags = e->flags; +    e->flags = 0; +    e->tv = tv; +    redisLibeventUpdate(e, flags, 0);  }  static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { @@ -87,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 */ @@ -96,13 +161,12 @@ 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->base = base;      return REDIS_OK;  }  #endif @@ -40,16 +40,21 @@  #include "net.h"  #include "dict.c"  #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) @@ -57,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); @@ -126,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; @@ -150,56 +169,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) { @@ -346,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 @@ -358,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);  } @@ -524,6 +544,76 @@ 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); +        } +    } + +    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); +} +  /* This function should be called when the socket is readable.   * It processes all replies that can be read and executes their callbacks.   */ @@ -539,6 +629,11 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {              return;      } +    if (c->flags & REDIS_SSL) { +        asyncSslRead(ac); +        return; +    } +      if (redisBufferRead(c) == REDIS_ERR) {          __redisAsyncDisconnect(ac);      } else { @@ -561,6 +656,11 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {              return;      } +    if (c->flags & REDIS_SSL) { +        asyncSslWrite(ac); +        return; +    } +      if (redisBufferWrite(c,&done) == REDIS_ERR) {          __redisAsyncDisconnect(ac);      } else { @@ -575,6 +675,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) { @@ -714,3 +838,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; +} @@ -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 @@ -106,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, @@ -113,12 +116,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/examples/example-libevent-ssl.c b/examples/example-libevent-ssl.c new file mode 100644 index 0000000..562e1a1 --- /dev/null +++ b/examples/example-libevent-ssl.c @@ -0,0 +1,72 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> + +#include <hiredis.h> +#include <async.h> +#include <adapters/libevent.h> + +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 <key> <host> <port> <cert> <certKey> [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, "sni") != 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/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); diff --git a/examples/example-ssl.c b/examples/example-ssl.c new file mode 100644 index 0000000..a90b78a --- /dev/null +++ b/examples/example-ssl.c @@ -0,0 +1,92 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <hiredis.h> + +int main(int argc, char **argv) { +    unsigned int j; +    redisContext *c; +    redisReply *reply; +    if (argc < 4) { +        printf("Usage: %s <host> <port> <cert> <key> [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, "sni") != 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; +} @@ -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); @@ -583,10 +584,10 @@ 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; @@ -597,7 +598,7 @@ static redisContext *redisContextInit(void) {          redisFree(c);          return NULL;      } - +    (void)options; /* options are used in other functions */      return c;  } @@ -614,6 +615,10 @@ void redisFree(redisContext *c) {      free(c->unix_sock.path);      free(c->timeout);      free(c->saddr); +    if (c->ssl) { +        redisFreeSsl(c->ssl); +    } +    memset(c, 0xff, sizeof(*c));      free(c);  } @@ -652,112 +657,109 @@ 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->options & REDIS_OPT_NOAUTOFREE) { +      c->flags |= REDIS_NO_AUTO_FREE; +    } + +    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; +    redisOptions options = {0}; +    options.type = REDIS_CONN_USERFD; +    options.endpoint.fd = fd; +    return redisConnectWithOptions(&options); +} -    c->fd = fd; -    c->flags |= REDIS_BLOCK | REDIS_CONNECTED; -    return c; +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. */ @@ -774,6 +776,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 +807,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 +844,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); @@ -74,6 +74,15 @@  /* 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 + */ +#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 @@ -109,9 +118,59 @@ 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 + +/** + * 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 +     * `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 */ @@ -137,8 +196,12 @@ typedef struct redisContext {      /* For non-blocking connect */      struct sockadr *saddr;      size_t addrlen; +    /* For SSL communication */ +    struct redisSsl *ssl; +  } 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); @@ -152,6 +215,13 @@ 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, const char *servername); + +/**   * Reconnect the given context using the saved information.   *   * This re-uses the exact same connect options as in the initial connection. @@ -502,7 +502,7 @@ 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); +    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. */ @@ -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 @@ -0,0 +1,216 @@ +#include "hiredis.h" +#include "sslio.h" + +#include <assert.h> +#ifdef HIREDIS_SSL +#include <pthread.h> + +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; +    /* 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, 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()); +    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; +        } +    } + +    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; +    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) { +        __redisSetError(c, REDIS_ERR_IO, "SSL_connect() failed"); +    } +    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 (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 (maybeCheckWant(c->ssl, err)) { +            return 0; +        } else { +            __redisSetError(c, REDIS_ERR_IO, NULL); +            return -1; +        } +    } +    return rv; +} + +#endif @@ -0,0 +1,64 @@ +#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 */ | 
