|
|
//++
//
// Copyright (C) Microsoft Corporation, 1987 - 1999
//
// Module Name:
//
// ldaptest.c
//
// Abstract:
//
// Queries into network drivers
//
// Author:
//
// Anilth - 4-20-1998
//
// Environment:
//
// User mode only.
// Contains NT-specific code.
//
// Revision History:
//
//--
#include "precomp.h"
#include "malloc.h"
BOOL TestLdapOnDc(IN PTESTED_DC TestedDc, NETDIAG_PARAMS* pParams, NETDIAG_RESULT* pResults); DWORD TestSpnOnDC(IN PTESTED_DC pDcInfo, NETDIAG_RESULT* pResults);
HRESULT LDAPTest( NETDIAG_PARAMS* pParams, NETDIAG_RESULT* pResults) { HRESULT hr = S_OK;
BOOL RetVal = TRUE; PTESTED_DOMAIN TestedDomain = pParams->pDomain; PTESTED_DC TestedDc = NULL; PLIST_ENTRY ListEntry; BOOLEAN OneLdapFailed = FALSE; BOOLEAN OneLdapWorked = FALSE; BOOL fSpnTested = FALSE; BOOL fSpnPassed = FALSE;
NET_API_STATUS NetStatus;
//if the machine is a member machine or DC, LDAP Test will get called.
//Otherwise, the test will be skipped
pResults->LDAP.fPerformed = TRUE;
// assume link entry is initialized to 0000
if(pResults->LDAP.lmsgOutput.Flink == NULL) InitializeListHead(&pResults->LDAP.lmsgOutput);
PrintStatusMessage(pParams, 4, IDS_LDAP_STATUS_MSG, TestedDomain->PrintableDomainName);
//
// If a DC hasn't been discovered yet,
// find one.
//
if ( TestedDomain->DcInfo == NULL ) { LPTSTR pszDcType;
if ( TestedDomain->fTriedToFindDcInfo ) { CHK_HR_CONTEXT(pResults->LDAP, S_FALSE, IDS_LDAP_NODC); }
pszDcType = LoadAndAllocString(IDS_DCTYPE_DC); NetStatus = DoDsGetDcName( pParams, pResults, &pResults->LDAP.lmsgOutput, TestedDomain, DS_DIRECTORY_SERVICE_PREFERRED, "DC", FALSE, &TestedDomain->DcInfo ); Free(pszDcType);
TestedDomain->fTriedToFindDcInfo = TRUE;
if ( NetStatus != NO_ERROR ) { CHK_HR_CONTEXT(pResults->LDAP, hr = HRESULT_FROM_WIN32(NetStatus), IDS_LDAP_NODC); } }
//
// Ensure the DC is running the Ds.
//
if ( (TestedDomain->DcInfo->Flags & DS_DS_FLAG) == 0 ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_NOTRUNNINGDS, TestedDomain->DcInfo->DomainControllerName); }
//
// Test ldap on all of the found DCs in the domain.
//
for ( ListEntry = TestedDomain->TestedDcs.Flink ; ListEntry != &TestedDomain->TestedDcs ; ListEntry = ListEntry->Flink ) {
//
// Loop through the list of DCs in this domain
//
TestedDc = CONTAINING_RECORD( ListEntry, TESTED_DC, Next );
//
// Only run test on DCs that might support LDAP.
//
if ( TestedDc->Flags & DC_IS_NT5 ) { if ( !TestLdapOnDc( TestedDc, pParams, pResults ) ) OneLdapFailed = TRUE; else OneLdapWorked = TRUE;
//test the SPN registration if this is a DC on the primary domain
if (TestedDomain->fPrimaryDomain) { fSpnTested = TRUE; if (TestSpnOnDC(TestedDc, pResults)) fSpnPassed = TRUE; } } else { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0, IDS_LDAP_NOTRUNNINGDS_SKIP, TestedDc->ComputerName); }
}
//
// If one of the DCs failed,
// and none worked.
// Don't do any more tests.
//
if ( OneLdapFailed && !OneLdapWorked ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_NOLDAPSERVERSWORK, TestedDomain->PrintableDomainName); CHK_HR_CONTEXT(pResults->LDAP, hr = E_FAIL, 0); }
if ( fSpnTested && !fSpnPassed ) { //IDS_LDAP_NO_SPN " [FATAL] The default SPNs are not properly registered on and DCs.\n"
AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_NO_SPN); CHK_HR_CONTEXT(pResults->LDAP, hr = E_FAIL, 0); }
L_ERR:
//$REVIEW (nsun) we should return S_FALSE or S_OK
//so that we can go on with other tests
if (!FHrOK(hr)) hr = S_FALSE; return hr; }
DWORD TestSpnOnDC(IN PTESTED_DC pDcInfo, NETDIAG_RESULT* pResults) { WCHAR FlatName[ MAX_PATH + 1 ]; PWSTR Dot ; HANDLE hDs = NULL; ULONG NetStatus ; PDS_NAME_RESULTW Result ; LPWSTR Flat = FlatName; LDAP *ld = NULL; int rc; LDAPMessage *e, *res; WCHAR *base_dn; WCHAR *search_dn, search_ava[ MAX_PATH + 30 ]; WCHAR Domain[ MAX_PATH + 1 ]; CHAR szDefaultFqdnSpn[MAX_PATH + 10]; CHAR szDefaultShortSpn[MAX_PATH + 10]; BOOL fFqdnSpnFound = FALSE; BOOL fShortSpnFound = FALSE; BOOL fFailQuerySpn = FALSE; USES_CONVERSION;
//construct the default SPN's
lstrcpy(szDefaultFqdnSpn, _T("HOST/")); lstrcat(szDefaultFqdnSpn, pResults->Global.szDnsHostName); lstrcpy(szDefaultShortSpn, _T("HOST/")); lstrcat(szDefaultShortSpn, W2T(pResults->Global.swzNetBiosName));
wcscpy(Domain, GetSafeStringW(pDcInfo->TestedDomain->DnsDomainName ? pDcInfo->TestedDomain->DnsDomainName : pDcInfo->TestedDomain->NetbiosDomainName));
ld = ldap_open(W2A(pDcInfo->ComputerName), LDAP_PORT); if (ld == NULL) { DebugMessage2("ldap_init failed = %x", LdapGetLastError()); fFailQuerySpn = TRUE; goto L_ERROR; }
rc = ldap_bind_s(ld, NULL, NULL, LDAP_AUTH_NEGOTIATE); if (rc != LDAP_SUCCESS) { DebugMessage2("ldap_bind failed = %x", LdapGetLastError()); fFailQuerySpn = TRUE; goto L_ERROR;
}
NetStatus = DsBindW( NULL, Domain, &hDs ); if ( NetStatus != 0 ) { DebugMessage3("Failed to bind to DC of domain %ws, %#x\n", Domain, NetStatus ); fFailQuerySpn = TRUE; goto L_ERROR; }
Dot = wcschr( Domain, L'.' );
if ( Dot ) { *Dot = L'\0'; } wcscpy( FlatName, Domain );
if ( Dot ) { *Dot = L'.' ; }
wcscat( FlatName, L"\\" ); wcscat( FlatName, pResults->Global.swzNetBiosName ); wcscat( FlatName, L"$" );
NetStatus = DsCrackNamesW( hDs, 0, DS_NT4_ACCOUNT_NAME, DS_FQDN_1779_NAME, 1, &Flat, &Result );
if ( NetStatus != 0) { DebugMessage3("Failed to crack name %ws into the FQDN, %#x\n", FlatName, NetStatus );
DsUnBind( &hDs );
fFailQuerySpn = TRUE; goto L_ERROR; }
search_dn = pResults->Global.swzNetBiosName;
if (0 == Result->cItems) { DsUnBind( &hDs );
fFailQuerySpn = TRUE; goto L_ERROR; }
if (DS_NAME_NO_ERROR != Result->rItems[0].status || NULL == Result->rItems[0].pName) { DsUnBind( &hDs );
fFailQuerySpn = TRUE; goto L_ERROR; }
base_dn = wcschr(Result->rItems[0].pName, L',');
if (!base_dn) base_dn = Result->rItems[0].pName; else base_dn++; DsUnBind( &hDs );
swprintf(search_ava, L"(sAMAccountName=%s$)", search_dn);
rc = ldap_search_s(ld, W2A(base_dn), LDAP_SCOPE_SUBTREE, W2A(search_ava), NULL, 0, &res); //base_dn can no longer be used because base_dn refers to that buffer
DsFreeNameResultW( Result );
if (rc != LDAP_SUCCESS) { DebugMessage2("ldap_search_s failed: %s", ldap_err2string(rc)); fFailQuerySpn = TRUE; goto L_ERROR; }
for (e = ldap_first_entry(ld, res); e; e = ldap_next_entry(ld, e)) { BerElement *b; CHAR *attr; //IDS_LDAP_REG_SPN "Registered Service Principal Names:\n"
AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0, IDS_LDAP_REG_SPN); for (attr = ldap_first_attribute(ld, e, &b); attr; attr = ldap_next_attribute(ld, e, b)) { CHAR **values, **p; values = ldap_get_values(ld, e, attr); for (p = values; *p; p++) { if (strcmp(attr, "servicePrincipalName") == 0) { // IDS_LDAP_SPN_NAME " %s\n"
AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0, IDS_LDAP_SPN_NAME, *p); if (0 == _stricmp(*p, szDefaultFqdnSpn)) fFqdnSpnFound = TRUE; else if (0 == _stricmp(*p, szDefaultShortSpn)) fShortSpnFound = TRUE; } } ldap_value_free(values); ldap_memfree(attr); }
}
ldap_msgfree(res);
L_ERROR: if (ld) { ldap_unbind(ld); }
//Only report fatal error when we successfully query SPN registration
//and all DCs doesn't have the default SPN's
if (fFailQuerySpn) { //IDS_LDAP_SPN_FAILURE "Failed to query SPN registration from DC %ws.\n"
AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_SPN_FAILURE, pDcInfo->ComputerName); return TRUE; } else if (!fFqdnSpnFound || !fShortSpnFound) { //IDS_LDAP_SPN_MISSING " [WARNING] The default SPN registration for '%s' is missing on DC '%ws'.\n"
if (!fFqdnSpnFound) AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_SPN_MISSING, szDefaultFqdnSpn, pDcInfo->ComputerName);
if (!fShortSpnFound) AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_SPN_MISSING, szDefaultShortSpn, pDcInfo->ComputerName);
return FALSE; } else return TRUE; }
void LDAPGlobalPrint(IN NETDIAG_PARAMS *pParams, IN OUT NETDIAG_RESULT *pResults) { if (pParams->fVerbose || !FHrOK(pResults->LDAP.hr)) { PrintNewLine(pParams, 2); PrintTestTitleResult(pParams, IDS_LDAP_LONG, IDS_LDAP_SHORT, pResults->LDAP.fPerformed, pResults->LDAP.hr, 0); if (!FHrOK(pResults->LDAP.hr)) { if(pResults->LDAP.idsContext) PrintError(pParams, pResults->LDAP.idsContext, pResults->LDAP.hr); }
PrintMessageList(pParams, &pResults->LDAP.lmsgOutput); }
}
void LDAPPerInterfacePrint(IN NETDIAG_PARAMS *pParams, IN OUT NETDIAG_RESULT *pResults, IN INTERFACE_RESULT *pIfResult) { // no perinterface information
}
void LDAPCleanup(IN NETDIAG_PARAMS *pParams, IN OUT NETDIAG_RESULT *pResults) { MessageListCleanUp(&pResults->LDAP.lmsgOutput); pResults->LDAP.lmsgOutput.Flink = NULL; }
BOOL TestLdapOnDc( IN PTESTED_DC TestedDc,NETDIAG_PARAMS* pParams, NETDIAG_RESULT* pResults ) /*++
Routine Description:
Ensure we can use LDAP focused at the specified DC
Arguments:
TestedDc - Description of DC to test
Return Value:
TRUE: Test suceeded. FALSE: Test failed
--*/ { NET_API_STATUS NetStatus; NTSTATUS Status; BOOL RetVal = TRUE; int LdapMessageId; PLDAPMessage LdapMessage = NULL; PLDAPMessage CurrentEntry; int LdapError; ULONG AuthType; LPSTR AuthTypeName; LPWSTR DcIpAddress;
LDAP *LdapHandle = NULL;
//
// Avoid this test if the DC is already known to be down.
//
if ( TestedDc->Flags & DC_IS_DOWN ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0, IDS_LDAP_DCDOWN, TestedDc->ComputerName);
goto Cleanup; }
//
// If there is no IP Address,
// get it.
//
if ( !GetIpAddressForDc( TestedDc ) ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_NOIPADDR, TestedDc->ComputerName); RetVal = FALSE; goto Cleanup; }
DcIpAddress = TestedDc->DcIpAddress;
//
// Loop trying each type of authentication.
//
for ( AuthType = 0; AuthType < 3; AuthType++ ) { int AuthMethod; SEC_WINNT_AUTH_IDENTITY_W NtAuthIdentity; LPSTR AuthGuru;
//
// Bind as appropropriate
//
RtlZeroMemory( &NtAuthIdentity, sizeof(NtAuthIdentity)); NtAuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; switch ( AuthType ) { case 0: AuthTypeName = "un-"; break; case 1: AuthTypeName = "NTLM "; AuthMethod = LDAP_AUTH_NTLM; AuthGuru = NTLM_GURU; break; case 2: AuthTypeName = "Negotiate "; AuthMethod = LDAP_AUTH_NEGOTIATE; AuthGuru = KERBEROS_GURU; break;
}
//
// Only Members and Domain controllers can use authenticated RPC.
//
if ( AuthType != 0 ) { if ( pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleMemberWorkstation || pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleMemberServer || pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleBackupDomainController || pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RolePrimaryDomainController ) {
//
// If we're logged onto a local account,
// we can't test authenticated RPC.
//
if ( pResults->Global.pLogonDomain == NULL ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_LOGONASLOCALUSER, pResults->Global.pLogonDomainName, pResults->Global.pLogonUser, AuthTypeName, TestedDc->ComputerName); goto Cleanup; }
} else { goto Cleanup; } }
AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0, IDS_LDAP_DOAUTHEN, AuthTypeName, TestedDc->ComputerName);
//
// Cleanup from a previous iteration.
//
if ( LdapMessage != NULL ) { ldap_msgfree( LdapMessage ); LdapMessage = NULL; }
if ( LdapHandle != NULL ) { ldap_unbind( LdapHandle ); LdapHandle = NULL; }
//
// Connect to the DC.
//
LdapHandle = ldap_openW( DcIpAddress, LDAP_PORT );
if ( LdapHandle == NULL ) {
AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_CANNOTOPEN, TestedDc->ComputerName, DcIpAddress ); goto Cleanup; }
//
// Bind to the DC.
//
if ( AuthType != 0 ) { LdapError = ldap_bind_s( LdapHandle, NULL, (char *)&NtAuthIdentity, AuthMethod );
if ( LdapError != LDAP_SUCCESS ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_CANNOTBIND, AuthTypeName, TestedDc->ComputerName, ldap_err2stringA(LdapError) );
//
// Try other authentication methods.
//
RetVal = FALSE; continue; }
}
//
// Do a trivial search to isolate LDAP problems from authentication
// problems
//
LdapError = ldap_search_sA( LdapHandle, NULL, // DN
LDAP_SCOPE_BASE, "(objectClass=*)", // filter
NULL, FALSE, &LdapMessage );
if ( LdapError != LDAP_SUCCESS ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_CANNOTSEARCH, AuthTypeName, TestedDc->ComputerName, ldap_err2stringA(LdapError) ); goto Cleanup; }
//
// How many entries were returned.
//
AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0, IDS_LDAP_ENTRIES, ldap_count_entries( LdapHandle, LdapMessage ) );
//
// Print the entries.
//
CurrentEntry = ldap_first_entry( LdapHandle, LdapMessage );
while ( CurrentEntry != NULL ) { PVOID Context; char *AttrName;
//
// Test for error
//
if ( LdapHandle->ld_errno != 0 ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_CANNOTFIRSTENTRY, TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) ); goto Cleanup; }
//
// Walk through the list of returned attributes.
//
AttrName = ldap_first_attributeA( LdapHandle, CurrentEntry, (PVOID)&Context ); while ( AttrName != NULL ) { PLDAP_BERVAL *Berval;
//
// Test for error
//
if ( LdapHandle->ld_errno != 0 ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_CANNOTFIRSTATTR, TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) ); goto Cleanup; }
//
// Grab the attribute and it's value
//
AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0, IDS_LDAP_ATTR, AttrName );
Berval = ldap_get_values_lenA( LdapHandle, CurrentEntry, AttrName );
if ( Berval == NULL ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_CANNOTLEN, TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) ); goto Cleanup; } else { int i; for ( i=0; Berval[i] != NULL; i++ ) { AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0, IDS_LDAP_VAL, Berval[i]->bv_len, Berval[i]->bv_val ); } ldap_value_free_len( Berval ); }
//
// Get the next entry
//
AttrName = ldap_next_attributeA( LdapHandle, CurrentEntry, (PVOID)Context ); }
//
// Get the next entry
//
CurrentEntry = ldap_next_entry( LdapHandle, CurrentEntry );
}
}
Cleanup:
if ( LdapMessage != NULL ) { ldap_msgfree( LdapMessage ); }
return RetVal; }
|