001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017
018package org.apache.logging.log4j.core.net;
019
020import java.net.URI;
021import java.net.URISyntaxException;
022import java.util.Properties;
023
024import javax.naming.Context;
025import javax.naming.InitialContext;
026import javax.naming.NamingException;
027
028import org.apache.logging.log4j.core.appender.AbstractManager;
029import org.apache.logging.log4j.core.appender.ManagerFactory;
030import org.apache.logging.log4j.core.util.JndiCloser;
031import org.apache.logging.log4j.util.PropertiesUtil;
032
033/**
034 * JNDI {@link javax.naming.Context} manager.
035 *
036 * @since 2.1
037 */
038public class JndiManager extends AbstractManager {
039
040    private static final JndiManagerFactory FACTORY = new JndiManagerFactory();
041    private static final String PREFIX = "log4j2.enableJndi";
042    private static final String JAVA_SCHEME = "java";
043
044    private static final boolean JNDI_CONTEXT_SELECTOR_ENABLED = isJndiEnabled("ContextSelector");
045    private static final boolean JNDI_JDBC_ENABLED = isJndiEnabled("Jdbc");
046    private static final boolean JNDI_JMS_ENABLED = isJndiEnabled("Jms");
047    private static final boolean JNDI_LOOKUP_ENABLED = isJndiEnabled("Lookup");
048
049    private final InitialContext context;
050
051    private static String createManagerName() {
052        return JndiManager.class.getName() + '@' + JndiManager.class.hashCode();
053    }
054
055    private static boolean isJndiEnabled(final String subKey) {
056        return PropertiesUtil.getProperties().getBooleanProperty(PREFIX + subKey, false);
057    }
058
059    public static boolean isJndiEnabled() {
060        return isJndiContextSelectorEnabled() || isJndiJdbcEnabled() || isJndiJmsEnabled() || isJndiLookupEnabled();
061    }
062
063    public static boolean isJndiContextSelectorEnabled() {
064        return JNDI_CONTEXT_SELECTOR_ENABLED;
065    }
066
067    public static boolean isJndiJdbcEnabled() {
068        return JNDI_JDBC_ENABLED;
069    }
070
071    public static boolean isJndiJmsEnabled() {
072        return JNDI_JMS_ENABLED;
073    }
074
075    public static boolean isJndiLookupEnabled() {
076        return JNDI_LOOKUP_ENABLED;
077    }
078
079    private JndiManager(final String name, final InitialContext context) {
080        super(name);
081        this.context = context;
082    }
083
084    /**
085     * Gets the default JndiManager using the default {@link javax.naming.InitialContext}.
086     *
087     * @return the default JndiManager
088     */
089    public static JndiManager getDefaultManager() {
090        return getManager(JndiManager.class.getName(), FACTORY, null);
091    }
092
093    /**
094     * Gets a named JndiManager using the default {@link javax.naming.InitialContext}.
095     * @param name the name of the JndiManager instance to create or use if available
096     * @return a default JndiManager
097     */
098    public static JndiManager getDefaultManager(final String name) {
099        return getManager(name, FACTORY, null);
100    }
101
102    /**
103     * Gets a JndiManager with the provided configuration information.
104     *
105     * @param properties JNDI properties, usually created by calling {@link #createProperties(String, String, String, String, String, Properties)}.
106     * @return the JndiManager for the provided parameters.
107     * @see #createProperties(String, String, String, String, String, Properties)
108     * @since 2.9
109     */
110    public static JndiManager getJndiManager(final Properties properties) {
111        return getManager(createManagerName(), FACTORY, properties);
112    }
113
114    /**
115     * Gets a JndiManager with the provided configuration information.
116     *
117     * @param initialContextFactoryName Fully qualified class name of an implementation of
118     *                                  {@link javax.naming.spi.InitialContextFactory}.
119     * @param providerURL               The provider URL to use for the JNDI connection (specific to the above factory).
120     * @param urlPkgPrefixes            A colon-separated list of package prefixes for the class name of the factory
121     *                                  class that will create a URL context factory
122     * @param securityPrincipal         The name of the identity of the Principal.
123     * @param securityCredentials       The security credentials of the Principal.
124     * @param additionalProperties      Any additional JNDI environment properties to set or {@code null} for none.
125     * @return the JndiManager for the provided parameters.
126     */
127    public static JndiManager getJndiManager(final String initialContextFactoryName,
128                                             final String providerURL,
129                                             final String urlPkgPrefixes,
130                                             final String securityPrincipal,
131                                             final String securityCredentials,
132                                             final Properties additionalProperties) {
133        final String name = JndiManager.class.getName() + '@' + JndiManager.class.hashCode();
134        if (initialContextFactoryName == null) {
135            return getManager(name, FACTORY, null);
136        }
137        final Properties properties = new Properties();
138        properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName);
139        if (providerURL != null) {
140            properties.setProperty(Context.PROVIDER_URL, providerURL);
141        } else {
142            LOGGER.warn("The JNDI InitialContextFactory class name [{}] was provided, but there was no associated " +
143                "provider URL. This is likely to cause problems.", initialContextFactoryName);
144        }
145        if (urlPkgPrefixes != null) {
146            properties.setProperty(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
147        }
148        if (securityPrincipal != null) {
149            properties.setProperty(Context.SECURITY_PRINCIPAL, securityPrincipal);
150            if (securityCredentials != null) {
151                properties.setProperty(Context.SECURITY_CREDENTIALS, securityCredentials);
152            } else {
153                LOGGER.warn("A security principal [{}] was provided, but with no corresponding security credentials.",
154                    securityPrincipal);
155            }
156        }
157        if (additionalProperties != null) {
158            properties.putAll(additionalProperties);
159        }
160        return getManager(name, FACTORY, properties);
161    }
162
163    @Override
164    protected void releaseSub() {
165        JndiCloser.closeSilently(this.context);
166    }
167
168    /**
169     * Looks up a named object through this JNDI context.
170     *
171     * @param name name of the object to look up.
172     * @param <T>  the type of the object.
173     * @return the named object if it could be located.
174     * @throws NamingException
175     */
176    @SuppressWarnings("unchecked")
177    public <T> T lookup(final String name) throws NamingException {
178        if (context == null) {
179            return null;
180        }
181        try {
182            URI uri = new URI(name);
183            if (uri.getScheme() == null || uri.getScheme().equals(JAVA_SCHEME)) {
184                return (T) this.context.lookup(name);
185            }
186            LOGGER.warn("Unsupported JNDI URI - {}", name);
187        } catch (URISyntaxException ex) {
188            LOGGER.warn("Invalid JNDI URI - {}", name);
189        }
190        return null;
191    }
192
193    private static class JndiManagerFactory implements ManagerFactory<JndiManager, Properties> {
194
195        @Override
196        public JndiManager createManager(final String name, final Properties data) {
197            if (!isJndiEnabled()) {
198                throw new IllegalStateException(String.format("JNDI must be enabled by setting one of the %s* properties to true", PREFIX));
199            }
200            try {
201                return new JndiManager(name, new InitialContext(data));
202            } catch (final NamingException e) {
203                LOGGER.error("Error creating JNDI InitialContext.", e);
204                return null;
205            }
206        }
207    }
208}