start SSL

This commit is contained in:
John
2022-02-07 22:34:43 +01:00
parent 0135dc9100
commit e3dfe32350
11 changed files with 339 additions and 36 deletions
+20 -1
View File
@@ -62,6 +62,7 @@ check_function_exists(pselect HAVE_PSELECT)
check_function_exists(ppoll HAVE_PPOLL) check_function_exists(ppoll HAVE_PPOLL)
check_include_file(linux/serial.h HAVE_LINUX_SERIAL -DHAVE_LINUX_SERIAL=1) check_include_file(linux/serial.h HAVE_LINUX_SERIAL -DHAVE_LINUX_SERIAL=1)
check_include_file(dev/usb/uftdiio.h HAVE_FREEBSD_UFTDI -DHAVE_FREEBSD_UFTDI=1) check_include_file(dev/usb/uftdiio.h HAVE_FREEBSD_UFTDI -DHAVE_FREEBSD_UFTDI=1)
check_function_exists(argp_parse HAVE_ARGP) check_function_exists(argp_parse HAVE_ARGP)
if(NOT HAVE_ARGP) if(NOT HAVE_ARGP)
find_library(LIB_ARGP argp) find_library(LIB_ARGP argp)
@@ -77,11 +78,13 @@ if(NOT coverage STREQUAL OFF)
link_libraries(gcov) link_libraries(gcov)
message(STATUS "coverage enabled") message(STATUS "coverage enabled")
endif(NOT coverage STREQUAL OFF) endif(NOT coverage STREQUAL OFF)
option(contrib "disable inclusion of contributed sources." ON) option(contrib "disable inclusion of contributed sources." ON)
if(contrib STREQUAL ON) if(contrib STREQUAL ON)
set(HAVE_CONTRIB ON) set(HAVE_CONTRIB ON)
message(STATUS "contrib enabled") message(STATUS "contrib enabled")
endif(contrib STREQUAL ON) endif(contrib STREQUAL ON)
find_library(HAVE_MQTT mosquitto) find_library(HAVE_MQTT mosquitto)
if(HAVE_MQTT) if(HAVE_MQTT)
option(mqtt "disable support for MQTT handling." ON) option(mqtt "disable support for MQTT handling." ON)
@@ -90,7 +93,23 @@ if(HAVE_MQTT)
else(mqtt STREQUAL ON) else(mqtt STREQUAL ON)
unset(HAVE_MQTT) unset(HAVE_MQTT)
endif(mqtt STREQUAL ON) endif(mqtt STREQUAL ON)
endif(HAVE_MQTT) endif(HAVE_MQTT)
find_library(HAVE_SSL ssl)
find_library(LIB_CRYPTO crypto)
if(HAVE_SSL)
if(LIB_CRYPTO)
option(ssl "disable support for SSL." ON)
if(ssl STREQUAL ON)
message(STATUS "SSL enabled")
set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${HAVE_SSL} ${LIB_CRYPTO}")
else(ssl STREQUAL ON)
unset(HAVE_SSL)
endif(ssl STREQUAL ON)
else(LIB_CRYPTO)
message(FATAL_ERROR "crypto library not available")
endif(LIB_CRYPTO)
endif(HAVE_SSL)
check_cxx_source_runs(" check_cxx_source_runs("
#include <stdint.h> #include <stdint.h>
+3
View File
@@ -7,6 +7,9 @@
/* Defined if MQTT handling is enabled. */ /* Defined if MQTT handling is enabled. */
#cmakedefine HAVE_MQTT #cmakedefine HAVE_MQTT
/* Defined if SSL is enabled. */
#cmakedefine HAVE_SSL
/* Defined if ppoll() is available. */ /* Defined if ppoll() is available. */
#cmakedefine HAVE_PPOLL #cmakedefine HAVE_PPOLL
+4
View File
@@ -18,6 +18,10 @@ if(HAVE_MQTT)
set(ebusd_LIBS ${ebusd_LIBS} mosquitto) set(ebusd_LIBS ${ebusd_LIBS} mosquitto)
endif(HAVE_MQTT) endif(HAVE_MQTT)
if(HAVE_SSL)
set(ebusd_LIBS ${ebusd_LIBS} ssl crypto)
endif(HAVE_SSL)
if(HAVE_CONTRIB) if(HAVE_CONTRIB)
set(ebusd_LIBS ${ebusd_LIBS} ebuscontrib) set(ebusd_LIBS ${ebusd_LIBS} ebuscontrib)
endif(HAVE_CONTRIB) endif(HAVE_CONTRIB)
+7 -3
View File
@@ -65,7 +65,11 @@ using std::cout;
#endif #endif
/** the default path of the configuration files. */ /** the default path of the configuration files. */
#ifdef HAVE_SSL
#define CONFIG_PATH "https://cfg.ebusd.eu/"
#else
#define CONFIG_PATH "http://cfg.ebusd.eu/" #define CONFIG_PATH "http://cfg.ebusd.eu/"
#endif
/** the opened PID file, or nullptr. */ /** the opened PID file, or nullptr. */
static FILE* pidFile = nullptr; static FILE* pidFile = nullptr;
@@ -189,7 +193,7 @@ static const struct argp_option argpoptions[] = {
{"latency", O_DEVLAT, "MSEC", 0, "Extra transfer latency in ms [0]", 0 }, {"latency", O_DEVLAT, "MSEC", 0, "Extra transfer latency in ms [0]", 0 },
{nullptr, 0, nullptr, 0, "Message configuration options:", 2 }, {nullptr, 0, nullptr, 0, "Message configuration options:", 2 },
{"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTP URL) [" CONFIG_PATH {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" CONFIG_PATH
"]", 0 }, "]", 0 },
{"scanconfig", 's', "ADDR", OPTION_ARG_OPTIONAL, "Pick CSV config files matching initial scan (ADDR=" {"scanconfig", 's', "ADDR", OPTION_ARG_OPTIONAL, "Pick CSV config files matching initial scan (ADDR="
"\"none\" or empty for no initial scan message, \"full\" for full scan, or a single hex address to scan, " "\"none\" or empty for no initial scan message, \"full\" for full scan, or a single hex address to scan, "
@@ -308,7 +312,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) {
break; break;
// Message configuration options: // Message configuration options:
case 'c': // --configpath=http://cfg.ebusd.eu/ case 'c': // --configpath=https://cfg.ebusd.eu/
if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) {
argp_error(state, "invalid configpath"); argp_error(state, "invalid configpath");
return EINVAL; return EINVAL;
@@ -1307,7 +1311,7 @@ int main(int argc, char* argv[]) {
logError(lf_main, "invalid configPath URL"); logError(lf_main, "invalid configPath URL");
return EINVAL; return EINVAL;
} }
if (!s_configHttpClient.connect(configHost, configPort, PACKAGE_NAME "/" PACKAGE_VERSION)) { if (!s_configHttpClient.connect(configHost, configPort, proto=="https", PACKAGE_NAME "/" PACKAGE_VERSION)) {
logError(lf_main, "invalid configPath URL"); logError(lf_main, "invalid configPath URL");
return EINVAL; return EINVAL;
} }
+1 -1
View File
@@ -41,7 +41,7 @@ struct options {
bool initialSend; //!< send an initial escape symbol after connecting device bool initialSend; //!< send an initial escape symbol after connecting device
unsigned int extraLatency; //!< extra transfer latency in ms [0 for USB, 10 for IP] unsigned int extraLatency; //!< extra transfer latency in ms [0 for USB, 10 for IP]
const char* configPath; //!< path to CSV configuration files [http://cfg.ebusd.eu/] const char* configPath; //!< path to CSV configuration files [https://cfg.ebusd.eu/]
bool scanConfig; //!< pick configuration files matching initial scan bool scanConfig; //!< pick configuration files matching initial scan
/** the initial address to scan for scanconfig /** the initial address to scan for scanconfig
* (@a ESC=none, 0xfe=broadcast ident, @a SYN=full scan, else: single slave address). */ * (@a ESC=none, 0xfe=broadcast ident, @a SYN=full scan, else: single slave address). */
+7 -1
View File
@@ -332,7 +332,13 @@ void MainLoop::run() {
} }
if (m_runUpdateCheck && !m_shutdown && now > nextCheckRun) { if (m_runUpdateCheck && !m_shutdown && now > nextCheckRun) {
HttpClient client; HttpClient client;
if (!client.connect("upd.ebusd.eu", 80, PACKAGE_NAME "/" PACKAGE_VERSION)) { if (!client.connect("upd.ebusd.eu",
#ifdef HAVE_SSL
443, true,
#else
80, false,
#endif
PACKAGE_NAME "/" PACKAGE_VERSION)) {
logError(lf_main, "update check connect error"); logError(lf_main, "update check connect error");
} else { } else {
ostringstream ostr; ostringstream ostr;
+203 -4
View File
@@ -17,9 +17,11 @@
*/ */
#include "lib/utils/httpclient.h" #include "lib/utils/httpclient.h"
#include "lib/utils/log.h"
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
#include <sstream> #include <sstream>
#include <csignal>
namespace ebusd { namespace ebusd {
@@ -28,6 +30,189 @@ using std::ostringstream;
using std::dec; using std::dec;
using std::hex; using std::hex;
#ifdef HAVE_SSL
bool checkError(const char* call) {
unsigned long err = ERR_get_error();
if (err) {
const char *const str = ERR_reason_error_string(err);
logError(lf_network, "SSL error %s: %ld=%s", call, err, str);
return true;
}
return false;
}
bool isError(const char* call, bool result) {
if (checkError(call)) {
return true;
}
if (!result) {
logError(lf_network, "SSL error %s: invalid result", call);
return true;
}
return false;
}
bool isError(const char* call, long result, long expected) {
if (checkError(call)) {
return true;
}
if (result!=expected) {
logError(lf_network, "SSL error %s: invalid result %d", call, result);
return true;
}
return false;
}
SSLSocket::~SSLSocket() {
BIO_free_all(m_bio);
if (m_ctx) {
SSL_CTX_free(m_ctx);
}
}
ssize_t SSLSocket::send(const char* data, size_t len) {
do {
size_t part = 0;
int res = BIO_write_ex(m_bio, data, len, &part);
if (res==1) {
return static_cast<signed>(part);
}
if (!BIO_should_retry(m_bio)) {
if (isError("write", true)) {
return -1;
}
return 0;
}
usleep(50000);
} while (true);
}
ssize_t SSLSocket::recv(char* data, size_t len) {
do {
size_t part = 0;
int res = BIO_read_ex(m_bio, data, len, &part);
if (res==1) {
return static_cast<signed>(part);
}
if (!BIO_should_retry(m_bio)) {
if (isError("read", true)) {
return -1;
}
return 0;
}
usleep(50000);
} while (true);
}
bool SSLSocket::isValid() {
return !BIO_eof(m_bio);
}
SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, const bool https, int timeout) {
BIO *bio = nullptr;
SSL_CTX *ctx = nullptr;
ostringstream ostr;
ostr << host << ':' << static_cast<unsigned>(port);
const string hostPort = ostr.str();
if (!https) {
do {
bio = BIO_new_connect(hostPort.c_str());
if (isError("connect", bio)) {
break;
}
BIO_set_nbio(bio, 1); // set non-blocking
return new SSLSocket(nullptr, bio);
} while (false);
} else {
SSL *ssl = nullptr;
do {
const SSL_METHOD *method = SSLv23_method();
if (isError("method", method)) {
break;
}
ctx = SSL_CTX_new(method);
if (isError("ctx_new", ctx)) {
break;
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);
if (isError("verify_loc", SSL_CTX_load_verify_locations(ctx, nullptr, "/etc/ssl/certs"), 1)) {
break;
}
SSL_CTX_set_verify_depth(ctx, 2);
const long flags = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION;
SSL_CTX_set_options(ctx, flags);
bio = BIO_new_ssl_connect(ctx);
if (isError("new_ssl_connect", bio)) {
break;
}
if (isError("conn_hostname", BIO_set_conn_hostname(bio, hostPort.c_str()), 1)) {
break;
}
BIO_set_nbio(bio, 1); // set non-blocking
BIO_get_ssl(bio, &ssl);
if (isError("get_ssl", ssl)) {
break;
}
const char *hostname = host.c_str();
if (isError("tlsext_host_name", SSL_set_tlsext_host_name(ssl, hostname), 1)) {
break;
}
time_t until = time(nullptr) + (timeout<=0 ? 1 : timeout);
long res = BIO_do_connect(bio);
while (res != 1 && BIO_should_retry(bio) && time(nullptr)<until) {
usleep(50000);
res = BIO_do_connect(bio);
}
if (isError("connect", res, 1)) {
break;
}
X509 *cert = SSL_get_peer_certificate(ssl);
if (cert) {
X509_free(cert);
}
if (isError("peer_cert", cert)) {
break;
}
if (isError("verify", SSL_get_verify_result(ssl), X509_V_OK)) {
break;
}
// check hostname
X509_NAME *sname = X509_get_subject_name(cert);
if (isError("subject_name", sname)) {
break;
}
char peerName[64];
if (isError("extract subject", X509_NAME_get_text_by_NID(sname, NID_commonName, peerName, sizeof(peerName)) > 0)) {
break;
}
if (isError("subject", strcmp(peerName, hostname), 0)) {
break;
}
return new SSLSocket(ctx, bio);
} while (false);
}
if (bio) {
BIO_free_all(bio);
}
if (ctx) {
SSL_CTX_free(ctx);
}
return nullptr;
}
void HttpClient::initialize() {
SSL_library_init();
SSL_load_error_strings();
signal(SIGPIPE, SIG_IGN); // needed to avoid SIGPIPE when writing to a closed pipe
}
#else // HAVE_SSL
void HttpClient::initialize() {
// empty
}
#endif // HAVE_SSL
bool HttpClient::parseUrl(const string& url, string* proto, string* host, uint16_t* port, string* uri) { bool HttpClient::parseUrl(const string& url, string* proto, string* host, uint16_t* port, string* uri) {
size_t hostPos = url.find("://"); size_t hostPos = url.find("://");
if (hostPos == string::npos) { if (hostPos == string::npos) {
@@ -35,9 +220,16 @@ bool HttpClient::parseUrl(const string& url, string* proto, string* host, uint16
} }
*proto = url.substr(0, hostPos); *proto = url.substr(0, hostPos);
hostPos += 3; hostPos += 3;
bool isSsl = *proto == "https";
#ifdef HAVE_SSL
if (!isSsl && *proto != "http") {
return false;
}
#else
if (*proto != "http") { if (*proto != "http") {
return false; return false;
} }
#endif
size_t pos = url.find('/', hostPos); size_t pos = url.find('/', hostPos);
if (pos == hostPos) { if (pos == hostPos) {
return false; return false;
@@ -56,7 +248,7 @@ bool HttpClient::parseUrl(const string& url, string* proto, string* host, uint16
if (pos == 0) { if (pos == 0) {
return false; return false;
} }
*port = 80; *port = isSsl ? 443 : 80;
if (pos != string::npos) { if (pos != string::npos) {
char* strEnd = nullptr; char* strEnd = nullptr;
unsigned long value = strtoul(host->c_str()+pos+1, &strEnd, 10); unsigned long value = strtoul(host->c_str()+pos+1, &strEnd, 10);
@@ -69,9 +261,16 @@ bool HttpClient::parseUrl(const string& url, string* proto, string* host, uint16
return true; return true;
} }
bool HttpClient::connect(const string& host, const uint16_t port, const string& userAgent, const int timeout) { bool HttpClient::connect(const string& host, const uint16_t port, const bool https, const string& userAgent, const int timeout) {
disconnect(); disconnect();
m_socket = m_client.connect(host, port, timeout); #ifdef HAVE_SSL
m_socket = SSLSocket::connect(host, port, https, timeout);
#else
if (https) {
return false;
}
m_socket = TCPSocket::connect(host, port, timeout);
#endif
if (!m_socket) { if (!m_socket) {
return false; return false;
} }
@@ -87,7 +286,7 @@ bool HttpClient::reconnect() {
if (m_host.empty() || !m_port) { if (m_host.empty() || !m_port) {
return false; return false;
} }
m_socket = m_client.connect(m_host, m_port, m_timeout); m_socket = SocketClass::connect(m_host, m_port, m_timeout);
if (!m_socket) { if (!m_socket) {
return false; return false;
} }
+83 -7
View File
@@ -19,11 +19,20 @@
#ifndef LIB_UTILS_HTTPCLIENT_H_ #ifndef LIB_UTILS_HTTPCLIENT_H_
#define LIB_UTILS_HTTPCLIENT_H_ #define LIB_UTILS_HTTPCLIENT_H_
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <unistd.h> #include <unistd.h>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "lib/utils/tcpsocket.h" #include "lib/utils/tcpsocket.h"
#ifdef HAVE_SSL
# include <openssl/ssl.h>
# include <openssl/bio.h>
# include <openssl/err.h>
#endif
/** typedef for referencing @a sockaddr_in within namespace. */ /** typedef for referencing @a sockaddr_in within namespace. */
typedef struct sockaddr_in socketaddress; typedef struct sockaddr_in socketaddress;
@@ -35,6 +44,70 @@ namespace ebusd {
using std::string; using std::string;
using std::ifstream; using std::ifstream;
#ifdef HAVE_SSL
class SSLSocket {
private:
/**
* Constructor.
* @param ctx the SSL_CTX for cleanup, or nullptr.
* @param bio the BIO instance, or nullptr.
*/
SSLSocket(SSL_CTX *ctx, BIO *bio) : m_ctx(ctx), m_bio(bio) {}
public:
/**
* Destructor.
*/
virtual ~SSLSocket();
/**
* Connect to the host on the specified port.
* @param host the host name or ip address to connect to.
* @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.
* @return the connected SSLSocket, or nullptr on error.
*/
static SSLSocket* connect(const string& server, const uint16_t& port, const bool https, int timeout = 0);
/**
* Write bytes to the socket.
* @param data the data to send.
* @param len number of bytes to send.
* @return number of bytes written, or -1 on error.
*/
ssize_t send(const char* data, size_t len);
/**
* Read bytes from the socket.
* @param data the buffer for the received bytes.
* @param len size of the buffer.
* @return number of bytes read, or -1 on error.
*/
ssize_t recv(char* data, size_t len);
/**
* Return whether the socket is still valid.
* @return true if the socket is still valid.
*/
bool isValid();
private:
/** the SSL_CTX for cleanup, or nullptr. */
SSL_CTX *m_ctx;
/** the BIO instance for communication. */
BIO *m_bio;
};
#define SocketClass SSLSocket
#else // HAVE_SSL
#define SocketClass TCPSocket
#endif // HAVE_SSL
/** /**
* Helper class for handling HTTP client requests. * Helper class for handling HTTP client requests.
*/ */
@@ -43,7 +116,7 @@ class HttpClient {
/** /**
* Constructor. * Constructor.
*/ */
HttpClient() : m_port(0), m_timeout(0), m_socket(nullptr), m_bufferSize(0), m_buffer(nullptr) {} HttpClient() : m_socket(nullptr), m_port(0), m_timeout(0), m_bufferSize(0), m_buffer(nullptr) {}
/** /**
* Destructor. * Destructor.
@@ -56,6 +129,11 @@ class HttpClient {
} }
} }
/**
* Initialize HttpClient.
*/
static void initialize();
/** /**
* Parse an HTTP URL. * Parse an HTTP URL.
* @param url the URL to parse. * @param url the URL to parse.
@@ -71,11 +149,12 @@ class HttpClient {
* Connect to the specified server. * Connect to the specified server.
* @param host the host name to connect to. * @param host the host name to connect to.
* @param port the port 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 timeout the timeout in seconds, defaults to 5 seconds.
* @param userAgent the optional user agent to send in the request header. * @param userAgent the optional user agent to send in the request header.
* @return true on success, false on connect failure. * @return true on success, false on connect failure.
*/ */
bool connect(const string& host, uint16_t port, const string& userAgent = "", int timeout = 5); bool connect(const string& host, uint16_t port, const bool https = false, const string& userAgent = "", int timeout = 5);
/** /**
* Re-connect to the last specified server. * Re-connect to the last specified server.
@@ -135,8 +214,8 @@ class HttpClient {
size_t readUntil(const string& delim, const size_t length, string* result); size_t readUntil(const string& delim, const size_t length, string* result);
private: private:
/** the @a TCPClient handling the traffic. */ /** the currently connected socket. */
TCPClient m_client; SocketClass* m_socket;
/** the name of the host last successfully connected to. */ /** the name of the host last successfully connected to. */
string m_host; string m_host;
@@ -150,9 +229,6 @@ class HttpClient {
/** the optional user agent to send in the request header. */ /** the optional user agent to send in the request header. */
string m_userAgent; string m_userAgent;
/** the currently connected socket. */
TCPSocket* m_socket;
/** the size of the @a m_buffer. */ /** the size of the @a m_buffer. */
size_t m_bufferSize; size_t m_bufferSize;
+1 -1
View File
@@ -45,7 +45,7 @@ bool TCPSocket::isValid() {
} }
TCPSocket* TCPClient::connect(const string& server, const uint16_t& port, int timeout) { TCPSocket* TCPSocket::connect(const string& server, const uint16_t& port, int timeout) {
socketaddress address; socketaddress address;
int ret; int ret;
+9 -16
View File
@@ -45,7 +45,6 @@ using std::string;
* Class for low level TCP socket operations (open, close, send, receive). * Class for low level TCP socket operations (open, close, send, receive).
*/ */
class TCPSocket { class TCPSocket {
friend class TCPClient;
friend class TCPServer; friend class TCPServer;
private: private:
@@ -62,6 +61,15 @@ class TCPSocket {
*/ */
~TCPSocket() { close(m_sfd); } ~TCPSocket() { close(m_sfd); }
/**
* initiate a tcp socket connection to a listening server.
* @param server the server name or ip address to connect.
* @param port the tcp port.
* @param timeout the connect, send, and receive timeout in seconds, or 0.
* @return pointer to an opened tcp socket.
*/
static TCPSocket* connect(const string& server, const uint16_t& port, int timeout = 0);
/** /**
* Write bytes to opened file descriptor. * Write bytes to opened file descriptor.
* @param buffer data to send. * @param buffer data to send.
@@ -125,21 +133,6 @@ class TCPSocket {
string m_ip; string m_ip;
}; };
/**
* class to initiate a TCP socket connection to a listening server.
*/
class TCPClient {
public:
/**
* initiate a tcp socket connection to a listening server.
* @param server the server name or ip address to connect.
* @param port the tcp port.
* @param timeout the connect, send, and receive timeout in seconds, or 0.
* @return pointer to an opened tcp socket.
*/
TCPSocket* connect(const string& server, const uint16_t& port, int timeout = 0);
};
/** /**
* class for a TCP based network server. * class for a TCP based network server.
*/ */
+1 -2
View File
@@ -261,8 +261,7 @@ string fetchData(ebusd::TCPSocket* socket, bool &listening, uint16_t timeout, bo
} }
bool connect(const char* host, uint16_t port, uint16_t timeout, char* const *args, int argCount) { bool connect(const char* host, uint16_t port, uint16_t timeout, char* const *args, int argCount) {
TCPClient* client = new TCPClient(); TCPSocket* socket = TCPSocket::connect(host, port, timeout);
TCPSocket* socket = client->connect(host, port, timeout);
bool ret; bool ret;
bool once = args != nullptr && argCount > 0; bool once = args != nullptr && argCount > 0;