/*++ 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 // DbgPrint prototype #include // DbgPrint #include // DataTypes and runtime APIs #include // generated by the MIDL complier #include // NET_API_STATUS #include // (needed by netrpc.h) #include // (needed by netrpc.h) #include // (needed by netrpc.h) #include // NetError codes #include // NET_REMOTE_ macros. #include #include #include #include #include #include #include #include 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 #include #include // // 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; }