Cross platform port of OpenLdap Client library (https://www.openldap.org/software/man.cgi?query=ldap)
and Windows Ldap (https://docs.microsoft.com/en-us/windows/win32/api/_ldap/) to DotNet Core
Help support the project:
For Linux\OSX you must ensure you have the latest OpenLDAP client libraries installed from http://www.openldap.org
For Linux you must also ensure that the appropriate symlinks for libldap.so.2
and liblber.so.2
exist.
It works with any LDAP protocol compatible directory server (including Microsoft Active Directory).
Supported paswordless authentication (Kerberos) on all platforms (on Linux\OSX supported SASL GSSAPI (Kerberos) authentication!).
Sample usage (Kerberos authentication)
using (var cn = new LdapConnection())
{
// connect
cn.Connect();
// bind using kerberos credential cache file
cn.Bind();
// call ldap op
var entries = cn.Search("<<basedn>>", "(objectClass=*)");
}
- Supported platforms
- Installation
- API
- Connect
- Connect TLS
- Connect SSL (with self signed certificate)
- Connect Timeout
- Bind
- BindAsync
- Bind Anonymous
- Bind DIGEST-MD5
- Bind SASL EXTERNAL (Client certificate)
- Bind SASL EXTERNAL (Client certificate & Active Directory)
- Bind SASL EXTERNAL (Unix Socket)
- Bind SASL proxy
- Search
- Search (attributes with binary values)
- Search (retrieve concrete list of attributes)
- SearchAsync
- SearchByCn
- SearchBySid
- GetOption
- SetOption
- Add
- Add Binary Values
- AddAsync
- Modify
- Modify Binary Values
- Reset password
- Change password
- ModifyAsync
- Delete
- DeleteAsync
- Rename
- RenameAsync
- SendRequest
- SendRequestAsync
- Ldap V3 Controls
- GetRootDse
- WhoAmI
- GetNativeLdapPtr (deprecated)
- License
- Authors
- Most of popular Linux distributives
- FreeBSD
- OSX
- Windows
- Supported on the .NET Standard - minimum required is 2.0 - compatible .NET runtimes: .NET Core, Mono, .NET Framework.
- Supported TLS\SSL
- Supported Unicode\Binary values
- Supported authentications:
- Simple \ Basic \ Anonymous
- SASL:
- GSSAPI \ Kerberos V5 \ Negotiate
- DIGEST-MD5
- EXTERNAL
- SASL proxy authorization
- Supported LDAP V3 controls:
Install-Package LdapForNet
dotnet add package LdapForNet
using (var cn = new LdapConnection())
{
// connect use Domain Controller host from computer hostname and default port 389
// Computer hostname - mycomp.example.com => DC host - example.com
cn.Connect();
....
}
using (var cn = new LdapConnection())
{
// connect use hostname and port
cn.Connect("dc.example.com",636);
....
}
using (var cn = new LdapConnection())
{
// connect with URI
cn.Connect(new Uri("ldaps://dc.example.com:636"));
....
}
using (var cn = new LdapConnection())
{
// connect with ldap version 2
cn.Connect(new Uri("ldaps://dc.example.com:636"), LdapForNet.Native.Native.LdapVersion.LDAP_VERSION2);
....
}
using (var cn = new LdapConnection())
{
// connect use hostname and port
cn.Connect("dc.example.com",389);
//set true if use self signed certificate for developing purpose
cn.StartTransportLayerSecurity(true);
....
}
using (var cn = new LdapConnection())
{
cn.Connect("dc.example.com", 636, LdapSchema.LDAPS);
cn.TrustAllCertificates();
....
}
using (var cn = new LdapConnection())
{
cn.Timeout = new TimeSpan(0, 1 ,0); // 1 minute
....
}
using (var cn = new LdapConnection())
{
cn.Connect();
// bind using kerberos credential cache file
cn.Bind();
...
}
using (var cn = new LdapConnection())
{
cn.Connect("ldap.forumsys.com");
// bind using userdn and password
cn.Bind(LdapAuthMechanism.SIMPLE,"cn=read-only-admin,dc=example,dc=com","password");
...
}
using (var cn = new LdapConnection())
{
cn.Connect();
// bind using kerberos credential cache file
cn.BindAsync().Wait();
...
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind(LdapAuthType.Anonymous, new LdapCredential());
...
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind(LdapAuthType.Digest, new LdapCredential
{
UserName = "username",
Password = "clearTextPassword"
});
...
}
About client certificate authentication in openldap
using (var cn = new LdapConnection())
{
cn.Connect("dc.example.com",636,LdapSchema.LDAPS);
var cert = new X509Certificate2("yourcert.pfx", "yourstrongpassword",
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
cn.SetClientCertificate(cert);
cn.Bind(LdapAuthType.External, new LdapCredential());
...
}
About client certificate authentication
using (var cn = new LdapConnection())
{
cn.Connect("dc.example.com",636,LdapSchema.LDAPS);
var cert = new X509Certificate2("yourcert.pfx", "yourstrongpassword",
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
cn.SetClientCertificate(cert);
cn.Bind(LdapAuthType.ExternalAd, new LdapCredential());
...
}
using (var cn = new LdapConnection())
{
cn.ConnectI("/tmp/yoursocketfile.unix");
cn.Bind(LdapAuthType.External, new LdapCredential());
...
}
About SASL auhtorization proxy
Works on UNIX systems
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind(LdapAuthType.Digest, new LdapCredential
{
UserName = "username",
Password = "clearTextPassword",
AuthorizationId = "dn:cn=admin,dc=example,dc=com"
});
...
}
Works on UNIX systems
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind(LdapAuthType.Digest, new LdapCredential
{
UserName = "username",
Password = "clearTextPassword",
AuthorizationId = "u:admin"
});
...
}
Works on UNIX systems
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind(LdapAuthType.GssApi, new LdapCredential
{
AuthorizationId = "u:admin"
});
...
}
Works on Windows system
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind(LdapAuthType.Negotiate, new LdapCredential
{
UserName = "username",
Password = "clearTextPassword"
});
...
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
//search all objects in catalog (default search scope = LdapSearchScope.LDAP_SCOPE_SUBTREE)
var entries = cn.Search("dc=example,dc=com","(objectClass=*)");
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
//search objects in catalog at first level scope
var entries = cn.Search("dc=example,dc=com","(objectClass=*)", LdapSearchScope.LDAP_SCOPE_ONELEVEL);
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var response = (SearchResponse) connection.SendRequest(new SearchRequest("cn=admin,dc=example,dc=com", "(&(objectclass=top)(cn=admin))", LdapSearchScope.LDAP_SCOPE_SUBTREE));
var directoryAttribute = response.Entries.First().Attributes["objectSid"];
var objectSid = directoryAttribute.GetValues<byte[]>().First();
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var response = (SearchResponse)connection.SendRequest(new SearchRequest(Config.RootDn, "(&(objectclass=top)(cn=admin))",LdapSearchScope.LDAP_SCOPE_SUBTREE,"cn","objectClass"));
var count = entries[0].Attributes.AttributeNames.Count; // 2
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
//search all objects in catalog (default search scope = LdapSearchScope.LDAP_SCOPE_SUBTREE)
var entries = cn.SearchAsync("dc=example,dc=com","(objectClass=*)").Result;
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
//search by CN, get @base from machine hostname (my.example.com => dn=example,dn=com )
var entries = cn.SearchByCn("read-only-admin");
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
//search by CN
var entries = cn.SearchByCn("ou=admins,dn=example,dn=com", "read-only-admin", LdapSearchScope.LDAP_SCOPE_ONELEVEL);
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
//search by CN, get @base from machine hostname (my.example.com => dn=example,dn=com )
var entries = cn.SearchBySid("S-1-5-21-2127521184-1604012920-1887927527-72713");
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
//search by CN
var entries = cn.SearchBySid("ou=admins,dn=example,dn=com", "S-1-5-21-2127521184-1604012920-1887927527-72713", LdapSearchScope.LDAP_SCOPE_ONELEVEL);
}
using (var cn = new LdapConnection())
{
cn.Connect();
var ldapVersion = cn.GetOption<int>(LdapOption.LDAP_OPT_PROTOCOL_VERSION);
var host = cn.GetOption<string>(LdapOption.LDAP_OPT_HOST_NAME);
var refferals = cn.GetOption<IntPtr>(LdapOption.LDAP_OPT_REFERRALS);
cn.Bind();
}
using (var cn = new LdapConnection())
{
cn.Connect();
var ldapVersion = (int)LdapVersion.LDAP_VERSION3;
cn.SetOption(LdapOption.LDAP_OPT_PROTOCOL_VERSION, ref ldapVersion);
cn.Bind();
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
cn.Add(new LdapEntry
{
Dn = "cn=test,dc=example,dc=com",
Attributes = new Dictionary<string, List<string>>
{
{"sn", new List<string> {"Winston"}},
{"objectclass", new List<string> {"inetOrgPerson"}},
{"givenName", new List<string> {"your_name"}},
{"description", new List<string> {"your_description"}}
}
});
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var image = new DirectoryAttribute
{
Name = "jpegPhoto"
};
image.Add(new byte[]{1,2,3,4});
directoryEntry.Attributes.Add(image);
var response = (AddResponse)connection.SendRequest(new AddRequest("cn=test,dc=example,dc=com", image));
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
await cn.AddAsync(new LdapEntry
{
Dn = "cn=test,dc=example,dc=com",
Attributes = new Dictionary<string, List<string>>
{
{"sn", new List<string> {"Winston"}},
{"objectclass", new List<string> {"inetOrgPerson"}},
{"givenName", new List<string> {"your_name"}},
{"description", new List<string> {"your_description"}}
}
});
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
cn.Modify(new LdapModifyEntry
{
Dn = "cn=test,dc=example,dc=com",
Attributes = new List<LdapModifyAttribute>
{
new LdapModifyAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_REPLACE,
Type = "givenName",
Values = new List<string> {"test_value_2"}
},
new LdapModifyAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_ADD,
Type = "displayName",
Values = new List<string> {"test_display_name"}
},
new LdapModifyAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_ADD,
Type = "sn",
Values = new List<string> {"test"}
},
new LdapModifyAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_DELETE,
Type = "description",
Values = new List<string> {"test_value"}
}
}
});
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var image = new DirectoryModificationAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_REPLACE,
Name = "jpegPhoto"
};
image.Add(new byte[]{ 5, 6, 7, 8});
var response = (ModifyResponse)connection.SendRequest(new ModifyRequest("cn=test,dc=example,dc=com", image));
}
Microsoft Active Directory
using (var cn = new LdapConnection())
{
// need use ssl/tls for reset password
cn.Connect("dc.example.com", 636, LdapSchema.LDAPS);
cn.Bind();
var attribute = new DirectoryModificationAttribute()
{
Name = "unicodePwd",
LdapModOperation = Native.LdapModOperation.LDAP_MOD_REPLACE
};
string password = "\"strongPassword\"";
byte[] encodedBytes = System.Text.Encoding.Unicode.GetBytes(password);
attribute.Add<byte[]>(encodedBytes);
var response = (ModifyResponse)cn.SendRequest(new ModifyRequest("CN=yourUser,CN=Users,dc=dc,dc=local", attribute));
}
Microsoft Active Directory
using (var cn = new LdapConnection())
{
// need use ssl/tls for reset password
cn.Connect("dc.example.com", 636, LdapSchema.LDAPS);
cn.Bind();
var oldPasswordAttribute = new DirectoryModificationAttribute
{
Name = "unicodePwd",
LdapModOperation = Native.LdapModOperation.LDAP_MOD_DELETE
};
oldPasswordAttribute.Add(Encoding.Unicode.GetBytes($"\"{oldPassword}\""));
var newPasswordAttribute = new DirectoryModificationAttribute
{
Name = "unicodePwd",
LdapModOperation = Native.LdapModOperation.LDAP_MOD_ADD
};
newPasswordAttribute.Add(Encoding.Unicode.GetBytes($"\"{newPassword}\""));
var response = await _ldapConnection.Value.SendRequestAsync(new ModifyRequest("CN=yourUser,CN=Users,dc=dc,dc=local", oldPasswordAttribute, newPasswordAttribute));
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
await cn.ModifyAsync(new LdapModifyEntry
{
Dn = "cn=test,dc=example,dc=com",
Attributes = new List<LdapModifyAttribute>
{
new LdapModifyAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_REPLACE,
Type = "givenName",
Values = new List<string> {"test_value_2"}
},
new LdapModifyAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_ADD,
Type = "displayName",
Values = new List<string> {"test_display_name"}
},
new LdapModifyAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_ADD,
Type = "sn",
Values = new List<string> {"test"}
},
new LdapModifyAttribute
{
LdapModOperation = LdapModOperation.LDAP_MOD_DELETE,
Type = "description",
Values = new List<string> {"test_value"}
}
}
});
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
cn.Delete("cn=test,dc=example,dc=com");
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
await cn.DeleteAsync("cn=test,dc=example,dc=com");
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
cn.Rename("cn=test,dc=example,dc=com", "cn=test2", null, true);
}
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
await cn.RenameAsync("cn=test,dc=example,dc=com", "cn=test2", null, true);
}
Generic method for ldap requests. Inspired by .NET Framework LdapConnection.SendRequest
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
cn.SendRequest(new DeleteRequest("cn=test,dc=example,dc=com"));
}
Generic method for ldap requests. Inspired by .NET Framework LdapConnection.SendRequest
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var cancellationTokenSource = new CancellationTokenSource();
//whoami
var res = await cn.SendRequestAsync(new ExtendedRequest("1.3.6.1.4.1.4203.1.11.3"), cancellationTokenSource.Token);
var extendedResponse = (ExtendedResponse) res;
var name = Encoding.UTF8.GetString(extendedResponse.ResponseValue);
}
PageResultRequestControl\PageResultResponseControl (1.2.840.113556.1.4.319)
using (var cn = new LdapConnection())
{
var results = new List<DirectoryEntry>();
cn.Connect();
cn.Bind();
var directoryRequest = new SearchRequest("dc=example,dc=com", "(objectclass=top)", LdapSearchScope.LDAP_SCOPE_SUB);
var resultRequestControl = new PageResultRequestControl(3);
directoryRequest.Controls.Add(resultRequestControl);
var response = (SearchResponse)cn.SendRequest(directoryRequest);
results.AddRange(response.Entries);
PageResultResponseControl pageResultResponseControl;
while (true)
{
pageResultResponseControl = (PageResultResponseControl)response.Controls.FirstOrDefault(_ => _ is PageResultResponseControl);
if (pageResultResponseControl == null || pageResultResponseControl.Cookie.Length == 0)
{
break;
}
resultRequestControl.Cookie = pageResultResponseControl.Cookie;
response = (SearchResponse)cn.SendRequest(directoryRequest);
results.AddRange(response.Entries);
}
var entries = results.Select(_=>_.ToLdapEntry()).ToList();
}
Note: If you are not getting results beyond the first page, this could because subordinate referrals are turned on as explained here. In that case, one option is to turn off subordinate referrals (as described in option 3 in the link's suggested workarounds). This can be done as follows:
cn.SetOption(LdapOption.LDAP_OPT_REFERRALS, IntPtr.Zero);
DirSyncRequestControl\DirSyncResponseControl (1.2.840.113556.1.4.841)
Ldap user should have DS-Replication-Get-Changes
extended right (https://docs.microsoft.com/en-us/windows/win32/ad/polling-for-changes-using-the-dirsync-control)
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var directoryRequest = new SearchRequest("dc=example,dc=com", "(objectclass=top)", LdapSearchScope.LDAP_SCOPE_SUB);
var dirSyncRequestControl = new DirSyncRequestControl
{
Cookie = new byte[0],
Option = DirectorySynchronizationOptions.IncrementalValues,
AttributeCount = int.MaxValue
};
directoryRequest.Controls.Add(dirSyncRequestControl);
var response = (SearchResponse)cn.SendRequest(directoryRequest);
while (true)
{
var responseControl = (DirSyncResponseControl)response.Controls.FirstOrDefault(_ => _ is DirSyncResponseControl);
if (responseControl == null || responseControl.Cookie.Length == 0)
{
break;
}
dirSyncRequestControl.Cookie = responseControl.Cookie;
Thread.Sleep(60*1000);
response = (SearchResponse)cn.SendRequest(directoryRequest);
if (response.Entries.Any())
{
//handle changes
}
}
}
SortRequestControl\SortResponseControl (1.2.840.113556.1.4.473\1.2.840.113556.1.4.474)
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var directoryRequest = new SearchRequest("dc=example,dc=com", "(objectclass=top)", LdapSearchScope.LDAP_SCOPE_SUB);
directoryRequest.Controls.Add(new SortRequestControl("cn", true));
var response = (SearchResponse)cn.SendRequest(directoryRequest);
}
AsqRequestControl\AsqResponseControl (1.2.840.113556.1.4.1504)
// get all members of group 'Domain Admins'
using (var connection = new LdapConnection())
{
connection.Connect();
connection.BindAsync().Wait();
var directoryRequest = new SearchRequest("CN=Domain Admins,CN=Users,dc=example,dc=com", "(objectClass=user)", LdapSearchScope.LDAP_SCOPE_BASE);
directoryRequest.Controls.Add(new AsqRequestControl("member"));
var response = (SearchResponse)connection.SendRequest(directoryRequest);
}
DirectoryNotificationControl (1.2.840.113556.1.4.528)
//get single notification from ldap server
var cts = new CancellationTokenSource();
using (var connection = new LdapConnection())
{
var results = new List<DirectoryEntry>();
connection.Connect();
connection.BindAsync().Wait();
var directoryRequest = new SearchRequest("CN=Administrator,CN=Users,dc=example,dc=com", "(objectClass=*)", LdapSearchScope.LDAP_SCOPE_BASE, "mail")
{
OnPartialResult = searchResponse =>
{
results.AddRange(searchResponse.Entries);
cts.Cancel();
}
};
var directoryNotificationControl = new DirectoryNotificationControl();
directoryRequest.Controls.Add(directoryNotificationControl);
var response = (SearchResponse) connection.SendRequestAsync(directoryRequest,cts.Token).Result;
}
VlvRequestControl\VlvResponseControl (2.16.840.1.113730.3.4.9\2.16.840.1.113730.3.4.10)
using (var connection = new LdapConnection())
{
var results = new List<DirectoryEntry>();
connection.Connect();
connection.Bind();
var directoryRequest = new SearchRequest("dc=example,dc=com", "(objectClass=*)", LdapSearchScope.LDAP_SCOPE_SUB);
var pageSize = 3;
var vlvRequestControl = new VlvRequestControl(0, pageSize - 1, 1);
directoryRequest.Controls.Add(new SortRequestControl("cn", false));
directoryRequest.Controls.Add(vlvRequestControl);
while (true)
{
var response = (SearchResponse)connection.SendRequest(directoryRequest);
results.AddRange(response.Entries);
var vlvResponseControl = (VlvResponseControl)response.Controls.Single(_ => _.GetType() == typeof(VlvResponseControl));
vlvRequestControl.Offset += pageSize;
if(vlvRequestControl.Offset > vlvResponseControl.ContentCount)
{
break;
}
}
var entries = results.Select(_ => _.ToLdapEntry()).ToList();
}
Information about server https://ldapwiki.com/wiki/RootDSE
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var rootDse = connection.GetRootDse();
}
Returns authorization id of user https://ldapwiki.com/wiki/Who%20Am%20I%20Extended%20Operation
using (var cn = new LdapConnection())
{
cn.Connect();
cn.Bind();
var authzId = connection.WhoAmI().Result;
}
This software is distributed under the terms of the MIT License (MIT).
Alexander Chermyanin / LinkedIn
Contributions and bugs reports are welcome.