//================================================================================
//  Copyright (C) 1997 Microsoft Corporation
//  Author: RameshV
//  Description: Download and Upload related code.
//================================================================================

//================================================================================
//  includes
//================================================================================
#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    <rpcapi2.h>
#include    <rpcstubs.h>

//================================================================================
//  utilities
//================================================================================

//DOC DhcpDsServerGetLastUpdateTime gets the last update time for this server in the DS
DWORD
DhcpDsServerGetLastUpdateTime(                     // get last update time for server
    IN      LPSTORE_HANDLE         hServer,        // server to get last update time of
    IN OUT  LPFILETIME             Time            // set this struct appropriately
)
{
    HRESULT                        Err;
    DWORD                          nAttributes;
    LPWSTR                         TimeAttrName;   // attribute name for time..
    PADS_ATTR_INFO                 Attributes;
    SYSTEMTIME                     SysTime;

    TimeAttrName = DHCP_ATTRIB_WHEN_CHANGED;

    Attributes = NULL; nAttributes = 0;
    Err = ADSIGetObjectAttributes                  // now read the last changed time attr
    (
        hServer->ADSIHandle,
        &TimeAttrName,
        1,                                         // only 1 attribute in above array
        &Attributes,
        &nAttributes
    );
    if( FAILED(Err) || 0 == nAttributes || 0 == Attributes->dwNumValues ) {
        if( Attributes ) {
            FreeADsMem(Attributes);
            return ERROR_GEN_FAILURE;              // blanket error? maybe better err msg..
        }
        return ConvertHresult(Err);
    }

    if( Attributes->pADsValues[0].dwType != ADSTYPE_UTC_TIME ) {
        FreeADsMem(Attributes);
        return ERROR_GEN_FAILURE;                  // unexpected data format
    }

    SysTime = Attributes->pADsValues[0].UTCTime;   // copy time structs
    FreeADsMem(Attributes);

    Err = SystemTimeToFileTime(&SysTime, Time);    // try to convert to filetime struct
    if( FAILED( Err ) ) return GetLastError();      // something went wrong?
    return ERROR_SUCCESS;
}

BOOL        _inline
AddressFoundInHostent(
    IN      DHCP_IP_ADDRESS        AddrToSearch,  // Host-Order addr
    IN      HOSTENT               *ServerEntry    // entry to search for..
)
{
    ULONG                          nAddresses, ThisAddress;

    if( NULL == ServerEntry ) return FALSE;       // no address to search in

    nAddresses = 0;                               // have a host entry to compare for addresses
    while( ServerEntry->h_addr_list[nAddresses] ) {
        ThisAddress = ntohl(*(DHCP_IP_ADDRESS*)ServerEntry->h_addr_list[nAddresses++] );
        if( ThisAddress == AddrToSearch ) {
            return TRUE;                          // yeah address matched.
        }
    }

    return FALSE;
}


//================================================================================
//  exports
//================================================================================
//BeginExport(function)
//DOC DhcpDsGetLastUpdateTime gets the last update time for the server
//DOC specified by name. If the server does not exist, or if server object doesnt
//DOC exist, then an error is returned.  If the time value
//DOC does not exist on the server object, again, an error is returned.
DWORD
DhcpDsGetLastUpdateTime(                          // last update time for server
    IN      LPWSTR                 ServerName,    // this is server of interest
    IN OUT  LPFILETIME             Time           // fill in this w./ the time
)   //EndExport(function)
{
    DWORD                          Err,i, LocType ;
    LPDHCPDS_SERVERS               Servers;
    extern STORE_HANDLE            hDhcpC, hDhcpRoot; // From rpcstubs.c
    LPWSTR                         Location, LocStr;
    BOOL                           Found;
    STORE_HANDLE                   hServer;
    HOSTENT                       *ServerEntry;

    if( NULL == ServerName ) return ERROR_INVALID_PARAMETER;
    do {                                          // name to IP lookup
        CHAR TmpBuf[300];
        wcstombs(TmpBuf, ServerName, sizeof(TmpBuf)-1);
        TmpBuf[sizeof(TmpBuf)-1] = '\0';
        ServerEntry = gethostbyname(TmpBuf);
    } while(0);

    Servers = NULL;
    Err = DhcpDsEnumServers                       // first get a list of servers
    (
        /* hDhcpC               */ &hDhcpC,       // frm rpcstubs.c, opened in DhcpDsInitDS
        /* hDhcpRoot            */ &hDhcpRoot,    // ditto
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* ServersInfo          */ &Servers
    );
    if( ERROR_SUCCESS != Err ) {
        Time->dwLowDateTime = Time->dwHighDateTime = 0;
        return Err;                               // error.. return w/ no time
    }

    Found = FALSE;
    LocStr = Location = NULL;                     // initialize..
    for( i = 0; i < Servers->NumElements ; i ++ ) {
        if( 0 != _wcsicmp(ServerName, Servers->Servers[i].ServerName) &&
            !AddressFoundInHostent(Servers->Servers[i].ServerAddress, ServerEntry) ) {

            continue;                             // ughm.. not the same server..

        } else {                                  // ok got the server

            Location = Servers->Servers[i].DsLocation;
            LocType = Servers->Servers[i].DsLocType;
            Found = TRUE;
            if( NULL != Location ) break;
        }
    }

    if( ! Found ) {                               // could not find server in list..?
        MemFree(Servers);
        return ERROR_FILE_NOT_FOUND;
    }

    if( NULL == Location ) {                      // could not find server location?
        MemFree(Servers);                         // dont need this anymore
        Servers = NULL;
        Location  = MakeColumnName(ServerName);   // just presume it is under hDhcpC container
        LocType = StoreGetChildType;              // child type
        if( NULL == Location ) return ERROR_NOT_ENOUGH_MEMORY;
    }

    Err = StoreGetHandle                          // now try to open the server object
    (
        /* hStore               */ &hDhcpC,
        /* Reserved             */ DDS_RESERVED_DWORD,
        /* StoreGetType         */ LocType,
        /* Path                 */ Location,
        /* hStoreOut            */ &hServer
    );

    if( Servers ) {                               // Location points into this
        MemFree(Servers);                         // freeing this also free Location
    } else {                                      // Location is allocated memory
        MemFree(Location);
    }

    if( ERROR_SUCCESS != Err ) return Err;        // some DS trouble?

    Err = DhcpDsServerGetLastUpdateTime(&hServer, Time);

    (void)StoreCleanupHandle(&hServer, 0);        // assume this wont fail.

    return Err;
}

//DOC ServerUploadClasses does rpc calls to server and copies stuff over to DS.
DWORD
ServerUploadClasses(                              // upload classes info to DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container to store at
    IN OUT  LPSTORE_HANDLE         hServer,       // server obj in DS
    IN      LPWSTR                 ServerAddress  // server ip address
)
{
    DWORD                          Err, Resume, PrefMax, i, nRead, nTotal;
    LPDHCP_CLASS_INFO_ARRAY        ClassesInfo;

    Resume = 0; PrefMax = 0xFFFFFFFF; nRead = nTotal = 0;
    ClassesInfo = NULL;
    Err = DhcpEnumClasses(ServerAddress, 0, &Resume, PrefMax, &ClassesInfo, &nRead, &nTotal);
    if( ERROR_NO_MORE_ITEMS == Err ) return ERROR_SUCCESS;
    if( ERROR_SUCCESS != Err ) return Err;        // could not enumerate classes.

    for( i = 0; i < ClassesInfo->NumElements; i ++ ) {
        Err = DhcpCreateClassDS(ServerAddress, 0, &ClassesInfo->Classes[i]);
        if( ERROR_SUCCESS != Err ) break;
#if 0
        Err = ServerUploadOptDefsForClass(
            hDhcpC,
            hServer,
            ServerAddress,
            ClassesInfo->Classes[i].ClassName
        );
        if( ERROR_SUCCESS != Err ) break;
#endif
    }

    if( ClassesInfo ) MemFree(ClassesInfo);
    return Err;
}

//DOC ServerUploadOptdefs does rpc calls to server and copies stuff over to DS
DWORD
ServerUploadOptdefs(                              // upload opt defs info to DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container to store at
    IN OUT  LPSTORE_HANDLE         hServer,       // server obj in DS
    IN      LPWSTR                 ServerAddress  // server ip address
)
{
    DWORD                          Err, i;
    LPDHCP_ALL_OPTIONS             Options;

    Options = NULL;
    Err = DhcpGetAllOptions(ServerAddress,0, &Options);
    if( ERROR_NO_MORE_ITEMS == Err ) return ERROR_SUCCESS;
    if( ERROR_SUCCESS != Err ) return Err;

    if( Options->NonVendorOptions ) {
        for( i = 0; i < Options->NonVendorOptions->NumElements; i ++ ) {
            Err = DhcpCreateOptionV5DS(
                ServerAddress,
                0,
                Options->NonVendorOptions->Options[i].OptionID,
                NULL /* no class */,
                NULL /* no vendor */,
                &Options->NonVendorOptions->Options[i]
            );
            if( ERROR_SUCCESS != Err ) break;
        }
    }

    if( ERROR_SUCCESS != Err ) {
        MemFree(Options);
        return Err;
    }


    for( i = 0; i < Options->NumVendorOptions; i ++ ) {
        Err = DhcpCreateOptionV5DS(
            ServerAddress,
            DHCP_FLAGS_OPTION_IS_VENDOR,
            Options->VendorOptions[i].Option.OptionID,
            Options->VendorOptions[i].ClassName,
            Options->VendorOptions[i].VendorName,
            &Options->VendorOptions[i].Option
        );
        if( ERROR_SUCCESS != Err ) break;
    }

    if( ERROR_SUCCESS != Err ) {
        MemFree(Options);
        return Err;
    }

    if( Options ) MemFree(Options);
    return Err;
}

//DOC UploadOptiosn does rpc calls to server and copies stuff over to DS
UploadOptions(                                    // upload options to DS
    IN      LPWSTR                 ServerAddress,
    IN      LPDHCP_OPTION_SCOPE_INFO ScopeInfo
)
{
    DWORD                          Err, Resume, PrefMax, i, nRead, nTotal;
    LPDHCP_ALL_OPTION_VALUES       Options;

    Resume = 0; PrefMax = 0xFFFFFFFF; nRead = nTotal = 0;
    Options = NULL;
    Err = DhcpGetAllOptionValues(                 // get list of default opt values
        ServerAddress,
        0,
        ScopeInfo,
        &Options
    );
    if( ERROR_NO_MORE_ITEMS == Err ) return ERROR_SUCCESS;
    if( ERROR_SUCCESS != Err ) return Err;        // oops could not do this simple task?

    for( i = 0; i < Options->NumElements; i ++ ) {// now try to set each list of options..
        if( NULL == Options->Options[i].OptionsArray ) {
            continue;                             // uh? another way to say no options..
        }

        Err = DhcpSetOptionValuesV5DS   (
            ServerAddress,
            Options->Options[i].IsVendor? DHCP_FLAGS_OPTION_IS_VENDOR:0,
            Options->Options[i].ClassName,
            Options->Options[i].VendorName,
            ScopeInfo,
            Options->Options[i].OptionsArray
        );
        if( ERROR_SUCCESS != Err ) {
            MemFree(Options);
            return Err;
        }
    }

    return ERROR_SUCCESS;                         // saved it
}

//DOC ServerUploadOptions does rpc calls to server and copies stuff over to DS
DWORD
ServerUploadOptions(                              // upload options info to DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container to store at
    IN OUT  LPSTORE_HANDLE         hServer,       // server obj in DS
    IN      LPWSTR                 ServerAddress  // server ip address
)
{
    DWORD                          Err;
    DHCP_OPTION_SCOPE_INFO         ScopeInfo;

#if 0
    ScopeInfo.ScopeInfo.DefaultScopeInfo = NULL;
    ScopeInfo.ScopeType = DhcpDefaultOptions;
    Err = UploadOptions(ServerAddress, &ScopeInfo);
    if( ERROR_SUCCESS != Err ) return Err;        // could not save default options..
#endif

    ScopeInfo.ScopeType = DhcpGlobalOptions;
    ScopeInfo.ScopeInfo.GlobalScopeInfo = NULL;
    Err = UploadOptions(ServerAddress, &ScopeInfo);
    if( ERROR_SUCCESS != Err ) return Err;        // could not save global options..

    return ERROR_SUCCESS;
}

//DOC ReservationUploadOptions does rpc calls to server and copies stuff to DS
DWORD
ReservationUploadOptions(                         // upload reservation options to DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container to store at
    IN OUT  LPSTORE_HANDLE         hServer,       // server obj in DS
    IN      LPWSTR                 ServerAddress, // server ip address
    IN      DWORD                  SubnetAddress, // add of subnet to add
    IN      DWORD                  ReserveAddress // address of reservation
)
{
    DHCP_OPTION_SCOPE_INFO         ScopeInfo;

    ScopeInfo.ScopeType = DhcpReservedOptions;
    ScopeInfo.ScopeInfo.ReservedScopeInfo.ReservedIpSubnetAddress = SubnetAddress;
    ScopeInfo.ScopeInfo.ReservedScopeInfo.ReservedIpAddress = ReserveAddress;
    return UploadOptions(ServerAddress, &ScopeInfo);
}

//DOC SubnetUploadOptions does rpc calls to server and copies stuff to DS
DWORD
SubnetUploadOptions(                              // upload subnet options to DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container to store at
    IN OUT  LPSTORE_HANDLE         hServer,       // server obj in DS
    IN      LPWSTR                 ServerAddress, // server ip address
    IN      DWORD                  SubnetAddress  // add of subnet to add
)
{
    DHCP_OPTION_SCOPE_INFO         ScopeInfo;

    ScopeInfo.ScopeType = DhcpSubnetOptions;
    ScopeInfo.ScopeInfo.SubnetScopeInfo = SubnetAddress;
    return UploadOptions(ServerAddress, &ScopeInfo);
}

//DOC ServerUploadSubnet does rpc calls to server and copies stuff over to DS
DWORD
ServerUploadSubnet(                               // upload subnet and relevant info to DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container to store at
    IN OUT  LPSTORE_HANDLE         hServer,       // server obj in DS
    IN      LPWSTR                 ServerAddress, // server ip address
    IN      DWORD                  SubnetAddress  // add of subnet to add
)
{
    DWORD                          Err, Resume, PrefMax, i, nRead, nTotal;
    LPDHCP_SUBNET_INFO             SubnetInfo;
    DHCP_SUBNET_ELEMENT_TYPE       SubnetEltType;
    LPDHCP_SUBNET_ELEMENT_INFO_ARRAY_V4 EltInfo;
    LPDHCP_SUPER_SCOPE_TABLE       SScopeTbl;

    Err = DhcpGetSubnetInfo(ServerAddress, SubnetAddress, &SubnetInfo);
    if( ERROR_SUCCESS != Err ) return Err;        // could not get subnet info..

    Err = DhcpCreateSubnetDS(ServerAddress, SubnetAddress, SubnetInfo);
    if( SubnetInfo ) MemFree(SubnetInfo);
    if( ERROR_SUCCESS != Err ) return Err;        // could not save onto DS

    SScopeTbl = NULL;
    Err = DhcpGetSuperScopeInfoV4(                // get superscope table
        ServerAddress,
        &SScopeTbl
    );
    if( ERROR_SUCCESS == Err ) {                  // could get superscope table
        for( i = 0; i < SScopeTbl->cEntries ; i ++ ) {
            if( SScopeTbl->pEntries[i].SubnetAddress == SubnetAddress ) {
                Err = DhcpSetSuperScopeV4DS(
                    ServerAddress,
                    SubnetAddress,
                    SScopeTbl->pEntries[i].SuperScopeName,
                    TRUE /* change superscope if it exists..*/
                );
                break;
            }
        }
        MemFree(SScopeTbl);
        if( ERROR_SUCCESS != Err ) return Err;    // could not set superscope..
    }

    Resume = 0; PrefMax = 0xFFFFFFFF;
    EltInfo = NULL; nRead = nTotal = 0;
    Err = DhcpEnumSubnetElementsV4(               // enumerate ranges
        ServerAddress,
        SubnetAddress,
        DhcpIpRanges,
        &Resume,
        PrefMax,
        &EltInfo,
        &nRead,
        &nTotal
    );
    if( ERROR_SUCCESS != Err ) return Err;        // could not get ranges

    for( i = 0; i < EltInfo->NumElements; i ++ ) {// try to add each range
        Err = DhcpAddSubnetElementV4DS(
            ServerAddress,
            SubnetAddress,
            &EltInfo->Elements[i]
        );
        if( ERROR_SUCCESS != Err ) break;
    }
    if( EltInfo ) MemFree(EltInfo);               // free-up memory
    if( ERROR_SUCCESS != Err ) return Err;        // could not add ranges

    Resume = 0; PrefMax = 0xFFFFFFFF;
    EltInfo = NULL;
    Err = DhcpEnumSubnetElementsV4(               // enumerate reservations
        ServerAddress,
        SubnetAddress,
        DhcpReservedIps,
        &Resume,
        PrefMax,
        &EltInfo,
        &nRead,
        &nTotal
    );
    if( ERROR_NO_MORE_ITEMS == Err ) goto try_Excl;
    if( ERROR_SUCCESS != Err ) return Err;        // could not get exclusions

    for( i = 0; i < EltInfo->NumElements; i ++ ) {// try to add each reservation
        Err = DhcpAddSubnetElementV4DS(
            ServerAddress,
            SubnetAddress,
            &EltInfo->Elements[i]
        );
        if( ERROR_SUCCESS != Err ) break;         // could not add reseration in DS

        Err = ReservationUploadOptions(
            hDhcpC,
            hServer,
            ServerAddress,
            SubnetAddress,
            EltInfo->Elements[i].Element.ReservedIp->ReservedIpAddress
        );
        if( ERROR_SUCCESS != Err ) break;         // could not add reservaation options
    }
    if( EltInfo ) MemFree(EltInfo);               // free-up memory
    if( ERROR_SUCCESS != Err ) return Err;        // could not add exclusions

  try_Excl:

    Resume = 0; PrefMax = 0xFFFFFFFF;
    EltInfo = NULL;
    Err = DhcpEnumSubnetElementsV4(               // enumerate exclusions
        ServerAddress,
        SubnetAddress,
        DhcpReservedIps,
        &Resume,
        PrefMax,
        &EltInfo,
        &nRead,
        &nTotal
    );
    if( ERROR_NO_MORE_ITEMS == Err ) goto try_Options;
    if( ERROR_SUCCESS != Err ) return Err;        // could not get exclusions

    for( i = 0; i < EltInfo->NumElements; i ++ ) {// try to add each exclusion
        Err = DhcpAddSubnetElementV4DS(
            ServerAddress,
            SubnetAddress,
            &EltInfo->Elements[i]
        );
        if( ERROR_SUCCESS != Err ) break;
    }
    if( EltInfo ) MemFree(EltInfo);               // free-up memory
    if( ERROR_SUCCESS != Err ) return Err;        // could not add exclusions

  try_Options:
    return SubnetUploadOptions(hDhcpC,hServer,ServerAddress,SubnetAddress);
}

//DOC ServerUploadSubnets does rpc calls to server and copies stuff over to DS
DWORD
ServerUploadSubnets(                              // upload subnets info to DS
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // dhcp container to store at
    IN OUT  LPSTORE_HANDLE         hServer,       // server obj in DS
    IN      LPWSTR                 ServerAddress  // server ip address
)
{
    DWORD                          Err, Resume, PrefMax, i, nRead, nTotal;
    LPDHCP_IP_ARRAY                Subnets;

    Resume = 0; PrefMax = 0xFFFFFFFF; nRead = nTotal = 0;
    Subnets = NULL;
    Err = DhcpEnumSubnets(ServerAddress, &Resume, PrefMax, &Subnets, &nRead, &nTotal);
    if( ERROR_NO_MORE_ITEMS == Err ) return ERROR_SUCCESS;
    if( ERROR_SUCCESS != Err ) return Err;        // could not get list of elements?

    for( i = 0; i < Subnets->NumElements ; i ++ ) {
        Err = ServerUploadSubnet(hDhcpC, hServer, ServerAddress, Subnets->Elements[i]);
        if( ERROR_SUCCESS != Err ) break;
    }

    if( Subnets ) MemFree(Subnets);
    return Err;
}

//DOC UploadServer downloads the server info by making RPC calls..
DWORD
UploadServer(                                     // make rpc calls and pull up info to DS.
    IN OUT  LPSTORE_HANDLE         hDhcpC,        // general container where info is stored
    IN OUT  LPSTORE_HANDLE         hServer,       // server obj in DS
    IN      LPWSTR                 ServerName,    // name of server
    IN      DWORD                  IpAddress      // ip address of server
)
{
    DWORD                          Err;
    LPSTR                          IpAddrStr;
    WCHAR                          ServerAddress[sizeof("000.000.000.000")];

    IpAddress = htonl(IpAddress);                 // use n/w order ip address..
    IpAddrStr = inet_ntoa(*(struct in_addr *)&IpAddress);
    Err = mbstowcs(ServerAddress, IpAddrStr, ( sizeof(ServerAddress)/sizeof( WCHAR ) ) );
    if( -1 == Err ) {                             // could not convert to LPWSTR
        return ERROR_GEN_FAILURE;
    }

    Err = ServerUploadClasses(hDhcpC, hServer, ServerAddress);
    if( ERROR_SUCCESS != Err ) {                  // could not upload server classes info
        return Err;
    }

    Err = ServerUploadOptdefs(hDhcpC, hServer, ServerAddress);
    if( ERROR_SUCCESS != Err ) {                  // could not upload option defs ?
        return Err;
    }

    Err = ServerUploadOptions(hDhcpC, hServer, ServerAddress);
    if( ERROR_SUCCESS != Err ) {                  // could not upload options?
        return Err;
    }

    Err = ServerUploadSubnets(hDhcpC, hServer, ServerAddress);
    if( ERROR_SUCCESS != Err ) {                  // could not upload subnets?
        return Err;
    }

    return ERROR_SUCCESS;
}

//BeginExport(function)
//DOC AddServer 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
AddServer(                                        // 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 to server object
    IN      DWORD                  IpAddress,     // IpAddress to add to server
    IN      DWORD                  State          // state of server
)   //EndExport(function)
{
    DWORD                          Err, Err2;
    STORE_HANDLE                   hServer;

    Err = StoreGetHandle(                         // get the server obj..
        hDhcpC,
        DDS_RESERVED_DWORD,
        StoreGetChildType,
        ADsPath,
        &hServer
    );

    if( ERROR_SUCCESS != Err ) return Err;        // could be because server obj elsewhere??

    if( !CFLAG_DONT_DO_DSWORK ) {                 // if DS stuff is enabled in the first place
        Err = UploadServer(hDhcpC, &hServer, ServerName, IpAddress);
    }

    StoreCleanupHandle(&hServer, 0);              // cleanup this server..

    return Err;
}

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