/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive.provider.jndi; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.naming.CompositeName; import javax.naming.InvalidNameException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.ReferralException; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapName; import javax.naming.ldap.LdapReferralException; import org.ldaptive.AddRequest; import org.ldaptive.BindRequest; import org.ldaptive.CompareRequest; import org.ldaptive.DeleteRequest; import org.ldaptive.DerefAliases; import org.ldaptive.LdapException; import org.ldaptive.ModifyDnRequest; import org.ldaptive.ModifyRequest; import org.ldaptive.Request; import org.ldaptive.Response; import org.ldaptive.ResultCode; import org.ldaptive.ReturnAttributes; import org.ldaptive.SearchReference; import org.ldaptive.SearchRequest; import org.ldaptive.SearchScope; import org.ldaptive.control.RequestControl; import org.ldaptive.control.ResponseControl; import org.ldaptive.extended.ExtendedRequest; import org.ldaptive.extended.ExtendedResponse; import org.ldaptive.extended.ExtendedResponseFactory; import org.ldaptive.extended.UnsolicitedNotificationListener; import org.ldaptive.provider.ControlProcessor; import org.ldaptive.provider.ProviderConnection; import org.ldaptive.provider.ProviderUtils; import org.ldaptive.provider.SearchItem; import org.ldaptive.provider.SearchIterator; import org.ldaptive.provider.SearchListener; import org.ldaptive.sasl.DigestMd5Config; import org.ldaptive.sasl.SaslConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * JNDI provider implementation of ldap operations. * * @author Middleware Services */ public class JndiConnection implements ProviderConnection { /** * The value of this property is a string that specifies the authentication mechanism(s) for the provider to use. The * value of this constant is {@value}. */ public static final String AUTHENTICATION = "java.naming.security.authentication"; /** * The value of this property is an object that specifies the credentials of the principal to be authenticated. The * value of this constant is {@value}. */ public static final String CREDENTIALS = "java.naming.security.credentials"; /** * The value of this property is a string that specifies the identity of the principal to be authenticated. The value * of this constant is {@value}. */ public static final String PRINCIPAL = "java.naming.security.principal"; /** * The value of this property is a string that specifies the sasl authorization id. The value of this constant is * {@value}. */ public static final String SASL_AUTHZ_ID = "java.naming.security.sasl.authorizationId"; /** * The value of this property is a string that specifies the sasl quality of protection. The value of this constant is * {@value}. */ public static final String SASL_QOP = "javax.security.sasl.qop"; /** * The value of this property is a string that specifies the sasl security strength. The value of this constant is * {@value}. */ public static final String SASL_STRENGTH = "javax.security.sasl.strength"; /** * The value of this property is a string that specifies the sasl mutual authentication flag. The value of this * constant is {@value}. */ public static final String SASL_MUTUAL_AUTH = "javax.security.sasl.server.authentication"; /** The value of this property is a string that specifies the sasl realm. The value of this constant is {@value}. */ public static final String SASL_REALM = "java.naming.security.sasl.realm"; /** * The value of this property is a string that specifies whether the RDN attribute should be deleted for a modify dn * operation. The value of this constant is {@value}. */ public static final String DELETE_RDN = "java.naming.ldap.deleteRDN"; /** * The value of this property is a string that specifies additional binary attributes. The value of this constant is * {@value}. */ public static final String BINARY_ATTRIBUTES = "java.naming.ldap.attributes.binary"; /** * The value of this property is a string that specifies how aliases shall be handled by the provider. The value of * this constant is {@value}. */ public static final String DEREF_ALIASES = "java.naming.ldap.derefAliases"; /** * The value of this property is a string that specifies how referrals shall be handled by the provider. The value of * this constant is {@value}. */ public static final String REFERRAL = "java.naming.referral"; /** * The value of this property is a string that specifies to only return attribute type names, no values. The value of * this constant is {@value}. */ public static final String TYPES_ONLY = "java.naming.ldap.typesOnly"; /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** Ldap context. */ private LdapContext context; /** Provider configuration. */ private final JndiProviderConfig config; /** * Creates a new jndi connection. * * @param lc ldap context * @param pc provider configuration */ public JndiConnection(final LdapContext lc, final JndiProviderConfig pc) { context = lc; config = pc; } /** * Returns the underlying ldap context. * * @return ldap context */ public LdapContext getLdapContext() { return context; } @Override public void close(final RequestControl[] controls) throws LdapException { if (controls != null) { throw new UnsupportedOperationException("Provider does not support unbind with controls"); } try { if (context != null) { context.close(); } } catch (NamingException e) { ResultCode rc = NamingExceptionUtils.getResultCode(e.getClass()); if (rc == null) { rc = NamingExceptionUtils.getResultCode(e.getMessage()); } throw new LdapException(e, rc); } finally { context = null; } } @Override public Response<Void> bind(final BindRequest request) throws LdapException { Response<Void> response; if (request.getSaslConfig() != null) { response = saslBind(request); } else if (request.getDn() == null && request.getCredential() == null) { response = anonymousBind(request); } else { response = simpleBind(request); } return response; } /** * Performs an anonymous bind. * * @param request to bind with * * @return bind response * * @throws LdapException if an error occurs */ protected Response<Void> anonymousBind(final BindRequest request) throws LdapException { Response<Void> response = null; try { context.addToEnvironment(AUTHENTICATION, "none"); context.removeFromEnvironment(PRINCIPAL); context.removeFromEnvironment(CREDENTIALS); context.reconnect(config.getControlProcessor().processRequestControls(request.getControls())); response = createResponse(request, null, ResultCode.SUCCESS, null, context); } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, context); } catch (NamingException e) { processNamingException(request, e, null, context); } return response; } /** * Performs a simple bind. * * @param request to bind with * * @return bind response * * @throws LdapException if an error occurs */ protected Response<Void> simpleBind(final BindRequest request) throws LdapException { Response<Void> response = null; try { context.addToEnvironment(AUTHENTICATION, "simple"); context.addToEnvironment(PRINCIPAL, request.getDn()); context.addToEnvironment(CREDENTIALS, request.getCredential().getBytes()); context.reconnect(config.getControlProcessor().processRequestControls(request.getControls())); response = createResponse(request, null, ResultCode.SUCCESS, null, context); } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, context); } catch (NamingException e) { processNamingException(request, e, null, context); } return response; } /** * Performs a sasl bind. * * @param request to bind with * * @return bind response * * @throws LdapException if an error occurs */ protected Response<Void> saslBind(final BindRequest request) throws LdapException { Response<Void> response = null; try { final String authenticationType = JndiUtils.getAuthenticationType(request.getSaslConfig().getMechanism()); for (Map.Entry<String, Object> entry : getSaslProperties(request.getSaslConfig()).entrySet()) { context.addToEnvironment(entry.getKey(), entry.getValue()); } context.addToEnvironment(AUTHENTICATION, authenticationType); if (request.getDn() != null) { context.addToEnvironment(PRINCIPAL, request.getDn()); if (request.getCredential() != null) { context.addToEnvironment(CREDENTIALS, request.getCredential().getBytes()); } } context.reconnect(config.getControlProcessor().processRequestControls(request.getControls())); response = createResponse(request, null, ResultCode.SUCCESS, null, context); } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, context); } catch (NamingException e) { processNamingException(request, e, null, context); } return response; } @Override public Response<Void> add(final AddRequest request) throws LdapException { Response<Void> response = null; LdapContext ctx = null; try { try { ctx = initializeContext(request); final JndiUtils bu = new JndiUtils(); ctx.createSubcontext(new LdapName(request.getDn()), bu.fromLdapAttributes(request.getLdapAttributes())).close(); response = createResponse(request, null, ResultCode.SUCCESS, null, ctx); } finally { if (ctx != null) { ctx.close(); } } } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, ctx); } catch (NamingException e) { processNamingException(request, e, null, ctx); } return response; } @Override public Response<Boolean> compare(final CompareRequest request) throws LdapException { Response<Boolean> response = null; LdapContext ctx = null; try { NamingEnumeration<SearchResult> en = null; try { ctx = initializeContext(request); en = ctx.search( new LdapName(request.getDn()), String.format("(%s={0})", request.getAttribute().getName()), request.getAttribute().isBinary() ? new Object[] {request.getAttribute().getBinaryValue()} : new Object[] { request.getAttribute().getStringValue(), }, getCompareSearchControls()); final boolean success = en.hasMore(); response = createResponse( request, success, success ? ResultCode.COMPARE_TRUE : ResultCode.COMPARE_FALSE, null, ctx); } finally { if (en != null) { en.close(); } if (ctx != null) { ctx.close(); } } } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, ctx); } catch (NamingException e) { processNamingException(request, e, null, ctx); } return response; } @Override public Response<Void> delete(final DeleteRequest request) throws LdapException { Response<Void> response = null; LdapContext ctx = null; try { try { ctx = initializeContext(request); ctx.destroySubcontext(new LdapName(request.getDn())); response = createResponse(request, null, ResultCode.SUCCESS, null, ctx); } finally { if (ctx != null) { ctx.close(); } } } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, ctx); } catch (NamingException e) { processNamingException(request, e, null, ctx); } return response; } @Override public Response<Void> modify(final ModifyRequest request) throws LdapException { Response<Void> response = null; LdapContext ctx = null; try { try { ctx = initializeContext(request); final JndiUtils bu = new JndiUtils(); ctx.modifyAttributes( new LdapName(request.getDn()), bu.fromAttributeModification(request.getAttributeModifications())); response = createResponse(request, null, ResultCode.SUCCESS, null, ctx); } finally { if (ctx != null) { ctx.close(); } } } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, ctx); } catch (NamingException e) { processNamingException(request, e, null, ctx); } return response; } @Override public Response<Void> modifyDn(final ModifyDnRequest request) throws LdapException { Response<Void> response = null; LdapContext ctx = null; try { try { ctx = initializeContext(request); ctx.addToEnvironment("java.naming.ldap.deleteRDN", Boolean.valueOf(request.getDeleteOldRDn()).toString()); ctx.rename(new LdapName(request.getDn()), new LdapName(request.getNewDn())); response = createResponse(request, null, ResultCode.SUCCESS, null, ctx); } finally { if (ctx != null) { ctx.close(); } } } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, ctx); } catch (NamingException e) { processNamingException(request, e, null, ctx); } return response; } @Override public SearchIterator search(final SearchRequest request) throws LdapException { final JndiSearchIterator i = new JndiSearchIterator(request); i.initialize(); return i; } @Override public void searchAsync(final SearchRequest request, final SearchListener listener) throws LdapException { throw new UnsupportedOperationException("Asynchronous searches not supported"); } @Override public void abandon(final int messageId, final RequestControl[] controls) throws LdapException { throw new UnsupportedOperationException("Abandons not supported"); } @Override public Response<?> extendedOperation(final ExtendedRequest request) throws LdapException { Response<?> response = null; LdapContext ctx = null; try { try { ctx = initializeContext(request); final JndiExtendedResponse jndiExtRes = (JndiExtendedResponse) ctx.extendedOperation( new JndiExtendedRequest(request.getOID(), request.encode())); final ExtendedResponse<?> extRes = ExtendedResponseFactory.createExtendedResponse( request.getOID(), jndiExtRes.getID(), jndiExtRes.getEncodedValue()); response = createResponse(request, extRes.getValue(), ResultCode.SUCCESS, null, ctx); } finally { if (ctx != null) { ctx.close(); } } } catch (ReferralException e) { final String[] refUrls = e.getReferralInfo() != null ? new String[] {(String) e.getReferralInfo()} : null; response = createResponse(request, null, ResultCode.REFERRAL, refUrls, ctx); } catch (NamingException e) { processNamingException(request, e, null, ctx); } return response; } @Override public void addUnsolicitedNotificationListener(final UnsolicitedNotificationListener listener) { throw new UnsupportedOperationException("Unsolicited notifications not supported"); } @Override public void removeUnsolicitedNotificationListener(final UnsolicitedNotificationListener listener) { throw new UnsupportedOperationException("Unsolicited notifications not supported"); } /** * Returns a search controls object configured to perform an LDAP compare operation. * * @return search controls */ public static SearchControls getCompareSearchControls() { final SearchControls ctls = new SearchControls(); ctls.setReturningAttributes(new String[0]); ctls.setSearchScope(SearchScope.OBJECT.ordinal()); return ctls; } /** * Creates a new ldap context using {@link LdapContext#newInstance(Control[])}. Adds any additional environment * properties found in the supplied request to the context. * * @param request to read properties from * * @return ldap context * * @throws NamingException if a property cannot be added to the context */ protected LdapContext initializeContext(final Request request) throws NamingException { final LdapContext ctx = context.newInstance( config.getControlProcessor().processRequestControls(request.getControls())); // by default set referral behavior to throw, otherwise jndi will send the // ManageDsaIT control ctx.addToEnvironment(REFERRAL, "throw"); return ctx; } /** * Creates an operation response with the supplied response data. * * @param <T> type of response * @param request containing controls * @param result of the operation * @param code operation result code * @param urls referral urls * @param ctx ldap context * * @return operation response */ protected <T> Response<T> createResponse( final Request request, final T result, final ResultCode code, final String[] urls, final LdapContext ctx) { return new Response<>( result, code, null, null, processResponseControls(config.getControlProcessor(), request.getControls(), ctx), urls, -1); } /** * Determines if the supplied naming exception should result in an operation retry. * * @param request that produced the exception * @param e that was produced * @param urls referral urls * @param ctx that the exception occurred on * * @throws LdapException wrapping the naming exception */ protected void processNamingException( final Request request, final NamingException e, final String[] urls, final LdapContext ctx) throws LdapException { ResultCode rc = NamingExceptionUtils.getResultCode(e.getClass()); if (rc == null) { rc = NamingExceptionUtils.getResultCode(e.getMessage()); } ProviderUtils.throwOperationException( config.getOperationExceptionResultCodes(), e, rc != null ? rc.value() : -1, null, processResponseControls(config.getControlProcessor(), request.getControls(), ctx), urls, true); } /** * Retrieves the response controls from the supplied context and processes them with the supplied control processor. * Logs a warning if controls cannot be retrieved. * * @param processor control processor * @param requestControls that produced this response * @param ctx to get controls from * * @return response controls */ protected ResponseControl[] processResponseControls( final ControlProcessor<Control> processor, final RequestControl[] requestControls, final LdapContext ctx) { ResponseControl[] ctls = null; if (ctx != null) { try { ctls = processor.processResponseControls(ctx.getResponseControls()); } catch (NamingException e) { final Logger l = LoggerFactory.getLogger(JndiUtils.class); l.warn("Error retrieving response controls.", e); } } return ctls; } /** * Returns the JNDI properties for the supplied sasl configuration. * * @param config sasl configuration * * @return JNDI properties for use in a context environment */ protected static Map<String, Object> getSaslProperties(final SaslConfig config) { final Map<String, Object> env = new HashMap<>(); if (config.getAuthorizationId() != null && !"".equals(config.getAuthorizationId())) { env.put(SASL_AUTHZ_ID, config.getAuthorizationId()); } if (config.getQualityOfProtection() != null) { env.put(SASL_QOP, JndiUtils.getQualityOfProtection(config.getQualityOfProtection())); } if (config.getSecurityStrength() != null) { env.put(SASL_STRENGTH, JndiUtils.getSecurityStrength(config.getSecurityStrength())); } if (config.getMutualAuthentication() != null) { env.put(SASL_MUTUAL_AUTH, config.getMutualAuthentication().toString()); } if (config instanceof DigestMd5Config) { if (((DigestMd5Config) config).getRealm() != null) { env.put(SASL_REALM, ((DigestMd5Config) config).getRealm()); } } return env; } /** Search iterator for JNDI naming enumeration. */ protected class JndiSearchIterator implements SearchIterator { /** Search request. */ private final SearchRequest request; /** Response data. */ private Response<Void> response; /** Response result code. */ private ResultCode responseResultCode; /** Search reference URLs. */ private List<String> searchReferences; /** Ldap context to search with. */ private LdapContext searchContext; /** Results read from the search operation. */ private NamingEnumeration<SearchResult> results; /** * Creates a new jndi search iterator. * * @param sr search request */ public JndiSearchIterator(final SearchRequest sr) { request = sr; } /** * Initializes this jndi search iterator. * * @throws LdapException if an error occurs */ public void initialize() throws LdapException { boolean closeContext = false; try { searchContext = context.newInstance(config.getControlProcessor().processRequestControls(request.getControls())); initializeSearchContext(searchContext, request); results = search(searchContext, request); } catch (LdapReferralException e) { closeContext = true; response = createResponse(request, null, ResultCode.REFERRAL, readReferralUrls(e), searchContext); } catch (NamingException e) { closeContext = true; processNamingException(request, e, null, searchContext); } finally { if (closeContext) { try { if (searchContext != null) { searchContext.close(); } } catch (NamingException e) { logger.debug("Problem closing context", e); } } } } /** * Adds any additional environment properties found in the supplied request to the supplied context. * * @param ctx to initialize for searching * @param sr to read properties from * * @throws NamingException if a property cannot be added to the context */ protected void initializeSearchContext(final LdapContext ctx, final SearchRequest sr) throws NamingException { // by default set referral behavior to throw, otherwise jndi will send the // ManageDsaIT control ctx.addToEnvironment(REFERRAL, "throw"); // by default set dereferencing aliases to never, jndi default is always if (sr.getDerefAliases() != null) { ctx.addToEnvironment(DEREF_ALIASES, sr.getDerefAliases().name().toLowerCase()); } else { ctx.addToEnvironment(DEREF_ALIASES, DerefAliases.NEVER.name().toLowerCase()); } if (sr.getBinaryAttributes() != null) { final String[] a = sr.getBinaryAttributes(); final StringBuilder sb = new StringBuilder(); for (int i = 0; i < a.length; i++) { sb.append(a[i]); if (i < a.length - 1) { sb.append(" "); } } ctx.addToEnvironment(BINARY_ATTRIBUTES, sb.toString()); } if (sr.getTypesOnly()) { ctx.addToEnvironment(TYPES_ONLY, Boolean.valueOf(sr.getTypesOnly()).toString()); } } /** * Executes {@link LdapContext#search( javax.naming.Name, String, Object[], SearchControls)}. * * @param ctx to search * @param sr to read properties from * * @return naming enumeration of search results * * @throws NamingException if an error occurs */ protected NamingEnumeration<SearchResult> search(final LdapContext ctx, final SearchRequest sr) throws NamingException { return ctx.search( sr.getBaseDn(), sr.getSearchFilter() != null ? request.getSearchFilter().format() : null, getSearchControls(sr)); } /** * Returns a search controls object configured with the supplied search request. * * @param sr search request containing configuration to create search controls * * @return search controls */ protected SearchControls getSearchControls(final SearchRequest sr) { final SearchControls ctls = new SearchControls(); if (ReturnAttributes.DEFAULT.equalsAttributes(sr.getReturnAttributes())) { ctls.setReturningAttributes(null); } else { ctls.setReturningAttributes(sr.getReturnAttributes()); } final int searchScope = getSearchScope(sr.getSearchScope()); if (searchScope != -1) { ctls.setSearchScope(searchScope); } ctls.setTimeLimit((int) sr.getTimeLimit().toMillis()); ctls.setCountLimit(sr.getSizeLimit()); ctls.setDerefLinkFlag(false); // note that if returning obj flag is set to true, object contexts on the // SearchResult must the explicitly closed: // ctx = (Context) SearchResult#getObject(); ctx.close(); ctls.setReturningObjFlag(false); return ctls; } /** * Returns the jndi integer constant for the supplied search scope. * * @param ss search scope * * @return integer constant */ protected int getSearchScope(final SearchScope ss) { int scope = -1; if (ss == SearchScope.OBJECT) { scope = SearchControls.OBJECT_SCOPE; } else if (ss == SearchScope.ONELEVEL) { scope = SearchControls.ONELEVEL_SCOPE; } else if (ss == SearchScope.SUBTREE) { scope = SearchControls.SUBTREE_SCOPE; } return scope; } @Override public boolean hasNext() throws LdapException { if (results == null || response != null) { return false; } boolean more = false; if (searchReferences != null) { more = true; } else { try { more = results.hasMore(); if (!more) { response = createResponse( request, null, responseResultCode != null ? responseResultCode : ResultCode.SUCCESS, null, searchContext); } } catch (LdapReferralException e) { searchReferences = new ArrayList<>(Arrays.asList(readReferralUrls(e))); more = true; } catch (NamingException e) { final ResultCode ignoreRc = ignoreSearchException(config.getSearchIgnoreResultCodes(), e); if (ignoreRc == null) { processNamingException(request, e, null, searchContext); } response = createResponse(request, null, ignoreRc, null, searchContext); } } return more; } @Override public SearchItem next() throws LdapException { SearchItem item = null; if (searchReferences != null) { if (!searchReferences.isEmpty()) { item = new SearchItem(new SearchReference(-1, null, searchReferences.remove(0))); } if (searchReferences.isEmpty()) { response = createResponse(request, null, ResultCode.SUCCESS, null, searchContext); } } else { final JndiUtils bu = new JndiUtils(request.getSortBehavior()); try { final SearchResult result = results.next(); logger.trace("reading search result: {}", result); result.setName(formatDn(result, getSearchDn(searchContext, request))); item = new SearchItem(bu.toSearchEntry(result)); } catch (LdapReferralException e) { item = new SearchItem(new SearchReference(-1, null, readReferralUrls(e))); } catch (NamingException e) { final ResultCode ignoreRc = ignoreSearchException(config.getSearchIgnoreResultCodes(), e); if (ignoreRc == null) { processNamingException(request, e, null, searchContext); } responseResultCode = ignoreRc; } } return item; } /** * Determines whether the supplied naming exception should be ignored. * * @param ignoreResultCodes to match against the exception * @param e naming exception to match * * @return result code that should be ignored or null */ protected ResultCode ignoreSearchException(final ResultCode[] ignoreResultCodes, final NamingException e) { ResultCode ignore = null; if (ignoreResultCodes != null && ignoreResultCodes.length > 0) { for (ResultCode rc : ignoreResultCodes) { if (NamingExceptionUtils.matches(e.getClass(), rc)) { logger.debug("Ignoring naming exception", e); ignore = rc; break; } } } return ignore; } /** * Reads all referral URLs associated with this exception by invoking the search operation on the referral context * until all referrals have been read. JNDI does not distinguish the URLs contained in specific references. So each * URL must be treated as a separate search reference. * * @param refEx to read URLs from * * @return referral urls */ protected String[] readReferralUrls(final LdapReferralException refEx) { final List<String> urls = new ArrayList<>(); LdapReferralException loopEx = refEx; urls.add((String) loopEx.getReferralInfo()); while (loopEx.skipReferral()) { try { final LdapContext ctx = (LdapContext) loopEx.getReferralContext( searchContext.getEnvironment(), config.getControlProcessor().processRequestControls(request.getControls())); search(ctx, request); } catch (LdapReferralException e) { if (e.getReferralInfo() != null && e.getReferralInfo() instanceof String) { urls.add((String) e.getReferralInfo()); } loopEx = e; } catch (NamingException namingEx) { logger.warn("Error reading search references", namingEx); break; } } logger.trace("read search references: {}", urls); return urls.toArray(new String[urls.size()]); } @Override public Response<Void> getResponse() { return response; } /** * Determines the DN of the supplied search request. Returns {@link LdapContext#getNameInNamespace()} if it is * available, otherwise returns {@link SearchRequest#getBaseDn()}. * * @param ctx ldap context the search was performed on * @param sr search request * * @return DN * * @throws NamingException if an error occurs */ protected String getSearchDn(final LdapContext ctx, final SearchRequest sr) throws NamingException { if (ctx != null && !"".equals(ctx.getNameInNamespace())) { return ctx.getNameInNamespace(); } else { return sr.getBaseDn(); } } /** * Returns a fully-qualified DN for the supplied search result. If search result is relative, the DN is created with * {@link SearchResult#getNameInNamespace()}. Otherwise the behavior is controlled by {@link * JndiProviderConfig#getRemoveDnUrls()}. * * @param sr to determine DN for * @param baseDn that search was performed on * * @return fully qualified DN * * @throws NamingException if search result name cannot be formatted as a DN */ protected String formatDn(final SearchResult sr, final String baseDn) throws NamingException { String fqName; if (sr.isRelative()) { logger.trace("formatting relative dn '{}'", sr.getNameInNamespace()); final LdapName lname = new LdapName(sr.getNameInNamespace()); fqName = lname.toString(); } else { logger.trace("formatting non-relative dn '{}'", sr.getName()); if (config.getRemoveDnUrls()) { fqName = readCompositeName(URI.create(sr.getName()).getPath().substring(1)); } else { fqName = readCompositeName(sr.getName()); } } logger.trace("formatted dn '{}'", fqName); return fqName; } /** * Uses a composite name to parse the supplied string. * * @param s composite name to read * * @return ldap name * * @throws InvalidNameException if the supplied string is not a valid composite name */ protected String readCompositeName(final String s) throws InvalidNameException { final StringBuilder name = new StringBuilder(); final CompositeName cName = new CompositeName(s); for (int i = 0; i < cName.size(); i++) { name.append(cName.get(i)); if (i + 1 < cName.size()) { name.append("/"); } } return name.toString(); } @Override public void close() throws LdapException { try { if (results != null) { results.close(); } } catch (NamingException e) { logger.error("Error closing naming enumeration", e); } try { if (searchContext != null) { searchContext.close(); } } catch (NamingException e) { logger.error("Error closing ldap context", e); } } } /** Class for exposing extended request properties. */ protected static class JndiExtendedRequest implements javax.naming.ldap.ExtendedRequest { /** OID of the extended request. */ private final String oid; /** BER encoded request data. */ private final byte[] encoded; /** * Creates a new jndi extended request. * * @param id request oid * @param berValue BER encoded request */ public JndiExtendedRequest(final String id, final byte[] berValue) { oid = id; encoded = berValue; } @Override public String getID() { return oid; } @Override public byte[] getEncodedValue() { return encoded; } @Override public javax.naming.ldap.ExtendedResponse createExtendedResponse( final String id, final byte[] berValue, final int offset, final int length) throws NamingException { byte[] b = null; if (berValue != null) { b = new byte[length]; System.arraycopy(berValue, offset, b, 0, length); } return new JndiExtendedResponse(id, b); } } /** Class for exposing extended response properties. */ protected static class JndiExtendedResponse implements javax.naming.ldap.ExtendedResponse { /** OID of the extended response. */ private final String oid; /** BER encoded response data. */ private final byte[] encoded; /** * Creates a new jndi extended response. * * @param id response oid * @param berValue BER encoded response */ public JndiExtendedResponse(final String id, final byte[] berValue) { oid = id; encoded = berValue; } @Override public String getID() { return oid; } @Override public byte[] getEncodedValue() { return encoded; } } }