/*
* 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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.apache.sshd.client.ClientBuilder;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.common.BuiltinFactory;
import org.apache.sshd.common.NamedFactory;
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.kex.DHFactory;
import org.apache.sshd.common.kex.KeyExchange;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
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.ValidateUtils;
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;
import org.apache.sshd.server.ServerBuilder;
import org.apache.sshd.server.SshServer;
/**
* Reads and interprets some useful configurations from an OpenSSH
* configuration file.
*
* @author <a href="mailto:dev@mina.apache.org">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 SshConfigFileReader {
public static final char COMMENT_CHAR = '#';
// Some well known configuration properties names and values
public static final String BANNER_CONFIG_PROP = "Banner";
public static final String VISUAL_HOST_KEY = "VisualHostKey";
public static final String DEFAULT_VISUAL_HOST_KEY = "no";
public static final String COMPRESSION_PROP = "Compression";
public static final String DEFAULT_COMPRESSION = CompressionConfigValue.NO.getName();
public static final String ALLOW_TCP_FORWARDING_CONFIG_PROP = "AllowTcpForwarding";
public static final String DEFAULT_TCP_FORWARDING = "yes";
public static final boolean DEFAULT_TCP_FORWARDING_VALUE = parseBooleanValue(DEFAULT_TCP_FORWARDING);
public static final String ALLOW_AGENT_FORWARDING_CONFIG_PROP = "AllowAgentForwarding";
public static final String DEFAULT_AGENT_FORWARDING = "yes";
public static final boolean DEFAULT_AGENT_FORWARDING_VALUE = parseBooleanValue(DEFAULT_AGENT_FORWARDING);
public static final String ALLOW_X11_FORWARDING_CONFIG_PROP = "X11Forwarding";
public static final String DEFAULT_X11_FORWARDING = "yes";
public static final boolean DEFAULT_X11_FORWARDING_VALUE = parseBooleanValue(DEFAULT_X11_FORWARDING);
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.IP_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,umac-64@openssh.com,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"
+ "," + "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 SshConfigFileReader() {
throw new UnsupportedOperationException("No instance allowed");
}
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.trimToEmpty(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 props The {@link Properties} - ignored if {@code null}/empty
* @param name The property name
* @param defaultValue The default value to return if the specified property
* does not exist in the properties map or is an empty string
* @return The resolved property
* @throws NumberFormatException if malformed value
*/
public static long getLongProperty(Properties props, String name, long defaultValue) {
String value = (props == null) ? null : props.getProperty(name);
if (GenericUtils.isEmpty(value)) {
return defaultValue;
} else {
return Long.parseLong(value);
}
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @param name The property name
* @return The {@link Long} value or {@code null} if property not found or
* empty string
* @throws NumberFormatException if malformed value
*/
public static Long getLong(Properties props, String name) {
String value = (props == null) ? null : props.getProperty(name);
if (GenericUtils.isEmpty(value)) {
return null;
} else {
return Long.valueOf(value);
}
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @param name The property name
* @param defaultValue The default value to return if the specified property
* does not exist in the properties map or is an empty string
* @return The resolved property
* @throws NumberFormatException if malformed value
*/
public static int getIntProperty(Properties props, String name, int defaultValue) {
String value = (props == null) ? null : props.getProperty(name);
if (GenericUtils.isEmpty(value)) {
return defaultValue;
} else {
return Integer.parseInt(value);
}
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @param name The property name
* @return The {@link Integer} value or {@code null} if property not found or
* empty string
* @throws NumberFormatException if malformed value
*/
public static Integer getInteger(Properties props, String name) {
String value = (props == null) ? null : props.getProperty(name);
if (GenericUtils.isEmpty(value)) {
return null;
} else {
return Integer.valueOf(value);
}
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @param name The property name
* @param defaultValue The default value to return if the specified property
* does not exist in the properties map or is an empty string
* @return The resolved property
* @throws NumberFormatException if malformed value
*/
public static boolean getBooleanProperty(Properties props, String name, boolean defaultValue) {
String value = (props == null) ? null : props.getProperty(name);
if (GenericUtils.isEmpty(value)) {
return defaultValue;
} else {
return parseBooleanValue(value);
}
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @param name The property name
* @return The {@link Boolean} value or {@code null} if property not found or
* empty string
* @throws NumberFormatException if malformed value
*/
public static Boolean getBoolean(Properties props, String name) {
String value = (props == null) ? null : props.getProperty(name);
if (GenericUtils.isEmpty(value)) {
return null;
} else {
return parseBooleanValue(value);
}
}
/**
* @param v The value to parse - if {@code null}/empty then the default
* value is returned, otherwise {@link #parseBooleanValue(String)} is used
* @param defaultValue The default value to return if {@code null}/empty
* input string
* @return The result
*/
public static boolean parseBooleanValue(String v, boolean defaultValue) {
if (GenericUtils.isEmpty(v)) {
return defaultValue;
} else {
return parseBooleanValue(v);
}
}
/**
* @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";
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @return A {@code ParseResult} of all the {@link NamedFactory}-ies
* whose name appears in the string and represent a built-in cipher.
* Any unknown name is <U>ignored</U>. The order of the returned result
* is the same as the original order - bar the unknown ciphers.
* <B>Note:</B> it is up to caller to ensure that the lists do not
* contain duplicates
* @see #CIPHERS_CONFIG_PROP
* @see BuiltinCiphers#parseCiphersList(String)
*/
public static BuiltinCiphers.ParseResult getCiphers(Properties props) {
return BuiltinCiphers.parseCiphersList((props == null) ? null : props.getProperty(CIPHERS_CONFIG_PROP));
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @return A {@code ParseResult} of all the {@link NamedFactory}-ies
* whose name appears in the string and represent a built-in MAC. Any
* unknown name is <U>ignored</U>. The order of the returned result
* is the same as the original order - bar the unknown MACs.
* <B>Note:</B> it is up to caller to ensure that the list does not
* contain duplicates
* @see #MACS_CONFIG_PROP
* @see BuiltinMacs#parseMacsList(String)
*/
public static BuiltinMacs.ParseResult getMacs(Properties props) {
return BuiltinMacs.parseMacsList((props == null) ? null : props.getProperty(MACS_CONFIG_PROP));
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @return A {@code ParseResult} of all the {@link NamedFactory}
* whose name appears in the string and represent a built-in signature. Any
* unknown name is <U>ignored</U>. The order of the returned result is the
* same as the original order - bar the unknown signatures. <B>Note:</B> it
* is up to caller to ensure that the list does not contain duplicates
* @see #HOST_KEY_ALGORITHMS_CONFIG_PROP
* @see BuiltinSignatures#parseSignatureList(String)
*/
public static BuiltinSignatures.ParseResult getSignatures(Properties props) {
return BuiltinSignatures.parseSignatureList((props == null) ? null : props.getProperty(HOST_KEY_ALGORITHMS_CONFIG_PROP));
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @return A {@code ParseResult} of all the {@link DHFactory}-ies
* whose name appears in the string and represent a built-in value. Any
* unknown name is <U>ignored</U>. The order of the returned result is the
* same as the original order - bar the unknown ones. <B>Note:</B> it is
* up to caller to ensure that the list does not contain duplicates
* @see #KEX_ALGORITHMS_CONFIG_PROP
* @see BuiltinDHFactories#parseDHFactoriesList(String)
*/
public static BuiltinDHFactories.ParseResult getKexFactories(Properties props) {
return BuiltinDHFactories.parseDHFactoriesList((props == null) ? null : props.getProperty(KEX_ALGORITHMS_CONFIG_PROP));
}
/**
* @param props The {@link Properties} - ignored if {@code null}/empty
* @return The matching {@link NamedFactory} for the configured value.
* {@code null} if no configuration or unknown name specified
*/
public static CompressionFactory getCompression(Properties props) {
return CompressionConfigValue.fromName((props == null) ? null : props.getProperty(COMPRESSION_PROP));
}
public static <S extends SshServer> S configure(S server, Properties props, boolean lenient, boolean ignoreUnsupported) {
configure((AbstractFactoryManager) server, props, lenient, ignoreUnsupported);
configureKeyExchanges(server, props, lenient, ServerBuilder.DH2KEX, ignoreUnsupported);
return server;
}
public static <C extends SshClient> C configure(C client, Properties props, boolean lenient, boolean ignoreUnsupported) {
configure((AbstractFactoryManager) client, props, lenient, ignoreUnsupported);
configureKeyExchanges(client, props, lenient, ClientBuilder.DH2KEX, ignoreUnsupported);
return client;
}
/**
* <P>Configures an {@link AbstractFactoryManager} with the values read from
* some configuration. Currently it configures:</P>
* <UL>
* <LI>The {@link Cipher}s - via the {@link #CIPHERS_CONFIG_PROP}</LI>
* <LI>The {@link Mac}s - via the {@link #MACS_CONFIG_PROP}</LI>
* <LI>The {@link Signature}s - via the {@link #HOST_KEY_ALGORITHMS_CONFIG_PROP}</LI>
* <LI>The {@link Compression} - via the {@link #COMPRESSION_PROP}</LI>
* </UL>
*
* @param <M> The generic factory manager
* @param manager The {@link AbstractFactoryManager} to configure
* @param props The {@link Properties} to use for configuration - <B>Note:</B>
* if any known configuration value has a default and does not appear in the
* properties, the default is used
* @param lenient If {@code true} then any unknown configuration values are ignored.
* Otherwise an {@link IllegalArgumentException} is thrown
* @param ignoreUnsupported filter out unsupported configuration values (e.g., ciphers,
* key exchanges, etc..). <B>Note:</B> if after filtering out all the unknown
* or unsupported values there is an empty configuration exception is thrown
* @return The configured manager
*/
public static <M extends AbstractFactoryManager> M configure(M manager, Properties props, boolean lenient, boolean ignoreUnsupported) {
configureCiphers(manager, props, lenient, ignoreUnsupported);
configureSignatures(manager, props, lenient, ignoreUnsupported);
configureMacs(manager, props, lenient, ignoreUnsupported);
configureCompression(manager, props, lenient, ignoreUnsupported);
return manager;
}
public static <M extends AbstractFactoryManager> M configureCiphers(M manager, Properties props, boolean lenient, boolean ignoreUnsupported) {
Objects.requireNonNull(props, "No properties to configure");
return configureCiphers(manager, props.getProperty(CIPHERS_CONFIG_PROP, DEFAULT_CIPHERS), lenient, ignoreUnsupported);
}
public static <M extends AbstractFactoryManager> M configureCiphers(M manager, String value, boolean lenient, boolean ignoreUnsupported) {
Objects.requireNonNull(manager, "No manager to configure");
BuiltinCiphers.ParseResult result = BuiltinCiphers.parseCiphersList(value);
Collection<String> unsupported = result.getUnsupportedFactories();
ValidateUtils.checkTrue(lenient || GenericUtils.isEmpty(unsupported), "Unsupported cipher(s) (%s) in %s", unsupported, value);
List<NamedFactory<Cipher>> factories =
BuiltinFactory.setUpFactories(ignoreUnsupported, result.getParsedFactories());
manager.setCipherFactories(ValidateUtils.checkNotNullAndNotEmpty(factories, "No known/unsupported ciphers(s): %s", value));
return manager;
}
public static <M extends AbstractFactoryManager> M configureSignatures(M manager, Properties props, boolean lenient, boolean ignoreUnsupported) {
Objects.requireNonNull(props, "No properties to configure");
return configureSignatures(manager, props.getProperty(HOST_KEY_ALGORITHMS_CONFIG_PROP, DEFAULT_HOST_KEY_ALGORITHMS), lenient, ignoreUnsupported);
}
public static <M extends AbstractFactoryManager> M configureSignatures(M manager, String value, boolean lenient, boolean ignoreUnsupported) {
Objects.requireNonNull(manager, "No manager to configure");
BuiltinSignatures.ParseResult result = BuiltinSignatures.parseSignatureList(value);
Collection<String> unsupported = result.getUnsupportedFactories();
ValidateUtils.checkTrue(lenient || GenericUtils.isEmpty(unsupported), "Unsupported signatures (%s) in %s", unsupported, value);
List<NamedFactory<Signature>> factories =
BuiltinFactory.setUpFactories(ignoreUnsupported, result.getParsedFactories());
manager.setSignatureFactories(ValidateUtils.checkNotNullAndNotEmpty(factories, "No known/supported signatures: %s", value));
return manager;
}
public static <M extends AbstractFactoryManager> M configureMacs(M manager, Properties props, boolean lenient, boolean ignoreUnsupported) {
Objects.requireNonNull(props, "No properties to configure");
return configureMacs(manager, props.getProperty(MACS_CONFIG_PROP, DEFAULT_MACS), lenient, ignoreUnsupported);
}
public static <M extends AbstractFactoryManager> M configureMacs(M manager, String value, boolean lenient, boolean ignoreUnsupported) {
Objects.requireNonNull(manager, "No manager to configure");
BuiltinMacs.ParseResult result = BuiltinMacs.parseMacsList(value);
Collection<String> unsupported = result.getUnsupportedFactories();
ValidateUtils.checkTrue(lenient || GenericUtils.isEmpty(unsupported), "Unsupported MAC(s) (%s) in %s", unsupported, value);
List<NamedFactory<Mac>> factories =
BuiltinFactory.setUpFactories(ignoreUnsupported, result.getParsedFactories());
manager.setMacFactories(ValidateUtils.checkNotNullAndNotEmpty(factories, "No known/supported MAC(s): %s", value));
return manager;
}
/**
* @param <M> The generic factory manager
* @param manager The {@link AbstractFactoryManager} to set up (may not be {@code null})
* @param props The (non-{@code null}) {@link Properties} containing the configuration
* @param lenient If {@code true} then any unknown/unsupported configuration
* values are ignored. Otherwise an {@link IllegalArgumentException} is thrown
* @param xformer A {@link Function} to convert the configured {@link DHFactory}-ies
* to {@link NamedFactory}-ies of {@link KeyExchange}
* @param ignoreUnsupported Filter out any un-supported configurations - <B>Note:</B>
* if after ignoring the unknown and un-supported values the result is an empty
* list of factories and exception is thrown
* @return The configured manager
* @see #KEX_ALGORITHMS_CONFIG_PROP
* @see #DEFAULT_KEX_ALGORITHMS
*/
public static <M extends AbstractFactoryManager> M configureKeyExchanges(
M manager, Properties props, boolean lenient, Function<? super DHFactory, ? extends NamedFactory<KeyExchange>> xformer, boolean ignoreUnsupported) {
Objects.requireNonNull(props, "No properties to configure");
return configureKeyExchanges(manager, props.getProperty(KEX_ALGORITHMS_CONFIG_PROP, DEFAULT_KEX_ALGORITHMS), lenient, xformer, ignoreUnsupported);
}
public static <M extends AbstractFactoryManager> M configureKeyExchanges(
M manager, String value, boolean lenient, Function<? super DHFactory, ? extends NamedFactory<KeyExchange>> xformer, boolean ignoreUnsupported) {
Objects.requireNonNull(manager, "No manager to configure");
Objects.requireNonNull(xformer, "No DHFactory transformer");
BuiltinDHFactories.ParseResult result = BuiltinDHFactories.parseDHFactoriesList(value);
Collection<String> unsupported = result.getUnsupportedFactories();
ValidateUtils.checkTrue(lenient || GenericUtils.isEmpty(unsupported), "Unsupported KEX(s) (%s) in %s", unsupported, value);
List<NamedFactory<KeyExchange>> factories =
NamedFactory.setUpTransformedFactories(ignoreUnsupported, result.getParsedFactories(), xformer);
manager.setKeyExchangeFactories(ValidateUtils.checkNotNullAndNotEmpty(factories, "No known/supported KEXS(s): %s", value));
return manager;
}
/**
* Configure the factory manager using one of the known {@link CompressionConfigValue}s.
*
* @param <M> The generic factory manager
* @param manager The {@link AbstractFactoryManager} to configure
* @param props The configuration {@link Properties}
* @param lenient If {@code true} and an unknown value is provided then
* it is ignored
* @param ignoreUnsupported If {@code false} then check if the compression
* is currently supported before setting it
* @return The configured manager - <B>Note:</B> if the result of filtering due
* to lenient mode or ignored unsupported value is empty then no factories are set
*/
public static <M extends AbstractFactoryManager> M configureCompression(M manager, Properties props, boolean lenient, boolean ignoreUnsupported) {
Objects.requireNonNull(manager, "No manager to configure");
Objects.requireNonNull(props, "No properties to configure");
String value = props.getProperty(COMPRESSION_PROP, DEFAULT_COMPRESSION);
CompressionFactory factory = CompressionConfigValue.fromName(value);
ValidateUtils.checkTrue(lenient || (factory != null), "Unsupported compression value: %s", value);
if ((factory != null) && factory.isSupported()) {
manager.setCompressionFactories(Collections.singletonList(factory));
}
return manager;
}
// accepts BOTH CompressionConfigValue(s) and/or BuiltinCompressions - including extensions
public static <M extends AbstractFactoryManager> M configureCompression(M manager, String value, boolean lenient, boolean ignoreUnsupported) {
Objects.requireNonNull(manager, "No manager to configure");
CompressionFactory factory = CompressionConfigValue.fromName(value);
if (factory != null) {
// SSH can work without compression
if (ignoreUnsupported || factory.isSupported()) {
manager.setCompressionFactories(Collections.singletonList(factory));
}
} else {
BuiltinCompressions.ParseResult result = BuiltinCompressions.parseCompressionsList(value);
Collection<String> unsupported = result.getUnsupportedFactories();
ValidateUtils.checkTrue(lenient || GenericUtils.isEmpty(unsupported), "Unsupported compressions(s) (%s) in %s", unsupported, value);
List<NamedFactory<Compression>> factories =
BuiltinFactory.setUpFactories(ignoreUnsupported, result.getParsedFactories());
// SSH can work without compression
if (GenericUtils.size(factories) > 0) {
manager.setCompressionFactories(factories);
}
}
return manager;
}
}