[SSHD-746] Added detection of IPv6 address strings
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/595c3595 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/595c3595 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/595c3595 Branch: refs/heads/master Commit: 595c359569005bd2bad67d37b37a343131112ada Parents: df4f912 Author: Goldstein Lyor <[email protected]> Authored: Tue Jan 9 15:29:21 2018 +0200 Committer: Goldstein Lyor <[email protected]> Committed: Tue Jan 9 16:08:36 2018 +0200 ---------------------------------------------------------------------- .../auth/hostbased/UserAuthHostBased.java | 2 +- .../sshd/common/config/SshConfigFileReader.java | 2 +- .../sshd/common/util/net/NetworkConnector.java | 2 +- .../sshd/common/util/net/SshdSocketAddress.java | 159 ++++++++++++++++++- .../sshd/server/x11/X11ForwardSupport.java | 2 +- .../pem/PKCS8PEMResourceKeyPairParserTest.java | 3 + .../util/net/SshdSocketIpv6AddressTest.java | 94 +++++++++++ .../apache/sshd/util/test/BaseTestSupport.java | 2 +- .../apache/sshd/git/pgm/GitPgmCommandTest.java | 2 +- 9 files changed, 254 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBased.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBased.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBased.java index c363eeb..17d090c 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBased.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBased.java @@ -212,6 +212,6 @@ public class UserAuthHostBased extends AbstractUserAuth implements SignatureFact value = SshdSocketAddress.toAddressString(SshdSocketAddress.getFirstExternalNetwork4Address()); } - return GenericUtils.isEmpty(value) ? SshdSocketAddress.LOCALHOST_IP : value; + return GenericUtils.isEmpty(value) ? SshdSocketAddress.LOCALHOST_IPV4 : value; } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java b/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java index 6bb24d7..1bdba24 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java @@ -83,7 +83,7 @@ public final class SshConfigFileReader { public static final String DEFAULT_PASSWORD_AUTH = "no"; public static final boolean DEFAULT_PASSWORD_AUTH_VALUE = parseBooleanValue(DEFAULT_PASSWORD_AUTH); public static final String LISTEN_ADDRESS_CONFIG_PROP = "ListenAddress"; - public static final String DEFAULT_BIND_ADDRESS = SshdSocketAddress.IP_ANYADDR; + public static final String DEFAULT_BIND_ADDRESS = SshdSocketAddress.IPV4_ANYADDR; public static final String PORT_CONFIG_PROP = "Port"; public static final int DEFAULT_PORT = 22; public static final String KEEP_ALIVE_CONFIG_PROP = "TCPKeepAlive"; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-core/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java b/sshd-core/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java index 4d9e547..df7683a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java @@ -27,7 +27,7 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean; * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ public class NetworkConnector extends AbstractLoggingBean { - public static final String DEFAULT_HOST = SshdSocketAddress.LOCALHOST_IP; + public static final String DEFAULT_HOST = SshdSocketAddress.LOCALHOST_IPV4; public static final long DEFAULT_CONNECT_TIMEOUT = TimeUnit.SECONDS.toMillis(5L); public static final long DEFAULT_READ_TIMEOUT = TimeUnit.SECONDS.toMillis(15L); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-core/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java b/sshd-core/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java index 4919966..010b402 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java @@ -25,11 +25,14 @@ import java.net.NetworkInterface; import java.net.SocketAddress; import java.net.SocketException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.List; +import java.util.NavigableSet; import java.util.Objects; +import java.util.TreeSet; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.NumberUtils; @@ -56,8 +59,19 @@ import org.apache.sshd.common.util.ValidateUtils; */ public class SshdSocketAddress extends SocketAddress { public static final String LOCALHOST_NAME = "localhost"; - public static final String LOCALHOST_IP = "127.0.0.1"; - public static final String IP_ANYADDR = "0.0.0.0"; + public static final String LOCALHOST_IPV4 = "127.0.0.1"; + public static final String IPV4_ANYADDR = "0.0.0.0"; + + public static final NavigableSet<String> WELL_KNOWN_IPV4_ADDRESSES = + Collections.unmodifiableNavigableSet( + new TreeSet<String>(String.CASE_INSENSITIVE_ORDER) { + // Not serializing it + private static final long serialVersionUID = 1L; + + { + addAll(Arrays.asList(LOCALHOST_IPV4, IPV4_ANYADDR)); + } + }); // 10.0.0.0 - 10.255.255.255 public static final String PRIVATE_CLASS_A_PREFIX = "10."; @@ -70,10 +84,35 @@ public class SshdSocketAddress extends SocketAddress { // The IPv4 broadcast address public static final String BROADCAST_ADDRESS = "255.255.255.255"; + /** Max. number of hex groups (separated by ":") in an IPV6 address */ + public static final int IPV6_MAX_HEX_GROUPS = 8; + + /** Max. hex digits in each IPv6 group */ + public static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; + + public static final String IPV6_LONG_ANY_ADDRESS = "0:0:0:0:0:0:0:0"; + public static final String IPV6_SHORT_ANY_ADDRESS = "::"; + + public static final String IPV6_LONG_LOCALHOST = "0:0:0:0:0:0:0:1"; + public static final String IPV6_SHORT_LOCALHOST = "::1"; + + public static final NavigableSet<String> WELL_KNOWN_IPV6_ADDRESSES = + Collections.unmodifiableNavigableSet( + new TreeSet<String>(String.CASE_INSENSITIVE_ORDER) { + // Not serializing it + private static final long serialVersionUID = 1L; + + { + addAll(Arrays.asList( + IPV6_LONG_LOCALHOST, IPV6_SHORT_LOCALHOST, + IPV6_LONG_ANY_ADDRESS, IPV6_SHORT_ANY_ADDRESS)); + } + }); + /** * A dummy placeholder that can be used instead of {@code null}s */ - public static final SshdSocketAddress LOCALHOST_ADDRESS = new SshdSocketAddress(LOCALHOST_IP, 0); + public static final SshdSocketAddress LOCALHOST_ADDRESS = new SshdSocketAddress(LOCALHOST_IPV4, 0); /** * Compares {@link InetAddress}-es according to their {@link InetAddress#getHostAddress()} @@ -118,12 +157,12 @@ public class SshdSocketAddress extends SocketAddress { private final int port; public SshdSocketAddress(int port) { - this(IP_ANYADDR, port); + this(IPV4_ANYADDR, port); } public SshdSocketAddress(String hostName, int port) { Objects.requireNonNull(hostName, "Host name may not be null"); - this.hostName = GenericUtils.isEmpty(hostName) ? IP_ANYADDR : hostName; + this.hostName = GenericUtils.isEmpty(hostName) ? IPV4_ANYADDR : hostName; ValidateUtils.checkTrue(port >= 0, "Port must be >= 0: %d", port); this.port = port; @@ -153,7 +192,7 @@ public class SshdSocketAddress extends SocketAddress { return true; } else { return (this.getPort() == that.getPort()) - && Objects.equals(this.getHostName(), that.getHostName()); + && Objects.equals(this.getHostName(), that.getHostName()); } } @@ -246,7 +285,7 @@ public class SshdSocketAddress extends SocketAddress { } if (!(addr instanceof Inet4Address)) { - return false; + return false; // TODO add support for IPv6 - see SSHD-746 } return !isLoopback(addr); @@ -284,10 +323,11 @@ public class SshdSocketAddress extends SocketAddress { return false; } - if (LOCALHOST_NAME.equals(ip) || LOCALHOST_IP.equals(ip)) { + if (LOCALHOST_NAME.equals(ip) || LOCALHOST_IPV4.equals(ip)) { return true; } + // TODO add support for IPv6 - see SSHD-746 String[] values = GenericUtils.split(ip, '.'); if (GenericUtils.length(values) != 4) { return false; @@ -387,10 +427,15 @@ public class SshdSocketAddress extends SocketAddress { } public static boolean isIPv4Address(String addr) { + addr = GenericUtils.trimToEmpty(addr); if (GenericUtils.isEmpty(addr)) { return false; } + if (WELL_KNOWN_IPV4_ADDRESSES.contains(addr)) { + return true; + } + String[] comps = GenericUtils.split(addr, '.'); if (GenericUtils.length(comps) != 4) { return false; @@ -500,4 +545,102 @@ public class SshdSocketAddress extends SocketAddress { int v = Integer.parseInt(c.toString()); return (v >= 0) && (v <= 255); } + + // Based on org.apache.commons.validator.routines.InetAddressValidator#isValidInet6Address + public static boolean isIPv6Address(String address) { + address = GenericUtils.trimToEmpty(address); + if (GenericUtils.isEmpty(address)) { + return false; + } + + if (WELL_KNOWN_IPV6_ADDRESSES.contains(address)) { + return true; + } + + boolean containsCompressedZeroes = address.contains("::"); + if (containsCompressedZeroes && (address.indexOf("::") != address.lastIndexOf("::"))) { + return false; + } + + if (((address.indexOf(':') == 0) && (!address.startsWith("::"))) + || (address.endsWith(":") && (!address.endsWith("::")))) { + return false; + } + + String[] splitOctets = GenericUtils.split(address, ':'); + List<String> octetList = new ArrayList<>(Arrays.asList(splitOctets)); + if (containsCompressedZeroes) { + if (address.endsWith("::")) { + // String.split() drops ending empty segments + octetList.add(""); + } else if (address.startsWith("::") && (!octetList.isEmpty())) { + octetList.remove(0); + } + } + + int numOctests = octetList.size(); + if (numOctests > IPV6_MAX_HEX_GROUPS) { + return false; + } + + int validOctets = 0; + int emptyOctets = 0; // consecutive empty chunks + for (int index = 0; index < numOctests; index++) { + String octet = octetList.get(index); + int pos = octet.indexOf('%'); // is it a zone index + if (pos >= 0) { + // zone index must come last + if (index != (numOctests - 1)) { + return false; + } + + octet = (pos > 0) ? octet.substring(0, pos) : ""; + } + + int octetLength = octet.length(); + if (octetLength == 0) { + emptyOctets++; + if (emptyOctets > 1) { + return false; + } + + validOctets++; + continue; + } + + emptyOctets = 0; + + // Is last chunk an IPv4 address? + if ((index == (numOctests - 1)) && (octet.indexOf('.') > 0)) { + if (!isIPv4Address(octet)) { + return false; + } + validOctets += 2; + continue; + } + + if (octetLength > IPV6_MAX_HEX_DIGITS_PER_GROUP) { + return false; + } + + int octetInt = 0; + try { + octetInt = Integer.parseInt(octet, 16); + } catch (NumberFormatException e) { + return false; + } + + if ((octetInt < 0) || (octetInt > 0x000ffff)) { + return false; + } + + validOctets++; + } + + if ((validOctets > IPV6_MAX_HEX_GROUPS) + || ((validOctets < IPV6_MAX_HEX_GROUPS) && (!containsCompressedZeroes))) { + return false; + } + return true; + } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java index 512d21e..bbc527d 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java @@ -68,7 +68,7 @@ public interface X11ForwardSupport extends Closeable, IoHandler { * is used */ String X11_BIND_HOST = "x11-fwd-bind-host"; - String DEFAULT_X11_BIND_HOST = SshdSocketAddress.LOCALHOST_IP; + String DEFAULT_X11_BIND_HOST = SshdSocketAddress.LOCALHOST_IPV4; /** * Key for the user DISPLAY variable http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java index 1a42c6e..aac5904 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java @@ -36,8 +36,10 @@ import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.util.test.BaseTestSupport; import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory; +import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.junit.runners.Parameterized.UseParametersRunnerFactory; @@ -49,6 +51,7 @@ import org.junit.runners.Parameterized.UseParametersRunnerFactory; */ @RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests @UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class PKCS8PEMResourceKeyPairParserTest extends BaseTestSupport { private final String algorithm; private final int keySize; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-core/src/test/java/org/apache/sshd/common/util/net/SshdSocketIpv6AddressTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/util/net/SshdSocketIpv6AddressTest.java b/sshd-core/src/test/java/org/apache/sshd/common/util/net/SshdSocketIpv6AddressTest.java new file mode 100644 index 0000000..b975589 --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/common/util/net/SshdSocketIpv6AddressTest.java @@ -0,0 +1,94 @@ +/* + * 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.sshd.common.util.net; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.sshd.util.test.BaseTestSupport; +import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.Parameterized.UseParametersRunnerFactory; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests +@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class SshdSocketIpv6AddressTest extends BaseTestSupport { + public static final List<String> VALID_ADDRESSES = + Collections.unmodifiableList( + Arrays.asList( + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8:85a3:0:0:8a2e:370:7334", + "2001:db8:85a3::8a2e:370:7334", + "2001:0db8::0001", "2001:db8::1", + "2001:db8:0:0:0:0:2:1", "2001:db8::2:1", + "2001:db8:0000:1:1:1:1:1", "2001:db8:0:1:1:1:1:1", + "2001:db8:85a3:8d3:1319:8a2e:370:7348", + "fe80::1ff:fe23:4567:890a", "fe80::1ff:fe23:4567:890a%eth2", + "fe80::1ff:fe23:4567:890a%3", "fe80:3::1ff:fe23:4567:890a", + "::ffff:c000:0280", "::ffff:192.0.2.128")); + + private final String address; + private final boolean matches; + + public SshdSocketIpv6AddressTest(String address, boolean matches) { + this.address = address; + this.matches = matches; + } + + @Parameters(name = "{0}") + public static List<Object[]> parameters() { + return new ArrayList<Object[]>() { + // Not serializing it + private static final long serialVersionUID = 1L; + + { + for (String address : SshdSocketAddress.WELL_KNOWN_IPV6_ADDRESSES) { + add(new Object[] {address, Boolean.TRUE}); + } + + for (String address : VALID_ADDRESSES) { + add(new Object[] {address, Boolean.TRUE}); + } + } + }; + } + + @Test + public void testIPv6AddressValidity() { + assertEquals(address, matches, SshdSocketAddress.isIPv6Address(address)); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[address=" + address + + " , matches=" + matches + + "]"; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java index 9e71f69..c8227ed 100644 --- a/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java +++ b/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java @@ -70,7 +70,7 @@ import org.junit.runner.RunWith; public abstract class BaseTestSupport extends Assert { public static final String TEMP_SUBFOLDER_NAME = "temp"; // can be used to override the 'localhost' with an address other than 127.0.0.1 in case it is required - public static final String TEST_LOCALHOST = System.getProperty("org.apache.sshd.test.localhost", SshdSocketAddress.LOCALHOST_IP); + public static final String TEST_LOCALHOST = System.getProperty("org.apache.sshd.test.localhost", SshdSocketAddress.LOCALHOST_IPV4); public static final boolean OUTPUT_DEBUG_MESSAGES = Boolean.parseBoolean(System.getProperty("org.apache.sshd.test.outputDebugMessages", "false")); public static final String MAIN_SUBFOLDER = "main"; public static final String TEST_SUBFOLDER = "test"; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/595c3595/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java ---------------------------------------------------------------------- diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java index 4b4fda8..4d84722 100644 --- a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java +++ b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java @@ -69,7 +69,7 @@ public class GitPgmCommandTest extends BaseTestSupport { try (SshClient client = setupTestClient()) { client.start(); - try (ClientSession session = client.connect(getCurrentTestName(), SshdSocketAddress.LOCALHOST_IP, port).verify(7L, TimeUnit.SECONDS).getSession()) { + try (ClientSession session = client.connect(getCurrentTestName(), SshdSocketAddress.LOCALHOST_IPV4, port).verify(7L, TimeUnit.SECONDS).getSession()) { session.addPasswordIdentity(getCurrentTestName()); session.auth().verify(5L, TimeUnit.SECONDS);
