From 64da57e0c7d2c3455b517576c2798ef14d31ebb0 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Tue, 16 Apr 2019 17:27:21 -0400 Subject: [PATCH] more test fixes: - include "main" test file which can be configured through various options - include sample ssl test files - add boilerplate async test --- cpptests/CMakeLists.txt | 13 +++- cpptests/common.cpp | 95 ++++++++++++++++++++++++----- cpptests/common.h | 69 ++++++++++++++++++++-- cpptests/example.crt | 30 ++++++++++ cpptests/example.key | 51 ++++++++++++++++ cpptests/main.cpp | 50 ++++++++++++++++ cpptests/run_test.py | 3 + cpptests/stunnel.conf | 128 ++++++++++++++++++++++++++++++++++++++++ cpptests/t_async.cpp | 69 +++++++++++++++------- cpptests/t_client.cpp | 61 +------------------ 10 files changed, 463 insertions(+), 106 deletions(-) create mode 100644 cpptests/example.crt create mode 100644 cpptests/example.key create mode 100644 cpptests/main.cpp create mode 100644 cpptests/run_test.py create mode 100644 cpptests/stunnel.conf diff --git a/cpptests/CMakeLists.txt b/cpptests/CMakeLists.txt index 14274e4..1c9b2bc 100644 --- a/cpptests/CMakeLists.txt +++ b/cpptests/CMakeLists.txt @@ -4,7 +4,14 @@ INCLUDE_DIRECTORIES(PROJECT_SOURCE_DIR) INCLUDE_DIRECTORIES(${LIBEVENT_INCLUDES}) ADD_EXECUTABLE(hiredis-gtest t_basic.cpp t_client.cpp t_async.cpp - common.cpp) -TARGET_LINK_LIBRARIES(hiredis-gtest gtest_main hiredis event) + common.cpp main.cpp) +TARGET_LINK_LIBRARIES(hiredis-gtest gtest hiredis event) ADD_TEST(NAME hiredis-test COMMAND hiredis-test) -SET_PROPERTY(TARGET hiredis-gtest PROPERTY CXX_STANDARD 11) \ No newline at end of file +SET_PROPERTY(TARGET hiredis-gtest PROPERTY CXX_STANDARD 11) + +SET(EXAMPLE_CERT "${CMAKE_CURRENT_SOURCE_DIR}/example.crt") +SET(EXAMPLE_KEY "${CMAKE_CURRENT_SOURCE_DIR}/example.key") + +ADD_DEFINITIONS(-DHIREDIS_TEST_SSL_CA="${EXAMPLE_CERT}") +ADD_DEFINITIONS(-DHIREDIS_TEST_SSL_KEY="${EXAMPLE_KEY}") +ADD_DEFINITIONS(-DHIREDIS_TEST_SSL_CERT="${EXAMPLE_CERT}") \ No newline at end of file diff --git a/cpptests/common.cpp b/cpptests/common.cpp index e2e6f15..f3745f8 100644 --- a/cpptests/common.cpp +++ b/cpptests/common.cpp @@ -1,11 +1,33 @@ #include "common.h" +#include hiredis::ClientSettings hiredis::settings_g; using namespace hiredis; +void ClientSettings::setHost(const char *s) { + m_mode = REDIS_CONN_TCP; + std::string hostval(s); + size_t idx = hostval.find(':'); + if (idx == std::string::npos) { + // First part is hostname only + m_hostname = hostval; + } else { + m_port = atoi(hostval.c_str() + idx + 1); + hostval.resize(idx); + } + if (!m_port) { + m_port = 6379; + } + m_hostname = hostval; +} -ClientSettings::ClientSettings() { +void ClientSettings::setUnix(const char *s) { + m_mode = REDIS_CONN_UNIX; + m_hostname = s; +} + +void ClientSettings::applyEnv() { std::string hostval; if (getenv("REDIS_SOCKET")) { m_hostname.assign(getenv("REDIS_SOCKET")); @@ -14,24 +36,20 @@ ClientSettings::ClientSettings() { } if (getenv("REDIS_HOST")) { - hostval.assign(getenv("REDIS_HOST")); - } - size_t idx = hostval.find(':'); - if (idx == std::string::npos) { - // First part is hostname only - m_hostname = hostval; - } else { - m_port = atoi(hostval.c_str() + idx); - hostval.resize(idx); - } - if (!m_port) { - m_port = 6379; + setHost(getenv("REDIS_HOST")); } // Handle SSL settings as well - m_ssl_cert_path = getenv("REDIS_SSL_CLIENT_CERT"); - m_ssl_key_path = getenv("REDIS_SSL_CLIENT_KEY"); - m_ssl_ca_path = getenv("REDIS_SSL_CA"); + if (getenv("REDIS_SSL_CLIENT_CERT")) { + m_ssl_cert_path = getenv("REDIS_SSL_CLIENT_CERT"); + } + if (getenv("REDIS_SSL_CLIENT_KEY")) { + m_ssl_key_path = getenv("REDIS_SSL_CLIENT_KEY"); + } + if (getenv("REDIS_SSL_CA")) { + m_ssl_ca_path = getenv("REDIS_SSL_CA"); + } + } void ClientSettings::initOptions(redisOptions& options) const { @@ -41,3 +59,48 @@ void ClientSettings::initOptions(redisOptions& options) const { REDIS_OPTIONS_SET_UNIX(&options, hostname()); } } + + +ConnectError::ConnectError(const redisOptions& options) { + if (options.type == REDIS_CONN_TCP) { + endpoint = options.endpoint.tcp.ip; + endpoint += ":"; + endpoint += options.endpoint.tcp.port; + } else if (options.type == REDIS_CONN_UNIX) { + endpoint = "unix://"; + endpoint += options.endpoint.unix_socket; + } +} + +void ClientError::throwCode(int code) { + switch (code) { + case REDIS_ERR_IO: + throw IOError(); + case REDIS_ERR_EOF: + throw IOError("EOF"); + case REDIS_ERR_PROTOCOL: + throw IOError("Protocol Error"); + case REDIS_ERR_TIMEOUT: + throw TimeoutError(); + default: { + std::stringstream ss; + ss << "unknown error code: "; + ss << code; + throw ClientError(ss.str().c_str()); + } + } +} + +void ClientError::throwContext(const redisContext *c) { + const char *s; + switch (c->err) { + case REDIS_ERR_IO: + case REDIS_ERR_EOF: + case REDIS_ERR_PROTOCOL: + throw IOError(c->errstr); + case REDIS_ERR_TIMEOUT: + throw TimeoutError(); + default: + throw ClientError(c->errstr); + } +} diff --git a/cpptests/common.h b/cpptests/common.h index 8d66f1d..4f47d38 100644 --- a/cpptests/common.h +++ b/cpptests/common.h @@ -3,23 +3,29 @@ #include #include +#include #include "hiredis.h" namespace hiredis { class ClientSettings { public: - ClientSettings(); + void applyEnv(); + void setHost(const char *s); + void setUnix(const char *s); + void setSsl(bool v) { m_ssl_enabled = v; } const char *ssl_cert() const { return m_ssl_cert_path; } const char *ssl_key() const { return m_ssl_key_path; } const char *ssl_ca() const { return m_ssl_ca_path; } bool is_ssl() const { - return m_ssl_ca_path != NULL; + return m_ssl_enabled; } + bool is_unix() const { - return false; + return m_mode == REDIS_CONN_UNIX; } + const char *hostname() const { return m_hostname.c_str(); } @@ -32,17 +38,68 @@ public: void initOptions(redisOptions& options) const; -private: - std::string m_hostname; - uint16_t m_port; + std::string m_hostname = "localhost"; + uint16_t m_port = 6379; int m_mode = REDIS_CONN_TCP; const char *m_ssl_cert_path = NULL; const char *m_ssl_ca_path = NULL; const char *m_ssl_key_path = NULL; + bool m_ssl_enabled = false; }; extern ClientSettings settings_g; +class ClientError : public std::runtime_error { +public: + ClientError() : std::runtime_error("hiredis error") { + } + ClientError(const char *s) : std::runtime_error(s) { + } + static void throwCode(int code); + static void throwContext(const redisContext *ac); +}; + +class ConnectError : public ClientError { +public: + ConnectError() : ClientError(){} + ConnectError(const redisOptions& options); + virtual const char *what() const noexcept override{ + return endpoint.c_str(); + } +private: + std::string endpoint; +}; + +class IOError : public ClientError { +public: + IOError() : ClientError(){} + IOError(const char *s) : ClientError(s) {} +}; + +class TimeoutError : public ClientError { +public: + TimeoutError() : ClientError("timed out") {} + TimeoutError(const char *s) : ClientError(s) {} +}; + +class SSLError : public ClientError { +public: + SSLError() : ClientError() {} + SSLError(const char *what) : ClientError(what) {} +}; + +class CommandError : public ClientError { +public: + CommandError(const redisReply *r) { + errstr = r->str; + } + virtual const char *what() const noexcept override { + return errstr.c_str(); + } +private: + std::string errstr; +}; + } #endif diff --git a/cpptests/example.crt b/cpptests/example.crt new file mode 100644 index 0000000..a14273b --- /dev/null +++ b/cpptests/example.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFJjCCAw6gAwIBAgIJALM3AMcxMe0VMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV +BAMTC2V4YW1wbGUuY29tMB4XDTE4MDUwNzExNTM1MloXDTI4MDUwNDExNTM1Mlow +FjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQCyNzRDE8b+COhBYZNiFnMeuF6mXRnd/jaK0axpKhRNx8JG2xGkmzq9 +Fse74dBbH/mmFwgpnOmRL2QK+qStUws6PQ8fk9fw3ntTQvMccCJqO/Ns1Rd3oFCt +kL5q3VMW3lYn/ZTZ9jaFfRRdOkfACkDUQ3ff2/8QVU+BFNL47GyVPiAQeN5efjMj +XSpE3aX3pn468wElO3JMxrUtE/pOg/TvlX4oDOJ9n56RhdpFU5rnvcdzxIMUgBih +9XRymPj/0aD9UCq5++6CQ0U0MP4tcCe6iMQsHOfReUPH81pyMVpox/MBUAHPXKqd +EePWz+OeEJfT8CeDt41DF6kEBledM41IYirlB/mCoV8TDnQYVmLIGyTiDQUsoGx/ +iZuYm+6bszdtgVK0ofBYDNgrY4VnC85YXg9sr+2fpZrNqzPY3W1esHdmdfQHP/Ff +Q1pDVz8jWhWKwEE8QSpw9tRzL2U6hDdAxdmxt6h35dZvJF30ZfLylX16KDNLcN2E +qRm/UTjjs06rzPGEK6rRuOgJ2ixlHzK2mZFaAD22THn1+H4QXOkylqAbGXemryrF +ESLtCSskOuacfz1bUoWXa0a9pI67vilvagO8/Yc73rqtYLrTnHEwmFDd2X33UryB +NADSV/a4VCIoOvThAbhZIaRAPCi7nn+hzu62myoVg6KDYLGTk7v4gQIDAQABo3cw +dTAdBgNVHQ4EFgQUOYtIaf/4JJK9IBtulHRfJSEa2G8wRgYDVR0jBD8wPYAUOYtI +af/4JJK9IBtulHRfJSEa2G+hGqQYMBYxFDASBgNVBAMTC2V4YW1wbGUuY29tggkA +szcAxzEx7RUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAgXe3OTSB +Xma+IU9MtZChAf4p6n+5magLRVeraImKeFFrk2euPcQ0FxzpwA/GlhzDGLLUIKcB +NnPOhZ3TVZTbzPhVSeBkFgpM/D+RnYnd2TLuVbubRynqKPPIa01hnI5UPodeQnuR +1p+kLXEcQvUj7eSipYOEIO78cy/1QKZq8Jb6qosgM1w140qthFEqrgg3ewgNHbB+ +SLOxUGIbVfi5pdWiekGwcR3nyaJdyq3Dvq65Rt6wfzfX21FZI7VGs5NPzA/TdRIX +uwTNe0Rif+4mbAah5SZlY9J/rKY+s41EdGPY24+ZywlyLgGgeRF2LaNu5iqG2FiQ +h/EvpFzZQttSFTNhfMwZZg0pbQ2kpG53P7Y61rtuTHI0ykTb+Ot/972DefP0jLUl ++SCuUHSb6OTlSddZt4zvxVgSOftrSLMl6PQpQZLLtxWvmFpWHTTpHEoRAXLv41bo +JEVNZNO+L0pVWCOWyqn/4A4umtXWRzU8qXxed7mVNbpLFj9EYFChdcJ7OO9gEW05 +RJZrzEqd0Moh0na9ol9qQmc52Z/27ex+hh6ZrF7vVjUzWUMwIgFKx7/ELL9BNBN3 +ir6hkQpH6lNA71MgkW6aUuo2exZwffpjo2/cydwcdieNyP1kHwakN6ClwCYcFZpd +LPKxSkVpeDRSDNFyYYUOTY4BJVbwnxy5Kto= +-----END CERTIFICATE----- diff --git a/cpptests/example.key b/cpptests/example.key new file mode 100644 index 0000000..b3985d2 --- /dev/null +++ b/cpptests/example.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJJwIBAAKCAgEAsjc0QxPG/gjoQWGTYhZzHrhepl0Z3f42itGsaSoUTcfCRtsR +pJs6vRbHu+HQWx/5phcIKZzpkS9kCvqkrVMLOj0PH5PX8N57U0LzHHAiajvzbNUX +d6BQrZC+at1TFt5WJ/2U2fY2hX0UXTpHwApA1EN339v/EFVPgRTS+OxslT4gEHje +Xn4zI10qRN2l96Z+OvMBJTtyTMa1LRP6ToP075V+KAzifZ+ekYXaRVOa573Hc8SD +FIAYofV0cpj4/9Gg/VAqufvugkNFNDD+LXAnuojELBzn0XlDx/NacjFaaMfzAVAB +z1yqnRHj1s/jnhCX0/Ang7eNQxepBAZXnTONSGIq5Qf5gqFfEw50GFZiyBsk4g0F +LKBsf4mbmJvum7M3bYFStKHwWAzYK2OFZwvOWF4PbK/tn6Wazasz2N1tXrB3ZnX0 +Bz/xX0NaQ1c/I1oVisBBPEEqcPbUcy9lOoQ3QMXZsbeod+XWbyRd9GXy8pV9eigz +S3DdhKkZv1E447NOq8zxhCuq0bjoCdosZR8ytpmRWgA9tkx59fh+EFzpMpagGxl3 +pq8qxREi7QkrJDrmnH89W1KFl2tGvaSOu74pb2oDvP2HO966rWC605xxMJhQ3dl9 +91K8gTQA0lf2uFQiKDr04QG4WSGkQDwou55/oc7utpsqFYOig2Cxk5O7+IECAwEA +AQKCAgAFca5FBkuj4v3FUYfBDVKC87rgdiOeJm/gGbucks5/+cQziemmD5/hutpr +IODOh9GGg1mae9KevsXdl/6D1O+Y3diibE/Cael2h6sJiVtjx2UORAwteVY9lxha +B1zMbApRumtbpSvRNBr1Jhye/zEvysfUrNHD2/dLyCkRtZczj+xG1IpmdJB3Whc6 +d1Lkl89vWZEFZCV/tuo98EhLMbi/wN9TteENWVzssRwT1hP7VE7NeIlQjQEzoV3n +SkkA95RlaJeFiu6kSA3LJFv/Y1ezWQ41EsT6Hyw05Xgz2NIcNU91EM6dWQVVOwCs +xTj80SDyNnneijLkg8qD9vWiNbrxGA69l+3ZzG252KariKWL+tQhK5ugDGJlZ7xR +BanyIq3GEVMreWehViHX8XwDcNQtn9qtYqmMz7+E7X0/urwkBfiLfoid6U0GYlMS +yQ4PQPv7kV97FKVsPin9vucg9Sl7xcZEOmJO25TDq2/oPSzLU+tGaYreYKjEdAJ7 +XRe7aOv1C/+16mo+aMuwVRbwGcsVeGqXuAEDm7TFY4eRSbuPvAa3WnMHG/vqKSig +5MpTwfrQmh7S1DMJ6DiNBtf0OebTMeIedcagGymmhANlV5Kg42+817ct1AWPlae+ +vs3bDTtUpS/iX8PYhidkAPqfJrESYgk6W12C5pz9d3zlXJFDSQKCAQEA3vzACnIE +vziTr6pZMQNNHaIQJaN7o4snqUXqxz+K6ZAhGxxIxhU956GRDrhU/kgOLAoc+mLV +PmGfLO72/g4Pxs7NM07zT3JBOmc9vvQfr9jkGUu00GHToba2IlBIBOcQXOl/nFtN +NtfV5N7YgBJRceP/TbsKuooq0ad2vwqFTVbEKprwXhoHu3OlISY6Z2wV+eP7fqbs +TfKvqhAPADkARmwBXYljuC5ORgDGffes+QXtwANB9vHHyN2oh7I2aY1E/ylyYhvJ +ISrLLVIw7Jdzcv4WZvNQILgP6xwGF+ljPCSWxV8mTsw9xS1T2BU2Ij8gfy5k96TO +nhxGUIWTZQfULwKCAQEAzJmaCRvbtLBO/IQpHIocD6elsZLWlLxJiaxHiR2SoDwL +z609fuElLDkmkKaXMBlI6ZtGVMBO9+6scjn8N11rzCzjCFIo68X/r0CgTcNuLUaN +7WkRRT4krIz/r5+UFys+DyrCxlNmzDKcSowxf3KYWkBwQ2q1ZFdM9HtQjbGAVCWv +4zNLqsOTo60GQOrn4AgxLuLNbES+pj+Gtymd94+Hh2L7Jsh3xkOK3CrW02uo1Rz2 +v0znGd44Ep+m9uLHE0LFTs7+qh00nyjcGx/UsF2k138+LsRA//g3ljbXLU5PVAqg +e2Y3jAVjeyouEecBWncY04Jg6FUpuc5ZIkiJEwDiTwKCAQAHf8CFmWgHdkOhOL1f +JJlHUdfxLBpQWbGvw6YtpTlquWojm0PnRXebfpd+Qzy4gHvZh02KLiC1xFqyDCdK +S/bD3NiRzSnplhITgL1W2qbmJwkkwKMIDwIrAhYF+WUypQKr3T5pZ7ilC4Up+USW +qgcLKXvAhXXK6DKgcl1P926cNzrJpARJZd60syLuhnaYW84xZTVkAQEZbfvyYC/g +9gnIVIGHP8OWwhfnysbiHZ43kbd5KaLiRydM3gd46Mljq1iSrDYojn6pGuNSVt1G +V6GOUHU4aR5cu1PtuDeMPlEUCLb5VEXZiIzbQLb9IVl8tVrGbC0BFw3Ly2+h7ZwT +XbwJAoIBACokfVjg9xk5s5tJsZoiTHNhCb0QzMgoHFGSPc3dXIVKuPgW6/LFdz2r +q+jhl4SdwKn1hMASOHHTJIwGq4/P21Nb74uYOLuPtgGoIxzBY2FKBhPfr2H+0dkE +1embygoXqxm+qg3lwefPiOfGBrAEr6LvYPBR+3jmjoBRIh99bzxl4tu+hhhvXmq5 +Se93MzmvFkpdBwkFA+wEa8Awf0wtsTHOzoKHijw5T1HYNRWpOEZlR+HRekyWvCAB +6Icz4ONzvmZkNopdp6gc53Fi1hFZyIlmuS0y2VygCPsU4q9/UNGzuqiQPmLF/V6y +KnkfhuTWYTO3yDQyznxqJ2vrWuiiJvsCggEAHYPDIWvhZPC0ZO13r3HO/lc7YTU1 +XGSCzAh/oyVLgUCqYw5t2F6maduwSia3z+g3BecutYk+tkmcBsrtSVE5GZ93VDGQ +1rj3Yulr8tnXC8HaaNsU227Js+J9ZP8CLQPMF0HbMESPp0cxIPVTsOb8K/Oa6e5h +rVxqpuLbw/3SMtqWN9fGbXAkPoaLhsueWK9nB03+fVg7P7ftJ+C3g16eXGEnPrg5 +DmdzpbnBs2WfZNSLkrIY8bZ7Ou+k3astMM2tFSEpFblMP44WmqR5GFtvMzDl1w4S +2KdS1mqDExX2lu9xatAApHykVjMJ+haBHkew7et90JsckKeDmQJ6p+Vrpg== +-----END RSA PRIVATE KEY----- diff --git a/cpptests/main.cpp b/cpptests/main.cpp new file mode 100644 index 0000000..e24b1c3 --- /dev/null +++ b/cpptests/main.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include "common.h" + +using namespace hiredis; + +static std::string getKvValue(const std::string& s) { + size_t n = s.find('='); + if (n == std::string::npos) { + return ""; + } + return s.substr(n+1); +} + +static bool isArg(const char *arg, const char *s) { + return strncasecmp(arg, s, strlen(s)) == 0; +} + +int main(int argc, char **argv) { + ClientSettings* settings = &settings_g; + +#ifdef HIREDIS_TEST_SSL_CA + printf("Setting SSL compile time defaults\n"); + settings->m_ssl_ca_path = HIREDIS_TEST_SSL_CA; + settings->m_ssl_cert_path = HIREDIS_TEST_SSL_CERT; + settings->m_ssl_key_path = HIREDIS_TEST_SSL_KEY; +#endif + + for (size_t ii = 1; ii < argc; ++ii) { + const char *ss = argv[ii]; + if (isArg(ss, "--unix")) { + settings->setUnix(getKvValue(ss).c_str()); + } else if (isArg(ss, "--host")) { + settings->setHost(getKvValue(ss).c_str()); + printf("Set host to %s:%u\n", settings->m_hostname.c_str(), settings->m_port); + } else if (isArg(ss, "--ssl")) { + auto v = getKvValue(ss); + if (v == "0" || v == "false") { + settings->setSsl(false); + } else { + printf("enabling ssl for tests\n"); + settings->setSsl(true); + } + } + } + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/cpptests/run_test.py b/cpptests/run_test.py new file mode 100644 index 0000000..df24435 --- /dev/null +++ b/cpptests/run_test.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +import argparse +import subprocess \ No newline at end of file diff --git a/cpptests/stunnel.conf b/cpptests/stunnel.conf new file mode 100644 index 0000000..2091f3e --- /dev/null +++ b/cpptests/stunnel.conf @@ -0,0 +1,128 @@ +; Sample stunnel configuration file for Unix by Michal Trojnara 1998-2018 +; Some options used here may be inadequate for your particular configuration +; This sample file does *not* represent stunnel.conf defaults +; Please consult the manual for detailed description of available options + +; ************************************************************************** +; * Global options * +; ************************************************************************** + +; It is recommended to drop root privileges if stunnel is started by root +;setuid = nobody +;setgid = nogroup + +; PID file is created inside the chroot jail (if enabled) +;pid = /usr/local/var/run/stunnel.pid + +; Debugging stuff (may be useful for troubleshooting) +foreground = yes +debug = debug +;output = /usr/local/var/log/stunnel.log + +; Enable FIPS 140-2 mode if needed for compliance +;fips = yes + +; The pkcs11 engine allows for authentication with cryptographic +; keys isolated in a hardware or software token +; MODULE_PATH specifies the path to the pkcs11 module shared library, +; e.g. softhsm2.dll or opensc-pkcs11.so +; Each section using this feature also needs the "engineId = pkcs11" option +;engine = pkcs11 +;engineCtrl = MODULE_PATH:/usr/lib/softhsm/libsofthsm2.so +;engineCtrl = PIN:1234 + +; ************************************************************************** +; * Service defaults may also be specified in individual service sections * +; ************************************************************************** + +; Enable support for the insecure SSLv3 protocol +delay = yes +options = NO_SSLv3 +options = NO_TLSv1 +options = CIPHER_SERVER_PREFERENCE +options = DONT_INSERT_EMPTY_FRAGMENTS + + +; These options provide additional security at some performance degradation +;options = SINGLE_ECDH_USE +;options = SINGLE_DH_USE + +; ************************************************************************** +; * Include all configuration file fragments from the specified folder * +; ************************************************************************** + +;include = /usr/local/etc/stunnel/conf.d + +; ************************************************************************** +; * Service definitions (remove all services for inetd mode) * +; ************************************************************************** + +; ***************************************** Example TLS client mode services + +; The following examples use /etc/ssl/certs, which is the common location +; of a hashed directory containing trusted CA certificates. This is not +; a hardcoded path of the stunnel package, as it is not related to the +; stunnel configuration in /usr/local/etc/stunnel/. + +[redis] +;client = yes +accept = 16379 +connect = 6379 +cert = example.crt +key = example.key + +; Encrypted HTTP proxy authenticated with a client certificate +; located in a cryptographic token +;[example-pkcs11] +;client = yes +;accept = 127.0.0.1:8080 +;connect = example.com:8443 +;engineId = pkcs11 +;cert = pkcs11:token=MyToken;object=MyCert +;key = pkcs11:token=MyToken;object=MyKey + +; ***************************************** Example TLS server mode services + +;[pop3s] +;accept = 995 +;connect = 110 +;cert = /usr/local/etc/stunnel/stunnel.pem + +;[imaps] +;accept = 993 +;connect = 143 +;cert = /usr/local/etc/stunnel/stunnel.pem + +; Either only expose this service to trusted networks, or require +; authentication when relaying emails originated from loopback. +; Otherwise the following configuration creates an open relay. +;[ssmtp] +;accept = 465 +;connect = 25 +;cert = /usr/local/etc/stunnel/stunnel.pem + +; TLS front-end to a web server +;[https] +;accept = 443 +;connect = 80 +;cert = /usr/local/etc/stunnel/stunnel.pem +; "TIMEOUTclose = 0" is a workaround for a design flaw in Microsoft SChannel +; Microsoft implementations do not use TLS close-notify alert and thus they +; are vulnerable to truncation attacks +;TIMEOUTclose = 0 + +; Remote shell protected with PSK-authenticated TLS +; Create "/usr/local/etc/stunnel/secrets.txt" containing IDENTITY:KEY pairs +;[shell] +;accept = 1337 +;exec = /bin/sh +;execArgs = sh -i +;PSKsecrets = /usr/local/etc/stunnel/secrets.txt + +; Non-standard MySQL-over-TLS encapsulation connecting the Unix socket +;[mysql] +;cert = /usr/local/etc/stunnel/stunnel.pem +;accept = 3307 +;connect = /run/mysqld/mysqld.sock + +; vim:ft=dosini diff --git a/cpptests/t_async.cpp b/cpptests/t_async.cpp index 13968a1..a82d35e 100644 --- a/cpptests/t_async.cpp +++ b/cpptests/t_async.cpp @@ -22,24 +22,43 @@ struct CmdData { }; struct AsyncClient { - AsyncClient(const redisOptions& options, event_base* b) { + void connectCommon(const redisOptions& orig, event_base *b, unsigned timeoutMs) { + redisOptions options = orig; + struct timeval tv = { 0 }; + if (timeoutMs) { + tv.tv_usec = timeoutMs * 1000; + options.timeout = &tv; + } + ac = redisAsyncConnectWithOptions(&options); + ac->data = this; redisLibeventAttach(ac, b); redisAsyncSetConnectCallback(ac, realConnectCb); - ac->data = this; + } - AsyncClient(const ClientSettings& settings, event_base *b) { + AsyncClient(const redisOptions& options, event_base* b, unsigned timeoutMs = 0) { + connectCommon(options, b, timeoutMs); + } + + AsyncClient(const ClientSettings& settings, event_base *b, unsigned timeoutMs = 0) { redisOptions options = { 0 }; - ac = redisAsyncConnectWithOptions(&options); - redisLibeventAttach(ac, b); - redisAsyncSetConnectCallback(ac, realConnectCb); + settings.initOptions(options); + connectCommon(options, b, timeoutMs); + + if (ac->c.err != REDIS_OK) { + ClientError::throwContext(&ac->c); + } + if (settings.is_ssl()) { - redisSecureConnection(&ac->c, + printf("Securing async connection...\n"); + int rc = redisSecureConnection(&ac->c, settings.ssl_ca(), settings.ssl_cert(), settings.ssl_key(), NULL); + if (rc != REDIS_OK) { + throw SSLError(ac->c.errstr); + } } - ac->data = this; } AsyncClient(redisAsyncContext *ac) : ac(ac) { @@ -68,11 +87,15 @@ struct AsyncClient { void disconnect(DisconnectCallback cb) { disconncb = cb; - redisAsyncDisconnect(ac); + auto tmpac = ac; + ac = NULL; + redisAsyncDisconnect(tmpac); } void disconnect() { - redisAsyncDisconnect(ac); + auto tmpac = ac; + ac = NULL; + redisAsyncDisconnect(tmpac); } ConnectionCallback conncb; @@ -80,7 +103,6 @@ struct AsyncClient { redisAsyncContext *ac; }; - static void realConnectCb(const redisAsyncContext *ac, int status) { auto self = reinterpret_cast(ac->data); if (self->conncb) { @@ -119,21 +141,24 @@ protected: }; TEST_F(AsyncTest, testAsync) { - redisOptions options = {0}; - struct timeval tv = {0}; - tv.tv_sec = 1; - options.timeout = &tv; - settings_g.initOptions(options); - AsyncClient client(options, libevent); + AsyncClient client(settings_g, libevent, 1000); - client.onConnect([](AsyncClient*, bool status) { - printf("Status: %d\n", status); + bool gotConnect = false; + bool gotCommand = false; + client.onConnect([&](AsyncClient*, bool status) { + ASSERT_TRUE(status); + gotConnect = true; }); - client.cmd([](AsyncClient *c, redisReply*){ - printf("Got reply!\n"); + client.cmd([&](AsyncClient *c, redisReply *r) { + ASSERT_TRUE(r != NULL); + ASSERT_EQ(REDIS_REPLY_STATUS, r->type); + ASSERT_STREQ("PONG", r->str); c->disconnect(); + gotCommand = true; }, "PING"); - wait(); + + ASSERT_TRUE(gotConnect); + ASSERT_TRUE(gotCommand); } diff --git a/cpptests/t_client.cpp b/cpptests/t_client.cpp index df60472..554f8ab 100644 --- a/cpptests/t_client.cpp +++ b/cpptests/t_client.cpp @@ -7,63 +7,6 @@ using namespace hiredis; -class ClientError : public std::runtime_error { -public: - ClientError() : std::runtime_error("hiredis error") { - } - ClientError(const char *s) : std::runtime_error(s) { - } -}; - -class ConnectError : public ClientError { -public: - ConnectError() : ClientError(){} - ConnectError(const redisOptions& options) { - if (options.type == REDIS_CONN_TCP) { - endpoint = options.endpoint.tcp.ip; - endpoint += ":"; - endpoint += options.endpoint.tcp.port; - } else if (options.type == REDIS_CONN_UNIX) { - endpoint = "unix://"; - endpoint += options.endpoint.unix_socket; - } - } - virtual const char *what() const noexcept override{ - return endpoint.c_str(); - } -private: - std::string endpoint; -}; - -class IOError : public ClientError {}; -class TimeoutError : public ClientError {}; -class SSLError : public ClientError {}; - -class CommandError : public ClientError { -public: - CommandError(const redisReply *r) { - errstr = r->str; - } - virtual const char *what() const noexcept override { - return errstr.c_str(); - } -private: - std::string errstr; -}; - -void errorFromCode(int code) { - switch (code) { - case REDIS_ERR_IO: - case REDIS_ERR_EOF: - case REDIS_ERR_PROTOCOL: - throw IOError(); - case REDIS_ERR_TIMEOUT: - throw TimeoutError(); - default: - throw ClientError(); - } -} - class Client { public: operator redisContext*() { @@ -107,7 +50,7 @@ public: void flushdb() { redisReply *p = cmd("FLUSHDB"); if (p == NULL) { - errorFromCode(ctx->err); + ClientError::throwCode(ctx->err); } if (p->type == REDIS_REPLY_ERROR) { auto pp = CommandError(p); @@ -136,7 +79,7 @@ private: int err = ctx->err; redisFree(ctx); ctx = NULL; - errorFromCode(err); + ClientError::throwCode(err); } void connectOrThrow(const redisOptions& options) { ctx = redisConnectWithOptions(&options);