How do I use Persistent Search and Entry Change Notification of supported controls,this is my code and associated environment
See original GitHub issueI want to listen to the related operations of the LDAP server to inform my own services to synchronize the related operations to achieve data consistency. I’m not sure whether it can be implemented. Can you help me? Most of the sample codes are examples. The LDAP used in the experiment is openldap. Although I can get the search results, I want to listen to the change operation of the LDAP server. When I connect to the LDAP server, I can’t get the relevant notification through the code by changing or adding the relevant properties. The related functions of LDAP are implemented in RFC 4533
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>ldap_observer</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="4.0.0-beta1" />
</ItemGroup>
</Project>
using System;
using System.Threading.Tasks;
using Novell.Directory.Ldap;
using Novell.Directory.Ldap.Controls;
namespace ldap_observer
{
class Program
{
const double TIME_OUT_IN_MINUTES = 0.5;
static DateTime timeOut;
static async Task Main(string[] args)
{
// if (args.Length != 5)
// {
// Console.WriteLine("Usage: mono SearchPersist <host name> <ldap port> <login dn>" + " <password> <search base>");
// Console.WriteLine("Example: mono SearchPersist Acme.com 389" + " \"cn=admin,o=Acme\"" + " secret \"ou=sales,o=Acme\"");
// return;
// }
// int ldapVersion = LdapConnection.LdapV3;
// String ldapHost = args[0];
// int ldapPort = Convert.ToInt32(args[1]); ;
// String loginDN = args[2];
// String password = args[3];
// String searchBase = args[4];
// LdapSearchQueue queue = null;
// LdapSearchConstraints constraints;
// LdapPersistSearchControl psCtrl;
// LdapConnection lc = new();
// constraints = new LdapSearchConstraints();
int ldapVersion = LdapConnection.LdapV3;
String ldapHost = "localhost";
int ldapPort = 389;
String loginDN = "cn=admin,dc=example,dc=org";
String password = "admin";
String searchBase = "dc=example,dc=org";
LdapSearchQueue queue = null;
LdapSearchConstraints constraints;
LdapPersistSearchControl psCtrl;
LdapConnection lc = new();
constraints = new LdapSearchConstraints();
try
{
// connect to the server
await lc.ConnectAsync(ldapHost, ldapPort);
// authenticate to the server
await lc.BindAsync(ldapVersion, loginDN, password);
//Create the persistent search control
psCtrl = new LdapPersistSearchControl(
LdapPersistSearchControl.Any, // any change
true, //only get changes
true, //return entry change controls
false); //control is critical
// add the persistent search control to the search constraints
constraints.SetControls(psCtrl);
// perform the search with no attributes returned
String[] allUserAttrs = { LdapConnection.AllUserAttrs };
queue = await lc.SearchAsync(
searchBase, // container to search
LdapConnection.ScopeSub, // search container's subtree
"(objectClass=*)", // search filter, all objects
allUserAttrs, // return all users attributes
false, // return attrs and values, ignored
null, // use default search queue
constraints); // use default search constraints
}
catch (LdapException e)
{
Console.WriteLine("Error: " + e.ToString());
try
{
lc.Disconnect();
}
catch (LdapException e2)
{
}
Environment.Exit(1);
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
return;
}
Console.WriteLine("Monitoring the events for {0} minutes..", TIME_OUT_IN_MINUTES);
Console.WriteLine();
//Set the timeout value
timeOut = DateTime.Now.AddMinutes(TIME_OUT_IN_MINUTES);
try
{
//Monitor till the timeout happens
while (true)
// while (DateTime.Now.CompareTo(timeOut) < 0)
{
// if (!CheckForAChange(queue))
// break;
CheckForAChange(queue);
System.Threading.Thread.Sleep(10);
}
}
catch (System.IO.IOException e)
{
System.Console.Out.WriteLine(e.Message);
}
catch (System.Threading.ThreadInterruptedException e)
{
}
//Disconnect from the server before exiting
try
{
lc.Abandon(queue); //abandon the search
lc.Disconnect();
}
catch (LdapException e)
{
Console.Out.WriteLine();
Console.Out.WriteLine("Error: " + e.ToString());
}
Environment.Exit(0);
}
/// <summary> Check the queue for a response. If a response has been received,
/// print the response information.
/// </summary>
static private bool CheckForAChange(LdapSearchQueue queue)
{
LdapMessage message;
bool result = true;
try
{
//check if a response has been received so we don't block
//when calling getResponse()
if (queue.IsResponseReceived())
{
message = queue.GetResponse();
if (message != null)
{
// is the response a search result reference?
if (message is LdapSearchResultReference)
{
String[] urls = ((LdapSearchResultReference)message).Referrals;
Console.Out.WriteLine("\nSearch result references:");
for (int i = 0; i < urls.Length; i++)
Console.Out.WriteLine(urls[i]);
}
// is the response a search result?
else if (message is LdapSearchResult)
{
LdapControl[] controls = message.Controls;
if (controls != null)
{
for (int i = 0; i < controls.Length; i++)
{
if (controls[i] is LdapEntryChangeControl)
{
LdapEntryChangeControl ecCtrl = controls[i] as LdapEntryChangeControl;
int changeType = ecCtrl.ChangeType;
Console.Out.WriteLine("\n\nchange type: " + GetChangeTypeString(changeType));
if (changeType == LdapPersistSearchControl.Moddn)
Console.Out.WriteLine("Prev. DN: " + ecCtrl.PreviousDn);
// send message to my service
if (ecCtrl.HasChangeNumber)
Console.Out.WriteLine("Change Number: " + ecCtrl.ChangeNumber);
// send message to my service
LdapEntry entry = ((LdapSearchResult)message).Entry;
Console.Out.WriteLine("entry: " + entry.Dn);
}
}
}
}
// the message is a search response
else
{
LdapResponse response = (LdapResponse)message;
int resultCode = response.ResultCode;
if (resultCode == LdapException.Success)
{
Console.Out.WriteLine("\nUnexpected success response.");
result = false;
}
else if (resultCode == LdapException.Referral)
{
String[] urls = ((LdapResponse)message).Referrals;
Console.Out.WriteLine("\n\nReferrals:");
for (int i = 0; i < urls.Length; i++)
Console.Out.WriteLine(urls[i]);
}
else
{
Console.Out.WriteLine("Persistent search failed.");
throw new LdapException(response.ErrorMessage, resultCode, response.MatchedDn);
}
}
}
}
}
catch (LdapException e)
{
Console.Out.WriteLine("Error: " + e.ToString());
result = false;
}
return result;
}
/// <summary> Return a string indicating the type of change represented by the
/// changeType parameter.
/// </summary>
private static String GetChangeTypeString(int changeType)
{
string changeTypeString = changeType switch
{
LdapPersistSearchControl.Add => "ADD",
LdapPersistSearchControl.Modify => "MODIFY",
LdapPersistSearchControl.Moddn => "MODDN",
LdapPersistSearchControl.Delete => "DELETE",
_ => "Unknown change type: " + changeType.ToString(),
};
return changeTypeString;
}
}
}
Issue Analytics
- State:
- Created 3 years ago
- Comments:6 (4 by maintainers)
Top Results From Across the Web
Notification of Changes
Applications that need change notification can use a persistent search or read the external change log: "Use Persistent Search" "Use the External Change...
Read more >Understanding and Using Persistent Search in Novell eDirectory
This is a Boolean field that tells the server whether or not you're interested in the initial result set. If ChangesOnly is True,...
Read more >ldap - Active Directory. Persistent Search or Entry Change ...
I want to get changes for user entities from active directory(AD) with UnboundID LDAP SDK. Does AD support Persistent Search or Entry Change...
Read more >Persistent search
Persistent search sends the set of entries that match the search criteria. It also provides clients a means to receive change notifications to...
Read more >Using Advanced Search Features
You can view the current list of controls for your directory server by searching the Root DSE entry for the supportedControl attribute. Run...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I have tried opendj as ldap-server.
After starting the server it must be configured to allow
persistent search
I used the following call to add the ACI:I copied your code and changed
isCritical
to true. I dont know why, but with false it does not work. An error with the message sCannot decode the provided persistent search control because it does not have a value (2) Protocol Error
occured.Change
isCritical
fromfalse
totrue
solves the problem for me.The console logs the following when i created a new ldap entry while your application / sample code runs:
In addition: The word
Persistent Search
is part of the RFC 4533. This is true. But the references contains the hintWork in Progress
https://tools.ietf.org/html/rfc4533#ref-PSEARCH Persistent search is still indraft
state: https://tools.ietf.org/html/draft-ietf-ldapext-psearch-03 So some implementation are supporting this feature but not all. OpenLDAP seems not supporting it.Hi, I would say it’s not a bug at all of
Novell.Directory.Ldap.NETStandard
lib. For me it’s more a misconfigured openldap installation. Here is the log of my OpenLDAP installation running in docker:This is the most relevant line:
6038d99a slap_global_control: unrecognized control: 2.16.840.1.113730.3.4.3
The docker image I used has activated the following controls (supportedControl):
The OID of
Persistent Search Control
which is required for the function is2.16.840.1.113730.3.4.3
I don’t know how to activate it, but perhaps someone else does