Background
Recently, I was presented with an issue when trying to connect to AD LDS (Active Directory Lightweight Directory Services (previously known as ADAM) using C# and System.DirectoryServices.AccountManagement.PrincipalContext. The integration intermittently resulted in an exception with message "LDAP server is unavailable".In this blog, I am going to discuss how this problem was tackled.
The Quick Solution
To save you time, please ensure that the AD LDS servers machine is accessible by hostname from your connecting client's location.The Nitty Gritty
Performing lots of searches on google to no avail, I decided to solve this issue the old fashioned way.Given the following:
- The problem only occurred on development machines and not on TeamCity Build Agent.
- The same code was working/passing tests on development machine before.
- The test AD LDS instance was configured on the same TeamCity Build Agent
- The DirectorySearcher was able to connect to the AD LDS and return the properties of the username in question.
- The AD LDS configuration caused similar issues before, the issues were with user authentication as the password was not being set properly on the user in question.
The piece of code causing the issue was this line:
var result = context.ValidateCredentials(userId, password, ContextOptions.SimpleBind);
Where context is an instance of PrincipalContext and userId is the distinguished name of the user.
Thinking this may be an issue with the way the PrincipalContext was being instantiated with ContextOptions, I wrote a console application which dynamically generated a list of PrincipalContext constructors with all possible combinations of ContextOptions and all parameters required to work with ApplicationDirectory context type (which is a must if you are connecting with AD LDS). I then tried calling ValidateCredentials with each constructor (about 34 calls) all the calls failed with the same error (except in some cases with Unknown Error, basically due to SecuredSocket binding not being supported).
I then resorted to applying the all "ContextOptions" combinations with the ValidateCredentials calls for each context as well, which resulted in 34x63 calls. All calls also resulted in the same error.
With the peace of mind that this has nothing to do with the "ContextOptions" as I have already tested all possible combinations, I had to step my effort up. I decided to decompile the System.DirectoryServices assembly (3 of them) using JetBrains dotPeek and followed the stack-trace returned in the exception. I ran my code in debug mode when it broke on exception I did a dry run of the decompiled code by looking at internal private variables made accessible through Visual Studio Non-Public member view.
I noticed that the CredentialValidator class inside the assembly resolved the AD LDS' local machine name and was trying to connect to the server with that hostname. The development machine was trying to connect to the AD LDS server over the VPN using an IP Address and not the hostname. The development machine was unable to resolve the IP Address for that hostname.