From e68ff3edf50b334668dd5563e746a491a58935ff Mon Sep 17 00:00:00 2001
From: Ralph Goers <rgoers@apache.org>
Date: Sat, 11 Dec 2021 16:05:14 -0700
Subject: [PATCH] LOG4J2-3208 - Disable JNDI by default

---
 .../log4j/core/appender/mom/JmsManager.java   | 13 ++--
 .../log4j/core/lookup/Interpolator.java       | 19 ++++--
 .../logging/log4j/core/net/JndiManager.java   | 54 +++++++++++-----
 .../core/selector/JndiContextSelector.java    |  6 ++
 .../core/appender/mom/JmsAppenderTest.java    |  6 ++
 .../routing/RoutingAppenderWithJndiTest.java  | 10 ++-
 .../log4j/core/lookup/InterpolatorTest.java   |  3 +
 .../core/lookup/JndiDisabledLookupTest.java   | 64 +++++++++++++++++++
 .../log4j/core/lookup/JndiLookupTest.java     |  6 ++
 .../core/lookup/JndiRestrictedLookupTest.java |  1 +
 src/site/xdoc/manual/appenders.xml            |  3 +
 src/site/xdoc/manual/configuration.xml.vm     |  8 +++
 src/site/xdoc/manual/logsep.xml               |  3 +
 src/site/xdoc/manual/lookups.xml              |  5 ++
 14 files changed, 172 insertions(+), 29 deletions(-)
 create mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiDisabledLookupTest.java

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
index ad57358..671cd5d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java
@@ -125,10 +125,15 @@ public class JmsManager extends AbstractManager {
 
         @Override
         public JmsManager createManager(final String name, final JmsManagerConfiguration data) {
-            try {
-                return new JmsManager(name, data);
-            } catch (final Exception e) {
-                logger().error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e);
+            if (JndiManager.isIsJndiEnabled()) {
+                try {
+                    return new JmsManager(name, data);
+                } catch (final Exception e) {
+                    logger().error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e);
+                    return null;
+                }
+            } else {
+                logger().error("JNDI has not been enabled. The log4j2.enableJndi property must be set to true");
                 return null;
             }
         }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
index f0c40f5..827b77e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
@@ -26,6 +26,7 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.config.ConfigurationAware;
 import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
+import org.apache.logging.log4j.core.net.JndiManager;
 import org.apache.logging.log4j.core.util.Loader;
 import org.apache.logging.log4j.core.util.ReflectionUtil;
 import org.apache.logging.log4j.status.StatusLogger;
@@ -77,7 +78,9 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
         for (final Map.Entry<String, PluginType<?>> entry : plugins.entrySet()) {
             try {
                 final Class<? extends StrLookup> clazz = entry.getValue().getPluginClass().asSubclass(StrLookup.class);
-                strLookupMap.put(entry.getKey().toLowerCase(), ReflectionUtil.instantiate(clazz));
+                if (!clazz.getName().equals(JndiLookup.class.getName()) || JndiManager.isIsJndiEnabled()) {
+                    strLookupMap.put(entry.getKey().toLowerCase(), ReflectionUtil.instantiate(clazz));
+                }
             } catch (final Throwable t) {
                 handleError(entry.getKey(), t);
             }
@@ -106,12 +109,14 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
         strLookupMap.put("lower", new LowerLookup());
         strLookupMap.put("upper", new UpperLookup());
         // JNDI
-        try {
-            // [LOG4J2-703] We might be on Android
-            strLookupMap.put(LOOKUP_KEY_JNDI,
-                Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JndiLookup", StrLookup.class));
-        } catch (final LinkageError | Exception e) {
-            handleError(LOOKUP_KEY_JNDI, e);
+        if (JndiManager.isIsJndiEnabled()) {
+            try {
+                // [LOG4J2-703] We might be on Android
+                strLookupMap.put(LOOKUP_KEY_JNDI,
+                        Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JndiLookup", StrLookup.class));
+            } catch (final LinkageError | Exception e) {
+                handleError(LOOKUP_KEY_JNDI, e);
+            }
         }
         // JMX input args
         try {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java
index 2d7604f..d946cd1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java
@@ -73,6 +73,10 @@ public class JndiManager extends AbstractManager {
 
     private final DirContext context;
 
+    public static boolean isIsJndiEnabled() {
+        return PropertiesUtil.getProperties().getBooleanProperty("log4j2.enableJndi", false);
+    }
+
     private JndiManager(final String name, final DirContext context, final List<String> allowedHosts,
             final List<String> allowedClasses, final List<String> allowedProtocols) {
         super(null, name);
@@ -82,6 +86,14 @@ public class JndiManager extends AbstractManager {
         this.allowedProtocols = allowedProtocols;
     }
 
+    private JndiManager(final String name) {
+        super(null, name);
+        this.context = null;
+        this.allowedProtocols = null;
+        this.allowedClasses = null;
+        this.allowedHosts = null;
+    }
+
     /**
      * Gets the default JndiManager using the default {@link javax.naming.InitialContext}.
      *
@@ -194,7 +206,10 @@ public class JndiManager extends AbstractManager {
 
     @Override
     protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
-        return JndiCloser.closeSilently(this.context);
+        if (context != null) {
+            return JndiCloser.closeSilently(this.context);
+        }
+        return true;
     }
 
     /**
@@ -207,6 +222,9 @@ public class JndiManager extends AbstractManager {
      */
     @SuppressWarnings("unchecked")
     public synchronized <T> T lookup(final String name) throws NamingException {
+        if (context == null) {
+            return null;
+        }
         try {
             URI uri = new URI(name);
             if (uri.getScheme() != null) {
@@ -261,21 +279,25 @@ public class JndiManager extends AbstractManager {
 
         @Override
         public JndiManager createManager(final String name, final Properties data) {
-            String hosts = data != null ? data.getProperty(ALLOWED_HOSTS) : null;
-            String classes = data != null ? data.getProperty(ALLOWED_CLASSES) : null;
-            String protocols = data != null ? data.getProperty(ALLOWED_PROTOCOLS) : null;
-            List<String> allowedHosts = new ArrayList<>();
-            List<String> allowedClasses = new ArrayList<>();
-            List<String> allowedProtocols = new ArrayList<>();
-            addAll(hosts, allowedHosts, permanentAllowedHosts, ALLOWED_HOSTS, data);
-            addAll(classes, allowedClasses, permanentAllowedClasses, ALLOWED_CLASSES, data);
-            addAll(protocols, allowedProtocols, permanentAllowedProtocols, ALLOWED_PROTOCOLS, data);
-            try {
-                return new JndiManager(name, new InitialDirContext(data), allowedHosts, allowedClasses,
-                        allowedProtocols);
-            } catch (final NamingException e) {
-                LOGGER.error("Error creating JNDI InitialContext.", e);
-                return null;
+            if (isIsJndiEnabled()) {
+                String hosts = data != null ? data.getProperty(ALLOWED_HOSTS) : null;
+                String classes = data != null ? data.getProperty(ALLOWED_CLASSES) : null;
+                String protocols = data != null ? data.getProperty(ALLOWED_PROTOCOLS) : null;
+                List<String> allowedHosts = new ArrayList<>();
+                List<String> allowedClasses = new ArrayList<>();
+                List<String> allowedProtocols = new ArrayList<>();
+                addAll(hosts, allowedHosts, permanentAllowedHosts, ALLOWED_HOSTS, data);
+                addAll(classes, allowedClasses, permanentAllowedClasses, ALLOWED_CLASSES, data);
+                addAll(protocols, allowedProtocols, permanentAllowedProtocols, ALLOWED_PROTOCOLS, data);
+                try {
+                    return new JndiManager(name, new InitialDirContext(data), allowedHosts, allowedClasses,
+                            allowedProtocols);
+                } catch (final NamingException e) {
+                    LOGGER.error("Error creating JNDI InitialContext.", e);
+                    return null;
+                }
+            } else {
+                return new JndiManager(name);
             }
         }
 
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
index 09bd0a6..43afdb7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
@@ -93,6 +93,12 @@ public class JndiContextSelector implements NamedContextSelector {
 
     private static final StatusLogger LOGGER = StatusLogger.getLogger();
 
+    public JndiContextSelector() {
+        if (!JndiManager.isIsJndiEnabled()) {
+            throw new IllegalStateException("JNDI must be enabled by setting log4j2.enableJndi=true");
+        }
+    }
+
     @Override
     public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
         LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/mom/JmsAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/mom/JmsAppenderTest.java
index 9216ef8..daf2d0e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/mom/JmsAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/mom/JmsAppenderTest.java
@@ -49,6 +49,7 @@ import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.message.StringMapMessage;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
@@ -83,6 +84,11 @@ public class JmsAppenderTest {
     @Rule
     public RuleChain rules = RuleChain.outerRule(jndiRule).around(ctx);
 
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        System.setProperty("log4j2.enableJndi", "true");
+    }
+
     public JmsAppenderTest() throws Exception {
         // this needs to set up before LoggerContextRule
         given(connectionFactory.createConnection()).willReturn(connection);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java
index 02a8a79..4ed02f2 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.appender.routing;
 
 import java.io.File;
 import java.util.Collections;
+import java.util.Map;
 import javax.naming.Context;
 import javax.naming.InitialContext;
 import javax.naming.NamingException;
@@ -29,6 +30,7 @@ import org.apache.logging.log4j.message.StructuredDataMessage;
 import org.apache.logging.log4j.test.appender.ListAppender;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -47,8 +49,12 @@ public class RoutingAppenderWithJndiTest {
     public static LoggerContextRule loggerContextRule = new LoggerContextRule("log4j-routing-by-jndi.xml");
 
     @ClassRule
-    public static RuleChain rules = RuleChain.outerRule(new JndiRule(Collections.<String, Object>emptyMap()))
-        .around(loggerContextRule);
+    public static RuleChain rules = RuleChain.outerRule(new JndiRule(initBindings())).around(loggerContextRule);
+
+    private static Map<String, Object> initBindings() {
+        System.setProperty("log4j2.enableJndi", "true");
+        return Collections.emptyMap();
+    }
 
     @Before
     public void before() throws NamingException {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java
index 9e55563..7ce5fa7 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java
@@ -23,6 +23,7 @@ import java.util.Map;
 
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.junit.JndiRule;
+import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.rules.ExternalResource;
@@ -48,12 +49,14 @@ public class InterpolatorTest {
         protected void before() throws Throwable {
             System.setProperty(TESTKEY, TESTVAL);
             System.setProperty(TESTKEY2, TESTVAL);
+            System.setProperty("log4j2.enableJndi", "true");
         }
 
         @Override
         protected void after() {
             System.clearProperty(TESTKEY);
             System.clearProperty(TESTKEY2);
+            System.clearProperty("log4j2.enableJndi");
         }
     }).around(new JndiRule(
         JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME));
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiDisabledLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiDisabledLookupTest.java
new file mode 100644
index 0000000..eb44bc6
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiDisabledLookupTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.lookup;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.junit.JndiRule;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * JndiDisabledLookupTest
+ *
+ * Verifies the Lookups are disabled without the log4j2.enableJndi property set to true.
+ */
+public class JndiDisabledLookupTest {
+
+    private static final String TEST_CONTEXT_RESOURCE_NAME = "logging/context-name";
+    private static final String TEST_CONTEXT_NAME = "app-1";
+    private static final String TEST_INTEGRAL_NAME = "int-value";
+    private static final int TEST_INTEGRAL_VALUE = 42;
+    private static final String TEST_STRINGS_NAME = "string-collection";
+    private static final Collection<String> TEST_STRINGS_COLLECTION = Arrays.asList("one", "two", "three");
+
+    @Rule
+    public JndiRule jndiRule = new JndiRule(createBindings());
+
+    private Map<String, Object> createBindings() {
+        final Map<String, Object> map = new HashMap<>();
+        map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME);
+        map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_INTEGRAL_NAME, TEST_INTEGRAL_VALUE);
+        map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_STRINGS_NAME, TEST_STRINGS_COLLECTION);
+        return map;
+    }
+
+    @Test
+    public void testLookup() {
+        final StrLookup lookup = new JndiLookup();
+
+        String contextName = lookup.lookup(TEST_CONTEXT_RESOURCE_NAME);
+        assertNull(contextName);
+    }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java
index c2e34e3..530d1ee 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.logging.log4j.junit.JndiRule;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -42,6 +43,11 @@ public class JndiLookupTest {
     @Rule
     public JndiRule jndiRule = new JndiRule(createBindings());
 
+    @BeforeClass
+    public static void beforeClass() {
+        System.setProperty("log4j2.enableJndi", "true");
+    }
+
     private Map<String, Object> createBindings() {
         final Map<String, Object> map = new HashMap<>();
         map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiRestrictedLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiRestrictedLookupTest.java
index 032c9c4..5a6d0ac 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiRestrictedLookupTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiRestrictedLookupTest.java
@@ -54,6 +54,7 @@ public class JndiRestrictedLookupTest {
     public static void beforeClass() {
         System.setProperty("log4j2.allowedLdapClasses", Level.class.getName());
         System.setProperty("log4j2.allowedJndiProtocols", "dns");
+        System.setProperty("log4j2.enableJndi", "true");
     }
 
     @Test
diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
index 267f54f..bcf6232 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -1527,6 +1527,9 @@ public class ConnectionFactory {
         <a name="JMSTopicAppender"/>
         <subsection name="JMS Appender">
           <p>The JMS Appender sends the formatted log event to a JMS Destination.</p>
+          <p>The JMS Appender requires JNDI support so as of release 2.15.1 this appender will not function unless
+            <code>log4j2.enableJndi=true</code>log4j2.enableJndi=true is configured as a system property or environment
+            variable. See the <a href="./configuration.html#enableJndi">enableJndi</a> system property.</p>
           <p>
             Note that in Log4j 2.0, this appender was split into a JMSQueueAppender and a JMSTopicAppender. Starting
             in Log4j 2.1, these appenders were combined into the JMS Appender which makes no distinction between queues
diff --git a/src/site/xdoc/manual/configuration.xml.vm b/src/site/xdoc/manual/configuration.xml.vm
index 402a96c..69737de 100644
--- a/src/site/xdoc/manual/configuration.xml.vm
+++ b/src/site/xdoc/manual/configuration.xml.vm
@@ -1960,6 +1960,14 @@ public class AwesomeTest {
       before falling back to the default class loader.
     </td>
   </tr>
+  <tr>
+    <td><a name="enableJndi"/>log4j2.enableJndi</td>
+    <td>LOG4J_ENABLE_JNDI</td>
+    <td>false</td>
+    <td>
+      When true, Log4j components that use JNDI are enabled. When false, the default, they are disabled.
+    </td>
+  </tr>
   <tr>
     <td><a name="allowedLdapClasses"/>log4j2.allowedLdapClasses</td>
     <td>LOG4J_ALLOWED_LDAP_CLASSES</td>
diff --git a/src/site/xdoc/manual/logsep.xml b/src/site/xdoc/manual/logsep.xml
index 55b59eb..6d8694a 100644
--- a/src/site/xdoc/manual/logsep.xml
+++ b/src/site/xdoc/manual/logsep.xml
@@ -111,6 +111,9 @@
                   to use JNDI to locate each web application's <code>LoggerContext</code>. Be sure to set the
                   <code>isLog4jContextSelectorNamed</code> context parameter to <kbd>true</kbd> and also set the
                   <code>log4jContextName</code> and <code>log4jConfiguration</code> context parameters.
+                  Note that the JndiContextSelector will not work unless <code>log4j2.enableJndi=true</code> is set as a
+                  system property or environment variable. See the
+                  <a href="./configuration.html#enableJndi">enableJndi</a> system property.
                 </li>
               </ol>
             <p>
diff --git a/src/site/xdoc/manual/lookups.xml b/src/site/xdoc/manual/lookups.xml
index cc6a66f..70024ff 100644
--- a/src/site/xdoc/manual/lookups.xml
+++ b/src/site/xdoc/manual/lookups.xml
@@ -266,6 +266,11 @@
         </subsection>
         <a name="JndiLookup"/>
         <subsection name="Jndi Lookup">
+          <p>
+            As of Log4j 2.15.1 JNDI operations require that <code>log4j2.enableJndi=true</code> be set as a system
+            property or the corresponding environment variable for this lookup to function. See the
+            <a href="./configuration.html#enableJndi">enableJndi</a> system property.
+          </p>
           <p>
             The JndiLookup allows variables to be retrieved via JNDI. By default the key will be prefixed with
             java:comp/env/, however if the key contains a ":" no prefix will be added.
-- 
2.27.0

