How to know that Active Directory exists with only ip address? - c#

How to know that AD exists?
I have only ip address. I tried to use those methods:
if(DirectoryEntry.Exists("LDAP://192.168.1.1"))
also
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://192.168.1.1")
but it didn't help.
I use LdapConnection right now, but I have a problem
LdapConnection connection = new LdapConnection(new LdapDirectoryIdentifier("192.168.1.1"));
connection.AuthType = AuthType.Basic;
NetworkCredential credential =
new NetworkCredential("a", '1");
connection.Credential = credential;
connection.Timeout = new TimeSpan(1000);
connection.Bind();
I'm getting 81 code and The LDAP unavailable.
Does somebody know is possible just to know is ip is correct and AD exists?
P.S. I use .NET 2

You can try this (works with .NET 2.0 and does not need credentials):
...
using System.DirectoryServices.Protocols;
...
string server = "192.168.1.1";
using (LdapConnection ldapConnection = new LdapConnection(server))
{
ldapConnection.AuthType = AuthType.Anonymous;
SearchRequest request = new SearchRequest(null, "(objectclass=*)",
SearchScope.Base, "defaultNamingContext");
SearchResponse result = (SearchResponse)ldapConnection.SendRequest(request);
if (result.Entries.Count == 1)
{
Console.WriteLine(result.Entries[0].Attributes["defaultNamingContext"][0]);
}
}
It binds anonymously to the AD domain controller and retrieves the rootDSE entry. It displays the DN of the AD domain.
You can also query another attributes, see https://msdn.microsoft.com/en-us/library/ms684291(v=vs.85).aspx

AD can only be set to run on port 389 and/or 636. So if the port is open, it is a pretty good chance that LDAP is present.
Know if it is AD or not, would, typically, require you to have a valid LDAP account to BIND to the LDAP service.
You can perform a LDAP query against the LDAP service and probably learn the VendorName.

Related

LDAP reset password from outside the domain network C# Error: RPC server is unavailable. (exception from hresult: 0x800706ba)

We're trying to Reset LDAP password, its working on development environment but not working on production environment.
Our development environment is inside the Domain and production environment is outside the Domain.
In development to connect LDAP, we have used Domain name like abc.com and production environment we use IPaddress:389, which is already working for LDAP User authentication in both environment. But not working for LDAP reset password.
Error: RPC server is unavailable. (exception from hresult: 0x800706ba)
Developement: (working)
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, "<domain.com>", container: "<DC=domain,DC=com>",
"<username>", "<password>");
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, "<LdapUserName>");
// "<username>", "<password>" are Administrative credential.
bool isValid = user.ValidateCredentials("<username>", "<password>");
_logger.Log($"Is Connection: {isValid}");
**// Output: Is Connection: True**
user.UserCannotChangePassword = false;
user.SetPassword("<NewPassword>");
// Password has been successfully reset.
Production: (working)
Also we are authenticate LDAP users using below method its working on Production:
Check user has LDAP account or not:
// "<username>", "<password>" are Administrative credential.
var entry = new DirectoryEntry($"LDAP://{"<IP:389>"}", "<username>", "<password>",
AuthenticationTypes.Secure | AuthenticationTypes.Sealing | AuthenticationTypes.ServerBind);
var search = new DirectorySearcher(entry);
var strFilter = $"(mail={"<UserEmailId>"})";
search.Filter = strFilter;
var result = await Task.Run(() => search.FindOne());
if (result != null)
{
//IsLdapUser = true;
//result.Properties["samaccountname"][0]);
}
else
{
//IsLdapUser = false;
}
// Successfully
// Authenticate LDAP user:
var ldapConnection = new LdapConnection(new LdapDirectoryIdentifier("<IP:389>", false, false));
var nc = new NetworkCredential("<LdapUserName>", "<LdapUserPassword>", "<IP:389>");
ldapConnection.Credential = nc;
ldapConnection.AuthType = AuthType.Negotiate;
ldapConnection.Bind(nc);
// Successfully
Production: (not working)
// "<username>", "<password>" are Administrative credential.
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, "<IP:389>", container: "<DC=domain,DC=com>",
"<username>", "<password>");
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, "<LdapUserName>");
bool isValid = user.ValidateCredentials("<username>", "<password>");
_logger.Log($"Is Connection: {isValid}");
**// Output: Is Connection: True**
user.UserCannotChangePassword = false;
user.SetPassword("<NewPassword>");
// Error: RPC server is unavailable. (exception from hresult: 0x800706ba)
Also tried with below code (not working)
// "<username>", "<password>" are Administrative credential.
DirectoryEntry de = new DirectoryEntry("<IP:389>","<username>", "<password>",
AuthenticationTypes.Secure | AuthenticationTypes.Sealing | AuthenticationTypes.ServerBind);
// LDAP Search Filter
DirectorySearcher ds = new DirectorySearcher(de);
ds.Filter = "(&(objectClass=user)(|(sAMAccountName=" + "<LdapUserName>"+ ")))";
// LDAP Properties to Load
ds.PropertiesToLoad.Add("displayName");
ds.PropertiesToLoad.Add("sAMAccountName");
ds.PropertiesToLoad.Add("DistinguishedName");
ds.PropertiesToLoad.Add("CN");
// Execute Search
SearchResult result = await Task.Run(() => ds.FindOne());
string dn = result.Properties["DistinguishedName"][0].ToString();
DirectoryEntry uEntry = result.GetDirectoryEntry();
uEntry.Invoke("SetPassword", new object[] { "<NewPassword>"}); //Set New Password
uEntry.CommitChanges();
uEntry.Close();
// Error: RPC server is unavailable. (exception from hresult: 0x800706ba)
The attribute used to modify the password is unicodePwd. That documentation reveals some conditions that must be met for the password to be changed. Primarily, the connection must be encrypted.
Calling .Invoke("SetPassword", ...) actually calls the native Windows IADsUser::SetPassword method. That documentation shows that it automatically attempts a few different ways to encrypt. The exception happens because none of these methods worked.
You can actually modify the unicodePwd attribute directly, without calling SetPassword, which I'll get to, but regardless, you have to resolve the issue of encryption first.
When you're running this from a computer inside the network, AuthenticationTypes.Sealing is enough. As the documentation says, the effect is that it uses Kerberos to encrypt the connection.
But when you're connecting from outside the domain, Kerberos won't work (maybe it will with effort - I'm no Kerberos expert). So the only usable encryption method is SSL. The SetPassword method does actually attempt to use SSL, but clearly it didn't work.
One problem I see right away is that you're using an IP address to connect to the DC, and SSL won't work using an IP address, since the domain name name on the SSL certificate must match the name you are using to access to the server, and the SSL cert will not have the IP address on it. So you will have to change that to use the domain name. If DNS will not resolve the name, you can add it to your hosts file.
Changing that may fix everything. If not, there can be two other issues:
Port 636 is not accessible (a firewall in the way).
The SSL certificate is not trusted on the computer you run this on
LDAP over SSL (LDAPS) works on port 636. You can test this connection in PowerShell:
Test-NetConnection example.com -Port 636
If that fails, fix that first.
Next, check the certificate. You can download the certificate with this PowerShell script:
$webRequest = [Net.WebRequest]::Create("https://example.com:636")
try { $webRequest.GetResponse() } catch {}
$cert = $webRequest.ServicePoint.Certificate
$bytes = $cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert)
set-content -value $bytes -encoding byte -path "certificate.cer"
Change the example.com in the first line to your domain name (leave the https:// and :636). Then you will have a file called certificate.cer in the current directory that you can open and inspect. It will warn you if it is not trusted. If it's not trusted, then you will have to install the root certificate on the server as a Trusted Root Certificate.
If it is already trusted, make sure the "Issued to:" domain name on the cert matches the name you used to connect. In our environment, the SSL certificates are in the name of each domain controller (dc1.example.com), not the domain name (example.com). So I have to target a specific domain controller for LDAPS to work.
Once you get all of that sorted out, your code should work.
If you want to change the unicodePwd attribute directly instead of using SetPassword (which may or may not perform a little faster), you will need to make the original connection via SSL. For example:
DirectoryEntry de = new DirectoryEntry("LDAP://dc1.example.com:636","<username>", "<password>",
AuthenticationTypes.Secure | AuthenticationTypes.SecureSocketsLayer | AuthenticationTypes.ServerBind);
Only use AuthenticationTypes.ServerBind if you are targeting a specific DC.
Then you can update the unicodePwd attribute in the very specific way that it wants:
uEntry.Properties["unicodePwd"].Value = Encoding.Unicode.GetBytes("\"NewPassword\"");
uEntry.CommitChanges();
Note that the new password must be enclosed in quotes.

LDAPS connection with ASP.Net/C#

I have a connection string for LDAP protocol
ldap://ldap.example.com:636/DC=users,DC=buyers
which works fine.
But I need to use a LDAPS connection :
ldaps://ldap.example.com/DC=users,DC=buyers
which does show up in ldp.exe windows form when I test the connection.
Unfotunately it does not work in the Asp.Net application. I get "Unknown error (0x80005000)".
I am not sure whether LDAPS string is even possible with Asp.Net. I downloaded the source code into LDAPConnection.cs class and was unable to find any valuable information.
The method you found that works is indeed using LDAPS:
ldap://ldap.example.com:636/DC=users,DC=buyers
That's the only way to do it. I do that in one of my existing projects. It doesn't understand "LDAPS://".
If you don't believe me :) fire up Wireshark as you debug. When it connects, you'll see the SSL handshake to your domain controller.
Port 636 is only for LDAPS. Port 389 is the non-SSL port.
If you have more than one domain, you can use port 3269 for the global catalog via SSL.
Below code worked for me to connect to AD using LDAPS
ldapConnection = new LdapConnection(new LdapDirectoryIdentifier("your.LDAPSserver.com", 636));
var networkCredential = new NetworkCredential("UsernameWithoutDomain", "yourPassword", "AD.yourDOMAIN.com");
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.ProtocolVersion = 3;
ldapConnection.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback(ServerCallback);
ldapConnection.AuthType = AuthType.Negotiate;
ldapConnection.Bind(networkCredential);
SearchRequest Srchrequest = new SearchRequest("CN=Users,DC=AD,DC=YOURCOMPANY,DC=COM", "mail=useremail#company.com", System.DirectoryServices.Protocols.SearchScope.Subtree);
SearchResponse SrchResponse = (SearchResponse)ldapConnection.SendRequest(Srchrequest);
// ServerCallback
private static bool ServerCallback(LdapConnection connection, X509Certificate certificate)
{
return true;
}
Surprisingly it is also working when I am not using networkCredential and just using ldapConnection.Bind(); Seems it is using my local credentials as default on my local machine.

Connect to Active Directory using LdapConnection class on remote server

I have a problem: I need to connect from a remote server to Active Directory, but the code has to be using the LdapConnection class. I need this because that way I can only test change notifiers when some event happen (such as user is deactivated or he changed group, data etc). OS on the remote server is Windows Server 2012.
I managed to do this from local using DirectoryServices with the following code:
String ldapPath = "LDAP://XRMSERVER02.a24xrmdomain.info";
directoryEntry = new DirectoryEntry(ldapPath, #"A24XRMDOMAIN\username", "pass");
//// Search AD to see if the user already exists.
DirectorySearcher search = new DirectorySearcher(directoryEntry);
search.Filter = "(&(objectClass=user))";
SearchResult result = search.FindOne();
This is okay and connection works but now I need to connect using the LdapConnection class.
I tried something like this on many ways but none of that helped me:
LdapConnection connection = new LdapConnection(XRMSERVER02.a24xrmdomain.info);
var credentials = new NetworkCredential(#"A24XRMDOMAIN\username", "pass");
connection.Credential = credentials;
connection.Bind();
It says that credentials are invalid but that is not true.
Explanations:
XRMSERVER02 - Domain controller
a24xrmdomain.info - Domain
A24XRMDOMAIN - Domain used for logging
Thanks for your help.
Even though I solved my problem I want to share with other developers what I achieved so far. Problem that I encountered was that I had remote server with OS Windows server 2012 and Active directory on it. I needed to connect on him via my local machine(Windows 10).
As I stated in my question it is possible to do that via DirectoryServices with the following code:
String ldapPath = "LDAP://(DomainController).a24xrmdomain.info";
directoryEntry = new DirectoryEntry(ldapPath, #"DOMAIN\username","pass");
//// Test search on AD to see if connection works.
DirectorySearcher search = new DirectorySearcher(directoryEntry);
search.Filter = "(&(objectClass=user))";
SearchResult result = search.FindOne();
This is one of the solutions, but since my task was to get notification and to identify when ever some object has changed in Active Directory, I needed connection to Active Directory on Remote server via LDAP class. Code for getting notifiers is taken from:
- Registering change notification with Active Directory using C#
I succeeded to connect with LDAP class via next code:
String ldapPath2 = "(DomainController).a24xrmdomain.info";
LdapConnection connection = new LdapConnection(ldapPath2);
var credentials = new NetworkCredential(#"username", "pass");
connection.Credential = credentials;
connection.Bind();
Want to mention that no IP address of remote server is needed, just Domain Controller that is used on him, and that Domain used for logging is unnecessary.
Happy coding
Try using NetworkCredential constructor with 3 parameters: username, password and domain. Specify domain separately from user name

Default behaviour of LDAP connection without LDAP URL

This is how I created DirectoryEntry to connect to AD server(13.198.123.456)
DirectoryEntry ldap_connection = new DirectoryEntry("LDAP://13.198.123.456/OU=Abc,DC=def,DC=ijk,DC=com", "username", "password");
But if I created DirectoryEntry without LDAP URL, It will connect to the AD that uses to logging to my PC
DirectoryEntry ldap_connection = new DirectoryEntry("", "username", "password");
Is this expected behavior? any documentation about this?
It's not particularly clear, but the version you use is another case of the default DirectoryEntry constructor, but with non-default credentials - as illustrated on this MSDN page, when you use:
DirectoryEntry ent = new DirectoryEntry();
it indicates that you bind to the domain that provides authentication for the user.
In the case of:
DirectoryEntry ldap_connection = new DirectoryEntry("", "username", "password");
The empty string implies that you bind to the domain that provides authentication to the logged in user, but using alternate credentials for the username and password.
I don't have a windows system to hand to test the difference, if any, between passing in an empty string "", as opposed to a null reference - it may barf in this situation.

How do I validate Active Directory creds over LDAP + SSL?

I'm trying to use the .NET 3.5 System.DirectoryServices.AccountManagement namespace to validate user credentials against our Active Directory LDAP server over an SSL encrypted LDAP connection. Here's the sample code:
using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:389", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
{
return pc.ValidateCredentials(_username, _password);
}
This code works fine over unsecured LDAP (port 389), however I'd rather not transmit a user/pass combination in clear text. But when I change to LDAP + SSL (port 636), I get the following exception:
System.DirectoryServices.Protocols.DirectoryOperationException: The server cannot handle directory requests.
at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error)
at System.DirectoryServices.Protocols.LdapSessionOptions.FastConcurrentBind()
at System.DirectoryServices.AccountManagement.CredentialValidator.BindLdap(NetworkCredential creds, ContextOptions contextOptions)
at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)
at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)
at (my code)
Port 636 works for other activities, such as looking up non-password information for that LDAP/AD entry...
UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, _username)
...so I know it's not my LDAP server's SSL setup, since it works over SSL for other lookups.
Has anyone gotten the ValidateCredentials(...) call to work over SSL? Can you explain how? Or is there another/better way to securely validate AD/LDAP credentials?
I was able to validate credentials using the System.DirectoryServices.Protocols namespace, thanks to a co-worker. Here's the code:
// See http://support.microsoft.com/kb/218185 for full list of LDAP error codes
const int ldapErrorInvalidCredentials = 0x31;
const string server = "sd.example.com:636";
const string domain = "sd.example.com";
try
{
using (var ldapConnection = new LdapConnection(server))
{
var networkCredential = new NetworkCredential(_username, _password, domain);
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.AuthType = AuthType.Negotiate;
ldapConnection.Bind(networkCredential);
}
// If the bind succeeds, the credentials are valid
return true;
}
catch (LdapException ldapException)
{
// Invalid credentials throw an exception with a specific error code
if (ldapException.ErrorCode.Equals(ldapErrorInvalidCredentials))
{
return false;
}
throw;
}
I'm not thrilled with using a try/catch block to control decisioning logic, but it's what works. :/
Maybe this is another way. There's nothing unusual in validate credentials. The ContextOptions must set properly.
Default value:
ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing
Add Ssl:
ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing | ContextOptions.SecureSocketLayer
ContextOptions.Negotiate or ContextOptions.SimpleBind is required. Or whatever your server need to perform authentication. ContextOptions only supports OR bit to bit.
You could try also set the ContextOptions directly this way in ValidateCredentials method.
using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:636", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate | ContextOptions.SecureSocketLayer))
{
return pc.ValidateCredentials(_username, _password);
}
Or
using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:636", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
{
return pc.ValidateCredentials(_username, _password, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer);
}
For me, the ValidateCredentials method works just fine. The problem, I found, was on the server hosting the AD (I'm using AD LDS). You needed to associate the server certificate with the AD instance. So if your instance was called 'MyAD' (or ActiveDirectoryWebService), you needed to open up the MMC, snap in the 'Certificates' module, select 'Service Account' and then select 'MyAD' from the list. From there you can add the SSL certificate into the 'MyAD' Personal store. This finally kicked the SSL processing into gear.
I suspect, from what I know of the LdapConnection method and the fact that you omitted the callback function, that you are not validating your server certificate. It's a messy job and ValidateCredentials does it for free. Probably not a big deal, but a security hole none-the-less.
I know this is old, but for anybody running into this again:
PrincipalContext.ValidateCredentials(...), by default, tries to open an SSL connection (ldap_init(NULL, 636)) followed by setting the option LDAP_OPT_FAST_CONCURRENT_BIND.
If a (trusted?) client certificate is present, however, the LDAP connection is implicitly bound and fast bind cannot be enabled anymore. PrincipalContext doesn't consider this case and fails with an unexpected DirectoryOperationException.
Workaround: To support SSL where possible, but have a fallback, call ValidateCredentials(...) with default options first (i.e. no options). If this fails with the DirectoryOperationException, try again by specifying the ContextOptions (Negotiate | Sealing | Signing), which is what ValidateCredentials internally does for the expected LdapException anyway.

Categories

Resources