//================================================================================
// Copyright (C) 1997 Microsoft Corporation
// Author: RameshV
// Description: most of the rpc apis are here and some miscellaneous functions too
//  all the functions here go to the DS directly.
//================================================================================

#include    <hdrmacro.h>
#include    <store.h>
#include    <dhcpmsg.h>
#include    <wchar.h>
#include    <dhcpbas.h>
#include    <mm\opt.h>
#include    <mm\optl.h>
#include    <mm\optdefl.h>
#include    <mm\optclass.h>
#include    <mm\classdefl.h>
#include    <mm\bitmask.h>
#include    <mm\reserve.h>
#include    <mm\range.h>
#include    <mm\subnet.h>
#include    <mm\sscope.h>
#include    <mm\oclassdl.h>
#include    <mm\server.h>
#include    <mm\address.h>
#include    <mm\server2.h>
#include    <mm\memfree.h>
#include    <mmreg\regutil.h>
#include    <mmreg\regread.h>
#include    <mmreg\regsave.h>
#include    <dhcpapi.h>
#include    <delete.h>
#include    <st_srvr.h>
#include    <upndown.h>
#include    <dnsapi.h>


//================================================================================
// helper functions
//================================================================================

#include <rpcapi2.h>

//
// Allow Debug prints to ntsd or kd
//

//  #ifdef DBG
//  #define DsAuthPrint(_x_) DsAuthDebugPrintRoutine _x_

//  #else
//  #define DebugPrint(_x_)
//  #endif


extern LPWSTR
CloneString( IN LPWSTR String );

typedef enum {
    LDAP_OPERATOR_EQUAL_TO,
    LDAP_OPERATOR_APPROX_EQUAL_TO,
    LDAP_OPERATOR_LESS_OR_EQUAL_TO,
    LDAP_OPERATOR_GREATER_OR_EQUAL_TO,
    LDAP_OPERATOR_AND,
    LDAP_OPERATOR_OR,
    LDAP_OPERATOR_NOT,
    
    LDAP_OPERATOR_TOTAL
} LDAP_OPERATOR_ENUM;

LPWSTR LdapOperators[ LDAP_OPERATOR_TOTAL ] =
    { L"=", L"~=", L"<=", L">=", L"&", L"|", L"!" };


VOID DsAuthPrintRoutine(
    LPWSTR Format,
    ...
)
{
    WCHAR   buf[2 * 256];
    va_list arg;
    DWORD   len;

    va_start( arg, Format );
    len = wsprintf(buf, L"DSAUTH: ");
    wvsprintf( &buf[ len ], Format, arg );

    va_end( arg );

    OutputDebugString( buf );
} // DsAuthPrint()

//
// This function creates an LDAP query filter string
// with the option type, value and operator.
// 
// Syntax: 
//   primitive : <filter>=(<attribute><operator><value>)
//   complex   : (<operator><filter1><filter2>)
//

LPWSTR
MakeLdapFilter(
    IN   LPWSTR             Operand1,
    IN   LDAP_OPERATOR_ENUM Operator,
    IN   LPWSTR             Operand2,
    IN   BOOL               Primitive
)
{
    LPWSTR Result;
    DWORD  Size;
    DWORD  Len;
    
    CHAR   buffer[100];

    Result = NULL;

    AssertRet((( NULL != Operand1 ) && 
	       ( NULL != Operand2 ) &&
	       (( Operator >= 0 ) && ( Operator < LDAP_OPERATOR_TOTAL ))),
	       NULL );
    
    // calculate the amount of memory needed
    Size = 0;
    Size += ROUND_UP_COUNT( sizeof( L"(" ), ALIGN_WORST );
    Size += ROUND_UP_COUNT( sizeof( L")" ), ALIGN_WORST );
    Size += ROUND_UP_COUNT( wcslen( Operand1 ), ALIGN_WORST );
    Size += ROUND_UP_COUNT( wcslen( Operand2 ), ALIGN_WORST );
    Size += ROUND_UP_COUNT( wcslen( LdapOperators[ Operator ] ), ALIGN_WORST );
    Size += 16; // padding

    Result = MemAlloc( Size * sizeof( WCHAR ));
    if ( NULL == Result ) {
	return NULL;
    }

    if ( Primitive ) {
	Len = wsprintf( Result, 
			L"(%ws%ws%ws)",
			Operand1, LdapOperators[ Operator ], Operand2
			);
    }
    else {
	Len = wsprintf( Result,
			L"(%ws%ws%ws)",
			LdapOperators[ Operator ], Operand1, Operand2
			);
	
    } // else

    AssertRet( Len <= Size, NULL );
    
    return Result;
} // MakeLdapFilter()

//
// Make a LDAP query filter like this:
// (&(objectCategory=dHCPClass)(|(dhcpServer="i<ip>*")(dhcpServer="*s<hostname>*")))
// 

LPWSTR
MakeFilter(
   LPWSTR LookupServerIP,   // Printable IP addr
   LPWSTR HostName
)
{
    LPWSTR Filter1, Filter2, Filter3, Filter4, SearchFilter;
    LPWSTR Buf;
    DWORD Len, CopiedLen;

    AssertRet((( NULL != LookupServerIP ) &&
	       ( NULL != HostName )), NULL );

    Filter1 = NULL;
    Filter2 = NULL;
    Filter3 = NULL;
    Filter4 = NULL;
    SearchFilter = NULL;

    do {

	Len = wcslen( HostName ) + 10 ;
	Buf = MemAlloc( Len * sizeof( WCHAR ));
	if ( NULL == Buf ) {
	    break;
	}

	// make (objectCategory=dHCPClass)
	Filter1 = MakeLdapFilter( ATTRIB_OBJECT_CATEGORY,
				  LDAP_OPERATOR_EQUAL_TO,
				  DEFAULT_DHCP_CLASS_ATTRIB_VALUE,
				  TRUE );

	if ( NULL == Filter1 ) {
	    break;
	}

	// The IP needs to be sent as i<ip>* to match the query
	
	// make (dhcpServers="i<ip>*")
	CopiedLen = _snwprintf( Buf, Len, L"i%ws*", LookupServerIP );
	Require( CopiedLen > 0 );
	Filter2 = MakeLdapFilter( DHCP_ATTRIB_SERVERS,
				  LDAP_OPERATOR_EQUAL_TO, Buf, TRUE );
	if ( NULL == Filter2 ) {
	    break;
	}

	// make (dhcpServers="*s<hostname>*")
	CopiedLen = _snwprintf( Buf, Len, L"*s%ws*", HostName );
	Require( CopiedLen > 0 );
	Filter3 = MakeLdapFilter( DHCP_ATTRIB_SERVERS, 
				  LDAP_OPERATOR_EQUAL_TO, Buf, TRUE );
	
	if ( NULL == Filter3 ) {
	    break;
	}

	// make (|(<ipfilter>)(<hostfilter))
	Filter4 = MakeLdapFilter( Filter2, LDAP_OPERATOR_OR,
				  Filter3, FALSE );

	if ( NULL == Filter4 ) {
	    break;
	}

	// Finally make the filter to be returned
	SearchFilter = MakeLdapFilter( Filter1, LDAP_OPERATOR_AND,
				       Filter4, FALSE );

    } while ( FALSE );
    
    if ( NULL != Buf ) {
	MemFree( Buf );
    }
    if ( NULL != Filter1 ) {
	MemFree( Filter1 );
    }
    if ( NULL != Filter2 ) {
	MemFree( Filter2 );
    }
    if ( NULL != Filter3 ) {
	MemFree( Filter3 );
    }
    if ( NULL != Filter4 ) {
	MemFree( Filter4 );
    }
    
    return SearchFilter;
} // MakeFilter()

//================================================================================
//  This function computes the unique identifier for a client; this is just
//  client subnet + client hw address type + client hw address. note that client
//  hardware address type is hardcoded as HARDWARE_TYPE_10MB_EITHERNET as there
//  is no way in the ui to specify type of reservations..
//  Also, DhcpValidateClient (cltapi.c?) uses the subnet address for validation.
//  Dont remove that.
//================================================================================
DWORD
DhcpMakeClientUID(                 // compute unique identifier for the client
    IN      LPBYTE                 ClientHardwareAddress,
    IN      DWORD                  ClientHardwareAddressLength,
    IN      BYTE                   ClientHardwareAddressType,
    IN      DHCP_IP_ADDRESS        ClientSubnetAddress,
    OUT     LPBYTE                *ClientUID,          // will be allocated by function
    OUT     DWORD                 *ClientUIDLength
)
{
    LPBYTE                         Buffer;
    LPBYTE                         ClientUIDBuffer;
    BYTE                           ClientUIDBufferLength;

    if( NULL == ClientUID || NULL == ClientUIDLength || 0 == ClientHardwareAddressLength )
        return ERROR_INVALID_PARAMETER;

    // see comment about on hardcoded hardware address type
    ClientHardwareAddressType = HARDWARE_TYPE_10MB_EITHERNET;

    ClientUIDBufferLength  =  sizeof(ClientSubnetAddress);
    ClientUIDBufferLength +=  sizeof(ClientHardwareAddressType);
    ClientUIDBufferLength +=  (BYTE)ClientHardwareAddressLength;

    ClientUIDBuffer = MemAlloc( ClientUIDBufferLength );

    if( ClientUIDBuffer == NULL ) {
        *ClientUIDLength = 0;
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    Buffer = ClientUIDBuffer;
    RtlCopyMemory(Buffer,&ClientSubnetAddress,sizeof(ClientSubnetAddress));

    Buffer += sizeof(ClientSubnetAddress);
    RtlCopyMemory(Buffer,&ClientHardwareAddressType,sizeof(ClientHardwareAddressType) );

    Buffer += sizeof(ClientHardwareAddressType);
    RtlCopyMemory(Buffer,ClientHardwareAddress,ClientHardwareAddressLength );

    *ClientUID = ClientUIDBuffer;
    *ClientUIDLength = ClientUIDBufferLength;

    return ERROR_SUCCESS;
}

VOID        static
MemFreeFunc(                                      // free memory
    IN OUT  LPVOID                 Memory
)
{
    MemFree(Memory);
}

//DOC ServerAddAddress should add the new address to the server's attribs
//DOC it should take this opportunity to reconcile the server.
//DOC Currently it does nothing. (at the least it should probably try to
//DOC check if the object exists, and if not create it.)
//DOC
DWORD
ServerAddAddress(                                 // add server and do misc work
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container for server obj
    IN      LPWSTR                 ServerName,    // [DNS?] name of server
    IN      LPWSTR                 ADsPath,       // ADS path of the server
    IN      DWORD                  IpAddress,     // IpAddress to add to server
    IN      DWORD                  State          // state of server
)
{
    return AddServer(hDhcpC, ServerName, ADsPath, IpAddress, State);
}

//DOC CreateServerObject creates the server object in the DS. It takes the
//DOC ServerName parameter and names the object using this.
//DOC The server is created with default values for most attribs.
//DOC Several attribs are just not set.
//DOC This returns ERROR_DDS_UNEXPECTED_ERROR if any DS operation fails.
DWORD
CreateServerObject(                               // create dhcp srvr obj in ds
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container to creat obj in
    IN      LPWSTR                 ServerName     // [DNS?] name of server
)
{
    DWORD                          Err;
    LPWSTR                         ServerCNName;  // container name

    ServerCNName = MakeColumnName(ServerName);    // convert from "name" to "CN=name"
    if( NULL == ServerCNName ) return ERROR_NOT_ENOUGH_MEMORY;

    Err = StoreCreateObject                       // now create the object
    (
        /* hStore               */ hDhcpC,
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* NewObjName           */ ServerCNName,
        /* ...                  */
        /* Identification       */
        ADSTYPE_DN_STRING,         ATTRIB_DN_NAME,          ServerName,
        ADSTYPE_DN_STRING,         ATTRIB_OBJECT_CLASS,     DEFAULT_DHCP_CLASS_ATTRIB_VALUE,

        /* systemMustContain    */
        ADSTYPE_INTEGER,           ATTRIB_DHCP_UNIQUE_KEY,  0,
        ADSTYPE_INTEGER,           ATTRIB_DHCP_TYPE,        DHCP_OBJ_TYPE_SERVER,
        ADSTYPE_DN_STRING,         ATTRIB_DHCP_IDENTIFICATION, DHCP_OBJ_TYPE_SERVER_DESC,
        ADSTYPE_INTEGER,           ATTRIB_DHCP_FLAGS,       0,
        ADSTYPE_INTEGER,           ATTRIB_INSTANCE_TYPE,    DEFAULT_INSTANCE_TYPE_ATTRIB_VALUE,

        /* terminator           */
        ADSTYPE_INVALID
    );
    if( ERROR_ALREADY_EXISTS == Err ) {           // if object exists, ignore this..
        Err = ERROR_SUCCESS;
    }

    MemFree(ServerCNName);
    return Err;
}

//DOC CreateSubnetObject creates the subnet object in the DS by cooking up a
//DOC name that is just a concatenation of the server name and the subnet address.
//DOC The object is set with some default values for most attribs.
//DOC This fn returns ERROR_DDS_UNEXPECTED_ERROR if any DS operation fails.
DWORD
CreateSubnetObject(                               // create dhcp subnet obj in ds
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container obj
    IN      LPWSTR                 SubnetCNName   // subnet name in "CN=xx" fmt
)
{
    DWORD                          Err;
    LPWSTR                         SubnetName;

    SubnetName = SubnetCNName + 3;                // skip the "CN=" part

    Err = StoreCreateObject                       // now create the object
    (
        /* hStore               */ hDhcpC,
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* NewObjName           */ SubnetCNName,
        /* ...                  */
        /* Identification       */
        ADSTYPE_DN_STRING,         ATTRIB_DN_NAME,          SubnetName,
        ADSTYPE_DN_STRING,         ATTRIB_OBJECT_CLASS,     DEFAULT_DHCP_CLASS_ATTRIB_VALUE,

        /* systemMustContain    */
        ADSTYPE_INTEGER,           ATTRIB_DHCP_UNIQUE_KEY,  0,
        ADSTYPE_INTEGER,           ATTRIB_DHCP_TYPE,        DHCP_OBJ_TYPE_SUBNET,
        ADSTYPE_DN_STRING,         ATTRIB_DHCP_IDENTIFICATION, DHCP_OBJ_TYPE_SUBNET_DESC,
        ADSTYPE_INTEGER,           ATTRIB_DHCP_FLAGS,       0,
        ADSTYPE_INTEGER,           ATTRIB_INSTANCE_TYPE,    DEFAULT_INSTANCE_TYPE_ATTRIB_VALUE,

        /* terminator           */
        ADSTYPE_INVALID
    );
    if( ERROR_ALREADY_EXISTS == Err ) {           // if object exists, ignore this..
        Err = ERROR_SUCCESS;
    }

    return Err;
}

//DOC CreateReservationObject creates a reservation object in the DS.
//DOC It just fills in some reasonable information for all the required fields.
//DOC If fails if the object already exists
//DOC
DWORD
CreateReservationObject(                          // create reservation object in DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container obj
    IN      LPWSTR                 ReserveCNName  // reservation name in "CN=X" fmt
)
{
    DWORD                          Err;
    LPWSTR                         ReserveName;

    ReserveName = ReserveCNName+ 3;

    Err = StoreCreateObject                       // now create the object
    (
        /* hStore               */ hDhcpC,
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* NewObjName           */ ReserveCNName,
        /* ...                  */
        /* Identification       */
        ADSTYPE_DN_STRING,         ATTRIB_DN_NAME,          ReserveName,
        ADSTYPE_DN_STRING,         ATTRIB_OBJECT_CLASS,     DEFAULT_DHCP_CLASS_ATTRIB_VALUE,

        /* systemMustContain    */
        ADSTYPE_INTEGER,           ATTRIB_DHCP_UNIQUE_KEY,  0,
        ADSTYPE_INTEGER,           ATTRIB_DHCP_TYPE,        DHCP_OBJ_TYPE_RESERVATION,
        ADSTYPE_DN_STRING,         ATTRIB_DHCP_IDENTIFICATION, DHCP_OBJ_TYPE_RESERVATION_DESC,
        ADSTYPE_INTEGER,           ATTRIB_DHCP_FLAGS,       0,
        ADSTYPE_INTEGER,           ATTRIB_INSTANCE_TYPE,    DEFAULT_INSTANCE_TYPE_ATTRIB_VALUE,

        /* terminator           */
        ADSTYPE_INVALID
    );
    if( ERROR_ALREADY_EXISTS == Err ) {           // if object exists, ignore this..
        Err = ERROR_SUCCESS;
    }

    return Err;
}

//DOC FindCollisions walks through an array of attribs and compares each
//DOC against the parameters to see if there is a collision.. If the parameters
//DOC passed have type RANGE, then an extension of a range is allowed.
//DOC If not, anything is allowed.  In case there is an extension, the Extender
//DOC parameter is filled with the attrib that gets extended..
//DOC This function returns TRUE if there is a collision and FALSE if ok.
BOOL
FindCollisions(                                   // find range vs range collisions
    IN      PARRAY                 Attribs,       // array of PEATTRIB's
    IN      DWORD                  RangeStart,
    IN      DWORD                  RangeEnd,
    IN      DWORD                  RangeType,     // RANGE_TYPE_RANGE || RANGE_TYPE_EXCL
    OUT     PEATTRIB              *Extender       // this attrib needs to be extended
)
{
    DWORD                          Err, Cond;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;

    *Extender = NULL;
    if( (RangeType & RANGE_TYPE_MASK) == RANGE_TYPE_EXCL ) {
        return ERROR_SUCCESS;                     // anything is ok for excl
    }

    for(                                          // walk thru the array
        Err = MemArrayInitLoc(Attribs, &Loc)
        ; ERROR_FILE_NOT_FOUND != Err ;
        Err = MemArrayNextLoc(Attribs, &Loc)
    ) {
        Err = MemArrayGetElement(Attribs, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no range start
            !IS_ADDRESS2_PRESENT(ThisAttrib) ||   // no range end
            !IS_FLAGS1_PRESENT(ThisAttrib) ) {    // range state?
            continue;                             //=  ds inconsistent
        }

        if( IS_FLAGS2_PRESENT(ThisAttrib) &&
            (RANGE_TYPE_MASK & ThisAttrib->Flags2) == RANGE_TYPE_EXCL ) {
            continue;                             // skip exclusions
        }

        if( ThisAttrib->Address2 < ThisAttrib->Address1 ) {
            continue;                             //=  ds inconsistent
        }

        Cond = MemRangeCompare(
            RangeStart,RangeEnd,                  // range X and below is range Y
            ThisAttrib->Address1, ThisAttrib->Address2
        );
        switch(Cond) {                            // make comparisons on 2 ranges
        case X_LESSTHAN_Y_OVERLAP:
        case Y_LESSTHAN_X_OVERLAP:
            if( NULL != *Extender ) return TRUE;  // double extensions not allowed
            *Extender = ThisAttrib;
            break;
        case X_IN_Y:
        case Y_IN_X:
            return TRUE;                          // head on collision is fatal
        }
    }
    return FALSE;
}

BOOL
ServerMatched(
    IN PEATTRIB ThisAttrib,
    IN LPWSTR ServerName,
    IN ULONG IpAddress,
    OUT BOOL *fExactMatch
    )
{
    BOOL fIpMatch, fNameMatch, fWildcardIp;
    
    (*fExactMatch) = FALSE;

    fIpMatch = (ThisAttrib->Address1 == IpAddress);
    if( INADDR_BROADCAST == ThisAttrib->Address1 ||
        INADDR_BROADCAST == IpAddress ) {
        fWildcardIp = TRUE;
    } else {
        fWildcardIp = FALSE;
    }
    
    if( FALSE == fIpMatch ) {
        //
        // If IP Addresses don't match, then check to see if
        // one of the IP addresses is a broadcast address..
        //
        if( !fWildcardIp ) return FALSE;
    }

    fNameMatch = DnsNameCompare_W(ThisAttrib->String1, ServerName);
    if( FALSE == fNameMatch ) {
        //
        // If names don't match _and_ IP's don't match, no match.
        //
        if( FALSE == fIpMatch || fWildcardIp ) return FALSE;
    } else {
        if( FALSE == fIpMatch ) return TRUE;
        
        (*fExactMatch) = TRUE;
    }
    return TRUE;
}

DWORD
GetListOfAllServersMatchingFilter(
    IN OUT LPSTORE_HANDLE hDhcpC,
    IN OUT PARRAY Servers,
    IN     LPWSTR SearchFilter  OPTIONAL
)
{
    DWORD Err, LastErr;
    STORE_HANDLE hContainer;
    LPWSTR Filter;

    AssertRet( ( NULL != hDhcpC ) && ( NULL != Servers ),
	       ERROR_INVALID_PARAMETER );

    Err = StoreSetSearchOneLevel(
        hDhcpC, DDS_RESERVED_DWORD );
    AssertRet( Err == NO_ERROR, Err );

    if ( NULL == SearchFilter ) {
	Filter = DHCP_SEARCH_FILTER;
    }
    else {
	Filter = SearchFilter;
    }
    AssertRet( NULL != Filter, ERROR_INVALID_PARAMETER );

    Err = StoreBeginSearch(
        hDhcpC, DDS_RESERVED_DWORD, Filter );
    AssertRet( Err == NO_ERROR, Err );

    while( TRUE ) {
        Err = StoreSearchGetNext(
            hDhcpC, DDS_RESERVED_DWORD, &hContainer );

        if( ERROR_DS_INVALID_DN_SYNTAX == Err ) {
            //
            // This nasty problem is because of an upgrade issue
            // in DS where some bad-named objects may exist..
            //
            Err = NO_ERROR;
            continue;
        }

        if( NO_ERROR != Err ) break;
        
        Err = DhcpDsGetLists
        (
            /* Reserved             */ DDS_RESERVED_DWORD,
            /* hStore               */ &hContainer,
            /* RecursionDepth       */ 0xFFFFFFFF,
            /* Servers              */ Servers,      // array of PEATTRIB 's
            /* Subnets              */ NULL,
            /* IpAddress            */ NULL,
            /* Mask                 */ NULL,
            /* Ranges               */ NULL,
            /* Sites                */ NULL,
            /* Reservations         */ NULL,
            /* SuperScopes          */ NULL,
            /* OptionDescription    */ NULL,
            /* OptionsLocation      */ NULL,
            /* Options              */ NULL,
            /* Classes              */ NULL
        );

        StoreCleanupHandle( &hContainer, DDS_RESERVED_DWORD );

        if( NO_ERROR != Err ) break;

    }

    if( Err == ERROR_NO_MORE_ITEMS ) Err = NO_ERROR;
    
    LastErr = StoreEndSearch( hDhcpC, DDS_RESERVED_DWORD );
    //Require( LastErr == NO_ERROR );

    return Err;
}

DWORD
DhcpDsAddServerInternal(                          // add a server in DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container for dhcp objects
    IN OUT  LPSTORE_HANDLE         hDhcpRoot,     // dhcp root object handle
    IN      DWORD                  Reserved,      // must be zero, future use
    IN      LPWSTR                 ServerName,    // [DNS?] name of server
    IN      LPWSTR                 ReservedPtr,   // Server location? future use
    IN      DWORD                  IpAddress,     // ip address of server
    IN      DWORD                  State          // currently un-interpreted
)
{
    DWORD                          Err, Err2, unused;
    ARRAY                          Servers;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    EATTRIB                        DummyAttrib;
    BOOL                           fServerExists;
    LPWSTR                         ServerLocation, Tmp;
    DWORD                          ServerLocType;

    if( NULL == hDhcpRoot || NULL == hDhcpC )     // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hDhcpRoot->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Servers);                 // cant fail
    //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of servers
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hDhcpRoot,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ &Servers,      // array of PEATTRIB 's
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    Tmp = NULL; ServerLocation = NULL;
    fServerExists = FALSE;                        // did we find the same servername?
    for(                                          // search list of servers
        Err = MemArrayInitLoc(&Servers, &Loc)     // initialize
        ; ERROR_FILE_NOT_FOUND != Err ;           // until we run out of elts
        Err = MemArrayNextLoc(&Servers, &Loc)     // skip to next element
        ) {
        BOOL fExactMatch = FALSE;
        
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Servers, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no name for this server
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no address for this server
            continue;                             //=  ds inconsistent
        }

        if( ServerMatched(ThisAttrib, ServerName, IpAddress, &fExactMatch ) ) {
            //
            // Server found in the list of servers.  Exact match not allowed.
            //
            if( fExactMatch ) {
                MemArrayFree(&Servers,MemFreeFunc);// free allocated memory
                return ERROR_DDS_SERVER_ALREADY_EXISTS;
            }
                
            fServerExists = TRUE;
            if( IS_ADDRESS1_PRESENT(ThisAttrib) &&
                NULL == ServerLocation ) {        // remember location in DS.
                ServerLocation = ThisAttrib->ADsPath;
                ServerLocType = ThisAttrib->StoreGetType;
            }
        }            
    }


    if( !fServerExists ) {                        // if freshly adding a server, create obj
        WCHAR Buf[sizeof("000.000.000.000")];
        LPWSTR SName;
        
        if( L'\0' != ServerName[0] ) {
            SName = ServerName;
        } else {
            ULONG IpAddr;
            LPSTR IpAddrString;

            IpAddr = htonl(IpAddress);
            IpAddrString = inet_ntoa(*(struct in_addr *)&IpAddr);
            Err = mbstowcs(Buf, IpAddrString, sizeof(Buf)/sizeof(WCHAR));
            if( -1 == Err ) {
                MemArrayFree(&Servers, MemFreeFunc);
                return ERROR_CAN_NOT_COMPLETE;
            }
            SName = Buf;
        }
        
        ServerLocation = Tmp = MakeColumnName(SName);
        ServerLocType = StoreGetChildType;
    }

    NothingPresent(&DummyAttrib);                 // fill in attrib w/ srvr info
    STRING1_PRESENT(&DummyAttrib);                // name
    ADDRESS1_PRESENT(&DummyAttrib);               // ip addr
    FLAGS1_PRESENT(&DummyAttrib);                 // state
    DummyAttrib.String1 = ServerName;
    DummyAttrib.Address1 = IpAddress;
    DummyAttrib.Flags1 = State;
    if( ServerLocation ) {
        ADSPATH_PRESENT(&DummyAttrib);            // ADsPath of location of server object
        STOREGETTYPE_PRESENT(&DummyAttrib);
        DummyAttrib.ADsPath = ServerLocation;
        DummyAttrib.StoreGetType = ServerLocType;
    }

    Err = MemArrayAddElement(&Servers, &DummyAttrib);
    if( ERROR_SUCCESS != Err ) {                  // could not add this to attrib array
        MemArrayFree(&Servers, MemFreeFunc);      // free allocated memory
        if( Tmp ) MemFree(Tmp);                   // if allocate mem for ServerLocation..
        return Err;
    }

    Err = DhcpDsSetLists                          // now set the new attrib list
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hDhcpRoot,
        /* SetParams            */ &unused,
        /* Servers              */ &Servers,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription..  */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescription     */ NULL,
        /* Classes              */ NULL
    );

    Err2 = MemArrayLastLoc(&Servers, &Loc);       // theres atleast 1 elt in array
    //= require ERROR_SUCCESS == Err2
    Err2 = MemArrayDelElement(&Servers, &Loc, &ThisAttrib);
    //= require ERROR_SUCCESS == Err2 && ThisAttrib == &DummyAttrib
    MemArrayFree(&Servers, MemFreeFunc);          // free allocated memory

    if( ERROR_SUCCESS != Err || fServerExists ) {
        if( Tmp ) MemFree(Tmp);                   // if allocated memory for ServerLocation..
        if( ERROR_SUCCESS != Err) return Err;     // check err for DhcpDsSetLists

        //: This wont do if there is a problem...
        if( fServerExists ) return ERROR_SUCCESS; // if server already existed.. not much work needed?
    }
    
    if( Tmp ) MemFree(Tmp);

    return Err;
}

//================================================================================
//  exported functions
//================================================================================

//BeginExport(function)
//DOC DhcpDsAddServer adds a server's entry in the DS.  Note that only the name
//DOC uniquely determines the server. There can be one server with many ip addresses.
//DOC If the server is created first time, a separate object is created for the
//DOC server. : TO DO: The newly added server should also have its data
//DOC updated in the DS uploaded from the server itself if it is still up.
//DOC Note that it takes as parameter the Dhcp root container.
//DOC If the requested address already exists in the DS (maybe to some other
//DOC server), then the function returns ERROR_DDS_SERVER_ALREADY_EXISTS
DWORD
DhcpDsAddServer(                                  // add a server in DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container for dhcp objects
    IN OUT  LPSTORE_HANDLE         hDhcpRoot,     // dhcp root object handle
    IN      DWORD                  Reserved,      // must be zero, future use
    IN      LPWSTR                 ServerName,    // [DNS?] name of server
    IN      LPWSTR                 ReservedPtr,   // Server location? future use
    IN      DWORD                  IpAddress,     // ip address of server
    IN      DWORD                  State          // currently un-interpreted
)   //EndExport(function)
{
    DWORD                          Err, Err2, unused;
    ARRAY                          Servers;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    BOOL                           fServerExists;
    LPWSTR                         ServerLocation, Tmp;
    DWORD                          ServerLocType;
    STORE_HANDLE                   hDhcpServer;

    if( NULL == hDhcpRoot || NULL == hDhcpC )     // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hDhcpRoot->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Servers);                 // cant fail
    //= require ERROR_SUCCESS == Err

    DsAuthPrint(( L"DhcpAddServer() \n" ));

    Err = GetListOfAllServersMatchingFilter( hDhcpC, &Servers,
					     DHCP_SEARCH_FILTER );
    if( ERROR_SUCCESS != Err ) return Err;

    Tmp = NULL; ServerLocation = NULL;
    fServerExists = FALSE;                        // did we find the same servername?
    for(                                          // search list of servers
        Err = MemArrayInitLoc(&Servers, &Loc)     // initialize
        ; ERROR_FILE_NOT_FOUND != Err ;           // until we run out of elts
        Err = MemArrayNextLoc(&Servers, &Loc)     // skip to next element
    ) {
        BOOL fExactMatch = FALSE;
        
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Servers, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no name for this server
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no address for this server
            continue;                             //=  ds inconsistent
        }

        if( ServerMatched(ThisAttrib, ServerName, IpAddress, &fExactMatch ) ) {
            //
            // Server found in the list of servers.  Exact match not allowed.
            //
            if( fExactMatch ) {
                MemArrayFree(&Servers,MemFreeFunc);// free allocated memory
                return ERROR_DDS_SERVER_ALREADY_EXISTS;
            }
                
            fServerExists = TRUE;
            if( IS_ADDRESS1_PRESENT(ThisAttrib) &&
                NULL == ServerLocation ) {        // remember location in DS.
                ServerLocation = ThisAttrib->ADsPath;
                ServerLocType = ThisAttrib->StoreGetType;
            }
        }            
    } // for

    if( !fServerExists ) {                        // if freshly adding a server, create obj
        WCHAR Buf[sizeof("000.000.000.000")];
        LPWSTR SName;
        
        if( L'\0' != ServerName[0] ) {
	    // do not use the name. Use the printable IP addr instead
            SName = ServerName;
        } else {
            ULONG IpAddr;
            LPSTR IpAddrString;

            IpAddr = htonl(IpAddress);
            IpAddrString = inet_ntoa(*(struct in_addr *)&IpAddr);
            Err = mbstowcs(Buf, IpAddrString, sizeof(Buf)/sizeof(WCHAR));
            if( -1 == Err ) {
                MemArrayFree(&Servers, MemFreeFunc);
                return ERROR_CAN_NOT_COMPLETE;
            }
            SName = Buf;
        }
        
        Err = CreateServerObject(
            /*  hDhcpC          */ hDhcpC,
            /*  ServerName      */ SName
        );
        if( ERROR_SUCCESS != Err ) {              // dont add server if obj cant be created
            MemArrayFree(&Servers, MemFreeFunc);  // free allocated memory
            return Err;
        }
        ServerLocation = Tmp = MakeColumnName(SName);
        ServerLocType = StoreGetChildType;
    }

    Err = StoreGetHandle(
        hDhcpC, 0, ServerLocType, ServerLocation, &hDhcpServer );
    if( NO_ERROR == Err ) {
        Err = DhcpDsAddServerInternal(
            hDhcpC, &hDhcpServer, Reserved, ServerName, ReservedPtr,
            IpAddress, State );
        StoreCleanupHandle( &hDhcpServer, 0 );
    }

    MemArrayFree(&Servers, MemFreeFunc);          // free allocated memory

    if( ERROR_SUCCESS != Err || fServerExists ) {
        if( Tmp ) MemFree(Tmp);                   // if allocated memory for ServerLocation..
        if( ERROR_SUCCESS != Err) return Err;     // check err for DhcpDsSetLists

        //: This wont do if there is a problem...
        if( fServerExists ) return ERROR_SUCCESS; // if server already existed.. not much work needed?
    }
    
    Err = ServerAddAddress                        // add the info into the server
    (
        /* hDhcpC               */ hDhcpC,
        /* ServerName           */ ServerName,
        /* ADsPath              */ ServerLocation,
        /* IpAddress            */ IpAddress,
        /* State                */ State
    );
    if( Tmp ) MemFree(Tmp);

    return Err;
}

DWORD
DhcpDsDelServerInternal(                                  // Delete a server from memory
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container for dhcp objects
    IN OUT  LPSTORE_HANDLE         hDhcpRoot,     // dhcp root object handle
    IN      DWORD                  Reserved,      // must be zero, for future use
    IN      LPWSTR                 ServerName,    // which server to delete for
    IN      LPWSTR                 ReservedPtr,   // server location ? future use
    IN      DWORD                  IpAddress      // the IpAddress to delete..
)
{
    DWORD                          Err, Err2, unused;
    ARRAY                          Servers;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib, SavedAttrib;
    BOOL                           fServerExists;
    BOOL                           fServerDeleted;
    LPWSTR                         SName;
    LPWSTR                         ServerLoc = NULL;
    DWORD                          ServerLocType;
    WCHAR                          Buf[sizeof("000.000.000.000")];
        
    if( NULL == hDhcpRoot || NULL == hDhcpC )     // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hDhcpRoot->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Servers);                 // cant fail
    //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of servers
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hDhcpRoot,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ &Servers,      // array of PEATTRIB 's
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    SavedAttrib = NULL;
    fServerExists = fServerDeleted = FALSE;
    for(                                          // search list of servers
        Err = MemArrayInitLoc(&Servers, &Loc)     // initialize
        ; ERROR_FILE_NOT_FOUND != Err ;           // until we run out of elts
        Err = MemArrayNextLoc(&Servers, &Loc)     // skip to next element
    ) {
        BOOL fExactMatch = FALSE;
            
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Servers, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no name for this server
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no address for this server
            continue;                             //=  ds inconsistent
        }

        if( ServerMatched(ThisAttrib, ServerName, IpAddress, &fExactMatch ) ) {
            //
            // Server found. If exact match, remove the element from list.
            //
            if( fExactMatch ) {
                Err2 = MemArrayDelElement(&Servers, &Loc, &ThisAttrib);
                //= ERROR_SUCCESS == Err2 && NULL != ThisAttrib
            }

            if( (NULL == ServerLoc || fExactMatch)
                && IS_ADSPATH_PRESENT(ThisAttrib) ) {
                ServerLocType = ThisAttrib->StoreGetType;
                ServerLoc = ThisAttrib->ADsPath;  // remember this path..
                SavedAttrib = ThisAttrib;         // remember this attrib.. to del later
            } else {                              // this attrib is useless, free it
                if( fExactMatch ) MemFree(ThisAttrib);
            }

            if( fExactMatch ) fServerDeleted = TRUE;
            else fServerExists = TRUE;
        }
    }

    if( !fServerDeleted ) {                       // never found the requested entry..
        MemArrayFree(&Servers, MemFreeFunc);      // free up memory
        return ERROR_DDS_SERVER_DOES_NOT_EXIST;
    }

    Err = DhcpDsSetLists                          // now set the new attrib list
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hDhcpRoot,
        /* SetParams            */ &unused,
        /* Servers              */ &Servers,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription..  */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescription     */ NULL,
        /* Classes              */ NULL
    );
    MemArrayFree(&Servers, MemFreeFunc);          // free allocated memory
    if( ERROR_SUCCESS != Err) {                   // check err for DhcpDsSetLists
        if(SavedAttrib) MemFree(SavedAttrib);
        return Err;
    }

    if( fServerExists ) {                         // still some addr left for this srvr
        if( SavedAttrib ) MemFree(SavedAttrib);
        return ERROR_SUCCESS;
    }

    if( SavedAttrib ) MemFree(SavedAttrib);

    return Err;
}

//BeginExport(function)
//DOC DhcpDsDelServer removes the requested servername-ipaddress pair from the ds.
//DOC If this is the last ip address for the given servername, then the server
//DOC is also removed from memory.  But objects referred by the Server are left in
//DOC the DS as they may also be referred to from else where.  This needs to be
//DOC fixed via references being tagged as direct and symbolic -- one causing deletion
//DOC and other not causing any deletion.  THIS NEEDS TO BE FIXED. 
DWORD
DhcpDsDelServer(                                  // Delete a server from memory
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container for dhcp objects
    IN OUT  LPSTORE_HANDLE         hDhcpRoot,     // dhcp root object handle
    IN      DWORD                  Reserved,      // must be zero, for future use
    IN      LPWSTR                 ServerName,    // which server to delete for
    IN      LPWSTR                 ReservedPtr,   // server location ? future use
    IN      DWORD                  IpAddress      // the IpAddress to delete..
) //EndExport(function)
{
    DWORD Err, LastErr, ReturnError;
    STORE_HANDLE hDhcpServer;
    ARRAY Servers;
    BOOL fEmpty;
    LPWSTR Location;
    LPWSTR IsDhcpRoot = NULL;
    
    MemArrayInit(&Servers);

    Err = StoreSetSearchOneLevel(
        hDhcpC, DDS_RESERVED_DWORD );
    AssertRet( Err == NO_ERROR, Err );

    Err = StoreBeginSearch(
        hDhcpC, DDS_RESERVED_DWORD, DHCP_SEARCH_FILTER );
    AssertRet( Err == NO_ERROR, Err );

    //
    // Look at each dhcp object in container
    //

    ReturnError = ERROR_DDS_SERVER_DOES_NOT_EXIST;

    while( TRUE ) {
        Err = StoreSearchGetNext(
            hDhcpC, DDS_RESERVED_DWORD, &hDhcpServer );

        if( ERROR_DS_INVALID_DN_SYNTAX == Err ) {
            //
            // This nasty problem is because of an upgrade issue
            // in DS where some bad-named objects may exist..
            //
            Err = NO_ERROR;
            continue;
        }

        if( NO_ERROR != Err ) break;

        //
        // Attempt to delete reqd server
        //

        Err = DhcpDsDelServerInternal(
            hDhcpC, &hDhcpServer, Reserved, ServerName,
            ReservedPtr, IpAddress );

        if( ERROR_DDS_SERVER_DOES_NOT_EXIST == Err ) {
            StoreCleanupHandle( &hDhcpServer, DDS_RESERVED_DWORD );
            continue;
        }
        
        if( NO_ERROR != Err ) {
            StoreCleanupHandle( &hDhcpServer, DDS_RESERVED_DWORD );
            break;
        }
        
        ReturnError = NO_ERROR;
        
        //
        // If the above succeeded, then check if the container
        // has no servers defined -- in this case we can delete
        // the container itself
        //
        
        Err = DhcpDsGetLists
        (
            /* Reserved             */ DDS_RESERVED_DWORD,
            /* hStore               */ &hDhcpServer,
            /* RecursionDepth       */ 0xFFFFFFFF,
            /* Servers              */ &Servers,      // array of PEATTRIB 's
            /* Subnets              */ NULL,
            /* IpAddress            */ NULL,
            /* Mask                 */ NULL,
            /* Ranges               */ NULL,
            /* Sites                */ NULL,
            /* Reservations         */ NULL,
            /* SuperScopes          */ NULL,
            /* OptionDescription    */ NULL,
            /* OptionsLocation      */ NULL,
            /* Options              */ NULL,
            /* Classes              */ NULL
            );
        
        if( NO_ERROR != Err ) {
            StoreCleanupHandle( &hDhcpServer, DDS_RESERVED_DWORD );
            break;
        }
        
        fEmpty = (0 == MemArraySize(&Servers));
        MemArrayFree(&Servers, MemFreeFunc);
        
        Location = CloneString(hDhcpServer.Location);
        StoreCleanupHandle( &hDhcpServer, DDS_RESERVED_DWORD );
        
        if( NULL == Location ) {
            Err = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        IsDhcpRoot = wcsstr( Location, DHCP_ROOT_OBJECT_NAME );

        if( fEmpty && ( IsDhcpRoot == NULL ) )
            Err = StoreDeleteThisObject(
                                        hDhcpC, 
                                        DDS_RESERVED_DWORD,
                                        StoreGetAbsoluteOtherServerType, 
                                        Location );


        MemFree( Location );

        if( NO_ERROR != Err ) break;
    }

    if( Err == ERROR_NO_MORE_ITEMS ) Err = NO_ERROR;
    
    LastErr = StoreEndSearch( hDhcpC, DDS_RESERVED_DWORD );
    //Require( LastErr == NO_ERROR );

    if( NO_ERROR == Err ) Err = ReturnError;
    
    return Err;
} // DhcpDsDelServer()

//BeginExport(function)
BOOL
DhcpDsLookupServer(                               // get info about a server
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container for dhcp objects
    IN OUT  LPSTORE_HANDLE         hDhcpRoot,     // dhcp root object handle
    IN      DWORD                  Reserved,      // must be zero, for future use
    IN      LPWSTR                 LookupServerIP,// Server to lookup IP
    IN      LPWSTR                 HostName      // Hostname to lookup
) //EndExport(function)
{
    DWORD                          Err, Err2, Size, Size2, i, N;
    ARRAY                          Servers;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    LPDHCPDS_SERVERS               LocalServers;
    LPBYTE                         Ptr;
    LPWSTR                         SearchFilter;
    STORE_HANDLE                   hContainer;

    if( NULL == hDhcpRoot || NULL == hDhcpC )     // check params
        return FALSE;
    if( NULL == hDhcpRoot->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return FALSE;

    if (( NULL == HostName ) ||
	( NULL == LookupServerIP )) {
	return FALSE;
    }

    SearchFilter = MakeFilter( LookupServerIP, HostName );
    if ( NULL == SearchFilter ) {
	return FALSE;
    }

    DsAuthPrint(( L"hostname = %ws, IP = %ws, Filter = %ws\n",
		  HostName, LookupServerIP, SearchFilter ));

    Err = StoreSetSearchOneLevel( hDhcpC, DDS_RESERVED_DWORD );
    AssertRet( Err == NO_ERROR, Err );

    Err = StoreBeginSearch( hDhcpC, DDS_RESERVED_DWORD, SearchFilter );
    MemFree( SearchFilter );
    AssertRet( Err == NO_ERROR, Err );

    Err = StoreSearchGetNext( hDhcpC, DDS_RESERVED_DWORD, &hContainer );

    StoreEndSearch( hDhcpC, DDS_RESERVED_DWORD );
    return ( NO_ERROR == Err );
} // DhcpDsLookupServer()


//BeginExport(function)
//DOC DhcpDsEnumServers retrieves a bunch of information about each server that
//DOC has an entry in the Servers attribute of the root object. There are no guarantees
//DOC on the order..
//DOC The memory for this is allocated in ONE shot -- so the output can be freed in
//DOC one shot too.
//DOC
DWORD
DhcpDsEnumServers(                                // get info abt all existing servers
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container for dhcp objects
    IN OUT  LPSTORE_HANDLE         hDhcpRoot,     // dhcp root object handle
    IN      DWORD                  Reserved,      // must be zero, for future use
    OUT     LPDHCPDS_SERVERS      *ServersInfo    // array of servers
) //EndExport(function)
{
    DWORD                          Err, Err2, Size, Size2, i, N;
    ARRAY                          Servers;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    LPDHCPDS_SERVERS               LocalServers;
    LPBYTE                         Ptr;
    LPWSTR                         Filter1, Filter2, Filter3;

    if( NULL == hDhcpRoot || NULL == hDhcpC )     // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hDhcpRoot->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( 0 != Reserved || NULL == ServersInfo )
        return ERROR_INVALID_PARAMETER;

    *ServersInfo = NULL; i = N = Size = Size2 = 0;

    Err = MemArrayInit(&Servers);                 // cant fail
    //= require ERROR_SUCCESS == Err

    DsAuthPrint(( L"DhcpDsEnumServers \n" ));

    Err = GetListOfAllServersMatchingFilter( hDhcpC, &Servers,
					     DHCP_SEARCH_FILTER );
    if( ERROR_SUCCESS != Err ) return Err;

    Size = Size2 = 0;
    for(                                          // walk thru list of servers
        Err = MemArrayInitLoc(&Servers, &Loc)     // initialize
        ; ERROR_FILE_NOT_FOUND != Err ;           // until we run out of elts
        Err = MemArrayNextLoc(&Servers, &Loc)     // skip to next element
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Servers, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no name for this server
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no address for this server
            continue;                             //=  ds inconsistent
        }

        Size2 = sizeof(WCHAR)*(1 + wcslen(ThisAttrib->String1));
        if( IS_ADSPATH_PRESENT(ThisAttrib) ) {    // if ADsPath there, account for it
            Size2 += sizeof(WCHAR)*(1 + wcslen(ThisAttrib->ADsPath));
        }

        Size += Size2;                            // keep track of total mem reqd
        i ++;
    }

    Size += ROUND_UP_COUNT(sizeof(DHCPDS_SERVERS), ALIGN_WORST);
    Size += ROUND_UP_COUNT(sizeof(DHCPDS_SERVER)*i, ALIGN_WORST);
    Ptr = MemAlloc(Size);                         // allocate memory
    if( NULL == Ptr ) {
        MemArrayFree(&Servers, MemFreeFunc );     // free allocated memory
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    LocalServers = (LPDHCPDS_SERVERS)Ptr;
    LocalServers->NumElements = i;
    LocalServers->Flags = 0;
    Size = 0;                                     // start from offset 0
    Size += ROUND_UP_COUNT(sizeof(DHCPDS_SERVERS), ALIGN_WORST);
    LocalServers->Servers = (LPDHCPDS_SERVER)(Size + Ptr);
    Size += ROUND_UP_COUNT(sizeof(DHCPDS_SERVER)*i, ALIGN_WORST);

    i = Size2 = 0;
    for(                                          // copy list of servers
        Err = MemArrayInitLoc(&Servers, &Loc)     // initialize
        ; ERROR_FILE_NOT_FOUND != Err ;           // until we run out of elts
        Err = MemArrayNextLoc(&Servers, &Loc)     // skip to next element
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Servers, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no name for this server
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no address for this server
            continue;                             //=  ds inconsistent
        }

        LocalServers->Servers[i].Version =0;      // version is always zero in this build
        LocalServers->Servers[i].State=0;
        LocalServers->Servers[i].ServerName = (LPWSTR)(Size + Ptr);
        wcscpy((LPWSTR)(Size+Ptr), ThisAttrib->String1);
        Size += sizeof(WCHAR)*(1 + wcslen(ThisAttrib->String1));
        LocalServers->Servers[i].ServerAddress = ThisAttrib->Address1;
        if( IS_FLAGS1_PRESENT(ThisAttrib) ) {     // State present
            LocalServers->Servers[i].Flags = ThisAttrib->Flags1;
        } else {
            LocalServers->Servers[i].Flags = 0;   // if no flags present, use zero
        }
        if( IS_ADSPATH_PRESENT(ThisAttrib) ) {    // if ADsPath there, copy it too
            LocalServers->Servers[i].DsLocType = ThisAttrib->StoreGetType;
            LocalServers->Servers[i].DsLocation = (LPWSTR)(Size + Ptr);
            wcscpy((LPWSTR)(Size + Ptr), ThisAttrib->ADsPath);
            Size += sizeof(WCHAR)*(1 + wcslen(ThisAttrib->ADsPath));
        } else {                                  // no ADsPath present
            LocalServers->Servers[i].DsLocType = 0;
            LocalServers->Servers[i].DsLocation = NULL;
        }
        i ++;
    }

    *ServersInfo = LocalServers;
    MemArrayFree(&Servers, MemFreeFunc );         // free allocated memory
    return ERROR_SUCCESS;
} // DhcpDsEnumServers()

//BeginExport(function)
//DOC DhcpDsSetSScope modifies the superscope that a subnet belongs to.
//DOC The function tries to set the superscope of the subnet referred by
//DOC address IpAddress to SScopeName.  It does not matter if the superscope
//DOC by that name does not exist, it is automatically created.
//DOC If the subnet already had a superscope, then the behaviour depends on
//DOC the flag ChangeSScope.  If this is TRUE, it sets the new superscopes.
//DOC If the flag is FALSE, it returns ERROR_DDS_SUBNET_HAS_DIFF_SSCOPE.
//DOC This flag is ignored if the subnet does not have a superscope already.
//DOC If SScopeName is NULL, the function removes the subnet from any superscope
//DOC if it belonged to one before.
//DOC If the specified subnet does not exist, it returns ERROR_DDS_SUBNET_NOT_PRESENT.
DWORD
DhcpDsSetSScope(                                  // change superscope of subnet
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container where dhcp objects are stored
    IN OUT  LPSTORE_HANDLE         hServer,       // the server object referred
    IN      DWORD                  Reserved,      // must be zero, for future use
    IN      DWORD                  IpAddress,     // subnet address to use
    IN      LPWSTR                 SScopeName,    // sscope it must now be in
    IN      BOOL                   ChangeSScope   // if it already has a SScope, change it?
)   //EndExport(function)
{
    DWORD                          Err, unused;
    ARRAY                          Subnets;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    BOOL                           SubnetPresent;

    if( 0 != Reserved )                           // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hDhcpC || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer || NULL == hServer->ADSIHandle )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Subnets);                 //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // fetch subnet array
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;        // shouldn't really fail

    SubnetPresent = FALSE;
    for(                                          // search for specified subnet
        Err = MemArrayInitLoc(&Subnets, &Loc)     // init
        ; ERROR_FILE_NOT_FOUND != Err ;           // 'til v run out of elts
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next elt
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no subnet name
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no subnet address
            continue;                             //=  ds inconsistent
        }

        if( ThisAttrib->Address1 != IpAddress ) { // not this subnet we're looking for
            continue;
        }

        SubnetPresent = TRUE;                     // found the subnet we're intersted in
        break;
    }

    if( !SubnetPresent ) {                        // did not even find the subnet?
        MemArrayFree(&Subnets, MemFreeFunc);      // free up memory taken
        return ERROR_DDS_SUBNET_NOT_PRESENT;
    }

    if( NULL == SScopeName ) {                    // we're trying to remove from sscope
        if( !IS_STRING3_PRESENT(ThisAttrib) ) {   // does not belong to any sscope ?
            MemArrayFree(&Subnets, MemFreeFunc);
            return ERROR_SUCCESS;                 // return as no change reqd
        }
        STRING3_ABSENT(ThisAttrib);               // remove SScope..
    } else {
        if( IS_STRING3_PRESENT(ThisAttrib) ) {    // sscope present.. trying to change it
            if( FALSE == ChangeSScope ) {         // we were not asked to do this
                MemArrayFree(&Subnets, MemFreeFunc);
                return ERROR_DDS_SUBNET_HAS_DIFF_SSCOPE;
            }
        }

        STRING3_PRESENT(ThisAttrib);
        ThisAttrib->String3 = SScopeName;         // set the new SScope for this
    }

    Err = DhcpDsSetLists                          // now write back the new info onto the DS
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* SetParams            */ &unused,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescripti..    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescriptio..    */ NULL,
        /* Classes              */ NULL
    );

    MemArrayFree(&Subnets, MemFreeFunc);

    return Err;
}

//BeginExport(function)
//DOC DhcpDsDelSScope deletes the superscope and removes all elements
//DOC that belong to that superscope in one shot. There is no error if the
//DOC superscope does not exist.
DWORD
DhcpDsDelSScope(                                  // delete superscope off DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container where dhcp objects are stored
    IN OUT  LPSTORE_HANDLE         hServer,       // the server object referred
    IN      DWORD                  Reserved,      // must be zero, for future use
    IN      LPWSTR                 SScopeName     // sscope to delete
)   //EndExport(function)
{
    DWORD                          Err, unused;
    ARRAY                          Subnets;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    BOOL                           AnythingChanged;

    if( 0 != Reserved || NULL == SScopeName )     // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hDhcpC || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer || NULL == hServer->ADSIHandle )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Subnets);                 //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // fetch subnet array
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;        // shouldn't really fail

    AnythingChanged = FALSE;
    for(                                          // search for specified SScope
        Err = MemArrayInitLoc(&Subnets, &Loc)     // init
        ; ERROR_FILE_NOT_FOUND != Err ;           // 'til v run out of elts
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next elt
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no subnet name
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no subnet address
            continue;                             //=  ds inconsistent
        }

        if( !IS_STRING3_PRESENT(ThisAttrib) ) {   // this subnet does not have a sscope anyways
            continue;
        }

        if( 0 != wcscmp(ThisAttrib->String3, SScopeName) ) {
            continue;                             // not the same superscope
        }

        STRING3_ABSENT(ThisAttrib);               // kill the superscope
        AnythingChanged = TRUE;
    }

    if( !AnythingChanged ) {
        Err = ERROR_SUCCESS;                      // nothing more to do now..
    } else {
        Err = DhcpDsSetLists                      // now write back the new info onto the DS
        (
            /* Reserved         */ DDS_RESERVED_DWORD,
            /* hStore           */ hServer,
            /* SetParams        */ &unused,
            /* Servers          */ NULL,
            /* Subnets          */ &Subnets,
            /* IpAddress        */ NULL,
            /* Mask             */ NULL,
            /* Ranges           */ NULL,
            /* Sites            */ NULL,
            /* Reservations     */ NULL,
            /* SuperScopes      */ NULL,
            /* OptionDescripti..*/ NULL,
            /* OptionsLocation  */ NULL,
            /* Options          */ NULL,
            /* ClassDescriptio..*/ NULL,
            /* Classes          */ NULL
        );
    }

    MemArrayFree(&Subnets, MemFreeFunc);
    return Err;
}

//BeginExport(function)
//DOC DhcpDsGetSScopeInfo retrieves the SuperScope table for the server of interest.
//DOC The table itself is allocated in one blob, so it can be freed lateron.
//DOC The SuperScopeNumber is garbage (always zero) and the NextInSuperScope reflects
//DOC the order in the DS which may/maynot be the same in the DHCP server.
//DOC SuperScopeName is NULL in for subnets that done have a sscope.
DWORD
DhcpDsGetSScopeInfo(                              // get superscope table from ds
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // container where dhcp objects are stored
    IN OUT  LPSTORE_HANDLE         hServer,       // the server object referred
    IN      DWORD                  Reserved,      // must be zero, for future use
    OUT     LPDHCP_SUPER_SCOPE_TABLE *SScopeTbl   // allocated by this func in one blob
)   //EndExport(function)
{
    DWORD                          Err, unused, Size, Size2, i;
    DWORD                          Index, nSubnets;
    ARRAY                          Subnets;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    LPDHCP_SUPER_SCOPE_TABLE       LocalTbl;
    LPBYTE                         Ptr;

    if( 0 != Reserved )                           // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hDhcpC || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer || NULL == hServer->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == SScopeTbl ) return ERROR_INVALID_PARAMETER;

    *SScopeTbl = NULL;

    Err = MemArrayInit(&Subnets);                 //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // fetch subnet array
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;        // shouldn't really fail

    Size = Size2 = i = 0;
    for(                                          // search for specified SScope
        Err = MemArrayInitLoc(&Subnets, &Loc)     // init
        ; ERROR_FILE_NOT_FOUND != Err ;           // 'til v run out of elts
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next elt
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no subnet name
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no subnet address
            continue;                             //=  ds inconsistent
        }

        if( IS_STRING3_PRESENT(ThisAttrib) ) {    // make space for sscope name
            Size += sizeof(WCHAR)*(1+wcslen(ThisAttrib->String3));
        }
        i ++;                                     // keep right count of # of subnets
    }

    Size += ROUND_UP_COUNT(sizeof(DHCP_SUPER_SCOPE_TABLE),ALIGN_WORST);
    Size += ROUND_UP_COUNT(i*sizeof(DHCP_SUPER_SCOPE_TABLE_ENTRY), ALIGN_WORST);

    Ptr = MemAlloc(Size);                         // allocate the blob
    if( NULL == Ptr ) {
        MemArrayFree(&Subnets, MemFreeFunc);
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    LocalTbl = (LPDHCP_SUPER_SCOPE_TABLE)Ptr;
    Size = ROUND_UP_COUNT(sizeof(DHCP_SUPER_SCOPE_TABLE),ALIGN_WORST);
    LocalTbl->cEntries = i;
    if( i ) LocalTbl->pEntries = (LPDHCP_SUPER_SCOPE_TABLE_ENTRY)(Size+Ptr);
    else LocalTbl->pEntries = NULL;
    Size += ROUND_UP_COUNT(i*sizeof(DHCP_SUPER_SCOPE_TABLE_ENTRY), ALIGN_WORST);

    i = 0;
    for(                                          // search for specified SScope
        Err = MemArrayInitLoc(&Subnets, &Loc)     // init
        ; ERROR_FILE_NOT_FOUND != Err ;           // 'til v run out of elts
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next elt
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_STRING1_PRESENT(ThisAttrib) ||    // no subnet name
            !IS_ADDRESS1_PRESENT(ThisAttrib) ) {  // no subnet address
            continue;                             //=  ds inconsistent
        }

        LocalTbl->pEntries[i].SubnetAddress = ThisAttrib->Address1;
        LocalTbl->pEntries[i].SuperScopeNumber = 0;
        LocalTbl->pEntries[i].NextInSuperScope = 0;
        LocalTbl->pEntries[i].SuperScopeName = NULL;

        if( !IS_STRING3_PRESENT(ThisAttrib) ) {   // no sscope, nothin to do
            i ++;
            continue;
        }

        LocalTbl->pEntries[i].SuperScopeName = (LPWSTR)(Size + Ptr);
        wcscpy((LPWSTR)(Size+Ptr), ThisAttrib->String3);
        Size += sizeof(WCHAR)*(1+wcslen(ThisAttrib->String3));
        i ++;                                     // keep right count of # of subnets
    }

    MemArrayFree(&Subnets, MemFreeFunc);

    nSubnets = i;
    for( Index = 0; Index < nSubnets ; Index ++){ // calculate for each Index, next value
        if( NULL == LocalTbl->pEntries[Index].SuperScopeName)
            continue;                             // skip subnets that dont have sscope

        LocalTbl->pEntries[Index].NextInSuperScope = Index;

        for( i = 0; i < Index ; i ++ ) {          // first set it to just prev subnet
            if( NULL == LocalTbl->pEntries[i].SuperScopeName)
                continue;
            if( 0 == wcscmp(
                LocalTbl->pEntries[Index].SuperScopeName,
                LocalTbl->pEntries[i].SuperScopeName)
            ) {                                   // both subnets have same superscope
                LocalTbl->pEntries[Index].NextInSuperScope = i;
                // set next as last match in array before position Index
            }
        }

        for( i = Index + 1; i < nSubnets; i ++ ) {// check to see if any real next exists
            if( NULL == LocalTbl->pEntries[i].SuperScopeName)
                continue;
            if( 0 == wcscmp(
                LocalTbl->pEntries[Index].SuperScopeName,
                LocalTbl->pEntries[i].SuperScopeName)
            ) {                                   // both subnets have same superscope
                LocalTbl->pEntries[Index].NextInSuperScope = i;
                break;
            }
        }
    }

    *SScopeTbl = LocalTbl;                        // done.
    return ERROR_SUCCESS;
}

//BeginExport(function)
//DOC DhcpDsServerAddSubnet tries to add a subnet to a given server. Each subnet
//DOC address has to be unique, but the other parameters dont have to.
//DOC The subnet address being added should not belong to any other subnet.
//DOC In this case it returns error ERROR_DDS_SUBNET_EXISTS
DWORD
DhcpDsServerAddSubnet(                            // create a new subnet
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      LPDHCP_SUBNET_INFO     Info           // info on new subnet to create
)   //EndExport(function)
{
    DWORD                          Err, Err2, unused, i;
    DWORD                          Index, nSubnets;
    ARRAY                          Subnets;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    EATTRIB                        Dummy;
    LPWSTR                         SubnetObjName;

    if( 0 != Reserved )                           // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hDhcpC || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer || NULL == hServer->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == Info ||  NULL == ServerName )
        return ERROR_INVALID_PARAMETER;
    if( Info->SubnetAddress != (Info->SubnetAddress & Info->SubnetMask ) )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Subnets);                 //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // fetch subnet array
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;        // shouldn't really fail

    for(                                          // search for specified SScope
        Err = MemArrayInitLoc(&Subnets, &Loc)     // init
        ; ERROR_FILE_NOT_FOUND != Err ;           // 'til v run out of elts
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next elt
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no subnet address
            !IS_ADDRESS2_PRESENT(ThisAttrib) ) {  // no subnet mask
            continue;                             //=  ds inconsistent
        }

        if( (Info->SubnetAddress & ThisAttrib->Address2 ) == ThisAttrib->Address1 ) {
            return ERROR_DDS_SUBNET_EXISTS;       // Info belongs to this subnet
        }

        if( Info->SubnetAddress == (ThisAttrib->Address1 & Info->SubnetMask) ) {
            return ERROR_DDS_SUBNET_EXISTS;       // Info subsumes some other subnet
        }
    }

    SubnetObjName = MakeSubnetLocation(ServerName, Info->SubnetAddress);
    if( NULL == SubnetObjName ) {                 // not enough memory?
        MemArrayFree(&Subnets, MemFreeFunc);
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    Err = CreateSubnetObject                      // try creating the subnet obj 1st
    (
        /* hDhcpC               */ hDhcpC,        // create obj here..
        /* SubnetName           */ SubnetObjName
    );
    if( ERROR_SUCCESS != Err ) {                  // could not create obj, dont proceed
        MemArrayFree(&Subnets, MemFreeFunc);
        MemFree(SubnetObjName);
        return Err;
    }

    NothingPresent(&Dummy);                       // prepare info for new subnet
    ADDRESS1_PRESENT(&Dummy);                     // subnet address
    Dummy.Address1 = Info->SubnetAddress;
    ADDRESS2_PRESENT(&Dummy);                     // subnet mask
    Dummy.Address2 = Info->SubnetMask;
    FLAGS1_PRESENT(&Dummy);                       // subnet state
    Dummy.Flags1 = Info->SubnetState;
    if( Info->SubnetName ) {                      // subnet name
        STRING1_PRESENT(&Dummy);
        Dummy.String1 = Info->SubnetName;
    }
    if( Info->SubnetComment ) {                   // subnet comment
        STRING2_PRESENT(&Dummy);
        Dummy.String2 = Info->SubnetComment;
    }
    ADSPATH_PRESENT(&Dummy);                      // subnet obj location
    STOREGETTYPE_PRESENT(&Dummy);
    Dummy.ADsPath = SubnetObjName;
    Dummy.StoreGetType = StoreGetChildType;

    Err = MemArrayAddElement(&Subnets, &Dummy);   // add new attrib at end

    if( ERROR_SUCCESS != Err ) {                  // add failed for some reason
        MemFree(SubnetObjName);
        MemArrayFree(&Subnets, MemFreeFunc);      // cleanup any mem used
        return Err;
    }

    Err = DhcpDsSetLists                          // write back new info onto DS
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* SetParams            */ &unused,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescripti..    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescriptio..    */ NULL,
        /* Classes              */ NULL
    );

    MemFree(SubnetObjName);
    Err2 = MemArrayLastLoc(&Subnets, &Loc);       //= require ERROR_SUCCESS == Err
    Err2 = MemArrayDelElement(&Subnets, &Loc, &ThisAttrib);

    MemArrayFree(&Subnets, MemFreeFunc);

    return Err;
}

//BeginExport(function)
//DOC DhcpDsServerDelSubnet removes a subnet from a given server. It removes not
//DOC just the subnet, but also all dependent objects like reservations etc.
//DOC This fn returns ERROR_DDS_SUBNET_NOT_PRESENT if the subnet is not found.
DWORD
DhcpDsServerDelSubnet(                            // Delete the subnet
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create obj
    IN      LPSTORE_HANDLE         hServer,       // server obj
    IN      DWORD                  Reserved,      // for future use, must be zero
    IN      LPWSTR                 ServerName,    // name of dhcp server 2 del off
    IN      DWORD                  IpAddress      // ip address of subnet to del
)   //EndExport(function)
{
    DWORD                          Err, Err2, unused;
    ARRAY                          Subnets;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    BOOL                           fSubnetExists;
    BOOL                           fSubnetDeleted;
    LPWSTR                         SubnetCNName;
    LPWSTR                         SubnetLoc;
    DWORD                          SubnetLocType;

    if( NULL == hServer || NULL == hDhcpC )       // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Subnets);                 //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of subnets
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,      // array of PEATTRIB 's
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    fSubnetExists = fSubnetDeleted = FALSE;
    for(                                          // search list of subnets
        Err = MemArrayInitLoc(&Subnets, &Loc)     // initialize
        ; ERROR_FILE_NOT_FOUND != Err ;           // until we run out of elts
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next element
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no subnet address
            !IS_ADDRESS2_PRESENT(ThisAttrib) ) {  // no subnet mask
            continue;                             //=  ds inconsistent
        }

        if( ThisAttrib->Address1 == IpAddress ) { // matching address?
            fSubnetExists = TRUE;
            Err2 = MemArrayDelElement(&Subnets, &Loc, &ThisAttrib);
            //= ERROR_SUCCESS == Err2 && NULL != ThisAttrib
            break;
        }
    }

    if( !fSubnetExists ) {                        // no matching subnet found
        MemArrayFree(&Subnets, MemFreeFunc);
        return ERROR_DDS_SUBNET_NOT_PRESENT;
    }

    Err = DhcpDsSetLists                          // now set the new attrib list
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* SetParams            */ &unused,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription..  */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescription     */ NULL,
        /* Classes              */ NULL
    );
    MemArrayFree(&Subnets, MemFreeFunc);          // free allocated memory
    if( ERROR_SUCCESS != Err) {                   // check err for DhcpDsSetLists
        MemFree(ThisAttrib);
        return Err;
    }

    if( IS_ADSPATH_PRESENT(ThisAttrib) ) {        // remember the location to delete
        SubnetLocType = ThisAttrib->StoreGetType;
        SubnetLoc = ThisAttrib->ADsPath;
    } else {
        SubnetLoc = NULL;
    }

    if( NULL == SubnetLoc ) {                     // Dont know location
        SubnetCNName = MakeSubnetLocation(ServerName,IpAddress);
        SubnetLoc = SubnetCNName;                 // set name for subnet obj
        SubnetLocType = StoreGetChildType;        // assume located in DhcpC container
    } else {
        SubnetCNName = NULL;                      // Did not allocate subnet name
    }

    if( NULL == SubnetLoc ) {                     // MakeSubnetLocation failed
        Err = ERROR_NOT_ENOUGH_MEMORY;
    } else {                                      // lets try to delete the subnet
        Err = ServerDeleteSubnet                  // delete the dhcp subnet object
        (
            /* hDhcpC              */ hDhcpC,
            /* ServerName          */ ServerName,
            /* hServer             */ hServer,
            /* ADsPath             */ SubnetLoc,
            /* StoreGetType        */ SubnetLocType
        );
    }

    if( SubnetCNName ) MemFree(SubnetCNName);     // if we allocated mem, free it
    MemFree(ThisAttrib);                          // lonely attrib needs to be freed

    return Err;
}

//BeginExport(function)
//DOC DhcpDsServerModifySubnet changes the subnet name, comment, state, mask
//DOC fields of the subnet.  Actually, currently, the mask should probably not
//DOC be changed, as no checks are performed in this case.  The address cannot
//DOC be changed.. If the subnet is not present, the error returned is
//DOC ERROR_DDS_SUBNET_NOT_PRESENT
DWORD
DhcpDsServerModifySubnet(                         // modify subnet info
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      LPDHCP_SUBNET_INFO     Info           // info on new subnet to create
)   //EndExport(function)
{
    DWORD                          Err, Err2, unused;
    ARRAY                          Subnets;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    BOOL                           fSubnetExists, fSubnetDeleted;

    if( NULL == hDhcpC )                          // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;
    if( NULL == Info )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Subnets);                 //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of subnets
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,      // array of PEATTRIB 's
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    fSubnetExists = fSubnetDeleted = FALSE;
    for(                                          // search list of subnets
        Err = MemArrayInitLoc(&Subnets, &Loc)     // initialize
        ; ERROR_FILE_NOT_FOUND != Err ;           // until we run out of elts
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next element
    ) {
        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no subnet address
            !IS_ADDRESS2_PRESENT(ThisAttrib) ) {  // no subnet mask
            continue;                             //=  ds inconsistent
        }

        if( ThisAttrib->Address1 == Info->SubnetAddress ) {
            fSubnetExists = TRUE;                 // matching address?
            break;
        }
    }

    if( !fSubnetExists ) {                        // no matching subnet found
        MemArrayFree(&Subnets, MemFreeFunc);
        return ERROR_DDS_SUBNET_NOT_PRESENT;
    }

    ThisAttrib->Address2 = Info->SubnetMask;      // alter information
    FLAGS1_PRESENT(ThisAttrib);
    ThisAttrib->Flags1 = Info->SubnetState;
    if( NULL == Info->SubnetName ) {
        STRING1_ABSENT(ThisAttrib);
    } else {
        STRING1_PRESENT(ThisAttrib);
        ThisAttrib->String1 = Info->SubnetName;
    }
    if( NULL == Info->SubnetComment ) {
        STRING2_ABSENT(ThisAttrib);
    } else {
        STRING2_PRESENT(ThisAttrib);
        ThisAttrib->String2 = Info->SubnetComment;
    }

    Err = DhcpDsSetLists                          // now set the new attrib list
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* SetParams            */ &unused,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription..  */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescription     */ NULL,
        /* Classes              */ NULL
    );
    MemArrayFree(&Subnets, MemFreeFunc);          // free allocated memory

    return Err;
}

//BeginExport(function)
//DOC DhcpDsServerEnumSubnets is not yet implemented.
DWORD
DhcpDsServerEnumSubnets(                          // get subnet list
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    OUT     LPDHCP_IP_ARRAY       *SubnetsArray   // give array of subnets
)   //EndExport(function)
{
    LPDHCP_IP_ARRAY                LocalSubnetArray;
    DWORD                          Err, Err2, unused, Size;
    ARRAY                          Subnets;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;

    if( NULL == hDhcpC )                          // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;
    if( NULL == SubnetsArray )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Subnets);                 //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of subnets
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,      // array of PEATTRIB 's
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    LocalSubnetArray = MemAlloc(sizeof(DHCP_IP_ARRAY));
    if( NULL == LocalSubnetArray ) {
        MemArrayFree(&Subnets, MemFreeFunc);
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    LocalSubnetArray->NumElements = 0;
    LocalSubnetArray->Elements = NULL;
    Size = sizeof(DHCP_IP_ADDRESS)*MemArraySize(&Subnets);
    if( Size ) LocalSubnetArray->Elements = MemAlloc(Size);

    for(                                          // accumulate the subnets
        Err = MemArrayInitLoc(&Subnets, &Loc)     // for each subnet
        ; ERROR_FILE_NOT_FOUND != Err ;           // until there are no more
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next
    ) {
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no subnet address
            !IS_ADDRESS2_PRESENT(ThisAttrib) ) {  // no subnet mask
            continue;                             //=  ds inconsistent
        }

        LocalSubnetArray->Elements[LocalSubnetArray->NumElements++] = ThisAttrib->Address1;
    }

    MemArrayFree(&Subnets, MemFreeFunc);
    *SubnetsArray = LocalSubnetArray;
    return ERROR_SUCCESS;

}

//BeginExport(function)
//DOC DhcpDsServerGetSubnetInfo is not yet implemented.
DWORD
DhcpDsServerGetSubnetInfo(                        // get info on subnet
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      DHCP_IP_ADDRESS        SubnetAddress, // address of subnet to get info for
    OUT     LPDHCP_SUBNET_INFO    *SubnetInfo     // o/p: allocated info
)   //EndExport(function)
{
    DWORD                          Err, Err2, unused;
    ARRAY                          Subnets;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    LPDHCP_SUBNET_INFO             Info;
    BOOL                           fSubnetExists;

    if( NULL == hDhcpC )                          // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;
    if( NULL == SubnetInfo )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Subnets);                 //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of subnets
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hServer,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ &Subnets,      // array of PEATTRIB 's
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    fSubnetExists = FALSE;
    for(                                          // accumulate the subnets
        Err = MemArrayInitLoc(&Subnets, &Loc)     // for each subnet
        ; ERROR_FILE_NOT_FOUND != Err ;           // until there are no more
        Err = MemArrayNextLoc(&Subnets, &Loc)     // skip to next
    ) {
        Err = MemArrayGetElement(&Subnets, &Loc, &ThisAttrib);

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no subnet address
            !IS_ADDRESS2_PRESENT(ThisAttrib) ) {  // no subnet mask
            continue;                             //=  ds inconsistent
        }

        if( ThisAttrib->Address1 == SubnetAddress ) {
            fSubnetExists = TRUE;                 // found the required subnet
            break;
        }
    }

    if( !fSubnetExists ) {                        // no subnet matching address
        MemArrayFree(&Subnets, MemFreeFunc);
        return ERROR_DDS_SUBNET_NOT_PRESENT;
    }

    Info = MemAlloc(sizeof(LPDHCP_SUBNET_INFO));
    if( NULL == Info) {
        MemArrayFree(&Subnets, MemFreeFunc);
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    Info->SubnetAddress = ThisAttrib->Address1;   // subnet address
    Info->SubnetMask = ThisAttrib->Address2;      // subnet mask

    if( !IS_STRING1_PRESENT(ThisAttrib) ) {       // subnet name?
        Info->SubnetName = NULL;
    } else {
        Info->SubnetName = MemAlloc(sizeof(WCHAR) * (1+wcslen(ThisAttrib->String1)));
        if( NULL != Info->SubnetName ) {
            wcscpy(Info->SubnetName, ThisAttrib->String1);
        }
    }

    if( !IS_STRING2_PRESENT(ThisAttrib) ) {       // subnet comment?
        Info->SubnetComment = NULL;               // no subnet comment
    } else {
        Info->SubnetComment = MemAlloc(sizeof(WCHAR) * (1+wcslen(ThisAttrib->String2)));
        if( NULL != Info->SubnetComment ) {
            wcscpy(Info->SubnetComment, ThisAttrib->String2);
        }
    }

    if( !IS_FLAGS1_PRESENT(ThisAttrib) ) {        // subnet state information
        Info->SubnetState = DhcpSubnetEnabled;
    } else {
        Info->SubnetState = ThisAttrib->Flags1;
    }

    MemArrayFree(&Subnets, MemFreeFunc);          // clear up memory
    *SubnetInfo = Info;

    Info->PrimaryHost.IpAddress = 0;              // : unsupported fields..
    Info->PrimaryHost.NetBiosName = NULL;
    Info->PrimaryHost.HostName = NULL;

    return ERROR_SUCCESS;
}

//BeginExport(function)
//DOC DhcpDsSubnetAddRangeOrExcl adds a range/excl to an existing subnet.
//DOC If there is a collision with between ranges, then the error code returned
//DOC is ERROR_DDS_POSSIBLE_RANGE_CONFLICT. Note that no checks are made for
//DOC exclusions though.  Also, if a RANGE is extended via this routine, then
//DOC there is no error returned, but a limitation currently is that multiple
//DOC ranges (two only right) cannot be simultaneously extended.
//DOC BUBGUG: The basic check of whether the range belongs in the subnet is
//DOC not done..
DWORD
DhcpDsSubnetAddRangeOrExcl(                       // add a range or exclusion
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN OUT  LPSTORE_HANDLE         hSubnet,       // subnet object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      DWORD                  Start,         // start addr in range
    IN      DWORD                  End,           // end addr in range
    IN      BOOL                   RangeOrExcl    // TRUE ==> Range,FALSE ==> Excl
)   //EndExport(function)
{
    DWORD                          Err, Err2, Type, unused;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    EATTRIB                        Dummy;
    ARRAY                          Ranges;

    if( NULL == hDhcpC || NULL == hServer )       // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == hSubnet || NULL == hSubnet->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;
    if( Start > End ) return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Ranges);                  //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of ranges frm ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ &Ranges,       // array of PEATTRIB 's
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    Type = (RangeOrExcl? RANGE_TYPE_RANGE: RANGE_TYPE_EXCL);
    ThisAttrib = NULL;
    if( FindCollisions(&Ranges,Start,End,Type, &ThisAttrib) ) {
        MemArrayFree(&Ranges, MemFreeFunc);
        return ERROR_DDS_POSSIBLE_RANGE_CONFLICT; // hit a range conflict!
    }

    if( NULL != ThisAttrib ) {                    // this is a collision case
        if( Start == ThisAttrib->Address2 ) {     // this is the collapse point
            ThisAttrib->Address2 = End;
        } else {
            ThisAttrib->Address1 = Start;
        }
    } else {                                      // not a collision
        NothingPresent(&Dummy);                   // create a new range
        ADDRESS1_PRESENT(&Dummy);                 // range start
        Dummy.Address1 = Start;
        ADDRESS2_PRESENT(&Dummy);                 // range end
        Dummy.Address2 = End;
        FLAGS1_PRESENT(&Dummy);
        Dummy.Flags1 = 0;
        FLAGS2_PRESENT(&Dummy);                   // range type or excl type?
        Dummy.Flags2 = Type;

        Err = MemArrayAddElement(&Ranges, &Dummy);
        if( ERROR_SUCCESS != Err ) {              // could not create new lists
            MemArrayFree(&Ranges, MemFreeFunc);
            return Err;
        }
    }

    Err = DhcpDsSetLists                          // write back new list to ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* SetParams            */ &unused,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ &Ranges,
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription..  */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescription     */ NULL,
        /* Classes              */ NULL
    );

    if( NULL == ThisAttrib ) {                    // in case we added the new range
        Err2 = MemArrayLastLoc(&Ranges, &Loc);    // try to delete frm mem the new elt
        Err2 = MemArrayDelElement(&Ranges, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && ThisAttrib == &Dummy
    }

    MemArrayFree(&Ranges, MemFreeFunc);
    return Err;                                   // DhcpDsSetLists's ret code
}

//BeginExport(function)
//DOC DhcpDsSubnetDelRangeOrExcl deletes a range or exclusion from off the ds.
//DOC To specify range, set the RangeOrExcl parameter to TRUE.
DWORD
DhcpDsSubnetDelRangeOrExcl(                       // del a range or exclusion
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN OUT  LPSTORE_HANDLE         hSubnet,       // subnet object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      DWORD                  Start,         // start addr in range
    IN      DWORD                  End,           // end addr in range
    IN      BOOL                   RangeOrExcl    // TRUE ==> Range,FALSE ==> Excl
)   //EndExport(function)
{
    DWORD                          Err, Err2, Type, ThisType, unused;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    ARRAY                          Ranges;
    BOOL                           Changed;

    if( NULL == hDhcpC || NULL == hServer )       // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == hSubnet || NULL == hSubnet->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;
    if( Start > End ) return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Ranges);                  //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of ranges frm ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ &Ranges,       // array of PEATTRIB 's
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    Type = (RangeOrExcl? RANGE_TYPE_RANGE: RANGE_TYPE_EXCL);
    Changed = FALSE;

    for(                                          // look for matching range/excl
        Err = MemArrayInitLoc(&Ranges, &Loc)
        ; ERROR_FILE_NOT_FOUND != Err ;
        Err = MemArrayNextLoc(&Ranges, &Loc)
    ) {
        Err = MemArrayGetElement(&Ranges, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no subnet address
            !IS_ADDRESS2_PRESENT(ThisAttrib) ) {  // no subnet mask
            continue;                             //=  ds inconsistent
        }

        if( !IS_FLAGS2_PRESENT(ThisAttrib) ) {    // this is a RANGE_TYPE_RANGE
            ThisType = RANGE_TYPE_RANGE;
        } else ThisType = ThisAttrib->Flags2;

        if( Start != ThisAttrib->Address1  ||
            End != ThisAttrib->Address2 ) {       // range mismatch
            continue;
        }

        if(Type != ThisType ) {                   // looking for x, bug this is !x.
            continue;
        }

        Err2 = MemArrayDelElement(&Ranges, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib
        MemFreeFunc(ThisAttrib);
        Changed = TRUE;
    }

    if( !Changed ) {                              // nothing found ni registry
        Err = ERROR_DDS_RANGE_DOES_NOT_EXIST;
    } else {
        Err = DhcpDsSetLists                      // write back new list to ds
        (
            /* Reserved         */ DDS_RESERVED_DWORD,
            /* hStore           */ hSubnet,
            /* SetParams        */ &unused,
            /* Servers          */ NULL,
            /* Subnets          */ NULL,
            /* IpAddress        */ NULL,
            /* Mask             */ NULL,
            /* Ranges           */ &Ranges,
            /* Sites            */ NULL,
            /* Reservations     */ NULL,
            /* SuperScopes      */ NULL,
            /* OptionDescription..  */ NULL,
            /* OptionsLocation  */ NULL,
            /* Options          */ NULL,
            /* ClassDescription */ NULL,
            /* Classes          */ NULL
        );
    }

    MemArrayFree(&Ranges, MemFreeFunc);
    return Err;                                   // DhcpDsSetLists's ret code
}

DWORD
ConvertAttribToRanges(                            // convert from array of attribs ..
    IN      DWORD                  nRanges,       // # of ranges,
    IN      PARRAY                 Ranges,        // input array of attribs
    IN      ULONG                  Type,          // TYPE_RANGE_TYPE or TYPE_EXCLUSION_TYPE?
    OUT     LPDHCP_SUBNET_ELEMENT_INFO_ARRAY_V4 *pRanges //output array..
)
{
    DWORD                          Err;
    ULONG                          Count, ThisType;
    PEATTRIB                       ThisAttrib;
    LPDHCP_SUBNET_ELEMENT_INFO_ARRAY_V4 localRanges;
    ARRAY_LOCATION                 Loc;
    DHCP_IP_RANGE                 *ThisRange;

    localRanges = MemAlloc(sizeof(DHCP_SUBNET_ELEMENT_INFO_ARRAY_V4));
    *pRanges = localRanges;
    if( NULL == localRanges ) return ERROR_NOT_ENOUGH_MEMORY;

    if( 0 == nRanges ) {
        localRanges->NumElements = 0;
        localRanges->Elements = NULL;
        return ERROR_SUCCESS;
    }

    localRanges->Elements = MemAlloc(nRanges*sizeof(DHCP_SUBNET_ELEMENT_DATA_V4));
    if( NULL == localRanges->Elements ) {
        MemFree(localRanges);
        *pRanges = NULL;
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    localRanges->NumElements = nRanges;
    for( Count = 0 ; Count < nRanges ; Count ++ ) {
        localRanges->Elements[Count].Element.IpRange =
        localRanges->Elements[Count].Element.ExcludeIpRange =
        ThisRange = MemAlloc(sizeof(DHCP_IP_RANGE));
        if( NULL == ThisRange ) {                 // oops could not allocate ? free everything and bail!
            while( Count != 0 ) {                 // remember Count is unsigned ..
                Count --;
                MemFree(localRanges->Elements[Count].Element.IpRange);
            }
            MemFree(localRanges->Elements);
            MemFree(localRanges);
            *pRanges = NULL;
            return ERROR_NOT_ENOUGH_MEMORY;
        }
    }

    Count = 0;
    for(                                          // look for matching range/excl
        Err = MemArrayInitLoc(Ranges, &Loc)
        ; ERROR_FILE_NOT_FOUND != Err ;
        Err = MemArrayNextLoc(Ranges, &Loc)
    ) {
        Err = MemArrayGetElement(Ranges, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no subnet address
            !IS_ADDRESS2_PRESENT(ThisAttrib) ) {  // no subnet mask
            continue;                             //=  ds inconsistent
        }

        if( !IS_FLAGS2_PRESENT(ThisAttrib) ) {    // this is a RANGE_TYPE_RANGE
            ThisType = RANGE_TYPE_RANGE;
        } else ThisType = ThisAttrib->Flags2;

        if(Type != ThisType ) {                   // looking for x, bug this is !x.
            continue;
        }

        //= require ThisAttrib->Address1 < ThisAttrib->Address2

        if( RANGE_TYPE_RANGE == Type ) {
            localRanges->Elements[Count].ElementType = DhcpIpRanges ;
            localRanges->Elements[Count].Element.IpRange->StartAddress = ThisAttrib->Address1;
            localRanges->Elements[Count].Element.IpRange->EndAddress = ThisAttrib->Address2;
        } else {
            localRanges->Elements[Count].ElementType =  DhcpExcludedIpRanges;
            localRanges->Elements[Count].Element.ExcludeIpRange->StartAddress = ThisAttrib->Address1;
            localRanges->Elements[Count].Element.ExcludeIpRange->EndAddress = ThisAttrib->Address2;
        }

        Count ++;
    }

    return ERROR_SUCCESS;
}


//BeginExport(function)
//DOC DhcpDsEnumRangesOrExcl is not yet implemented.
DWORD
DhcpDsEnumRangesOrExcl(                           // enum list of ranges 'n excl
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN OUT  LPSTORE_HANDLE         hSubnet,       // subnet object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      BOOL                   RangeOrExcl,   // TRUE ==> Range, FALSE ==> Excl
    OUT     LPDHCP_SUBNET_ELEMENT_INFO_ARRAY_V4 *pRanges
)   //EndExport(function)
{
    DWORD                          Err, Err2, Type, ThisType, unused;
    DWORD                          Count;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    ARRAY                          Ranges;

    if( NULL == hDhcpC || NULL == hServer )       // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == hSubnet || NULL == hSubnet->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Ranges);                  //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of ranges frm ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ &Ranges,       // array of PEATTRIB 's
        /* Sites                */ NULL,
        /* Reservations         */ NULL,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    Type = (RangeOrExcl? RANGE_TYPE_RANGE: RANGE_TYPE_EXCL);

    Count = 0;
    for(                                          // look for matching range/excl
        Err = MemArrayInitLoc(&Ranges, &Loc)
        ; ERROR_FILE_NOT_FOUND != Err ;
        Err = MemArrayNextLoc(&Ranges, &Loc)
    ) {
        Err = MemArrayGetElement(&Ranges, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no subnet address
            !IS_ADDRESS2_PRESENT(ThisAttrib) ) {  // no subnet mask
            continue;                             //=  ds inconsistent
        }

        if( !IS_FLAGS2_PRESENT(ThisAttrib) ) {    // this is a RANGE_TYPE_RANGE
            ThisType = RANGE_TYPE_RANGE;
        } else ThisType = ThisAttrib->Flags2;

        if(Type != ThisType ) {                   // looking for x, bug this is !x.
            continue;
        }

        Count ++;
    }

    Err = ConvertAttribToRanges(Count, &Ranges, Type, pRanges);
    MemArrayFree(&Ranges, MemFreeFunc);

    return Err;
}

//BeginExport(function)
//DOC DhcpDsSubnetAddReservation tries to add a reservation object in the DS.
//DOC Neither the ip address not hte hw-address must exist in the DS prior to this.
//DOC If they do exist, the error returned is ERROR_DDS_RESERVATION_CONFLICT.
//DOC No checks are made on the sanity of the address in this subnet..
DWORD
DhcpDsSubnetAddReservation(                       // add a reservation
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN OUT  LPSTORE_HANDLE         hSubnet,       // subnet object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      DWORD                  ReservedAddr,  // reservation ip address to add
    IN      LPBYTE                 HwAddr,        // RAW [ethernet?] hw addr of the client
    IN      DWORD                  HwAddrLen,     // length in # of bytes of hw addr
    IN      DWORD                  ClientType     // client is BOOTP, DHCP, or both?
)   //EndExport(function)
{
    DWORD                          Err, Err2, Type, ClientUIDSize, unused;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    EATTRIB                        Dummy;
    ARRAY                          Reservations;
    LPBYTE                         ClientUID;
    LPWSTR                         ReservationCNName;

    if( NULL == hDhcpC || NULL == hServer )       // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;
    if( NULL == HwAddr || 0 == HwAddrLen )
        return ERROR_INVALID_PARAMETER;

    ClientUID = NULL;
    Err = DhcpMakeClientUID(
        HwAddr,
        HwAddrLen,
        HARDWARE_TYPE_10MB_EITHERNET,
        ReservedAddr,
        &ClientUID,
        &ClientUIDSize
    );
    if( ERROR_SUCCESS != Err ) {                  // should not happen
        return Err;
    }

    Err = MemArrayInit(&Reservations);            //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of ranges frm ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ &Reservations,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) {
        MemFree(ClientUID);
        return Err;
    }

    for(                                          // search for existing reservation
        Err = MemArrayInitLoc(&Reservations, &Loc)
        ; ERROR_FILE_NOT_FOUND != Err ;
        Err = MemArrayNextLoc(&Reservations, &Loc)
    ) {
        BOOL                           Mismatch;

        //= require ERROR_SUCCESS == Err
        Err = MemArrayGetElement(&Reservations, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib)      // no address for reservations
            || !IS_BINARY1_PRESENT(ThisAttrib)    // no hw len specified
            || !IS_FLAGS1_PRESENT(ThisAttrib) ) { // no client type present
            continue;                             //=  ds inconsistent
        }

        Mismatch = FALSE;
        if( ThisAttrib->Address1 == ReservedAddr ) {
            Mismatch = TRUE;                      // address already reserved
        }

        if( ThisAttrib->BinLen1 == ClientUIDSize  // see if hw address matches
            && 0 == memcmp(ThisAttrib->Binary1, ClientUID, ClientUIDSize)
        ) {
            Mismatch = TRUE;
        }

        if( Mismatch ) {                          // ip addr or hw-addr in use
            MemArrayFree(&Reservations, MemFreeFunc);
            MemFree(ClientUID);
            return ERROR_DDS_RESERVATION_CONFLICT;
        }
    }

    ReservationCNName = MakeReservationLocation(ServerName, ReservedAddr);
    if( NULL == ReservationCNName ) {             // not enough mem to create string
        Err = ERROR_NOT_ENOUGH_MEMORY;
    } else {
        Err = CreateReservationObject             // create the new reservation object
        (
            /* hDhcpC           */ hDhcpC,        // container to create obj in
            /* ReserveCNName    */ ReservationCNName
        );
    }
    if( ERROR_SUCCESS != Err ) {                  // could not create reservation object
        MemArrayFree(&Reservations, MemFreeFunc);
        MemFree(ClientUID);
        return Err;
    }

    NothingPresent(&Dummy);                       // create a new reservation
    ADDRESS1_PRESENT(&Dummy);                     // ip address
    Dummy.Address1 = ReservedAddr;
    FLAGS1_PRESENT(&Dummy);                       // client type
    Dummy.Flags1 = ClientType;
    BINARY1_PRESENT(&Dummy);                      // client uid
    Dummy.BinLen1 = ClientUIDSize;
    Dummy.Binary1 = ClientUID;
    STOREGETTYPE_PRESENT(&Dummy);                 // relative location for reservation obj
    ADSPATH_PRESENT(&Dummy);
    Dummy.StoreGetType = StoreGetChildType;
    Dummy.ADsPath = ReservationCNName;

    Err = MemArrayAddElement(&Reservations,&Dummy);
    if( ERROR_SUCCESS != Err ) {                  // oops, cannot add reservation
        MemArrayFree(&Reservations, MemFreeFunc);
        MemFree(ClientUID);
        MemFree(ReservationCNName);
        return Err;
    }

    Err = DhcpDsSetLists                          // write back new list to ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* SetParams            */ &unused,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ &Reservations,
        /* SuperScopes          */ NULL,
        /* OptionDescription..  */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescription     */ NULL,
        /* Classes              */ NULL
    );

    Err2 = MemArrayLastLoc(&Reservations, &Loc);  // try to delete frm mem the new elt
    Err2 = MemArrayDelElement(&Reservations, &Loc, &ThisAttrib);
    //= require ERROR_SUCCESS == Err && ThisAttrib == &Dummy

    MemArrayFree(&Reservations, MemFreeFunc);
    MemFree(ClientUID);
    MemFree(ReservationCNName);
    return Err;                                   // DhcpDsSetLists's ret code
}

//BeginExport(function)
//DOC DhcpDsSubnetDelReservation deletes a reservation from the DS.
//DOC If the reservation does not exist, it returns ERROR_DDS_RESERVATION_NOT_PRESENT.
//DOC Reservations cannot be deleted by anything but ip address for now.
DWORD
DhcpDsSubnetDelReservation(                       // delete a reservation
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN OUT  LPSTORE_HANDLE         hSubnet,       // subnet object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      DWORD                  ReservedAddr   // ip address to delete reserv. by
)   //EndExport(function)
{
    DWORD                          Err, Err2, Type, ThisType, unused;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    ARRAY                          Reservations;
    BOOL                           ReservationExists;
    LPWSTR                         ReservationLocPath, ReservationCNName;
    DWORD                          ReservationLocType;

    if( NULL == hDhcpC || NULL == hServer )       // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Reservations);            //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of ranges frm ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ &Reservations,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    ReservationExists = FALSE;
    for(                                          // look for matching range/excl
        Err = MemArrayInitLoc(&Reservations, &Loc)
        ; ERROR_FILE_NOT_FOUND != Err ;
        Err = MemArrayNextLoc(&Reservations, &Loc)
    ) {
        Err = MemArrayGetElement(&Reservations, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no reservation address
            !IS_BINARY1_PRESENT(ThisAttrib) ) {   // no hw addr specified
            continue;                             //=  ds inconsistent
        }

        if( ThisAttrib->Address1 != ReservedAddr ) {
            continue;                             // not this reservation
        }

        ReservationExists = TRUE;
        Err2 = MemArrayDelElement(&Reservations, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib
        break;
    }

    if( !ReservationExists ) {                    // no matching reservation found
        MemArrayFree(&Reservations, MemFreeFunc);
        return ERROR_DDS_RESERVATION_NOT_PRESENT;
    }

    Err = DhcpDsSetLists                          // write back new list to ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* SetParams            */ &unused,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ &Reservations,
        /* SuperScopes          */ NULL,
        /* OptionDescription..  */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* ClassDescription     */ NULL,
        /* Classes              */ NULL
    );
    MemArrayFree(&Reservations, MemFreeFunc);
    if( ERROR_SUCCESS != Err ) {
        MemFree(ThisAttrib);
        return Err;
    }

    if( IS_ADSPATH_PRESENT(ThisAttrib) ) {        // deleted reservation's location in ds
        ReservationLocType = ThisAttrib->StoreGetType;
        ReservationLocPath = ThisAttrib->ADsPath;
    } else {
        ReservationLocPath = NULL;
    }

    if( NULL == ReservationLocPath ) {            // no path present, but guess it anyways
        ReservationCNName = MakeReservationLocation(ServerName,ReservedAddr);
        ReservationLocPath = ReservationCNName;   // same name that is used generally
        ReservationLocType = StoreGetChildType;   // child object
    } else {
        ReservationCNName = NULL;                 // NULL indicating no alloc
    }

    if( NULL == ReservationLocPath ) {            // dont know what to delete..
        Err = ERROR_NOT_ENOUGH_MEMORY;            // MakeReservationLocation failed
    } else {                                      // try to delete subnet object
        Err = SubnetDeleteReservation             // actual delete in ds
        (
            /* hDhcpC           */ hDhcpC,
            /* ServerName       */ ServerName,
            /* hServer          */ hServer,
            /* hSubnet          */ hSubnet,
            /* ADsPath          */ ReservationLocPath,
            /* StoreGetType     */ ReservationLocType
        );
    }

    if( ReservationCNName ) MemFree(ReservationCNName);
    MemFree(ThisAttrib);                          // free all ptrs left..

    return Err;
}


LPBYTE _inline
DupeBytes(                                        // allocate mem and copy bytes
    IN      LPBYTE                 Data,
    IN      ULONG                  DataLen
)
{
    LPBYTE                         NewData;

    if( 0 == DataLen ) return NULL;
    NewData = MemAlloc(DataLen);
    if( NULL != NewData ) {
        memcpy(NewData, Data, DataLen);
    }
    return NewData;
}


DWORD
ConvertAttribToReservations(                      // convert from arry of attribs ..
    IN       DWORD                 nRes,          // # of reservatiosn to convert
    IN       PARRAY                Res,           // the actual array of reservations
    OUT      LPDHCP_SUBNET_ELEMENT_INFO_ARRAY_V4 *pResInfo
)
{
    LPDHCP_SUBNET_ELEMENT_INFO_ARRAY_V4 localRes;
    DWORD                          Err, Count;
    LPWSTR                         ReservationLocPath, ReservationCNName;
    DWORD                          ReservationLocType;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    ARRAY                          Reservations;
    LPDHCP_IP_RESERVATION_V4       ThisRes;
    LPVOID                         Data;

    localRes = MemAlloc(sizeof(DHCP_SUBNET_ELEMENT_INFO_ARRAY_V4));
    *pResInfo = localRes;
    if( NULL == localRes ) return ERROR_NOT_ENOUGH_MEMORY;

    if( 0 == nRes ) {
        localRes->NumElements = 0;
        localRes->Elements = NULL;
        return ERROR_SUCCESS;
    }

    localRes->Elements = MemAlloc(nRes*sizeof(DHCP_SUBNET_ELEMENT_DATA_V4));
    if( NULL == localRes->Elements ) {
        MemFree(localRes);
        *pResInfo = NULL;
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    localRes->NumElements = nRes;
    for( Count = 0 ; Count < nRes ; Count ++ ) {
        localRes->Elements[Count].Element.ReservedIp =
        ThisRes = MemAlloc(sizeof(DHCP_IP_RESERVATION_V4));
        if( NULL != ThisRes ) {                   // successfull allocation..
            ThisRes->ReservedForClient = MemAlloc(sizeof(DHCP_CLIENT_UID));
            if( NULL == ThisRes->ReservedForClient) {
                MemFree(ThisRes);                 // duh it failed here..
                ThisRes = NULL;                   // fake an upper level fail
            }
        }

        if( NULL == ThisRes ) {                   // oops could not allocate ? free everything and bail!
            while( Count != 0 ) {                 // remember Count is unsigned ..
                Count --;
                MemFree(localRes->Elements[Count].Element.ReservedIp->ReservedForClient);
                MemFree(localRes->Elements[Count].Element.ReservedIp);
            }
            MemFree(localRes->Elements);
            MemFree(localRes);
            *pResInfo = NULL;
            return ERROR_NOT_ENOUGH_MEMORY;
        }
    }

    Count = 0;
    for(                                          // look for matching range/excl
        Err = MemArrayInitLoc(Res, &Loc)
        ; ERROR_FILE_NOT_FOUND != Err ;
        Err = MemArrayNextLoc(Res, &Loc)
    ) {
        Err = MemArrayGetElement(Res, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no reservation address
            !IS_BINARY1_PRESENT(ThisAttrib) ) {   // no hw addr specified
            continue;                             //=  ds inconsistent
        }

        localRes->Elements[Count].ElementType = DhcpReservedIps;
        if( IS_FLAGS1_PRESENT(ThisAttrib) ) {
            localRes->Elements[Count].Element.ReservedIp->bAllowedClientTypes = (BYTE)ThisAttrib->Flags1;
        } else {
            localRes->Elements[Count].Element.ReservedIp->bAllowedClientTypes = 0;
        }

        localRes->Elements[Count].Element.ReservedIp->ReservedIpAddress = ThisAttrib->Address1;
        localRes->Elements[Count].Element.ReservedIp->ReservedForClient->Data =
        Data = DupeBytes(ThisAttrib->Binary1, ThisAttrib->BinLen1);
        if( NULL == Data ) {                      // could not allocate memory..
            localRes->Elements[Count].Element.ReservedIp->ReservedForClient->DataLength = 0;
        } else {
            localRes->Elements[Count].Element.ReservedIp->ReservedForClient->DataLength = ThisAttrib->BinLen1;
        }

        Count++;
    }

    return ERROR_SUCCESS;
}


//BeginExport(function)
//DOC DhcpDsEnumReservations enumerates the reservations..
DWORD
DhcpDsEnumReservations(                           // enumerate reservations frm DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN OUT  LPSTORE_HANDLE         hSubnet,       // subnet object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    OUT     LPDHCP_SUBNET_ELEMENT_INFO_ARRAY_V4 *pReservations
)   //EndExport(function)
{
    DWORD                          Err, Err2, Type, ThisType, unused;
    DWORD                          Count;
    ARRAY_LOCATION                 Loc;
    PEATTRIB                       ThisAttrib;
    ARRAY                          Reservations;

    if( NULL == hDhcpC || NULL == hServer )       // check params
        return ERROR_INVALID_PARAMETER;
    if( NULL == hServer->ADSIHandle || NULL == hDhcpC->ADSIHandle )
        return ERROR_INVALID_PARAMETER;
    if( NULL == ServerName || 0 != Reserved )
        return ERROR_INVALID_PARAMETER;

    Err = MemArrayInit(&Reservations);            //= require ERROR_SUCCESS == Err
    Err = DhcpDsGetLists                          // get list of ranges frm ds
    (
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* hStore               */ hSubnet,
        /* RecursionDepth       */ 0xFFFFFFFF,
        /* Servers              */ NULL,
        /* Subnets              */ NULL,
        /* IpAddress            */ NULL,
        /* Mask                 */ NULL,
        /* Ranges               */ NULL,
        /* Sites                */ NULL,
        /* Reservations         */ &Reservations,
        /* SuperScopes          */ NULL,
        /* OptionDescription    */ NULL,
        /* OptionsLocation      */ NULL,
        /* Options              */ NULL,
        /* Classes              */ NULL
    );
    if( ERROR_SUCCESS != Err ) return Err;

    Count = 0;
    for(                                          // look for matching range/excl
        Err = MemArrayInitLoc(&Reservations, &Loc)
        ; ERROR_FILE_NOT_FOUND != Err ;
        Err = MemArrayNextLoc(&Reservations, &Loc)
    ) {
        Err = MemArrayGetElement(&Reservations, &Loc, &ThisAttrib);
        //= require ERROR_SUCCESS == Err && NULL != ThisAttrib

        if( !IS_ADDRESS1_PRESENT(ThisAttrib) ||   // no reservation address
            !IS_BINARY1_PRESENT(ThisAttrib) ) {   // no hw addr specified
            continue;                             //=  ds inconsistent
        }

    }

    Err = ConvertAttribToReservations(Count, &Reservations, pReservations);

    MemArrayFree(&Reservations, MemFreeFunc);
    return Err;
}

//BeginExport(function)
//DOC DhcpDsEnumSubnetElements enumerates the list of subnet elements in a
//DOC subnet... such as IpRanges, Exclusions, Reservations..
//DOC
DWORD
DhcpDsEnumSubnetElements(
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // root container to create objects
    IN OUT  LPSTORE_HANDLE         hServer,       // server object
    IN OUT  LPSTORE_HANDLE         hSubnet,       // subnet object
    IN      DWORD                  Reserved,      // for future use, reserved
    IN      LPWSTR                 ServerName,    // name of server we're using
    IN      DHCP_SUBNET_ELEMENT_TYPE ElementType, // what kind of elt to enum?
    OUT     LPDHCP_SUBNET_ELEMENT_INFO_ARRAY_V4 *ElementInfo
)   //EndExport(function)
{
    DWORD                          Err;

    switch(ElementType) {
    case DhcpIpRanges:
        Err = DhcpDsEnumRangesOrExcl(
            hDhcpC,
            hServer,
            hSubnet,
            Reserved,
            ServerName,
            TRUE,
            ElementInfo
        );
        break;
    case DhcpExcludedIpRanges:
        Err = DhcpDsEnumRangesOrExcl(
            hDhcpC,
            hServer,
            hSubnet,
            Reserved,
            ServerName,
            FALSE,
            ElementInfo
        );
        break;
    case DhcpReservedIps:
        Err = DhcpDsEnumReservations(
            hDhcpC,
            hServer,
            hSubnet,
            Reserved,
            ServerName,
            ElementInfo
        );
        break;
    default:
        return ERROR_CALL_NOT_IMPLEMENTED;
    }
    return Err;
}

//================================================================================
//  end of file
//================================================================================