563 lines
19 KiB
C++
563 lines
19 KiB
C++
|
// --------------------------------------------------------------------------
|
||
|
// Module Name: Access.cpp
|
||
|
//
|
||
|
// Copyright (c) 1999, Microsoft Corporation
|
||
|
//
|
||
|
// This file contains a few classes that assist with ACL manipulation on
|
||
|
// objects to which a handle has already been opened. This handle must have
|
||
|
// (obvisouly) have WRITE_DAC access.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// 2000-02-01 vtan moved from Neptune to Whistler
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
#include "StandardHeader.h"
|
||
|
#include "Access.h"
|
||
|
|
||
|
#include "StatusCode.h"
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CSecurityDescriptor::CSecurityDescriptor
|
||
|
//
|
||
|
// Arguments: iCount = Count of ACCESS_CONTROLS passed in.
|
||
|
// pAccessControl = Pointer to ACCESS_CONTROLS.
|
||
|
//
|
||
|
// Returns: <none>
|
||
|
//
|
||
|
// Purpose: Allocates and assigns the PSECURITY_DESCRIPTOR that
|
||
|
// corresponds to the description given by the parameters. The
|
||
|
// caller must release the memory allocated via LocalFree.
|
||
|
//
|
||
|
// History: 2000-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
PSECURITY_DESCRIPTOR CSecurityDescriptor::Create (int iCount, const ACCESS_CONTROL *pAccessControl)
|
||
|
|
||
|
{
|
||
|
PSECURITY_DESCRIPTOR pSecurityDescriptor;
|
||
|
PSID *pSIDs;
|
||
|
|
||
|
pSecurityDescriptor = NULL;
|
||
|
|
||
|
// Allocate an array of PSIDs required to hold all the SIDs to add.
|
||
|
|
||
|
pSIDs = reinterpret_cast<PSID*>(LocalAlloc(LPTR, iCount * sizeof(PSID)));
|
||
|
if (pSIDs != NULL)
|
||
|
{
|
||
|
bool fSuccessfulAllocate;
|
||
|
int i;
|
||
|
const ACCESS_CONTROL *pAC;
|
||
|
|
||
|
for (fSuccessfulAllocate = true, pAC = pAccessControl, i = 0; fSuccessfulAllocate && (i < iCount); ++pAC, ++i)
|
||
|
{
|
||
|
fSuccessfulAllocate = (AllocateAndInitializeSid(pAC->pSIDAuthority,
|
||
|
static_cast<BYTE>(pAC->iSubAuthorityCount),
|
||
|
pAC->dwSubAuthority0,
|
||
|
pAC->dwSubAuthority1,
|
||
|
pAC->dwSubAuthority2,
|
||
|
pAC->dwSubAuthority3,
|
||
|
pAC->dwSubAuthority4,
|
||
|
pAC->dwSubAuthority5,
|
||
|
pAC->dwSubAuthority6,
|
||
|
pAC->dwSubAuthority7,
|
||
|
&pSIDs[i]) != FALSE);
|
||
|
}
|
||
|
if (fSuccessfulAllocate)
|
||
|
{
|
||
|
DWORD dwACLSize;
|
||
|
unsigned char *pBuffer;
|
||
|
|
||
|
// Calculate the size of the ACL required by totalling the ACL header
|
||
|
// struct and the 2 ACCESS_ALLOWED_ACE structs with the SID sizes.
|
||
|
// Add the SECURITY_DESCRIPTOR struct size as well.
|
||
|
|
||
|
dwACLSize = sizeof(ACL) + ((sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)) * 3);
|
||
|
for (i = 0; i < iCount; ++i)
|
||
|
{
|
||
|
dwACLSize += GetLengthSid(pSIDs[i]);
|
||
|
}
|
||
|
|
||
|
// Allocate the buffer for everything and portion off the buffer to
|
||
|
// the right place.
|
||
|
|
||
|
pBuffer = static_cast<unsigned char*>(LocalAlloc(LMEM_FIXED, sizeof(SECURITY_DESCRIPTOR) + dwACLSize));
|
||
|
if (pBuffer != NULL)
|
||
|
{
|
||
|
PSECURITY_DESCRIPTOR pSD;
|
||
|
PACL pACL;
|
||
|
|
||
|
pSD = reinterpret_cast<PSECURITY_DESCRIPTOR>(pBuffer);
|
||
|
pACL = reinterpret_cast<PACL>(pBuffer + sizeof(SECURITY_DESCRIPTOR));
|
||
|
|
||
|
// Initialize the ACL. Fill in the ACL.
|
||
|
// Initialize the SECURITY_DESCRIPTOR. Set the security descriptor.
|
||
|
|
||
|
if ((InitializeAcl(pACL, dwACLSize, ACL_REVISION) != FALSE) &&
|
||
|
AddAces(pACL, pSIDs, iCount, pAccessControl) &&
|
||
|
(InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION) != FALSE) &&
|
||
|
(SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE) != FALSE))
|
||
|
{
|
||
|
pSecurityDescriptor = pSD;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
(HLOCAL)LocalFree(pBuffer);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (i = iCount - 1; i >= 0; --i)
|
||
|
{
|
||
|
if (pSIDs[i] != NULL)
|
||
|
{
|
||
|
(void*)FreeSid(pSIDs[i]);
|
||
|
}
|
||
|
}
|
||
|
(HLOCAL)LocalFree(pSIDs);
|
||
|
}
|
||
|
return(pSecurityDescriptor);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CSecurityDescriptor::AddAces
|
||
|
//
|
||
|
// Arguments: pACL = PACL to add ACEs to.
|
||
|
// pSIDs = Pointer to SIDs.
|
||
|
// iCount = Count of ACCESS_CONTROLS passed in.
|
||
|
// pAccessControl = Pointer to ACCESS_CONTROLS.
|
||
|
//
|
||
|
// Returns: bool
|
||
|
//
|
||
|
// Purpose: Adds access allowed ACEs to the given ACL.
|
||
|
//
|
||
|
// History: 2000-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
bool CSecurityDescriptor::AddAces (PACL pACL, PSID *pSIDs, int iCount, const ACCESS_CONTROL *pAC)
|
||
|
|
||
|
{
|
||
|
bool fResult;
|
||
|
int i;
|
||
|
|
||
|
for (fResult = true, i = 0; fResult && (i < iCount); ++pSIDs, ++pAC, ++i)
|
||
|
{
|
||
|
fResult = (AddAccessAllowedAce(pACL, ACL_REVISION, pAC->dwAccessMask, *pSIDs) != FALSE);
|
||
|
}
|
||
|
return(fResult);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CAccessControlList::CAccessControlList
|
||
|
//
|
||
|
// Arguments: <none>
|
||
|
//
|
||
|
// Returns: <none>
|
||
|
//
|
||
|
// Purpose: Initializes the CAccessControlList object.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
CAccessControlList::CAccessControlList (void) :
|
||
|
_pACL(NULL)
|
||
|
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CAccessControlList::~CAccessControlList
|
||
|
//
|
||
|
// Arguments: <none>
|
||
|
//
|
||
|
// Returns: <none>
|
||
|
//
|
||
|
// Purpose: Releases resources used by the CAccessControlList object.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
CAccessControlList::~CAccessControlList (void)
|
||
|
|
||
|
{
|
||
|
ReleaseMemory(_pACL);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CAccessControlList::operator PACL
|
||
|
//
|
||
|
// Arguments: <none>
|
||
|
//
|
||
|
// Returns: PACL
|
||
|
//
|
||
|
// Purpose: If the ACL has been constructed returns that value. If not
|
||
|
// then the ACL is constructed from the ACEs and then returned.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
CAccessControlList::operator PACL (void)
|
||
|
|
||
|
{
|
||
|
PACL pACL;
|
||
|
|
||
|
if (_pACL == NULL)
|
||
|
{
|
||
|
int i;
|
||
|
DWORD dwACLSize, dwSizeOfAllACEs;
|
||
|
|
||
|
pACL = NULL;
|
||
|
dwSizeOfAllACEs = 0;
|
||
|
|
||
|
// Walk thru all the ACEs to calculate the total size
|
||
|
// required for the ACL.
|
||
|
|
||
|
for (i = _ACEArray.GetCount() - 1; i >= 0; --i)
|
||
|
{
|
||
|
ACCESS_ALLOWED_ACE *pACE;
|
||
|
|
||
|
pACE = static_cast<ACCESS_ALLOWED_ACE*>(_ACEArray.Get(i));
|
||
|
dwSizeOfAllACEs += pACE->Header.AceSize;
|
||
|
}
|
||
|
dwACLSize = sizeof(ACL) + dwSizeOfAllACEs;
|
||
|
_pACL = pACL = static_cast<ACL*>(LocalAlloc(LMEM_FIXED, dwACLSize));
|
||
|
if (pACL != NULL)
|
||
|
{
|
||
|
TBOOL(InitializeAcl(pACL, dwACLSize, ACL_REVISION));
|
||
|
|
||
|
// Construct the ACL in reverse order of the ACEs. This
|
||
|
// allows CAccessControlList::Add to actually insert the
|
||
|
// granted access at the head of the list which is usually
|
||
|
// the desired result. The order of the ACEs is important!
|
||
|
|
||
|
for (i = _ACEArray.GetCount() - 1; i >= 0; --i)
|
||
|
{
|
||
|
ACCESS_ALLOWED_ACE *pACE;
|
||
|
|
||
|
pACE = static_cast<ACCESS_ALLOWED_ACE*>(_ACEArray.Get(i));
|
||
|
TBOOL(AddAccessAllowedAceEx(pACL, ACL_REVISION, pACE->Header.AceFlags, pACE->Mask, reinterpret_cast<PSID>(&pACE->SidStart)));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pACL = _pACL;
|
||
|
}
|
||
|
return(pACL);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CAccessControlList::Add
|
||
|
//
|
||
|
// Arguments: pSID = SID to grant access to.
|
||
|
// dwMask = Level of access to grant.
|
||
|
// ucInheritence = Type of inheritence for this access.
|
||
|
//
|
||
|
// Returns: NTSTATUS
|
||
|
//
|
||
|
// Purpose: Adds the given SID, access and inheritence type as an ACE to
|
||
|
// the list of ACEs to build into an ACL. The ACE array is
|
||
|
// allocated in blocks of 16 pointers to reduce repeated calls
|
||
|
// to allocate memory if many ACEs are added.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS CAccessControlList::Add (PSID pSID, ACCESS_MASK dwMask, UCHAR ucInheritence)
|
||
|
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
DWORD dwSIDLength, dwACESize;
|
||
|
ACCESS_ALLOWED_ACE *pACE;
|
||
|
|
||
|
dwSIDLength = GetLengthSid(pSID);
|
||
|
dwACESize = sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + dwSIDLength;
|
||
|
pACE = static_cast<ACCESS_ALLOWED_ACE*>(LocalAlloc(LMEM_FIXED, dwACESize));
|
||
|
if (pACE != NULL)
|
||
|
{
|
||
|
pACE->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;
|
||
|
pACE->Header.AceFlags = ucInheritence;
|
||
|
pACE->Header.AceSize = static_cast<USHORT>(dwACESize);
|
||
|
pACE->Mask = dwMask;
|
||
|
CopyMemory(&pACE->SidStart, pSID, dwSIDLength);
|
||
|
status = _ACEArray.Add(pACE);
|
||
|
if (STATUS_NO_MEMORY == status)
|
||
|
{
|
||
|
(HLOCAL)LocalFree(pACE);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
status = STATUS_NO_MEMORY;
|
||
|
}
|
||
|
return(status);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CAccessControlList::Remove
|
||
|
//
|
||
|
// Arguments: pSID = SID to revoke access from.
|
||
|
//
|
||
|
// Returns: NTSTATUS
|
||
|
//
|
||
|
// Purpose: Removes all references to the given SID from the ACE list.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS CAccessControlList::Remove (PSID pSID)
|
||
|
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
|
||
|
// Set up for an iteration of the array.
|
||
|
|
||
|
_searchSID = pSID;
|
||
|
_iFoundIndex = -1;
|
||
|
status = _ACEArray.Iterate(this);
|
||
|
while (NT_SUCCESS(status) && (_iFoundIndex >= 0))
|
||
|
{
|
||
|
|
||
|
// When the SIDs are found to match remove this entry.
|
||
|
// ALL matching SID entries are removed!
|
||
|
|
||
|
status = _ACEArray.Remove(_iFoundIndex);
|
||
|
if (NT_SUCCESS(status))
|
||
|
{
|
||
|
_iFoundIndex = -1;
|
||
|
status = _ACEArray.Iterate(this);
|
||
|
}
|
||
|
}
|
||
|
return(status);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CAccessControlList::Callback
|
||
|
//
|
||
|
// Arguments: pvData = Pointer to the array index data.
|
||
|
// iElementIndex = Index into the array.
|
||
|
//
|
||
|
// Returns: NTSTATUS
|
||
|
//
|
||
|
// Purpose: Callback from the CDynamicArray::Iterate function. This
|
||
|
// method can be used to process the array contents by index or
|
||
|
// by content when iterating thru the array. Return an error
|
||
|
// status to stop the iteration and have that value returned to
|
||
|
// the caller of CDynamicArray::Iterate.
|
||
|
//
|
||
|
// Converts the pointer into a pointer to an ACCESS_ALLOWED_ACE.
|
||
|
// The compares the SID in that ACE to the desired search SID.
|
||
|
// Saves the index if found.
|
||
|
//
|
||
|
// History: 1999-11-15 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS CAccessControlList::Callback (const void *pvData, int iElementIndex)
|
||
|
|
||
|
{
|
||
|
const ACCESS_ALLOWED_ACE *pACE;
|
||
|
|
||
|
pACE = *reinterpret_cast<const ACCESS_ALLOWED_ACE* const*>(pvData);
|
||
|
if (EqualSid(reinterpret_cast<PSID>(const_cast<unsigned long*>((&pACE->SidStart))), _searchSID) != FALSE)
|
||
|
{
|
||
|
_iFoundIndex = iElementIndex;
|
||
|
}
|
||
|
return(STATUS_SUCCESS);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CSecuredObject::CSecuredObject
|
||
|
//
|
||
|
// Arguments: hObject = Optional HANDLE to the object to secure.
|
||
|
// seObjectType = Type of object specified in handle.
|
||
|
//
|
||
|
// Returns: <none>
|
||
|
//
|
||
|
// Purpose: Sets the optionally given HANDLE into the member variables.
|
||
|
// The HANDLE is duplicated so the caller must release their
|
||
|
// HANDLE.
|
||
|
//
|
||
|
// In order for this class to work the handle you pass it MUST
|
||
|
// have DUPLICATE access as well as READ_CONTROL and WRITE_DAC.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
CSecuredObject::CSecuredObject (HANDLE hObject, SE_OBJECT_TYPE seObjectType) :
|
||
|
_hObject(hObject),
|
||
|
_seObjectType(seObjectType)
|
||
|
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CSecuredObject::~CSecuredObject
|
||
|
//
|
||
|
// Arguments: <none>
|
||
|
//
|
||
|
// Returns: <none>
|
||
|
//
|
||
|
// Purpose: Release our HANDLE reference.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
CSecuredObject::~CSecuredObject (void)
|
||
|
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CSecuredObject::Allow
|
||
|
//
|
||
|
// Arguments: pSID = SID to grant access to.
|
||
|
// dwMask = Level of access to grant.
|
||
|
// ucInheritence = Type of inheritence for this access.
|
||
|
//
|
||
|
// Returns: NTSTATUS
|
||
|
//
|
||
|
// Purpose: Get the DACL for the object. Add the desired access. Set the
|
||
|
// DACL for the object.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS CSecuredObject::Allow (PSID pSID, ACCESS_MASK dwMask, UCHAR ucInheritence) const
|
||
|
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
CAccessControlList accessControlList;
|
||
|
|
||
|
status = GetDACL(accessControlList);
|
||
|
if (NT_SUCCESS(status))
|
||
|
{
|
||
|
status = accessControlList.Add(pSID, dwMask, ucInheritence);
|
||
|
if (NT_SUCCESS(status))
|
||
|
{
|
||
|
status = SetDACL(accessControlList);
|
||
|
}
|
||
|
}
|
||
|
return(status);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CSecuredObject::Remove
|
||
|
//
|
||
|
// Arguments: pSID = SID to revoke access from.
|
||
|
//
|
||
|
// Returns: NTSTATUS
|
||
|
//
|
||
|
// Purpose: Get the DACL for the object. Remove the desired access. Set
|
||
|
// the DACL for the object.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS CSecuredObject::Remove (PSID pSID) const
|
||
|
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
CAccessControlList accessControlList;
|
||
|
|
||
|
status = GetDACL(accessControlList);
|
||
|
if (NT_SUCCESS(status))
|
||
|
{
|
||
|
status = accessControlList.Remove(pSID);
|
||
|
if (NT_SUCCESS(status))
|
||
|
{
|
||
|
status = SetDACL(accessControlList);
|
||
|
}
|
||
|
}
|
||
|
return(status);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CSecuredObject::GetDACL
|
||
|
//
|
||
|
// Arguments: accessControlList = CAccessControlList that gets the
|
||
|
// decomposed DACL into ACEs.
|
||
|
//
|
||
|
// Returns: NTSTATUS
|
||
|
//
|
||
|
// Purpose: Gets the object's DACL and walk the individual ACEs and add
|
||
|
// this access to the CAccessControlList object given. The access
|
||
|
// is walked backward to allow CAccessControlList::Add to add to
|
||
|
// end of the list but actually add to the head of the list.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS CSecuredObject::GetDACL (CAccessControlList& accessControlList) const
|
||
|
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
DWORD dwResult;
|
||
|
PACL pDACL;
|
||
|
PSECURITY_DESCRIPTOR pSD;
|
||
|
|
||
|
status = STATUS_SUCCESS;
|
||
|
pSD = NULL;
|
||
|
pDACL = NULL;
|
||
|
dwResult = GetSecurityInfo(_hObject,
|
||
|
_seObjectType,
|
||
|
DACL_SECURITY_INFORMATION,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
&pDACL,
|
||
|
NULL,
|
||
|
&pSD);
|
||
|
if ((ERROR_SUCCESS == dwResult) && (pDACL != NULL))
|
||
|
{
|
||
|
int i;
|
||
|
ACCESS_ALLOWED_ACE *pAce;
|
||
|
|
||
|
for (i = pDACL->AceCount - 1; NT_SUCCESS(status) && (i >= 0); --i)
|
||
|
{
|
||
|
if (GetAce(pDACL, i, reinterpret_cast<void**>(&pAce)) != FALSE)
|
||
|
{
|
||
|
ASSERTMSG(pAce->Header.AceType == ACCESS_ALLOWED_ACE_TYPE, "Expect only access allowed ACEs in CSecuredObject::MakeIndividualACEs");
|
||
|
status = accessControlList.Add(reinterpret_cast<PSID>(&pAce->SidStart), pAce->Mask, pAce->Header.AceFlags);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ReleaseMemory(pSD);
|
||
|
return(status);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// CSecuredObject::SetDACL
|
||
|
//
|
||
|
// Arguments: accessControlList = CAccessControlList that contains all
|
||
|
// ACEs to build into an ACL.
|
||
|
//
|
||
|
// Returns: NTSTATUS
|
||
|
//
|
||
|
// Purpose: Builds the ACL for the given ACE list and sets the DACL into
|
||
|
// the object handle.
|
||
|
//
|
||
|
// History: 1999-10-05 vtan created
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS CSecuredObject::SetDACL (CAccessControlList& accessControlList) const
|
||
|
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
DWORD dwResult;
|
||
|
|
||
|
dwResult = SetSecurityInfo(_hObject,
|
||
|
_seObjectType,
|
||
|
DACL_SECURITY_INFORMATION,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
accessControlList,
|
||
|
NULL);
|
||
|
if (ERROR_SUCCESS == dwResult)
|
||
|
{
|
||
|
status = STATUS_SUCCESS;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
status = CStatusCode::StatusCodeOfErrorCode(dwResult);
|
||
|
}
|
||
|
return(status);
|
||
|
}
|
||
|
|