/*++

Copyright (c) 1991-1996  Microsoft Corporation

Module Name:

    dfsstub.c

Abstract:

    These are the server service API RPC client stubs for DFS operations

Environment:

    User Mode - Win32

--*/

//
// INCLUDES
//

#include <nt.h>         // DbgPrint prototype

#include <ntrtl.h>      // DbgPrint
#include <rpc.h>        // DataTypes and runtime APIs

#include <srvsvc.h>     // generated by the MIDL complier
#include <lmcons.h>     // NET_API_STATUS
#include <debuglib.h>   // (needed by netrpc.h)
#include <lmsvc.h>      // (needed by netrpc.h)
#include <netdebug.h>   // (needed by netrpc.h)
#include <lmerr.h>      // NetError codes
#include <netrpc.h>     // NET_REMOTE_ macros.
#include <nturtl.h>
#include <winbase.h>
#include <dfspriv.h>
#include <Winsock2.h>
#include <Dsgetdc.h>
#include <malloc.h>
#include <stdio.h>
#include <Lm.h>



NET_API_STATUS NET_API_FUNCTION
I_NetDfsGetVersion(
    IN  LPWSTR  servername,
    OUT LPDWORD Version)
{
    NET_API_STATUS apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsGetVersion( servername, Version );

    NET_REMOTE_RPC_FAILED(
            "I_NetDfsGetVersion",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END

    return(apiStatus);

}


NET_API_STATUS NET_API_FUNCTION
I_NetDfsCreateLocalPartition (
    IN  LPWSTR                          servername,
    IN  LPWSTR                          ShareName,
    IN  LPGUID                          EntryUid,
    IN  LPWSTR                          EntryPrefix,
    IN  LPWSTR                          ShortName,
    IN  LPNET_DFS_ENTRY_ID_CONTAINER    RelationInfo,
    IN  BOOL                            Force
    )
{
    NET_API_STATUS  apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsCreateLocalPartition (
                        servername,
                        ShareName,
                        EntryUid,
                        EntryPrefix,
                        ShortName,
                        RelationInfo,
                        Force
                        );

    NET_REMOTE_RPC_FAILED(
            "NetDfsCreateLocalPartition",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END

    return(apiStatus);

}

NET_API_STATUS NET_API_FUNCTION
I_NetDfsDeleteLocalPartition (
    IN  LPWSTR  servername OPTIONAL,
    IN  LPGUID  Uid,
    IN  LPWSTR  Prefix
    )
{
    NET_API_STATUS  apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsDeleteLocalPartition (
                        servername,
                        Uid,
                        Prefix
                        );

    NET_REMOTE_RPC_FAILED(
            "NetDfsDeleteLocalPartition",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END;

    return apiStatus;
}

NET_API_STATUS NET_API_FUNCTION
I_NetDfsSetLocalVolumeState (
    IN  LPWSTR  servername OPTIONAL,
    IN  LPGUID  Uid,
    IN  LPWSTR  Prefix,
    IN  ULONG   State
    )
{
    NET_API_STATUS  apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsSetLocalVolumeState (
                        servername,
                        Uid,
                        Prefix,
                        State
                        );

    NET_REMOTE_RPC_FAILED(
            "NetDfsSetLocalVolumeState",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END;

    return apiStatus;
}

NET_API_STATUS NET_API_FUNCTION
I_NetDfsSetServerInfo (
    IN  LPWSTR  servername OPTIONAL,
    IN  LPGUID  Uid,
    IN  LPWSTR  Prefix
    )
{
    NET_API_STATUS  apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsSetServerInfo (
                        servername,
                        Uid,
                        Prefix
                        );

    NET_REMOTE_RPC_FAILED(
            "NetDfsSetServerInfo",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END;

    return apiStatus;
}

NET_API_STATUS NET_API_FUNCTION
I_NetDfsCreateExitPoint (
    IN  LPWSTR  servername OPTIONAL,
    IN  LPGUID  Uid,
    IN  LPWSTR  Prefix,
    IN  ULONG   Type,
    IN  ULONG   ShortPrefixSize,
    OUT LPWSTR  ShortPrefix
    )
{
    NET_API_STATUS  apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsCreateExitPoint (
                        servername,
                        Uid,
                        Prefix,
                        Type,
                        ShortPrefixSize,
                        ShortPrefix
                        );

    NET_REMOTE_RPC_FAILED(
            "NetDfsCreateExitPoint",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END;

    return apiStatus;
}

NET_API_STATUS NET_API_FUNCTION
I_NetDfsDeleteExitPoint (
    IN  LPWSTR  servername OPTIONAL,
    IN  LPGUID  Uid,
    IN  LPWSTR  Prefix,
    IN  ULONG   Type
    )
{
    NET_API_STATUS  apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsDeleteExitPoint (
                        servername,
                        Uid,
                        Prefix,
                        Type
                        );

    NET_REMOTE_RPC_FAILED(
            "NetDfsDeleteExitPoint",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END;

    return apiStatus;
}

NET_API_STATUS NET_API_FUNCTION
I_NetDfsModifyPrefix (
    IN  LPWSTR  servername OPTIONAL,
    IN  LPGUID  Uid,
    IN  LPWSTR  Prefix
    )
{
    NET_API_STATUS  apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsModifyPrefix (
                        servername,
                        Uid,
                        Prefix
                        );

    NET_REMOTE_RPC_FAILED(
            "NetDfsModifyPrefix",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END;

    return apiStatus;
}

NET_API_STATUS NET_API_FUNCTION
I_NetDfsFixLocalVolume (
    IN  LPWSTR                          servername OPTIONAL,
    IN  LPWSTR                          VolumeName,
    IN  ULONG                           EntryType,
    IN  ULONG                           ServiceType,
    IN  LPWSTR                          StgId,
    IN  LPGUID                          EntryUid,       // unique id for this partition
    IN  LPWSTR                          EntryPrefix,    // path prefix for this partition
    IN  LPNET_DFS_ENTRY_ID_CONTAINER    RelationInfo,
    IN  ULONG                           CreateDisposition
    )
{
    NET_API_STATUS  apiStatus;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsFixLocalVolume (
                        servername,
                        VolumeName,
                        EntryType,
                        ServiceType,
                        StgId,
                        EntryUid,
                        EntryPrefix,
                        RelationInfo,
                        CreateDisposition
                        );

    NET_REMOTE_RPC_FAILED(
            "NetDfsFixLocalVolume",
            servername,
            apiStatus,
            NET_REMOTE_FLAG_NORMAL,
            SERVICE_SERVER)

        apiStatus = ERROR_NOT_SUPPORTED;

    NET_REMOTE_END;

    return apiStatus;
}

NET_API_STATUS NET_API_FUNCTION
I_NetDfsManagerReportSiteInfo (
    IN  LPWSTR                          ServerName,
    OUT LPDFS_SITELIST_INFO            *ppSiteInfo
    )
{
    struct sockaddr_in Destination;
    struct hostent * pHostEnt;
    SOCKET_ADDRESS SocketAddress;
    NET_API_STATUS  apiStatus;
    LPWSTR *SiteName = NULL;
    char* ServerNameA = NULL;

    NET_REMOTE_TRY_RPC

        apiStatus = NetrDfsManagerReportSiteInfo (
                        ServerName,
                        ppSiteInfo
                        );



    NET_REMOTE_RPC_FAILED(
             "NetDfsMangerReportSiteInfo",
             ServerName,
             apiStatus,
             NET_REMOTE_FLAG_NORMAL,
             SERVICE_SERVER)


    NET_REMOTE_END;


    if(apiStatus != ERROR_SUCCESS) {
        WORD wVersionRequested;
        WSADATA wsaData;
        DWORD dwErr = ERROR_SUCCESS;
        int err;
        PDOMAIN_CONTROLLER_INFO pDCInfo;

        wVersionRequested = MAKEWORD( 2, 2 );
 
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            /* We could not find a usable */
            /* WinSock DLL.                                  */
            return apiStatus;
        }
 
 

        // we couldn't get the site name
        ServerNameA = malloc(wcslen(ServerName) + 1);

        if(ServerNameA == NULL) {
            apiStatus = ERROR_NOT_ENOUGH_MEMORY;
        } else {
            // need to convert from WCHAR* to char*
            sprintf(ServerNameA, "%ws", ServerName);

            if ((pHostEnt = gethostbyname(ServerNameA)) != NULL) {
                memcpy(&(Destination.sin_addr), pHostEnt->h_addr, pHostEnt->h_length);
                Destination.sin_family = pHostEnt->h_addrtype;

                if(pHostEnt->h_addrtype != AF_INET) {
                    apiStatus = ERROR_NOT_SUPPORTED;
                } else {
                    SocketAddress.lpSockaddr = (struct sockaddr *)&Destination;
                    SocketAddress.iSockaddrLength = sizeof(Destination);
                    Destination.sin_port = 0;
                    
                    dwErr = DsGetDcName(
                        NULL,                            // Computer to remote to
                        NULL,                            // Domain - use local domain
                        NULL,                            // Domain Guid
                        NULL,                            // Site Guid
                        0,                 // Flags
                        &pDCInfo);

                    if(dwErr == ERROR_SUCCESS) {
                        apiStatus = DsAddressToSiteNames(pDCInfo->DomainControllerAddress,
                                                         1,
                                                         &SocketAddress,
                                                         &SiteName
                                                         );

                        NetApiBufferFree( pDCInfo );
                    } else {
                        apiStatus = ERROR_NOT_SUPPORTED;
                    }

                    if(apiStatus == NO_ERROR) {
			if((SiteName == NULL) || (*SiteName == NULL)) {
			    // If DsAddressToSiteNames can't map to a site name,
			    // it returns success but sets the buffer to NULL.
			    apiStatus = ERROR_NO_SITENAME;
			} else {
			    // we got the site name
			    apiStatus = NetApiBufferAllocate(
				sizeof(DFS_SITELIST_INFO) + ((wcslen(*SiteName) + 1) * sizeof(WCHAR)),
				ppSiteInfo
				);

			    if(apiStatus == ERROR_SUCCESS) {
				(*ppSiteInfo)->cSites = 1;
				(*ppSiteInfo)->Site[0].SiteName = (LPWSTR)((ULONG_PTR)(*ppSiteInfo) + sizeof(DFS_SITELIST_INFO));
				wcscpy((*ppSiteInfo)->Site[0].SiteName, *SiteName);
			    }
			}
                    }
                } 
            } else {
                apiStatus = WSAGetLastError();
                apiStatus = ERROR_NOT_SUPPORTED;
            }
            free(ServerNameA);

        }

        WSACleanup();
    }


    return apiStatus;
}



#include    <dsgetdc.h>
#include    <winldap.h>
#include    <lmapibuf.h>

//
// This is the container which holds the DFS configuration data
//
static const WCHAR DfsConfigContainer[] = L"CN=Dfs-Configuration,CN=System";

typedef struct
{
    int         cPieces;
    PCHAR       rpPieces[1];
} DNS_NAME, *PDNS_NAME;

static
DWORD
BreakDnsName(
    IN  CHAR        *pName,
    OUT PDNS_NAME   *ppDnsName
    )

/*++

Routine Description:

    Breaks a DNS name in dotted string format (eg: dbsd.microsoft.com) into
    its constituent parts.

Arguments:

    pName - pointer to string representing dotted DNS name to break.

    ppDnsName - pointer to pointer to DNS_NAME struct which should be
        deallocated by NetApiBufferFree().
--*/

{
    int     cPieces;
    CHAR    *p;
    DWORD   cBytes;
    CHAR    *buffer;
    int     i;
    LPSTR   seps = ".";

    if ( (NULL == pName) || ('\0' == *pName) || ('.' == *pName) )
    {
        return(ERROR_INVALID_PARAMETER);
    }

    // Count number of pieces so we can figure out how much to allocate.

    cPieces = 1;
    p = pName;

    for ( p = pName; '\0' != *p; p++ )
    {
        if ( '.' == *p )
        {
            cPieces++;
        }
    }

    // Calculate bytes to allocate.  Allocate memory which will hold (in order)
    // the DNS_NAME struct, the DNS_NAME.rpPieces pointer array, and finally
    // a scratch buffer where we can strtok the input name.

    cBytes = sizeof(DNS_NAME);
    cBytes += cPieces * sizeof(PCHAR);
    cBytes += strlen(pName) + 1;

    NetApiBufferAllocate( cBytes, (PVOID *)ppDnsName );

    if ( *ppDnsName == NULL )
    {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    // Fill in the buffer and call strtok as often as required to chop it
    // into pieces filling the DNS_NAME as we go.

    buffer = (CHAR *) &((*ppDnsName)->rpPieces[cPieces]);
    strcpy(buffer, pName);

    (*ppDnsName)->cPieces = cPieces;
    (*ppDnsName)->rpPieces[0] = strtok(buffer, seps);

    for ( i = 1; i < cPieces; i++ )
    {
        (*ppDnsName)->rpPieces[i] = strtok(NULL, seps);
    }

    return(NO_ERROR);
}

static
DWORD
FindContext(
    IN  CHAR    *pName,
    IN  int     cDnValues,
    IN  CHAR    **rpDnValues,
    OUT int     *pMatchingValueIndex
    )

/*++

Routine Description:

    Determines the best match of a DNS name (eg: dbsd.microsoft.com)
    to a set of RFC 1779 DNs (eg: ou=dbsd, ou=microsoft, c=us).  We assume
    that the array of DNs represent NT5 DS naming contexts (i.e. domains)
    which is true with the exception of the Configuration naming context.
    For example, let's say a DC hosted three naming contexts:

        1 - ou=dbsd, ou=microsoft, c=us
        2 - ou=nt, ou=dbsd, ou=microsoft, c=us
        3 - ou=configuration, ou=microsoft, c=us

    Then dbsd.microsoft.com would match the 1st DN in the list.  This is not
    foolproof in the case of a deviant namespace which has a domain structure
    like:

        ou=dbsd, ou=microsoft, ou=com, ou=dbsd, ou=microsoft, c=us

    But anyone with a namespace like that is going to have other problems
    anyway.

Arguments:

    pName - pointer to DNS name to match.

    cDnValues - count of values in rpDnValues.

    rpDnValues - array of pointers to DNs to match against.

    pMatchingValueIndex - pointer to int which will identify the best
        matching DN in rpDnValues on successful return.

Return Value:

    NO_ERROR                        - success
    ERROR_NOT_ENOUGH_MEMORY         - allocation error
    ERROR_INVALID_PARAMETER         - invalid parameter
    ERROR_INVALID_DOMAINNAME        - bad DNS or DN domain name

--*/
{
    DWORD       dwErr;
    int         i, j;
    CHAR        **rpDn = NULL;
    int         currentMatchLength;
    int         bestMatchLength;
    int         bestMatchIndex;
    PDNS_NAME   pDomainDnsName = NULL;

    dwErr = BreakDnsName(pName, &pDomainDnsName);

    if ( NO_ERROR != dwErr )
    {
        return(dwErr);
    }

    // Iterate over the DN values and see which one has the longest match.

    bestMatchIndex = 0;
    bestMatchLength = -1;

    for ( i = 0; i < cDnValues; i++ )
    {
        rpDn = ldap_explode_dn(rpDnValues[i], 1);   // 1 ==> notypes

        if ( NULL == rpDn )
        {
            dwErr = ERROR_INVALID_DOMAINNAME;
            goto Cleanup;
        }

        currentMatchLength = 0;

        // Try to match each piece of the domain name to each piece of the
        // DN.  Fortunately, RFC 1779 DNs are ordered least to most significant
        // just as DNS domain names are.  rpDn[] is "terminated" with a NULL.

        for ( j = 0; (j < pDomainDnsName->cPieces) && (NULL != rpDn[j]); j++ )
        {
            if ( 0 == _stricmp(pDomainDnsName->rpPieces[j], rpDn[j]) )
            {
                currentMatchLength++;
            }
        }

        if ( (0 != currentMatchLength) &&
             (currentMatchLength > bestMatchLength) )
        {
            bestMatchLength = currentMatchLength;
            bestMatchIndex = i;
        }

        ldap_value_free(rpDn);
    }

    *pMatchingValueIndex = bestMatchIndex;
    dwErr = NO_ERROR;

Cleanup:

    if ( pDomainDnsName != NULL ) {
        NetApiBufferFree( pDomainDnsName );
    }

    return(dwErr);
}

/*
 * This API returns a vector of \\server\share combinations which form the
 *  root of a Fault Tolerant DFS.  This  null-terminated vector should be
 *  freed by the caller with NetApiBufferFree().
 *
 * If pLDAP is supplied, we asssume that this is the handle to the DS server
 *  holding the configuration data.  Else, we use wszDomainName to locate the
 *  proper DS server.
 *
 * wszDfsName is the name of the fault tolerant DFS for which individual servers
 *  are to be discovered.
 *
 */
NET_API_STATUS NET_API_FUNCTION
I_NetDfsGetFtServers(
    IN PVOID  LdapInputArg OPTIONAL,
    IN LPWSTR wszDomainName OPTIONAL,
    IN LPWSTR wszDfsName OPTIONAL,
    OUT LPWSTR **List
    )
{
    PLDAP pLDAP = (PLDAP)LdapInputArg;
    BOOLEAN bUnbindNeeded = FALSE;
    DWORD dwErr;
    NTSTATUS status;
    PWCHAR attrs[2];
    LDAPMessage *pMsg = NULL;
    LDAPMessage *pEntry = NULL;
    WCHAR *pAttr = NULL;
    WCHAR **rpValues = NULL;
    WCHAR **allValues = NULL;
    WCHAR ***rpValuesToFree = NULL;
    INT cValues = 0;
    INT i;
    WCHAR *dfsDn = NULL;
    DWORD len;
    USHORT cChar;
    PWCHAR *resultVector;
    ULONG cBytes;

    if (List == NULL) {
        return ERROR_INVALID_PARAMETER;
    }

    *List = NULL;

    if (!ARGUMENT_PRESENT(pLDAP)) {

        DOMAIN_CONTROLLER_INFO *pInfo = NULL;
        ULONG dsAdditionalFlags = 0;
        ULONG retry;

        for (retry = 0; pLDAP == NULL && retry < 2; retry++) {

            //
            // Find a DC for the given domain.
            //
            dwErr = DsGetDcName(
                        NULL,                       // computer name
                        wszDomainName,              // DNS domain name
                        NULL,                       // domain guid
                        NULL,                       // site guid
                        DS_DIRECTORY_SERVICE_REQUIRED |
                            DS_IP_REQUIRED |
                            dsAdditionalFlags,
                        &pInfo);

            if (dwErr != NO_ERROR) {
                return dwErr;
            }

            //
            // DomainControllerAddress is prefixed with "\\" so
            // aditionally ensure there's some useful data there.
            //

            if (DS_INET_ADDRESS != pInfo->DomainControllerAddressType ||
                 (cChar = (USHORT)wcslen(pInfo->DomainControllerAddress)) < 3) {

                NetApiBufferFree(pInfo);
                return ERROR_NO_SUCH_DOMAIN;
            }

            //
            // Try to connect to the DS server on the DC
            //

            pLDAP = ldap_openW(&pInfo->DomainControllerAddress[2], 0);

            if (pLDAP == NULL) {
                //
                // Couldn't connect.  Let's force rediscovery and see if we
                //  can connect to a DC which is working!
                //
                NetApiBufferFree(pInfo);
                dsAdditionalFlags |= DS_FORCE_REDISCOVERY;

            } else {

              dwErr = ldap_bind_s(pLDAP, NULL, NULL, LDAP_AUTH_SSPI);

            }

            NetApiBufferFree(pInfo);

        }

        if (pLDAP == NULL || dwErr != LDAP_SUCCESS) {
            return ERROR_PATH_NOT_FOUND;
        }

        bUnbindNeeded = TRUE;

    }

    //
    // Read the namingContexts operational attribute.
    //

    pLDAP->ld_sizelimit = 0;                    // no search limit
    pLDAP->ld_timelimit = 0;                    // no time limit
    pLDAP->ld_deref = LDAP_DEREF_NEVER;

    attrs[0] = L"defaultnamingContext";
    attrs[1] = NULL;

    if ((dwErr = ldap_search_sW(
                    pLDAP,
                    L"",                         // search base
                    LDAP_SCOPE_BASE,
                    L"(objectClass=*)",          // filter
                    attrs,
                    0,                          // attrs and values
                    &pMsg)) != LDAP_SUCCESS) {

        goto Cleanup;

    }

    //
    // Make sure we got back something reasonable
    //
    if (ldap_count_entries(pLDAP, pMsg) != 1 ||
        (pEntry = ldap_first_entry(pLDAP, pMsg)) == NULL ||
        (rpValues = ldap_get_valuesW(pLDAP, pEntry, attrs[0])) == NULL ||
        (cValues = ldap_count_valuesW(rpValues)) == 0
    ) {

        dwErr = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }

    if (ARGUMENT_PRESENT(wszDfsName)) {

        //
        // Looks good.  Allocate enough memory to hold the DN of the
        // DFS configuration data for the fault tolerant DFS in question
        //

        len = (DWORD)(3 * sizeof(WCHAR) +
                (wcslen(wszDfsName) + 1) * sizeof(WCHAR) +
                    (wcslen(DfsConfigContainer) + 1) * sizeof(WCHAR) +
                        (wcslen(rpValues[0]) + 1) * sizeof(WCHAR));

        dwErr = NetApiBufferAllocate(len, (PVOID *)&dfsDn);

        if (dfsDn == NULL) {
            dwErr = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        //
        // Construct the DN
        //

        RtlZeroMemory(dfsDn, len);
        wcscpy(dfsDn, L"CN=");
        wcscat(dfsDn, wszDfsName);
        wcscat(dfsDn, L",");
        wcscat(dfsDn, DfsConfigContainer);
        wcscat(dfsDn, L",");
        wcscat(dfsDn, rpValues[0]);

        //
        // Now see if we can get at the 'remoteServerName' property of this object.
        //  This property holds the names of the servers hosting this DFS
        //

        pLDAP->ld_sizelimit = 0;
        pLDAP->ld_timelimit= 0;
        pLDAP->ld_deref = LDAP_DEREF_NEVER;

        ldap_msgfree(pMsg);
        pMsg = NULL;

        ldap_value_freeW(rpValues);
        rpValues = NULL;

        attrs[0] = L"remoteServerName";
        attrs[1] = NULL;

        dwErr = ldap_search_sW(
                            pLDAP,
                            dfsDn,
                            LDAP_SCOPE_BASE,
                            L"(objectClass=*)",
                            attrs,
                            0,
                            &pMsg);

        //
        // Make sure the result is reasonable
        //
        if (ldap_count_entries(pLDAP, pMsg) == 0 ||
            (pEntry = ldap_first_entry(pLDAP, pMsg)) == NULL ||
            (rpValues = ldap_get_valuesW(pLDAP, pEntry, attrs[0])) == NULL ||
            rpValues[0][0] == L'\0'
        ) {

            dwErr = ERROR_PATH_NOT_FOUND;
            goto Cleanup;
        }

        //
        // The result is reasonable, just point allValues to rpValues
        //

        allValues = rpValues;

    } else {

        //
        // The caller is trying to retrieve the names of all the FT DFSs in the domain
        //
        // Allocate enough memory to hold the DN of the
        // DFS configuration container
        //

        len = (wcslen(DfsConfigContainer) + 1) * sizeof(WCHAR) +
                (wcslen(rpValues[0]) + 1) * sizeof(WCHAR);

        dwErr = NetApiBufferAllocate(len, (PVOID *)&dfsDn);

        if (dfsDn == NULL) {
            dwErr = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        //
        // Construct the DN
        //

        RtlZeroMemory(dfsDn, len);
        wcscpy(dfsDn, DfsConfigContainer);
        wcscat(dfsDn, L",");
        wcscat(dfsDn, rpValues[0]);

        //
        // Now see if we can enumerate the objects below this one.  The names
        //   of these objects will be the different FT dfs's available
        //
        pLDAP->ld_sizelimit = 0;
        pLDAP->ld_timelimit= 0;
        pLDAP->ld_deref = LDAP_DEREF_NEVER;

        ldap_msgfree(pMsg);
        pMsg = NULL;

        ldap_value_freeW(rpValues);
        rpValues = NULL;

        attrs[0] = L"CN";
        attrs[1] = NULL;

        dwErr = ldap_search_sW(
                            pLDAP,
                            dfsDn,
                            LDAP_SCOPE_ONELEVEL,
                            L"(objectClass=fTDfs)",
                            attrs,
                            0,
                            &pMsg);

        //
        // Make sure the result is reasonable
        //
        if (
            ((cValues = ldap_count_entries(pLDAP, pMsg)) == 0) ||
             (pEntry = ldap_first_entry(pLDAP, pMsg)) == NULL
        ) {
            dwErr = ERROR_PATH_NOT_FOUND;
            goto Cleanup;
        }

        //
        // The search for all FTDfs's returns multiple entries, each with
        // one value for the object's CN. Coalesce these into a single array.
        //

        dwErr = NetApiBufferAllocate(2 * (cValues + 1) * sizeof(PWSTR), (PVOID *)&allValues);

        if (dwErr != ERROR_SUCCESS) {
            goto Cleanup;
        }

        rpValuesToFree = (WCHAR ***) &allValues[cValues + 1];

        for (i = 0; (i < cValues) && (dwErr == ERROR_SUCCESS); i++) {

            rpValues = ldap_get_valuesW(pLDAP, pEntry, attrs[0]);
            rpValuesToFree[i] = rpValues;
            //
            // Sanity check
            //
            if (ldap_count_valuesW(rpValues) == 0 || rpValues[0][0] == L'\0') {
                dwErr = ERROR_PATH_NOT_FOUND;
            } else {
                allValues[i] = rpValues[0];
                pEntry = ldap_next_entry(pLDAP, pEntry);
            }

        }

        if (dwErr == ERROR_SUCCESS) {
            allValues[i] = NULL;
            rpValuesToFree[i] = NULL;
        } else {
            goto Cleanup;
        }

    }

    if (dwErr != LDAP_SUCCESS) {
        dwErr = ERROR_PATH_NOT_FOUND;
        goto Cleanup;
    }

    //
    // Now we need to allocate the memory to hold this vector and return the results.
    //
    // First see how much space we need
    //

    for (len = cValues = 0; allValues[cValues]; cValues++) {
        len += sizeof(LPWSTR) + (wcslen(allValues[cValues]) + 1) * sizeof(WCHAR);
    }
    len += sizeof(LPWSTR);        // for the final NULL pointer

    dwErr = NetApiBufferAllocate(len, (PVOID *)&resultVector); 

    if (dwErr == NO_ERROR) {

        LPWSTR pstr = (LPWSTR)((PCHAR)resultVector + (cValues + 1) * sizeof(LPWSTR));
        ULONG slen;

        RtlZeroMemory(resultVector, len);

        len -= (cValues+1) * sizeof(LPWSTR);

        for (cValues = 0; allValues[cValues] && len >= sizeof(WCHAR); cValues++) {

            resultVector[cValues] = pstr;
            wcscpy(pstr, allValues[cValues]);
            slen = wcslen(allValues[cValues]);
            pstr += slen + 1;
            len -= (slen + 1) * sizeof(WCHAR);

        }

    }

    if (dwErr == NO_ERROR) {
        *List = resultVector;
    }

Cleanup:

    if (ARGUMENT_PRESENT(wszDfsName)) {
        if (rpValues != NULL) {
            ldap_value_freeW(rpValues);
        }
    } else {
        if (rpValuesToFree != NULL) {
            for (i = 0; rpValuesToFree[i] != NULL; i++) {
                ldap_value_freeW(rpValuesToFree[i]);
            }
        }
        if (allValues != NULL) {
            NetApiBufferFree(allValues);
        }
    }

    if (pMsg != NULL) {
        ldap_msgfree(pMsg);
    }

    if (dfsDn != NULL) {
        NetApiBufferFree(dfsDn);
    }

    if (pLDAP != NULL && bUnbindNeeded == TRUE) {
        ldap_unbind(pLDAP);
    }

    return dwErr;
}
