2025-11-28 00:35:46 +09:00

623 lines
16 KiB
C++

//***************************************************************************
//
// WBEMSEC.CPP
//
// Purpose: Provides some security helper functions.
//
// Copyright (c) Microsoft Corporation, All Rights Reserved
//
//***************************************************************************
//#undef _WIN32_WINNT
//#define _WIN32_WINNT 0x0400
#include "precomp.h"
#include <wbemidl.h>
#include "wbemsec.h"
//***************************************************************************
//
// InitializeSecurity(DWORD dwAuthLevel, DWORD dwImpLevel)
//
// DESCRIPTION:
//
// Initialize DCOM security. The authentication level is typically
// RPC_C_AUTHN_LEVEL_PKT_PRIVACY, and the impersonation level is typically
// RPC_C_IMP_LEVEL_IMPERSONATE.
// When using asynchronous WMI callbacks remotely in an environment where the "Local System" account
// has no network identity (such as non-Kerberos domains), the authentication level of
// RPC_C_AUTHN_LEVEL_NONE is needed. However, lowering the authentication level to
// RPC_C_AUTHN_LEVEL_NONE makes your application less secure. It is wise to
// use semi-synchronous API's for accessing WMI data and events instead of the asynchronous ones.
//
// RETURN VALUE:
//
// see description.
//
//***************************************************************************
HRESULT InitializeSecurity(DWORD dwAuthLevel, DWORD dwImpLevel)
{
// Initialize security
// ===================
DWORD dwCapabilities;
if (dwAuthLevel == RPC_C_AUTHN_LEVEL_NONE)
dwCapabilities = EOAC_NONE;
else
dwCapabilities = EOAC_SECURE_REFS;
return CoInitializeSecurity
(NULL, -1, NULL, NULL,
dwAuthLevel, dwImpLevel,
NULL,
dwCapabilities,
0);
}
//***************************************************************************
//
// bool bIsNT
//
// DESCRIPTION:
//
// Returns true if running windows NT.
//
// RETURN VALUE:
//
// see description.
//
//***************************************************************************
bool bIsNT(void)
{
OSVERSIONINFO os;
os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if(!GetVersionEx(&os))
return FALSE; // should never happen
return os.dwPlatformId == VER_PLATFORM_WIN32_NT;
}
//***************************************************************************
//
// SCODE ParseAuthorityUserArgs
//
// DESCRIPTION:
//
// NOTE: For Windows XP or later, consider calling CredUIParseUserName() instead of this method.
//
// Examines the Authority and User argument and determines the authentication
// type and possibly extracts the domain name from the user arugment in the
// NTLM case. For NTLM, the domain can be at the end of the authentication
// string, or in the front of the user name, ex: "TestDomain\JoeUser"
//
// PARAMETERS:
//
// ConnType Returned with the connection type, ie wbem, ntlm
// AuthArg Output, contains the domain name
// UserArg Output, user name
// Authority Input
// User Input
//
// RETURN VALUE:
//
// S_OK all is well
// else error listed in WBEMSVC.H
//
//***************************************************************************
SCODE ParseAuthorityUserArgs(BSTR & AuthArg, BSTR & UserArg,BSTR & Authority,BSTR & User)
{
// Determine the connection type by examining the Authority string
if(!(Authority == NULL || wcslen(Authority) == 0 || !_wcsnicmp(Authority, L"NTLMDOMAIN:",11)))
return E_INVALIDARG;
// The ntlm case is more complex. There are four cases
// 1) Authority = NTLMDOMAIN:name" and User = "User"
// 2) Authority = NULL and User = "User"
// 3) Authority = "NTLMDOMAIN:" User = "domain\user"
// 4) Authority = NULL and User = "domain\user"
// first step is to determine if there is a backslash in the user name somewhere between the
// second and second to last character
WCHAR * pSlashInUser = NULL;
if(User)
{
WCHAR * pEnd = User + wcslen(User) - 1;
for(pSlashInUser = User; pSlashInUser <= pEnd; pSlashInUser++)
if(*pSlashInUser == L'\\') // dont think forward slash is allowed!
break;
if(pSlashInUser > pEnd)
pSlashInUser = NULL;
}
if(Authority && wcslen(Authority) > 11)
{
if(pSlashInUser)
return E_INVALIDARG;
AuthArg = SysAllocString(Authority + 11);
if (!AuthArg)
return E_OUTOFMEMORY;
if(User)
{
UserArg = SysAllocString(User);
if (!UserArg)
return E_OUTOFMEMORY;
}
return S_OK;
}
else if(pSlashInUser)
{
INT_PTR iDomLen = min(MAX_PATH-1, pSlashInUser-User);
WCHAR cTemp[MAX_PATH];
wcsncpy_s(cTemp, User, iDomLen);
cTemp[iDomLen] = 0;
AuthArg = SysAllocString(cTemp);
if (!AuthArg)
return E_OUTOFMEMORY;
if(wcslen(pSlashInUser+1))
{
UserArg = SysAllocString(pSlashInUser+1);
if (!UserArg)
return E_OUTOFMEMORY;
}
}
else
if(User)
{
UserArg = SysAllocString(User);
if (!UserArg)
return E_OUTOFMEMORY;
}
return S_OK;
}
//***************************************************************************
//
// SCODE GetAuthImp
//
// DESCRIPTION:
//
// Gets the authentication and impersonation levels for a current interface.
//
// PARAMETERS:
//
// pFrom the interface to be tested.
// pdwAuthLevel Set to the authentication level
// pdwImpLevel Set to the impersonation level
// RETURN VALUE:
//
// S_OK all is well
// else error listed in WBEMSVC.H
//
//***************************************************************************
SCODE GetAuthImp(IUnknown * pFrom, DWORD * pdwAuthLevel, DWORD * pdwImpLevel)
{
if(pFrom == NULL || pdwAuthLevel == NULL || pdwImpLevel == NULL)
return WBEM_E_INVALID_PARAMETER;
IClientSecurity * pFromSec = NULL;
SCODE sc = pFrom->QueryInterface(IID_IClientSecurity, (void **) &pFromSec);
if(sc == S_OK)
{
DWORD dwAuthnSvc, dwAuthzSvc, dwCapabilities;
sc = pFromSec->QueryBlanket(pFrom, &dwAuthnSvc, &dwAuthzSvc,
NULL,
pdwAuthLevel, pdwImpLevel,
NULL, &dwCapabilities);
// Special case of going to a win9x share level box
if (sc == 0x800706d2)
{
*pdwAuthLevel = RPC_C_AUTHN_LEVEL_NONE;
*pdwImpLevel = RPC_C_IMP_LEVEL_IDENTIFY;
sc = S_OK;
}
pFromSec->Release();
}
return sc;
}
//***************************************************************************
//
// SCODE SetInterfaceSecurity
//
// DESCRIPTION:
//
// This routine is used by clients in order to set the security settings of a particular
// interface proxy. NOTE that setting the security blanket on the interface is not always needed:
// simple client applications that execute in their own process can typically just call
// CoInitializeSecurity( NULL, -1, NULL, NULL,
// RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
// RPC_C_IMP_LEVEL_IMPERSONATE,
// NULL,
// EOAC_NONE,
// NULL );
// before calling out to WMI.
// However, for clients that reside in DLLs and do not control
// the hosting process' COM security setting (MMC snap-ins, Visual Studio extensions and such),
// it is necessary to set security on WMI interface proxies by calling
// CoSetProxyBlanket() directly or through this helper.
//
// Another case where setting proxy security for WMI connection is needed is when
// credentials different from the client process user context must be specified.
//
//
// PARAMETERS:
//
// pInterface Interface to be set
// pDomain Input, domain
// pUser Input, user name
// pPassword Input, password.
// pFrom Input, if not NULL, then the authentication level of this interface
// is used
// bAuthArg If pFrom is NULL, then this is the authentication level
// RETURN VALUE:
//
// S_OK all is well
// else error listed in WBEMSVC.H
//
//***************************************************************************
HRESULT SetInterfaceSecurity(IUnknown * pInterface, LPWSTR pAuthority, LPWSTR pUser,
LPWSTR pPassword, DWORD dwAuthLevel, DWORD dwImpLevel)
{
SCODE sc;
if(pInterface == NULL)
return E_INVALIDARG;
// If we are lowering the security, no need to deal with the identification info
if(dwAuthLevel == RPC_C_AUTHN_LEVEL_NONE)
return CoSetProxyBlanket(pInterface, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
// If we are doing trivial case, just pass in a null authentication structure which is used
// if the current logged in user's credentials are OK.
if((pAuthority == NULL || wcslen(pAuthority) < 1) &&
(pUser == NULL || wcslen(pUser) < 1) &&
(pPassword == NULL || wcslen(pPassword) < 1))
return CoSetProxyBlanket(pInterface, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
dwAuthLevel, dwImpLevel, NULL, EOAC_NONE);
// If user, or Authority was passed in, the we need to create an authority argument for the login
COAUTHIDENTITY authident;
BSTR AuthArg = NULL, UserArg = NULL;
sc = ParseAuthorityUserArgs(AuthArg, UserArg, pAuthority, pUser);
size_t * intReturnValue = NULL;
if(sc != S_OK)
return sc;
memset((void *)&authident,0,sizeof(COAUTHIDENTITY));
if(bIsNT())
{
if(UserArg)
{
authident.UserLength = (ULONG)wcslen(UserArg);
authident.User = (USHORT*)UserArg;
}
if(AuthArg)
{
authident.DomainLength = (ULONG)wcslen(AuthArg);
authident.Domain = (USHORT*)AuthArg;
}
if(pPassword)
{
authident.PasswordLength = (ULONG)wcslen(pPassword);
authident.Password = (USHORT*)pPassword;
}
authident.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
}
else
{
char szUser[MAX_PATH], szAuthority[MAX_PATH], szPassword[MAX_PATH];
ZeroMemory(szUser, sizeof(szUser));
ZeroMemory(szAuthority, sizeof(szAuthority));
ZeroMemory(szPassword, sizeof(szPassword));
size_t written = 0;
// Fill in the indentity structure
if(UserArg)
{
written = wcstombs_s(intReturnValue, szUser, sizeof(szUser), UserArg, sizeof(szUser));
//written = wcstombs(szUser, UserArg, sizeof(szUser));
if (written < sizeof(szUser) - 1)
{
//we could retry by calling wcstombs to find out required buffer size, but this is a simple sample
sc = E_INVALIDARG;
goto cleanup;
}
authident.UserLength = (ULONG)strlen(szUser);
authident.User = (USHORT*)szUser;
}
if(AuthArg)
{
written = wcstombs_s(intReturnValue, szAuthority, sizeof(szAuthority), AuthArg, sizeof(szAuthority));
//written = wcstombs(szAuthority, AuthArg, sizeof(szAuthority));
if (written < sizeof(szAuthority) - 1)
{
//we could retry by calling wcstombs to find out required buffer size, but this is a simple sample
sc = E_INVALIDARG;
goto cleanup;
}
authident.DomainLength = (ULONG)strlen(szAuthority);
authident.Domain = (USHORT*)szAuthority;
}
if(pPassword)
{
written = wcstombs_s(intReturnValue, szPassword, sizeof(szPassword), pPassword, sizeof(szPassword));
//written = wcstombs(szPassword, pPassword, sizeof(szPassword));
if (written < sizeof(szPassword) - 1)
{
//we could retry by calling wcstombs to find out required buffer size, but this is a simple sample
sc = E_INVALIDARG;
goto cleanup;
}
authident.PasswordLength = (ULONG)strlen(szPassword);
authident.Password = (USHORT*)szPassword;
}
authident.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
}
sc = CoSetProxyBlanket(pInterface, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
dwAuthLevel, dwImpLevel, &authident, EOAC_NONE);
cleanup:
if(UserArg)
SysFreeString(UserArg);
if(AuthArg)
SysFreeString(AuthArg);
return sc;
}
/******************************************************************************
*
* Name: CheckAccess
*
* Description: Allow provider to evaluate permissions against a security descriptor
*
* This method should be called by WMI providers in scenarios where
* they cannot or should not impersonate the client. This happens in two scenarios:
* a) when the providers access resources that are not protected by ACL's
* b) when the client connects at the impersonation level of RPC_C_IMP_LEVEL_IDENTIFY
*
*****************************************************************************/
HRESULT CheckAccess (SECURITY_DESCRIPTOR *a_SecurityDescriptor ,
DWORD a_Access ,
GENERIC_MAPPING *a_Mapping)
{
HRESULT t_Result = S_OK ;
HANDLE t_Token = NULL ;
BOOL t_Status = OpenThreadToken (
GetCurrentThread () ,
TOKEN_QUERY ,
TRUE ,
& t_Token
) ;
DWORD t_LastError = GetLastError () ;
if ( ! t_Status)
{
//the thread token should always be available
switch ( t_LastError )
{
case E_ACCESSDENIED:
{
return WBEM_E_ACCESS_DENIED ;
}
break ;
default:
{
return WBEM_E_FAILED ;
}
break ;
}
}
DWORD t_Access = 0 ;
BOOL t_AccessStatus = FALSE ;
PRIVILEGE_SET *t_PrivilegeSet = NULL ;
DWORD t_PrivilegeSetSize = 0 ;
MapGenericMask (
& a_Access ,
a_Mapping
) ;
t_Status = AccessCheck (
a_SecurityDescriptor ,
t_Token,
a_Access ,
a_Mapping ,
NULL ,
& t_PrivilegeSetSize ,
& t_Access ,
& t_AccessStatus
) ;
if (!t_Status || !t_AccessStatus )
{
DWORD t_LastError = GetLastError () ;
if ( t_LastError == ERROR_INSUFFICIENT_BUFFER )
{
t_PrivilegeSet = ( PRIVILEGE_SET * ) new BYTE [ t_PrivilegeSetSize ] ;
if ( t_PrivilegeSet )
{
t_Status = AccessCheck (
a_SecurityDescriptor ,
t_Token,
a_Access ,
a_Mapping ,
t_PrivilegeSet ,
& t_PrivilegeSetSize ,
& t_Access ,
& t_AccessStatus
) ;
if ( !t_Status || !t_AccessStatus )
{
t_Result = WBEM_E_ACCESS_DENIED ;
}
delete [] ( BYTE * ) t_PrivilegeSet ;
}
else
{
t_Result = WBEM_E_OUT_OF_MEMORY ;
}
}
else
{
t_Result = WBEM_E_ACCESS_DENIED;
}
}
CloseHandle ( t_Token ) ;
return t_Result ;
}
/******************************************************************************
*
* Name: GetCurrentImpersonationLevel
*
*
* Description:
*
* Get COM impersonation level of caller.
*
*****************************************************************************/
DWORD GetCurrentImpersonationLevel ()
{
DWORD t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
HANDLE t_ThreadToken = NULL ;
BOOL t_Status = OpenThreadToken (
GetCurrentThread() ,
TOKEN_QUERY,
TRUE,
&t_ThreadToken
) ;
if ( t_Status )
{
SECURITY_IMPERSONATION_LEVEL t_Level = SecurityAnonymous ;
DWORD t_Returned = 0 ;
t_Status = GetTokenInformation (
t_ThreadToken ,
TokenImpersonationLevel ,
& t_Level ,
sizeof ( SECURITY_IMPERSONATION_LEVEL ) ,
& t_Returned
) ;
CloseHandle ( t_ThreadToken ) ;
if ( t_Status == FALSE )
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
}
else
{
switch ( t_Level )
{
case SecurityAnonymous:
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
}
break ;
case SecurityIdentification:
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_IDENTIFY ;
}
break ;
case SecurityImpersonation:
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE ;
}
break ;
case SecurityDelegation:
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_DELEGATE ;
}
break ;
default:
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
}
break ;
}
}
}
else
{
ULONG t_LastError = GetLastError () ;
if ( t_LastError == ERROR_NO_IMPERSONATION_TOKEN || t_LastError == ERROR_NO_TOKEN )
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_DELEGATE ;
}
else
{
if ( t_LastError == ERROR_CANT_OPEN_ANONYMOUS )
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
}
else
{
t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
}
}
}
return t_ImpersonationLevel ;
}