/***************************************************************************
                          csocket.cpp  -  description
                             -------------------
    begin                : Sat Oct 6 2001
    copyright            : (C) 2001-2003 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "csocket.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>

// includes for sun os
#ifdef HAVE_SYS_SOCKIO_H 
#include <sys/sockio.h>
#endif
#ifdef HAVE_SYS_TERMIOS_H
#include <sys/termios.h>
#endif
#ifdef HAVE_SYS_FILIO_H 
#include <sys/filio.h>
#endif
#endif

#include <sys/types.h>

#ifndef MSG_NOSIGNAL
// 0x2000  /* don't raise SIGPIPE */
#define MSG_NOSIGNAL      0
#endif

#include "../dcos.h"
#include "casyncdns.h"
#include "clogfile.h"
#include "cssl.h"
#include "cnetaddr.h"

#ifdef HAVE_SOCKS
extern "C" {
#include <socks.h>
}
#endif

// init static socket vars
CTraffic CSocket::m_Traffic = CTraffic();
eSocketLog CSocket::m_eSocketLog = eslNONE;

CSocket::CSocket( eSocketType type )
{
	SocketType    = type;
	iHandle       = INVALID_SOCKET;
	m_eSocketMode = esmSOCKET;

	m_pCTX = 0;
	m_pSSL = 0;
}

CSocket::~CSocket()
{
	Disconnect();

#if DCLIB_USES_OPENSSL == 1
	/* This is in case iHandle == INVALID_SOCKET but somehow m_pSSL / m_pCTX are still here */
	if ( m_pSSL )
	{
		SSL_free(m_pSSL);
		m_pSSL = 0;
	}
	
	if ( m_pCTX )
	{
		SSL_CTX_free(m_pCTX);
		m_pCTX = 0;
	}
#endif
}

/** */
int CSocket::SetSocket( int handle, eSocketType sockettype )
{
	if ( handle == INVALID_SOCKET )
	{
		return -1;
	}

	if ( (sockettype != estTCP) && (sockettype != estUDP) )
	{
		return -1;
	}

	SocketType = sockettype;
	iHandle    = handle;

#if DCLIB_USES_OPENSSL == 1
	if ( (m_eSocketMode == esmFULLSSLSERVER) || (m_eSocketMode == esmFULLSSLCLIENT) )
	{
		if ( SSL_set_fd(m_pSSL,iHandle) == 0 )
		{
			sError  = "CSocket::SetSocket: SSL_set_fd failed: ";
			sError += ERR_reason_error_string( ERR_get_error() );
			return -1;
		}
	}
#endif

	return 0;
}

/** */
int CSocket::GetFreeSendBufferSize()
{
	int tot, free = 0;
#ifndef WIN32
	int unsent;
#endif
	socklen_t ilen;

	if ( iHandle == INVALID_SOCKET )
	{
		return free;
	}

	ilen = sizeof(int);

	if ( getsockopt(iHandle, SOL_SOCKET, SO_SNDBUF, (char*)&tot, &ilen) == 0 )
	{
		// quick & dirty fix for free bsd
		free = tot;
#ifndef WIN32
#ifndef __CYGWIN__
		if ( ioctl(iHandle, TIOCOUTQ, &unsent) == 0 )
		{
			free = tot - unsent;
		}
#endif
#endif
	}

	return free;
}

/** */
int CSocket::IsConnect()
{
	int i,err;
	struct timeval tv;
	fd_set rset,wset,eset;

	if ( iHandle == INVALID_SOCKET )
	{
		return -1;
	}

	FD_ZERO(&rset);
	FD_ZERO(&wset);
	FD_ZERO(&eset);
	FD_SET(iHandle, &rset);
	FD_SET(iHandle, &wset);
	FD_SET(iHandle, &eset);

	tv.tv_sec  = 0;
	tv.tv_usec = 1;

	err = -1;

	i = select(FD_SETSIZE, &rset, &wset, &eset, &tv);

	if ( (i > 0) && (!FD_ISSET(iHandle, &eset)) && (FD_ISSET(iHandle, &wset)) )
	{
		err = 1;
	}
	else if ( i == 0 )
	{
		err = 0;
	}
	else if ( err == -1 )
	{
		err = SocketError();

		if ( err != 0 )
		{
			sError = ext_strerror(err);
			err = -1;
		}
	}

	FD_CLR(iHandle, &rset);
	FD_CLR(iHandle, &wset);
	FD_CLR(iHandle, &eset);

	return err;
}

/** */
int CSocket::SocketError()
{
	int err = 0;
	socklen_t ilen;

	if ( iHandle == INVALID_SOCKET )
	{
		return err;
	}

	ilen = sizeof(int);

	if ( getsockopt(iHandle, SOL_SOCKET, SO_ERROR, (char*)&err, &ilen) != 0 )
	{
		err = 0;
	}

	return err;
}

/** */
eConnectState CSocket::Connect( CString Host, bool bAsync )
{
	unsigned int port;
	CString s;

	CNetAddr::ParseHost( Host, s, port );

	// set default port
	if ( port == 0 )
	{
		port = 411;
	}

	return Connect( s, port, bAsync );
}

/** */
eConnectState CSocket::Connect( CString Host, int Port, bool bAsync )
{
	struct sockaddr_in sin,t_sin;
	SOCKET sockfd;
	eAsyncDns ead;

	if ( iHandle != INVALID_SOCKET )
	{
		Disconnect();
	}

	memset((void *)&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;

	if ( Host.IsEmpty() )
	{
		if ( SocketType == estTCP )
		{
			return ecsERROR;
		}
		else
		{
			sin.sin_addr.s_addr = htonl(INADDR_ANY);
		}
	}
	else
	{
		/* skip host lookup if it is a valid IP address */
#ifdef WIN32
		sin.sin_addr.s_addr = inet_addr( Host.Data() );
		if ( sin.sin_addr.s_addr != INADDR_NONE )
#else
		if ( inet_aton( Host.Data(), &sin.sin_addr ) != 0 )
#endif
		{
			m_sIP = Host;
		}
		else if ( bAsync == false )
		{
			// use blocked getaddrinfo
			if ( CNetAddr::GetHostI4( Host.Data(), &t_sin, &sError ) == false )
			{
				return ecsERROR;
			}
			else
			{
		 		memcpy((void *)&sin.sin_addr, (void *)&t_sin.sin_addr, sizeof(t_sin.sin_addr));
				m_sIP  = inet_ntoa(sin.sin_addr);
			}
		}
		else
		{
			// use async dns class
			if ( CAsyncDns::Instance() )
			{
				ead = CAsyncDns::Instance()->GetHostI4( Host, &t_sin, &sError );
			}
			else
			{
				return ecsERROR;
			}

			if ( ead == eadAGAIN )
			{
				return ecsAGAIN;
			}
			else if ( ead == eadERROR )
			{
				return ecsERROR;
			}
			else
			{
				memcpy((void *)&sin.sin_addr, (void *)&t_sin.sin_addr, sizeof(t_sin.sin_addr));
				m_sIP  = inet_ntoa(sin.sin_addr);
			}
		}
	}

	sin.sin_port = htons((unsigned short)Port);

	// update m_sIP
	m_sIP += ':';
	m_sIP += CString::number(Port);

	// socket
	if ( SocketType == estTCP )
	{
		if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
		{
			sError = ext_strerror(errno);
			return ecsERROR;
		}
	}
	else
	{
		if( (sockfd = socket( PF_INET, SOCK_DGRAM, 0 )) < 0 )
		{
			sError = ext_strerror(errno);
			return ecsERROR;
		}
	}

	// set async flag
#ifdef WIN32
	unsigned long flag = bAsync;
	if ( ioctlsocket(sockfd, FIONBIO, &flag ) != 0 )
#else
	int flag = bAsync;
	if ( ioctl(sockfd, FIONBIO, &flag ) != 0 )
#endif
	{
		sError = ext_strerror(errno);
		return ecsERROR;
	}

	if ( Host.NotEmpty() )
	{
		// connect
		if ( connect(sockfd, (struct sockaddr *)&sin, sizeof(sin)) != 0 )
		{
#ifdef WIN32
			int e = WSAGetLastError();
			if ( (e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK) )
#else
			if ( errno != EINPROGRESS )
#endif
			{
				sError = ext_strerror(errno);
#ifdef WIN32
				closesocket(sockfd);
#else
				close(sockfd);
#endif
				return ecsERROR;
			}
		}
	}
	else
	{
		if ( bind(sockfd, (struct sockaddr *) &sin, sizeof(sin)) < 0 )
		{
			sError = ext_strerror(errno);
#ifdef WIN32
			closesocket(sockfd);
#else
			close(sockfd);
#endif
			return ecsERROR;
		}
	}

#if DCLIB_USES_OPENSSL == 1
	if ( m_eSocketMode == esmFULLSSLCLIENT )
	{
		if ( SSL_set_fd(m_pSSL, sockfd) == 0 )
		{
			sError  = "CSocket::Connect: SSL_set_fd failed: ";
			sError += ERR_reason_error_string( ERR_get_error() );
#ifdef WIN32
			closesocket(sockfd);
#else
			close(sockfd);
#endif
			return ecsERROR;
		}
	}
#endif /* DCLIB_USES_OPENSSL */
	
	iHandle = sockfd;
	return ecsSUCCESS;
}

/** */
int CSocket::Disconnect()
{
	if ( iHandle != INVALID_SOCKET )
	{
#if DCLIB_USES_OPENSSL == 1
		if ( (m_eSocketMode != esmSOCKET) && (m_pSSL) )
		{
			SSL_shutdown(m_pSSL); // FIXME check return value
			SSL_free(m_pSSL);
			m_pSSL = 0;
		}
#endif

#ifdef WIN32
		closesocket(iHandle);
#else
		close(iHandle);
#endif
#if DCLIB_USES_OPENSSL == 1
		if ( (m_eSocketMode != esmSOCKET) && (m_pCTX) )
		{
			SSL_CTX_free(m_pCTX);
			m_pCTX = 0;
		}
#endif
		m_eSocketMode = esmSOCKET;
		iHandle       = INVALID_SOCKET;
	}

	return 0;
}

/** */
int CSocket::Read( char * buffer, int len, int sec_timeout, int usec_timeout )
{
	int l = 0, i = 0;
	struct timeval tv;
	fd_set readset;
	struct sockaddr_in cli_addr;
	socklen_t cli_len = sizeof(cli_addr);

	if ( (iHandle == INVALID_SOCKET) || (!buffer) || (len <= 0) )
	{
		return -1;
	}
	
#if DCLIB_USES_OPENSSL == 1
	if ( m_eSocketMode != esmSOCKET )
	{
		l = SSL_read(m_pSSL, buffer, len);

		// if ( (l <= 0) && ((i=IsConnect()) != -1) )
		if ( l <= 0 )
		{
//			printf("READ: %d %d\n",l,i);

			l = SSL_get_error(m_pSSL,l);

			if ( (l != SSL_ERROR_WANT_READ) && (l != SSL_ERROR_WANT_WRITE) )
			{
				l = -1;
				unsigned long sslerr = ERR_peek_error();  // ERR_get_error() if not printed
				ERR_print_errors_fp(stderr);
				sError  = "SSL ERROR lib:";
				sError += ERR_lib_error_string(sslerr);
				sError += " func:";
				sError += ERR_func_error_string(sslerr);
				sError += " reason:";
				sError += ERR_reason_error_string(sslerr);
				Disconnect();
			}
			else
			{
				l = i = 0;
			}
		}
	}
	else
#endif
	{
		if ( IsConnect() < 0 )
		{
			i = 1;
			l = 0;
		}
		else
		{
		FD_ZERO(&readset);
		FD_SET(iHandle, &readset);

		tv.tv_sec  = sec_timeout;
		tv.tv_usec = usec_timeout;

		i = select(FD_SETSIZE, &readset, NULL, NULL, &tv);

		if ( (i > 0) && (FD_ISSET(iHandle,&readset)) )
		{
			// handle udp socket
			if ( SocketType == estUDP )
			{
				if ( (l=recvfrom(iHandle,buffer,len,0,(struct sockaddr*)&cli_addr,&cli_len)) < 0 )
				{
#ifdef WIN32
					int e = WSAGetLastError();
					if ( (e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK) )
					{
#else
					int e = errno;
					if ( (e != EINPROGRESS) && (e != EAGAIN) )
					{
#endif
						sError = ext_strerror(e);
					}
					else
					{
						i = l = 0;
					}
				}
				else if ( l > 0 )
				{
					m_sIP = inet_ntoa( cli_addr.sin_addr );
				}
			}
			// handle tcp socket
			else if ( SocketType == estTCP )
			{
				if ( (l=recv(iHandle,buffer,len,0)) < 0 )
				{
#ifdef WIN32
					int e = WSAGetLastError();
					if ( (e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK) )
					{
#else
					int e = errno;
					if ( (e != EINPROGRESS) && (e != EAGAIN) )
					{
#endif
						sError = ext_strerror(e);
					}
					else
					{
						i = l = 0;
					}
				}
			}
		}
		else if ( i < 0 )
		{
			i = SocketError();

			if ( i == 0 )
			{
				l = i = 0;
			}
			else
			{
				sError = ext_strerror(i);
				i = l = -1;
			}
		}

		FD_CLR(iHandle, &readset);
		}
	}

	// log options TODO: fix
	if ( (CSocket::m_eSocketLog == eslRECV) ||
	     (CSocket::m_eSocketLog == eslBOTH) )
	{
		if ( l > 0 )
		{
			CString s = "RECV:";
			s += CString::number(l);
			CLogFile::Write("dcsocket.log",eltINFO,s);
			
			if( l <= len )
			{
				s.Set(buffer,l);
				CLogFile::Write("dcsocket.log",eltINFO,s);
			}
		}
	}

	/** we are disconnected */
	if ( (i==1) && (l==0) )
	{
		i = errno;
		l = SocketError();

//		printf("1: %s\n",ext_strerror(i).Data());
//		printf("2: %s\n",ext_strerror(l).Data());

		if ( l == 0 )
			l = i;

//		if ( (l != EINPROGRESS) && (l != EAGAIN) )
//		{
			sError = ext_strerror(l);
			l = -1;
//		}
//		else
//		{
//			l = 0;
//		}
	}

	if ( l > 0 )
		CSocket::m_Traffic.AddTraffic( ettRX, l );

	return l;
}

/** */
int CSocket::Write( const unsigned char * buffer, int len, int sec_timeout, int usec_timeout )
{
	int i;
	struct timeval tv;
	fd_set writeset;

	if ( (iHandle == INVALID_SOCKET) || (!buffer) || (len <= 0) )
	{
		return -1;
	}

#if DCLIB_USES_OPENSSL == 1
	if ( m_eSocketMode != esmSOCKET )
	{
		i = SSL_write(m_pSSL, buffer, len);
		
		// if ( (i <= 0) && (IsConnect() != -1) )
		if ( i <= 0 )
		{
//			printf("WRITE: %d %d\n",i,IsConnect());

			i = SSL_get_error(m_pSSL,i);

			if ( (i != SSL_ERROR_WANT_READ) && (i != SSL_ERROR_WANT_WRITE) )
			{
				i = -1;
				unsigned long sslerr = ERR_peek_error();  // ERR_get_error() if not printed
				ERR_print_errors_fp(stderr);
				sError  = "SSL ERROR lib:";
				sError += ERR_lib_error_string(sslerr);
				sError += " func:";
				sError += ERR_func_error_string(sslerr);
				sError += " reason:";
				sError += ERR_reason_error_string(sslerr);
				Disconnect();
			}
			else
			{
				i = 0;
			}
		}
	}
	else
#endif
	{
		if ( IsConnect() < 0 )
		{
			i = -1;
		}
		else
		{
		FD_ZERO(&writeset);
		FD_SET(iHandle, &writeset);

		tv.tv_sec  = sec_timeout;
		tv.tv_usec = usec_timeout;

		i = select(FD_SETSIZE, NULL, &writeset, NULL, &tv);

		FD_CLR(iHandle, &writeset);
		}

		if ( i > 0 )
		{
			i = send( iHandle, (const char*)buffer, len, MSG_NOSIGNAL );

			// send error
			if ( i < 0 )
			{
#ifdef WIN32
				int e = WSAGetLastError();
				if ( (e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK) )
				{
#else
				int e = errno;
				if ( (e != EINPROGRESS) && (e != 0) && (e != EAGAIN) )
				{
#endif
					sError = ext_strerror(e);
				}
				else
				{
					i = 0;
				}
			}
			else if ( i == 0 )
			{
				i = -1;
			}

		}
		else if ( i < 0 )
		{
			i = SocketError();

			if ( i != 0 )
			{
				sError = ext_strerror(i);
				i = -1;
			}
		}
	}

	// log options TODO: fix
	if ( (CSocket::m_eSocketLog == eslSEND) ||
	     (CSocket::m_eSocketLog == eslBOTH) )
	{
		if ( i > 0 )
		{
			CString s = "SEND:";
			s += CString::number(len);
			CLogFile::Write("dcsocket.log",eltINFO,s);
			
			s.Set( (const char*)buffer ,i );
			CLogFile::Write("dcsocket.log",eltINFO,s);
		}
	}

	if ( i > 0 )
		CSocket::m_Traffic.AddTraffic( ettTX, i );

	return i;
}

/** */
int CSocket::Accept()
{
	struct timeval tv;
	fd_set readset;
	struct sockaddr_in serv_addr;
	SOCKET s = INVALID_SOCKET;
	int i;
	socklen_t sin_size = sizeof(struct sockaddr_in);

	if ( iHandle == INVALID_SOCKET )
	{
		return s;
	}

	FD_ZERO(&readset);
	FD_SET(iHandle, &readset);

	tv.tv_sec  = 0;
	tv.tv_usec = 1;

	i = select(FD_SETSIZE, &readset, NULL, NULL, &tv);

	FD_CLR(iHandle, &readset);

	if ( i > 0 )
	{
		if ((s = accept(iHandle, (struct sockaddr *)&serv_addr, &sin_size)) == INVALID_SOCKET)
		{
			sError = ext_strerror(SocketError());
			return -1;
		}
		else
		{
			// set async flag
#ifdef WIN32
			unsigned long flag = 1;
			if ( ioctlsocket(s, FIONBIO, &flag ) != 0 )
#else
			int flag = 1;
			if ( ioctl(s, FIONBIO, &flag ) != 0 )
#endif
			{
				sError = ext_strerror(errno);
				return -1;
			}
		}
	}
	
	return s;
}

/** */
int CSocket::Listen( int port, CString ip )
{
	SOCKET sockfd;
	struct sockaddr_in serv_addr;

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
	{
#ifdef WIN32
		sError = ext_strerror(WSAGetLastError());
#else
		sError = ext_strerror(errno);
#endif
		return -1;
	}

	/* Let the kernel reuse the socket address. This lets us run
	   twice in a row, without waiting for the (ip, port) tuple
	   to time out. */
#ifdef WIN32
	const char i = 1;
#else
	int i = 1;
#endif
	if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) != 0 )
	{
#ifdef WIN32
		sError = ext_strerror(WSAGetLastError());
		closesocket(sockfd);
#else
		sError = ext_strerror(errno);
		close(sockfd);
#endif
		return -1;
	}

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons((unsigned short)port);

	if ( ip.NotEmpty() )
	{
#ifdef WIN32
		serv_addr.sin_addr.s_addr = inet_addr( ip.Data() );
		if ( serv_addr.sin_addr.s_addr == INADDR_NONE )
#else
		if ( inet_aton( ip.Data(), &serv_addr.sin_addr ) == 0 )
#endif
		{
			sError = "Invalid IP address";
			return -1;
		}
	}
	else
	{
		serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	}

	if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1)
	{
#ifdef WIN32
		sError = ext_strerror(WSAGetLastError());
		closesocket(sockfd);
#else
		sError = ext_strerror(errno);
		close(sockfd);
#endif
		return -1;
	}

	if (listen(sockfd, 5) == -1)
	{
#ifdef WIN32
		sError = ext_strerror(WSAGetLastError());
		closesocket(sockfd);
#else
		sError = ext_strerror(errno);
		close(sockfd);
#endif
		return -1;
	}

#if DCLIB_USES_OPENSSL == 1
	if ( m_eSocketMode == esmFULLSSLSERVER )
	{
		if ( SSL_set_fd(m_pSSL, sockfd) == 0 )
		{
			sError  = "CSocket::Listen: SSL_set_fd failed: ";
			sError += ERR_reason_error_string( ERR_get_error() );
#ifdef WIN32
			closesocket(sockfd);
#else
			close(sockfd);
#endif
			return -1;
		}
	}
#endif /* DCLIB_USES_OPENSSL */

	iHandle = sockfd;
	return 0;
}

/** */
bool CSocket::GetPeerName( CString * host, int * port )
{
	struct sockaddr_in addr;
	socklen_t sin_size;

	if ( (iHandle == INVALID_SOCKET) || (!host) || (!port) )
	{
		return false;
	}

	sin_size = sizeof(struct sockaddr_in);

	if ( getpeername( iHandle, (struct sockaddr*)&addr, &sin_size ) == -1 )
	{
		sError = ext_strerror(SocketError());
		return false;
	}

	*host = inet_ntoa(addr.sin_addr);
	*port = ntohs(addr.sin_port);

	return true;
}

/** */
#if DCLIB_USES_OPENSSL == 1
bool CSocket::ChangeSocketMode( eSocketMode mode, CString cert, CString key )
#else
bool CSocket::ChangeSocketMode( eSocketMode mode, CString /* cert */, CString /* key */ )
#endif
{
	bool res = false;

	switch(mode)
	{
		case esmSOCKET:
		{
			m_eSocketMode = mode;
			res = true;
			break;
		}
#if DCLIB_USES_OPENSSL == 1
		case esmSSLCLIENT:
		case esmSSLSERVER:
		case esmFULLSSLCLIENT:
		case esmFULLSSLSERVER:
		{
			if ( (cert.IsEmpty() || key.IsEmpty()) && ((mode == esmSSLSERVER) || (mode == esmFULLSSLSERVER)) )
			{
				printf("no cert/key available\n");
				return res;
			}

			if ( m_eSocketMode == esmSOCKET )
			{
				if ( mode == esmFULLSSLCLIENT )
				{
					m_pCTX = CSSL::NewTLSv1ClientCTX();
					
					if ( m_pCTX == 0 )
					{
						printf("CSocket::ChangeSocketMode NewTLSv1ClientCTX failed\n");
						return res;
					}
				}
				else if ( mode == esmFULLSSLSERVER )
				{
					m_pCTX = CSSL::NewTLSv1ServerCTX();
					
					if ( m_pCTX == 0 )
					{
						printf("CSocket::ChangeSocketMode NewTLSv1ServerCTX failed\n");
						return res;
					}
				}
				else if ( mode == esmSSLCLIENT )
				{
					m_pCTX = CSSL::InitClientCTX();
					
					if ( m_pCTX == 0 )
					{
						printf("InitClientCTX failed\n");
						return res;
					}
				}
				else 
				{
					m_pCTX = CSSL::InitServerCTX();
					
					if ( m_pCTX == 0 )
					{
						printf("InitServerCTX failed\n");
						return res;
					}
				}
				
				if ( cert.NotEmpty() && key.NotEmpty() )
				{
					if ( CSSL::LoadCertificates( m_pCTX, cert.Data(), key.Data() ) == false )
					{
						SSL_CTX_free(m_pCTX);
						m_pCTX = 0;
						
						printf("load cert/key failed\n");
						return res;
					}
				}

				SSL_CTX_set_mode(m_pCTX, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE);

				if ( (m_pSSL = SSL_new(m_pCTX)) == 0 )
				{
					printf("SSL_new failed\n");
					SSL_CTX_free(m_pCTX);
					m_pCTX = 0;

					return res;
				}

				if ( (mode == esmSSLSERVER) || (mode == esmFULLSSLSERVER) )
				{
					SSL_set_accept_state(m_pSSL);
				}
				else
				{
					SSL_set_connect_state(m_pSSL);
				}

				if ( SSL_set_fd(m_pSSL, iHandle) == 0 )
				{
					printf("SSL_set_fd failed\n");
					SSL_CTX_free(m_pCTX);
					m_pCTX = 0;
					SSL_free(m_pSSL);
					m_pSSL = 0;

					return res;
				}

				m_eSocketMode = mode;
				res = true;
			}
			else
			{
				printf("CSocket: wrong socket mode to change\n");
			}

			break;
		}
#endif
		default:
			break;
	}

	return res;
}

/** http://synergy2.sourceforge.net/ */
CString CSocket::ext_strerror( int err )
{
#ifdef WIN32
	// built-in windows function for looking up error message strings
	// may not look up network error messages correctly.  we'll have
	// to do it ourself.
	static const struct { int m_code; const char* m_msg; } s_netErrorCodes[] = {
		/* 10004 */{WSAEINTR,			"The (blocking) call was canceled via WSACancelBlockingCall"},
		/* 10009 */{WSAEBADF,			"Bad file handle"},
		/* 10013 */{WSAEACCES,			"The requested address is a broadcast address, but the appropriate flag was not set"},
		/* 10014 */{WSAEFAULT,			"WSAEFAULT"},
		/* 10022 */{WSAEINVAL,			"WSAEINVAL"},
		/* 10024 */{WSAEMFILE,			"No more file descriptors available"},
		/* 10035 */{WSAEWOULDBLOCK,		"Socket is marked as non-blocking and no connections are present or the receive operation would block"},
		/* 10036 */{WSAEINPROGRESS,		"A blocking Windows Sockets operation is in progress"},
		/* 10037 */{WSAEALREADY,		"The asynchronous routine being canceled has already completed"},
		/* 10038 */{WSAENOTSOCK,		"At least on descriptor is not a socket"},
		/* 10039 */{WSAEDESTADDRREQ,	"A destination address is required"},
		/* 10040 */{WSAEMSGSIZE,		"The datagram was too large to fit into the specified buffer and was truncated"},
		/* 10041 */{WSAEPROTOTYPE,		"The specified protocol is the wrong type for this socket"},
		/* 10042 */{WSAENOPROTOOPT,		"The option is unknown or unsupported"},
		/* 10043 */{WSAEPROTONOSUPPORT,"The specified protocol is not supported"},
		/* 10044 */{WSAESOCKTNOSUPPORT,"The specified socket type is not supported by this address family"},
		/* 10045 */{WSAEOPNOTSUPP,		"The referenced socket is not a type that supports that operation"},
		/* 10046 */{WSAEPFNOSUPPORT,	"BSD: Protocol family not supported"},
		/* 10047 */{WSAEAFNOSUPPORT,	"The specified address family is not supported"},
		/* 10048 */{WSAEADDRINUSE,		"The specified address is already in use"},
		/* 10049 */{WSAEADDRNOTAVAIL,	"The specified address is not available from the local machine"},
		/* 10050 */{WSAENETDOWN,		"The Windows Sockets implementation has detected that the network subsystem has failed"},
		/* 10051 */{WSAENETUNREACH,		"The network can't be reached from this hos at this time"},
		/* 10052 */{WSAENETRESET,		"The connection must be reset because the Windows Sockets implementation dropped it"},
		/* 10053 */{WSAECONNABORTED,	"The virtual circuit was aborted due to timeout or other failure"},
		/* 10054 */{WSAECONNRESET,		"The virtual circuit was reset by the remote side"},
		/* 10055 */{WSAENOBUFS,			"No buffer space is available or a buffer deadlock has occured. The socket cannot be created"},
		/* 10056 */{WSAEISCONN,			"The socket is already connected"},
		/* 10057 */{WSAENOTCONN,		"The socket is not connected"},
		/* 10058 */{WSAESHUTDOWN,		"The socket has been shutdown"},
		/* 10059 */{WSAETOOMANYREFS,	"BSD: Too many references"},
		/* 10060 */{WSAETIMEDOUT,		"Attempt to connect timed out without establishing a connection"},
		/* 10061 */{WSAECONNREFUSED,	"The attempt to connect was forcefully rejected"},
		/* 10062 */{WSAELOOP,			"Undocumented WinSock error code used in BSD"},
		/* 10063 */{WSAENAMETOOLONG,	"Undocumented WinSock error code used in BSD"},
		/* 10064 */{WSAEHOSTDOWN,		"Undocumented WinSock error code used in BSD"},
		/* 10065 */{WSAEHOSTUNREACH,	"No route to host"},
		/* 10066 */{WSAENOTEMPTY,		"Undocumented WinSock error code"},
		/* 10067 */{WSAEPROCLIM,		"Undocumented WinSock error code"},
		/* 10068 */{WSAEUSERS,			"Undocumented WinSock error code"},
		/* 10069 */{WSAEDQUOT,			"Undocumented WinSock error code"},
		/* 10070 */{WSAESTALE,			"Undocumented WinSock error code"},
		/* 10071 */{WSAEREMOTE,			"Undocumented WinSock error code"},
		/* 10091 */{WSASYSNOTREADY,		"Underlying network subsytem is not ready for network communication"},
		/* 10092 */{WSAVERNOTSUPPORTED,	"The version of WinSock API support requested is not provided in this implementation"},
		/* 10093 */{WSANOTINITIALISED,	"WinSock subsystem not properly initialized"},
		/* 10101 */{WSAEDISCON,			"Virtual circuit has gracefully terminated connection"},
		/* 11001 */{WSAHOST_NOT_FOUND,	"The specified host is unknown"},
		/* 11002 */{WSATRY_AGAIN,		"A temporary error occurred on an authoritative name server"},
		/* 11003 */{WSANO_RECOVERY,		"A non-recoverable name server error occurred"},
		/* 11004 */{WSANO_DATA,			"The requested name is valid but does not have an IP address"},
		/* end   */{0,					NULL}
	};

	for (unsigned int i = 0; s_netErrorCodes[i].m_code != 0; ++i)
	{
		if ( s_netErrorCodes[i].m_code == err )
		{
			return s_netErrorCodes[i].m_msg;
		}
	}
#endif
	return strerror(err);
}

/** */
CString CSocket::GetSSLVersion()
{
	CString result;
#if DCLIB_USES_OPENSSL == 1
	if ( m_pSSL )
	{
		result = SSL_get_cipher_version(m_pSSL);
	}
#endif
	return result;
}

/** */
CString CSocket::GetSSLCipher()
{
	CString result;
#if DCLIB_USES_OPENSSL == 1
	if ( m_pSSL )
	{
		result = SSL_get_cipher(m_pSSL);
	}
#endif
	return result;
}

/** */
CString CSocket::VerifyPeerCertificate()
{
	CString result;
#if DCLIB_USES_OPENSSL == 1
	if ( m_pSSL )
	{
		if ( SSL_get_peer_certificate(m_pSSL) == NULL )
		{
			result = "No certificate";
		}
		else
		{
			int certerr = SSL_get_verify_result(m_pSSL);
			
			if ( certerr == X509_V_OK )
			{
				result = "Certificate verified";
			}
			else
			{
				result  = "Certificate verify failed: ";
				result += X509_verify_cert_error_string(certerr);
			}
		}
	}
#endif
	return result;
}

/** */
int CSocket::SysInit()
{
#ifdef WIN32
	/* According to the docs WSAStartup()
	 * will fail if the requested version could not be
	 * provided, plus 2.2 is the latest version and that
	 * can even be installed on Windows 95.
	 */
	WORD wVersionRequested = MAKEWORD( 2, 2 );
	WSADATA wsaData;
	return WSAStartup( wVersionRequested, &wsaData );
#else
	/* nothing to do */
	return 0;
#endif
}

/** */
int CSocket::SysDeInit()
{
#ifdef WIN32
	return WSACleanup();
#else
	/* nothing to do */
	return 0;
#endif
}
