From d5f4ced49e74132402ed9f5d0bf013b225b5f084 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 Feb 2022 11:33:55 +0100 Subject: [PATCH] prepare CA path+file options, use ssl lib defaults, fix reconnect with https --- src/ebusd/mainloop.cpp | 9 ++++---- src/ebusd/mainloop.h | 4 ++++ src/lib/utils/httpclient.cpp | 45 +++++++++++++++++++++++++++++++----- src/lib/utils/httpclient.h | 29 +++++++++++++++++++---- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 59da07e8..3aa9474a 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -26,7 +26,6 @@ #include #include "ebusd/main.h" #include "lib/utils/log.h" -#include "lib/utils/httpclient.h" #include "lib/ebus/data.h" namespace ebusd { @@ -108,7 +107,8 @@ result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map< MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages) : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_address(opt.address), m_scanConfig(opt.scanConfig), m_initialScan(opt.readOnly ? ESC : opt.initialScan), - m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck) { + m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck), + m_httpClient(nullptr, nullptr) { m_device->setListener(this); // open Device result_t result = m_device->open(); @@ -331,8 +331,7 @@ void MainLoop::run() { } } if (m_runUpdateCheck && !m_shutdown && now > nextCheckRun) { - HttpClient client; - if (!client.connect("upd.ebusd.eu", + if (!m_httpClient.connect("upd.ebusd.eu", #ifdef HAVE_SSL 443, true, #else @@ -363,7 +362,7 @@ void MainLoop::run() { m_busHandler->formatUpdateInfo(&ostr); ostr << "}"; string response; - if (!client.post("/", ostr.str(), &response)) { + if (!m_httpClient.post("/", ostr.str(), &response)) { logError(lf_main, "update check error: %s", response.c_str()); } else { m_updateCheck = response.empty() ? "unknown" : response; diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index d6d32392..1092182c 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -30,6 +30,7 @@ #include "lib/ebus/filereader.h" #include "lib/ebus/message.h" #include "lib/utils/rotatefile.h" +#include "lib/utils/httpclient.h" namespace ebusd { @@ -430,6 +431,9 @@ class MainLoop : public Thread, DeviceListener { /** perform automatic update check. */ bool m_runUpdateCheck; + /** the @a HttpClient for performing the update check. */ + HttpClient m_httpClient; + /** the created @a BusHandler instance. */ BusHandler* m_busHandler; diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index b78815c7..1b3ad236 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -21,6 +21,11 @@ #include #include #include +#ifdef HAVE_SSL +#if OPENSSL_VERSION_NUMBER < 0x10101000L +#include +#endif +#endif // HAVE_SSL #include "lib/utils/log.h" namespace ebusd { @@ -32,6 +37,15 @@ using std::hex; #ifdef HAVE_SSL +#if OPENSSL_VERSION_NUMBER < 0x10101000L +// default CA path +#define DEFAULT_CAFILE "/etc/ssl/certs/ca-certificates.crt" + +// default CA path +#define DEFAULT_CAPATH "/etc/ssl/certs" +#endif + +// the time slice to sleep between repeated SSL reads/writes #define SLEEP_NANOS 50000 bool checkError(const char* call) { @@ -131,7 +145,8 @@ bool SSLSocket::isValid() { // general switch for future insecure option static const bool verifyPeer = true; -SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool https, int timeout) { +SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool https, int timeout, const char* caFile, + const char* caPath) { BIO *bio = nullptr; SSL_CTX *ctx = nullptr; ostringstream ostr; @@ -159,8 +174,21 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt break; } SSL_CTX_set_verify(ctx, verifyPeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, nullptr); - if (verifyPeer && isError("verify_loc", SSL_CTX_load_verify_locations(ctx, nullptr, "/etc/ssl/certs"), 1)) { - break; + if (verifyPeer) { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + SSL_CTX_set_default_verify_paths(ctx); +#else + struct stat stat_buf = {}; + if (!caFile && stat(DEFAULT_CAFILE, &stat_buf) == 0) { + caFile = DEFAULT_CAFILE; // use default CA file + } + if (!caPath && stat(DEFAULT_CAPATH, &stat_buf) == 0) { + caPath = DEFAULT_CAPATH; // use default CA path + } +#endif + if ((caFile || caPath) && isError("verify_loc", SSL_CTX_load_verify_locations(ctx, caFile, caPath), 1)) { + break; + } } const long flags = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; SSL_CTX_set_options(ctx, flags); @@ -291,7 +319,8 @@ bool HttpClient::connect(const string& host, const uint16_t port, bool https, co const int timeout) { disconnect(); #ifdef HAVE_SSL - m_socket = SSLSocket::connect(host, port, https, timeout); + m_socket = SSLSocket::connect(host, port, https, timeout, m_caFile, m_caPath); + m_https = https; #else if (https) { return false; @@ -313,7 +342,11 @@ bool HttpClient::reconnect() { if (m_host.empty() || !m_port) { return false; } - m_socket = SocketClass::connect(m_host, m_port, m_timeout); +#ifdef HAVE_SSL + m_socket = SSLSocket::connect(m_host, m_port, m_https, m_timeout, m_caFile, m_caPath); +#else + m_socket = TCPSocket::connect(m_host, m_port, m_timeout); +#endif if (!m_socket) { return false; } @@ -458,7 +491,7 @@ bool HttpClient::request(const string& method, const string& uri, const string& return pos == length; } -size_t HttpClient::readUntil(const string& delim, const size_t length, string* result) { +size_t HttpClient::readUntil(const string& delim, size_t length, string* result) { if (!m_buffer) { m_buffer = reinterpret_cast(malloc(1024+1)); // 1 extra for final terminator if (!m_buffer) { diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 258d2cb9..f844f795 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -67,9 +67,12 @@ class SSLSocket { * @param port the port number. * @param https true for HTTPS, false for HTTP. * @param timeout the connect, send, and receive timeout in seconds, or 0 for blocking mode. + * @param caFile the CA file to use (uses defaults if neither caFile nor caPath are set). + * @param caPath the path with CA files to use (uses defaults if neither caFile nor caPath are set). * @return the connected SSLSocket, or nullptr on error. */ - static SSLSocket* connect(const string& server, const uint16_t& port, const bool https, int timeout = 0); + static SSLSocket* connect(const string& server, const uint16_t& port, bool https, int timeout, + const char* caFile = nullptr, const char* caPath = nullptr); /** * Write bytes to the socket. @@ -119,8 +122,17 @@ class HttpClient { public: /** * Constructor. + * @param caFile the CA file to use (uses defaults if neither caFile nor caPath are set). + * @param caPath the path with CA files to use (uses defaults if neither caFile nor caPath are set). */ - HttpClient() : m_socket(nullptr), m_port(0), m_timeout(0), m_bufferSize(0), m_buffer(nullptr) { + explicit HttpClient(const char* caFile = nullptr, const char* caPath = nullptr) : +#ifdef HAVE_SSL + m_https(false), + m_caFile(caFile), + m_caPath(caPath), +#endif + m_socket(nullptr), m_port(0), m_timeout(0), m_bufferSize(0), m_buffer(nullptr) + { initialize(); } @@ -156,8 +168,8 @@ class HttpClient { * @param host the host name to connect to. * @param port the port to connect to. * @param https true for HTTPS, false for HTTP. - * @param timeout the timeout in seconds, defaults to 5 seconds. * @param userAgent the optional user agent to send in the request header. + * @param timeout the timeout in seconds, defaults to 5 seconds. * @return true on success, false on connect failure. */ bool connect(const string& host, uint16_t port, bool https = false, const string& userAgent = "", int timeout = 5); @@ -217,12 +229,21 @@ class HttpClient { * @param result the string to append the read data to and in which to find the delimiter. * @return the position of the delimiter if delimiter was set or the number of bytes received, or string::npos if not found. */ - size_t readUntil(const string& delim, const size_t length, string* result); + size_t readUntil(const string& delim, size_t length, string* result); private: #ifdef HAVE_SSL /** true once @a initialize() was called. */ static bool s_initialized; + + /** true for HTTPS, false for HTTP. */ + bool m_https; + + /** the CA file to use. */ + const char* m_caFile; + + /** the path with CA files to use. */ + const char* m_caPath; #endif /** the currently connected socket. */