How to connect to Active Directory via LDAPS in C#? - c#

Found a documentation (here) in an answer thread on this site but i can´t get an connection to an AD. When i use a program like Active Directory Explorer i can connect. I think, because i am trying to connect to a LDAPS i need a different approach?
I have the server IP, a domain, username/pwd and the port 636.
I tried various combinations # new DirectoryEntry but couldn´t get it to connect. Always get a COMException Domain is not existing .
static DirectoryEntry createDirectoryEntry()
{
DirectoryEntry ldapConnection = new DirectoryEntry("LDAP://192.168.2.59", USER, PWD);
ldapConnection.AuthenticationType = AuthenticationTypes.SecureSocketsLayer;
return ldapConnection;
}
Background Infos:
User places his card to a Card Reader Unit. Porgram gets ID from card and searches the DB for this ID and returns the eMail address belonging to the ID/User
.
And here the working solution:
private string getEmail(string userID)
{
try
{
string ldapfilter = "(&(otherPager=" + userID + "))";
DirectoryEntry myLdapConnection = new DirectoryEntry("LDAP://" + SERVER, USER, PWD);
DirectorySearcher search = new DirectorySearcher(myLdapConnection);
search.Filter = ldapfilter;
/*search.PropertiesToLoad.Add("mail");
SearchResult result = search.FindOne();*/
string[] requiredValue = new String[] { "mail" };
foreach (String value in requiredValue)
search.PropertiesToLoad.Add(value);
SearchResult result = search.FindOne();
if (result != null)
{
foreach (String value in requiredValue)
foreach (Object myCollection in result.Properties[value])
{
return myCollection.ToString();
}
}
else
{
return "No Entry fround";
}
}
catch (Exception e)
{
Console.WriteLine("Exception Problem: " + e.ToString());
return null;
}
return null;
}
private void cmdClose_Click(object sender, EventArgs e)
{
Close();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
label1.Text = getEmail(textBox1.Text);
}

You need to specify the port, since 636 is the default LDAPS port.
new DirectoryEntry("LDAP://192.168.2.59:636", USER, PWD)
I do this in some of my code, and using "LDAP://" (not "LDAPS://") is what works.
If that doesn't work, then there may be a certificate error. You can test this with a browser. If you use Chrome, open Chrome with this (so it lets you use port 636):
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --explicitly-allowed-ports=636
Then go to https://192.168.2.59:636. If you get a big fancy certificate error, then the problem is that the certificate is not trusted. View the certificate from Chrome and see what the problem is. It could be issued by an authority that is not in the Windows cert store.

Related

Active Directory move a user to a different OU

I'm working on a program that will automate the separation process for users leaving our network. One of the tasks it performs is moving the user account from the OU it is in, to a Former Employees OU. I've been having problems with this step even though I've not had any issues doing other processes with DirectoryServices. Here's my code thus far (note: I know I need to stop catching and eating all exceptions. This will be addressed and corrected before release. Any advice on which exceptions I should catch and which I should not would be appreciated too):
private const string AD_DOMAIN_NAME = "domain.com";
private const string AD_NEW_PASSWORD = "TestPassword123";
private const string AD_FORMER_EMPLOYEES_OU = "LDAP://OU=Former Employees,DC=domain,DC=com";
static DirectoryEntry CreateDirectoryEntry(string connectionPath,
string adUserName, string adPassword)
{
DirectoryEntry ldapConnection = null;
try
{
ldapConnection = new DirectoryEntry(AD_DOMAIN_NAME, adUserName, adPassword);
ldapConnection.Path = connectionPath;
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
}
catch (Exception ex)
{
MessageBox.Show("Exception Caught in createDirectoryEntry():\n\n" + ex.ToString());
}
return ldapConnection;
}
private void btnProcessSeparation_Click(object sender, EventArgs e)
{
if (cboOffice.SelectedItem != null && lstUsers.SelectedItem != null)
{
string userOU = cboOffice.SelectedItem.ToString();
string userName = lstUsers.SelectedItem.ToString();
string userDn = "LDAP://OU=" + userOU + ",OU=Employees,DC=domain,DC=com";
using (DirectoryEntry ldapConnection = CreateDirectoryEntry(userDn))
{
using (DirectorySearcher searcher = CreateDirectorySearcher(ldapConnection,
SearchScope.OneLevel, "(samaccountname=" + userName + ")", "samaccountname"))
{
SearchResult result = searcher.FindOne();
if (result != null)
{
using (DirectoryEntry userEntry = result.GetDirectoryEntry())
{
if (userEntry != null)
{
using (DirectoryEntry formerEmployees = CreateDirectoryEntry(
AD_FORMER_EMPLOYEES_OU))
{
userEntry.MoveTo(formerEmployees); // This line throws an DirectoryServicesCOMException.
}
userEntry.CommitChanges();
userEntry.Close();
MessageBox.Show("Separation for {0} has completed successfully.", userName);
}
}
}
}
}
}
else
{
MessageBox.Show("Error, you did not select an OU or a user. Please try again.");
}
}
The above code works just fine until the userEntry.MoveTo(formerEmployees); line. That line throws a DirectoryServicesCOMException with the additional information saying An invalid dn syntax has been specified. It is strange because I'm using the same format as the other DirectoryEntry's that work just fine. I've added a break point and confirmed that formerEmployees is set to: LDAP://OU=Former Employees,DC=domain,DC=com. I copied everything after LDAP:// directly from the OU's distinguishedName attribute in Active Directory to make sure it was correct.
Is the space in the OU name causing the problem? I got this to work once just fine and moved on to the other tasks and must have changed something that broke this. I've been looking at the code too much I think and just can't seem to see why it thinks I'm sending an invalid dn.
Thanks for any and all help!
Hope this helps:
DirectoryEntry eLocation = Conexion.Conectar(Localitation);
DirectoryEntry nLocation =Conexion.Conectar(NewLocalitation);
string newName = eLocation.Name;
eLocation.MoveTo(nLocation, newName);
nLocation.Close();
eLocation.Close();
After #David pointed me in the right direction of making sure I had the correct permissions to the OU, I discovered the problem. I added an overloaded CreateDirectoryEntry method that uses the username and password (which is what I put in the code above). However, if you notice in the code above, I call the method that only takes the connection path.
Thanks for the help #David!

Get Global Catalog for a given LDAP server

Question first, explanation later: How can I get the GC server for any given LDAP server?
To understand my needs, let me explain:
I had to extend Henning Krause's ExchangeAddressListService (I am not sure whether I should/may c'n'p all of Henning's code into this post?) to get useful debug output:
private DirectoryEntry GetDirectoryEntry(string path, string protocol)
{
var ldapPath = string.IsNullOrEmpty(path) ? string.Format("{0}:", protocol) : string.Format("{0}://{1}", protocol, path);
dbg.Add("Getting DirectoryEntry for path " + ldapPath);
return new DirectoryEntry(ldapPath);
}
public ActiveDirectoryConnection(Debug dbg)
{
this.dbg = dbg;
}
and to allow for selection of a certain domain:
internal AddressList(string path, ActiveDirectoryConnection connection, string domain)
{
    _Path = path;
    _Connection = connection;
_Domain = domain;
}
...
private IEnumerable<AddressList> GetAddressLists(string containerName)
{
string exchangeRootPath;
using (var root = _Connection.GetLdapDirectoryEntry(_Domain+"/RootDSE"))
...
foreach (SearchResult addressBook in searchResultCollection)
{
yield return
new AddressList((string)addressBook.Properties["distinguishedName"][0], _Connection, _Domain);
}
...
}
Now I have a problem with the domain because it seems as if for some domains SOMEDOMAIN the Global Catalog cannot be accessed via GC://SOMEDOMAIN. This is my code I use:
var domain = User.Identity.Name.Split('\\')[0]; // SOMEDOMAIN\SomeUser -> Domain is SOMEDOMAIN
dbg.Add("User NETBIOS domain is "+domain);
AddressListService addressListService = new ExchangeAddressListService(connection,domain);
IEnumerable<AddressList> addressLists = addressListService.GetGlobalAddressLists();
AddressList addressList = addressLists.First()
try {
IEnumerable<SearchResult> searchResults = addressList.GetMembers("displayName", "distinguishedname", "mail")
} catch(Exception e) {
dbg.Add("Error in GetMembers: "+e.Message);
return new AjaxAnswer(dbg.Flush());
}
It produces the error log:
User NETBIOS domain is SOMEDOMAIN
Getting DirectoryEntry for path LDAP://SOMEDOMAIN/RootDSE
Getting DirectoryEntry for path LDAP://CN=Microsoft Exchange, CN=Services, CN=Configuration,DC=somedomain,DC=net
Getting DirectoryEntry for path LDAP://CN=All Global Address Lists,CN=Address Lists Container, CN=MYMAIL,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=somedomain,DC=net
Getting DirectoryEntry for path LDAP://CN=Default Global Address List,CN=All Global Address Lists,CN=Address Lists Container,CN=MYMAIL,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=somedomain,DC=net
Getting DirectoryEntry for path GC://SOMEDOMAIN
Error in GetMembers: The server is not operational.
Not all DC are GC. So GC://SOMEDOMAIN may fail if SOMEDOMAIN is not a GC.
In my project, I use the DsGetDcName Win32 function to discover the GC.
Details of DsGetDcName function:
http://msdn.microsoft.com/en-us/library/ms675983%28v=vs.85%29.aspx
See below for how to pinvoke the call:
http://www.pinvoke.net/default.aspx/netapi32.dsgetdcname
As I know System.DirectoryServices.ActiveDirectory also provide classes to handle GC.
e.g. Forest.GlobalCatalogs
I already use the DsGetDcName function, so never tried this before.

Active directory authentication using vpn in c#

I am developing a web application that authenticate the user against an Active Directory Server. Now if I run my code from the development PC under the domain of that AD server, my code is running smoothly. We need to run the code from a totally different network using VPN and here the development PC is not into that AD. I am getting following error while trying to access the AD server.
The specified domain either does not exist or could not be contacted.
My VPN is working fine. I could access remote desktops using this VPN. I know a little tweak is required to solve the problem but could not find it. I went through following links but could not find any solution.
Domain Authentication from .NET Client over VPN
How do I get the Current User identity for a VPN user in a Windows forms app?
Following is my settings in web.config
<appSettings>
<add key="LDAPPath" value="LDAP://DC=MYSERVER,DC=COM" />
<add key="ADGroupName" value="Analyst"/>
</appSettings>
and here is my code
public class LdapAuthentication
{
private string _path;
private string _filterAttribute;
public LdapAuthentication()
{
_path = System.Configuration.ConfigurationManager.AppSettings["LDAPPath"].ToString();
}
public bool IsAuthenticated(string username, string pwd)
{
try
{
DirectoryEntry entry = new DirectoryEntry(_path, username, pwd);
entry.Path = _path;
entry.Username = username;
entry.Password = pwd;
// Bind to the native AdsObject to force authentication.
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (null == result)
{
return false;
}
// Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (string)result.Properties["cn"][0];
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message);
}
return true;
}
}
Any help would be appreciated. Thank you.
I had a similar, though simpler problem. I had success in using the following code:
private bool DoLogin(string userName, string password)
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "DomainName.com")) {
bool isValid = pc.ValidateCredentials(userName, password);
if (isValid) {
// authenticated
...
return true;
}
else {
// invalid credentials
...
return false;
}
}
}
Using the ".com" at the end of the domain name was important to get it working for me. Without it I got the same symptoms you describe.
I've just been grappling with this for a couple of hours. No problems when on the network, lots of problems when connecting via VPN. It seems that when you are connecting over a VPN, the 'connection string' for DirectoryEntry has to be a lot more precise. I finally got it to work with an LDAP address/connection string like this:
LDAP://ip_of_primary_domain_controller/fully qualified path of the container object where the binding user is located
So for example something like this worked for me:
DirectoryEntry directoryEntry = new DirectoryEntry(
"LDAP://192.168.0.20/OU=Service Accounts,OU=Admin Accounts,DC=myserver,DC=com",
"username#myserver.com", "password");
... where "username#myserver.com" is located in OU=Service Accounts,OU=Admin Accounts,DC=myserver,DC=com. If you use SysInternals ADExplorer (or similar) to search for your username, it will tell you the correct fully qualified path for the container.
See here for a long answer about exactly whats should be in the 'connection string': https://serverfault.com/a/130556

How to get all members of a local WinNT group?

When I retrieve members of a local WinNT group, someway somehow not all members are returned. I do add:
Active Directory users
Active Directory groups
Both successful (see picture), but only the users show up afterwards.
Question is:
What happens to added groups?
See last method in code sample 'GetMembers()'
Is this a known issue?
Any workaround available?
Many thanks!!
string _domainName = #"MYDOMAIN";
string _basePath = #"WinNT://MYDOMAIN/myserver";
string _userName = #"MYDOMAIN\SvcAccount";
string _password = #"********";
void Main()
{
CreateGroup("lg_TestGroup");
AddMember("lg_TestGroup", #"m.y.username");
AddMember("lg_TestGroup", #"Test_DomainGroup");
GetMembers("lg_TestGroup");
}
// Method added for reference.
void CreateGroup(string accountName)
{
using (DirectoryEntry rootEntry = new DirectoryEntry(_basePath, _userName, _password))
{
DirectoryEntry newEntry = rootEntry.Children.Add(accountName, "group");
newEntry.CommitChanges();
}
}
// Add Active Directory member to the local group.
void AddMember(string groupAccountName, string userName)
{
string path = string.Format(#"{0}/{1}", _basePath, groupAccountName);
using (DirectoryEntry entry = new DirectoryEntry(path, _userName, _password))
{
userName = string.Format("WinNT://{0}/{1}", _domainName, userName);
entry.Invoke("Add", new object[] { userName });
entry.CommitChanges();
}
}
// Get all members of the local group.
void GetMembers(string groupAccountName)
{
string path = string.Format(#"{0}/{1}", _basePath, groupAccountName);
using (DirectoryEntry entry = new DirectoryEntry(path, _userName, _password))
{
foreach (object member in (IEnumerable) entry.Invoke("Members"))
{
using (DirectoryEntry memberEntry = new DirectoryEntry(member))
{
string accountName = memberEntry.Path.Replace(string.Format("WinNT://{0}/", _domainName), string.Format(#"{0}\", _domainName));
Console.WriteLine("- " + accountName); // No groups displayed...
}
}
}
}
Update #1
The sequence of the group members seems to be essential. As soon as the enumerator in GetMembers() stumbles on an Active Directory group, the remaining items are not displayed either. So if 'Test_DomainGroup' is listed first in this example, GetMembers() does not display anything at all.
I know it's an old question and you've likely found the answers you need, but just in case someone else stumbles accross this...
The WinNT ADSI provider you're using in your DirectoryEntry [ie. WinNT://MYDOMAIN/myserver] has pretty limited capabilities for working with Windows Domains that are not stuck in the old Windows 2000/NT functional level (https://support.microsoft.com/en-us/kb/322692).
In this case the problem is that the WinNT provider doesn't know how to handle Global or Universal security groups (which didn't exist in Windows NT and are activated as soon as you raise your domain level above Windows 2000 mixed mode). So, if any groups of those types are nested under a local group you'll generally have problems like the one you described.
The only solution/workaround I've found is to determine if the group you're enumerating is from a domain and if so, then switch to the LDAP provider which will display all members properly when invoking "Members".
Unfortunately I don't know of an "easy" way to just switch from using the WinNT provider to using the LDAP provider using the DirectoryEntry you already have bound to the WinNT provider. So, in the projects I've worked on, I generally prefer to get the SID of the current WinNT object and then use LDAP to search for domain objects with that same SID.
For Windows 2003+ domains you can convert your SID byte array to the usual SDDL format (S-1-5-21...) and then bind to an object with a matching SID using something like:
Byte[] SIDBytes = (Byte[])memberEntry.Properties["objectSID"].Value;
System.Security.Principal.SecurityIdentifier SID = new System.Security.Principal.SecurityIdentifier(SIDBytes, 0);
memberEntry.Dispose();
memberEntry = new DirectoryEntry("LDAP://" + _domainName + "/<SID=" + SID.ToString() + ">");
For Windows 2000 domains you can't bind directly to an object by SID. So, you have to convert your SID byte array to an array of the hex values with a "\" prefix (\01\06\05\16\EF\A2..) and then use the DirectorySearcher to find an object with a matching SID. A method to do this would look something like:
public DirectoryEntry FindMatchingSID(Byte[] SIDBytes, String Win2KDNSDomainName)
{
using (DirectorySearcher Searcher = new DirectorySearcher("LDAP://" + Win2KDNSDomainName))
{
System.Text.StringBuilder SIDByteString = new System.Text.StringBuilder(SIDBytes.Length * 3);
for (Int32 sidByteIndex = 0; sidByteIndex < SIDBytes.Length; sidByteIndex++)
SIDByteString.AppendFormat("\\{0:x2}", SIDBytes[sidByteIndex]);
Searcher.Filter = "(objectSid=" + SIDByteString.ToString() + ")";
SearchResult result = Searcher.FindOne();
if (result == null)
throw new Exception("Unable to find an object using \"" + Searcher.Filter + "\".");
else
return result.GetDirectoryEntry();
}
}

Code To Create a User and Establish Permissions

I have a C# windows service application that generates a log file that writes to C:\Program Files\Company\Product\log.txt. I am using the installer provided by Visual Studio 2010 and need to have the installer:
1. Create a user named ProductUser
2. Set permission for C:\Program Files\Company\Product\ to allow ProductUser to write to the directory.
Not the best solution... but only an idea: what about running the command
net user password username /ADD?
If you can do that, I can find an old vbs I used on a server to grant permissions to user on a folder.
OK, I googled a little bit and found this:
public void CreateUserAccount(string login , string password , string fullName, bool isAdmin)
{
try
{
DirectoryEntry dirEntry = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntries entries = dirEntry.Children;
DirectoryEntry newUser = entries.Add (login, "user");
newUser.Properties["FullName"].Add(fullName);
newUser.Invoke("SetPassword", password);
newUser.CommitChanges();
// Remove the if condition along with the else to create user account in "user" group.
DirectoryEntry grp;
if (isAdmin)
{
grp = dirEntry.Children.Find("Administrators", "group");
if (grp != null) {grp.Invoke("Add", new object[] {newUser.Path.ToString()});}
}
else
{
grp = dirEntry.Children.Find("Guests", "group");
if (grp != null) {grp.Invoke("Add", new object[] {newUser.Path.ToString()});}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
It's not mine: here is the link.
Let me know if it works for you

Categories

Resources