/*++ Copyright (c) 2001 Microsoft Corporation Module Name: config.cpp Abstract: This file implements metabase access routines for virtual directories. --*/ #include "precomp.h" VDirConfig::VDirConfig( StringHandle Path, IMSAdminBase *AdminBase ) : m_Refs(1) { // // Read in all the metabase configuration for the virtual directory. // HRESULT Hr; METADATA_HANDLE MdVDirKey = NULL; GetSystemTimeAsFileTime( &m_LastLookup ); try { m_Path = Path; StringHandleW UnicodePath = Path; Hr = AdminBase->OpenKey( METADATA_MASTER_ROOT_HANDLE, (const WCHAR*)UnicodePath, METADATA_PERMISSION_READ, 30000, &MdVDirKey ); if ( FAILED(Hr) ) throw ServerException( Hr ); m_PhysicalPath = GetMetaDataString( AdminBase, MdVDirKey, NULL, MD_VR_PATH, "" ); DWORD UploadEnabled = GetMetaDataDWORD( AdminBase, MdVDirKey, NULL, g_PropertyMan->GetPropertyMetabaseID( MD_BITS_UPLOAD_ENABLED ), 0); m_UploadEnabled = UploadEnabled ? true : false; m_ConnectionsDir = GetMetaDataString( AdminBase, MdVDirKey, NULL, g_PropertyMan->GetPropertyMetabaseID( MD_BITS_CONNECTION_DIR ), MD_DEFAULT_BITS_CONNECTION_DIRA ); m_NoProgressTimeout = GetMetaDataDWORD( AdminBase, MdVDirKey, NULL, g_PropertyMan->GetPropertyMetabaseID( MD_BITS_NO_PROGRESS_TIMEOUT ), MD_DEFAULT_NO_PROGESS_TIMEOUT ); StringHandle MaxFilesizeString = GetMetaDataString( AdminBase, MdVDirKey, NULL, g_PropertyMan->GetPropertyMetabaseID( MD_BITS_MAX_FILESIZE ), MD_DEFAULT_BITS_MAX_FILESIZEA ); if ( MaxFilesizeString.Size() == 0 ) { m_MaxFileSize = 0xFFFFFFFFFFFFFFFF; } else { UINT64 MaxFileSize; int ScanRet = sscanf( (const char*)MaxFilesizeString, "%I64u", &MaxFileSize ); if ( 1 != ScanRet ) throw ServerException( E_INVALIDARG ); m_MaxFileSize = MaxFileSize; } DWORD NotificationType = GetMetaDataDWORD( AdminBase, MdVDirKey, NULL, g_PropertyMan->GetPropertyMetabaseID( MD_BITS_NOTIFICATION_URL_TYPE ), MD_DEFAULT_BITS_NOTIFICATION_URL_TYPE ); if ( NotificationType > BITS_NOTIFICATION_TYPE_MAX ) throw ServerException( E_INVALIDARG ); m_NotificationType = (BITS_SERVER_NOTIFICATION_TYPE)NotificationType; m_NotificationURL = GetMetaDataString( AdminBase, MdVDirKey, NULL, g_PropertyMan->GetPropertyMetabaseID( MD_BITS_NOTIFICATION_URL ), MD_DEFAULT_BITS_NOTIFICATION_URLA ); m_HostId = GetMetaDataString( AdminBase, MdVDirKey, NULL, g_PropertyMan->GetPropertyMetabaseID( MD_BITS_HOSTID ), MD_DEFAULT_BITS_HOSTIDA ); m_HostIdFallbackTimeout = GetMetaDataDWORD( AdminBase, MdVDirKey, NULL, g_PropertyMan->GetPropertyMetabaseID( MD_BITS_HOSTID_FALLBACK_TIMEOUT ), MD_DEFAULT_HOSTID_FALLBACK_TIMEOUT ); m_ExecutePermissions = GetMetaDataDWORD( AdminBase, MdVDirKey, NULL, MD_ACCESS_PERM, MD_ACCESS_READ ); AdminBase->CloseKey( MdVDirKey ); } catch( ServerException Exception ) { if ( MdVDirKey ) AdminBase->CloseKey( MdVDirKey ); throw; } } ConfigurationManager::ConfigurationManager() { m_IISAdminBase = NULL; bool CSInitialize = false; memset( m_PathCacheEntries, 0, sizeof( m_PathCacheEntries ) ); memset( m_MapCacheEntries, 0, sizeof( m_MapCacheEntries ) ); HRESULT Hr = CoInitializeEx( NULL, COINIT_MULTITHREADED ); if ( FAILED(Hr) ) throw ServerException( Hr ); try { if ( !InitializeCriticalSectionAndSpinCount( &m_CacheCS, 0x80000100 ) ) throw ServerException( HRESULT_FROM_WIN32( GetLastError() ) ); CSInitialize = true; Hr = CoCreateInstance( GETAdminBaseCLSID(TRUE), NULL, CLSCTX_SERVER, __uuidof( IMSAdminBase ), (LPVOID*)&m_IISAdminBase ); if ( FAILED(Hr) ) throw ServerException( Hr ); Hr = m_IISAdminBase->GetSystemChangeNumber( &m_ChangeNumber ); if ( FAILED(Hr)) throw ServerException( Hr ); CoUninitialize(); } catch( ServerException ) { if ( m_IISAdminBase ) m_IISAdminBase->Release(); if ( CSInitialize ) DeleteCriticalSection( &m_CacheCS ); CoUninitialize(); throw; } } ConfigurationManager::~ConfigurationManager() { FlushCache(); DeleteCriticalSection( &m_CacheCS ); if ( m_IISAdminBase ) m_IISAdminBase->Release(); } void ConfigurationManager::FlushCache() { for( unsigned int i = 0; i < PATH_CACHE_ENTRIES; i++ ) { if ( m_PathCacheEntries[i] ) { m_PathCacheEntries[i]->Release(); m_PathCacheEntries[i] = NULL; } } for( unsigned int i = 0; i < MAP_CACHE_ENTRIES; i++ ) { delete m_MapCacheEntries[i]; m_MapCacheEntries[i] = NULL; } } VDirConfig* ConfigurationManager::Lookup( StringHandle Path ) { VDirConfig* ReturnVal = NULL; for( unsigned int i=0; i < PATH_CACHE_ENTRIES; i++ ) { if ( m_PathCacheEntries[i] ) { if ( strcmp( (const char*)m_PathCacheEntries[i]->m_Path, (const char*)Path) == 0 ) { ReturnVal = m_PathCacheEntries[i]; GetSystemTimeAsFileTime( &ReturnVal->m_LastLookup ); ReturnVal->AddRef(); } } } return ReturnVal; } void ConfigurationManager::Insert( VDirConfig *NewConfig ) { // // Insert a new virtual directory configuration into the // virtual directory cache. Expire an old entry if needed. // int BestSlot = 0; FILETIME WorstTime; memset( &WorstTime, 0xFF, sizeof( WorstTime ) ); for( unsigned int i=0; i < PATH_CACHE_ENTRIES; i++ ) { if ( !m_PathCacheEntries[i] ) { BestSlot = i; break; } else if ( CompareFileTime( &m_PathCacheEntries[i]->m_LastLookup, &WorstTime ) < 0 ) { WorstTime = m_PathCacheEntries[i]->m_LastLookup; BestSlot = i; } } if ( m_PathCacheEntries[BestSlot] ) m_PathCacheEntries[BestSlot]->Release(); NewConfig->AddRef(); m_PathCacheEntries[BestSlot] = NewConfig; } VDirConfig* ConfigurationManager::Lookup( StringHandle InstanceMetabasePath, StringHandle URL ) { // // Find the virtual directories configuration in the cache. // VDirConfig* ReturnVal = NULL; for( unsigned int i=0; i < MAP_CACHE_ENTRIES; i++ ) { MapCacheEntry* CacheEntry = m_MapCacheEntries[i]; if ( CacheEntry ) { if ( ( strcmp( (const char*)CacheEntry->m_InstanceMetabasePath, (const char*)InstanceMetabasePath) == 0 ) && ( strcmp( (const char*)CacheEntry->m_URL, (const char*)URL ) == 0 ) ) { GetSystemTimeAsFileTime( &CacheEntry->m_LastLookup ); ReturnVal = m_MapCacheEntries[i]->m_Config; ReturnVal->AddRef(); return ReturnVal; } } } return ReturnVal; } VDirConfig* ConfigurationManager::GetVDirConfig( StringHandle Path ) { VDirConfig* Config = Lookup( Path ); if ( !Config ) { try { Config = new VDirConfig( Path, m_IISAdminBase ); Insert( Config ); } catch( ServerException Exception ) { if ( Config ) Config->Release(); throw; } } return Config; } VDirConfig* ConfigurationManager::Insert( StringHandle InstanceMetabasePath, StringHandle URL, StringHandle Path ) { VDirConfig* Config = GetVDirConfig( Path ); try { MapCacheEntry* CacheEntry = new MapCacheEntry( InstanceMetabasePath, URL, Config ); int BestSlot = 0; FILETIME WorstTime; memset( &WorstTime, 0xFF, sizeof( WorstTime ) ); for( unsigned int i=0; i < MAP_CACHE_ENTRIES; i++ ) { if ( !m_MapCacheEntries[i] ) { BestSlot = i; break; } else if ( CompareFileTime( &m_MapCacheEntries[i]->m_LastLookup, &WorstTime ) < 0 ) { WorstTime = m_MapCacheEntries[i]->m_LastLookup; BestSlot = i; } } if ( m_MapCacheEntries[BestSlot] ) delete m_MapCacheEntries[BestSlot]; m_MapCacheEntries[BestSlot] = CacheEntry; return Config; } catch( ServerException Exception ) { Config->Release(); throw; } } StringHandle ConfigurationManager::GetVDirPath( StringHandle InstanceMetabasePath, StringHandle URL ) { // // Find the virtual directory that coresponds to the URL. // Do this by matching the URL up with the metabase keys. Keep // pruning off the URL untill the longest metabase path is found // that is a virtual directory. // StringHandleW InstanceMetabasePathW = InstanceMetabasePath; StringHandleW URLW = URL; WCHAR *Path = NULL; METADATA_HANDLE MdVDirKey = NULL; try { WCHAR *PathEnd = NULL; WCHAR *CurrentEnd = NULL; WCHAR RootString[] = L"/Root"; SIZE_T InstancePathSize = InstanceMetabasePathW.Size(); SIZE_T URLSize = URLW.Size(); SIZE_T RootStringSize = ( sizeof( RootString ) / sizeof( *RootString ) ) - 1; Path = new WCHAR[ InstancePathSize + URLSize + RootStringSize + 1 ]; memcpy( Path, (const WCHAR*)InstanceMetabasePathW, InstancePathSize * sizeof( WCHAR ) ); PathEnd = Path + InstancePathSize; memcpy( PathEnd, RootString, RootStringSize * sizeof( WCHAR ) ); memcpy( PathEnd + RootStringSize, (const WCHAR*)URLW, ( URLSize + 1 )* sizeof( WCHAR ) ); CurrentEnd = PathEnd + RootStringSize + URLSize; while( 1 ) { HRESULT Hr = m_IISAdminBase->OpenKey( METADATA_MASTER_ROOT_HANDLE, //metabase handle. Path, //path to the key, relative to hMDHandle. METADATA_PERMISSION_READ, //specifies read and write permissions. 5000, //the time, in milliseconds, before the method times out. &MdVDirKey //receives the handle to the opened key. ); if ( SUCCEEDED( Hr ) ) { // // Check if this is a virtual directory // WCHAR NodeName[ 255 ]; DWORD RequiredDataLen; METADATA_RECORD MDRecord; MDRecord.dwMDIdentifier = MD_KEY_TYPE; MDRecord.dwMDAttributes = METADATA_NO_ATTRIBUTES; MDRecord.dwMDUserType = IIS_MD_UT_SERVER; MDRecord.dwMDDataType = STRING_METADATA; MDRecord.dwMDDataLen = sizeof( NodeName ); MDRecord.pbMDData = (unsigned char*)NodeName; MDRecord.dwMDDataTag = 0; Hr = m_IISAdminBase->GetData( MdVDirKey, NULL, &MDRecord, &RequiredDataLen ); if ( FAILED(Hr) && ( Hr != MD_ERROR_DATA_NOT_FOUND ) && ( Hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) ) ) throw ServerException( Hr ); if ( SUCCEEDED( Hr ) && wcscmp( L"IIsWebVirtualDir", NodeName ) == 0 ) { // Found the path, so return the data StringHandle VDirPath = Path; delete[] Path; m_IISAdminBase->CloseKey( MdVDirKey ); return VDirPath; } } else if ( Hr != HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ) ) { throw ServerException( Hr ); } // // If this is the end of the URL, then nothing else can be done // if ( CurrentEnd == PathEnd ) throw ServerException( E_INVALIDARG ); m_IISAdminBase->CloseKey( MdVDirKey ); MdVDirKey = NULL; // Chop off the rightmost subpart while( CurrentEnd != PathEnd && *CurrentEnd != L'/' && *CurrentEnd != L'\\' ) CurrentEnd--; if ( *CurrentEnd == L'/' || *CurrentEnd == L'\\' ) *CurrentEnd = L'\0'; // Attempt another round } } catch( ServerException Exception ) { delete[] Path; if ( MdVDirKey ) m_IISAdminBase->CloseKey( MdVDirKey ); throw; } } bool ConfigurationManager::HandleCacheConsistency() { // // Handle cache consistency. This is done my calling IIS to check the change number. // If the current change number is different then the change number for the last lookup, // then flush the cache. // DWORD ChangeNumber; HRESULT Hr = m_IISAdminBase->GetSystemChangeNumber( &ChangeNumber ); if ( FAILED(Hr) ) { throw ServerException( Hr ); } if ( ChangeNumber == m_ChangeNumber ) return true; // cache is consistent FlushCache(); m_ChangeNumber = ChangeNumber; return false; // cache was flushed. } VDirConfig* ConfigurationManager::GetConfig( StringHandle InstanceMetabasePath, StringHandle URL ) { // // Toplevel function to do everything to lookup the configuration to use for an URL. // METADATA_HANDLE MdVDirKey = NULL; VDirConfig * Config = NULL; HRESULT Hr = CoInitializeEx( NULL, COINIT_MULTITHREADED ); if ( FAILED(Hr) ) throw ServerException( Hr ); HANDLE ImpersonationToken = NULL; bool DidRevertToSelf = false; try { EnterCriticalSection( &m_CacheCS ); if ( HandleCacheConsistency() ) { // The cache was consistent. Chances are good // that the lookup will succeed Config = Lookup( InstanceMetabasePath, URL ); if ( Config ) { CoUninitialize(); LeaveCriticalSection( &m_CacheCS ); return Config; } } // Need to revert to the system process // to address the metabase if ( !OpenThreadToken( GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &ImpersonationToken ) ) { DWORD dwError = GetLastError(); if (dwError != ERROR_NO_TOKEN) throw ServerException( HRESULT_FROM_WIN32( dwError ) ); } else { if ( !RevertToSelf() ) throw ServerException( HRESULT_FROM_WIN32( GetLastError() ) ); DidRevertToSelf = true; } StringHandle Path = GetVDirPath( InstanceMetabasePath, URL ); Config = Insert( InstanceMetabasePath, URL, Path ); if ( DidRevertToSelf ) { BITSSetCurrentThreadToken( ImpersonationToken ); } if ( ImpersonationToken ) CloseHandle( ImpersonationToken ); CoUninitialize(); LeaveCriticalSection( &m_CacheCS ); return Config; } catch( ServerException Exception ) { if ( Config ) delete Config; if ( MdVDirKey ) m_IISAdminBase->CloseKey( MdVDirKey ); if ( DidRevertToSelf ) BITSSetCurrentThreadToken( ImpersonationToken ); if ( ImpersonationToken ) CloseHandle( ImpersonationToken ); CoUninitialize(); LeaveCriticalSection( &m_CacheCS ); throw; } }