1

I'm writing a program (WinForms, C#) that runs on a Win 7 client machine. It obtains credentials from the user (user id, password, and subdomain name) and uses them to authenticate (via Active Directory) to other servers to which the program remotely connects. The other servers are on a domain different than the domain the Win 7 client machine is on.

Using the NetworkCredential, LdapDirectoryIdentifier, and LdapConnection classes, I can test the credentials with no more than the user id, password, and subdomain name (See answer for S.O. Why does Active Directory validate last password?).

For example, for the user account ssmith@xyz.gov, I need only provide ssmith (user id), the password for ssmith, and xyz (the subdomain). I don't need to provide the top-level domain name (gov in this case).

Now, I want to programmatically obtain the top-level domain name for this user account (gov in this case). I've examined the properties and methods of the NetworkCredential, LdapDirectoryIdentifier, and LdapConnection classes. I've looked over the other classes in the System.DirectoryServices.Protocols Namespace. I don't see a way to programmatically obtain the top-level domain name.

Given user id, password, and subdomain name, how can I obtain the top-level domain name for a user account?

Here is my code. Given a user account of ssmith@xyz.gov, My call looks like this (asterisks represent a SecureString password)

bool result = ValidateCredentials("ssmith","******", "xyz");

Here is my code for the method.

private const int ERROR_LOGON_FAILURE = 0x31;

private bool ValidateCredentials(string username, SecureString ssPassword, string domain)
{

    //suports secure string 
    NetworkCredential credentials = new NetworkCredential(username, ssPassword, domain);

    LdapDirectoryIdentifier id = new LdapDirectoryIdentifier(domain);

    using (LdapConnection connection = new LdapConnection(id, credentials, AuthType.Kerberos))
    {
        connection.SessionOptions.Sealing = true;
        connection.SessionOptions.Signing = true;

        try
        {
            // The only way to test credentials on a LDAP connection seems to be to attempt a 
            // Bind operation, which will throw an exception if the credentials are bad
            connection.Bind();
        }
        catch (LdapException lEx)
        {
            credentials = null;
            id = null;

            if (ERROR_LOGON_FAILURE == lEx.ErrorCode)
            {
                return false;
            }

            throw;
        }
    }

    credentials = null;
    id = null;

    return true;
}
VA systems engineer
  • 2,856
  • 2
  • 14
  • 38

1 Answers1

1

After a successful bind, then the full DNS name of the domain will be in the LdapConnection object:

var domain = connection.SessionOptions.DomainName;

In this case, that would be "xyz.gov". If you need just "gov", then you can just take everything after the last dot:

var tld = domain.Substring(domain.LastIndexOf('.') + 1);
Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • "After a successful bind". Thanks - it did not occur to me to look at my `connection` object **after** the successful bind :-( – VA systems engineer May 03 '18 at 16:28
  • Searching only for the last dot will break horribly in cases such as a `.co.uk` domain name. Depending on the need, you may instead choose to split on the first dot. – Patrick Mevzek May 04 '18 at 13:28
  • I guess I could modify the signature of the method to pass the fully qualified domain name back out: `private bool ValidateCredentials(string username, SecureString ssPassword, string domain, out string fqdn)`. See this: "The `out` keyword causes arguments to be passed by reference.". [out parameter modifier (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier) – VA systems engineer May 05 '18 at 10:29
  • 1
    Or, if you're using the latest Visual Studio, you can use C# 7's new feature of [returning multiple values](https://blogs.msdn.microsoft.com/mazhou/2017/05/26/c-7-series-part-1-value-tuples/): `private (bool, string) ValidateCredentials(...)` then `return (true, domainName);` – Gabriel Luci May 05 '18 at 22:27