diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 16:28:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 16:28:20 +0000 |
commit | dcc721a95bef6f0d8e6d8775b8efe33e5aecd562 (patch) | |
tree | 66a2774cd0ee294d019efd71d2544c70f42b2842 /runtime/nsd_ossl.c | |
parent | Initial commit. (diff) | |
download | rsyslog-dcc721a95bef6f0d8e6d8775b8efe33e5aecd562.tar.xz rsyslog-dcc721a95bef6f0d8e6d8775b8efe33e5aecd562.zip |
Adding upstream version 8.2402.0.upstream/8.2402.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | runtime/nsd_ossl.c | 1510 |
1 files changed, 1510 insertions, 0 deletions
diff --git a/runtime/nsd_ossl.c b/runtime/nsd_ossl.c new file mode 100644 index 0000000..2d70fb6 --- /dev/null +++ b/runtime/nsd_ossl.c @@ -0,0 +1,1510 @@ +/* nsd_ossl.c + * + * An implementation of the nsd interface for OpenSSL. + * + * Copyright 2018-2023 Adiscon GmbH. + * Author: Andre Lorbach + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <strings.h> + +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <pthread.h> + +#include "rsyslog.h" +#include "syslogd-types.h" +#include "module-template.h" +#include "cfsysline.h" +#include "obj.h" +#include "stringbuf.h" +#include "errmsg.h" +#include "net.h" +#include "netstrm.h" +#include "netstrms.h" +#include "datetime.h" +#include "net_ossl.h" // Include OpenSSL Helpers +#include "nsd_ptcp.h" +#include "nsdsel_ossl.h" +#include "nsd_ossl.h" +#include "unicode-helper.h" +#include "rsconf.h" + +MODULE_TYPE_LIB +MODULE_TYPE_KEEP + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(datetime) +DEFobjCurrIf(nsd_ptcp) +DEFobjCurrIf(net_ossl) + +/* Some prototypes for helper functions used inside openssl driver */ +static rsRetVal applyGnutlsPriorityString(nsd_ossl_t *const pNsd); + +/*--------------------------------------OpenSSL helpers ------------------------------------------*/ +void nsd_ossl_lastOpenSSLErrorMsg(nsd_ossl_t const *pThis, const int ret, SSL *ssl, int severity, + const char* pszCallSource, const char* pszOsslApi) +{ + uchar *fromHost = NULL; + int errno_store = errno; + + if(pThis != NULL) { + nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost); + } + + // Call helper in net_ossl + net_ossl_lastOpenSSLErrorMsg(fromHost, ret, ssl, severity, pszCallSource, pszOsslApi); + + free(fromHost); + errno = errno_store; +} + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) +long BIO_debug_callback_ex(BIO *bio, int cmd, const char __attribute__((unused)) *argp, + size_t __attribute__((unused)) len, int argi, long __attribute__((unused)) argl, + int ret, size_t __attribute__((unused)) *processed) +#else +long BIO_debug_callback(BIO *bio, int cmd, const char __attribute__((unused)) *argp, + int argi, long __attribute__((unused)) argl, long ret) +#endif +{ + long ret2 = ret; // Helper value to avoid printf compile errors long<>int + long r = 1; + if (BIO_CB_RETURN & cmd) + r = ret; + dbgprintf("openssl debugmsg: BIO[%p]: ", (void *)bio); + switch (cmd) { + case BIO_CB_FREE: + dbgprintf("Free - %s\n", RSYSLOG_BIO_method_name(bio)); + break; +/* Disabled due API changes for OpenSSL 1.1.0+ */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + case BIO_CB_READ: + if (bio->method->type & BIO_TYPE_DESCRIPTOR) + dbgprintf("read(%d,%lu) - %s fd=%d\n", + RSYSLOG_BIO_number_read(bio), (unsigned long)argi, + RSYSLOG_BIO_method_name(bio), RSYSLOG_BIO_number_read(bio)); + else + dbgprintf("read(%d,%lu) - %s\n", RSYSLOG_BIO_number_read(bio), + (unsigned long)argi, RSYSLOG_BIO_method_name(bio)); + break; + case BIO_CB_WRITE: + if (bio->method->type & BIO_TYPE_DESCRIPTOR) + dbgprintf("write(%d,%lu) - %s fd=%d\n", + RSYSLOG_BIO_number_written(bio), (unsigned long)argi, + RSYSLOG_BIO_method_name(bio), RSYSLOG_BIO_number_written(bio)); + else + dbgprintf("write(%d,%lu) - %s\n", + RSYSLOG_BIO_number_written(bio), + (unsigned long)argi, + RSYSLOG_BIO_method_name(bio)); + break; +#else + case BIO_CB_READ: + dbgprintf("read %s\n", RSYSLOG_BIO_method_name(bio)); + break; + case BIO_CB_WRITE: + dbgprintf("write %s\n", RSYSLOG_BIO_method_name(bio)); + break; +#endif + case BIO_CB_PUTS: + dbgprintf("puts() - %s\n", RSYSLOG_BIO_method_name(bio)); + break; + case BIO_CB_GETS: + dbgprintf("gets(%lu) - %s\n", (unsigned long)argi, + RSYSLOG_BIO_method_name(bio)); + break; + case BIO_CB_CTRL: + dbgprintf("ctrl(%lu) - %s\n", (unsigned long)argi, + RSYSLOG_BIO_method_name(bio)); + break; + case BIO_CB_RETURN | BIO_CB_READ: + dbgprintf("read return %ld\n", ret2); + break; + case BIO_CB_RETURN | BIO_CB_WRITE: + dbgprintf("write return %ld\n", ret2); + break; + case BIO_CB_RETURN | BIO_CB_GETS: + dbgprintf("gets return %ld\n", ret2); + break; + case BIO_CB_RETURN | BIO_CB_PUTS: + dbgprintf("puts return %ld\n", ret2); + break; + case BIO_CB_RETURN | BIO_CB_CTRL: + dbgprintf("ctrl return %ld\n", ret2); + break; + default: + dbgprintf("bio callback - unknown type (%d)\n", cmd); + break; + } + + return (r); +} + +/* try to receive a record from the remote peer. This works with + * our own abstraction and handles local buffering and EAGAIN. + * See details on local buffering in Rcv(9 header-comment. + * This function MUST only be called when the local buffer is + * empty. Calling it otherwise will cause losss of current buffer + * data. + * rgerhards, 2008-06-24 + */ +rsRetVal +osslRecordRecv(nsd_ossl_t *pThis) +{ + ssize_t lenRcvd; + DEFiRet; + int err; + + ISOBJ_TYPE_assert(pThis, nsd_ossl); + DBGPRINTF("osslRecordRecv: start\n"); + + lenRcvd = SSL_read(pThis->pNetOssl->ssl, pThis->pszRcvBuf, NSD_OSSL_MAX_RCVBUF); + if(lenRcvd > 0) { + DBGPRINTF("osslRecordRecv: SSL_read received %zd bytes\n", lenRcvd); + pThis->lenRcvBuf = lenRcvd; + pThis->ptrRcvBuf = 0; + + /* Check for additional data in SSL buffer */ + int iBytesLeft = SSL_pending(pThis->pNetOssl->ssl); + if (iBytesLeft > 0 ){ + DBGPRINTF("osslRecordRecv: %d Bytes pending after SSL_Read, expand buffer.\n", iBytesLeft); + /* realloc buffer size and preserve char content */ + char *const newbuf = realloc(pThis->pszRcvBuf, NSD_OSSL_MAX_RCVBUF+iBytesLeft); + CHKmalloc(newbuf); + pThis->pszRcvBuf = newbuf; + + /* 2nd read will read missing bytes from the current SSL Packet */ + lenRcvd = SSL_read(pThis->pNetOssl->ssl, pThis->pszRcvBuf+NSD_OSSL_MAX_RCVBUF, iBytesLeft); + if(lenRcvd > 0) { + DBGPRINTF("osslRecordRecv: 2nd SSL_read received %zd bytes\n", + (NSD_OSSL_MAX_RCVBUF+lenRcvd)); + pThis->lenRcvBuf = NSD_OSSL_MAX_RCVBUF+lenRcvd; + } else { + goto sslerr; + } + } + } else { +sslerr: + err = SSL_get_error(pThis->pNetOssl->ssl, lenRcvd); + if( err == SSL_ERROR_ZERO_RETURN ) { + DBGPRINTF("osslRecordRecv: SSL_ERROR_ZERO_RETURN received, connection may closed already\n"); + ABORT_FINALIZE(RS_RET_RETRY); + } + else if(err == SSL_ERROR_SYSCALL) { + /* Output error and abort */ + nsd_ossl_lastOpenSSLErrorMsg(pThis, lenRcvd, pThis->pNetOssl->ssl, LOG_INFO, + "osslRecordRecv", "SSL_read 1"); + iRet = RS_RET_NO_ERRCODE; + /* Check for underlaying socket errors **/ + if ( errno == ECONNRESET) { + DBGPRINTF("osslRecordRecv: SSL_ERROR_SYSCALL Errno %d, connection reset by peer\n", + errno); + /* Connection was dropped from remote site */ + iRet = RS_RET_CLOSED; + } else { + DBGPRINTF("osslRecordRecv: SSL_ERROR_SYSCALL Errno %d\n", errno); + } + ABORT_FINALIZE(iRet); + } + else if(err != SSL_ERROR_WANT_READ && + err != SSL_ERROR_WANT_WRITE) { + DBGPRINTF("osslRecordRecv: SSL_get_error #1 = %d, lenRcvd=%zd\n", err, lenRcvd); + /* Output OpenSSL error*/ + nsd_ossl_lastOpenSSLErrorMsg(pThis, lenRcvd, pThis->pNetOssl->ssl, LOG_ERR, + "osslRecordRecv", "SSL_read 2"); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } else { + DBGPRINTF("osslRecordRecv: SSL_get_error #2 = %d, lenRcvd=%zd\n", err, lenRcvd); + pThis->rtryCall = osslRtry_recv; + pThis->rtryOsslErr = err; /* Store SSL ErrorCode into*/ + ABORT_FINALIZE(RS_RET_RETRY); + } + } + +// TODO: Check if MORE retry logic needed? + +finalize_it: + dbgprintf("osslRecordRecv return. nsd %p, iRet %d, lenRcvd %zd, lenRcvBuf %d, ptrRcvBuf %d\n", + pThis, iRet, lenRcvd, pThis->lenRcvBuf, pThis->ptrRcvBuf); + RETiRet; +} + +static rsRetVal +osslInitSession(nsd_ossl_t *pThis, osslSslState_t osslType) /* , nsd_ossl_t *pServer) */ +{ + DEFiRet; + BIO *conn; + char pristringBuf[4096]; + nsd_ptcp_t *pPtcp = (nsd_ptcp_t*) pThis->pTcp; + + if(!(pThis->pNetOssl->ssl = SSL_new(pThis->pNetOssl->ctx))) { + pThis->pNetOssl->ssl = NULL; + nsd_ossl_lastOpenSSLErrorMsg(pThis, 0, pThis->pNetOssl->ssl, LOG_ERR, "osslInitSession", "SSL_new"); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } + + // Set SSL_MODE_AUTO_RETRY to SSL obj + SSL_set_mode(pThis->pNetOssl->ssl, SSL_MODE_AUTO_RETRY); + + if (pThis->pNetOssl->authMode != OSSL_AUTH_CERTANON) { + dbgprintf("osslInitSession: enable certificate checking (Mode=%d, VerifyDepth=%d)\n", + pThis->pNetOssl->authMode, pThis->DrvrVerifyDepth); + /* Enable certificate valid checking */ + net_ossl_set_ssl_verify_callback(pThis->pNetOssl->ssl, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT); + if (pThis->DrvrVerifyDepth != 0) { + SSL_set_verify_depth(pThis->pNetOssl->ssl, pThis->DrvrVerifyDepth); + } + } else if (pThis->gnutlsPriorityString == NULL) { + /* Allow ANON Ciphers only in ANON Mode and if no custom priority string is defined */ + #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) + /* NOTE: do never use: +eNULL, it DISABLES encryption! */ + strncpy(pristringBuf, "ALL:+COMPLEMENTOFDEFAULT:+ADH:+ECDH:+aNULL@SECLEVEL=0", + sizeof(pristringBuf)); + #else + strncpy(pristringBuf, "ALL:+COMPLEMENTOFDEFAULT:+ADH:+ECDH:+aNULL", + sizeof(pristringBuf)); + #endif + + dbgprintf("osslInitSession: setting anon ciphers: %s\n", pristringBuf); + if ( SSL_set_cipher_list(pThis->pNetOssl->ssl, pristringBuf) == 0 ){ + dbgprintf("osslInitSession: Error setting ciphers '%s'\n", pristringBuf); + ABORT_FINALIZE(RS_RET_SYS_ERR); + } + } + + /* Create BIO from ptcp socket! */ + conn = BIO_new_socket(pPtcp->sock, BIO_CLOSE /*BIO_NOCLOSE*/); + dbgprintf("osslInitSession: Init conn BIO[%p] done\n", (void *)conn); + + /* Set debug Callback for conn BIO as well! */ + net_ossl_set_bio_callback(conn); + + /* TODO: still needed? Set to NON blocking ! */ + BIO_set_nbio( conn, 1 ); + SSL_set_bio(pThis->pNetOssl->ssl, conn, conn); + + if (osslType == osslServer) { + /* Server Socket */ + SSL_set_accept_state(pThis->pNetOssl->ssl); /* sets ssl to work in server mode. */ + pThis->pNetOssl->sslState = osslServer; /*set Server state */ + } else { + /* Client Socket */ + SSL_set_connect_state(pThis->pNetOssl->ssl); /*sets ssl to work in client mode.*/ + pThis->pNetOssl->sslState = osslClient; /*set Client state */ + } + pThis->bHaveSess = 1; + + /* we are done */ + FINALIZE; + +finalize_it: + RETiRet; +} + + +/* check if it is OK to talk to the remote peer + * rgerhards, 2008-05-21 + */ +rsRetVal +osslChkPeerAuth(nsd_ossl_t *pThis) +{ + DEFiRet; + X509 *certpeer; + + ISOBJ_TYPE_assert(pThis, nsd_ossl); + uchar *fromHostIP = NULL; + nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHostIP); + + /* call the actual function based on current auth mode */ + switch(pThis->pNetOssl->authMode) { + case OSSL_AUTH_CERTNAME: + /* if we check the name, we must ensure the cert is valid */ + certpeer = net_ossl_getpeercert(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP); + dbgprintf("osslChkPeerAuth: Check peer certname[%p]=%s\n", + (void *)pThis->pNetOssl->ssl, (certpeer != NULL ? "VALID" : "NULL")); + CHKiRet(net_ossl_chkpeercertvalidity(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP)); + CHKiRet(net_ossl_chkpeername(pThis->pNetOssl, certpeer, fromHostIP)); + break; + case OSSL_AUTH_CERTFINGERPRINT: + certpeer = net_ossl_getpeercert(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP); + dbgprintf("osslChkPeerAuth: Check peer fingerprint[%p]=%s\n", + (void *)pThis->pNetOssl->ssl, (certpeer != NULL ? "VALID" : "NULL")); + CHKiRet(net_ossl_chkpeercertvalidity(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP)); + CHKiRet(net_ossl_peerfingerprint(pThis->pNetOssl, certpeer, fromHostIP)); + + break; + case OSSL_AUTH_CERTVALID: + certpeer = net_ossl_getpeercert(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP); + dbgprintf("osslChkPeerAuth: Check peer valid[%p]=%s\n", + (void *)pThis->pNetOssl->ssl, (certpeer != NULL ? "VALID" : "NULL")); + CHKiRet(net_ossl_chkpeercertvalidity(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP)); + break; + case OSSL_AUTH_CERTANON: + FINALIZE; + break; + } +finalize_it: + if (fromHostIP != NULL) { + free(fromHostIP); + } + RETiRet; +} + +/* end a OpenSSL session + * The function checks if we have a session and ends it only if so. So it can + * always be called, even if there currently is no session. + */ +static rsRetVal +osslEndSess(nsd_ossl_t *pThis) +{ + DEFiRet; + uchar *fromHostIP = NULL; + int ret; + int err; + + /* try closing SSL Connection */ + if(pThis->bHaveSess) { + DBGPRINTF("osslEndSess: closing SSL Session ...\n"); + ret = SSL_shutdown(pThis->pNetOssl->ssl); + nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHostIP); + if (ret <= 0) { + err = SSL_get_error(pThis->pNetOssl->ssl, ret); + DBGPRINTF("osslEndSess: shutdown failed with err = %d\n", err); + + /* ignore those SSL Errors on shutdown */ + if( err != SSL_ERROR_SYSCALL && + err != SSL_ERROR_ZERO_RETURN && + err != SSL_ERROR_WANT_READ && + err != SSL_ERROR_WANT_WRITE) { + /* Output Warning only */ + nsd_ossl_lastOpenSSLErrorMsg(pThis, ret, pThis->pNetOssl->ssl, LOG_WARNING, + "osslEndSess", "SSL_shutdown"); + } + /* Shutdown not finished, call SSL_read to do a bidirectional shutdown, see doc for more: + * https://www.openssl.org/docs/man1.1.1/man3/SSL_shutdown.html + */ + char rcvBuf[NSD_OSSL_MAX_RCVBUF]; + int iBytesRet = SSL_read(pThis->pNetOssl->ssl, rcvBuf, NSD_OSSL_MAX_RCVBUF); + DBGPRINTF("osslEndSess: Forcing ssl shutdown SSL_read (%d) to do a bidirectional shutdown\n", + iBytesRet); + if (ret < 0) { + /* Unsuccessful shutdown, log as INFO */ + LogMsg(0, RS_RET_NO_ERRCODE, LOG_INFO, "nsd_ossl: " + "TLS session terminated successfully to remote syslog server '%s' with SSL Error '%d':" + " End Session", fromHostIP, ret); + } + dbgprintf( "osslEndSess: TLS session terminated successfully to remote syslog server '%s'" + " End Session", fromHostIP); + } else { + dbgprintf("osslEndSess: TLS session terminated successfully with remote syslog server '%s':" + " End Session", fromHostIP); + } + + /* Session closed */ + pThis->bHaveSess = 0; + } + + if (fromHostIP != NULL) { + free(fromHostIP); + } + RETiRet; +} +/* ---------------------------- end OpenSSL specifics ---------------------------- */ + + +/* Standard-Constructor */ +BEGINobjConstruct(nsd_ossl) /* be sure to specify the object type also in END macro! */ + /* construct nsd_ptcp helper */ + CHKiRet(nsd_ptcp.Construct(&pThis->pTcp)); + /* construct net_ossl helper */ + CHKiRet(net_ossl.Construct(&pThis->pNetOssl)); +finalize_it: +ENDobjConstruct(nsd_ossl) + + +/* destructor for the nsd_ossl object */ +PROTOTYPEobjDestruct(nsd_ossl); +BEGINobjDestruct(nsd_ossl) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsd_ossl) + DBGPRINTF("nsd_ossl_destruct: [%p] Mode %d\n", pThis, pThis->iMode); + if(pThis->iMode == 1) { + osslEndSess(pThis); + } + /* TODO MOVE Free SSL obj also if we do not have a session - or are NOT in TLS mode! */ + if (pThis->pNetOssl->ssl != NULL) { + DBGPRINTF("nsd_ossl_destruct: [%p] FREE pThis->pNetOssl->ssl \n", pThis); + SSL_free(pThis->pNetOssl->ssl); + pThis->pNetOssl->ssl = NULL; + } + /**/ + if(pThis->pTcp != NULL) { + nsd_ptcp.Destruct(&pThis->pTcp); + } + if(pThis->pNetOssl != NULL) { + net_ossl.Destruct(&pThis->pNetOssl); + } + + free(pThis->pszConnectHost); + free(pThis->pszRcvBuf); +ENDobjDestruct(nsd_ossl) + + +/* Set the driver mode. For us, this has the following meaning: + * 0 - work in plain tcp mode, without tls (e.g. before a STARTTLS) + * 1 - work in TLS mode + * rgerhards, 2008-04-28 + */ +static rsRetVal +SetMode(nsd_t *pNsd, const int mode) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + if(mode != 0 && mode != 1) { + LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by" + " ossl netstream driver", mode); + } + pThis->iMode = mode; + + RETiRet; +} + +/* Set the authentication mode. For us, the following is supported: + * anon - no certificate checks whatsoever (discouraged, but supported) + * x509/certvalid - (just) check certificate validity + * x509/fingerprint - certificate fingerprint + * x509/name - cerfificate name check + * mode == NULL is valid and defaults to x509/name + * rgerhards, 2008-05-16 + */ +static rsRetVal +SetAuthMode(nsd_t *const pNsd, uchar *const mode) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + if(mode == NULL || !strcasecmp((char*)mode, "x509/name")) { + pThis->pNetOssl->authMode = OSSL_AUTH_CERTNAME; + } else if(!strcasecmp((char*) mode, "x509/fingerprint")) { + pThis->pNetOssl->authMode = OSSL_AUTH_CERTFINGERPRINT; + } else if(!strcasecmp((char*) mode, "x509/certvalid")) { + pThis->pNetOssl->authMode = OSSL_AUTH_CERTVALID; + } else if(!strcasecmp((char*) mode, "anon")) { + pThis->pNetOssl->authMode = OSSL_AUTH_CERTANON; + + } else { + LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by " + "ossl netstream driver", mode); + ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); + } + + dbgprintf("SetAuthMode: Set Mode %s/%d\n", mode, pThis->pNetOssl->authMode); + +finalize_it: + RETiRet; +} + + +/* Set the PermitExpiredCerts mode. For us, the following is supported: + * on - fail if certificate is expired + * off - ignore expired certificates + * warn - warn if certificate is expired + * alorbach, 2018-12-20 + */ +static rsRetVal +SetPermitExpiredCerts(nsd_t *pNsd, uchar *mode) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + /* default is set to off! */ + if(mode == NULL || !strcasecmp((char*)mode, "off")) { + pThis->permitExpiredCerts = OSSL_EXPIRED_DENY; + } else if(!strcasecmp((char*) mode, "warn")) { + pThis->permitExpiredCerts = OSSL_EXPIRED_WARN; + } else if(!strcasecmp((char*) mode, "on")) { + pThis->permitExpiredCerts = OSSL_EXPIRED_PERMIT; + } else { + LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: permitexpiredcerts mode '%s' not supported by " + "ossl netstream driver", mode); + ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); + } + + dbgprintf("SetPermitExpiredCerts: Set Mode %s/%d\n", mode, pThis->permitExpiredCerts); + +/* TODO: clear stored IDs! */ + +finalize_it: + RETiRet; +} + + +/* Set permitted peers. It is depending on the auth mode if this are + * fingerprints or names. -- rgerhards, 2008-05-19 + */ +static rsRetVal +SetPermPeers(nsd_t *pNsd, permittedPeers_t *pPermPeers) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + if(pPermPeers == NULL) + FINALIZE; + + if(pThis->pNetOssl->authMode != OSSL_AUTH_CERTFINGERPRINT && pThis->pNetOssl->authMode != OSSL_AUTH_CERTNAME) { + LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by " + "ossl netstream driver in the configured authentication mode - ignored"); + ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE); + } + pThis->pNetOssl->pPermPeers = pPermPeers; + +finalize_it: + RETiRet; +} + + +/* Provide access to the underlying OS socket. This is primarily + * useful for other drivers (like nsd_ossl) who utilize ourselfs + * for some of their functionality. -- rgerhards, 2008-04-18 + */ +static rsRetVal +SetSock(nsd_t *pNsd, int sock) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + ISOBJ_TYPE_assert((pThis), nsd_ossl); + assert(sock >= 0); + + DBGPRINTF("SetSock for [%p]: Setting sock %d\n", pNsd, sock); + nsd_ptcp.SetSock(pThis->pTcp, sock); + + RETiRet; +} + + +/* Keep Alive Options + */ +static rsRetVal +SetKeepAliveIntvl(nsd_t *pNsd, int keepAliveIntvl) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + assert(keepAliveIntvl >= 0); + + dbgprintf("SetKeepAliveIntvl: keepAliveIntvl=%d\n", keepAliveIntvl); + nsd_ptcp.SetKeepAliveIntvl(pThis->pTcp, keepAliveIntvl); + + RETiRet; +} + + +/* Keep Alive Options + */ +static rsRetVal +SetKeepAliveProbes(nsd_t *pNsd, int keepAliveProbes) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + assert(keepAliveProbes >= 0); + + dbgprintf("SetKeepAliveProbes: keepAliveProbes=%d\n", keepAliveProbes); + nsd_ptcp.SetKeepAliveProbes(pThis->pTcp, keepAliveProbes); + + RETiRet; +} + + +/* Keep Alive Options + */ +static rsRetVal +SetKeepAliveTime(nsd_t *pNsd, int keepAliveTime) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + assert(keepAliveTime >= 0); + + dbgprintf("SetKeepAliveTime: keepAliveTime=%d\n", keepAliveTime); + nsd_ptcp.SetKeepAliveTime(pThis->pTcp, keepAliveTime); + + RETiRet; +} + + +/* abort a connection. This is meant to be called immediately + * before the Destruct call. -- rgerhards, 2008-03-24 + */ +static rsRetVal +Abort(nsd_t *pNsd) +{ + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + DEFiRet; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + + if(pThis->iMode == 0) { + nsd_ptcp.Abort(pThis->pTcp); + } + + RETiRet; +} + + +/* Callback after netstrm obj init in nsd_ptcp - permits us to add some data */ +static rsRetVal +LstnInitDrvr(netstrm_t *const pThis) +{ + DEFiRet; + nsd_ossl_t* pNsdOssl = (nsd_ossl_t*) pThis->pDrvrData; + /* Create main CTX Object. Use SSLv23_method for < Openssl 1.1.0 and TLS_method for all newer versions! */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + CHKiRet(net_ossl.osslCtxInit(pNsdOssl->pNetOssl, SSLv23_method())); +#else + CHKiRet(net_ossl.osslCtxInit(pNsdOssl->pNetOssl, TLS_method())); +#endif + // Apply PriorityString after Ctx Creation + applyGnutlsPriorityString(pNsdOssl); +finalize_it: + RETiRet; +} + + +/* initialize the tcp socket for a listner + * Here, we use the ptcp driver - because there is nothing special + * at this point with OpenSSL. Things become special once we accept + * a session, but not during listener setup. + */ +static rsRetVal +LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + const int iSessMax, const tcpLstnParams_t *const cnf_params) +{ + DEFiRet; + + dbgprintf("LstnInit for openssl: entering LstnInit (%p) for %s:%s SessMax=%d\n", + fAddLstn, cnf_params->pszAddr, cnf_params->pszPort, iSessMax); + + pNS->fLstnInitDrvr = LstnInitDrvr; + /* Init TCP Listener using base ptcp class */ + iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, iSessMax, cnf_params); + RETiRet; +} + + +/* This function checks if the connection is still alive - well, kind of... + * This is a dummy here. For details, check function common in ptcp driver. + * rgerhards, 2008-06-09 + */ +static rsRetVal +CheckConnection(nsd_t __attribute__((unused)) *pNsd) +{ + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ossl); + + dbgprintf("CheckConnection for %p\n", pNsd); + return nsd_ptcp.CheckConnection(pThis->pTcp); +} + + +/* get the remote hostname. The returned hostname must be freed by the caller. + * rgerhards, 2008-04-25 + */ +static rsRetVal +GetRemoteHName(nsd_t *pNsd, uchar **ppszHName) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ossl); + iRet = nsd_ptcp.GetRemoteHName(pThis->pTcp, ppszHName); + dbgprintf("GetRemoteHName for %p = %s\n", pNsd, *ppszHName); + RETiRet; +} + + +/* Provide access to the sockaddr_storage of the remote peer. This + * is needed by the legacy ACL system. --- gerhards, 2008-12-01 + */ +static rsRetVal +GetRemAddr(nsd_t *pNsd, struct sockaddr_storage **ppAddr) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ossl); + iRet = nsd_ptcp.GetRemAddr(pThis->pTcp, ppAddr); + dbgprintf("GetRemAddr for %p\n", pNsd); + RETiRet; +} + + +/* get the remote host's IP address. Caller must Destruct the object. */ +static rsRetVal +GetRemoteIP(nsd_t *pNsd, prop_t **ip) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ossl); + iRet = nsd_ptcp.GetRemoteIP(pThis->pTcp, ip); + dbgprintf("GetRemoteIP for %p\n", pNsd); + RETiRet; +} + + +/* Perform all necessary checks after Handshake + */ +rsRetVal +osslPostHandshakeCheck(nsd_ossl_t *pNsd) +{ + DEFiRet; + char szDbg[255]; + const SSL_CIPHER* sslCipher; + + /* Some extra output for debugging openssl */ + if (SSL_get_shared_ciphers(pNsd->pNetOssl->ssl,szDbg, sizeof szDbg) != NULL) + dbgprintf("osslPostHandshakeCheck: Debug Shared ciphers = %s\n", szDbg); + + #if OPENSSL_VERSION_NUMBER >= 0x10002000L + if(SSL_get_shared_curve(pNsd->pNetOssl->ssl, -1) == 0) { + // This is not a failure + LogMsg(0, RS_RET_NO_ERRCODE, LOG_INFO, "nsd_ossl: " + "Information, no shared curve between syslog client and server"); + } + #endif + dbgprintf("osslPostHandshakeCheck: Debug Protocol Version: %s\n", + SSL_get_version(pNsd->pNetOssl->ssl)); + + sslCipher = (const SSL_CIPHER*) SSL_get_current_cipher(pNsd->pNetOssl->ssl); + if (sslCipher != NULL){ + if(SSL_CIPHER_get_version(sslCipher) == NULL) { + LogError(0, RS_RET_NO_ERRCODE, "nsd_ossl:" + "TLS version mismatch between syslog client and server."); + } + dbgprintf("osslPostHandshakeCheck: Debug Cipher Version: %s Name: %s\n", + SSL_CIPHER_get_version(sslCipher), SSL_CIPHER_get_name(sslCipher)); + }else { + LogError(0, RS_RET_NO_ERRCODE, "nsd_ossl:No shared ciphers between syslog client and server."); + } + + FINALIZE; + +finalize_it: + RETiRet; +} + + +/* Perform all necessary actions for Handshake + */ +rsRetVal +osslHandshakeCheck(nsd_ossl_t *pNsd) +{ + DEFiRet; + uchar *fromHostIP = NULL; + int res, resErr; + dbgprintf("osslHandshakeCheck: Starting TLS Handshake for ssl[%p]\n", (void *)pNsd->pNetOssl->ssl); + + if (pNsd->pNetOssl->sslState == osslServer) { + /* Handle Server SSL Object */ + if((res = SSL_accept(pNsd->pNetOssl->ssl)) <= 0) { + /* Obtain SSL Error code */ + nsd_ptcp.GetRemoteHName((nsd_t*)pNsd->pTcp, &fromHostIP); + resErr = SSL_get_error(pNsd->pNetOssl->ssl, res); + if( resErr == SSL_ERROR_WANT_READ || + resErr == SSL_ERROR_WANT_WRITE) { + pNsd->rtryCall = osslRtry_handshake; + pNsd->rtryOsslErr = resErr; /* Store SSL ErrorCode into*/ + dbgprintf("osslHandshakeCheck: OpenSSL Server handshake does not complete " + "immediately - setting to retry (this is OK and normal)\n"); + FINALIZE; + } else if(resErr == SSL_ERROR_SYSCALL) { + dbgprintf("osslHandshakeCheck: OpenSSL Server handshake failed with SSL_ERROR_SYSCALL " + "- Aborting handshake.\n"); + nsd_ossl_lastOpenSSLErrorMsg(pNsd, res, pNsd->pNetOssl->ssl, LOG_WARNING, + "osslHandshakeCheck Server", "SSL_accept"); + LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, + "nsd_ossl:TLS session terminated with remote client '%s': " + "Handshake failed with SSL_ERROR_SYSCALL", fromHostIP); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } else { + nsd_ossl_lastOpenSSLErrorMsg(pNsd, res, pNsd->pNetOssl->ssl, LOG_ERR, + "osslHandshakeCheck Server", "SSL_accept"); + LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, + "nsd_ossl:TLS session terminated with remote client '%s': " + "Handshake failed with error code: %d", fromHostIP, resErr); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } + } + } else { + /* Handle Client SSL Object */ + if((res = SSL_do_handshake(pNsd->pNetOssl->ssl)) <= 0) { + /* Obtain SSL Error code */ + nsd_ptcp.GetRemoteHName((nsd_t*)pNsd->pTcp, &fromHostIP); + resErr = SSL_get_error(pNsd->pNetOssl->ssl, res); + if( resErr == SSL_ERROR_WANT_READ || + resErr == SSL_ERROR_WANT_WRITE) { + pNsd->rtryCall = osslRtry_handshake; + pNsd->rtryOsslErr = resErr; /* Store SSL ErrorCode into*/ + dbgprintf("osslHandshakeCheck: OpenSSL Client handshake does not complete " + "immediately - setting to retry (this is OK and normal)\n"); + FINALIZE; + } else if(resErr == SSL_ERROR_SYSCALL) { + dbgprintf("osslHandshakeCheck: OpenSSL Client handshake failed with SSL_ERROR_SYSCALL " + "- Aborting handshake.\n"); + nsd_ossl_lastOpenSSLErrorMsg(pNsd, res, pNsd->pNetOssl->ssl, LOG_WARNING, + "osslHandshakeCheck Client", "SSL_do_handshake"); + ABORT_FINALIZE(RS_RET_NO_ERRCODE /*RS_RET_RETRY*/); + } else { + dbgprintf("osslHandshakeCheck: OpenSSL Client handshake failed with %d " + "- Aborting handshake.\n", resErr); + nsd_ossl_lastOpenSSLErrorMsg(pNsd, res, pNsd->pNetOssl->ssl, LOG_ERR, + "osslHandshakeCheck Client", "SSL_do_handshake"); + LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, + "nsd_ossl:TLS session terminated with remote syslog server '%s':" + "Handshake failed with error code: %d", fromHostIP, resErr); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } + } + } + + /* Do post handshake stuff */ + CHKiRet(osslPostHandshakeCheck(pNsd)); + + /* Now check authorization */ + CHKiRet(osslChkPeerAuth(pNsd)); +finalize_it: + if (fromHostIP != NULL) { + free(fromHostIP); + } + if(iRet == RS_RET_OK) { + /* If no error occurred, set socket to SSL mode */ + pNsd->iMode = 1; + } + + RETiRet; +} + +/* accept an incoming connection request - here, we do the usual accept + * handling. TLS specific handling is done thereafter (and if we run in TLS + * mode at this time). + * rgerhards, 2008-04-25 + */ +static rsRetVal +AcceptConnReq(nsd_t *pNsd, nsd_t **ppNew) +{ + DEFiRet; + nsd_ossl_t *pNew = NULL; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + CHKiRet(nsd_osslConstruct(&pNew)); + CHKiRet(nsd_ptcp.Destruct(&pNew->pTcp)); + dbgprintf("AcceptConnReq for [%p]: Accepting connection ... \n", (void *)pThis); + CHKiRet(nsd_ptcp.AcceptConnReq(pThis->pTcp, &pNew->pTcp)); + + if(pThis->iMode == 0) { + /*we are in non-TLS mode, so we are done */ + DBGPRINTF("AcceptConnReq: NOT in TLS mode!\n"); + *ppNew = (nsd_t*) pNew; + FINALIZE; + } + + /* If we reach this point, we are in TLS mode */ + pNew->pNetOssl->authMode = pThis->pNetOssl->authMode; + pNew->permitExpiredCerts = pThis->permitExpiredCerts; + pNew->pNetOssl->pPermPeers = pThis->pNetOssl->pPermPeers; + pNew->DrvrVerifyDepth = pThis->DrvrVerifyDepth; + pNew->gnutlsPriorityString = pThis->gnutlsPriorityString; + pNew->pNetOssl->ctx = pThis->pNetOssl->ctx; + pNew->pNetOssl->ctx_is_copy = 1; // do not free on pNew Destruction + CHKiRet(osslInitSession(pNew, osslServer)); + + /* Store nsd_ossl_t* reference in SSL obj */ + SSL_set_ex_data(pNew->pNetOssl->ssl, 0, pThis->pTcp); + SSL_set_ex_data(pNew->pNetOssl->ssl, 1, &pThis->permitExpiredCerts); + + /* We now do the handshake */ + CHKiRet(osslHandshakeCheck(pNew)); + + *ppNew = (nsd_t*) pNew; +finalize_it: + /* Accept appears to be done here */ + if(pNew != NULL) { + DBGPRINTF("AcceptConnReq: END iRet = %d, pNew=[%p], pNsd->rtryCall=%d\n", + iRet, pNew, pNew->rtryCall); + } + if(iRet != RS_RET_OK) { + if(pNew != NULL) { + nsd_osslDestruct(&pNew); + } + } + RETiRet; +} + + +/* receive data from a tcp socket + * The lenBuf parameter must contain the max buffer size on entry and contains + * the number of octets read on exit. This function + * never blocks, not even when called on a blocking socket. That is important + * for client sockets, which are set to block during send, but should not + * block when trying to read data. -- rgerhards, 2008-03-17 + * The function now follows the usual iRet calling sequence. + * With GnuTLS, we may need to restart a recv() system call. If so, we need + * to supply the SAME buffer on the retry. We can not assure this, as the + * caller is free to call us with any buffer location (and in current + * implementation, it is on the stack and extremely likely to change). To + * work-around this problem, we allocate a buffer ourselfs and always receive + * into that buffer. We pass data on to the caller only after we have received it. + * To save some space, we allocate that internal buffer only when it is actually + * needed, which means when we reach this function for the first time. To keep + * the algorithm simple, we always supply data only from the internal buffer, + * even if it is a single byte. As we have a stream, the caller must be prepared + * to accept messages in any order, so we do not need to take care about this. + * Please note that the logic also forces us to do some "faking" in select(), as + * we must provide a fake "is ready for readign" status if we have data inside our + * buffer. -- rgerhards, 2008-06-23 + */ +static rsRetVal +Rcv(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf, int *const oserr) +{ + DEFiRet; + ssize_t iBytesCopy; /* how many bytes are to be copied to the client buffer? */ + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ossl); + DBGPRINTF("Rcv for %p\n", pNsd); + + if(pThis->bAbortConn) + ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ); + + if(pThis->iMode == 0) { + CHKiRet(nsd_ptcp.Rcv(pThis->pTcp, pBuf, pLenBuf, oserr)); + FINALIZE; + } + + /* --- in TLS mode now --- */ + + /* Buffer logic applies only if we are in TLS mode. Here we + * assume that we will switch from plain to TLS, but never back. This + * assumption may be unsafe, but it is the model for the time being and I + * do not see any valid reason why we should switch back to plain TCP after + * we were in TLS mode. However, in that case we may lose something that + * is already in the receive buffer ... risk accepted. -- rgerhards, 2008-06-23 + */ + + if(pThis->pszRcvBuf == NULL) { + /* we have no buffer, so we need to malloc one */ + CHKmalloc(pThis->pszRcvBuf = malloc(NSD_OSSL_MAX_RCVBUF)); + pThis->lenRcvBuf = -1; + } + + /* now check if we have something in our buffer. If so, we satisfy + * the request from buffer contents. + */ + if(pThis->lenRcvBuf == -1) { /* no data present, must read */ + CHKiRet(osslRecordRecv(pThis)); + } + + if(pThis->lenRcvBuf == 0) { /* EOS */ + *oserr = errno; + ABORT_FINALIZE(RS_RET_CLOSED); + } + + /* if we reach this point, data is present in the buffer and must be copied */ + iBytesCopy = pThis->lenRcvBuf - pThis->ptrRcvBuf; + if(iBytesCopy > *pLenBuf) { + iBytesCopy = *pLenBuf; + } else { + pThis->lenRcvBuf = -1; /* buffer will be emptied below */ + } + + memcpy(pBuf, pThis->pszRcvBuf + pThis->ptrRcvBuf, iBytesCopy); + pThis->ptrRcvBuf += iBytesCopy; + *pLenBuf = iBytesCopy; + +finalize_it: + if (iRet != RS_RET_OK) { + if (iRet == RS_RET_CLOSED) { + if (pThis->pNetOssl->ssl != NULL) { + /* Set SSL Shutdown */ + SSL_shutdown(pThis->pNetOssl->ssl); + dbgprintf("osslRcv SSL_shutdown done\n"); + } + } else if (iRet != RS_RET_RETRY) { + /* We need to free the receive buffer in error error case unless a retry is wanted. , if we + * allocated one. -- rgerhards, 2008-12-03 -- moved here by alorbach, 2015-12-01 + */ + *pLenBuf = 0; + free(pThis->pszRcvBuf); + pThis->pszRcvBuf = NULL; + } else { + /* RS_RET_RETRY | Check for SSL Shutdown */ + if (SSL_get_shutdown(pThis->pNetOssl->ssl) == SSL_RECEIVED_SHUTDOWN) { + dbgprintf("osslRcv received SSL_RECEIVED_SHUTDOWN!\n"); + iRet = RS_RET_CLOSED; + + /* Send Shutdown message back */ + SSL_shutdown(pThis->pNetOssl->ssl); + } + } + } + dbgprintf("osslRcv return. nsd %p, iRet %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, + iRet, pThis->lenRcvBuf, pThis->ptrRcvBuf); + RETiRet; +} + + +/* send a buffer. On entry, pLenBuf contains the number of octets to + * write. On exit, it contains the number of octets actually written. + * If this number is lower than on entry, only a partial buffer has + * been written. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf) +{ + DEFiRet; + int iSent; + int err; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + DBGPRINTF("Send for %p\n", pNsd); + + ISOBJ_TYPE_assert(pThis, nsd_ossl); + + if(pThis->bAbortConn) + ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ); + + if(pThis->iMode == 0) { + CHKiRet(nsd_ptcp.Send(pThis->pTcp, pBuf, pLenBuf)); + FINALIZE; + } + + while(1) { + iSent = SSL_write(pThis->pNetOssl->ssl, pBuf, *pLenBuf); + if(iSent > 0) { + *pLenBuf = iSent; + break; + } else { + err = SSL_get_error(pThis->pNetOssl->ssl, iSent); + if( err == SSL_ERROR_ZERO_RETURN ) { + DBGPRINTF("Send: SSL_ERROR_ZERO_RETURN received, retry next time\n"); + ABORT_FINALIZE(RS_RET_RETRY); + } + else if(err == SSL_ERROR_SYSCALL) { + /* Output error and abort */ + nsd_ossl_lastOpenSSLErrorMsg(pThis, iSent, pThis->pNetOssl->ssl, LOG_INFO, + "Send", "SSL_write"); + iRet = RS_RET_NO_ERRCODE; + /* Check for underlaying socket errors **/ + if ( errno == ECONNRESET) { + dbgprintf("Send: SSL_ERROR_SYSCALL Connection was reset by remote\n"); + /* Connection was dropped from remote site */ + iRet = RS_RET_CLOSED; + } else { + DBGPRINTF("Send: SSL_ERROR_SYSCALL Errno %d\n", errno); + } + ABORT_FINALIZE(iRet); + } + else if(err != SSL_ERROR_WANT_READ && + err != SSL_ERROR_WANT_WRITE) { + /* Output error and abort */ + nsd_ossl_lastOpenSSLErrorMsg(pThis, iSent, pThis->pNetOssl->ssl, LOG_ERR, + "Send", "SSL_write"); + ABORT_FINALIZE(RS_RET_NO_ERRCODE); + } else { + /* Check for SSL Shutdown */ + if (SSL_get_shutdown(pThis->pNetOssl->ssl) == SSL_RECEIVED_SHUTDOWN) { + dbgprintf("osslRcv received SSL_RECEIVED_SHUTDOWN!\n"); + ABORT_FINALIZE(RS_RET_CLOSED); + } + } + } + } +finalize_it: + RETiRet; +} + + +/* Enable KEEPALIVE handling on the socket. + * rgerhards, 2009-06-02 + */ +static rsRetVal +EnableKeepAlive(nsd_t *pNsd) +{ + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ossl); + return nsd_ptcp.EnableKeepAlive(pThis->pTcp); +} + + +/* open a connection to a remote host (server). With OpenSSL, we always + * open a plain tcp socket and then, if in TLS mode, do a handshake on it. + */ +static rsRetVal +Connect(nsd_t *pNsd, int family, uchar *port, uchar *host, char *device) +{ + DEFiRet; + DBGPRINTF("openssl: entering Connect family=%d, device=%s\n", family, device); + nsd_ossl_t* pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert(pThis, nsd_ossl); + assert(port != NULL); + assert(host != NULL); + + /* Create main CTX Object. Use SSLv23_method for < Openssl 1.1.0 and TLS_method for all newer versions! */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + CHKiRet(net_ossl.osslCtxInit(pThis->pNetOssl, SSLv23_method())); +#else + CHKiRet(net_ossl.osslCtxInit(pThis->pNetOssl, TLS_method())); +#endif + // Apply PriorityString after Ctx Creation + applyGnutlsPriorityString(pThis); + + // Perform TCP Connect + CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host, device)); + + if(pThis->iMode == 0) { + /*we are in non-TLS mode, so we are done */ + DBGPRINTF("Connect: NOT in TLS mode!\n"); + FINALIZE; + } + + LogMsg(0, RS_RET_NO_ERRCODE, LOG_INFO, "nsd_ossl: " + "TLS Connection initiated with remote syslog server."); + /*if we reach this point we are in tls mode */ + DBGPRINTF("Connect: TLS Mode\n"); + + /* Do SSL Session init */ + CHKiRet(osslInitSession(pThis, osslClient)); + + /* Store nsd_ossl_t* reference in SSL obj */ + SSL_set_ex_data(pThis->pNetOssl->ssl, 0, pThis->pTcp); + SSL_set_ex_data(pThis->pNetOssl->ssl, 1, &pThis->permitExpiredCerts); + + /* We now do the handshake */ + iRet = osslHandshakeCheck(pThis); +finalize_it: + /* Connect appears to be done here */ + dbgprintf("Connect: END iRet = %d, pThis=[%p], pNsd->rtryCall=%d\n", + iRet, pThis, pThis->rtryCall); + if(iRet != RS_RET_OK) { + if(pThis->bHaveSess) { + pThis->bHaveSess = 0; + SSL_free(pThis->pNetOssl->ssl); + pThis->pNetOssl->ssl = NULL; + } + } + RETiRet; +} + +static rsRetVal +SetGnutlsPriorityString(nsd_t *const pNsd, uchar *const gnutlsPriorityString) +{ + DEFiRet; + nsd_ossl_t* pThis = (nsd_ossl_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ossl); + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER) + sbool ApplySettings = 0; + if ( (gnutlsPriorityString != NULL && pThis->gnutlsPriorityString == NULL) || + (gnutlsPriorityString != NULL && + strcmp( (const char*)pThis->gnutlsPriorityString, (const char*)gnutlsPriorityString) != 0) + ) { + ApplySettings = 1; + } + + pThis->gnutlsPriorityString = gnutlsPriorityString; + dbgprintf("gnutlsPriorityString: set to '%s' Apply %s\n", + (gnutlsPriorityString != NULL ? (char*)gnutlsPriorityString : "NULL"), + (ApplySettings == 1? "TRUE" : "FALSE")); + if (ApplySettings == 1) { + /* Apply Settings if necessary */ + applyGnutlsPriorityString(pThis); + } + +#else + LogError(0, RS_RET_SYS_ERR, "Warning: TLS library does not support SSL_CONF_cmd API" + "(maybe it is too old?). Cannot use gnutlsPriorityString ('%s'). For more see: " + "https://www.rsyslog.com/doc/master/configuration/modules/imtcp.html#gnutlsprioritystring", + gnutlsPriorityString); +#endif + RETiRet; +} + + +static rsRetVal +applyGnutlsPriorityString(nsd_ossl_t *const pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_ossl); + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER) + /* Note: we disable unkonwn functions. The corresponding error message is + * generated during SetGntuTLSPriorityString(). + */ + if(pThis->gnutlsPriorityString == NULL || pThis->pNetOssl->ctx == NULL) { + FINALIZE; + } else { + CHKiRet(net_ossl_apply_tlscgfcmd(pThis->pNetOssl, pThis->gnutlsPriorityString)); + } +#endif + +finalize_it: + RETiRet; +} + +/* Set the driver cert extended key usage check setting, for now it is empty wrapper. + * TODO: implement openSSL version + * jvymazal, 2019-08-16 + */ +static rsRetVal +SetCheckExtendedKeyUsage(nsd_t __attribute__((unused)) *pNsd, int ChkExtendedKeyUsage) +{ + DEFiRet; + if(ChkExtendedKeyUsage != 0) { + LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver ChkExtendedKeyUsage %d " + "not supported by ossl netstream driver", ChkExtendedKeyUsage); + ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); + } +finalize_it: + RETiRet; +} + +/* Set the driver name checking strictness, for now it is empty wrapper. + * TODO: implement openSSL version + * jvymazal, 2019-08-16 + */ +static rsRetVal +SetPrioritizeSAN(nsd_t __attribute__((unused)) *pNsd, int prioritizeSan) +{ + DEFiRet; + if(prioritizeSan != 0) { + LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver prioritizeSan %d " + "not supported by ossl netstream driver", prioritizeSan); + ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); + } +finalize_it: + RETiRet; +} + +/* Set the driver tls verifyDepth + * alorbach, 2019-12-20 + */ +static rsRetVal +SetTlsVerifyDepth(nsd_t *pNsd, int verifyDepth) +{ + DEFiRet; + nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + if (verifyDepth == 0) { + FINALIZE; + } + assert(verifyDepth >= 2); + pThis->DrvrVerifyDepth = verifyDepth; + +finalize_it: + RETiRet; +} + + +static rsRetVal +SetTlsCAFile(nsd_t *pNsd, const uchar *const caFile) +{ + DEFiRet; + nsd_ossl_t *const pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + if(caFile == NULL) { + pThis->pNetOssl->pszCAFile = NULL; + } else { + CHKmalloc(pThis->pNetOssl->pszCAFile = (const uchar*) strdup((const char*) caFile)); + } + +finalize_it: + RETiRet; +} + +static rsRetVal +SetTlsCRLFile(nsd_t *pNsd, const uchar *const crlFile) +{ + DEFiRet; + nsd_ossl_t *const pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + if(crlFile == NULL) { + pThis->pNetOssl->pszCRLFile = NULL; + } else { + CHKmalloc(pThis->pNetOssl->pszCRLFile = (const uchar*) strdup((const char*) crlFile)); + } + +finalize_it: + RETiRet; +} + + +static rsRetVal +SetTlsKeyFile(nsd_t *pNsd, const uchar *const pszFile) +{ + DEFiRet; + nsd_ossl_t *const pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + if(pszFile == NULL) { + pThis->pNetOssl->pszKeyFile = NULL; + } else { + CHKmalloc(pThis->pNetOssl->pszKeyFile = (const uchar*) strdup((const char*) pszFile)); + } + +finalize_it: + RETiRet; +} + +static rsRetVal +SetTlsCertFile(nsd_t *pNsd, const uchar *const pszFile) +{ + DEFiRet; + nsd_ossl_t *const pThis = (nsd_ossl_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_ossl); + if(pszFile == NULL) { + pThis->pNetOssl->pszCertFile = NULL; + } else { + CHKmalloc(pThis->pNetOssl->pszCertFile = (const uchar*) strdup((const char*) pszFile)); + } + +finalize_it: + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(nsd_ossl) +CODESTARTobjQueryInterface(nsd_ossl) + if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = (rsRetVal(*)(nsd_t**)) nsd_osslConstruct; + pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_osslDestruct; + pIf->Abort = Abort; + pIf->LstnInit = LstnInit; + pIf->AcceptConnReq = AcceptConnReq; + pIf->Rcv = Rcv; + pIf->Send = Send; + pIf->Connect = Connect; + pIf->SetSock = SetSock; + pIf->SetMode = SetMode; + pIf->SetAuthMode = SetAuthMode; + pIf->SetPermitExpiredCerts = SetPermitExpiredCerts; + pIf->SetPermPeers =SetPermPeers; + pIf->CheckConnection = CheckConnection; + pIf->GetRemoteHName = GetRemoteHName; + pIf->GetRemoteIP = GetRemoteIP; + pIf->GetRemAddr = GetRemAddr; + pIf->EnableKeepAlive = EnableKeepAlive; + pIf->SetKeepAliveIntvl = SetKeepAliveIntvl; + pIf->SetKeepAliveProbes = SetKeepAliveProbes; + pIf->SetKeepAliveTime = SetKeepAliveTime; + pIf->SetGnutlsPriorityString = SetGnutlsPriorityString; /* we don't NEED this interface! */ + pIf->SetCheckExtendedKeyUsage = SetCheckExtendedKeyUsage; /* we don't NEED this interface! */ + pIf->SetPrioritizeSAN = SetPrioritizeSAN; /* we don't NEED this interface! */ + pIf->SetTlsVerifyDepth = SetTlsVerifyDepth; + pIf->SetTlsCAFile = SetTlsCAFile; + pIf->SetTlsCRLFile = SetTlsCRLFile; + pIf->SetTlsKeyFile = SetTlsKeyFile; + pIf->SetTlsCertFile = SetTlsCertFile; + +finalize_it: +ENDobjQueryInterface(nsd_ossl) + + +/* exit our class + */ +BEGINObjClassExit(nsd_ossl, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsd_ossl) + /* release objects we no longer need */ + objRelease(net_ossl, CORE_COMPONENT); + objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME); + objRelease(net, LM_NET_FILENAME); + objRelease(glbl, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDObjClassExit(nsd_ossl) + + +/* Initialize the nsd_ossl class. Must be called as the very first method + * before anything else is called inside this class. + */ +BEGINObjClassInit(nsd_ossl, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME)); + CHKiRet(objUse(net_ossl, CORE_COMPONENT)); +ENDObjClassInit(nsd_ossl) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + nsdsel_osslClassExit(); + nsd_osslClassExit(); + net_osslClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(net_osslClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + CHKiRet(nsd_osslClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + CHKiRet(nsdsel_osslClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit |