|
|
/*
* F S U T I L . C P P * * File system routines * * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved */
#include "_davfs.h"
#include <aclapi.h>
const CHAR gc_szUncPrefix[] = "\\\\"; const UINT gc_cchszUncPrefix = CElems(gc_szUncPrefix) - 1;
// Location checking ---------------------------------------------------------
//
// ScCheckForLocationCorrectness() will check the url against the
// resource and either add the appropriate location header, or it will
// request a redirect if the url and the resource do not agree. The
// caller has the control over whether or not a true redirect is desired.
// As an informational return, if a location header has been added S_FALSE
// will be returned to the caller.
//
SCODE ScCheckForLocationCorrectness (IMethUtil* pmu, CResourceInfo& cri, UINT modeRedirect) { SCODE sc = S_OK; BOOL fTrailing;
Assert (pmu); fTrailing = FTrailingSlash (pmu->LpwszRequestUrl());
// If the trailing slash existance does not jive with the resource type...
//
if (!cri.FCollection() != !fTrailing) { if (modeRedirect == REDIRECT) { auto_heap_ptr<CHAR> pszLocation;
// Construct the redirect url.
//
sc = pmu->ScConstructRedirectUrl (cri.FCollection(), pszLocation.load()); if (FAILED (sc)) goto ret;
// Redirect this badboy
//
sc = pmu->ScRedirect (pszLocation); if (FAILED (sc)) goto ret; } else { // EmitLocation takes care of the trailing slash checking
//
pmu->EmitLocation (gc_szContent_Location, pmu->LpwszRequestUrl(), cri.FCollection()); }
// Tell the caller we had to change the location
//
sc = S_FALSE; }
ret:
return sc; }
// Access checking -----------------------------------------------------------
//
// class safe_security_revert ------------------------------------------------
//
// Switches the current thread's impersonation token to the cached
// "Reverted Security-enabled Thread Token" when FSecurityInit is called,
// for the duration of the object's lifespan.
// Unconditionally reimpersonates on exit, based on the provided handle.
//
// NOTE: UNCONDITIONALLY reimpersonates on exit, using the impersonation
// handle provided at construction-time.
// (Just wanted to make that clear.)
//
// WARNING: the safe_revert class should only be used by FChildISAPIAccessCheck
// below. It is not a "quick way to get around" impersonation. If
// you do need to do something like this, please see Becky -- she will then
// wack you up'side the head.
//
class safe_security_revert { // Local client token to re-impersonate at dtor time.
HANDLE m_hClientToken;
// This is our cached security-enabled thread token.
static HANDLE s_hSecurityThreadToken;
// NOT IMPLEMENTED
//
safe_security_revert (const safe_security_revert&); safe_security_revert& operator= (const safe_security_revert&);
public:
explicit safe_security_revert (HANDLE h) : m_hClientToken(h) { Assert (m_hClientToken); } ~safe_security_revert() { if (!ImpersonateLoggedOnUser (m_hClientToken)) { DebugTrace ("ImpersonateLoggedOnUser failed with last error %d\n", GetLastError());
// There's not much we can do in this dtor. throw
//
throw CLastErrorException(); } }
BOOL FSecurityInit (BOOL fForceRefresh);
// Token cache manipulators
//
static inline HANDLE GetToken(); static inline VOID ClearToken(); static inline BOOL FSetToken( HANDLE hToken ); };
// Storage for our metaclass data (the cached thread token).
//
HANDLE safe_security_revert::s_hSecurityThreadToken = NULL;
// Public function to clear out the cached thread token.
// Simply calls the metaclass method.
//
void CleanupSecurityToken() { safe_security_revert::ClearToken(); }
// ------------------------------------------------------------------------
//
// GetToken()
//
// Return the cached security token.
//
HANDLE safe_security_revert::GetToken() { return s_hSecurityThreadToken; }
// ------------------------------------------------------------------------
//
// FSetToken()
//
// Set the cached security token.
//
BOOL safe_security_revert::FSetToken( HANDLE hToken ) { //
// If the cache is clear then set it with this token
// and return whether we cache the token.
//
return NULL == InterlockedCompareExchangePointer(&s_hSecurityThreadToken, hToken, NULL); }
// ------------------------------------------------------------------------
//
// ClearToken()
//
// Clear out the cached security token
//
VOID safe_security_revert::ClearToken() { //
// Replace whatever token is cached with NULL.
//
HANDLE hToken = InterlockedExchangePointer( &s_hSecurityThreadToken, NULL);
//
// If we replaced a non-NULL token then close it.
//
if (hToken) CloseHandle (hToken); }
// ------------------------------------------------------------------------
//
// FSecurityInit()
//
// Set our thread token to the cached security-enabled thread token.
// If no security-enabled token is cached, go get one.
//
BOOL safe_security_revert::FSecurityInit (BOOL fForceRefresh) { auto_handle<HANDLE> hTokenNew; HANDLE hToken;
// Clear out the cached security token if told to do so.
//
if (fForceRefresh) ClearToken();
// Fetch the cached security token. Note that even if
// we just cleared it out, we may get back a non-NULL
// token here if another thread has already reloaded
// the cache.
//
hToken = GetToken();
//
// If the cache was clear then create our own new token
// that is set up to do security access queries.
//
if ( NULL == hToken ) { LUID SecurityPrivilegeID; TOKEN_PRIVILEGES tkp;
// RevertToSelf to get us running as system (the local diety).
//
if (!RevertToSelf()) return FALSE;
// ImpersonateSelf copies the process token down to this thread.
// Then we can change the thread token's privileges without messing
// up the process token.
//
if (!ImpersonateSelf (SecurityImpersonation)) { DebugTrace ("ssr::FSecurityInit--ImpersonateSelf failed with %d.\n", GetLastError()); return FALSE; }
// Open our newly-copied thread token to add a privilege (security).
// NOTE: The adjust and query flags are needed for this operation.
// The impersonate flag is needed for use to use this token for
// impersonation -- as we do in SetThreadToken below.
// OpenAsSelf -- FALSE means open as thread, possibly impersonated.
// TRUE means open as the calling process, not as the local (impersonated) thread.
//
if (!OpenThreadToken (GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, hTokenNew.load())) { DebugTrace ("ssr::FSecurityInit--OpenThreadToken failed with %d.\n", GetLastError()); return FALSE; }
// Enable the SE_SECURITY_NAME privilege, so that we can fetch
// security descriptors and call AccessCheck.
//
if (!LookupPrivilegeValue (NULL, SE_SECURITY_NAME, &SecurityPrivilegeID)) { DebugTrace ("ssr::FSecurityInit--LookupPrivilegeValue failed with %d\n", GetLastError()); return FALSE; }
tkp.PrivilegeCount = 1; tkp.Privileges[0].Luid = SecurityPrivilegeID; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges (hTokenNew, FALSE, &tkp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL);
// The return value of AdjustTokenPrivileges cannot be tested directly...
// (always returns 1)
//
if (GetLastError() != ERROR_SUCCESS) { DebugTrace ("ssr::FSecurityInit--AdjustTokenPrivileges failed with %d\n", GetLastError()); return FALSE; }
// Use this new token
//
hToken = hTokenNew.get(); }
// At this point we must have a token
//
Assert (NULL != hToken);
// Set the current thread to use the token.
//
if (!SetThreadToken (NULL, hToken)) { DebugTrace ("ssr::FSecurityInit--SetThreadToken failed with %d.\n", GetLastError()); return FALSE; }
// Everything's cool. We are now running with a thread token
// that has security-checking privileges.
//
// If we created a new token along the way then attempt to cache it.
// We don't care if caching fails, but if it succeeds we DON'T want
// to close the handle because we just gave it to the cache.
//
if (hTokenNew.get()) { if (FSetToken(hTokenNew.get())) { hTokenNew.relinquish(); } }
return TRUE; }
GENERIC_MAPPING gc_gmFile = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS };
// ------------------------------------------------------------------------
//
// ScChildISAPIAccessCheck
//
// Checks if the client (our impersonation handle from off the ECB)
// has the specified access to the specified resource.
// NOTE: Uses a cached "security-enabled-thread-token" to query the
// security descriptor for the specified resource.
//
SCODE __fastcall ScChildISAPIAccessCheck (const IEcb& ecb, LPCWSTR pwsz, DWORD dwAccess, LPBYTE pbSD) { SECURITY_DESCRIPTOR * pSD = NULL; DWORD dwRet; auto_handle<HANDLE> hToken; BYTE psFile[256]; DWORD dwPS = sizeof (psFile); DWORD dwGrantedAccess = 0; BOOL fAccess = FALSE; BOOL fRet;
// pbSD is used only in DAVEX, should never be passed in from HTTPEXT
//
if (NULL != pbSD) { // This should never happen. Removing the param is not
//
throw CHresultException (E_FAIL); }
// IIS should have granted our impersonated token the proper access
// rights to check the ACL's on the resource. So we are going to go
// after it without any change of impersonation.
//
dwRet = GetNamedSecurityInfoW (const_cast<LPWSTR>(pwsz), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, NULL, NULL, NULL, NULL, reinterpret_cast<VOID **>(&pSD)); if (ERROR_SUCCESS != dwRet) { // If the resource does not exist at all, as no security prevent
// us from trying to access a non-existing resource, so we
// should allow the access.
//
if ((dwRet == ERROR_PATH_NOT_FOUND) || (dwRet == ERROR_FILE_NOT_FOUND)) { fAccess = TRUE; goto ret; }
// Now then... If we got here, we don't really know what went wrong,
// so we are going to try and do things the old way.
//
// BTW: We really do not expect this code to ever get run.
//
DebugTrace ("WARNING: WARNING: WARNING: ScChildISAPIAccessCheck() -- " "GetNamedSecurityInfoW() failed %d (0x%08x): falling back...\n", dwRet, dwRet);
// Scope to control the lifetime of our un-impersonation.
//
safe_security_revert sr (ecb.HitUser());
dwRet = GetNamedSecurityInfoW (const_cast<LPWSTR>(pwsz), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, NULL, NULL, NULL, NULL, reinterpret_cast<VOID **>(&pSD)); if (ERROR_SUCCESS != dwRet) { // If the resource does not exist at all, as no security prevent
// us from trying to access a non-existing resource, so we
// should allow the access.
//
if ((dwRet == ERROR_PATH_NOT_FOUND) || (dwRet == ERROR_FILE_NOT_FOUND)) { fAccess = TRUE; } goto ret; }
// End of safe_security_revert scope.
// Now the safe_security_revert dtor will re-impersonate us.
//
}
// Get our thread's access token.
// OpenAsSelf -- TRUE means open the thread token as the process
// itself FALSE would mean as thread, possibly impersonated
// We want the impersonated access token, so we want FALSE here!
//
fRet = OpenThreadToken (GetCurrentThread(), TOKEN_QUERY, TRUE, hToken.load()); if (!fRet) { // This should NEVER fail. We are impersonated, so we do have
// a thread-level access token. If conditions change, and we
// have a state where this can fail, remove the TrapSz below!
//
//$ REVIEW: OpenThreadToken() can fail for any number of reasons
// not excluding resource availability. So, this trap is a bit
// harsh, no?
//
// TrapSz("OpenThreadToken failed while we are impersonated!");
//
//$ REVIEW: end.
DebugTrace ("ScChildISAPIAccessCheck--" "Error from OpenThreadToken %d (0x%08x).\n", GetLastError(), GetLastError()); goto ret; }
// Map the requested access to file-specific access bits....
//
MapGenericMask (&dwAccess, &gc_gmFile);
// And now check for this access on the file.
//
fRet = AccessCheck (pSD, hToken, dwAccess, &gc_gmFile, (PRIVILEGE_SET*)psFile, &dwPS, &dwGrantedAccess, &fAccess); if (!fRet) { DebugTrace ("ScChildISAPIAccessCheck--Error from AccessCheck %d (0x%08x).\n", GetLastError(), GetLastError()); goto ret; }
// Now, fAccess tells whether the impersonated token has
// the requested access. Return this to the caller.
//
ret: if (pSD) LocalFree (pSD);
return fAccess ? S_OK : E_ACCESSDENIED; }
|