/*++ Copyright (c) 1995 Microsoft Corporation Module Name: Token.cxx Abstract: Implementation for Windows NT security interfaces. Platform: Windows NT user mode. Notes: Not portable to non-Windows NT platforms. Author: Mario Goertzel [MarioGo] Revision History: MarioGo 12/21/1995 Bits 'n pieces --*/ #include #include #include #include CRITICAL_SECTION gcsTokenLock; ORSTATUS LookupOrCreateTokenFromHandle( IN HANDLE hClientToken, OUT CToken **ppToken ) /*++ Routine Description: Finds or allocates a new token object for the caller. Arguments: hCaller - RPC binding handle of the caller of RPCSS. pToken - Upon a successful return this will hold the token. It can be destroyed by calling Release(); Return Value: OR_OK - success OR_NOMEM - Unable to allocate an object. --*/ { ORSTATUS status; UINT type; LUID luid; PTOKEN_USER ptu; TOKEN_STATISTICS ts; BOOL fSuccess; DWORD needed; HANDLE hJobObject = NULL; LUID luidMod; needed = sizeof(ts); fSuccess = GetTokenInformation(hClientToken, TokenStatistics, &ts, sizeof(ts), &needed ); if (!fSuccess) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: GetTokenInfo failed %d\n", GetLastError())); ASSERT(GetLastError() != ERROR_INSUFFICIENT_BUFFER); status = OR_NOMEM; goto Cleanup; } luid = ts.AuthenticationId; luidMod = ts.ModifiedId; // // Check if the token is already in the list // { CMutexLock lock(&gcsTokenLock); CListElement *ple; ple = gpTokenList->First(); fSuccess = FALSE; while(ple) { CToken *pToken = CToken::ContainingRecord(ple); if (pToken->MatchLuid(luid) && (pToken->MatchModifiedLuid(luidMod)) && (S_OK == pToken->MatchToken(hClientToken, TRUE))) { pToken->AddRef(); *ppToken = pToken; status = OR_OK; fSuccess = TRUE; break; } else { ple = ple->Next(); } } } if (fSuccess) { status = OR_OK; CloseHandle(hClientToken); goto Cleanup; } // // New user, need to allocate a token object. // // Lookup the SID to store in the new token object. needed = DEBUG_MIN(1, 0x2c); do { ptu = (PTOKEN_USER)alloca(needed); ASSERT(ptu); fSuccess = GetTokenInformation(hClientToken, TokenUser, (PBYTE)ptu, needed, &needed); // If this assert is hit increase the 24 both here and above ASSERT(needed <= 0x2c); } while ( fSuccess == FALSE && GetLastError() == ERROR_INSUFFICIENT_BUFFER); if (!fSuccess) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: GetTokenInfo (2) failed %d\n", GetLastError())); ASSERT(GetLastError() != ERROR_INSUFFICIENT_BUFFER); status = OR_NOMEM; goto Cleanup; } PSID psid; psid = ptu->User.Sid; ASSERT(IsValidSid(psid) == TRUE); // Allocate the token object needed = GetLengthSid(psid) - sizeof(SID); *ppToken = new(needed) CToken(hClientToken, hJobObject, luid, psid, needed + sizeof(SID)); if (*ppToken) { CMutexLock lock(&gcsTokenLock); (*ppToken)->Insert(); status = OR_OK; #if DBG_DETAIL { DWORD d = 50; WCHAR buffer[50]; GetUserName(buffer, &d); KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: New user connected: %S (%p)\n", buffer, *ppToken)); } #endif } else { status = OR_NOMEM; } Cleanup: if (OR_OK != status) { if (NULL != hJobObject) CloseHandle(hJobObject); } // status contains the result of the operation. return(status); } ORSTATUS LookupOrCreateTokenForRPCClient( IN handle_t hCaller, IN BOOL fAllowUnsecure, OUT CToken **ppToken, OUT BOOL* pfUnsecure ) /*++ Routine Description: Finds or allocates a new token object for the caller. Arguments: hCaller - RPC binding handle of the caller of RPCSS. fAllowUnsecure - whether to return a token for an unsecure caller, or an error instead. pToken - Upon a successful return this will hold the token. It can be destroyed by calling Release(); pfUnsecure - Upon a successful return this will determine if the caller was unsecure or not. Return Value: OR_OK - success OR_NOACCESS - error occurred, or client was unsecure and !fAllowUnsecure OR_NOMEM - Unable to allocate an object. --*/ { RPC_STATUS status = RPC_S_OK; HANDLE hClientToken = 0; BOOL fSuccess = FALSE; BOOL fUsedAnonymousToken = FALSE; if (pfUnsecure) *pfUnsecure = FALSE; status = RpcImpersonateClient(hCaller); if (status == RPC_S_CANNOT_SUPPORT) { // Check if the caller will accept an unsecure client if (!fAllowUnsecure) { return OR_NOACCESS; } // // Getting back RPC_S_CANNOT_SUPPORT from RpcImpClient // signifies that the client is _intentionally_ making an // unsecure call. Until .NET Server we had a lot of // custom code for handling this case in RPCSS. Now we map // such users to the Anonymous identity. This is a DCOM-specific // policy decision. The reasoning behind this is that it allows // COM servers (that care) to allow access to such clients access // in a programmatic way by granting access to Anonymous. See // comments in CheckForAccess in ole32\dcomss\olescm\security.cxx. // fSuccess = ImpersonateAnonymousToken(GetCurrentThread()); if (!fSuccess) { return OR_NOACCESS; } fUsedAnonymousToken = TRUE; // This caller is considered "unsecure" if (pfUnsecure) *pfUnsecure = TRUE; } else if (status != RPC_S_OK) { return OR_NOACCESS; } fSuccess = OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hClientToken); if (fSuccess) { status = LookupOrCreateTokenFromHandle(hClientToken, ppToken); if(OR_OK == status) { // The token object now controls the life of the token handle hClientToken = 0; } else { CloseHandle(hClientToken); hClientToken = 0; } } else { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: OpenThreadToken failed %d\n", GetLastError())); status = OR_NOMEM; ASSERT(hClientToken == 0); goto Cleanup; } Cleanup: // status contains the result of the operation. if (fUsedAnonymousToken) { fSuccess = RevertToSelf(); ASSERT(fSuccess); } else { RPC_STATUS t = RpcRevertToSelfEx(hCaller); ASSERT(t == RPC_S_OK); } return(status); } CToken::~CToken() { ASSERT(_lHKeyRefs == 0); ASSERT(_hHKCRKey == NULL); if (_hHKCRKey != NULL) { // Shouldn't happen...but close it anyway just in // case. Assert above will catch this if it occurs RegCloseKey(_hHKCRKey); } CloseHandle(_hImpersonationToken); if (NULL != _hJobObject) { TerminateJobObject(_hJobObject, 0); CloseHandle(_hJobObject); } } STDMETHODIMP CToken::QueryInterface(REFIID riid, LPVOID* ppv) { if (riid == IID_IUnknown || riid == IID_IUserToken) { *ppv = this; AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) CToken::AddRef() { return InterlockedIncrement(&_lRefs); } STDMETHODIMP_(ULONG) CToken::Release() { LONG lNewRefs; CMutexLock lock(&gcsTokenLock); lNewRefs = InterlockedDecrement(&_lRefs); if (lNewRefs == 0) { Remove(); delete this; } return lNewRefs; } STDMETHODIMP CToken::GetUserClassesRootKey(HKEY* phKey) { CMutexLock lock(&gcsTokenLock); HRESULT hr = S_OK; if ( _lHKeyRefs++ == 0 ) { ASSERT(_hHKCRKey == NULL); // The original IUserToken implementation allowed for not // having a token. That should never happen with a CToken. ASSERT(_hImpersonationToken); // Open per-user hive LONG lRet = RegOpenUserClassesRoot(_hImpersonationToken, 0, KEY_READ, &_hHKCRKey); if (lRet != ERROR_SUCCESS) { // Failed to open the user's HKCR. We're going to use // HKLM\Software\Classes. lRet = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Classes", 0, KEY_READ, &_hHKCRKey); if (lRet != ERROR_SUCCESS) { _hHKCRKey = NULL; --_lHKeyRefs; hr = HRESULT_FROM_WIN32(lRet); } } } *phKey = _hHKCRKey; return hr; } STDMETHODIMP CToken::ReleaseUserClassesRootKey() { CMutexLock lock(&gcsTokenLock); if (_hHKCRKey && (--_lHKeyRefs == 0) ) { ASSERT(_hHKCRKey != HKEY_CLASSES_ROOT); RegCloseKey(_hHKCRKey); _hHKCRKey = NULL; } return S_OK; } STDMETHODIMP CToken::GetUserSid(BYTE **ppSid, USHORT *pcbSid) { // IUserToken interface assumes that sid lengths always // <= USHRT_MAX. Truncating here on purpose; assert is // to catch cases where this is a bad idea. GetLengthSid // is a very cheap call, so there's no need to cache it. DWORD dwSidLen = GetLengthSid(&_sid); ASSERT(dwSidLen <= USHRT_MAX); *pcbSid = (USHORT)dwSidLen; *ppSid = (BYTE*)&_sid; return S_OK; } STDMETHODIMP CToken::GetUserToken(HANDLE* phToken) { *phToken = _hImpersonationToken; return S_OK; } void CToken::Impersonate() { ASSERT(_hImpersonationToken); BOOL f = SetThreadToken(0, _hImpersonationToken); ASSERT(f); return; } void CToken::Revert() { BOOL f = SetThreadToken(0, 0); ASSERT(f); return; } ULONG GetSessionId2( HANDLE hToken) { BOOL Result; ULONG SessionId = 0; ULONG ReturnLength; // // Use the _HYDRA_ extension to GetTokenInformation to // return the SessionId from the token. // Result = GetTokenInformation( hToken, TokenSessionId, &SessionId, sizeof(SessionId), &ReturnLength ); if( !Result ) { SessionId = 0; // Default to console } return SessionId; } ULONG CToken::GetSessionId() { return GetSessionId2(_hImpersonationToken); } BOOL CToken::MatchModifiedLuid(LUID luid) { ASSERT(_hImpersonationToken); TOKEN_STATISTICS ts; BOOL fSuccess; DWORD needed; LUID luidMod; needed = sizeof(ts); fSuccess = GetTokenInformation(_hImpersonationToken, TokenStatistics, &ts, sizeof(ts), &needed ); if (!fSuccess) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: GetTokenInfo failed %d\n", GetLastError())); ASSERT(GetLastError() != ERROR_INSUFFICIENT_BUFFER); return FALSE; } luidMod = ts.ModifiedId; return( luidMod.LowPart == luid.LowPart && luidMod.HighPart == luid.HighPart); } HRESULT CompareRestrictedSids( HANDLE hToken1, HANDLE hToken2) { HRESULT hr = S_OK; PSID pRestrictedSid1 = NULL; PSID pRestrictedSid2 = NULL; #if(_WIN32_WINNT >= 0x0500) PTOKEN_GROUPS pSids1; PTOKEN_GROUPS pSids2; NTSTATUS error; ULONG needed; //Get restricted SIDs. needed = DEBUG_MIN(1, 300); do { pSids1 = (PTOKEN_GROUPS) alloca(needed); error = NtQueryInformationToken(hToken1, TokenRestrictedSids, pSids1, needed, &needed); } while (error == STATUS_BUFFER_TOO_SMALL); if(!error && pSids1->GroupCount > 0) { pRestrictedSid1 = pSids1->Groups[0].Sid; } //Get restricted SIDs. needed = DEBUG_MIN(1, 300); do { pSids2 = (PTOKEN_GROUPS) alloca(needed); error = NtQueryInformationToken(hToken2, TokenRestrictedSids, pSids2, needed, &needed); } while (error == STATUS_BUFFER_TOO_SMALL); if(!error && pSids2->GroupCount > 0) { pRestrictedSid2 = pSids2->Groups[0].Sid; } if(pRestrictedSid1 && pRestrictedSid2) { //We have two restricted tokens. //Compare the first restricted SID. if(EqualSid(pRestrictedSid1, pRestrictedSid2)) { hr = S_OK; } else { hr = S_FALSE; } } else if(pRestrictedSid1 || pRestrictedSid2) { //We have one restricted token and one normal token. hr = S_FALSE; } else { //We have two normal tokens. hr = S_OK; } #endif //(_WIN32_WINNT >= 0x0500) return hr; } HRESULT CToken::MatchToken( IN HANDLE hToken, IN BOOL bMatchRestricted) { HRESULT hr; NTSTATUS error; PTOKEN_USER ptu; DWORD needed = DEBUG_MIN(1, 0x2c); //Get the user SID. do { ptu = (PTOKEN_USER)alloca(needed); error = NtQueryInformationToken(hToken, TokenUser, (PBYTE)ptu, needed, &needed); // If this assert is hit increase the 24 both here and above ASSERT(needed <= 0x2c); } while (error == STATUS_BUFFER_TOO_SMALL); if (error) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: GetTokenInfo (2) failed %d\n", error)); return HRESULT_FROM_WIN32(error); } //Compare the user SID. if(!EqualSid(ptu->User.Sid, &_sid)) return S_FALSE; //Compare the Hydra session ID. if(GetSessionId2(hToken) != GetSessionId()) return S_FALSE; //Compare the restricted SID. if (bMatchRestricted) hr = CompareRestrictedSids(hToken, _hImpersonationToken); else hr = S_OK; return hr; } HRESULT CToken::MatchToken2( IN CToken *pToken, IN BOOL bMatchRestricted) { HRESULT hr; if(!pToken) return S_OK; //Compare the user SID. if(!EqualSid(&pToken->_sid, &_sid)) return S_FALSE; //Compare the Hydra session id. if(GetSessionId2(pToken->_hImpersonationToken) != GetSessionId()) return S_FALSE; //Compare the restricted SID. if (bMatchRestricted) hr = CompareRestrictedSids(pToken->_hImpersonationToken, _hImpersonationToken); else hr = S_OK; return hr; } HRESULT CToken::CompareSaferLevels(CToken *pToken) /*++ Routine Description: Compare the safer trust level of the specified token with our own. Arguments: pToken - token to compare against Return Value: S_FALSE: This token is of lesser authorization than the other token. S_OK: This token is of greater or equal authorization than the other token. Anything else: An error occured. --*/ { if (!pToken) return S_OK; return CompareSaferLevels(pToken->_hImpersonationToken); } HRESULT CToken::CompareSaferLevels(HANDLE hToken) /*++ Routine Description: Compare the safer trust level of the specified token with our own. Arguments: hToken - token to compare against Return Value: S_FALSE: This token is of lesser authorization than the other token. S_OK: This token is of greater or equal authorization than the other token. Anything else: An error occured. --*/ { HRESULT hr = S_OK; DWORD dwResult; BOOL bRet = SaferiCompareTokenLevels(_hImpersonationToken, hToken, &dwResult); if (bRet) { // -1 = Client's access token (_hImpersonationToken) is more authorized // than Server's (hToken). if ( ((LONG)dwResult) > 0 ) hr = S_FALSE; } else hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // NT #307301 // Sometimes we just need to check the SessionID HRESULT CToken::MatchTokenSessionID(CToken *pToken) { //Compare the Hydra session id. if(GetSessionId2(pToken->_hImpersonationToken) != GetSessionId()) return S_FALSE; return S_OK; } // // MatchTokenLUID // // Compares this token's LUID to that of the passed in token. // Returns S_OK on a match, S_FALSE on a mismatch. // HRESULT CToken::MatchTokenLuid(CToken* pToken) { return MatchLuid(pToken->_luid) ? S_OK : S_FALSE; }