/*++ Copyright (c) 2000-2001 Microsoft Corporation Module Name : dll_manager.cxx Abstract: IIS Plus ISAPI Handler. Dll management classes. Author: Taylor Weiss (TaylorW) 03-Feb-2000 Wade A. Hilmo (WadeH) 08-Mar-2001 Project: w3isapi.dll --*/ #include "precomp.hxx" #include "dll_manager.h" ISAPI_DLL_MANAGER * g_pDllManager = NULL; PTRACE_LOG ISAPI_DLL::sm_pTraceLog; ALLOC_CACHE_HANDLER * ISAPI_DLL::sm_pachIsapiDlls; // // Generic mapping for Application access check // GENERIC_MAPPING g_FileGenericMapping = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS }; //static HRESULT ISAPI_DLL::Initialize( VOID ) { ALLOC_CACHE_CONFIGURATION acConfig; #if DBG sm_pTraceLog = CreateRefTraceLog( 2000, 0 ); #endif // // Initialize a lookaside for this structure // acConfig.nConcurrency = 1; acConfig.nThreshold = 100; acConfig.cbSize = sizeof( ISAPI_DLL ); DBG_ASSERT( sm_pachIsapiDlls == NULL ); sm_pachIsapiDlls = new ALLOC_CACHE_HANDLER( "ISAPI_DLL", &acConfig ); if ( sm_pachIsapiDlls == NULL ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } return NO_ERROR; } //static VOID ISAPI_DLL::Terminate( VOID ) { if ( sm_pTraceLog != NULL ) { DestroyRefTraceLog( sm_pTraceLog ); sm_pTraceLog = NULL; } if ( sm_pachIsapiDlls != NULL ) { delete sm_pachIsapiDlls; sm_pachIsapiDlls = NULL; } } HRESULT ISAPI_DLL::SetName( const WCHAR * szModuleName, HANDLE hImpersonation ) { HRESULT hr; BOOL fImpersonateForUnc = FALSE; DBG_ASSERT( CheckSignature() ); hr = m_strModuleName.Copy( szModuleName ); if ( FAILED( hr ) ) { return hr; } // // Store a copy of the anti-canon module name // hr = MakePathCanonicalizationProof( const_cast( szModuleName ), &m_strAntiCanonModuleName ); if ( SUCCEEDED( hr ) ) { // // Verify that the file exists by getting its attributes. // // Note that in the UNC case, we have to do an impersonation // before we can make this call. // if ( wcsncmp( szModuleName, L"\\\\", 2 ) == 0 ) { fImpersonateForUnc = SetThreadToken( NULL, hImpersonation ); if ( !fImpersonateForUnc ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "Attempt impersonate to get file attributes on %S failed " "with error 0x%08x.\r\n", szModuleName, hr )); return hr; } } if ( GetFileAttributes( m_strAntiCanonModuleName.QueryStr() ) == 0xffffffff ) { IF_DEBUG( DLL_MANAGER ) { DBGPRINTF(( DBG_CONTEXT, "Attempt to get file attributes on %S failed " "with error %d.\r\n", szModuleName, GetLastError() )); } // // CODEWORK - This smoke and mirrors game with the last // error is to preserve the old behavior that extensions // that are not found return 500 errors to the client // instead of 404. // if ( GetLastError() == ERROR_FILE_NOT_FOUND ) { SetLastError( ERROR_MOD_NOT_FOUND ); } hr = HRESULT_FROM_WIN32( GetLastError() ); } } if ( fImpersonateForUnc ) { RevertToSelf(); } return hr; } HRESULT ISAPI_DLL::SetFastSid( IN PSID pSid ) { DBG_ASSERT( pSid != NULL ); if ( GetLengthSid( pSid ) <= sizeof( m_abFastSid ) ) { memcpy( m_abFastSid, pSid, GetLengthSid( pSid ) ); m_pFastSid = m_abFastSid; } return NO_ERROR; } HRESULT ISAPI_DLL::Load( IN HANDLE hImpersonation, IN PSID pSid ) { HRESULT hr; HSE_VERSION_INFO VersionInfo; BOOL fImpersonatingForUnc = FALSE; DBG_ASSERT( CheckSignature() ); // // Check to see if the dll is already loaded. // If so, just return NO_ERROR. // if ( m_fIsLoaded ) { return NO_ERROR; } // // So the dll is not loaded. Grab the lock and // check again (another thread may have snuck in between // the first test and now and already loaded it... // Lock(); if ( !m_fIsLoaded ) { // // Load the ACL for the dll file and do an access check // before loading the dll itself. This is a slightly different // approach than what earlier versions of IIS did. We are // not going to be actually doing impersonation at the time // we load the dll as earlier versions did. As a result, // DllMain's DLL_PROCESS_ATTACH will run in the context of // the worker process primary token instead of as the // authenticated user. // // This new approach is the right way to do it, but it might // introduce an incompatibility with any extensions that implement // code in DllMain that depends on running as the authenticated // user. It is very, very unlikely that we'll see this problem. // If we do, the solution is to impersonate the token before // calling LoadLibraryEx. // // // Before calling LoadAcl, we should check to see if this // dll lives on a UNC path. If it does, then we need to // do the impersonation before attempting to load the ACL, // since we can't assume that the inetinfo or w3wp process // token can jump off the box. // if ( wcsncmp( m_strModuleName.QueryStr(), L"\\\\", 2 ) == 0 ) { fImpersonatingForUnc = SetThreadToken( NULL, hImpersonation ); if ( !fImpersonatingForUnc ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "[ISAPI_DLL::Load] SetThreadToken failed for %S. Error %0x08x\n", m_strModuleName.QueryStr(), hr )); goto Failed; } } hr = LoadAcl( m_strAntiCanonModuleName ); if ( FAILED( hr ) ) { goto Failed; } if ( !AccessCheck( hImpersonation, pSid ) ) { hr = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED ); goto Failed; } // // Since we've just passed an AccessCheck, we can cache // the fast SID now. // if ( pSid != NULL ) { hr = SetFastSid( pSid ); if ( FAILED( hr ) ) { goto Failed; } } m_hModule = LoadLibraryEx( m_strAntiCanonModuleName.QueryStr(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH ); if ( m_hModule == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "[ISAPI_DLL::Load] LoadLibrary %S failed. Error %d\n", m_strModuleName.QueryStr(), GetLastError() )); goto Failed; } // // Lose the impersonation token before we call any more entry points... // if ( fImpersonatingForUnc ) { RevertToSelf(); fImpersonatingForUnc = FALSE; } // // Get the entry points // m_pfnGetExtensionVersion = (PFN_GETEXTENSIONVERSION)GetProcAddress( m_hModule, "GetExtensionVersion" ); m_pfnTerminateExtension = (PFN_TERMINATEEXTENSION)GetProcAddress( m_hModule, "TerminateExtension" ); m_pfnHttpExtensionProc = (PFN_HTTPEXTENSIONPROC)GetProcAddress( m_hModule, "HttpExtensionProc" ); // // HttpExtensionProc and GetExtensionVersion are required // if( !m_pfnGetExtensionVersion || !m_pfnHttpExtensionProc ) { hr = HRESULT_FROM_WIN32( ERROR_PROC_NOT_FOUND ); // // Make sure we dont call TerminateExtension on // cleanup. // m_pfnTerminateExtension = NULL; goto Failed; } if( !m_pfnGetExtensionVersion( &VersionInfo ) ) { hr = GetLastError() == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32( GetLastError() ); goto Failed; } DBGPRINTF(( DBG_CONTEXT, "ISAPI_DLL::Load() Loaded extension %S, " " description \"%s\"\n", m_strModuleName.QueryStr(), VersionInfo.lpszExtensionDesc )); m_fIsLoaded = TRUE; } Unlock(); return NO_ERROR; Failed: DBG_ASSERT( FAILED( hr ) ); // // Clean up after the failed load attempt // if ( fImpersonatingForUnc ) { RevertToSelf(); fImpersonatingForUnc = FALSE; } if ( m_pfnHttpExtensionProc ) { m_pfnHttpExtensionProc = NULL; } if ( m_pfnGetExtensionVersion ) { m_pfnGetExtensionVersion = NULL; } if ( m_pfnTerminateExtension ) { m_pfnTerminateExtension = NULL; } if ( m_hModule ) { FreeLibrary( m_hModule ); m_hModule = NULL; } Unlock(); return hr; } VOID ISAPI_DLL::Unload( VOID ) { if( m_pfnTerminateExtension ) { m_pfnTerminateExtension( HSE_TERM_MUST_UNLOAD ); m_pfnTerminateExtension = NULL; } m_pfnGetExtensionVersion = NULL; m_pfnHttpExtensionProc = NULL; if( m_hModule ) { FreeLibrary( m_hModule ); } } HRESULT ISAPI_DLL::LoadAcl( STRU &strModuleName ) { DWORD cbSecDesc = m_buffSD.QuerySize(); DWORD dwError; DBG_ASSERT( CheckSignature() ); // // Force an access check on the next request // while ( !GetFileSecurity( strModuleName.QueryStr(), (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION), m_buffSD.QueryPtr(), m_buffSD.QuerySize(), &cbSecDesc )) { if ( ( dwError = GetLastError() ) != ERROR_INSUFFICIENT_BUFFER ) { return HRESULT_FROM_WIN32( dwError ); } if ( !m_buffSD.Resize( cbSecDesc ) ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } // // Hopefully, we have sufficient buffer now, retry // } return NOERROR; } BOOL ISAPI_DLL::AccessCheck( IN HANDLE hImpersonation, IN PSID pSid ) { BOOL fRet = TRUE; DWORD dwGrantedAccess = 0; BYTE PrivSet[400]; DWORD cbPrivilegeSet = sizeof(PrivSet); BOOL fAccessGranted; DBG_ASSERT( CheckSignature() ); // // First compare to the fast check SID if possible // if ( pSid != NULL && QueryFastSid() != NULL ) { if ( EqualSid( pSid, QueryFastSid() ) ) { return TRUE; } } // // Ok, just do the real access check // fRet = ( ::AccessCheck( QuerySecDesc(), hImpersonation, FILE_GENERIC_EXECUTE, &g_FileGenericMapping, (PRIVILEGE_SET *) &PrivSet, &cbPrivilegeSet, &dwGrantedAccess, &fAccessGranted ) && fAccessGranted); return fRet; } HRESULT ISAPI_DLL_MANAGER::GetIsapi( IN const WCHAR * szModuleName, OUT ISAPI_DLL ** ppIsapiDll, IN HANDLE hImpersonation, IN PSID pSid ) { HRESULT hr = NOERROR; ISAPI_DLL * pIsapiDll = NULL; LK_RETCODE lkrc; IF_DEBUG( DLL_MANAGER ) { DBGPRINTF(( DBG_CONTEXT, "DllManager looking for %S.\r\n", szModuleName )); } // // Check for the ISAPI in the hash table. If we don't // find it, then we'll need to create an entry in the // hash for it. // lkrc = m_IsapiHash.FindKey( szModuleName, &pIsapiDll ); if ( lkrc == LK_SUCCESS ) { IF_DEBUG( DLL_MANAGER ) { DBGPRINTF(( DBG_CONTEXT, "Found ISAPI_DLL %p (%S).\r\n", pIsapiDll, pIsapiDll->QueryModuleName() )); } } else { // // Create a new entry // IF_DEBUG( DLL_MANAGER ) { DBGPRINTF(( DBG_CONTEXT, "Creating new ISAPI_DLL object for %S.\r\n", szModuleName )); } pIsapiDll = new ISAPI_DLL; if ( !pIsapiDll ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); goto Failed; } if ( FAILED( hr = pIsapiDll->SetName( szModuleName, hImpersonation ) ) ) { pIsapiDll->DereferenceIsapiDll(); pIsapiDll = NULL; goto Failed; } // // Insert the new object into the hash table. If someone // else beat us to it, then we'll just release our new one. // lkrc = m_IsapiHash.InsertRecord( pIsapiDll ); if ( lkrc == LK_SUCCESS ) { IF_DEBUG( DLL_MANAGER ) { DBGPRINTF(( DBG_CONTEXT, "Added ISAPI_DLL %p to table for %S.\r\n", pIsapiDll, szModuleName )); } } else { pIsapiDll->DereferenceIsapiDll(); pIsapiDll = NULL; if ( lkrc == LK_KEY_EXISTS ) { IF_DEBUG( DLL_MANAGER ) { DBGPRINTF(( DBG_CONTEXT, "InsertRecord for %S returned LK_KEY_EXISTS.\r\n", szModuleName )); } // // Ok, so let's get the existing one // lkrc = m_IsapiHash.FindKey( szModuleName, &pIsapiDll ); } if ( lkrc != LK_SUCCESS ) { hr = HRESULT_FROM_WIN32( lkrc ); goto Failed; } } } DBG_ASSERT( pIsapiDll ); // // Call the Load function for the ISAPI_DLL. Note that the ISAPI_DLL // function is smart enough to deal with locking on GetExtensionVersion // and in only allowing it to happen one time. // hr = pIsapiDll->Load( hImpersonation, pSid ); if ( FAILED( hr ) ) { goto Failed; } // // We've got the extension, but we still need to do // an access check to make sure that the client // is allowed to run it. // DBG_ASSERT( pIsapiDll ); if ( !pIsapiDll->AccessCheck( hImpersonation, pSid ) ) { hr = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED ); goto Failed; } // // We're about to return successfully. Set the out parameter now. // *ppIsapiDll = pIsapiDll; DBG_ASSERT( SUCCEEDED( hr ) ); return hr; Failed: DBG_ASSERT( FAILED( hr ) ); IF_DEBUG( DLL_MANAGER ) { DBGPRINTF(( DBG_CONTEXT, "Error %d(0x%08x) occurred getting ISAPI_DLL object for %S.\r\n", hr, hr, szModuleName )); } if ( pIsapiDll ) { pIsapiDll->DereferenceIsapiDll(); pIsapiDll = NULL; } return hr; }