<commit_before>/*
 * Copyright (c) 2005, <NAME> and IFS Institute for Software at HSR Rapperswil, Switzerland
 * All rights reserved.
 *
 * This library/application is free software; you can redistribute and/or modify it under the terms of
 * the license that is included with this library/application in the file license.txt.
 */

//--- interface include --------------------------------------------------------
#include "SSLSocket.h"

//--- standard modules used ----------------------------------------------------
#include "SysLog.h"
#include "System.h"
#include "SSLSocketStream.h"
#include "Resolver.h" // to verify host name versus certificate
#include "SSLSocketUtils.h"
#include "SSLObjectManager.h"

//#define STREAM_TRACE

//--- SSLSocket ----------------------
SSLSocket::SSLSocket(SSL_CTX *ctx, int socket, const Anything &clientInfo, bool doClose, long  timeout, Allocator *a)
	: Socket(socket, clientInfo, doClose, timeout, a)
	, fContext(ctx)
	, fPeerCert(0)
	, fSSLSocketArgs()
{
	StartTrace1(SSLSocket.Ctor, "using allocator: [" << (long)a << "]");
}

SSLSocket::SSLSocket(SSLSocketArgs &sslSocketArgs, SSL_CTX *ctx, int socket, const Anything &clientInfo, bool doClose, long  timeout, Allocator *a)
	: Socket(socket, clientInfo, doClose, timeout, a)
	, fContext(ctx)
	, fPeerCert(0)
	, fSSLSocketArgs(sslSocketArgs)
{
	StartTrace1(SSLSocket.Ctor, "using allocator: [" << (long)a << "]");
}

SSLSocket::~SSLSocket()
{
	StartTrace(SSLSocket.~SSLSocket);
}

unsigned long SSLSocket::GetSSLError(SSL *ssl, int res)
{
	return ( ssl ? SSL_get_error(ssl, res) : ERR_get_error() );
}

void SSLSocket::ReportSSLError( unsigned long err)
{
	StartTrace(SSLSocket.ReportSSLError);
	while (err) {
		// need to empty per thread error queue of SSL
		const int buflen = 256;
		char buf[buflen];
#if defined(__sun) // SOP: FIXME use openssl version > 0.9.6
		// old openssl call:
		ERR_error_string(err, buf);
#else
		// new openssl call:
		ERR_error_string_n(err, buf, buflen);
#endif
		String logMsg("SSL error: ");
		logMsg << (long)err << " : " << buf;
		SysLog::Error(logMsg);
		Trace(logMsg);

		err = GetSSLError();
	}
}

Anything SSLSocket::ReportSSLError(Anything &errAny, unsigned long err)
{
	StartTrace(SSLSocket.ReportSSLError);
	while (err) {
		// need to empty per thread error queue of SSL
		const int buflen = 256;
		char buf[buflen];
#if defined(__sun) // SOP: FIXME use openssl version > 0.9.6
		// old openssl call:
		ERR_error_string(err, buf);
#else
		// new openssl call:
		ERR_error_string_n(err, buf, buflen);
#endif
		String logMsg("SSL error: ");
		logMsg << (long)err << " : " << buf;
		errAny.Append(logMsg);
		Trace(logMsg);
		err = GetSSLError();
	}
	return errAny;
}

bool SSLSocket::ShouldRetry(SSL *ssl, int res, bool handshake)
{
	StartTrace(SSLSocket.ShouldRetry);
	Trace("res = " << long(res) << " ssl = " << (long) ssl);
	if (res >= 1) {
		return false;
	}
	unsigned long err = GetSSLError(ssl, res);
	if (SSL_ERROR_WANT_READ == err) {
		return IsReadyForReading();
	} else if (SSL_ERROR_WANT_WRITE == err) {
		return IsReadyForWriting();
	} else if (SSL_ERROR_ZERO_RETURN) { // clean  way to handle peer did not send data
#if defined(STREAM_TRACE)
		// Do not report SSL error
		String msg("SSLSocket: end of data (connection closed) on file descriptor: ");
		msg << GetFd() << (handshake ? " at Handshake" : " at normal r/w");
		SysLog::Info(msg);
#endif
		return false;
	}
	ReportSSLError(err);
	return false;
}

String SSLSocket::GetSessionID(SSL *ssl)
{
	Assert(ssl);

	String result;
	SSL_SESSION *session = SSL_get_session(ssl);
	if (!session) {
		return String();
	}
	return String((void *)session->session_id, session->session_id_length);
}

SSL_SESSION *SSLSocket::SessionResumptionHookResumeSession(SSL *ssl)
{
	StartTrace(SSLSocket.SessionResumptionHookResumeSession);
	return (SSL_SESSION *) NULL;
}

void SSLSocket::SessionResumptionHookSetSession(SSL *ssl, SSL_SESSION *sslSessionStored, bool wasResumed)
{
	StartTrace(SSLSocket.SessionResumptionHookSetSession);
	return;
}

iostream *SSLSocket::DoMakeStream()
{
	StartTrace(SSLSocket.DoMakeStream);
	Anything &sslinfo = fClientInfo["SSL"];
	sslinfo["Peer"]["SSLCertVerifyStatus"]["SSL"]["Ok"] = false;
	sslinfo["Peer"]["AppLevelCertVerifyStatus"] = false;
	SSL *ssl = SSL_new (fContext);
	Assert(ssl);

	// Continuing with invalid SSL structure will segfault.
	// Returning a NULL pointer is a trifle better.
	if ( ssl == (SSL *) NULL ) {
		String logMsg("SSL error: SSLContext not good.");
		SysLog::Error(logMsg);
		return NULL;
	}

	bool verifyCallbackWasSet = SSL_CTX_get_verify_callback(fContext) != NULL;
	Anything appData;
	if ( verifyCallbackWasSet ) {
		appData["SSLSocketArgs"] = (IFAObject *) &fSSLSocketArgs;
		appData.Append(sslinfo);
		if ( SSL_set_ex_data(ssl, Thread::MyId(), &appData) == false ) {
			String logMsg("SSL error: Setting application specific data failed.");
			SysLog::Error(logMsg);
			return NULL;
		}
	}
	if (GetTimeout() > 0) {
		Socket::SetToNonBlocking(GetFd());
	}
	Trace("SSL_set_fd: " << GetFd());
	int res = SSL_set_fd (ssl, GetFd());
	ReportSSLError(GetSSLError(ssl, res));

	SSL_SESSION *sslSessionStored = SessionResumptionHookResumeSession(ssl);

	Trace("---- connect sequence");
	do {
		res = PrepareSocket(ssl);
	} while (ShouldRetry(ssl, res, true));
	if ( res <= 0 ) { // 0 = Handshake failure, SSL layer shut down connection properly, -1 = error, 1 = ok
		if ( ssl ) {
			SSL_free (ssl);
		}
		ssl = 0;
		ERR_clear_error();
		return 0;
	}
	Trace("---- end connect sequence");

	Trace("SSL connection using " << SSL_get_cipher (ssl));
	String sessionID = GetSessionID(ssl);
	sslinfo["SessionId"] = String().AppendAsHex((const unsigned char *)(const char *)sessionID, sessionID.Length());
	if ( verifyCallbackWasSet ) {
		// This data will be filled if /SSLUseAppCallBack was given in SSLModule
		sslinfo["PreVerify"] = appData["Chain"];
	}
	sslinfo["Cipher"] = String(SSL_get_cipher(ssl));
	sslinfo["SessionIsResumed"] = SSL_session_reused(ssl);

	DoCheckPeerCertificate(sslinfo, ssl);

	// Application decides to continue or not depending on CLIENT_INFO
	TraceAny(fClientInfo, "SSL client info");
	Trace(" session cache number: " << SSL_CTX_sess_number(fContext) << ENDL
		  << " size: " << SSL_CTX_sess_get_cache_size(fContext) << ENDL
		  << " hits: " << SSL_CTX_sess_hits(fContext) << ENDL
		  << " misses: " << SSL_CTX_sess_misses(fContext) << ENDL
		  << " timeouts: " << SSL_CTX_sess_timeouts(fContext) << ENDL
		  << " overflows: " << SSL_CTX_sess_cache_full(fContext) << ENDL
		  << " mode: " << SSL_CTX_get_session_cache_mode(fContext) << ENDL
		  << " timeout : " << SSL_CTX_get_timeout(fContext) << ENDL
		 );

	SessionResumptionHookSetSession(ssl, sslSessionStored, sslinfo["SessionIsResumed"].AsLong(0) != 0L);

	SSL_SESSION *sslSessionCurrent;
	if ( (sslSessionCurrent = SSL_get_session(ssl)) == (SSL_SESSION *) NULL) {
		Trace("No valid SSL_SESSION created!!!");
		String logMsg("SSL error: SSLSession not good.");
		SysLog::Error(logMsg);
		return NULL;
	} else {
		TraceAny(SSLObjectManager::TraceSSLSession(sslSessionCurrent), "sslSessionCurrent");
		TraceAny(sslinfo, "SSL  info");
	}
//	SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
	return new(GetAllocator()) SSLSocketStream(ssl, this, GetTimeout());
}

void SSLSocket::DoCheckPeerCertificate(Anything &sslinfo, SSL *ssl)
{
	StartTrace(SSLSocket.DoCheckPeerCertificate);
// Commented code should only be done on clients for checking servers
// more work needed for wildcard certs:
// sslinfo["hostnameverify"] = (fClientInfo["REMOTE_ADDR"].AsString()
// == Resolver::DNS2IPAddress(sslinfo["peer"]["CN"].AsString()));
// should validate host name of peer also
	Trace("VerifyCertifiedEntity: " << fSSLSocketArgs.VerifyCertifiedEntity());
	Trace("CertVerifyString: " << fSSLSocketArgs.CertVerifyString());
	Trace("CertVerifyStringIsFilter: " << fSSLSocketArgs.CertVerifyStringIsFilter());
	if ( CheckPeerCertificate(ssl, sslinfo) == true ) {
		if ( fSSLSocketArgs.VerifyCertifiedEntity() == 1 ) {
			sslinfo["Peer"]["Whole"] = SSLSocketUtils::GetPeerAsString(fPeerCert);
			sslinfo["Peer"]["ParsedDn"] = SSLSocketUtils::ParseDN(sslinfo["Peer"]["Whole"].AsString());
			sslinfo["Peer"]["Issuer"] = SSLSocketUtils::GetPeerIssuerAsString(fPeerCert);
			if (fSSLSocketArgs.CertVerifyStringIsFilter()) {
				sslinfo["Peer"]["AppLevelCertVerifyStatus"] = SSLSocketUtils::VerifyDN(fSSLSocketArgs.CertVerifyString(), sslinfo["Peer"]["ParsedDn"]);
			} else {
				sslinfo["Peer"]["AppLevelCertVerifyStatus"] = (fSSLSocketArgs.CertVerifyString() == sslinfo["Peer"]["Whole"].AsString());
				Trace("Comparing: " << fSSLSocketArgs.CertVerifyString() << " vs. " << sslinfo["Peer"]["Whole"].AsString());
			}
		}
	} else {
		sslinfo["Peer"]["AppLevelCertVerifyStatus"] = false;
	}
	if ( fPeerCert ) {
		X509_free(fPeerCert);
	}
	// A not legal, but effective way to terminate the ssl handshake.
	// You need to define SSL_set_verify_result yourself, not part of
	// OpenSSL public API, but known "hack".
	// SSL_set_verify_result(ssl,X509_V_ERR_APPLICATION_VERIFICATION);

}

int SSLSocket::SSLVerifyCallbackOk(int preverify_ok, X509_STORE_CTX *ctx)
{
	StartTrace(SSLSocket.SSLVerifyCallbackOk);
	return 1;
}

int SSLSocket::SSLVerifyCallbackFalse(int preverify_ok, X509_STORE_CTX *ctx)
{
	StartTrace(SSLSocket.SSLVerifyCallbackFalse);
	return 0;
}

int SSLSocket::SSLVerifyCallback(int preverify_ok, X509_STORE_CTX *ctx)
{
	StartTrace(SSLSocket.SSLVerifyCallback);
	SSL *ssl  = NULL;;
	Anything *pAppData = NULL;
	SSLSocketArgs *pSSLSocketArgs;
	X509 *cert;
	int depth = 0;
	int err = 0;
	int allowedDepth = 0;

	err  = X509_STORE_CTX_get_error(ctx);

	depth = X509_STORE_CTX_get_error_depth(ctx);
//   /*
//	* Retrieve the pointer to the SSL of the connection currently treated
//	* and the application specific data stored into the SSL object.
//	*/
	ssl = (SSL *)  X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
	allowedDepth = SSL_get_verify_depth(ssl);

	pAppData =  (Anything *)  SSL_get_ex_data(ssl, Thread::MyId());
	if (depth > allowedDepth + 1) {
		preverify_ok = 0;
		err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
		X509_STORE_CTX_set_error(ctx, err);
	}

	pSSLSocketArgs = (SSLSocketArgs *) (*pAppData)["SSLSocketArgs"].AsIFAObject(0);
	cert = X509_STORE_CTX_get_current_cert(ctx);
	(*pAppData)["Chain"]["Subjects"].Append(SSLSocketUtils::GetPeerAsString(cert));
	(*pAppData)["Chain"]["Issuers"].Append(SSLSocketUtils::GetPeerIssuerAsString(cert));
	(*pAppData)["Chain"]["Depth"] = depth;
	(*pAppData)["Chain"]["Alloweddepth"] = allowedDepth;
	if (!preverify_ok) {
		(*pAppData)["Chain"]["Error"] = String(X509_verify_cert_error_string(err));
	}
	return preverify_ok;
}

long SSLSocket::GetReadCount() const
{
	StartTrace(SSLSocket.GetReadCount);
	if (fStream) {<commit_msg>implemented temporary workaround for ThreadID out of range for use with SSL_*et_ex_data.<commit_after>/*
 * Copyright (c) 2005, <NAME> and IFS Institute for Software at HSR Rapperswil, Switzerland
 * All rights reserved.
 *
 * This library/application is free software; you can redistribute and/or modify it under the terms of
 * the license that is included with this library/application in the file license.txt.
 */

//--- interface include --------------------------------------------------------
#include "SSLSocket.h"

//--- standard modules used ----------------------------------------------------
#include "SysLog.h"
#include "System.h"
#include "SSLSocketStream.h"
#include "Resolver.h" // to verify host name versus certificate
#include "SSLSocketUtils.h"
#include "SSLObjectManager.h"

//#define STREAM_TRACE

//--- SSLSocket ----------------------
SSLSocket::SSLSocket(SSL_CTX *ctx, int socket, const Anything &clientInfo, bool doClose, long  timeout, Allocator *a)
	: Socket(socket, clientInfo, doClose, timeout, a)
	, fContext(ctx)
	, fPeerCert(0)
	, fSSLSocketArgs()
{
	StartTrace1(SSLSocket.Ctor, "using allocator: [" << (long)a << "]");
}

SSLSocket::SSLSocket(SSLSocketArgs &sslSocketArgs, SSL_CTX *ctx, int socket, const Anything &clientInfo, bool doClose, long  timeout, Allocator *a)
	: Socket(socket, clientInfo, doClose, timeout, a)
	, fContext(ctx)
	, fPeerCert(0)
	, fSSLSocketArgs(sslSocketArgs)
{
	StartTrace1(SSLSocket.Ctor, "using allocator: [" << (long)a << "]");
}

SSLSocket::~SSLSocket()
{
	StartTrace(SSLSocket.~SSLSocket);
}

unsigned long SSLSocket::GetSSLError(SSL *ssl, int res)
{
	return ( ssl ? SSL_get_error(ssl, res) : ERR_get_error() );
}

void SSLSocket::ReportSSLError( unsigned long err)
{
	StartTrace(SSLSocket.ReportSSLError);
	while (err) {
		// need to empty per thread error queue of SSL
		const int buflen = 256;
		char buf[buflen];
#if defined(__sun) // SOP: FIXME use openssl version > 0.9.6
		// old openssl call:
		ERR_error_string(err, buf);
#else
		// new openssl call:
		ERR_error_string_n(err, buf, buflen);
#endif
		String logMsg("SSL error: ");
		logMsg << (long)err << " : " << buf;
		SysLog::Error(logMsg);
		Trace(logMsg);

		err = GetSSLError();
	}
}

Anything SSLSocket::ReportSSLError(Anything &errAny, unsigned long err)
{
	StartTrace(SSLSocket.ReportSSLError);
	while (err) {
		// need to empty per thread error queue of SSL
		const int buflen = 256;
		char buf[buflen];
#if defined(__sun) // SOP: FIXME use openssl version > 0.9.6
		// old openssl call:
		ERR_error_string(err, buf);
#else
		// new openssl call:
		ERR_error_string_n(err, buf, buflen);
#endif
		String logMsg("SSL error: ");
		logMsg << (long)err << " : " << buf;
		errAny.Append(logMsg);
		Trace(logMsg);
		err = GetSSLError();
	}
	return errAny;
}

bool SSLSocket::ShouldRetry(SSL *ssl, int res, bool handshake)
{
	StartTrace(SSLSocket.ShouldRetry);
	Trace("res = " << long(res) << " ssl = " << (long) ssl);
	if (res >= 1) {
		return false;
	}
	unsigned long err = GetSSLError(ssl, res);
	if (SSL_ERROR_WANT_READ == err) {
		return IsReadyForReading();
	} else if (SSL_ERROR_WANT_WRITE == err) {
		return IsReadyForWriting();
	} else if (SSL_ERROR_ZERO_RETURN) { // clean  way to handle peer did not send data
#if defined(STREAM_TRACE)
		// Do not report SSL error
		String msg("SSLSocket: end of data (connection closed) on file descriptor: ");
		msg << GetFd() << (handshake ? " at Handshake" : " at normal r/w");
		SysLog::Info(msg);
#endif
		return false;
	}
	ReportSSLError(err);
	return false;
}

String SSLSocket::GetSessionID(SSL *ssl)
{
	Assert(ssl);

	String result;
	SSL_SESSION *session = SSL_get_session(ssl);
	if (!session) {
		return String();
	}
	return String((void *)session->session_id, session->session_id_length);
}

SSL_SESSION *SSLSocket::SessionResumptionHookResumeSession(SSL *ssl)
{
	StartTrace(SSLSocket.SessionResumptionHookResumeSession);
	return (SSL_SESSION *) NULL;
}

void SSLSocket::SessionResumptionHookSetSession(SSL *ssl, SSL_SESSION *sslSessionStored, bool wasResumed)
{
	StartTrace(SSLSocket.SessionResumptionHookSetSession);
	return;
}

iostream *SSLSocket::DoMakeStream()
{
	StartTrace(SSLSocket.DoMakeStream);
	Anything &sslinfo = fClientInfo["SSL"];
	sslinfo["Peer"]["SSLCertVerifyStatus"]["SSL"]["Ok"] = false;
	sslinfo["Peer"]["AppLevelCertVerifyStatus"] = false;
	SSL *ssl = SSL_new (fContext);
	Assert(ssl);

	// Continuing with invalid SSL structure will segfault.
	// Returning a NULL pointer is a trifle better.
	if ( ssl == (SSL *) NULL ) {
		String logMsg("SSL error: SSLContext not good.");
		SysLog::Error(logMsg);
		return NULL;
	}

	bool verifyCallbackWasSet = SSL_CTX_get_verify_callback(fContext) != NULL;
	Anything appData;
	if ( verifyCallbackWasSet ) {
		appData["SSLSocketArgs"] = (IFAObject *) &fSSLSocketArgs;
		appData.Append(sslinfo);

		int thread_id = (unsigned)Thread::MyId() % 1000000;
		if ( SSL_set_ex_data(ssl, thread_id, &appData) == false ) {
			String logMsg("SSL error: Setting application specific data failed.");
			SysLog::Error(logMsg);
			return NULL;
		}
	}
	if (GetTimeout() > 0) {
		Socket::SetToNonBlocking(GetFd());
	}
	Trace("SSL_set_fd: " << GetFd());
	int res = SSL_set_fd (ssl, GetFd());
	ReportSSLError(GetSSLError(ssl, res));

	SSL_SESSION *sslSessionStored = SessionResumptionHookResumeSession(ssl);

	Trace("---- connect sequence");
	do {
		res = PrepareSocket(ssl);
	} while (ShouldRetry(ssl, res, true));
	if ( res <= 0 ) { // 0 = Handshake failure, SSL layer shut down connection properly, -1 = error, 1 = ok
		if ( ssl ) {
			SSL_free (ssl);
		}
		ssl = 0;
		ERR_clear_error();
		return 0;
	}
	Trace("---- end connect sequence");

	Trace("SSL connection using " << SSL_get_cipher (ssl));
	String sessionID = GetSessionID(ssl);
	sslinfo["SessionId"] = String().AppendAsHex((const unsigned char *)(const char *)sessionID, sessionID.Length());
	if ( verifyCallbackWasSet ) {
		// This data will be filled if /SSLUseAppCallBack was given in SSLModule
		sslinfo["PreVerify"] = appData["Chain"];
	}
	sslinfo["Cipher"] = String(SSL_get_cipher(ssl));
	sslinfo["SessionIsResumed"] = SSL_session_reused(ssl);

	DoCheckPeerCertificate(sslinfo, ssl);

	// Application decides to continue or not depending on CLIENT_INFO
	TraceAny(fClientInfo, "SSL client info");
	Trace(" session cache number: " << SSL_CTX_sess_number(fContext) << ENDL
		  << " size: " << SSL_CTX_sess_get_cache_size(fContext) << ENDL
		  << " hits: " << SSL_CTX_sess_hits(fContext) << ENDL
		  << " misses: " << SSL_CTX_sess_misses(fContext) << ENDL
		  << " timeouts: " << SSL_CTX_sess_timeouts(fContext) << ENDL
		  << " overflows: " << SSL_CTX_sess_cache_full(fContext) << ENDL
		  << " mode: " << SSL_CTX_get_session_cache_mode(fContext) << ENDL
		  << " timeout : " << SSL_CTX_get_timeout(fContext) << ENDL
		 );

	SessionResumptionHookSetSession(ssl, sslSessionStored, sslinfo["SessionIsResumed"].AsLong(0) != 0L);

	SSL_SESSION *sslSessionCurrent;
	if ( (sslSessionCurrent = SSL_get_session(ssl)) == (SSL_SESSION *) NULL) {
		Trace("No valid SSL_SESSION created!!!");
		String logMsg("SSL error: SSLSession not good.");
		SysLog::Error(logMsg);
		return NULL;
	} else {
		TraceAny(SSLObjectManager::TraceSSLSession(sslSessionCurrent), "sslSessionCurrent");
		TraceAny(sslinfo, "SSL  info");
	}
//	SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
	return new(GetAllocator()) SSLSocketStream(ssl, this, GetTimeout());
}

void SSLSocket::DoCheckPeerCertificate(Anything &sslinfo, SSL *ssl)
{
	StartTrace(SSLSocket.DoCheckPeerCertificate);
// Commented code should only be done on clients for checking servers
// more work needed for wildcard certs:
// sslinfo["hostnameverify"] = (fClientInfo["REMOTE_ADDR"].AsString()
// == Resolver::DNS2IPAddress(sslinfo["peer"]["CN"].AsString()));
// should validate host name of peer also
	Trace("VerifyCertifiedEntity: " << fSSLSocketArgs.VerifyCertifiedEntity());
	Trace("CertVerifyString: " << fSSLSocketArgs.CertVerifyString());
	Trace("CertVerifyStringIsFilter: " << fSSLSocketArgs.CertVerifyStringIsFilter());
	if ( CheckPeerCertificate(ssl, sslinfo) == true ) {
		if ( fSSLSocketArgs.VerifyCertifiedEntity() == 1 ) {
			sslinfo["Peer"]["Whole"] = SSLSocketUtils::GetPeerAsString(fPeerCert);
			sslinfo["Peer"]["ParsedDn"] = SSLSocketUtils::ParseDN(sslinfo["Peer"]["Whole"].AsString());
			sslinfo["Peer"]["Issuer"] = SSLSocketUtils::GetPeerIssuerAsString(fPeerCert);
			if (fSSLSocketArgs.CertVerifyStringIsFilter()) {
				sslinfo["Peer"]["AppLevelCertVerifyStatus"] = SSLSocketUtils::VerifyDN(fSSLSocketArgs.CertVerifyString(), sslinfo["Peer"]["ParsedDn"]);
			} else {
				sslinfo["Peer"]["AppLevelCertVerifyStatus"] = (fSSLSocketArgs.CertVerifyString() == sslinfo["Peer"]["Whole"].AsString());
				Trace("Comparing: " << fSSLSocketArgs.CertVerifyString() << " vs. " << sslinfo["Peer"]["Whole"].AsString());
			}
		}
	} else {
		sslinfo["Peer"]["AppLevelCertVerifyStatus"] = false;
	}
	if ( fPeerCert ) {
		X509_free(fPeerCert);
	}
	// A not legal, but effective way to terminate the ssl handshake.
	// You need to define SSL_set_verify_result yourself, not part of
	// OpenSSL public API, but known "hack".
	// SSL_set_verify_result(ssl,X509_V_ERR_APPLICATION_VERIFICATION);

}

int SSLSocket::SSLVerifyCallbackOk(int preverify_ok, X509_STORE_CTX *ctx)
{
	StartTrace(SSLSocket.SSLVerifyCallbackOk);
	return 1;
}

int SSLSocket::SSLVerifyCallbackFalse(int preverify_ok, X509_STORE_CTX *ctx)
{
	StartTrace(SSLSocket.SSLVerifyCallbackFalse);
	return 0;
}

int SSLSocket::SSLVerifyCallback(int preverify_ok, X509_STORE_CTX *ctx)
{
	StartTrace(SSLSocket.SSLVerifyCallback);
	SSL *ssl  = NULL;;
	Anything *pAppData = NULL;
	SSLSocketArgs *pSSLSocketArgs;
	X509 *cert;
	int depth = 0;
	int err = 0;
	int allowedDepth = 0;

	err  = X509_STORE_CTX_get_error(ctx);
	depth = X509_STORE_CTX_get_error_depth(ctx);
//   /*
//	* Retrieve the pointer to the SSL of the connection currently treated
//	* and the application specific data stored into the SSL object.
//	*/
	ssl = (SSL *)  X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
	allowedDepth = SSL_get_verify_depth(ssl);
	int thread_id = (unsigned)Thread::MyId() % 1000000;
	pAppData =  (Anything *)  SSL_get_ex_data(ssl, thread_id);
	if (depth > allowedDepth + 1) {
		preverify_ok = 0;
		err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
		X509_STORE_CTX_set_error(ctx, err);
	}
	pSSLSocketArgs = (SSLSocketArgs *) (*pAppData)["SSLSocketArgs"].AsIFAObject(0);
	cert = X509_STORE_CTX_get_current_cert(ctx);
	(*pAppData)["Chain"]["Subjects"].Append(SSLSocketUtils::GetPeerAsString(cert));
	(*pAppData)["Chain"]["Issuers"].Append(SSLSocketUtils::GetPeerIssuerAsString(cert));
	(*pAppData)["Chain"]["Depth"] = depth;
	(*pAppData)["Chain"]["Alloweddepth"] = allowedDepth;
	if (!preverify_ok) {
		(*pAppData)["Chain"]["Error"] = String(X509_verify_cert_error_string(err));
	}
	return preverify_ok;
}

long SSLSocket::GetReadCount() const
{
	StartTrace(SSLSocket.GetReadCount);
	if (fStream) {
{'fraction_non_alphanumeric': 0.1545296326829715, 'fraction_numerical': 0.008152422826783915, 'mean_word_length': 5.0451273532668885, 'pattern_counts': {'":': 0, '<': 191, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2466519', 'n_tokens_mistral': 7936, 'n_tokens_neox': 7631, 'n_words': 1631}