diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 4ef6264179e35..146964696be33 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -233,6 +233,7 @@ jdk.jfr; exports jdk.internal.ref to java.desktop, + java.naming, java.net.http; exports jdk.internal.reflect to java.logging, diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/AbstractLdapNamingEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/AbstractLdapNamingEnumeration.java index 0442eae328d0f..85faabec20a33 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/AbstractLdapNamingEnumeration.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/AbstractLdapNamingEnumeration.java @@ -33,6 +33,11 @@ import javax.naming.directory.Attributes; import javax.naming.ldap.Control; +import java.lang.invoke.VarHandle; +import java.lang.ref.Cleaner.Cleanable; +import java.lang.ref.Reference; +import jdk.internal.ref.CleanerFactory; + /** * Basic enumeration for NameClassPair, Binding, and SearchResults. */ @@ -42,17 +47,60 @@ abstract class AbstractLdapNamingEnumeration protected Name listArg; - private boolean cleaned = false; - private LdapResult res; - private LdapClient enumClnt; private Continuation cont; // used to fill in exceptions private Vector entries = null; private int limit = 0; private int posn = 0; - protected LdapCtx homeCtx; + private LdapReferralException refEx = null; private NamingException errEx = null; + /* This class maintains the pieces of state that need to be cleaned up (or + * are needed for cleanup). It gets registered with Cleaner to perform cleanup. + * + * reachabilityFences are used to ensure that an AbstractLdapNamingEnumeration + * instance does not become unreachable while one of its methods is still + * executing (possibly leading to EnumCtx being cleaned up while it's still in use). + * + * Because the state is mutable, fullFence() is used to ensure that changes + * made on the main/program thread are seen by the cleanup thread. + */ + private static class EnumCtx implements Runnable { + private LdapCtx homeCtx; + private LdapResult res; + private LdapClient enumClnt; + + private EnumCtx(LdapCtx homeCtx, LdapResult answer, LdapClient client) { + this.homeCtx = homeCtx; + this.res = answer; + this.enumClnt = client; + } + + @Override + public void run() { + // Ensure changes on the main/program thread happens-before cleanup + VarHandle.fullFence(); + + if (enumClnt != null) { + if (homeCtx != null) { + enumClnt.clearSearchReply(res, homeCtx.reqCtls); + } + enumClnt = null; + } + if (homeCtx != null) { + homeCtx.decEnumCount(); + homeCtx = null; + } + } + } + + private final EnumCtx enumCtx; + private final Cleanable cleanable; + + // Subclasses interact directly with the LdapCtx. This method provides + // access to the LdapCtx within the EnumCtx. + protected final LdapCtx getHomeCtx() { return enumCtx.homeCtx; } + /* * Record the next set of entries and/or referrals. */ @@ -84,7 +132,6 @@ abstract class AbstractLdapNamingEnumeration // otherwise continue - res = answer; entries = answer.entries; limit = (entries == null) ? 0 : entries.size(); // handle empty set this.listArg = listArg; @@ -94,16 +141,38 @@ abstract class AbstractLdapNamingEnumeration refEx = answer.refEx; } + this.enumCtx = new EnumCtx(homeCtx, answer, homeCtx.clnt); // Ensures that context won't get closed from underneath us - this.homeCtx = homeCtx; - homeCtx.incEnumCount(); - enumClnt = homeCtx.clnt; // remember + this.enumCtx.homeCtx.incEnumCount(); + this.cleanable = CleanerFactory.cleaner().register(this, this.enumCtx); + // For finalizers, JLS guarantees that the constructor of a finalizeable + // class will complete before the finalizer is run. Classes that use + // Cleaner instead don't currently have such a guarantee. + + // 'this' needs to stay reachable until registration with Cleaner + // completes, to ensure that the cleanable state that has been created + // will eventually be cleaned up. Within Cleaner.register(), there + // are reachability fences to ensure that the registered object remains + // reachable. + // TODO: Is anything else needed so that this constructor + // "happens-before" the cleaning action ? } @Override public final T nextElement() { try { - return next(); + try { + return next(); + } finally { + // See comment in nextImpl(). This is similar, but in this case, next() + // *is* overridable. Fences are included here, in case next() is + // overridden to access the cleanable state without the proper fences. + + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); + } } catch (NamingException e) { // can't throw exception cleanup(); @@ -114,7 +183,16 @@ public final T nextElement() { @Override public final boolean hasMoreElements() { try { - return hasMore(); + try { + return hasMore(); + } finally { + // Same situation as nextElement() - see comment above + + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); + } } catch (NamingException e) { // can't throw exception cleanup(); @@ -126,45 +204,51 @@ public final boolean hasMoreElements() { * Retrieve the next set of entries and/or referrals. */ private void getNextBatch() throws NamingException { + try { + enumCtx.res = enumCtx.homeCtx.getSearchReply(enumCtx.enumClnt, enumCtx.res); + if (enumCtx.res == null) { + limit = posn = 0; + return; + } - res = homeCtx.getSearchReply(enumClnt, res); - if (res == null) { - limit = posn = 0; - return; - } - - entries = res.entries; - limit = (entries == null) ? 0 : entries.size(); // handle empty set - posn = 0; // reset + entries = enumCtx.res.entries; + limit = (entries == null) ? 0 : entries.size(); // handle empty set + posn = 0; // reset - // minimize the number of calls to processReturnCode() - // (expensive when batchSize is small and there are many results) - if ((res.status != LdapClient.LDAP_SUCCESS) || - ((res.status == LdapClient.LDAP_SUCCESS) && - (res.referrals != null))) { + // minimize the number of calls to processReturnCode() + // (expensive when batchSize is small and there are many results) + if ((enumCtx.res.status != LdapClient.LDAP_SUCCESS) || + ((enumCtx.res.status == LdapClient.LDAP_SUCCESS) && + (enumCtx.res.referrals != null))) { - try { - // convert referrals into a chain of LdapReferralException - homeCtx.processReturnCode(res, listArg); + try { + // convert referrals into a chain of LdapReferralException + enumCtx.homeCtx.processReturnCode(enumCtx.res, listArg); - } catch (LimitExceededException | PartialResultException e) { - setNamingException(e); + } catch (LimitExceededException | PartialResultException e) { + setNamingException(e); + } } - } - // merge any newly received referrals with any current referrals - if (res.refEx != null) { - if (refEx == null) { - refEx = res.refEx; - } else { - refEx = refEx.appendUnprocessedReferrals(res.refEx); + // merge any newly received referrals with any current referrals + if (enumCtx.res.refEx != null) { + if (refEx == null) { + refEx = enumCtx.res.refEx; + } else { + refEx = refEx.appendUnprocessedReferrals(enumCtx.res.refEx); + } + enumCtx.res.refEx = null; // reset } - res.refEx = null; // reset - } - if (res.resControls != null) { - homeCtx.respCtls = res.resControls; + if (enumCtx.res.resControls != null) { + enumCtx.homeCtx.respCtls = enumCtx.res.resControls; + } + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); } } @@ -176,17 +260,23 @@ private void getNextBatch() throws NamingException { */ @Override public final boolean hasMore() throws NamingException { + try { + if (hasMoreCalled) { + return more; + } - if (hasMoreCalled) { - return more; - } - - hasMoreCalled = true; + hasMoreCalled = true; - if (!more) { - return false; - } else { - return (more = hasMoreImpl()); + if (!more) { + return false; + } else { + return (more = hasMoreImpl()); + } + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(enumCtx); } } @@ -195,49 +285,62 @@ public final boolean hasMore() throws NamingException { */ @Override public final T next() throws NamingException { - - if (!hasMoreCalled) { - hasMore(); + try { + if (!hasMoreCalled) { + hasMore(); + } + hasMoreCalled = false; + return nextImpl(); + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(enumCtx); } - hasMoreCalled = false; - return nextImpl(); } /* * Test if unprocessed entries or referrals exist. */ private boolean hasMoreImpl() throws NamingException { - // when page size is supported, this - // might generate an exception while attempting - // to fetch the next batch to determine - // whether there are any more elements - - // test if the current set of entries has been processed - if (posn == limit) { - getNextBatch(); - } - - // test if any unprocessed entries exist - if (posn < limit) { - return true; - } else { - - try { - // try to process another referral - return hasMoreReferrals(); + try { + // when page size is supported, this + // might generate an exception while attempting + // to fetch the next batch to determine + // whether there are any more elements + + // test if the current set of entries has been processed + if (posn == limit) { + getNextBatch(); + } - } catch (LdapReferralException | - LimitExceededException | - PartialResultException e) { - cleanup(); - throw e; + // test if any unprocessed entries exist + if (posn < limit) { + return true; + } else { - } catch (NamingException e) { - cleanup(); - PartialResultException pre = new PartialResultException(); - pre.setRootCause(e); - throw pre; + try { + // try to process another referral + return hasMoreReferrals(); + + } catch (LdapReferralException | + LimitExceededException | + PartialResultException e) { + cleanup(); + throw e; + + } catch (NamingException e) { + cleanup(); + PartialResultException pre = new PartialResultException(); + pre.setRootCause(e); + throw pre; + } } + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(enumCtx); } } @@ -251,22 +354,33 @@ private T nextImpl() throws NamingException { cleanup(); throw cont.fillInException(e); } + // No fences here. nextAux() (source just below) has its own fences. The + // only other thing this method does is call cleanup(). + // If nextAux() were overrideable, this method should probably have + // fences, but it seems OK for now without. } private T nextAux() throws NamingException { - if (posn == limit) { - getNextBatch(); // updates posn and limit - } + try { + if (posn == limit) { + getNextBatch(); // updates posn and limit + } - if (posn >= limit) { - cleanup(); - throw new NoSuchElementException("invalid enumeration handle"); - } + if (posn >= limit) { + cleanup(); + throw new NoSuchElementException("invalid enumeration handle"); + } - LdapEntry result = entries.elementAt(posn++); + LdapEntry result = entries.elementAt(posn++); - // gets and outputs DN from the entry - return createItem(result.DN, result.attributes, result.respCtls); + // gets and outputs DN from the entry + return createItem(result.DN, result.attributes, result.respCtls); + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(enumCtx); + } } protected final String getAtom(String dn) { @@ -278,6 +392,7 @@ protected final String getAtom(String dn) { } catch (NamingException e) { return dn; } + // No fences - method clearly does not access cleanable state } protected abstract T createItem(String dn, Attributes attrs, @@ -289,15 +404,23 @@ protected abstract T createItem(String dn, Attributes attrs, */ @Override public void appendUnprocessedReferrals(LdapReferralException ex) { - if (refEx != null) { - refEx = refEx.appendUnprocessedReferrals(ex); - } else { - refEx = ex.appendUnprocessedReferrals(refEx); + try { + if (refEx != null) { + refEx = refEx.appendUnprocessedReferrals(ex); + } else { + refEx = ex.appendUnprocessedReferrals(refEx); + } + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); } } final void setNamingException(NamingException e) { errEx = e; + // No fences - method clearly does not access cleanable state } protected abstract AbstractLdapNamingEnumeration getReferredResults( @@ -309,53 +432,59 @@ protected abstract AbstractLdapNamingEnumeration getRef * results. */ protected final boolean hasMoreReferrals() throws NamingException { + try { + if ((refEx != null) && !(errEx instanceof LimitExceededException) && + (refEx.hasMoreReferrals() || refEx.hasMoreReferralExceptions())) { - if ((refEx != null) && !(errEx instanceof LimitExceededException) && - (refEx.hasMoreReferrals() || refEx.hasMoreReferralExceptions())) { - - if (homeCtx.handleReferrals == LdapClient.LDAP_REF_THROW) { - throw (NamingException)(refEx.fillInStackTrace()); - } - - // process the referrals sequentially - while (true) { - - LdapReferralContext refCtx = - (LdapReferralContext)refEx.getReferralContext( - homeCtx.envprops, homeCtx.reqCtls); + if (enumCtx.homeCtx.handleReferrals == LdapClient.LDAP_REF_THROW) { + throw (NamingException)(refEx.fillInStackTrace()); + } - try { + // process the referrals sequentially + while (true) { - update(getReferredResults(refCtx)); - break; + LdapReferralContext refCtx = + (LdapReferralContext)refEx.getReferralContext( + enumCtx.homeCtx.envprops, enumCtx.homeCtx.reqCtls); - } catch (LdapReferralException re) { + try { - // record a previous exception and quit if any limit is reached - var namingException = re.getNamingException(); - if (namingException instanceof LimitExceededException) { - errEx = namingException; + update(getReferredResults(refCtx)); break; - } else if (errEx == null) { - errEx = namingException; - } - refEx = re; - continue; - } finally { - // Make sure we close referral context - refCtx.close(); + } catch (LdapReferralException re) { + + // record a previous exception and quit if any limit is reached + var namingException = re.getNamingException(); + if (namingException instanceof LimitExceededException) { + errEx = namingException; + break; + } else if (errEx == null) { + errEx = namingException; + } + refEx = re; + continue; + + } finally { + // Make sure we close referral context + refCtx.close(); + } } - } - return hasMoreImpl(); + return hasMoreImpl(); - } else { - cleanup(); + } else { + cleanup(); - if (errEx != null) { - throw errEx; + if (errEx != null) { + throw errEx; + } + return (false); } - return (false); + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(enumCtx); } } @@ -364,49 +493,43 @@ protected final boolean hasMoreReferrals() throws NamingException { * with those of the current enumeration. */ protected void update(AbstractLdapNamingEnumeration ne) { - // Cleanup previous context first - homeCtx.decEnumCount(); - - // New enum will have already incremented enum count and recorded clnt - homeCtx = ne.homeCtx; - enumClnt = ne.enumClnt; - - // Do this to prevent referral enumeration (ne) from decrementing - // enum count because we'll be doing that here from this - // enumeration. - ne.homeCtx = null; - - // Record rest of information from new enum - posn = ne.posn; - limit = ne.limit; - res = ne.res; - entries = ne.entries; - refEx = ne.refEx; - listArg = ne.listArg; - // record a previous exception and quit if any limit is reached - if (errEx == null || ne.errEx instanceof LimitExceededException) { - errEx = ne.errEx; + try { + // Cleanup previous context first + getHomeCtx().decEnumCount(); + + // New enum will have already incremented enum count and recorded clnt + enumCtx.homeCtx = ne.enumCtx.homeCtx; + enumCtx.enumClnt = ne.enumCtx.enumClnt; + + // 'this' and 'ne' now both refer to ne's homeCtx. 'this' will + // decrement homeCtx's enum count later (via cleanup() or Cleaner). + // Clear ne's reference to homeCtx so ne's Cleaner doesn't + // *also* decrement the count. + ne.enumCtx.homeCtx = null; + + // Record rest of information from new enum + posn = ne.posn; + limit = ne.limit; + enumCtx.res = ne.enumCtx.res; + entries = ne.entries; + refEx = ne.refEx; + listArg = ne.listArg; + // record a previous exception and quit if any limit is reached + if (errEx == null || ne.errEx instanceof LimitExceededException) { + errEx = ne.errEx; + } + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(ne); + Reference.reachabilityFence(this); } } - @SuppressWarnings("removal") - protected final void finalize() { - cleanup(); - } - protected final void cleanup() { - if (cleaned) return; // been there; done that - - if(enumClnt != null) { - enumClnt.clearSearchReply(res, homeCtx.reqCtls); - } - - enumClnt = null; - cleaned = true; - if (homeCtx != null) { - homeCtx.decEnumCount(); - homeCtx = null; - } + // Run the cleaning action (if it has not run already) + cleanable.clean(); } @Override diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java index 668741e9b95ca..91b6b60113979 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package com.sun.jndi.ldap; +import java.lang.invoke.VarHandle; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedActionException; @@ -34,6 +35,7 @@ import javax.naming.directory.*; import javax.naming.ldap.Control; import javax.naming.spi.*; +import java.lang.ref.Reference; import com.sun.jndi.toolkit.ctx.Continuation; @@ -54,57 +56,71 @@ final class LdapBindingEnumeration protected Binding createItem(String dn, Attributes attrs, Vector respCtls) throws NamingException { + try { + Object obj = null; + String atom = getAtom(dn); + LdapCtx homeCtx = getHomeCtx(); - Object obj = null; - String atom = getAtom(dn); - - if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) { - // serialized object or object reference - try { - PrivilegedExceptionAction pa = () -> Obj.decodeObject(attrs); - obj = AccessController.doPrivileged(pa, acc); - } catch (PrivilegedActionException e) { - throw (NamingException)e.getException(); + if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) { + // serialized object or object reference + try { + PrivilegedExceptionAction pa = () -> Obj.decodeObject(attrs); + obj = AccessController.doPrivileged(pa, acc); + } catch (PrivilegedActionException e) { + throw (NamingException) e.getException(); + } + } + if (obj == null) { + // DirContext object + obj = new LdapCtx(homeCtx, dn); } - } - if (obj == null) { - // DirContext object - obj = new LdapCtx(homeCtx, dn); - } - CompositeName cn = new CompositeName(); - cn.add(atom); + CompositeName cn = new CompositeName(); + cn.add(atom); - try { - obj = DirectoryManager.getObjectInstance(obj, cn, homeCtx, - homeCtx.envprops, attrs); + try { + obj = DirectoryManager.getObjectInstance(obj, cn, homeCtx, + homeCtx.envprops, attrs); - } catch (NamingException e) { - throw e; + } catch (NamingException e) { + throw e; - } catch (Exception e) { - NamingException ne = - new NamingException( - "problem generating object using object factory"); - ne.setRootCause(e); - throw ne; - } + } catch (Exception e) { + NamingException ne = + new NamingException( + "problem generating object using object factory"); + ne.setRootCause(e); + throw ne; + } - Binding binding; - if (respCtls != null) { - binding = new BindingWithControls(cn.toString(), obj, - homeCtx.convertControls(respCtls)); - } else { - binding = new Binding(cn.toString(), obj); + Binding binding; + if (respCtls != null) { + binding = new BindingWithControls(cn.toString(), obj, + homeCtx.convertControls(respCtls)); + } else { + binding = new Binding(cn.toString(), obj); + } + binding.setNameInNamespace(dn); + return binding; + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); } - binding.setNameInNamespace(dn); - return binding; } @Override protected AbstractLdapNamingEnumeration getReferredResults( LdapReferralContext refCtx) throws NamingException{ - // repeat the original operation at the new context - return (AbstractLdapNamingEnumeration)refCtx.listBindings(listArg); + try { + // repeat the original operation at the new context + return (AbstractLdapNamingEnumeration) refCtx.listBindings(listArg); + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); + } } } diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapNamingEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapNamingEnumeration.java index c923fda26f76b..268a3c521a885 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapNamingEnumeration.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapNamingEnumeration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,9 +29,10 @@ import javax.naming.directory.*; import com.sun.jndi.toolkit.ctx.Continuation; +import java.lang.invoke.VarHandle; import java.util.Vector; import javax.naming.ldap.Control; - +import java.lang.ref.Reference; final class LdapNamingEnumeration extends AbstractLdapNamingEnumeration { @@ -46,35 +47,49 @@ final class LdapNamingEnumeration @Override protected NameClassPair createItem(String dn, Attributes attrs, Vector respCtls) throws NamingException { + try { + LdapCtx homeCtx = getHomeCtx(); + Attribute attr; + String className = null; - Attribute attr; - String className = null; - - // use the Java classname if present - if ((attr = attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME])) != null) { - className = (String)attr.get(); - } else { - className = defaultClassName; - } - CompositeName cn = new CompositeName(); - cn.add(getAtom(dn)); + // use the Java classname if present + if ((attr = attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME])) != null) { + className = (String) attr.get(); + } else { + className = defaultClassName; + } + CompositeName cn = new CompositeName(); + cn.add(getAtom(dn)); - NameClassPair ncp; - if (respCtls != null) { - ncp = new NameClassPairWithControls( + NameClassPair ncp; + if (respCtls != null) { + ncp = new NameClassPairWithControls( cn.toString(), className, homeCtx.convertControls(respCtls)); - } else { - ncp = new NameClassPair(cn.toString(), className); + } else { + ncp = new NameClassPair(cn.toString(), className); + } + ncp.setNameInNamespace(dn); + return ncp; + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); } - ncp.setNameInNamespace(dn); - return ncp; } @Override protected AbstractLdapNamingEnumeration getReferredResults( LdapReferralContext refCtx) throws NamingException { - // repeat the original operation at the new context - return (AbstractLdapNamingEnumeration)refCtx.list(listArg); + try { + // repeat the original operation at the new context + return (AbstractLdapNamingEnumeration)refCtx.list(listArg); + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); + } } } diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java index 9df03d88c55e9..4b4167e098402 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package com.sun.jndi.ldap; +import java.lang.invoke.VarHandle; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedActionException; @@ -35,6 +36,7 @@ import javax.naming.spi.*; import javax.naming.ldap.*; import javax.naming.ldap.LdapName; +import java.lang.ref.Reference; import com.sun.jndi.toolkit.ctx.Continuation; @@ -58,6 +60,10 @@ final class LdapSearchEnumeration // fully qualified name of starting context of search startName = new LdapName(starter); searchArgs = args; + // No fences here for now. + // super() call (aka AbstrctLdapNamingEnumeration ctor) keeps 'this' + // reachable until end of Cleaner registration. Code after that + // clearly does not touch cleanable state. } @SuppressWarnings("removal") @@ -65,127 +71,134 @@ final class LdapSearchEnumeration protected SearchResult createItem(String dn, Attributes attrs, Vector respCtls) throws NamingException { + try { + LdapCtx homeCtx = getHomeCtx(); + Object obj = null; - Object obj = null; - - String relStart; // name relative to starting search context - String relHome; // name relative to homeCtx.currentDN - boolean relative = true; // whether relative to currentDN + String relStart; // name relative to starting search context + String relHome; // name relative to homeCtx.currentDN + boolean relative = true; // whether relative to currentDN - // need to strip off all but lowest component of dn - // so that is relative to current context (currentDN) + // need to strip off all but lowest component of dn + // so that is relative to current context (currentDN) - try { - Name parsed = new LdapName(dn); - // System.err.println("dn string: " + dn); - // System.err.println("dn name: " + parsed); - - if (startName != null && parsed.startsWith(startName)) { - relStart = parsed.getSuffix(startName.size()).toString(); - relHome = parsed.getSuffix(homeCtx.currentParsedDN.size()).toString(); - } else { + try { + Name parsed = new LdapName(dn); + // System.err.println("dn string: " + dn); + // System.err.println("dn name: " + parsed); + + if (startName != null && parsed.startsWith(startName)) { + relStart = parsed.getSuffix(startName.size()).toString(); + relHome = parsed.getSuffix(homeCtx.currentParsedDN.size()).toString(); + } else { + relative = false; + relHome = relStart = + LdapURL.toUrlString(homeCtx.hostname, homeCtx.port_number, + dn, homeCtx.hasLdapsScheme); + } + } catch (NamingException e) { + // could not parse name relative = false; relHome = relStart = LdapURL.toUrlString(homeCtx.hostname, homeCtx.port_number, dn, homeCtx.hasLdapsScheme); } - } catch (NamingException e) { - // could not parse name - relative = false; - relHome = relStart = - LdapURL.toUrlString(homeCtx.hostname, homeCtx.port_number, - dn, homeCtx.hasLdapsScheme); - } - // Name relative to search context - CompositeName cn = new CompositeName(); - if (!relStart.isEmpty()) { - cn.add(relStart); - } - - // Name relative to homeCtx - CompositeName rcn = new CompositeName(); - if (!relHome.isEmpty()) { - rcn.add(relHome); - } - //System.err.println("relStart: " + cn); - //System.err.println("relHome: " + rcn); - - // Fix attributes to be able to get schema - homeCtx.setParents(attrs, rcn); + // Name relative to search context + CompositeName cn = new CompositeName(); + if (!relStart.isEmpty()) { + cn.add(relStart); + } - // only generate object when requested - if (searchArgs.cons.getReturningObjFlag()) { + // Name relative to homeCtx + CompositeName rcn = new CompositeName(); + if (!relHome.isEmpty()) { + rcn.add(relHome); + } + //System.err.println("relStart: " + cn); + //System.err.println("relHome: " + rcn); + + // Fix attributes to be able to get schema + homeCtx.setParents(attrs, rcn); + + // only generate object when requested + if (searchArgs.cons.getReturningObjFlag()) { + + if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) { + // Entry contains Java-object attributes (ser/ref object) + // serialized object or object reference + try { + PrivilegedExceptionAction pea = () -> Obj.decodeObject(attrs); + obj = AccessController.doPrivileged(pea, acc); + } catch (PrivilegedActionException e) { + throw (NamingException)e.getException(); + } + } + if (obj == null) { + obj = new LdapCtx(homeCtx, dn); + } - if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) { - // Entry contains Java-object attributes (ser/ref object) - // serialized object or object reference + // Call getObjectInstance before removing unrequested attributes try { - PrivilegedExceptionAction pea = () -> Obj.decodeObject(attrs); - obj = AccessController.doPrivileged(pea, acc); - } catch (PrivilegedActionException e) { - throw (NamingException)e.getException(); + // rcn is either relative to homeCtx or a fully qualified DN + obj = DirectoryManager.getObjectInstance( + obj, rcn, (relative ? homeCtx : null), + homeCtx.envprops, attrs); + } catch (NamingException e) { + throw e; + } catch (Exception e) { + NamingException ne = + new NamingException( + "problem generating object using object factory"); + ne.setRootCause(e); + throw ne; } - } - if (obj == null) { - obj = new LdapCtx(homeCtx, dn); - } - - // Call getObjectInstance before removing unrequested attributes - try { - // rcn is either relative to homeCtx or a fully qualified DN - obj = DirectoryManager.getObjectInstance( - obj, rcn, (relative ? homeCtx : null), - homeCtx.envprops, attrs); - } catch (NamingException e) { - throw e; - } catch (Exception e) { - NamingException ne = - new NamingException( - "problem generating object using object factory"); - ne.setRootCause(e); - throw ne; - } - // remove Java attributes from result, if necessary - // Even if CLASSNAME attr not there, there might be some - // residual attributes + // remove Java attributes from result, if necessary + // Even if CLASSNAME attr not there, there might be some + // residual attributes - String[] reqAttrs; - if ((reqAttrs = searchArgs.reqAttrs) != null) { - // create an attribute set for those requested - Attributes rattrs = new BasicAttributes(true); // ignore case - for (int i = 0; i < reqAttrs.length; i++) { - rattrs.put(reqAttrs[i], null); - } - for (int i = 0; i < Obj.JAVA_ATTRIBUTES.length; i++) { - // Remove Java-object attributes if not requested - if (rattrs.get(Obj.JAVA_ATTRIBUTES[i]) == null) { - attrs.remove(Obj.JAVA_ATTRIBUTES[i]); + String[] reqAttrs; + if ((reqAttrs = searchArgs.reqAttrs) != null) { + // create an attribute set for those requested + Attributes rattrs = new BasicAttributes(true); // ignore case + for (int i = 0; i < reqAttrs.length; i++) { + rattrs.put(reqAttrs[i], null); + } + for (int i = 0; i < Obj.JAVA_ATTRIBUTES.length; i++) { + // Remove Java-object attributes if not requested + if (rattrs.get(Obj.JAVA_ATTRIBUTES[i]) == null) { + attrs.remove(Obj.JAVA_ATTRIBUTES[i]); + } } } - } - } + } - /* - * name in search result is either the stringified composite name - * relative to the search context that can be passed directly to - * methods of the search context, or the fully qualified DN - * which can be used with the initial context. - */ - SearchResult sr; - if (respCtls != null) { - sr = new SearchResultWithControls( - (relative ? cn.toString() : relStart), obj, attrs, - relative, homeCtx.convertControls(respCtls)); - } else { - sr = new SearchResult( - (relative ? cn.toString() : relStart), - obj, attrs, relative); + /* + * name in search result is either the stringified composite name + * relative to the search context that can be passed directly to + * methods of the search context, or the fully qualified DN + * which can be used with the initial context. + */ + SearchResult sr; + if (respCtls != null) { + sr = new SearchResultWithControls( + (relative ? cn.toString() : relStart), obj, attrs, + relative, homeCtx.convertControls(respCtls)); + } else { + sr = new SearchResult( + (relative ? cn.toString() : relStart), + obj, attrs, relative); + } + sr.setNameInNamespace(dn); + return sr; + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); } - sr.setNameInNamespace(dn); - return sr; } @Override @@ -194,14 +207,24 @@ public void appendUnprocessedReferrals(LdapReferralException ex) { // a referral has been followed so do not create relative names startName = null; super.appendUnprocessedReferrals(ex); + // No fences here for now. + // Rely on fences in the super call. + // Other code in this method clearly doesn't access cleanable state. } @Override protected AbstractLdapNamingEnumeration getReferredResults( LdapReferralContext refCtx) throws NamingException { - // repeat the original operation at the new context - return (AbstractLdapNamingEnumeration)refCtx.search( - searchArgs.name, searchArgs.filter, searchArgs.cons); + try { + // repeat the original operation at the new context + return (AbstractLdapNamingEnumeration) refCtx.search( + searchArgs.name, searchArgs.filter, searchArgs.cons); + } finally { + // Ensure writes are visible to the Cleaner thread + VarHandle.fullFence(); + // Ensure Cleaner does not run until after this method completes + Reference.reachabilityFence(this); + } } @Override @@ -211,9 +234,15 @@ protected void update(AbstractLdapNamingEnumeration ne) // Update search-specific variables LdapSearchEnumeration se = (LdapSearchEnumeration)ne; startName = se.startName; + // No fences here for now. + // super.update() (aka AbstractLdapNamingEnumeration.update()) already + // keeps 'this' and 'ne' reachable throughout that method call. + // After that, we're clearly not accessing cleanable state from 'this' + // or 'ne'. } void setStartName(Name nm) { startName = nm; + // No fences - clearly doesn't access cleanable state } } diff --git a/test/jdk/com/sun/jndi/ldap/CleanerTests/LdapEnumeration.java b/test/jdk/com/sun/jndi/ldap/CleanerTests/LdapEnumeration.java new file mode 100644 index 0000000000000..e0d1edbf2774b --- /dev/null +++ b/test/jdk/com/sun/jndi/ldap/CleanerTests/LdapEnumeration.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8283660 + * @summary Verify the AbstractLdapNamingEnumeration Cleaner performs cleanup correctly + * @modules java.naming/com.sun.jndi.ldap:+open + * @library /test/lib ../lib/ /javax/naming/module/src/test/test/ + * @build LDAPServer LDAPTestUtils + * @run main/othervm LdapEnumeration + */ + +import javax.naming.NamingEnumeration; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.SocketAddress; +import java.util.Hashtable; +import java.util.WeakHashMap; + +import jdk.test.lib.net.URIBuilder; +import jdk.test.lib.util.ForceGC; + +/* + * This test is a copy of com/sun/jndi/ldap/blits/AddTests/AddNewEntry.java, + * altered to confirm correct operation of the NamingEnumeration + * cleaning action. + */ +public class LdapEnumeration { + static WeakHashMap whm = new WeakHashMap(); + + public static void main(String[] args) throws Exception { + // Create unbound server socket + ServerSocket serverSocket = new ServerSocket(); + + // Bind it to the loopback address + SocketAddress sockAddr = new InetSocketAddress( + InetAddress.getLoopbackAddress(), 0); + serverSocket.bind(sockAddr); + + // Construct the provider URL for LDAPTestUtils + String providerURL = URIBuilder.newBuilder() + .scheme("ldap") + .loopback() + .port(serverSocket.getLocalPort()) + .buildUnchecked().toString(); + + Hashtable env; + + // initialize test + env = LDAPTestUtils.initEnv(serverSocket, providerURL, + LdapEnumeration.class.getName(), args, true); + + /* Build attribute set */ + String[] ids = { "objectClass", "sn", "cn", "telephoneNumber", "mail", + "description", "uid" }; + Attribute objectClass = new BasicAttribute(ids[0]); + objectClass.add("top"); + objectClass.add("person"); + objectClass.add("organizationalPerson"); + objectClass.add("inetOrgPerson"); + + Attribute sn = new BasicAttribute(ids[1], "Powers"); + Attribute cn = new BasicAttribute(ids[2], + "Austin \\\"Danger\\\" Powers"); + Attribute telephoneNumber = new BasicAttribute(ids[3], "+44 582 10101"); + Attribute mail = new BasicAttribute(ids[4], "secret_agent_man@imc.org"); + Attribute description = new BasicAttribute(ids[5], "Yea Baby!!"); + description.add("Behave!"); + Attribute uid = new BasicAttribute(ids[6], "secret_agent_man"); + + Attributes attrs = new BasicAttributes(); + attrs.put(objectClass); + attrs.put(sn); + attrs.put(cn); + attrs.put(telephoneNumber); + attrs.put(mail); + attrs.put(description); + attrs.put(uid); + + DirContext ctx = null; + String[] bases = new String[] { (String) env.get("client"), + (String) env.get("vendor"), "Add" }; + String baseDN = LDAPTestUtils.buildDN(bases, (String) env.get("root")); + String entryDN = "cn=Austin Powers," + baseDN; + String expect = ""; // relative name + + try { + // connect to server + ctx = new InitialDirContext(env); + + // add entry + ctx.createSubcontext(entryDN, attrs); + + // specify base search + SearchControls constraints = new SearchControls(); + constraints.setSearchScope(SearchControls.OBJECT_SCOPE); + + NamingEnumeration results = ctx + .search(entryDN, "(objectclass=*)", constraints); + + if (!"LdapSearchEnumeration".equals(results.getClass().getSimpleName())) { + throw new RuntimeException("Unexpected results class: " + results.getClass()); + } + + // fetch enumCount from homeCtx, from EnumCtx, from results + Object enumCtx = getField(results.getClass().getSuperclass(), "enumCtx", results); + Object homeCtx = getField(enumCtx.getClass(), "homeCtx", enumCtx); + int enumCountBefore = (Integer) getField(homeCtx.getClass(), "enumCount", homeCtx); + + whm.put(results, null); + results = null; + + // If the Cleaner holds a reference to 'results', it won't be cleared from the map + ForceGC gc = new ForceGC(); + if (!gc.wait(() -> whm.size() == 0)) { + throw new RuntimeException("NamingEnumeration is still strongly reachable"); + } + + // The enum count should have been decremented + int enumCountAfter = (Integer) getField(homeCtx.getClass(), "enumCount", homeCtx); + int expected = enumCountBefore - 1; + if (enumCountAfter != expected) { + throw new RuntimeException("enumCount was not decremented. Expected: " + + expected + ", Got:" + enumCountAfter); + } + } finally { + LDAPTestUtils.cleanupSubcontext(ctx, entryDN); + LDAPTestUtils.cleanup(ctx); + } + } + /** + * Get an object from a named field. + */ + static Object getField(Class clazz, String fieldName, Object instance) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(instance); + } catch (NoSuchFieldException | IllegalAccessException ex) { + throw new RuntimeException("field unknown or not accessible", ex); + } + } +} diff --git a/test/jdk/com/sun/jndi/ldap/CleanerTests/LdapEnumeration.ldap b/test/jdk/com/sun/jndi/ldap/CleanerTests/LdapEnumeration.ldap new file mode 100644 index 0000000000000..b8960f3bd4b07 --- /dev/null +++ b/test/jdk/com/sun/jndi/ldap/CleanerTests/LdapEnumeration.ldap @@ -0,0 +1,141 @@ +# +# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +################################################################################ +# Capture file for LdapEnumeration.java +# +# NOTE: This hexadecimal dump of LDAP protocol messages was generated by +# running the LdapEnumeration application program against a real LDAP +# server and setting the JNDI/LDAP environment property: +# com.sun.jndi.ldap.trace.ber to activate LDAP message tracing. +# +################################################################################ + + +# LDAP BindRequest + +0000: 30 27 02 01 01 60 22 02 01 03 04 13 63 6E 3D 61 0'...`".....cn=a +0010: 64 6D 69 6E 2C 6F 3D 49 4D 43 2C 63 3D 55 53 80 dmin,o=IMC,c=US. +0020: 08 73 65 63 72 65 74 39 39 .secret99 + + +# LDAP BindResponse + +0000: 30 0C 02 01 01 61 07 0A 01 00 04 00 04 00 0....a........ + + +# LDAP AddRequest + +0000: 30 82 01 5F 02 01 02 68 82 01 3B 04 38 63 6E 3D 0.._...h..;.8cn= +0010: 41 75 73 74 69 6E 20 50 6F 77 65 72 73 2C 6F 75 Austin Powers,ou +0020: 3D 43 6C 69 65 6E 74 31 2C 6F 75 3D 56 65 6E 64 =Client1,ou=Vend +0030: 6F 72 31 2C 6F 75 3D 41 64 64 2C 6F 3D 49 4D 43 or1,ou=Add,o=IMC +0040: 2C 63 3D 55 53 30 81 FE 30 41 04 0B 6F 62 6A 65 ,c=US0..0A..obje +0050: 63 74 43 6C 61 73 73 31 32 04 03 74 6F 70 04 06 ctClass12..top.. +0060: 70 65 72 73 6F 6E 04 14 6F 72 67 61 6E 69 7A 61 person..organiza +0070: 74 69 6F 6E 61 6C 50 65 72 73 6F 6E 04 0D 69 6E tionalPerson..in +0080: 65 74 4F 72 67 50 65 72 73 6F 6E 30 22 04 04 6D etOrgPerson0"..m +0090: 61 69 6C 31 1A 04 18 73 65 63 72 65 74 5F 61 67 ail1...secret_ag +00A0: 65 6E 74 5F 6D 61 6E 40 69 6D 63 2E 6F 72 67 30 ent_man@imc.org0 +00B0: 19 04 03 75 69 64 31 12 04 10 73 65 63 72 65 74 ...uid1...secret +00C0: 5F 61 67 65 6E 74 5F 6D 61 6E 30 24 04 0B 64 65 _agent_man0$..de +00D0: 73 63 72 69 70 74 69 6F 6E 31 15 04 0A 59 65 61 scription1...Yea +00E0: 20 42 61 62 79 21 21 04 07 42 65 68 61 76 65 21 Baby!!..Behave! +00F0: 30 0E 04 02 73 6E 31 08 04 06 50 6F 77 65 72 73 0...sn1...Powers +0100: 30 22 04 0F 74 65 6C 65 70 68 6F 6E 65 4E 75 6D 0"..telephoneNum +0110: 62 65 72 31 0F 04 0D 2B 34 34 20 35 38 32 20 31 ber1...+44 582 1 +0120: 30 31 30 31 30 20 04 02 63 6E 31 1A 04 18 41 75 01010 ..cn1...Au +0130: 73 74 69 6E 20 5C 22 44 61 6E 67 65 72 5C 22 20 stin \"Danger\" +0140: 50 6F 77 65 72 73 A0 1B 30 19 04 17 32 2E 31 36 Powers..0...2.16 +0150: 2E 38 34 30 2E 31 2E 31 31 33 37 33 30 2E 33 2E .840.1.113730.3. +0160: 34 2E 32 4.2 + + +# LDAP AddResponse + +0000: 30 0C 02 01 02 69 07 0A 01 00 04 00 04 00 0....i........ + + +# LDAP SearchRequest + +0000: 30 7A 02 01 03 63 58 04 38 63 6E 3D 41 75 73 74 0z...cX.8cn=Aust +0010: 69 6E 20 50 6F 77 65 72 73 2C 6F 75 3D 43 6C 69 in Powers,ou=Cli +0020: 65 6E 74 31 2C 6F 75 3D 56 65 6E 64 6F 72 31 2C ent1,ou=Vendor1, +0030: 6F 75 3D 41 64 64 2C 6F 3D 49 4D 43 2C 63 3D 55 ou=Add,o=IMC,c=U +0040: 53 0A 01 00 0A 01 03 02 01 00 02 01 00 01 01 00 S............... +0050: 87 0B 6F 62 6A 65 63 74 63 6C 61 73 73 30 00 A0 ..objectclass0.. +0060: 1B 30 19 04 17 32 2E 31 36 2E 38 34 30 2E 31 2E .0...2.16.840.1. +0070: 31 31 33 37 33 30 2E 33 2E 34 2E 32 113730.3.4.2 + + +# LDAP SearchResultEntry + +0000: 30 82 01 52 02 01 03 64 82 01 4B 04 38 63 6E 3D 0..R...d..K.8cn= +0010: 41 75 73 74 69 6E 20 50 6F 77 65 72 73 2C 6F 75 Austin Powers,ou +0020: 3D 43 6C 69 65 6E 74 31 2C 6F 75 3D 56 65 6E 64 =Client1,ou=Vend +0030: 6F 72 31 2C 6F 75 3D 41 64 64 2C 6F 3D 49 4D 43 or1,ou=Add,o=IMC +0040: 2C 63 3D 55 53 30 82 01 0D 30 41 04 0B 6F 62 6A ,c=US0...0A..obj +0050: 65 63 74 43 6C 61 73 73 31 32 04 03 74 6F 70 04 ectClass12..top. +0060: 06 70 65 72 73 6F 6E 04 14 6F 72 67 61 6E 69 7A .person..organiz +0070: 61 74 69 6F 6E 61 6C 50 65 72 73 6F 6E 04 0D 69 ationalPerson..i +0080: 6E 65 74 4F 72 67 50 65 72 73 6F 6E 30 22 04 04 netOrgPerson0".. +0090: 6D 61 69 6C 31 1A 04 18 73 65 63 72 65 74 5F 61 mail1...secret_a +00A0: 67 65 6E 74 5F 6D 61 6E 40 69 6D 63 2E 6F 72 67 gent_man@imc.org +00B0: 30 19 04 03 75 69 64 31 12 04 10 73 65 63 72 65 0...uid1...secre +00C0: 74 5F 61 67 65 6E 74 5F 6D 61 6E 30 24 04 0B 64 t_agent_man0$..d +00D0: 65 73 63 72 69 70 74 69 6F 6E 31 15 04 0A 59 65 escription1...Ye +00E0: 61 20 42 61 62 79 21 21 04 07 42 65 68 61 76 65 a Baby!!..Behave +00F0: 21 30 0E 04 02 73 6E 31 08 04 06 50 6F 77 65 72 !0...sn1...Power +0100: 73 30 22 04 0F 74 65 6C 65 70 68 6F 6E 65 4E 75 s0"..telephoneNu +0110: 6D 62 65 72 31 0F 04 0D 2B 34 34 20 35 38 32 20 mber1...+44 582 +0120: 31 30 31 30 31 30 2F 04 02 63 6E 31 29 04 18 41 101010/..cn1)..A +0130: 75 73 74 69 6E 20 5C 22 44 61 6E 67 65 72 5C 22 ustin \"Danger\" +0140: 20 50 6F 77 65 72 73 04 0D 41 75 73 74 69 6E 20 Powers..Austin +0150: 50 6F 77 65 72 73 Powers + + +# LDAP SearchResultDone + +0000: 30 0C 02 01 03 65 07 0A 01 00 04 00 04 00 0....e........ + + +# LDAP DeleteRequest + +0000: 30 5A 02 01 04 4A 38 63 6E 3D 41 75 73 74 69 6E 0Z...J8cn=Austin +0010: 20 50 6F 77 65 72 73 2C 6F 75 3D 43 6C 69 65 6E Powers,ou=Clien +0020: 74 31 2C 6F 75 3D 56 65 6E 64 6F 72 31 2C 6F 75 t1,ou=Vendor1,ou +0030: 3D 41 64 64 2C 6F 3D 49 4D 43 2C 63 3D 55 53 A0 =Add,o=IMC,c=US. +0040: 1B 30 19 04 17 32 2E 31 36 2E 38 34 30 2E 31 2E .0...2.16.840.1. +0050: 31 31 33 37 33 30 2E 33 2E 34 2E 32 113730.3.4.2 + + +# LDAP DeleteResponse + +0000: 30 0C 02 01 04 6B 07 0A 01 00 04 00 04 00 0....k........ + + + +0000: 30 22 02 01 05 42 00 A0 1B 30 19 04 17 32 2E 31 0"...B...0...2.1 +0010: 36 2E 38 34 30 2E 31 2E 31 31 33 37 33 30 2E 33 6.840.1.113730.3 +0020: 2E 34 2E 32 .4.2 +