summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml11
-rw-r--r--Makefile.am24
-rw-r--r--configure.ac97
-rw-r--r--crawler.c4
-rw-r--r--doc/tls.txt133
-rw-r--r--logger.c4
-rw-r--r--memcached.c350
-rw-r--r--memcached.h34
-rwxr-xr-xt/binary.t34
-rw-r--r--t/cacert.pem22
-rw-r--r--t/cakey.pem30
-rw-r--r--t/client_crt.pem68
-rw-r--r--t/client_key.pem15
-rw-r--r--t/idle-timeout.t8
-rw-r--r--t/lib/MemcachedTest.pm86
-rwxr-xr-xt/misbehave.t2
-rwxr-xr-xt/multiversioning.t12
-rw-r--r--t/noreply.t1
-rw-r--r--t/server.pem79
-rw-r--r--t/server_crt.pem67
-rw-r--r--t/server_key.pem15
-rw-r--r--t/ssl_cert_refresh.t76
-rw-r--r--t/ssl_ports.t31
-rw-r--r--t/ssl_settings.t36
-rw-r--r--t/ssl_verify_modes.t22
-rwxr-xr-xt/stats.t15
-rw-r--r--testapp.c204
-rw-r--r--thread.c32
-rw-r--r--tls.c195
-rw-r--r--tls.h14
30 files changed, 1630 insertions, 91 deletions
diff --git a/.travis.yml b/.travis.yml
index 3b00743..9686526 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
sudo: required
-dist: trusty
+dist: xenial
language: cpp
compiler:
@@ -7,11 +7,14 @@ compiler:
install:
- sudo apt-get update -y
- - sudo apt-get install -y build-essential automake1.11 autoconf libevent-dev libseccomp-dev git
+ - sudo apt-get install -y build-essential automake1.11 autoconf libevent-dev libseccomp-dev git tar wget libio-socket-ssl-perl
+ - wget https://www.openssl.org/source/openssl-1.1.0g.tar.gz && tar xzvf openssl-1.1.0g.tar.gz
+ - cd openssl-1.1.0g && ./config -Wl,--enable-new-dtags,-rpath,'$(LIBRPATH)' && make && sudo make install && openssl version && cd ../
+ - rm -rf openssl-1.1.0g && rm openssl-1.1.0g.tar.gz*
script:
- ./autogen.sh
- - ./configure --enable-seccomp
+ - ./configure --enable-seccomp --enable-tls
- make -j
- make test
-
+ - make test_tls
diff --git a/Makefile.am b/Makefile.am
index 22ea696..594c794 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,6 +53,10 @@ memcached_SOURCES += extstore.c extstore.h \
slab_automove_extstore.c slab_automove_extstore.h
endif
+if ENABLE_TLS
+memcached_SOURCES += tls.c tls.h
+endif
+
memcached_debug_SOURCES = $(memcached_SOURCES)
memcached_CPPFLAGS = -DNDEBUG
memcached_debug_LDADD = @PROFILER_LDFLAGS@
@@ -102,9 +106,29 @@ EXTRA_DIST = doc scripts t memcached.spec memcached_dtrace.d version.m4 README.m
MOSTLYCLEANFILES = *.gcov *.gcno *.gcda *.tcov
+if ENABLE_TLS
+test_tls:
+ $(MAKE) SSL_TEST=1 test
+
+test_basic_tls:
+ @if test $(SSL_TEST)1 != 1; then \
+ echo "Running basic tests with TLS"; \
+ $(srcdir)/testapp; \
+ prove $(srcdir)/t/binary.t $(srcdir)/t/getset.t $(srcdir)/t/ssl*; \
+ echo "Finished running basic TLS tests"; \
+ else \
+ echo "Set SSL_TEST=1 to enable TLS tests"; \
+ fi
+endif
+
test: memcached-debug sizes testapp
$(srcdir)/sizes
$(srcdir)/testapp
+if ENABLE_TLS
+ @if test $(SSL_TEST)1 = 1; then \
+ $(MAKE) SSL_TEST=1 test_basic_tls; \
+ fi
+endif
@if test -n "${PARALLEL}"; then \
prove $(srcdir)/t -j ${PARALLEL}; \
else \
diff --git a/configure.ac b/configure.ac
index abcb8ae..93046dc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -100,6 +100,8 @@ AC_ARG_ENABLE(sasl_pwdb,
AS_IF([test "x$enable_sasl_pwdb" = "xyes"],
[enable_sasl=yes ])
+AC_ARG_ENABLE(tls,
+ [AS_HELP_STRING([--enable-tls], [Enable Transport Layer Security EXPERIMENTAL ])])
dnl **********************************************************************
@@ -190,6 +192,10 @@ if test "x$enable_extstore" = "xyes"; then
AC_DEFINE([EXTSTORE],1,[Set to nonzero if you want to enable extstore])
fi
+if test "x$enable_tls" = "xyes"; then
+ AC_DEFINE([TLS],1,[Set to nonzero if you want to enable TLS])
+fi
+
if test "x$enable_arm_crc32" = "xyes"; then
AC_DEFINE([ARM_CRC32],1,[Set to nonzero if you want to enable ARMv8 crc32])
fi
@@ -199,6 +205,8 @@ AM_CONDITIONAL([DTRACE_INSTRUMENT_OBJ],[test "$dtrace_instrument_obj" = "yes"])
AM_CONDITIONAL([ENABLE_SASL],[test "$enable_sasl" = "yes"])
AM_CONDITIONAL([ENABLE_EXTSTORE],[test "$enable_extstore" = "yes"])
AM_CONDITIONAL([ENABLE_ARM_CRC32],[test "$enable_arm_crc32" = "yes"])
+AM_CONDITIONAL([ENABLE_TLS],[test "$enable_tls" = "yes"])
+
AC_SUBST(DTRACE)
AC_SUBST(DTRACEFLAGS)
@@ -354,6 +362,95 @@ if test $ac_cv_libevent_dir != "(system)"; then
fi
fi
+trylibssldir=""
+AC_ARG_WITH(libssl,
+ [ --with-libssl=PATH Specify path to libssl installation ],
+ [
+ if test "x$withval" != "xno" ; then
+ trylibssldir=$withval
+ fi
+ ]
+)
+
+dnl ----------------------------------------------------------------------------
+dnl libssl detection. swiped from libevent. modified for openssl detection.
+
+OPENSSL_URL=https://www.openssl.org/
+if test "x$enable_tls" = "xyes"; then
+ AC_CACHE_CHECK([for libssl directory], ac_cv_libssl_dir, [
+ saved_LIBS="$LIBS"
+ saved_LDFLAGS="$LDFLAGS"
+ saved_CPPFLAGS="$CPPFLAGS"
+ le_found=no
+ for ledir in $trylibssldir "" $prefix /usr/local ; do
+ LDFLAGS="$saved_LDFLAGS"
+ LIBS="-lssl -lcrypto $saved_LIBS"
+
+ # Skip the directory if it isn't there.
+ if test ! -z "$ledir" -a ! -d "$ledir" ; then
+ continue;
+ fi
+ if test ! -z "$ledir" ; then
+ if test -d "$ledir/lib" ; then
+ LDFLAGS="-L$ledir/lib $LDFLAGS"
+ else
+ LDFLAGS="-L$ledir $LDFLAGS"
+ fi
+ if test -d "$ledir/include" ; then
+ CPPFLAGS="-I$ledir/include $CPPFLAGS"
+ else
+ CPPFLAGS="-I$ledir $CPPFLAGS"
+ fi
+ fi
+ # Can I compile and link it?
+ AC_TRY_LINK([#include <sys/time.h>
+ #include <sys/types.h>
+ #include <assert.h>
+ #include <openssl/ssl.h>], [ SSL_CTX* ssl_ctx = SSL_CTX_new(TLS_server_method());
+ assert(OPENSSL_VERSION_NUMBER >= 0x10100000L);],
+ [ libssl_linked=yes ], [ libssl_linked=no ])
+ if test $libssl_linked = yes; then
+ if test ! -z "$ledir" ; then
+ ac_cv_libssl_dir=$ledir
+ _myos=`echo $target_os | cut -f 1 -d .`
+ AS_IF(test "$SUNCC" = "yes" -o "x$_myos" = "xsolaris2",
+ [saved_LDFLAGS="$saved_LDFLAGS -Wl,-R$ledir/lib"],
+ [AS_IF(test "$GCC" = "yes",
+ [saved_LDFLAGS="$saved_LDFLAGS -Wl,-rpath,$ledir/lib"])])
+ else
+ ac_cv_libssl_dir="(system)"
+ fi
+ le_found=yes
+ break
+ fi
+ done
+ LIBS="$saved_LIBS"
+ LDFLAGS="$saved_LDFLAGS"
+ CPPFLAGS="$saved_CPPFLAGS"
+ if test $le_found = no ; then
+ AC_MSG_ERROR([libssl (at least version 1.1.0) is required. You can get it from $OPENSSL_URL
+
+ If it's already installed, specify its path using --with-libssl=/dir/
+ ])
+ fi
+ ])
+ LIBS="-lssl -lcrypto $LIBS"
+ if test $ac_cv_libssl_dir != "(system)"; then
+ if test -d "$ac_cv_libssl_dir/lib" ; then
+ LDFLAGS="-L$ac_cv_libssl_dir/lib $LDFLAGS"
+ le_libdir="$ac_cv_libssl_dir/lib"
+ else
+ LDFLAGS="-L$ac_cv_libssl_dir $LDFLAGS"
+ le_libdir="$ac_cv_libssl_dir"
+ fi
+ if test -d "$ac_cv_libssl_dir/include" ; then
+ CPPFLAGS="-I$ac_cv_libssl_dir/include $CPPFLAGS"
+ else
+ CPPFLAGS="-I$ac_cv_libssl_dir $CPPFLAGS"
+ fi
+ fi
+fi
+
dnl ----------------------------------------------------------------------------
AC_SEARCH_LIBS(umem_cache_create, umem)
diff --git a/crawler.c b/crawler.c
index a1a5ead..e30d09b 100644
--- a/crawler.c
+++ b/crawler.c
@@ -293,7 +293,7 @@ static int lru_crawler_poll(crawler_client_t *c) {
if (to_poll[0].revents & POLLIN) {
char buf[1];
- int res = read(c->sfd, buf, 1);
+ int res = ((conn*)c->c)->read(c->c, buf, 1);
if (res == 0 || (res == -1 && (errno != EAGAIN && errno != EWOULDBLOCK))) {
lru_crawler_close_client(c);
return -1;
@@ -304,7 +304,7 @@ static int lru_crawler_poll(crawler_client_t *c) {
lru_crawler_close_client(c);
return -1;
} else if (to_poll[0].revents & POLLOUT) {
- int total = write(c->sfd, data, data_size);
+ int total = ((conn*)c->c)->write(c->c, data, data_size);
if (total == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
lru_crawler_close_client(c);
diff --git a/doc/tls.txt b/doc/tls.txt
new file mode 100644
index 0000000..fef56ab
--- /dev/null
+++ b/doc/tls.txt
@@ -0,0 +1,133 @@
+Securing Memcached with TLS
+
+Requirements
+------------
+We are required to encrypt Memcached network traffic as we deploy our servers in public cloud
+environments. We decided to implement SSL/TLS for TCP at the network layer of Memcached
+using OpenSSL libraries. This provides following benefits with the expense of added latency
+and reduced throughput (to be quantified).
+
+# Encryption :Data is encrypted on the wire between Memcached client and server.
+# Authentication : Optionally, both server and client authenticate each other.
+# Integrity: Data is not tampered or altered when transmitted between client and server
+
+Following are a few additional features.
+# Certificate refresh: when the server gets a new certificate, new connections
+will use new certificates without a need of re-starting the server process.
+
+# Multiple ports with and without TLS : by default all TCP ports are secured. Optionally we can setup
+the server to secure a specific TCP port.
+
+Note that initial implementation does not support session resumption or renegotiation.
+
+Design
+------
+We experimented two options for implementing TLS, with SSL buffered events and directly using
+OpenSSL API.
+
+Bufferevents can use the OpenSSL library to implement SSL/TLS. Our experiment used
+a socket-based bufferevent that tells OpenSSL to communicate with the network directly over.
+Unlike a worker thread sets callback on the socket, this uses a “bufferevent” object for
+callbacks. Memcached still has to setup the SSL Context but SSL handshake and object
+management is done via the “bufferevent_” API. While this was fairly easy to implement,
+we noticed a higher memory usage as we don’t have much control over allocating evbuffer
+objects in bufferevents. More over there is a discussion on removing the libevent dependency
+from Memcached; hence this option was not chosen.
+
+OpenSSL library provides APIs for us to directly read/write from a socket. With this option,
+we create an SSL Context and many SSL objects. The SSL Context object, created at the process level,
+holds certificates, a private key, and options regarding the TLS protocol and algorithms.
+SSL objects, created at the connection level, represents SSL sessions. SSL objects are responsible
+for encryption, and session handshake among other things.
+
+There are two ways to do network IO over TLS, either only use SSL_read/SSL_write with a network socket or
+use the API along with an output/input buffer pair. These buffers are referred as BIO
+(Basic Input Output) buffers.
+
+We started with the first option, create SSL objects with the socket and only interact with SSL_read/SSL_write.
+
+ +------+ +-----+
+ |......|--> read(fd) --> BIO_write(rbio) -->|.....|--> SSL_read(ssl) --> IN
+ |......| |.....|
+ |.sock.| |.SSL.|
+ |......| |.....|
+ |......|<-- write(fd) <-- BIO_read(wbio) <--|.....|<-- SSL_write(ssl) <-- OUT
+ +------+ +-----+
+ | | | |
+ |<-------------------------------->| |<------------------->|
+ | encrypted bytes | | unencrypted bytes |
+
+ Figure 1 : Network sockets, BIO buffers and SSL_read/SSL_write
+
+(reference: https://gist.github.com/darrenjs/4645f115d10aa4b5cebf57483ec82eca)
+
+Memcached uses non blocking sockets and implements a rather complex state machine for
+network IO. A listener thread does the TCP handshake and initiates the SSL handshake after
+creating an SSL object based on the SSL Context object of the server. If there are no
+fatal errors, the listener thread hands over the socket to a worker thread. A worker completes
+the SSL handshake.
+
+----------- ----------------------
+ | |
+ Client | | Memcached Server
+ | |
+ | |---------------------
+ | | Listener thread |
+ | TCP connect | |
+ |---------------------> | (accept) |
+ | ClientHello | |
+ |---------------------> | (SSL_accept) |
+ | | |
+ | ServerHello and | |
+ | Certificate, | |
+ | ServerHelloDone | |
+ | <---------------------| |
+ | |---------------------
+ | | |
+ | | V
+ | |-------------------
+ | | Worker thread |
+ | ClientKeyExchange, | |
+ | ChangeCipherSpec, | |
+ | Finished | |
+ |---------------------> | (SSL_read) |
+ | | |
+ | | |
+ | NewSessionTicket, | |
+ | ChangeCipherSpec, | |
+ | Finished | |
+ | <---------------------| |
+ | | |
+ | Memcached request/ | |
+ | response | |
+ | <-------------------> | (SSL_read/ |
+ | | SSL_write) |
+----------- -------------------------
+
+ Figure 2 : The initial SSL handshake
+
+
+Setting-up callbacks when the socket is ready for reading/writing is the same
+for both TLS and non-TLS connections. When the socket is ready, the state machine kicks off
+and issues a SSL_read/ SSL_write. Note that we implement a SSL_sendmsg wrapper on top of
+SSL_write to simulate the sendmsg API.
+This way we don't explicitly use BIO buffers or do BIO_write/BIO_read, but let OpenSSL
+library to do it on our behalf. Existing state machine takes care of reading the correct amount
+of bytes and do the error handling when needed.
+
+As a best practice, server certificates and keys are periodically refreshed by the PKI.
+When this happens we want server to use the new certificate without restarting the process.
+Memcached is a cache and restarting servers affects the latency of applications. We implement
+the automatic certificate refresh through a command. Upon receiving the "refresh_certs" command,
+the server reloads the certificates and key to the SSL Context object. Existing connection won't be
+interrupted but new connections will use the new certificate.
+
+We understand not all users want to use TLS or have the OpenSSL dependency. Therefore
+it's an optional module at the compile time. We can build a TLS capable Memcached server with
+"./configure --enable-tls". Once the server is built with TLS support, we can enabled it with
+"-Z" flag or "--enable-ssl". Certificate (-o ssl_chain_cert) and (-o ssl_key) are required
+parameters while others are optional. Supported options can be listed through "memcached -h".
+
+Developers need to have libio-socket-ssl-perl installed for running unit tests. When the server is
+built with TLS support, we can use "test_tls" make target to run all existing tests over TLS and some
+additional TLS specific tests. The minimum required OpenSSL version is 1.1.0g.
diff --git a/logger.c b/logger.c
index 1322d7d..a8a6c23 100644
--- a/logger.c
+++ b/logger.c
@@ -443,7 +443,7 @@ static int logger_thread_poll_watchers(int force_poll, int watcher) {
*/
if (watchers_pollfds[nfd].revents & POLLIN) {
char buf[1];
- int res = read(w->sfd, buf, 1);
+ int res = ((conn*)w->c)->read(w->c, buf, 1);
if (res == 0 || (res == -1 && (errno != EAGAIN && errno != EWOULDBLOCK))) {
L_DEBUG("LOGGER: watcher closed remotely\n");
logger_thread_close_watcher(w);
@@ -464,7 +464,7 @@ static int logger_thread_poll_watchers(int force_poll, int watcher) {
total = fwrite(data, 1, data_size, stderr);
break;
case LOGGER_WATCHER_CLIENT:
- total = write(w->sfd, data, data_size);
+ total = ((conn*)w->c)->write(w->c, data, data_size);
break;
}
diff --git a/memcached.c b/memcached.c
index 8d79eba..31555c5 100644
--- a/memcached.c
+++ b/memcached.c
@@ -55,6 +55,10 @@
#include <getopt.h>
#endif
+#ifdef TLS
+#include "tls.h"
+#endif
+
/* FreeBSD 4.x doesn't have IOV_MAX exposed. */
#ifndef IOV_MAX
#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__GNU__)
@@ -73,6 +77,9 @@
static void drive_machine(conn *c);
static int new_socket(struct addrinfo *ai);
static int try_read_command(conn *c);
+static ssize_t tcp_read(conn *arg, void *buf, size_t count);
+static ssize_t tcp_sendmsg(conn *arg, struct msghdr *msg, int flags);
+static ssize_t tcp_write(conn *arg, void *buf, size_t count);
enum try_read_result {
READ_DATA_RECEIVED,
@@ -146,6 +153,22 @@ enum transmit_result {
TRANSMIT_HARD_ERROR /** Can't write (c->state is set to conn_closing) */
};
+/* Default methods to read from/ write to a socket */
+ssize_t tcp_read(conn *c, void *buf, size_t count) {
+ assert (c != NULL);
+ return read(c->sfd, buf, count);
+}
+
+ssize_t tcp_sendmsg(conn *c, struct msghdr *msg, int flags) {
+ assert (c != NULL);
+ return sendmsg(c->sfd, msg, flags);
+}
+
+ssize_t tcp_write(conn *c, void *buf, size_t count) {
+ assert (c != NULL);
+ return write(c->sfd, buf, count);
+}
+
static enum transmit_result transmit(conn *c);
/* This reduces the latency without adding lots of extra wiring to be able to
@@ -223,6 +246,18 @@ static void settings_init(void) {
settings.access = 0700;
settings.port = 11211;
settings.udpport = 0;
+#ifdef TLS
+ settings.ssl_enabled = false;
+ settings.ssl_ctx = NULL;
+ settings.ssl_chain_cert = NULL;
+ settings.ssl_key = NULL;
+ settings.ssl_verify_mode = SSL_VERIFY_NONE;
+ settings.ssl_keyformat = SSL_FILETYPE_PEM;
+ settings.ssl_ciphers = NULL;
+ settings.ssl_ca_cert = NULL;
+ settings.ssl_last_cert_refresh_time = current_time;
+ settings.ssl_wbuf_size = 16 * 1024; // default is 16KB (SSL max frame size is 17KB)
+#endif
/* By default this string should be NULL for getaddrinfo() */
settings.inter = NULL;
settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */
@@ -502,7 +537,7 @@ void conn_worker_readd(conn *c) {
conn *conn_new(const int sfd, enum conn_states init_state,
const int event_flags,
const int read_buffer_size, enum network_transport transport,
- struct event_base *base) {
+ struct event_base *base, void *ssl) {
conn *c;
assert(sfd >= 0 && sfd < max_fds);
@@ -517,7 +552,9 @@ conn *conn_new(const int sfd, enum conn_states init_state,
return NULL;
}
MEMCACHED_CONN_CREATE(c);
-
+ c->read = NULL;
+ c->sendmsg = NULL;
+ c->write = NULL;
c->rbuf = c->wbuf = 0;
c->ilist = 0;
c->suffixlist = 0;
@@ -597,7 +634,11 @@ conn *conn_new(const int sfd, enum conn_states init_state,
assert(false);
}
}
-
+#ifdef TLS
+ c->ssl = NULL;
+ c->ssl_wbuf = NULL;
+ c->ssl_enabled = false;
+#endif
c->state = init_state;
c->rlbytes = 0;
c->cmd = -1;
@@ -626,6 +667,25 @@ conn *conn_new(const int sfd, enum conn_states init_state,
c->noreply = false;
+#ifdef TLS
+ if (ssl) {
+ c->ssl = (SSL*)ssl;
+ c->read = ssl_read;
+ c->sendmsg = ssl_sendmsg;
+ c->write = ssl_write;
+ c->ssl_enabled = true;
+ SSL_set_info_callback(c->ssl, ssl_callback);
+ } else
+#else
+ // This must be NULL if TLS is not enabled.
+ assert(ssl == NULL);
+#endif
+ {
+ c->read = tcp_read;
+ c->sendmsg = tcp_sendmsg;
+ c->write = tcp_write;
+ }
+
event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
event_base_set(base, &c->event);
c->ev_flags = event_flags;
@@ -793,6 +853,11 @@ void conn_free(conn *c) {
free(c->suffixlist);
if (c->iov)
free(c->iov);
+#ifdef TLS
+ if (c->ssl_wbuf)
+ c->ssl_wbuf = NULL;
+#endif
+
free(c);
}
}
@@ -810,8 +875,13 @@ static void conn_close(conn *c) {
MEMCACHED_CONN_RELEASE(c->sfd);
conn_set_state(c, conn_closed);
+#ifdef TLS
+ if (c->ssl) {
+ SSL_shutdown(c->ssl);
+ SSL_free(c->ssl);
+ }
+#endif
close(c->sfd);
-
pthread_mutex_lock(&conn_lock);
allow_new_conns = true;
pthread_mutex_unlock(&conn_lock);
@@ -3217,6 +3287,13 @@ static void server_stats(ADD_STAT add_stats, conn *c) {
APPEND_STAT("extstore_io_queue", "%llu", (unsigned long long)(st.io_queue));
}
#endif
+#ifdef TLS
+ if (settings.ssl_enabled) {
+ SSL_LOCK();
+ APPEND_STAT("time_since_server_cert_refresh", "%u", now - settings.ssl_last_cert_refresh_time);
+ SSL_UNLOCK();
+ }
+#endif
}
static void process_stat_settings(ADD_STAT add_stats, void *c) {
@@ -3288,6 +3365,16 @@ static void process_stat_settings(ADD_STAT add_stats, void *c) {
APPEND_STAT("slab_automove_freeratio", "%.3f", settings.slab_automove_freeratio);
APPEND_STAT("ext_drop_unread", "%s", settings.ext_drop_unread ? "yes" : "no");
#endif
+#ifdef TLS
+ APPEND_STAT("ssl_enabled", "%s", settings.ssl_enabled ? "yes" : "no");
+ APPEND_STAT("ssl_chain_cert", "%s", settings.ssl_chain_cert);
+ APPEND_STAT("ssl_key", "%s", settings.ssl_key);
+ APPEND_STAT("ssl_verify_mode", "%d", settings.ssl_verify_mode);
+ APPEND_STAT("ssl_keyformat", "%d", settings.ssl_keyformat);
+ APPEND_STAT("ssl_ciphers", "%s", settings.ssl_ciphers ? settings.ssl_ciphers : "NULL");
+ APPEND_STAT("ssl_ca_cert", "%s", settings.ssl_ca_cert ? settings.ssl_ca_cert : "NULL");
+ APPEND_STAT("ssl_wbuf_size", "%u", settings.ssl_wbuf_size);
+#endif
}
static void conn_to_str(const conn *c, char *buf) {
@@ -4927,6 +5014,17 @@ static void process_command(conn *c, char *command) {
} else if (ntokens >= 3 && strcmp(tokens[COMMAND_TOKEN].value, "extstore") == 0) {
process_extstore_command(c, tokens, ntokens);
#endif
+#ifdef TLS
+ } else if (ntokens == 2 && strcmp(tokens[COMMAND_TOKEN].value, "refresh_certs") == 0) {
+ set_noreply_maybe(c, tokens, ntokens);
+ char *errmsg = NULL;
+ if (refresh_certs(&errmsg)) {
+ out_string(c, "OK");
+ } else {
+ write_and_free(c, errmsg, strlen(errmsg));
+ }
+ return;
+#endif
} else {
if (ntokens >= 2 && strncmp(tokens[ntokens - 2].value, "HTTP/", 5) == 0) {
conn_set_state(c, conn_closing);
@@ -5158,7 +5256,7 @@ static enum try_read_result try_read_network(conn *c) {
}
int avail = c->rsize - c->rbytes;
- res = read(c->sfd, c->rbuf + c->rbytes, avail);
+ res = c->read(c, c->rbuf + c->rbytes, avail);
if (res > 0) {
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.bytes_read += res;
@@ -5262,7 +5360,7 @@ static enum transmit_result transmit(conn *c) {
ssize_t res;
struct msghdr *m = &c->msglist[c->msgcurr];
- res = sendmsg(c->sfd, m, 0);
+ res = c->sendmsg(c, m, 0);
if (res > 0) {
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.bytes_written += res;
@@ -5357,7 +5455,7 @@ static int read_into_chunked_item(conn *c) {
}
} else {
/* now try reading from the socket */
- res = read(c->sfd, ch->data + ch->used,
+ res = c->read(c, ch->data + ch->used,
(unused > c->rlbytes ? c->rlbytes : unused));
if (res > 0) {
pthread_mutex_lock(&c->thread->stats.mutex);
@@ -5458,8 +5556,47 @@ static void drive_machine(conn *c) {
stats.rejected_conns++;
STATS_UNLOCK();
} else {
+ void *ssl_v = NULL;
+#ifdef TLS
+ SSL *ssl = NULL;
+ if (c->ssl_enabled) {
+ assert(IS_TCP(c->transport) && settings.ssl_enabled);
+
+ if (settings.ssl_ctx == NULL) {
+ if (settings.verbose) {
+ fprintf(stderr, "SSL context is not initialized\n");
+ }
+ close(sfd);
+ break;
+ }
+ SSL_LOCK();
+ ssl = SSL_new(settings.ssl_ctx);
+ SSL_UNLOCK();
+ if (ssl == NULL) {
+ if (settings.verbose) {
+ fprintf(stderr, "Failed to created the SSL object\n");
+ }
+ close(sfd);
+ break;
+ }
+ SSL_set_fd(ssl, sfd);
+ int ret = SSL_accept(ssl);
+ if (ret < 0) {
+ int err = SSL_get_error(ssl, ret);
+ if (err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) {
+ if (settings.verbose) {
+ fprintf(stderr, "SSL connection failed with error code : %d : %s\n", err, strerror(errno));
+ }
+ close(sfd);
+ break;
+ }
+ }
+ }
+ ssl_v = (void*) ssl;
+#endif
+
dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
- DATA_BUFFER_SIZE, c->transport);
+ DATA_BUFFER_SIZE, c->transport, ssl_v);
}
stop = true;
@@ -5565,7 +5702,7 @@ static void drive_machine(conn *c) {
}
/* now try reading from the socket */
- res = read(c->sfd, c->ritem, c->rlbytes);
+ res = c->read(c, c->ritem, c->rlbytes);
if (res > 0) {
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.bytes_read += res;
@@ -5635,7 +5772,7 @@ static void drive_machine(conn *c) {
}
/* now try reading from the socket */
- res = read(c->sfd, c->rbuf, c->rsize > c->sbytes ? c->sbytes : c->rsize);
+ res = c->read(c, c->rbuf, c->rsize > c->sbytes ? c->sbytes : c->rsize);
if (res > 0) {
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.bytes_read += res;
@@ -5849,7 +5986,7 @@ static void maximize_sndbuf(const int sfd) {
static int server_socket(const char *interface,
int port,
enum network_transport transport,
- FILE *portnumber_file) {
+ FILE *portnumber_file, bool ssl_enabled) {
int sfd;
struct linger ling = {0, 0};
struct addrinfo *ai;
@@ -5972,15 +6109,20 @@ static int server_socket(const char *interface,
int per_thread_fd = c ? dup(sfd) : sfd;
dispatch_conn_new(per_thread_fd, conn_read,
EV_READ | EV_PERSIST,
- UDP_READ_BUFFER_SIZE, transport);
+ UDP_READ_BUFFER_SIZE, transport, NULL);
}
} else {
if (!(listen_conn_add = conn_new(sfd, conn_listening,
EV_READ | EV_PERSIST, 1,
- transport, main_base))) {
+ transport, main_base, NULL))) {
fprintf(stderr, "failed to create listening connection\n");
exit(EXIT_FAILURE);
}
+#ifdef TLS
+ listen_conn_add->ssl_enabled = ssl_enabled;
+#else
+ assert(ssl_enabled == false);
+#endif
listen_conn_add->next = listen_conn;
listen_conn = listen_conn_add;
}
@@ -5994,8 +6136,15 @@ static int server_socket(const char *interface,
static int server_sockets(int port, enum network_transport transport,
FILE *portnumber_file) {
+ bool ssl_enabled = false;
+
+#ifdef TLS
+ const char *notls = "notls";
+ ssl_enabled = settings.ssl_enabled;
+#endif
+
if (settings.inter == NULL) {
- return server_socket(settings.inter, port, transport, portnumber_file);
+ return server_socket(settings.inter, port, transport, portnumber_file, ssl_enabled);
} else {
// tokenize them and bind to each one of them..
char *b;
@@ -6007,9 +6156,21 @@ static int server_sockets(int port, enum network_transport transport,
return 1;
}
for (char *p = strtok_r(list, ";,", &b);
- p != NULL;
- p = strtok_r(NULL, ";,", &b)) {
+ p != NULL;
+ p = strtok_r(NULL, ";,", &b)) {
int the_port = port;
+#ifdef TLS
+ ssl_enabled = settings.ssl_enabled;
+ // "notls" option is valid only when memcached is run with SSL enabled.
+ if (strncmp(p, notls, strlen(notls)) == 0) {
+ if (!settings.ssl_enabled) {
+ fprintf(stderr, "'notls' option is valid only when SSL is enabled\n");
+ return 1;
+ }
+ ssl_enabled = false;
+ p += strlen(notls) + 1;
+ }
+#endif
char *h = NULL;
if (*p == '[') {
@@ -6049,7 +6210,7 @@ static int server_sockets(int port, enum network_transport transport,
if (strcmp(p, "*") == 0) {
p = NULL;
}
- ret |= server_socket(p, the_port, transport, portnumber_file);
+ ret |= server_socket(p, the_port, transport, portnumber_file, ssl_enabled);
}
free(list);
return ret;
@@ -6126,7 +6287,7 @@ static int server_socket_unix(const char *path, int access_mask) {
}
if (!(listen_conn = conn_new(sfd, conn_listening,
EV_READ | EV_PERSIST, 1,
- local_transport, main_base))) {
+ local_transport, main_base, NULL))) {
fprintf(stderr, "failed to create listening connection\n");
exit(EXIT_FAILURE);
}
@@ -6205,6 +6366,10 @@ static void usage(void) {
"-A, --enable-shutdown enable ascii \"shutdown\" command\n"
"-a, --unix-mask=<mask> access mask for UNIX socket, in octal (default: 0700)\n"
"-l, --listen=<addr> interface to listen on (default: INADDR_ANY)\n"
+#ifdef TLS
+ " if TLS/SSL is enabled, 'notls' prefix can be used to\n"
+ " disable for specific listeners (-l notls:<ip>:<port>) \n"
+#endif
"-d, --daemon run as a daemon\n"
"-r, --enable-coredumps maximize core file limit\n"
"-u, --user=<user> assume identity of <username> (only when run as root)\n"
@@ -6241,6 +6406,9 @@ static void usage(void) {
#endif
printf("-F, --disable-flush-all disable flush_all command\n");
printf("-X, --disable-dumping disable stats cachedump and lru_crawler metadump\n");
+#ifdef TLS
+ printf("-Z, --enable-ssl enable TLS/SSL\n");
+#endif
printf("-o, --extended comma separated list of extended options\n"
" most options have a 'no_' prefix to disable\n"
" - maxconns_fast: immediately close new connections after limit\n"
@@ -6304,6 +6472,17 @@ static void usage(void) {
" - slab_automove_freeratio: ratio of memory to hold free as buffer.\n"
" (see doc/storage.txt for more info)\n"
#endif
+#ifdef TLS
+ " - ssl_chain_cert: certificate chain file in PEM format\n"
+ " - ssl_key: private key, if not part of the -ssl_chain_cert\n"
+ " - ssl_keyformat: private key format (PEM, DER or ENGINE) PEM default\n"
+ " - ssl_verify_mode: peer certificate verification mode, default is 0(None).\n"
+ " valid values are 0(None), 1(Request), 2(Require)\n"
+ " or 3(Once)\n"
+ " - ssl_ciphers: specify cipher list to be used\n"
+ " - ssl_ca_cert: PEM format file of acceptable client CA's\n"
+ " - ssl_wbuf_size: size in kilobytes of per-connection SSL output buffer\n"
+#endif
);
return;
}
@@ -6628,6 +6807,15 @@ int main (int argc, char **argv) {
NO_LRU_MAINTAINER,
NO_DROP_PRIVILEGES,
DROP_PRIVILEGES,
+#ifdef TLS
+ SSL_CERT,
+ SSL_KEY,
+ SSL_VERIFY_MODE,
+ SSL_KEYFORM,
+ SSL_CIPHERS,
+ SSL_CA_CERT,
+ SSL_WBUF_SIZE,
+#endif
#ifdef MEMCACHED_DEBUG
RELAXED_PRIVILEGES,
#endif
@@ -6685,6 +6873,15 @@ int main (int argc, char **argv) {
[NO_LRU_MAINTAINER] = "no_lru_maintainer",
[NO_DROP_PRIVILEGES] = "no_drop_privileges",
[DROP_PRIVILEGES] = "drop_privileges",
+#ifdef TLS
+ [SSL_CERT] = "ssl_chain_cert",
+ [SSL_KEY] = "ssl_key",
+ [SSL_VERIFY_MODE] = "ssl_verify_mode",
+ [SSL_KEYFORM] = "ssl_keyformat",
+ [SSL_CIPHERS] = "ssl_ciphers",
+ [SSL_CA_CERT] = "ssl_ca_cert",
+ [SSL_WBUF_SIZE] = "ssl_wbuf_size",
+#endif
#ifdef MEMCACHED_DEBUG
[RELAXED_PRIVILEGES] = "relaxed_privileges",
#endif
@@ -6711,7 +6908,7 @@ int main (int argc, char **argv) {
return EX_OSERR;
}
- /* handle SIGINT and SIGTERM */
+ /* handle SIGINT, SIGTERM */
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
@@ -6745,6 +6942,7 @@ int main (int argc, char **argv) {
char *shortopts =
"a:" /* access mask for unix socket */
"A" /* enable admin shutdown command */
+ "Z" /* enable SSL */
"p:" /* TCP port number to listen on */
"s:" /* unix socket path to listen on */
"U:" /* UDP port number to listen on */
@@ -6780,6 +6978,7 @@ int main (int argc, char **argv) {
const struct option longopts[] = {
{"unix-mask", required_argument, 0, 'a'},
{"enable-shutdown", no_argument, 0, 'A'},
+ {"enable-ssl", no_argument, 0, 'Z'},
{"port", required_argument, 0, 'p'},
{"unix-socket", required_argument, 0, 's'},
{"udp-port", required_argument, 0, 'U'},
@@ -6822,12 +7021,19 @@ int main (int argc, char **argv) {
/* enables "shutdown" command */
settings.shutdown_command = true;
break;
-
+ case 'Z':
+ /* enable secure communication*/
+#ifdef TLS
+ settings.ssl_enabled = true;
+#else
+ fprintf(stderr, "This server is not built with TLS support.\n");
+ exit(EX_USAGE);
+#endif
+ break;
case 'a':
/* access for unix domain socket, as octal mask (like chmod)*/
settings.access= strtol(optarg,NULL,8);
break;
-
case 'U':
settings.udpport = atoi(optarg);
udp_specified = true;
@@ -7242,6 +7448,90 @@ int main (int argc, char **argv) {
start_lru_maintainer = false;
settings.lru_segmented = false;
break;
+#ifdef TLS
+ case SSL_CERT:
+ if (subopts_value == NULL) {
+ fprintf(stderr, "Missing ssl_chain_cert argument\n");
+ return 1;
+ }
+ settings.ssl_chain_cert = strdup(subopts_value);
+ break;
+ case SSL_KEY:
+ if (subopts_value == NULL) {
+ fprintf(stderr, "Missing ssl_key argument\n");
+ return 1;
+ }
+ settings.ssl_key = strdup(subopts_value);
+ break;
+ case SSL_VERIFY_MODE:
+ {
+ if (subopts_value == NULL) {
+ fprintf(stderr, "Missing ssl_verify_mode argument\n");
+ return 1;
+ }
+ int verify = 0;
+ if (!safe_strtol(subopts_value, &verify)) {
+ fprintf(stderr, "could not parse argument to ssl_verify_mode\n");
+ return 1;
+ }
+ switch(verify) {
+ case 0:
+ settings.ssl_verify_mode = SSL_VERIFY_NONE;
+ break;
+ case 1:
+ settings.ssl_verify_mode = SSL_VERIFY_PEER;
+ break;
+ case 2:
+ settings.ssl_verify_mode = SSL_VERIFY_PEER |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ break;
+ case 3:
+ settings.ssl_verify_mode = SSL_VERIFY_PEER |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
+ SSL_VERIFY_CLIENT_ONCE;
+ break;
+ default:
+ fprintf(stderr, "Invalid ssl_verify_mode. Use help to see valid options.\n");
+ return 1;
+ }
+ break;
+ }
+ case SSL_KEYFORM:
+ if (subopts_value == NULL) {
+ fprintf(stderr, "Missing ssl_keyformat argument\n");
+ return 1;
+ }
+ if (!safe_strtol(subopts_value, &settings.ssl_keyformat)) {
+ fprintf(stderr, "could not parse argument to ssl_keyformat\n");
+ return 1;
+ }
+ break;
+ case SSL_CIPHERS:
+ if (subopts_value == NULL) {
+ fprintf(stderr, "Missing ssl_ciphers argument\n");
+ return 1;
+ }
+ settings.ssl_ciphers = strdup(subopts_value);
+ break;
+ case SSL_CA_CERT:
+ if (subopts_value == NULL) {
+ fprintf(stderr, "Missing ssl_ca_cert argument\n");
+ return 1;
+ }
+ settings.ssl_ca_cert = strdup(subopts_value);
+ break;
+ case SSL_WBUF_SIZE:
+ if (subopts_value == NULL) {
+ fprintf(stderr, "Missing ssl_wbuf_size argument\n");
+ return 1;
+ }
+ if (!safe_strtoul(subopts_value, &settings.ssl_wbuf_size)) {
+ fprintf(stderr, "could not parse argument to ssl_wbuf_size\n");
+ return 1;
+ }
+ settings.ssl_wbuf_size *= 1024; /* kilobytes */
+ break;
+#endif
#ifdef EXTSTORE
case EXT_PAGE_SIZE:
if (subopts_value == NULL) {
@@ -7540,6 +7830,24 @@ int main (int argc, char **argv) {
settings.port = settings.udpport;
}
+
+#ifdef TLS
+ /*
+ * Setup SSL if enabled
+ */
+ if (settings.ssl_enabled) {
+ if (!settings.port) {
+ fprintf(stderr, "ERROR: You cannot enable SSL without a TCP port.\n");
+ exit(EX_USAGE);
+ }
+ // openssl init methods.
+ SSL_load_error_strings();
+ SSLeay_add_ssl_algorithms();
+ // Initiate the SSL context.
+ ssl_init();
+ }
+#endif
+
if (maxcore != 0) {
struct rlimit rlim_new;
/*
diff --git a/memcached.h b/memcached.h
index a8dd59d..0645287 100644
--- a/memcached.h
+++ b/memcached.h
@@ -31,6 +31,9 @@
#endif
#include "sasl_defs.h"
+#ifdef TLS
+#include <openssl/ssl.h>
+#endif
/** Maximum length of a key. */
#define KEY_MAX_LENGTH 250
@@ -433,6 +436,18 @@ struct settings {
/* per-slab-class free chunk limit */
unsigned int ext_free_memchunks[MAX_NUMBER_OF_SLAB_CLASSES];
#endif
+#ifdef TLS
+ bool ssl_enabled; /* indicates whether SSL is enabled */
+ SSL_CTX *ssl_ctx; /* holds the SSL server context which has the server certificate */
+ char *ssl_chain_cert; /* path to the server SSL chain certificate */
+ char *ssl_key; /* path to the server key */
+ int ssl_verify_mode; /* client certificate verify mode */
+ int ssl_keyformat; /* key format , defult is PEM */
+ char *ssl_ciphers; /* list of SSL ciphers */
+ char *ssl_ca_cert; /* certificate with CAs. */
+ rel_time_t ssl_last_cert_refresh_time; /* time of the last server certificate refresh */
+ unsigned int ssl_wbuf_size; /* size of the write buffer used by ssl_sendmsg method */
+#endif
};
extern struct stats stats;
@@ -563,6 +578,10 @@ typedef struct {
#endif
logger *l; /* logger buffer */
void *lru_bump_buf; /* async LRU bump buffer */
+#ifdef TLS
+ char *ssl_wbuf;
+#endif
+
} LIBEVENT_THREAD;
typedef struct conn conn;
#ifdef EXTSTORE
@@ -584,6 +603,11 @@ typedef struct _io_wrap {
*/
struct conn {
int sfd;
+#ifdef TLS
+ SSL *ssl;
+ char *ssl_wbuf;
+ bool ssl_enabled;
+#endif
sasl_conn_t *sasl_conn;
bool sasl_started;
bool authenticated;
@@ -676,6 +700,9 @@ struct conn {
int keylen;
conn *next; /* Used for generating a list of conn structures */
LIBEVENT_THREAD *thread; /* Pointer to the thread object serving this connection */
+ ssize_t (*read)(conn *c, void *buf, size_t count);
+ ssize_t (*sendmsg)(conn *c, struct msghdr *msg, int flags);
+ ssize_t (*write)(conn *c, void *buf, size_t count);
};
/* array of conn structures, indexed by file descriptor */
@@ -716,7 +743,9 @@ enum delta_result_type do_add_delta(conn *c, const char *key,
const int64_t delta, char *buf,
uint64_t *cas, const uint32_t hv);
enum store_item_type do_store_item(item *item, int comm, conn* c, const uint32_t hv);
-conn *conn_new(const int sfd, const enum conn_states init_state, const int event_flags, const int read_buffer_size, enum network_transport transport, struct event_base *base);
+conn *conn_new(const int sfd, const enum conn_states init_state, const int event_flags, const int read_buffer_size,
+ enum network_transport transport, struct event_base *base, void *ssl);
+
void conn_worker_readd(conn *c);
extern int daemonize(int nochdir, int noclose);
@@ -740,7 +769,8 @@ extern int daemonize(int nochdir, int noclose);
*/
void memcached_thread_init(int nthreads, void *arg);
void redispatch_conn(conn *c);
-void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags, int read_buffer_size, enum network_transport transport);
+void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags, int read_buffer_size,
+ enum network_transport transport, void *ssl);
void sidethread_conn_close(conn *c);
/* Lock wrappers for cache functions that are called from main loop. */
diff --git a/t/binary.t b/t/binary.t
index 90da1c6..1c6568a 100755
--- a/t/binary.t
+++ b/t/binary.t
@@ -441,7 +441,14 @@ $mc->silent_mutation(::CMD_ADDQ, 'silentadd', 'silentaddval');
my %stats = $mc->stats('settings');
is(1024, $stats{'maxconns'});
- isnt('NULL', $stats{'domain_socket'});
+ # we run SSL tests over TCP; hence the domain_socket
+ # is expected to be NULL.
+ if (enabled_tls_testing()) {
+ is('NULL', $stats{'domain_socket'});
+ } else {
+ isnt('NULL', $stats{'domain_socket'});
+ }
+
is('on', $stats{'evictions'});
is('yes', $stats{'cas_enabled'});
is('yes', $stats{'flush_enabled'});
@@ -484,14 +491,14 @@ $mc->silent_mutation(::CMD_ADDQ, 'silentadd', 'silentaddval');
$data .= $mc->build_command(::CMD_SETQ, "alt_$k", "blah", 0, $extra, 0);
if (length($data) > 2024) {
for (my $j = 2024; $j < min(2096, length($data)); $j++) {
- $mc->{socket}->send(substr($data, 0, $j));
+ $mc->{socket}->syswrite(substr($data, 0, $j));
$mc->flush_socket;
sleep(0.001);
- $mc->{socket}->send(substr($data, $j));
+ $mc->{socket}->syswrite(substr($data, $j));
$mc->flush_socket;
}
} else {
- $mc->{socket}->send($data);
+ $mc->{socket}->syswrite($data);
}
$mc->flush_socket;
$check->($k, 82, $v);
@@ -571,9 +578,18 @@ sub send_command {
my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
- my $sent = $self->{socket}->send($full_msg);
- die("Send failed: $!") unless $sent;
- if($sent != length($full_msg)) {
+ my $sent = 0;
+ my $data_len = length($full_msg);
+ while ($sent < $data_len) {
+ my $sent_bytes = $self->{socket}->syswrite($full_msg,
+ $data_len - $sent > MemcachedTest::MAX_READ_WRITE_SIZE ?
+ MemcachedTest::MAX_READ_WRITE_SIZE : ($data_len - $sent),
+ $sent);
+ last if ($sent_bytes <= 0);
+ $sent += $sent_bytes;
+ }
+ die("Send failed: $!") unless $data_len;
+ if($sent != $data_len) {
die("only sent $sent of " . length($full_msg) . " bytes");
}
}
@@ -612,7 +628,7 @@ sub _handle_single_response {
my $hdr = "";
while(::MIN_RECV_BYTES - length($hdr) > 0) {
- $self->{socket}->recv(my $response, ::MIN_RECV_BYTES - length($hdr));
+ $self->{socket}->sysread(my $response, ::MIN_RECV_BYTES - length($hdr));
$hdr .= $response;
}
Test::More::is(length($hdr), ::MIN_RECV_BYTES, "Expected read length");
@@ -628,7 +644,7 @@ sub _handle_single_response {
# fetch the value
my $rv="";
while($remaining - length($rv) > 0) {
- $self->{socket}->recv(my $buf, $remaining - length($rv));
+ $self->{socket}->sysread(my $buf, $remaining - length($rv));
$rv .= $buf;
}
if(length($rv) != $remaining) {
diff --git a/t/cacert.pem b/t/cacert.pem
new file mode 100644
index 0000000..522bd27
--- /dev/null
+++ b/t/cacert.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIJAOFc3ZmVKolRMA0GCSqGSIb3DQEBBQUAMIGLMSgwJgYD
+VQQDDB9UZXN0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQswCQYDVQQIDAJD
+QTELMAkGA1UEBhMCVVMxHDAaBgkqhkiG9w0BCQEWDXJvb3RAdGVzdC5jb20xDTAL
+BgNVBAoMBFRlc3QxGDAWBgNVBAsMD1Rlc3QgRGVwYXJ0bWVudDAeFw0xOTAxMDcx
+ODIzMjlaFw0yNDAxMDYxODIzMjlaMIGLMSgwJgYDVQQDDB9UZXN0IFJvb3QgQ2Vy
+dGlmaWNhdGUgQXV0aG9yaXR5MQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMxHDAa
+BgkqhkiG9w0BCQEWDXJvb3RAdGVzdC5jb20xDTALBgNVBAoMBFRlc3QxGDAWBgNV
+BAsMD1Rlc3QgRGVwYXJ0bWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAJQcDfenVed0uJCykaWowL2qGUs9e5gyjGo9URoNhI5LDYSSq3ebxZGfbi1T
+BcC7oL5OI/B0lCShPR9sJjKFkQ60vs43ltmkTLNSGoKLnxXMlBjdpCxweDDAMiF3
+p/vCG2hUa7auOkMWMYIkM81rcQsRB0qj0ilt3zcTsS860oKGzNrtPeAcz3KxbYWI
+nJEhQVy3S4U59b7mm3cGz3/3m2NUjn+b8sA7J8F9K5mnFkCUCRva6zte6qmL2ruH
+sGRav9ICLGxDqiJoic6Y2ReffgU77RDJO+sTuJme+VeTDE77vBIHvCVCSG+e0RSs
+L+6nQYDtjHH5bLgoSXq9D3hgxEsCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkq
+hkiG9w0BAQUFAAOCAQEARWcYJvkleq+qfOPPI1eQAuBf5OZwz8mthsBvMfsQMCLk
++wSxdlJvJyvPoFgZBv8YbTde2b40lQe94gTsGDUXGEkOsERrjt/jqHxO9X8n7a+z
+M/okeSSYnam0Zcl1M9sa5L7BVXbGh/sE9j/bXrAhz64np5P773dZTLTPYjBf2Grp
+NheCsGDtJbegJqn7pp5MfAKcyzLxnZAE0cilSVKZB7R3urISJVdwiRtkprJL7IwQ
+oIu+XhUgdZbx7TQQnjTkq3COSIIof5+5oqFnhzTqfSgi/06dWWvCwl17Mz+vCMZ9
+1MA8L4cR+iNdJYlCQPbk30laJx1akfqnpv7qTXq7nQ==
+-----END CERTIFICATE-----
diff --git a/t/cakey.pem b/t/cakey.pem
new file mode 100644
index 0000000..3396f25
--- /dev/null
+++ b/t/cakey.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIigxGWoxgfJACAggA
+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECHgfMUpgAX2tBIIEyFcJT6kFw1TH
+kmwmbtB+MPW3+zUxoKZuP+L7UCaMHQPHcQ4/AThVNPPhXa++/lgL0P1q33SBhqsm
+HK5nRfzEe75bQIFoZ1FFg3YGaAB4KUPm5vJ4XhmyzCEiUvp75mgiw38fkL5ZCPqW
+eitNKgyAYEl9zIrF1cRyN/s6O6mPR8NOPbVL2ZtCGGvy/LN+mhlolvuMFySEEI+s
+nzu5682LU7QQOzLFB/q3Vqrt2tafzoIHYtj8YYVsPMO6bWkoKx1BWyTCuesFOLTN
+agnXx3AKtmD8Q0C3vvdmpqNnWw1iP1Vc4r/BZwmD2MFXUh8cExBGRaPIfUr41BoR
+p1bHqtcAsbipuU7qQ6wjnLyuqAvFQxy1LCBGNyBrzMMD25+lXh9BoeS9H5L+WW0N
+pcAPA5FoD1RQc27ZXVf0kRGEy8Vj/UsE3Pf74Z8u16bC+6ojAUvvvc49ERm0t85O
+T5rP9ql3sITtg4EvNdNhHtYdJGVkZQ7H8T9G1OXEf4dE/vqYNnqhtFm+5CyWb8Vk
+Rly2XFbBs4I4UnVIi0Nt0ybIqqwAvvKAU5kSOIEy6Q9mvUYyvBZrhY5Va2KEwaTU
+ig2yjWxWFoOArE1UVu2kJaslaOKqAr9OPqW510+6G9s+lREm7EYqUd6Ut1cwcXUv
+/s1fdx/As6U6qApH9+TC0XqNm5yXNCuADVtK2PmOoanczpEXZTZGijylGFDROQQj
+bofjoB0hiN80Xc+II0zClGwXwZz43Xy4uVIMbvMBi3yp03ct1RMwORPCGkiH/1uV
+iazsfV4DAD8KdGmgUL57WfnMPZJCEfqpM4YrM7rKu3Md8v8hKyFGUAdeNsCFTwwY
+g2E6NXWbtqGrUFC/r/70/axfijWRoyPaLcBoourc5HcZ+K/TmmY+uOxKBcJsfS/t
+HiRZ5l48sva+lZVWN6KNG5N93pa3TqgCCfp56VPOo3o3kj4XAJ3aMK6RsK3QrGi7
+TTXlv/hKFYW3GUJ0IGNfinnARbdMoD6ww8nQLe3Id3HKzqh7xfQb4xJZpQhpSXVS
+KJmf5H80GHQnJq54Xwi7daYmxpTS+yNHuxKq+ryBqO7WeotNkOCbG6Flc/GBO6a3
+7rbSOTmqpyJPTRPQEf1ogHNgCG9txPIbZRAi9PPJVn/gV/dKttUpj5OGI9fjDaSK
+ILEwLuokdRwTAGeFRIkucKnvA0pzXQkZRMG4D4kYzxskutUQVvKFAsqvEkb2c6qL
+j8MjKYfYZqxG44o68aOk8H42vR6lkemclj6byvLfnqbZPUka3MUnrG8oPI+vNLlg
+N57QpU2Kw5joBf1oCSROTXEv3BT/JYE3qQ2T6q/NGgG0s8aXbROh/7HaZJrENmoD
+pDJZp+bSGn6On6vmDu98F3fyubKgg611tA5pyDdWE4MSvJBGKHc2/HmqgxbLtK1c
+gn2BBefAi+Qqzc+XSeJxh2nlAx7ohvgEcodO3Vqb9eSoPviMHyO9bap8QIFJqTNW
+QK6rMqtou0QTEcXp5cIlVRW59zZHKEPpNpJ08fNtxB5n/Ngo+vo86LLVUysfbwkY
+p7GK4kraBGhU3rEXzuFBXn1Cr7015VrAC8mLafKqar7OLhLduHDRPxGidXJc6oxE
+2PpJudZFhS6P+0M5AkMq2Q==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/t/client_crt.pem b/t/client_crt.pem
new file mode 100644
index 0000000..f9031ee
--- /dev/null
+++ b/t/client_crt.pem
@@ -0,0 +1,68 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 2 (0x2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Root Certificate Authority, ST=CA, C=US/emailAddress=root@test.com, O=Test, OU=Test Department
+ Validity
+ Not Before: Jan 7 19:26:14 2019 GMT
+ Not After : Jan 6 19:26:14 2024 GMT
+ Subject: CN=client.test.com, ST=CA, C=US/emailAddress=root@client.test.com, O=Test Client, OU=Subunit of Test Organization
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:a6:a4:24:19:14:9e:96:0a:3e:b8:14:7b:e9:04:
+ ca:a0:ef:61:1a:e6:63:84:51:34:10:58:9e:f2:6f:
+ 9d:27:64:e5:a9:32:02:e2:fb:f0:c5:47:d0:b2:8f:
+ f4:19:71:2a:de:f8:de:ae:cb:0b:41:cf:cc:76:63:
+ 9b:4a:9b:12:50:5f:b9:b4:fc:e3:fd:05:85:7f:a7:
+ 1c:ad:ec:d1:40:70:fa:4c:51:88:a3:d4:e5:49:b2:
+ 72:7c:2e:4d:c5:00:ae:40:96:15:84:34:5f:99:75:
+ 7b:6a:00:d3:ec:a0:7d:82:d3:71:a3:79:cc:d4:4c:
+ 3b:50:49:d0:9b:27:e4:0b:cb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ X509v3 Key Usage:
+ Digital Signature, Non Repudiation, Key Encipherment
+ X509v3 Subject Alternative Name:
+ DNS:client.test.com, DNS:alt.client.test.com
+ Signature Algorithm: sha1WithRSAEncryption
+ 4f:11:ff:ed:05:e2:80:75:bd:e0:b3:f3:21:34:65:50:67:ff:
+ 3c:45:88:58:58:77:9a:6e:f6:1e:74:f9:cb:02:e1:31:e0:52:
+ d9:f9:63:e5:fb:01:d2:83:df:20:c1:77:a7:15:da:18:3a:e0:
+ ea:e0:66:ab:41:21:9e:36:9c:36:28:1c:cb:20:43:94:94:e8:
+ 9b:0d:3d:2a:ac:20:48:5b:b8:c0:45:0d:5c:30:91:be:ba:67:
+ b9:f6:bd:64:08:ab:af:35:a1:db:dd:54:e8:32:c9:3a:95:34:
+ 26:8f:a1:1b:a1:a2:32:47:a0:e0:a4:11:06:dc:d2:67:87:1a:
+ 51:50:bc:09:26:e3:1c:e7:83:a5:69:48:92:6c:87:94:46:f6:
+ b2:45:55:6f:5e:f2:6b:c8:9d:65:61:31:83:09:71:60:71:d5:
+ 9c:44:65:27:f6:3f:fd:fb:40:30:47:02:b1:6f:5a:ff:7a:c5:
+ 83:e0:80:52:53:a7:2c:24:71:51:81:df:3f:2f:1d:42:df:bc:
+ 86:b4:0c:18:64:8d:33:a7:c0:e8:f2:9e:f5:0b:92:c4:4d:f7:
+ 4b:2f:13:8d:81:25:f3:47:f5:72:71:c2:62:3d:36:09:3c:ec:
+ d1:15:6d:15:77:28:c6:de:f9:73:5d:5b:a0:a4:0f:f2:50:a0:
+ 00:20:87:fa
+-----BEGIN CERTIFICATE-----
+MIIDZTCCAk2gAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBizEoMCYGA1UEAwwfVGVz
+dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eTELMAkGA1UECAwCQ0ExCzAJBgNV
+BAYTAlVTMRwwGgYJKoZIhvcNAQkBFg1yb290QHRlc3QuY29tMQ0wCwYDVQQKDARU
+ZXN0MRgwFgYDVQQLDA9UZXN0IERlcGFydG1lbnQwHhcNMTkwMTA3MTkyNjE0WhcN
+MjQwMTA2MTkyNjE0WjCBljEYMBYGA1UEAwwPY2xpZW50LnRlc3QuY29tMQswCQYD
+VQQIDAJDQTELMAkGA1UEBhMCVVMxIzAhBgkqhkiG9w0BCQEWFHJvb3RAY2xpZW50
+LnRlc3QuY29tMRQwEgYDVQQKDAtUZXN0IENsaWVudDElMCMGA1UECwwcU3VidW5p
+dCBvZiBUZXN0IE9yZ2FuaXphdGlvbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEApqQkGRSelgo+uBR76QTKoO9hGuZjhFE0EFie8m+dJ2TlqTIC4vvwxUfQso/0
+GXEq3vjerssLQc/MdmObSpsSUF+5tPzj/QWFf6ccrezRQHD6TFGIo9TlSbJyfC5N
+xQCuQJYVhDRfmXV7agDT7KB9gtNxo3nM1Ew7UEnQmyfkC8sCAwEAAaNLMEkwCQYD
+VR0TBAIwADALBgNVHQ8EBAMCBeAwLwYDVR0RBCgwJoIPY2xpZW50LnRlc3QuY29t
+ghNhbHQuY2xpZW50LnRlc3QuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQBPEf/tBeKA
+db3gs/MhNGVQZ/88RYhYWHeabvYedPnLAuEx4FLZ+WPl+wHSg98gwXenFdoYOuDq
+4GarQSGeNpw2KBzLIEOUlOibDT0qrCBIW7jARQ1cMJG+ume59r1kCKuvNaHb3VTo
+Msk6lTQmj6EboaIyR6DgpBEG3NJnhxpRULwJJuMc54OlaUiSbIeURvayRVVvXvJr
+yJ1lYTGDCXFgcdWcRGUn9j/9+0AwRwKxb1r/esWD4IBSU6csJHFRgd8/Lx1C37yG
+tAwYZI0zp8Do8p71C5LETfdLLxONgSXzR/VyccJiPTYJPOzRFW0VdyjG3vlzXVug
+pA/yUKAAIIf6
+-----END CERTIFICATE-----
diff --git a/t/client_key.pem b/t/client_key.pem
new file mode 100644
index 0000000..22d1e37
--- /dev/null
+++ b/t/client_key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCmpCQZFJ6WCj64FHvpBMqg72Ea5mOEUTQQWJ7yb50nZOWpMgLi
++/DFR9Cyj/QZcSre+N6uywtBz8x2Y5tKmxJQX7m0/OP9BYV/pxyt7NFAcPpMUYij
+1OVJsnJ8Lk3FAK5AlhWENF+ZdXtqANPsoH2C03GjeczUTDtQSdCbJ+QLywIDAQAB
+AoGAXFVMrxzutiIdGHA5LCb8g2m/+2C2uYUo/PmtsJVJlZ9hZXuRf+WrRhSBvb7n
+uQUshPmOoXld1mxmVR7h19fOsBbgKYqwDTlLZEnvdMVzaGiyeHiwDAvJgkkrK7pV
+kod5JIjFd+UMho6+Qn3K7tzfttLze5xuBPNrMH3q2b8LgBECQQDbJVyF+gLk4gmd
+BBInYl0BHPjYmGW9xfHfn0fhkKhQCZ9eNzH5Wbk1D8yqdlfmJ5nI8FkLQunzwzgv
+P51m+USJAkEAwqpaJoVOaaRfmsgEXOsZop32DBCBHwOqOyqH9qMez8qfBPDHrUbA
+TMtJN/TqLhi4VEPeaHbioUdUybA+1MggswJALWEgNrId1U2lVflY1QT+Y1OfiCKO
+tux9eKQgG2p8IA7ODJF7bLoSqxU5eXcGHqfGpaB3n+hcT9j2Enqm2oL1mQJAIp53
+D9ivPDxeQEGH/RpWYcQjyLt6qxBUytbifSs/RIbtRsynRhqKAl44tDnbF72PsnSr
+bfqOjU4JNyEf22mH3wJBAK1yx2ilG//KYHtcfFYtll0Rkkife+2It6bTMVdkeVCH
+o5iDnW/+VsJCawhS9jHLZAjors+A9iyjygl1O6zLZTE=
+-----END RSA PRIVATE KEY-----
diff --git a/t/idle-timeout.t b/t/idle-timeout.t
index 66558ac..bc908e9 100644
--- a/t/idle-timeout.t
+++ b/t/idle-timeout.t
@@ -30,7 +30,13 @@ is($stats->{idle_kicks}, "0", "check stats 2");
sleep(5);
mem_stats($sock); # Network activity, so socket code will see dead socket
sleep(1);
-is($sock->connected(), undef, "check disconnected");
+# we run SSL tests over TCP; hence IO::Socket::SSL returns
+# '' upon disconnecting with the server.
+if (enabled_tls_testing()) {
+ is($sock->connected(),'', "check disconnected");
+} else {
+ is($sock->connected(),undef, "check disconnected");
+}
$sock = $server->sock;
$stats = mem_stats($sock);
diff --git a/t/lib/MemcachedTest.pm b/t/lib/MemcachedTest.pm
index 416daaf..72ebe08 100644
--- a/t/lib/MemcachedTest.pm
+++ b/t/lib/MemcachedTest.pm
@@ -15,7 +15,22 @@ my @unixsockets = ();
@EXPORT = qw(new_memcached sleep mem_get_is mem_gets mem_gets_is mem_stats
supports_sasl free_port supports_drop_priv supports_extstore
- wait_ext_flush);
+ wait_ext_flush, supports_tls, enabled_tls_testing);
+
+use constant MAX_READ_WRITE_SIZE => 16384;
+use constant SRV_CRT => "server_crt.pem";
+use constant SRV_KEY => "server_key.pem";
+use constant CLIENT_CRT => "client_crt.pem";
+use constant CLIENT_KEY => "client_key.pem";
+use constant CA_CRT => "cacert.pem";
+
+my $testdir = $builddir . "/t/";
+my $client_crt = $testdir. CLIENT_CRT;
+my $client_key = $testdir. CLIENT_KEY;
+my $server_crt = $testdir . SRV_CRT;
+my $server_key = $testdir . SRV_KEY;
+
+my $tls_checked = 0;
sub sleep {
my $n = shift;
@@ -149,10 +164,20 @@ sub free_port {
my $port;
while (!$sock) {
$port = int(rand(20000)) + 30000;
- $sock = IO::Socket::INET->new(LocalAddr => '127.0.0.1',
+ if (enabled_tls_testing()) {
+ $sock = eval qq{ IO::Socket::SSL->new(LocalAddr => '127.0.0.1',
+ LocalPort => $port,
+ Proto => '$type',
+ ReuseAddr => 1,
+ SSL_verify_mode => SSL_VERIFY_NONE);
+ };
+ die $@ if $@; # sanity check.
+ } else {
+ $sock = IO::Socket::INET->new(LocalAddr => '127.0.0.1',
LocalPort => $port,
Proto => $type,
ReuseAddr => 1);
+ }
}
return $port;
}
@@ -175,6 +200,23 @@ sub supports_extstore {
return 0;
}
+sub supports_tls {
+ my $output = `$builddir/memcached-debug -h`;
+ return 1 if $output =~ /enable-ssl/i;
+ return 0;
+}
+
+sub enabled_tls_testing {
+ if ($tls_checked) {
+ return 1;
+ } elsif (supports_tls() && $ENV{SSL_TEST}) {
+ eval "use IO::Socket::SSL";
+ croak("IO::Socket::SSL not installed or failed to load, cannot run SSL tests as requested") if $@;
+ $tls_checked = 1;
+ return 1;
+ }
+}
+
sub supports_drop_priv {
my $output = `$builddir/memcached-debug -h`;
return 1 if $output =~ /no_drop_privileges/i;
@@ -185,10 +227,21 @@ sub new_memcached {
my ($args, $passed_port) = @_;
my $port = $passed_port;
my $host = '127.0.0.1';
+ my $ssl_enabled = enabled_tls_testing();
if ($ENV{T_MEMD_USE_DAEMON}) {
my ($host, $port) = ($ENV{T_MEMD_USE_DAEMON} =~ m/^([^:]+):(\d+)$/);
- my $conn = IO::Socket::INET->new(PeerAddr => "$host:$port");
+ my $conn;
+ if ($ssl_enabled) {
+ $conn = eval qq{IO::Socket::SSL->new(PeerAddr => "$host:$port",
+ SSL_verify_mode => SSL_VERIFY_NONE,
+ SSL_cert_file => '$client_crt',
+ SSL_key_file => '$client_key');
+ };
+ die $@ if $@; # sanity check.
+ } else {
+ $conn = IO::Socket::INET->new(PeerAddr => "$host:$port");
+ }
if ($conn) {
return Memcached::Handle->new(conn => $conn,
host => $host,
@@ -203,13 +256,18 @@ sub new_memcached {
$args .= " -o relaxed_privileges";
my $udpport;
- if ($args =~ /-l (\S+)/) {
- $port = free_port();
+ if ($args =~ /-l (\S+)/ || ($ssl_enabled && ($args !~ /-s (\S+)/))) {
+ if (!$port) {
+ $port = free_port();
+ }
$udpport = free_port("udp");
$args .= " -p $port";
if (supports_udp()) {
$args .= " -U $udpport";
}
+ if ($ssl_enabled) {
+ $args .= " -Z -o ssl_chain_cert=$server_crt -o ssl_key=$server_key";
+ }
} elsif ($args !~ /-s (\S+)/) {
my $num = @unixsockets;
my $file = "/tmp/memcachetest.$$.$num";
@@ -246,7 +304,17 @@ sub new_memcached {
# sockets
for (1..20) {
- my $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$port");
+ my $conn;
+ if ($ssl_enabled) {
+ $conn = eval qq{ IO::Socket::SSL->new(PeerAddr => "127.0.0.1:$port",
+ SSL_verify_mode => SSL_VERIFY_NONE,
+ SSL_cert_file => '$client_crt',
+ SSL_key_file => '$client_key');
+ };
+ die $@ if $@; # sanity check.
+ } else {
+ $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$port");
+ }
if ($conn) {
return Memcached::Handle->new(pid => $childpid,
conn => $conn,
@@ -299,6 +367,12 @@ sub new_sock {
my $self = shift;
if ($self->{domainsocket}) {
return IO::Socket::UNIX->new(Peer => $self->{domainsocket});
+ } elsif (MemcachedTest::enabled_tls_testing()) {
+ return eval qq{ IO::Socket::SSL->new(PeerAddr => "$self->{host}:$self->{port}",
+ SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
+ SSL_cert_file => '$client_crt',
+ SSL_key_file => '$client_key');
+ };
} else {
return IO::Socket::INET->new(PeerAddr => "$self->{host}:$self->{port}");
}
diff --git a/t/misbehave.t b/t/misbehave.t
index 13cb7f3..1312a27 100755
--- a/t/misbehave.t
+++ b/t/misbehave.t
@@ -7,7 +7,7 @@ use Socket qw(MSG_PEEK MSG_DONTWAIT);
use lib "$Bin/lib";
use MemcachedTest;
-if (supports_drop_priv()) {
+if (!enabled_tls_testing() && supports_drop_priv()) {
plan tests => 1;
} else {
plan skip_all => 'Privilege drop not supported';
diff --git a/t/multiversioning.t b/t/multiversioning.t
index df9eab4..c65c2df 100755
--- a/t/multiversioning.t
+++ b/t/multiversioning.t
@@ -24,7 +24,17 @@ mem_get_is($sock, "big", $bigval, "big value got correctly");
print $sock "get big\r\n";
my $buf;
-is(read($sock, $buf, $size / 2), $size / 2, "read half the answer back");
+my $read = 0;
+my $to_read = $size / 2;
+while ($read < $to_read) {
+ my $read_bytes = $sock->sysread($buf,
+ ($to_read - $read > MemcachedTest::MAX_READ_WRITE_SIZE ?
+ MemcachedTest::MAX_READ_WRITE_SIZE : $to_read - $read),
+ $read);
+ last if ($read_bytes <= 0);
+ $read += $read_bytes;
+}
+is($read, $size / 2, "read half the answer back");
like($buf, qr/VALUE big/, "buf has big value header in it");
like($buf, qr/abcdef/, "buf has some data in it");
unlike($buf, qr/abcde\]/, "buf doesn't yet close");
diff --git a/t/noreply.t b/t/noreply.t
index 54a3f13..6aa3dc2 100644
--- a/t/noreply.t
+++ b/t/noreply.t
@@ -44,4 +44,3 @@ mem_get_is($sock, "noreply:foo", "7");
print $sock "delete noreply:foo noreply\r\n";
mem_get_is($sock, "noreply:foo");
-
diff --git a/t/server.pem b/t/server.pem
new file mode 100644
index 0000000..d4bc393
--- /dev/null
+++ b/t/server.pem
@@ -0,0 +1,79 @@
+subject= C = UK, O = OpenSSL Group, OU = FOR TESTING PURPOSES ONLY, CN = Test Server Cert
+issuer= C = UK, O = OpenSSL Group, OU = FOR TESTING PURPOSES ONLY, CN = OpenSSL Test Intermediate CA
+-----BEGIN CERTIFICATE-----
+MIID0DCCArigAwIBAgIIcsOElVeHzfYwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UE
+BhMCVUsxFjAUBgNVBAoMDU9wZW5TU0wgR3JvdXAxIjAgBgNVBAsMGUZPUiBURVNU
+SU5HIFBVUlBPU0VTIE9OTFkxJTAjBgNVBAMMHE9wZW5TU0wgVGVzdCBJbnRlcm1l
+ZGlhdGUgQ0EwIBcNMTgwNjE0MTI0NjI4WhgPMjExODA2MTQxMjQ2MjhaMGQxCzAJ
+BgNVBAYTAlVLMRYwFAYDVQQKDA1PcGVuU1NMIEdyb3VwMSIwIAYDVQQLDBlGT1Ig
+VEVTVElORyBQVVJQT1NFUyBPTkxZMRkwFwYDVQQDDBBUZXN0IFNlcnZlciBDZXJ0
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0jIZ8IZ4dIzBc+ZfdmG5
+n8G3JzRX99QvIqv52s4hFVfdzoa+AciKJpo9zkegWPmfsAVNa4uVceg/ZQt6qJsu
+G/pxbQSZVnyjDQGtt7rgaDEbyUP0XJCnzyRdWSUjFS8yNZn4NkmZU01GlHtXdzWy
+dEa5PaiTIwW0HI+bjjOEhwJ1hFuFqzlKHVKHA6DBzNcl6ly0E/q2kyslbR+0hq7p
+NMqKvvuAxqgc//W8KvLDlKAt9D3t5zgh2+BrMPemrzjEaM97yHTogJo7+SKVDdUw
+YQ7Br3xfyki9u2bUYib1BMSvLezxNP0qf/iU91z4xyLmMvOXE6W0D1WHwya1CfE7
+vwIDAQABo3gwdjAdBgNVHQ4EFgQU3ulCbvgfxej6rHnddMpBidwnLIIwHwYDVR0j
+BBgwFoAUCgNEpWg658NdblSLsvg43EA1WwUwCQYDVR0TBAIwADATBgNVHSUEDDAK
+BggrBgEFBQcDATAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQAD
+ggEBAENMzaqJtmWED++W4KXFVwNBkQ87errBXe4jVeYKpjNb0JGMm60MS5ty54fb
+r27SsR2EEk3EK2rcd85RR7TEKZCn9SvPykVtVf0tru7nOptQJgSbRvxIzyyq1UcE
+K+BXDgN/I0f1X6qbk4Stb6uJF7yyAUabacjwKqgVifOOeKF9WJhVA8qJKoVq7HLN
+k+uvm0geO1I4LKeULXVnQy8kwB6twcxN8iPyO45ZxbYIVeEKaYtbj/XPoq6KsLIb
+5fj+mK1r/LkWk352ksNhf73r3alF8TBcSLqnbMoy1/ZvzlI4ksp9IGWtIU+CzP/f
+VUjh00NOwDLd5jJbPoWW0oNp9m4=
+-----END CERTIFICATE-----
+subject= C = UK, O = OpenSSL Group, OU = FOR TESTING PURPOSES ONLY, CN = OpenSSL Test Intermediate CA
+issuer= C = UK, O = OpenSSL Group, OU = FOR TESTING PURPOSES ONLY, CN = OpenSSL Test Root CA
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAqegAwIBAgIILsaQqJAjK4IwDQYJKoZIhvcNAQELBQAwaDELMAkGA1UE
+BhMCVUsxFjAUBgNVBAoMDU9wZW5TU0wgR3JvdXAxIjAgBgNVBAsMGUZPUiBURVNU
+SU5HIFBVUlBPU0VTIE9OTFkxHTAbBgNVBAMMFE9wZW5TU0wgVGVzdCBSb290IENB
+MCAXDTE4MDYxNDEyNDYyOFoYDzIxMTgwNjE0MTI0NjI4WjBwMQswCQYDVQQGEwJV
+SzEWMBQGA1UECgwNT3BlblNTTCBHcm91cDEiMCAGA1UECwwZRk9SIFRFU1RJTkcg
+UFVSUE9TRVMgT05MWTElMCMGA1UEAwwcT3BlblNTTCBUZXN0IEludGVybWVkaWF0
+ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANIpVng2wNFJp2kF
+oJ6Yji25wy1YufnS8NxA82fk5OHdhGWj1CWqnQNotEqEQzcOUszQYrNxd8tEvoWk
+Ik4JMBVoEcgBGedchftptTNulFWodWpi1yFaqA/Nz2BsVgcCJW4C+UWDT7VeHtGU
+7tYKKr35lxp6io/a4jUDQXvO2nJA9YlrxOktunMqtoZSYqUz35ZXsdkn58o8Fbqm
+dEpw6AqAr9aBgY5DSaGxbaX2lwNt9NvB+f9ucOqEnPP8AfTlPYc/ENwJ6u/H8RGw
+d1im71mu2lHjcws3aHkbluH860U3vlKWx6Ff1qdQcH98e2HwElqxCK00xya8leu4
+u64nljkCAwEAAaNjMGEwHQYDVR0OBBYEFAoDRKVoOufDXW5Ui7L4ONxANVsFMB8G
+A1UdIwQYMBaAFDZjTeLsQUG6KL9xuLhzXVdB4pkKMA8GA1UdEwEB/wQFMAMBAf8w
+DgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBgQDZQJLA90ewVaS3E3du
+gSjPkQ1xsHm8H1am+7zr5oZ81J+R8XYIZgMR+9ShVo38OradiYNqDLso+4iuVdxh
+hzoSoQELoDXCficzWKnlAtWvwDDoczyK+/p94g3VKx14n2+GvQzoZ4kwQQgaFH1w
+YI6w0oH9zwoklCxvihj8D069QrYyuTT8JGZ2m0FHqVJg6teuQKFahSgwYR2CUoIb
+6PrpSUQeCVCH8TPkzlRT6UgtM3ERt7+TlQ+zZ80dSf4YTAsDv9Z/CJXiF/5wZr6/
+lWuFjWmX2HkpEW6Wiv5KF8QP6Ft7Z+RYua7RMtELCYvqYbWDBs7fXWGBkZ5xhB09
+jCxz+F7zOeRbyzacfFq9DhxCWCRbIrdgGGE/Of2ujJtmK/2p4M6E5IsKNAI2SJBW
+iJXvIgQgR22ehPqy6er2Gog5LkWUwqB0kHZJJpbp1IW01IGTpD6YAJyVCEAlyMbo
+Kto9+wQFLT3Auv/W5h6OwxkNdfAyZBYy0ZSFk4EE8OdWWY4=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA0jIZ8IZ4dIzBc+ZfdmG5n8G3JzRX99QvIqv52s4hFVfdzoa+
+AciKJpo9zkegWPmfsAVNa4uVceg/ZQt6qJsuG/pxbQSZVnyjDQGtt7rgaDEbyUP0
+XJCnzyRdWSUjFS8yNZn4NkmZU01GlHtXdzWydEa5PaiTIwW0HI+bjjOEhwJ1hFuF
+qzlKHVKHA6DBzNcl6ly0E/q2kyslbR+0hq7pNMqKvvuAxqgc//W8KvLDlKAt9D3t
+5zgh2+BrMPemrzjEaM97yHTogJo7+SKVDdUwYQ7Br3xfyki9u2bUYib1BMSvLezx
+NP0qf/iU91z4xyLmMvOXE6W0D1WHwya1CfE7vwIDAQABAoIBAQC2HAo1RYvfDoQc
+sh9LJWf5bZANO2Brqz4bP/x9AdHP+AyH/l1oliJ7R2785TmbXMppam6lGo4j3h/u
+n39pzOip/NWAqldfgySRBD9Jy3LZUpLMUT/JYtrAsLTfozk+BWHu5rMR9boNXgok
+Yqho8/DkpNGhBghUc4CUricLkL7laD3ziAHpx8yALL3tnLGOpgT9hNrA8Dm3yfUS
+JEfiG12ILXvq1IP+vUNuaLpTLJZuqUmLpK8v+CBYgKxfd+TDnEjul4PqhhIIFK3A
+xEZYQR2D/AXUwng9hP9uCbVm5lOY6vRbi9Fpbt+KRv+m25s1AnuhJFBOsL30h/Tb
+iCKWm/nhAoGBAO0bFqMvZHjaT2KiwOwG/Ze9NsjynFPVltiuCqNj8HE5wM6imC5J
+SdB+jMkgN6ERXALWrtr8Uf2pqzfeMsi6pekOOVTWLe/8c4bAZRxaCZn/BlZRysZI
+vB9Gb7m7Oymw5iDSqrYywgOiUu+oIiCrmPOealhmn7zmHzHaETvdL9zDAoGBAOLy
+DVT1csoexnuHVIWqnp7FK7lv6eOGZSdXpfJ3XYjmKJLK2hpVZe+J/mFOL1wsKSt4
+0k/V0dnkHR7V4Pa4ECiCthkWMWrBVIHe7+ZnZ0ocKQSC+EEecavOiZ57S/qnUlT6
+NtQP4cSy4DHzzFZdTZnn+2oymapPZpb2mvSN/GVVAoGADrIlHwwq8Aqn7Pclefuc
+8DC8GoxfABs29EslQadKGdp4htYxFH1aY9/UHgsvJ36J82sW/1+wPUas5BOTljlr
+WxyUlRuJUVyWVH3MRouWGMNjwynipZOQhWe6OQrPye+688Ha7twKhmsjNNN4+glo
+u4DQGpaRxAWHXXGkq88zzj0CgYEAsICEceD7R8srnwMfb13FQ8IhQXWSuAvcO/7k
+53CCZGhsgc4WVoi4YNY360G9f7gwxMiQ+NpY/Vd2dnbtIbUBjCAss9IY2OhHa0IR
+3mXpZTAFjqa1oR+mVHKrgYBvFSBw3fpEDiXT9wEPcIomD709D0fmty9nZ5edOCfP
+WAfdlokCgYEAqXuMuAg3NMMgEv+eBfsf43v3hRwBqPYanE26wcO3GoT/S8BpB6wy
+vBoPZOlO5ZfsD2jaTec60GLay+MofxC7qNXIjzHOw50ry4bqHqqoQbn2cONE1k+0
+ov7H2keTcG9FEGgL7dRUq3pRUo/W12WmRuDN17IEgkzAeisJnoiPtaQ=
+-----END RSA PRIVATE KEY-----
diff --git a/t/server_crt.pem b/t/server_crt.pem
new file mode 100644
index 0000000..6b7a315
--- /dev/null
+++ b/t/server_crt.pem
@@ -0,0 +1,67 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Root Certificate Authority, ST=CA, C=US/emailAddress=root@test.com, O=Test, OU=Test Department
+ Validity
+ Not Before: Jan 7 18:25:01 2019 GMT
+ Not After : Jan 6 18:25:01 2024 GMT
+ Subject: CN=test.com, ST=CA, C=US/emailAddress=root@test.com, O=Test, OU=Subunit of Test Organization
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:ca:76:35:79:91:e8:a2:ee:ef:f4:35:7e:29:85:
+ 75:90:65:5a:60:f8:a0:d9:ef:68:d1:61:79:69:d7:
+ e2:7b:f1:67:71:65:50:31:4e:9a:f8:6f:27:e9:05:
+ 0b:0e:76:95:24:9b:c2:bf:90:e5:6b:45:fd:e3:54:
+ ac:d5:62:90:4e:37:de:8f:ae:96:f6:b3:57:eb:ad:
+ b8:44:13:5d:a7:34:76:c1:26:49:91:67:3e:5e:52:
+ 68:c1:1c:7f:91:c7:9e:01:e2:be:a7:a8:eb:3f:44:
+ 6d:c7:c5:82:4b:97:d9:3f:c0:51:99:1b:20:df:12:
+ a3:e6:bb:66:45:6d:b4:e1:07
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ X509v3 Key Usage:
+ Digital Signature, Non Repudiation, Key Encipherment
+ X509v3 Subject Alternative Name:
+ DNS:test.com, DNS:alt.test.com
+ Signature Algorithm: sha1WithRSAEncryption
+ 61:c1:18:b4:04:79:05:0e:b9:79:2f:0e:3f:aa:f1:36:e3:90:
+ b9:c6:99:25:53:4a:06:64:52:92:29:c5:09:41:a6:16:74:1b:
+ 3c:4e:81:32:c2:d8:54:e0:1e:08:45:cf:f1:d0:ad:ea:11:1a:
+ b5:cf:7c:98:8a:dd:c1:01:e4:d0:f5:8e:60:fa:7f:e7:74:2d:
+ 91:43:81:bd:95:92:41:66:84:8b:8c:70:d7:2f:d4:2f:37:82:
+ 8f:9a:ef:c0:7d:c5:56:56:92:7a:00:b6:30:65:37:4c:6c:7a:
+ ba:cc:e2:dc:73:e9:f5:2c:3c:3e:31:67:ee:3d:b7:78:96:89:
+ ba:be:4f:85:a2:a8:83:3e:53:20:f0:bf:29:50:dc:23:38:58:
+ d8:33:f5:7b:4a:12:df:2b:34:4c:1c:f1:76:6b:86:95:74:43:
+ 29:f7:68:f0:ca:04:08:89:ac:97:7d:05:14:a0:ca:81:56:5c:
+ dd:c3:56:a3:53:01:0a:01:5c:55:b4:39:10:1b:be:47:19:58:
+ a1:f2:e6:0c:08:95:b0:35:4e:6c:81:6d:b9:cf:0e:5c:70:ff:
+ f3:b4:a7:95:69:1a:58:b7:ac:cc:2c:79:47:7e:20:17:cc:36:
+ be:2c:10:11:31:28:63:dd:6f:8d:8d:e9:11:ea:ca:fc:10:0a:
+ e0:ae:53:db
+-----BEGIN CERTIFICATE-----
+MIIDQjCCAiqgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBizEoMCYGA1UEAwwfVGVz
+dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eTELMAkGA1UECAwCQ0ExCzAJBgNV
+BAYTAlVTMRwwGgYJKoZIhvcNAQkBFg1yb290QHRlc3QuY29tMQ0wCwYDVQQKDARU
+ZXN0MRgwFgYDVQQLDA9UZXN0IERlcGFydG1lbnQwHhcNMTkwMTA3MTgyNTAxWhcN
+MjQwMTA2MTgyNTAxWjCBgTERMA8GA1UEAwwIdGVzdC5jb20xCzAJBgNVBAgMAkNB
+MQswCQYDVQQGEwJVUzEcMBoGCSqGSIb3DQEJARYNcm9vdEB0ZXN0LmNvbTENMAsG
+A1UECgwEVGVzdDElMCMGA1UECwwcU3VidW5pdCBvZiBUZXN0IE9yZ2FuaXphdGlv
+bjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAynY1eZHoou7v9DV+KYV1kGVa
+YPig2e9o0WF5adfie/FncWVQMU6a+G8n6QULDnaVJJvCv5Dla0X941Ss1WKQTjfe
+j66W9rNX6624RBNdpzR2wSZJkWc+XlJowRx/kceeAeK+p6jrP0Rtx8WCS5fZP8BR
+mRsg3xKj5rtmRW204QcCAwEAAaM9MDswCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAw
+IQYDVR0RBBowGIIIdGVzdC5jb22CDGFsdC50ZXN0LmNvbTANBgkqhkiG9w0BAQUF
+AAOCAQEAYcEYtAR5BQ65eS8OP6rxNuOQucaZJVNKBmRSkinFCUGmFnQbPE6BMsLY
+VOAeCEXP8dCt6hEatc98mIrdwQHk0PWOYPp/53QtkUOBvZWSQWaEi4xw1y/ULzeC
+j5rvwH3FVlaSegC2MGU3TGx6uszi3HPp9Sw8PjFn7j23eJaJur5PhaKogz5TIPC/
+KVDcIzhY2DP1e0oS3ys0TBzxdmuGlXRDKfdo8MoECImsl30FFKDKgVZc3cNWo1MB
+CgFcVbQ5EBu+RxlYofLmDAiVsDVObIFtuc8OXHD/87SnlWkaWLeszCx5R34gF8w2
+viwQETEoY91vjY3pEerK/BAK4K5T2w==
+-----END CERTIFICATE-----
diff --git a/t/server_key.pem b/t/server_key.pem
new file mode 100644
index 0000000..9e02ce4
--- /dev/null
+++ b/t/server_key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDKdjV5keii7u/0NX4phXWQZVpg+KDZ72jRYXlp1+J78WdxZVAx
+Tpr4byfpBQsOdpUkm8K/kOVrRf3jVKzVYpBON96Prpb2s1frrbhEE12nNHbBJkmR
+Zz5eUmjBHH+Rx54B4r6nqOs/RG3HxYJLl9k/wFGZGyDfEqPmu2ZFbbThBwIDAQAB
+AoGBAItN/ItSVrRNHTN13wHovzSEWERiriJl9UQkAmtOTJqlRlyiriPPBxgrO1W8
+z5e7BfGzbrNqmkBOX1uctnL4J3tA7xFO6OnquDCLXhLc49mw+zxkcDP5ta1ROeiV
+kIm8rSsvh8ks7StA17m5910rNfn5/IRHK6dC+G4FK7qLfCiRAkEA5Szy22sFeE4D
+OM/wmCHpoOc0qA5k0FeyvnfGFKcAsVZ2+b/MPI2/KbB0uY/PWj43H6h9fEqMGVf6
+J7Ukas1N2QJBAOIozZjzfs3PQvKHMzOG3mQt+qSjsDKiEq/cPMIRPXYX5pS0+2Gz
+mtUaikI+Nk5hekXqA2i+4uis9UhCzm+W+d8CQQCEo/ZPrmp1DdnpiNh8hKw+l3Kv
+jd0lhIyMlrALhfjtqtijhjHEHlo0289DEwv09CtdZFx0koTxqiy7zKiuM/NJAkBZ
+S2sB/QIQGMmCIMeijJm6TD0uTEMBeuSN8xM6PLxbqEwuYtbuWI/FnFkClrWydOJm
+QGNgNB47aC7gfSAtBxtZAkBiCJcoVmXkm3rS7scUgCHrNhKH0G0nJbqjgfj57I31
+rCw34N6L+fyUozJQCBYdECyI1xG2eajqrMmnVZ046cjF
+-----END RSA PRIVATE KEY-----
diff --git a/t/ssl_cert_refresh.t b/t/ssl_cert_refresh.t
new file mode 100644
index 0000000..6fdb37a
--- /dev/null
+++ b/t/ssl_cert_refresh.t
@@ -0,0 +1,76 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use File::Copy;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+if (!enabled_tls_testing()) {
+ plan skip_all => 'SSL testing is not enabled';
+ exit 0;
+}
+
+my $cert = "t/". MemcachedTest::SRV_CRT;
+my $key = "t/". MemcachedTest::SRV_KEY;
+my $cert_back = "t/cert_back";
+my $key_back = "t/pkey_back";
+my $new_cert_key = "t/server.pem";
+my $default_crt_ou = "OU=Subunit of Test Organization";
+
+my $server = new_memcached();
+my $stats = mem_stats($server->sock);
+my $pid = $stats->{pid};
+my $sock = $server->sock;
+
+# This connection should return the default server certificate
+# memcached was started with.
+my $cert_details =$sock->dump_peer_certificate();
+$cert_details =~ m/(OU=([^\/\n]*))/;
+is($1, $default_crt_ou, 'Got the default cert');
+
+# Swap a new certificate with a key
+copy($cert, $cert_back) or die "Cert backup failed: $!";
+copy($key, $key_back) or die "Key backup failed: $!";
+copy($new_cert_key, $cert) or die "New Cert copy failed: $!";
+copy($new_cert_key, $key) or die "New key copy failed: $!";
+
+# Ask server to refresh certificates
+print $sock "refresh_certs\r\n";
+is(scalar <$sock>, "OK\r\n", "refreshed certificates");
+
+# New connections should use the new certificate
+$cert_details = $server->new_sock->dump_peer_certificate();
+$cert_details =~ m/(OU=([^\/]*))/;
+is($1, 'OU=FOR TESTING PURPOSES ONLY','Got the new cert');
+# Old connection should use the previous certificate
+$cert_details =$sock->dump_peer_certificate();
+$cert_details =~ m/(OU=([^\/\n]*))/;
+is($1, $default_crt_ou, 'Old connection still has the old cert');
+
+# Just sleep a while to test the time_since_server_cert_refresh as it's counted
+# in seconds.
+sleep 2;
+$stats = mem_stats($sock);
+
+# Restore and ensure previous certificate is back for new connections.
+move($cert_back, $cert) or die "Cert restore failed: $!";
+move($key_back, $key) or die "Key restore failed: $!";
+print $sock "refresh_certs\r\n";
+is(scalar <$sock>, "OK\r\n", "refreshed certificates");
+
+
+$cert_details = $server->new_sock->dump_peer_certificate();
+$cert_details =~ m/(OU=([^\/\n]*))/;
+is($1, $default_crt_ou, 'Got the old cert back');
+
+my $stats_after = mem_stats($sock);
+
+# We should see last refresh time is reset; hence the new
+# time_since_server_cert_refresh should be less.
+cmp_ok($stats_after->{time_since_server_cert_refresh}, '<',
+ $stats->{time_since_server_cert_refresh}, 'Certs refreshed');
+
+done_testing();
diff --git a/t/ssl_ports.t b/t/ssl_ports.t
new file mode 100644
index 0000000..d717f63
--- /dev/null
+++ b/t/ssl_ports.t
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+
+if (!enabled_tls_testing()) {
+ plan skip_all => 'SSL testing is not enabled';
+ exit 0;
+}
+
+my $tcp_port = free_port();
+my $ssl_port = free_port();
+
+my $server = new_memcached("-l notls:127.0.0.1:$tcp_port,127.0.0.1:$ssl_port", $ssl_port);
+my $sock = $server->sock;
+
+# Make sure we can talk over SSL
+print $sock "set foo:123 0 0 16\r\nfoo set over SSL\r\n";
+is(scalar <$sock>, "STORED\r\n", "stored foo");
+
+
+#.. and TCP
+my $tcp_sock = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$tcp_port");
+mem_get_is($tcp_sock, "foo:123", "foo set over SSL");
+
+done_testing()
diff --git a/t/ssl_settings.t b/t/ssl_settings.t
new file mode 100644
index 0000000..57f9668
--- /dev/null
+++ b/t/ssl_settings.t
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+use Cwd;
+
+if (!enabled_tls_testing()) {
+ plan skip_all => 'SSL testing is not enabled';
+ exit 0;
+}
+
+my $server = new_memcached();
+my $settings = mem_stats($server->sock, ' settings');
+
+my $cert = getcwd ."/t/". MemcachedTest::SRV_CRT;
+my $key = getcwd ."/t/". MemcachedTest::SRV_KEY;
+
+is($settings->{'ssl_enabled'}, 'yes');
+is($settings->{'ssl_chain_cert'}, $cert);
+is($settings->{'ssl_key'}, $key);
+is($settings->{'ssl_verify_mode'}, 0);
+is($settings->{'ssl_keyformat'}, 1);
+is($settings->{'ssl_ciphers'}, 'NULL');
+is($settings->{'ssl_ca_cert'}, 'NULL');
+is($settings->{'ssl_wbuf_size'}, 16384);
+
+$server->DESTROY();
+$server = new_memcached("-o ssl_wbuf_size=64");
+$settings = mem_stats($server->sock, ' settings');
+is($settings->{'ssl_wbuf_size'},65536);
+
+done_testing();
diff --git a/t/ssl_verify_modes.t b/t/ssl_verify_modes.t
new file mode 100644
index 0000000..4d6445c
--- /dev/null
+++ b/t/ssl_verify_modes.t
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+use Cwd;
+
+if (!enabled_tls_testing()) {
+ plan skip_all => 'SSL testing is not enabled';
+ exit 0;
+}
+
+my $ca_crt = getcwd() . "/t/" . MemcachedTest::CA_CRT;
+my $server = new_memcached("-o ssl_verify_mode=2 -o ssl_ca_cert=$ca_crt");
+# just using stats to make sure everything is working fine.
+my $stats = mem_stats($server->sock);
+is($stats->{accepting_conns}, 1, "client cert is verified");
+
+done_testing();
diff --git a/t/stats.t b/t/stats.t
index d62f015..aa421ae 100755
--- a/t/stats.t
+++ b/t/stats.t
@@ -24,7 +24,12 @@ my $sock = $server->sock;
my $stats = mem_stats($sock);
# Test number of keys
-is(scalar(keys(%$stats)), 70, "expected count of stats values");
+if (MemcachedTest::enabled_tls_testing()) {
+ # when TLS is enabled, stats contains time_since_server_cert_refresh
+ is(scalar(keys(%$stats)), 71, "expected count of stats values");
+} else {
+ is(scalar(keys(%$stats)), 70, "expected count of stats values");
+}
# Test initial state
foreach my $key (qw(curr_items total_items bytes cmd_get cmd_set get_hits evictions get_misses get_expired
@@ -130,7 +135,13 @@ is('z', $v, 'got the expected value');
my $settings = mem_stats($sock, ' settings');
is(1024, $settings->{'maxconns'});
-isnt('NULL', $settings->{'domain_socket'});
+# we run SSL tests over TCP; hence the domain_socket
+# is expected to be NULL.
+if (enabled_tls_testing()) {
+ is('NULL', $settings->{'domain_socket'});
+} else {
+ isnt('NULL', $settings->{'domain_socket'});
+}
is('on', $settings->{'evictions'});
is('yes', $settings->{'cas_enabled'});
is('no', $settings->{'auth_enabled_sasl'});
diff --git a/testapp.c b/testapp.c
index ea8081b..96a4afb 100644
--- a/testapp.c
+++ b/testapp.c
@@ -22,15 +22,73 @@
#include "cache.h"
#include "util.h"
#include "protocol_binary.h"
+#ifdef TLS
+#include <openssl/ssl.h>
+#endif
#define TMP_TEMPLATE "/tmp/test_file.XXXXXXX"
enum test_return { TEST_SKIP, TEST_PASS, TEST_FAIL };
+struct conn {
+ int sock;
+#ifdef TLS
+ SSL_CTX *ssl_ctx;
+ SSL *ssl;
+#endif
+ ssize_t (*read)(struct conn *c, void *buf, size_t count);
+ ssize_t (*write)(struct conn *c, const void *buf, size_t count);
+};
+
+
+static ssize_t tcp_read(struct conn *c, void *buf, size_t count);
+static ssize_t tcp_write(struct conn *c, const void *buf, size_t count);
+#ifdef TLS
+static ssize_t ssl_read(struct conn *c, void *buf, size_t count);
+static ssize_t ssl_write(struct conn *c, const void *buf, size_t count);
+#endif
+
+ssize_t tcp_read(struct conn *c, void *buf, size_t count) {
+ assert(c != NULL);
+ return read(c->sock, buf, count);
+}
+
+ssize_t tcp_write(struct conn *c, const void *buf, size_t count) {
+ assert(c != NULL);
+ return write(c->sock, buf, count);
+}
+#ifdef TLS
+ssize_t ssl_read(struct conn *c, void *buf, size_t count) {
+ assert(c != NULL);
+ return SSL_read(c->ssl, buf, count);
+}
+
+ssize_t ssl_write(struct conn *c, const void *buf, size_t count) {
+ assert(c != NULL);
+ return SSL_write(c->ssl, buf, count);
+}
+#endif
+
static pid_t server_pid;
static in_port_t port;
-static int sock;
+static struct conn *con = NULL;
static bool allow_closed_read = false;
+static bool enable_ssl = false;
+
+static void close_conn() {
+ if (con == NULL) return;
+#ifdef TLS
+ if (con->ssl) {
+ SSL_shutdown(con->ssl);
+ SSL_free(con->ssl);
+ }
+ if (con->ssl_ctx)
+ SSL_CTX_free(con->ssl_ctx);
+#endif
+ if (con->sock > 0) close(con->sock);
+ free(con);
+ con = NULL;
+}
static enum test_return cache_create_test(void)
{
@@ -318,7 +376,7 @@ static pid_t start_server(in_port_t *port_out, bool daemon, int timeout) {
if (pid == 0) {
/* Child */
- char *argv[20];
+ char *argv[24];
int arg = 0;
char tmo[24];
snprintf(tmo, sizeof(tmo), "%u", timeout);
@@ -339,6 +397,15 @@ static pid_t start_server(in_port_t *port_out, bool daemon, int timeout) {
argv[arg++] = "-1";
argv[arg++] = "-U";
argv[arg++] = "0";
+#ifdef TLS
+ if (enable_ssl) {
+ argv[arg++] = "-Z";
+ argv[arg++] = "-o";
+ argv[arg++] = "ssl_chain_cert=t/server_crt.pem";
+ argv[arg++] = "-o";
+ argv[arg++] = "ssl_key=t/server_key.pem";
+ }
+#endif
/* Handle rpmbuild and the like doing this as root */
if (getuid() == 0) {
argv[arg++] = "-u";
@@ -445,8 +512,15 @@ static struct addrinfo *lookuphost(const char *hostname, in_port_t port)
return ai;
}
-static int connect_server(const char *hostname, in_port_t port, bool nonblock)
+static struct conn *connect_server(const char *hostname, in_port_t port,
+ bool nonblock, const bool ssl)
{
+ struct conn *c;
+ if (!(c = (struct conn *)calloc(1, sizeof(struct conn)))) {
+ fprintf(stderr, "Failed to allocate the client connection: %s\n",
+ strerror(errno));
+ }
+
struct addrinfo *ai = lookuphost(hostname, port);
int sock = -1;
if (ai != NULL) {
@@ -472,7 +546,43 @@ static int connect_server(const char *hostname, in_port_t port, bool nonblock)
freeaddrinfo(ai);
}
- return sock;
+ c->sock = sock;
+#ifdef TLS
+ if (sock > 0 && ssl) {
+ c->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if (c->ssl_ctx == NULL) {
+ fprintf(stderr, "Failed to create the SSL context: %s\n",
+ strerror(errno));
+ close(sock);
+ sock = -1;
+ }
+ c->ssl = SSL_new(c->ssl_ctx);
+ if (c->ssl == NULL) {
+ fprintf(stderr, "Failed to create the SSL object: %s\n",
+ strerror(errno));
+ close(sock);
+ sock = -1;
+ }
+ SSL_set_fd (c->ssl, c->sock);
+ int ret = SSL_connect(c->ssl);
+ if (ret < 0) {
+ int err = SSL_get_error(c->ssl, ret);
+ if (err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) {
+ fprintf(stderr, "SSL connection failed with error code : %s\n",
+ strerror(errno));
+ close(sock);
+ sock = -1;
+ }
+ }
+ c->read = ssl_read;
+ c->write = ssl_write;
+ } else
+#endif
+ {
+ c->read = tcp_read;
+ c->write = tcp_write;
+ }
+ return c;
}
static enum test_return test_vperror(void) {
@@ -525,7 +635,7 @@ static void send_ascii_command(const char *buf) {
size_t len = strlen(buf);
do {
- ssize_t nw = write(sock, ptr + offset, len - offset);
+ ssize_t nw = con->write((void*)con, ptr + offset, len - offset);
if (nw == -1) {
if (errno != EINTR) {
fprintf(stderr, "Failed to write: %s\n", strerror(errno));
@@ -547,7 +657,7 @@ static void read_ascii_response(char *buffer, size_t size) {
off_t offset = 0;
bool need_more = true;
do {
- ssize_t nr = read(sock, buffer + offset, 1);
+ ssize_t nr = con->read(con, buffer + offset, 1);
if (nr == -1) {
if (errno != EINTR) {
fprintf(stderr, "Failed to read: %s\n", strerror(errno));
@@ -568,10 +678,11 @@ static void read_ascii_response(char *buffer, size_t size) {
static enum test_return test_issue_92(void) {
char buffer[1024];
- close(sock);
- sock = connect_server("127.0.0.1", port, false);
+ close_conn();
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
send_ascii_command("stats cachedump 1 0 0\r\n");
+
read_ascii_response(buffer, sizeof(buffer));
assert(strncmp(buffer, "END", strlen("END")) == 0);
@@ -579,8 +690,8 @@ static enum test_return test_issue_92(void) {
read_ascii_response(buffer, sizeof(buffer));
assert(strncmp(buffer, "CLIENT_ERROR", strlen("CLIENT_ERROR")) == 0);
- close(sock);
- sock = connect_server("127.0.0.1", port, false);
+ close_conn();
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
return TEST_PASS;
}
@@ -589,14 +700,14 @@ static enum test_return test_issue_102(void) {
memset(buffer, ' ', sizeof(buffer));
buffer[sizeof(buffer) - 1] = '\0';
- close(sock);
- sock = connect_server("127.0.0.1", port, false);
+ close_conn();
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
send_ascii_command(buffer);
/* verify that the server closed the connection */
- assert(read(sock, buffer, sizeof(buffer)) == 0);
- close(sock);
- sock = connect_server("127.0.0.1", port, false);
+ assert(con->read(con, buffer, sizeof(buffer)) == 0);
+
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
snprintf(buffer, sizeof(buffer), "gets ");
size_t offset = 5;
@@ -625,22 +736,23 @@ static enum test_return test_issue_102(void) {
buffer[sizeof(buffer) - 1] = '\0';
send_ascii_command(buffer);
/* verify that the server closed the connection */
- assert(read(sock, buffer, sizeof(buffer)) == 0);
+ assert(con->read(con, buffer, sizeof(buffer)) == 0);
- close(sock);
- sock = connect_server("127.0.0.1", port, false);
+ close_conn();
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
return TEST_PASS;
}
static enum test_return start_memcached_server(void) {
server_pid = start_server(&port, false, 600);
- sock = connect_server("127.0.0.1", port, false);
+ close_conn();
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
return TEST_PASS;
}
static enum test_return stop_memcached_server(void) {
- close(sock);
+ close_conn();
if (server_pid != -1) {
assert(kill(server_pid, SIGTERM) == 0);
}
@@ -651,14 +763,14 @@ static enum test_return stop_memcached_server(void) {
static enum test_return shutdown_memcached_server(void) {
char buffer[1024];
- close(sock);
- sock = connect_server("127.0.0.1", port, false);
+ close_conn();
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
send_ascii_command("shutdown\r\n");
/* verify that the server closed the connection */
- assert(read(sock, buffer, sizeof(buffer)) == 0);
+ assert(con->read(con, buffer, sizeof(buffer)) == 0);
- close(sock);
+ close_conn();
/* We set server_pid to -1 so that we don't later call kill() */
if (kill(server_pid, 0) == 0) {
@@ -694,8 +806,7 @@ static void safe_send(const void* buf, size_t len, bool hickup)
num_bytes = (rand() % 1023) + 1;
}
}
-
- ssize_t nw = write(sock, ptr + offset, num_bytes);
+ ssize_t nw = con->write(con, ptr + offset, num_bytes);
if (nw == -1) {
if (errno != EINTR) {
fprintf(stderr, "Failed to write: %s\n", strerror(errno));
@@ -716,7 +827,7 @@ static bool safe_recv(void *buf, size_t len) {
}
off_t offset = 0;
do {
- ssize_t nr = read(sock, ((char*)buf) + offset, len - offset);
+ ssize_t nr = con->read(con, ((char*)buf) + offset, len - offset);
if (nr == -1) {
if (errno != EINTR) {
fprintf(stderr, "Failed to read: %s\n", strerror(errno));
@@ -1064,9 +1175,9 @@ static enum test_return test_binary_quit_impl(uint8_t cmd) {
}
/* Socket should be closed now, read should return 0 */
- assert(read(sock, buffer.bytes, sizeof(buffer.bytes)) == 0);
- close(sock);
- sock = connect_server("127.0.0.1", port, false);
+ assert(con->read(con, buffer.bytes, sizeof(buffer.bytes)) == 0);
+ close_conn();
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
return TEST_PASS;
}
@@ -1867,7 +1978,7 @@ static enum test_return test_binary_pipeline_hickup(void)
static enum test_return test_issue_101(void) {
enum { max = 2 };
enum test_return ret = TEST_PASS;
- int fds[max];
+ struct conn *conns[max];
int ii = 0;
pid_t child = 0;
@@ -1881,15 +1992,16 @@ static enum test_return test_issue_101(void) {
server_pid = start_server(&port, false, 1000);
for (ii = 0; ii < max; ++ii) {
- fds[ii] = connect_server("127.0.0.1", port, true);
- assert(fds[ii] > 0);
+ conns[ii] = NULL;
+ conns[ii] = connect_server("127.0.0.1", port, true, enable_ssl);
+ assert(conns[ii]->sock > 0);
}
/* Send command on the connection until it blocks */
for (ii = 0; ii < max; ++ii) {
bool more = true;
do {
- ssize_t err = write(fds[ii], command, cmdlen);
+ ssize_t err = conns[ii]->write(conns[ii], command, cmdlen);
if (err == -1) {
switch (errno) {
case EINTR:
@@ -1916,16 +2028,27 @@ static enum test_return test_issue_101(void) {
assert(c == child);
assert(stat == 0);
} else {
- sock = connect_server("127.0.0.1", port, false);
+ con = connect_server("127.0.0.1", port, false, enable_ssl);
ret = test_binary_noop();
- close(sock);
exit(0);
}
cleanup:
/* close all connections */
for (ii = 0; ii < max; ++ii) {
- close(fds[ii]);
+ struct conn *c = conns[ii];
+ if (c == NULL) continue;
+#ifdef TLS
+ if (c->ssl) {
+ SSL_shutdown(c->ssl);
+ SSL_free(c->ssl);
+ }
+ if (c->ssl_ctx)
+ SSL_CTX_free(c->ssl_ctx);
+#endif
+ if (c->sock > 0) close(c->sock);
+ free(conns[ii]);
+ conns[ii] = NULL;
}
assert(kill(server_pid, SIGTERM) == 0);
@@ -2000,6 +2123,13 @@ int main(int argc, char **argv)
{
int exitcode = 0;
int ii = 0, num_cases = 0;
+#ifdef TLS
+ if (getenv("SSL_TEST") != NULL) {
+ SSLeay_add_ssl_algorithms();
+ SSL_load_error_strings();
+ enable_ssl = true;
+ }
+#endif
for (num_cases = 0; testcases[num_cases].description; num_cases++) {
/* Just counting */
diff --git a/thread.c b/thread.c
index 618ffac..7a61950 100644
--- a/thread.c
+++ b/thread.c
@@ -17,6 +17,10 @@
#include <atomic.h>
#endif
+#ifdef TLS
+#include <openssl/ssl.h>
+#endif
+
#define ITEMS_PER_ALLOC 64
/* An item in the connection queue. */
@@ -33,6 +37,7 @@ struct conn_queue_item {
enum network_transport transport;
enum conn_queue_item_modes mode;
conn *c;
+ void *ssl;
CQ_ITEM *next;
};
@@ -361,6 +366,15 @@ static void setup_thread(LIBEVENT_THREAD *me) {
exit(EXIT_FAILURE);
}
#endif
+#ifdef TLS
+ if (settings.ssl_enabled) {
+ me->ssl_wbuf = (char *)malloc((size_t)settings.ssl_wbuf_size);
+ if (me->ssl_wbuf == NULL) {
+ fprintf(stderr, "Failed to allocate the SSL write buffer\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+#endif
}
/*
@@ -419,7 +433,7 @@ static void thread_libevent_process(int fd, short which, void *arg) {
case queue_new_conn:
c = conn_new(item->sfd, item->init_state, item->event_flags,
item->read_buffer_size, item->transport,
- me->base);
+ me->base, item->ssl);
if (c == NULL) {
if (IS_UDP(item->transport)) {
fprintf(stderr, "Can't listen for events on UDP socket\n");
@@ -433,6 +447,12 @@ static void thread_libevent_process(int fd, short which, void *arg) {
}
} else {
c->thread = me;
+#ifdef TLS
+ if (settings.ssl_enabled && c->ssl != NULL) {
+ assert(c->thread && c->thread->ssl_wbuf);
+ c->ssl_wbuf = c->thread->ssl_wbuf;
+ }
+#endif
}
break;
@@ -467,7 +487,7 @@ static int last_thread = -1;
* of an incoming connection.
*/
void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
- int read_buffer_size, enum network_transport transport) {
+ int read_buffer_size, enum network_transport transport, void *ssl) {
CQ_ITEM *item = cqi_new();
char buf[1];
if (item == NULL) {
@@ -489,6 +509,7 @@ void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
item->read_buffer_size = read_buffer_size;
item->transport = transport;
item->mode = queue_new_conn;
+ item->ssl = ssl;
cq_push(thread->new_conn_queue, item);
@@ -531,6 +552,13 @@ void sidethread_conn_close(conn *c) {
c->state = conn_closed;
if (settings.verbose > 1)
fprintf(stderr, "<%d connection closed from side thread.\n", c->sfd);
+#ifdef TLS
+ if (c->ssl) {
+ c->ssl_wbuf = NULL;
+ SSL_shutdown(c->ssl);
+ SSL_free(c->ssl);
+ }
+#endif
close(c->sfd);
STATS_LOCK();
diff --git a/tls.c b/tls.c
new file mode 100644
index 0000000..faf85eb
--- /dev/null
+++ b/tls.c
@@ -0,0 +1,195 @@
+#include "memcached.h"
+
+#ifdef TLS
+
+#include "tls.h"
+#include <string.h>
+#include <sysexits.h>
+#include <sys/param.h>
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 4096
+#endif
+
+static pthread_mutex_t ssl_ctx_lock = PTHREAD_MUTEX_INITIALIZER;
+
+const unsigned MAX_ERROR_MSG_SIZE = 128;
+
+void SSL_LOCK() {
+ pthread_mutex_lock(&(ssl_ctx_lock));
+}
+
+void SSL_UNLOCK(void) {
+ pthread_mutex_unlock(&(ssl_ctx_lock));
+}
+
+/*
+ * Reads decrypted data from the underlying BIO read buffers,
+ * which reads from the socket.
+ */
+ssize_t ssl_read(conn *c, void *buf, size_t count) {
+ assert (c != NULL);
+ /* TODO : document the state machine interactions for SSL_read with
+ non-blocking sockets/ SSL re-negotiations
+ */
+ return SSL_read(c->ssl, buf, count);
+}
+
+/*
+ * SSL sendmsg implementation. Perform a SSL_write.
+ */
+ssize_t ssl_sendmsg(conn *c, struct msghdr *msg, int flags) {
+ assert (c != NULL);
+ size_t buf_remain = settings.ssl_wbuf_size;
+ size_t bytes = 0;
+ size_t to_copy;
+ int i;
+
+ // ssl_wbuf is pointing to the buffer allocated in the worker thread.
+ assert(c->ssl_wbuf);
+ // TODO: allocate a fix buffer in crawler/logger if they start using
+ // the sendmsg method. Also, set c->ssl_wbuf when the side thread
+ // start owning the connection and reset the pointer in
+ // conn_worker_readd.
+ // Currntly this connection would not be served by a different thread
+ // than the one it's assigned.
+ assert(c->thread->thread_id == (unsigned long)pthread_self());
+
+ char *bp = c->ssl_wbuf;
+ for (i = 0; i < msg->msg_iovlen; i++) {
+ size_t len = msg->msg_iov[i].iov_len;
+ to_copy = len < buf_remain ? len : buf_remain;
+
+ memcpy(bp + bytes, (void*)msg->msg_iov[i].iov_base, to_copy);
+ buf_remain -= to_copy;
+ bytes += to_copy;
+ if (buf_remain == 0)
+ break;
+ }
+ /* TODO : document the state machine interactions for SSL_write with
+ non-blocking sockets/ SSL re-negotiations
+ */
+ return SSL_write(c->ssl, c->ssl_wbuf, bytes);
+}
+
+/*
+ * Writes data to the underlying BIO write buffers,
+ * which encrypt and write them to the socket.
+ */
+ssize_t ssl_write(conn *c, void *buf, size_t count) {
+ assert (c != NULL);
+ return SSL_write(c->ssl, buf, count);
+}
+
+/*
+ * Loads server certificates to the SSL context and validate them.
+ * @return whether certificates are successfully loaded and verified or not.
+ * @param error_msg contains the error when unsuccessful.
+ */
+static bool load_server_certificates(char **errmsg) {
+ bool success = true;
+ char *error_msg = malloc(MAXPATHLEN + MAX_ERROR_MSG_SIZE);
+ size_t errmax = MAXPATHLEN + MAX_ERROR_MSG_SIZE - 1;
+ if (error_msg == NULL) {
+ *errmsg = NULL;
+ return false;
+ }
+ SSL_LOCK();
+ if (!SSL_CTX_use_certificate_chain_file(settings.ssl_ctx,
+ settings.ssl_chain_cert)) {
+ snprintf(error_msg, errmax, "Error loading the certificate chain: %s\r\n",
+ settings.ssl_chain_cert);
+ success = false;
+ } else if (!SSL_CTX_use_PrivateKey_file(settings.ssl_ctx, settings.ssl_key,
+ settings.ssl_keyformat)) {
+ snprintf(error_msg, errmax, "Error loading the key: %s\r\n", settings.ssl_key);
+ success = false;
+ } else if (!SSL_CTX_check_private_key(settings.ssl_ctx)) {
+ snprintf(error_msg, errmax, "Error validating the certificate\r\n");
+ success = false;
+ } else {
+ settings.ssl_last_cert_refresh_time = current_time;
+ }
+ SSL_UNLOCK();
+ if (success) {
+ free(error_msg);
+ } else {
+ *errmsg = error_msg;
+ }
+ return success;
+}
+
+/*
+ * Verify SSL settings and initiates the SSL context.
+ */
+int ssl_init(void) {
+ assert(settings.ssl_enabled);
+ // SSL context for the process. All connections will share one
+ // process level context.
+ settings.ssl_ctx = SSL_CTX_new(TLS_server_method());
+ // Clients should use at least TLSv1.2
+ int flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_TLSv1 |SSL_OP_NO_TLSv1_1;
+ SSL_CTX_set_options(settings.ssl_ctx, flags);
+
+ // The server certificate, private key and validations.
+ char *error_msg;
+ if (!load_server_certificates(&error_msg)) {
+ if (settings.verbose) {
+ fprintf(stderr, "%s", error_msg);
+ }
+ free(error_msg);
+ exit(EX_USAGE);
+ }
+
+ // The verification mode of client certificate, default is SSL_VERIFY_PEER.
+ SSL_CTX_set_verify(settings.ssl_ctx, settings.ssl_verify_mode, NULL);
+ if (settings.ssl_ciphers && !SSL_CTX_set_cipher_list(settings.ssl_ctx,
+ settings.ssl_ciphers)) {
+ if (settings.verbose) {
+ fprintf(stderr, "Error setting the provided cipher(s): %s\n",
+ settings.ssl_ciphers);
+ }
+ exit(EX_USAGE);
+ }
+ // List of acceptable CAs for client certificates.
+ if (settings.ssl_ca_cert)
+ {
+ SSL_CTX_set_client_CA_list(settings.ssl_ctx,
+ SSL_load_client_CA_file(settings.ssl_ca_cert));
+ if (!SSL_CTX_load_verify_locations(settings.ssl_ctx,
+ settings.ssl_ca_cert, NULL)) {
+ if (settings.verbose) {
+ fprintf(stderr, "Error loading the client CA cert (%s)\n",
+ settings.ssl_ca_cert);
+ }
+ exit(EX_USAGE);
+ }
+ }
+ settings.ssl_last_cert_refresh_time = current_time;
+ return 0;
+}
+
+/*
+ * This method is registered with each SSL connection and abort the SSL session
+ * if a client initiates a renegotiation.
+ * TODO : Proper way to do this is to set SSL_OP_NO_RENEGOTIATION
+ * using the SSL_CTX_set_options but that option only available in
+ * openssl 1.1.0h or above.
+ */
+void ssl_callback(const SSL *s, int where, int ret) {
+ SSL* ssl = (SSL*)s;
+ if (SSL_in_before(ssl)) {
+ if (settings.verbose) {
+ fprintf(stderr, "%d: SSL renegotiation is not supported, "
+ "closing the connection\n", SSL_get_fd(ssl));
+ }
+ SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
+ return;
+ }
+}
+
+bool refresh_certs(char **errmsg) {
+ return load_server_certificates(errmsg);
+}
+#endif
diff --git a/tls.h b/tls.h
new file mode 100644
index 0000000..15dbcd8
--- /dev/null
+++ b/tls.h
@@ -0,0 +1,14 @@
+#ifndef TLS_H
+#define TLS_H
+
+void SSL_LOCK(void);
+void SSL_UNLOCK(void);
+ssize_t ssl_read(conn *c, void *buf, size_t count);
+ssize_t ssl_sendmsg(conn *c, struct msghdr *msg, int flags);
+ssize_t ssl_write(conn *c, void *buf, size_t count);
+
+int ssl_init(void);
+bool refresh_certs(char **errmsg);
+void ssl_callback(const SSL *s, int where, int ret);
+
+#endif