/*++ Copyright (c) 2000 Microsoft Corporation Module Name: urlauth.cxx Abstract: Implements an ISAPI * scriptmap which handles authorization by calling into the NT AuthZ framework. Author: Bilal Alam (balam) Nov 26, 2001 --*/ #include "precomp.hxx" DECLARE_DEBUG_PRINTS_OBJECT(); DECLARE_DEBUG_VARIABLE(); // local function declarations HRESULT GetTokenForExecution( EXTENSION_CONTROL_BLOCK *pecb, HANDLE * phToken ); // global data ADMIN_MANAGER_CACHE * g_pAdminManagerCache; VOID WINAPI UrlAuthCompletion( EXTENSION_CONTROL_BLOCK * pecb, PVOID pvContext, DWORD, DWORD ) /*++ Routine Description: Routine to be called whenever ISAPI async stuff completes Arguments: pecb - EXTENSION_CONTROL_BLOCK * pContext - Unused cbIo - Unused dwError - Unused Return Value: None --*/ { if ( pvContext ) { DBG_REQUIRE( CloseHandle( pvContext ) ); pvContext = NULL; } pecb->ServerSupportFunction( pecb->ConnID, HSE_REQ_DONE_WITH_SESSION, NULL, NULL, NULL ); } DWORD WINAPI HttpExtensionProc( EXTENSION_CONTROL_BLOCK * pecb ) /*++ Routine Description: Main entry point for ISAPI Arguments: pecb - EXTENSION_CONTROL_BLOCK * Return Value: HSE_STATUS_* --*/ { BOOL fRet; STACK_BUFFER( buff, 512 ); DWORD cbBuffer; BOOL fAccessGranted = FALSE; BOOL fFatalError = FALSE; WCHAR * pszAdminStore; ADMIN_MANAGER * pAdminManager = NULL; AZ_APPLICATION * pApplication = NULL; WCHAR * pszScopeName; HRESULT hr = NO_ERROR; METADATA_RECORD * pRecord; HANDLE hImpersonationToken = NULL; HSE_CUSTOM_ERROR_INFO CustomErrorInfo = { "500 Server Error", 0, TRUE }; // // First determine whether URL authorization is even enabled for this // URL // cbBuffer = buff.QuerySize(); fRet = pecb->ServerSupportFunction( pecb, HSE_REQ_GET_METADATA_PROPERTY, buff.QueryPtr(), &cbBuffer, (LPDWORD) MD_ENABLE_URL_AUTHORIZATION ); if ( !fRet ) { if ( GetLastError() == ERROR_INVALID_INDEX ) { // the common case is to fail with ERROR_INVALID_INDEX == access granted by no present metadata // if the above call failed with someting like OUTOFMEMORY or INVALIDPARAMETER do not allow access. fAccessGranted = TRUE; } else { fFatalError = TRUE; hr = HRESULT_FROM_WIN32( GetLastError() ); } goto AccessCheckComplete; } // // Check the property now. If invalid type or if 0, then we're done // pRecord = (METADATA_RECORD*) buff.QueryPtr(); if ( pRecord->dwMDDataType != DWORD_METADATA || *((DWORD*)pRecord->pbMDData) == 0 ) { fAccessGranted = TRUE; goto AccessCheckComplete; } // // If we're still a go, then retrieve the store name from the metabase. // This is our key into the AdminManager cache we maintain // cbBuffer = buff.QuerySize(); fRet = pecb->ServerSupportFunction( pecb, HSE_REQ_GET_METADATA_PROPERTY, buff.QueryPtr(), &cbBuffer, (LPDWORD) MD_URL_AUTHORIZATION_STORE_NAME ); if ( !fRet ) { // // The only acceptable error is insufficient buffer. Otherwise, we // don't have a store name and we're sunk // if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER ) { CustomErrorInfo.pszStatus = "500 Server Error"; CustomErrorInfo.uHttpSubError = 17; CustomErrorInfo.fAsync = TRUE; goto AccessCheckComplete; } DBG_ASSERT( cbBuffer > buff.QuerySize() ); if ( !buff.Resize( cbBuffer ) ) { fFatalError = TRUE; hr = HRESULT_FROM_WIN32( GetLastError() ); goto AccessCheckComplete; } cbBuffer = buff.QuerySize(); fRet = pecb->ServerSupportFunction( pecb, HSE_REQ_GET_METADATA_PROPERTY, buff.QueryPtr(), &cbBuffer, (LPDWORD) MD_URL_AUTHORIZATION_STORE_NAME ); if ( !fRet ) { DBG_ASSERT( GetLastError() != ERROR_INSUFFICIENT_BUFFER ); fFatalError = TRUE; hr = HRESULT_FROM_WIN32( GetLastError() ); goto AccessCheckComplete; } } // // Store name better be a string // pRecord = (METADATA_RECORD*) buff.QueryPtr(); if ( pRecord->dwMDDataType != STRING_METADATA ) { fFatalError = TRUE; hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); goto AccessCheckComplete; } // // We should have an AdminManager store name now. Get an ADMIN_MANAGER // from the cache (cache will create it if not already in cache) // pszAdminStore = (WCHAR*) pRecord->pbMDData; DBG_ASSERT( g_pAdminManagerCache != NULL ); hr = g_pAdminManagerCache->GetAdminManager( pszAdminStore, &pAdminManager ); if ( FAILED( hr ) ) { // // That sucks. We couldn't find an admin manager // CustomErrorInfo.pszStatus = "500 Server Error"; CustomErrorInfo.uHttpSubError = 18; CustomErrorInfo.fAsync = TRUE; goto AccessCheckComplete; } DBG_ASSERT( pAdminManager != NULL ); // // Get the application from the AdminManager. That should be trivial // since we always have the same application name. // hr = pAdminManager->GetApplication( &pApplication ); if ( FAILED( hr ) ) { fFatalError = TRUE; goto AccessCheckComplete; } DBG_ASSERT( pApplication != NULL ); // // Get the scope name for this request // cbBuffer = buff.QuerySize(); fRet = pecb->ServerSupportFunction( pecb, HSE_REQ_GET_METADATA_PROPERTY, buff.QueryPtr(), &cbBuffer, (LPDWORD) MD_URL_AUTHORIZATION_SCOPE_NAME ); if ( !fRet ) { // // It is acceptable to have no scope name. Then we use the NULL scope // if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { DBG_ASSERT( cbBuffer > buff.QuerySize() ); if ( !buff.Resize( cbBuffer ) ) { fFatalError = TRUE; hr = HRESULT_FROM_WIN32( GetLastError() ); goto AccessCheckComplete; } cbBuffer = buff.QuerySize(); fRet = pecb->ServerSupportFunction( pecb, HSE_REQ_GET_METADATA_PROPERTY, buff.QueryPtr(), &cbBuffer, (LPDWORD) MD_URL_AUTHORIZATION_SCOPE_NAME ); if ( !fRet ) { DBG_ASSERT( GetLastError() != ERROR_INSUFFICIENT_BUFFER ); fFatalError = TRUE; hr = HRESULT_FROM_WIN32( GetLastError() ); goto AccessCheckComplete; } } } if ( fRet ) { pRecord = (METADATA_RECORD*) buff.QueryPtr(); if ( pRecord->dwMDDataType != STRING_METADATA ) { fFatalError = TRUE; hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); goto AccessCheckComplete; } // // We have a scope name. It better be a string // pszScopeName = (WCHAR*) pRecord->pbMDData; } else { pszScopeName = NULL; } // // Do the access check // hr = pApplication->DoAccessCheck( pecb, pszScopeName, &fAccessGranted ); if ( FAILED( hr ) ) { fFatalError = TRUE; goto AccessCheckComplete; } // // Determine what impersonation token should be used for execution of real URL // if ( fAccessGranted ) { hr = GetTokenForExecution( pecb, &hImpersonationToken ); if ( FAILED ( hr ) ) { DBG_ASSERT( !hImpersonationToken ); fAccessGranted = FALSE; fFatalError = TRUE; goto AccessCheckComplete; } } else { // // Didn't have access. Thats a 401.7 // CustomErrorInfo.pszStatus = "401 Unauthorized"; CustomErrorInfo.uHttpSubError = 7; CustomErrorInfo.fAsync = TRUE; } AccessCheckComplete: // not OK to have both access granted and a fatal error. DBG_ASSERT ( !( fAccessGranted && fFatalError ) ); // // Do some cleanup // if ( pAdminManager != NULL ) { pAdminManager->DereferenceCacheEntry(); pAdminManager = NULL; } // // Time to do something (TM). // if ( fAccessGranted ) { HSE_EXEC_UNICODE_URL_INFO ExecUrlInfo; HSE_EXEC_UNICODE_URL_USER_INFO ExecUrlUserInfo; // // Everything is ok. Just execute the original URL // fRet = pecb->ServerSupportFunction( pecb->ConnID, HSE_REQ_IO_COMPLETION, UrlAuthCompletion, NULL, (LPDWORD) hImpersonationToken ); if ( !fRet ) { DBG_ASSERT( FALSE ); if ( hImpersonationToken ) { DBG_REQUIRE( CloseHandle( hImpersonationToken ) ); hImpersonationToken = NULL; } return HSE_STATUS_ERROR; } ZeroMemory( &ExecUrlInfo, sizeof( ExecUrlInfo ) ); ExecUrlInfo.dwExecUrlFlags = HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR; if ( hImpersonationToken ) { ExecUrlInfo.pUserInfo = &ExecUrlUserInfo; ZeroMemory( &ExecUrlUserInfo, sizeof( ExecUrlUserInfo ) ); cbBuffer = buff.QuerySize(); fRet = pecb->GetServerVariable( pecb->ConnID, "UNICODE_REMOTE_USER", buff.QueryPtr(), &cbBuffer ); if ( !fRet ) { if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER ) { if ( hImpersonationToken ) { DBG_REQUIRE( CloseHandle( hImpersonationToken ) ); hImpersonationToken = NULL; } return HSE_STATUS_ERROR; } DBG_ASSERT( cbBuffer > buff.QuerySize() ); fRet = buff.Resize( cbBuffer ); if ( !fRet ) { if ( hImpersonationToken ) { DBG_REQUIRE( CloseHandle( hImpersonationToken ) ); hImpersonationToken = NULL; } return HSE_STATUS_ERROR; } cbBuffer = buff.QuerySize(); fRet = pecb->GetServerVariable( pecb->ConnID, "UNICODE_REMOTE_USER", buff.QueryPtr(), &cbBuffer ); if ( !fRet ) { if ( hImpersonationToken ) { DBG_REQUIRE( CloseHandle( hImpersonationToken ) ); hImpersonationToken = NULL; } return HSE_STATUS_ERROR; } } ExecUrlUserInfo.pszCustomUserName = (WCHAR*) buff.QueryPtr(); ExecUrlUserInfo.pszCustomAuthType = "URLAUTH"; ExecUrlUserInfo.hImpersonationToken = hImpersonationToken; } fRet = pecb->ServerSupportFunction( pecb->ConnID, HSE_REQ_EXEC_UNICODE_URL, &ExecUrlInfo, NULL, NULL ); if ( !fRet && hImpersonationToken ) { DBG_REQUIRE( CloseHandle( hImpersonationToken ) ); } hImpersonationToken = NULL; return fRet ? HSE_STATUS_PENDING : HSE_STATUS_ERROR; } else if ( !fFatalError ) { // // This just means access wasn't granted. Send back an error // fRet = pecb->ServerSupportFunction( pecb->ConnID, HSE_REQ_IO_COMPLETION, UrlAuthCompletion, NULL, NULL ); if ( !fRet ) { DBG_ASSERT( FALSE ); return HSE_STATUS_ERROR; } fRet = pecb->ServerSupportFunction( pecb->ConnID, HSE_REQ_SEND_CUSTOM_ERROR, &CustomErrorInfo, NULL, NULL ); if ( !fRet ) { pecb->ServerSupportFunction( pecb->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "500 Server Error", NULL, NULL ); return HSE_STATUS_ERROR; } else { return HSE_STATUS_PENDING; } } else { // // Fatal error. Just set the last error and bail. // SetLastError( WIN32_FROM_HRESULT( hr ) ); return HSE_STATUS_ERROR; } } BOOL WINAPI GetExtensionVersion( HSE_VERSION_INFO * pver ) /*++ Routine Description: Initialization routine for ISAPI Arguments: pver - Version information Return Value: TRUE if successful, else FALSE --*/ { HRESULT hr; // // Do ISAPI interface init crap // pver->dwExtensionVersion = MAKELONG( 0, 1 ); strcpy( pver->lpszExtensionDesc, "URL Authorization ISAPI" ); // // Create an admin manager cache // g_pAdminManagerCache = new ADMIN_MANAGER_CACHE; if ( g_pAdminManagerCache == NULL ) { return FALSE; } // // Initialize ADMIN_MANAGER globals // hr = ADMIN_MANAGER::Initialize(); if ( FAILED( hr ) ) { delete g_pAdminManagerCache; g_pAdminManagerCache = NULL; return FALSE; } // // Initialize AZ_APPLICATION globals // hr = AZ_APPLICATION::Initialize(); if ( FAILED( hr ) ) { ADMIN_MANAGER::Terminate(); delete g_pAdminManagerCache; g_pAdminManagerCache = NULL; return FALSE; } return TRUE; } BOOL WINAPI TerminateExtension( DWORD ) /*++ Routine Description: Cleanup ISAPI extension Arguments: None Return Value: TRUE if successful, else FALSE --*/ { AZ_APPLICATION::Terminate(); ADMIN_MANAGER::Terminate(); delete g_pAdminManagerCache; g_pAdminManagerCache = NULL; return TRUE; } BOOL WINAPI DllMain( HINSTANCE hDll, DWORD dwReason, LPVOID ) /*++ Routine Description: DLL Entry Routine Arguments: Return Value: TRUE if successful, else FALSE --*/ { switch ( dwReason ) { case DLL_PROCESS_ATTACH: CREATE_DEBUG_PRINT_OBJECT( "urlauth" ); DisableThreadLibraryCalls( hDll ); break; case DLL_PROCESS_DETACH: DELETE_DEBUG_PRINT_OBJECT(); break; default: break; } return TRUE; } HRESULT GetTokenForExecution( EXTENSION_CONTROL_BLOCK *pecb, HANDLE * phToken ) /*++ Routine Description: From metadata, determine the impersonation token to return Arguments: pecb - current ecb for request phToken - where to store the token on success Return Value: HRESULT --*/ { HRESULT hr = S_OK; BOOL fRet = FALSE; METADATA_RECORD *pRecord = NULL; STACK_BUFFER( buff, 512 ); DWORD cbBuffer; DWORD dwValue= 0; HANDLE hToken = NULL; HANDLE hToken2 = NULL; DBG_ASSERT(pecb); DBG_ASSERT(phToken); *phToken = NULL; cbBuffer = buff.QuerySize(); fRet = pecb->ServerSupportFunction( pecb, HSE_REQ_GET_METADATA_PROPERTY, buff.QueryPtr(), &cbBuffer, (LPDWORD) MD_URL_AUTHORIZATION_IMPERSONATION_LEVEL ); if ( !fRet && GetLastError() != ERROR_INVALID_INDEX ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto exit; } else if ( fRet ) { // // Check the property now. If invalid type then we're done // pRecord = (METADATA_RECORD*) buff.QueryPtr(); if ( pRecord->dwMDDataType != DWORD_METADATA ) { hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto exit; } // valid type of data - validity of data value done in switch below dwValue = *(DWORD*)pRecord->pbMDData; } else { DBG_ASSERT( !fRet && GetLastError() == ERROR_INVALID_INDEX ); // no metadata present, therefore use the default of zero dwValue = 0; } switch(dwValue) { case 0: // use the current authenticated user fRet = OpenThreadToken( GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hToken ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto exit; } break; case 1: // use the process token to impersonate // first get the current impersonation token fRet = OpenThreadToken( GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &hToken2 ); if ( fRet ) { DBG_ASSERT( hToken2 != NULL ); RevertToSelf(); } // get the current process token - it's a primary token fRet = OpenProcessToken( GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken ); if ( hToken2 != NULL ) { if ( !SetThreadToken( NULL, hToken2 ) ) { DBG_ASSERT( FALSE ); } DBG_REQUIRE( CloseHandle( hToken2 ) ); hToken2 = NULL; } // checking the return value from open the process token here if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto exit; } // create an impersonation token from the primary process token fRet = DuplicateHandle ( GetCurrentProcess(), hToken, GetCurrentProcess(), &hToken2, 0, FALSE, DUPLICATE_SAME_ACCESS ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto exit; } DBG_REQUIRE( CloseHandle( hToken ) ); hToken = hToken2; hToken2 = NULL; break; case 2: // get the anonymous token for impersonation cbBuffer = buff.QuerySize(); fRet = pecb->GetServerVariable( pecb->ConnID, "UNICODE_SCRIPT_NAME", (CHAR*) buff.QueryPtr(), &cbBuffer ); if ( !fRet ) { if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto exit; } DBG_ASSERT( cbBuffer > buff.QuerySize() ); fRet = buff.Resize( cbBuffer ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto exit; } cbBuffer = buff.QuerySize(); fRet = pecb->GetServerVariable( pecb->ConnID, "UNICODE_SCRIPT_NAME", (CHAR*) buff.QueryPtr(), &cbBuffer ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto exit; } } fRet = pecb->ServerSupportFunction( pecb->ConnID, HSE_REQ_GET_UNICODE_ANONYMOUS_TOKEN, buff.QueryPtr(), (LPDWORD)&hToken, NULL ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto exit; } break; default: // If not 0, 1, or 2 then not valid metadata hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto exit; break; } *phToken = hToken; hToken = NULL; hr = S_OK; exit: if ( hToken ) { DBG_REQUIRE( CloseHandle( hToken ) ); hToken = NULL; } return hr; };