diff --git a/BungeeCord-Patches/0062-Backport-log4j-2.15.0-bugfix.patch b/BungeeCord-Patches/0062-Backport-log4j-2.15.0-bugfix.patch new file mode 100644 index 0000000..71d39d9 --- /dev/null +++ b/BungeeCord-Patches/0062-Backport-log4j-2.15.0-bugfix.patch @@ -0,0 +1,836 @@ +From ffbeaa7e0a9703bcd027451e47d7ddb3e80f6c23 Mon Sep 17 00:00:00 2001 +From: Ralph Goers +Date: Thu, 9 Dec 2021 17:52:54 +0000 +Subject: [PATCH] Backport log4j 2.15.0 bugfix + + +diff --git a/log4j/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java b/log4j/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java +new file mode 100644 +index 00000000..67a78391 +--- /dev/null ++++ b/log4j/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java +@@ -0,0 +1,294 @@ ++/* ++ * 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.appender.mom; ++ ++import java.io.Serializable; ++import java.util.Properties; ++import java.util.concurrent.TimeUnit; ++ ++import org.apache.logging.log4j.core.Appender; ++import org.apache.logging.log4j.core.Filter; ++import org.apache.logging.log4j.core.Layout; ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.appender.AbstractAppender; ++import org.apache.logging.log4j.core.appender.AbstractManager; ++import org.apache.logging.log4j.core.appender.mom.JmsManager.JmsManagerConfiguration; ++import org.apache.logging.log4j.core.config.Node; ++import org.apache.logging.log4j.core.config.Property; ++import org.apache.logging.log4j.core.config.plugins.Plugin; ++import org.apache.logging.log4j.core.config.plugins.PluginAliases; ++import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; ++import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; ++import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; ++import org.apache.logging.log4j.core.net.JndiManager; ++ ++/** ++ * Generic JMS Appender plugin for both queues and topics. This Appender replaces the previous split ones. However, ++ * configurations set up for the 2.0 version of the JMS appenders will still work. ++ */ ++@Plugin(name = "JMS", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) ++@PluginAliases({ "JMSQueue", "JMSTopic" }) ++public class JmsAppender extends AbstractAppender { ++ ++ public static class Builder> extends AbstractAppender.Builder ++ implements org.apache.logging.log4j.core.util.Builder { ++ ++ public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000; ++ ++ @PluginBuilderAttribute ++ private String factoryName; ++ ++ @PluginBuilderAttribute ++ private String providerUrl; ++ ++ @PluginBuilderAttribute ++ private String urlPkgPrefixes; ++ ++ @PluginBuilderAttribute ++ private String securityPrincipalName; ++ ++ @PluginBuilderAttribute(sensitive = true) ++ private String securityCredentials; ++ ++ @PluginBuilderAttribute ++ @Required(message = "A javax.jms.ConnectionFactory JNDI name must be specified") ++ private String factoryBindingName; ++ ++ @PluginBuilderAttribute ++ @PluginAliases({ "queueBindingName", "topicBindingName" }) ++ @Required(message = "A javax.jms.Destination JNDI name must be specified") ++ private String destinationBindingName; ++ ++ @PluginBuilderAttribute ++ private String userName; ++ ++ @PluginBuilderAttribute(sensitive = true) ++ private char[] password; ++ ++ @PluginBuilderAttribute ++ private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS; ++ ++ @PluginBuilderAttribute ++ private boolean immediateFail; ++ ++ @PluginBuilderAttribute ++ private String allowedLdapClasses; ++ ++ @PluginBuilderAttribute ++ private String allowedLdapHosts; ++ ++ @PluginBuilderAttribute ++ private String allowedJndiProtocols; ++ ++ // Programmatic access only for now. ++ private JmsManager jmsManager; ++ ++ private Builder() { ++ } ++ ++ @SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender ++ @Override ++ public JmsAppender build() { ++ JmsManager actualJmsManager = jmsManager; ++ JmsManagerConfiguration configuration = null; ++ if (actualJmsManager == null) { ++ Properties additionalProperties = null; ++ if (allowedLdapClasses != null || allowedLdapHosts != null) { ++ additionalProperties = new Properties(); ++ if (allowedLdapHosts != null) { ++ additionalProperties.put(JndiManager.ALLOWED_HOSTS, allowedLdapHosts); ++ } ++ if (allowedLdapClasses != null) { ++ additionalProperties.put(JndiManager.ALLOWED_CLASSES, allowedLdapClasses); ++ } ++ if (allowedJndiProtocols != null) { ++ additionalProperties.put(JndiManager.ALLOWED_PROTOCOLS, allowedJndiProtocols); ++ } ++ } ++ final Properties jndiProperties = JndiManager.createProperties(factoryName, providerUrl, urlPkgPrefixes, ++ securityPrincipalName, securityCredentials, additionalProperties); ++ configuration = new JmsManagerConfiguration(jndiProperties, factoryBindingName, destinationBindingName, ++ userName, password, false, reconnectIntervalMillis); ++ actualJmsManager = AbstractManager.getManager(getName(), JmsManager.FACTORY, configuration); ++ } ++ if (actualJmsManager == null) { ++ // JmsManagerFactory has already logged an ERROR. ++ return null; ++ } ++ final Layout layout = getLayout(); ++ if (layout == null) { ++ LOGGER.error("No layout provided for JmsAppender"); ++ return null; ++ } ++ //try { ++ return new JmsAppender(getName(), getFilter(), layout, isIgnoreExceptions(), getPropertyArray(), ++ actualJmsManager); ++ /*} catch (final JMSException e) { ++ // Never happens since the ctor no longer actually throws a JMSException. ++ throw new IllegalStateException(e); ++ }*/ ++ } ++ ++ public Builder setDestinationBindingName(final String destinationBindingName) { ++ this.destinationBindingName = destinationBindingName; ++ return this; ++ } ++ ++ public Builder setFactoryBindingName(final String factoryBindingName) { ++ this.factoryBindingName = factoryBindingName; ++ return this; ++ } ++ ++ public Builder setFactoryName(final String factoryName) { ++ this.factoryName = factoryName; ++ return this; ++ } ++ ++ public Builder setImmediateFail(final boolean immediateFail) { ++ this.immediateFail = immediateFail; ++ return this; ++ } ++ ++ public Builder setJmsManager(final JmsManager jmsManager) { ++ this.jmsManager = jmsManager; ++ return this; ++ } ++ ++ public Builder setPassword(final char[] password) { ++ this.password = password; ++ return this; ++ } ++ ++ /** ++ * @deprecated Use setPassword(char[]) ++ */ ++ @Deprecated ++ public Builder setPassword(final String password) { ++ this.password = password == null ? null : password.toCharArray(); ++ return this; ++ } ++ ++ public Builder setProviderUrl(final String providerUrl) { ++ this.providerUrl = providerUrl; ++ return this; ++ } ++ ++ public Builder setReconnectIntervalMillis(final long reconnectIntervalMillis) { ++ this.reconnectIntervalMillis = reconnectIntervalMillis; ++ return this; ++ } ++ ++ public Builder setSecurityCredentials(final String securityCredentials) { ++ this.securityCredentials = securityCredentials; ++ return this; ++ } ++ ++ public Builder setSecurityPrincipalName(final String securityPrincipalName) { ++ this.securityPrincipalName = securityPrincipalName; ++ return this; ++ } ++ ++ public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) { ++ this.urlPkgPrefixes = urlPkgPrefixes; ++ return this; ++ } ++ ++ /** ++ * @deprecated Use {@link #setUserName(String)}. ++ */ ++ @Deprecated ++ public Builder setUsername(final String username) { ++ this.userName = username; ++ return this; ++ } ++ ++ public Builder setUserName(final String userName) { ++ this.userName = userName; ++ return this; ++ } ++ ++ public Builder setAllowedLdapClasses(final String allowedLdapClasses) { ++ this.allowedLdapClasses = allowedLdapClasses; ++ return this; ++ } ++ ++ public Builder setAllowedLdapHosts(final String allowedLdapHosts) { ++ this.allowedLdapHosts = allowedLdapHosts; ++ return this; ++ } ++ ++ public Builder setAllowedJndiProtocols(final String allowedJndiProtocols) { ++ this.allowedJndiProtocols = allowedJndiProtocols; ++ return this; ++ } ++ ++ /** ++ * Does not include the password. ++ */ ++ @Override ++ public String toString() { ++ return "Builder [name=" + getName() + ", factoryName=" + factoryName + ", providerUrl=" + providerUrl ++ + ", urlPkgPrefixes=" + urlPkgPrefixes + ", securityPrincipalName=" + securityPrincipalName ++ + ", securityCredentials=" + securityCredentials + ", factoryBindingName=" + factoryBindingName ++ + ", destinationBindingName=" + destinationBindingName + ", username=" + userName + ", layout=" ++ + getLayout() + ", filter=" + getFilter() + ", ignoreExceptions=" + isIgnoreExceptions() ++ + ", jmsManager=" + jmsManager + ", allowedLdapClasses=" + allowedLdapClasses ++ + ", allowedLdapHosts=" + allowedLdapHosts + ", allowedJndiProtocols=" + allowedJndiProtocols + "]"; ++ } ++ ++ } ++ ++ @PluginBuilderFactory ++ public static Builder newBuilder() { ++ return new Builder(); ++ } ++ ++ private volatile JmsManager manager; ++ ++ protected JmsAppender(final String name, final Filter filter, final Layout layout, ++ final boolean ignoreExceptions, final Property[] properties, final JmsManager manager) /*throws JMSException*/ { ++ super(name, filter, layout, ignoreExceptions, properties); ++ this.manager = manager; ++ } ++ ++ @Deprecated ++ protected JmsAppender(final String name, final Filter filter, final Layout layout, ++ final boolean ignoreExceptions, final JmsManager manager) /*throws JMSException*/ { ++ super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY); ++ this.manager = manager; ++ } ++ ++ @Override ++ public void append(final LogEvent event) { ++ this.manager.send(event, toSerializable(event)); ++ } ++ ++ public JmsManager getManager() { ++ return manager; ++ } ++ ++ @Override ++ public boolean stop(final long timeout, final TimeUnit timeUnit) { ++ setStopping(); ++ boolean stopped = super.stop(timeout, timeUnit, false); ++ stopped &= this.manager.stop(timeout, timeUnit); ++ setStopped(); ++ return stopped; ++ } ++ ++} +diff --git a/log4j/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java b/log4j/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java +new file mode 100644 +index 00000000..75ec3a7f +--- /dev/null ++++ b/log4j/src/main/java/org/apache/logging/log4j/core/net/JndiManager.java +@@ -0,0 +1,301 @@ ++/* ++ * 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.net; ++ ++import java.net.URI; ++import java.net.URISyntaxException; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Properties; ++import java.util.concurrent.TimeUnit; ++ ++import javax.naming.Context; ++import javax.naming.NamingEnumeration; ++import javax.naming.NamingException; ++import javax.naming.directory.Attribute; ++import javax.naming.directory.Attributes; ++import javax.naming.directory.DirContext; ++import javax.naming.directory.InitialDirContext; ++ ++import org.apache.logging.log4j.core.appender.AbstractManager; ++import org.apache.logging.log4j.core.appender.ManagerFactory; ++import org.apache.logging.log4j.core.util.JndiCloser; ++import org.apache.logging.log4j.core.util.NetUtils; ++import org.apache.logging.log4j.util.PropertiesUtil; ++ ++/** ++ * Manages a JNDI {@link javax.naming.directory.DirContext}. ++ * ++ * @since 2.1 ++ */ ++public class JndiManager extends AbstractManager { ++ ++ public static final String ALLOWED_HOSTS = "allowedLdapHosts"; ++ public static final String ALLOWED_CLASSES = "allowedLdapClasses"; ++ public static final String ALLOWED_PROTOCOLS = "allowedJndiProtocols"; ++ ++ private static final JndiManagerFactory FACTORY = new JndiManagerFactory(); ++ private static final String PREFIX = "log4j2."; ++ private static final String LDAP = "ldap"; ++ private static final String LDAPS = "ldaps"; ++ private static final String JAVA = "java"; ++ private static final List permanentAllowedHosts = NetUtils.getLocalIps(); ++ private static final List permanentAllowedClasses = Arrays.asList(Boolean.class.getName(), ++ Byte.class.getName(), Character.class.getName(), Double.class.getName(), Float.class.getName(), ++ Integer.class.getName(), Long.class.getName(), Short.class.getName(), String.class.getName()); ++ private static final List permanentAllowedProtocols = Arrays.asList(JAVA, LDAP, LDAPS); ++ private static final String SERIALIZED_DATA = "javaSerializedData"; ++ private static final String CLASS_NAME = "javaClassName"; ++ private static final String REFERENCE_ADDRESS = "javaReferenceAddress"; ++ private static final String OBJECT_FACTORY = "javaFactory"; ++ private final List allowedHosts; ++ private final List allowedClasses; ++ private final List allowedProtocols; ++ ++ private final DirContext context; ++ ++ private JndiManager(final String name, final DirContext context, final List allowedHosts, ++ final List allowedClasses, final List allowedProtocols) { ++ super(null, name); ++ this.context = context; ++ this.allowedHosts = allowedHosts; ++ this.allowedClasses = allowedClasses; ++ this.allowedProtocols = allowedProtocols; ++ } ++ ++ /** ++ * Gets the default JndiManager using the default {@link javax.naming.InitialContext}. ++ * ++ * @return the default JndiManager ++ */ ++ public static JndiManager getDefaultManager() { ++ return getManager(JndiManager.class.getName(), FACTORY, null); ++ } ++ ++ /** ++ * Gets a named JndiManager using the default {@link javax.naming.InitialContext}. ++ * ++ * @param name the name of the JndiManager instance to create or use if available ++ * @return a default JndiManager ++ */ ++ public static JndiManager getDefaultManager(final String name) { ++ return getManager(name, FACTORY, null); ++ } ++ ++ /** ++ * Gets a JndiManager with the provided configuration information. ++ * ++ * @param initialContextFactoryName Fully qualified class name of an implementation of ++ * {@link javax.naming.spi.InitialContextFactory}. ++ * @param providerURL The provider URL to use for the JNDI connection (specific to the above factory). ++ * @param urlPkgPrefixes A colon-separated list of package prefixes for the class name of the factory ++ * class that will create a URL context factory ++ * @param securityPrincipal The name of the identity of the Principal. ++ * @param securityCredentials The security credentials of the Principal. ++ * @param additionalProperties Any additional JNDI environment properties to set or {@code null} for none. ++ * @return the JndiManager for the provided parameters. ++ */ ++ public static JndiManager getJndiManager(final String initialContextFactoryName, ++ final String providerURL, ++ final String urlPkgPrefixes, ++ final String securityPrincipal, ++ final String securityCredentials, ++ final Properties additionalProperties) { ++ final Properties properties = createProperties(initialContextFactoryName, providerURL, urlPkgPrefixes, ++ securityPrincipal, securityCredentials, additionalProperties); ++ return getManager(createManagerName(), FACTORY, properties); ++ } ++ ++ /** ++ * Gets a JndiManager with the provided configuration information. ++ * ++ * @param properties JNDI properties, usually created by calling {@link #createProperties(String, String, String, String, String, Properties)}. ++ * @return the JndiManager for the provided parameters. ++ * @see #createProperties(String, String, String, String, String, Properties) ++ * @since 2.9 ++ */ ++ public static JndiManager getJndiManager(final Properties properties) { ++ return getManager(createManagerName(), FACTORY, properties); ++ } ++ ++ private static String createManagerName() { ++ return JndiManager.class.getName() + '@' + JndiManager.class.hashCode(); ++ } ++ ++ /** ++ * Creates JNDI Properties with the provided configuration information. ++ * ++ * @param initialContextFactoryName ++ * Fully qualified class name of an implementation of {@link javax.naming.spi.InitialContextFactory}. ++ * @param providerURL ++ * The provider URL to use for the JNDI connection (specific to the above factory). ++ * @param urlPkgPrefixes ++ * A colon-separated list of package prefixes for the class name of the factory class that will create a ++ * URL context factory ++ * @param securityPrincipal ++ * The name of the identity of the Principal. ++ * @param securityCredentials ++ * The security credentials of the Principal. ++ * @param additionalProperties ++ * Any additional JNDI environment properties to set or {@code null} for none. ++ * @return the Properties for the provided parameters. ++ * @since 2.9 ++ */ ++ public static Properties createProperties(final String initialContextFactoryName, final String providerURL, ++ final String urlPkgPrefixes, final String securityPrincipal, final String securityCredentials, ++ final Properties additionalProperties) { ++ if (initialContextFactoryName == null) { ++ return null; ++ } ++ final Properties properties = new Properties(); ++ properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName); ++ if (providerURL != null) { ++ properties.setProperty(Context.PROVIDER_URL, providerURL); ++ } else { ++ LOGGER.warn("The JNDI InitialContextFactory class name [{}] was provided, but there was no associated " ++ + "provider URL. This is likely to cause problems.", initialContextFactoryName); ++ } ++ if (urlPkgPrefixes != null) { ++ properties.setProperty(Context.URL_PKG_PREFIXES, urlPkgPrefixes); ++ } ++ if (securityPrincipal != null) { ++ properties.setProperty(Context.SECURITY_PRINCIPAL, securityPrincipal); ++ if (securityCredentials != null) { ++ properties.setProperty(Context.SECURITY_CREDENTIALS, securityCredentials); ++ } else { ++ LOGGER.warn("A security principal [{}] was provided, but with no corresponding security credentials.", ++ securityPrincipal); ++ } ++ } ++ if (additionalProperties != null) { ++ properties.putAll(additionalProperties); ++ } ++ return properties; ++ } ++ ++ @Override ++ protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { ++ return JndiCloser.closeSilently(this.context); ++ } ++ ++ /** ++ * Looks up a named object through this JNDI context. ++ * ++ * @param name name of the object to look up. ++ * @param the type of the object. ++ * @return the named object if it could be located. ++ * @throws NamingException if a naming exception is encountered ++ */ ++ @SuppressWarnings("unchecked") ++ public synchronized T lookup(final String name) throws NamingException { ++ try { ++ URI uri = new URI(name); ++ if (uri.getScheme() != null) { ++ if (!allowedProtocols.contains(uri.getScheme().toLowerCase(Locale.ROOT))) { ++ LOGGER.warn("Log4j JNDI does not allow protocol {}", uri.getScheme()); ++ return null; ++ } ++ if (LDAP.equalsIgnoreCase(uri.getScheme()) || LDAPS.equalsIgnoreCase(uri.getScheme())) { ++ if (!allowedHosts.contains(uri.getHost())) { ++ LOGGER.warn("Attempt to access ldap server not in allowed list"); ++ return null; ++ } ++ Attributes attributes = this.context.getAttributes(name); ++ if (attributes != null) { ++ // In testing the "key" for attributes seems to be lowercase while the attribute id is ++ // camelcase, but that may just be true for the test LDAP used here. This copies the Attributes ++ // to a Map ignoring the "key" and using the Attribute's id as the key in the Map so it matches ++ // the Java schema. ++ Map attributeMap = new HashMap<>(); ++ NamingEnumeration enumeration = attributes.getAll(); ++ while (enumeration.hasMore()) { ++ Attribute attribute = enumeration.next(); ++ attributeMap.put(attribute.getID(), attribute); ++ } ++ Attribute classNameAttr = attributeMap.get(CLASS_NAME); ++ if (attributeMap.get(SERIALIZED_DATA) != null) { ++ if (classNameAttr != null) { ++ String className = classNameAttr.get().toString(); ++ if (!allowedClasses.contains(className)) { ++ LOGGER.warn("Deserialization of {} is not allowed", className); ++ return null; ++ } ++ } else { ++ LOGGER.warn("No class name provided for {}", name); ++ return null; ++ } ++ } else if (attributeMap.get(REFERENCE_ADDRESS) != null ++ || attributeMap.get(OBJECT_FACTORY) != null) { ++ LOGGER.warn("Referenceable class is not allowed for {}", name); ++ return null; ++ } ++ } ++ } ++ } ++ } catch (URISyntaxException ex) { ++ // This is OK. ++ } ++ return (T) this.context.lookup(name); ++ } ++ ++ private static class JndiManagerFactory implements ManagerFactory { ++ ++ @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 allowedHosts = new ArrayList<>(); ++ List allowedClasses = new ArrayList<>(); ++ List 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; ++ } ++ } ++ ++ private void addAll(String toSplit, List list, List permanentList, String propertyName, ++ Properties data) { ++ if (toSplit != null) { ++ list.addAll(Arrays.asList(toSplit.split("\\s*,\\s*"))); ++ data.remove(propertyName); ++ } ++ toSplit = PropertiesUtil.getProperties().getStringProperty(PREFIX + propertyName); ++ if (toSplit != null) { ++ list.addAll(Arrays.asList(toSplit.split("\\s*,\\s*"))); ++ } ++ list.addAll(permanentList); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ return "JndiManager [context=" + context + ", count=" + count + "]"; ++ } ++ ++} +diff --git a/log4j/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java b/log4j/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java +new file mode 100644 +index 00000000..661f74f9 +--- /dev/null ++++ b/log4j/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java +@@ -0,0 +1,214 @@ ++/* ++ * 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.util; ++ ++import java.io.File; ++import java.net.Inet4Address; ++import java.net.Inet6Address; ++import java.net.InetAddress; ++import java.net.MalformedURLException; ++import java.net.NetworkInterface; ++import java.net.SocketException; ++import java.net.URI; ++import java.net.URISyntaxException; ++import java.net.URL; ++import java.net.UnknownHostException; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Enumeration; ++import java.util.List; ++ ++import org.apache.logging.log4j.Logger; ++import org.apache.logging.log4j.status.StatusLogger; ++import org.apache.logging.log4j.util.Strings; ++ ++/** ++ * Networking-related convenience methods. ++ */ ++public final class NetUtils { ++ ++ private static final Logger LOGGER = StatusLogger.getLogger(); ++ private static final String UNKNOWN_LOCALHOST = "UNKNOWN_LOCALHOST"; ++ ++ private NetUtils() { ++ // empty ++ } ++ ++ /** ++ * This method gets the network name of the machine we are running on. Returns "UNKNOWN_LOCALHOST" in the unlikely ++ * case where the host name cannot be found. ++ * ++ * @return String the name of the local host ++ */ ++ public static String getLocalHostname() { ++ try { ++ final InetAddress addr = InetAddress.getLocalHost(); ++ return addr == null ? UNKNOWN_LOCALHOST : addr.getHostName(); ++ } catch (final UnknownHostException uhe) { ++ try { ++ final Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); ++ if (interfaces != null) { ++ while (interfaces.hasMoreElements()) { ++ final NetworkInterface nic = interfaces.nextElement(); ++ final Enumeration addresses = nic.getInetAddresses(); ++ while (addresses.hasMoreElements()) { ++ final InetAddress address = addresses.nextElement(); ++ if (!address.isLoopbackAddress()) { ++ final String hostname = address.getHostName(); ++ if (hostname != null) { ++ return hostname; ++ } ++ } ++ } ++ } ++ } ++ } catch (final SocketException se) { ++ // ignore and log below. ++ } ++ LOGGER.error("Could not determine local host name", uhe); ++ return UNKNOWN_LOCALHOST; ++ } ++ } ++ ++ /** ++ * Returns all the local host names and ip addresses. ++ * @return The local host names and ip addresses. ++ */ ++ public static List getLocalIps() { ++ List localIps = new ArrayList<>(); ++ localIps.add("localhost"); ++ localIps.add("127.0.0.1"); ++ try { ++ final InetAddress addr = Inet4Address.getLocalHost(); ++ setHostName(addr, localIps); ++ } catch (final UnknownHostException ex) { ++ // Ignore this. ++ } ++ try { ++ final Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); ++ if (interfaces != null) { ++ while (interfaces.hasMoreElements()) { ++ final NetworkInterface nic = interfaces.nextElement(); ++ final Enumeration addresses = nic.getInetAddresses(); ++ while (addresses.hasMoreElements()) { ++ final InetAddress address = addresses.nextElement(); ++ setHostName(address, localIps); ++ } ++ } ++ } ++ } catch (final SocketException se) { ++ // ignore. ++ } ++ return localIps; ++ } ++ ++ private static void setHostName(InetAddress address, List localIps) { ++ String[] parts = address.toString().split("\\s*/\\s*"); ++ if (parts.length > 0) { ++ for (String part : parts) { ++ if (Strings.isNotBlank(part) && !localIps.contains(part)) { ++ localIps.add(part); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Returns the local network interface's MAC address if possible. The local network interface is defined here as ++ * the {@link java.net.NetworkInterface} that is both up and not a loopback interface. ++ * ++ * @return the MAC address of the local network interface or {@code null} if no MAC address could be determined. ++ */ ++ public static byte[] getMacAddress() { ++ byte[] mac = null; ++ try { ++ final InetAddress localHost = InetAddress.getLocalHost(); ++ try { ++ final NetworkInterface localInterface = NetworkInterface.getByInetAddress(localHost); ++ if (isUpAndNotLoopback(localInterface)) { ++ mac = localInterface.getHardwareAddress(); ++ } ++ if (mac == null) { ++ final Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); ++ if (networkInterfaces != null) { ++ while (networkInterfaces.hasMoreElements() && mac == null) { ++ final NetworkInterface nic = networkInterfaces.nextElement(); ++ if (isUpAndNotLoopback(nic)) { ++ mac = nic.getHardwareAddress(); ++ } ++ } ++ } ++ } ++ } catch (final SocketException e) { ++ LOGGER.catching(e); ++ } ++ if (ArrayUtils.isEmpty(mac) && localHost != null) { ++ // Emulate a MAC address with an IP v4 or v6 ++ final byte[] address = localHost.getAddress(); ++ // Take only 6 bytes if the address is an IPv6 otherwise will pad with two zero bytes ++ mac = Arrays.copyOf(address, 6); ++ } ++ } catch (final UnknownHostException ignored) { ++ // ignored ++ } ++ return mac; ++ } ++ ++ /** ++ * Returns the mac address, if it is available, as a string with each byte separated by a ":" character. ++ * @return the mac address String or null. ++ */ ++ public static String getMacAddressString() { ++ final byte[] macAddr = getMacAddress(); ++ if (!ArrayUtils.isEmpty(macAddr)) { ++ StringBuilder sb = new StringBuilder(String.format("%02x", macAddr[0])); ++ for (int i = 1; i < macAddr.length; ++i) { ++ sb.append(":").append(String.format("%02x", macAddr[i])); ++ } ++ return sb.toString(); ++ ++ } ++ return null; ++ } ++ ++ private static boolean isUpAndNotLoopback(final NetworkInterface ni) throws SocketException { ++ return ni != null && !ni.isLoopback() && ni.isUp(); ++ } ++ ++ /** ++ * Converts a URI string or file path to a URI object. ++ * ++ * @param path the URI string or path ++ * @return the URI object ++ */ ++ public static URI toURI(final String path) { ++ try { ++ // Resolves absolute URI ++ return new URI(path); ++ } catch (final URISyntaxException e) { ++ // A file path or a Apache Commons VFS URL might contain blanks. ++ // A file path may start with a driver letter ++ try { ++ final URL url = new URL(path); ++ return new URI(url.getProtocol(), url.getHost(), url.getPath(), null); ++ } catch (MalformedURLException | URISyntaxException nestedEx) { ++ return new File(path).toURI(); ++ } ++ } ++ } ++ ++} +-- +2.34.1 +