NT4/private/ole32/com/dcomrem/security.cxx
2020-09-30 17:12:29 +02:00

3061 lines
100 KiB
C++

//+-------------------------------------------------------------------
//
// File: security.cxx
//
// Copyright (c) 1996-1996, Microsoft Corp. All rights reserved.
//
// Contents: Classes for channel security
//
// Classes: CClientSecurity, CServerSecurity
//
// History: 11 Oct 95 AlexMit Created
//
//--------------------------------------------------------------------
#include <ole2int.h>
#include <locks.hxx>
#include <security.hxx>
#include <channelb.hxx>
#include <ipidtbl.hxx>
#include <resolver.hxx>
#include <service.hxx>
#include <oleext.h>
#include <stream.hxx>
#ifdef _CHICAGO_
#include <apiutil.h>
#include <wksta.h>
#endif
#ifdef DCOM_SECURITY
/**********************************************************************/
// Definitions.
// Versions of the permissions in the registry.
const WORD COM_PERMISSION_SECDESC = 1;
const WORD COM_PERMISSION_ACCCTRL = 2;
// Guess length of user name.
const DWORD SIZEOF_NAME = 80;
// This leaves space for 8 sub authorities. Currently NT only uses 6 and
// Cairo uses 7.
const DWORD SIZEOF_SID = 44;
// This leaves space for 2 access allowed ACEs in the ACL.
const DWORD SIZEOF_ACL = sizeof(ACL) + 2 * sizeof(ACCESS_ALLOWED_ACE) +
2 * SIZEOF_SID;
const DWORD SIZEOF_TOKEN_USER = sizeof(TOKEN_USER) + SIZEOF_SID;
const SID LOCAL_SYSTEM_SID = {SID_REVISION, 1, {0,0,0,0,0,5},
SECURITY_LOCAL_SYSTEM_RID };
const DWORD NUM_SEC_PKG = 8;
const DWORD ACCESS_CACHE_LEN = 5;
const DWORD VALID_INIT_FLAGS = EOAC_SECURE_REFS | EOAC_MUTUAL_AUTH |
EOAC_ACCESS_CONTROL | EOAC_APPID | EOAC_DYNAMIC;
// Remove this for NT 5.0 when we link to oleext.lib
const IID IID_IAccessControl = {0xEEDD23E0,0x8410,0x11CE,{0xA1,0xC3,0x08,0x00,0x2B,0x2B,0x8D,0x8F}};
// Stores results of AccessCheck.
typedef struct
{
BOOL fAccess;
DWORD lHash;
SID sid;
} SAccessCache;
// Header in access permission key.
typedef struct
{
WORD wVersion;
WORD wPad;
GUID gClass;
} SPermissionHeader;
#ifdef _CHICAGO_
typedef unsigned
(*NetWkstaGetInfoFn) ( const char FAR * pszServer,
short sLevel,
char FAR * pbBuffer,
unsigned short cbBuffer,
unsigned short FAR * pcbTotalAvail );
#endif
/**********************************************************************/
// Externals.
EXTERN_C const IID IID_IObjServer;
/**********************************************************************/
// Prototypes.
void CacheAccess ( SID *pSid, BOOL fAccess );
BOOL CacheAccessCheck ( SID *pSid, BOOL *pAccess );
HRESULT CopySecDesc ( SECURITY_DESCRIPTOR *pOrig,
SECURITY_DESCRIPTOR **pCopy );
HRESULT FixupAccessControl ( SECURITY_DESCRIPTOR **pSD, DWORD cbSD );
HRESULT FixupSecurityDescriptor( SECURITY_DESCRIPTOR **pSD, DWORD cbSD );
HRESULT GetLegacySecDesc ( SECURITY_DESCRIPTOR **, DWORD * );
HRESULT GetRegistrySecDesc ( HKEY, WCHAR *pValue,
SECURITY_DESCRIPTOR **pSD, DWORD * );
DWORD HashSid ( SID * );
BOOL IsLocalAuthnService ( USHORT wAuthnService );
HRESULT MakeSecDesc ( SECURITY_DESCRIPTOR **, DWORD * );
HRESULT DefaultAuthnServices ( void );
HRESULT RegisterAuthnServices ( DWORD cbSvc, SOLE_AUTHENTICATION_SERVICE * );
#ifndef _CHICAGO_
HRESULT LookupPrincName ( WCHAR ** );
#else
HRESULT LookupPrincName(
USHORT *pwAuthnServices,
ULONG cAuthnServices,
WCHAR **pPrincName
);
#endif // _CHICAGO_
/**********************************************************************/
// Globals.
// These variables hold the default authentication information.
DWORD gAuthnLevel = RPC_C_AUTHN_LEVEL_NONE;
DWORD gImpLevel = RPC_C_IMP_LEVEL_IDENTIFY;
DWORD gCapabilities = EOAC_NONE;
SECURITYBINDING *gLegacySecurity = NULL;
// These variables define a list of security providers OLE clients can
// use and a list OLE servers can use.
USHORT *gClientSvcList = NULL;
DWORD gClientSvcListLen = 0;
USHORT *gServerSvcList = NULL;
DWORD gServerSvcListLen = 0;
// gDisableDCOM is read from the registry by CRpcResolver::GetConnection.
// If TRUE, all machine remote calls will be failed. It is set TRUE in WOW.
BOOL gDisableDCOM = FALSE;
// Set TRUE when CRpcResolver::GetConnection initializes the previous globals.
BOOL gGotSecurityData = FALSE;
// The security descriptor to check when new connections are established.
// gAccessControl and gSecDesc will not both be nonNULL at the same time.
IAccessControl *gAccessControl = NULL;
SECURITY_DESCRIPTOR *gSecDesc = NULL;
// The security string array. If gDefaultService is TRUE, compute the
// security string array the first time a remote protocol sequence is
// registered.
DUALSTRINGARRAY *gpsaSecurity = NULL;
BOOL gDefaultService = FALSE;
// The security descriptor to check in RundownOID.
SECURITY_DESCRIPTOR *gRundownSD = NULL;
// Don't map any of the generic bits to COM_RIGHTS_EXECUTE or any other bit.
GENERIC_MAPPING gMap = { 0, 0, 0, 0 };
PRIVILEGE_SET gPriv = { 1, 0 };
// Cache of results of calls to AccessCheck.
SAccessCache *gAccessCache[ACCESS_CACHE_LEN] = {NULL, NULL, NULL, NULL, NULL};
DWORD gMostRecentAccess = 0;
//+-------------------------------------------------------------------
//
// Function: CacheAccess
//
// Synopsis: Store the results of the access check in the cache.
//
//--------------------------------------------------------------------
void CacheAccess( SID *pSid, BOOL fAccess )
{
SAccessCache *pNew;
DWORD cbSid;
ASSERT_LOCK_RELEASED
LOCK
// Allocate a new record.
cbSid = GetLengthSid( pSid );
pNew = (SAccessCache *) PrivMemAlloc( sizeof(SAccessCache) + cbSid -
sizeof(SID) );
// Initialize the record.
if (pNew != NULL)
{
pNew->fAccess = fAccess;
pNew->lHash = HashSid( pSid );
memcpy( &pNew->sid, pSid, cbSid );
// Free the old record and insert the new.
gMostRecentAccess += 1;
if (gMostRecentAccess >= ACCESS_CACHE_LEN)
gMostRecentAccess = 0;
PrivMemFree( gAccessCache[gMostRecentAccess] );
gAccessCache[gMostRecentAccess] = pNew;
}
UNLOCK
ASSERT_LOCK_RELEASED
}
//+-------------------------------------------------------------------
//
// Function: CacheAccessCheck
//
// Synopsis: Look for the specified SID in the cache. If found,
// return the results of the cached access check.
//
//--------------------------------------------------------------------
BOOL CacheAccessCheck( SID *pSid, BOOL *pAccess )
{
DWORD i;
DWORD lHash = HashSid( pSid );
DWORD j;
BOOL fFound = FALSE;
SAccessCache *pSwap;
ASSERT_LOCK_RELEASED
LOCK
// Look for the SID.
j = gMostRecentAccess;
for (i = 0; i < ACCESS_CACHE_LEN; i++)
{
if (gAccessCache[j] != NULL &&
gAccessCache[j]->lHash == lHash &&
EqualSid( pSid, &gAccessCache[j]->sid ))
{
// Move this entry to the head.
fFound = TRUE;
*pAccess = gAccessCache[j]->fAccess;
pSwap = gAccessCache[gMostRecentAccess];
gAccessCache[gMostRecentAccess] = gAccessCache[j];
gAccessCache[j] = pSwap;
break;
}
if (j == 0)
j = ACCESS_CACHE_LEN - 1;
else
j -= 1;
}
UNLOCK
ASSERT_LOCK_RELEASED
return fFound;
}
//+-------------------------------------------------------------------
//
// Member: CClientSecurity::CopyProxy, public
//
// Synopsis: Create a new IPID entry for the specified IID.
//
//--------------------------------------------------------------------
STDMETHODIMP CClientSecurity::CopyProxy( IUnknown *pProxy, IUnknown **ppCopy )
{
// Make sure TLS is initialized on this thread.
HRESULT hr;
COleTls tls(hr);
if (FAILED(hr))
return hr;
// Ask the marshaller to copy the proxy.
return _pStdId->PrivateCopyProxy( pProxy, ppCopy );
}
//+-------------------------------------------------------------------
//
// Member: CClientSecurity::QueryBlanket, public
//
// Synopsis: Get the binding handle for a proxy. Query RPC for the
// authentication information for that handle.
//
//--------------------------------------------------------------------
STDMETHODIMP CClientSecurity::QueryBlanket(
IUnknown *pProxy,
DWORD *pAuthnSvc,
DWORD *pAuthzSvc,
OLECHAR **pServerPrincName,
DWORD *pAuthnLevel,
DWORD *pImpLevel,
void **pAuthInfo,
DWORD *pCapabilities )
{
HRESULT hr;
IPIDEntry *pIpid;
RPC_STATUS sc;
DWORD iLen;
OLECHAR *pCopy;
handle_t hHandle;
IRemUnknown *pRemUnk = NULL;
RPC_SECURITY_QOS sQos;
ASSERT_LOCK_RELEASED
LOCK
// Initialize all out parameters to default values.
if (pServerPrincName != NULL)
*pServerPrincName = NULL;
if (pAuthnLevel != NULL)
*pAuthnLevel = RPC_C_AUTHN_LEVEL_PKT_PRIVACY;
if (pImpLevel != NULL)
*pImpLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
if (pAuthnSvc != NULL)
*pAuthnSvc = RPC_C_AUTHN_WINNT;
if (pAuthInfo != NULL)
*pAuthInfo = NULL;
if (pAuthzSvc != NULL)
*pAuthzSvc = RPC_C_AUTHZ_NONE;
if (pCapabilities != NULL)
*pCapabilities = EOAC_NONE;
// For IUnknown just call QueryBlanket on the IRemUnknown of
// the IPID or the OXID.
if (_pStdId->GetCtrlUnk() == pProxy)
{
pIpid = _pStdId->GetConnectedIPID();
hr = _pStdId->GetSecureRemUnk( &pRemUnk, pIpid->pOXIDEntry );
if (pRemUnk != NULL)
{
UNLOCK
hr = CoQueryProxyBlanket( pRemUnk, pAuthnSvc, pAuthzSvc,
pServerPrincName, pAuthnLevel,
pImpLevel, pAuthInfo, pCapabilities );
LOCK
}
}
// Find the right IPID entry.
else
{
hr = _pStdId->FindIPIDEntryByInterface( pProxy, &pIpid );
if (SUCCEEDED(hr))
{
// Disallow server entries.
if (pIpid->dwFlags & IPIDF_SERVERENTRY)
hr = E_INVALIDARG;
// No security for disconnected proxies.
else if (pIpid->dwFlags & IPIDF_DISCONNECTED)
hr = RPC_E_DISCONNECTED;
// If it is local, use the default values for everything but the
// impersonation level.
else if (pIpid->pChnl->ProcessLocal())
{
if (pImpLevel != NULL)
*pImpLevel = pIpid->pChnl->GetImpLevel();
}
// Otherwise ask RPC.
else
{
hr = pIpid->pChnl->GetHandle( &hHandle );
if (SUCCEEDED(hr))
{
sc = RpcBindingInqAuthInfoExW( hHandle,
pServerPrincName, pAuthnLevel,
pAuthnSvc, pAuthInfo,
pAuthzSvc,
RPC_C_SECURITY_QOS_VERSION,
&sQos );
// RPC sometimes sets out parameters on error.
if (sc != RPC_S_OK)
{
if (pServerPrincName != NULL)
*pServerPrincName = NULL;
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, sc );
}
else
{
// Return the impersonation level and capabilities.
if (pImpLevel != NULL)
*pImpLevel = sQos.ImpersonationType;
if (pCapabilities != NULL)
if (sQos.Capabilities & RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH)
*pCapabilities = EOAC_MUTUAL_AUTH;
else
*pCapabilities = EOAC_NONE;
// Reallocate the principle name using the OLE memory allocator.
if (pServerPrincName != NULL && *pServerPrincName != NULL)
{
iLen = lstrlenW( *pServerPrincName ) + 1;
pCopy = (OLECHAR *) CoTaskMemAlloc( iLen * sizeof(OLECHAR) );
if (pCopy != NULL)
memcpy( pCopy, *pServerPrincName, iLen*sizeof(USHORT) );
else
hr = E_OUTOFMEMORY;
RpcStringFree( pServerPrincName );
*pServerPrincName = pCopy;
}
}
}
}
}
}
UNLOCK
ASSERT_LOCK_RELEASED
return hr;
}
//+-------------------------------------------------------------------
//
// Member: CClientSecurity::SetBlanket, public
//
// Synopsis: Get the binding handle for a proxy. Call RPC to set the
// authentication information for that handle.
//
//--------------------------------------------------------------------
STDMETHODIMP CClientSecurity::SetBlanket(
IUnknown *pProxy,
DWORD AuthnSvc,
DWORD AuthzSvc,
OLECHAR *pServerPrincName,
DWORD AuthnLevel,
DWORD ImpLevel,
void *pAuthInfo,
DWORD Capabilities )
{
HRESULT hr;
IPIDEntry *pIpid;
RPC_STATUS sc;
BOOL fSuccess;
HANDLE hToken = NULL;
HANDLE hProcess;
handle_t hHandle;
IRemUnknown *pRemUnk;
IRemUnknown *pSecureRemUnk = NULL;
RPC_SECURITY_QOS sQos;
SECURITY_IMPERSONATION_LEVEL eDuplicate;
DWORD dwOpen;
ASSERT_LOCK_RELEASED
// IUnknown is special. Set the security on IRemUnknown instead.
if (_pStdId->GetCtrlUnk() == pProxy)
{
// Make sure the identity has its own copy of the OXID's
// IRemUnknown.
if (!_pStdId->CheckSecureRemUnk())
{
// This will get the remote unknown from the OXID.
LOCK
pIpid = _pStdId->GetConnectedIPID();
hr = _pStdId->GetSecureRemUnk( &pRemUnk, pIpid->pOXIDEntry );
if (SUCCEEDED(hr))
{
UNLOCK
hr = CoCopyProxy( pRemUnk, (IUnknown **) &pSecureRemUnk );
LOCK
if (SUCCEEDED(hr))
{
// Remote Unknown proxies are not supposed to ref count
// the OXID.
pIpid->pOXIDEntry->cRefs -= 1;
// Only keep the proxies if no one else made a copy
// while this thread was making a copy.
if (!_pStdId->CheckSecureRemUnk())
_pStdId->SetSecureRemUnk( pSecureRemUnk );
else
{
pSecureRemUnk->Release();
hr = _pStdId->GetSecureRemUnk( &pSecureRemUnk, NULL );
}
}
}
UNLOCK
}
else
hr = _pStdId->GetSecureRemUnk( &pSecureRemUnk, NULL );
// Call SetBlanket on the copy of IRemUnknown.
if (pSecureRemUnk != NULL)
hr = CoSetProxyBlanket( pSecureRemUnk, AuthnSvc, AuthzSvc,
pServerPrincName, AuthnLevel,
ImpLevel, pAuthInfo, Capabilities );
}
else
{
// Find the right IPID entry.
LOCK
hr = _pStdId->FindIPIDEntryByInterface( pProxy, &pIpid );
if (SUCCEEDED(hr))
{
// Disallow server entries.
if (pIpid->dwFlags & IPIDF_SERVERENTRY)
hr = E_INVALIDARG;
// No security for disconnected proxies.
else if (pIpid->dwFlags & IPIDF_DISCONNECTED)
hr = RPC_E_DISCONNECTED;
else if (pIpid->pChnl->ProcessLocal())
{
// Local calls can use no authn service or winnt.
if (AuthnSvc != RPC_C_AUTHN_NONE &&
AuthnSvc != RPC_C_AUTHN_WINNT)
hr = E_INVALIDARG;
// Make sure the authentication level is not invalid.
else if ((AuthnSvc == RPC_C_AUTHN_NONE &&
AuthnLevel != RPC_C_AUTHN_LEVEL_NONE) ||
(AuthnSvc == RPC_C_AUTHN_WINNT &&
AuthnLevel > RPC_C_AUTHN_LEVEL_PKT_PRIVACY))
hr = E_INVALIDARG;
// No authorization services are supported locally.
else if (AuthzSvc != RPC_C_AUTHZ_NONE)
hr = E_INVALIDARG;
// You cannot supply credentials locally.
else if (pAuthInfo != NULL)
hr = E_INVALIDARG;
// Impersonation is not legal yet.
else if (ImpLevel != RPC_C_IMP_LEVEL_IMPERSONATE &&
ImpLevel != RPC_C_IMP_LEVEL_IDENTIFY)
hr = E_INVALIDARG;
// No capabilities are supported yet.
else if (Capabilities != EOAC_NONE)
hr = E_INVALIDARG;
// Don't do delegation for NT 4.0
#ifndef _SOME_FUTURE_PRODUCT_
pIpid->pChnl->SetAuthnLevel( AuthnLevel );
pIpid->pChnl->SetImpLevel( ImpLevel );
#else
// Save the user token if the app asked for security.
else if (AuthnLevel != RPC_C_AUTHN_LEVEL_NONE)
{
if (ImpLevel == RPC_C_IMP_LEVEL_IMPERSONATE)
{
eDuplicate = SecurityImpersonation;
dwOpen = TOKEN_IMPERSONATE;
}
else
{
eDuplicate = SecurityIdentification;
dwOpen = TOKEN_QUERY;
}
fSuccess = OpenThreadToken( GetCurrentThread(), dwOpen,
TRUE, &hToken );
hr = GetLastError();
// If the application is not impersonating, no thread token
// will be present. Get the process token instead.
if (!fSuccess && hr == ERROR_NO_TOKEN)
{
fSuccess = OpenProcessToken( GetCurrentProcess(),
TOKEN_DUPLICATE, &hProcess );
if (fSuccess)
{
fSuccess = DuplicateToken( hProcess, eDuplicate,
&hToken );
CloseHandle( hProcess );
}
}
if (fSuccess)
{
hToken = pIpid->pChnl->SwapSecurityToken( hToken );
pIpid->pChnl->SetAuthnLevel( AuthnLevel );
pIpid->pChnl->SetImpLevel( ImpLevel );
hr = S_OK;
}
else
{
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
hToken = pIpid->pChnl->SwapSecurityToken( NULL );
}
CloseHandle( hToken );
}
// If there was an old token, toss it.
else if (pIpid->pChnl->GetSecurityToken() != NULL)
{
hToken = pIpid->pChnl->SwapSecurityToken( NULL );
CloseHandle( hToken );
pIpid->pChnl->SetAuthnLevel( AuthnLevel );
pIpid->pChnl->SetImpLevel( ImpLevel );
}
#endif // !_SOME_FUTURE_PRODUCT_
}
// If it is remote, tell RPC.
else
{
// Validate the capabilities.
if (Capabilities & ~ EOAC_MUTUAL_AUTH)
hr = E_INVALIDARG;
else
hr = pIpid->pChnl->GetHandle( &hHandle );
if (SUCCEEDED(hr))
{
#ifdef _CHICAGO_
// If the principal name is not known, the server must be
// NT. Replace the principal name in that case
// because a NULL principal name is a flag for some
// Chicago security hack.
if (pServerPrincName == NULL &&
AuthnSvc == RPC_C_AUTHN_WINNT &&
(pIpid->pOXIDEntry->dwFlags & OXIDF_MACHINE_LOCAL) == 0)
pServerPrincName = L"Default";
#endif // _CHICAGO_
// Suspend any outstanding impersonation and ignore failures.
COleTls tls(hr);
BOOL resume = FALSE;
if (SUCCEEDED(hr))
SuspendImpersonate( tls->pCallContext, &resume );
else
hr = S_OK;
sQos.Version = RPC_C_SECURITY_QOS_VERSION;
sQos.IdentityTracking = RPC_C_QOS_IDENTITY_STATIC;
sQos.ImpersonationType = ImpLevel;
sQos.Capabilities = (Capabilities & EOAC_MUTUAL_AUTH) ?
RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH : RPC_C_QOS_CAPABILITIES_DEFAULT;
sc = RpcBindingSetAuthInfoExW( hHandle,
pServerPrincName, AuthnLevel,
AuthnSvc, pAuthInfo, AuthzSvc,
&sQos );
// Resume any outstanding impersonation.
ResumeImpersonate( tls->pCallContext, resume );
if (sc != RPC_S_OK)
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, sc );
else
pIpid->pChnl->SetAuthnLevel( AuthnLevel );
}
}
}
UNLOCK
}
ASSERT_LOCK_RELEASED
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CheckAccessControl
//
// Synopsis: Call the access control and ask it to check access.
//
//--------------------------------------------------------------------
RPC_STATUS CheckAccessControl( RPC_IF_HANDLE pIid, void *pContext )
{
HRESULT hr;
TRUSTEE_W sTrustee;
CServerSecurity sSecurity;
IUnknown *pSave;
BOOL fAccess = FALSE;
COleTls tls(hr);
#if DBG == 1
char *pFailure = "";
#endif
sTrustee.ptstrName = NULL;
if (FAILED(hr))
{
#if DBG == 1
pFailure = "Bad TLS: 0x%x\n";
#endif
}
else
{
#ifdef _CHICAGO_
// On Chicago RpcBindingInqAuthClientW doesn't work locally. Since
// IObjServer is the only interface that uses security locally on
// Chicago, allow it if the call is local.
if (pIid == NULL)
return RPC_S_OK;
else if ((*(IID *) pIid) == IID_IObjServer)
{
#if DBG == 1
pFailure = "IObjServer can't be called remotely: 0x%x\n";
#endif
hr = E_ACCESSDENIED;
}
#else
// Since IObjServer always uses dynamic impersonation, allow access here.
// It will be checked later in CheckObjactAccess.
if (pIid != NULL && *((IID *) pIid) == IID_IObjServer)
return RPC_S_OK;
#endif
if (SUCCEEDED(hr))
{
// Get the trustee name.
hr = RpcBindingInqAuthClientW( NULL,
(void **) &sTrustee.ptstrName,
NULL, NULL, NULL, NULL );
if (hr == RPC_S_OK)
{
// Save the security context in TLS.
pSave = tls->pCallContext;
tls->pCallContext = &sSecurity;
// Check access.
sTrustee.pMultipleTrustee = NULL;
sTrustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
sTrustee.TrusteeForm = TRUSTEE_IS_NAME;
sTrustee.TrusteeType = TRUSTEE_IS_USER;
hr = gAccessControl->IsAccessAllowed( &sTrustee, NULL,
COM_RIGHTS_EXECUTE, &fAccess );
#if DBG==1
if (FAILED(hr))
pFailure = "IsAccessAllowed failed: 0x%x\n";
#endif
if (SUCCEEDED(hr) && !fAccess)
{
hr = E_ACCESSDENIED;
#if DBG==1
pFailure = "IAccessControl does not allow user access.\n";
#endif
}
// Restore the security context.
tls->pCallContext = pSave;
}
#if DBG == 1
else
pFailure = "RpcBindingInqAuthClientW failed: 0x%x\n";
#endif
}
}
#if DBG==1
if (FAILED(hr))
{
ComDebOut(( DEB_WARN, "***** ACCESS DENIED *****\n" ));
ComDebOut(( DEB_WARN, pFailure, hr ));
// Print the user name.
if (sTrustee.ptstrName != NULL)
ComDebOut(( DEB_WARN, "User: %ws\n", sTrustee.ptstrName ));
}
#endif
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CheckAcl
//
// Synopsis: Impersonate and do an AccessCheck against the global ACL.
//
//--------------------------------------------------------------------
RPC_STATUS CheckAcl( RPC_IF_HANDLE pIid, void *pContext )
{
RPC_STATUS sc;
BOOL fAccess = FALSE;
BOOL fSuccess;
DWORD lGrantedAccess;
DWORD lSetLen = sizeof(gPriv);
HANDLE hToken;
DWORD i;
DWORD lSize = SIZEOF_TOKEN_USER;
TOKEN_USER *pTokenInfo = (TOKEN_USER *) _alloca( lSize );
SID *pSid = NULL;
#if DBG==1
char *pFailure = "";
#endif
// Since IObjServer always uses dynamic impersonation, allow access here.
// It will be checked later in CheckObjactAccess.
if (pIid != NULL && *((IID *) pIid) == IID_IObjServer)
return RPC_S_OK;
// Impersonate.
sc = RpcImpersonateClient( NULL );
if (sc == RPC_S_OK)
{
// Open the thread token.
fSuccess = OpenThreadToken( GetCurrentThread(), TOKEN_READ,
TRUE, &hToken );
// Revert.
RpcRevertToSelf();
if (fSuccess)
{
// Get the SID and see if its cached.
if (GetTokenInformation( hToken, TokenUser, pTokenInfo,
lSize, &lSize ))
{
pSid = (SID *) pTokenInfo->User.Sid;
fSuccess = CacheAccessCheck( pSid, &fAccess );
if (fSuccess)
{
CloseHandle( hToken );
if (fAccess)
return RPC_S_OK;
else
return RPC_E_ACCESS_DENIED;
}
}
// Access check.
fSuccess = AccessCheck( gSecDesc, hToken, COM_RIGHTS_EXECUTE,
&gMap, &gPriv, &lSetLen, &lGrantedAccess,
&fAccess );
if (fSuccess)
CacheAccess( pSid, fAccess );
if (!fAccess)
{
sc = RPC_E_ACCESS_DENIED;
#if DBG==1
pFailure = "Security descriptor does not allow user access.\n";
#endif
}
#if DBG==1
if (!fSuccess)
pFailure = "Bad security descriptor";
#endif
CloseHandle( hToken );
}
else
{
sc = GetLastError();
#if DBG==1
pFailure = "Could not open thread token: 0x%x\n";
#endif
}
}
#if DBG==1
else
pFailure = "Could not impersonate client: 0x%x\n";
#endif
#if DBG==1
if (sc != 0)
{
ComDebOut(( DEB_WARN, "***** ACCESS DENIED *****\n" ));
ComDebOut(( DEB_WARN, pFailure, sc ));
// Print the user name.
WCHAR *pClient;
if (0 == RpcBindingInqAuthClient( NULL, (void **) &pClient, NULL,
NULL, NULL, NULL ) &&
pClient != NULL)
ComDebOut(( DEB_WARN, "User: %ws\n", pClient ));
// Print the user sid.
ComDebOut(( DEB_WARN, "Security Descriptor 0x%x\n", gSecDesc ));
if (pSid != NULL)
{
ComDebOut(( DEB_WARN, "SID:\n" ));
ComDebOut(( DEB_WARN, " Revision: 0x%02x\n", pSid->Revision ));
ComDebOut(( DEB_WARN, " SubAuthorityCount: 0x%x\n", pSid->SubAuthorityCount ));
ComDebOut(( DEB_WARN, " IdentifierAuthority: 0x%02x%02x%02x%02x%02x%02x\n",
pSid->IdentifierAuthority.Value[0],
pSid->IdentifierAuthority.Value[1],
pSid->IdentifierAuthority.Value[2],
pSid->IdentifierAuthority.Value[3],
pSid->IdentifierAuthority.Value[4],
pSid->IdentifierAuthority.Value[5] ));
for (DWORD i = 0; i < pSid->SubAuthorityCount; i++)
ComDebOut(( DEB_WARN, " SubAuthority[%d]: 0x%08x\n", i,
pSid->SubAuthority[i] ));
}
else
ComDebOut(( DEB_WARN, " Unknown\n" ));
}
#endif
return sc;
}
//+-------------------------------------------------------------------
//
// Function: CheckObjactAccess, private
//
// Synopsis: Determine whether caller has permission to make call.
//
// Notes: Since IObjServer uses dynamic delegation, we have to allow
// all calls to IObjServer through the normal security (which only
// checks access on connect) and check them manually.
//
//--------------------------------------------------------------------
BOOL CheckObjactAccess()
{
RPC_IF_CALLBACK_FN *pAccess;
// Get the access check function.
pAccess = GetAclFn();
// Check access. Lie about the IID since the check access functions
// won't fail calls to IID_IObjServer.
if (pAccess != NULL)
return pAccess( NULL, NULL ) == S_OK;
else
return TRUE;
}
//+-------------------------------------------------------------------
//
// Function: CoCopyProxy, public
//
// Synopsis: Copy a proxy.
//
//--------------------------------------------------------------------
WINOLEAPI CoCopyProxy(
IUnknown *pProxy,
IUnknown **ppCopy )
{
HRESULT hr;
IClientSecurity *pickle;
// Ask the proxy for IClientSecurity.
hr = ((IUnknown *) pProxy)->QueryInterface( IID_IClientSecurity,
(void **) &pickle );
if (FAILED(hr))
return hr;
// Ask IClientSecurity to do the copy.
hr = pickle->CopyProxy( pProxy, ppCopy );
pickle->Release();
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CoGetCallContext
//
// Synopsis: Get an interface that supplies contextual information
// about the call. Currently only IServerSecurity.
//
//--------------------------------------------------------------------
WINOLEAPI CoGetCallContext( REFIID riid, void **ppInterface )
{
HRESULT hr;
COleTls tls(hr);
if (SUCCEEDED(hr))
{
// Fail if there is no call context.
if (tls->pCallContext == NULL)
return RPC_E_CALL_COMPLETE;
// Look up the requested interface.
return tls->pCallContext->QueryInterface( riid, ppInterface );
}
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CoImpersonateClient
//
// Synopsis: Get the server security for the current call and ask it
// to do an impersonation.
//
//--------------------------------------------------------------------
WINOLEAPI CoImpersonateClient()
{
HRESULT hr;
IServerSecurity *pSS;
// Get the IServerSecurity.
hr = CoGetCallContext( IID_IServerSecurity, (void **) &pSS );
if (FAILED(hr))
return hr;
// Ask IServerSecurity to do the impersonate.
hr = pSS->ImpersonateClient();
pSS->Release();
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CoInitializeSecurity, public
//
// Synopsis: Set the values to use for automatic security. This API
// can only be called once so it does not need to be thread
// safe.
//
//--------------------------------------------------------------------
WINOLEAPI CoInitializeSecurity(
PSECURITY_DESCRIPTOR pVoid,
LONG cAuthSvc,
SOLE_AUTHENTICATION_SERVICE *asAuthSvc,
void *pReserved1,
DWORD dwAuthnLevel,
DWORD dwImpLevel,
void *pReserved2,
DWORD dwCapabilities,
void *pReserved3 )
{
HRESULT hr = S_OK;
DWORD i;
SECURITY_DESCRIPTOR *pSecDesc = (SECURITY_DESCRIPTOR *) pVoid;
SECURITY_DESCRIPTOR *pCopySecDesc = NULL;
IAccessControl *pAccessControl = NULL;
BOOL fFreeSecDesc = FALSE;
SOLE_AUTHENTICATION_SERVICE sAuthSvc;
// Fail if OLE is not initialized or TLS cannot be allocated.
if (!IsApartmentInitialized())
return CO_E_NOTINITIALIZED;
// Make sure the security data is available.
if (!gGotSecurityData)
{
hr = gResolver.GetConnection();
if (FAILED(hr))
return hr;
Win4Assert(gGotSecurityData);
}
// Make sure only one of the flags defining the pVoid parameter is set.
if ((dwCapabilities & (EOAC_APPID | EOAC_ACCESS_CONTROL)) ==
(EOAC_APPID | EOAC_ACCESS_CONTROL))
return E_INVALIDARG;
// If the appid flag is set, read the registry security.
if (dwCapabilities & EOAC_APPID)
{
// Get a security descriptor from the registry.
if (gAuthnLevel != RPC_C_AUTHN_LEVEL_NONE)
{
hr = GetLegacySecDesc( &pSecDesc, &dwCapabilities );
if (FAILED(hr))
return hr;
fFreeSecDesc = TRUE;
}
// Fix up the security binding.
if (gLegacySecurity != NULL)
{
cAuthSvc = 1;
asAuthSvc = &sAuthSvc;
sAuthSvc.dwAuthnSvc = gLegacySecurity->wAuthnSvc;
sAuthSvc.dwAuthzSvc = gLegacySecurity->wAuthzSvc;
sAuthSvc.pPrincipalName = NULL;
if (sAuthSvc.dwAuthzSvc == COM_C_AUTHZ_NONE)
sAuthSvc.dwAuthzSvc = RPC_C_AUTHZ_NONE;
}
else
cAuthSvc = 0xFFFFFFFF;
// Initialize remaining parameters.
pReserved1 = NULL;
dwAuthnLevel = gAuthnLevel;
dwImpLevel = gImpLevel;
pReserved2 = NULL;
pReserved3 = NULL;
dwCapabilities |= gCapabilities;
}
// Fail if called too late, recalled, or called with bad parameters.
if (dwImpLevel > RPC_C_IMP_LEVEL_DELEGATE ||
dwAuthnLevel > RPC_C_AUTHN_LEVEL_PKT_PRIVACY ||
pReserved1 != NULL ||
pReserved2 != NULL ||
pReserved3 != NULL ||
(dwCapabilities & ~VALID_INIT_FLAGS))
{
hr = E_INVALIDARG;
goto Error;
}
if ((dwCapabilities & EOAC_SECURE_REFS) &&
dwAuthnLevel == RPC_C_AUTHN_LEVEL_NONE)
{
hr = E_INVALIDARG;
goto Error;
}
// Validate the pointers.
if (pSecDesc != NULL)
if (dwCapabilities & EOAC_ACCESS_CONTROL)
{
if (!IsValidPtrIn( pSecDesc, 4 ))
{
hr = E_INVALIDARG;
goto Error;
}
}
else if (!IsValidPtrIn( pSecDesc, sizeof(SECURITY_DESCRIPTOR) ))
{
hr = E_INVALIDARG;
goto Error;
}
if (cAuthSvc != 0 && cAuthSvc != -1 &&
!IsValidPtrOut( asAuthSvc, sizeof(SOLE_AUTHENTICATION_SERVICE) * cAuthSvc ))
{
hr = E_INVALIDARG;
goto Error;
}
LOCK
if (gpsaSecurity != NULL)
hr = RPC_E_TOO_LATE;
if (SUCCEEDED(hr))
{
// If the app doesn't want security, don't set up a security
// descriptor.
if (dwAuthnLevel == RPC_C_AUTHN_LEVEL_NONE)
{
// Check for some more invalid parameters.
if (pSecDesc != NULL)
hr = E_INVALIDARG;
}
// Check whether security is done with ACLs or IAccessControl.
else if (dwCapabilities & EOAC_ACCESS_CONTROL)
{
if (pSecDesc == NULL)
hr = E_INVALIDARG;
else
hr = ((IUnknown *) pSecDesc)->QueryInterface(
IID_IAccessControl, (void **) &pAccessControl );
}
else
{
#ifdef _CHICAGO_
if (pSecDesc != NULL)
hr = E_INVALIDARG;
#else
// If specified, copy the security descriptor.
if (pSecDesc != NULL)
hr = CopySecDesc( pSecDesc, &pCopySecDesc );
#endif
}
}
if (SUCCEEDED(hr))
{
// Delay the registration of authentication services if the caller
// isn't picky.
if (cAuthSvc == -1)
{
gpsaSecurity = (DUALSTRINGARRAY *) PrivMemAlloc( SASIZE(4) );
if (gpsaSecurity != NULL)
{
gDefaultService = TRUE;
gpsaSecurity->wNumEntries = 4;
gpsaSecurity->wSecurityOffset = 2;
memset( gpsaSecurity->aStringArray, 0, 4*sizeof(WCHAR) );
}
else
hr = E_OUTOFMEMORY;
}
// Otherwise, register the ones the caller specified.
else
hr = RegisterAuthnServices( cAuthSvc, asAuthSvc );
}
// If everything succeeded, change the globals.
if (SUCCEEDED(hr))
{
// Save the defaults.
gAuthnLevel = dwAuthnLevel;
gImpLevel = dwImpLevel;
gCapabilities = dwCapabilities;
gSecDesc = pCopySecDesc;
gAccessControl = pAccessControl;
if ( dwCapabilities & EOAC_DYNAMIC )
gResolver.SetDynamicSecurity();
}
// Otherwise free any memory allocated.
else
{
PrivMemFree( pCopySecDesc );
}
UNLOCK
// If anything was allocated for app id security, free it.
Error:
if (fFreeSecDesc && pSecDesc != NULL)
if (dwCapabilities & EOAC_ACCESS_CONTROL)
((IAccessControl *) pSecDesc)->Release();
else
PrivMemFree( pSecDesc );
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CopySecDesc
//
// Synopsis: Copy a security descriptor.
//
// Notes: The function does not copy the SACL because we do not do
// auditing.
//
//--------------------------------------------------------------------
HRESULT CopySecDesc( SECURITY_DESCRIPTOR *pOrig, SECURITY_DESCRIPTOR **pCopy )
{
SID *pOwner;
SID *pGroup;
ACL *pDacl;
ULONG cSize;
ULONG cOwner;
ULONG cGroup;
ULONG cDacl;
// Assert if there is a new revision for the security descriptor or
// ACL.
#if DBG== 1
if (pOrig->Revision != SECURITY_DESCRIPTOR_REVISION)
ComDebOut(( DEB_ERROR, "Someone made a new security descriptor revision without telling me." ));
if (pOrig->Dacl != NULL)
Win4Assert( pOrig->Dacl->AclRevision == ACL_REVISION ||
!"Someone made a new acl revision without telling me." );
#endif
// Validate the security descriptor and ACL.
if (pOrig->Revision != SECURITY_DESCRIPTOR_REVISION ||
(pOrig->Control & SE_SELF_RELATIVE) != 0 ||
pOrig->Owner == NULL ||
pOrig->Group == NULL ||
pOrig->Sacl != NULL ||
(pOrig->Dacl != NULL && pOrig->Dacl->AclRevision != ACL_REVISION))
return E_INVALIDARG;
// Figure out how much memory to allocate for the copy and allocate it.
cOwner = GetLengthSid( pOrig->Owner );
cGroup = GetLengthSid( pOrig->Group );
cDacl = pOrig->Dacl == NULL ? 0 : pOrig->Dacl->AclSize;
cSize = sizeof(SECURITY_DESCRIPTOR) + cOwner + cGroup + cDacl;
*pCopy = (SECURITY_DESCRIPTOR *) PrivMemAlloc( cSize );
if (*pCopy == NULL)
return E_OUTOFMEMORY;
// Get pointers to each of the parts of the security descriptor.
pOwner = (SID *) (*pCopy + 1);
pGroup = (SID *) (((char *) pOwner) + cOwner);
if (pOrig->Dacl != NULL)
pDacl = (ACL *) (((char *) pGroup) + cGroup);
else
pDacl = NULL;
// Copy each piece.
**pCopy = *pOrig;
memcpy( pOwner, pOrig->Owner, cOwner );
memcpy( pGroup, pOrig->Group, cGroup );
if (pDacl != NULL)
memcpy( pDacl, pOrig->Dacl, pOrig->Dacl->AclSize );
(*pCopy)->Owner = pOwner;
(*pCopy)->Group = pGroup;
(*pCopy)->Dacl = pDacl;
(*pCopy)->Sacl = NULL;
// Check the security descriptor.
#if DBG==1
if (!IsValidSecurityDescriptor( *pCopy ))
{
Win4Assert( !"COM Created invalid security descriptor." );
return GetLastError();
}
#endif
return S_OK;
}
//+-------------------------------------------------------------------
//
// Function: CoQueryAuthenticationServices, public
//
// Synopsis: Return a list of the registered authentication services.
//
//--------------------------------------------------------------------
WINOLEAPI CoQueryAuthenticationServices( DWORD *pcAuthSvc,
SOLE_AUTHENTICATION_SERVICE **asAuthSvc )
{
DWORD i;
DWORD lNum = 0;
USHORT *pNext;
HRESULT hr = S_OK;
ASSERT_LOCK_RELEASED
LOCK
// Count the number of services in the security string array.
if (gpsaSecurity != NULL)
{
pNext = &gpsaSecurity->aStringArray[gpsaSecurity->wSecurityOffset];
while (*pNext != 0)
{
lNum++;
pNext += lstrlenW(pNext)+1;
}
}
// Return nothing if there are no authentication services.
*pcAuthSvc = lNum;
if (lNum == 0)
{
*asAuthSvc = NULL;
goto exit;
}
// Allocate a list of pointers.
*asAuthSvc = (SOLE_AUTHENTICATION_SERVICE *)
CoTaskMemAlloc( lNum * sizeof(void *) );
if (*asAuthSvc == NULL)
{
hr = E_OUTOFMEMORY;
goto exit;
}
// Initialize it.
for (i = 0; i < lNum; i++)
(*asAuthSvc)[i].pPrincipalName = NULL;
// Fill in one SOLE_AUTHENTICATION_SERVICE record per service
pNext = &gpsaSecurity->aStringArray[gpsaSecurity->wSecurityOffset];
for (i = 0; i < lNum; i++)
{
(*asAuthSvc)[i].dwAuthnSvc = *(pNext++);
(*asAuthSvc)[i].dwAuthzSvc = *(pNext++);
(*asAuthSvc)[i].hr = S_OK;
// Allocate memory for the principal name string.
(*asAuthSvc)[i].pPrincipalName = (OLECHAR *)
CoTaskMemAlloc( (lstrlenW(pNext)+1)*sizeof(OLECHAR) );
if ((*asAuthSvc)[i].pPrincipalName == NULL)
{
hr = E_OUTOFMEMORY;
break;
}
lstrcpyW( (*asAuthSvc)[i].pPrincipalName, pNext );
pNext += lstrlenW(pNext) + 1;
}
// Clean up if there wasn't enough memory.
if (FAILED(hr))
{
for (i = 0; i < lNum; i++)
CoTaskMemFree( (*asAuthSvc)[i].pPrincipalName );
CoTaskMemFree( *asAuthSvc );
*asAuthSvc = NULL;
*pcAuthSvc = 0;
}
exit:
UNLOCK
ASSERT_LOCK_RELEASED
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CoQueryClientBlanket
//
// Synopsis: Get the authentication settings the client used to call
// the server.
//
//--------------------------------------------------------------------
WINOLEAPI CoQueryClientBlanket(
DWORD *pAuthnSvc,
DWORD *pAuthzSvc,
OLECHAR **pServerPrincName,
DWORD *pAuthnLevel,
DWORD *pImpLevel,
RPC_AUTHZ_HANDLE *pPrivs,
DWORD *pCapabilities )
{
HRESULT hr;
IServerSecurity *pSS;
// Get the IServerSecurity.
hr = CoGetCallContext( IID_IServerSecurity, (void **) &pSS );
if (FAILED(hr))
return hr;
// Ask IServerSecurity to do the query.
hr = pSS->QueryBlanket( pAuthnSvc, pAuthzSvc, pServerPrincName,
pAuthnLevel, pImpLevel, pPrivs, pCapabilities );
pSS->Release();
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CoQueryProxyBlanket, public
//
// Synopsis: Get the authentication settings from a proxy.
//
//--------------------------------------------------------------------
WINOLEAPI CoQueryProxyBlanket(
IUnknown *pProxy,
DWORD *pAuthnSvc,
DWORD *pAuthzSvc,
OLECHAR **pServerPrincName,
DWORD *pAuthnLevel,
DWORD *pImpLevel,
RPC_AUTH_IDENTITY_HANDLE *pAuthInfo,
DWORD *pCapabilities )
{
HRESULT hr;
IClientSecurity *pickle;
// Ask the proxy for IClientSecurity.
hr = ((IUnknown *) pProxy)->QueryInterface( IID_IClientSecurity,
(void **) &pickle );
if (FAILED(hr))
return hr;
// Ask IClientSecurity to do the query.
hr = pickle->QueryBlanket( pProxy, pAuthnSvc, pAuthzSvc, pServerPrincName,
pAuthnLevel, pImpLevel, pAuthInfo,
pCapabilities );
pickle->Release();
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CoRevertToSelf
//
// Synopsis: Get the server security for the current call and ask it
// to revert.
//
//--------------------------------------------------------------------
WINOLEAPI CoRevertToSelf()
{
HRESULT hr;
IServerSecurity *pSS;
// Get the IServerSecurity.
hr = CoGetCallContext( IID_IServerSecurity, (void **) &pSS );
if (FAILED(hr))
return hr;
// Ask IServerSecurity to do the revert.
hr = pSS->RevertToSelf();
pSS->Release();
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CoSetProxyBlanket, public
//
// Synopsis: Set the authentication settings for a proxy.
//
//--------------------------------------------------------------------
WINOLEAPI CoSetProxyBlanket(
IUnknown *pProxy,
DWORD dwAuthnSvc,
DWORD dwAuthzSvc,
OLECHAR *pServerPrincName,
DWORD dwAuthnLevel,
DWORD dwImpLevel,
RPC_AUTH_IDENTITY_HANDLE pAuthInfo,
DWORD dwCapabilities )
{
HRESULT hr;
IClientSecurity *pickle;
// Ask the proxy for IClientSecurity.
hr = ((IUnknown *) pProxy)->QueryInterface( IID_IClientSecurity,
(void **) &pickle );
if (FAILED(hr))
return hr;
// Ask IClientSecurity to do the set.
hr = pickle->SetBlanket( pProxy, dwAuthnSvc, dwAuthzSvc, pServerPrincName,
dwAuthnLevel, dwImpLevel, pAuthInfo,
dwCapabilities );
pickle->Release();
return hr;
}
//+-------------------------------------------------------------------
//
// Function: CoSwitchCallContext
//
// Synopsis: Replace the call context object in TLS. Return the old
// context object. This API is used by custom marshallers
// to support security.
//
//--------------------------------------------------------------------
WINOLEAPI CoSwitchCallContext( IUnknown *pNewObject, IUnknown **ppOldObject )
{
HRESULT hr;
COleTls tls(hr);
if (SUCCEEDED(hr))
{
*ppOldObject = tls->pCallContext;
tls->pCallContext = pNewObject;
}
return hr;
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::AddRef, public
//
// Synopsis: Adds a reference to an interface
//
// Note: This is created in the stack so its reference count is ignored.
//
//--------------------------------------------------------------------
STDMETHODIMP_(ULONG) CServerSecurity::AddRef()
{
InterlockedIncrement( (long *) &_iRefCount );
return _iRefCount;
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::CServerSecurity, public
//
// Synopsis: Construct a server security for a remote call.
//
//--------------------------------------------------------------------
CServerSecurity::CServerSecurity()
{
_iRefCount = 1;
_pChannel = NULL;
_pHandle = NULL;
_iFlags = 0;
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::CServerSecurity, public
//
// Synopsis: Construct a server security for a call.
//
//--------------------------------------------------------------------
CServerSecurity::CServerSecurity( CChannelCallInfo *call )
{
_iRefCount = 1;
if (call->iFlags & CF_PROCESS_LOCAL)
{
_pChannel = call->pChannel;
_pHandle = NULL;
_iFlags = SS_PROCESS_LOCAL;
}
else
{
_pChannel = NULL;
_pHandle = (handle_t *) call->pmessage->reserved1;
_iFlags = 0;
}
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::EndCall, public
//
// Synopsis: Clears the stored binding handle because the call
// this object represents is over.
//
//--------------------------------------------------------------------
void CServerSecurity::EndCall()
{
// Revert if the app forgot to.
RevertToSelf();
_iFlags |= SS_CALL_DONE;
_pChannel = NULL;
_pHandle = NULL;
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::ImpersonateClient, public
//
// Synopsis: Calls RPC to impersonate for the stored binding handle.
//
//--------------------------------------------------------------------
STDMETHODIMP CServerSecurity::ImpersonateClient()
{
#ifdef _CHICAGO_
return E_NOTIMPL;
#else
HRESULT hr = S_OK;
RPC_STATUS sc;
BOOL fSuccess;
HANDLE hProcess;
HANDLE hToken;
SECURITY_IMPERSONATION_LEVEL eDuplicate;
// If the call is over, fail this request.
if (_iFlags & SS_CALL_DONE)
hr = RPC_E_CALL_COMPLETE;
// For process local calls, ask the channel to impersonate.
else if (_iFlags & SS_PROCESS_LOCAL)
{
if (_pChannel->GetSecurityToken() == NULL)
{
// Determine what rights to duplicate the token with.
if (_pChannel->GetImpLevel() == RPC_C_IMP_LEVEL_IMPERSONATE)
eDuplicate = SecurityImpersonation;
else
eDuplicate = SecurityIdentification;
// If the channel doesn't have a token, use the process token.
if (OpenProcessToken( GetCurrentProcess(),
TOKEN_DUPLICATE,
&hProcess ))
{
if (DuplicateToken( hProcess, eDuplicate, &hToken ))
{
if (!SetThreadToken( NULL, hToken ))
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
// If the channel still doesn't have a token, save this one.
LOCK
if (_pChannel->GetSecurityToken() == NULL)
_pChannel->SwapSecurityToken( hToken );
else
CloseHandle( hToken );
UNLOCK
}
else
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
CloseHandle( hProcess );
}
else
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
}
else
{
fSuccess = SetThreadToken( NULL, _pChannel->GetSecurityToken() );
if (!fSuccess)
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
}
}
// For process remote calls, ask RPC to impersonate.
else
{
sc = RpcImpersonateClient( _pHandle );
if (sc != RPC_S_OK)
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, sc );
}
if (SUCCEEDED(hr))
_iFlags |= SS_IMPERSONATING;
return hr;
#endif
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::IsImpersonating, public
//
// Synopsis: Return TRUE if ImpersonateClient has been called.
//
//--------------------------------------------------------------------
STDMETHODIMP_(BOOL) CServerSecurity::IsImpersonating()
{
#ifdef _CHICAGO_
return FALSE;
#else
return _iFlags & SS_IMPERSONATING;
#endif
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::QueryBlanket, public
//
// Synopsis: Calls RPC to return the authentication information
// for the stored binding handle.
//
//--------------------------------------------------------------------
STDMETHODIMP CServerSecurity::QueryBlanket(
DWORD *pAuthnSvc,
DWORD *pAuthzSvc,
OLECHAR **pServerPrincName,
DWORD *pAuthnLevel,
DWORD *pImpLevel,
void **pPrivs,
DWORD *pCapabilities )
{
HRESULT hr = S_OK;
RPC_STATUS sc;
DWORD iLen;
OLECHAR *pCopy;
// Initialize the out parameters. Currently the impersonation level
// and capabilities can not be determined.
if (pPrivs != NULL)
*((void **) pPrivs) = NULL;
if (pServerPrincName != NULL)
*pServerPrincName = NULL;
if (pAuthnSvc != NULL)
*pAuthnSvc = RPC_C_AUTHN_WINNT;
if (pAuthnLevel != NULL)
*pAuthnLevel = RPC_C_AUTHN_LEVEL_PKT_PRIVACY;
if (pImpLevel != NULL)
*pImpLevel = RPC_C_IMP_LEVEL_ANONYMOUS;
if (pAuthzSvc != NULL)
*pAuthzSvc = RPC_C_AUTHZ_NONE;
if (pCapabilities != NULL)
*pCapabilities = EOAC_NONE;
// If the call is over, fail this request.
if (_iFlags & SS_CALL_DONE)
hr = RPC_E_CALL_COMPLETE;
// For process local calls, use the defaults. Otherwise ask RPC.
else if ((_iFlags & SS_PROCESS_LOCAL) == 0)
{
sc = RpcBindingInqAuthClientW( _pHandle, pPrivs, pServerPrincName,
pAuthnLevel, pAuthnSvc, pAuthzSvc );
// Sometimes RPC sets out parameters in error cases.
if (sc != RPC_S_OK)
{
if (pServerPrincName != NULL)
*pServerPrincName = NULL;
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, sc );
}
else if (pServerPrincName != NULL && *pServerPrincName != NULL)
{
// Reallocate the principle name using the OLE memory allocator.
iLen = lstrlenW( *pServerPrincName );
pCopy = (OLECHAR *) CoTaskMemAlloc( (iLen+1) * sizeof(OLECHAR) );
if (pCopy != NULL)
lstrcpyW( pCopy, *pServerPrincName );
else
hr = E_OUTOFMEMORY;
RpcStringFree( pServerPrincName );
*pServerPrincName = pCopy;
}
}
return hr;
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::QueryInterface, public
//
// Synopsis: Returns a pointer to the requested interface.
//
//--------------------------------------------------------------------
STDMETHODIMP CServerSecurity::QueryInterface( REFIID riid, LPVOID FAR* ppvObj)
{
if (IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_IServerSecurity))
{
*ppvObj = (IServerSecurity *) this;
}
else
{
*ppvObj = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::Release, public
//
// Synopsis: Releases an interface
//
// Note: This is created in the stack so its reference count is ignored.
//
//--------------------------------------------------------------------
STDMETHODIMP_(ULONG) CServerSecurity::Release()
{
ULONG lRef = _iRefCount - 1;
if (InterlockedDecrement( (long*) &_iRefCount ) == 0)
{
Win4Assert( !"Illegal release of IServerSecurity." );
delete this;
return 0;
}
else
{
return lRef;
}
}
//+-------------------------------------------------------------------
//
// Member: CServerSecurity::RevertToSelf, public
//
// Synopsis: If ImpersonateClient was called, then either ask RPC to
// revert or NULL the thread token ourself.
//
//--------------------------------------------------------------------
HRESULT CServerSecurity::RevertToSelf()
{
#ifdef _CHICAGO_
return S_OK;
#else
HRESULT hr = RPC_S_OK;
RPC_STATUS sc;
BOOL fSuccess;
if (_iFlags & SS_IMPERSONATING)
{
// Ask win32 to revert for process local calls.
_iFlags &= ~SS_IMPERSONATING;
if (_iFlags & SS_PROCESS_LOCAL)
{
fSuccess = SetThreadToken( NULL, NULL );
if (!fSuccess)
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
}
// Ask RPC to revert for process remote calls.
else
{
sc = RpcRevertToSelfEx( _pHandle );
if (sc != RPC_S_OK)
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, sc );
}
}
return hr;
#endif
}
//+-------------------------------------------------------------------
//
// Function: DefaultAuthnServices, private
//
// Synopsis: Register authentication services with RPC and build
// a string array of authentication services and principal
// names.
//
//--------------------------------------------------------------------
HRESULT DefaultAuthnServices()
{
HRESULT hr = S_OK;
DWORD i;
WCHAR *pPrincName = NULL;
DWORD lNameLen;
USHORT *pNextString;
DUALSTRINGARRAY *pOld;
DWORD cBinding = gServerSvcListLen ? gServerSvcListLen : 1;
ASSERT_LOCK_HELD
// Return if the security bindings are already computed.
if (!gDefaultService)
return S_OK;
// Only look up the current user name if the only security provider
// is not NTLMSSP since NTLMSSP doesn't do mutual auth.
if (gServerSvcListLen != 0 &&
(gServerSvcListLen != 1 || gServerSvcList[0] != RPC_C_AUTHN_WINNT))
{
#ifndef _CHICAGO_
hr = LookupPrincName( &pPrincName );
if (SUCCEEDED(hr))
lNameLen = lstrlenW( pPrincName ) + 1;
#else
hr = LookupPrincName( gServerSvcList, gServerSvcListLen, &pPrincName );
if (SUCCEEDED(hr))
lNameLen = lstrlenW( pPrincName ) + 1;
else
{
// BUGBUG: the whole PrincName mess still needs clean up
// especially given the state of msnsspc.dll
pPrincName = NULL;
hr = S_OK;
lNameLen = 1;
}
#endif // _CHICAGO_
}
else
lNameLen = 1;
if (SUCCEEDED(hr))
{
// Allocate memory for the string array.
Win4Assert( gGotSecurityData );
pOld = gpsaSecurity;
gpsaSecurity = (DUALSTRINGARRAY *)
PrivMemAlloc( sizeof(DUALSTRINGARRAY) + 2 * sizeof(WCHAR) +
cBinding * (sizeof(SECURITYBINDING) +
lNameLen*sizeof(WCHAR)) );
if (gpsaSecurity != NULL)
{
// Fill in the array of security information. First two characters
// are NULLs to signal empty binding strings.
PrivMemFree( pOld );
gDefaultService = FALSE;
gpsaSecurity->wSecurityOffset = 2;
gpsaSecurity->aStringArray[0] = 0;
gpsaSecurity->aStringArray[1] = 0;
pNextString = &gpsaSecurity->aStringArray[2];
for (i = 0; i < gServerSvcListLen; i++)
{
// Ignore errors since applications using automatic security
// may not care if they can't receive secure calls.
hr = RpcServerRegisterAuthInfoW( pPrincName, gServerSvcList[i],
NULL, NULL );
if (hr == RPC_S_OK)
{
// Fill in authentication service, authorization service,
// and principal name.
*(pNextString++) = gServerSvcList[i];
*(pNextString++) = COM_C_AUTHZ_NONE;
if (pPrincName == NULL)
*pNextString = 0;
else
memcpy( pNextString, pPrincName, lNameLen*sizeof(USHORT) );
pNextString += lNameLen;
}
}
// Add a final NULL. Special case an empty list which requires
// two NULLs.
*(pNextString++) = 0;
if (gServerSvcListLen == 0)
*(pNextString++) = 0;
gpsaSecurity->wNumEntries = (USHORT)
(pNextString-gpsaSecurity->aStringArray);
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
gpsaSecurity = pOld;
}
}
PrivMemFree( pPrincName );
return hr;
}
//+-------------------------------------------------------------------
//
// Function: FixupAccessControl, internal
//
// Synopsis: Get the access control class id. Instantiate the access
// control class and load the data.
//
// Notes: The caller has already insured that the structure is
// at least as big as a SPermissionHeader structure.
//
//--------------------------------------------------------------------
HRESULT FixupAccessControl( SECURITY_DESCRIPTOR **pSD, DWORD cbSD )
{
SPermissionHeader *pHeader;
IAccessControl *pControl = NULL;
IPersistStream *pPersist = NULL;
CNdrStream cStream( ((unsigned char *) *pSD) + sizeof(SPermissionHeader),
cbSD - sizeof(SPermissionHeader) );
HRESULT hr;
// Get the class id.
pHeader = (SPermissionHeader *) *pSD;
// Instantiate the class.
hr = CoCreateInstance( pHeader->gClass, NULL, CLSCTX_INPROC_SERVER,
IID_IAccessControl, (void **) &pControl );
// Get IPeristStream
if (SUCCEEDED(hr))
{
hr = pControl->QueryInterface( IID_IPersistStream, (void **) &pPersist );
// Load the stream.
if (SUCCEEDED(hr))
hr = pPersist->Load( &cStream );
}
// Release resources.
if (pPersist != NULL)
pPersist->Release();
if (SUCCEEDED(hr))
{
PrivMemFree( *pSD );
*pSD = (SECURITY_DESCRIPTOR *) pControl;
}
else if (pControl != NULL)
pControl->Release();
return hr;
}
//+-------------------------------------------------------------------
//
// Function: FixupSecurityDescriptor, internal
//
// Synopsis: Convert the security descriptor from self relative to
// absolute form and check for errors.
//
//--------------------------------------------------------------------
HRESULT FixupSecurityDescriptor( SECURITY_DESCRIPTOR **pSD, DWORD cbSD )
{
// Fix up the security descriptor.
(*pSD)->Control &= ~SE_SELF_RELATIVE;
(*pSD)->Sacl = NULL;
if ((*pSD)->Dacl != NULL)
{
if (cbSD < sizeof(ACL) + sizeof(SECURITY_DESCRIPTOR) ||
(ULONG) (*pSD)->Dacl > cbSD - sizeof(ACL))
return REGDB_E_INVALIDVALUE;
(*pSD)->Dacl = (ACL *) (((char *) *pSD) + ((ULONG) (*pSD)->Dacl));
if ((*pSD)->Dacl->AclSize + sizeof(SECURITY_DESCRIPTOR) > cbSD)
return REGDB_E_INVALIDVALUE;
}
// Set up the owner and group SIDs.
if ((*pSD)->Group == 0 || ((ULONG) (*pSD)->Group) + sizeof(SID) > cbSD ||
(*pSD)->Owner == 0 || ((ULONG) (*pSD)->Owner) + sizeof(SID) > cbSD)
return REGDB_E_INVALIDVALUE;
(*pSD)->Group = (SID *) (((BYTE *) *pSD) + (ULONG) (*pSD)->Group);
(*pSD)->Owner = (SID *) (((BYTE *) *pSD) + (ULONG) (*pSD)->Owner);
// Check the security descriptor.
#if DBG==1
if (!IsValidSecurityDescriptor( *pSD ))
return REGDB_E_INVALIDVALUE;
#endif
return S_OK;
}
//+-------------------------------------------------------------------
//
// Function: GetLegacySecDesc, internal
//
// Synopsis: Get a security descriptor for the current app. First,
// look under the app id for the current exe name. If that
// fails look up the default descriptor. If that fails,
// create one.
//
// Note: It is possible that the security descriptor size could change
// during the size computation. Add code to retry.
//
//--------------------------------------------------------------------
HRESULT GetLegacySecDesc( SECURITY_DESCRIPTOR **pSD, DWORD *pCapabilities )
{
// Holds either Appid\{guid} or Appid\module_name.
WCHAR aKeyName[MAX_PATH+7];
HRESULT hr;
HKEY hKey = NULL;
DWORD lSize;
WCHAR aModule[MAX_PATH];
DWORD cModule;
DWORD i;
WCHAR aAppid[40]; // Hold a registry GUID.
DWORD lType;
// If the flag EOAC_APPID is set, the security descriptor contains the
// app id.
if ((*pCapabilities & EOAC_APPID) && *pSD != NULL)
{
if (StringFromIID2( *((GUID *) *pSD), aAppid, sizeof(aAppid) ) == 0)
return RPC_E_UNEXPECTED;
*pSD = NULL;
// Open the application id key. A GUID in the registry is stored.
// as a 38 character string.
lstrcpyW( aKeyName, L"AppID\\" );
memcpy( &aKeyName[6], aAppid, 39*sizeof(WCHAR) );
hr = RegOpenKeyEx( HKEY_CLASSES_ROOT, aKeyName,
NULL, KEY_READ, &hKey );
// Get the security descriptor from the registry.
if (hr == ERROR_SUCCESS)
hr = GetRegistrySecDesc( hKey, L"AccessPermission", pSD,
pCapabilities );
else
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, hr );
}
// Look up the app id from the exe name.
else
{
// Get the executable's name. Find the start of the file name.
cModule = GetModuleFileName( NULL, aModule, MAX_PATH );
if (cModule >= MAX_PATH)
{
Win4Assert( !"Module name too long." );
return RPC_E_UNEXPECTED;
}
for (i = cModule-1; i > 0; i--)
if (aModule[i] == '/' ||
aModule[i] == '\\' ||
aModule[i] == ':')
break;
if (i != 0)
i += 1;
// Open the key for the EXE's module name.
lstrcpyW( aKeyName, L"AppID\\" );
memcpy( &aKeyName[6], &aModule[i], (cModule - i + 1) * sizeof(WCHAR) );
hr = RegOpenKeyEx( HKEY_CLASSES_ROOT, aKeyName,
NULL, KEY_READ, &hKey );
// Look for an application id.
if (hr == ERROR_SUCCESS)
{
lSize = sizeof(aAppid);
hr = RegQueryValueEx( hKey, L"AppID", NULL, &lType,
(unsigned char *) &aAppid, &lSize );
RegCloseKey( hKey );
hKey = NULL;
// Open the application id key. A GUID in the registry is stored.
// as a 38 character string.
if (hr == ERROR_SUCCESS && lType == REG_SZ &&
lSize == 39*sizeof(WCHAR))
{
memcpy( &aKeyName[6], aAppid, 39*sizeof(WCHAR) );
hr = RegOpenKeyEx( HKEY_CLASSES_ROOT, aKeyName,
NULL, KEY_READ, &hKey );
// Get the security descriptor from the registry.
if (hr == ERROR_SUCCESS)
{
hr = GetRegistrySecDesc( hKey, L"AccessPermission", pSD,
pCapabilities );
if (SUCCEEDED(hr) || hr == REGDB_E_INVALIDVALUE)
goto cleanup;
RegCloseKey( hKey );
hKey = NULL;
}
}
}
// Open the default key.
hr = RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\OLE",
NULL, KEY_READ, &hKey );
// Get the security descriptor from the registry.
if (hr == ERROR_SUCCESS)
{
hr = GetRegistrySecDesc( hKey, L"DefaultAccessPermission", pSD,
pCapabilities );
// If that failed, make one.
if (FAILED(hr) && hr != REGDB_E_INVALIDVALUE)
hr = MakeSecDesc( pSD, pCapabilities );
}
else
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, hr );
}
cleanup:
// Free the security descriptor memory if anything failed.
if (FAILED(hr))
{
PrivMemFree( *pSD );
*pSD = NULL;
}
// Close the registry key.
if (hKey != NULL)
RegCloseKey( hKey );
return hr;
}
//+-------------------------------------------------------------------
//
// Function: GetRegistrySecDesc, internal
//
// Synopsis: Convert a security descriptor from self relative to
// absolute form. Stuff in an owner and a group.
//
// Notes:
// REGDB_E_INVALIDVALUE is returned when there is something
// at the specified value, but it is not a security descriptor.
//
// The caller must free the security descriptor in both the
// success and failure cases.
//
// Codework: It would be nice to use the unicode APIs on NT.
//
//--------------------------------------------------------------------
HRESULT GetRegistrySecDesc( HKEY hKey, WCHAR *pValue,
SECURITY_DESCRIPTOR **pSD, DWORD *pCapabilities )
{
SID *pGroup;
SID *pOwner;
DWORD cbSD = 256;
DWORD lType;
HRESULT hr;
WORD wVersion;
// Guess how much memory to allocate for the security descriptor.
*pSD = (SECURITY_DESCRIPTOR *) PrivMemAlloc( cbSD );
if (*pSD == NULL)
{
hr = E_OUTOFMEMORY;
goto cleanup;
}
// Find put how much memory to allocate for the security
// descriptor.
hr = RegQueryValueEx( hKey, pValue, NULL, &lType,
(unsigned char *) *pSD, &cbSD );
if (hr != ERROR_SUCCESS && hr != ERROR_MORE_DATA)
{
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, hr );
goto cleanup;
}
if (lType != REG_BINARY || cbSD < sizeof(SECURITY_DESCRIPTOR))
{
hr = REGDB_E_INVALIDVALUE;
goto cleanup;
}
// If the first guess wasn't large enough, reallocate the memory.
if (hr == ERROR_MORE_DATA)
{
PrivMemFree( *pSD );
*pSD = (SECURITY_DESCRIPTOR *) PrivMemAlloc( cbSD );
if (*pSD == NULL)
{
hr = E_OUTOFMEMORY;
goto cleanup;
}
// Read the security descriptor.
hr = RegQueryValueEx( hKey, pValue, NULL, &lType,
(unsigned char *) *pSD, &cbSD );
if (hr != ERROR_SUCCESS)
{
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, hr );
goto cleanup;
}
if (lType != REG_BINARY || cbSD < sizeof(SECURITY_DESCRIPTOR))
{
hr = REGDB_E_INVALIDVALUE;
goto cleanup;
}
}
// Check the first DWORD to determine what type of data is in the
// registry value.
wVersion = *((WORD *) *pSD);
#ifndef _CHICAGO_
if (wVersion == COM_PERMISSION_SECDESC)
hr = FixupSecurityDescriptor( pSD, cbSD );
else
#endif
if (wVersion == COM_PERMISSION_ACCCTRL)
{
hr = FixupAccessControl( pSD, cbSD );
if (SUCCEEDED(hr))
*pCapabilities |= EOAC_ACCESS_CONTROL;
}
else
hr = REGDB_E_INVALIDVALUE;
cleanup:
if (FAILED(hr))
{
PrivMemFree( *pSD );
*pSD = NULL;
}
return hr;
}
//+-------------------------------------------------------------------
//
// Function: HashSid
//
// Synopsis: Create a 32 bit hash of a SID.
//
//--------------------------------------------------------------------
DWORD HashSid( SID *pSid )
{
DWORD lHash = 0;
DWORD cbSid = GetLengthSid( pSid );
DWORD i;
unsigned char *pData = (unsigned char *) pSid;
for (i = 0; i < cbSid; i++)
lHash = (lHash << 1) + *pData++;
return lHash;
}
//+-------------------------------------------------------------------
//
// Function: InitializeSecurity, internal
//
// Synopsis: Called the first time the channel is used. If the app
// has not initialized security yet, this function sets
// up legacy security.
//
//--------------------------------------------------------------------
HRESULT InitializeSecurity()
{
HRESULT hr;
ASSERT_LOCK_HELD
// Return if already initialized.
if (gpsaSecurity != NULL)
return S_OK;
// Initialize. All parameters are ignored except the security descriptor
// since the capability is set to app id.
hr = CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE,
RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_APPID,
NULL );
// Convert confusing error codes.
if (hr == E_INVALIDARG)
hr = REGDB_E_INVALIDVALUE;
return hr;
}
//+-------------------------------------------------------------------
//
// Function: IsCallerLocalSystem
//
// Synopsis: Impersonate the caller and do an ACL check. The first
// time this function is called, create the ACL
//
//--------------------------------------------------------------------
BOOL IsCallerLocalSystem()
{
HRESULT hr = S_OK;
DWORD granted_access;
BOOL access;
HANDLE token;
DWORD privilege_size = sizeof(gPriv);
BOOL success;
SECURITY_DESCRIPTOR *pSecDesc = NULL;
DWORD lIgnore;
ASSERT_LOCK_RELEASED
// If the security descriptor does not exist, create it.
if (gRundownSD == NULL)
{
// Make the security descriptor.
hr = MakeSecDesc( &pSecDesc, &lIgnore );
// Save the security descriptor.
LOCK
if (gRundownSD == NULL)
gRundownSD = pSecDesc;
else
PrivMemFree( pSecDesc );
UNLOCK
}
// Impersonate.
if (SUCCEEDED(hr))
hr = CoImpersonateClient();
// Get the thread token.
if (SUCCEEDED(hr))
{
success = OpenThreadToken( GetCurrentThread(), TOKEN_READ,
TRUE, &token );
if (!success)
hr = E_FAIL;
}
// Check access.
if (SUCCEEDED(hr))
{
success = AccessCheck( gRundownSD, token, COM_RIGHTS_EXECUTE,
&gMap, &gPriv, &privilege_size,
&granted_access, &access );
if (!success || !access)
hr = E_FAIL;
CloseHandle( token );
}
// Just call revert since it detects whether or not the impersonate
// succeeded.
CoRevertToSelf();
ASSERT_LOCK_RELEASED
return SUCCEEDED(hr);
}
//+-------------------------------------------------------------------
//
// Function: IsLocalAuthnService
//
// Synopsis: Return TRUE is the specified authentication service is
// on the list of services this machine supports.
//
// NOTE: If we ever expect the list to be more then three items
// long, we can add code to sort it.
//
//--------------------------------------------------------------------
BOOL IsLocalAuthnService( USHORT wAuthnService )
{
DWORD l;
for (l = 0; l < gClientSvcListLen; l++)
if (gClientSvcList[l] == wAuthnService)
return TRUE;
return FALSE;
}
#ifndef _CHICAGO_
//+-------------------------------------------------------------------
//
// Function: LookupPrincName, private
//
// Synopsis: Open the process token and find the user's name.
//
//--------------------------------------------------------------------
HRESULT LookupPrincName( WCHAR **pPrincName )
{
HRESULT hr = S_OK;
BYTE aMemory[SIZEOF_TOKEN_USER];
TOKEN_USER *pTokenUser = (TOKEN_USER *) &aMemory;
HANDLE hToken = NULL;
DWORD lIgnore;
DWORD lNameLen = 80;
DWORD lDomainLen = 80;
WCHAR *pDomainName = NULL;
SID_NAME_USE sIgnore;
BOOL fSuccess;
// Open the process's token.
*pPrincName = NULL;
if (OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ))
{
// Lookup SID of process token.
if (GetTokenInformation( hToken, TokenUser, pTokenUser, sizeof(aMemory),
&lIgnore ))
{
// Preallocate some memory.
*pPrincName = (WCHAR *) PrivMemAlloc( lNameLen*sizeof(WCHAR) );
pDomainName = (WCHAR *) _alloca( lDomainLen*sizeof(WCHAR) );
if (*pPrincName != NULL && pDomainName != NULL)
{
// Find the user's name.
fSuccess = LookupAccountSidW( NULL, pTokenUser->User.Sid,
*pPrincName, &lNameLen,
pDomainName, &lDomainLen,
&sIgnore );
// If the call failed, try allocating more memory.
if (!fSuccess)
{
// Allocate memory for the user's name.
PrivMemFree( *pPrincName );
*pPrincName = (WCHAR *) PrivMemAlloc( lNameLen*sizeof(WCHAR) );
pDomainName = (WCHAR *) _alloca( lDomainLen*sizeof(WCHAR) );
if (*pPrincName != NULL && pDomainName != NULL)
{
// Find the user's name.
if (!LookupAccountSidW( NULL, pTokenUser->User.Sid,
*pPrincName, &lNameLen, pDomainName,
&lDomainLen, &sIgnore ))
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
}
else
hr = E_OUTOFMEMORY;
}
}
else
hr = E_OUTOFMEMORY;
}
else
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
CloseHandle( hToken );
}
else
{
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
#if DBG==1
Win4Assert( !"Why did OpenProcessToken fail?" );
OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken );
#endif
}
if (hr != S_OK)
{
PrivMemFree( *pPrincName );
*pPrincName = NULL;
}
return hr;
}
#else // _CHICAGO_
//+-------------------------------------------------------------------
//
// Function: LookupPrincName, private
//
// Synopsis: We have a service other than NTLMSSP.
// Find the first (!) such and find the user's name.
//
// BUGBUG: This is a hack until the principal name issue is properly
// sorted out.
//
//--------------------------------------------------------------------
HRESULT LookupPrincName(
USHORT *pwAuthnServices,
ULONG cAuthnServices,
WCHAR **pPrincName
)
{
// assume failure lest thou be disappointed
RPC_STATUS status = RPC_S_INVALID_AUTH_IDENTITY;
*pPrincName = NULL;
for (ULONG i = 0; i < cAuthnServices; i++)
{
if (pwAuthnServices[i] != RPC_C_AUTHN_WINNT)
{
status = RpcServerInqDefaultPrincNameW(
pwAuthnServices[i],
pPrincName);
if (status == RPC_S_OK)
{
break;
}
}
}
return HRESULT_FROM_WIN32(status);
}
#endif // _CHICAGO_
#ifdef _CHICAGO_
//+-------------------------------------------------------------------
//
// Function: MakeSecDesc, private
//
// Synopsis: Make an access control that allows the current user
// access.
//
// NOTE: NetWkstaGetInfo does not return the size needed unless the size
// in is zero.
//
//--------------------------------------------------------------------
HRESULT MakeSecDesc( SECURITY_DESCRIPTOR **pSD, DWORD *pCapabilities )
{
HRESULT hr = S_OK;
IAccessControl *pAccess = NULL;
DWORD cTrustee;
WCHAR *pTrusteeW;
char *pTrusteeA;
DWORD cDomain;
DWORD cUser;
char *pBuffer;
struct wksta_info_10 *wi10;
USHORT cbBuffer;
HINSTANCE hMsnet;
NetWkstaGetInfoFn fnNetWkstaGetInfo;
ACTRL_ACCESSW sAccessList;
ACTRL_PROPERTY_ENTRYW sProperty;
ACTRL_ACCESS_ENTRY_LISTW sEntryList;
ACTRL_ACCESS_ENTRYW sEntry;
// Load msnet32.dll
hMsnet = LoadLibraryA( "msnet32.dll" );
if (hMsnet == NULL)
return MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
// Get the function NetWkstaGetInfo.
fnNetWkstaGetInfo = (NetWkstaGetInfoFn) GetProcAddress( hMsnet,
(char *) 57 );
if (fnNetWkstaGetInfo == NULL)
{
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
goto cleanup;
}
// Find out how much space to allocate for the domain and user names.
cbBuffer = 0;
fnNetWkstaGetInfo( NULL, 10, NULL, 0, &cbBuffer );
pBuffer = (char *) _alloca( cbBuffer );
// Get the domain and user names.
hr = fnNetWkstaGetInfo( NULL, 10, pBuffer, cbBuffer, &cbBuffer );
if (hr != 0)
{
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, hr );
goto cleanup;
}
// Stick the user name and domain name in the same string.
wi10 = (struct wksta_info_10 *) pBuffer;
Win4Assert( wi10->wki10_logon_domain != NULL );
Win4Assert( wi10->wki10_username != NULL );
cDomain = lstrlenA( wi10->wki10_logon_domain );
cUser = lstrlenA( wi10->wki10_username );
pTrusteeA = (char *) _alloca( cDomain+cUser+2 );
lstrcpyA( pTrusteeA, wi10->wki10_logon_domain );
lstrcpyA( &pTrusteeA[cDomain+1], wi10->wki10_username );
pTrusteeA[cDomain] = '\\';
// Find out how long the name is in Unicode.
cTrustee = MultiByteToWideChar( GetConsoleCP(), 0, pTrusteeA,
cDomain+cUser+2, NULL, 0 );
// Convert the name to Unicode.
pTrusteeW = (WCHAR *) _alloca( cTrustee * sizeof(WCHAR) );
if (!MultiByteToWideChar( GetConsoleCP(), 0, pTrusteeA,
cDomain+cUser+2, pTrusteeW, cTrustee ))
{
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
goto cleanup;
}
// Create an AccessControl.
*pSD = NULL;
hr = CoCreateInstance( CLSID_DCOMAccessControl, NULL,
CLSCTX_INPROC_SERVER,
IID_IAccessControl, (void **) &pAccess );
if (FAILED(hr))
goto cleanup;
// Give the current user access.
sAccessList.cEntries = 1;
sAccessList.pPropertyAccessList = &sProperty;
sProperty.lpProperty = NULL;
sProperty.pAccessEntryList = &sEntryList;
sProperty.fListFlags = 0;
sEntryList.cEntries = 1;
sEntryList.pAccessList = &sEntry;
sEntry.fAccessFlags = ACTRL_ACCESS_ALLOWED;
sEntry.Access = COM_RIGHTS_EXECUTE;
sEntry.ProvSpecificAccess = 0;
sEntry.Inheritance = NO_INHERITANCE;
sEntry.lpInheritProperty = NULL;
sEntry.Trustee.pMultipleTrustee = NULL;
sEntry.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
sEntry.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
sEntry.Trustee.TrusteeType = TRUSTEE_IS_USER;
sEntry.Trustee.ptstrName = pTrusteeW;
hr = pAccess->GrantAccessRights( &sAccessList );
cleanup:
FreeLibrary( hMsnet );
if (SUCCEEDED(hr))
{
*pSD = (SECURITY_DESCRIPTOR *) pAccess;
*pCapabilities |= EOAC_ACCESS_CONTROL;
}
else if (pAccess != NULL)
pAccess->Release();
return hr;
}
#else
//+-------------------------------------------------------------------
//
// Function: MakeSecDesc, private
//
// Synopsis: Make a security descriptor that allows the current user
// and local system access.
//
// NOTE: Compute the length of the sids used rather then using constants.
//
//--------------------------------------------------------------------
HRESULT MakeSecDesc( SECURITY_DESCRIPTOR **pSD, DWORD *pCapabilities )
{
HRESULT hr = S_OK;
ACL *pAcl;
DWORD lSidLen;
SID *pGroup;
SID *pOwner;
BYTE aMemory[SIZEOF_TOKEN_USER];
TOKEN_USER *pTokenUser = (TOKEN_USER *) &aMemory;
HANDLE hToken = NULL;
DWORD lIgnore;
HANDLE hThread;
Win4Assert( *pSD == NULL );
// Open the process's token.
if (!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ))
{
// If the thread has a token, remove it and try again.
if (!OpenThreadToken( GetCurrentThread(), TOKEN_IMPERSONATE, TRUE,
&hThread ))
{
Win4Assert( !"How can both OpenThreadToken and OpenProcessToken fail?" );
return MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
}
if (!SetThreadToken( NULL, NULL ))
{
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
CloseHandle( hThread );
return hr;
}
if (!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ))
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
SetThreadToken( NULL, hThread );
CloseHandle( hThread );
if (FAILED(hr))
return hr;
}
// Lookup SID of process token.
if (!GetTokenInformation( hToken, TokenUser, pTokenUser, sizeof(aMemory),
&lIgnore ))
goto last_error;
// Compute the length of the SID.
lSidLen = GetLengthSid( pTokenUser->User.Sid );
Win4Assert( lSidLen <= SIZEOF_SID );
// Allocate the security descriptor.
*pSD = (SECURITY_DESCRIPTOR *) PrivMemAlloc(
sizeof(SECURITY_DESCRIPTOR) + 2*lSidLen + SIZEOF_ACL );
if (*pSD == NULL)
{
hr = E_OUTOFMEMORY;
goto cleanup;
}
pGroup = (SID *) (*pSD + 1);
pOwner = (SID *) (((BYTE *) pGroup) + lSidLen);
pAcl = (ACL *) (((BYTE *) pOwner) + lSidLen);
// Initialize a new security descriptor.
if (!InitializeSecurityDescriptor(*pSD, SECURITY_DESCRIPTOR_REVISION))
goto last_error;
// Initialize a new ACL.
if (!InitializeAcl(pAcl, SIZEOF_ACL, ACL_REVISION2))
goto last_error;
// Allow the current user access.
if (!AddAccessAllowedAce( pAcl, ACL_REVISION2, COM_RIGHTS_EXECUTE,
pTokenUser->User.Sid))
goto last_error;
// Allow local system access.
if (!AddAccessAllowedAce( pAcl, ACL_REVISION2, COM_RIGHTS_EXECUTE,
(void *) &LOCAL_SYSTEM_SID ))
goto last_error;
// Add a new ACL to the security descriptor.
if (!SetSecurityDescriptorDacl( *pSD, TRUE, pAcl, FALSE ))
goto last_error;
// Set the group.
memcpy( pGroup, pTokenUser->User.Sid, lSidLen );
if (!SetSecurityDescriptorGroup( *pSD, pGroup, FALSE ))
goto last_error;
// Set the owner.
memcpy( pOwner, pTokenUser->User.Sid, lSidLen );
if (!SetSecurityDescriptorOwner( *pSD, pOwner, FALSE ))
goto last_error;
// Check the security descriptor.
#if DBG==1
if (!IsValidSecurityDescriptor( *pSD ))
{
Win4Assert( !"COM Created invalid security descriptor." );
goto last_error;
}
#endif
goto cleanup;
last_error:
hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, GetLastError() );
cleanup:
if (hToken != NULL)
CloseHandle( hToken );
if (FAILED(hr))
{
PrivMemFree( *pSD );
*pSD = NULL;
}
return hr;
}
#endif
//+-------------------------------------------------------------------
//
// Function: RegisterAuthnServices, public
//
// Synopsis: Register the specified services. Build a security
// binding.
//
//--------------------------------------------------------------------
HRESULT RegisterAuthnServices( DWORD cAuthSvc,
SOLE_AUTHENTICATION_SERVICE *asAuthSvc )
{
DWORD i;
RPC_STATUS sc;
USHORT wNumEntries = 0;
USHORT *pNext;
HRESULT hr;
DWORD lNameLen;
ASSERT_LOCK_HELD
// Register all the authentication services specified.
for (i = 0; i < cAuthSvc; i++)
{
sc = RpcServerRegisterAuthInfoW( asAuthSvc[i].pPrincipalName,
asAuthSvc[i].dwAuthnSvc,
NULL, NULL );
// If the registration failed, store the failure code.
if (sc != RPC_S_OK)
asAuthSvc[i].hr = MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, sc );
// Otherwise determine how much space to reserve for it in the string
// array.
else
{
asAuthSvc[i].hr = S_OK;
if (asAuthSvc[i].pPrincipalName != NULL)
wNumEntries += lstrlenW( asAuthSvc[i].pPrincipalName ) + 3;
else
wNumEntries += 3;
}
}
if (wNumEntries == 0)
hr = RPC_E_NO_GOOD_SECURITY_PACKAGES;
else
// make room for the two NULLs that placehold for the empty
// string binding and the trailing NULL.
wNumEntries += 3;
// If some services were registered, build a string array.
if (wNumEntries != 0)
{
gpsaSecurity = (DUALSTRINGARRAY *) PrivMemAlloc(
wNumEntries*sizeof(USHORT) + sizeof(DUALSTRINGARRAY) );
if (gpsaSecurity == NULL)
hr = E_OUTOFMEMORY;
else
{
gpsaSecurity->wNumEntries = wNumEntries;
gpsaSecurity->wSecurityOffset = 2;
gpsaSecurity->aStringArray[0] = 0;
gpsaSecurity->aStringArray[1] = 0;
pNext = &gpsaSecurity->aStringArray[2];
for (i = 0; i < cAuthSvc; i++)
{
if (asAuthSvc[i].hr == S_OK)
{
// Fill in authentication service, authorization service,
// and principal name.
*(pNext++) = (USHORT) asAuthSvc[i].dwAuthnSvc;
*(pNext++) = (USHORT) (asAuthSvc[i].dwAuthzSvc == 0 ?
COM_C_AUTHZ_NONE :
asAuthSvc[i].dwAuthzSvc);
if (asAuthSvc[i].pPrincipalName != NULL)
{
lNameLen = lstrlenW( asAuthSvc[i].pPrincipalName ) + 1;
memcpy( pNext, asAuthSvc[i].pPrincipalName,
lNameLen*sizeof(USHORT) );
pNext += lNameLen;
}
else
*(pNext++) = 0;
}
}
*pNext = 0;
hr = S_OK;
}
}
ASSERT_LOCK_HELD
return hr;
}
//+-------------------------------------------------------------------
//
// Function: SetAuthnService, internal
//
// Synopsis: Determine the authentication information to set on a
// binding handle for a newly unmarshalled interface.
// The authentication level is the higher of the process
// default and the level in the interface. The
// impersonation level is the process default. If the
// authentication level is not zero, the function
// scans the list of authentication services in the
// interface looking for one this machine supports.
//
//--------------------------------------------------------------------
HRESULT SetAuthnService( handle_t hHandle, OXID_INFO *pOxidInfo,
OXIDEntry *pOxid )
{
DWORD lAuthnLevel;
DWORD lAuthnSvc;
USHORT wNext;
USHORT wAuthzSvc;
RPC_STATUS sc;
WCHAR *pPrincipal;
RPC_SECURITY_QOS sQos;
// Pick the highest authentication level between the process default
// and the interface hint. The constant RPC_C_AUTHN_LEVEL_DEFAULT
// has value zero and maps to connect.
if (gAuthnLevel == RPC_C_AUTHN_LEVEL_DEFAULT)
lAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT;
else
lAuthnLevel = gAuthnLevel;
if (pOxidInfo->dwAuthnHint == RPC_C_AUTHN_LEVEL_DEFAULT)
pOxidInfo->dwAuthnHint = RPC_C_AUTHN_LEVEL_CONNECT;
if (lAuthnLevel > pOxidInfo->dwAuthnHint)
lAuthnLevel = gAuthnLevel;
else
lAuthnLevel = pOxidInfo->dwAuthnHint;
// For machine local servers, only set the authentication information if
// the impersonation level is not the default.
sQos.Version = RPC_C_SECURITY_QOS_VERSION;
sQos.IdentityTracking = RPC_C_QOS_IDENTITY_STATIC;
sQos.ImpersonationType = gImpLevel;
sQos.Capabilities = (gCapabilities & EOAC_MUTUAL_AUTH) ?
RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH : RPC_C_QOS_CAPABILITIES_DEFAULT;
if (pOxid->dwFlags & OXIDF_MACHINE_LOCAL)
{
if (gImpLevel != RPC_C_IMP_LEVEL_IMPERSONATE)
{
sc = RpcBindingSetAuthInfoExW( hHandle, NULL, lAuthnLevel,
RPC_C_AUTHN_WINNT, NULL,
RPC_C_AUTHZ_NONE, &sQos );
if (sc != RPC_S_OK)
return MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, sc );
else
return S_OK;
}
}
// For machine remote servers, set the authentication information if any
// parameter differs from RPC's default.
else if (lAuthnLevel != RPC_C_AUTHN_LEVEL_NONE)
{
// Look through all the authentication services in the interface
// till we find one that works on this machine.
wNext = pOxidInfo->psa->wSecurityOffset;
while (wNext < pOxidInfo->psa->wNumEntries &&
pOxidInfo->psa->aStringArray[wNext] != 0)
{
if (IsLocalAuthnService( pOxidInfo->psa->aStringArray[wNext] ))
{
// Set the authentication info on the binding handle.
pPrincipal = &pOxidInfo->psa->aStringArray[wNext+2];
if (pPrincipal[0] == 0)
pPrincipal = NULL;
#ifdef _CHICAGO_
// If the principal name is not known, the server must be
// NT. Replace the principal name in that case
// because a NULL principal name is a flag for some
// Chicago security hack.
if (pPrincipal == NULL &&
pOxidInfo->psa->aStringArray[wNext] == RPC_C_AUTHN_WINNT)
pPrincipal = L"Default";
#endif // _CHICAGO_
wAuthzSvc = pOxidInfo->psa->aStringArray[wNext+1];
if (wAuthzSvc == COM_C_AUTHZ_NONE)
wAuthzSvc = RPC_C_AUTHZ_NONE;
sc = RpcBindingSetAuthInfoExW(
hHandle,
pPrincipal,
lAuthnLevel,
pOxidInfo->psa->aStringArray[wNext],
NULL,
wAuthzSvc,
&sQos );
if (sc != RPC_S_OK)
return MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, sc );
else
return S_OK;
}
// Skip to the next authentication service.
wNext += lstrlenW( &pOxidInfo->psa->aStringArray[wNext] ) + 1;
}
// No valid authentication service was found. This is an error.
return RPC_E_NO_GOOD_SECURITY_PACKAGES;
}
return S_OK;
}
//+-------------------------------------------------------------------
//
// Function: UninitializeSecurity, internal
//
// Synopsis: Free resources allocated while initializing security.
//
//--------------------------------------------------------------------
void UninitializeSecurity()
{
DWORD i;
ASSERT_LOCK_HELD
PrivMemFree(gSecDesc);
PrivMemFree(gpsaSecurity);
PrivMemFree( gRundownSD );
#ifndef SHRMEM_OBJEX
MIDL_user_free( gClientSvcList );
MIDL_user_free( gServerSvcList );
MIDL_user_free( gLegacySecurity );
#else // SHRMEM_OBJEX
delete [] gClientSvcList;
delete [] gServerSvcList;
delete [] gLegacySecurity;
#endif // SHRMEM_OBJEX
for (i = 0; i < ACCESS_CACHE_LEN; i++)
{
PrivMemFree( gAccessCache[i] );
gAccessCache[i] = NULL;
}
if (gAccessControl != NULL)
gAccessControl->Release();
gAccessControl = NULL;
gSecDesc = NULL;
gAuthnLevel = RPC_C_AUTHN_LEVEL_NONE;
gImpLevel = RPC_C_IMP_LEVEL_IDENTIFY;
gCapabilities = EOAC_NONE;
gLegacySecurity = NULL;
gpsaSecurity = NULL;
gClientSvcList = NULL;
gServerSvcList = NULL;
gGotSecurityData = FALSE;
gRundownSD = NULL;
gDefaultService = FALSE;
gMostRecentAccess = 0;
}
#endif