Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

819 lines
19 KiB

/*++
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 <or.hxx>
#include <aclapi.h>
#include <winsaferp.h>
#include <access.hxx>
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;
}