//+------------------------------------------------------------------- // // 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 #include #include #include #include #include #include #include #include #ifdef _CHICAGO_ #include #include #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