4
votes

I'm trying to do a paged search in an Active Directory using Novell.Directory.Ldap.NETStandard (https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard) and Simple Paged Results control (https://ldapwiki.com/wiki/Simple%20Paged%20Results%20Control).

First page works fine but the second one throws "Unavailable Critical Extension" on the searchResult.next() line. When looking in the event log for ActiveDirectory I found:

00000057: LdapErr: DSID-0C090809, comment: Error processing control, data 0, v23f0 0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:

We have also tried the LdapVirtualListControl but run into a different problem, see How to do a paged search on an Ldap server with > 10000 entries using Novell.Directory.Ldap.NETStandard?

Here are a simplified code we use to reproduce:

        // Connection
        var ldapConn = new LdapConnection()
        {
            SecureSocketLayer = true,
        };
        ldapConn.UserDefinedServerCertValidationDelegate += (sender, certificate, chain, sslPolicyErrors) => true;
        ldapConn.Connect(host, 636);
        ldapConn.Bind(username, password);

        // Constraints
        LdapSearchConstraints searchConstraints = (LdapSearchConstraints)_conn.SearchConstraints.Clone();
        int pageSize = 100, count = 0;
        bool exit = false;
        const string LDAP_SERVER_SIMPLE_PAGED_RESULT_OID = "1.2.840.113556.1.4.319";

        LdapControl pageControl = null;

        do
        {
            int inPageCount = 0;

            // Add Simple Paged Result control
            var request = new Asn1Sequence(2);
            request.add(new Asn1Integer(pageSize));
            request.add(pageControl == null ? new Asn1OctetString("") : new Asn1OctetString(pageControl.getValue()));
            searchConstraints.setControls(
                new LdapControl(LDAP_SERVER_SIMPLE_PAGED_RESULT_OID, true, request.getEncoding(new LBEREncoder()))
            );

            // Get search result
            var searchResult = (LdapSearchResults)ldapConn.Search(container, LdapConnection.SCOPE_SUB, query, null, false, searchConstraints);
            while (searchResult.hasMore())
            {

                // Detect simple paged result control
                pageControl = searchResult.ResponseControls?.Where(rc => rc.ID == LDAP_SERVER_SIMPLE_PAGED_RESULT_OID).FirstOrDefault();
                if (pageControl != null) break;

                var nextEntry = searchResult.next();
                inPageCount++;

            }
            count += inPageCount;

            // Exit if no more pages
            exit = pageControl == null;

        } while (!exit);
1

1 Answers

4
votes

Why code does not work

According to RFC Simple Paged Results Control encoded as

realSearchControlValue ::= SEQUENCE { size INTEGER (0..maxInt), -- requested page size from client -- result set size estimate from server cookie OCTET STRING } which may be clear seen on the next screenshot (taken from Wireshark).

:

When the client adds control to the request, size is set to the desired number of elements in the page and cookie is the opaque structure from the previous server response (empty for the first request).

When you try to construct control in your request, you mistakenly add the whole control value instead of cookie (pageControl.getValue()):

 var request = new Asn1Sequence(2);
 request.add(new Asn1Integer(pageSize));
 request.add(pageControl == null ? new Asn1OctetString("") : new Asn1OctetString(pageControl.getValue()));

It makes all requests after the first one incorrect.

Proposed solution

Take a look at https://github.com/metacube/PagedResultsControl. I've created typed Simple Paged Results Control implementation which encapsulates decoding/ encoding logic. Works perfectly fine for me in the case of 100 000+ entries from Active Directory.

The test application shows basic usage.