17

Trying to migrate an application from WebLogic 12.2.1 to Tomcat 8.5.4, what under Weblogic was an entry as Foreign JNDI Providers for an LDAP connection has been migrated to a new Resource under Tomcat.

Following this advice on Stack Overflow, a custom LdapContextFactory has been packaged as a new jar file under Tomcat lib folder.

In the Tomcat server.xml file the following GlobalNamingResources/Resource has been configured:

    <Resource name="ldapConnection" 
        auth="Container"
        type="javax.naming.ldap.LdapContext"
        factory="com.sample.custom.LdapContextFactory"
        singleton="false"
        java.naming.referral="follow"
        java.naming.factory.initial="com.sun.jndi.ldap.LdapCtxFactory"
        java.naming.provider.url="ldap://some.host:389"
        java.naming.security.authentication="simple"
        java.naming.security.principal="CN=some,OU=some,OU=some,DC=some,DC=a,DC=b"
        java.naming.security.credentials="password"
        com.sun.jndi.ldap.connect.pool="true"
        com.sun.jndi.ldap.connect.pool.maxsize="10"
        com.sun.jndi.ldap.connect.pool.prefsize="4"
        com.sun.jndi.ldap.connect.pool.timeout="30000" />

The connection above works fine when browsing the LDAP directory via an LDAP browser like Apache Directory Studio / LDAP Browser embedded in Eclipse.

The custom com.sample.custom.LdapContextFactory is quite simple:

public class LdapContextFactory implements ObjectFactory {

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
            throws Exception {

        Hashtable<Object, Object> env = new Hashtable<>();
        Reference reference = (Reference) obj;
        Enumeration<RefAddr> references = reference.getAll();

        while (references.hasMoreElements()) {
            RefAddr address = references.nextElement();
            String type = address.getType();
            String content = (String) address.getContent();
            env.put(type, content);
        }
        return new InitialLdapContext(env, null);
    }
}

However, at start-up Tomcat is throwing the following exception:

07-Sep-2016 15:04:01.064 SEVERE [main] org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans Exception processing Global JNDI Resources
 javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-031001E5, problem 2001 (NO_OBJECT), data 0, best match of:
    ''
 ]; remaining name ''
    at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3160)
    at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3081)
    at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2888)
    at com.sun.jndi.ldap.LdapCtx.c_listBindings(LdapCtx.java:1189)
    at com.sun.jndi.toolkit.ctx.ComponentContext.p_listBindings(ComponentContext.java:592)
    at com.sun.jndi.toolkit.ctx.PartialCompositeContext.listBindings(PartialCompositeContext.java:330)
    at com.sun.jndi.toolkit.ctx.PartialCompositeContext.listBindings(PartialCompositeContext.java:317)
    at javax.naming.InitialContext.listBindings(InitialContext.java:472)
    at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:136)
    at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:145)
    at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:110)
    at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.lifecycleEvent(GlobalResourcesLifecycleListener.java:82)
    at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:94)
    at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:401)
    at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:345)
    at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:784)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:152)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:655)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:355)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:495)

Similar questions and investigations suggest an invalid LDAP DN, but:

  • The same LDAP configuration works fine via an LDAP Client
  • No search is actually performed, at start-up time Tomcat throws this exception without any query
  • The error suggests an empty string '' as remaining name, hence not really something not found, apparently

Question(s): Is this the correct way to migrate an Foreign JNDI Providers entry from WebLogic to Tomcat? How to fix an invalid LDAP DN entry with an empty remaining name? Could it be a missing baseDN to configure somewhere?


Update
The same exact error happens when changing the LdapContextFactory to the following, as suggested via comments:

public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
        throws Exception {

    Hashtable<Object, Object> env = new Hashtable<>();
    Reference reference = (Reference) obj;
    Enumeration<RefAddr> references = reference.getAll();

    String providerUrl = "no valid URL";

    while (references.hasMoreElements()) {
        RefAddr address = references.nextElement();
        String type = address.getType();
        String content = (String) address.getContent();

        switch (type) {
        case Context.PROVIDER_URL:
            env.put(Context.PROVIDER_URL, content);
            providerUrl = content;
            break;

        default:
            env.put(type, content);
            break;
        }
    }

    InitialLdapContext context = null;
    Object result = null;
    try {
        context = new InitialLdapContext(env, null);

        LOGGER.info("looking up for " + providerUrl);
        result = context.lookup(providerUrl);
    } finally {
        if (context != null) {
            context.close();
        }
    }
    LOGGER.info("Created new LDAP Context");
    return result;
}

Change is confirmed via logging, to make sure it was deployed properly.

The involved listener is defined by default at the top of the server.xml file as

<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

And cannot be disabled as per official documentation:

The Global Resources Lifecycle Listener initializes the Global JNDI resources defined in server.xml as part of the Global Resources element. Without this listener, none of the Global Resources will be available.


The same also happens on Tomcat version 8.5.5 and 7.0.69: simply adding the new global resource as above and the additional jar providing the factory above, the exception pointing at an empty remaining name will be thrown.

Community
  • 1
  • 1
A_Di-Matteo
  • 26,902
  • 7
  • 94
  • 128
  • 1
    I don't know why it's calling `listBindings()`. My LDAP context factory returns `iniitalLdapContext.lookup(url);` where `url` is the provider URL, and closes the `InitialLdapContext` in a `finally` block. . – user207421 Sep 08 '16 at 00:57
  • 1
    @EJP indeed looking [here](https://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/mbeans/GlobalResourcesLifecycleListener.java), during mbean creation it invokes a `listBindings` with an empty string, which is most probably the cause of the issue. I would then disable mbean for this `Resource` (or for all), but I can't find how to do it – A_Di-Matteo Sep 08 '16 at 08:04
  • Well try it my way with the lookup. – user207421 Sep 08 '16 at 08:24
  • @EJP same stacktrace – A_Di-Matteo Sep 08 '16 at 08:28
  • It can't be the same, it would be doing a `listBindings()` on the real URL which would yield a real result. Are you sure you rebuilt/deployed correctly? – user207421 Sep 08 '16 at 10:15
  • @EJP updated the question with the current implementation of the factory, same stacktrace. Moreover, mbean listener cannot be disabled since it would make any global resource not available, as per [official doc](https://tomcat.apache.org/tomcat-8.0-doc/config/listeners.html#Global_Resources_Lifecycle_Listener_-_org.apache.catalina.mbeans.GlobalResourcesLifecycleListener) – A_Di-Matteo Sep 08 '16 at 11:22
  • Not sure I can help, I don't have the MBean listener in my system. – user207421 Sep 09 '16 at 02:17
  • Where do you use the resource name `ldapConnection`? – ROMANIA_engineer Sep 13 '16 at 05:35
  • @ROMANIA in the server side `context.xml` file, as a `ResourceLink` entry to expose it to deployed applications, with name `DC=some,DC=a,DC=b` (last part of user id, same as it was on WebLogic actually) – A_Di-Matteo Sep 13 '16 at 07:26
  • @ROMANIA @EJP it might be a further hint: when changing the `java.naming.provider.url` to `ldap://some.host:389/DC=some,DC=a,DC=b`, I get a `javax.naming.SizeLimitExceededException: [LDAP: error code 4 - Sizelimit Exceeded]; remaining name ''`, different error but again an empty remaining name error – A_Di-Matteo Sep 16 '16 at 14:01
  • @ROMANIA issue solved, check my answer. However I'm still willing to award the bounty if someone could explain why I needed to do so (and why Weblogic or eclipse client didn't need it) – A_Di-Matteo Sep 16 '16 at 16:16
  • 1
    @A_Di-Matteo , check this http://docs.oracle.com/javase/7/docs/technotes/guides/jndi/jndi-ldap-gl.html#url . The value of this property is a list of space-separated LDAP or LDAPS URL strings, each of which specifies the hostname and port number of the LDAP server, and the **root distinguished name** of the naming context to use. ...The **default root distinguished name is the empty string**. If this property is not set, or if either the hostname or port number is omitted, then the default values are used in place of the missing information. – ROMANIA_engineer Sep 16 '16 at 16:34

1 Answers1

2

The stacktrace went away by appending to the java.naming.provider.url property the LDAP schema DN, using the first factory implementation provided in the question.

Below a screenshot of the LDAP client used in this context, the Apache Directory Studio / LDAP Browser embedded in Eclipse, from which it was possible to browse the concerned LDAP simply using the initial values of the question.

enter image description here

By appending the schema DN of the Root element to the connection URL, the exception went away and the LDAP resource is now shared via JNDI in Tomcat 8.


Further details as outcome of the troubleshooting:

In Tomcat 8 global resources are handled via a global resource listener, the GlobalResourcesLifecycleListener, defined by default in the server.xml file. Such a listener invokes a context.listBindings("") on bean creation, hence effectively browsing the LDAP directory.

This initial browsing may most probably be the difference between Tomcat and WebLogic, where LDAP is looked up via JNDI only when required, hence via direct query, rather than at start-up with a generic query. As such, in Tomcat the LDAP url would need further details, that is, a slightly different configuration as part of its url to directly point to a valid base DN.

From official WebLogic documentation:

On start up, WebLogic Server attempts to connect to the JNDI source. If the connection is successful, WebLogic Server sets up the requested objects and links in the local JNDI tree, making them available to WebLogic Server clients.

Hence, a connection is rather simpler than a listBindings:

Enumerates the names bound in the named context, along with the objects bound to them. The contents of any subcontexts are not included.

A_Di-Matteo
  • 26,902
  • 7
  • 94
  • 128