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

2458 lines
73 KiB

/*++
Copyright (c) 1999 Microsoft Corporation
Abstract:
@doc
@module security.cxx | Implementation of IsAdministrator
@end
Author:
Adi Oltean [aoltean] 07/09/1999
TBD:
Add comments.
Revision History:
Name Date Comments
aoltean 07/09/1999 Created
aoltean 08/26/1999 Adding RegisterProvider
aoltean 08/26/1999 Adding UnregisterProvider
aoltean 08/27/1999 Adding IsAdministrator,
Adding unique provider name test.
aoltean 08/30/1999 Calling OnUnregister on un-registering
Improving IsProviderNameAlreadyUsed.
aoltean 09/09/1999 dss -> vss
aoltean 09/21/1999 Adding a new header for the "ptr" class.
aoltean 09/27/1999 Adding new headers
aoltean 10/15/1999 Moving declaration in security.hxx
aoltean 01/18/2000 Moved into a separate directory
brianb 04/04/2000 Add IsBackupOperator
brianb 04/27/2000 Change IsBackupOperator to check SE_BACKUP_NAME privilege
brianb 05/03/2000 Added GetClientTokenOwner method
brianb 05/10/2000 fix problem with uninitialized variable
brianb 05/12/2000 handle in proc case for impersonation failures
--*/
/////////////////////////////////////////////////////////////////////////////
// Includes
#include "stdafx.hxx"
// This must be included before vs_inc.h since contains the NetApiXXX
#include <lm.h>
#include "vs_inc.hxx"
#include "vs_sec.hxx"
#include "sddl.h"
#include "vs_reg.hxx"
#include "vssmsg.h"
////////////////////////////////////////////////////////////////////////
// Standard foo for file name aliasing. This code block must be after
// all includes of VSS header files.
//
#ifdef VSS_FILE_ALIAS
#undef VSS_FILE_ALIAS
#endif
#define VSS_FILE_ALIAS "SECSECRC"
//
////////////////////////////////////////////////////////////////////////
BOOL GetCurrentAccessToken
(
IN BOOL bPerformImpersonation,
OUT HANDLE *phToken
)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"GetCurrentAccessToken");
BS_ASSERT(phToken);
if (bPerformImpersonation)
{
// Impersonate the client to get its identity access token.
// The client should not have RPC_C_IMP_LEVEL_ANONYMOUS otherwise an error will be returned
ft.hr = ::CoImpersonateClient();
if (ft.HrFailed())
{
if (ft.hr != RPC_E_CALL_COMPLETE)
ft.TranslateGenericError( VSSDBG_GEN, ft.hr, L"CoImpersonateClient");
// this means that the call came from the same thread
// Do not perform impersonation. Just use the process
// token
if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, phToken))
ft.TranslateWin32Error(VSSDBG_GEN,L"OpenProcessToken");
ft.hr = S_OK;
return FALSE;
}
BOOL bRes = ::OpenThreadToken
(
GetCurrentThread(), // IN HANDLE ThreadHandle,
TOKEN_QUERY, // IN DWORD DesiredAccess,
TRUE, // IN BOOL OpenAsSelf
phToken // OUT PHANDLE TokenHandle
);
HRESULT hrErr = HRESULT_FROM_WIN32(GetLastError());
// Revert the thread's access token - finish the impersonation
ft.hr = ::CoRevertToSelf();
if (ft.HrFailed())
ft.TranslateWin32Error(VSSDBG_GEN,L"CoRevertToSelf");
if (!bRes)
ft.TranslateGenericError(VSSDBG_GEN, hrErr, L"OpenThreadToken");
return TRUE;
}
else // i.e. if (!bPerformImpersonation)
{
// First try to get the thread token. (assuming we are already impersonating)
// If we succeed, we will go with this.
// If we fail, we ignore the error and proceed with OpenProcessToken (who must succeed).
if (::OpenThreadToken
(
GetCurrentThread(), // IN HANDLE ThreadHandle,
TOKEN_QUERY, // IN DWORD DesiredAccess,
TRUE, // IN BOOL OpenAsSelf
phToken // OUT PHANDLE TokenHandle
))
return TRUE;
// We don't have a thread token (so we cannot be under impersonation).
// Now try to get the process token
if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, phToken))
ft.TranslateWin32Error(VSSDBG_GEN, L"OpenProcessToken");
return FALSE;
}
}
bool IsInGroup(DWORD dwGroup, bool bImpersonate)
/*++
Routine Description:
Return TRUE if the current thread/process is running under the context of an administrator
Arguments:
none
Remarks:
The current impersonation thread is asked for the access token.
After that we check if the specified group is between token groups.
Return Value:
true, if the caller thread is running under the context of the specified group
false, otherwise
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
CVssFunctionTracer ft( VSSDBG_GEN, L"IsInGroup" );
BOOL bIsInGroup = FALSE;
PSID psidGroup = NULL;
BOOL bRes;
// Reset the error code
ft.hr = S_OK;
// Build the SID for the Administrators group
SID_IDENTIFIER_AUTHORITY SidAuth = SECURITY_NT_AUTHORITY;
bRes = AllocateAndInitializeSid
(
&SidAuth, // IN PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority,
2, // IN BYTE nSubAuthorityCount,
SECURITY_BUILTIN_DOMAIN_RID, // IN DWORD nSubAuthority0,
dwGroup, // IN DWORD nSubAuthority1,
0, // IN DWORD nSubAuthority2,
0, // IN DWORD nSubAuthority3,
0, // IN DWORD nSubAuthority4,
0, // IN DWORD nSubAuthority5,
0, // IN DWORD nSubAuthority6,
0, // IN DWORD nSubAuthority7,
&psidGroup // OUT PSID *pSid
);
if (!bRes)
ft.TranslateError
(
VSSDBG_GEN,
HRESULT_FROM_WIN32(GetLastError()),
L"AllocateAndInitializeSid"
);
try
{
if (!bImpersonate)
bRes = CheckTokenMembership(NULL, psidGroup, &bIsInGroup);
else
{
CVssAutoWin32Handle hToken;
// impersonate client (or get process token)
if (GetCurrentAccessToken(true, hToken.ResetAndGetAddress()))
// check token membership
bRes = CheckTokenMembership(hToken, psidGroup, &bIsInGroup);
else
// called from same thread
bRes = CheckTokenMembership(NULL, psidGroup, &bIsInGroup);
}
if (!bRes)
ft.TranslateError
(
VSSDBG_GEN,
HRESULT_FROM_WIN32(GetLastError()),
L"CheckTokenMembership"
);
}
VSS_STANDARD_CATCH(ft)
HRESULT hr = ft.hr;
// Catch possible AVs
try
{
// Free the previously allocated SID
if (psidGroup)
::FreeSid( psidGroup );
}
VSS_STANDARD_CATCH(ft)
// Pass down the exception, if any
if (FAILED(hr))
throw(hr);
return bIsInGroup ? true : false;
}
bool HasPrivilege(LPWSTR wszPriv, bool bImpersonate)
/*++
Routine Description:
Return TRUE if the current thread/process has a specific privilege
Arguments:
none
Remarks:
The current impersonation thread is asked for the access token.
After that we check if the specified group is between token groups.
Return Value:
true, if the caller thread is running under the context of the specified group
false, otherwise
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
CVssFunctionTracer ft( VSSDBG_GEN, L"HasPrivilege" );
BOOL bHasPrivilege = false;
CVssAutoWin32Handle hToken;
LUID TokenValue;
if (!LookupPrivilegeValue (NULL, wszPriv, &TokenValue))
ft.TranslateError
(
VSSDBG_GEN,
HRESULT_FROM_WIN32(GetLastError()),
L"LookupPrivilegeValue"
);
GetCurrentAccessToken(bImpersonate, hToken.ResetAndGetAddress());
BYTE rgb[sizeof(LUID_AND_ATTRIBUTES) + sizeof(PRIVILEGE_SET)];
PRIVILEGE_SET *pSet = (PRIVILEGE_SET *) rgb;
pSet->PrivilegeCount = 1;
pSet->Control = PRIVILEGE_SET_ALL_NECESSARY;
pSet->Privilege[0].Luid = TokenValue;
pSet->Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!PrivilegeCheck(hToken, pSet, &bHasPrivilege))
ft.TranslateError
(
VSSDBG_GEN,
HRESULT_FROM_WIN32(GetLastError()),
L"PrivilegeCheck"
);
return bHasPrivilege ? true : false;
}
TOKEN_USER * GetClientTokenUser(BOOL bImpersonate)
/*++
Routine Description:
Return TOKEN_USER of client process
Arguments:
bImpersonate - TRUE iif an impersonation is required.
Remarks:
The current impersonation thread is asked for the access token.
After that we return the client sid of that token
Return Value:
SID of client thread
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
CVssFunctionTracer ft( VSSDBG_GEN, L"GetClientTokenUser" );
BOOL bRes;
CVssAutoWin32Handle hToken;
GetCurrentAccessToken(bImpersonate, hToken.ResetAndGetAddress());
DWORD cbSid;
bRes = ::GetTokenInformation
(
hToken, // IN HANDLE TokenHandle,
TokenUser, // IN TOKEN_INFORMATION_CLASS TokenInformationClass,
NULL, // OUT LPVOID TokenInformation,
0, // IN DWORD TokenInformationLength,
&cbSid // OUT PDWORD ReturnLength
);
BS_ASSERT( bRes == FALSE );
DWORD dwError = GetLastError();
if ( dwError != ERROR_INSUFFICIENT_BUFFER )
{
ft.LogError(VSS_ERROR_EXPECTED_INSUFFICENT_BUFFER, VSSDBG_GEN << (HRESULT) dwError);
ft.Throw
(
VSSDBG_GEN,
E_UNEXPECTED,
L"ERROR_INSUFFICIENT_BUFFER expected error . [0x%08lx]",
dwError
);
}
// Allocate the buffer needed to get the Token Groups information
CVssAutoCppPtr<TOKEN_USER*> ptrTokenUser;
ptrTokenUser.AllocateBytes(cbSid);
// Get the all Group SIDs in the token
DWORD cbTokenObtained;
bRes = ::GetTokenInformation
(
hToken, // IN HANDLE TokenHandle,
TokenUser, // IN TOKEN_INFORMATION_CLASS TokenInformationClass,
ptrTokenUser, // OUT LPVOID TokenInformation,
cbSid, // IN DWORD TokenInformationLength,
&cbTokenObtained // OUT PDWORD ReturnLength
);
if ( !bRes )
ft.TranslateError
(
VSSDBG_GEN,
HRESULT_FROM_WIN32(GetLastError()),
L"GetTokenInformation"
);
if (cbTokenObtained != cbSid)
{
ft.LogError(VSS_ERROR_GET_TOKEN_INFORMATION_BUFFER_SIZE_MISMATCH, VSSDBG_GEN << (INT) cbTokenObtained << (INT) cbSid);
ft.Throw
(
VSSDBG_GEN,
E_UNEXPECTED,
L"Unexpected error. Final buffer size = %lu, original size was %lu",
cbTokenObtained,
cbSid
);
}
return ptrTokenUser.Detach();
}
TOKEN_OWNER * GetClientTokenOwner(BOOL bImpersonate)
/*++
Routine Description:
Return TOKEN_OWNER of client process
Arguments:
none
Remarks:
The current impersonation thread is asked for the access token.
After that we return the client sid of that token
Return Value:
SID of client thread
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
CVssFunctionTracer ft( VSSDBG_GEN, L"GetClientTokenOwner" );
BOOL bRes;
CVssAutoWin32Handle hToken;
GetCurrentAccessToken(bImpersonate, hToken.ResetAndGetAddress());
DWORD cbSid;
bRes = ::GetTokenInformation
(
hToken, // IN HANDLE TokenHandle,
TokenOwner, // IN TOKEN_INFORMATION_CLASS TokenInformationClass,
NULL, // OUT LPVOID TokenInformation,
0, // IN DWORD TokenInformationLength,
&cbSid // OUT PDWORD ReturnLength
);
BS_ASSERT( bRes == FALSE );
DWORD dwError = GetLastError();
if ( dwError != ERROR_INSUFFICIENT_BUFFER )
{
ft.LogError(VSS_ERROR_EXPECTED_INSUFFICENT_BUFFER, VSSDBG_GEN << (HRESULT) dwError);
ft.Throw
(
VSSDBG_GEN,
E_UNEXPECTED,
L"ERROR_INSUFFICIENT_BUFFER expected error . [0x%08lx]",
dwError
);
}
// Allocate the buffer needed to get the Token Groups information
CVssAutoCppPtr<TOKEN_OWNER*> ptrTokenOwner;
ptrTokenOwner.AllocateBytes(cbSid);
// Get the all Group SIDs in the token
DWORD cbTokenObtained;
bRes = ::GetTokenInformation
(
hToken, // IN HANDLE TokenHandle,
TokenOwner, // IN TOKEN_INFORMATION_CLASS TokenInformationClass,
ptrTokenOwner, // OUT LPVOID TokenInformation,
cbSid, // IN DWORD TokenInformationLength,
&cbTokenObtained // OUT PDWORD ReturnLength
);
if ( !bRes )
ft.TranslateError
(
VSSDBG_GEN,
HRESULT_FROM_WIN32(GetLastError()),
L"GetTokenInformation"
);
if (cbTokenObtained != cbSid)
{
ft.LogError(VSS_ERROR_GET_TOKEN_INFORMATION_BUFFER_SIZE_MISMATCH, VSSDBG_GEN << (INT) cbTokenObtained << (INT) cbSid);
ft.Throw
(
VSSDBG_GEN,
E_UNEXPECTED,
L"Unexpected error. Final buffer size = %lu, original size was %lu",
cbTokenObtained,
cbSid
);
}
return ptrTokenOwner.Detach();
}
bool IsAdministrator()
/*++
Routine Description:
Return TRUE if the current thread/process is running under the context of an administrator
Arguments:
none
Remarks:
The current impersonation thread is asked for the access token.
After that we check if the Administrators group is between token groups.
Return Value:
true, if the caller thread is running under the context of an administrator
false, otherwise
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
return IsInGroup(DOMAIN_ALIAS_RID_ADMINS, true);
}
bool IsProcessAdministrator()
/*++
Routine Description:
Return TRUE if the current process is running under the context of an administrator
Arguments:
none
Remarks:
The current process is asked for the access token.
After that we check if the Administrators group is between token groups.
Return Value:
true, if the process is running under the context of an administrator
false, otherwise
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
return IsInGroup(DOMAIN_ALIAS_RID_ADMINS, false);
}
bool IsBackupOperator()
/*++
Routine Description:
Return TRUE if the current thread/process is running under the context of a backup operator
Arguments:
none
Remarks:
The current impersonation thread is asked for the access token.
After that we check if the Administrators group is in the groups token
or the backup privilege is enabled
Return Value:
true, if the caller thread is running under the context of an administrator
false, otherwise
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
// Bug #554480 VSS: Authorize backup operators based on group membership not privilege
// return HasPrivilege(SE_BACKUP_NAME, true) || IsAdministrator();
return HasPrivilege(SE_BACKUP_NAME, true)
|| IsInGroup(DOMAIN_ALIAS_RID_BACKUP_OPS, true)
|| IsAdministrator();
}
bool IsRestoreOperator()
/*++
Routine Description:
Return TRUE if the current thread/process is running under the context of a restore operator
Arguments:
none
Remarks:
The current impersonation thread is asked for the access token.
After that we check if the Administrators group is in the token groups or
if the restore privilege is enabled.
Return Value:
true, if the caller thread is running under the context of an administrator
false, otherwise
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
return HasPrivilege(SE_RESTORE_NAME, true) || IsAdministrator();
}
bool IsProcessBackupOperator()
/*++
Routine Description:
Return TRUE if the current process is running under the context of a backup operator
Arguments:
none
Remarks:
Return Value:
true, if the process is running under the context of an administrator or
has SE_BACKUP_NAME privilege enabled
false, otherwise
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
// Bug #554480 VSS: Authorize backup operators based on group membership not privilege
// (We still keep the check for backup privilege, for safety)
// return HasPrivilege(SE_BACKUP_NAME, false) || IsProcessAdministrator();
return HasPrivilege(SE_BACKUP_NAME, false)
|| IsInGroup(DOMAIN_ALIAS_RID_BACKUP_OPS, false)
|| IsProcessAdministrator();
}
bool IsProcessRestoreOperator()
/*++
Routine Description:
Return TRUE if the current process is running under the context of a restore operator
Arguments:
none
Remarks:
Return Value:
true, if the process is running under the context of an administrator
or has the SE_RESTORE_NAME privilege; false otherwise
Thrown exceptions:
E_UNEXPECTED // Runtime error
E_OUTOFMEMORY // Memory allocation error
--*/
{
return HasPrivilege(SE_RESTORE_NAME, false) || IsProcessAdministrator();
}
// turn on a particular security privilege
HRESULT TurnOnSecurityPrivilege(LPCWSTR wszPriv)
/*++
Routine Description:
sets the specified privilege on the process token
Arguments:
none
Remarks:
Return Value:
status code for operation
Thrown exceptions:
none
--*/
{
CVssFunctionTracer ft(VSSDBG_GEN, L"TurnOnSecurityPrivilege");
CVssAutoWin32Handle hProcessToken;
try
{
LUID TokenValue = {0, 0};
if (!OpenProcessToken (
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES,
hProcessToken.ResetAndGetAddress()
))
ft.TranslateWin32Error(VSSDBG_GEN, L"OpenProcessToken");
if (!LookupPrivilegeValue (NULL, wszPriv, &TokenValue))
ft.TranslateWin32Error(VSSDBG_GEN, L"LookupPrivilegeValue(%s)", wszPriv);
TOKEN_PRIVILEGES NewTokenPrivileges;
NewTokenPrivileges.PrivilegeCount = 1;
NewTokenPrivileges.Privileges[0].Luid = TokenValue;
NewTokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// AdjustTokenPrivileges succeeds even the token isn't set
SetLastError(ERROR_SUCCESS);
if (!AdjustTokenPrivileges(
hProcessToken,
FALSE,
&NewTokenPrivileges,
sizeof (NewTokenPrivileges),
NULL,
NULL
))
ft.TranslateWin32Error(VSSDBG_GEN, L"AdjustTokenPrivileges");
}
VSS_STANDARD_CATCH(ft)
return ft.hr;
}
// turn on backup security privilege
HRESULT TurnOnSecurityPrivilegeBackup()
{
return TurnOnSecurityPrivilege(SE_BACKUP_NAME);
}
// turn on restore security privilege
HRESULT TurnOnSecurityPrivilegeRestore()
{
return TurnOnSecurityPrivilege(SE_RESTORE_NAME);
}
// create a basic well known sid such as LOCAL_SERVICE, LOCAL_SYSTEM,
// or NETWORK_SERVICE.
void CAutoSid::CreateBasicSid(WELL_KNOWN_SID_TYPE type)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CAutoSid::CreateBasicSid");
BS_ASSERT(!Base::IsValid());
DWORD cbSid = 0;
CreateWellKnownSid(type, NULL, NULL, &cbSid);
DWORD dwErr = GetLastError();
if (dwErr != ERROR_INSUFFICIENT_BUFFER)
{
ft.hr = HRESULT_FROM_WIN32(dwErr);
ft.CheckForError(VSSDBG_GEN, L"CreateWellKnownSidType");
}
CVssAutoLocalPtr<SID*> pSid;
pSid.AllocateBytes(cbSid);
if (!CreateWellKnownSid(type, NULL, pSid, &cbSid))
{
ft.hr = HRESULT_FROM_WIN32(GetLastError());
ft.CheckForError(VSSDBG_GEN, L"CreateWellKnownSidType");
}
Base::Attach(pSid.Detach());
}
// create a sid based on a STRING sid
void CAutoSid::CreateFromString(LPCWSTR wsz)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CAutoSid::CreateFromString");
BS_ASSERT(!Base::IsValid());
if (!ConvertStringSidToSid (wsz, (PSID*)&Base::GetPtrObject()))
{
ft.hr = HRESULT_FROM_WIN32(GetLastError());
ft.CheckForError(VSSDBG_GEN, L"ConvertStringSidToSid");
}
}
//////////////////////////////////////////////////////////////////////////////
// CVssSecurityDescriptor
//
CVssSecurityDescriptor::CVssSecurityDescriptor()
{
m_pSD = NULL;
m_pOwner = NULL;
m_pGroup = NULL;
m_pDACL = NULL;
m_pSACL= NULL;
}
CVssSecurityDescriptor::~CVssSecurityDescriptor()
{
if (m_pSD)
delete m_pSD;
if (m_pOwner)
free(m_pOwner);
if (m_pGroup)
free(m_pGroup);
if (m_pDACL)
free(m_pDACL);
if (m_pSACL)
free(m_pSACL);
}
HRESULT CVssSecurityDescriptor::Initialize()
{
if (m_pSD)
{
delete m_pSD;
m_pSD = NULL;
}
if (m_pOwner)
{
free(m_pOwner);
m_pOwner = NULL;
}
if (m_pGroup)
{
free(m_pGroup);
m_pGroup = NULL;
}
if (m_pDACL)
{
free(m_pDACL);
m_pDACL = NULL;
}
if (m_pSACL)
{
free(m_pSACL);
m_pSACL = NULL;
}
ATLTRY(m_pSD = new SECURITY_DESCRIPTOR);
if (m_pSD == NULL)
return E_OUTOFMEMORY;
if (!InitializeSecurityDescriptor(m_pSD, SECURITY_DESCRIPTOR_REVISION))
{
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
delete m_pSD;
m_pSD = NULL;
ATLASSERT(FALSE);
return hr;
}
return S_OK;
}
HRESULT CVssSecurityDescriptor::InitializeFromProcessToken(BOOL bDefaulted)
{
PSID pUserSid = NULL;
PSID pGroupSid = NULL;
HRESULT hr;
Initialize();
hr = GetProcessSids(&pUserSid, &pGroupSid);
if (SUCCEEDED(hr))
{
hr = SetOwner(pUserSid, bDefaulted);
if (SUCCEEDED(hr))
hr = SetGroup(pGroupSid, bDefaulted);
}
if (pUserSid != NULL)
free(pUserSid);
if (pGroupSid != NULL)
free(pGroupSid);
return hr;
}
HRESULT CVssSecurityDescriptor::InitializeFromThreadToken(BOOL bDefaulted, BOOL bRevertToProcessToken)
{
PSID pUserSid = NULL;
PSID pGroupSid = NULL;
HRESULT hr;
Initialize();
hr = GetThreadSids(&pUserSid, &pGroupSid);
if (HRESULT_CODE(hr) == ERROR_NO_TOKEN && bRevertToProcessToken)
hr = GetProcessSids(&pUserSid, &pGroupSid);
if (SUCCEEDED(hr))
{
hr = SetOwner(pUserSid, bDefaulted);
if (SUCCEEDED(hr))
hr = SetGroup(pGroupSid, bDefaulted);
}
if (pUserSid != NULL)
free(pUserSid);
if (pGroupSid != NULL)
free(pGroupSid);
return hr;
}
HRESULT CVssSecurityDescriptor::SetOwner(PSID pOwnerSid, BOOL bDefaulted)
{
ATLASSERT(m_pSD);
// Mark the SD as having no owner
if (!SetSecurityDescriptorOwner(m_pSD, NULL, bDefaulted))
{
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
return hr;
}
if (m_pOwner)
{
free(m_pOwner);
m_pOwner = NULL;
}
// If they asked for no owner don't do the copy
if (pOwnerSid == NULL)
return S_OK;
// Make a copy of the Sid for the return value
DWORD dwSize = GetLengthSid(pOwnerSid);
m_pOwner = (PSID) malloc(dwSize);
if (m_pOwner == NULL)
return E_OUTOFMEMORY;
if (!CopySid(dwSize, m_pOwner, pOwnerSid))
{
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
free(m_pOwner);
m_pOwner = NULL;
return hr;
}
ATLASSERT(IsValidSid(m_pOwner));
if (!SetSecurityDescriptorOwner(m_pSD, m_pOwner, bDefaulted))
{
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
free(m_pOwner);
m_pOwner = NULL;
return hr;
}
return S_OK;
}
HRESULT CVssSecurityDescriptor::SetGroup(PSID pGroupSid, BOOL bDefaulted)
{
ATLASSERT(m_pSD);
// Mark the SD as having no Group
if (!SetSecurityDescriptorGroup(m_pSD, NULL, bDefaulted))
{
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
return hr;
}
if (m_pGroup)
{
free(m_pGroup);
m_pGroup = NULL;
}
// If they asked for no Group don't do the copy
if (pGroupSid == NULL)
return S_OK;
// Make a copy of the Sid for the return value
DWORD dwSize = GetLengthSid(pGroupSid);
m_pGroup = (PSID) malloc(dwSize);
if (m_pGroup == NULL)
return E_OUTOFMEMORY;
if (!CopySid(dwSize, m_pGroup, pGroupSid))
{
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
free(m_pGroup);
m_pGroup = NULL;
return hr;
}
ATLASSERT(IsValidSid(m_pGroup));
if (!SetSecurityDescriptorGroup(m_pSD, m_pGroup, bDefaulted))
{
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
free(m_pGroup);
m_pGroup = NULL;
return hr;
}
return S_OK;
}
HRESULT CVssSecurityDescriptor::Allow(LPCTSTR pszPrincipal, DWORD dwAccessMask, DWORD dwAceFlags)
{
HRESULT hr = AddAccessAllowedACEToACL(&m_pDACL, pszPrincipal, dwAccessMask, dwAceFlags);
if (SUCCEEDED(hr))
SetSecurityDescriptorDacl(m_pSD, TRUE, m_pDACL, FALSE);
return hr;
}
HRESULT CVssSecurityDescriptor::Deny(LPCTSTR pszPrincipal, DWORD dwAccessMask, DWORD dwAceFlags)
{
HRESULT hr = AddAccessDeniedACEToACL(&m_pDACL, pszPrincipal, dwAccessMask, dwAceFlags);
if (SUCCEEDED(hr))
SetSecurityDescriptorDacl(m_pSD, TRUE, m_pDACL, FALSE);
return hr;
}
HRESULT CVssSecurityDescriptor::Allow(PSID pSid, DWORD dwAccessMask, DWORD dwAceFlags)
{
HRESULT hr = AddAccessAllowedACEToACL(&m_pDACL, pSid, dwAccessMask, dwAceFlags);
if (SUCCEEDED(hr))
SetSecurityDescriptorDacl(m_pSD, TRUE, m_pDACL, FALSE);
return hr;
}
HRESULT CVssSecurityDescriptor::Deny(PSID pSid, DWORD dwAccessMask, DWORD dwAceFlags)
{
HRESULT hr = AddAccessDeniedACEToACL(&m_pDACL, pSid, dwAccessMask, dwAceFlags);
if (SUCCEEDED(hr))
SetSecurityDescriptorDacl(m_pSD, TRUE, m_pDACL, FALSE);
return hr;
}
HRESULT CVssSecurityDescriptor::Revoke(LPCTSTR pszPrincipal)
{
HRESULT hr = RemovePrincipalFromACL(m_pDACL, pszPrincipal);
if (SUCCEEDED(hr))
SetSecurityDescriptorDacl(m_pSD, TRUE, m_pDACL, FALSE);
return hr;
}
HRESULT CVssSecurityDescriptor::GetProcessSids(PSID* ppUserSid, PSID* ppGroupSid)
{
BOOL bRes;
HRESULT hr;
HANDLE hToken = NULL;
if (ppUserSid)
*ppUserSid = NULL;
if (ppGroupSid)
*ppGroupSid = NULL;
bRes = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
if (!bRes)
{
// Couldn't open process token
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
return hr;
}
hr = GetTokenSids(hToken, ppUserSid, ppGroupSid);
CloseHandle(hToken);
return hr;
}
HRESULT CVssSecurityDescriptor::GetThreadSids(PSID* ppUserSid, PSID* ppGroupSid, BOOL bOpenAsSelf)
{
BOOL bRes;
HRESULT hr;
HANDLE hToken = NULL;
if (ppUserSid)
*ppUserSid = NULL;
if (ppGroupSid)
*ppGroupSid = NULL;
bRes = OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, bOpenAsSelf, &hToken);
if (!bRes)
{
// Couldn't open thread token
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
hr = GetTokenSids(hToken, ppUserSid, ppGroupSid);
CloseHandle(hToken);
return hr;
}
HRESULT CVssSecurityDescriptor::GetTokenSids(HANDLE hToken, PSID* ppUserSid, PSID* ppGroupSid)
{
DWORD dwSize;
HRESULT hr;
PTOKEN_USER ptkUser = NULL;
PTOKEN_PRIMARY_GROUP ptkGroup = NULL;
if (ppUserSid)
*ppUserSid = NULL;
if (ppGroupSid)
*ppGroupSid = NULL;
if (ppUserSid)
{
// Get length required for TokenUser by specifying buffer length of 0
GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
hr = GetLastError();
if (hr != ERROR_INSUFFICIENT_BUFFER)
{
// Expected ERROR_INSUFFICIENT_BUFFER
ATLASSERT(FALSE);
hr = HRESULT_FROM_WIN32(hr);
goto failed;
}
ptkUser = (TOKEN_USER*) malloc(dwSize);
if (ptkUser == NULL)
{
hr = E_OUTOFMEMORY;
goto failed;
}
// Get Sid of process token.
if (!GetTokenInformation(hToken, TokenUser, ptkUser, dwSize, &dwSize))
{
// Couldn't get user info
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
goto failed;
}
// Make a copy of the Sid for the return value
dwSize = GetLengthSid(ptkUser->User.Sid);
PSID pSid;
pSid = (PSID) malloc(dwSize);
if (pSid == NULL)
{
hr = E_OUTOFMEMORY;
goto failed;
}
if (!CopySid(dwSize, pSid, ptkUser->User.Sid))
{
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
goto failed;
}
ATLASSERT(IsValidSid(pSid));
*ppUserSid = pSid;
free(ptkUser);
ptkUser = NULL;
}
if (ppGroupSid)
{
// Get length required for TokenPrimaryGroup by specifying buffer length of 0
GetTokenInformation(hToken, TokenPrimaryGroup, NULL, 0, &dwSize);
hr = GetLastError();
if (hr != ERROR_INSUFFICIENT_BUFFER)
{
// Expected ERROR_INSUFFICIENT_BUFFER
ATLASSERT(FALSE);
hr = HRESULT_FROM_WIN32(hr);
goto failed;
}
ptkGroup = (TOKEN_PRIMARY_GROUP*) malloc(dwSize);
if (ptkGroup == NULL)
{
hr = E_OUTOFMEMORY;
goto failed;
}
// Get Sid of process token.
if (!GetTokenInformation(hToken, TokenPrimaryGroup, ptkGroup, dwSize, &dwSize))
{
// Couldn't get user info
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
goto failed;
}
// Make a copy of the Sid for the return value
dwSize = GetLengthSid(ptkGroup->PrimaryGroup);
PSID pSid;
pSid = (PSID) malloc(dwSize);
if (pSid == NULL)
{
hr = E_OUTOFMEMORY;
goto failed;
}
if (!CopySid(dwSize, pSid, ptkGroup->PrimaryGroup))
{
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
goto failed;
}
ATLASSERT(IsValidSid(pSid));
*ppGroupSid = pSid;
free(ptkGroup);
ptkGroup = NULL;
}
return S_OK;
failed:
if (ptkUser)
free(ptkUser);
if (ptkGroup)
free (ptkGroup);
return hr;
}
HRESULT CVssSecurityDescriptor::GetCurrentUserSID(PSID *ppSid)
{
HANDLE tkHandle;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tkHandle))
{
TOKEN_USER *tkUser;
DWORD tkSize;
DWORD sidLength;
// Call to get size information for alloc
GetTokenInformation(tkHandle, TokenUser, NULL, 0, &tkSize);
tkUser = (TOKEN_USER *) malloc(tkSize);
if (tkUser == NULL)
return E_OUTOFMEMORY;
// Now make the real call
if (GetTokenInformation(tkHandle, TokenUser, tkUser, tkSize, &tkSize))
{
sidLength = GetLengthSid(tkUser->User.Sid);
*ppSid = (PSID) malloc(sidLength);
if (*ppSid == NULL)
return E_OUTOFMEMORY;
memcpy(*ppSid, tkUser->User.Sid, sidLength);
CloseHandle(tkHandle);
free(tkUser);
return S_OK;
}
else
{
free(tkUser);
return HRESULT_FROM_WIN32(GetLastError());
}
}
return HRESULT_FROM_WIN32(GetLastError());
}
HRESULT CVssSecurityDescriptor::GetPrincipalSID(LPCTSTR pszPrincipal, PSID *ppSid)
{
HRESULT hr;
LPTSTR pszRefDomain = NULL;
DWORD dwDomainSize = 0;
DWORD dwSidSize = 0;
SID_NAME_USE snu;
// Call to get size info for alloc
LookupAccountName(NULL, pszPrincipal, *ppSid, &dwSidSize, pszRefDomain, &dwDomainSize, &snu);
hr = GetLastError();
if (hr != ERROR_INSUFFICIENT_BUFFER)
return HRESULT_FROM_WIN32(hr);
ATLTRY(pszRefDomain = new TCHAR[dwDomainSize]);
if (pszRefDomain == NULL)
return E_OUTOFMEMORY;
*ppSid = (PSID) malloc(dwSidSize);
if (*ppSid != NULL)
{
if (!LookupAccountName(NULL, pszPrincipal, *ppSid, &dwSidSize, pszRefDomain, &dwDomainSize, &snu))
{
free(*ppSid);
*ppSid = NULL;
delete[] pszRefDomain;
return HRESULT_FROM_WIN32(GetLastError());
}
delete[] pszRefDomain;
return S_OK;
}
delete[] pszRefDomain;
return E_OUTOFMEMORY;
}
HRESULT CVssSecurityDescriptor::Attach(PSECURITY_DESCRIPTOR pSelfRelativeSD)
{
PACL pDACL = NULL;
PACL pSACL = NULL;
BOOL bDACLPresent, bSACLPresent;
BOOL bDefaulted;
ACCESS_ALLOWED_ACE* pACE;
HRESULT hr;
PSID pUserSid;
PSID pGroupSid;
hr = Initialize();
if(FAILED(hr))
return hr;
// get the existing DACL.
if (!GetSecurityDescriptorDacl(pSelfRelativeSD, &bDACLPresent, &pDACL, &bDefaulted))
goto failed;
if (bDACLPresent)
{
if (pDACL)
{
// allocate new DACL.
m_pDACL = (PACL) malloc(pDACL->AclSize);
if (m_pDACL == NULL)
{
hr = E_OUTOFMEMORY;
goto failedMemory;
}
// initialize the DACL
if (!InitializeAcl(m_pDACL, pDACL->AclSize, ACL_REVISION))
goto failed;
// copy the ACES
for (int i = 0; i < pDACL->AceCount; i++)
{
if (!GetAce(pDACL, i, (void **)&pACE))
goto failed;
if (!AddAccessAllowedAce(m_pDACL, ACL_REVISION, pACE->Mask, (PSID)&(pACE->SidStart)))
goto failed;
}
if (!IsValidAcl(m_pDACL))
goto failed;
}
// set the DACL
if (!SetSecurityDescriptorDacl(m_pSD, m_pDACL ? TRUE : FALSE, m_pDACL, bDefaulted))
goto failed;
}
// get the existing SACL.
if (!GetSecurityDescriptorSacl(pSelfRelativeSD, &bSACLPresent, &pSACL, &bDefaulted))
goto failed;
if (bSACLPresent)
{
if (pSACL)
{
// allocate new SACL.
m_pSACL = (PACL) malloc(pSACL->AclSize);
if (m_pSACL == NULL)
{
hr = E_OUTOFMEMORY;
goto failedMemory;
}
// initialize the SACL
if (!InitializeAcl(m_pSACL, pSACL->AclSize, ACL_REVISION))
goto failed;
// copy the ACES
for (int i = 0; i < pSACL->AceCount; i++)
{
if (!GetAce(pSACL, i, (void **)&pACE))
goto failed;
if (!AddAccessAllowedAce(m_pSACL, ACL_REVISION, pACE->Mask, (PSID)&(pACE->SidStart)))
goto failed;
}
if (!IsValidAcl(m_pSACL))
goto failed;
}
// set the SACL
if (!SetSecurityDescriptorSacl(m_pSD, m_pSACL ? TRUE : FALSE, m_pSACL, bDefaulted))
goto failed;
}
if (!GetSecurityDescriptorOwner(m_pSD, &pUserSid, &bDefaulted))
goto failed;
if (FAILED(SetOwner(pUserSid, bDefaulted)))
goto failed;
if (!GetSecurityDescriptorGroup(m_pSD, &pGroupSid, &bDefaulted))
goto failed;
if (FAILED(SetGroup(pGroupSid, bDefaulted)))
goto failed;
if (!IsValidSecurityDescriptor(m_pSD))
goto failed;
return hr;
failed:
hr = HRESULT_FROM_WIN32(hr);
failedMemory:
if (m_pDACL)
{
free(m_pDACL);
m_pDACL = NULL;
}
if (m_pSD)
{
free(m_pSD);
m_pSD = NULL;
}
return hr;
}
HRESULT CVssSecurityDescriptor::AttachObject(HANDLE hObject)
{
HRESULT hr;
DWORD dwSize = 0;
PSECURITY_DESCRIPTOR pSD = NULL;
GetKernelObjectSecurity(hObject, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION, pSD, 0, &dwSize);
hr = GetLastError();
if (hr != ERROR_INSUFFICIENT_BUFFER)
return HRESULT_FROM_WIN32(hr);
pSD = (PSECURITY_DESCRIPTOR) malloc(dwSize);
if (pSD == NULL)
return E_OUTOFMEMORY;
if (!GetKernelObjectSecurity(hObject, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION, pSD, dwSize, &dwSize))
{
hr = HRESULT_FROM_WIN32(GetLastError());
free(pSD);
return hr;
}
hr = Attach(pSD);
free(pSD);
return hr;
}
HRESULT CVssSecurityDescriptor::CopyACL(PACL pDest, PACL pSrc)
{
ACL_SIZE_INFORMATION aclSizeInfo;
LPVOID pAce;
ACE_HEADER *aceHeader;
if (pSrc == NULL)
return S_OK;
if (!GetAclInformation(pSrc, (LPVOID) &aclSizeInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation))
return HRESULT_FROM_WIN32(GetLastError());
// Copy all of the ACEs to the new ACL
for (UINT i = 0; i < aclSizeInfo.AceCount; i++)
{
if (!GetAce(pSrc, i, &pAce))
return HRESULT_FROM_WIN32(GetLastError());
aceHeader = (ACE_HEADER *) pAce;
if (!AddAce(pDest, ACL_REVISION, 0xffffffff, pAce, aceHeader->AceSize))
return HRESULT_FROM_WIN32(GetLastError());
}
return S_OK;
}
HRESULT CVssSecurityDescriptor::AddAccessDeniedACEToACL(PACL *ppAcl, LPCTSTR pszPrincipal, DWORD dwAccessMask, DWORD dwAceFlags)
{
ACL_SIZE_INFORMATION aclSizeInfo;
int aclSize;
DWORD returnValue;
PSID principalSID;
PACL oldACL, newACL = NULL;
oldACL = *ppAcl;
returnValue = GetPrincipalSID(pszPrincipal, &principalSID);
if (FAILED(returnValue))
return returnValue;
aclSizeInfo.AclBytesInUse = 0;
if (*ppAcl != NULL)
GetAclInformation(oldACL, (LPVOID) &aclSizeInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
aclSize = aclSizeInfo.AclBytesInUse + sizeof(ACL) + sizeof(ACCESS_DENIED_ACE) + GetLengthSid(principalSID) - sizeof(DWORD);
ATLTRY(newACL = (PACL) new BYTE[aclSize]);
if (newACL == NULL)
return E_OUTOFMEMORY;
if (!InitializeAcl(newACL, aclSize, ACL_REVISION))
{
free(principalSID);
delete [] newACL;
return HRESULT_FROM_WIN32(GetLastError());
}
if (!AddAccessDeniedAceEx(newACL, ACL_REVISION2, dwAceFlags, dwAccessMask, principalSID))
{
free(principalSID);
delete [] newACL;
return HRESULT_FROM_WIN32(GetLastError());
}
returnValue = CopyACL(newACL, oldACL);
if (FAILED(returnValue))
{
free(principalSID);
delete [] newACL;
return returnValue;
}
*ppAcl = newACL;
newACL = NULL;
if (oldACL != NULL)
free(oldACL);
free(principalSID);
return S_OK;
}
HRESULT CVssSecurityDescriptor::AddAccessAllowedACEToACL(PACL *ppAcl, LPCTSTR pszPrincipal, DWORD dwAccessMask, DWORD dwAceFlags)
{
ACL_SIZE_INFORMATION aclSizeInfo;
int aclSize;
DWORD returnValue;
PSID principalSID;
PACL oldACL, newACL = NULL;
oldACL = *ppAcl;
returnValue = GetPrincipalSID(pszPrincipal, &principalSID);
if (FAILED(returnValue))
return returnValue;
aclSizeInfo.AclBytesInUse = 0;
if (*ppAcl != NULL)
GetAclInformation(oldACL, (LPVOID) &aclSizeInfo, (DWORD) sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
aclSize = aclSizeInfo.AclBytesInUse + sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(principalSID) - sizeof(DWORD);
ATLTRY(newACL = (PACL) new BYTE[aclSize]);
if (newACL == NULL)
return E_OUTOFMEMORY;
if (!InitializeAcl(newACL, aclSize, ACL_REVISION))
{
free(principalSID);
delete [] newACL;
return HRESULT_FROM_WIN32(GetLastError());
}
returnValue = CopyACL(newACL, oldACL);
if (FAILED(returnValue))
{
free(principalSID);
delete [] newACL;
return returnValue;
}
if (!AddAccessAllowedAceEx(newACL, ACL_REVISION2, dwAceFlags, dwAccessMask, principalSID))
{
free(principalSID);
delete [] newACL;
return HRESULT_FROM_WIN32(GetLastError());
}
*ppAcl = newACL;
newACL = NULL;
if (oldACL != NULL)
free(oldACL);
free(principalSID);
return S_OK;
}
HRESULT CVssSecurityDescriptor::AddAccessDeniedACEToACL(PACL *ppAcl, PSID principalSID, DWORD dwAccessMask, DWORD dwAceFlags)
{
ACL_SIZE_INFORMATION aclSizeInfo;
int aclSize;
DWORD returnValue;
PACL oldACL, newACL = NULL;
oldACL = *ppAcl;
aclSizeInfo.AclBytesInUse = 0;
if (*ppAcl != NULL)
GetAclInformation(oldACL, (LPVOID) &aclSizeInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
aclSize = aclSizeInfo.AclBytesInUse + sizeof(ACL) + sizeof(ACCESS_DENIED_ACE) + GetLengthSid(principalSID) - sizeof(DWORD);
ATLTRY(newACL = (PACL) new BYTE[aclSize]);
if (newACL == NULL)
return E_OUTOFMEMORY;
if (!InitializeAcl(newACL, aclSize, ACL_REVISION))
{
delete [] newACL;
return HRESULT_FROM_WIN32(GetLastError());
}
if (!AddAccessDeniedAceEx(newACL, ACL_REVISION2, dwAceFlags, dwAccessMask, principalSID))
{
delete [] newACL;
return HRESULT_FROM_WIN32(GetLastError());
}
returnValue = CopyACL(newACL, oldACL);
if (FAILED(returnValue))
{
delete [] newACL;
return returnValue;
}
*ppAcl = newACL;
newACL = NULL;
if (oldACL != NULL)
free(oldACL);
return S_OK;
}
HRESULT CVssSecurityDescriptor::AddAccessAllowedACEToACL(PACL *ppAcl, PSID principalSID, DWORD dwAccessMask, DWORD dwAceFlags)
{
ACL_SIZE_INFORMATION aclSizeInfo;
int aclSize;
DWORD returnValue;
PACL oldACL, newACL = NULL;
oldACL = *ppAcl;
aclSizeInfo.AclBytesInUse = 0;
if (*ppAcl != NULL)
GetAclInformation(oldACL, (LPVOID) &aclSizeInfo, (DWORD) sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
aclSize = aclSizeInfo.AclBytesInUse + sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(principalSID) - sizeof(DWORD);
ATLTRY(newACL = (PACL) new BYTE[aclSize]);
if (newACL == NULL)
return E_OUTOFMEMORY;
if (!InitializeAcl(newACL, aclSize, ACL_REVISION))
{
delete [] newACL;
return HRESULT_FROM_WIN32(GetLastError());
}
returnValue = CopyACL(newACL, oldACL);
if (FAILED(returnValue))
{
delete [] newACL;
return returnValue;
}
if (!AddAccessAllowedAceEx(newACL, ACL_REVISION2, dwAceFlags, dwAccessMask, principalSID))
{
delete [] newACL;
return HRESULT_FROM_WIN32(GetLastError());
}
*ppAcl = newACL;
newACL = NULL;
if (oldACL != NULL)
free(oldACL);
return S_OK;
}
HRESULT CVssSecurityDescriptor::RemovePrincipalFromACL(PACL pAcl, LPCTSTR pszPrincipal)
{
ACL_SIZE_INFORMATION aclSizeInfo;
ULONG i;
LPVOID ace;
ACCESS_ALLOWED_ACE *accessAllowedAce;
ACCESS_DENIED_ACE *accessDeniedAce;
SYSTEM_AUDIT_ACE *systemAuditAce;
PSID principalSID;
DWORD returnValue;
ACE_HEADER *aceHeader;
returnValue = GetPrincipalSID(pszPrincipal, &principalSID);
if (FAILED(returnValue))
return returnValue;
GetAclInformation(pAcl, (LPVOID) &aclSizeInfo, (DWORD) sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
for (i = 0; i < aclSizeInfo.AceCount; i++)
{
if (!GetAce(pAcl, i, &ace))
{
free(principalSID);
return HRESULT_FROM_WIN32(GetLastError());
}
aceHeader = (ACE_HEADER *) ace;
if (aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE)
{
accessAllowedAce = (ACCESS_ALLOWED_ACE *) ace;
if (EqualSid(principalSID, (PSID) &accessAllowedAce->SidStart))
{
DeleteAce(pAcl, i);
free(principalSID);
return S_OK;
}
} else
if (aceHeader->AceType == ACCESS_DENIED_ACE_TYPE)
{
accessDeniedAce = (ACCESS_DENIED_ACE *) ace;
if (EqualSid(principalSID, (PSID) &accessDeniedAce->SidStart))
{
DeleteAce(pAcl, i);
free(principalSID);
return S_OK;
}
} else
if (aceHeader->AceType == SYSTEM_AUDIT_ACE_TYPE)
{
systemAuditAce = (SYSTEM_AUDIT_ACE *) ace;
if (EqualSid(principalSID, (PSID) &systemAuditAce->SidStart))
{
DeleteAce(pAcl, i);
free(principalSID);
return S_OK;
}
}
}
free(principalSID);
return S_OK;
}
HRESULT CVssSecurityDescriptor::SetPrivilege(LPCTSTR privilege, BOOL bEnable, HANDLE hToken)
{
HRESULT hr;
TOKEN_PRIVILEGES tpPrevious;
TOKEN_PRIVILEGES tp;
DWORD cbPrevious = sizeof(TOKEN_PRIVILEGES);
LUID luid;
HANDLE hTokenUsed;
// if no token specified open process token
if (hToken == 0)
{
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hTokenUsed))
{
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
return hr;
}
}
else
hTokenUsed = hToken;
if (!LookupPrivilegeValue(NULL, privilege, &luid ))
{
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
if (hToken == 0)
CloseHandle(hTokenUsed);
return hr;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = 0;
if (!AdjustTokenPrivileges(hTokenUsed, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &tpPrevious, &cbPrevious))
{
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
if (hToken == 0)
CloseHandle(hTokenUsed);
return hr;
}
tpPrevious.PrivilegeCount = 1;
tpPrevious.Privileges[0].Luid = luid;
if (bEnable)
tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
else
tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED & tpPrevious.Privileges[0].Attributes);
if (!AdjustTokenPrivileges(hTokenUsed, FALSE, &tpPrevious, cbPrevious, NULL, NULL))
{
hr = HRESULT_FROM_WIN32(GetLastError());
ATLASSERT(FALSE);
if (hToken == 0)
CloseHandle(hTokenUsed);
return hr;
}
return S_OK;
}
/////////////////////////////////////////////////////////////////////////////////////
// Class - CVssSidCollection
//
CVssSidCollection::CVssSidCollection()
{
m_bInitialized = false;
}
CVssSidCollection::~CVssSidCollection()
{
for(INT nIndex = 0; nIndex < m_SidArray.GetSize(); nIndex++) {
LocalFree(m_SidArray.GetKeyAt(nIndex));
LocalFree((m_SidArray.GetValueAt(nIndex)).GetSid());
LocalFree((m_SidArray.GetValueAt(nIndex)).GetName());
LocalFree((m_SidArray.GetValueAt(nIndex)).GetDomain());
}
}
// Get the total count of stored SIDs
INT CVssSidCollection::GetSidCount()
{
BS_ASSERT(m_bInitialized);
return m_SidArray.GetSize();
}
// Get the SID with the given index (starts with 0)
PSID CVssSidCollection::GetSid(INT nIndex) throw(HRESULT)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::GetSid");
BS_ASSERT(m_bInitialized);
if ((nIndex < 0) || (nIndex >= GetSidCount()))
ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"Index out of range %ld", nIndex);
return (m_SidArray.GetValueAt(nIndex)).GetSid();
}
// Get the SID use with the given index
SID_NAME_USE CVssSidCollection::GetSidUse(INT nIndex) throw(HRESULT)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::GetSidUse");
BS_ASSERT(m_bInitialized);
if ((nIndex < 0) || (nIndex >= GetSidCount()))
ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"Index out of range %ld", nIndex);
return (m_SidArray.GetValueAt(nIndex)).GetUse();
}
// Check if the the SID is allowed
bool CVssSidCollection::IsSidAllowed(INT nIndex) throw(HRESULT)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::IsSidAllowed");
BS_ASSERT(m_bInitialized);
if ((nIndex < 0) || (nIndex >= GetSidCount()))
ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"Index out of range %ld", nIndex);
return (m_SidArray.GetValueAt(nIndex)).IsSidAllowed();
}
// Check if the the SID is local
bool CVssSidCollection::IsLocal(INT nIndex) throw(HRESULT)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::IsLocal");
BS_ASSERT(m_bInitialized);
if ((nIndex < 0) || (nIndex >= GetSidCount()))
ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"Index out of range %ld", nIndex);
return (m_SidArray.GetValueAt(nIndex)).IsLocal();
}
// Get the SID with the given index (starts with 0)
LPWSTR CVssSidCollection::GetPrincipal(INT nIndex) throw(HRESULT)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::GetPrincipal");
BS_ASSERT(m_bInitialized);
if ((nIndex < 0) || (nIndex >= GetSidCount()))
ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"Index out of range %ld", nIndex);
return m_SidArray.GetKeyAt(nIndex);
}
LPWSTR CVssSidCollection::GetName(INT nIndex) throw(HRESULT)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::GetName");
BS_ASSERT(m_bInitialized);
if ((nIndex < 0) || (nIndex >= GetSidCount()))
ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"Index out of range %ld", nIndex);
return (m_SidArray.GetValueAt(nIndex)).GetName();;
}
LPWSTR CVssSidCollection::GetDomain(INT nIndex) throw(HRESULT)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::GetDomain");
BS_ASSERT(m_bInitialized);
if ((nIndex < 0) || (nIndex >= GetSidCount()))
ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"Index out of range %ld", nIndex);
return (m_SidArray.GetValueAt(nIndex)).GetDomain();;
}
// Add a well-known SID to the internal map
void CVssSidCollection::AddWellKnownSid(
IN WELL_KNOWN_SID_TYPE type
)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::AddWellKnownSid");
// Get the SID size
DWORD cbSid = 0;
if (CreateWellKnownSid(type, NULL, NULL, &cbSid))
ft.TranslateWin32Error(VSSDBG_GEN, L"CreateWellKnownSid(type, NULL, NULL, &dwSid)");
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
ft.TranslateWin32Error(VSSDBG_GEN, L"CreateWellKnownSid(type, NULL, NULL, &dwSid)");
// Allocate the SID
CVssAutoLocalPtr<PSID> pSID;
pSID.AllocateBytes(cbSid);
// Create the SID
if (!CreateWellKnownSid(type, NULL, pSID, &cbSid))
ft.TranslateWin32Error(VSSDBG_GEN, L"CreateWellKnownSid(type, NULL, pSID, [%ld])", cbSid);
// Get the string representation in the "domain\name" format
// First get the sizes
DWORD cchName = 0;
DWORD cchDomain = 0;
SID_NAME_USE peUse;
if (LookupAccountSid(NULL, pSID, NULL, &cchName, NULL, &cchDomain, &peUse))
ft.TranslateWin32Error(VSSDBG_GEN, L"LookupAccountSid()");
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
ft.TranslateWin32Error(VSSDBG_GEN, L"LookupAccountSid()");
// Now allocate the buffers
CVssAutoLocalString pwszName, pwszDomain, pwszComposedName;
pwszName.AllocateString(cchName);
pwszDomain.AllocateString(cchDomain);
// Try again
if (!LookupAccountSid(NULL, pSID, pwszName, &cchName, pwszDomain, &cchDomain, &peUse))
ft.TranslateWin32Error(VSSDBG_GEN, L"LookupAccountSid()");
// Check to see if the account is local.
// and also complete the m_pwszBuiltinDomain if the sid is for Adminstrators
bool bLocalAccount = VerifyIsLocal(pwszDomain, (WinBuiltinAdministratorsSid == type));
// Compose the big name ("domain\name")
pwszComposedName.CopyFrom(pwszDomain);
pwszComposedName.Append(L"\\");
pwszComposedName.Append(pwszName);
// Now add it to the array
if (!m_SidArray.Add(pwszComposedName,
CVssSidWrapper(true, pSID, peUse, pwszName, pwszDomain, bLocalAccount)))
ft.ThrowOutOfMemory(VSSDBG_GEN);
// Transfer ownership into the array
pSID.Detach();
pwszComposedName.Detach();
pwszName.Detach();
pwszDomain.Detach();
}
// Add a user to the internal map based on the user name
// Return "false" if the user name does not correspond to a real user.
bool CVssSidCollection::AddUser(
IN LPCWSTR pwszUser,
IN bool bAllow
)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::AddSidOrUser");
if ( (pwszUser == NULL) || (pwszUser[0] == L'\0'))
ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"Invalid argument '%s'", pwszUser);
// First, check to see if this is a valid user name
DWORD cbSid = 0;
DWORD cchDomain = 0;
SID_NAME_USE sbUse;
if (LookupAccountName( NULL, pwszUser, NULL, &cbSid, NULL, &cchDomain, &sbUse))
ft.TranslateWin32Error(VSSDBG_GEN, L"LookupAccountName( NULL, %s, NULL, p, NULL, p, p)", pwszUser);
// If the user does not exist, return false
if (GetLastError() == ERROR_NONE_MAPPED)
return false;
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
ft.TranslateWin32Error(VSSDBG_GEN, L"LookupAccountName( NULL, %s, NULL, p, NULL, p, p)", pwszUser);
// It appears we are on the correct path.
// Allocate the SID and the domain
CVssAutoLocalPtr<PSID> pSID;
pSID.AllocateBytes(cbSid);
CVssAutoLocalString pwszDomain;
pwszDomain.AllocateString(cchDomain);
// Retrieve the SID
if (!LookupAccountName( NULL, pwszUser,
pSID, &cbSid, pwszDomain, &cchDomain, &sbUse))
ft.TranslateWin32Error(VSSDBG_GEN, L"LookupAccountName( NULL, %s,...)", pwszUser);
// Check to see if the account is local.
bool bLocalAccount = VerifyIsLocal(pwszDomain, false);
// Now we are sure that the user is real
CVssAutoLocalString pwszComposedName, pwszUserName;
pwszUserName.CopyFrom(pwszUser);
// Compose the big name ("domain\name")
pwszComposedName.CopyFrom(pwszDomain);
pwszComposedName.Append(L"\\");
pwszComposedName.Append(pwszUserName);
// Now we have both the string and the SID. Add them into the array.
if (!m_SidArray.Add(pwszComposedName,
CVssSidWrapper(bAllow, pSID, sbUse, pwszUserName, pwszDomain, bLocalAccount)))
ft.ThrowOutOfMemory(VSSDBG_GEN);
// Transfer ownership into the array
pSID.Detach();
pwszComposedName.Detach();
pwszUserName.Detach();
pwszDomain.Detach();
return true;
}
// Return "true" if the user name with the given domain is local user/group
bool CVssSidCollection::VerifyIsLocal(
IN LPCWSTR pwszDomain,
IN bool bIsAdministratorsAccount
)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::VerifyIsLocal");
// If this is the Administrators SID, then complete the m_pwszBuiltinDomain field.
// Otherwise check to see if the account is local.
if (bIsAdministratorsAccount)
{
BS_ASSERT(!m_pwszBuiltinDomain.IsValid());
BS_ASSERT(m_SidArray.GetSize() == 0);
m_pwszBuiltinDomain.CopyFrom(pwszDomain);
return true;
}
else
{
BS_ASSERT(m_pwszBuiltinDomain.IsValid());
BS_ASSERT(m_SidArray.GetSize() != 0);
// we consider it local if the domain is "BUILTIN" or the computer name
if(0 == _wcsicmp(pwszDomain, m_pwszBuiltinDomain))
return true;
// get the computer name
WCHAR wszComputerName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD dwSize = SIZEOF_ARRAY(wszComputerName);
if (0 == GetComputerNameW(wszComputerName, &dwSize))
ft.TranslateWin32Error(VSSDBG_GEN, L"GetComputerNameW");
// If this is the computer name, return TRUE
if(0 == _wcsicmp(pwszDomain, wszComputerName))
return true;
}
return false;
}
//
// Initialize the security descriptor from registry
// Contents: Admin, BO, System always enabled.
// The rest of users are read from the SYSTEM\\CurrentControlSet\\Services\\VSS\\VssAccessControl key
// The format of this registry key is a set of values of the form:
// REG_DWORD Name "domain1\user1", 1
// REG_DWORD Name "domain2\user2", 1
// REG_DWORD Name "domain3\user3", 0
// where 1 means "Allow" and 0 means "Deny"
//
void CVssSidCollection::Initialize()
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::Initialize");
// only initialize once
if (m_bInitialized)
return;
//
// Add well-known SIDs
//
// This MUST be added first since fills out the m_pwszBuiltinDomain member
AddWellKnownSid(WinBuiltinAdministratorsSid);
AddWellKnownSid(WinBuiltinBackupOperatorsSid);
AddWellKnownSid(WinLocalSystemSid);
//
// Add other users from registry
//
// Open the registry and enumerate all users specified there
CVssRegistryKey keyUserList(KEY_READ);
if (keyUserList.Open(HKEY_LOCAL_MACHINE, x_wszVssAccessControlKey))
{
CVssRegistryValueIterator iterator;
iterator.Attach(keyUserList);
// for each value take the value name as the user name (in the "domain\user" format)
for(;!iterator.IsEOF();iterator.MoveNext())
{
// Check to see ifthe value is of the right type
if (iterator.GetCurrentValueType() != REG_DWORD) {
ft.LogError(VSS_ERROR_WRONG_REG_USER_VALUE_TYPE,
VSSDBG_GEN << iterator.GetCurrentValueName() << x_wszVssAccessControlKey);
continue;
}
// Get the allow/deny flag
DWORD dwValue = 0;
iterator.GetCurrentValueContent(dwValue);
// Interpret the allow/deny flag
bool bIsAllowed;
switch(dwValue) {
case 0:
bIsAllowed = false;
break;
case 1:
bIsAllowed = true;
break;
default:
ft.LogError(VSS_ERROR_WRONG_REG_USER_VALUE,
VSSDBG_GEN << iterator.GetCurrentValueName()
<< x_wszVssAccessControlKey << (INT)dwValue);
continue;
}
// Add the user (if exists)
if (!AddUser(iterator.GetCurrentValueName(), bIsAllowed )) {
ft.LogError(VSS_ERROR_WRONG_USER_NAME,
VSSDBG_GEN << iterator.GetCurrentValueName() << x_wszVssAccessControlKey);
continue;
}
}
}
// Build the security descriptor
m_SD.Initialize();
// Add a valid owner (the value does not matter)
CAutoSid userSid;
userSid.CreateBasicSid(WinLocalSystemSid);
ft.hr = m_SD.SetOwner(userSid.Get());
if (ft.HrFailed())
ft.TranslateGenericError( VSSDBG_COORD, ft.hr, L"SetOwner");
// Add a valid group (the value does not matter)
CAutoSid groupSid;
groupSid.CreateBasicSid(WinBuiltinAdministratorsSid);
ft.hr = m_SD.SetGroup(groupSid.Get());
if (ft.HrFailed())
ft.TranslateGenericError( VSSDBG_COORD, ft.hr, L"SetGroup");
// Make sure the SACL is NULL (not supported by COM)
if (m_SD.m_pSACL) {
free(m_SD.m_pSACL);
m_SD.m_pSACL= NULL;
}
// Add principals to the DACL
for (INT nIndex = 0; nIndex < m_SidArray.GetSize(); nIndex++)
{
if ((m_SidArray.GetValueAt(nIndex)).IsSidAllowed())
{
ft.hr = m_SD.Allow((m_SidArray.GetValueAt(nIndex)).GetSid(),
COM_RIGHTS_EXECUTE);
if (ft.HrFailed())
ft.TranslateGenericError( VSSDBG_GEN, ft.hr,
L"m_SD.Allow(%s, COM_RIGHTS_EXECUTE);",
m_SidArray.GetKeyAt(nIndex));
}
else
{
ft.hr = m_SD.Deny((m_SidArray.GetValueAt(nIndex)).GetSid(),
COM_RIGHTS_EXECUTE);
if (ft.HrFailed())
ft.TranslateGenericError( VSSDBG_GEN, ft.hr,
L"m_SD.Deny(%s, COM_RIGHTS_EXECUTE);",
m_SidArray.GetKeyAt(nIndex));
}
}
BS_ASSERT(::IsValidSecurityDescriptor(m_SD));
// We mark object as initialized (since we need it in GetXXX routines)
m_bInitialized = true;
}
// determine if the process is a valid writer
bool CVssSidCollection::IsProcessValidWriter()
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::IsProcessValidWriter");
CVssAutoCppPtr<TOKEN_USER*> ptrTokenOwner = GetClientTokenUser(FALSE);
return IsSidAllowedToFire(ptrTokenOwner.Get()->User.Sid);
}
bool CVssSidCollection::IsSidAllowedToFire(
IN PSID psid
)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::IsSidAllowedToFire");
BS_ASSERT(psid);
// First check if the SID is explicitely dissalowed
if (CheckIfExplicitelySpecified(psid, false))
{
if ( g_cDbgTrace.IsTracingEnabled() ) {
CVssAutoLocalString aszWriterSid;
if (ConvertSidToStringSid( psid, aszWriterSid.ResetAndGetAddress()))
ft.Trace( VSSDBG_XML, L"WriterSid: %s was explicitely denied to fire", aszWriterSid.Get() );
}
return false;
}
// Then check if the SID is explicitely allowed
if (CheckIfExplicitelySpecified(psid, true))
return true;
if ( g_cDbgTrace.IsTracingEnabled() ) {
CVssAutoLocalString aszWriterSid;
if (ConvertSidToStringSid( psid, aszWriterSid.ResetAndGetAddress()))
ft.Trace( VSSDBG_XML, L"WriterSid: %s was implicitely denied to fire", aszWriterSid.Get() );
}
// Check failed
return false;
}
//
// if (bCheckToBeDone == false) check if the SID is explicitely denied to fire
// if (bCheckToBeDone == true) check if the SID is explicitely allowed to fire
//
// Return true if the check succeeded or false otherwise
//
bool CVssSidCollection::CheckIfExplicitelySpecified(
IN PSID psid,
IN bool bCheckToBeDone
)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::CheckIfExplicitelySpecified");
BS_ASSERT(psid);
// Now look only for the checked SIDs
for (INT nIndex = 0; nIndex < GetSidCount(); nIndex++)
{
// Ignore opposite checks
if (bCheckToBeDone != IsSidAllowed(nIndex))
continue;
switch(GetSidUse(nIndex))
{
case SidTypeUser:
case SidTypeComputer:
if (EqualSid(psid, GetSid(nIndex)))
return true;
break;
case SidTypeAlias:
case SidTypeWellKnownGroup:
if (IsLocal(nIndex)) {
if (IsSidRelatedWithLocalSid(psid, GetName(nIndex), GetSid(nIndex)))
return true;
}
else {
if (EqualSid(psid, GetSid(nIndex)))
return true;
}
break;
default:
// Unknown SID type in the collection. Ignoring.
ft.Trace(VSSDBG_GEN,
L"Unknown SID %s with type %ld in the SID collection. Ignoring. [%d]",
GetPrincipal(nIndex), GetSidUse(nIndex), nIndex);
BS_ASSERT(false);
break;
}
}
return false;
}
// is a local group mentioned in the SID collection or is even a member of the SID collection
bool CVssSidCollection::IsSidRelatedWithLocalSid(
IN PSID pSid,
IN LPWSTR pwszWellKnownPrincipal,
IN PSID pWellKnownSid
)
{
CVssFunctionTracer ft(VSSDBG_GEN, L"CVssSidCollection::IsSidRelatedWithLocalSid");
// Assert parameters
BS_ASSERT(pSid);
BS_ASSERT(pwszWellKnownPrincipal);
BS_ASSERT(pWellKnownSid);
if (EqualSid(pSid, pWellKnownSid) == TRUE)
return true;
// get list of local group members
CVssAutoNetApiPtr apBuffer;
DWORD_PTR ResumeHandle = NULL;
DWORD cEntriesRead = 0, cEntriesTotal = 0;
NET_API_STATUS status =
NetLocalGroupGetMembers(
NULL,
pwszWellKnownPrincipal,
0,
apBuffer.ResetAndGetAddress(),
MAX_PREFERRED_LENGTH,
&cEntriesRead,
&cEntriesTotal,
&ResumeHandle
);
// If this is not a group - then compare SIDs directly
// (for example for LocalSystem account)
if (status == NERR_GroupNotFound)
return false;
// We have a different error?
if (status != NERR_Success)
ft.TranslateGenericError(VSSDBG_GEN, HRESULT_FROM_WIN32(status),
L"NetGroupGetUsers(%s)", pwszWellKnownPrincipal);
BS_ASSERT(cEntriesRead == cEntriesTotal);
// loop through member list to see if any sids mach the sid of the owner
// of the subscription
LOCALGROUP_MEMBERS_INFO_0 *rgMembers = (LOCALGROUP_MEMBERS_INFO_0 *) apBuffer.Get();
for(DWORD iEntry = 0; iEntry < cEntriesRead; iEntry++)
{
PSID psidMember = rgMembers[iEntry].lgrmi0_sid;
if (EqualSid(psidMember, pSid))
return true;
}
return false;
}