/*++ Copyright (c) 2001 Microsoft Corporation Module Name : sslconfigpipe.cxx Abstract: SSL CONFIG PIPE implementation simple blocking pipe implementation that enables - sending/receiving commands, - sending/receiving response headers - sending/receiving arbitrary data blocks - implementing pipe listener that runs on dedicated thread - safe cleanup for thread running pipe listener Author: Jaroslav Dunajsky April-24-2001 Environment: Win32 - User Mode Project: Stream Filter Worker Process --*/ // BUGBUG - how to handle errors during pipe operations // if there is some data left over on pipe unread then all subsequent // users of the pipe will be toast. Should we just close and reopen pipe connection? // Well typically #include "precomp.hxx" //static DWORD SSL_CONFIG_PIPE::PipeListenerThread( LPVOID ThreadParam ) /*++ Routine Description: start PipeListener() method on listener thread Arguments: Return Value: HRESULT --*/ { DBG_ASSERT( ThreadParam != NULL ); HRESULT hr = E_FAIL; SSL_CONFIG_PIPE * pConfigPipe = reinterpret_cast(ThreadParam); hr = pConfigPipe->PipeListener(); if ( FAILED( hr ) ) { return WIN32_FROM_HRESULT( hr ); } return NO_ERROR; } HRESULT SSL_CONFIG_PIPE::PipeInitialize( IN const WCHAR * wszPipeName, IN BOOL fServer ) /*++ Routine Description: Create/connect named pipe Create listener thread ( if PipeListener() implemented ) Arguments: wszPipeName - pipe name to create/connect fServer - indicate server side pipe (determines whether to create or connect to pipe ) Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; HANDLE hEvent = NULL; INITIALIZE_CRITICAL_SECTION( &_csPipeLock ); // // Setup overlapped // ZeroMemory( &_OverlappedR, sizeof( _OverlappedR ) ); ZeroMemory( &_OverlappedW, sizeof( _OverlappedW ) ); // Create an event object for this instance. _OverlappedR.hEvent = CreateEvent( NULL, // no security attribute TRUE, // manual-reset event TRUE, // initial state = signaled NULL); // unnamed event object if ( _OverlappedR.hEvent == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "Failed to create Event. hr = %x\n", hr )); goto Cleanup; } // Create an event object for this instance. _OverlappedW.hEvent = CreateEvent( NULL, // no security attribute TRUE, // manual-reset event TRUE, // initial state = signaled NULL); // unnamed event object if ( _OverlappedW.hEvent == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "Failed to create Event. hr = %x\n", hr )); goto Cleanup; } if( fServer ) { // // create a named pipe // _hSslConfigPipe = CreateNamedPipe( wszPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED , PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, // number of instances 4096, 4096, 0, NULL /* pSa */ ); } else { // // Client (connect to existing pipe) // _hSslConfigPipe = CreateFile( wszPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL,//&sa, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ); } if ( _hSslConfigPipe == INVALID_HANDLE_VALUE ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "Failed to create SSL_CONFIG_PIPE pipe. hr = %x\n", wszPipeName, hr )); goto Cleanup; } if ( QueryEnablePipeListener() ) { _hPipeListenerThread = ::CreateThread( NULL, // default security descriptor 0, // default process stack size SSL_CONFIG_PIPE::PipeListenerThread, this, // thread argument - pointer to this class 0, // create running NULL // don't care for thread identifier ); if ( _hPipeListenerThread == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "Failed to create thread for SSL_CONFIG_PIPE. hr=0x%x\n", hr )); goto Cleanup; } } hr = S_OK; Cleanup: if ( FAILED( hr ) ) { PipeTerminate( fServer ); } return hr; } HRESULT SSL_CONFIG_PIPE::PipeTerminate( IN BOOL /*fServer*/ ) /*++ Routine Description: close pipe, handle proper cleanup ot the listener thread Arguments: fServer - not used Return Value: HRESULT --*/ { DWORD dwRet = 0; SetPipeIsTerminating(); // // _SSLConfigucationPipe is created before // _hSslConfigurationPipeHandlingThread is created // and based on typical cleanup logic it would be expected // that will be closed closed after thread completed // However, we have to close _SSLConfigucationPipe beforehand // because that will actually trigger thread to complete // if ( _hSslConfigPipe != INVALID_HANDLE_VALUE ) { CloseHandle( _hSslConfigPipe ); _hSslConfigPipe = INVALID_HANDLE_VALUE; } if ( _hPipeListenerThread != NULL ) { // // Wait till worker thread has completed // dwRet = WaitForSingleObject( _hPipeListenerThread, INFINITE ); DBG_ASSERT( dwRet == WAIT_OBJECT_0 ); CloseHandle( _hPipeListenerThread ); _hPipeListenerThread = NULL; } // // Cleanup overlapped // if ( _OverlappedR.hEvent == NULL ) { CloseHandle( _OverlappedR.hEvent ); _OverlappedR.hEvent = NULL; } if ( _OverlappedW.hEvent == NULL ) { CloseHandle( _OverlappedW.hEvent ); _OverlappedW.hEvent = NULL; } DeleteCriticalSection( &_csPipeLock ); return S_OK; } VOID SSL_CONFIG_PIPE::PipeLock( VOID ) /*++ Routine Description: Lock named pipe to guarantee exclusive access Arguments: Return Value: VOID --*/ { EnterCriticalSection( &_csPipeLock ); } VOID SSL_CONFIG_PIPE::PipeUnlock( VOID ) /*++ Routine Description: Unlock named pipe Arguments: Return Value: VOID --*/ { LeaveCriticalSection( &_csPipeLock ); } HRESULT SSL_CONFIG_PIPE::PipeConnectServer( VOID ) /*++ Routine Description: Connect pipe on the server side Call is blocking until pipe connected Arguments: Return Value: VOID --*/ { BOOL fRet = FALSE; DWORD cbBytes = 0; HRESULT hr = E_FAIL; // Start an overlapped connection for this pipe instance. fRet = ConnectNamedPipe( _hSslConfigPipe, &_OverlappedR ); // Overlapped ConnectNamedPipe should return zero. if ( fRet ) { return S_OK; } hr = HRESULT_FROM_WIN32( GetLastError() ); hr = PipeWaitForCompletion( hr, &_OverlappedR, &cbBytes ); if ( FAILED( hr ) && !QueryPipeIsTerminating() ) { DBGPRINTF(( DBG_CONTEXT, "Failed on ConnectNamedPipe(). hr = %x\n", hr )); } return hr; } HRESULT SSL_CONFIG_PIPE::PipeDisconnectServer( VOID ) /*++ Routine Description: Disconnect server side pipe Arguments: Return Value: VOID --*/ { if( ! DisconnectNamedPipe( _hSslConfigPipe ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } return S_OK; } HRESULT SSL_CONFIG_PIPE::PipeSendData( IN DWORD cbNumberOfBytesToWrite, IN BYTE * pbBuffer ) /*++ Routine Description: Send specified number of bytes from named pipe Arguments: cbNumberOfBytesToWrite - bytes to write pbBuffer - data Return Value: HRESULT --*/ { DWORD cbNumberOfBytesWritten; BOOL fRet = FALSE; HRESULT hr = E_FAIL; fRet = WriteFile ( _hSslConfigPipe, pbBuffer, cbNumberOfBytesToWrite, &cbNumberOfBytesWritten, &_OverlappedW ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); hr = PipeWaitForCompletion( hr, &_OverlappedW, &cbNumberOfBytesWritten ); if ( FAILED( hr ) ) { if ( !QueryPipeIsTerminating() && hr != HRESULT_FROM_WIN32( ERROR_PIPE_LISTENING ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to send response over named pipe SSL_CONFIG_PIPE. hr = %x\n", hr )); } return hr; } } if ( cbNumberOfBytesToWrite != cbNumberOfBytesWritten ) { // // BUGBUG: better error code // hr = E_FAIL; DBGPRINTF(( DBG_CONTEXT, "Failed to send response over named pipe SSL_CONFIG_PIPE. hr = %x\n", hr )); return hr; } IF_DEBUG( TRACE ) { DBGPRINTF(( DBG_CONTEXT, "Sent %d bytes\n", cbNumberOfBytesWritten )); } return S_OK; } HRESULT SSL_CONFIG_PIPE::PipeReceiveData( IN DWORD cbBytesToRead, OUT BYTE * pbBuffer ) /*++ Routine Description: Receive specified number of bytes from named pipe Arguments: cbNumberOfBytesToRead - number of bytes to read - function will not return success unless specified number of bytes was read pbBuffer - allocated by caller Return Value: HRESULT --*/ { DWORD cbNumberOfBytesRead = 0; DWORD cbTotalNumberOfBytesRead = 0; BOOL fRet = FALSE; HRESULT hr = E_FAIL; DBG_ASSERT ( cbBytesToRead != 0 ); do { fRet = ReadFile( _hSslConfigPipe, pbBuffer, cbBytesToRead - cbTotalNumberOfBytesRead, &cbNumberOfBytesRead, &_OverlappedR ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); hr = PipeWaitForCompletion( hr, &_OverlappedR, &cbNumberOfBytesRead ); if ( FAILED( hr ) ) { if ( !QueryPipeIsTerminating() && ( hr != HRESULT_FROM_WIN32( ERROR_BROKEN_PIPE ) ) ) { // // do not dump broken pipe errors // DBGPRINTF(( DBG_CONTEXT, "Failed to receive request over named pipe SSL_INFO_PROV. hr = %x\n", hr )); } return hr; } } if ( cbNumberOfBytesRead == 0 ) { hr = HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); DBGPRINTF(( DBG_CONTEXT, "Failed to receive request over named pipe SSL_INFO_PROV - end of pipe. hr = %x\n", hr )); return hr; } cbTotalNumberOfBytesRead += cbNumberOfBytesRead; } while ( cbTotalNumberOfBytesRead != cbBytesToRead ); IF_DEBUG( TRACE ) { DBGPRINTF(( DBG_CONTEXT, "PipeReceiveData: %d bytes\n", cbTotalNumberOfBytesRead )); } return S_OK; } HRESULT SSL_CONFIG_PIPE::PipeWaitForCompletion( IN HRESULT hrLastError, IN OVERLAPPED * pOverlapped, OUT DWORD * pcbTransferred ) /*++ Routine Description: Wait for completion of nonblocking operation used for CreateNamedPipe, ReadFile and WriteFile Note: To outside world this pipe implementation is blocking but internally we use OVERLAPPED and that wait for completion. That way it is possible to terminate pipe by closing handle Arguments: Return Value: VOID --*/ { BOOL fRet = FALSE; DWORD dwRet = 0; HRESULT hr = E_FAIL; switch ( hrLastError ) { case HRESULT_FROM_WIN32( ERROR_IO_PENDING ): // // The overlapped connection in progress. // wait for event to be signalled // dwRet = WaitForSingleObject( pOverlapped->hEvent, INFINITE ); DBG_ASSERT( dwRet == WAIT_OBJECT_0 ); fRet = GetOverlappedResult( _hSslConfigPipe, // handle to pipe pOverlapped, // OVERLAPPED structure pcbTransferred, // bytes transferred FALSE ); // do not wait if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); return hr; } return S_OK; case HRESULT_FROM_WIN32( ERROR_PIPE_CONNECTED ): // Client is already connected return S_OK; default: // If an error occurs during the operation... hr = HRESULT_FROM_WIN32( GetLastError() ); return hr; } } HRESULT SSL_CONFIG_PIPE::PipeReceiveCommand( OUT SSL_CONFIG_PIPE_COMMAND * pCommand ) /*++ Routine Description: Receive command to execute Arguments: pCommand Return Value: HRESULT --*/ { return PipeReceiveData( sizeof(SSL_CONFIG_PIPE_COMMAND), reinterpret_cast(pCommand) ); } HRESULT SSL_CONFIG_PIPE::PipeReceiveResponseHeader( OUT SSL_CONFIG_PIPE_RESPONSE_HEADER * pResponseHeader ) /*++ Routine Description: after command was sent over named pipe, use PipeReceiveResponseHeader to retrieve initial header of the response (it contains all the relevant information to complete reading the whole response) Arguments: ResponseHeader Return Value: HRESULT --*/ { return PipeReceiveData( sizeof(SSL_CONFIG_PIPE_RESPONSE_HEADER), reinterpret_cast(pResponseHeader) ); } HRESULT SSL_CONFIG_PIPE::PipeSendCommand( OUT SSL_CONFIG_PIPE_COMMAND * pCommand ) /*++ Routine Description: Send command to execute Arguments: pCommand Return Value: HRESULT --*/ { return PipeSendData( sizeof(SSL_CONFIG_PIPE_COMMAND), reinterpret_cast(pCommand) ); } HRESULT SSL_CONFIG_PIPE::PipeSendResponseHeader( OUT SSL_CONFIG_PIPE_RESPONSE_HEADER * pResponseHeader ) /*++ Routine Description: send response header Arguments: pResponseHeader Return Value: HRESULT --*/ { return PipeSendData( sizeof(SSL_CONFIG_PIPE_RESPONSE_HEADER), reinterpret_cast(pResponseHeader) ); }