// -*- C++ -*-
// generic socket DLL, winsock version
// disclaimer:  a C programmer wrote this.

// $Id: gensock.cpp 1.15 1994/11/23 22:38:10 rushing Exp $

//#include <wincrypt.h>
	
#define WANTIP6

#ifdef WANTIP6

#include <winsock2.h>
#include <Ws2tcpip.h>

#if !defined (_WIN64)
#include <wspiapi.h>  //for getaddrinfo backwards compatibility with pre XP OS
#else
#pragma comment (lib, "Ws2_32.lib")
#endif

#else
#include <winsock.h>
#endif

#ifdef SSLSTATIC
#include <openssl/ssl.h>
#include <openssl/err.h>
#ifdef _WIN64
#pragma comment(lib, "libssl64.lib")
#pragma comment(lib, "libcrypto64.lib")
#else
#pragma comment(lib, "libssl.lib")
#pragma comment(lib, "libcrypto.lib")
#pragma comment (lib, "Ws2_32.lib")
#endif

#pragma comment (lib, "crypt32")
#endif //SSLSTATIC

#include "intgensock.h"
#include "resource.h"
#include "wvglob.h"

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "zlib.h"



#define TIMELIMIT (120*1000)  //time to wait for a connection


//MDS increased buffer from 512 19991214
#define SOCKET_BUFFER_SIZE  1500
#define ZBUFFER_SIZE	8192
#define DESCR_SIZE 256

/* This is for NT */
HANDLE  dll_module_handle;
#define GET_CURRENT_TASK    dll_module_handle
#define TASK_HANDLE_TYPE    HANDLE
char    szSSLDescription[DESCR_SIZE] = {0}; //description of SSL dll loaded


extern int WVTimedMessageBox(HWND hwnd, LPSTR lpText, LPSTR lpCaption,UINT uType);
extern DWORD GetFileVersion(LPTSTR lpszFilename, LPTSTR lpszDescription=NULL, int nDescLen=-1, BOOL* pBIs64=NULL);
extern char *strntcpy (char *dest, const char *src, int len);

int  init_winsock (eConnection UseSSL);
void deinit_winsock (void);

#ifdef _INCLSSL
BOOL SSLLoaded=FALSE;
#define SSLconnstr "SSL connection"

void DeInitSSL();
int InitSSL();


typedef struct ssl_ctx_st SSL_CTX;
typedef struct ssl_st SSL;
SSL_CTX* SSLCtx=NULL;

#ifndef SSLSTATIC
typedef int (*PFN_SSL_int_int_pvoid)(int, PVOID);
typedef int (*PFN_SSL_int_void)(void);
typedef PVOID (*PFN_SSL_pvoid_void)(void);
typedef PVOID (*PFN_SSL_pvoid_pvoid)(PVOID);
typedef SSL* (*PFN_SSL_pssl_pvoid)(PVOID);
typedef SSL_CTX* (*PFN_SSL_pctx_pvoid)(PVOID);
typedef void (*PFN_SSL_void_pvoid)(PVOID);
typedef void (*PFN_SSL_void_pvoid_int)(PVOID, int);
typedef void (*PFN_SSL_void_void) (void);
typedef int (*PFN_SSL_int_pvoid_int)(PVOID, int);
typedef int (*PFN_SSL_int_pvoid)(PVOID);
typedef int (*PFN_SSL_int_pvoid_pvoid_int)(PVOID, PVOID, int);
typedef int (*PFN_SSL_int_pvoid_pint)(PVOID, int*);
typedef char* (*PFN_SSL_pchar_pvoid)(PVOID);
typedef char* (*PFN_SSL_pchar_pvoid_pchar_int)(PVOID, char*, int);
typedef long (*PFN_SSL_long_pvoid_int_long_pvoid) (PVOID, int, long, PVOID);
typedef unsigned long (*PFN_SSL_ulong_void) (void);
typedef void (*PFN_SSL_ulong_pvoid_size_t) (unsigned long, PVOID, size_t);


PFN_SSL_int_int_pvoid	OPENSSL_init_ssl;		// int OPENSSL_init_ssl(int,pvoid)
PFN_SSL_pvoid_void		TLS_client_method;	// SSL_METHOD *TLS_client_method()
PFN_SSL_pvoid_void		TLSv1_client_method;	// SSL_METHOD *TLSv1_client_method()
PFN_SSL_pvoid_void		SSLv2_client_method;	// SSL_METHOD *SSLv2_client_method(void);
PFN_SSL_pctx_pvoid		SSL_CTX_new;			// SSL_CTX *SSL_CTX_new(SSL_METHOD *method)
PFN_SSL_void_pvoid		SSL_CTX_free;			// void SSL_CTX_free(SSL_CTX *ctx);
PFN_SSL_pssl_pvoid		SSL_new;				// SSL *SSL_new(SSL_CTX *ctx)
PFN_SSL_void_pvoid		SSL_free;				// void SSL_free(SSL *ssl);
PFN_SSL_int_pvoid_int	SSL_set_fd;				// int SSL_set_fd(SSL *ssl, int fd);
PFN_SSL_int_pvoid		SSL_connect;			// int SSL_connect(SSL *ssl);
PFN_SSL_int_pvoid_pvoid_int	SSL_read;			// int SSL_read(SSL *ssl, void *buffer, int bufsize)
PFN_SSL_int_pvoid_pvoid_int	SSL_write;			// int SSL_write(SSL *ssl, void *buffer, int bufsize)
PFN_SSL_int_pvoid_int	SSL_get_error;			// int SSL_write(SSL *ssl, int ret)
PFN_SSL_int_pvoid       SSL_pending;			// int SSL_pending(SSL *ssl);
PFN_SSL_int_pvoid		SSL_get_fd;				// int SSL_get_fd(const SSL *ssl);
PFN_SSL_pvoid_pvoid		SSL_get_current_cipher; // SSL_CIPHER *SSL_get_current_cipher(const SSL *ssl);
PFN_SSL_pchar_pvoid     SSL_CIPHER_get_name;	// const char *SSL_CIPHER_get_name(const SSL_CIPHER *cipher);
PFN_SSL_int_pvoid_pint	SSL_CIPHER_get_bits;	// int SSL_CIPHER_get_bits(const SSL_CIPHER *cipher, int *alg_bits);
PFN_SSL_pchar_pvoid		SSL_CIPHER_get_version;	// char *SSL_CIPHER_get_version(const SSL_CIPHER *cipher);
PFN_SSL_pchar_pvoid_pchar_int SSL_CIPHER_description; //char *SSL_CIPHER_description(SSL_CIPHER *cipher, char *buf, int size);
PFN_SSL_int_pvoid		SSL_do_handshake;		// int SSL_do_handshake(SSL *ssl);
PFN_SSL_int_pvoid		SSL_state;				// int SSL_state(SSL *ssl);
PFN_SSL_long_pvoid_int_long_pvoid  SSL_ctrl;	// long SSL_ctrl(SSL *s,int cmd,long larg,void *parg)
PFN_SSL_int_pvoid		SSL_shutdown;			// int SSL_shutdown(SSL *s);
PFN_SSL_void_pvoid_int  SSL_set_shutdown;		// void SSL_set_shutdown(SSL *ssl,int mode);
PFN_SSL_void_void	    SSL_load_error_strings; // void SSL_load_error_strings(void);
PFN_SSL_void_pvoid		SSL_set_connect_state;  // void SSL_set_connect_state(SSL *s);
PFN_SSL_int_pvoid       SSL_clear;				// void PFN_SSL_void_pvoid(SSL *s);

PFN_SSL_void_void		ERR_clear_error;		// void ERR_clear_error(void );
PFN_SSL_ulong_void		ERR_get_error;		// unsigned long ERR_get_error(void)
PFN_SSL_ulong_pvoid_size_t 	ERR_error_string_n;	// void ERR_error_string_n(unsigned long e, char *buf, size_t len)
PFN_SSL_void_void       ERR_free_strings;       // void ERR_free_strings(void);

#define SSL_ERROR_NONE			0
#define SSL_ERROR_SSL			1
#define SSL_ERROR_WANT_READ		2
#define SSL_ERROR_WANT_WRITE		3
#define SSL_ERROR_WANT_X509_LOOKUP	4
#define SSL_ERROR_SYSCALL		5 /* look at error stack/return value/errno */
#define SSL_ERROR_ZERO_RETURN		6
#define SSL_ERROR_WANT_CONNECT		7
#define SSL_ERROR_WANT_ACCEPT		8

#define SSL_get_cipher(s) \
                SSL_CIPHER_get_name(SSL_get_current_cipher(s))
#define SSL_get_cipher_name(s) \
                SSL_CIPHER_get_name(SSL_get_current_cipher(s))
#define SSL_get_cipher_bits(s,np) \
                SSL_CIPHER_get_bits(SSL_get_current_cipher(s),np)
#define SSL_get_cipher_version(s) \
                SSL_CIPHER_get_version(SSL_get_current_cipher(s))
#define SSL_get_cipher_description(s,cp,n) \
	SSL_CIPHER_description(SSL_get_current_cipher(s), cp, n)

#define SSL_MODE_AUTO_RETRY 0x00000004L
#define SSL_CTRL_MODE				33


#define SSL_set_mode(ssl,op) \
	SSL_ctrl((ssl),SSL_CTRL_MODE,(op),NULL)

/* Used in SSL_set_shutdown()/SSL_get_shutdown(); */
#define SSL_SENT_SHUTDOWN	1
#define SSL_RECEIVED_SHUTDOWN	2
#endif
LPCTSTR SSLGetDescription() { return szSSLDescription; };
HINSTANCE hSSLLibrary=(HINSTANCE)NULL;
HINSTANCE hLibeayLibrary = (HINSTANCE) NULL;


CRITICAL_SECTION sslHandleMutex;
#endif
char  g_message[2048];

//
//
//

#ifdef _DEBUG
	void complain (char * message)
	{
		OutputDebugString (message);
		MessageBox (NULL, message, "Winsock Error", MB_OK|MB_ICONHAND);

	}
	extern void TRACE(LPCTSTR szFormat, ...);

#else
	void complain (char * message)
	{
		//MessageBox (NetDoc.hWndFrame, message, "Winsock Error", MB_OK|MB_ICONHAND);
		//MessageBox (NULL, message, "Winsock Error", MB_OK|MB_ICONHAND);

		if (!message)
			return;

		int nLen = strlen(message) + 80;
				
		char* error_string = new char[nLen];
		if (error_string)
		{
			strcpy(error_string, message);

			strcat(	error_string, "\r\nClose in 90 seconds...");
			WVTimedMessageBox(NULL, error_string, "Winsock Error" ,MB_OK|MB_ICONHAND);
		
			delete [] error_string;
		}
			

	}
#if (_MSC_VER > 1200)  //1200 = VC6
		#define TRACE (void)__noop
#else
		#define TRACE (void)0
#endif


#endif

typedef enum eInflate
{
	NO_INFLATE = 0,
	INFLATING = 1,
	INFLATE_CONT = 2,
	INFLATE_DONE = 3,
};

typedef enum eYDecState
{
	Y_NONE =0,
	Y_BEGIN = 1,
	Y_DEC = 2,
	Y_END = 3
};

extern BOOL ReadHEX32( LPCSTR szHex, DWORD* pdwHex);
extern void CrcInit(DWORD* pcrc_val );
extern void CrcAdd(int c, DWORD* crc_val );



//
// ---------------------------------------------------------------------------
// container for a buffered SOCK_STREAM.

class connection
{
private:
	SOCKET    the_socket;
	char *    in_buffer;
	char *    out_buffer;
	char *	  z_inBuffer;
	char *    z_buffer;
	unsigned int  in_index;
	unsigned int  out_index;
	unsigned int  in_buffer_total;
	unsigned int  out_buffer_total;
	unsigned int  last_winsock_error;
	TASK_HANDLE_TYPE      owner_task;
	fd_set    fds;
	struct timeval    timeout;
	bool	bIsSSL;
	char *	szDescription; //description of connection protocol
	char *  szErrorMsg;
	
	eInflate zInflateState;
	unsigned int z_buffer_total;
	unsigned int z_index;
	unsigned int z_inbuf_total;

	//state vars for ydec
	DWORD	 dwCRC32;
	bool	 bEscape;
	bool	 bNewLine;
	eYDecState nYDec;
	z_stream zstm;	//for compressed headers



#ifdef _INCLSSL

	//SSL*     ssl;
	//X509*    server_cert;
	//SSL_METHOD *meth;

	//PVOID SSLCtx;
	//PVOID TLSCtx;
	bool ConEstablished;
	SSL* hConnection;
	fd_set write_fds, read_fds;
	bool read_blocked;
	bool read_blocked_on_write;
	bool write_blocked_on_read;
	int  nReconnects;


#endif


public:

	connection (void);
	~connection (void);

	int       get_connected (char far * hostname, char far * service, eConnection UseSSL, int ipfamily);
	SOCKET    get_socket(void) { return (the_socket); }
	TASK_HANDLE_TYPE      get_owner_task(void) { return (owner_task); }
	int       get_buffer(int wait);
	int       close (void);
	int       wvgetchar (int wait, char far * ch);
	int       put_data (char far * data, unsigned long length);
	int       put_data_buffered (char far* data, unsigned long length);
	int       put_data_flush (void);
	const char *get_description(char* desc = NULL, int length = 0);
	int		  starttls();
	char *	  format_system_error(DWORD dwError, const char *szFmt, ...);
	int		  wvstartZinflate(int nType);
	int		  wvendZinflate();
	int		  yDecBuf();
};

connection::connection (void)
{
	the_socket = 0;
	in_index = 0;
	out_index = 0;
	in_buffer_total = 0;
	out_buffer_total = 0;
	in_buffer = 0;
	zInflateState = NO_INFLATE ;
	z_buffer = 0;
	z_inBuffer = 0;

	in_buffer = new char[SOCKET_BUFFER_SIZE];
	z_inBuffer = new char[SOCKET_BUFFER_SIZE];
	out_buffer = new char[SOCKET_BUFFER_SIZE+1];

	szDescription = NULL;
	szErrorMsg = NULL;
	last_winsock_error = 0;
	bIsSSL = false;
#ifdef _INCLSSL
	//SSLCtx = NULL;
	//TLSCtx = NULL;
	ConEstablished = false;
	hConnection = NULL;
	read_blocked = false;
	read_blocked_on_write = false; 
	write_blocked_on_read = false; 
	nReconnects = 0;

#endif
}

connection::~connection (void)
{
	delete [] in_buffer;
	delete [] out_buffer;
	if (z_buffer)
	{
		delete [] z_buffer;
		z_buffer = NULL;
	}
	if (z_inBuffer)
	{
		delete [] z_inBuffer;
		z_inBuffer = NULL;
	}
	if (szDescription)
	{
		delete szDescription;
		szDescription = NULL;
	}
	if (szErrorMsg)
	{
		LocalFree(szErrorMsg);
		szErrorMsg = NULL;

	}
}

int	gensock_is_a_number (char far* string)
{
	while (*string) 
	{
		if (!isdigit (*string)) 
		{
			return (0);
		}
		string++;
	}
	return (1);
}

////////////////////////////////////////
// Replace "##" in format string with error description
//
char * connection::format_system_error(DWORD dwLastError,  const char *szFmt, ...)
{

	HMODULE hModule = NULL; // default to system source
	LPSTR MessageBuffer = NULL;
	DWORD dwBufferLength;

	static TCHAR buf[2048];
    va_list vargs;

    //1st generate message
	if (szFmt && *szFmt) 
    {
		va_start( vargs, szFmt );
		wvsprintf( buf, szFmt, vargs );
		va_end( vargs );  

    }


	if (szErrorMsg)
	{
		LocalFree(szErrorMsg);
		szErrorMsg = NULL;

	}

	//
	// if dwLastError is in the network range, load the message source
	//
	if(dwLastError >= WSABASEERR)
	{
		hModule = LoadLibraryEx(TEXT("netmsg.dll"), NULL, LOAD_LIBRARY_AS_DATAFILE );
	}
	// call FormatMessage() to allow for message text to be acquired
	// from the system or the supplied module handle
	//
	if ( dwBufferLength = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
							FORMAT_MESSAGE_IGNORE_INSERTS |
							FORMAT_MESSAGE_FROM_SYSTEM | // always consider system table
							((hModule != NULL) ? FORMAT_MESSAGE_FROM_HMODULE : 0),
			hModule, // module to get message from (NULL == system)
			dwLastError,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
			(LPSTR) &MessageBuffer,
			0,
			NULL))
	{

		
		int nLen = strlen(buf) + strlen(MessageBuffer);
		szErrorMsg = (char *) LocalAlloc(LMEM_FIXED, nLen+1);
		char *p = strstr( buf, "##");
		
		if (p)
		{
			strntcpy(szErrorMsg, buf, p-buf);
			szErrorMsg[p-buf] = '\0';
			strcat(szErrorMsg, MessageBuffer);
			strcat(szErrorMsg, p+2);
		}
		else
		{
			strcpy(szErrorMsg, buf);
			strcat(szErrorMsg, MessageBuffer);
		}
		
		//
		// free the buffer allocated by the system
		//
		LocalFree(MessageBuffer);
	}
	else
	{
		int nLen = strlen(buf);
		szErrorMsg = (char *) LocalAlloc(LMEM_FIXED, nLen+1);
		char *p = strstr( buf, "##");
		if (p)
		{
			strntcpy(szErrorMsg, buf, p-buf);
			szErrorMsg[p-buf] = '\0';
			strcat(szErrorMsg, p+2);
		}
		else
		{
			strcpy(	szErrorMsg, buf);
		}


	}

	//
	// if you loaded a message source, unload it
	//
	if(hModule != NULL)
		FreeLibrary(hModule);


	return 	szErrorMsg;
}



//
// ---------------------------------------------------------------------------
// UseSSL 0 = none, 1 = SSL, 2 = TLS

int	connection::get_connected (char FAR * hostname, char FAR * service, eConnection UseSSL, int ipfamily)
{
	struct hostent FAR *  hostentry;
	struct servent FAR *  serventry;
	unsigned long     ip_address;
	struct sockaddr_in    sa_in;
	int           our_port;
	int           retval = 0, err_code = 0;
	unsigned long     ioctl_blocking = 1;

	// if the ctor couldn't get a buffer
	if (!in_buffer || !out_buffer)
		return (ERR_CANT_MALLOC);
	if (szDescription)
	{
		delete szDescription;
		szDescription = NULL;
	}

	

#ifdef _INCLSSL
	if(!SSLLoaded && UseSSL != CONN_NORMAL)
	{
		//may be 1st SSL connection
		if ( InitSSL() != 0)
#ifdef _WIN64
		return ERR_SSL_NOTLOADED64;
#else
		return ERR_SSL_NOTLOADED;
#endif
	}
#endif

	// --------------------------------------------------
	// resolve the service name
	//

#ifdef WANTIP6
	//MSVC6 USES WSPiApi.h TO MAP getaddrinfo to WspiapiGetAddrInfo
	
	ADDRINFO Hints, *AddrInfo = NULL, *ai;
	memset(&Hints, 0, sizeof (Hints));
    
	//AF_INET6 AF_INET
	switch(ipfamily)
	{
		case 4:
			Hints.ai_family = PF_INET;    // Accept IPv4
			break;
		case 6:
			Hints.ai_family = PF_INET6;    // Accept IPv6
			break;
		default:
			Hints.ai_family = PF_UNSPEC;    // Accept either IPv4 or IPv6
			break;

	}
	
    Hints.ai_socktype = SOCK_STREAM;
	Hints.ai_protocol = IPPROTO_TCP;
    err_code = 0;
    
	retval = getaddrinfo(hostname, service, &Hints, &AddrInfo);
	if (!retval)
	{
		
		for (ai=AddrInfo; ai; ai = ai->ai_next) 
		{
			the_socket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
			
			retval = connect (the_socket, AddrInfo->ai_addr, AddrInfo->ai_addrlen);

			if (retval < 0)
			{
				err_code = WSAGetLastError();
				closesocket(the_socket);
				the_socket = 0;
				
			}
			else
			{
				err_code = 0;
				break;
			}
		}
		freeaddrinfo(AddrInfo);

		if (err_code)
		{
			

			if ((err_code == WSANO_DATA) || (err_code == WSANO_RECOVERY)) 
			{
				return (ERR_CANT_RESOLVE_SERVICE);
			} 
			else 
			{
				complain(format_system_error(err_code, "unexpected error %d from winsock\n##", err_code));

				return (err_code - 5000);
			}			
			/*switch (err_code) 
			{
				case WSAECONNREFUSED:
					return (ERR_CONNECTION_REFUSED);
					break;
				default:
					complain(format_system_error(err_code, "unexpected error %d from winsock\n##", err_code));
					return (ERR_CANT_CONNECT);
					break;
			}*/

		}
		
	}
	else
	{
		freeaddrinfo(AddrInfo);
		err_code = WSAGetLastError();
		complain(format_system_error(err_code, "unexpected error %d from winsock\n##", err_code));
		return (ERR_CANT_RESOLVE_SERVICE);
	}
#else  //old IP4 only code

	// If they've specified a number, just use it.
	if (gensock_is_a_number (service)) 
	{
		char far* tail;
		our_port = (int) strtol (service, &tail, 10);
		if (tail == service) 
		{
			return (ERR_CANT_RESOLVE_SERVICE);
		} 
		else 
		{
			our_port = htons (our_port);
		}
	} 
	else 
	{
		// we have a name, we must resolve it.
		serventry = getservbyname (service, (LPSTR)"tcp");

		if (serventry)
			our_port = serventry->s_port;
		else 
		{
			retval = WSAGetLastError();
			// Chicago beta is throwing a WSANO_RECOVERY here...
			if ((retval == WSANO_DATA) || (retval == WSANO_RECOVERY)) 
			{
				return (ERR_CANT_RESOLVE_SERVICE);
			} 
			else 
			{
				return (retval - 5000);
			}
		}
	}

	// --------------------------------------------------
	// resolve the hostname/ipaddress
	//

	if ((ip_address = inet_addr (hostname)) != INADDR_NONE) 
	{
		sa_in.sin_addr.s_addr = ip_address;
	}
	else 
	{
		if ((hostentry = gethostbyname(hostname)) == NULL) 
		{
			return (ERR_CANT_RESOLVE_HOSTNAME);
		}
		sa_in.sin_addr.s_addr = *(long far *)hostentry->h_addr;

	}




	// --------------------------------------------------
	// get a socket
	//

	if ((the_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) 
	{
		return (ERR_CANT_GET_SOCKET);
	}

	sa_in.sin_family = AF_INET;
	sa_in.sin_port = our_port;

	// get a connection

	if ((retval = connect (the_socket, (struct sockaddr *)&sa_in,
							sizeof(struct sockaddr_in))==SOCKET_ERROR)) 
	{
		switch ((err_code = WSAGetLastError())) 
		{
			/* twiddle our thumbs until the connect succeeds */
			case WSAEWOULDBLOCK:
				break;
			case WSAECONNREFUSED:
				return (ERR_CONNECTION_REFUSED);
				break;
			default:
				complain(format_system_error(err_code, "unexpected error %d from winsock\n##", err_code));
				//wsprintf(g_message, "unexpected error %d from winsock\n%s", err_code, GetSystemErrorText(err_code));
				//complain(g_message);
				return (ERR_CANT_CONNECT);
				break;
		}
	}

#endif //!WANTIP6

	owner_task = GET_CURRENT_TASK;

#ifdef _INCLSSL
	if (!SSLLoaded || UseSSL != CONN_SSL)
#endif
	{
		// Make this a non-blocking socket
		ioctlsocket (the_socket, FIONBIO, &ioctl_blocking);


		// make the FD_SET and timeout structures for later operations...

		FD_ZERO (&fds);
		FD_SET  (the_socket, &fds);
			// normal timeout, can be changed by the wait option.
		timeout.tv_sec = 30;
		timeout.tv_usec = 0;
		return 0;



	}
#ifdef _INCLSSL

	// Make this a non-blocking socket
	ioctlsocket (the_socket, FIONBIO, &ioctl_blocking);


	// normal timeout, can be changed by the wait option.
	timeout.tv_sec = 30;
	timeout.tv_usec = 0;
	retval = 0;


#ifdef _DEBUG
		TRACE("<connect SSL>\n");
#endif


	bIsSSL = (UseSSL == CONN_SSL && SSLLoaded);

	if(NULL==(hConnection=SSL_new(SSLCtx)))
		retval = ERR_ESSL_CREATESSL;
	
	ERR_clear_error();
	
	if(!retval)
		SSL_clear(hConnection);

	if(!retval)
		err_code = SSL_set_fd(hConnection,the_socket);
	
	if (err_code < 1)
		retval = ERR_ESSL_SETSOCKET;

	if(!retval)
		SSL_set_connect_state(hConnection);//client

	int nError = 0;
	DWORD dwStart = GetTickCount() ;
	if(!retval)
	{
		do
		{
		  err_code = SSL_connect(hConnection); 	/* fails with SSL_ERROR_WANT_READ because of non-blocking */
		  if (err_code < 0)
			   nError = SSL_get_error(hConnection, err_code);
		  else 
			nError = 0;

		if (GetTickCount() - dwStart >= TIMELIMIT )
			break;
		
		} while (nError == SSL_ERROR_WANT_READ);
	}

	if (err_code < 1)
	{


		int nError = SSL_get_error(hConnection, err_code);
		if (nError == SSL_ERROR_SYSCALL)
		{
			
			int ws_error = WSAGetLastError();
			
			//tls_sess = SSL_get_session(tls_conn);

			
			
			switch (ws_error) 
			{
				
				case 0:
				{
					
					unsigned long l;
					TRACE("SSL_connect returned SSL_ERROR_SYSCALL: Winsock Error 0\r\n");
					while((l=ERR_get_error()))
					{
						ERR_error_string_n(l, (char *) g_message, sizeof(g_message));
						TRACE("%s\r\n", g_message);
					}

					nReconnects++;
					if (nReconnects%10 == 0)
					{
						wsprintf (g_message, "SSL_connect returned SSL_ERROR_SYSCALL without a system error for %d tries!", nReconnects);

						complain (g_message);

					}
					



				}				
				// all these indicate loss of connection (are there more?)
				case WSAENOTCONN:
				case WSAENETDOWN:
				case WSAENETUNREACH:
				case WSAENETRESET:
				case WSAECONNABORTED:
				case WSAECONNRESET:
				{
					close();
					bIsSSL = FALSE;
					Sleep( min(nReconnects*100,3000));
					//try again?
					if (nReconnects < 40)
						return (get_connected (hostname, service,UseSSL, ipfamily));

				}
				break;
			}
			
			if (nReconnects < 40)
				complain(format_system_error(ws_error, "SSL_connect returned SSL_ERROR_SYSCALL: Winsock Error %d\n##", ws_error));


		}
		else
		{ 
		  
			wsprintf (g_message, "SSL_connect unexpected error: %d", err_code);
			complain (g_message);

		}

		retval= ERR_ESSL_CONNECT;
	}
	else
	{
	  
		nReconnects = 0;
	  
	  
	  TRACE ("SSL connection using %s:%s\n", SSL_get_cipher_name (hConnection), SSL_get_cipher_version(hConnection));

	  char desc[1024] = {0};

	  //SSL_get_cipher_description(hConnection, desc, 1023); 
	  err_code = SSL_get_error(hConnection, 0);
	  desc[1023]=0;

		if (szDescription)
		{
			delete szDescription;
			szDescription = NULL;
		}

	  int nLen = strlen(SSL_get_cipher (hConnection));
	  nLen += strlen(SSL_get_cipher_version(hConnection));
	  
	  szDescription = new char[	nLen +5];
	  if ( szDescription)
	  {
		_sprintf(szDescription, "%s:%s",SSL_get_cipher (hConnection), SSL_get_cipher_version(hConnection));
		//strncpy(szDescription, SSL_get_cipher (hConnection), nLen);
		szDescription[nLen+1 ] = 0;


	  }

  
#if CERT
		/* Get server's certificate (note: beware of dynamic allocation) - opt */
		if (SSL_get_peer_certificate)
		{
			char *resp;
			X509* server_cert = SSL_get_peer_certificate (hConnection);       

			TRACE ("Server certificate:\n");

			resp = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
			if (resp)
			{
				TRACE ("\t subject: %s\n", resp);
				CRYPTO_free (resp);
			}

			resp = X509_NAME_oneline (X509_get_issuer_name  (server_cert),0,0);
			if (resp)
			{
				TRACE ("\t issuer: %s\n", resp);
				CRYPTO_free (resp);
			}

			/* We could do all sorts of certificate verification stuff here before
			 deallocating the certificate. */


			//init CertStore:

			void * m_store = X509_STORE_new();
			void * m_lookup = X509_STORE_add_lookup(m_store,X509_LOOKUP_file());
			X509_STORE_load_locations(m_store, (char*)"CAFile.pem", NULL);
			X509_STORE_set_default_paths(m_store);
			//			X509_LOOKUP_load_file(m_lookup,"CAFile.pem",X509_FILETYPE_PEM)
			X509_LOOKUP_ctrl(m_lookup, X509_L_FILE_LOAD,"CAFile.pem",X509_FILETYPE_PEM,NULL);

			m_lookup=X509_STORE_add_lookup(m_store,X509_LOOKUP_hash_dir());

			//VerifyCert:

			X509_STORE_CTX *storeCtx = (X509_STORE_CTX*)X509_STORE_CTX_new();
			X509_STORE_CTX_init(storeCtx,m_store, server_cert ,NULL);
			X509_STORE_CTX_set_flags(storeCtx, X509_V_FLAG_CB_ISSUER_CHECK);
			if (X509_verify_cert(storeCtx) == 1)
			{
				TRACE("success");
			}
			else
			{
				TRACE("Verification error: %s",X509_verify_cert_error_string(((X509_STORE_CTX*)storeCtx)->error));
			}
			X509_STORE_CTX_free(storeCtx);

			//you also need to cleanup m_store

			if(m_store != NULL)
			{
				X509_STORE_free(m_store);
				m_store = NULL;
			}



			X509_free (server_cert);
		}
#endif
	}
	
	if (!retval)
	{
		ConEstablished=TRUE;
	//	ioctlsocket (the_socket, FIONBIO, &ioctl_blocking);
	// make the FD_SET and timeout structures for later operations...

	FD_ZERO (&read_fds);
	FD_ZERO (&write_fds);
	FD_SET( (SOCKET) SSL_get_fd(hConnection) ,&read_fds);
	FD_SET((SOCKET) SSL_get_fd(hConnection) ,&write_fds);



#if _DEBUG
	TRACE("%s to %s:%d %s.",SSLconnstr,hostname,service,"established");
#endif
	}
	else
	{
		ConEstablished = FALSE;
		bIsSSL = FALSE;
	}

#endif //_INCLSSL

return (retval);
}

int	connection::starttls()
{
	int retval = ERR_CANT_CONNECT, err_code = 0;

#ifdef _INCLSSL
	if (!SSLLoaded && InitSSL() != 0)	
#ifdef _WIN64
		return ERR_SSL_NOTLOADED64;
#else
		return ERR_SSL_NOTLOADED;
#endif


	// normal timeout, can be changed by the wait option.
	timeout.tv_sec = 30;
	timeout.tv_usec = 0;
	retval = 0;

	TRACE("<connect SSL TLS>\n");



	bIsSSL =  true;


	if(NULL==(hConnection=SSL_new(SSLCtx)))
		retval = ERR_ESSL_CREATESSL;
	
	if(!retval)
		err_code = SSL_set_fd(hConnection,the_socket);
	if (err_code <1)
		retval = ERR_ESSL_SETSOCKET;

	SSL_set_mode(hConnection, SSL_MODE_AUTO_RETRY);

	if(!retval)
		  err_code = SSL_connect(hConnection);

	/* Initiates the TLS handshake with a server */
	while((retval= SSL_connect(hConnection)) <= 0)
	{
		int err = SSL_get_error(hConnection, retval);

		FD_ZERO (&read_fds);
		FD_ZERO (&write_fds);
		
		

		if(err == SSL_ERROR_WANT_READ)
			FD_SET( (SOCKET) SSL_get_fd(hConnection) ,&read_fds);
		else if(err == SSL_ERROR_WANT_WRITE)
			FD_SET((SOCKET) SSL_get_fd(hConnection) ,&write_fds);
		else
		{
			TRACE("SSL connect failed %d",retval);
			ConEstablished = FALSE;
			bIsSSL = FALSE;

			return -1;
		}
		retval=select(SSL_get_fd(hConnection) + 1, &read_fds, &write_fds, NULL, &timeout);
	}
	{

		ConEstablished = TRUE;
		bIsSSL = TRUE;
		retval = 0;
	  
	  
		TRACE ("SSL connection using %s\n", SSL_get_cipher (hConnection));

		char desc[1024]={0};

		//SSL_get_cipher_description(hConnection, desc, 1023); 
		err_code = SSL_get_error(hConnection, 0);
		if (szDescription)
		{
			delete szDescription;
			szDescription = NULL;
		}

		szDescription = new char[	strlen(desc) +1];
		if ( szDescription)
		{
			strcpy(szDescription, desc);
		}


	}
	
#endif
return (retval);

	
}


const char* connection::get_description(char* desc, int length)
{
#ifdef _INCLSSL
	if (desc && length > 0)
	{	
		*desc = '\0';
		if (szDescription)
			strntcpy(desc, szDescription, length);
	}
#endif
return szDescription;

}


//
//---------------------------------------------------------------------------
//
// The 'wait' parameter, if set, says to return WAIT_A_BIT
// if there's no data waiting to be read.

int	connection::get_buffer(int wait)
{
	int retval =0;
	int bytes_read = 0;
	int error =0;

	// Use select to see if data is waiting...

#ifdef _INCLSSL
	while (bIsSSL)
	{

		if (!hConnection)
			return ERR_NOT_CONNECTED;
		
		FD_ZERO (&read_fds);
		FD_ZERO (&write_fds);

		if (read_blocked_on_write)
		{
			FD_SET(SSL_get_fd(hConnection),&write_fds);

		}
		else
		{
		
			FD_SET(SSL_get_fd(hConnection),&read_fds);
		}
		// if wait is not set, we are polling, return immediately
		if (wait) 
		{
			timeout.tv_sec = 0;
		}
		else 
		{
			timeout.tv_sec = 30;
		}
		//if there is data to read, don't issue select
		if (!SSL_pending(hConnection))
		{

			retval=select(0,&read_fds,&write_fds,NULL,&timeout);

			if (read_blocked_on_write)
			{
				read_blocked_on_write = 0;
				if (!retval && wait)
					return (WAIT_A_BIT);
				
				continue;
			}

			//if there is no data on socket or not data pending...
			if (!FD_ISSET(SSL_get_fd(hConnection),&read_fds) && !SSL_pending(hConnection))
			{

					if (wait)
						return (WAIT_A_BIT);
					continue;
			}
		}


		//
		//if (!retval && wait)
		//	return (WAIT_A_BIT);
		// Request ownership of the critical section.
		__try 
		{
			EnterCriticalSection(&sslHandleMutex); 

			// Access the shared resource.
			ZeroMemory(in_buffer,SOCKET_BUFFER_SIZE);
			ERR_clear_error();
			read_blocked=0;
			do
			{

				bytes_read = SSL_read(hConnection, in_buffer, SOCKET_BUFFER_SIZE);
				error = SSL_get_error (hConnection, bytes_read);

			}
			while (bytes_read < 0
				&& (error == SSL_ERROR_SYSCALL)
				&& WSAGetLastError() == WSAEINTR);

			if (!bytes_read)
				error = SSL_get_error (hConnection, bytes_read);

		}
		__finally 
		{
			// Release ownership of the critical section.
			LeaveCriticalSection(&sslHandleMutex);
		}


		

		switch(	error)
		{
			case SSL_ERROR_NONE:
				/* Note: this call could block, which blocks the
				entire application. It's arguable this is the
				right behavior since this is essentially a terminal
				client. However, in some other applications you
				would have to prevent this condition */
				in_buffer_total	= bytes_read;
				in_index = 0;
				if (!bytes_read)
					return WAIT_A_BIT;
				break;
			case SSL_ERROR_ZERO_RETURN:
				TRACE("SSL_ERROR_ZERO_RETURN\r\n");
			//connection gone
				return (ERR_NOT_CONNECTED);
				break;
			case SSL_ERROR_WANT_READ:
				//just means ssl wants more data before U get any
				read_blocked=true;
				continue;
				break;

			/* We get a WANT_WRITE if we're
			trying to rehandshake and we block on
			a write during that rehandshake.

			We need to wait on the socket to be 
			writeable but reinitiate the read
			when it is */
			case SSL_ERROR_WANT_WRITE:
				TRACE("SSL_ERROR_WANT_WRITE\r\n");
				read_blocked_on_write=1;
				break;
			case SSL_ERROR_SYSCALL: //some kind of I/O error
			{
				
				int ws_error = WSAGetLastError();
				switch (ws_error) 
				{
					// all these indicate loss of connection (are there more?)
					case WSAENOTCONN:
					case WSAENETDOWN:
					case WSAENETUNREACH:
					case WSAENETRESET:
					case WSAECONNABORTED:
					case WSAECONNRESET:
						return (ERR_NOT_CONNECTED);
						break;
				}
				
				if (ws_error == WSAEWOULDBLOCK || ws_error == 0)
					return (WAIT_A_BIT);
			
				close(); //at this point we could get recalled

				complain(format_system_error(ws_error, "connection::get_buffer() SSL_read (SSL_ERROR_SYSCALL) error: %d\n##", ws_error));
				return (ERR_NOT_CONNECTED);
			}
				break;
			default:
			{

				TRACE("SSL read problem %d\r\n", error);
				wsprintf (g_message, "connection::get_buffer() SSL_read unexpected error: %d", error);
				complain (g_message);

				
				return (ERR_READING_SOCKET);
			}

		}

		if(bytes_read < 1)
		{
			
			wsprintf (g_message, "connection::get_buffer() SSL_read unexpected error: %d", error);
			complain (g_message);
		}
		else
		{
			in_buffer_total	= bytes_read;
			in_index = 0;
		}
#if 0 //_DEBUG
		*(in_buffer+bytes_read)=0;				//end the buffer to write it to file
		TRACE("SSL recv %s",in_buffer);

#endif
		return (0);
	}

#endif

	//non SSL


	FD_ZERO (&fds);
	FD_SET  (the_socket, &fds);


	// if wait is set, we are polling, return immediately
	if (wait) 
	{
		timeout.tv_sec = 0;
	}
	else 
	{
		timeout.tv_sec = 30;
	}



	if ((retval = select (0, &fds, NULL, NULL, &timeout)) == SOCKET_ERROR) 
	{
		int error_code = WSAGetLastError();

		if (error_code == WSAEINPROGRESS && wait) 
		{
			return (WAIT_A_BIT);
		}

		complain(format_system_error(error_code, "connection::get_buffer() unexpected error from select: %d\n##", error_code));
	}


	// if we don't want to wait
	if (!retval && wait) 
	{
		return (WAIT_A_BIT);
	}



	// we have data waiting...
	bytes_read = recv (the_socket, in_buffer, SOCKET_BUFFER_SIZE, 0);

	// just in case.
	if (bytes_read == 0) 
	{
		// connection terminated (semi-) gracefully by the other side
		return (ERR_NOT_CONNECTED);
	}

	if (bytes_read == SOCKET_ERROR) 
	{
		int ws_error = WSAGetLastError();
		switch (ws_error) {
			// all these indicate loss of connection (are there more?)
			case WSAENOTCONN:
			case WSAENETDOWN:
			case WSAENETUNREACH:
			case WSAENETRESET:
			case WSAECONNABORTED:
			case WSAECONNRESET:
				return (ERR_NOT_CONNECTED);
				break;

			case WSAEWOULDBLOCK:
				return (WAIT_A_BIT);
				break;

			default:
				complain(format_system_error(ws_error, "connection::get_buffer() unexpected error: %d\n##", ws_error));

		}
	}

	// reset buffer indices.
	in_buffer_total = bytes_read;
	in_index = 0;
	return (0);

}

//
//---------------------------------------------------------------------------
// get a character from this connection.
//

int connection::wvgetchar(int wait, char FAR * ch)
{
	int retval;
	int err = Z_OK;
	if (zInflateState != NO_INFLATE )
	{
		while (z_index >= z_buffer_total) 
		{
			
			if (zInflateState == INFLATE_DONE)
			{
				in_buffer[in_buffer_total] = 0;
				TRACE("INFLATE_DONE %d remain (%s)\r\n", in_buffer_total-in_index, (in_index < in_buffer_total ? in_buffer+in_index : ""));
				zInflateState = NO_INFLATE;
				return wvgetchar(wait, ch);
			}
			else if (zInflateState == INFLATE_CONT)
			{
				//call inflate again
				zstm.next_out = (Bytef*)z_buffer;
				zstm.avail_out = ZBUFFER_SIZE;
				if (in_index >= in_buffer_total)
					zInflateState = INFLATING; //can't have 0 avail_in

			}
			if (zInflateState == INFLATING)
			{
				if (in_index >= in_buffer_total)
				{
					if ((retval = get_buffer(wait)))
					{
						if (retval != WAIT_A_BIT)
						{
							//socket error? close zstream
							wvendZinflate();
						}
						return (retval); //wait a bit
					
					}

					if (nYDec)
						yDecBuf();

					
					zstm.next_in = (Bytef*)in_buffer;
					zstm.avail_in = in_buffer_total;
					zstm.next_out = (Bytef*)z_buffer;
					zstm.avail_out = ZBUFFER_SIZE;
				}
				else
				{
					zstm.next_in = (Bytef*)in_buffer+in_index;
					zstm.avail_in = in_buffer_total-in_index;

					zstm.next_out = (Bytef*)z_buffer;
					zstm.avail_out = ZBUFFER_SIZE;

				}
			}

			err = inflate(&zstm, Z_SYNC_FLUSH);

			in_index = ( (char *) zstm.next_in - in_buffer);

			switch (err)
			{
				case Z_STREAM_END:
				{
					zInflateState = INFLATE_DONE;
					z_buffer_total = ZBUFFER_SIZE - zstm.avail_out;
					z_index = 0;
					
				}
				break;
				case Z_BUF_ERROR:
				{
					//make sure that avail_in and avail_out are not zero
					TRACE("Z_BUF_ERROR\r\n");

				}
				//fall through
				case Z_OK:
					z_buffer_total = ZBUFFER_SIZE - zstm.avail_out;
					z_index = 0;
					if (zstm.avail_out == 0)
						zInflateState = INFLATE_CONT;
					else
						zInflateState = INFLATING;
					break;


				default:
				{
					TRACE("ZStream inflate err = %d (%s)\r\n", err, zstm.msg);
					
					zInflateState = INFLATE_DONE;
					return wvgetchar(wait, ch);

				}
				break;


			}

		}
				
		*ch = z_buffer[z_index++];
		return (0);
	
	}
		
	if (in_index >= in_buffer_total) 
	{

		if ((retval = get_buffer(wait)))
			return (retval);

	}
	*ch = in_buffer[in_index++];
	return (0);
}

int	connection::wvstartZinflate(int nType)
{

	if (zInflateState != NO_INFLATE) //already in zstream?
	{
		inflateEnd(&zstm);
		zInflateState = NO_INFLATE;
	}
	memset(&zstm,0,sizeof(z_stream));
	if  (!inflateInit(&zstm) == Z_OK)
		return -1;

	if (!z_buffer)
	{
		z_buffer = new char[ZBUFFER_SIZE];
	}
	z_buffer_total = 0;
	z_index = 0;
	zInflateState = INFLATING;

	CrcInit(&dwCRC32);
	bEscape = false;
	bNewLine = false;
	nYDec = Y_NONE;
	z_inbuf_total = 0;
	int err = 0;
	if (nType)
	{
		
		if  (!inflateInit2(&zstm, -15) == Z_OK)
			return -1;
		
		nYDec = Y_BEGIN;
		err = yDecBuf();
	}
	if (in_index < in_buffer_total)
	{
		//feed the first amount to inflate
		zstm.next_in = (Bytef*)in_buffer + in_index;
		zstm.avail_in = in_buffer_total - in_index;
		zstm.next_out = (Bytef*)z_buffer;
		zstm.avail_out = ZBUFFER_SIZE;

		err = inflate(&zstm, Z_SYNC_FLUSH);
		
		in_index = ( (char *) zstm.next_in - in_buffer);
		z_buffer_total = ZBUFFER_SIZE - zstm.avail_out;
		z_index = 0;
		if (zstm.avail_out == 0)
			zInflateState = INFLATE_CONT;
		else
			zInflateState = INFLATING;

		if (err == Z_STREAM_END)
		{
			zInflateState = INFLATE_DONE;
			TRACE("wvstartZinflate gets Z_STREAM_END %d\r\n", err);
			
		} 
		else if (err != Z_OK)
		{
			//stream error
			TRACE("wvstartZinflate gets %d\r\n", err);
			zInflateState = NO_INFLATE;
			return 1;
		}

	}

	return (z_buffer == NULL);

}
int	connection::wvendZinflate()
{
	int retval;

	if (zInflateState != NO_INFLATE)
	{
		retval = inflateEnd(&zstm);
		zInflateState = NO_INFLATE;
		return retval == Z_OK ? 0 : 1;

	}
	return 0;


}

//un-yEnc the data.  Returns 0 to continue, 1 on end or -1 on end with error
int connection::yDecBuf()
{
    int nReturn = 0, zindex = 0;
	char c;
	
	//copy incoming data to working buffer, yenc decompressed data size is always <= incoming size
	memcpy(z_inBuffer+z_inbuf_total, in_buffer+in_index, in_buffer_total-in_index);
	z_inbuf_total += (in_buffer_total-in_index);

	bNewLine = (nYDec == Y_BEGIN);
	in_index = 0;
	in_buffer_total = 0;

	while ( zindex < z_inbuf_total)
	{
		c = z_inBuffer[zindex++];

		if (c == 0x0A || c == 0x0D)
		{
			bNewLine = true;
			continue;
		}
		if (bNewLine)
		{
			bNewLine = false;
			if (c == '.')
			{
				if (z_inBuffer[zindex] == '.')
				{
					TRACE("Eat double-dot\r\n"); 
					continue; //does this ever happen?

				}
			}
			if (!strncmp(z_inBuffer+zindex-1, "=ybegin ", 8))
			{
				//start of ystream //=ybegin line=128 size=-1
				//skip to EOL
				while (zindex < z_inbuf_total && z_inBuffer[zindex] != 0x0A	&& z_inBuffer[zindex] != 0x0D)
					zindex++;
				//skip past EOL
				while (zindex < z_inbuf_total && (z_inBuffer[zindex] == 0x0A || z_inBuffer[zindex] == 0x0D) )
					zindex++;
				
				nYDec = Y_DEC;
				in_buffer_total = 0;
				continue;


			}
			else if (!strncmp(z_inBuffer+zindex-1, "=yend ", 6))
			{
				//end of ystream //=yend crc32=FFFFFFFF
				int zend = zindex;
				while (zend < z_inbuf_total && z_inBuffer[zend] != 0x0A	&& z_inBuffer[zend] != 0x0D)
					zend++;
				while (zend < z_inbuf_total && (z_inBuffer[zend] == 0x0A || z_inBuffer[zend] == 0x0D) )
					zend++;
				//find crc
				while (zindex < zend && z_inBuffer[zindex] != '=' )
					zindex++;

				//copy any leftover incoming data back
				while (zend < z_inbuf_total)
					in_buffer[in_buffer_total++] = z_inBuffer[zend++]; 
				
				while (zindex < zend && z_inBuffer[zindex] != '=' )
					zindex++;
				DWORD crc = 0;
				ReadHEX32(z_inBuffer+zindex+1, &crc);
				if ((dwCRC32 ^ 0xFFFFFFFFl) != crc )
				{
					TRACE("Stream ydata error\r\n");
					nReturn = -1;
				}
				else
				{
					nReturn = 1;
				}
				if (zindex < z_inbuf_total)
				{
					memmove(z_inBuffer+zindex, z_inBuffer, z_inbuf_total-zindex);
					z_inbuf_total -= zindex;
				}
				nYDec = Y_NONE;
				break;
				 
			}
		} //if newline

		if (nYDec == Y_DEC)
		{
			if (c == '=')
			{
				bEscape = true;
				continue;
			}

			if (bEscape)
			{
				bEscape = false;
				
				if (c >= 64)
					c -= 64;
			}

			c -= 42;
			in_buffer[in_buffer_total++] = (unsigned char) c;
			CrcAdd(c, &dwCRC32);

		}

	}
	if (zindex < z_inbuf_total && nYDec)
	{
		memmove(z_inBuffer+zindex, z_inBuffer, z_inbuf_total-zindex);
	    z_inbuf_total -= zindex;
	}
	else
	{
		z_inbuf_total = 0;
	}

	return nReturn;
}


//
//---------------------------------------------------------------------------
// FIXME: should try to handle the fact that send can only take
// an int, not an unsigned long.

int connection::put_data (char far* data, unsigned long length)
{
	int num_sent = 0;
	int retval;	
	int error = 0;

#ifdef _INCLSSL

	FD_ZERO (&read_fds);
	FD_ZERO (&write_fds);
#endif

	timeout.tv_sec = 30;

	while (length > 0) 
	{

#ifdef _INCLSSL
		while (bIsSSL)
		{
			bool write_blocked = 0;
			error = 0;
			// Request ownership of the critical section.
			__try 
			{
				EnterCriticalSection(&sslHandleMutex); 
				ERR_clear_error();			
				num_sent = SSL_write(hConnection,(PVOID)data, length);
				//can generate SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE, sys err
			}
			__finally 
			{
				// Release ownership of the critical section.
				LeaveCriticalSection(&sslHandleMutex);
			}

			if (num_sent <= 0)
			{
				error = SSL_get_error (hConnection, num_sent);							
			}

			switch (error)
			{
			
				case SSL_ERROR_NONE:
					break;
					
				case SSL_ERROR_ZERO_RETURN:
					TRACE("SSL_ERROR_ZERO_RETURN\r\n");
					//connection gone
					return (ERR_NOT_CONNECTED);					
					break;
				
				/* We get a WANT_READ if we're
				   trying to rehandshake and we block on
				   write during the current connection.
               
				   We need to wait on the socket to be readable
				   but reinitiate our write when it is */

				case SSL_ERROR_WANT_READ:
					TRACE("SSL_ERROR_WANT_READ\r\n");
					write_blocked_on_read=1;
					break;
					
					//we would have blocked
				case SSL_ERROR_WANT_WRITE:
					write_blocked = 1;
					TRACE("SSL_ERROR_WANT_WRITE\r\n");
					break;
				case SSL_ERROR_SYSCALL:
				 if (WSAGetLastError () == WSAEWOULDBLOCK
					 || WSAGetLastError () == WSAEINTR)
					{
						TRACE("connection::put_data() SSL_Write unexpected winsock error: %d\n##", WSAGetLastError ());
						WSASetLastError (WSAEWOULDBLOCK);
						return 0;					
					}
					complain(format_system_error(error, "connection::put_data() SSL_Write unexpected winsock error: %d\n##", WSAGetLastError ()));
					return 0;													
					break; 
				
				default:
				ERR_error_string_n(error, (char *) g_message, sizeof(g_message));
				TRACE("SSL_Write unknown response %d, %s\r\n", error, g_message);
				complain(format_system_error(error, "connection::put_data() SSL_Write unknown response %d %s\n##", error, g_message));
				return 0;					
					break;
			}


			if (write_blocked_on_read || write_blocked)
			{
				if (write_blocked_on_read)
					FD_SET(SSL_get_fd(hConnection), &read_fds);
				else
					FD_SET(SSL_get_fd(hConnection), &write_fds);

				write_blocked_on_read =0;
				write_blocked =0;

				if ((retval = select (0, &read_fds, &write_fds, NULL, &timeout)) == SOCKET_ERROR) 
				{
					int error_code = WSAGetLastError();
					complain(format_system_error(error_code, "connection::put_data() unexpected error from select: %d\n##", error_code));
				}
				continue;

			}


			if(num_sent < 0)
			{

				complain(format_system_error(error, "connection::put_data() unexpected error from send(): %d\n##", error));
				return 0;


			}
			else if (num_sent != length)
			{
				
				if (num_sent == 0)
					return (ERR_NOT_CONNECTED);
				
				length -= num_sent;
				data += num_sent;

				continue;

			}
			else
				return 0;
		
		} //if SSL

#endif 
		
		FD_ZERO (&fds);
		FD_SET(the_socket, &fds);
		if ((retval = select (0, NULL, &fds, NULL, &timeout)) == SOCKET_ERROR) 
		{
			int error_code = WSAGetLastError();
			complain(format_system_error(error_code, "connection::put_data() unexpected error from select: %d\n##", error_code));
		}

		num_sent = send (the_socket, data, length > 1024 ? 1024 : (int)length, 0);

		if (num_sent == SOCKET_ERROR) 
		{
			int ws_error = WSAGetLastError();
			switch (ws_error) 
			{
				// this is the only error we really expect to see.
				case WSAENOTCONN:
					return (ERR_NOT_CONNECTED);
					break;

				// seems that we can still get a block
				case WSAEWOULDBLOCK:
				case WSAEINPROGRESS:
					break;

				default:
					complain(format_system_error(ws_error, "connection::put_data() unexpected error from send(): %d\n##", ws_error));
					return (ERR_SENDING_DATA);
			}
		}
		else 
		{
			length -= num_sent;
			data += num_sent;
		}
	}

	return (0);
}

//
//
// buffered output
//

int	connection::put_data_buffered (char far* data, unsigned long length)
{
	int retval;

	while (length) 
	{
		if ((out_index + length) < SOCKET_BUFFER_SIZE) 
		{
			// we won't overflow, simply copy into the buffer
			memcpy (out_buffer + out_index, data, (size_t) length);
			out_index += (unsigned int) length;
			length = 0;
		}
		else 
		{
			unsigned int orphaned_chunk = SOCKET_BUFFER_SIZE - out_index;
			// we will overflow, handle it
			memcpy (out_buffer + out_index, data, orphaned_chunk);
			// send this buffer...
			
			if ((retval = put_data (out_buffer, SOCKET_BUFFER_SIZE))) 
			{
				return (retval);
			}
			length -= orphaned_chunk;
			out_index = 0;
			data += orphaned_chunk;
		}
	}

	return (0);
}

int	connection::put_data_flush (void)
{
	int retval;
	*(out_buffer + out_index ) = 0;
	if ((retval = put_data (out_buffer, out_index)))
		return (retval);
	else
		out_index = 0;

	return(0);
}

//
//---------------------------------------------------------------------------
//

int	connection::close (void)
{

	wvendZinflate();

#ifdef _INCLSSL

	if(hConnection != (HANDLE)NULL)
	{
		int paranoia = 10000;
		SSL_set_shutdown(hConnection, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
		while(paranoia-- && !SSL_shutdown(hConnection));
		SSL_free(hConnection);
	}
	
	hConnection=NULL;
#ifdef _DEBUG
	if (ConEstablished) 
		TRACE("%s %s.",SSLconnstr,"closed");
#endif
	
	ConEstablished=FALSE;
#endif

	int retval = closesocket(the_socket);
	the_socket=INVALID_SOCKET;

	if (retval == SOCKET_ERROR)
		return (ERR_CLOSING);
	else
		return (0);
}


//
//---------------------------------------------------------------------------
// we keep lists of connections in this class

class connection_list
{
private:
	connection *      data;
	connection_list *     next;

public:
	connection_list   (void);
	~connection_list  (void);
	void push         (connection & conn);

	// should really use pointer-to-memberfun for these
	connection * find (SOCKET sock);
	int how_many_are_mine (void);

	void remove       (socktag sock);
};

connection_list::connection_list (void)
{
	next = 0;
}

connection_list::~connection_list(void)
{
	delete data;
}

// add a new connection to the list

void connection_list::push (connection & conn)
{
	connection_list * new_conn;

	new_conn = new connection_list();

	new_conn->data = data;
	new_conn->next = next;

	data = &conn;
	next = new_conn;

}

int connection_list::how_many_are_mine(void)
{
	TASK_HANDLE_TYPE  current_task = GET_CURRENT_TASK;
	connection_list * iter = this;
	int num = 0;

	while (iter->data) 
	{
		if (iter->data->get_owner_task() == current_task)
			num++;
		iter = iter->next;
	}
	return (num);
}

// find a particular socket's connection object.

connection * connection_list::find (SOCKET sock)
{
	connection_list * iter = this;

	while (iter->data) 
	{
		if (iter->data->get_socket() == sock)
			return (iter->data);
		iter = iter->next;
	}

	return (0);
}

void connection_list::remove (socktag sock)
{
	// at the end
	if (!data)
		return;

	// we can assume next is valid because
	// the last node is always {0,0}
	if (data == sock) 
	{
		delete data;
		data = next->data;
		next = next->next;  // 8^)
		return;
	}

	// recurse
	next->remove(sock);
}

//
// ---------------------------------------------------------------------------
// global variables (shared by all DLL users)

connection_list global_socket_list;
int network_initialized;



// ---------------------------------------------------------------------------
// C/DLL interface
//
int FAR PASCAL  gensock_connect (char FAR * hostname, char FAR * service, socktag FAR * pst, char UseSSL, int ipfamily)
{
	int retval;
	connection * conn = new connection;

	if (!conn)
		return (ERR_INITIALIZING);

	// if this task hasn't opened any sockets yet, then
	// call WSAStartup()

	if (global_socket_list.how_many_are_mine() < 1)
	{
		retval = init_winsock( (eConnection) UseSSL);
		if (!retval)
		{
#ifdef _INCLSSL
			if (UseSSL && !SSLLoaded)
				retval = InitSSL();
			if (retval != 0)
			{
				deinit_winsock();
				return (ERR_SSL_INITIALIZE);
			}
#endif		
		}
	}

	global_socket_list.push(*conn);

	if ((retval = conn->get_connected (hostname, service, (eConnection) UseSSL, ipfamily))) 
	{
		gensock_close(conn);
		*pst = 0;
		return (retval);
	}
	*pst = (void FAR *) conn;

	return (0);
}

int FAR PASCAL  gensock_starttls (socktag st)
{

	connection * conn;
	int retval = 0;

	conn = (connection *) st;
	if (!conn)
		return (ERR_NOT_A_SOCKET);

	if ((retval = conn->starttls()))
		return (retval);
	else
		return (0);


}

int FAR PASCAL gensock_description (socktag st, char* desc, int nLen)
{

#ifdef _INCLSSL

	connection * conn;
	
	if (global_socket_list.how_many_are_mine() < 1)
		init_winsock( CONN_NORMAL);
	
	conn = (connection *) st;
	if (!conn)
	{
		
		strntcpy(desc, szSSLDescription, nLen);
		return (0);
	}

	if ((conn->get_description(desc, nLen)))
		return (1);
	else
#endif
		return (0);


}


//
//
//

int FAR PASCAL  gensock_getchar (socktag st, int wait, char FAR * ch)
{
	connection * conn;
	int retval = 0;

	conn = (connection *) st;
	if (!conn)
		return (ERR_NOT_A_SOCKET);

	if ((retval = conn->wvgetchar(wait, ch)))
		return (retval);
	else
		return (0);
}

//initiate decompression of compressed stream
int FAR PASCAL gensock_startZstream (socktag st, int nType)
{

	connection * conn;
	int retval = 0;

	conn = (connection *) st;
	if (!conn)
		return (ERR_NOT_A_SOCKET);

	retval = conn->wvstartZinflate(nType);
	return retval;
}

int FAR PASCAL gensock_endZstream (socktag st)
{
	connection * conn;
	int retval = 0;

	conn = (connection *) st;
	if (!conn)
		return (ERR_NOT_A_SOCKET);

	retval = conn->wvendZinflate();
	return retval;
}



//---------------------------------------------------------------------------
//
//

int FAR PASCAL  gensock_put_data (socktag st, char FAR * data, unsigned long length)
{
	connection * conn;
	int retval = 0;

	conn = (connection *) st;

	if (!conn)
		return (ERR_NOT_A_SOCKET);

	if ((retval = conn->put_data(data, length)))
		return (retval);

	return (0);
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL  gensock_put_data_buffered (socktag st, char FAR * data, unsigned long length)
{
	connection * conn;
	int retval = 0;

	conn = (connection *) st;

	if (!conn)
		return (ERR_NOT_A_SOCKET);

	if ((retval = conn->put_data_buffered (data, length)))
		return (retval);

	return (0);
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL  gensock_put_data_flush (socktag st)
{
	connection * conn;
	int retval = 0;

	conn = (connection *) st;

	if (!conn)
		return (ERR_NOT_A_SOCKET);

	if ((retval = conn->put_data_flush() ))
		return (retval);

	return (0);
}

//---------------------------------------------------------------------------
//
//
int FAR PASCAL  gensock_gethostname (char FAR * name, int namelen)
{
	int retval;
	if ((retval = gethostname(name, namelen))) 
	{
		return (retval - 5000);
	}
	else return (0);
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL  gensock_close (socktag st)
{
	connection * conn;
	int retval;

	conn = (connection *) st;

	if (!conn)
		return (ERR_NOT_A_SOCKET);

	retval = conn->close();
	
	global_socket_list.remove((connection *)st);


	if (global_socket_list.how_many_are_mine() < 1) 
	{
#ifdef _INCSSL		
		DeInitSSL();
#endif
		deinit_winsock();

	}

	return (retval);
}

//---------------------------------------------------------------------------
//
//

int	init_winsock(eConnection UseSSL)
{
	int retval  = 0;
	WSADATA winsock_data;
	WORD version_required = 0x0101; /* Version 1.1 */

#ifdef WANTIP6
	version_required = MAKEWORD(2,2); 
#endif

//dynamic load ws2_32.dll?
#ifdef _INCLSSL
	//try winsock 2.0 in case we need SSL later
	version_required =MAKEWORD(2,0);

#ifdef WANTIP6
	version_required = MAKEWORD(2,2); 
#endif

	retval = WSAStartup (version_required, &winsock_data);
	if (retval != 0)
	{
		if (UseSSL == CONN_NORMAL)
		{
			//okay fall back to winsock 1.1
			version_required = 0x0101;
			retval = WSAStartup (version_required, &winsock_data);
		}
			
	}
#else
	retval = WSAStartup (version_required, &winsock_data);
#endif

	switch (retval) 
	{
		case 0:
		/* successful */
			break;
		case WSASYSNOTREADY:
			return (ERR_SYS_NOT_READY);
			break;
		case WSAEINVAL:
			return (ERR_EINVAL);
			break;
		case WSAVERNOTSUPPORTED:
			return (ERR_VER_NOT_SUPPORTED);
			break;
	}
	network_initialized = 1;

	return (0);
}

void deinit_winsock(void)
{
	network_initialized = 0;
#ifdef _INCLSSL
	DeInitSSL();
#endif
	WSACleanup();
}

#ifdef _INCLSSL

DWORD GetSSLVersion(LPTSTR lpszFilename, LPTSTR szDescription, int nDescLen, BOOL& bIs64)
{
    bIs64 = FALSE;
    DWORD dwReturn = 0;
    dwReturn = GetFileVersion(lpszFilename, szDescription, nDescLen, &bIs64);
    
    return dwReturn;
} 
void Dummyvoid(void) {};

int InitSSL()
{

	if (SSLLoaded)
	{
		return ERR_ESSL_ALREADYLOADED;
	}
	
#ifndef SSLSTATIC

	{	
		TCHAR path[_MAX_PATH], *p;
		BOOL bIs64 = FALSE;
#ifdef _WIN64
		BOOL bWant64 = TRUE;
		//ignore libssl-1_1-x64.dll in app dir if bit size does not match required		
		if (SearchPath(NULL,"libssl-1_1-x64.dll", NULL, _MAX_PATH, path, &p))
		{
			GetSSLVersion(path, szSSLDescription, DESCR_SIZE, bIs64);
			if (bWant64 == bIs64)
				hSSLLibrary = LoadLibrary(path);
			if (!hSSLLibrary)
			{
				GetSystemDirectory(path, _MAX_PATH);
				_tcscat(path, "\\libssl-1_1-x64.dll");
				if (GetSSLVersion(path, szSSLDescription, DESCR_SIZE, bIs64) && bWant64 == bIs64)
					hSSLLibrary = LoadLibrary(path);				 
			}
		}
		if (SearchPath(NULL,"libcrypto-1_1-x64.dll", NULL, _MAX_PATH, path, &p))
		{
			
			if ( GetFileVersion(path,NULL, 0, &bIs64) != (DWORD) -1 && bWant64 == bIs64)
				hLibeayLibrary = LoadLibrary(path);
			else
			{
				GetSystemDirectory(path, _MAX_PATH);
				_tcscat(path, "\\libcrypto-1_1-x64.dll");
				if ( GetFileVersion(path,NULL, 0, &bIs64) != (DWORD) -1 && bWant64 == bIs64)
					hLibeayLibrary = LoadLibrary(path);				 
				
			}
			
		}				
#else
		BOOL bWant64 = FALSE;
		
		//ignore libssl-1_1.dll in app dir if bit size does not match required		
		if (SearchPath(NULL,"libssl-1_1.dll", NULL, _MAX_PATH, path, &p) )
		{
			GetSSLVersion(path, szSSLDescription, DESCR_SIZE, bIs64);
			if (bWant64 == bIs64)
				hSSLLibrary = LoadLibrary(path);
			if (!hSSLLibrary)
			{
				GetSystemDirectory(path, _MAX_PATH);
				_tcscat(path, "\\libssl-1_1.dll");
				if (GetSSLVersion(path, szSSLDescription, DESCR_SIZE, bIs64) && bWant64 == bIs64)
					hSSLLibrary = LoadLibrary(path);				 
			}
		}
		if (SearchPath(NULL,"libcrypto-1_1.dll", NULL, _MAX_PATH, path, &p))
		{
			
			if ( GetFileVersion(path,NULL, 0, &bIs64) != (DWORD) -1 && bWant64 == bIs64)
				hLibeayLibrary = LoadLibrary(path);
			else
			{
				GetSystemDirectory(path, _MAX_PATH);
				_tcscat(path, "\\libcrypto-1_1.dll");
				if ( GetFileVersion(path,NULL, 0, &bIs64) != (DWORD) -1 && bWant64 == bIs64)
					hLibeayLibrary = LoadLibrary(path);				 
				
			}
			
		}				
#endif
				
		if (!hLibeayLibrary && hSSLLibrary)
		{
			FreeLibrary(hSSLLibrary);
			hSSLLibrary = NULL;
		}


	}
	
	if (!hSSLLibrary || !hLibeayLibrary)
		return ERR_SSL_NOTLOADED;
		
	
	if(NULL!=(OPENSSL_init_ssl=(PFN_SSL_int_int_pvoid)GetProcAddress(hSSLLibrary,"OPENSSL_init_ssl")))
	if(NULL!=(TLS_client_method=(PFN_SSL_pvoid_void)GetProcAddress(hSSLLibrary,"TLS_client_method")))
	if(NULL!=(SSL_CTX_new=(PFN_SSL_pctx_pvoid)GetProcAddress(hSSLLibrary,"SSL_CTX_new")))
	if(NULL!=(SSL_CTX_free=(PFN_SSL_void_pvoid)GetProcAddress(hSSLLibrary,"SSL_CTX_free")))
	if(NULL!=(SSL_new=(PFN_SSL_pssl_pvoid)GetProcAddress(hSSLLibrary,"SSL_new")))
	if(NULL!=(SSL_free=(PFN_SSL_void_pvoid)GetProcAddress(hSSLLibrary,"SSL_free")))
	if(NULL!=(SSL_set_fd=(PFN_SSL_int_pvoid_int)GetProcAddress(hSSLLibrary,"SSL_set_fd")))
	if(NULL!=(SSL_get_fd=(PFN_SSL_int_pvoid)GetProcAddress(hSSLLibrary,"SSL_get_fd")))
	if(NULL!=(SSL_connect=(PFN_SSL_int_pvoid)GetProcAddress(hSSLLibrary,"SSL_connect")))
	if(NULL!=(SSL_pending=(PFN_SSL_int_pvoid)GetProcAddress(hSSLLibrary,"SSL_pending")))
	if(NULL!=(SSL_read=(PFN_SSL_int_pvoid_pvoid_int)GetProcAddress(hSSLLibrary,"SSL_read")))
	if(NULL!=(SSL_write=(PFN_SSL_int_pvoid_pvoid_int)GetProcAddress(hSSLLibrary,"SSL_write")))
	if(NULL!=(SSL_do_handshake=(PFN_SSL_int_pvoid)GetProcAddress(hSSLLibrary,"SSL_do_handshake")))
	if(NULL!=(SSL_shutdown=(PFN_SSL_int_pvoid)GetProcAddress(hSSLLibrary,"SSL_shutdown")))
	if(NULL!=(SSL_set_shutdown=(PFN_SSL_void_pvoid_int)GetProcAddress(hSSLLibrary,"SSL_set_shutdown")))
	//used to determine encryption
	if(NULL!=(SSL_CIPHER_get_name=(PFN_SSL_pchar_pvoid)GetProcAddress(hSSLLibrary,"SSL_CIPHER_get_name")))
	if(NULL!=(SSL_get_current_cipher=(PFN_SSL_pvoid_pvoid)GetProcAddress(hSSLLibrary,"SSL_get_current_cipher")))
	if(NULL!=(SSL_CIPHER_get_bits=(PFN_SSL_int_pvoid_pint)GetProcAddress(hSSLLibrary,"SSL_CIPHER_get_bits")))
	if(NULL!=(SSL_CIPHER_get_version=(PFN_SSL_pchar_pvoid)GetProcAddress(hSSLLibrary,"SSL_CIPHER_get_version")))
	if(NULL!=(SSL_CIPHER_description=(PFN_SSL_pchar_pvoid_pchar_int)GetProcAddress(hSSLLibrary,"SSL_CIPHER_description")))
	if(NULL!=(SSL_set_connect_state=(PFN_SSL_void_pvoid)GetProcAddress(hSSLLibrary,"SSL_set_connect_state")))
	if(NULL!=(SSL_clear=(PFN_SSL_int_pvoid)GetProcAddress(hSSLLibrary,"SSL_clear")))
	if(NULL!=(SSL_get_error=(PFN_SSL_int_pvoid_int)GetProcAddress(hSSLLibrary,"SSL_get_error")))
	{
		ERR_clear_error = &Dummyvoid;
		ERR_free_strings = &Dummyvoid;

		
		if(NULL!=(ERR_clear_error=(PFN_SSL_void_void)GetProcAddress(hLibeayLibrary,"ERR_clear_error")))
		if(NULL!=(ERR_free_strings=(PFN_SSL_void_void)GetProcAddress(hLibeayLibrary,"ERR_free_strings")))
		if(NULL!=(ERR_get_error=(PFN_SSL_ulong_void)GetProcAddress(hLibeayLibrary,"ERR_get_error")))
		if(NULL!=(ERR_error_string_n=(PFN_SSL_ulong_pvoid_size_t)GetProcAddress(hLibeayLibrary,"ERR_error_string_n")))
		{
			TRACE("loaded errors\r\n");
	
		}
		else
		{
			TRACE("Error loading errors\r\n");
		}
#else
	{
#endif //#ifndef SSLSTATIC
		
//#define OPENSSL_INIT_LOAD_SSL_STRINGS       (0x00200000L)
//#define OPENSSL_INIT_LOAD_CRYPTO_STRINGS    (0x00000002L)		
		
		OPENSSL_init_ssl(0x00200002, NULL);
	    
		SSLCtx=SSL_CTX_new(TLS_client_method());	//SSL2,3 & TLS1 used

		SSLLoaded=TRUE;
		TRACE("SSL registered");
		InitializeCriticalSection(&sslHandleMutex);

		return 0;
	}
#ifndef SSLSTATIC
	FreeLibrary(hSSLLibrary);
	hSSLLibrary=(HINSTANCE)NULL;

	TRACE("Failed to load SSL support");
#endif
	return 1;		
	

}


void DeInitSSL()
{
	if(SSLLoaded)
	{
#ifndef SSLSTATIC
		if (ERR_free_strings)
#endif
			ERR_free_strings();

		SSL_CTX_free(SSLCtx);

#ifndef SSLSTATIC
		FreeLibrary(hSSLLibrary);
		hSSLLibrary=(HINSTANCE)NULL;
		if (hLibeayLibrary)
			FreeLibrary(hLibeayLibrary);
		hLibeayLibrary = (HINSTANCE) NULL;
#endif
		DeleteCriticalSection(&sslHandleMutex);

		SSLLoaded = FALSE;
		TRACE("SSL unregistered.");
	}
}
#endif //_INCLSSL
