1204 lines
31 KiB
C
1204 lines
31 KiB
C
|
/*
|
||
|
File usrparms.c
|
||
|
|
||
|
Callback routines exported to SAM for migrating and updating
|
||
|
user parms.
|
||
|
|
||
|
Paul Mayfield, 9/10/98
|
||
|
*/
|
||
|
|
||
|
#include <nt.h>
|
||
|
#include <ntrtl.h>
|
||
|
#include <nturtl.h>
|
||
|
#include <ntlsa.h>
|
||
|
#include <ntsam.h>
|
||
|
#include <samrpc.h>
|
||
|
#include <samisrv.h>
|
||
|
#include <windows.h>
|
||
|
#include <lm.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <memory.h>
|
||
|
#include <malloc.h>
|
||
|
#include <raserror.h>
|
||
|
#include <rasman.h>
|
||
|
#include <rasppp.h>
|
||
|
#include <mprapi.h>
|
||
|
#include <mprapip.h>
|
||
|
#include <usrparms.h> // for UP_CLIENT_DIAL
|
||
|
#include <cleartxt.h> // for IASParmsGetUserPassword
|
||
|
#include <rassfmhp.h> // for RasSfmHeap
|
||
|
#include <oaidl.h>
|
||
|
|
||
|
//
|
||
|
// Flags that restrict the values generated for a given
|
||
|
// set of ras user properties. See UPGenerateDsAttribs
|
||
|
//
|
||
|
#define UP_F_Dialin 0x1 // Generate dialup params
|
||
|
#define UP_F_Callback 0x2 // Generate callback params
|
||
|
#define UP_F_Upgrade 0x4 // Generate upgraded params
|
||
|
|
||
|
//
|
||
|
// Constants in the profiles
|
||
|
//
|
||
|
#define SDO_FRAMED 2
|
||
|
#define SDO_FRAMED_CALLBACK 4
|
||
|
|
||
|
// Names of user attributes that we set
|
||
|
//
|
||
|
static const WCHAR pszAttrDialin[] = L"msNPAllowDialin";
|
||
|
static const WCHAR pszAttrServiceType[] = L"msRADIUSServiceType";
|
||
|
static const WCHAR pszAttrCbNumber[] = L"msRADIUSCallbackNumber";
|
||
|
static const WCHAR pszAttrSavedCbNumber[] = L"msRASSavedCallbackNumber";
|
||
|
|
||
|
//
|
||
|
// Will be equal to the number of times the common allocation
|
||
|
// routine is called minus the number of times the common free
|
||
|
// routine is called. Should be zero else leaking memory.
|
||
|
//
|
||
|
DWORD dwUpLeakCount = 0;
|
||
|
|
||
|
//
|
||
|
// Prototype of free func.
|
||
|
//
|
||
|
VOID WINAPI
|
||
|
UserParmsFree(
|
||
|
IN PVOID pvData);
|
||
|
|
||
|
//
|
||
|
// Common tracing for the UserParm functions.
|
||
|
//
|
||
|
DWORD UpTrace (LPSTR pszTrace, ...) {
|
||
|
#if 0
|
||
|
va_list arglist;
|
||
|
char szBuffer[1024], szTemp[1024];
|
||
|
|
||
|
va_start(arglist, pszTrace);
|
||
|
vsprintf(szTemp, pszTrace, arglist);
|
||
|
va_end(arglist);
|
||
|
|
||
|
sprintf(szBuffer, "UserParms: %s\n", szTemp);
|
||
|
|
||
|
OutputDebugStringA(szBuffer);
|
||
|
#endif
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocation and free routines for UserParms functions
|
||
|
//
|
||
|
PVOID
|
||
|
UpAlloc(
|
||
|
IN DWORD dwSize,
|
||
|
IN BOOL bZero)
|
||
|
{
|
||
|
dwUpLeakCount++;
|
||
|
return RtlAllocateHeap(
|
||
|
RasSfmHeap(),
|
||
|
(bZero ? HEAP_ZERO_MEMORY : 0),
|
||
|
dwSize
|
||
|
);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Callback function called by NT5 SAM to free the blob
|
||
|
// returned by UserParmsConvert.
|
||
|
//
|
||
|
VOID
|
||
|
UpFree(
|
||
|
IN PVOID pvData)
|
||
|
{
|
||
|
dwUpLeakCount--;
|
||
|
if (pvData)
|
||
|
RtlFreeHeap(
|
||
|
RasSfmHeap(),
|
||
|
0,
|
||
|
pvData
|
||
|
);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Returns a heap-allocated copy of the given
|
||
|
// string
|
||
|
//
|
||
|
PWCHAR
|
||
|
UpStrDup(
|
||
|
IN PCWSTR pszSrc)
|
||
|
{
|
||
|
PWCHAR pszRet = NULL;
|
||
|
DWORD dwLen = wcslen(pszSrc);
|
||
|
|
||
|
pszRet = (PWCHAR) UpAlloc((dwLen + 1) * sizeof(WCHAR), FALSE);
|
||
|
if (pszRet)
|
||
|
wcscpy(pszRet, pszSrc);
|
||
|
|
||
|
return pszRet;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Returns a heap-allocated copy of the given unicode
|
||
|
// string converted into multibyte.
|
||
|
//
|
||
|
PUCHAR
|
||
|
UpWcstombsDup(
|
||
|
IN PWCHAR pszSrc)
|
||
|
{
|
||
|
PUCHAR pszRet = NULL;
|
||
|
DWORD dwSize = (wcslen(pszSrc) + 1) * sizeof(WCHAR);
|
||
|
|
||
|
pszRet = (PUCHAR) UpAlloc(dwSize, TRUE);
|
||
|
if (pszRet)
|
||
|
wcstombs(pszRet, pszSrc, dwSize);
|
||
|
|
||
|
return pszRet;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Returns a heap-allocated copy of the given
|
||
|
// blob
|
||
|
//
|
||
|
PVOID
|
||
|
UpBlobDup(
|
||
|
IN PVOID pvSrc,
|
||
|
IN ULONG ulLen)
|
||
|
{
|
||
|
PVOID pvRet = NULL;
|
||
|
|
||
|
if (ulLen == 0)
|
||
|
return NULL;
|
||
|
|
||
|
pvRet = UpAlloc(ulLen + sizeof(WCHAR), TRUE);
|
||
|
if (pvRet)
|
||
|
{
|
||
|
CopyMemory(pvRet, pvSrc, ulLen);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UpTrace("UpBlobDup: Failed to dupe %x %d.", pvSrc, ulLen);
|
||
|
}
|
||
|
|
||
|
return pvRet;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocates and initializes a dword attribute
|
||
|
//
|
||
|
NTSTATUS
|
||
|
UpInitializeDwordAttr(
|
||
|
IN SAM_USERPARMS_ATTR * pAttr,
|
||
|
IN PWCHAR pszAttr,
|
||
|
IN DWORD dwVal)
|
||
|
{
|
||
|
if (pszAttr == NULL)
|
||
|
{
|
||
|
return STATUS_NO_MEMORY;
|
||
|
}
|
||
|
|
||
|
// Initialize the name
|
||
|
RtlInitUnicodeString (&(pAttr->AttributeIdentifier), pszAttr);
|
||
|
pAttr->Syntax = Syntax_Attribute;
|
||
|
|
||
|
// Alloc/Initailze the value structure
|
||
|
pAttr->Values =
|
||
|
(SAM_USERPARMS_ATTRVALS*)
|
||
|
UpAlloc(sizeof(SAM_USERPARMS_ATTRVALS), TRUE);
|
||
|
if (pAttr->Values == NULL)
|
||
|
return STATUS_NO_MEMORY;
|
||
|
|
||
|
// Alloc/Init the value
|
||
|
pAttr->Values->value = UpAlloc(sizeof(DWORD), TRUE);
|
||
|
if (pAttr->Values->value == NULL)
|
||
|
return STATUS_NO_MEMORY;
|
||
|
*((DWORD*)pAttr->Values->value) = dwVal;
|
||
|
pAttr->Values->length = sizeof(DWORD);
|
||
|
|
||
|
// Put in the value count
|
||
|
pAttr->CountOfValues = 1;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocates and initializes a dword attribute
|
||
|
//
|
||
|
NTSTATUS
|
||
|
UpInitializeStringAttrA(
|
||
|
OUT SAM_USERPARMS_ATTR * pAttr,
|
||
|
IN PWCHAR pszAttr,
|
||
|
IN PUCHAR pszVal)
|
||
|
{
|
||
|
if (pszAttr == NULL)
|
||
|
{
|
||
|
return STATUS_NO_MEMORY;
|
||
|
}
|
||
|
|
||
|
// Initialize the name
|
||
|
RtlInitUnicodeString (&(pAttr->AttributeIdentifier), pszAttr);
|
||
|
pAttr->Syntax = Syntax_Attribute;
|
||
|
|
||
|
// Alloc/Initailze the value structure
|
||
|
pAttr->Values =
|
||
|
(SAM_USERPARMS_ATTRVALS*)
|
||
|
UpAlloc(sizeof(SAM_USERPARMS_ATTRVALS), TRUE);
|
||
|
if (pAttr->Values == NULL)
|
||
|
return STATUS_NO_MEMORY;
|
||
|
|
||
|
// Alloc/Init the value
|
||
|
pAttr->Values->value = pszVal;
|
||
|
|
||
|
if (pszVal)
|
||
|
{
|
||
|
pAttr->Values->length = (strlen(pszVal) + 1) * sizeof(CHAR);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pAttr->Values->length = 1 * sizeof(CHAR);
|
||
|
}
|
||
|
|
||
|
// Put in the value count
|
||
|
pAttr->CountOfValues = 1;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocates and initializes a cleartext password attribute
|
||
|
//
|
||
|
NTSTATUS
|
||
|
UpInitializePasswordAttr(
|
||
|
OUT SAM_USERPARMS_ATTR * pAttr,
|
||
|
IN PWSTR pszPassword)
|
||
|
{
|
||
|
// Alloc/Initialize the value structure
|
||
|
pAttr->Values =
|
||
|
(SAM_USERPARMS_ATTRVALS*)
|
||
|
UpAlloc(sizeof(SAM_USERPARMS_ATTRVALS), TRUE);
|
||
|
if (pAttr->Values == NULL)
|
||
|
return STATUS_NO_MEMORY;
|
||
|
|
||
|
// Alloc/Init the value
|
||
|
pAttr->Values->value = pszPassword;
|
||
|
pAttr->Values->length = (wcslen(pszPassword) + 1) * sizeof(WCHAR);
|
||
|
|
||
|
// Put in the value count
|
||
|
pAttr->CountOfValues = 1;
|
||
|
|
||
|
// Initialize the name and syntax.
|
||
|
RtlInitUnicodeString(
|
||
|
&pAttr->AttributeIdentifier,
|
||
|
UpStrDup(L"CLEARTEXT")
|
||
|
);
|
||
|
pAttr->Syntax = Syntax_EncryptedAttribute;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocates and initializes an attribute
|
||
|
// to be deleted.
|
||
|
//
|
||
|
NTSTATUS
|
||
|
UpInitializeDeletedAttr(
|
||
|
OUT SAM_USERPARMS_ATTR * pAttr,
|
||
|
IN PWCHAR pszAttr)
|
||
|
{
|
||
|
if (pszAttr == NULL)
|
||
|
{
|
||
|
return STATUS_NO_MEMORY;
|
||
|
}
|
||
|
|
||
|
// Initialize the name
|
||
|
RtlInitUnicodeString (&(pAttr->AttributeIdentifier), pszAttr);
|
||
|
pAttr->Syntax = Syntax_Attribute;
|
||
|
|
||
|
// Value count of zero means delete
|
||
|
//
|
||
|
pAttr->CountOfValues = 0;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Converts the given user parms blob into a set of
|
||
|
// ras attributes
|
||
|
//
|
||
|
NTSTATUS
|
||
|
UpUserParmsToRasUser0 (
|
||
|
IN PVOID pvUserParms,
|
||
|
OUT RAS_USER_0 * pRasUser0)
|
||
|
{
|
||
|
DWORD dwErr;
|
||
|
|
||
|
// Initalize
|
||
|
ZeroMemory(pRasUser0, sizeof(RAS_USER_0));
|
||
|
pRasUser0->bfPrivilege = RASPRIV_NoCallback;
|
||
|
pRasUser0->wszPhoneNumber[0] = UNICODE_NULL;
|
||
|
|
||
|
// The the user parms are null, the defaults
|
||
|
// will do.
|
||
|
if (pvUserParms == NULL)
|
||
|
{
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
// Truncate user parms at sizeof USER_PARMS
|
||
|
if (lstrlenW((PWCHAR)pvUserParms) >= sizeof(USER_PARMS))
|
||
|
{
|
||
|
// We slam in a null at sizeof(USER_PARMS)-1 which
|
||
|
// corresponds to user_parms.up_Null
|
||
|
((PWCHAR)pvUserParms)[sizeof(USER_PARMS)-1] = L'\0';
|
||
|
}
|
||
|
|
||
|
// Get RAS info (and validate) from usr_parms
|
||
|
dwErr = MprGetUsrParams(
|
||
|
UP_CLIENT_DIAL,
|
||
|
(LPWSTR) pvUserParms,
|
||
|
(LPWSTR) pRasUser0);
|
||
|
if (dwErr == NO_ERROR)
|
||
|
{
|
||
|
// Get RAS Privilege and callback number
|
||
|
RasPrivilegeAndCallBackNumber(FALSE, pRasUser0);
|
||
|
}
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/////////
|
||
|
// Signature of the extraction function.
|
||
|
/////////
|
||
|
typedef HRESULT (WINAPI *IASPARMSQUERYUSERPROPERTY)(
|
||
|
IN PCWSTR pszUserParms,
|
||
|
IN PCWSTR pszName,
|
||
|
OUT VARIANT *pvarValue
|
||
|
);
|
||
|
|
||
|
//////////
|
||
|
// Uplevel per-user attributes that will be migrated.
|
||
|
//////////
|
||
|
CONST PCWSTR UPLEVEL_PARMS[] =
|
||
|
{
|
||
|
L"msNPAllowDialin",
|
||
|
L"msNPCallingStationID",
|
||
|
L"msRADIUSCallbackNumber",
|
||
|
L"msRADIUSFramedIPAddress",
|
||
|
L"msRADIUSFramedRoute",
|
||
|
L"msRADIUSServiceType"
|
||
|
};
|
||
|
|
||
|
//////////
|
||
|
// Number of per-user attributes.
|
||
|
//////////
|
||
|
#define NUM_UPLEVEL_PARMS (sizeof(UPLEVEL_PARMS)/sizeof(UPLEVEL_PARMS[0]))
|
||
|
|
||
|
/////////
|
||
|
// Converts a ULONG into a SAM_USERPARMS_ATTRVALS struct.
|
||
|
/////////
|
||
|
NTSTATUS
|
||
|
NTAPI
|
||
|
ConvertULongToAttrVal(
|
||
|
IN ULONG ulValue,
|
||
|
OUT PSAM_USERPARMS_ATTRVALS pAttrVal
|
||
|
)
|
||
|
{
|
||
|
// Allocate memory to hold the ULONG.
|
||
|
pAttrVal->value = UpAlloc(sizeof(ULONG), FALSE);
|
||
|
if (pAttrVal->value == NULL) { return STATUS_NO_MEMORY; }
|
||
|
|
||
|
// Copy in the value.
|
||
|
*(PULONG)pAttrVal->value = ulValue;
|
||
|
|
||
|
// Set the length.
|
||
|
pAttrVal->length = sizeof(ULONG);
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
// Converts a single-valued VARIANT into a SAM_USERPARMS_ATTRVALS struct.
|
||
|
//////////
|
||
|
NTSTATUS
|
||
|
NTAPI
|
||
|
ConvertVariantToAttrVal(
|
||
|
IN CONST VARIANT *pvarValue,
|
||
|
OUT PSAM_USERPARMS_ATTRVALS pAttrVal
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
ULONG length;
|
||
|
UNICODE_STRING wide;
|
||
|
ANSI_STRING ansi;
|
||
|
|
||
|
switch (V_VT(pvarValue))
|
||
|
{
|
||
|
case VT_EMPTY:
|
||
|
{
|
||
|
// VT_EMPTY means the attribute was deleted.
|
||
|
pAttrVal->value = NULL;
|
||
|
pAttrVal->length = 0;
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
case VT_I2:
|
||
|
return ConvertULongToAttrVal(V_I2(pvarValue), pAttrVal);
|
||
|
|
||
|
case VT_I4:
|
||
|
return ConvertULongToAttrVal(V_I4(pvarValue), pAttrVal);
|
||
|
|
||
|
case VT_BSTR:
|
||
|
{
|
||
|
// Check the BSTR.
|
||
|
if (V_BSTR(pvarValue) == NULL) { return STATUS_INVALID_PARAMETER; }
|
||
|
|
||
|
// Initialize the source string.
|
||
|
RtlInitUnicodeString(&wide, V_BSTR(pvarValue));
|
||
|
|
||
|
// Initialize the destination buffer.
|
||
|
ansi.Length = 0;
|
||
|
ansi.MaximumLength = wide.MaximumLength / sizeof(WCHAR);
|
||
|
ansi.Buffer = UpAlloc(ansi.MaximumLength, FALSE);
|
||
|
if (ansi.Buffer == NULL) { return STATUS_NO_MEMORY; }
|
||
|
|
||
|
// Convert from wide to ansi.
|
||
|
status = RtlUnicodeStringToAnsiString(&ansi, &wide, FALSE);
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
UpFree(ansi.Buffer);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
// Store the result.
|
||
|
pAttrVal->value = ansi.Buffer;
|
||
|
pAttrVal->length = ansi.Length + 1;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
case VT_BOOL:
|
||
|
return ConvertULongToAttrVal((V_BOOL(pvarValue) ? 1 : 0), pAttrVal);
|
||
|
|
||
|
case VT_I1:
|
||
|
return ConvertULongToAttrVal(V_I1(pvarValue), pAttrVal);
|
||
|
|
||
|
case VT_UI1:
|
||
|
return ConvertULongToAttrVal(V_UI1(pvarValue), pAttrVal);
|
||
|
|
||
|
case VT_UI2:
|
||
|
return ConvertULongToAttrVal(V_UI2(pvarValue), pAttrVal);
|
||
|
|
||
|
case VT_UI4:
|
||
|
return ConvertULongToAttrVal(V_UI4(pvarValue), pAttrVal);
|
||
|
|
||
|
case VT_ARRAY | VT_I1:
|
||
|
case VT_ARRAY | VT_UI1:
|
||
|
{
|
||
|
// Check the SAFEARRAY.
|
||
|
if (V_ARRAY(pvarValue) == NULL) { return STATUS_INVALID_PARAMETER; }
|
||
|
|
||
|
// Allocate memory for the octet string.
|
||
|
length = V_ARRAY(pvarValue)->rgsabound[0].cElements;
|
||
|
pAttrVal->value = UpAlloc(length, FALSE);
|
||
|
if (pAttrVal->value == NULL) { return STATUS_NO_MEMORY; }
|
||
|
|
||
|
// Copy in the data.
|
||
|
memcpy(pAttrVal->value, V_ARRAY(pvarValue)->pvData, length);
|
||
|
|
||
|
// Set the length.
|
||
|
pAttrVal->length = length;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we made it here it was an unsupported VARTYPE.
|
||
|
return STATUS_INVALID_PARAMETER;
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
// Frees the values array of a SAM_USERPARMS_ATTR struct.
|
||
|
//////////
|
||
|
VOID
|
||
|
NTAPI
|
||
|
FreeUserParmsAttrValues(
|
||
|
IN PSAM_USERPARMS_ATTR pAttrs
|
||
|
)
|
||
|
{
|
||
|
ULONG i;
|
||
|
|
||
|
if (pAttrs)
|
||
|
{
|
||
|
for (i = 0; i < pAttrs->CountOfValues; ++i)
|
||
|
{
|
||
|
UpFree(pAttrs->Values[i].value);
|
||
|
}
|
||
|
|
||
|
UpFree(pAttrs->Values);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
// Converts a VARIANT into a SAM_USERPARMS_ATTR struct.
|
||
|
//////////
|
||
|
NTSTATUS
|
||
|
NTAPI
|
||
|
ConvertVariantToUserParmsAttr(
|
||
|
IN CONST VARIANT *pvarSrc,
|
||
|
OUT PSAM_USERPARMS_ATTR pAttrs
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
ULONG nelem;
|
||
|
CONST VARIANT *srcVal;
|
||
|
SAM_USERPARMS_ATTRVALS *dstVal;
|
||
|
|
||
|
// Get the array of values to be converted.
|
||
|
if (V_VT(pvarSrc) != (VT_VARIANT | VT_ARRAY))
|
||
|
{
|
||
|
nelem = 1;
|
||
|
srcVal = pvarSrc;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nelem = V_ARRAY(pvarSrc)->rgsabound[0].cElements;
|
||
|
srcVal = (CONST VARIANT *)V_ARRAY(pvarSrc)->pvData;
|
||
|
}
|
||
|
|
||
|
// Initialize CountOfValues to zero. We'll use this to track how many
|
||
|
// values have been successfully converted.
|
||
|
pAttrs->CountOfValues = 0;
|
||
|
|
||
|
// Allocate memory to hold the values.
|
||
|
pAttrs->Values = UpAlloc(sizeof(SAM_USERPARMS_ATTRVALS) * nelem, TRUE);
|
||
|
if (pAttrs->Values == NULL) { return STATUS_NO_MEMORY; }
|
||
|
|
||
|
// Loop through each value to be converted.
|
||
|
for (dstVal = pAttrs->Values; nelem > 0; ++srcVal, ++dstVal, --nelem)
|
||
|
{
|
||
|
status = ConvertVariantToAttrVal(srcVal, dstVal);
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
// Clean-up the partial results.
|
||
|
FreeUserParmsAttrValues(pAttrs);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
++(pAttrs->CountOfValues);
|
||
|
}
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
// Extracts the NT5 per-user attributes from a SAM UserParameters string and
|
||
|
// converts them to a SAM_USERPARMS_ATTRBLOCK struct.
|
||
|
//////////
|
||
|
NTSTATUS
|
||
|
NTAPI
|
||
|
ConvertUserParmsToAttrBlock(
|
||
|
IN PCWSTR lpUserParms,
|
||
|
OUT PSAM_USERPARMS_ATTRBLOCK *ppAttrs
|
||
|
)
|
||
|
{
|
||
|
static IASPARMSQUERYUSERPROPERTY IASParmsQueryUserProperty;
|
||
|
|
||
|
NTSTATUS status;
|
||
|
PSAM_USERPARMS_ATTR dst;
|
||
|
PWSTR szPassword;
|
||
|
ULONG i;
|
||
|
HRESULT hr;
|
||
|
VARIANT src;
|
||
|
|
||
|
//////////
|
||
|
// Make sure we have the extraction function loaded.
|
||
|
//////////
|
||
|
|
||
|
if (IASParmsQueryUserProperty == NULL)
|
||
|
{
|
||
|
IASParmsQueryUserProperty = (IASPARMSQUERYUSERPROPERTY)
|
||
|
GetProcAddress(
|
||
|
LoadLibraryW(
|
||
|
L"IASSAM.DLL"
|
||
|
),
|
||
|
"IASParmsQueryUserProperty"
|
||
|
);
|
||
|
|
||
|
if (!IASParmsQueryUserProperty) { return STATUS_PROCEDURE_NOT_FOUND; }
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
// Allocate memory for the SAM_USERPARMS_ATTRBLOCK.
|
||
|
//////////
|
||
|
|
||
|
*ppAttrs = (PSAM_USERPARMS_ATTRBLOCK)
|
||
|
UpAlloc(
|
||
|
sizeof(SAM_USERPARMS_ATTRBLOCK),
|
||
|
TRUE
|
||
|
);
|
||
|
if (*ppAttrs == NULL)
|
||
|
{
|
||
|
return STATUS_NO_MEMORY;
|
||
|
}
|
||
|
|
||
|
(*ppAttrs)->UserParmsAttr = (PSAM_USERPARMS_ATTR)
|
||
|
UpAlloc(
|
||
|
sizeof(SAM_USERPARMS_ATTR) *
|
||
|
(NUM_UPLEVEL_PARMS + 1),
|
||
|
TRUE
|
||
|
);
|
||
|
if ((*ppAttrs)->UserParmsAttr == NULL)
|
||
|
{
|
||
|
UpFree(*ppAttrs);
|
||
|
return STATUS_NO_MEMORY;
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
// Convert the cleartext password.
|
||
|
//////////
|
||
|
|
||
|
dst = (*ppAttrs)->UserParmsAttr;
|
||
|
|
||
|
szPassword = NULL;
|
||
|
IASParmsGetUserPassword(
|
||
|
lpUserParms,
|
||
|
&szPassword
|
||
|
);
|
||
|
|
||
|
if (szPassword)
|
||
|
{
|
||
|
status = UpInitializePasswordAttr(
|
||
|
dst,
|
||
|
UpStrDup(szPassword)
|
||
|
);
|
||
|
|
||
|
LocalFree(szPassword);
|
||
|
|
||
|
if (NT_SUCCESS(status))
|
||
|
{
|
||
|
++dst;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
// Convert the dial-in parameters.
|
||
|
//////////
|
||
|
|
||
|
for (i = 0; i < NUM_UPLEVEL_PARMS; ++i)
|
||
|
{
|
||
|
// Try to extract the parameter from UserParms.
|
||
|
hr = IASParmsQueryUserProperty(
|
||
|
lpUserParms,
|
||
|
UPLEVEL_PARMS[i],
|
||
|
&src
|
||
|
);
|
||
|
if (FAILED(hr) || V_VT(&src) == VT_EMPTY) { continue; }
|
||
|
|
||
|
// Convert to a SAM_USERPARMS_ATTRVALS array.
|
||
|
status = ConvertVariantToUserParmsAttr(
|
||
|
&src,
|
||
|
dst
|
||
|
);
|
||
|
if (NT_SUCCESS(status))
|
||
|
{
|
||
|
// Fill in the AttributeIdentifier ...
|
||
|
RtlInitUnicodeString(
|
||
|
&dst->AttributeIdentifier,
|
||
|
UpStrDup(UPLEVEL_PARMS[i])
|
||
|
);
|
||
|
|
||
|
// ... and the Syntax.
|
||
|
dst->Syntax = Syntax_Attribute;
|
||
|
|
||
|
// All went well, so advance to the next element in the array.
|
||
|
++dst;
|
||
|
}
|
||
|
|
||
|
// We're done with the VARIANT.
|
||
|
VariantClear(&src);
|
||
|
}
|
||
|
|
||
|
(*ppAttrs)->attCount = (ULONG)(dst - (*ppAttrs)->UserParmsAttr);
|
||
|
|
||
|
// If there weren't any attributes, then free the UserParmsAttr array.
|
||
|
if ((*ppAttrs)->attCount == 0)
|
||
|
{
|
||
|
UpFree((*ppAttrs)->UserParmsAttr);
|
||
|
|
||
|
(*ppAttrs)->UserParmsAttr = NULL;
|
||
|
}
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Generate an appropriate set of ds attributes based on the
|
||
|
// ras user information provided
|
||
|
//
|
||
|
NTSTATUS
|
||
|
UpGenerateDsAttribs (
|
||
|
IN DWORD dwFlags,
|
||
|
IN RAS_USER_0 * pRasUser0,
|
||
|
IN PWSTR szPassword,
|
||
|
OUT PSAM_USERPARMS_ATTRBLOCK * ppAttrs)
|
||
|
{
|
||
|
PSAM_USERPARMS_ATTRBLOCK pRet = NULL;
|
||
|
SAM_USERPARMS_ATTR * pCurAttr = NULL;
|
||
|
PWCHAR pszDupPassword, pszCurAttr = NULL;
|
||
|
DWORD dwCurVal = 0, dwDsParamCount;
|
||
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
||
|
|
||
|
UpTrace("UpGenerateDsAttribs: enter %x", dwFlags);
|
||
|
|
||
|
do
|
||
|
{
|
||
|
// pmay: 330184
|
||
|
//
|
||
|
// If we're upgrading, then having NULL userparms or having
|
||
|
// deny set explicitly should cause us to not add the msNPAllowDialin
|
||
|
// value so that user will be managed by policy.
|
||
|
//
|
||
|
if (
|
||
|
(dwFlags & UP_F_Upgrade) &&
|
||
|
(!(pRasUser0->bfPrivilege & RASPRIV_DialinPrivilege))
|
||
|
)
|
||
|
{
|
||
|
dwFlags &= ~UP_F_Dialin;
|
||
|
}
|
||
|
|
||
|
// Initialize the return value
|
||
|
pRet = (PSAM_USERPARMS_ATTRBLOCK)
|
||
|
UpAlloc(sizeof(SAM_USERPARMS_ATTRBLOCK), TRUE);
|
||
|
if (pRet == NULL)
|
||
|
{
|
||
|
UpTrace("UpGenerateDsAttribs: alloc block failed");
|
||
|
ntStatus = STATUS_NO_MEMORY;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Calculate the total number of values
|
||
|
dwDsParamCount = 0;
|
||
|
if (dwFlags & UP_F_Dialin)
|
||
|
{
|
||
|
dwDsParamCount += 1;
|
||
|
}
|
||
|
if (dwFlags & UP_F_Callback)
|
||
|
{
|
||
|
dwDsParamCount += 3;
|
||
|
}
|
||
|
if (szPassword != NULL)
|
||
|
{
|
||
|
dwDsParamCount += 1;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set the array to be big enough to accomodate 4 attributes:
|
||
|
// 1. Dialin bit
|
||
|
// 2. Callback Number or Saved Callback Number
|
||
|
// 3. Deleted version of #2
|
||
|
// 4. Service Type (for callback policy)
|
||
|
//
|
||
|
pCurAttr = (SAM_USERPARMS_ATTR*)
|
||
|
UpAlloc(sizeof(SAM_USERPARMS_ATTR) * dwDsParamCount, TRUE);
|
||
|
if (pCurAttr == NULL)
|
||
|
{
|
||
|
ntStatus = STATUS_NO_MEMORY;
|
||
|
UpTrace("UpGenerateDsAttribs: alloc of %d values failed",
|
||
|
dwDsParamCount);
|
||
|
break;
|
||
|
}
|
||
|
pRet->attCount = dwDsParamCount;
|
||
|
pRet->UserParmsAttr = pCurAttr;
|
||
|
|
||
|
// Set any appropriate dialin parameters
|
||
|
//
|
||
|
if (dwFlags & UP_F_Dialin)
|
||
|
{
|
||
|
dwCurVal =
|
||
|
(pRasUser0->bfPrivilege & RASPRIV_DialinPrivilege) ? 1 : 0;
|
||
|
|
||
|
// Initialize the dialin setting
|
||
|
ntStatus = UpInitializeDwordAttr(
|
||
|
pCurAttr,
|
||
|
UpStrDup((PWCHAR)pszAttrDialin),
|
||
|
dwCurVal);
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UpGenerateDsAttribs: fail dialin val %x", ntStatus);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pCurAttr++;
|
||
|
}
|
||
|
|
||
|
// Set any appropriate callback parameters
|
||
|
//
|
||
|
if (dwFlags & UP_F_Callback)
|
||
|
{
|
||
|
|
||
|
// The following logic was modified for SP1 of Win2K. The reason is that
|
||
|
// the values being set did not conform to the rules outlined in the
|
||
|
// comments to the UserParmsConvert function.
|
||
|
//
|
||
|
// Namely,
|
||
|
// 1. the msRADIUSServiceType was being set to SDO_FRAMED instead of
|
||
|
// <empty> when RASPRIV_NoCallback was set.
|
||
|
//
|
||
|
// 2. When RASPRIV_NoCallback was set, the msRADIUSCallbackNumber was
|
||
|
// set and the msRASSavedCallbackNumber was deleted instead of the
|
||
|
// vice-versa
|
||
|
//
|
||
|
|
||
|
// Initialize the service type
|
||
|
if (pRasUser0->bfPrivilege & RASPRIV_NoCallback)
|
||
|
{
|
||
|
ntStatus = UpInitializeDeletedAttr(
|
||
|
pCurAttr,
|
||
|
UpStrDup((PWCHAR)pszAttrServiceType));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ntStatus = UpInitializeDwordAttr(
|
||
|
pCurAttr,
|
||
|
UpStrDup((PWCHAR)pszAttrServiceType),
|
||
|
SDO_FRAMED_CALLBACK);
|
||
|
}
|
||
|
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UpGenerateDsAttribs: fail ST val %x", ntStatus);
|
||
|
break;
|
||
|
}
|
||
|
pCurAttr++;
|
||
|
|
||
|
// Initialize the callback number that will be committed
|
||
|
pszCurAttr = (pRasUser0->bfPrivilege & RASPRIV_AdminSetCallback) ?
|
||
|
(PWCHAR) pszAttrCbNumber :
|
||
|
(PWCHAR) pszAttrSavedCbNumber;
|
||
|
if (*(pRasUser0->wszPhoneNumber))
|
||
|
{
|
||
|
ntStatus = UpInitializeStringAttrA(
|
||
|
pCurAttr,
|
||
|
UpStrDup(pszCurAttr),
|
||
|
UpWcstombsDup(pRasUser0->wszPhoneNumber));
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UpGenerateDsAttribs: fail CB val %x", ntStatus);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ntStatus = UpInitializeDeletedAttr(
|
||
|
pCurAttr,
|
||
|
UpStrDup(pszCurAttr));
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UpGenerateDsAttribs: fail del CB val %x", ntStatus);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
pCurAttr++;
|
||
|
|
||
|
// Remove the callback number that doesn't apply.
|
||
|
pszCurAttr = (pszCurAttr == pszAttrCbNumber) ?
|
||
|
(PWCHAR) pszAttrSavedCbNumber :
|
||
|
(PWCHAR) pszAttrCbNumber;
|
||
|
ntStatus = UpInitializeDeletedAttr(
|
||
|
pCurAttr,
|
||
|
UpStrDup(pszCurAttr));
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UpGenerateDsAttribs: fail del SCB val %x", ntStatus);
|
||
|
break;
|
||
|
}
|
||
|
pCurAttr++;
|
||
|
}
|
||
|
|
||
|
// Set the cleartext password if present
|
||
|
//
|
||
|
if (szPassword != NULL)
|
||
|
{
|
||
|
// Make a duplicate copy of the password
|
||
|
if ((pszDupPassword = UpStrDup(szPassword)) == NULL)
|
||
|
{
|
||
|
ntStatus = STATUS_NO_MEMORY;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Initialize the password attribute
|
||
|
ntStatus = UpInitializePasswordAttr(
|
||
|
pCurAttr,
|
||
|
pszDupPassword);
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UpGenerateDsAttribs: fail password val %x", ntStatus);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pCurAttr++;
|
||
|
}
|
||
|
|
||
|
} while (FALSE);
|
||
|
|
||
|
// Cleanup
|
||
|
{
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UserParmsFree(pRet);
|
||
|
*ppAttrs = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*ppAttrs = pRet;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Callback function called by NT5 SAM whenever the user parms
|
||
|
// of a particular user are modified. The job of this callout
|
||
|
// is to take the new value of user parms and generate a set of
|
||
|
// domain attributes that need to be set for the given user so
|
||
|
// that per-user DS attributes and userparms are kept in sync.
|
||
|
//
|
||
|
// This callout will be invoked during dcpromo to upgrade user parms
|
||
|
// and whenever userparms is modified (by downlevel api's and apps).
|
||
|
//
|
||
|
// Callback functions for NT5 SAM are registered in the following
|
||
|
// registry key:
|
||
|
//
|
||
|
// HKLM\SYS\CCS\Control\LSA\NotificationPackages
|
||
|
//
|
||
|
// The following are the rules of the RAS LDAP parameters:
|
||
|
//
|
||
|
// msNPAllowDialin
|
||
|
// - Empty = Use policy to determine dialin privilege
|
||
|
// - 1 = Allow dialin
|
||
|
// - 2 = Deny dialin
|
||
|
//
|
||
|
// msRADIUSServiceType
|
||
|
// - Empty = NoCallback policy
|
||
|
// - 4 = CallerCallback if msRADIUSCallbackNumber is empty
|
||
|
// AdminCallback if msRADIUSCallbackNumber is not empty
|
||
|
//
|
||
|
// msRADIUSCallbackNumber
|
||
|
// - Determines the callback policy depending on msRADIUSServiceType
|
||
|
//
|
||
|
// msRASSavedCallbackNumber
|
||
|
// - Used to store the last known value of msRADIUSCallbackNumber when
|
||
|
// switching from AdminCallback policy to some other policy.
|
||
|
//
|
||
|
|
||
|
NTSTATUS
|
||
|
UserParmsConvert (
|
||
|
IN ULONG ulFlags,
|
||
|
IN PSID pDomainSid,
|
||
|
IN ULONG ulObjectRid,
|
||
|
IN ULONG ulOrigLen,
|
||
|
IN PVOID pvOrigUserParms,
|
||
|
IN ULONG ulNewLen,
|
||
|
IN PVOID pvNewUserParms,
|
||
|
OUT PSAM_USERPARMS_ATTRBLOCK * ppAttrs)
|
||
|
{
|
||
|
RAS_USER_0 RasUser00, *pOrigUser = &RasUser00;
|
||
|
RAS_USER_0 RasUser01, *pNewUser = &RasUser01;
|
||
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
||
|
PVOID pvOrig = NULL, pvNew = NULL;
|
||
|
DWORD dwFlags = 0, dwMask;
|
||
|
PWSTR szPassword = NULL;
|
||
|
|
||
|
UpTrace(
|
||
|
"UPConvert: F=%x, Rid=%x, OLen=%d, OPar=%x, NLen=%d, NPar=%x",
|
||
|
ulFlags, ulObjectRid, ulOrigLen, pvOrigUserParms,
|
||
|
ulNewLen, pvNewUserParms);
|
||
|
|
||
|
// Validate parameters
|
||
|
if (ppAttrs == NULL)
|
||
|
return STATUS_INVALID_PARAMETER;
|
||
|
|
||
|
// Initialize the return value;
|
||
|
*ppAttrs = NULL;
|
||
|
|
||
|
// If the user parms passed to us are NULL,
|
||
|
// then keep defaults.
|
||
|
if ((pvNewUserParms == NULL) || (ulNewLen == 0))
|
||
|
{
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
do
|
||
|
{
|
||
|
// Allocate and initialize local copies of
|
||
|
// the user parms
|
||
|
pvOrig = UpBlobDup(pvOrigUserParms, ulOrigLen);
|
||
|
pvNew = UpBlobDup(pvNewUserParms, ulNewLen);
|
||
|
|
||
|
// If this is a NT5 standalone being promoted to a DC, then we
|
||
|
// just convert the uplevel userparms 'as is'.
|
||
|
if ((ulFlags & SAM_USERPARMS_DURING_UPGRADE) &&
|
||
|
!SamINT4UpgradeInProgress())
|
||
|
{
|
||
|
ntStatus = ConvertUserParmsToAttrBlock(pvNew, ppAttrs);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Get the new ras properties
|
||
|
ntStatus = UpUserParmsToRasUser0(
|
||
|
pvNew,
|
||
|
pNewUser);
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UPConvert: Conversion to RAS_USER_0 failed.(1)");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If we're upgrading, then we should blindly
|
||
|
// set whatever information is stored in the user.
|
||
|
if (ulFlags & SAM_USERPARMS_DURING_UPGRADE)
|
||
|
{
|
||
|
IASParmsGetUserPassword(pvNewUserParms, &szPassword);
|
||
|
|
||
|
ntStatus = UpGenerateDsAttribs(
|
||
|
UP_F_Dialin | UP_F_Callback | UP_F_Upgrade,
|
||
|
pNewUser,
|
||
|
szPassword,
|
||
|
ppAttrs);
|
||
|
|
||
|
LocalFree(szPassword);
|
||
|
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UPConvert: GenerateDsAttribs failed %x", ntStatus);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Get the ras properties of the old user parms
|
||
|
ntStatus = UpUserParmsToRasUser0(
|
||
|
pvOrig,
|
||
|
pOrigUser);
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UPConvert: Conversion to RAS_USER_0 failed.(2)");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Find out if the dialin privilege should be updated
|
||
|
dwFlags = 0;
|
||
|
if (!!(pOrigUser->bfPrivilege & RASPRIV_DialinPrivilege) !=
|
||
|
!!(pNewUser->bfPrivilege & RASPRIV_DialinPrivilege))
|
||
|
{
|
||
|
dwFlags |= UP_F_Dialin;
|
||
|
}
|
||
|
|
||
|
// pmay: 264409
|
||
|
//
|
||
|
// If we are adding null usrparms for the first time,
|
||
|
// go ahead and add the dialin bit value to the ds.
|
||
|
//
|
||
|
if ((pvOrig == NULL) && (pvNew != NULL))
|
||
|
{
|
||
|
dwFlags |= UP_F_Dialin;
|
||
|
}
|
||
|
|
||
|
// Findout if any callback info should be updated
|
||
|
dwMask = RASPRIV_NoCallback |
|
||
|
RASPRIV_CallerSetCallback |
|
||
|
RASPRIV_AdminSetCallback;
|
||
|
if (((pOrigUser->bfPrivilege & dwMask) !=
|
||
|
(pNewUser->bfPrivilege & dwMask)) ||
|
||
|
(wcscmp(pOrigUser->wszPhoneNumber, pNewUser->wszPhoneNumber) != 0)
|
||
|
)
|
||
|
{
|
||
|
dwFlags |= UP_F_Callback;
|
||
|
}
|
||
|
|
||
|
// If there were no changes, we're done.
|
||
|
if (dwFlags == 0)
|
||
|
{
|
||
|
UpTrace("UPConvert: nothing to update.");
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Create the new attributes
|
||
|
ntStatus = UpGenerateDsAttribs(dwFlags, pNewUser, NULL, ppAttrs);
|
||
|
if (ntStatus != STATUS_SUCCESS)
|
||
|
{
|
||
|
UpTrace("UPConvert: UpGenerateDsAttribs failed %x.", ntStatus);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
} while (FALSE);
|
||
|
|
||
|
// Cleanup
|
||
|
{
|
||
|
if (pvOrig)
|
||
|
{
|
||
|
UpFree(pvOrig);
|
||
|
}
|
||
|
if (pvNew)
|
||
|
{
|
||
|
UpFree(pvNew);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Callback function called by NT5 SAM to free the blob
|
||
|
// returned by UserParmsConvert.
|
||
|
//
|
||
|
VOID
|
||
|
UserParmsFree(
|
||
|
IN PSAM_USERPARMS_ATTRBLOCK pData)
|
||
|
{
|
||
|
SAM_USERPARMS_ATTR * pCur = NULL;
|
||
|
DWORD i, j;
|
||
|
|
||
|
UpTrace("UserParmsFree: Entered. %x", pData);
|
||
|
|
||
|
// If no attributes were given, we're all done
|
||
|
if (pData == NULL)
|
||
|
return;
|
||
|
|
||
|
if (pData->UserParmsAttr)
|
||
|
{
|
||
|
// Loop through all the attributes, freeing them
|
||
|
// as you go.
|
||
|
for (i = 0; i < pData->attCount; i++)
|
||
|
{
|
||
|
// Keep track of the current attribute
|
||
|
pCur = &(pData->UserParmsAttr[i]);
|
||
|
|
||
|
// Free the copied attribute name
|
||
|
if (pCur->AttributeIdentifier.Buffer)
|
||
|
UpFree(pCur->AttributeIdentifier.Buffer);
|
||
|
|
||
|
// Free any associated values as well.
|
||
|
if (pCur->Values)
|
||
|
{
|
||
|
for (j = 0; j < pCur->CountOfValues; j++)
|
||
|
{
|
||
|
// Assume there's only one value since that's
|
||
|
// all we ever set. Free the value
|
||
|
if (pCur->Values[j].value)
|
||
|
UpFree(pCur->Values[j].value);
|
||
|
}
|
||
|
|
||
|
// Free the value structure
|
||
|
UpFree(pCur->Values);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Free the array of attributes
|
||
|
UpFree (pData->UserParmsAttr);
|
||
|
}
|
||
|
|
||
|
// Finally, free the whole structure
|
||
|
UpFree (pData);
|
||
|
}
|