/*++ Copyright (c) 1998 Microsoft Corporation Module Name : siteconfig.cxx Abstract: SSL configuration for a given site Author: Bilal Alam (BAlam) 29-March-2000 Environment: Win32 - User Mode Project: Stream Filter Worker Process --*/ #include "precomp.hxx" ENDPOINT_CONFIG_HASH * ENDPOINT_CONFIG::sm_pEndpointConfigHash; ENDPOINT_CONFIG::INIT_STATE ENDPOINT_CONFIG::sm_InitState = INIT_NONE; HANDLE ENDPOINT_CONFIG::sm_hHttpApiConfigChangeEvent = NULL; HKEY ENDPOINT_CONFIG::sm_hHttpApiConfigKey = NULL; HANDLE ENDPOINT_CONFIG::sm_hWaitHandle = NULL; //static HRESULT ENDPOINT_CONFIG::Initialize( VOID ) /*++ Routine Description: Initialize site configuration globals Arguments: None Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; DWORD dwError = NO_ERROR; HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_1; // // Initialize HttpApi to be able to retrieve SSL config // dwError = HttpInitialize( HttpApiVersion, HTTP_INITIALIZE_CONFIG, NULL ); if ( dwError != NO_ERROR ) { hr = HRESULT_FROM_WIN32( dwError ); goto Failed; } sm_InitState = INIT_HTTPAPI; sm_pEndpointConfigHash = new ENDPOINT_CONFIG_HASH(); if ( sm_pEndpointConfigHash == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); goto Failed; } sm_InitState = INIT_HASH; // // Create event for HttpApi config store change notifications // sm_hHttpApiConfigChangeEvent = CreateEvent( NULL, // SD FALSE, // fManualReset FALSE, //fInitialState NULL ); // name*/ if( sm_hHttpApiConfigChangeEvent == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "CreateEvent() failed. hr = 0x%x\n", hr )); goto Failed; } dwError = RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\HTTP\\Parameters\\SslBindingInfo", 0, KEY_READ, &sm_hHttpApiConfigKey ); if ( dwError != ERROR_SUCCESS ) { // backup plan // if SslBindingInfo doesn't extst yet, simply listen on // parent node. HTTP\Parameters is guaranteed to exist on valid install // dwError = RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\HTTP\\Parameters", 0, KEY_READ, &sm_hHttpApiConfigKey ); if ( dwError != ERROR_SUCCESS ) { hr = HRESULT_FROM_WIN32( dwError ); DBGPRINTF(( DBG_CONTEXT, "Failed to open registry. hr = 0x%x\n", hr )); CloseHandle( sm_hHttpApiConfigChangeEvent ); sm_hHttpApiConfigChangeEvent = NULL; goto Failed; } } DBG_ASSERT( sm_hHttpApiConfigKey != NULL ); // // Watch the registry key for a change // dwError = RegNotifyChangeKeyValue( sm_hHttpApiConfigKey, TRUE, REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME, sm_hHttpApiConfigChangeEvent, TRUE ); if( dwError != ERROR_SUCCESS ) { hr = HRESULT_FROM_WIN32( dwError ); RegCloseKey( sm_hHttpApiConfigKey ); sm_hHttpApiConfigKey = NULL; CloseHandle( sm_hHttpApiConfigChangeEvent ); sm_hHttpApiConfigChangeEvent = NULL; DBGPRINTF(( DBG_CONTEXT, "RegNotifyChangeKeyValue failed. hr = 0x%x\n", hr )); goto Failed; } // // Register a callback function to wait on the event // if( !RegisterWaitForSingleObject( &sm_hWaitHandle, sm_hHttpApiConfigChangeEvent, ( WAITORTIMERCALLBACK )ENDPOINT_CONFIG::ConfigStoreChangeCallback, NULL, INFINITE, WT_EXECUTEDEFAULT ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); RegCloseKey( sm_hHttpApiConfigKey ); sm_hHttpApiConfigKey = NULL; CloseHandle( sm_hHttpApiConfigChangeEvent ); sm_hHttpApiConfigChangeEvent = NULL; DBGPRINTF(( DBG_CONTEXT, "RegisterWaitForSingleObject failed. hr = 0x%x\n", hr )); goto Failed; } sm_InitState = INIT_CHANGE_NOTIF; return NO_ERROR; Failed: Terminate(); return hr; } //static VOID ENDPOINT_CONFIG::Terminate( VOID ) /*++ Routine Description: Cleanup site configuration globals Arguments: None Return Value: None --*/ { switch( sm_InitState ) { case INIT_CHANGE_NOTIF: if ( sm_hWaitHandle != NULL ) { UnregisterWaitEx( sm_hWaitHandle, INVALID_HANDLE_VALUE ); sm_hWaitHandle = NULL; } // // NOTE: // registry key must be closed before event is destroyed // because closing handle will assure that sm_hHttpApiConfigChangeEvent event // will not be used any more // if ( sm_hHttpApiConfigKey != NULL ) { CloseHandle( sm_hHttpApiConfigKey ); sm_hHttpApiConfigKey = NULL; } if ( sm_hHttpApiConfigChangeEvent != NULL ) { CloseHandle( sm_hHttpApiConfigChangeEvent ); sm_hHttpApiConfigChangeEvent = NULL; } case INIT_HASH: if ( sm_pEndpointConfigHash != NULL ) { // // Clear hash table before deleting it // sm_pEndpointConfigHash->Clear(); delete sm_pEndpointConfigHash; sm_pEndpointConfigHash = NULL; } case INIT_HTTPAPI: HttpTerminate( HTTP_INITIALIZE_CONFIG, NULL ); case INIT_NONE: break; } } //private //static HRESULT ENDPOINT_CONFIG::GetEndpointConfigData( IN DWORD LocalAddress, IN USHORT LocalPort, OUT PHTTP_SERVICE_CONFIG_SSL_SET * ppEndpointConfigData, OUT BOOL * pfWildcardMatch ) /*++ Routine Description: lookup SSL configuration Arguments: LocalAddress - IP address in network order LocalPort - Port in network order ppEndpointConfigData - allocated within this function - delete[] must be used to free it pfWildcardMatch - was wildcard IP address match used? ) Return Value: HRESULT --*/ { DWORD dwError; DWORD ReturnLength = 0; HTTP_SERVICE_CONFIG_SSL_QUERY QueryParam; PHTTP_SERVICE_CONFIG_SSL_SET pSetParam = NULL; HRESULT hr = NO_ERROR; SOCKADDR_IN SockAddr; if ( ppEndpointConfigData == NULL || pfWildcardMatch == NULL ) { return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } ZeroMemory( &SockAddr, sizeof(SockAddr) ); ZeroMemory( &QueryParam, sizeof(QueryParam) ); *pfWildcardMatch = FALSE; QueryParam.QueryDesc = HttpServiceConfigQueryExact; // // build sock addr for HTTPAPI lookup // SockAddr.sin_family = AF_INET; SockAddr.sin_port = LocalPort; SockAddr.sin_addr.s_addr = LocalAddress; QueryParam.KeyDesc.pIpPort = (PSOCKADDR) &SockAddr; // Loop - check nonwildcard info first, if it fails then try wildcard // for(;;) { dwError = HttpQueryServiceConfiguration( NULL, HttpServiceConfigSSLCertInfo, &QueryParam, sizeof(QueryParam), NULL, 0, &ReturnLength, NULL ); if ( dwError == ERROR_INSUFFICIENT_BUFFER ) { pSetParam = (PHTTP_SERVICE_CONFIG_SSL_SET) new BYTE[ReturnLength]; if ( pSetParam == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY ); goto Finished; } dwError = HttpQueryServiceConfiguration( NULL, HttpServiceConfigSSLCertInfo, &QueryParam, sizeof(QueryParam), pSetParam, ReturnLength, &ReturnLength, NULL ); // // It is possible that the size returned on the first call may // have changed between first and second call. // It would be caused by configuration change. // Probability of it happening is very low. // If it happens then one connection attempt will fail and it is // acceptable // } // If not found (and other error will mean end of trying) // or if wildcard lookup already executed // then leave the loop // if ( dwError != ERROR_FILE_NOT_FOUND || SockAddr.sin_addr.s_addr == INADDR_ANY ) { break; } else { // // continue loop for wildcard lookup // SockAddr.sin_addr.s_addr = INADDR_ANY; } } if ( dwError != NO_ERROR ) { hr = HRESULT_FROM_WIN32( dwError ); goto Finished; } if ( SockAddr.sin_addr.s_addr == INADDR_ANY ) { *pfWildcardMatch = TRUE; } *ppEndpointConfigData = pSetParam; hr = S_OK; Finished: return hr; } //static HRESULT ENDPOINT_CONFIG::GetEndpointConfig( CONNECTION_INFO * pConnectionInfo, ENDPOINT_CONFIG ** ppEndpointConfig, BOOL fCreateEmptyIfNotFound ) /*++ Routine Description: Lookup site configuration in hash table. If not there then create it and add it to table Arguments: dwSiteId - Site ID to lookup ppEndpointConfig - Filled with pointer to site config on success fCreateEmptyIfNotFound - if endpoint config was not located in the persistent store, create empty endpoint config and stick it to cache That will optimize lookup for EnableRawFilter checks Return Value: HRESULT HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) when endpoint config not found --*/ { LK_RETCODE lkrc; ENDPOINT_CONFIG * pEndpointConfig = NULL; SERVER_CERT * pServerCert = NULL; IIS_CTL * pIisCtl = NULL; HRESULT hr = NO_ERROR; STACK_STRU( strMBPath, 64 ); DWORD LocalAddress = 0; USHORT LocalPort = 0; BOOL fWildcardMatch = FALSE; PHTTP_SERVICE_CONFIG_SSL_SET pEndpointConfigData = NULL; if ( ppEndpointConfig == NULL || pConnectionInfo == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *ppEndpointConfig = NULL; // // Figure out Ip Address and Port // // // Pass Port and IP address in network order // (HTTP service will return them in network order so no changes are needed) // if( pConnectionInfo->LocalAddressType == TDI_ADDRESS_TYPE_IP ) { LocalAddress = pConnectionInfo->SockLocalAddress.ipv4SockAddress.sin_addr.s_addr; LocalPort = pConnectionInfo->SockLocalAddress.ipv4SockAddress.sin_port; } else if ( pConnectionInfo->LocalAddressType == TDI_ADDRESS_TYPE_IP6 ) { // // IPv6 connections will be able to handle SSL connections only // in the case when all IP addresses on the machine are configured to // listen on secure port (IPv4 wildcard binding must be configured). // CODEWORK: Once in the future there may be a need to implement // for IPv6 support. // LocalAddress = INADDR_ANY; // Wildcard IP LocalPort = pConnectionInfo->SockLocalAddress.ipv6SockAddress.sin6_port; } else { // // We support only IPv4 and IPv6. Any other value means error // DBG_ASSERT( FALSE ); hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto ExitPoint; } // // First lookup in the cache // Because we don't preread all the endpoint configuration stored in HttpAPI Config // store in out hash table, it is not straightforward to handle wildcard endpoint // configuration // If endpoint configuration lookup returned data based on wildcard endpoint config // we will still store it in the hash table for the specific endpoint we looked up // Otherwise there is is risk that wildcard endpoint config stored in the hash table // could override individual endpoint settings that exist in the HttpAPI config store // but were not yet read and stored in our hash table // DBG_ASSERT( sm_pEndpointConfigHash != NULL ); ENDPOINT_KEY EndpointKey = GenerateEndpointKey( LocalAddress, LocalPort ); lkrc = sm_pEndpointConfigHash->FindKey( &EndpointKey, &pEndpointConfig ); if ( lkrc == LK_SUCCESS ) { DBG_ASSERT( pEndpointConfig != NULL ); *ppEndpointConfig = pEndpointConfig; hr = S_OK; goto ExitPoint; } // // Ok, nothing cached. We will have to make a lookup in the persistent store // // // Try to lookup config info through HTTPAPI // fWildcardMatch is currently ignored we will store retrieved config data // for the specific endpoint // hr = GetEndpointConfigData( LocalAddress, LocalPort, &pEndpointConfigData, &fWildcardMatch ); if ( hr == HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) && fCreateEmptyIfNotFound ) { // // Create empty endpoint info // This will optimize the EnableRawFilter lookups // because without cached entry for each new raw Filter connection // we would need to make HttpApi config lookup // (empty endpoint indicates that it is OK to do Raw Filter handling) // pEndpointConfigData = NULL; pEndpointConfig = new ENDPOINT_CONFIG( LocalAddress, LocalPort, NULL, // no Server Cert info NULL, // No Ctl info pEndpointConfigData ); if ( pEndpointConfig == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); goto ExitPoint; } else { // ENDPOINT_CONFIG took ownership of pEndpointConfigData pEndpointConfigData = NULL; } } else if ( FAILED( hr ) ) { // // Not finding the site just means this is not an SSL site // goto ExitPoint; } else if ( pEndpointConfigData != NULL && pEndpointConfigData->ParamDesc.SslHashLength == 0 ) { // // Store endpoint config for nonsecure Endpoint // to flag if Raw Filters are enabled or disabled // pEndpointConfig = new ENDPOINT_CONFIG( LocalAddress, LocalPort, NULL, NULL, pEndpointConfigData ); if ( pEndpointConfig == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); goto ExitPoint; } else { // ENDPOINT_CONFIG took ownership of pEndpointConfigData pEndpointConfigData = NULL; } } else { // // To get this far means that we expect correct SSL endpoint info // to be available // // // We have enough to lookup in SERVER_CERT cache // hr = SERVER_CERT::GetServerCertificate( (PBYTE) pEndpointConfigData->ParamDesc.pSslHash, pEndpointConfigData->ParamDesc.SslHashLength, pEndpointConfigData->ParamDesc.pSslCertStoreName, &pServerCert ); if ( FAILED( hr ) ) { // // If we couldn't get a server cert, SSL will // not be enablable for this site // goto ExitPoint; } DBG_ASSERT( pServerCert != NULL ); hr = IIS_CTL::GetIisCtl( pEndpointConfigData->ParamDesc.pDefaultSslCtlIdentifier, pEndpointConfigData->ParamDesc.pDefaultSslCtlStoreName, &pIisCtl ); // // if CTL context creation failed, we must not fail site config creation // because CTLs are not mandatory for all scenarios (only client certificate // scenarios require them, so we have to make sure that those would be failing ) // if ( FAILED( hr ) ) { pIisCtl = NULL; // // BUGBUG: Error loading CTL should be logged into event log // hr = S_OK; } // // OK. Create the config and attempt to add it to the cache // pEndpointConfig = new ENDPOINT_CONFIG( ( LocalAddress ), LocalPort, pServerCert, pIisCtl, pEndpointConfigData ); if ( pEndpointConfig == NULL ) { if ( pServerCert != NULL ) { pServerCert->DereferenceServerCert(); pServerCert = NULL; } if ( pIisCtl != NULL ) { pIisCtl->DereferenceIisCtl(); pIisCtl = NULL; } hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); goto ExitPoint; } else { // ENDPOINT_CONFIG took ownership of pEndpointConfigData pEndpointConfigData = NULL; } // // Acquire credentials // hr = pEndpointConfig->AcquireCredentials(); if ( FAILED( hr ) ) { delete pEndpointConfig; pEndpointConfig = NULL; goto ExitPoint; } } // // We don't care what the success of the insertion was. If it failed, // then the pEndpointConfig will not be extra referenced and the caller // will clean it up when it derefs // sm_pEndpointConfigHash->InsertRecord( pEndpointConfig ); *ppEndpointConfig = pEndpointConfig; hr = S_OK; ExitPoint: if ( pEndpointConfigData != NULL ) { delete [] pEndpointConfigData; pEndpointConfigData = NULL; } return hr; } //static LK_PREDICATE ENDPOINT_CONFIG::ServerCertPredicate( ENDPOINT_CONFIG * pEndpointConfig, void * pvState ) /*++ Description: DeleteIf() predicate used to find items which reference the SERVER_CERT pointed to by pvState Arguments: pEndpointConfig - SSL endpoint config pvState - SERVER_CERT to check for Returns: LK_PREDICATE - LKP_PERFORM indicates removing the current token from token cache LKP_NO_ACTION indicates doing nothing. --*/ { LK_PREDICATE lkpAction; SERVER_CERT * pServerCert; DBG_ASSERT( pEndpointConfig != NULL ); pServerCert = (SERVER_CERT*) pvState; DBG_ASSERT( pServerCert != NULL ); DBG_ASSERT( pServerCert->CheckSignature() ); if ( pEndpointConfig->QueryServerCert() == pServerCert ) { lkpAction = LKP_PERFORM; } else { lkpAction = LKP_NO_ACTION; } return lkpAction; } //static HRESULT ENDPOINT_CONFIG::FlushByServerCert( SERVER_CERT * pServerCert ) /*++ Routine Description: Flush the ENDPOINT_CONFIG cache of anything referecing the given server certificate Arguments: pServerCert - Server certificate to reference Return Value: HRESULT --*/ { DBG_ASSERT( sm_pEndpointConfigHash != NULL ); sm_pEndpointConfigHash->DeleteIf( ENDPOINT_CONFIG::ServerCertPredicate, pServerCert ); return NO_ERROR; } //static LK_PREDICATE ENDPOINT_CONFIG::IisCtlPredicate( ENDPOINT_CONFIG * pEndpointConfig, void * pvState ) /*++ Description: DeleteIf() predicate used to find items which reference the SERVER_CERT pointed to by pvState Arguments: pEndpointConfig - SSL endpoint config pvState - IIS_CTL to check for Returns: LK_PREDICATE - LKP_PERFORM indicates removing the current token from token cache LKP_NO_ACTION indicates doing nothing. --*/ { LK_PREDICATE lkpAction; IIS_CTL * pIisCtl; DBG_ASSERT( pEndpointConfig != NULL ); pIisCtl = (IIS_CTL*) pvState; DBG_ASSERT( pIisCtl != NULL ); DBG_ASSERT( pIisCtl->CheckSignature() ); if ( pEndpointConfig->QueryIisCtl() == pIisCtl ) { lkpAction = LKP_PERFORM; } else { lkpAction = LKP_NO_ACTION; } return lkpAction; } //static HRESULT ENDPOINT_CONFIG::FlushByIisCtl( IIS_CTL * pIisCtl ) /*++ Routine Description: Flush the ENDPOINT_CONFIG cache of anything referecing the given CTL Arguments: pIisCtl - Return Value: HRESULT --*/ { DBG_ASSERT( sm_pEndpointConfigHash != NULL ); sm_pEndpointConfigHash->DeleteIf( ENDPOINT_CONFIG::IisCtlPredicate, pIisCtl ); return NO_ERROR; } //static LK_PREDICATE ENDPOINT_CONFIG::SiteIdPredicate( ENDPOINT_CONFIG * /*pEndpointConfig*/, void * pvState ) /*++ Description: DeleteIf() predicate to delete config specified by site id (pvState) Arguments: pEndpointConfig - SSL Endpoint config pvState - Site ID Returns: LK_PREDICATE - LKP_PERFORM indicates removing the current token from token cache LKP_NO_ACTION indicates doing nothing. --*/ { LK_PREDICATE lkpAction; DWORD dwSiteId; dwSiteId = PtrToUlong(pvState); // // Site Id is legacy value - all endpoints will be deleted // upon change because we don't internally track site ID any more // CODEWORK: Site concept should be completely removed from the code // Remove before RC1 lkpAction = LKP_PERFORM; return lkpAction; } //static LK_PREDICATE ENDPOINT_CONFIG::EndpointPredicate( ENDPOINT_CONFIG * pEndpointConfig, void * pvState ) /*++ Description: DeleteIf() predicate to delete config specified by site id (pvState) Arguments: pEndpointConfig - SSL Endpoint config pvState - Endpoint key Returns: LK_PREDICATE - LKP_PERFORM indicates removing the current token from token cache LKP_NO_ACTION indicates doing nothing. --*/ { LK_PREDICATE lkpAction; ENDPOINT_KEY * pEndpointKey; DBG_ASSERT( pEndpointConfig != NULL ); pEndpointKey = (ENDPOINT_KEY *) (pvState); if ( *( pEndpointConfig->QueryEndpointKey() ) == *pEndpointKey || *pEndpointKey == GenerateEndpointKey( INADDR_ANY, 0 ) ) { // 0 means to delete all the endpoint info lkpAction = LKP_PERFORM; } else { lkpAction = LKP_NO_ACTION; } return lkpAction; } //static HRESULT ENDPOINT_CONFIG::FlushByEndpoint( DWORD LocalAddress, USHORT LocalPort ) /*++ Routine Description: Flush specified site configuration. If dwSiteId is 0, then flush all Arguments: LocalAddress - IP Address LocalPort - Port if LocalAddress=INADDR_ANY and LocalPort=0 then all endpoints will be flushed Return Value: HRESULT --*/ { DBG_ASSERT( sm_pEndpointConfigHash != NULL ); ENDPOINT_KEY EndpointKey = GenerateEndpointKey( LocalAddress, LocalPort ); sm_pEndpointConfigHash->DeleteIf( ENDPOINT_CONFIG::EndpointPredicate, (PVOID) &EndpointKey ); return NO_ERROR; } ENDPOINT_CONFIG::~ENDPOINT_CONFIG() { if ( _pServerCert != NULL ) { _pServerCert->DereferenceServerCert(); _pServerCert = NULL; } if ( _pIisCtl != NULL ) { _pIisCtl->DereferenceIisCtl(); _pIisCtl = NULL; } if ( _pEndpointConfigData != NULL ) { delete [] _pEndpointConfigData; _pEndpointConfigData = NULL; } _dwSignature = ENDPOINT_CONFIG_SIGNATURE_FREE; } HRESULT ENDPOINT_CONFIG::AcquireCredentials( VOID ) /*++ Routine Description: Do the Schannel thing to get credentials handle representing the server cert/mapping configuration of this site Arguments: None Return Value: HRESULT --*/ { DBG_ASSERT( _pServerCert != NULL ); return _SiteCreds.AcquireCredentials( _pServerCert, QueryUseDSMapper() ); } // static VOID WINAPI ENDPOINT_CONFIG::ConfigStoreChangeCallback( PVOID /*pParam*/, BOOL /*fWaitFired*/ ) /*++ Routine Description: Callback when HttpApi config store change occurs Note: Currently we listen directly on registry changes CODEWORK: once httpapi provides change notification mechanism we have to use that one Arguments: not used Return Value: --*/ { // // If we fail to re-register for change notifications // we will ignore it. This will unfortunately stop all // the future change notifications // CODEWORK: consider if there is anything else we can do // RegNotifyChangeKeyValue( sm_hHttpApiConfigKey, TRUE, REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME, sm_hHttpApiConfigChangeEvent, TRUE ); // // There is no straightforward mechanism to figure out individual change // that happened. That's why we will flush all endpoints config // ( Changes in the endpoint config are not frequent so this should not be // a performance problem at all ) // FlushByEndpoint( INADDR_ANY, 0 ); } //static VOID ENDPOINT_CONFIG::Cleanup( VOID ) /*++ Routine Description: Cleanup must be called before Terminate Arguments: none Return Value: VOID --*/ { sm_pEndpointConfigHash->Clear(); }