/*
* 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.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.function.Function;
import org.apache.sshd.common.BaseBuilder;
import org.apache.sshd.common.Closeable;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.cipher.BuiltinCiphers;
import org.apache.sshd.common.cipher.Cipher;
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.helpers.AbstractFactoryManager;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.mac.BuiltinMacs;
import org.apache.sshd.common.mac.Mac;
import org.apache.sshd.common.signature.BuiltinSignatures;
import org.apache.sshd.common.signature.Signature;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.Transformer;
import org.apache.sshd.util.test.BaseTestSupport;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SshConfigFileReaderTest extends BaseTestSupport {
public SshConfigFileReaderTest() {
super();
}
@Test
public void testReadFromURL() throws IOException {
URL url = getClass().getResource("sshd_config");
assertNotNull("Cannot locate test file", url);
Properties props = SshConfigFileReader.readConfigFile(url);
assertFalse("No properties read", props.isEmpty());
assertTrue("Unexpected commented property data", GenericUtils.isEmpty(props.getProperty("ListenAddress")));
assertTrue("Unexpected non-existing property data", GenericUtils.isEmpty(props.getProperty(getCurrentTestName())));
String keysList = props.getProperty("HostKey");
assertFalse("No host keys", GenericUtils.isEmpty(keysList));
String[] keys = GenericUtils.split(keysList, ',');
assertTrue("No multiple keys", GenericUtils.length((Object[]) keys) > 1);
}
@Test
public void testParseCiphersList() {
List<? extends NamedResource> expected = BaseBuilder.DEFAULT_CIPHERS_PREFERENCE;
Properties props = initNamedResourceProperties(SshConfigFileReader.CIPHERS_CONFIG_PROP, expected);
BuiltinCiphers.ParseResult result = SshConfigFileReader.getCiphers(props);
testParsedFactoriesList(expected, result.getParsedFactories(), result.getUnsupportedFactories());
}
@Test
public void testParseMacsList() {
List<? extends NamedResource> expected = BaseBuilder.DEFAULT_MAC_PREFERENCE;
Properties props = initNamedResourceProperties(SshConfigFileReader.MACS_CONFIG_PROP, expected);
BuiltinMacs.ParseResult result = SshConfigFileReader.getMacs(props);
testParsedFactoriesList(expected, result.getParsedFactories(), result.getUnsupportedFactories());
}
@Test
public void testParseSignaturesList() {
List<? extends NamedResource> expected = BaseBuilder.DEFAULT_SIGNATURE_PREFERENCE;
Properties props = initNamedResourceProperties(SshConfigFileReader.HOST_KEY_ALGORITHMS_CONFIG_PROP, expected);
BuiltinSignatures.ParseResult result = SshConfigFileReader.getSignatures(props);
testParsedFactoriesList(expected, result.getParsedFactories(), result.getUnsupportedFactories());
}
@Test
public void testParseKexFactoriesList() {
List<? extends NamedResource> expected = BaseBuilder.DEFAULT_KEX_PREFERENCE;
Properties props = initNamedResourceProperties(SshConfigFileReader.KEX_ALGORITHMS_CONFIG_PROP, expected);
BuiltinDHFactories.ParseResult result = SshConfigFileReader.getKexFactories(props);
testParsedFactoriesList(expected, result.getParsedFactories(), result.getUnsupportedFactories());
}
@Test
public void testGetCompression() {
Properties props = new Properties();
for (CompressionConfigValue expected : CompressionConfigValue.VALUES) {
props.setProperty(SshConfigFileReader.COMPRESSION_PROP, expected.name().toLowerCase());
NamedResource actual = SshConfigFileReader.getCompression(props);
assertNotNull("No match for " + expected.name(), actual);
assertEquals(expected.name(), expected.getName(), actual.getName());
}
}
@Test
public void testConfigureAbstractFactoryManagerWithDefaults() {
Properties props = new Properties(); // empty means use defaults
AbstractFactoryManager expected = new AbstractFactoryManager() {
@Override
protected Closeable getInnerCloseable() {
return null;
}
};
// must be lenient since we do not cover the full default spectrum
AbstractFactoryManager actual = SshConfigFileReader.configure(expected, props, true, true);
assertSame("Mismatched configured result", expected, actual);
validateAbstractFactoryManagerConfiguration(expected, props, true);
}
@Test(expected = IllegalArgumentException.class)
public void testNonLenientCiphersConfiguration() {
FactoryManager manager = SshConfigFileReader.configureCiphers(
new AbstractFactoryManager() {
@Override
protected Closeable getInnerCloseable() {
return null;
}
},
getCurrentTestName(),
false,
true);
fail("Unexpected success: " + NamedResource.getNames(manager.getCipherFactories()));
}
@Test(expected = IllegalArgumentException.class)
public void testNonLenientSignaturesConfiguration() {
FactoryManager manager = SshConfigFileReader.configureSignatures(
new AbstractFactoryManager() {
@Override
protected Closeable getInnerCloseable() {
return null;
}
},
getCurrentTestName(),
false,
true);
fail("Unexpected success: " + NamedResource.getNames(manager.getSignatureFactories()));
}
@Test(expected = IllegalArgumentException.class)
public void testNonLenientMacsConfiguration() {
FactoryManager manager = SshConfigFileReader.configureMacs(
new AbstractFactoryManager() {
@Override
protected Closeable getInnerCloseable() {
return null;
}
},
getCurrentTestName(),
false,
true);
fail("Unexpected success: " + NamedResource.getNames(manager.getMacFactories()));
}
@Test
public void testConfigureCompressionFromStringAcceptsCombinedValues() {
testConfigureCompressionFromStringAcceptsCombinedValues(CompressionConfigValue.class, Transformer.ENUM_NAME_EXTRACTOR);
testConfigureCompressionFromStringAcceptsCombinedValues(BuiltinCompressions.class, NamedResource.NAME_EXTRACTOR);
}
private static <E extends Enum<E> & CompressionFactory> void testConfigureCompressionFromStringAcceptsCombinedValues(
Class<E> facs, Function<? super E, String> configValueXformer) {
for (E expected : facs.getEnumConstants()) {
String value = configValueXformer.apply(expected);
String prefix = facs.getSimpleName() + "[" + expected.name() + "][" + value + "]";
FactoryManager manager = SshConfigFileReader.configureCompression(
new AbstractFactoryManager() {
@Override
protected Closeable getInnerCloseable() {
return null;
}
},
value,
false,
true);
List<NamedFactory<Compression>> compressions = manager.getCompressionFactories();
assertEquals(prefix + "(size)", 1, GenericUtils.size(compressions));
assertSame(prefix + "[instance]", expected, compressions.get(0));
}
}
private static <M extends FactoryManager> M validateAbstractFactoryManagerConfiguration(M manager, Properties props, boolean lenient) {
validateFactoryManagerCiphers(manager, props);
validateFactoryManagerSignatures(manager, props);
validateFactoryManagerMacs(manager, props);
validateFactoryManagerCompressions(manager, props, lenient);
return manager;
}
private static <M extends FactoryManager> M validateFactoryManagerCiphers(M manager, Properties props) {
return validateFactoryManagerCiphers(manager, props.getProperty(SshConfigFileReader.CIPHERS_CONFIG_PROP, SshConfigFileReader.DEFAULT_CIPHERS));
}
private static <M extends FactoryManager> M validateFactoryManagerCiphers(M manager, String value) {
BuiltinCiphers.ParseResult result = BuiltinCiphers.parseCiphersList(value);
validateFactoryManagerFactories(Cipher.class, result.getParsedFactories(), manager.getCipherFactories());
return manager;
}
private static <M extends FactoryManager> M validateFactoryManagerSignatures(M manager, Properties props) {
return validateFactoryManagerSignatures(manager, props.getProperty(SshConfigFileReader.HOST_KEY_ALGORITHMS_CONFIG_PROP, SshConfigFileReader.DEFAULT_HOST_KEY_ALGORITHMS));
}
private static <M extends FactoryManager> M validateFactoryManagerSignatures(M manager, String value) {
BuiltinSignatures.ParseResult result = BuiltinSignatures.parseSignatureList(value);
validateFactoryManagerFactories(Signature.class, result.getParsedFactories(), manager.getSignatureFactories());
return manager;
}
private static <M extends FactoryManager> M validateFactoryManagerMacs(M manager, Properties props) {
return validateFactoryManagerMacs(manager, props.getProperty(SshConfigFileReader.MACS_CONFIG_PROP, SshConfigFileReader.DEFAULT_MACS));
}
private static <M extends FactoryManager> M validateFactoryManagerMacs(M manager, String value) {
BuiltinMacs.ParseResult result = BuiltinMacs.parseMacsList(value);
validateFactoryManagerFactories(Mac.class, result.getParsedFactories(), manager.getMacFactories());
return manager;
}
private static <M extends FactoryManager> M validateFactoryManagerCompressions(M manager, Properties props, boolean lenient) {
return validateFactoryManagerCompressions(manager, props.getProperty(SshConfigFileReader.COMPRESSION_PROP, SshConfigFileReader.DEFAULT_COMPRESSION), lenient);
}
private static <M extends FactoryManager> M validateFactoryManagerCompressions(M manager, String value, boolean lenient) {
NamedFactory<Compression> factory = CompressionConfigValue.fromName(value);
assertTrue("Unknown compression: " + value, lenient || (factory != null));
if (factory != null) {
validateFactoryManagerFactories(Compression.class, Collections.singletonList(factory), manager.getCompressionFactories());
}
return manager;
}
private static <T, F extends NamedFactory<T>> void validateFactoryManagerFactories(Class<T> type, List<? extends F> expected, List<? extends F> actual) {
validateFactoryManagerSettings(type, expected, actual);
}
private static <R extends NamedResource> void validateFactoryManagerSettings(Class<?> type, List<? extends R> expected, List<? extends R> actual) {
validateFactoryManagerSettings(type.getSimpleName(), expected, actual);
}
private static <R extends NamedResource> void validateFactoryManagerSettings(String type, List<? extends R> expected, List<? extends R> actual) {
assertListEquals(type, expected, actual);
}
private static <T extends NamedResource> List<T> testParsedFactoriesList(
List<? extends NamedResource> expected, List<T> actual, Collection<String> unsupported) {
assertTrue("Unexpected unsupported factories: " + unsupported, GenericUtils.isEmpty(unsupported));
assertEquals("Mismatched list size", expected.size(), GenericUtils.size(actual));
for (int index = 0; index < expected.size(); index++) {
NamedResource e = expected.get(index);
String n1 = e.getName();
NamedResource a = actual.get(index);
String n2 = a.getName();
assertEquals("Mismatched name at index=" + index, n1, n2);
}
return actual;
}
private static <R extends NamedResource> Properties initNamedResourceProperties(String key, Collection<? extends R> values) {
return initProperties(key, NamedResource.getNames(values));
}
private static Properties initProperties(String key, String value) {
Properties props = new Properties();
props.setProperty(key, value);
return props;
}
}