/* nsd_gtls.c
 *
 * An implementation of the nsd interface for GnuTLS.
 * 
 * Copyright (C) 2007, 2008 Rainer Gerhards and Adiscon GmbH.
 *
 * 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 <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gcrypt.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 "nsd_ptcp.h"
#include "nsdsel_gtls.h"
#include "nsd_gtls.h"

/* things to move to some better place/functionality - TODO */
#define DH_BITS 1024
#define CRLFILE "crl.pem"


GCRY_THREAD_OPTION_PTHREAD_IMPL;
MODULE_TYPE_LIB

/* static data */
DEFobjStaticHelpers
DEFobjCurrIf(errmsg)
DEFobjCurrIf(glbl)
DEFobjCurrIf(net)
DEFobjCurrIf(nsd_ptcp)

static int bGlblSrvrInitDone = 0;	/**< 0 - server global init not yet done, 1 - already done */

static pthread_mutex_t mutGtlsStrerror; /**< a mutex protecting the potentially non-reentrant gtlStrerror() function */

/* a macro to check GnuTLS calls against unexpected errors */
#define CHKgnutls(x) \
	if((gnuRet = (x)) != 0) { \
		uchar *pErr = gtlsStrerror(gnuRet); \
		dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); \
		free(pErr); \
		ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \
	}


/* ------------------------------ GnuTLS specifics ------------------------------ */
static gnutls_certificate_credentials xcred;
static gnutls_dh_params dh_params;

#ifdef DEBUG
#if 0 /* uncomment, if needed some time again -- DEV Debug only */
/* This defines a log function to be provided to GnuTLS. It hopefully
 * helps us track down hard to find problems.
 * rgerhards, 2008-06-20
 */
static void logFunction(int level, const char *msg)
{
	dbgprintf("GnuTLS log msg, level %d: %s\n", level, msg);
}
#endif
#endif /* #ifdef DEBUG */


/* read in the whole content of a file. The caller is responsible for
 * freeing the buffer. To prevent DOS, this function can NOT read
 * files larger than 1MB (which still is *very* large).
 * rgerhards, 2008-05-26
 */
static rsRetVal
readFile(uchar *pszFile, gnutls_datum_t *pBuf)
{
	int fd;
	struct stat stat_st;
	DEFiRet;

	assert(pszFile != NULL);
	assert(pBuf != NULL);

	pBuf->data = NULL;

	if((fd = open((char*)pszFile, 0)) == -1) {
		errmsg.LogError(0, RS_RET_FILE_NOT_FOUND, "can not read file '%s'", pszFile);
		ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);

	}

	if(fstat(fd, &stat_st) == -1) {
		errmsg.LogError(0, RS_RET_FILE_NO_STAT, "can not stat file '%s'", pszFile);
		ABORT_FINALIZE(RS_RET_FILE_NO_STAT);
	}
	
	/* 1MB limit */
	if(stat_st.st_size > 1024 * 1024) {
		errmsg.LogError(0, RS_RET_FILE_TOO_LARGE, "file '%s' too large, max 1MB", pszFile);
		ABORT_FINALIZE(RS_RET_FILE_TOO_LARGE);
	}

	CHKmalloc(pBuf->data = malloc(stat_st.st_size));
	pBuf->size = stat_st.st_size;
	if(read(fd,  pBuf->data, stat_st.st_size) != stat_st.st_size) {
		errmsg.LogError(0, RS_RET_IO_ERROR, "error or incomplete read of file '%s'", pszFile);
		ABORT_FINALIZE(RS_RET_IO_ERROR);
	}

	close(fd);

finalize_it:
	if(iRet != RS_RET_OK) {
		if(pBuf->data != NULL) {
			free(pBuf->data);
			pBuf->data = NULL;
			pBuf->size = 0;
			}
	}
	RETiRet;
}


/* Load the certificate and the private key into our own store. We need to do
 * this in the client case, to support fingerprint authentication. In that case,
 * we may be presented no matching root certificate, but we must provide ours.
 * The only way to do that is via the cert callback interface, but for it we
 * need to load certificates into our private store.
 * rgerhards, 2008-05-26
 */
static rsRetVal
gtlsLoadOurCertKey(nsd_gtls_t *pThis)
{
	DEFiRet;
	int gnuRet;
	gnutls_datum_t data = { NULL, 0 };
	uchar *keyFile;
	uchar *certFile;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	certFile = glbl.GetDfltNetstrmDrvrCertFile();
	keyFile = glbl.GetDfltNetstrmDrvrKeyFile();

	if(certFile == NULL || keyFile == NULL) {
		/* in this case, we can not set our certificate. If we are 
		 * a client and the server is running in "anon" auth mode, this
		 * may be well acceptable. In other cases, we will see some
		 * more error messages down the road. -- rgerhards, 2008-07-02
		 */
		dbgprintf("our certificate is not set, file name values are cert: '%s', key: '%s'\n",
			  certFile, keyFile);
		ABORT_FINALIZE(RS_RET_CERTLESS);
	}

	/* try load certificate */
	CHKiRet(readFile(certFile, &data));
	CHKgnutls(gnutls_x509_crt_init(&pThis->ourCert));
	pThis->bOurCertIsInit = 1;
	CHKgnutls(gnutls_x509_crt_import(pThis->ourCert, &data, GNUTLS_X509_FMT_PEM));
	free(data.data);
	data.data = NULL;

	/* try load private key */
	CHKiRet(readFile(keyFile, &data));
	CHKgnutls(gnutls_x509_privkey_init(&pThis->ourKey));
	pThis->bOurKeyIsInit = 1;
	CHKgnutls(gnutls_x509_privkey_import(pThis->ourKey, &data, GNUTLS_X509_FMT_PEM));
	free(data.data);

finalize_it:
	if(iRet != RS_RET_OK) {
		if(data.data != NULL)
			free(data.data);
		if(pThis->bOurCertIsInit)
			gnutls_x509_crt_deinit(pThis->ourCert);
		if(pThis->bOurKeyIsInit)
			gnutls_x509_privkey_deinit(pThis->ourKey);
	}
	RETiRet;
}


/* This callback must be associated with a session by calling
 * gnutls_certificate_client_set_retrieve_function(session, cert_callback),
 * before a handshake. We will always return the configured certificate,
 * even if it does not match the peer's trusted CAs. This is necessary
 * to use self-signed certs in fingerprint mode. And, yes, this usage
 * of the callback is quite a hack. But it seems the only way to
 * obey to the IETF -transport-tls I-D.
 * Note: GnuTLS requires the function to return 0 on success and
 * -1 on failure.
 * rgerhards, 2008-05-27
 */
static int
gtlsClientCertCallback(gnutls_session session,
              __attribute__((unused)) const gnutls_datum* req_ca_rdn, int __attribute__((unused)) nreqs,
              __attribute__((unused)) const gnutls_pk_algorithm* sign_algos, int __attribute__((unused)) sign_algos_length,
              gnutls_retr_st *st)
{
	nsd_gtls_t *pThis;

	pThis = (nsd_gtls_t*) gnutls_session_get_ptr(session);

	st->type = GNUTLS_CRT_X509;
	st->ncerts = 1;
	st->cert.x509 = &pThis->ourCert;
	st->key.x509 = pThis->ourKey;
	st->deinit_all = 0;

	return 0;
}


/* This function extracts some information about this session's peer
 * certificate. Works for X.509 certificates only. Adds all
 * of the info to a cstr_t, which is handed over to the caller.
 * Caller must destruct it when no longer needed.
 * rgerhards, 2008-05-21
 */
static rsRetVal
gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr)
{
	char dn[128];
	uchar lnBuf[256];
	size_t size;
	unsigned int algo, bits;
	time_t expiration_time, activation_time;
	const gnutls_datum *cert_list;
	unsigned cert_list_size = 0;
	gnutls_x509_crt cert;
	cstr_t *pStr = NULL;
	int gnuRet;
	DEFiRet;
	unsigned iAltName;
	size_t szAltNameLen;
	char szAltName[1024]; /* this is sufficient for the DNSNAME... */

	assert(ppStr != NULL);
	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509)
		return RS_RET_TLS_CERT_ERR;

	cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size);

	CHKiRet(rsCStrConstruct(&pStr));

	snprintf((char*)lnBuf, sizeof(lnBuf), "peer provided %d certificate(s). ", cert_list_size);
	CHKiRet(rsCStrAppendStr(pStr, lnBuf));

	if(cert_list_size > 0) {
		/* we only print information about the first certificate */
		CHKgnutls(gnutls_x509_crt_init(&cert));
		CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER));

		CHKiRet(rsCStrAppendStr(pStr, (uchar*)"Certificate 1 info: "));

		expiration_time = gnutls_x509_crt_get_expiration_time(cert);
		activation_time = gnutls_x509_crt_get_activation_time(cert);
		ctime_r(&activation_time, dn);
		dn[strlen(dn) - 1] = '\0'; /* strip linefeed */
		snprintf((char*)lnBuf, sizeof(lnBuf), "certificate valid from %s ", dn);
		CHKiRet(rsCStrAppendStr(pStr, lnBuf));

		ctime_r(&expiration_time, dn);
		dn[strlen(dn) - 1] = '\0'; /* strip linefeed */
		snprintf((char*)lnBuf, sizeof(lnBuf), "to %s; ", dn);
		CHKiRet(rsCStrAppendStr(pStr, lnBuf));

		/* Extract some of the public key algorithm's parameters */
		algo = gnutls_x509_crt_get_pk_algorithm(cert, &bits);

		snprintf((char*)lnBuf, sizeof(lnBuf), "Certificate public key: %s; ",
			 gnutls_pk_algorithm_get_name(algo));
		CHKiRet(rsCStrAppendStr(pStr, lnBuf));

		/* names */
		size = sizeof(dn);
		gnutls_x509_crt_get_dn(cert, dn, &size);
		snprintf((char*)lnBuf, sizeof(lnBuf), "DN: %s; ", dn);
		CHKiRet(rsCStrAppendStr(pStr, lnBuf));

		size = sizeof(dn);
		gnutls_x509_crt_get_issuer_dn(cert, dn, &size);
		snprintf((char*)lnBuf, sizeof(lnBuf), "Issuer DN: %s; ", dn);
		CHKiRet(rsCStrAppendStr(pStr, lnBuf));

		/* dNSName alt name */
		iAltName = 0;
		while(1) { /* 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) {
				/* we found it! */
				snprintf((char*)lnBuf, sizeof(lnBuf), "SAN:DNSname: %s; ", szAltName);
				CHKiRet(rsCStrAppendStr(pStr, lnBuf));
				/* do NOT break, because there may be multiple dNSName's! */
			}
			++iAltName;
		}

		gnutls_x509_crt_deinit(cert);
	}

	CHKiRet(cstrFinalize(pStr));
	*ppStr = pStr;

finalize_it:
	if(iRet != RS_RET_OK) {
		if(pStr != NULL)
			rsCStrDestruct(&pStr);
	}

	RETiRet;
}



#if 0 /* we may need this in the future - code needs to be looked at then! */
/* This function will print some details of the
 * given pThis->sess.
 */
static rsRetVal
print_info(nsd_gtls_t *pThis)
{
	const char *tmp;
	gnutls_credentials_type cred;
	gnutls_kx_algorithm kx;
	DEFiRet;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);
	/* print the key exchange's algorithm name
	*/
	kx = gnutls_kx_get(pThis->sess);
	tmp = gnutls_kx_get_name(kx);
	dbgprintf("- Key Exchange: %s\n", tmp);

	/* Check the authentication type used and switch
	* to the appropriate.
	*/
	cred = gnutls_auth_get_type(pThis->sess);
	switch (cred) {
	case GNUTLS_CRD_ANON:       /* anonymous authentication */
		dbgprintf("- Anonymous DH using prime of %d bits\n",
		gnutls_dh_get_prime_bits(pThis->sess));
		break;
	case GNUTLS_CRD_CERTIFICATE:        /* certificate authentication */
		/* Check if we have been using ephemeral Diffie Hellman.
		*/
		if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) {
		 dbgprintf("\n- Ephemeral DH using prime of %d bits\n",
			gnutls_dh_get_prime_bits(pThis->sess));
		}

		/* if the certificate list is available, then
		* print some information about it.
		*/
		gtlsPrintCert(pThis);
		break;
	case GNUTLS_CRD_SRP:        /* certificate authentication */
		dbgprintf("GNUTLS_CRD_SRP/IA");
		break;
	case GNUTLS_CRD_PSK:        /* certificate authentication */
		dbgprintf("GNUTLS_CRD_PSK");
		break;
	case GNUTLS_CRD_IA:        /* certificate authentication */
		dbgprintf("GNUTLS_CRD_IA");
		break;
	} /* switch */

	/* print the protocol's name (ie TLS 1.0) */
	tmp = gnutls_protocol_get_name(gnutls_protocol_get_version(pThis->sess));
	dbgprintf("- Protocol: %s\n", tmp);

	/* print the certificate type of the peer.
	* ie X.509
	*/
	tmp = gnutls_certificate_type_get_name(
	gnutls_certificate_type_get(pThis->sess));

	dbgprintf("- Certificate Type: %s\n", tmp);

	/* print the compression algorithm (if any)
	*/
	tmp = gnutls_compression_get_name( gnutls_compression_get(pThis->sess));
	dbgprintf("- Compression: %s\n", tmp);

	/* print the name of the cipher used.
	* ie 3DES.
	*/
	tmp = gnutls_cipher_get_name(gnutls_cipher_get(pThis->sess));
	dbgprintf("- Cipher: %s\n", tmp);

	/* Print the MAC algorithms name.
	* ie SHA1
	*/
	tmp = gnutls_mac_get_name(gnutls_mac_get(pThis->sess));
	dbgprintf("- MAC: %s\n", tmp);

	RETiRet;
}
#endif


/* Convert a fingerprint to printable data. The  conversion is carried out
 * according IETF I-D syslog-transport-tls-12. The fingerprint string is
 * returned in a new cstr object. It is the caller's responsibility to
 * destruct that object.
 * rgerhards, 2008-05-08
 */
static rsRetVal
GenFingerprintStr(uchar *pFingerprint, size_t sizeFingerprint, cstr_t **ppStr)
{
	cstr_t *pStr = NULL;
	uchar buf[4];
	size_t i;
	DEFiRet;

	CHKiRet(rsCStrConstruct(&pStr));
	CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*)"SHA1", 4));
	for(i = 0 ; i < sizeFingerprint ; ++i) {
		snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]);
		CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3));
	}
	CHKiRet(cstrFinalize(pStr));

	*ppStr = pStr;

finalize_it:
	if(iRet != RS_RET_OK) {
		if(pStr != NULL)
			rsCStrDestruct(&pStr);
	}
	RETiRet;
}


/* a thread-safe variant of gnutls_strerror
 * The caller must free the returned string.
 * rgerhards, 2008-04-30
 */
uchar *gtlsStrerror(int error)
{
	uchar *pErr;

	pthread_mutex_lock(&mutGtlsStrerror);
	pErr = (uchar*) strdup(gnutls_strerror(error));
	pthread_mutex_unlock(&mutGtlsStrerror);

	return pErr;
}


/* 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
gtlsRecordRecv(nsd_gtls_t *pThis)
{
	ssize_t lenRcvd;
	DEFiRet;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);
	lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF);
	if(lenRcvd >= 0) {
		pThis->lenRcvBuf = lenRcvd;
		pThis->ptrRcvBuf = 0;
	} else if(lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) {
		pThis->rtryCall = gtlsRtry_recv;
		dbgprintf("GnuTLS receive requires a retry (this most probably is OK and no error condition)\n");
		ABORT_FINALIZE(RS_RET_RETRY);
	} else {
		int gnuRet; /* TODO: build a specific function for GnuTLS error reporting */
		CHKgnutls(lenRcvd); /* this will abort the function */
	}

finalize_it:
	dbgprintf("gtlsRecordRecv return. nsd %p, iRet %d, lenRcvd %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, (int) lenRcvd, pThis->lenRcvBuf, pThis->ptrRcvBuf);
	RETiRet;
}


/* add our own certificate to the certificate set, so that the peer
 * can identify us. Please note that we try to use mutual authentication,
 * so we always add a cert, even if we are in the client role (later,
 * this may be controlled by a config setting).
 * rgerhards, 2008-05-15
 */
static rsRetVal
gtlsAddOurCert(void)
{
	int gnuRet;
	uchar *keyFile;
	uchar *certFile;
	uchar *pGnuErr; /* for GnuTLS error reporting */
	DEFiRet;

	certFile = glbl.GetDfltNetstrmDrvrCertFile();
	keyFile = glbl.GetDfltNetstrmDrvrKeyFile();
	dbgprintf("GTLS certificate file: '%s'\n", certFile);
	dbgprintf("GTLS key file: '%s'\n", keyFile);
	CHKgnutls(gnutls_certificate_set_x509_key_file(xcred, (char*)certFile, (char*)keyFile, GNUTLS_X509_FMT_PEM));

finalize_it:
	if(iRet != RS_RET_OK) {
		pGnuErr = gtlsStrerror(gnuRet);
		errno = 0;
		errmsg.LogError(0, iRet, "error adding our certificate. GnuTLS error %d, message: '%s', "
				"key: '%s', cert: '%s'", gnuRet, pGnuErr, keyFile, certFile);
		free(pGnuErr);
	}
	RETiRet;
}


/* globally initialize GnuTLS */
static rsRetVal
gtlsGlblInit(void)
{
	int gnuRet;
	uchar *cafile;
	DEFiRet;

	/* gcry_control must be called first, so that the thread system is correctly set up */
	gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
	CHKgnutls(gnutls_global_init());
	
	/* X509 stuff */
	CHKgnutls(gnutls_certificate_allocate_credentials(&xcred));

	/* sets the trusted cas file */
	cafile = glbl.GetDfltNetstrmDrvrCAF();
	dbgprintf("GTLS CA file: '%s'\n", cafile);
	gnuRet = gnutls_certificate_set_x509_trust_file(xcred, (char*)cafile, GNUTLS_X509_FMT_PEM);
	if(gnuRet < 0) {
		/* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */
		uchar *pErr = gtlsStrerror(gnuRet);
		dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr);
		free(pErr);
		ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
	}

#	ifdef DEBUG
#if 	0 /* do this in special cases only. WARNING: if active, it may reveal sensitive information! */
	/* intialize log function - set a level only for hard-to-find bugs */
	gnutls_global_set_log_function(logFunction);
	gnutls_global_set_log_level(10); /* 0 (no) to 9 (most), 10 everything */
#	endif
#	endif

finalize_it:
	RETiRet;
}

static rsRetVal
gtlsInitSession(nsd_gtls_t *pThis)
{
	DEFiRet;
	int gnuRet;
	gnutls_session session;

	gnutls_init(&session, GNUTLS_SERVER);
	pThis->bHaveSess = 1;
	pThis->bIsInitiator = 0;

	/* avoid calling all the priority functions, since the defaults are adequate. */
	CHKgnutls(gnutls_set_default_priority(session));
	CHKgnutls(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred));

	/* request client certificate if any.  */
	gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUEST);
	gnutls_dh_set_prime_bits(session, DH_BITS);

	pThis->sess = session;

finalize_it:
	RETiRet;
}


static rsRetVal
generate_dh_params(void)
{
	int gnuRet;
	DEFiRet;
	/* Generate Diffie Hellman parameters - for use with DHE
	 * kx algorithms. These should be discarded and regenerated
	 * once a day, once a week or once a month. Depending on the
	 * security requirements.
	 */
	CHKgnutls(gnutls_dh_params_init( &dh_params));
	CHKgnutls(gnutls_dh_params_generate2( dh_params, DH_BITS));
finalize_it:
	RETiRet;
}


/* set up all global things that are needed for server operations
 * rgerhards, 2008-04-30
 */
static rsRetVal
gtlsGlblInitLstn(void)
{
	DEFiRet;

	if(bGlblSrvrInitDone == 0) {
		/* we do not use CRLs right now, and I doubt we'll ever do. This functionality is
		 * considered legacy. -- rgerhards, 2008-05-05
		 */
		/*CHKgnutls(gnutls_certificate_set_x509_crl_file(xcred, CRLFILE, GNUTLS_X509_FMT_PEM));*/
		CHKiRet(generate_dh_params());
		gnutls_certificate_set_dh_params(xcred, dh_params); /* this is void */
		bGlblSrvrInitDone = 1; /* we are all set now */

		/* now we need to add our certificate */
		CHKiRet(gtlsAddOurCert());
	}

finalize_it:
	RETiRet;
}


/* 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.
 * If no CN is contained in the cert, no string is returned
 * (*ppstrCN remains NULL). *ppstrCN MUST be NULL on entry!
 * rgerhards, 2008-05-22
 */
static rsRetVal
gtlsGetCN(nsd_gtls_t *pThis, gnutls_x509_crt *pCert, cstr_t **ppstrCN)
{
	DEFiRet;
	int gnuRet;
	int i;
	int bFound;
	cstr_t *pstrCN = NULL;
	size_t size;
	/* big var the last, so we hope to have all we usually neeed within one mem cache line */
	uchar szDN[1024]; /* this should really be large enough for any non-malicious case... */

	ISOBJ_TYPE_assert(pThis, nsd_gtls);
	assert(pCert != NULL);
	assert(ppstrCN != NULL);
	assert(*ppstrCN == NULL);

	size = sizeof(szDN);
	CHKgnutls(gnutls_x509_crt_get_dn(*pCert, (char*)szDN, &size));

	/* 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) {
		FINALIZE; /* we are done */
	}

	/* we found a common name, now extract it */
	CHKiRet(cstrConstruct(&pstrCN));
	while(szDN[i] != '\0' && szDN[i] != ',') {
		if(szDN[i] == '\\') {
			/* hex escapes are not implemented */
			++i; /* escape char processed */
			if(szDN[i] == '\0')
				ABORT_FINALIZE(RS_RET_CERT_INVALID_DN);
			CHKiRet(cstrAppendChar(pstrCN, szDN[i]));
		} else {
			CHKiRet(cstrAppendChar(pstrCN, szDN[i]));
		}
		++i; /* char processed */
	}
	CHKiRet(cstrFinalize(pstrCN));

	/* 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
	 */

	*ppstrCN = pstrCN;

finalize_it:
	if(iRet != RS_RET_OK) {
		if(pstrCN != NULL)
			cstrDestruct(&pstrCN);
	}

	RETiRet;
}


/* Check the peer's ID in fingerprint auth mode.
 * rgerhards, 2008-05-22
 */
static rsRetVal
gtlsChkPeerFingerprint(nsd_gtls_t *pThis, gnutls_x509_crt *pCert)
{
	uchar fingerprint[20];
	size_t size;
	cstr_t *pstrFingerprint = NULL;
	int bFoundPositiveMatch;
	permittedPeers_t *pPeer;
	int gnuRet;
	DEFiRet;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	/* obtain the SHA1 fingerprint */
	size = sizeof(fingerprint);
	CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA1, fingerprint, &size));
	CHKiRet(GenFingerprintStr(fingerprint, size, &pstrFingerprint));
	dbgprintf("peer's certificate SHA1 fingerprint: %s\n", cstrGetSzStr(pstrFingerprint));

	/* now search through the permitted peers to see if we can find a permitted one */
	bFoundPositiveMatch = 0;
	pPeer = pThis->pPermPeers;
	while(pPeer != NULL && !bFoundPositiveMatch) {
		if(!rsCStrSzStrCmp(pstrFingerprint, pPeer->pszID, strlen((char*) pPeer->pszID))) {
			bFoundPositiveMatch = 1;
		} else {
			pPeer = pPeer->pNext;
		}
	}

	if(!bFoundPositiveMatch) {
		dbgprintf("invalid peer fingerprint, not permitted to talk to it\n");
		if(pThis->bReportAuthErr == 1) {
			errno = 0;
			errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer fingerprint '%s' unknown - we are "
					"not permitted to talk to it", cstrGetSzStr(pstrFingerprint));
			pThis->bReportAuthErr = 0;
		}
		ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
	}

finalize_it:
	if(pstrFingerprint != NULL)
		cstrDestruct(&pstrFingerprint);
	RETiRet;
}


/* 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).
 * TODO: implemet wildcards?
 * rgerhards, 2008-05-26
 */
static rsRetVal
gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatch)
{
	permittedPeers_t *pPeer;
	DEFiRet;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);
	assert(pszPeerID != NULL);
	assert(pbFoundPositiveMatch != NULL);

	if(pThis->pPermPeers) { /* do we have configured peer IDs? */
		pPeer = pThis->pPermPeers;
		while(pPeer != NULL) {
			CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch));
			if(*pbFoundPositiveMatch)
				break;
			pPeer = pPeer->pNext;
		}
	} else {
		/* we do not have configured peer IDs, so we use defaults */
		if(   pThis->pszConnectHost
		   && !strcmp((char*)pszPeerID, (char*)pThis->pszConnectHost)) {
			*pbFoundPositiveMatch = 1;
		}
	}

finalize_it:
	RETiRet;
}


/* Check the peer's ID in name auth mode.
 * rgerhards, 2008-05-22
 */
static rsRetVal
gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert)
{
	uchar lnBuf[256];
	char szAltName[1024]; /* this is sufficient for the DNSNAME... */
	int iAltName;
	size_t szAltNameLen;
	int bFoundPositiveMatch;
	cstr_t *pStr = NULL;
	cstr_t *pstrCN = NULL;
	int gnuRet;
	DEFiRet;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	bFoundPositiveMatch = 0;
	CHKiRet(rsCStrConstruct(&pStr));

	/* 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(*pCert, iAltName,
				szAltName, &szAltNameLen, NULL);
		if(gnuRet < 0)
			break;
		else if(gnuRet == GNUTLS_SAN_DNSNAME) {
			dbgprintf("subject alt dnsName: '%s'\n", szAltName);
			snprintf((char*)lnBuf, sizeof(lnBuf), "DNSname: %s; ", szAltName);
			CHKiRet(rsCStrAppendStr(pStr, lnBuf));
			CHKiRet(gtlsChkOnePeerName(pThis, (uchar*)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... */
		CHKiRet(gtlsGetCN(pThis, pCert, &pstrCN));
		if(pstrCN != NULL) { /* NULL if there was no CN present */
			dbgprintf("gtls now checking auth for CN '%s'\n", cstrGetSzStr(pstrCN));
			snprintf((char*)lnBuf, sizeof(lnBuf), "CN: %s; ", cstrGetSzStr(pstrCN));
			CHKiRet(rsCStrAppendStr(pStr, lnBuf));
			CHKiRet(gtlsChkOnePeerName(pThis, cstrGetSzStr(pstrCN), &bFoundPositiveMatch));
		}
	}

	if(!bFoundPositiveMatch) {
		dbgprintf("invalid peer name, not permitted to talk to it\n");
		if(pThis->bReportAuthErr == 1) {
			CHKiRet(cstrFinalize(pStr));
			errno = 0;
			errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer name not authorized -  "
					"not permitted to talk to it. Names: %s",
					cstrGetSzStr(pStr));
			pThis->bReportAuthErr = 0;
		}
		ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
	}

finalize_it:
	if(pStr != NULL)
		rsCStrDestruct(&pStr);
	if(pstrCN != NULL)
		rsCStrDestruct(&pstrCN);
	RETiRet;
}


/* check the ID of the remote peer - used for both fingerprint and
 * name authentication. This is common code. Will call into specific
 * drivers once the certificate has been obtained.
 * rgerhards, 2008-05-08
 */
static rsRetVal
gtlsChkPeerID(nsd_gtls_t *pThis)
{
	const gnutls_datum *cert_list;
	unsigned int list_size = 0;
	gnutls_x509_crt cert;
	int bMustDeinitCert = 0;
	int gnuRet;
	DEFiRet;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	/* This function only works for X.509 certificates.  */
	if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509)
		return RS_RET_TLS_CERT_ERR;

	cert_list = gnutls_certificate_get_peers(pThis->sess, &list_size);

	if(list_size < 1) {
		if(pThis->bReportAuthErr == 1) {
			errno = 0;
			errmsg.LogError(0, RS_RET_TLS_NO_CERT, "error: peer did not provide a certificate, "
					"not permitted to talk to it");
			pThis->bReportAuthErr = 0;
		}
		ABORT_FINALIZE(RS_RET_TLS_NO_CERT);
	}

	/* 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
	 */
	CHKgnutls(gnutls_x509_crt_init(&cert));
	bMustDeinitCert = 1; /* indicate cert is initialized and must be freed on exit */
	CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER));

	/* Now we see which actual authentication code we must call.  */
	if(pThis->authMode == GTLS_AUTH_CERTFINGERPRINT) {
		CHKiRet(gtlsChkPeerFingerprint(pThis, &cert));
	} else {
		assert(pThis->authMode == GTLS_AUTH_CERTNAME);
		CHKiRet(gtlsChkPeerName(pThis, &cert));
	}

finalize_it:
	if(bMustDeinitCert)
		gnutls_x509_crt_deinit(cert);

	RETiRet;
}


/* Verify the validity of the remote peer's certificate.
 * rgerhards, 2008-05-21
 */
static rsRetVal
gtlsChkPeerCertValidity(nsd_gtls_t *pThis)
{
	DEFiRet;
	char *pszErrCause;
	int gnuRet;
	cstr_t *pStr;
	unsigned stateCert;
	const gnutls_datum *cert_list;
	unsigned cert_list_size = 0;
	gnutls_x509_crt cert;
	unsigned i;
	time_t ttCert;
	time_t ttNow;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	/* check if we have at least one cert */
	cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size);
	if(cert_list_size < 1) {
		errno = 0;
		errmsg.LogError(0, RS_RET_TLS_NO_CERT, "peer did not provide a certificate, not permitted to talk to it");
		ABORT_FINALIZE(RS_RET_TLS_NO_CERT);
	}

	CHKgnutls(gnutls_certificate_verify_peers2(pThis->sess, &stateCert));

	if(stateCert & GNUTLS_CERT_INVALID) {
		/* provide error details if we have them */
		if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) {
			pszErrCause = "signer not found";
		} else if(stateCert & GNUTLS_CERT_SIGNER_NOT_CA) {
			pszErrCause = "signer is not a CA";
		} else if(stateCert & GNUTLS_CERT_INSECURE_ALGORITHM) {
			pszErrCause = "insecure algorithm";
		} else if(stateCert & GNUTLS_CERT_REVOKED) {
			pszErrCause = "certificate revoked";
		} else {
			pszErrCause = "GnuTLS returned no specific reason";
			dbgprintf("GnuTLS returned no specific reason for GNUTLS_CERT_INVALID, certificate "
				 "status is %d\n", stateCert);
		}
		errmsg.LogError(0, NO_ERRCODE, "not permitted to talk to peer, certificate invalid: %s",
				pszErrCause);
		gtlsGetCertInfo(pThis, &pStr);
		errmsg.LogError(0, NO_ERRCODE, "invalid cert info: %s", cstrGetSzStr(pStr));
		cstrDestruct(&pStr);
		ABORT_FINALIZE(RS_RET_CERT_INVALID);
	}

	/* get current time for certificate validation */
	if(time(&ttNow) == -1)
		ABORT_FINALIZE(RS_RET_SYS_ERR);

	/* as it looks, we need to validate the expiration dates ourselves...
	 * We need to loop through all certificates as we need to make sure the
	 * interim certificates are also not expired.
	 */
	for(i = 0 ; i < cert_list_size ; ++i) {
		CHKgnutls(gnutls_x509_crt_init(&cert));
		CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER));
		ttCert = gnutls_x509_crt_get_activation_time(cert);
		if(ttCert == -1)
			ABORT_FINALIZE(RS_RET_TLS_CERT_ERR);
		else if(ttCert > ttNow) {
			errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "not permitted to talk to peer: certificate %d not yet active", i);
			gtlsGetCertInfo(pThis, &pStr);
			errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "invalid cert info: %s", cstrGetSzStr(pStr));
			cstrDestruct(&pStr);
			ABORT_FINALIZE(RS_RET_CERT_NOT_YET_ACTIVE);
		}

		ttCert = gnutls_x509_crt_get_expiration_time(cert);
		if(ttCert == -1)
			ABORT_FINALIZE(RS_RET_TLS_CERT_ERR);
		else if(ttCert < ttNow) {
			errmsg.LogError(0, RS_RET_CERT_EXPIRED, "not permitted to talk to peer: certificate %d expired", i);
			gtlsGetCertInfo(pThis, &pStr);
			errmsg.LogError(0, RS_RET_CERT_EXPIRED, "invalid cert info: %s", cstrGetSzStr(pStr));
			cstrDestruct(&pStr);
			ABORT_FINALIZE(RS_RET_CERT_EXPIRED);
		}
		gnutls_x509_crt_deinit(cert);
	}

finalize_it:
	RETiRet;
}


/* check if it is OK to talk to the remote peer
 * rgerhards, 2008-05-21
 */
rsRetVal
gtlsChkPeerAuth(nsd_gtls_t *pThis)
{
	DEFiRet;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	/* call the actual function based on current auth mode */
	switch(pThis->authMode) {
		case GTLS_AUTH_CERTNAME:
			/* if we check the name, we must ensure the cert is valid */
			CHKiRet(gtlsChkPeerCertValidity(pThis));
			CHKiRet(gtlsChkPeerID(pThis));
			break;
		case GTLS_AUTH_CERTFINGERPRINT:
			CHKiRet(gtlsChkPeerID(pThis));
			break;
		case GTLS_AUTH_CERTVALID:
			CHKiRet(gtlsChkPeerCertValidity(pThis));
			break;
		case GTLS_AUTH_CERTANON:
			FINALIZE;
			break;
	}

finalize_it:
	RETiRet;
}


/* globally de-initialize GnuTLS */
static rsRetVal
gtlsGlblExit(void)
{
	DEFiRet;
	/* X509 stuff */
	gnutls_certificate_free_credentials(xcred);
	gnutls_global_deinit(); /* we are done... */
	RETiRet;
}


/* end a GnuTLS 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
gtlsEndSess(nsd_gtls_t *pThis)
{
	int gnuRet;
	DEFiRet;

	if(pThis->bHaveSess) {
		if(pThis->bIsInitiator) {
			gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR);
			while(gnuRet == GNUTLS_E_INTERRUPTED || gnuRet == GNUTLS_E_AGAIN) {
				gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR);
			}
		}
		gnutls_deinit(pThis->sess);
	}
	RETiRet;
}


/* a small wrapper for gnutls_transport_set_ptr(). The main intension for
 * creating this wrapper is to get the annoying "cast to pointer from different
 * size" compiler warning just once. There seems to be no way around it, see:
 * http://lists.gnu.org/archive/html/help-gnutls/2008-05/msg00000.html
 * rgerhards, 2008.05-07
 */
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
static inline void
gtlsSetTransportPtr(nsd_gtls_t *pThis, int sock)
{
	/* Note: the compiler warning for the next line is OK - see header comment! */
	gnutls_transport_set_ptr(pThis->sess, (gnutls_transport_ptr_t) sock);
}
#pragma GCC diagnostic warning "-Wint-to-pointer-cast"

/* ---------------------------- end GnuTLS specifics ---------------------------- */


/* Standard-Constructor */
BEGINobjConstruct(nsd_gtls) /* be sure to specify the object type also in END macro! */
	iRet = nsd_ptcp.Construct(&pThis->pTcp);
	pThis->bReportAuthErr = 1;
ENDobjConstruct(nsd_gtls)


/* destructor for the nsd_gtls object */
BEGINobjDestruct(nsd_gtls) /* be sure to specify the object type also in END and CODESTART macros! */
CODESTARTobjDestruct(nsd_gtls)
	if(pThis->iMode == 1) {
		gtlsEndSess(pThis);
	}

	if(pThis->pTcp != NULL) {
		nsd_ptcp.Destruct(&pThis->pTcp);
	}

	if(pThis->pszConnectHost != NULL) {
		free(pThis->pszConnectHost);
	}

	if(pThis->pszRcvBuf == NULL) {
		free(pThis->pszRcvBuf);
	}

	if(pThis->bOurCertIsInit)
		gnutls_x509_crt_deinit(pThis->ourCert);
	if(pThis->bOurKeyIsInit)
		gnutls_x509_privkey_deinit(pThis->ourKey);
ENDobjDestruct(nsd_gtls)


/* 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, int mode)
{
	DEFiRet;
	nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;

	ISOBJ_TYPE_assert((pThis), nsd_gtls);
	if(mode != 0 && mode != 1) {
		errmsg.LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by "
				"gtls netstream driver", mode);
		ABORT_FINALIZE(RS_RET_INVALID_DRVR_MODE);
	}

	pThis->iMode = mode;

finalize_it:
	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 *pNsd, uchar *mode)
{
	DEFiRet;
	nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;

	ISOBJ_TYPE_assert((pThis), nsd_gtls);
	if(mode == NULL || !strcasecmp((char*)mode, "x509/name")) {
		pThis->authMode = GTLS_AUTH_CERTNAME;
	} else if(!strcasecmp((char*) mode, "x509/fingerprint")) {
		pThis->authMode = GTLS_AUTH_CERTFINGERPRINT;
	} else if(!strcasecmp((char*) mode, "x509/certvalid")) {
		pThis->authMode = GTLS_AUTH_CERTVALID;
	} else if(!strcasecmp((char*) mode, "anon")) {
		pThis->authMode = GTLS_AUTH_CERTANON;
	} else {
		errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by "
				"gtls netstream driver", mode);
		ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
	}

/* 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_gtls_t *pThis = (nsd_gtls_t*) pNsd;

	ISOBJ_TYPE_assert((pThis), nsd_gtls);
	if(pPermPeers == NULL)
		FINALIZE;

	if(pThis->authMode != GTLS_AUTH_CERTFINGERPRINT && pThis->authMode != GTLS_AUTH_CERTNAME) {
		errmsg.LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by "
			"gtls netstream driver in the configured authentication mode - ignored");
		ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE);
	}

	pThis->pPermPeers = pPermPeers;

finalize_it:
	RETiRet;
}


/* Provide access to the underlying OS socket. This is primarily
 * useful for other drivers (like nsd_gtls) who utilize ourselfs
 * for some of their functionality. -- rgerhards, 2008-04-18
 */
static rsRetVal
SetSock(nsd_t *pNsd, int sock)
{
	DEFiRet;
	nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;

	ISOBJ_TYPE_assert((pThis), nsd_gtls);
	assert(sock >= 0);

	nsd_ptcp.SetSock(pThis->pTcp, sock);

	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_gtls_t *pThis = (nsd_gtls_t*) pNsd;
	DEFiRet;

	ISOBJ_TYPE_assert((pThis), nsd_gtls);

	if(pThis->iMode == 0) {
		nsd_ptcp.Abort(pThis->pTcp);
	}

	RETiRet;
}



/* initialize the tcp socket for a listner
 * Here, we use the ptcp driver - because there is nothing special
 * at this point with GnuTLS. Things become special once we accept
 * a session, but not during listener setup.
 * gerhards, 2008-04-25
 */
static rsRetVal
LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
	 uchar *pLstnPort, uchar *pLstnIP, int iSessMax)
{
	DEFiRet;
	CHKiRet(gtlsGlblInitLstn());
	iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, pLstnPort, pLstnIP, iSessMax);
finalize_it:
	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 void
CheckConnection(nsd_t __attribute__((unused)) *pNsd)
{
	/* dummy, do nothing */
}


/* 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_gtls_t *pThis = (nsd_gtls_t*) pNsd;
	ISOBJ_TYPE_assert(pThis, nsd_gtls);
	iRet = nsd_ptcp.GetRemoteHName(pThis->pTcp, 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_gtls_t *pThis = (nsd_gtls_t*) pNsd;
	ISOBJ_TYPE_assert(pThis, nsd_gtls);
	iRet = nsd_ptcp.GetRemAddr(pThis->pTcp, ppAddr);
	RETiRet;
}


/* get the remote host's IP address. The returned string must be freed by the
 * caller. -- rgerhards, 2008-04-25
 */
static rsRetVal
GetRemoteIP(nsd_t *pNsd, uchar **ppszIP)
{
	DEFiRet;
	nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
	ISOBJ_TYPE_assert(pThis, nsd_gtls);
	iRet = nsd_ptcp.GetRemoteIP(pThis->pTcp, ppszIP);
	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;
	int gnuRet;
	nsd_gtls_t *pNew = NULL;
	nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;

	ISOBJ_TYPE_assert((pThis), nsd_gtls);
	CHKiRet(nsd_gtlsConstruct(&pNew)); // TODO: prevent construct/destruct!
	CHKiRet(nsd_ptcp.Destruct(&pNew->pTcp));
	CHKiRet(nsd_ptcp.AcceptConnReq(pThis->pTcp, &pNew->pTcp));
	
	if(pThis->iMode == 0) {
		/* we are in non-TLS mode, so we are done */
		*ppNew = (nsd_t*) pNew;
		FINALIZE;
	}

	/* if we reach this point, we are in TLS mode */
	CHKiRet(gtlsInitSession(pNew));
	gtlsSetTransportPtr(pNew, ((nsd_ptcp_t*) (pNew->pTcp))->sock);
	pNew->authMode = pThis->authMode;
	pNew->pPermPeers = pThis->pPermPeers;

	/* we now do the handshake. This is a bit complicated, because we are 
	 * on non-blocking sockets. Usually, the handshake will not complete
	 * immediately, so that we need to retry it some time later.
	 */
	gnuRet = gnutls_handshake(pNew->sess);
	if(gnuRet == GNUTLS_E_AGAIN || gnuRet == GNUTLS_E_INTERRUPTED) {
		pNew->rtryCall = gtlsRtry_handshake;
		dbgprintf("GnuTLS handshake does not complete immediately - setting to retry (this is OK and normal)\n");
	} else if(gnuRet == 0) {
		/* we got a handshake, now check authorization */
		CHKiRet(gtlsChkPeerAuth(pNew));
	} else {
		ABORT_FINALIZE(RS_RET_TLS_HANDSHAKE_ERR);
	}

	pNew->iMode = 1; /* this session is now in TLS mode! */

	*ppNew = (nsd_t*) pNew;

finalize_it:
	if(iRet != RS_RET_OK) {
		if(pNew != NULL)
			nsd_gtlsDestruct(&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)
{
	DEFiRet;
	ssize_t iBytesCopy; /* how many bytes are to be copied to the client buffer? */
	nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	if(pThis->bAbortConn)
		ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ);

	if(pThis->iMode == 0) {
		CHKiRet(nsd_ptcp.Rcv(pThis->pTcp, pBuf, pLenBuf));
		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_GTLS_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(gtlsRecordRecv(pThis));
	}

	if(pThis->lenRcvBuf == 0) { /* EOS */
		*pLenBuf = 0;
		/* in this case, we also need to free the receive buffer, if we
		 * allocated one. -- rgerhards, 2008-12-03
		 */
		if(pThis->pszRcvBuf != NULL) {
			free(pThis->pszRcvBuf);
			pThis->pszRcvBuf = NULL;
		}
		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:
	dbgprintf("gtlsRcv 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)
{
	int iSent;
	nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
	DEFiRet;
	ISOBJ_TYPE_assert(pThis, nsd_gtls);

	if(pThis->bAbortConn)
		ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ);

	if(pThis->iMode == 0) {
		CHKiRet(nsd_ptcp.Send(pThis->pTcp, pBuf, pLenBuf));
		FINALIZE;
	}

	/* in TLS mode now */
	while(1) { /* loop broken inside */
		iSent = gnutls_record_send(pThis->sess, pBuf, *pLenBuf);
		if(iSent >= 0) {
			*pLenBuf = iSent;
			break;
		}
		if(iSent != GNUTLS_E_INTERRUPTED && iSent != GNUTLS_E_AGAIN) {
			dbgprintf("unexpected GnuTLS error %d in %s:%d\n", iSent, __FILE__, __LINE__);
			gnutls_perror(iSent); /* TODO: can we do better? */
			ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
		}
	}

finalize_it:
	RETiRet;
}

/* Enable KEEPALIVE handling on the socket.
 * rgerhards, 2009-06-02
 */
static rsRetVal
EnableKeepAlive(nsd_t *pNsd)
{
	return nsd_ptcp.EnableKeepAlive(pNsd);
}



/* open a connection to a remote host (server). With GnuTLS, we always
 * open a plain tcp socket and then, if in TLS mode, do a handshake on it.
 * rgerhards, 2008-03-19
 */
static rsRetVal
Connect(nsd_t *pNsd, int family, uchar *port, uchar *host)
{
	nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
	int sock;
	int gnuRet;
	/* TODO: later? static const int cert_type_priority[3] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 };*/
	static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 };
	DEFiRet;

	ISOBJ_TYPE_assert(pThis, nsd_gtls);
	assert(port != NULL);
	assert(host != NULL);

	CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host));

	if(pThis->iMode == 0)
		FINALIZE;
	
	/* we reach this point if in TLS mode */
	CHKgnutls(gnutls_init(&pThis->sess, GNUTLS_CLIENT));
	pThis->bHaveSess = 1;
	pThis->bIsInitiator = 1;

	/* in the client case, we need to set a callback that ensures our certificate
	 * will be presented to the server even if it is not signed by one of the server's
	 * trusted roots. This is necessary to support fingerprint authentication.
	 */
	/* store a pointer to ourselfs (needed by callback) */
	gnutls_session_set_ptr(pThis->sess, (void*)pThis);
	iRet = gtlsLoadOurCertKey(pThis); /* first load .pem files */
	if(iRet == RS_RET_OK) {
		gnutls_certificate_client_set_retrieve_function(xcred, gtlsClientCertCallback);
	} else if(iRet != RS_RET_CERTLESS) {
		FINALIZE; /* we have an error case! */
	}

	/* Use default priorities */
	CHKgnutls(gnutls_set_default_priority(pThis->sess));
	CHKgnutls(gnutls_certificate_type_set_priority(pThis->sess, cert_type_priority));

	/* put the x509 credentials to the current session */
	CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, xcred));

	/* assign the socket to GnuTls */
	CHKiRet(nsd_ptcp.GetSock(pThis->pTcp, &sock));
	gtlsSetTransportPtr(pThis, sock);

	/* we need to store the hostname as an alternate mean of authentication if no
	 * permitted peer names are given. Using the hostname is quite useful. It permits
	 * auto-configuration of security if a commen root cert is present. -- rgerhards, 2008-05-26
	 */
	CHKmalloc(pThis->pszConnectHost = (uchar*)strdup((char*)host));

	/* and perform the handshake */
	CHKgnutls(gnutls_handshake(pThis->sess));
	dbgprintf("GnuTLS handshake succeeded\n");

	/* now check if the remote peer is permitted to talk to us - ideally, we 
	 * should do this during the handshake, but GnuTLS does not yet provide 
	 * the necessary callbacks -- rgerhards, 2008-05-26
	 */
	CHKiRet(gtlsChkPeerAuth(pThis));

finalize_it:
	if(iRet != RS_RET_OK) {
		if(pThis->bHaveSess) {
			gnutls_deinit(pThis->sess);
			pThis->bHaveSess = 0;
		}
	}

	RETiRet;
}


/* queryInterface function */
BEGINobjQueryInterface(nsd_gtls)
CODESTARTobjQueryInterface(nsd_gtls)
	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_gtlsConstruct;
	pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_gtlsDestruct;
	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->SetPermPeers =SetPermPeers;
	pIf->CheckConnection = CheckConnection;
	pIf->GetRemoteHName = GetRemoteHName;
	pIf->GetRemoteIP = GetRemoteIP;
	pIf->GetRemAddr = GetRemAddr;
	pIf->EnableKeepAlive = EnableKeepAlive;
finalize_it:
ENDobjQueryInterface(nsd_gtls)


/* exit our class
 */
BEGINObjClassExit(nsd_gtls, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
CODESTARTObjClassExit(nsd_gtls)
	gtlsGlblExit();	/* shut down GnuTLS */

	/* release objects we no longer need */
	objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME);
	objRelease(net, LM_NET_FILENAME);
	objRelease(glbl, CORE_COMPONENT);
	objRelease(errmsg, CORE_COMPONENT);
ENDObjClassExit(nsd_gtls)


/* Initialize the nsd_gtls class. Must be called as the very first method
 * before anything else is called inside this class.
 * rgerhards, 2008-02-19
 */
BEGINObjClassInit(nsd_gtls, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
	/* request objects we use */
	CHKiRet(objUse(errmsg, CORE_COMPONENT));
	CHKiRet(objUse(glbl, CORE_COMPONENT));
	CHKiRet(objUse(net, LM_NET_FILENAME));
	CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME));

	/* now do global TLS init stuff */
	CHKiRet(gtlsGlblInit());
ENDObjClassInit(nsd_gtls)


/* --------------- here now comes the plumbing that makes as a library module --------------- */


BEGINmodExit
CODESTARTmodExit
	nsdsel_gtlsClassExit();
	nsd_gtlsClassExit();
	pthread_mutex_destroy(&mutGtlsStrerror);
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(nsd_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
	CHKiRet(nsdsel_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */

	pthread_mutex_init(&mutGtlsStrerror, NULL);
ENDmodInit
/* vi:set ai:
 */
