/* This implements the relp mapping onto TCP.
*
* Copyright 2008-2018 by Rainer Gerhards and Adiscon GmbH.
*
* This file is part of librelp.
*
* Note: gnutls_certificate_set_verify_function is problematic, as it
* is not available in old GnuTLS versions, but rather important
* for verifying certificates correctly.
*
* Librelp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Librelp 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Librelp. If not, see <http://www.gnu.org/licenses/>.
*
* A copy of the GPL can be found in the file "COPYING" in this distribution.
*
* If the terms of the GPL are unsuitable for your needs, you may obtain
* a commercial license from Adiscon. Contact sales@adiscon.com for further
* details.
*
* ALL CONTRIBUTORS PLEASE NOTE that by sending contributions, you assign
* your copyright to Adiscon GmbH, Germany. This is necessary to permit the
* dual-licensing set forth here. Our apologies for this inconvenience, but
* we sincerely believe that the dual-licensing model helps us provide great
* free software while at the same time obtaining some funding for further
* development.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <assert.h>
#include "relp.h"
#include "relpsrv.h"
#include "relpclt.h"
#include "relpsess.h"
#include "tcp.h"
#ifdef ENABLE_TLS
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
# if GNUTLS_VERSION_NUMBER <= 0x020b00
# include <gcrypt.h>
GCRY_THREAD_OPTION_PTHREAD_IMPL;
# endif
static int called_gnutls_global_init = 0;
#endif
#ifndef SOL_TCP
# define SOL_TCP (getprotobyname("tcp")->p_proto)
#endif
/* AIXPORT : MSG_DONTWAIT not supported */
#if defined(_AIX) && !defined(MSG_DONTWAIT)
#define MSG_DONTWAIT MSG_NONBLOCK
#endif
#ifdef ENABLE_TLS
/* forward definitions */
#ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION
static int relpTcpVerifyCertificateCallback(gnutls_session_t session);
#endif /* #ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION */
static relpRetVal relpTcpPermittedPeerWildcardCompile(tcpPermittedPeerEntry_t *pEtry);
/* helper to free permittedPeer structure */
static inline void
relpTcpFreePermittedPeers(relpTcp_t *const pThis)
{
int i;
for(i = 0 ; i < pThis->permittedPeers.nmemb ; ++i)
free(pThis->permittedPeers.peer[i].name);
pThis->permittedPeers.nmemb = 0;
}
#endif /* #ifdef ENABLE_TLS */
/** Construct a RELP tcp instance
* This is the first thing that a caller must do before calling any
* RELP function. The relp tcp must only destructed after all RELP
* operations have been finished. Parameter pParent contains a pointer
* to the "parent" client or server object, depending on connType.
*/
relpRetVal
relpTcpConstruct(relpTcp_t **ppThis, relpEngine_t *const pEngine,
const int connType,
void *const pParent)
{
relpTcp_t *pThis;
ENTER_RELPFUNC;
assert(ppThis != NULL);
if((pThis = calloc(1, sizeof(relpTcp_t))) == NULL) {
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
RELP_CORE_CONSTRUCTOR(pThis, Tcp);
if(connType == RELP_SRV_CONN) {
pThis->pSrv = (relpSrv_t*) pParent;
} else {
pThis->pClt = (relpClt_t*) pParent;
}
pThis->sock = -1;
pThis->pEngine = pEngine;
pThis->iSessMax = 500; /* default max nbr of sessions - TODO: make configurable -- rgerhards, 2008-03-17*/
pThis->bTLSActive = 0;
pThis->dhBits = DEFAULT_DH_BITS;
pThis->pristring = NULL;
pThis->authmode = eRelpAuthMode_None;
pThis->caCertFile = NULL;
pThis->ownCertFile = NULL;
pThis->privKeyFile = NULL;
pThis->pUsr = NULL;
pThis->permittedPeers.nmemb = 0;
*ppThis = pThis;
finalize_it:
LEAVE_RELPFUNC;
}
/** Destruct a RELP tcp instance
*/
relpRetVal
relpTcpDestruct(relpTcp_t **ppThis)
{
relpTcp_t *pThis;
int i;
#ifdef ENABLE_TLS
int gnuRet;
#endif /* #ifdef ENABLE_TLS */
ENTER_RELPFUNC;
assert(ppThis != NULL);
pThis = *ppThis;
RELPOBJ_assert(pThis, Tcp);
if(pThis->sock != -1) {
close(pThis->sock);
pThis->sock = -1;
}
if(pThis->socks != NULL) {
/* if we have some sockets at this stage, we need to close them */
for(i = 1 ; i <= pThis->socks[0] ; ++i)
close(pThis->socks[i]);
free(pThis->socks);
}
#ifdef ENABLE_TLS
if(pThis->bTLSActive) {
gnuRet = gnutls_bye(pThis->session, GNUTLS_SHUT_RDWR);
while(gnuRet == GNUTLS_E_INTERRUPTED || gnuRet == GNUTLS_E_AGAIN) {
gnuRet = gnutls_bye(pThis->session, GNUTLS_SHUT_RDWR);
}
gnutls_deinit(pThis->session);
}
relpTcpFreePermittedPeers(pThis);
#endif /* #ifdef ENABLE_TLS */
free(pThis->pRemHostIP);
free(pThis->pRemHostName);
free(pThis->pristring);
free(pThis->caCertFile);
free(pThis->ownCertFile);
free(pThis->privKeyFile);
/* done with de-init work, now free tcp object itself */
free(pThis);
*ppThis = NULL;
LEAVE_RELPFUNC;
}
/* helper to call onErr if set */
static void
callOnErr(const relpTcp_t *__restrict__ const pThis,
char *__restrict__ const emsg,
const relpRetVal ecode)
{
char objinfo[1024];
pThis->pEngine->dbgprint("librelp: generic error: ecode %d, "
"emsg '%s'\n", ecode, emsg);
if(pThis->pEngine->onErr != NULL) {
if(pThis->pSrv == NULL) { /* client */
snprintf(objinfo, sizeof(objinfo), "conn to srvr %s:%s",
pThis->pClt->pSess->srvAddr,
pThis->pClt->pSess->srvPort);
} else if(pThis->pRemHostIP == NULL) { /* server listener */
snprintf(objinfo, sizeof(objinfo), "lstn %s",
pThis->pSrv->pLstnPort);
} else { /* server connection to client */
snprintf(objinfo, sizeof(objinfo), "lstn %s: conn to clt %s/%s",
pThis->pSrv->pLstnPort, pThis->pRemHostIP,
pThis->pRemHostName);
}
objinfo[sizeof(objinfo)-1] = '\0';
pThis->pEngine->onErr(pThis->pUsr, objinfo, emsg, ecode);
}
}
#ifdef ENABLE_TLS
/* helper to call an error code handler if gnutls failed. If there is a failure,
* an error message is pulled form gnutls and the error message properly
* populated.
* Returns 1 if an error was detected, 0 otherwise. This can be used as a
* shortcut for error handling (safes doing it twice).
*/
static int
chkGnutlsCode(relpTcp_t *const pThis, char *emsg, relpRetVal ecode, const int gnuRet)
{
char msgbuf[4096];
int r;
if(gnuRet == GNUTLS_E_SUCCESS) {
r = 0;
} else {
r = 1;
snprintf(msgbuf, sizeof(msgbuf), "%s [gnutls error %d: %s]",
emsg, gnuRet, gnutls_strerror(gnuRet));
msgbuf[sizeof(msgbuf)-1] = '\0';
callOnErr(pThis, msgbuf, ecode);
}
return r;
}
/* helper to call onAuthErr if set */
static inline void
callOnAuthErr(relpTcp_t *const pThis, char *authdata, char *emsg, relpRetVal ecode)
{
pThis->pEngine->dbgprint("librelp: auth error: authdata:'%s', ecode %d, "
"emsg '%s'\n", authdata, ecode, emsg);
if(pThis->pEngine->onAuthErr != NULL) {
pThis->pEngine->onAuthErr(pThis->pUsr, authdata, emsg, ecode);
}
}
#endif /* #ifdef ENABLE_TLS */
/* abort a tcp connection. This is much like relpTcpDestruct(), but tries
* to discard any unsent data. -- rgerhards, 2008-03-24
*/
relpRetVal
relpTcpAbortDestruct(relpTcp_t **ppThis)
{
struct linger ling;
ENTER_RELPFUNC;
assert(ppThis != NULL);
RELPOBJ_assert((*ppThis), Tcp);
if((*ppThis)->sock != -1) {
ling.l_onoff = 1;
ling.l_linger = 0;
if(setsockopt((*ppThis)->sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) < 0 ) {
(*ppThis)->pEngine->dbgprint("could not set SO_LINGER, errno %d\n", errno);
}
}
iRet = relpTcpDestruct(ppThis);
LEAVE_RELPFUNC;
}
#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
# define SALEN(sa) ((sa)->sa_len)
#else
static inline size_t SALEN(struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET: return (sizeof(struct sockaddr_in));
case AF_INET6: return (sizeof(struct sockaddr_in6));
default: return 0;
}
}
#endif
/* we may later change the criteria, thus we encapsulate it
* into a function.
*/
static inline int8_t
isAnonAuth(relpTcp_t *const pThis)
{
return pThis->ownCertFile == NULL;
}
/* Set pRemHost based on the address provided. This is to be called upon accept()ing
* a connection request. It must be provided by the socket we received the
* message on as well as a NI_MAXHOST size large character buffer for the FQDN.
* Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats)
* for some explanation of the code found below. If we detect a malicious
* hostname, we return RELP_RET_MALICIOUS_HNAME and let the caller decide
* on how to deal with that.
* rgerhards, 2008-03-31
*/
static relpRetVal
relpTcpSetRemHost(relpTcp_t *const pThis, struct sockaddr *pAddr)
{
relpEngine_t *pEngine;
int error;
unsigned char szIP[NI_MAXHOST] = "";
unsigned char szHname[NI_MAXHOST] = "";
struct addrinfo hints, *res;
size_t len;
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
pEngine = pThis->pEngine;
assert(pAddr != NULL);
error = getnameinfo(pAddr, SALEN(pAddr), (char*)szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST);
if(error) {
pThis->pEngine->dbgprint("Malformed from address %s\n", gai_strerror(error));
strcpy((char*)szHname, "???");
strcpy((char*)szIP, "???");
ABORT_FINALIZE(RELP_RET_INVALID_HNAME);
}
if(pEngine->bEnableDns) {
error = getnameinfo(pAddr, SALEN(pAddr), (char*)szHname, sizeof(szHname), NULL, 0, NI_NAMEREQD);
if(error == 0) {
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_socktype = SOCK_STREAM;
/* we now do a lookup once again. This one should fail,
* because we should not have obtained a non-numeric address. If
* we got a numeric one, someone messed with DNS!
*/
if(getaddrinfo((char*)szHname, NULL, &hints, &res) == 0) {
freeaddrinfo (res);
/* OK, we know we have evil, so let's indicate this to our caller */
snprintf((char*)szHname, NI_MAXHOST, "[MALICIOUS:IP=%s]", szIP);
pEngine->dbgprint("Malicious PTR record, IP = \"%s\" HOST = \"%s\"", szIP, szHname);
iRet = RELP_RET_MALICIOUS_HNAME;
}
} else {
strcpy((char*)szHname, (char*)szIP);
}
} else {
strcpy((char*)szHname, (char*)szIP);
}
/* We now have the names, so now let's allocate memory and store them permanently.
* (side note: we may hold on to these values for quite a while, thus we trim their
* memory consumption)
*/
len = strlen((char*)szIP) + 1; /* +1 for \0 byte */
if((pThis->pRemHostIP = malloc(len)) == NULL)
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
memcpy(pThis->pRemHostIP, szIP, len);
len = strlen((char*)szHname) + 1; /* +1 for \0 byte */
if((pThis->pRemHostName = malloc(len)) == NULL) {
free(pThis->pRemHostIP); /* prevent leak */
pThis->pRemHostIP = NULL;
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
memcpy(pThis->pRemHostName, szHname, len);
finalize_it:
LEAVE_RELPFUNC;
}
/* this copies a *complete* permitted peers structure into the
* tcp object.
*/
relpRetVal
relpTcpSetPermittedPeers(relpTcp_t __attribute__((unused)) *pThis,
relpPermittedPeers_t __attribute__((unused)) *pPeers)
{
ENTER_RELPFUNC;
#ifdef ENABLE_TLS
int i;
relpTcpFreePermittedPeers(pThis);
if(pPeers->nmemb != 0) {
if((pThis->permittedPeers.peer =
malloc(sizeof(tcpPermittedPeerEntry_t) * pPeers->nmemb)) == NULL) {
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
for(i = 0 ; i < pPeers->nmemb ; ++i) {
if((pThis->permittedPeers.peer[i].name = strdup(pPeers->name[i])) == NULL) {
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
pThis->permittedPeers.peer[i].wildcardRoot = NULL;
pThis->permittedPeers.peer[i].wildcardLast = NULL;
CHKRet(relpTcpPermittedPeerWildcardCompile(&(pThis->permittedPeers.peer[i])));
}
}
pThis->permittedPeers.nmemb = pPeers->nmemb;
#else
ABORT_FINALIZE(RELP_RET_ERR_NO_TLS);
#endif /* #ifdef ENABLE_TLS */
finalize_it:
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpSetUsrPtr(relpTcp_t *const pThis, void *pUsr)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
pThis->pUsr = pUsr;
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpSetAuthMode(relpTcp_t *const pThis, relpAuthMode_t authmode)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
pThis->authmode = authmode;
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpSetConnTimeout(relpTcp_t *const pThis, const int connTimeout)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
pThis->connTimeout = connTimeout;
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpSetGnuTLSPriString(relpTcp_t *const pThis, char *pristr)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
free(pThis->pristring);
if(pristr == NULL) {
pThis->pristring = NULL;
} else {
if((pThis->pristring = strdup(pristr)) == NULL)
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
finalize_it:
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpSetCACert(relpTcp_t *const pThis, char *cert)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
free(pThis->caCertFile);
if(cert == NULL) {
pThis->caCertFile = NULL;
} else {
if((pThis->caCertFile = strdup(cert)) == NULL)
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
finalize_it:
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpSetOwnCert(relpTcp_t *const pThis, char *cert)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
free(pThis->ownCertFile);
if(cert == NULL) {
pThis->ownCertFile = NULL;
} else {
if((pThis->ownCertFile = strdup(cert)) == NULL)
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
finalize_it:
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpSetPrivKey(relpTcp_t *const pThis, char *cert)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
free(pThis->privKeyFile);
if(cert == NULL) {
pThis->privKeyFile = NULL;
} else {
# ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION
if((pThis->privKeyFile = strdup(cert)) == NULL)
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
# else
ABORT_FINALIZE(RELP_RET_ERR_NO_TLS_AUTH);
# endif
}
finalize_it:
LEAVE_RELPFUNC;
}
/* Enable TLS mode. */
relpRetVal
relpTcpEnableTLS(relpTcp_t __attribute__((unused)) *pThis)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
#ifdef ENABLE_TLS
pThis->bEnableTLS = 1;
#else
iRet = RELP_RET_ERR_NO_TLS;
#endif /* #ifdef ENABLE_TLS */
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpEnableTLSZip(relpTcp_t __attribute__((unused)) *pThis)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
#ifdef ENABLE_TLS
pThis->bEnableTLSZip = 1;
#else
iRet = RELP_RET_ERR_NO_TLS;
#endif /* #ifdef ENABLE_TLS */
LEAVE_RELPFUNC;
}
relpRetVal
relpTcpSetDHBits(relpTcp_t *const pThis, const int bits)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
pThis->dhBits = bits;
LEAVE_RELPFUNC;
}
#ifdef ENABLE_TLS
/* set TLS priority string, common code both for client and server */
static relpRetVal
relpTcpTLSSetPrio(relpTcp_t *const pThis)
{
int r;
ENTER_RELPFUNC;
r = gnutls_set_default_priority(pThis->session);
if(r == GNUTLS_E_INVALID_REQUEST) {
ABORT_FINALIZE(RELP_RET_INVLD_TLS_PRIO);
} else if(r != GNUTLS_E_SUCCESS) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
finalize_it:
if(iRet != RELP_RET_OK)
chkGnutlsCode(pThis, "Failed to set GnuTLS priority", iRet, r);
LEAVE_RELPFUNC;
}
#ifndef _AIX
#pragma GCC diagnostic push
/* per https://lists.gnupg.org/pipermail/gnutls-help/2004-August/000154.html This is expected */
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
#endif
static relpRetVal
relpTcpAcceptConnReqInitTLS(relpTcp_t *const pThis, relpSrv_t *const pSrv)
{
int r;
ENTER_RELPFUNC;
r = gnutls_init(&pThis->session, GNUTLS_SERVER);
if(chkGnutlsCode(pThis, "Failed to initialize GnuTLS", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
gnutls_session_set_ptr(pThis->session, pThis);
if(pSrv->pTcp->pristring != NULL)
pThis->pristring = strdup(pSrv->pTcp->pristring);
pThis->authmode = pSrv->pTcp->authmode;
pThis->pUsr = pSrv->pUsr;
CHKRet(relpTcpTLSSetPrio(pThis));
if(isAnonAuth(pSrv->pTcp)) {
r = gnutls_credentials_set(pThis->session, GNUTLS_CRD_ANON, pSrv->pTcp->anoncredSrv);
if(chkGnutlsCode(pThis, "Failed setting anonymous credentials", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
} else { /* cert-based auth */
if(pSrv->pTcp->caCertFile == NULL) {
gnutls_certificate_send_x509_rdn_sequence(pThis->session, 0);
}
r = gnutls_credentials_set(pThis->session, GNUTLS_CRD_CERTIFICATE, pSrv->pTcp->xcred);
if(chkGnutlsCode(pThis, "Failed setting certificate credentials", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
}
gnutls_dh_set_prime_bits(pThis->session, pThis->dhBits);
gnutls_certificate_server_set_request(pThis->session, GNUTLS_CERT_REQUEST);
gnutls_transport_set_ptr(pThis->session, (gnutls_transport_ptr_t) pThis->sock);
r = gnutls_handshake(pThis->session);
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) {
pThis->pEngine->dbgprint("librelp: gnutls_handshake retry necessary (this is OK and expected)\n");
pThis->rtryOp = relpTCP_RETRY_handshake;
} else if(r != GNUTLS_E_SUCCESS) {
chkGnutlsCode(pThis, "TLS handshake failed", RELP_RET_ERR_TLS_HANDS, r);
ABORT_FINALIZE(RELP_RET_ERR_TLS_HANDS);
}
pThis->bTLSActive = 1;
finalize_it:
LEAVE_RELPFUNC;
}
#ifndef _AIX
#pragma GCC diagnostic pop
#endif
#endif /* #ifdef ENABLE_TLS */
/* Enable KEEPALIVE handling on the socket. */
static void
EnableKeepAlive(const relpTcp_t *__restrict__ const pThis,
const relpSrv_t *__restrict__ const pSrv,
const int sock)
{
int ret;
int optval;
socklen_t optlen;
optval = 1;
optlen = sizeof(optval);
ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);
if(ret < 0) {
pThis->pEngine->dbgprint("librelp: EnableKeepAlive socket call "
"returns error %d\n", ret);
goto done;
}
# if defined(TCP_KEEPCNT)
if(pSrv->iKeepAliveProbes > 0) {
optval = pSrv->iKeepAliveProbes;
optlen = sizeof(optval);
ret = setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &optval, optlen);
} else {
ret = 0;
}
# else
ret = -1;
# endif
if(ret < 0) {
callOnErr(pThis, "librelp cannot set keepalive probes - ignored",
RELP_RET_WRN_NO_KEEPALIVE);
}
# if defined(TCP_KEEPCNT)
if(pSrv->iKeepAliveTime > 0) {
optval = pSrv->iKeepAliveTime;
optlen = sizeof(optval);
ret = setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &optval, optlen);
} else {
ret = 0;
}
# else
ret = -1;
# endif
if(ret < 0) {
callOnErr(pThis, "librelp cannot set keepalive time - ignored",
RELP_RET_WRN_NO_KEEPALIVE);
}
# if defined(TCP_KEEPCNT)
if(pSrv->iKeepAliveIntvl > 0) {
optval = pSrv->iKeepAliveIntvl;
optlen = sizeof(optval);
ret = setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &optval, optlen);
} else {
ret = 0;
}
# else
ret = -1;
# endif
if(ret < 0) {
callOnErr(pThis, "librelp cannot set keepalive intvl - ignored",
RELP_RET_WRN_NO_KEEPALIVE);
}
// pThis->pEngine->dbgprint("KEEPALIVE enabled for socket %d\n", sock);
done:
return;
}
/* a portable way to put the current thread asleep. Note that
* using the sleep() API family may result in the whole process
* to be put asleep on some platforms.
*/
static void
doSleep(int iSeconds, const int iuSeconds)
{
struct timeval tvSelectTimeout;
tvSelectTimeout.tv_sec = iSeconds;
tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */
select(0, NULL, NULL, NULL, &tvSelectTimeout);
}
/* accept an incoming connection request, sock provides the socket on which we can
* accept the new session.
* rgerhards, 2008-03-17
*/
relpRetVal
relpTcpAcceptConnReq(relpTcp_t **ppThis, const int sock, relpSrv_t *const pSrv)
{
relpTcp_t *pThis = NULL;
int sockflags;
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
int iNewSock = -1;
relpEngine_t *pEngine = pSrv->pEngine;
ENTER_RELPFUNC;
assert(ppThis != NULL);
iNewSock = accept(sock, (struct sockaddr*) &addr, &addrlen);
int errnosave = errno;
if(iNewSock < 0) {
pSrv->pEngine->dbgprint("error during accept, sleeping 20ms: %s\n",
strerror(errnosave));
doSleep(0, 20000);
pSrv->pEngine->dbgprint("END SLEEP\n");
ABORT_FINALIZE(RELP_RET_ACCEPT_ERR);
}
/* construct our object so that we can use it... */
CHKRet(relpTcpConstruct(&pThis, pEngine, RELP_SRV_CONN, pSrv));
if(pSrv->bKeepAlive)
EnableKeepAlive(pThis, pSrv, iNewSock);
/* TODO: obtain hostname, normalize (callback?), save it */
CHKRet(relpTcpSetRemHost(pThis, (struct sockaddr*) &addr));
pThis->pEngine->dbgprint("remote host is '%s', ip '%s'\n", pThis->pRemHostName, pThis->pRemHostIP);
/* set the new socket to non-blocking IO */
if((sockflags = fcntl(iNewSock, F_GETFL)) != -1) {
sockflags |= O_NONBLOCK;
/* SETFL could fail too, so get it caught by the subsequent
* error check.
*/
sockflags = fcntl(iNewSock, F_SETFL, sockflags);
}
if(sockflags == -1) {
pThis->pEngine->dbgprint("error %d setting fcntl(O_NONBLOCK) on relp socket %d", errno, iNewSock);
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
pThis->sock = iNewSock;
#ifdef ENABLE_TLS
if(pSrv->pTcp->bEnableTLS) {
pThis->bEnableTLS = 1;
pThis->pSrv = pSrv;
CHKRet(relpTcpSetPermittedPeers(pThis, &(pSrv->permittedPeers)));
CHKRet(relpTcpAcceptConnReqInitTLS(pThis, pSrv));
}
#endif /* #ifdef ENABLE_TLS */
*ppThis = pThis;
finalize_it:
if(iRet != RELP_RET_OK) {
if(pThis != NULL)
relpTcpDestruct(&pThis);
/* the close may be redundant, but that doesn't hurt... */
if(iNewSock >= 0)
close(iNewSock);
}
LEAVE_RELPFUNC;
}
#ifdef ENABLE_TLS
#ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION
/* Glue to use the right type of function depending of the version */
#if GNUTLS_VERSION_NUMBER < 0x030202
//gnutls_mac_algorithm_t and gnutls_digest_algorithm_t are aligned
//So we can use the result to get the fingerprint without trouble
typedef gnutls_mac_algorithm_t digest_id_t;
digest_id_t digest_get_id(const char * name){return gnutls_mac_get_id (name);}
const char* digest_get_name(digest_id_t id){return gnutls_mac_get_name (id);}
# define UNK_DIGEST GNUTLS_MAC_UNKNOWN
#else
typedef gnutls_digest_algorithm_t digest_id_t;
digest_id_t digest_get_id(const char * name){return gnutls_digest_get_id (name);}
const char* digest_get_name(digest_id_t id){return gnutls_digest_get_name (id);}
# define UNK_DIGEST GNUTLS_DIG_UNKNOWN
#endif
/* Convert a fingerprint to printable data. The function must be provided a
* sufficiently large buffer. 1024 bytes shall always do (for sha512 and future).
* To the size of the hexadecimal fingerprint, we must add the name of the fingerprint, and the separators.
* Warn : if the size of the buffer isn't sufficient, then the data is truncated.
*/
static void
GenFingerprintStr(const char *pFingerprint,const int sizeFingerprint,
char * fpBuf,const size_t bufLen,
const digest_id_t type,relpEngine_t * pEngine)
{
int iSrc, iDst;
size_t sizeTotal=0,sizeDigest=0;
//statically assigned char*, no free.
const char* digestType=digest_get_name(type);
if (NULL==digestType)
{
if (pEngine!=NULL)
pEngine->dbgprint("warn : the signature type %d is unknown\n",type);
digestType="0000";
}
sizeDigest=strlen(digestType);
//digestname + 3 char by byte (:xx) + last '\0'
sizeTotal=sizeDigest+(sizeFingerprint*3)+1;
if (sizeTotal <bufLen)
{
strncpy(fpBuf,digestType,sizeDigest);
iDst=sizeDigest;
for(iSrc = 0; iSrc < sizeFingerprint ; ++iSrc, iDst += 3) {
sprintf(fpBuf+iDst, ":%2.2X", (unsigned char) pFingerprint[iSrc]);
}
}else if(bufLen>=1){
if (pEngine!=NULL)
pEngine->dbgprint("warn: buffer overflow for %s signature\n",digestType);
fpBuf[0]='\0';//an empty string
}else{
if (pEngine!=NULL)
pEngine->dbgprint("warn: buffer empty, unable to print the signature\n");
}
}
#define MAX_DIGEST_PEER 10
static size_t ListDigestPeer(digest_id_t* listSigPeer,
tcpPermittedPeers_t* listPeers,relpEngine_t* pEngine)
{
int i;
int maxDigest=0;
//No signature name of more than 32 bytes.
//Most take 4. Some are slightly longer (SHA3_256, ...)
char digest[32];
if (NULL==listPeers || listPeers->nmemb<=0 )
{
if (pEngine!=NULL) pEngine->dbgprint("warn: no PermittedPeer listed\n");
return 0;
}
for (i=0; i<listPeers->nmemb;++i)
{
if ((listPeers->peer[i].name)!=NULL)
{
char*eow=strchr(listPeers->peer[i].name,':');//The first separator
if (eow!=NULL)
{
int sizeDigest=(int)(eow-(listPeers->peer[i].name));
//31= sizeof(digest)-1;
sizeDigest = sizeDigest > 31 ? 31: sizeDigest;
strncpy(digest,listPeers->peer[i].name,sizeDigest);
digest[sizeDigest]='\0';
digest_id_t actualDigest=digest_get_id(digest);
if (actualDigest!=UNK_DIGEST)
{
int alreadyExist=0;
int j;
for (j=0;j<maxDigest && alreadyExist==0 && j <MAX_DIGEST_PEER;++j)
{
if (listSigPeer[j]==actualDigest)
alreadyExist=1;
}
if (maxDigest<MAX_DIGEST_PEER && alreadyExist==0)
{
if (pEngine!=NULL)
pEngine->dbgprint("DDDD: adding digest %s\n",
digest);
listSigPeer[maxDigest++]=actualDigest;
}
}
}
}
}
return maxDigest;
}
/* Check the peer's ID in fingerprint auth mode. */
static int
relpTcpChkPeerFingerprint(relpTcp_t *const pThis, gnutls_x509_crt_t cert)
{
int r = 0;
int i;
char fingerprint[126];
char fpPrintable[256];
digest_id_t listSigPeer[MAX_DIGEST_PEER];
size_t maxDigest,k;
size_t size;
int8_t found;
/* List which digest we have in our permittedPeer list. We verify only the first MAX_DIGEST_PEER.*/
maxDigest=ListDigestPeer(listSigPeer,&(pThis->permittedPeers),pThis->pEngine);
/* obtain the SHA1 fingerprint */
found = 0;
for(k=0; k<maxDigest && found==0;++k)
{
digest_id_t digest=listSigPeer[k];
size = sizeof(fingerprint);
r = gnutls_x509_crt_get_fingerprint(cert, (gnutls_digest_algorithm_t) digest, fingerprint, &size);
if(chkGnutlsCode(pThis, "Failed to obtain fingerprint from certificate", RELP_RET_ERR_TLS, r)) {
r = GNUTLS_E_CERTIFICATE_ERROR; goto done;
}
GenFingerprintStr(fingerprint, (int) size,
(char*)fpPrintable,sizeof(fpPrintable),digest,pThis->pEngine);
pThis->pEngine->dbgprint("peer's certificate %s fingerprint: %s\n",
digest_get_name(digest), fpPrintable);
/* now search through the permitted peers to see if we can find a permitted one */
pThis->pEngine->dbgprint("n peers %d\n", pThis->permittedPeers.nmemb);
for(i = 0 ; i < pThis->permittedPeers.nmemb ; ++i) {
pThis->pEngine->dbgprint("checking peer '%s','%s'\n",
fpPrintable, pThis->permittedPeers.peer[i].name);
if(!strcmp(fpPrintable, pThis->permittedPeers.peer[i].name)) {
found = 1;
break;
}
}
}
if(!found) {
r = GNUTLS_E_CERTIFICATE_ERROR; goto done;
}
done:
if(r != 0) {
callOnAuthErr(pThis, fpPrintable, "non-permited fingerprint", RELP_RET_AUTH_ERR_FP);
}
return r;
}
#endif /* #ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION */
/* add a wildcard entry to this permitted peer. Entries are always
* added at the tail of the list. pszStr and lenStr identify the wildcard
* entry to be added. Note that the string is NOT \0 terminated, so
* we must rely on lenStr for when it is finished.
* rgerhards, 2008-05-27
*/
static relpRetVal
AddPermittedPeerWildcard(tcpPermittedPeerEntry_t *pEtry, char* pszStr, const int lenStr)
{
tcpPermittedPeerWildcardComp_t *pNew = NULL;
int iSrc;
int iDst;
ENTER_RELPFUNC;
if((pNew = calloc(1, sizeof(tcpPermittedPeerWildcardComp_t))) == NULL) {
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
if(lenStr == 0) {
pNew->wildcardType = tcpPEER_WILDCARD_EMPTY_COMPONENT;
FINALIZE;
} else {
/* alloc memory for the domain component. We may waste a byte or
* two, but that's ok.
*/
if((pNew->pszDomainPart = malloc(lenStr +1 )) == NULL) {
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
}
if(pszStr[0] == '*') {
pNew->wildcardType = tcpPEER_WILDCARD_AT_START;
iSrc = 1; /* skip '*' */
} else {
iSrc = 0;
}
for(iDst = 0 ; iSrc < lenStr && pszStr[iSrc] != '*' ; ++iSrc, ++iDst) {
pNew->pszDomainPart[iDst] = pszStr[iSrc];
}
if(iSrc < lenStr) {
if(iSrc + 1 == lenStr && pszStr[iSrc] == '*') {
if(pNew->wildcardType == tcpPEER_WILDCARD_AT_START) {
ABORT_FINALIZE(RELP_RET_INVLD_WILDCARD);
} else {
pNew->wildcardType = tcpPEER_WILDCARD_AT_END;
}
} else {
/* we have an invalid wildcard, something follows the asterisk! */
ABORT_FINALIZE(RELP_RET_INVLD_WILDCARD);
}
}
if(lenStr == 1 && pNew->wildcardType == tcpPEER_WILDCARD_AT_START) {
pNew->wildcardType = tcpPEER_WILDCARD_MATCH_ALL;
}
/* if we reach this point, we had a valid wildcard. We now need to
* properly terminate the domain component string.
*/
pNew->pszDomainPart[iDst] = '\0';
pNew->lenDomainPart = strlen((char*)pNew->pszDomainPart);
finalize_it:
if(iRet != RELP_RET_OK) {
if(pNew != NULL) {
if(pNew->pszDomainPart != NULL)
free(pNew->pszDomainPart);
free(pNew);
}
} else {
/* add the element to linked list */
if(pEtry->wildcardRoot == NULL) {
pEtry->wildcardRoot = pNew;
pEtry->wildcardLast = pNew;
} else {
pEtry->wildcardLast->pNext = pNew;
}
pEtry->wildcardLast = pNew;
}
LEAVE_RELPFUNC;
}
/* Compile a wildcard - must not yet be compiled */
static relpRetVal
relpTcpPermittedPeerWildcardCompile(tcpPermittedPeerEntry_t *pEtry)
{
char *pC;
char *pStart;
ENTER_RELPFUNC;
/* first check if we have a wildcard */
for(pC = pEtry->name ; *pC != '\0' && *pC != '*' ; ++pC)
/*EMPTY, just skip*/;
if(*pC == '\0') { /* no wildcard found, we are done */
FINALIZE;
}
/* if we reach this point, the string contains wildcards. So let's
* compile the structure. To do so, we must parse from dot to dot
* and create a wildcard entry for each domain component we find.
* We must also flag problems if we have an asterisk in the middle
* of the text (it is supported at the start or end only).
*/
pC = pEtry->name;
while(*pC) {
pStart = pC;
/* find end of domain component */
for( ; *pC != '\0' && *pC != '.' ; ++pC)
/*EMPTY, just skip*/;
CHKRet(AddPermittedPeerWildcard(pEtry, pStart, pC - pStart));
/* now check if we have an empty component at end of string */
if(*pC == '.' && *(pC + 1) == '\0') {
/* pStart is a dummy, it is not used if length is 0 */
CHKRet(AddPermittedPeerWildcard(pEtry, pStart, 0));
}
if(*pC != '\0')
++pC;
}
finalize_it:
LEAVE_RELPFUNC;
}
#ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION
/* check a peer against a wildcard entry. This is a more lengthy
* operation.
*/
static void
relpTcpChkOnePeerWildcard(tcpPermittedPeerWildcardComp_t *pRoot,
const char *peername,
int *pbFoundPositiveMatch,
relpEngine_t *const pEngine)
{
tcpPermittedPeerWildcardComp_t *pWildcard;
const char *pC;
const char *pStart; /* start of current domain component */
int iWildcard, iName; /* work indexes for backward comparisons */
*pbFoundPositiveMatch = 0;
pWildcard = pRoot;
pC = peername;
while(*pC != '\0') {
if(pWildcard == NULL) {
/* we have more domain components than we have wildcards --> no match */
goto done;
}
pStart = pC;
while(*pC != '\0' && *pC != '.') {
++pC;
}
/* got the component, now do the match */
switch(pWildcard->wildcardType) {
case tcpPEER_WILDCARD_NONE:
if( pWildcard->lenDomainPart != pC - pStart
|| strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pC - pStart)) {
goto done;
}
break;
case tcpPEER_WILDCARD_AT_START:
/* we need to do the backwards-matching manually */
if(pWildcard->lenDomainPart > pC - pStart) {
goto done;
}
iName = (size_t) (pC - pStart) - pWildcard->lenDomainPart;
iWildcard = 0;
while(iWildcard < pWildcard->lenDomainPart) {
if(pWildcard->pszDomainPart[iWildcard] != pStart[iName]) {
goto done;
}
++iName;
++iWildcard;
}
break;
case tcpPEER_WILDCARD_AT_END:
if( pWildcard->lenDomainPart > pC - pStart
|| strncmp((char*)pStart, (char*)pWildcard->pszDomainPart,
pWildcard->lenDomainPart)) {
goto done;
}
break;
case tcpPEER_WILDCARD_MATCH_ALL:
/* everything is OK, just continue */
break;
case tcpPEER_WILDCARD_EMPTY_COMPONENT:
if(pC - pStart > 0) {
/* if it is not empty, it is no match... */
goto done;
}
break;
default:assert(0); /* make sure we die when debugging */
relpEngineCallOnGenericErr(pEngine,
"librelp", RELP_RET_ERR_INTERNAL,
"invalid wildcardType %d in %s:%d",
pWildcard->wildcardType, __FILE__, __LINE__);
break;
}
pWildcard = pWildcard->pNext; /* we processed this entry */
/* skip '.' if we had it and so prepare for next iteration */
if(*pC == '.')
++pC;
}
/* we need to adjust for a border case, that is if the last component is
* empty. That happens frequently if the domain root (e.g. "example.com.")
* is properly given.
*/
if(pWildcard != NULL && pWildcard->wildcardType == tcpPEER_WILDCARD_EMPTY_COMPONENT)
pWildcard = pWildcard->pNext;
if(pWildcard != NULL) {
/* we have more domain components than in the name to be
* checked. So this is no match.
*/
goto done;
}
*pbFoundPositiveMatch = 1;
done: return;
}
/* Perform a match on ONE peer name obtained from the certificate. This name
* is checked against the set of configured credentials. *pbFoundPositiveMatch is
* set to 1 if the ID matches. *pbFoundPositiveMatch must have been initialized
* to 0 by the caller (this is a performance enhancement as we expect to be
* called multiple times).
*/
static void
relpTcpChkOnePeerName(relpTcp_t *const pThis, char *peername, int *pbFoundPositiveMatch)
{
int i;
for(i = 0 ; i < pThis->permittedPeers.nmemb ; ++i) {
if(pThis->permittedPeers.peer[i].wildcardRoot == NULL) {
/* simple string, only, no wildcards */
if(!strcmp(peername, pThis->permittedPeers.peer[i].name)) {
*pbFoundPositiveMatch = 1;
break;
}
} else {
relpTcpChkOnePeerWildcard(pThis->permittedPeers.peer[i].wildcardRoot,
peername, pbFoundPositiveMatch, pThis->pEngine);
if (*pbFoundPositiveMatch)
break;
}
}
}
/* Obtain the CN from the DN field and hand it back to the caller
* (which is responsible for destructing it). We try to follow
* RFC2253 as far as it makes sense for our use-case. This function
* is considered a compromise providing good-enough correctness while
* limiting code size and complexity. If a problem occurs, we may enhance
* this function. A (pointer to a) certificate must be caller-provided.
* The buffer for the name (namebuf) must also be caller-provided. A
* size of 1024 is most probably sufficien. The
* function returns 0 if all went well, something else otherwise.
* Note that non-0 is also returned if no CN is found.
*/
static int
relpTcpGetCN(relpTcp_t *const pThis, gnutls_x509_crt_t cert, char *namebuf, const int lenNamebuf)
{
int r;
int gnuRet;
int i,j;
int bFound;
size_t size;
char szDN[1024]; /* this should really be large enough for any non-malicious case... */
size = sizeof(szDN);
gnuRet = gnutls_x509_crt_get_dn(cert, (char*)szDN, &size);
if(chkGnutlsCode(pThis, "Failed to obtain DN from certificate", RELP_RET_ERR_TLS, gnuRet)) {
r = 1; goto done;
}
/* now search for the CN part */
i = 0;
bFound = 0;
while(!bFound && szDN[i] != '\0') {
/* note that we do not overrun our string due to boolean shortcut
* operations. If we have '\0', the if does not match and evaluation
* stops. Order of checks is obviously important!
*/
if(szDN[i] == 'C' && szDN[i+1] == 'N' && szDN[i+2] == '=') {
bFound = 1;
i += 2;
}
i++;
}
if(!bFound) {
r = 1; goto done;
}
/* we found a common name, now extract it */
j = 0;
while(szDN[i] != '\0' && szDN[i] != ',' && j < lenNamebuf-1) {
if(szDN[i] == '\\') {
/* hex escapes are not implemented */
r = 2; goto done;
} else {
namebuf[j++] = szDN[i];
}
++i; /* char processed */
}
namebuf[j] = '\0';
/* we got it - we ignore the rest of the DN string (if any). So we may
* not detect if it contains more than one CN
*/
r = 0;
done:
return r;
}
/* helper to consistently add names to error message buffer */
static int
relpTcpAddToCertNamesBuffer(relpTcp_t *const pThis,
char *const buf,
const size_t buflen,
int *p_currIdx,
const char *const certName)
{
int r = 0;
assert(buf != NULL);
assert(p_currIdx != NULL);
const int currIdx = *p_currIdx;
const int n = snprintf(buf + currIdx, buflen - currIdx,
"DNSname: %s; ", certName);
if(n < 0 || n >= (int) (buflen - currIdx)) {
callOnAuthErr(pThis, "", "certificate validation failed, names "
"inside certifcate are way to long (> 32KiB)",
RELP_RET_AUTH_CERT_INVL);
r = GNUTLS_E_CERTIFICATE_ERROR;
} else {
*p_currIdx += n;
}
return r;
}
/* Check the peer's ID in name auth mode. */
static int
relpTcpChkPeerName(relpTcp_t *const pThis, gnutls_x509_crt_t cert)
{
int r = 0;
int ret;
unsigned int status = 0;
char cnBuf[1024]; /* this is sufficient for the DNSNAME... */
char szAltName[1024]; /* this is sufficient for the DNSNAME... */
int iAltName;
char allNames[32*1024]; /* for error-reporting */
int iAllNames;
size_t szAltNameLen;
int bFoundPositiveMatch;
int gnuRet;
ret = gnutls_certificate_verify_peers2(pThis->session, &status);
if(ret < 0) {
callOnAuthErr(pThis, "", "certificate validation failed",
RELP_RET_AUTH_CERT_INVL);
r = GNUTLS_E_CERTIFICATE_ERROR; goto done;
}
if(status != 0) { /* Certificate is not trusted */
callOnAuthErr(pThis, "", "certificate validation failed",
RELP_RET_AUTH_CERT_INVL);
r = GNUTLS_E_CERTIFICATE_ERROR; goto done;
}
bFoundPositiveMatch = 0;
iAllNames = 0;
/* first search through the dNSName subject alt names */
iAltName = 0;
while(!bFoundPositiveMatch) { /* loop broken below */
szAltNameLen = sizeof(szAltName);
gnuRet = gnutls_x509_crt_get_subject_alt_name(cert, iAltName,
szAltName, &szAltNameLen, NULL);
if(gnuRet < 0)
break;
else if(gnuRet == GNUTLS_SAN_DNSNAME) {
pThis->pEngine->dbgprint("librelp: subject alt dnsName: '%s'\n", szAltName);
r = relpTcpAddToCertNamesBuffer(pThis, allNames, sizeof(allNames),
&iAllNames, szAltName);
if(r != 0) goto done;
relpTcpChkOnePeerName(pThis, szAltName, &bFoundPositiveMatch);
/* do NOT break, because there may be multiple dNSName's! */
}
++iAltName;
}
if(!bFoundPositiveMatch) {
/* if we did not succeed so far, we try the CN part of the DN... */
if(relpTcpGetCN(pThis, cert, cnBuf, sizeof(cnBuf)) == 0) {
pThis->pEngine->dbgprint("librelp: relpTcp now checking auth for CN '%s'\n", cnBuf);
r = relpTcpAddToCertNamesBuffer(pThis, allNames, sizeof(allNames),
&iAllNames, cnBuf);
if(r != 0) goto done;
relpTcpChkOnePeerName(pThis, cnBuf, &bFoundPositiveMatch);
}
}
if(!bFoundPositiveMatch) {
callOnAuthErr(pThis, allNames, "no permited name found", RELP_RET_AUTH_ERR_NAME);
r = GNUTLS_E_CERTIFICATE_ERROR; goto done;
}
r = 0;
done:
return r;
}
/* This function will verify the peer's certificate, and check
* if the hostname matches, as well as the activation, expiration dates.
*/
static int
relpTcpVerifyCertificateCallback(gnutls_session_t session)
{
int r = 0;
const gnutls_datum_t *cert_list;
unsigned int list_size = 0;
gnutls_x509_crt_t cert;
int bMustDeinitCert = 0;
relpTcp_t *const pThis = (relpTcp_t*) gnutls_session_get_ptr(session);
/* This function only works for X.509 certificates. */
if(gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
r = GNUTLS_E_CERTIFICATE_ERROR; goto done;
}
cert_list = gnutls_certificate_get_peers(pThis->session, &list_size);
if(list_size < 1) {
callOnAuthErr(pThis, "", "peer did not provide a certificate",
RELP_RET_AUTH_NO_CERT);
r = GNUTLS_E_CERTIFICATE_ERROR; goto done;
}
/* If we reach this point, we have at least one valid certificate.
* We always use only the first certificate. As of GnuTLS documentation, the
* first certificate always contains the remote peer's own certificate. All other
* certificates are issuer's certificates (up the chain). We are only interested
* in the first certificate, which is our peer. -- rgerhards, 2008-05-08
*/
gnutls_x509_crt_init(&cert);
bMustDeinitCert = 1; /* indicate cert is initialized and must be freed on exit */
gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
if(pThis->authmode == eRelpAuthMode_Fingerprint) {
r = relpTcpChkPeerFingerprint(pThis, cert);
} else {
r = relpTcpChkPeerName(pThis, cert);
}
if(r != 0) goto done;
/* notify gnutls to continue handshake normally */
r = 0;
done:
if(bMustDeinitCert)
gnutls_x509_crt_deinit(cert);
return r;
}
#endif /* #ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION */
#if 0 /* enable if needed for debugging */
static void logFunction(int level, const char *msg)
{
fprintf(stdout, "DDDD: GnuTLS log msg, level %d: %s", level, msg);
fflush(stdout);
}
#endif
/* initialize the listener for TLS use */
static relpRetVal
relpTcpLstnInitTLS(relpTcp_t *const pThis)
{
int r;
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
#if GNUTLS_VERSION_NUMBER <= 0x020b00
/* gcry_control must be called first, so that the thread system is correctly set up */
gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
#endif
gnutls_global_init();
/* uncomment for (very intense) debug help
* gnutls_global_set_log_function(logFunction);
* gnutls_global_set_log_level(10); // 0 (no) to 9 (most), 10 everything
*/
if(isAnonAuth(pThis)) {
r = gnutls_dh_params_init(&pThis->dh_params);
if(chkGnutlsCode(pThis, "Failed to initialize dh_params", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
r = gnutls_dh_params_generate2(pThis->dh_params, pThis->dhBits);
if(chkGnutlsCode(pThis, "Failed to generate dh_params", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
r = gnutls_anon_allocate_server_credentials(&pThis->anoncredSrv);
if(chkGnutlsCode(pThis, "Failed to allocate server credentials", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
gnutls_anon_set_server_dh_params(pThis->anoncredSrv, pThis->dh_params);
} else {
# ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION
r = gnutls_certificate_allocate_credentials(&pThis->xcred);
if(chkGnutlsCode(pThis, "Failed to allocate certificate credentials", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
if(pThis->caCertFile != NULL) {
r = gnutls_certificate_set_x509_trust_file(pThis->xcred,
pThis->caCertFile, GNUTLS_X509_FMT_PEM);
if(r < 0) {
chkGnutlsCode(pThis, "Failed to set certificate trust files",
RELP_RET_ERR_TLS_SETUP, r);
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
pThis->pEngine->dbgprint("librelp: obtained %d certificates from %s\n", r, pThis->caCertFile);
}
r = gnutls_certificate_set_x509_key_file (pThis->xcred,
pThis->ownCertFile, pThis->privKeyFile, GNUTLS_X509_FMT_PEM);
if(chkGnutlsCode(pThis, "Failed to set certificate key files", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
if(pThis->authmode == eRelpAuthMode_None)
pThis->authmode = eRelpAuthMode_Fingerprint;
gnutls_certificate_set_verify_function(pThis->xcred, relpTcpVerifyCertificateCallback);
# else /* #ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION */
ABORT_FINALIZE(RELP_RET_ERR_NO_TLS_AUTH);
# endif /* #ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION */
}
finalize_it:
LEAVE_RELPFUNC;
}
#endif /* #ifdef ENABLE_TLS */
/* initialize the tcp socket for a listner
* pLstnPort is a pointer to a port name. NULL is not permitted.
* gerhards, 2008-03-17
* pLstnAddr is a pointer to a bind address. NULL is permitted.
* perlei, 2018-04-19
*/
relpRetVal
relpTcpLstnInit(relpTcp_t *pThis, unsigned char *pLstnPort, unsigned char *pLstnAddr, int ai_family)
{
struct addrinfo hints, *res = NULL, *r;
int error, maxs, *s, on = 1;
int sockflags;
unsigned char *pLstnPt;
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
pLstnPt = pLstnPort;
assert(pLstnPt != NULL);
pThis->pEngine->dbgprint("creating relp tcp listen socket on port %s\n", pLstnPt);
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = ai_family;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo((char*)pLstnAddr, (char*) pLstnPt, &hints, &res);
if(error) {
pThis->pEngine->dbgprint("error %d querying port '%s'\n", error, pLstnPt);
ABORT_FINALIZE(RELP_RET_INVALID_PORT);
}
/* Count max number of sockets we may open */
for(maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++)
/* EMPTY */;
pThis->socks = malloc((maxs+1) * sizeof(int));
if (pThis->socks == NULL) {
pThis->pEngine->dbgprint("couldn't allocate memory for TCP listen sockets, "
"suspending RELP message reception.");
ABORT_FINALIZE(RELP_RET_OUT_OF_MEMORY);
}
*pThis->socks = 0; /* num of sockets counter at start of array */
s = pThis->socks + 1;
for(r = res; r != NULL ; r = r->ai_next) {
*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
if (*s < 0) {
if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT))
pThis->pEngine->dbgprint("creating relp tcp listen socket");
/* it is debatable if PF_INET with EAFNOSUPPORT should
* also be ignored...
*/
continue;
}
#ifdef IPV6_V6ONLY
if (r->ai_family == AF_INET6) {
int iOn = 1;
if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
(char *)&iOn, sizeof (iOn)) < 0) {
close(*s);
*s = -1;
continue;
}
}
#endif
if(setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) {
pThis->pEngine->dbgprint("error %d setting relp/tcp socket option\n", errno);
close(*s);
*s = -1;
continue;
}
/* We use non-blocking IO! */
if((sockflags = fcntl(*s, F_GETFL)) != -1) {
sockflags |= O_NONBLOCK;
/* SETFL could fail too, so get it caught by the subsequent
* error check.
*/
sockflags = fcntl(*s, F_SETFL, sockflags);
}
if(sockflags == -1) {
pThis->pEngine->dbgprint("error %d setting fcntl(O_NONBLOCK) on relp socket", errno);
close(*s);
*s = -1;
continue;
}
#ifdef ENABLE_TLS
if(pThis->bEnableTLS) {
CHKRet(relpTcpLstnInitTLS(pThis));
}
#endif /* #ifdef ENABLE_TLS */
if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0)
#ifndef IPV6_V6ONLY
&& (errno != EADDRINUSE)
#endif
) {
char msgbuf[4096];
snprintf(msgbuf, sizeof(msgbuf), "error while binding relp tcp socket "
"on port '%s'", pLstnPort);
msgbuf[sizeof(msgbuf)-1] = '\0';
callOnErr(pThis, msgbuf, errno);
close(*s);
*s = -1;
continue;
}
if(listen(*s,pThis->iSessMax / 10 + 5) < 0) {
/* If the listen fails, it most probably fails because we ask
* for a too-large backlog. So in this case we first set back
* to a fixed, reasonable, limit that should work. Only if
* that fails, too, we give up.
*/
pThis->pEngine->dbgprint("listen with a backlog of %d failed - retrying with default of 32.",
pThis->iSessMax / 10 + 5);
if(listen(*s, 32) < 0) {
pThis->pEngine->dbgprint("relp listen error %d, suspending\n", errno);
close(*s);
*s = -1;
continue;
}
}
(*pThis->socks)++;
s++;
}
if(*pThis->socks != maxs)
pThis->pEngine->dbgprint("We could initialize %d RELP TCP listen sockets out of %d we received "
"- this may or may not be an error indication.\n", *pThis->socks, maxs);
if(*pThis->socks == 0) {
pThis->pEngine->dbgprint("No RELP TCP listen socket could successfully be initialized, "
"message reception via RELP disabled.\n");
free(pThis->socks);
ABORT_FINALIZE(RELP_RET_COULD_NOT_BIND);
}
finalize_it:
if(res != NULL)
freeaddrinfo(res);
LEAVE_RELPFUNC;
}
/* receive data from a tcp socket
* The lenBuf parameter must contain the max buffer size on entry and contains
* the number of octets read (or -1 in case of error) 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. If *pLenBuf is -1, an error occured and
* errno holds the exact error cause.
* rgerhards, 2008-03-17
*/
relpRetVal
relpTcpRcv(relpTcp_t *const pThis, relpOctet_t *pRcvBuf, ssize_t *pLenBuf)
{
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
#ifdef ENABLE_TLS
int r;
if(pThis->bEnableTLS) {
r = gnutls_record_recv(pThis->session, pRcvBuf, *pLenBuf);
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) {
pThis->pEngine->dbgprint("librelp: gnutls_record_recv must be retried\n");
pThis->rtryOp = relpTCP_RETRY_recv;
} else {
if(r < 0)
chkGnutlsCode(pThis, "TLS record reception failed", RELP_RET_IO_ERR, r);
pThis->rtryOp = relpTCP_RETRY_none;
}
*pLenBuf = (r < 0) ? -1 : r;
} else {
#endif /* #ifdef ENABLE_TLS */
*pLenBuf = recv(pThis->sock, pRcvBuf, *pLenBuf, MSG_DONTWAIT);
pThis->pEngine->dbgprint("relpTcpRcv: read %zd bytes from sock %d\n",
*pLenBuf, pThis->sock);
#ifdef ENABLE_TLS
}
#endif /* #ifdef ENABLE_TLS */
LEAVE_RELPFUNC;
}
/* helper for CORK option manipulation. As this is not portable, the
* helper abstracts it. Note that it is a null-operation if no
* such option is available on the platform in question.
*/
static void
setCORKopt(const relpTcp_t *const pThis, const int onOff)
{
#if defined(TCP_CORK)
if(setsockopt(pThis->sock, SOL_TCP, TCP_CORK, &onOff, sizeof (onOff)) == -1) {
pThis->pEngine->dbgprint("relpTcp: setsockopt() TCP_CORK failed\n");
}
#elif defined(TCP_NOPUSH)
if(setsockopt(pThis->sock, IPPROTO_TCP, TCP_NOPUSH, &onOff, sizeof (onOff)) == -1) {
pThis->pEngine->dbgprint("relpTcp: setsockopt() TCP_NOPUSH failed\n");
}
#endif
}
/* this function is called to hint librelp that a "burst" of data is to be
* sent. librelp can than try to optimize it's handling. Right now, this
* means we turn on the CORK option and will turn it off when we are
* hinted that the burst is over.
* The function is intentionally void as it must operate in a way that
* does not interfere with normal operations.
*/
void
relpTcpHintBurstBegin(relpTcp_t *const pThis)
{
setCORKopt(pThis, 1);
}
/* this is the counterpart to relpTcpHintBurstBegin -- see there for doc */
void
relpTcpHintBurstEnd(relpTcp_t *const pThis)
{
setCORKopt(pThis, 0);
}
/* send a buffer via TCP.
* 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
*/
relpRetVal
relpTcpSend(relpTcp_t *const pThis, relpOctet_t *pBuf, ssize_t *pLenBuf)
{
ssize_t written;
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
#ifdef ENABLE_TLS
if(pThis->bEnableTLS) {
written = gnutls_record_send(pThis->session, pBuf, *pLenBuf);
pThis->pEngine->dbgprint("librelp: TLS send returned %d\n", (int) written);
if(written == GNUTLS_E_AGAIN || written == GNUTLS_E_INTERRUPTED) {
pThis->rtryOp = relpTCP_RETRY_send;
written = 0;
} else {
pThis->rtryOp = relpTCP_RETRY_none;
if(written < 1) {
chkGnutlsCode(pThis, "TLS record write failed", RELP_RET_IO_ERR, written);
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
}
} else {
#endif /* #ifdef ENABLE_TLS */
written = send(pThis->sock, pBuf, *pLenBuf, 0);
const int errno_save = errno;
pThis->pEngine->dbgprint("relpTcpSend: sock %d, lenbuf %zd, send returned %d [errno %d]\n",
(int)pThis->sock, *pLenBuf, (int) written, errno_save);
if(written == -1) {
switch(errno_save) {
case EAGAIN:
case EINTR:
/* this is fine, just retry... */
written = 0;
break;
default:
ABORT_FINALIZE(RELP_RET_IO_ERR);
break;
}
}
#ifdef ENABLE_TLS
}
#endif /* #ifdef ENABLE_TLS */
*pLenBuf = written;
finalize_it:
LEAVE_RELPFUNC;
}
#ifdef ENABLE_TLS
#ifndef _AIX
#pragma GCC diagnostic push /* we need to disable a warning below */
/* per https://lists.gnupg.org/pipermail/gnutls-help/2004-August/000154.html This is expected */
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
#endif
/* this is only called for client-initiated sessions */
static relpRetVal
relpTcpConnectTLSInit(relpTcp_t *const pThis)
{
int r;
int sockflags;
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
/* We expect a non blocking socket to establish a tls session */
if((sockflags = fcntl(pThis->sock, F_GETFL)) != -1) {
sockflags &= ~O_NONBLOCK;
sockflags = fcntl(pThis->sock, F_SETFL, sockflags);
}
if(sockflags == -1) {
pThis->pEngine->dbgprint("error %d unsetting fcntl(O_NONBLOCK) on relp socket", errno);
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
if(!called_gnutls_global_init) {
#if GNUTLS_VERSION_NUMBER <= 0x020b00
/* gcry_control must be called first, so that the thread system is correctly set up */
gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
#endif
gnutls_global_init();
/* uncomment for (very intense) debug help
* gnutls_global_set_log_function(logFunction);
* gnutls_global_set_log_level(10); // 0 (no) to 9 (most), 10 everything
*/
pThis->pEngine->dbgprint("DDDD: gnutls_global_init() called\n");
called_gnutls_global_init = 1;
}
r = gnutls_init(&pThis->session, GNUTLS_CLIENT);
if(chkGnutlsCode(pThis, "Failed to initialize GnuTLS", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
gnutls_session_set_ptr(pThis->session, pThis);
CHKRet(relpTcpTLSSetPrio(pThis));
if(isAnonAuth(pThis)) {
r = gnutls_anon_allocate_client_credentials(&pThis->anoncred);
if(chkGnutlsCode(pThis, "Failed to allocate client credentials", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
/* put the anonymous credentials to the current session */
r = gnutls_credentials_set(pThis->session, GNUTLS_CRD_ANON, pThis->anoncred);
if(chkGnutlsCode(pThis, "Failed to set credentials", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
} else {
#ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION
r = gnutls_certificate_allocate_credentials(&pThis->xcred);
if(chkGnutlsCode(pThis, "Failed to allocate certificate credentials", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
if(pThis->caCertFile != NULL) {
r = gnutls_certificate_set_x509_trust_file(pThis->xcred,
pThis->caCertFile, GNUTLS_X509_FMT_PEM);
if(r < 0) {
chkGnutlsCode(pThis, "Failed to set certificate trust file", RELP_RET_ERR_TLS_SETUP, r);
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
pThis->pEngine->dbgprint("librelp: obtained %d certificates from %s\n", r, pThis->caCertFile);
}
if(pThis->ownCertFile != NULL) {
r = gnutls_certificate_set_x509_key_file (pThis->xcred,
pThis->ownCertFile, pThis->privKeyFile, GNUTLS_X509_FMT_PEM);
if(chkGnutlsCode(pThis, "Failed to set certificate key file", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
}
r = gnutls_credentials_set(pThis->session, GNUTLS_CRD_CERTIFICATE, pThis->xcred);
if(chkGnutlsCode(pThis, "Failed to set credentials", RELP_RET_ERR_TLS_SETUP, r)) {
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
if(pThis->authmode == eRelpAuthMode_None)
pThis->authmode = eRelpAuthMode_Fingerprint;
gnutls_certificate_set_verify_function(pThis->xcred, relpTcpVerifyCertificateCallback);
# else /* #ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION */
ABORT_FINALIZE(RELP_RET_ERR_NO_TLS_AUTH);
# endif /* #ifdef HAVE_GNUTLS_CERTIFICATE_SET_VERIFY_FUNCTION */
}
gnutls_transport_set_ptr(pThis->session, (gnutls_transport_ptr_t) pThis->sock);
//gnutls_handshake_set_timeout(pThis->session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
/* Perform the TLS handshake */
do {
r = gnutls_handshake(pThis->session);
pThis->pEngine->dbgprint("DDDD: gnutls_handshake: %d: %s\n", r, gnutls_strerror(r));
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) {
pThis->pEngine->dbgprint("librelp: gnutls_handshake must be retried\n");
pThis->rtryOp = relpTCP_RETRY_handshake;
} else if(r != GNUTLS_E_SUCCESS) {
chkGnutlsCode(pThis, "TLS handshake failed", RELP_RET_ERR_TLS_SETUP, r);
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
}
while(0);
//while (r < 0 && gnutls_error_is_fatal(r) == 0);
/* set the socket to non-blocking IO (we do this on the recv() for non-TLS */
if((sockflags = fcntl(pThis->sock, F_GETFL)) != -1) {
sockflags |= O_NONBLOCK;
/* SETFL could fail too, so get it caught by the subsequent
* error check. */
if(fcntl(pThis->sock, F_SETFL, sockflags) == -1) {
callOnErr(pThis, "error setting socket to non-blocking",
RELP_RET_ERR_TLS_SETUP);
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
}
finalize_it:
LEAVE_RELPFUNC;
}
#ifndef _AIX
#pragma GCC diagnostic pop
#endif
#endif /* #ifdef ENABLE_TLS */
/* open a connection to a remote host (server).
* This is only use for client initiated connections.
* rgerhards, 2008-03-19
*/
relpRetVal
relpTcpConnect(relpTcp_t *const pThis,
const int family,
unsigned char *port,
unsigned char *host,
unsigned char *clientIP)
{
struct addrinfo *res = NULL;
struct addrinfo hints;
struct addrinfo *reslocal = NULL;
struct pollfd pfd;
ENTER_RELPFUNC;
RELPOBJ_assert(pThis, Tcp);
assert(port != NULL);
assert(host != NULL);
assert(pThis->sock == -1);
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
if(getaddrinfo((char*)host, (char*)port, &hints, &res) != 0) {
pThis->pEngine->dbgprint("error %d in getaddrinfo\n", errno);
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
if((pThis->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
if(clientIP != NULL) {
if(getaddrinfo((char*)clientIP, (char*)NULL, &hints, &reslocal) != 0) {
pThis->pEngine->dbgprint("error %d in getaddrinfo of clientIP\n", errno);
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
if(bind(pThis->sock, reslocal->ai_addr, reslocal->ai_addrlen) != 0) {
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
}
if(fcntl(pThis->sock, F_SETFL, O_NONBLOCK) == -1) {
callOnErr(pThis, "error setting socket to non-blocking",
RELP_RET_IO_ERR);
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
if(connect(pThis->sock, res->ai_addr, res->ai_addrlen) == -1) {
if(errno != EINPROGRESS) {
callOnErr(pThis, "error connecting", RELP_RET_IO_ERR);
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
}
pfd.fd = pThis->sock;
pfd.events = POLLOUT;
if (poll(&pfd, 1, pThis->connTimeout * 1000) != 1) {
pThis->pEngine->dbgprint("connection timed out after %d seconds\n", pThis->connTimeout);
ABORT_FINALIZE(RELP_RET_TIMED_OUT);
}
int so_error;
socklen_t len = sizeof so_error;
int r = getsockopt(pThis->sock, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (r == -1 || so_error != 0) {
pThis->pEngine->dbgprint("socket has an error %d\n", so_error);
ABORT_FINALIZE(RELP_RET_IO_ERR);
}
#ifdef ENABLE_TLS
if(pThis->bEnableTLS) {
CHKRet(relpTcpConnectTLSInit(pThis));
pThis->bTLSActive = 1;
}
#endif /* #ifdef ENABLE_TLS */
finalize_it:
if(res != NULL)
freeaddrinfo(res);
if(reslocal != NULL)
freeaddrinfo(reslocal);
if(iRet != RELP_RET_OK) {
if(pThis->sock != -1) {
close(pThis->sock);
pThis->sock = -1;
}
}
LEAVE_RELPFUNC;
}
#ifdef ENABLE_TLS
/* return direction in which retry must be done. We return the original
* gnutls code, which means:
* "0 if trying to read data, 1 if trying to write data."
*/
int
relpTcpGetRtryDirection(relpTcp_t *const pThis)
{
return gnutls_record_get_direction(pThis->session);
}
relpRetVal
relpTcpRtryHandshake(relpTcp_t *const pThis)
{
int r;
ENTER_RELPFUNC;
r = gnutls_handshake(pThis->session);
if(r < 0) {
pThis->pEngine->dbgprint("librelp: state %d during retry handshake: %s\n", r, gnutls_strerror(r));
}
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) {
; /* nothing to do, just keep our status... */
} else if(r == 0) {
pThis->rtryOp = relpTCP_RETRY_none;
} else {
chkGnutlsCode(pThis, "TLS handshake failed", RELP_RET_ERR_TLS_SETUP, r);
ABORT_FINALIZE(RELP_RET_ERR_TLS_SETUP);
}
finalize_it:
LEAVE_RELPFUNC;
}
#endif /* #ifdef ENABLE_TLS */
/* wait until a socket is writable again. This is primarily for use in client cases.
* It does NOT take care of our regular event loop, and must not be used by parts
* of the code that are driven by this loop. Returns 0 if a timeout occured and 1
* otherwise.
*/
int
relpTcpWaitWriteable(relpTcp_t *const pThis, struct timespec *const tTimeout)
{
int r;
struct timespec tCurr; /* current time */
struct pollfd pfd;
clock_gettime(CLOCK_REALTIME, &tCurr);
const int timeout = (tTimeout->tv_sec - tCurr.tv_sec) * 1000
+ (tTimeout->tv_nsec - tCurr.tv_nsec) / 1000000000;
if(timeout < 0) {
r = 0; goto done;
}
pThis->pEngine->dbgprint("librelp: telpTcpWaitWritable doing poll() "
"on fd %d, timoeut %d\n", pThis->sock, timeout);
pfd.fd = pThis->sock;
pfd.events = POLLOUT;
r = poll(&pfd, 1, timeout);
done: return r;
}