Commit bf759cc9 authored by Oran Agra's avatar Oran Agra
Browse files

Merge remote-tracking branch 'antirez/unstable' into jemalloc_purge_bg

parents 2e19b941 ee1cef18
/*
* 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 */
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
...@@ -34,36 +34,64 @@ ...@@ -34,36 +34,64 @@
#include "fmacros.h" #include "fmacros.h"
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <string.h> #include <string.h>
#include <netdb.h>
#include <errno.h> #include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <poll.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include "net.h" #include "net.h"
#include "sds.h" #include "sds.h"
#include "sockcompat.h"
#include "win32.h"
/* Defined in hiredis.c */ /* Defined in hiredis.c */
void __redisSetError(redisContext *c, int type, const char *str); void __redisSetError(redisContext *c, int type, const char *str);
static void redisContextCloseFd(redisContext *c) { void redisNetClose(redisContext *c) {
if (c && c->fd >= 0) { if (c && c->fd != REDIS_INVALID_FD) {
close(c->fd); close(c->fd);
c->fd = -1; c->fd = REDIS_INVALID_FD;
} }
} }
int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
int nread = recv(c->fd, buf, bufcap, 0);
if (nread == -1) {
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
/* Try again later */
return 0;
} else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
/* especially in windows */
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
return -1;
} 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;
}
}
int redisNetWrite(redisContext *c) {
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
if (nwritten < 0) {
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
/* Try again later */
} else {
__redisSetError(c, REDIS_ERR_IO, NULL);
return -1;
}
}
return nwritten;
}
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
int errorno = errno; /* snprintf() may change errno */ int errorno = errno; /* snprintf() may change errno */
char buf[128] = { 0 }; char buf[128] = { 0 };
...@@ -79,15 +107,15 @@ static int redisSetReuseAddr(redisContext *c) { ...@@ -79,15 +107,15 @@ static int redisSetReuseAddr(redisContext *c) {
int on = 1; int on = 1;
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
} }
static int redisCreateSocket(redisContext *c, int type) { static int redisCreateSocket(redisContext *c, int type) {
int s; redisFD s;
if ((s = socket(type, SOCK_STREAM, 0)) == -1) { if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -101,6 +129,7 @@ static int redisCreateSocket(redisContext *c, int type) { ...@@ -101,6 +129,7 @@ static int redisCreateSocket(redisContext *c, int type) {
} }
static int redisSetBlocking(redisContext *c, int blocking) { static int redisSetBlocking(redisContext *c, int blocking) {
#ifndef _WIN32
int flags; int flags;
/* Set the socket nonblocking. /* Set the socket nonblocking.
...@@ -108,7 +137,7 @@ static int redisSetBlocking(redisContext *c, int blocking) { ...@@ -108,7 +137,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
* interrupted by a signal. */ * interrupted by a signal. */
if ((flags = fcntl(c->fd, F_GETFL)) == -1) { if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -119,15 +148,23 @@ static int redisSetBlocking(redisContext *c, int blocking) { ...@@ -119,15 +148,23 @@ static int redisSetBlocking(redisContext *c, int blocking) {
if (fcntl(c->fd, F_SETFL, flags) == -1) { if (fcntl(c->fd, F_SETFL, flags) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR;
}
#else
u_long mode = blocking ? 0 : 1;
if (ioctl(c->fd, FIONBIO, &mode) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
#endif /* _WIN32 */
return REDIS_OK; return REDIS_OK;
} }
int redisKeepAlive(redisContext *c, int interval) { int redisKeepAlive(redisContext *c, int interval) {
int val = 1; int val = 1;
int fd = c->fd; redisFD fd = c->fd;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
...@@ -170,7 +207,7 @@ static int redisSetTcpNoDelay(redisContext *c) { ...@@ -170,7 +207,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
int yes = 1; int yes = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
...@@ -212,12 +249,12 @@ static int redisContextWaitReady(redisContext *c, long msec) { ...@@ -212,12 +249,12 @@ static int redisContextWaitReady(redisContext *c, long msec) {
if ((res = poll(wfd, 1, msec)) == -1) { if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} else if (res == 0) { } else if (res == 0) {
errno = ETIMEDOUT; errno = ETIMEDOUT;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -230,7 +267,7 @@ static int redisContextWaitReady(redisContext *c, long msec) { ...@@ -230,7 +267,7 @@ static int redisContextWaitReady(redisContext *c, long msec) {
} }
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
redisContextCloseFd(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -277,11 +314,18 @@ int redisCheckSocketError(redisContext *c) { ...@@ -277,11 +314,18 @@ int redisCheckSocketError(redisContext *c) {
} }
int redisContextSetTimeout(redisContext *c, const struct timeval tv) { int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { const void *to_ptr = &tv;
size_t to_sz = sizeof(tv);
#ifdef _WIN32
DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
to_ptr = &timeout_msec;
to_sz = sizeof(timeout_msec);
#endif
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR; return REDIS_ERR;
} }
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -291,7 +335,8 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) { ...@@ -291,7 +335,8 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout, const struct timeval *timeout,
const char *source_addr) { const char *source_addr) {
int s, rv, n; redisFD s;
int rv, n;
char _port[6]; /* strlen("65535"); */ char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *bservinfo, *p, *b; struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
int blocking = (c->flags & REDIS_BLOCK); int blocking = (c->flags & REDIS_BLOCK);
...@@ -360,7 +405,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -360,7 +405,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
} }
for (p = servinfo; p != NULL; p = p->ai_next) { for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry: addrretry:
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
continue; continue;
c->fd = s; c->fd = s;
...@@ -401,16 +446,14 @@ addrretry: ...@@ -401,16 +446,14 @@ addrretry:
} }
/* For repeat connection */ /* For repeat connection */
if (c->saddr) {
free(c->saddr); free(c->saddr);
}
c->saddr = malloc(p->ai_addrlen); c->saddr = malloc(p->ai_addrlen);
memcpy(c->saddr, p->ai_addr, p->ai_addrlen); memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
c->addrlen = p->ai_addrlen; c->addrlen = p->ai_addrlen;
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) { if (errno == EHOSTUNREACH) {
redisContextCloseFd(c); redisNetClose(c);
continue; continue;
} else if (errno == EINPROGRESS) { } else if (errno == EINPROGRESS) {
if (blocking) { if (blocking) {
...@@ -424,7 +467,7 @@ addrretry: ...@@ -424,7 +467,7 @@ addrretry:
if (++reuses >= REDIS_CONNECT_RETRIES) { if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error; goto error;
} else { } else {
redisContextCloseFd(c); redisNetClose(c);
goto addrretry; goto addrretry;
} }
} else { } else {
...@@ -471,8 +514,9 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, ...@@ -471,8 +514,9 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
} }
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
#ifndef _WIN32
int blocking = (c->flags & REDIS_BLOCK); int blocking = (c->flags & REDIS_BLOCK);
struct sockaddr_un sa; struct sockaddr_un *sa;
long timeout_msec = -1; long timeout_msec = -1;
if (redisCreateSocket(c,AF_UNIX) < 0) if (redisCreateSocket(c,AF_UNIX) < 0)
...@@ -499,9 +543,11 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time ...@@ -499,9 +543,11 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
sa.sun_family = AF_UNIX; sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); c->addrlen = sizeof(struct sockaddr_un);
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { sa->sun_family = AF_UNIX;
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
if (errno == EINPROGRESS && !blocking) { if (errno == EINPROGRESS && !blocking) {
/* This is ok. */ /* This is ok. */
} else { } else {
...@@ -516,4 +562,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time ...@@ -516,4 +562,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
return REDIS_OK; return REDIS_OK;
#else
/* We currently do not support Unix sockets for Windows. */
/* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
errno = EPROTONOSUPPORT;
return REDIS_ERR;
#endif /* _WIN32 */
} }
...@@ -37,6 +37,10 @@ ...@@ -37,6 +37,10 @@
#include "hiredis.h" #include "hiredis.h"
void redisNetClose(redisContext *c);
int redisNetRead(redisContext *c, char *buf, size_t bufcap);
int redisNetWrite(redisContext *c);
int redisCheckSocketError(redisContext *c); int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextSetTimeout(redisContext *c, const struct timeval tv);
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
......
...@@ -31,10 +31,10 @@ ...@@ -31,10 +31,10 @@
#include "fmacros.h" #include "fmacros.h"
#include <string.h> #include <string.h>
#include <strings.h>
#include <stdlib.h> #include <stdlib.h>
#ifndef _MSC_VER #ifndef _MSC_VER
#include <unistd.h> #include <unistd.h>
#include <strings.h>
#endif #endif
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#include "read.h" #include "read.h"
#include "sds.h" #include "sds.h"
#include "win32.h"
static void __redisReaderSetError(redisReader *r, int type, const char *str) { static void __redisReaderSetError(redisReader *r, int type, const char *str) {
size_t len; size_t len;
...@@ -294,9 +295,9 @@ static int processLineItem(redisReader *r) { ...@@ -294,9 +295,9 @@ static int processLineItem(redisReader *r) {
buf[len] = '\0'; buf[len] = '\0';
if (strcasecmp(buf,",inf") == 0) { if (strcasecmp(buf,",inf") == 0) {
d = 1.0/0.0; /* Positive infinite. */ d = INFINITY; /* Positive infinite. */
} else if (strcasecmp(buf,",-inf") == 0) { } else if (strcasecmp(buf,",-inf") == 0) {
d = -1.0/0.0; /* Nevative infinite. */ d = -INFINITY; /* Nevative infinite. */
} else { } else {
d = strtod((char*)buf,&eptr); d = strtod((char*)buf,&eptr);
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
...@@ -379,10 +380,18 @@ static int processBulkItem(redisReader *r) { ...@@ -379,10 +380,18 @@ static int processBulkItem(redisReader *r) {
/* Only continue when the buffer contains the entire bulk item. */ /* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */ bytelen += len+2; /* include \r\n */
if (r->pos+bytelen <= r->len) { if (r->pos+bytelen <= r->len) {
if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
(cur->type == REDIS_REPLY_VERB && s[5] != ':'))
{
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Verbatim string 4 bytes of content type are "
"missing or incorrectly encoded.");
return REDIS_ERR;
}
if (r->fn && r->fn->createString) if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,s+2,len); obj = r->fn->createString(cur,s+2,len);
else else
obj = (void*)REDIS_REPLY_STRING; obj = (void*)(long)cur->type;
success = 1; success = 1;
} }
} }
...@@ -430,7 +439,7 @@ static int processAggregateItem(redisReader *r) { ...@@ -430,7 +439,7 @@ static int processAggregateItem(redisReader *r) {
root = (r->ridx == 0); root = (r->ridx == 0);
if (elements < -1 || elements > INT_MAX) { if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range"); "Multi-bulk length out of range");
return REDIS_ERR; return REDIS_ERR;
...@@ -523,6 +532,9 @@ static int processItem(redisReader *r) { ...@@ -523,6 +532,9 @@ static int processItem(redisReader *r) {
case '#': case '#':
cur->type = REDIS_REPLY_BOOL; cur->type = REDIS_REPLY_BOOL;
break; break;
case '=':
cur->type = REDIS_REPLY_VERB;
break;
default: default:
__redisReaderSetErrorProtocolByte(r,*p); __redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR; return REDIS_ERR;
...@@ -543,6 +555,7 @@ static int processItem(redisReader *r) { ...@@ -543,6 +555,7 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_BOOL: case REDIS_REPLY_BOOL:
return processLineItem(r); return processLineItem(r);
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_VERB:
return processBulkItem(r); return processBulkItem(r);
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP: case REDIS_REPLY_MAP:
...@@ -657,8 +670,11 @@ int redisReaderGetReply(redisReader *r, void **reply) { ...@@ -657,8 +670,11 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Emit a reply when there is one. */ /* Emit a reply when there is one. */
if (r->ridx == -1) { if (r->ridx == -1) {
if (reply != NULL) if (reply != NULL) {
*reply = r->reply; *reply = r->reply;
} else if (r->reply != NULL && r->fn && r->fn->freeObject) {
r->fn->freeObject(r->reply);
}
r->reply = NULL; r->reply = NULL;
} }
return REDIS_OK; return REDIS_OK;
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
#define REDIS_ERR_EOF 3 /* End of file */ #define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */ #define REDIS_ERR_OOM 5 /* Out of memory */
#define REDIS_ERR_TIMEOUT 6 /* Timed out */
#define REDIS_ERR_OTHER 2 /* Everything else... */ #define REDIS_ERR_OTHER 2 /* Everything else... */
#define REDIS_REPLY_STRING 1 #define REDIS_REPLY_STRING 1
...@@ -55,12 +56,12 @@ ...@@ -55,12 +56,12 @@
#define REDIS_REPLY_ERROR 6 #define REDIS_REPLY_ERROR 6
#define REDIS_REPLY_DOUBLE 7 #define REDIS_REPLY_DOUBLE 7
#define REDIS_REPLY_BOOL 8 #define REDIS_REPLY_BOOL 8
#define REDIS_REPLY_VERB 9
#define REDIS_REPLY_MAP 9 #define REDIS_REPLY_MAP 9
#define REDIS_REPLY_SET 10 #define REDIS_REPLY_SET 10
#define REDIS_REPLY_ATTR 11 #define REDIS_REPLY_ATTR 11
#define REDIS_REPLY_PUSH 12 #define REDIS_REPLY_PUSH 12
#define REDIS_REPLY_BIGNUM 13 #define REDIS_REPLY_BIGNUM 13
#define REDIS_REPLY_VERB 14
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ #define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
...@@ -79,7 +80,7 @@ typedef struct redisReadTask { ...@@ -79,7 +80,7 @@ typedef struct redisReadTask {
typedef struct redisReplyObjectFunctions { typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t); void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int); void *(*createArray)(const redisReadTask*, size_t);
void *(*createInteger)(const redisReadTask*, long long); void *(*createInteger)(const redisReadTask*, long long);
void *(*createDouble)(const redisReadTask*, double, char*, size_t); void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*); void *(*createNil)(const redisReadTask*);
......
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
#define __SDS_H #define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024) #define SDS_MAX_PREALLOC (1024*1024)
#ifdef _MSC_VER
#define __attribute__(x)
#endif
#include <sys/types.h> #include <sys/types.h>
#include <stdarg.h> #include <stdarg.h>
...@@ -132,20 +135,20 @@ static inline void sdssetlen(sds s, size_t newlen) { ...@@ -132,20 +135,20 @@ static inline void sdssetlen(sds s, size_t newlen) {
case SDS_TYPE_5: case SDS_TYPE_5:
{ {
unsigned char *fp = ((unsigned char*)s)-1; unsigned char *fp = ((unsigned char*)s)-1;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
} }
break; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->len = newlen; SDS_HDR(8,s)->len = (uint8_t)newlen;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->len = newlen; SDS_HDR(16,s)->len = (uint16_t)newlen;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->len = newlen; SDS_HDR(32,s)->len = (uint32_t)newlen;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->len = newlen; SDS_HDR(64,s)->len = (uint64_t)newlen;
break; break;
} }
} }
...@@ -156,21 +159,21 @@ static inline void sdsinclen(sds s, size_t inc) { ...@@ -156,21 +159,21 @@ static inline void sdsinclen(sds s, size_t inc) {
case SDS_TYPE_5: case SDS_TYPE_5:
{ {
unsigned char *fp = ((unsigned char*)s)-1; unsigned char *fp = ((unsigned char*)s)-1;
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
} }
break; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->len += inc; SDS_HDR(8,s)->len += (uint8_t)inc;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->len += inc; SDS_HDR(16,s)->len += (uint16_t)inc;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->len += inc; SDS_HDR(32,s)->len += (uint32_t)inc;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->len += inc; SDS_HDR(64,s)->len += (uint64_t)inc;
break; break;
} }
} }
...@@ -200,16 +203,16 @@ static inline void sdssetalloc(sds s, size_t newlen) { ...@@ -200,16 +203,16 @@ static inline void sdssetalloc(sds s, size_t newlen) {
/* Nothing to do, this type has no total allocation info. */ /* Nothing to do, this type has no total allocation info. */
break; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->alloc = newlen; SDS_HDR(8,s)->alloc = (uint8_t)newlen;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->alloc = newlen; SDS_HDR(16,s)->alloc = (uint16_t)newlen;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->alloc = newlen; SDS_HDR(32,s)->alloc = (uint32_t)newlen;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->alloc = newlen; SDS_HDR(64,s)->alloc = (uint64_t)newlen;
break; break;
} }
} }
......
/*
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
*
* 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.
*/
#define REDIS_SOCKCOMPAT_IMPLEMENTATION
#include "sockcompat.h"
#ifdef _WIN32
static int _wsaErrorToErrno(int err) {
switch (err) {
case WSAEWOULDBLOCK:
return EWOULDBLOCK;
case WSAEINPROGRESS:
return EINPROGRESS;
case WSAEALREADY:
return EALREADY;
case WSAENOTSOCK:
return ENOTSOCK;
case WSAEDESTADDRREQ:
return EDESTADDRREQ;
case WSAEMSGSIZE:
return EMSGSIZE;
case WSAEPROTOTYPE:
return EPROTOTYPE;
case WSAENOPROTOOPT:
return ENOPROTOOPT;
case WSAEPROTONOSUPPORT:
return EPROTONOSUPPORT;
case WSAEOPNOTSUPP:
return EOPNOTSUPP;
case WSAEAFNOSUPPORT:
return EAFNOSUPPORT;
case WSAEADDRINUSE:
return EADDRINUSE;
case WSAEADDRNOTAVAIL:
return EADDRNOTAVAIL;
case WSAENETDOWN:
return ENETDOWN;
case WSAENETUNREACH:
return ENETUNREACH;
case WSAENETRESET:
return ENETRESET;
case WSAECONNABORTED:
return ECONNABORTED;
case WSAECONNRESET:
return ECONNRESET;
case WSAENOBUFS:
return ENOBUFS;
case WSAEISCONN:
return EISCONN;
case WSAENOTCONN:
return ENOTCONN;
case WSAETIMEDOUT:
return ETIMEDOUT;
case WSAECONNREFUSED:
return ECONNREFUSED;
case WSAELOOP:
return ELOOP;
case WSAENAMETOOLONG:
return ENAMETOOLONG;
case WSAEHOSTUNREACH:
return EHOSTUNREACH;
case WSAENOTEMPTY:
return ENOTEMPTY;
default:
/* We just return a generic I/O error if we could not find a relevant error. */
return EIO;
}
}
static void _updateErrno(int success) {
errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());
}
static int _initWinsock() {
static int s_initialized = 0;
if (!s_initialized) {
static WSADATA wsadata;
int err = WSAStartup(MAKEWORD(2,2), &wsadata);
if (err != 0) {
errno = _wsaErrorToErrno(err);
return 0;
}
s_initialized = 1;
}
return 1;
}
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
/* Note: This function is likely to be called before other functions, so run init here. */
if (!_initWinsock()) {
return EAI_FAIL;
}
switch (getaddrinfo(node, service, hints, res)) {
case 0: return 0;
case WSATRY_AGAIN: return EAI_AGAIN;
case WSAEINVAL: return EAI_BADFLAGS;
case WSAEAFNOSUPPORT: return EAI_FAMILY;
case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;
case WSAHOST_NOT_FOUND: return EAI_NONAME;
case WSATYPE_NOT_FOUND: return EAI_SERVICE;
case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE;
default: return EAI_FAIL; /* Including WSANO_RECOVERY */
}
}
const char *win32_gai_strerror(int errcode) {
switch (errcode) {
case 0: errcode = 0; break;
case EAI_AGAIN: errcode = WSATRY_AGAIN; break;
case EAI_BADFLAGS: errcode = WSAEINVAL; break;
case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break;
case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break;
case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break;
case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break;
case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break;
default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */
}
return gai_strerror(errcode);
}
void win32_freeaddrinfo(struct addrinfo *res) {
freeaddrinfo(res);
}
SOCKET win32_socket(int domain, int type, int protocol) {
SOCKET s;
/* Note: This function is likely to be called before other functions, so run init here. */
if (!_initWinsock()) {
return INVALID_SOCKET;
}
_updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);
return s;
}
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {
int ret = ioctlsocket(fd, (long)request, argp);
_updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1;
}
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
int ret = bind(sockfd, addr, addrlen);
_updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1;
}
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
int ret = connect(sockfd, addr, addrlen);
_updateErrno(ret != SOCKET_ERROR);
/* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
* EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
* logic consistent. */
if (errno == EWOULDBLOCK) {
errno = EINPROGRESS;
}
return ret != SOCKET_ERROR ? ret : -1;
}
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
int ret = 0;
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
if (*optlen >= sizeof (struct timeval)) {
struct timeval *tv = optval;
DWORD timeout = 0;
socklen_t dwlen = 0;
ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
tv->tv_sec = timeout / 1000;
tv->tv_usec = (timeout * 1000) % 1000000;
} else {
ret = WSAEFAULT;
}
*optlen = sizeof (struct timeval);
} else {
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
}
_updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1;
}
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
int ret = 0;
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
struct timeval *tv = optval;
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
} else {
ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
}
_updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1;
}
int win32_close(SOCKET fd) {
int ret = closesocket(fd);
_updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1;
}
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {
int ret = recv(sockfd, (char*)buf, (int)len, flags);
_updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1;
}
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {
int ret = send(sockfd, (const char*)buf, (int)len, flags);
_updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1;
}
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
int ret = WSAPoll(fds, nfds, timeout);
_updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1;
}
#endif /* _WIN32 */
/*
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
*
* 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 __SOCKCOMPAT_H
#define __SOCKCOMPAT_H
#ifndef _WIN32
/* For POSIX systems we use the standard BSD socket API. */
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <poll.h>
#else
/* For Windows we use winsock. */
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stddef.h>
#ifdef _MSC_VER
typedef signed long ssize_t;
#endif
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
const char *win32_gai_strerror(int errcode);
void win32_freeaddrinfo(struct addrinfo *res);
SOCKET win32_socket(int domain, int type, int protocol);
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
int win32_close(SOCKET fd);
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
typedef ULONG nfds_t;
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
#undef gai_strerror
#define gai_strerror(errcode) win32_gai_strerror(errcode)
#define freeaddrinfo(res) win32_freeaddrinfo(res)
#define socket(domain, type, protocol) win32_socket(domain, type, protocol)
#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)
#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)
#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)
#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)
#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)
#define close(fd) win32_close(fd)
#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)
#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)
#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)
#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */
#endif /* _WIN32 */
#endif /* __SOCKCOMPAT_H */
/*
* 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 = "Resource temporarily unavailable";
}
__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
};
...@@ -13,12 +13,16 @@ ...@@ -13,12 +13,16 @@
#include <limits.h> #include <limits.h>
#include "hiredis.h" #include "hiredis.h"
#ifdef HIREDIS_TEST_SSL
#include "hiredis_ssl.h"
#endif
#include "net.h" #include "net.h"
enum connection_type { enum connection_type {
CONN_TCP, CONN_TCP,
CONN_UNIX, CONN_UNIX,
CONN_FD CONN_FD,
CONN_SSL
}; };
struct config { struct config {
...@@ -33,6 +37,14 @@ struct config { ...@@ -33,6 +37,14 @@ struct config {
struct { struct {
const char *path; const char *path;
} unix_sock; } unix_sock;
struct {
const char *host;
int port;
const char *ca_cert;
const char *cert;
const char *key;
} ssl;
}; };
/* The following lines make up our testing "framework" :) */ /* The following lines make up our testing "framework" :) */
...@@ -93,11 +105,27 @@ static int disconnect(redisContext *c, int keep_fd) { ...@@ -93,11 +105,27 @@ static int disconnect(redisContext *c, int keep_fd) {
return -1; return -1;
} }
static void do_ssl_handshake(redisContext *c, struct config config) {
#ifdef HIREDIS_TEST_SSL
redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL);
if (c->err) {
printf("SSL error: %s\n", c->errstr);
redisFree(c);
exit(1);
}
#else
(void) c;
(void) config;
#endif
}
static redisContext *do_connect(struct config config) { static redisContext *do_connect(struct config config) {
redisContext *c = NULL; redisContext *c = NULL;
if (config.type == CONN_TCP) { if (config.type == CONN_TCP) {
c = redisConnect(config.tcp.host, config.tcp.port); c = redisConnect(config.tcp.host, config.tcp.port);
} else if (config.type == CONN_SSL) {
c = redisConnect(config.ssl.host, config.ssl.port);
} else if (config.type == CONN_UNIX) { } else if (config.type == CONN_UNIX) {
c = redisConnectUnix(config.unix_sock.path); c = redisConnectUnix(config.unix_sock.path);
} else if (config.type == CONN_FD) { } else if (config.type == CONN_FD) {
...@@ -121,9 +149,21 @@ static redisContext *do_connect(struct config config) { ...@@ -121,9 +149,21 @@ static redisContext *do_connect(struct config config) {
exit(1); exit(1);
} }
if (config.type == CONN_SSL) {
do_ssl_handshake(c, config);
}
return select_database(c); return select_database(c);
} }
static void do_reconnect(redisContext *c, struct config config) {
redisReconnect(c);
if (config.type == CONN_SSL) {
do_ssl_handshake(c, config);
}
}
static void test_format_commands(void) { static void test_format_commands(void) {
char *cmd; char *cmd;
int len; int len;
...@@ -360,7 +400,8 @@ static void test_reply_reader(void) { ...@@ -360,7 +400,8 @@ static void test_reply_reader(void) {
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Set error when array > INT_MAX: "); #if LLONG_MAX > SIZE_MAX
test("Set error when array > SIZE_MAX: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29); redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
ret = redisReaderGetReply(reader,&reply); ret = redisReaderGetReply(reader,&reply);
...@@ -369,7 +410,6 @@ static void test_reply_reader(void) { ...@@ -369,7 +410,6 @@ static void test_reply_reader(void) {
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
#if LLONG_MAX > SIZE_MAX
test("Set error when bulk > SIZE_MAX: "); test("Set error when bulk > SIZE_MAX: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28); redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
...@@ -434,22 +474,23 @@ static void test_free_null(void) { ...@@ -434,22 +474,23 @@ static void test_free_null(void) {
test_cond(reply == NULL); test_cond(reply == NULL);
} }
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
static void test_blocking_connection_errors(void) { static void test_blocking_connection_errors(void) {
redisContext *c; redisContext *c;
struct addrinfo hints = {.ai_family = AF_INET}; struct addrinfo hints = {.ai_family = AF_INET};
struct addrinfo *ai_tmp = NULL; struct addrinfo *ai_tmp = NULL;
const char *bad_domain = "idontexist.com";
int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp); int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
if (rv != 0) { if (rv != 0) {
// Address does *not* exist // Address does *not* exist
test("Returns error when host cannot be resolved: "); test("Returns error when host cannot be resolved: ");
// First see if this domain name *actually* resolves to NXDOMAIN // First see if this domain name *actually* resolves to NXDOMAIN
c = redisConnect("dontexist.com", 6379); c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
test_cond( test_cond(
c->err == REDIS_ERR_OTHER && c->err == REDIS_ERR_OTHER &&
(strcmp(c->errstr, "Name or service not known") == 0 || (strcmp(c->errstr, "Name or service not known") == 0 ||
strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 || strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
strcmp(c->errstr, "Name does not resolve") == 0 ||
strcmp(c->errstr, strcmp(c->errstr,
"nodename nor servname provided, or not known") == 0 || "nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "No address associated with hostname") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 ||
...@@ -574,7 +615,8 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -574,7 +615,8 @@ static void test_blocking_connection_timeouts(struct config config) {
c = do_connect(config); c = do_connect(config);
test("Does not return a reply when the command times out: "); test("Does not return a reply when the command times out: ");
s = write(c->fd, cmd, strlen(cmd)); redisAppendFormattedCommand(c, cmd, strlen(cmd));
s = c->funcs->write(c);
tv.tv_sec = 0; tv.tv_sec = 0;
tv.tv_usec = 10000; tv.tv_usec = 10000;
redisSetTimeout(c, tv); redisSetTimeout(c, tv);
...@@ -583,7 +625,7 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -583,7 +625,7 @@ static void test_blocking_connection_timeouts(struct config config) {
freeReplyObject(reply); freeReplyObject(reply);
test("Reconnect properly reconnects after a timeout: "); test("Reconnect properly reconnects after a timeout: ");
redisReconnect(c); do_reconnect(c, config);
reply = redisCommand(c, "PING"); reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -591,7 +633,7 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -591,7 +633,7 @@ static void test_blocking_connection_timeouts(struct config config) {
test("Reconnect properly uses owned parameters: "); test("Reconnect properly uses owned parameters: ");
config.tcp.host = "foo"; config.tcp.host = "foo";
config.unix_sock.path = "foo"; config.unix_sock.path = "foo";
redisReconnect(c); do_reconnect(c, config);
reply = redisCommand(c, "PING"); reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -894,6 +936,23 @@ int main(int argc, char **argv) { ...@@ -894,6 +936,23 @@ int main(int argc, char **argv) {
throughput = 0; throughput = 0;
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
test_inherit_fd = 0; test_inherit_fd = 0;
#ifdef HIREDIS_TEST_SSL
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
argv++; argc--;
cfg.ssl.port = atoi(argv[0]);
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
argv++; argc--;
cfg.ssl.host = argv[0];
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
argv++; argc--;
cfg.ssl.ca_cert = argv[0];
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
argv++; argc--;
cfg.ssl.cert = argv[0];
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
argv++; argc--;
cfg.ssl.key = argv[0];
#endif
} else { } else {
fprintf(stderr, "Invalid argument: %s\n", argv[0]); fprintf(stderr, "Invalid argument: %s\n", argv[0]);
exit(1); exit(1);
...@@ -922,6 +981,20 @@ int main(int argc, char **argv) { ...@@ -922,6 +981,20 @@ int main(int argc, char **argv) {
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
#ifdef HIREDIS_TEST_SSL
if (cfg.ssl.port && cfg.ssl.host) {
printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
cfg.type = CONN_SSL;
test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg);
test_append_formatted_commands(cfg);
if (throughput) test_throughput(cfg);
}
#endif
if (test_inherit_fd) { if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path); printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
cfg.type = CONN_FD; cfg.type = CONN_FD;
......
#!/bin/sh -ue
REDIS_SERVER=${REDIS_SERVER:-redis-server}
REDIS_PORT=${REDIS_PORT:-56379}
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
TEST_SSL=${TEST_SSL:-0}
SSL_TEST_ARGS=
tmpdir=$(mktemp -d)
PID_FILE=${tmpdir}/hiredis-test-redis.pid
SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
if [ "$TEST_SSL" = "1" ]; then
SSL_CA_CERT=${tmpdir}/ca.crt
SSL_CA_KEY=${tmpdir}/ca.key
SSL_CERT=${tmpdir}/redis.crt
SSL_KEY=${tmpdir}/redis.key
openssl genrsa -out ${tmpdir}/ca.key 4096
openssl req \
-x509 -new -nodes -sha256 \
-key ${SSL_CA_KEY} \
-days 3650 \
-subj '/CN=Hiredis Test CA' \
-out ${SSL_CA_CERT}
openssl genrsa -out ${SSL_KEY} 2048
openssl req \
-new -sha256 \
-key ${SSL_KEY} \
-subj '/CN=Hiredis Test Cert' | \
openssl x509 \
-req -sha256 \
-CA ${SSL_CA_CERT} \
-CAkey ${SSL_CA_KEY} \
-CAserial ${tmpdir}/ca.txt \
-CAcreateserial \
-days 365 \
-out ${SSL_CERT}
SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
fi
cleanup() {
set +e
kill $(cat ${PID_FILE})
rm -rf ${tmpdir}
}
trap cleanup INT TERM EXIT
cat > ${tmpdir}/redis.conf <<EOF
daemonize yes
pidfile ${PID_FILE}
port ${REDIS_PORT}
bind 127.0.0.1
unixsocket ${SOCK_FILE}
EOF
if [ "$TEST_SSL" = "1" ]; then
cat >> ${tmpdir}/redis.conf <<EOF
tls-port ${REDIS_SSL_PORT}
tls-ca-cert-file ${SSL_CA_CERT}
tls-cert-file ${SSL_CERT}
tls-key-file ${SSL_KEY}
EOF
fi
cat ${tmpdir}/redis.conf
${REDIS_SERVER} ${tmpdir}/redis.conf
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS}
...@@ -2,10 +2,20 @@ ...@@ -2,10 +2,20 @@
#define _WIN32_HELPER_INCLUDE #define _WIN32_HELPER_INCLUDE
#ifdef _MSC_VER #ifdef _MSC_VER
#include <winsock2.h> /* for struct timeval */
#ifndef inline #ifndef inline
#define inline __inline #define inline __inline
#endif #endif
#ifndef strcasecmp
#define strcasecmp stricmp
#endif
#ifndef strncasecmp
#define strncasecmp strnicmp
#endif
#ifndef va_copy #ifndef va_copy
#define va_copy(d,s) ((d) = (s)) #define va_copy(d,s) ((d) = (s))
#endif #endif
...@@ -37,6 +47,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...) ...@@ -37,6 +47,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...)
return count; return count;
} }
#endif #endif
#endif /* _MSC_VER */
#endif #ifdef _WIN32
#endif #define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
\ No newline at end of file #endif /* _WIN32 */
#endif /* _WIN32_HELPER_INCLUDE */
...@@ -336,13 +336,11 @@ replica-read-only yes ...@@ -336,13 +336,11 @@ replica-read-only yes
# Replication SYNC strategy: disk or socket. # Replication SYNC strategy: disk or socket.
# #
# ------------------------------------------------------- # New replicas and reconnecting replicas that are not able to continue the
# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY # replication process just receiving differences, need to do what is called a
# ------------------------------------------------------- # "full synchronization". An RDB file is transmitted from the master to the
# replicas.
# #
# New replicas and reconnecting replicas that are not able to continue the replication
# process just receiving differences, need to do what is called a "full
# synchronization". An RDB file is transmitted from the master to the replicas.
# The transmission can happen in two different ways: # The transmission can happen in two different ways:
# #
# 1) Disk-backed: The Redis master creates a new process that writes the RDB # 1) Disk-backed: The Redis master creates a new process that writes the RDB
...@@ -352,14 +350,14 @@ replica-read-only yes ...@@ -352,14 +350,14 @@ replica-read-only yes
# RDB file to replica sockets, without touching the disk at all. # RDB file to replica sockets, without touching the disk at all.
# #
# With disk-backed replication, while the RDB file is generated, more replicas # With disk-backed replication, while the RDB file is generated, more replicas
# can be queued and served with the RDB file as soon as the current child producing # can be queued and served with the RDB file as soon as the current child
# the RDB file finishes its work. With diskless replication instead once # producing the RDB file finishes its work. With diskless replication instead
# the transfer starts, new replicas arriving will be queued and a new transfer # once the transfer starts, new replicas arriving will be queued and a new
# will start when the current one terminates. # transfer will start when the current one terminates.
# #
# When diskless replication is used, the master waits a configurable amount of # When diskless replication is used, the master waits a configurable amount of
# time (in seconds) before starting the transfer in the hope that multiple replicas # time (in seconds) before starting the transfer in the hope that multiple
# will arrive and the transfer can be parallelized. # replicas will arrive and the transfer can be parallelized.
# #
# With slow disks and fast (large bandwidth) networks, diskless replication # With slow disks and fast (large bandwidth) networks, diskless replication
# works better. # works better.
...@@ -370,16 +368,42 @@ repl-diskless-sync no ...@@ -370,16 +368,42 @@ repl-diskless-sync no
# to the replicas. # to the replicas.
# #
# This is important since once the transfer starts, it is not possible to serve # This is important since once the transfer starts, it is not possible to serve
# new replicas arriving, that will be queued for the next RDB transfer, so the server # new replicas arriving, that will be queued for the next RDB transfer, so the
# waits a delay in order to let more replicas arrive. # server waits a delay in order to let more replicas arrive.
# #
# The delay is specified in seconds, and by default is 5 seconds. To disable # The delay is specified in seconds, and by default is 5 seconds. To disable
# it entirely just set it to 0 seconds and the transfer will start ASAP. # it entirely just set it to 0 seconds and the transfer will start ASAP.
repl-diskless-sync-delay 5 repl-diskless-sync-delay 5
# Replicas send PINGs to server in a predefined interval. It's possible to change # -----------------------------------------------------------------------------
# this interval with the repl_ping_replica_period option. The default value is 10 # WARNING: RDB diskless load is experimental. Since in this setup the replica
# seconds. # does not immediately store an RDB on disk, it may cause data loss during
# failovers. RDB diskless load + Redis modules not handling I/O reads may also
# cause Redis to abort in case of I/O errors during the initial synchronization
# stage with the master. Use only if your do what you are doing.
# -----------------------------------------------------------------------------
#
# Replica can load the RDB it reads from the replication link directly from the
# socket, or store the RDB to a file and read that file after it was completely
# recived from the master.
#
# In many cases the disk is slower than the network, and storing and loading
# the RDB file may increase replication time (and even increase the master's
# Copy on Write memory and salve buffers).
# However, parsing the RDB file directly from the socket may mean that we have
# to flush the contents of the current database before the full rdb was
# received. For this reason we have the following options:
#
# "disabled" - Don't use diskless load (store the rdb file to the disk first)
# "on-empty-db" - Use diskless load only when it is completely safe.
# "swapdb" - Keep a copy of the current db contents in RAM while parsing
# the data directly from the socket. note that this requires
# sufficient memory, if you don't have it, you risk an OOM kill.
repl-diskless-load disabled
# Replicas send PINGs to server in a predefined interval. It's possible to
# change this interval with the repl_ping_replica_period option. The default
# value is 10 seconds.
# #
# repl-ping-replica-period 10 # repl-ping-replica-period 10
...@@ -411,10 +435,10 @@ repl-diskless-sync-delay 5 ...@@ -411,10 +435,10 @@ repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no repl-disable-tcp-nodelay no
# Set the replication backlog size. The backlog is a buffer that accumulates # Set the replication backlog size. The backlog is a buffer that accumulates
# replica data when replicas are disconnected for some time, so that when a replica # replica data when replicas are disconnected for some time, so that when a
# wants to reconnect again, often a full resync is not needed, but a partial # replica wants to reconnect again, often a full resync is not needed, but a
# resync is enough, just passing the portion of data the replica missed while # partial resync is enough, just passing the portion of data the replica
# disconnected. # missed while disconnected.
# #
# The bigger the replication backlog, the longer the time the replica can be # The bigger the replication backlog, the longer the time the replica can be
# disconnected and later be able to perform a partial resynchronization. # disconnected and later be able to perform a partial resynchronization.
...@@ -436,13 +460,13 @@ repl-disable-tcp-nodelay no ...@@ -436,13 +460,13 @@ repl-disable-tcp-nodelay no
# #
# repl-backlog-ttl 3600 # repl-backlog-ttl 3600
# The replica priority is an integer number published by Redis in the INFO output. # The replica priority is an integer number published by Redis in the INFO
# It is used by Redis Sentinel in order to select a replica to promote into a # output. It is used by Redis Sentinel in order to select a replica to promote
# master if the master is no longer working correctly. # into a master if the master is no longer working correctly.
# #
# A replica with a low priority number is considered better for promotion, so # A replica with a low priority number is considered better for promotion, so
# for instance if there are three replicas with priority 10, 100, 25 Sentinel will # for instance if there are three replicas with priority 10, 100, 25 Sentinel
# pick the one with priority 10, that is the lowest. # will pick the one with priority 10, that is the lowest.
# #
# However a special priority of 0 marks the replica as not able to perform the # However a special priority of 0 marks the replica as not able to perform the
# role of master, so a replica with priority of 0 will never be selected by # role of master, so a replica with priority of 0 will never be selected by
...@@ -502,6 +526,39 @@ replica-priority 100 ...@@ -502,6 +526,39 @@ replica-priority 100
# replica-announce-ip 5.5.5.5 # replica-announce-ip 5.5.5.5
# replica-announce-port 1234 # replica-announce-port 1234
############################### KEYS TRACKING #################################
# Redis implements server assisted support for client side caching of values.
# This is implemented using an invalidation table that remembers, using
# 16 millions of slots, what clients may have certain subsets of keys. In turn
# this is used in order to send invalidation messages to clients. Please
# to understand more about the feature check this page:
#
# https://redis.io/topics/client-side-caching
#
# When tracking is enabled for a client, all the read only queries are assumed
# to be cached: this will force Redis to store information in the invalidation
# table. When keys are modified, such information is flushed away, and
# invalidation messages are sent to the clients. However if the workload is
# heavily dominated by reads, Redis could use more and more memory in order
# to track the keys fetched by many clients.
#
# For this reason it is possible to configure a maximum fill value for the
# invalidation table. By default it is set to 10%, and once this limit is
# reached, Redis will start to evict caching slots in the invalidation table
# even if keys are not modified, just to reclaim memory: this will in turn
# force the clients to invalidate the cached values. Basically the table
# maximum fill rate is a trade off between the memory you want to spend server
# side to track information about who cached what, and the ability of clients
# to retain cached objects in memory.
#
# If you set the value to 0, it means there are no limits, and all the 16
# millions of caching slots can be used at the same time. In the "stats"
# INFO section, you can find information about the amount of caching slots
# used at every given moment.
#
# tracking-table-max-fill 10
################################## SECURITY ################################### ################################## SECURITY ###################################
# Warning: since Redis is pretty fast an outside user can try up to # Warning: since Redis is pretty fast an outside user can try up to
...@@ -731,17 +788,17 @@ replica-priority 100 ...@@ -731,17 +788,17 @@ replica-priority 100
# DEL commands to the replica as keys evict in the master side. # DEL commands to the replica as keys evict in the master side.
# #
# This behavior ensures that masters and replicas stay consistent, and is usually # This behavior ensures that masters and replicas stay consistent, and is usually
# what you want, however if your replica is writable, or you want the replica to have # what you want, however if your replica is writable, or you want the replica
# a different memory setting, and you are sure all the writes performed to the # to have a different memory setting, and you are sure all the writes performed
# replica are idempotent, then you may change this default (but be sure to understand # to the replica are idempotent, then you may change this default (but be sure
# what you are doing). # to understand what you are doing).
# #
# Note that since the replica by default does not evict, it may end using more # Note that since the replica by default does not evict, it may end using more
# memory than the one set via maxmemory (there are certain buffers that may # memory than the one set via maxmemory (there are certain buffers that may
# be larger on the replica, or data structures may sometimes take more memory and so # be larger on the replica, or data structures may sometimes take more memory
# forth). So make sure you monitor your replicas and make sure they have enough # and so forth). So make sure you monitor your replicas and make sure they
# memory to never hit a real out-of-memory condition before the master hits # have enough memory to never hit a real out-of-memory condition before the
# the configured maxmemory setting. # master hits the configured maxmemory setting.
# #
# replica-ignore-maxmemory yes # replica-ignore-maxmemory yes
......
...@@ -13,4 +13,4 @@ then ...@@ -13,4 +13,4 @@ then
fi fi
make -C tests/modules && \ make -C tests/modules && \
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter "${@}" $TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/fork --single unit/moduleapi/testrdb --single unit/moduleapi/infotest --single unit/moduleapi/propagate "${@}"
...@@ -164,7 +164,7 @@ endif ...@@ -164,7 +164,7 @@ endif
REDIS_SERVER_NAME=redis-server REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o acl.o gopher.o REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o acl.o gopher.o tracking.o sha256.o
REDIS_CLI_NAME=redis-cli REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
REDIS_BENCHMARK_NAME=redis-benchmark REDIS_BENCHMARK_NAME=redis-benchmark
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
*/ */
#include "server.h" #include "server.h"
#include "sha256.h"
#include <fcntl.h> #include <fcntl.h>
/* ============================================================================= /* =============================================================================
...@@ -93,6 +94,9 @@ void ACLResetSubcommandsForCommand(user *u, unsigned long id); ...@@ -93,6 +94,9 @@ void ACLResetSubcommandsForCommand(user *u, unsigned long id);
void ACLResetSubcommands(user *u); void ACLResetSubcommands(user *u);
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub); void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
/* The length of the string representation of a hashed password. */
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
/* ============================================================================= /* =============================================================================
* Helper functions for the rest of the ACL implementation * Helper functions for the rest of the ACL implementation
* ==========================================================================*/ * ==========================================================================*/
...@@ -139,6 +143,25 @@ int time_independent_strcmp(char *a, char *b) { ...@@ -139,6 +143,25 @@ int time_independent_strcmp(char *a, char *b) {
return diff; /* If zero strings are the same. */ return diff; /* If zero strings are the same. */
} }
/* Given an SDS string, returns the SHA256 hex representation as a
* new SDS string. */
sds ACLHashPassword(unsigned char *cleartext, size_t len) {
SHA256_CTX ctx;
unsigned char hash[SHA256_BLOCK_SIZE];
char hex[HASH_PASSWORD_LEN];
char *cset = "0123456789abcdef";
sha256_init(&ctx);
sha256_update(&ctx,(unsigned char*)cleartext,len);
sha256_final(&ctx,hash);
for (int j = 0; j < SHA256_BLOCK_SIZE; j++) {
hex[j*2] = cset[((hash[j]&0xF0)>>4)];
hex[j*2+1] = cset[(hash[j]&0xF)];
}
return sdsnewlen(hex,HASH_PASSWORD_LEN);
}
/* ============================================================================= /* =============================================================================
* Low level ACL API * Low level ACL API
* ==========================================================================*/ * ==========================================================================*/
...@@ -295,7 +318,7 @@ int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) { ...@@ -295,7 +318,7 @@ int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
* Note that this function does not check the ALLCOMMANDS flag of the user * Note that this function does not check the ALLCOMMANDS flag of the user
* but just the lowlevel bitmask. * but just the lowlevel bitmask.
* *
* If the bit overflows the user internal represetation, zero is returned * If the bit overflows the user internal representation, zero is returned
* in order to disallow the execution of the command in such edge case. */ * in order to disallow the execution of the command in such edge case. */
int ACLGetUserCommandBit(user *u, unsigned long id) { int ACLGetUserCommandBit(user *u, unsigned long id) {
uint64_t word, bit; uint64_t word, bit;
...@@ -311,7 +334,7 @@ int ACLUserCanExecuteFutureCommands(user *u) { ...@@ -311,7 +334,7 @@ int ACLUserCanExecuteFutureCommands(user *u) {
} }
/* Set the specified command bit for the specified user to 'value' (0 or 1). /* Set the specified command bit for the specified user to 'value' (0 or 1).
* If the bit overflows the user internal represetation, no operation * If the bit overflows the user internal representation, no operation
* is performed. As a side effect of calling this function with a value of * is performed. As a side effect of calling this function with a value of
* zero, the user flag ALLCOMMANDS is cleared since it is no longer possible * zero, the user flag ALLCOMMANDS is cleared since it is no longer possible
* to skip the command bit explicit test. */ * to skip the command bit explicit test. */
...@@ -350,7 +373,7 @@ int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) { ...@@ -350,7 +373,7 @@ int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) {
/* Return the number of commands allowed (on) and denied (off) for the user 'u' /* Return the number of commands allowed (on) and denied (off) for the user 'u'
* in the subset of commands flagged with the specified category name. * in the subset of commands flagged with the specified category name.
* If the categoty name is not valid, C_ERR is returend, otherwise C_OK is * If the category name is not valid, C_ERR is returned, otherwise C_OK is
* returned and on and off are populated by reference. */ * returned and on and off are populated by reference. */
int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off, int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off,
const char *category) const char *category)
...@@ -502,7 +525,7 @@ sds ACLDescribeUser(user *u) { ...@@ -502,7 +525,7 @@ sds ACLDescribeUser(user *u) {
listRewind(u->passwords,&li); listRewind(u->passwords,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
sds thispass = listNodeValue(ln); sds thispass = listNodeValue(ln);
res = sdscatlen(res,">",1); res = sdscatlen(res,"#",1);
res = sdscatsds(res,thispass); res = sdscatsds(res,thispass);
res = sdscatlen(res," ",1); res = sdscatlen(res," ",1);
} }
...@@ -626,10 +649,17 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) { ...@@ -626,10 +649,17 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
* It is possible to specify multiple patterns. * It is possible to specify multiple patterns.
* allkeys Alias for ~* * allkeys Alias for ~*
* resetkeys Flush the list of allowed keys patterns. * resetkeys Flush the list of allowed keys patterns.
* ><password> Add this passowrd to the list of valid password for the user. * ><password> Add this password to the list of valid password for the user.
* For example >mypass will add "mypass" to the list. * For example >mypass will add "mypass" to the list.
* This directive clears the "nopass" flag (see later). * This directive clears the "nopass" flag (see later).
* #<hash> Add this password hash to the list of valid hashes for
* the user. This is useful if you have previously computed
* the hash, and don't want to store it in plaintext.
* This directive clears the "nopass" flag (see later).
* <<password> Remove this password from the list of valid passwords. * <<password> Remove this password from the list of valid passwords.
* !<hash> Remove this hashed password from the list of valid passwords.
* This is useful when you want to remove a password just by
* hash without knowing its plaintext version at all.
* nopass All the set passwords of the user are removed, and the user * nopass All the set passwords of the user are removed, and the user
* is flagged as requiring no password: it means that every * is flagged as requiring no password: it means that every
* password will work against this user. If this directive is * password will work against this user. If this directive is
...@@ -665,6 +695,7 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) { ...@@ -665,6 +695,7 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
* EEXIST: You are adding a key pattern after "*" was already added. This is * EEXIST: You are adding a key pattern after "*" was already added. This is
* almost surely an error on the user side. * almost surely an error on the user side.
* ENODEV: The password you are trying to remove from the user does not exist. * ENODEV: The password you are trying to remove from the user does not exist.
* EBADMSG: The hash you are trying to add is not a valid hash.
*/ */
int ACLSetUser(user *u, const char *op, ssize_t oplen) { int ACLSetUser(user *u, const char *op, ssize_t oplen) {
if (oplen == -1) oplen = strlen(op); if (oplen == -1) oplen = strlen(op);
...@@ -700,14 +731,48 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { ...@@ -700,14 +731,48 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
} else if (!strcasecmp(op,"resetpass")) { } else if (!strcasecmp(op,"resetpass")) {
u->flags &= ~USER_FLAG_NOPASS; u->flags &= ~USER_FLAG_NOPASS;
listEmpty(u->passwords); listEmpty(u->passwords);
} else if (op[0] == '>') { } else if (op[0] == '>' || op[0] == '#') {
sds newpass = sdsnewlen(op+1,oplen-1); sds newpass;
if (op[0] == '>') {
newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
} else {
if (oplen != HASH_PASSWORD_LEN + 1) {
errno = EBADMSG;
return C_ERR;
}
/* Password hashes can only be characters that represent
* hexadecimal values, which are numbers and lowercase
* characters 'a' through 'f'.
*/
for(int i = 1; i < HASH_PASSWORD_LEN + 1; i++) {
char c = op[i];
if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
errno = EBADMSG;
return C_ERR;
}
}
newpass = sdsnewlen(op+1,oplen-1);
}
listNode *ln = listSearchKey(u->passwords,newpass); listNode *ln = listSearchKey(u->passwords,newpass);
/* Avoid re-adding the same password multiple times. */ /* Avoid re-adding the same password multiple times. */
if (ln == NULL) listAddNodeTail(u->passwords,newpass); if (ln == NULL)
listAddNodeTail(u->passwords,newpass);
else
sdsfree(newpass);
u->flags &= ~USER_FLAG_NOPASS; u->flags &= ~USER_FLAG_NOPASS;
} else if (op[0] == '<') { } else if (op[0] == '<' || op[0] == '!') {
sds delpass = sdsnewlen(op+1,oplen-1); sds delpass;
if (op[0] == '<') {
delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
} else {
if (oplen != HASH_PASSWORD_LEN + 1) {
errno = EBADMSG;
return C_ERR;
}
delpass = sdsnewlen(op+1,oplen-1);
}
listNode *ln = listSearchKey(u->passwords,delpass); listNode *ln = listSearchKey(u->passwords,delpass);
sdsfree(delpass); sdsfree(delpass);
if (ln) { if (ln) {
...@@ -724,7 +789,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { ...@@ -724,7 +789,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
sds newpat = sdsnewlen(op+1,oplen-1); sds newpat = sdsnewlen(op+1,oplen-1);
listNode *ln = listSearchKey(u->patterns,newpat); listNode *ln = listSearchKey(u->patterns,newpat);
/* Avoid re-adding the same pattern multiple times. */ /* Avoid re-adding the same pattern multiple times. */
if (ln == NULL) listAddNodeTail(u->patterns,newpat); if (ln == NULL)
listAddNodeTail(u->patterns,newpat);
else
sdsfree(newpat);
u->flags &= ~USER_FLAG_ALLKEYS; u->flags &= ~USER_FLAG_ALLKEYS;
} else if (op[0] == '+' && op[1] != '@') { } else if (op[0] == '+' && op[1] != '@') {
if (strchr(op,'|') == NULL) { if (strchr(op,'|') == NULL) {
...@@ -822,6 +890,9 @@ char *ACLSetUserStringError(void) { ...@@ -822,6 +890,9 @@ char *ACLSetUserStringError(void) {
else if (errno == ENODEV) else if (errno == ENODEV)
errmsg = "The password you are trying to remove from the user does " errmsg = "The password you are trying to remove from the user does "
"not exist"; "not exist";
else if (errno == EBADMSG)
errmsg = "The password hash must be exactly 64 characters and contain "
"only lowercase hexadecimal characters";
return errmsg; return errmsg;
} }
...@@ -879,11 +950,15 @@ int ACLCheckUserCredentials(robj *username, robj *password) { ...@@ -879,11 +950,15 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
listIter li; listIter li;
listNode *ln; listNode *ln;
listRewind(u->passwords,&li); listRewind(u->passwords,&li);
sds hashed = ACLHashPassword(password->ptr,sdslen(password->ptr));
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
sds thispass = listNodeValue(ln); sds thispass = listNodeValue(ln);
if (!time_independent_strcmp(password->ptr, thispass)) if (!time_independent_strcmp(hashed, thispass)) {
sdsfree(hashed);
return C_OK; return C_OK;
} }
}
sdsfree(hashed);
/* If we reached this point, no password matched. */ /* If we reached this point, no password matched. */
errno = EINVAL; errno = EINVAL;
...@@ -949,9 +1024,9 @@ user *ACLGetUserByName(const char *name, size_t namelen) { ...@@ -949,9 +1024,9 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
return myuser; return myuser;
} }
/* Check if the command ready to be excuted in the client 'c', and already /* Check if the command is ready to be executed in the client 'c', already
* referenced by c->cmd, can be executed by this client according to the * referenced by c->cmd, and can be executed by this client according to the
* ACls associated to the client user c->user. * ACLs associated to the client user c->user.
* *
* If the user can execute the command ACL_OK is returned, otherwise * If the user can execute the command ACL_OK is returned, otherwise
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the * ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
...@@ -1122,7 +1197,7 @@ int ACLLoadConfiguredUsers(void) { ...@@ -1122,7 +1197,7 @@ int ACLLoadConfiguredUsers(void) {
} }
/* This function loads the ACL from the specified filename: every line /* This function loads the ACL from the specified filename: every line
* is validated and shold be either empty or in the format used to specify * is validated and should be either empty or in the format used to specify
* users in the redis.conf configuration or in the ACL file, that is: * users in the redis.conf configuration or in the ACL file, that is:
* *
* user <username> ... rules ... * user <username> ... rules ...
...@@ -1172,7 +1247,7 @@ sds ACLLoadFromFile(const char *filename) { ...@@ -1172,7 +1247,7 @@ sds ACLLoadFromFile(const char *filename) {
* to the real user mentioned in the ACL line. */ * to the real user mentioned in the ACL line. */
user *fakeuser = ACLCreateUnlinkedUser(); user *fakeuser = ACLCreateUnlinkedUser();
/* We do all the loading in a fresh insteance of the Users radix tree, /* We do all the loading in a fresh instance of the Users radix tree,
* so if there are errors loading the ACL file we can rollback to the * so if there are errors loading the ACL file we can rollback to the
* old version. */ * old version. */
rax *old_users = Users; rax *old_users = Users;
...@@ -1248,7 +1323,7 @@ sds ACLLoadFromFile(const char *filename) { ...@@ -1248,7 +1323,7 @@ sds ACLLoadFromFile(const char *filename) {
} }
/* Note that the same rules already applied to the fake user, so /* Note that the same rules already applied to the fake user, so
* we just assert that everything goess well: it should. */ * we just assert that everything goes well: it should. */
for (j = 2; j < argc; j++) for (j = 2; j < argc; j++)
serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK); serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK);
...@@ -1611,7 +1686,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) { ...@@ -1611,7 +1686,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
setDeferredSetLen(c, flaglen, flagcount); setDeferredSetLen(c, flaglen, flagcount);
} }
/* AUTH <passowrd> /* AUTH <password>
* AUTH <username> <password> (Redis >= 6.0 form) * AUTH <username> <password> (Redis >= 6.0 form)
* *
* When the user is omitted it means that we are trying to authenticate * When the user is omitted it means that we are trying to authenticate
......
...@@ -193,6 +193,20 @@ int anetSendTimeout(char *err, int fd, long long ms) { ...@@ -193,6 +193,20 @@ int anetSendTimeout(char *err, int fd, long long ms) {
return ANET_OK; return ANET_OK;
} }
/* Set the socket receive timeout (SO_RCVTIMEO socket option) to the specified
* number of milliseconds, or disable it if the 'ms' argument is zero. */
int anetRecvTimeout(char *err, int fd, long long ms) {
struct timeval tv;
tv.tv_sec = ms/1000;
tv.tv_usec = (ms%1000)*1000;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
anetSetError(err, "setsockopt SO_RCVTIMEO: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to /* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
* do the actual work. It resolves the hostname "host" and set the string * do the actual work. It resolves the hostname "host" and set the string
* representation of the IP address into the buffer pointed by "ipbuf". * representation of the IP address into the buffer pointed by "ipbuf".
......
...@@ -70,6 +70,7 @@ int anetEnableTcpNoDelay(char *err, int fd); ...@@ -70,6 +70,7 @@ int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd); int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd); int anetTcpKeepAlive(char *err, int fd);
int anetSendTimeout(char *err, int fd, long long ms); int anetSendTimeout(char *err, int fd, long long ms);
int anetRecvTimeout(char *err, int fd, long long ms);
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port); int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
int anetKeepAlive(char *err, int fd, int interval); int anetKeepAlive(char *err, int fd, int interval);
int anetSockName(int fd, char *ip, size_t ip_len, int *port); int anetSockName(int fd, char *ip, size_t ip_len, int *port);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment