question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How do I use Persistent Search and Entry Change Notification of supported controls,this is my code and associated environment

See original GitHub issue

I 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:closed
  • Created 3 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
nesc58commented, Feb 26, 2021

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:

./ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --trustAll \
 --bindDN cn=admin,dc=example,dc=com \
 --bindPassword password << EOF
dn: dc=example,dc=com
changetype: modify
add: aci
aci: (targetcontrol = "2.16.840.1.113730.3.4.3")
 (version 3.0;acl "Allow Persistent Search for My App";
 allow (read)(userdn = "ldap:///cn=admin,dc=example,dc=com");)
EOF

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 s Cannot decode the provided persistent search control because it does not have a value (2) Protocol Error occured.

Change isCritical from false to true solves the problem for me.

The console logs the following when i created a new ldap entry while your application / sample code runs:

Monitoring the events for 0,5 minutes..
change type: ADD
entry: sn=test32,ou=test,dc=example,dc=com

In addition: The word Persistent Search is part of the RFC 4533. This is true. But the references contains the hint Work in Progress https://tools.ietf.org/html/rfc4533#ref-PSEARCH Persistent search is still in draft 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.

1reaction
nesc58commented, Feb 26, 2021

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:

6038d999 conn=1000 fd=12 ACCEPT from IP=172.17.0.1:56350 (IP=0.0.0.0:389)
6038d999 conn=1000 op=0 BIND dn="cn=admin,dc=example,dc=org" method=128
6038d999 conn=1000 op=0 BIND dn="cn=admin,dc=example,dc=org" mech=SIMPLE ssf=0
6038d999 conn=1000 op=0 RESULT tag=97 err=0 text=
6038d99a conn=1000 op=1 SRCH base="dc=example,dc=org" scope=2 deref=0 filter="(objectClass=*)"
6038d99a conn=1000 op=1 SRCH attr=*
6038d99a slap_global_control: unrecognized control: 2.16.840.1.113730.3.4.3
6038d99a conn=1000 op=1 SEARCH RESULT tag=101 err=0 nentries=1 text=
6038da07 conn=1001 fd=13 ACCEPT from IP=172.17.0.1:56354 (IP=0.0.0.0:389)

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):

1.2.826.0.1.3344810.2.3
1.2.840.113556.1.4.319
1.3.6.1.1.12
1.3.6.1.1.13.1
1.3.6.1.1.13.2
1.3.6.1.1.22
1.3.6.1.4.1.4203.1.10.1
2.16.840.1.113730.3.4.18
2.16.840.1.113730.3.4.2

The OID of Persistent Search Control which is required for the function is 2.16.840.1.113730.3.4.3

I don’t know how to activate it, but perhaps someone else does

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found