/* $Revision: 14 $ $Date: 9/10/03 15:44 $ Copyright © 1999-2003, FSL Technologies Limited. Contact "http://fost3.fsltech.com". */ #include "stdafx.h" #include // The openssl include directory must be on the include path! #include #include #include #include #include #include using namespace std; using namespace FSLib; using namespace FSLib::Exceptions; namespace { Revision c_revision( L"$Archive: /FOST.3/F3Util/sslstream.cpp $", __DATE__, L"$Revision: 14 $", L"$Date: 9/10/03 15:44 $" ); const wchar_t *CertificateErrorMsg = L"Unknown certificate error"; const wchar_t *PrivateKeyErrorMsg = L"Unknown private key error"; const wchar_t *SSLSocketErrorMsg = L"SSL layer error."; } inline const wchar_t *const CertificateError::message() const { return CertificateErrorMsg; } inline const wchar_t *const PrivateKeyError::message() const { return PrivateKeyErrorMsg; } SSLSocketError::SSLSocketError( const FSLib::wstring & fn, long ssl_code ) : SocketError( fn ) { switch ( ssl_code ) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_X509_LOOKUP: m_info << L"SSL low level requested (unavailable?) retry.\n"; m_info << L"Probable timeout in progress.\n"; break; case SSL_ERROR_NONE: m_info << L"No error. Programming error in exception handling.\n"; break; case SSL_ERROR_ZERO_RETURN: m_info << L"No error. Connection closed cleanly.\n"; break; case SSL_ERROR_SYSCALL: m_info << L"Connection closed unexpectedly.\n"; break; case SSL_ERROR_SSL: m_info << L"Unknown SSL Internal failure.\n"; break; default: m_info << L"Unknown and undocumented SSL error.\n"; } } inline const wchar_t *const SSLSocketError::message() const { return SSLSocketErrorMsg; } /* SSLStream */ SSLStream::SSLStream( const Host &a_host, const t_port a_port, const FSLib::wstring &a_cert_name) : basic_iostream< char >( new SSLBuf() ) { open(a_host, a_port, a_cert_name); } SSLStream::~SSLStream() { // Don't think I need anything here. // In that case where does the buffer get dealocated? // After lots of experimentation in FSStreams, I have to do something bizarre, like: try { streambuf * sb = rdbuf(); rdbuf( NULL ); delete sb; } catch ( exception & ) { absorbException(); } } void SSLStream::open( const Host &a_host, const t_port a_port, const FSLib::wstring &a_cert_name) { if ( a_cert_name.length() > 0 ) { m_cert_name=a_cert_name; dynamic_cast< SSLBuf * >( rdbuf() )->useCertificate( m_cert_name ); } else { m_cert_name = L"* none *"; } dynamic_cast< SSLBuf * >( rdbuf() )->open( a_host, a_port ); } /* SSLBuf */ SSLBuf::SSLBuf() : std::basic_streambuf< char >(), m_sin( new sockaddr_in() ), m_ssl( 0 ), m_ctx( 0 ) { SSLeay_add_ssl_algorithms(); m_meth = SSLv23_client_method(); SSL_load_error_strings(); m_ctx = SSL_CTX_new(m_meth); m_outBuffer = new char[2048]; } SSLBuf::~SSLBuf() { try { if( m_ssl ) { SSL_shutdown( m_ssl ); SSL_free( m_ssl ); } if( m_ctx ) { SSL_CTX_free( m_ctx ); } delete m_sin; if(eback()) { delete[] eback(); } delete[] m_outBuffer; } catch ( exception & ) { absorbException(); } } void SSLBuf::useCertificate( const FSLib::wstring &a_cert_name ) { m_cert_name = a_cert_name; if( 0 >= SSL_CTX_use_certificate_file( m_ctx, narrow( m_cert_name ).c_str(), SSL_FILETYPE_PEM ) ) { throw CertificateError(); } if( 0 >= SSL_CTX_use_PrivateKey_file( m_ctx, narrow( m_cert_name ).c_str(), SSL_FILETYPE_PEM ) ) { throw PrivateKeyError(); } } void SSLBuf::open( const Host &a_host, const t_port a_port) { m_sin->sin_family=AF_INET; m_sin->sin_addr.s_addr=htonl( a_host.address() ); m_sin->sin_port=htons(a_port); #pragma warning ( push ) #pragma warning ( disable : 4244 ) m_socket=socket(AF_INET, SOCK_STREAM, getprotobyname( "tcp" )->p_proto ); #pragma warning ( pop ) if(SOCKET_ERROR==m_socket) { throw SocketError( L"socket() : ", WSAGetLastError() ); } { long unsigned int tmp=1; ioctlsocket(m_socket, FIONBIO, &tmp); // Set non-blocking mode. } if(SOCKET_ERROR==connect(m_socket, (struct sockaddr *)m_sin, sizeof(struct sockaddr_in))) { if(WSAEWOULDBLOCK!=WSAGetLastError()) { // Normal with non-blocking sockets. throw SocketError( L"connect() : ", WSAGetLastError() ); } } { fd_set fds[1]; FD_ZERO(fds); #pragma warning ( push ) #pragma warning ( disable : 4127 ) FD_SET(m_socket, fds); #pragma warning ( pop ) TIMEVAL tv[1]; tv->tv_sec=30; tv->tv_usec=0; int select_ret = select( /*m_socket+1*/ 0, NULL, fds, NULL, tv ); if(select_ret==SOCKET_ERROR) { throw SocketError( L"connect-select", WSAGetLastError() ); } else if(select_ret==0) { throw SocketError( L"connect-select", WSAETIMEDOUT ); } } { int new_error; int new_error_size=sizeof(new_error); getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char *)&new_error, &new_error_size); if(new_error!=0) { throw SocketError( L"getsockopt-error", new_error ); } } { long unsigned int tmp=0; ioctlsocket(m_socket, FIONBIO, &tmp); // Set blocking mode again for SSL. } m_ssl=SSL_new(m_ctx); #pragma warning ( push ) #pragma warning ( disable : 4244 ) SSL_set_fd(m_ssl, m_socket); #pragma warning ( pop ) int ret( SSL_connect( m_ssl ) ); long tmp; switch(tmp=SSL_get_error(m_ssl,ret)) { case SSL_ERROR_NONE: //(*mylog)<<"Connected, SSL session up.\n"; break; case SSL_ERROR_ZERO_RETURN: throw SocketError( L"SSL_connect: Connection closed cleanly, but immediately." ); case SSL_ERROR_SYSCALL: if( ret!=0 ) { throw SocketError( L"SSL_connect: Reported networking failure:", WSAGetLastError() ); } default: throw SSLSocketError( L"SSL_connect", tmp ); } } int SSLBuf::overflow(int a_ch) { // Dump characters to output. __int64 dump_len=pptr()-pbase(); if(a_ch!=EOF) ++dump_len; if( dump_len==0 ) return 0; // shortcut. char mymem[2048]; char * dump_data=mymem; memcpy(dump_data, pbase(), pptr()-pbase()); if(a_ch!=EOF) dump_data[dump_len-1]=static_cast< char >( a_ch ); int __w64 ret; for(int go=1; go; ) { #pragma warning ( push ) #pragma warning ( disable : 4244 ) ret=SSL_write(m_ssl, dump_data, dump_len); switch( long tmp=SSL_get_error( m_ssl, ret ) ) { #pragma warning ( pop ) case SSL_ERROR_NONE: dump_data+=ret; dump_len-=ret; if(dump_len <= 0) go=0; break; case SSL_ERROR_ZERO_RETURN: return(EOF); case SSL_ERROR_SYSCALL: if(0==WSAGetLastError() ) { // This because on Windows, SSLeay doesn't pick up on the zero error return from recv(). return(EOF); } else if( ret!=0 ) { throw SocketError( L"Unexpected system error during write", WSAGetLastError() ); } default: throw SSLSocketError( L"SSL_connect", tmp ); } } setp( m_outBuffer, m_outBuffer+2048 ); return 0; // Assumes that EOF!=0. Better assertion required? - DWD // So then what is the return? (I mean logically) // Return should be EOF on error, and "something else" on success. - DWD } int SSLBuf::sync() { overflow(EOF); return 0; } int SSLBuf::underflow() { sync(); const int data_len=10240; char dump_data[data_len]; int ret; ret=m_meth->ssl_pending(m_ssl); // Eeeek... Scary structure, that method one. Almost a C++ class, in some respects. if(ret==0) { fd_set fds[1]; TIMEVAL tv; FD_ZERO(fds); #pragma warning ( push ) #pragma warning ( disable : 4127 ) FD_SET(m_socket, fds); #pragma warning ( pop ) tv.tv_sec=60; tv.tv_usec=0; ret = select( /*m_socket+1*/ 0, fds, NULL, NULL, &tv ); if(ret==SOCKET_ERROR) { throw SocketError( WSAGetLastError() ); } else if(ret==0) { throw SocketError( WSAETIMEDOUT ); } } for(int retry=1; retry; ) { ret=SSL_read(m_ssl, dump_data, data_len); long tmp2; switch(tmp2=SSL_get_error(m_ssl,ret)) { case SSL_ERROR_NONE: retry=0; break; case SSL_ERROR_ZERO_RETURN: return(EOF); case SSL_ERROR_SYSCALL: if( 0 == WSAGetLastError() ) { // This because on Windows, SSLeay doesn't pick up on the zero error return from recv(). return(EOF); } else if( ret!=0 ) { throw SocketError( L"Unexpected system error during write", WSAGetLastError() ); } default: throw SSLSocketError( L"SSL_connect", tmp2 ); } } if ( ret == 0 ) { return EOF ; } if ( eback() ) { delete[] eback(); } char * tmp = new char[ret]; // Allocate enough for buffer. memcpy( tmp, dump_data, ret ); // Copy across. setg( tmp, tmp, tmp + ret ); return dump_data[ 0 ]; } /* $History: sslstream.cpp $ * * ***************** Version 14 ***************** * User: Kirit Date: 9/10/03 Time: 15:44 * Updated in $/FOST.3/F3Util * Headers re-arranged to give a proper SDK feel and to make determination * of required headers simpler. * * ***************** Version 12 ***************** * User: Kirit Date: 7/10/03 Time: 16:01 * Updated in $/FOST.3/F3Util * F3Objects DLL now compiles and registers. Not debugged. * * ***************** Version 11 ***************** * User: Kirit Date: 7/10/03 Time: 12:55 * Updated in $/FOST.3/F3Util * F3Base.DLL now works correctly. * * ***************** Version 10 ***************** * User: Kirit Date: 4/10/03 Time: 15:40 * Updated in $/FOST.3/F3Util * Internet library now added to F3Util.DLL (including use of newer * version of SSLeay). * * ***************** Version 9 ***************** * User: Kirit Date: 3/10/03 Time: 16:09 * Updated in $/FOST/Cpp/FSInternet * Added stdafx.h and stdafx.cpp pre-compiled header support. * * ***************** Version 8 ***************** * User: Kirit Date: 7/05/02 Time: 12:01 * Updated in $/FOST/Cpp/FSInternet * Wrapped std::basic_string<> in order to change the copy semantics to * stop the problems with using C++ in the COM layer for IIS. * * ***************** Version 7 ***************** * User: Kirit Date: 28/04/02 Time: 21:18 * Updated in $/FOST/Cpp/FSInternet * Added support for converting Windows Structured Exceptions to the * std::exception hierarchy. * Updated exceptions so that catch (...) is only done where necessary and * correct. * * ***************** Version 6 ***************** * User: Kirit Date: 19/03/02 Time: 20:54 * Updated in $/FOST/Cpp/FSInternet * Corrected history from last check-in. * Updated TestAX.IDL to remove registry entries from removed classes. * * ***************** Version 5 ***************** * User: Kirit Date: 19/03/02 Time: 19:48 * Updated in $/FOST/Cpp/FSInternet * Debug compiles now all end in '_d'. * The C++ implementation names (Type.cppName) have been changed to also * include the DLL that the implementation is in as per the documentation. * In all source files the following have been done: * * Ensured that every header file has a revision object. * * Ensured that every translation unit has a revision object. * * Ensured that every source file (which understands comments) * has a history section. * * Changed all copyright notices to be for Obsideon Ltd. * * Changed copyright notices to use the longest possible timeframe. * * ***************** Version 4 ***************** * User: Kirit Date: 20/02/02 Time: 0:55 * Updated in $/FOST/Cpp/FSInternet * Destructors corrected to give correct behaviour with exceptions. * * ***************** Version 3 ***************** * User: Kirit Date: 16/11/01 Time: 14:31 * Updated in $/FOST/Cpp/FSInternet * Added pragmas.hpp before all other headers to remove problems * introduced by winsock2.h. * * ***************** Version 2 ***************** * User: Kirit Date: 22/10/01 Time: 15:45 * Updated in $/FOST/FSInternet * Working HTTP protocol class. * * ***************** Version 1 ***************** * User: Kirit Date: 19/10/01 Time: 18:28 * Created in $/FOST/FSInternet */