1 2 Previous Next 23 Replies Latest reply: May 16, 2011 2:18 PM by Al_Maline RSS

    JNDI, Active Directory & Persistent Searches (part 1)

    800477
      I have noted a few users having problems implementing persistent searches against Active Directory.

      Active Directory does not support the persistent search control, implemented in the JNDI NamingListener class (ObjectChange & NamespaceChange methods).

      Active Directory does support a number of mechanisms to allow applications to receive updates from the Directory.

      One of these is the Microsoft DirSync protocol, and is designed to allow an application to poll the directory and retrieve the changes that have occurred since the preceeeding call.

      The second is LDAP Notifications, where a client application maintains a persistent search against the Active Directory and receives notifications of changes that have occured. (see part 2)

      For Microsoft information on both of these mechanisms refer to http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ad/ad/overview_of_change_tracking_techniques.asp

      The DirSync control implemented in the Sun LDAP Booster pack is an implementation of the Microsoft DirSync protocol.

      The following code examples demonstrates the use of the DirSync control.

      Please note that these are not necessarily a good example of Java programming style as I'll readily admit to not being a Java developer !

      Also from a security perspective, these are poor examples. First of all the credentials are embedded in the code, a big NO NO. Just as importantly not only are the user's credentials sent in clear but also all traffic between the client and the Active Directory, which may include confidential information, are also sent in clear. Ideally, you should use JAAS (GSSAPI & Kerberos) to support the authentication and the communications should be protected using SSL, either directly over SSL or using StartTLS.

      The first example detects all objects that have been deleted in the Active Directory, demonstrating both the use of the DirSync Control and the Deleted
      /**
       * delchanges.java
       * December 2004
       * Sample JNDI application to perform a DirSync search against AD
       * Use both DirSync & Deletion controls to detect deleted objects
       * 
        */
      
      import java.io.*;
      import java.util.Hashtable;
      import javax.naming.*;
      import javax.naming.ldap.*;
      import javax.naming.directory.*;
      import com.sun.jndi.ldap.ctl.*;
      import com.sun.jndi.ldap.ctl.DirSyncControl.*;
      import com.sun.jndi.ldap.ctl.DirSyncResponseControl.*;
      
      class DeletedControl implements Control {
           public byte[] getEncodedValue() {
                return new byte[] {};
           }
           public String getID() {
                return "1.2.840.113556.1.4.417";
           }
           public boolean isCritical() {
                return true;
           }
      }
      
      public class delchanges     {
           public static void main (String[] args)     {
                Hashtable env = new Hashtable();
                String adminName = "CN=Administrator,CN=Users,DC=ANTIPODES,DC=COM";
                String adminPassword = "XXXXXXX";
                String ldapURL = "ldap://mydc.antipodes.com:389";
                env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
                //set security credentials, note using simple cleartext authentication
                env.put(Context.SECURITY_AUTHENTICATION,"simple");
                env.put(Context.SECURITY_PRINCIPAL,adminName);
                env.put(Context.SECURITY_CREDENTIALS,adminPassword);
                          
                //connect to my domain controller
                env.put(Context.PROVIDER_URL,ldapURL);
                
                try {
      
                     // Create the initial directory context
                     LdapContext ctx = new InitialLdapContext(env,null);
                
                     // Create the search controls           
                     SearchControls searchCtls = new SearchControls();
                
                     //Specify the attributes to return
                     //For dirsync use null to return all changes
                     String returnedAtts[]={};
                     searchCtls.setReturningAttributes(returnedAtts);
                
                     //Specify the search scope
                     searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
      
                     //specify the LDAP search filter
                     String searchFilter = "(&(objectClass=user)(isDeleted=TRUE))";
      
                     //Specify the Base for the search
                     String searchBase = "DC=antipodes,DC=com";
      
                     //initialize counter to total the results
                     int totalResults = 0;
      
                     //Specifiy the Deleted Control
                     Control[] delCtl = new Control[]{new DeletedControl()};
                     ctx.setRequestControls(delCtl);
                      
                     //Specify the DirSync and DirSyncResponse controls
                     byte[] cookie = null;
                     Control[] rspCtls;
                     Control[] ctls = new Control[] {new DirSyncControl()};
                     ctx.setRequestControls(ctls);
      
                     // Search for objects using the filter
                     NamingEnumeration answer = ctx.search(searchBase, searchFilter, searchCtls);
      
                     //Loop through the search results
                     while (answer.hasMoreElements()) {
                              SearchResult sr = (SearchResult)answer.next();
      
                          totalResults++;
      
                          System.out.println(">>>" + sr.getName());
      
                     }
      
                      System.out.println("Current deleted users: " + totalResults);
      
                     //save the response controls
                     if ((rspCtls = ctx.getResponseControls()) != null) {
                          System.out.println("Response Controls: " + rspCtls.length);
                          for (int i=0; i < rspCtls.length;i++) {
                               if (rspCtls[i] instanceof DirSyncResponseControl) {
                                    DirSyncResponseControl rspCtl = (DirSyncResponseControl)rspCtls;
                                    cookie = rspCtl.getCookie();
                                    System.out.println("Flag: " + rspCtl.getFlag());
                                    System.out.println("Length: " + rspCtl.getMaxReturnLength());
                                    System.out.println("Cookie: " + cookie.toString());
                               }
                          }
                     }

           //Now start a loop, prompt the user to continue/quit and continue searching for newly deleted objects
                     BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                     while (true) {
                          try {
                               String input;
                               while (true) {
                                    System.out.println("Press x to exit, return to continue");
                                    input = in.readLine();
                                    if (input.startsWith("x") || input.startsWith("X")) {
                                         ctx.close();
                                         System.exit(0);
                                    }
                                    else {
                                         break;
                                    }
                               }
                          }     
                          catch (IOException e)     {
                               System.err.println();
                          }

                          //now lets do the search again using the cookie
                          totalResults = 0;

                          //use the saved cookie
                          ctx.setRequestControls(new Control[]{new DirSyncControl(1,Integer.MAX_VALUE,cookie,true)});

                          //execute the search again
                          answer = ctx.search(searchBase, searchFilter, searchCtls);

                          //Loop through the search results
                          while (answer.hasMoreElements()) {
                               SearchResult sr = (SearchResult)answer.next();

                               totalResults++;

                               System.out.println(">>>" + sr.getName());

                          }

                          System.out.println("Recently deleted users: " + totalResults);

                          //Save the response control again
                          if ((rspCtls = ctx.getResponseControls()) != null) {
                               System.out.println("Response Controls: " + rspCtls.length);
                               for (int i=0; i < rspCtls.length;i++) {
                                    if (rspCtls[i] instanceof DirSyncResponseControl) {
                                         DirSyncResponseControl rspCtl = (DirSyncResponseControl)rspCtls[i];
                                         cookie = rspCtl.getCookie();
                                         System.out.println("Flag: " + rspCtl.getFlag());
                                         System.out.println("Length: " + rspCtl.getMaxReturnLength());
                                         System.out.println("Cookie: " + cookie.toString());
                                    }
                               }
                          }
                
                     }

                }
                catch (NamingException e) {
                     System.err.println("Problem searching directory: " + e);
                }
                catch (java.io.IOException e) {
      System.err.println("Problem searching directory: " + e);
                }
           
           }
      }



      The next example, which is merely a variation of the above, retrieves any changes that have been to any object that satisfy the search criteria, in this case, any user whose first name is "John"
      /**
       * modchanges.java
       * December 2004
       * Sample JNDI application to perform a DirSync search against AD
       * Retrieve any newly modified object and dsiplay the changed attributes
       *
       */
      
      import java.io.*;
      import java.util.Hashtable;
      import javax.naming.*;
      import javax.naming.ldap.*;
      import javax.naming.directory.*;
      import com.sun.jndi.ldap.ctl.*;
      import com.sun.jndi.ldap.ctl.DirSyncControl.*;
      import com.sun.jndi.ldap.ctl.DirSyncResponseControl.*;
      
      public class modchanges     {
           public static void main (String[] args)     {
                Hashtable env = new Hashtable();
                String adminName = "CN=Administrator,CN=Users,DC=ANTIPODES,DC=COM";
                String adminPassword = "XXXXXXXX";
                String ldapURL = "ldap://mydc.antipodes.com:389";
                env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
                //set security credentials, note using simple cleartext authentication
                env.put(Context.SECURITY_AUTHENTICATION,"simple");
                env.put(Context.SECURITY_PRINCIPAL,adminName);
                env.put(Context.SECURITY_CREDENTIALS,adminPassword);
                          
                //connect to my domain controller
                env.put(Context.PROVIDER_URL,ldapURL);
                
                try {
      
                     // Create the initial directory context
                     LdapContext ctx = new InitialLdapContext(env,null);
                
                     //Create the search controls           
                     SearchControls searchCtls = new SearchControls();
                
                     //Specify the attributes to return, null initially return all values     
                     //and in subsequent calls, null returns all modified attributes
                     String returnedAtts[]={};
                     searchCtls.setReturningAttributes(returnedAtts);
                
                     //Specify the search scope
                     searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
      
                     //specify the LDAP search filter
                     String searchFilter = "(&(objectClass=user)(givenName=John))";
      
                     //Specify the Base for the search
                     String searchBase = "DC=antipodes,DC=com";
      
                     //initialize counter to total the results
                     int totalResults = 0;
      
                     //Specify the DirSync and DirSyncResponse controls
                     byte[] cookie = null;
                     Control[] rspCtls;
                     Control[] ctls = new Control[] {new DirSyncControl()};
                     ctx.setRequestControls(ctls);
      
                     //Search for objects using the filter
                     NamingEnumeration answer = ctx.search(searchBase, searchFilter, searchCtls);
      
                     //Loop through the search results
                     while (answer.hasMoreElements()) {
                              SearchResult sr = (SearchResult)answer.next();
      
                          totalResults++;
      
                          System.out.println(">>>" + sr.getName());
      
                          //First time around, no need to print any attributes 
                
                     }
      
                      System.out.println("Current results: " + totalResults);
      
                     //save the response controls
                     if ((rspCtls = ctx.getResponseControls()) != null) {
                          System.out.println("Response Controls: " + rspCtls.length);
                          for (int i=0; i < rspCtls.length;i++) {
                               if (rspCtls[i] instanceof DirSyncResponseControl) {
                                    DirSyncResponseControl rspCtl = (DirSyncResponseControl)rspCtls;
                                    cookie = rspCtl.getCookie();
                                    System.out.println("Flag: " + rspCtl.getFlag());
                                    System.out.println("Length: " + rspCtl.getMaxReturnLength());
                                    System.out.println("Cookie: " + cookie.toString());
                               }
                          }
                     }

                     //Now start a loop, prompt the user to continue/quit and continue searching for modified objects
                     BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                     while (true) {
                          try {
                               String input;
                               while (true) {
                                    System.out.println("Press x to exit, return to continue");
                                    input = in.readLine();
                                    if (input.startsWith("x") || input.startsWith("X")) {
                                         ctx.close();
                                         System.exit(0);
                                    }
                                    else {
                                         break;
                                    }
                               }
                          }     
                          catch (IOException e)     {
                               System.err.println();
                          }

                          //now lets do the search again using the cookie
                          totalResults = 0;

                          //use the saved cookie in the DirSync control
                          ctx.setRequestControls(new Control[]{new DirSyncControl(1,Integer.MAX_VALUE,cookie,true)});

                          //execute the search again
                          answer = ctx.search(searchBase, searchFilter, searchCtls);

                          //Loop through the search results
                          while (answer.hasMoreElements()) {
                               SearchResult sr = (SearchResult)answer.next();

                               totalResults++;

                               System.out.println(">>>" + sr.getName());

                               //Print out the modified attributes
                               //instanceType and objectGUID are always returned
                               Attributes attrs = sr.getAttributes();
                               if (attrs != null) {
                                    try {

                                         for (NamingEnumeration ae = attrs.getAll();ae.hasMore();) {
                                              Attribute attr = (Attribute)ae.next();
                                              System.out.println("Attribute: " + attr.getID());
                                              for (NamingEnumeration e = attr.getAll();e.hasMore();System.out.println(" " + e.next().toString()));

                                         }
                                    }
                                    catch (NullPointerException e)     {
                                         System.err.println();
                                    }
                          
                               }

                          }

                          System.out.println("Recently Modified: " + totalResults);

                          //Save the response control again
                          if ((rspCtls = ctx.getResponseControls()) != null) {
                               System.out.println("Response Controls: " + rspCtls.length);
                               for (int i=0; i < rspCtls.length;i++) {
                                    if (rspCtls[i] instanceof DirSyncResponseControl) {
                                         DirSyncResponseControl rspCtl = (DirSyncResponseControl)rspCtls[i];
                                         cookie = rspCtl.getCookie();
                                         System.out.println("Flag: " + rspCtl.getFlag());
                                         System.out.println("Length: " + rspCtl.getMaxReturnLength());
                                         System.out.println("Cookie: " + cookie.toString());
                                    }
                               }
                          }
                
                     }

                }
                catch (NamingException e) {
                     System.err.println("Problem searching directory: " + e);
                }
                catch (java.io.IOException e) {
                     System.err.println("Problem searching directory: " + e);
                }
           }
      }


        • 1. Re: JNDI, Active Directory & Persistent Searches (part 1)
          843793
          Hi Steven,

          How do we do the 2 way [LDAP Notifications]?
          • 2. Re: JNDI, Active Directory & Persistent Searches (part 1)
            800477
            I assume you mean the LDAP Notification Control?
            Refer to "JNDI, Active Directory and Persistent Searches (part 2)" which can be found at
            http://forum.java.sun.com/thread.jspa?threadID=578342&tstart=200
            • 3. Re: JNDI, Active Directory & Persistent Searches (part 1)
              843793
              Hi Steven,

              Thanks for your information. I have executed your code for retrieving the deleted objects. This program returns all the deleted objects as expected.

              My requirement is , say I have an organizationalUnit object Bangalore under dc=jc,dc=com [ OU=Bangalore, DC=JC,DC=Com]. This organizational unit contains user objects like CN=RAM & CN=RAMANIAN.

              I have deleted CN=RAM,OU=Bangalore,DC=JC,DC=Com. Your program returns only CN=RAM. But, I want to get OU=Bangalore also. How do I change the above code to achieve this?

              Thanks in Advance,
              Ramanian S
              • 4. Re: JNDI, Active Directory & Persistent Searches (part 1)
                800477
                You want to retrieve the attribute "lastKnownParent"

                All deleted objects are moved into a special "Deleted Objects" container and have their distinguishedName mangled by appending a carriage return, DEL and a unique GUID. (Note that most of the object's attributes are also deleted)

                Using your example, if you delete CN=RAM,OU=Bangalore,DC=JC,DC=Com, then you will end up with something like
                CN=RAM\0ADEL:some really horrible GUID,CN=Deleted Objects,DC=JC,DC=Com

                If you retrieve the lastKnownParent for that object, it will return OU=Bangalore,DC=JC,DC=Com
                • 5. Re: JNDI, Active Directory & Persistent Searches (part 1)
                  843793
                  Hi,

                  Thanks for the reply. I have come to know that the 'lastKnownParent' attribute is a part of Windows 2003 domain controller. But we are using 2000 version of the same.

                  Is there any way in this ?

                  Also, How do I retrieve all the deleted objects from the Sun ONE directory server?
                  From your program I am unable to do that. I am getting the following exception:

                  "NamingException: Problem getting attribute: inside Sync javax.naming.OperationNotSupportedException: [LDAP: error code 12 - Unavailable Critical Extension];"

                  Thanks in Advance,
                  Subramanian S
                  • 6. Re: JNDI, Active Directory & Persistent Searches (part 1)
                    800477
                    No, lastKnownParent is only supportedon Windows2003 domain controllers. It is used if objects are to be "undeleted", as an alternative to an authoritative restore.

                    The whole purpose of both the DirSynch and LDAP Notification controls are for third party applications such as metadirectories, auditing & reporting, where the application tracks changes made to Active Directory objects.

                    It really is your applications role to save the prior state of objects in an application specific store and be able to determine the historical changes made to objects.

                    With regards to your question about SunOne Directory Server, my expertise is with Active Directory, so I cannot offer an answer. Your application should check to see whether SunOne Directory Server supports the LDAP Notification control (OID = 1.2.840.113556.1.4.528) by querying the supportedControl attribute of the RootDSE.
                    • 7. Re: JNDI, Active Directory & Persistent Searches (part 1)
                      843793
                      I'm trying to retrieve changes from Active Directory using code based on what is listed above in the modChanges class. However, when I retrieve the requestControls from the context, I get back an instance of BasicControl as opposed to an instance of DirSyncResponseControl. I wondered if this was a problem anyone had come across in the past.

                      Thanks in advance.

                      Steve.
                      • 8. Re: JNDI, Active Directory & Persistent Searches (part 1)
                        843793
                        Of course, I meant responseControls in the previous message, not requestControls.
                        • 9. Re: JNDI, Active Directory & Persistent Searches (part 1)
                          800477
                          I have to sincerely apologise as there was conflict with my sample code and the forum's formatting tags. (Note where the code sample becomes italicised).

                          I think the problem you are experiencing is related to this error, especially if you have just copied and pasted the sample code.

                          Change the lines from:
                          if (rspCtls instanceof DirSyncResponseControl) {

                          to

                          if (rspCtls [ i ] instanceof DirSyncResponseControl) {

                          Good luck.
                          • 10. Re: JNDI, Active Directory & Persistent Searches (part 1)
                            843793
                            Steven,

                            Thanks for that, but I'd figured out the reason for the italics, and have the code as you have suggested it !! The search works fine, but at the end of the search (strangely 999 changes only are returned - could this be a clue?) when I get the responseControls back out of the context, element 0 of the Control[] is an instance of a BasicControl where I would expect (and hope) it to be a DirSyncResponseControl, so that I can obtain the cookie.

                            Don't know if that sheds any more light on my predicament?

                            Steve.
                            • 11. Re: JNDI, Active Directory & Persistent Searches (part 1)
                              800477
                              Hmm.... 999 could indeed be the clue:-)

                              By default AD returns 1000 results per query.

                              The way to overcome this limitation is to either increase MaxPageSize in the LDAP Query Policy (refer to http://support.microsoft.com/?kbid=315071), however this is not the recomend approach.

                              The preferred approach is to use Paged Results. (refer to JNDI, Active Directory, Paging and Range Retrieval http://forum.java.sun.com/thread.jspa?threadID=578347&tstart=0)

                              Without duplicating your results, I would guess that the response control being returned could be a LDAP error message indicating a partial result or a continuation reference or something like that.
                              • 12. Re: JNDI, Active Directory & Persistent Searches (part 1)
                                843793
                                Thanks Steven, I'll give that a go in the morning.
                                • 13. Re: JNDI, Active Directory & Persistent Searches (part 1)
                                  843793
                                  Hello,

                                  I attempted to run the application against Windows 2000 server.
                                  It returns the error

                                  Problem searching directory: javax.naming.NoPermissionException: [LDAP: error code 50 - 00002105: LdapErr: DSID-xxxx, comment: Error processing control, data 0, v893

                                  also if I try to run my own implementation based on the Novell JDK, I'm getting olmost the same

                                  LDAPException: Unwilling To Perform (53) Unwilling To Perform
                                  LDAPException: Server Message: 00002111: LdapErr: DSID-0C0906A4, comment: Error processing control, data 0, v893

                                  Could you help me with that?

                                  thank you                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
                                  • 14. Re: JNDI, Active Directory & Persistent Searches (part 1)
                                    800477
                                    I recomend that you contact Microsoft's official support channels.

                                    The sample code runs fine on my Windows Server 2003 test domain. I don't have Win2K running anymore so I can't comment on the differences between the two. (How strange to think that Win2K is almost 6 years old !)

                                    As far as I can tell with Win2K3, if a user doesn't have permissions to list the contents of OU's or doesn't have permissions to read an objects attributes, they just don't get returned in the results set.

                                    Sorry that this is not much help.
                                    1 2 Previous Next