http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java new file mode 100644 index 0000000..ae39dd3 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java @@ -0,0 +1,580 @@ +/* + * 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.cipher; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.security.interfaces.ECKey; +import java.security.spec.ECField; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.EllipticCurve; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.OptionalFeature; +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.digest.BuiltinDigests; +import org.apache.sshd.common.digest.Digest; +import org.apache.sshd.common.digest.DigestFactory; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * Utilities for working with elliptic curves. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public enum ECCurves implements NamedResource, OptionalFeature { + nistp256(Constants.NISTP256, new int[]{1, 2, 840, 10045, 3, 1, 7}, + new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)), + new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16), + new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)), + new ECPoint( + new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16), + new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)), + new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16), + 1), + 32, + BuiltinDigests.sha256), + nistp384(Constants.NISTP384, new int[]{1, 3, 132, 0, 34}, + new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16)), + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16), + new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16)), + new ECPoint( + new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16), + new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16)), + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16), + 1), + 48, + BuiltinDigests.sha384), + nistp521(Constants.NISTP521, new int[]{1, 3, 132, 0, 35}, + new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)), + new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16), + new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951" + + "EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16)), + new ECPoint( + new BigInteger("00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77" + + "EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", 16), + new BigInteger("011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE7299" + + "5EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", 16)), + new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B" + + "7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16), + 1), + 66, + BuiltinDigests.sha512); + + /** + * A {@link Set} of all the known curves + */ + public static final Set<ECCurves> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(ECCurves.class)); + + /** + * A {@link Set} of all the known curves names + */ + public static final NavigableSet<String> NAMES = + Collections.unmodifiableNavigableSet(GenericUtils.mapSort( + VALUES, + ECCurves::getName, + String.CASE_INSENSITIVE_ORDER)); + + /** + * A {@link Set} of all the known curves key types + */ + public static final NavigableSet<String> KEY_TYPES = + Collections.unmodifiableNavigableSet(GenericUtils.mapSort( + VALUES, + ECCurves::getKeyType, + String.CASE_INSENSITIVE_ORDER)); + + public static final Comparator<ECCurves> BY_KEY_SIZE = (o1, o2) -> { + int k1 = (o1 == null) ? Integer.MAX_VALUE : o1.getKeySize(); + int k2 = (o2 == null) ? Integer.MAX_VALUE : o2.getKeySize(); + return Integer.compare(k1, k2); + }; + + public static final List<ECCurves> SORTED_KEY_SIZE = + Collections.unmodifiableList(VALUES.stream() + .sorted(BY_KEY_SIZE) + .collect(Collectors.toList())); + + private final String name; + private final String keyType; + private final String oidString; + private final List<Integer> oidValue; + private final ECParameterSpec params; + private final int keySize; + private final int numOctets; + private final DigestFactory digestFactory; + + ECCurves(String name, int[] oid, ECParameterSpec params, int numOctets, DigestFactory digestFactory) { + this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No curve name"); + this.oidString = NumberUtils.join('.', ValidateUtils.checkNotNullAndNotEmpty(oid, "No OID")); + this.oidValue = Collections.unmodifiableList(NumberUtils.asList(oid)); + this.keyType = Constants.ECDSA_SHA2_PREFIX + name; + this.params = ValidateUtils.checkNotNull(params, "No EC params for %s", name); + this.keySize = getCurveSize(params); + this.numOctets = numOctets; + this.digestFactory = Objects.requireNonNull(digestFactory, "No digestFactory"); + } + + @Override // The curve name + public final String getName() { + return name; + } + + public final String getOID() { + return oidString; + } + + public final List<Integer> getOIDValue() { + return oidValue; + } + + /** + * @return The standard key type used to represent this curve + */ + public final String getKeyType() { + return keyType; + } + + @Override + public final boolean isSupported() { + return SecurityUtils.isECCSupported() && digestFactory.isSupported(); + } + + public final ECParameterSpec getParameters() { + return params; + } + + /** + * @return The size (in bits) of the key + */ + public final int getKeySize() { + return keySize; + } + + /** + * @return The number of octets used to represent the point(s) for the curve + */ + public final int getNumPointOctets() { + return numOctets; + } + + /** + * @return The {@link Digest} to use when hashing the curve's parameters + */ + public final Digest getDigestForParams() { + return digestFactory.create(); + } + + /** + * @param type The key type value - ignored if {@code null}/empty + * @return The matching {@link ECCurves} constant - {@code null} if + * no match found case <U>insensitive</U> + */ + public static ECCurves fromKeyType(String type) { + if (GenericUtils.isEmpty(type)) { + return null; + } + + for (ECCurves c : VALUES) { + if (type.equalsIgnoreCase(c.getKeyType())) { + return c; + } + } + + return null; + } + + /** + * @param name The curve name (case <U>insensitive</U> - ignored if + * {@code null}/empty + * @return The matching {@link ECCurves} instance - {@code null} if no + * match found + */ + public static ECCurves fromCurveName(String name) { + return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES); + } + + /** + * @param key The {@link ECKey} - ignored if {@code null} + * @return The matching {@link ECCurves} instance - {@code null} if no + * match found + */ + public static ECCurves fromECKey(ECKey key) { + return fromCurveParameters((key == null) ? null : key.getParams()); + } + + /** + * @param params The curve's {@link ECParameterSpec} - ignored if {@code null} + * @return The matching {@link ECCurves} value - {@code null} if no match found + * @see #getCurveSize(ECParameterSpec) + * @see #fromCurveSize(int) + */ + public static ECCurves fromCurveParameters(ECParameterSpec params) { + if (params == null) { + return null; + } else { + return fromCurveSize(getCurveSize(params)); + } + } + + /** + * @param keySize The key size (in bits) + * @return The matching {@link ECCurves} value - {@code null} if no + * match found + */ + public static ECCurves fromCurveSize(int keySize) { + if (keySize <= 0) { + return null; + } + + for (ECCurves c : VALUES) { + if (keySize == c.getKeySize()) { + return c; + } + } + + return null; + } + + public static ECCurves fromOIDValue(List<? extends Number> oid) { + if (GenericUtils.isEmpty(oid)) { + return null; + } + + for (ECCurves c : VALUES) { + List<? extends Number> v = c.getOIDValue(); + if (oid.size() != v.size()) { + continue; + } + + boolean matches = true; + for (int index = 0; index < v.size(); index++) { + Number exp = v.get(index); + Number act = oid.get(index); + if (exp.intValue() != act.intValue()) { + matches = false; + break; + } + } + + if (matches) { + return c; + } + } + + return null; + } + + public static ECCurves fromOID(String oid) { + if (GenericUtils.isEmpty(oid)) { + return null; + } + + for (ECCurves c : VALUES) { + if (oid.equalsIgnoreCase(c.getOID())) { + return c; + } + } + + return null; + } + + /** + * @param params The curve's {@link ECParameterSpec} + * @return The curve's key size in bits + * @throws IllegalArgumentException if invalid parameters provided + */ + public static int getCurveSize(ECParameterSpec params) { + EllipticCurve curve = Objects.requireNonNull(params, "No EC params").getCurve(); + ECField field = Objects.requireNonNull(curve, "No EC curve").getField(); + return Objects.requireNonNull(field, "No EC field").getFieldSize(); + } + + public static byte[] encodeECPoint(ECPoint group, ECParameterSpec params) { + return encodeECPoint(group, params.getCurve()); + } + + public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve) { + // M has len 2 ceil(log_2(q)/8) + 1 ? + int elementSize = (curve.getField().getFieldSize() + 7) / 8; + byte[] m = new byte[2 * elementSize + 1]; + + // Uncompressed format + m[0] = 0x04; + + byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray()); + System.arraycopy(affineX, 0, m, 1 + elementSize - affineX.length, affineX.length); + + byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray()); + System.arraycopy(affineY, 0, m, 1 + elementSize + elementSize - affineY.length, affineY.length); + + return m; + } + + private static byte[] removeLeadingZeroes(byte[] input) { + if (input[0] != 0x00) { + return input; + } + + int pos = 1; + while (pos < input.length - 1 && input[pos] == 0x00) { + pos++; + } + + byte[] output = new byte[input.length - pos]; + System.arraycopy(input, pos, output, 0, output.length); + return output; + } + + /** + * Converts the given octet string (defined by ASN.1 specifications) to a {@link BigInteger} + * As octet strings always represent positive integers, a zero-byte is prepended to + * the given array if necessary (if is MSB equal to 1), then this is converted to BigInteger + * The conversion is defined in the Section 2.3.8 + * + * @param octets - octet string bytes to be converted + * @return The {@link BigInteger} representation of the octet string + */ + public static BigInteger octetStringToInteger(byte... octets) { + if (octets == null) { + return null; + } else if (octets.length == 0) { + return BigInteger.ZERO; + } else { + return new BigInteger(1, octets); + } + } + + public static ECPoint octetStringToEcPoint(byte... octets) { + if (NumberUtils.isEmpty(octets)) { + return null; + } + + int startIndex = findFirstNonZeroIndex(octets); + if (startIndex < 0) { + throw new IllegalArgumentException("All zeroes ECPoint N/A"); + } + + byte indicator = octets[startIndex]; + ECCurves.ECPointCompression compression = ECCurves.ECPointCompression.fromIndicatorValue(indicator); + if (compression == null) { + throw new UnsupportedOperationException("Unknown compression indicator value: 0x" + Integer.toHexString(indicator & 0xFF)); + } + + // The coordinates actually start after the compression indicator + return compression.octetStringToEcPoint(octets, startIndex + 1, octets.length - startIndex - 1); + } + + private static int findFirstNonZeroIndex(byte... octets) { + if (NumberUtils.isEmpty(octets)) { + return -1; + } + + for (int index = 0; index < octets.length; index++) { + if (octets[index] != 0) { + return index; + } + } + + return -1; // all zeroes + } + + public static final class Constants { + /** + * Standard prefix of NISTP key types when encoded + */ + public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-"; + + public static final String NISTP256 = "nistp256"; + public static final String NISTP384 = "nistp384"; + public static final String NISTP521 = "nistp521"; + + private Constants() { + throw new UnsupportedOperationException("No instance allowed"); + } + } + + /** + * The various {@link ECPoint} representation compression indicators + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see <A HREF="https://www.ietf.org/rfc/rfc5480.txt">RFC-5480 - section 2.2</A> + */ + public enum ECPointCompression { + // see http://tools.ietf.org/html/draft-jivsov-ecc-compact-00 + // see http://crypto.stackexchange.com/questions/8914/ecdsa-compressed-public-key-point-back-to-uncompressed-public-key-point + VARIANT2((byte) 0x02) { + @Override + public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) { + byte[] xp = new byte[len]; + System.arraycopy(octets, startIndex, xp, 0, len); + BigInteger x = octetStringToInteger(xp); + + // TODO derive even Y... + throw new UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + ") compression support N/A"); + } + }, + VARIANT3((byte) 0x03) { + @Override + public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) { + byte[] xp = new byte[len]; + System.arraycopy(octets, startIndex, xp, 0, len); + BigInteger x = octetStringToInteger(xp); + + // TODO derive odd Y... + throw new UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + ") compression support N/A"); + } + }, + UNCOMPRESSED((byte) 0x04) { + @Override + public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) { + int numElements = len / 2; /* x, y */ + if (len != (numElements * 2)) { // make sure length is not odd + throw new IllegalArgumentException("octetStringToEcPoint(" + name() + ") " + + " invalid remainder octets representation: " + + " expected=" + (2 * numElements) + ", actual=" + len); + } + + byte[] xp = new byte[numElements]; + byte[] yp = new byte[numElements]; + System.arraycopy(octets, startIndex, xp, 0, numElements); + System.arraycopy(octets, startIndex + numElements, yp, 0, numElements); + + BigInteger x = octetStringToInteger(xp); + BigInteger y = octetStringToInteger(yp); + return new ECPoint(x, y); + } + + @Override + public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException { + ECCurves curve = fromCurveName(curveName); + if (curve == null) { + throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + curveName + "] cannot determine octets count"); + } + + int numElements = curve.getNumPointOctets(); + KeyEntryResolver.encodeInt(s, 1 /* the indicator */ + 2 * numElements); + s.write(getIndicatorValue()); + writeCoordinate(s, "X", p.getAffineX(), numElements); + writeCoordinate(s, "Y", p.getAffineY(), numElements); + } + }; + + public static final Set<ECPointCompression> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class)); + + private final byte indicatorValue; + + ECPointCompression(byte indicator) { + indicatorValue = indicator; + } + + public final byte getIndicatorValue() { + return indicatorValue; + } + + public abstract ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len); + + public byte[] ecPointToOctetString(String curveName, ECPoint p) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream((2 * 66) + Long.SIZE)) { + writeECPoint(baos, curveName, p); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("ecPointToOctetString(" + curveName + ")" + + " failed (" + e.getClass().getSimpleName() + ")" + + " to write data: " + e.getMessage(), + e); + } + } + + public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException { + if (s == null) { + throw new EOFException("No output stream"); + } + + throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + p + "] N/A"); + } + + protected void writeCoordinate(OutputStream s, String n, BigInteger v, int numElements) throws IOException { + byte[] vp = v.toByteArray(); + int startIndex = 0; + int vLen = vp.length; + if (vLen > numElements) { + if (vp[0] == 0) { // skip artificial positive sign + startIndex++; + vLen--; + } + } + + if (vLen > numElements) { + throw new StreamCorruptedException("writeCoordinate(" + name() + ")[" + n + "]" + + " value length (" + vLen + ") exceeds max. (" + numElements + ")" + + " for " + v); + } + + if (vLen < numElements) { + byte[] tmp = new byte[numElements]; + System.arraycopy(vp, startIndex, tmp, numElements - vLen, vLen); + vp = tmp; + } + + s.write(vp, startIndex, vLen); + } + + public static ECPointCompression fromIndicatorValue(int value) { + if ((value < 0) || (value > 0xFF)) { + return null; // must be a byte value + } + + for (ECPointCompression c : VALUES) { + if (value == c.getIndicatorValue()) { + return c; + } + } + + return null; + } + } +}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/package.html ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/package.html b/sshd-common/src/main/java/org/apache/sshd/common/cipher/package.html new file mode 100644 index 0000000..197a89d --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/package.html @@ -0,0 +1,26 @@ +<!-- + 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. +--> +<html> +<head> +</head> +<body> + +<a href="{@docRoot}/org/apache/sshd/common/cipher/Cipher.html"><code>Cipher</code></a> +implementations. + +</body> +</html> http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/BaseCompression.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/BaseCompression.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/BaseCompression.java new file mode 100644 index 0000000..ff31947 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/BaseCompression.java @@ -0,0 +1,48 @@ +/* + * 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.compression; + +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class BaseCompression implements Compression { + private final String name; + + protected BaseCompression(String name) { + this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No compression name"); + } + + @Override + public final String getName() { + return name; + } + + @Override + public boolean isCompressionExecuted() { + return true; + } + + @Override + public String toString() { + return getName(); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/BuiltinCompressions.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/BuiltinCompressions.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/BuiltinCompressions.java new file mode 100644 index 0000000..49ad0ab --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/BuiltinCompressions.java @@ -0,0 +1,239 @@ +/* + * 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.compression; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.NamedFactoriesListParseResult; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public enum BuiltinCompressions implements CompressionFactory { + none(Constants.NONE) { + @Override + public Compression create() { + return new CompressionNone(); + } + + @Override + public boolean isCompressionExecuted() { + return false; + } + }, + zlib(Constants.ZLIB) { + @Override + public Compression create() { + return new CompressionZlib(); + } + }, + delayedZlib(Constants.DELAYED_ZLIB) { + @Override + public Compression create() { + return new CompressionDelayedZlib(); + } + + @Override + public boolean isDelayed() { + return true; + } + }; + + public static final Set<BuiltinCompressions> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(BuiltinCompressions.class)); + + private static final Map<String, CompressionFactory> EXTENSIONS = + new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + private final String name; + + BuiltinCompressions(String n) { + name = n; + } + + @Override + public final String getName() { + return name; + } + + @Override + public boolean isDelayed() { + return false; + } + + @Override + public boolean isCompressionExecuted() { + return true; + } + + @Override + public final String toString() { + return getName(); + } + + @Override + public final boolean isSupported() { + return true; + } + + /** + * Registered a {@link org.apache.sshd.common.NamedFactory} to be available besides the built-in + * ones when parsing configuration + * + * @param extension The factory to register + * @throws IllegalArgumentException if factory instance is {@code null}, + * or overrides a built-in one or overrides another registered factory + * with the same name (case <U>insensitive</U>). + */ + public static void registerExtension(CompressionFactory extension) { + String name = Objects.requireNonNull(extension, "No extension provided").getName(); + ValidateUtils.checkTrue(fromFactoryName(name) == null, "Extension overrides built-in: %s", name); + + synchronized (EXTENSIONS) { + ValidateUtils.checkTrue(!EXTENSIONS.containsKey(name), "Extension overrides existing: %s", name); + EXTENSIONS.put(name, extension); + } + } + + /** + * @return A {@link SortedSet} of the currently registered extensions, sorted + * according to the factory name (case <U>insensitive</U>) + */ + public static NavigableSet<CompressionFactory> getRegisteredExtensions() { + synchronized (EXTENSIONS) { + return GenericUtils.asSortedSet(NamedResource.BY_NAME_COMPARATOR, EXTENSIONS.values()); + } + } + + /** + * Unregisters specified extension + * + * @param name The factory name - ignored if {@code null}/empty + * @return The registered extension - {@code null} if not found + */ + public static CompressionFactory unregisterExtension(String name) { + if (GenericUtils.isEmpty(name)) { + return null; + } + + synchronized (EXTENSIONS) { + return EXTENSIONS.remove(name); + } + } + + public static BuiltinCompressions fromFactoryName(String name) { + return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES); + } + + /** + * @param compressions A comma-separated list of Compressions' names - ignored + * if {@code null}/empty + * @return A {@link ParseResult} containing the successfully parsed + * factories and the unknown ones. <B>Note:</B> it is up to caller to + * ensure that the lists do not contain duplicates + */ + public static ParseResult parseCompressionsList(String compressions) { + return parseCompressionsList(GenericUtils.split(compressions, ',')); + } + + public static ParseResult parseCompressionsList(String... compressions) { + return parseCompressionsList(GenericUtils.isEmpty((Object[]) compressions) ? Collections.emptyList() : Arrays.asList(compressions)); + } + + public static ParseResult parseCompressionsList(Collection<String> compressions) { + if (GenericUtils.isEmpty(compressions)) { + return ParseResult.EMPTY; + } + + List<CompressionFactory> factories = new ArrayList<>(compressions.size()); + List<String> unknown = Collections.emptyList(); + for (String name : compressions) { + CompressionFactory c = resolveFactory(name); + if (c != null) { + factories.add(c); + } else { + // replace the (unmodifiable) empty list with a real one + if (unknown.isEmpty()) { + unknown = new ArrayList<>(); + } + unknown.add(name); + } + } + + return new ParseResult(factories, unknown); + } + + /** + * @param name The factory name + * @return The factory or {@code null} if it is neither a built-in one + * or a registered extension + */ + public static CompressionFactory resolveFactory(String name) { + if (GenericUtils.isEmpty(name)) { + return null; + } + + CompressionFactory c = fromFactoryName(name); + if (c != null) { + return c; + } + + synchronized (EXTENSIONS) { + return EXTENSIONS.get(name); + } + } + + /** + * Holds the result of {@link BuiltinCompressions#parseCompressionsList(String)} + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ + public static class ParseResult extends NamedFactoriesListParseResult<Compression, CompressionFactory> { + public static final ParseResult EMPTY = new ParseResult(Collections.emptyList(), Collections.emptyList()); + + public ParseResult(List<CompressionFactory> parsed, List<String> unsupported) { + super(parsed, unsupported); + } + } + + public static final class Constants { + public static final String NONE = "none"; + public static final String ZLIB = "zlib"; + public static final String DELAYED_ZLIB = "[email protected]"; + + private Constants() { + throw new UnsupportedOperationException("No instance allowed"); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/Compression.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/Compression.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/Compression.java new file mode 100644 index 0000000..91ac27e --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/Compression.java @@ -0,0 +1,71 @@ +/* + * 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.compression; + +import java.io.IOException; + +import org.apache.sshd.common.util.buffer.Buffer; + +/** + * Interface used to compress the stream of data between the + * SSH server and clients. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface Compression extends CompressionInformation { + + /** + * Enum identifying if this object will be used to compress + * or uncompress data. + */ + enum Type { + Inflater, + Deflater + } + + /** + * Initialize this object to either compress or uncompress data. + * This method must be called prior to any calls to either + * <code>compress</code> or <code>uncompress</code>. + * Once the object has been initialized, only one of + * <code>compress</code> or <code>uncompress</code> methods can be + * called. + * + * @param type compression type + * @param level compression level + */ + void init(Type type, int level); + + /** + * Compress the given buffer in place. + * + * @param buffer the buffer containing the data to compress + * @throws IOException if an error occurs + */ + void compress(Buffer buffer) throws IOException; + + /** + * Uncompress the data in a buffer into another buffer. + * + * @param from the buffer containing the data to uncompress + * @param to the buffer receiving the uncompressed data + * @throws IOException if an error occurs + */ + void uncompress(Buffer from, Buffer to) throws IOException; +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionDelayedZlib.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionDelayedZlib.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionDelayedZlib.java new file mode 100644 index 0000000..c062e38 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionDelayedZlib.java @@ -0,0 +1,40 @@ +/* + * 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.compression; + + +/** + * ZLib delayed compression. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see Compression#isDelayed() + */ +public class CompressionDelayedZlib extends CompressionZlib { + /** + * Create a new instance of a delayed ZLib compression + */ + public CompressionDelayedZlib() { + super(BuiltinCompressions.Constants.DELAYED_ZLIB); + } + + @Override + public boolean isDelayed() { + return true; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionFactory.java new file mode 100644 index 0000000..355594a --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionFactory.java @@ -0,0 +1,31 @@ +/* + * 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.compression; + +import org.apache.sshd.common.BuiltinFactory; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +// CHECKSTYLE:OFF +public interface CompressionFactory extends BuiltinFactory<Compression>, CompressionInformation { + // nothing extra +} +//CHECKSTYLE:ON http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionInformation.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionInformation.java new file mode 100644 index 0000000..428689f --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionInformation.java @@ -0,0 +1,42 @@ +/* + * 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.compression; + +import org.apache.sshd.common.NamedResource; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface CompressionInformation extends NamedResource { + /** + * Delayed compression is an Open-SSH specific feature which + * informs both the client and server to not compress data before + * the session has been authenticated. + * + * @return if the compression is delayed after authentication or not + */ + boolean isDelayed(); + + /** + * @return {@code true} if there is any compression executed by + * this "compressor" - special case for 'none' + */ + boolean isCompressionExecuted(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionNone.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionNone.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionNone.java new file mode 100644 index 0000000..815e8ce --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionNone.java @@ -0,0 +1,76 @@ +/* + * 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.compression; + +import java.io.IOException; +import java.io.StreamCorruptedException; + +import org.apache.sshd.common.util.buffer.Buffer; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class CompressionNone extends BaseCompression { + private Type type; + private int level; + + public CompressionNone() { + super(BuiltinCompressions.Constants.NONE); + } + + @Override + public void init(Type type, int level) { + this.type = type; + this.level = level; + } + + @Override + public boolean isCompressionExecuted() { + return false; + } + + @Override + public void compress(Buffer buffer) throws IOException { + if (!Type.Deflater.equals(type)) { + throw new StreamCorruptedException("Not set up for compression: " + type); + } + } + + @Override + public void uncompress(Buffer from, Buffer to) throws IOException { + if (!Type.Inflater.equals(type)) { + throw new StreamCorruptedException("Not set up for de-compression: " + type); + } + + if (from != to) { + throw new StreamCorruptedException("Separate de-compression buffers provided"); + } + } + + @Override + public boolean isDelayed() { + return false; + } + + @Override + public String toString() { + return super.toString() + "[" + type + "/" + level + "]"; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java new file mode 100644 index 0000000..e365b91 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java @@ -0,0 +1,85 @@ +/* + * 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.compression; + +import java.io.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import org.apache.sshd.common.util.buffer.Buffer; + +/** + * ZLib based Compression. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class CompressionZlib extends BaseCompression { + + private static final int BUF_SIZE = 4096; + + private byte[] tmpbuf = new byte[BUF_SIZE]; + private Deflater compresser; + private Inflater decompresser; + + /** + * Create a new instance of a ZLib base compression + */ + public CompressionZlib() { + this(BuiltinCompressions.Constants.ZLIB); + } + + protected CompressionZlib(String name) { + super(name); + } + + @Override + public boolean isDelayed() { + return false; + } + + @Override + public void init(Type type, int level) { + compresser = new Deflater(level); + decompresser = new Inflater(); + } + + @Override + public void compress(Buffer buffer) throws IOException { + compresser.setInput(buffer.array(), buffer.rpos(), buffer.available()); + buffer.wpos(buffer.rpos()); + for (int len = compresser.deflate(tmpbuf, 0, tmpbuf.length, Deflater.SYNC_FLUSH); + len > 0; + len = compresser.deflate(tmpbuf, 0, tmpbuf.length, Deflater.SYNC_FLUSH)) { + buffer.putRawBytes(tmpbuf, 0, len); + } + } + + @Override + public void uncompress(Buffer from, Buffer to) throws IOException { + decompresser.setInput(from.array(), from.rpos(), from.available()); + try { + for (int len = decompresser.inflate(tmpbuf); len > 0; len = decompresser.inflate(tmpbuf)) { + to.putRawBytes(tmpbuf, 0, len); + } + } catch (DataFormatException e) { + throw new IOException("Error decompressing data", e); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/package.html ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/package.html b/sshd-common/src/main/java/org/apache/sshd/common/compression/package.html new file mode 100644 index 0000000..9bd4652 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/package.html @@ -0,0 +1,25 @@ +<!-- + 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. +--> +<html> +<head> +</head> +<body> + +<a href="{@docRoot}/org/apache/sshd/common/compression/Compression.html"><code>Compression</code></a> implementations. + +</body> +</html> http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/CompressionConfigValue.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/CompressionConfigValue.java b/sshd-common/src/main/java/org/apache/sshd/common/config/CompressionConfigValue.java new file mode 100644 index 0000000..e153adc --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/CompressionConfigValue.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.config; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + +import org.apache.sshd.common.compression.BuiltinCompressions; +import org.apache.sshd.common.compression.Compression; +import org.apache.sshd.common.compression.CompressionFactory; +import org.apache.sshd.common.util.GenericUtils; + +/** + * Provides a "bridge" between the configuration values and the + * actual {@link org.apache.sshd.common.NamedFactory} for the {@link Compression}. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public enum CompressionConfigValue implements CompressionFactory { + YES(BuiltinCompressions.zlib), + NO(BuiltinCompressions.none), + DELAYED(BuiltinCompressions.delayedZlib); + + public static final Set<CompressionConfigValue> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(CompressionConfigValue.class)); + + private final CompressionFactory factory; + + CompressionConfigValue(CompressionFactory delegate) { + factory = delegate; + } + + @Override + public final String getName() { + return factory.getName(); + } + + @Override + public final Compression create() { + return factory.create(); + } + + @Override + public boolean isSupported() { + return factory.isSupported(); + } + + @Override + public final String toString() { + return getName(); + } + + @Override + public boolean isDelayed() { + return factory.isDelayed(); + } + + @Override + public boolean isCompressionExecuted() { + return factory.isCompressionExecuted(); + } + + public static CompressionConfigValue fromName(String n) { + if (GenericUtils.isEmpty(n)) { + return null; + } + + for (CompressionConfigValue v : VALUES) { + if (n.equalsIgnoreCase(v.name())) { + return v; + } + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java new file mode 100644 index 0000000..10b131c --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java @@ -0,0 +1,230 @@ +/* + * 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.config; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StreamCorruptedException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.common.util.io.NoCloseInputStream; +import org.apache.sshd.common.util.io.NoCloseReader; +import org.apache.sshd.common.util.net.SshdSocketAddress; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see <a href="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5">ssh_config(5)</a> + */ +public final class ConfigFileReaderSupport { + + public static final char COMMENT_CHAR = '#'; + + public static final String COMPRESSION_PROP = "Compression"; + public static final String DEFAULT_COMPRESSION = CompressionConfigValue.NO.getName(); + public static final String MAX_SESSIONS_CONFIG_PROP = "MaxSessions"; + public static final int DEFAULT_MAX_SESSIONS = 10; + public static final String PASSWORD_AUTH_CONFIG_PROP = "PasswordAuthentication"; + 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.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"; + public static final boolean DEFAULT_KEEP_ALIVE = true; + public static final String USE_DNS_CONFIG_PROP = "UseDNS"; + // NOTE: the usual default is TRUE + public static final boolean DEFAULT_USE_DNS = true; + public static final String PUBKEY_AUTH_CONFIG_PROP = "PubkeyAuthentication"; + public static final String DEFAULT_PUBKEY_AUTH = "yes"; + public static final boolean DEFAULT_PUBKEY_AUTH_VALUE = parseBooleanValue(DEFAULT_PUBKEY_AUTH); + public static final String AUTH_KEYS_FILE_CONFIG_PROP = "AuthorizedKeysFile"; + public static final String MAX_AUTH_TRIES_CONFIG_PROP = "MaxAuthTries"; + public static final int DEFAULT_MAX_AUTH_TRIES = 6; + public static final String MAX_STARTUPS_CONFIG_PROP = "MaxStartups"; + public static final int DEFAULT_MAX_STARTUPS = 10; + public static final String LOGIN_GRACE_TIME_CONFIG_PROP = "LoginGraceTime"; + public static final long DEFAULT_LOGIN_GRACE_TIME = TimeUnit.SECONDS.toMillis(120); + public static final String KEY_REGENERATE_INTERVAL_CONFIG_PROP = "KeyRegenerationInterval"; + public static final long DEFAULT_REKEY_TIME_LIMIT = TimeUnit.HOURS.toMillis(1L); + // see http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html + public static final String CIPHERS_CONFIG_PROP = "Ciphers"; + public static final String DEFAULT_CIPHERS = + "aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour"; + // see http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html + public static final String MACS_CONFIG_PROP = "MACs"; + public static final String DEFAULT_MACS = + "hmac-md5,hmac-sha1,[email protected],hmac-ripemd160,hmac-sha1-96,hmac-md5-96,hmac-sha2-256,hmac-sha2-256-96,hmac-sha2-512,hmac-sha2-512-96"; + // see http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html + public static final String KEX_ALGORITHMS_CONFIG_PROP = "KexAlgorithms"; + public static final String DEFAULT_KEX_ALGORITHMS = + "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521" + + "," + "diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1" + // RFC-8268 groups + + "," + "diffie-hellman-group18-sha512,diffie-hellman-group17-sha512" + + "," + "diffie-hellman-group16-sha512,diffie-hellman-group15-sha512" + + "," + "diffie-hellman-group14-sha256" + // Legacy groups + + "," + "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"; + // see http://linux.die.net/man/5/ssh_config + public static final String HOST_KEY_ALGORITHMS_CONFIG_PROP = "HostKeyAlgorithms"; + // see https://tools.ietf.org/html/rfc5656 + public static final String DEFAULT_HOST_KEY_ALGORITHMS = + KeyPairProvider.SSH_RSA + "," + KeyPairProvider.SSH_DSS; + // see http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html + public static final String LOG_LEVEL_CONFIG_PROP = "LogLevel"; + public static final LogLevelValue DEFAULT_LOG_LEVEL = LogLevelValue.INFO; + // see https://www.freebsd.org/cgi/man.cgi?query=sshd_config&sektion=5 + public static final String SYSLOG_FACILITY_CONFIG_PROP = "SyslogFacility"; + public static final SyslogFacilityValue DEFAULT_SYSLOG_FACILITY = SyslogFacilityValue.AUTH; + public static final String SUBSYSTEM_CONFIG_PROP = "Subsystem"; + + private ConfigFileReaderSupport() { + throw new UnsupportedOperationException("No instance"); + } + + public static Properties readConfigFile(File file) throws IOException { + return readConfigFile(file.toPath(), IoUtils.EMPTY_OPEN_OPTIONS); + } + + public static Properties readConfigFile(Path path, OpenOption... options) throws IOException { + try (InputStream input = Files.newInputStream(path, options)) { + return readConfigFile(input, true); + } + } + + public static Properties readConfigFile(URL url) throws IOException { + try (InputStream input = url.openStream()) { + return readConfigFile(input, true); + } + } + + public static Properties readConfigFile(String path) throws IOException { + try (InputStream input = new FileInputStream(path)) { + return readConfigFile(input, true); + } + } + + public static Properties readConfigFile(InputStream input, boolean okToClose) throws IOException { + try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(input, okToClose), StandardCharsets.UTF_8)) { + return readConfigFile(reader, true); + } + } + + public static Properties readConfigFile(Reader reader, boolean okToClose) throws IOException { + try (BufferedReader buf = new BufferedReader(NoCloseReader.resolveReader(reader, okToClose))) { + return readConfigFile(buf); + } + } + + /** + * Reads the configuration file contents into a {@link Properties} instance. + * <B>Note:</B> multiple keys value are concatenated using a comma - it is up to + * the caller to know which keys are expected to have multiple values and handle + * the split accordingly + * + * @param rdr The {@link BufferedReader} for reading the file + * @return The read properties + * @throws IOException If failed to read or malformed content + */ + public static Properties readConfigFile(BufferedReader rdr) throws IOException { + Properties props = new Properties(); + int lineNumber = 1; + for (String line = rdr.readLine(); line != null; line = rdr.readLine(), lineNumber++) { + line = GenericUtils.replaceWhitespaceAndTrim(line); + if (GenericUtils.isEmpty(line)) { + continue; + } + + int pos = line.indexOf(COMMENT_CHAR); + if (pos == 0) { + continue; + } + + if (pos > 0) { + line = line.substring(0, pos); + line = line.trim(); + } + + /* + * Some options use '=', others use ' ' - try both + * NOTE: we do not validate the format for each option separately + */ + pos = line.indexOf(' '); + if (pos < 0) { + pos = line.indexOf('='); + } + + if (pos < 0) { + throw new StreamCorruptedException("No delimiter at line " + lineNumber + ": " + line); + } + + String key = line.substring(0, pos); + String value = line.substring(pos + 1).trim(); + // see if need to concatenate multi-valued keys + String prev = props.getProperty(key); + if (!GenericUtils.isEmpty(prev)) { + value = prev + "," + value; + } + + props.setProperty(key, value); + } + + return props; + } + + /** + * @param v Checks if the value is "yes", "y" + * or "on" or "true". + * @return The result - <B>Note:</B> {@code null}/empty values are + * interpreted as {@code false} + */ + public static boolean parseBooleanValue(String v) { + return "yes".equalsIgnoreCase(v) + || "y".equalsIgnoreCase(v) + || "on".equalsIgnoreCase(v) + || "true".equalsIgnoreCase(v); + } + + /** + * Returns a "yes" or "no" value based on the input + * parameter + * + * @param flag The required state + * @return "yes" if {@code true}, "no" otherwise + */ + public static String yesNoValueOf(boolean flag) { + return flag ? "yes" : "no"; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/FactoriesListParseResult.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/FactoriesListParseResult.java b/sshd-common/src/main/java/org/apache/sshd/common/config/FactoriesListParseResult.java new file mode 100644 index 0000000..7a1cee1 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/FactoriesListParseResult.java @@ -0,0 +1,51 @@ +/* + * 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.config; + +import java.util.List; + +import org.apache.sshd.common.Factory; + +/** + * @param <T> Result type + * @param <F> Factory type + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class FactoriesListParseResult<T, F extends Factory<T>> extends ListParseResult<F> { + protected FactoriesListParseResult(List<F> parsed, List<String> unsupported) { + super(parsed, unsupported); + } + + /** + * @return The {@link List} of successfully parsed {@link Factory} instances + * in the <U>same order</U> as they were encountered during parsing + */ + public final List<F> getParsedFactories() { + return getParsedValues(); + } + + /** + * @return A {@link List} of unknown/unsupported configuration values for + * the factories + */ + public List<String> getUnsupportedFactories() { + return getUnsupportedValues(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/ListParseResult.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/ListParseResult.java b/sshd-common/src/main/java/org/apache/sshd/common/config/ListParseResult.java new file mode 100644 index 0000000..97590cb --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/ListParseResult.java @@ -0,0 +1,66 @@ +/* + * 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.config; + +import java.util.List; + +import org.apache.sshd.common.util.GenericUtils; + +/** + * Used to hold the result of parsing a list of value. Such result contains known + * and unknown values - which are accessible via the respective {@link #getParsedValues()} + * and {@link #getUnsupportedValues()} methods. <B>Note:</B> the returned {@link List}s may + * be un-modifiable, so it is recommended to avoid attempting changing the, returned + * list(s) + * + * @param <E> Type of list item + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class ListParseResult<E> { + private final List<E> parsed; + private final List<String> unsupported; + + protected ListParseResult(List<E> parsed, List<String> unsupported) { + this.parsed = parsed; + this.unsupported = unsupported; + } + + /** + * @return The {@link List} of successfully parsed value instances + * in the <U>same order</U> as they were encountered during parsing + */ + public final List<E> getParsedValues() { + return parsed; + } + + /** + * @return A {@link List} of unknown/unsupported configuration values for + * the factories + */ + public List<String> getUnsupportedValues() { + return unsupported; + } + + @Override + public String toString() { + return "parsed=" + GenericUtils.join(getParsedValues(), ',') + + ";unsupported=" + GenericUtils.join(getUnsupportedValues(), ','); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/LogLevelValue.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/LogLevelValue.java b/sshd-common/src/main/java/org/apache/sshd/common/config/LogLevelValue.java new file mode 100644 index 0000000..2fbdc80 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/LogLevelValue.java @@ -0,0 +1,56 @@ +/* + * 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.config; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + +import org.apache.sshd.common.util.GenericUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see <A HREF="http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html"><I>LogLevel</I> configuration value</A> + */ +public enum LogLevelValue { + /* + * NOTE(s): + * 1. DEBUG and DEBUG1 are EQUIVALENT + * 2. Order is important (!!!) + */ + QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, DEBUG3; + + public static final Set<LogLevelValue> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(LogLevelValue.class)); + + public static LogLevelValue fromName(String n) { + if (GenericUtils.isEmpty(n)) { + return null; + } + + for (LogLevelValue l : VALUES) { + if (n.equalsIgnoreCase(l.name())) { + return l; + } + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/NamedFactoriesListParseResult.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/NamedFactoriesListParseResult.java b/sshd-common/src/main/java/org/apache/sshd/common/config/NamedFactoriesListParseResult.java new file mode 100644 index 0000000..246cae0 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/NamedFactoriesListParseResult.java @@ -0,0 +1,47 @@ +/* + * 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.config; + +import java.util.List; + +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.util.GenericUtils; + +/** + * Holds the result of parsing a list of {@link NamedFactory}ies + * + * @param <T> Result type + * @param <F> Factory type + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class NamedFactoriesListParseResult<T, F extends NamedFactory<T>> + extends FactoriesListParseResult<T, F> { + + protected NamedFactoriesListParseResult(List<F> parsed, List<String> unsupported) { + super(parsed, unsupported); + } + + @Override + public String toString() { + return "parsed=" + NamedResource.getNames(getParsedFactories()) + + ";unknown=" + GenericUtils.join(getUnsupportedFactories(), ','); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/NamedResourceListParseResult.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/NamedResourceListParseResult.java b/sshd-common/src/main/java/org/apache/sshd/common/config/NamedResourceListParseResult.java new file mode 100644 index 0000000..feb45f4 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/NamedResourceListParseResult.java @@ -0,0 +1,57 @@ +/* + * 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.config; + +import java.util.List; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.util.GenericUtils; + +/** + * @param <R> Type of result {@link NamedResource} + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class NamedResourceListParseResult<R extends NamedResource> extends ListParseResult<R> { + protected NamedResourceListParseResult(List<R> parsed, List<String> unsupported) { + super(parsed, unsupported); + } + + /** + * @return The {@link List} of successfully parsed {@link NamedResource} instances + * in the <U>same order</U> as they were encountered during parsing + */ + public final List<R> getParsedResources() { + return getParsedValues(); + } + + /** + * @return A {@link List} of unknown/unsupported configuration values for + * the resources + */ + public List<String> getUnsupportedResources() { + return getUnsupportedValues(); + } + + @Override + public String toString() { + return "parsed=" + NamedResource.getNames(getParsedResources()) + + ";unknown=" + GenericUtils.join(getUnsupportedResources(), ','); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/SyslogFacilityValue.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/SyslogFacilityValue.java b/sshd-common/src/main/java/org/apache/sshd/common/config/SyslogFacilityValue.java new file mode 100644 index 0000000..f52ae36 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/SyslogFacilityValue.java @@ -0,0 +1,51 @@ +/* + * 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.config; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + +import org.apache.sshd.common.util.GenericUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see <A HREF="https://www.freebsd.org/cgi/man.cgi?query=sshd_config&sektion=5"><I>SyslogFacility</I> configuration value</A> + */ +public enum SyslogFacilityValue { + DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7; + + public static final Set<SyslogFacilityValue> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(SyslogFacilityValue.class)); + + public static SyslogFacilityValue fromName(String n) { + if (GenericUtils.isEmpty(n)) { + return null; + } + + for (SyslogFacilityValue f : VALUES) { + if (n.equalsIgnoreCase(f.name())) { + return f; + } + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java b/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java new file mode 100644 index 0000000..222cf84 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java @@ -0,0 +1,180 @@ +/* + * 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.config; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.apache.sshd.common.util.GenericUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + * @see <A HREF="http://unixhelp.ed.ac.uk/CGI/man-cgi?sshd_config+5">Time formats for SSH configuration values</A> + */ +public enum TimeValueConfig { + SECONDS('s', 'S', TimeUnit.SECONDS.toMillis(1L)), + MINUTES('m', 'M', TimeUnit.MINUTES.toMillis(1L)), + HOURS('h', 'H', TimeUnit.HOURS.toMillis(1L)), + DAYS('d', 'D', TimeUnit.DAYS.toMillis(1L)), + WEEKS('w', 'W', TimeUnit.DAYS.toMillis(7L)); + + public static final Set<TimeValueConfig> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(TimeValueConfig.class)); + + private final char loChar; + private final char hiChar; + private final long interval; + + TimeValueConfig(char lo, char hi, long interval) { + loChar = lo; + hiChar = hi; + this.interval = interval; + } + + public final char getLowerCaseValue() { + return loChar; + } + + public final char getUpperCaseValue() { + return hiChar; + } + + public final long getInterval() { + return interval; + } + + public static TimeValueConfig fromValueChar(char ch) { + if ((ch <= ' ') || (ch >= 0x7F)) { + return null; + } + + for (TimeValueConfig v : VALUES) { + if ((v.getLowerCaseValue() == ch) || (v.getUpperCaseValue() == ch)) { + return v; + } + } + + return null; + } + + /** + * @param s A time specification + * @return The specified duration in milliseconds + * @see #parse(String) + * @see #durationOf(Map) + */ + public static long durationOf(String s) { + Map<TimeValueConfig, Long> spec = parse(s); + return durationOf(spec); + } + + /** + * @param s An input time specification containing possibly mixed numbers + * and units - e.g., {@code 3h10m} to indicate 3 hours and 10 minutes + * @return A {@link Map} specifying for each time unit its count + * @throws NumberFormatException If bad numbers found - e.g., negative counts + * @throws IllegalArgumentException If bad format - e.g., unknown unit + */ + public static Map<TimeValueConfig, Long> parse(String s) throws IllegalArgumentException { + if (GenericUtils.isEmpty(s)) { + return Collections.emptyMap(); + } + + int lastPos = 0; + Map<TimeValueConfig, Long> spec = new EnumMap<>(TimeValueConfig.class); + for (int curPos = 0; curPos < s.length(); curPos++) { + char ch = s.charAt(curPos); + if ((ch >= '0') && (ch <= '9')) { + continue; + } + + if (curPos <= lastPos) { + throw new IllegalArgumentException("parse(" + s + ") missing count value at index=" + curPos); + } + + TimeValueConfig c = fromValueChar(ch); + if (c == null) { + throw new IllegalArgumentException("parse(" + s + ") unknown time value character: '" + ch + "'"); + } + + String v = s.substring(lastPos, curPos); + long count = Long.parseLong(v); + if (count < 0L) { + throw new IllegalArgumentException("parse(" + s + ") negative count (" + v + ") for " + c.name()); + } + + Long prev = spec.put(c, count); + if (prev != null) { + throw new IllegalArgumentException("parse(" + s + ") " + c.name() + " value re-specified: current=" + count + ", previous=" + prev); + } + + lastPos = curPos + 1; + if (lastPos >= s.length()) { + break; + } + } + + if (lastPos < s.length()) { + String v = s.substring(lastPos); + long count = Long.parseLong(v); + if (count < 0L) { + throw new IllegalArgumentException("parse(" + s + ") negative count (" + v + ") for last component"); + } + + Long prev = spec.put(SECONDS, count); + if (prev != null) { + throw new IllegalArgumentException("parse(" + s + ") last component (" + SECONDS.name() + ") value re-specified: current=" + count + ", previous=" + prev); + } + } + + return spec; + } + + /** + * @param spec The {@link Map} specifying the count for each {@link TimeValueConfig} + * @return The total duration in milliseconds + * @throws IllegalArgumentException If negative count for a time unit + */ + public static long durationOf(Map<TimeValueConfig, ? extends Number> spec) throws IllegalArgumentException { + if (GenericUtils.isEmpty(spec)) { + return -1L; + } + + long total = 0L; + // Cannot use forEach because the total value is not effectively final + for (Map.Entry<TimeValueConfig, ? extends Number> se : spec.entrySet()) { + TimeValueConfig v = se.getKey(); + Number c = se.getValue(); + long factor = c.longValue(); + if (factor < 0L) { + throw new IllegalArgumentException("valueOf(" + spec + ") bad factor (" + c + ") for " + v.name()); + } + + long added = v.getInterval() * factor; + total += added; + } + + return total; + } +}
