You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/09/06 16:03:59 UTC
[49/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common
utilities code from sshd-core into sshd-common (new artifact)
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostHashValue.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostHashValue.java b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostHashValue.java
new file mode 100644
index 0000000..28a3d1f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostHashValue.java
@@ -0,0 +1,170 @@
+/*
+ * 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.client.config.hosts;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Objects;
+
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.RuntimeSshException;
+import org.apache.sshd.common.mac.Mac;
+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.buffer.BufferUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class KnownHostHashValue {
+ /**
+ * Character used to indicate a hashed host pattern
+ */
+ public static final char HASHED_HOST_DELIMITER = '|';
+
+ public static final NamedFactory<Mac> DEFAULT_DIGEST = KnownHostDigest.SHA1;
+
+ private NamedFactory<Mac> digester = DEFAULT_DIGEST;
+ private byte[] saltValue;
+ private byte[] digestValue;
+
+ public KnownHostHashValue() {
+ super();
+ }
+
+ public NamedFactory<Mac> getDigester() {
+ return digester;
+ }
+
+ public void setDigester(NamedFactory<Mac> digester) {
+ this.digester = digester;
+ }
+
+ public byte[] getSaltValue() {
+ return saltValue;
+ }
+
+ public void setSaltValue(byte[] saltValue) {
+ this.saltValue = saltValue;
+ }
+
+ public byte[] getDigestValue() {
+ return digestValue;
+ }
+
+ public void setDigestValue(byte[] digestValue) {
+ this.digestValue = digestValue;
+ }
+
+ /**
+ * Checks if the host matches the hash
+ *
+ * @param host The host name/address - ignored if {@code null}/empty
+ * @return {@code true} if host matches the hash
+ * @throws RuntimeException If entry not properly initialized
+ */
+ public boolean isHostMatch(String host) {
+ if (GenericUtils.isEmpty(host)) {
+ return false;
+ }
+
+ try {
+ byte[] expected = getDigestValue();
+ byte[] actual = calculateHashValue(host, getDigester(), getSaltValue());
+ return Arrays.equals(expected, actual);
+ } catch (Throwable t) {
+ if (t instanceof RuntimeException) {
+ throw (RuntimeException) t;
+ }
+ throw new RuntimeSshException("Failed (" + t.getClass().getSimpleName() + ")"
+ + " to calculate hash value: " + t.getMessage(), t);
+ }
+ }
+
+ @Override
+ public String toString() {
+ if ((getDigester() == null) || NumberUtils.isEmpty(getSaltValue()) || NumberUtils.isEmpty(getDigestValue())) {
+ return Objects.toString(getDigester(), null)
+ + "-" + BufferUtils.toHex(':', getSaltValue())
+ + "-" + BufferUtils.toHex(':', getDigestValue());
+ }
+
+ try {
+ return append(new StringBuilder(Byte.MAX_VALUE), this).toString();
+ } catch (IOException | RuntimeException e) { // unexpected
+ return e.getClass().getSimpleName() + ": " + e.getMessage();
+ }
+ }
+
+ // see http://nms.lcs.mit.edu/projects/ssh/README.hashed-hosts
+ public static byte[] calculateHashValue(String host, Factory<? extends Mac> factory, byte[] salt) throws Exception {
+ return calculateHashValue(host, factory.create(), salt);
+ }
+
+ public static byte[] calculateHashValue(String host, Mac mac, byte[] salt) throws Exception {
+ mac.init(salt);
+
+ byte[] hostBytes = host.getBytes(StandardCharsets.UTF_8);
+ mac.update(hostBytes);
+ return mac.doFinal();
+ }
+
+ public static <A extends Appendable> A append(A sb, KnownHostHashValue hashValue) throws IOException {
+ return (hashValue == null) ? sb : append(sb, hashValue.getDigester(), hashValue.getSaltValue(), hashValue.getDigestValue());
+ }
+
+ public static <A extends Appendable> A append(A sb, NamedResource factory, byte[] salt, byte[] digest) throws IOException {
+ Base64.Encoder encoder = Base64.getEncoder();
+ sb.append(HASHED_HOST_DELIMITER).append(factory.getName());
+ sb.append(HASHED_HOST_DELIMITER).append(encoder.encodeToString(salt));
+ sb.append(HASHED_HOST_DELIMITER).append(encoder.encodeToString(digest));
+ return sb;
+ }
+
+ public static KnownHostHashValue parse(String patternString) {
+ String pattern = GenericUtils.replaceWhitespaceAndTrim(patternString);
+ return parse(pattern, GenericUtils.isEmpty(pattern) ? null : new KnownHostHashValue());
+ }
+
+ public static <V extends KnownHostHashValue> V parse(String patternString, V value) {
+ String pattern = GenericUtils.replaceWhitespaceAndTrim(patternString);
+ if (GenericUtils.isEmpty(pattern)) {
+ return value;
+ }
+
+ String[] components = GenericUtils.split(pattern, HASHED_HOST_DELIMITER);
+ ValidateUtils.checkTrue(components.length == 4 /* 1st one is empty */, "Invalid hash pattern (insufficient data): %s", pattern);
+ ValidateUtils.checkTrue(GenericUtils.isEmpty(components[0]), "Invalid hash pattern (unexpected extra data): %s", pattern);
+
+ NamedFactory<Mac> factory =
+ ValidateUtils.checkNotNull(KnownHostDigest.fromName(components[1]),
+ "Invalid hash pattern (unknown digest): %s", pattern);
+ Base64.Decoder decoder = Base64.getDecoder();
+ value.setDigester(factory);
+ value.setSaltValue(decoder.decode(components[2]));
+ value.setDigestValue(decoder.decode(components[3]));
+ return value;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcher.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcher.java
new file mode 100644
index 0000000..ab9929b
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcher.java
@@ -0,0 +1,106 @@
+/*
+ * 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.client.config.keys;
+
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class BuiltinClientIdentitiesWatcher extends ClientIdentitiesWatcher {
+ private final boolean supportedOnly;
+
+ public BuiltinClientIdentitiesWatcher(Path keysFolder, boolean supportedOnly,
+ ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
+ this(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES), supportedOnly, loader, provider, strict);
+ }
+
+ public BuiltinClientIdentitiesWatcher(Path keysFolder, Collection<String> ids, boolean supportedOnly,
+ ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
+ this(keysFolder, ids, supportedOnly,
+ GenericUtils.supplierOf(Objects.requireNonNull(loader, "No client identity loader")),
+ GenericUtils.supplierOf(Objects.requireNonNull(provider, "No password provider")),
+ strict);
+ }
+
+ public BuiltinClientIdentitiesWatcher(Path keysFolder, boolean supportedOnly,
+ Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider, boolean strict) {
+ this(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES), supportedOnly, loader, provider, strict);
+ }
+
+ public BuiltinClientIdentitiesWatcher(Path keysFolder, Collection<String> ids, boolean supportedOnly,
+ Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider, boolean strict) {
+ super(getBuiltinIdentitiesPaths(keysFolder, ids), loader, provider, strict);
+ this.supportedOnly = supportedOnly;
+ }
+
+ public final boolean isSupportedOnly() {
+ return supportedOnly;
+ }
+
+ @Override
+ public Iterable<KeyPair> loadKeys() {
+ return isSupportedOnly() ? loadKeys(this::isSupported) : super.loadKeys();
+ }
+
+ private boolean isSupported(KeyPair kp) {
+ BuiltinIdentities id = BuiltinIdentities.fromKeyPair(kp);
+ if ((id != null) && id.isSupported()) {
+ return true;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("loadKeys - remove unsupported identity={}, key-type={}, key={}",
+ id, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
+ }
+ return false;
+ }
+
+ public static List<Path> getDefaultBuiltinIdentitiesPaths(Path keysFolder) {
+ return getBuiltinIdentitiesPaths(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES));
+ }
+
+ public static List<Path> getBuiltinIdentitiesPaths(Path keysFolder, Collection<String> ids) {
+ Objects.requireNonNull(keysFolder, "No keys folder");
+ if (GenericUtils.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+
+ List<Path> paths = new ArrayList<>(ids.size());
+ for (String id : ids) {
+ String fileName = ClientIdentity.getIdentityFileName(id);
+ paths.add(keysFolder.resolve(fileName));
+ }
+
+ return paths;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java
new file mode 100644
index 0000000..094766f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java
@@ -0,0 +1,139 @@
+/*
+ * 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.client.config.keys;
+
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * Watches over a group of files that contains client identities
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ClientIdentitiesWatcher extends AbstractKeyPairProvider implements KeyPairProvider {
+ private final Collection<ClientIdentityProvider> providers;
+
+ public ClientIdentitiesWatcher(Collection<? extends Path> paths,
+ ClientIdentityLoader loader, FilePasswordProvider provider) {
+ this(paths, loader, provider, true);
+ }
+
+ public ClientIdentitiesWatcher(Collection<? extends Path> paths,
+ ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
+ this(paths,
+ GenericUtils.supplierOf(Objects.requireNonNull(loader, "No client identity loader")),
+ GenericUtils.supplierOf(Objects.requireNonNull(provider, "No password provider")),
+ strict);
+ }
+
+ public ClientIdentitiesWatcher(Collection<? extends Path> paths,
+ Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider) {
+ this(paths, loader, provider, true);
+ }
+
+ public ClientIdentitiesWatcher(Collection<? extends Path> paths,
+ Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider, boolean strict) {
+ this(buildProviders(paths, loader, provider, strict));
+ }
+
+ public ClientIdentitiesWatcher(Collection<ClientIdentityProvider> providers) {
+ this.providers = providers;
+ }
+
+ @Override
+ public Iterable<KeyPair> loadKeys() {
+ return loadKeys(null);
+ }
+
+ protected Iterable<KeyPair> loadKeys(Predicate<? super KeyPair> filter) {
+ return () -> {
+ Stream<KeyPair> stream = safeMap(GenericUtils.stream(providers), this::doGetKeyPair);
+ if (filter != null) {
+ stream = stream.filter(filter);
+ }
+ return stream.iterator();
+ };
+ }
+
+ /**
+ * Performs a mapping operation on the stream, discarding any null values
+ * returned by the mapper.
+ *
+ * @param <U> Original type
+ * @param <V> Mapped type
+ * @param stream Original values stream
+ * @param mapper Mapper to target type
+ * @return Mapped stream
+ */
+ protected <U, V> Stream<V> safeMap(Stream<U> stream, Function<? super U, ? extends V> mapper) {
+ return stream.map(u -> Optional.ofNullable(mapper.apply(u)))
+ .filter(Optional::isPresent)
+ .map(Optional::get);
+ }
+
+ protected KeyPair doGetKeyPair(ClientIdentityProvider p) {
+ try {
+ KeyPair kp = p.getClientIdentity();
+ if (kp == null) {
+ if (log.isDebugEnabled()) {
+ log.debug("loadKeys({}) no key loaded", p);
+ }
+ }
+ return kp;
+ } catch (Throwable e) {
+ log.warn("loadKeys({}) failed ({}) to load key: {}", p, e.getClass().getSimpleName(), e.getMessage());
+ if (log.isDebugEnabled()) {
+ log.debug("loadKeys(" + p + ") key load failure details", e);
+ }
+ return null;
+ }
+ }
+
+ public static List<ClientIdentityProvider> buildProviders(
+ Collection<? extends Path> paths, ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
+ return buildProviders(paths,
+ GenericUtils.supplierOf(Objects.requireNonNull(loader, "No client identity loader")),
+ GenericUtils.supplierOf(Objects.requireNonNull(provider, "No password provider")),
+ strict);
+ }
+
+ public static List<ClientIdentityProvider> buildProviders(
+ Collection<? extends Path> paths, Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider, boolean strict) {
+ if (GenericUtils.isEmpty(paths)) {
+ return Collections.emptyList();
+ }
+
+ return GenericUtils.map(paths, p -> new ClientIdentityFileWatcher(p, loader, provider, strict));
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
new file mode 100644
index 0000000..931d96f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
@@ -0,0 +1,268 @@
+/*
+ * 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.client.config.keys;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.IdentityUtils;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.FileInfoExtractor;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * Provides keys loading capability from the user's keys folder - e.g., {@code id_rsa}
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see org.apache.sshd.common.util.security.SecurityUtils#getKeyPairResourceParser()
+ */
+public final class ClientIdentity {
+
+ public static final String ID_FILE_PREFIX = "id_";
+
+ public static final String ID_FILE_SUFFIX = "";
+
+ public static final Function<String, String> ID_GENERATOR =
+ ClientIdentity::getIdentityFileName;
+
+ private ClientIdentity() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ /**
+ * @param name The file name - ignored if {@code null}/empty
+ * @return The identity type - {@code null} if cannot determine it - e.g.,
+ * does not start with the {@link #ID_FILE_PREFIX}
+ */
+ public static String getIdentityType(String name) {
+ if (GenericUtils.isEmpty(name)
+ || (name.length() <= ID_FILE_PREFIX.length())
+ || (!name.startsWith(ID_FILE_PREFIX))) {
+ return null;
+ } else {
+ return name.substring(ID_FILE_PREFIX.length());
+ }
+ }
+
+ public static String getIdentityFileName(NamedResource r) {
+ return getIdentityFileName((r == null) ? null : r.getName());
+ }
+
+ /**
+ * @param type The identity type - e.g., {@code rsa} - ignored
+ * if {@code null}/empty
+ * @return The matching file name for the identity - {@code null}
+ * if no name
+ * @see #ID_FILE_PREFIX
+ * @see #ID_FILE_SUFFIX
+ * @see IdentityUtils#getIdentityFileName(String, String, String)
+ */
+ public static String getIdentityFileName(String type) {
+ return IdentityUtils.getIdentityFileName(ID_FILE_PREFIX, type, ID_FILE_SUFFIX);
+ }
+
+ /**
+ * @param strict If {@code true} then files that do not have the required
+ * access rights are excluded from consideration
+ * @param supportedOnly If {@code true} then ignore identities that are not
+ * supported internally
+ * @param provider A {@link FilePasswordProvider} - may be {@code null}
+ * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+ * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+ * file whose key is to be loaded
+ * @param options The {@link LinkOption}s to apply when checking
+ * for existence
+ * @return A {@link KeyPair} for the identities - {@code null} if no identities
+ * available (e.g., after filtering unsupported ones or strict permissions)
+ * @throws IOException If failed to access the file system
+ * @throws GeneralSecurityException If failed to load the keys
+ * @see PublicKeyEntry#getDefaultKeysFolderPath()
+ * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...)
+ */
+ public static KeyPairProvider loadDefaultKeyPairProvider(
+ boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption... options)
+ throws IOException, GeneralSecurityException {
+ return loadDefaultKeyPairProvider(PublicKeyEntry.getDefaultKeysFolderPath(), strict, supportedOnly, provider, options);
+ }
+
+ /**
+ * @param dir The folder to scan for the built-in identities
+ * @param strict If {@code true} then files that do not have the required
+ * access rights are excluded from consideration
+ * @param supportedOnly If {@code true} then ignore identities that are not
+ * supported internally
+ * @param provider A {@link FilePasswordProvider} - may be {@code null}
+ * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+ * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+ * file whose key is to be loaded
+ * @param options The {@link LinkOption}s to apply when checking
+ * for existence
+ * @return A {@link KeyPair} for the identities - {@code null} if no identities
+ * available (e.g., after filtering unsupported ones or strict permissions)
+ * @throws IOException If failed to access the file system
+ * @throws GeneralSecurityException If failed to load the keys
+ * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...)
+ * @see IdentityUtils#createKeyPairProvider(Map, boolean)
+ */
+ public static KeyPairProvider loadDefaultKeyPairProvider(
+ Path dir, boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption... options)
+ throws IOException, GeneralSecurityException {
+ Map<String, KeyPair> ids = loadDefaultIdentities(dir, strict, provider, options);
+ return IdentityUtils.createKeyPairProvider(ids, supportedOnly);
+ }
+
+ /**
+ * @param strict If {@code true} then files that do not have the required
+ * access rights are excluded from consideration
+ * @param provider A {@link FilePasswordProvider} - may be {@code null}
+ * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+ * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+ * file whose key is to be loaded
+ * @param options The {@link LinkOption}s to apply when checking
+ * for existence
+ * @return A {@link Map} of the found files where key=identity type (case
+ * <U>insensitive</U>), value=the {@link KeyPair} of the identity
+ * @throws IOException If failed to access the file system
+ * @throws GeneralSecurityException If failed to load the keys
+ * @see PublicKeyEntry#getDefaultKeysFolderPath()
+ * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...)
+ */
+ public static Map<String, KeyPair> loadDefaultIdentities(boolean strict, FilePasswordProvider provider, LinkOption... options)
+ throws IOException, GeneralSecurityException {
+ return loadDefaultIdentities(PublicKeyEntry.getDefaultKeysFolderPath(), strict, provider, options);
+ }
+
+ /**
+ * @param dir The folder to scan for the built-in identities
+ * @param strict If {@code true} then files that do not have the required
+ * access rights are excluded from consideration
+ * @param provider A {@link FilePasswordProvider} - may be {@code null}
+ * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+ * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+ * file whose key is to be loaded
+ * @param options The {@link LinkOption}s to apply when checking
+ * for existence
+ * @return A {@link Map} of the found files where key=identity type (case
+ * <U>insensitive</U>), value=the {@link KeyPair} of the identity
+ * @throws IOException If failed to access the file system
+ * @throws GeneralSecurityException If failed to load the keys
+ * @see #loadIdentities(Path, boolean, Collection, Function, FilePasswordProvider, LinkOption...)
+ * @see BuiltinIdentities
+ */
+ public static Map<String, KeyPair> loadDefaultIdentities(Path dir, boolean strict, FilePasswordProvider provider, LinkOption... options)
+ throws IOException, GeneralSecurityException {
+ return loadIdentities(dir, strict, BuiltinIdentities.NAMES, ID_GENERATOR, provider, options);
+ }
+
+ /**
+ * Scans a folder and loads all available identity files
+ *
+ * @param dir The {@link Path} of the folder to scan - ignored if not exists
+ * @param strict If {@code true} then files that do not have the required
+ * access rights are excluded from consideration
+ * @param types The identity types - ignored if {@code null}/empty
+ * @param idGenerator A {@link Function} to derive the file name
+ * holding the specified type
+ * @param provider A {@link FilePasswordProvider} - may be {@code null}
+ * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+ * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+ * file whose key is to be loaded
+ * @param options The {@link LinkOption}s to apply when checking
+ * for existence
+ * @return A {@link Map} of the found files where key=identity type (case
+ * <U>insensitive</U>), value=the {@link KeyPair} of the identity
+ * @throws IOException If failed to access the file system
+ * @throws GeneralSecurityException If failed to load the keys
+ * @see #scanIdentitiesFolder(Path, boolean, Collection, Function, LinkOption...)
+ * @see IdentityUtils#loadIdentities(Map, FilePasswordProvider, java.nio.file.OpenOption...)
+ */
+ public static Map<String, KeyPair> loadIdentities(
+ Path dir, boolean strict, Collection<String> types, Function<String, String> idGenerator, FilePasswordProvider provider, LinkOption... options)
+ throws IOException, GeneralSecurityException {
+ Map<String, Path> paths = scanIdentitiesFolder(dir, strict, types, idGenerator, options);
+ return IdentityUtils.loadIdentities(paths, provider, IoUtils.EMPTY_OPEN_OPTIONS);
+ }
+
+ /**
+ * Scans a folder for possible identity files
+ *
+ * @param dir The {@link Path} of the folder to scan - ignored if not exists
+ * @param strict If {@code true} then files that do not have the required
+ * access rights are excluded from consideration
+ * @param types The identity types - ignored if {@code null}/empty
+ * @param idGenerator A {@link Function} to derive the file name
+ * holding the specified type
+ * @param options The {@link LinkOption}s to apply when checking
+ * for existence
+ * @return A {@link Map} of the found files where key=identity type (case
+ * <U>insensitive</U>), value=the {@link Path} of the file holding the key
+ * @throws IOException If failed to access the file system
+ * @see KeyUtils#validateStrictKeyFilePermissions(Path, LinkOption...)
+ */
+ public static Map<String, Path> scanIdentitiesFolder(
+ Path dir, boolean strict, Collection<String> types, Function<String, String> idGenerator, LinkOption... options)
+ throws IOException {
+ if (GenericUtils.isEmpty(types)) {
+ return Collections.emptyMap();
+ }
+
+ if (!Files.exists(dir, options)) {
+ return Collections.emptyMap();
+ }
+
+ ValidateUtils.checkTrue(FileInfoExtractor.ISDIR.infoOf(dir, options), "Not a directory: %s", dir);
+
+ Map<String, Path> paths = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ for (String t : types) {
+ String fileName = idGenerator.apply(t);
+ Path p = dir.resolve(fileName);
+ if (!Files.exists(p, options)) {
+ continue;
+ }
+
+ if (strict) {
+ if (KeyUtils.validateStrictKeyFilePermissions(p, options) != null) {
+ continue;
+ }
+ }
+
+ Path prev = paths.put(t, p);
+ ValidateUtils.checkTrue(prev == null, "Multiple mappings for type=%s", t);
+ }
+
+ return paths;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java
new file mode 100644
index 0000000..a00cb24
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java
@@ -0,0 +1,141 @@
+/*
+ * 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.client.config.keys;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.ModifiableFileWatcher;
+
+/**
+ * A {@link ClientIdentityProvider} that watches a given key file re-loading
+ * its contents if it is ever modified, deleted or (re-)created
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ClientIdentityFileWatcher extends ModifiableFileWatcher implements ClientIdentityProvider {
+ private final AtomicReference<KeyPair> identityHolder = new AtomicReference<>(null);
+ private final Supplier<ClientIdentityLoader> loaderHolder;
+ private final Supplier<FilePasswordProvider> providerHolder;
+ private final boolean strict;
+
+ public ClientIdentityFileWatcher(Path path, ClientIdentityLoader loader, FilePasswordProvider provider) {
+ this(path, loader, provider, true);
+ }
+
+ public ClientIdentityFileWatcher(Path path, ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
+ this(path,
+ GenericUtils.supplierOf(Objects.requireNonNull(loader, "No client identity loader")),
+ GenericUtils.supplierOf(Objects.requireNonNull(provider, "No password provider")),
+ strict);
+ }
+
+ public ClientIdentityFileWatcher(Path path, Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider) {
+ this(path, loader, provider, true);
+ }
+
+ public ClientIdentityFileWatcher(Path path, Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider, boolean strict) {
+ super(path);
+ this.loaderHolder = Objects.requireNonNull(loader, "No client identity loader");
+ this.providerHolder = Objects.requireNonNull(provider, "No password provider");
+ this.strict = strict;
+ }
+
+ public final boolean isStrict() {
+ return strict;
+ }
+
+ public final ClientIdentityLoader getClientIdentityLoader() {
+ return loaderHolder.get();
+ }
+
+ public final FilePasswordProvider getFilePasswordProvider() {
+ return providerHolder.get();
+ }
+
+ @Override
+ public KeyPair getClientIdentity() throws IOException, GeneralSecurityException {
+ if (checkReloadRequired()) {
+ KeyPair kp = identityHolder.getAndSet(null); // start fresh
+ Path path = getPath();
+
+ if (exists()) {
+ KeyPair id = reloadClientIdentity(path);
+ if (!KeyUtils.compareKeyPairs(kp, id)) {
+ if (log.isDebugEnabled()) {
+ log.debug("getClientIdentity({}) identity {}", path, (kp == null) ? "loaded" : "re-loaded");
+ }
+ }
+
+ updateReloadAttributes();
+ identityHolder.set(id);
+ }
+ }
+
+ return identityHolder.get();
+ }
+
+ protected KeyPair reloadClientIdentity(Path path) throws IOException, GeneralSecurityException {
+ if (isStrict()) {
+ Map.Entry<String, Object> violation = KeyUtils.validateStrictKeyFilePermissions(path, IoUtils.EMPTY_LINK_OPTIONS);
+ if (violation != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("reloadClientIdentity({}) ignore due to {}", path, violation.getKey());
+ }
+ return null;
+ }
+ }
+
+ String location = path.toString();
+ ClientIdentityLoader idLoader = Objects.requireNonNull(getClientIdentityLoader(), "No client identity loader");
+ if (idLoader.isValidLocation(location)) {
+ KeyPair kp = idLoader.loadClientIdentity(location, Objects.requireNonNull(getFilePasswordProvider(), "No file password provider"));
+ if (log.isTraceEnabled()) {
+ PublicKey key = (kp == null) ? null : kp.getPublic();
+ if (key != null) {
+ log.trace("reloadClientIdentity({}) loaded {}-{}",
+ location, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key));
+
+ } else {
+ log.trace("reloadClientIdentity({}) no key loaded", location);
+ }
+ }
+
+ return kp;
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("reloadClientIdentity({}) invalid location", location);
+ }
+
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
new file mode 100644
index 0000000..8b3a295
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
@@ -0,0 +1,95 @@
+/*
+ * 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.client.config.keys;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ClientIdentityLoader {
+ /**
+ * <P>A default implementation that assumes a file location that <U>must</U> exist.</P>
+ *
+ * <P>
+ * <B>Note:</B> It calls {@link SecurityUtils#loadKeyPairIdentity(String, InputStream, FilePasswordProvider)}
+ * </P>
+ */
+ ClientIdentityLoader DEFAULT = new ClientIdentityLoader() {
+ @Override
+ public boolean isValidLocation(String location) throws IOException {
+ Path path = toPath(location);
+ return Files.exists(path, IoUtils.EMPTY_LINK_OPTIONS);
+ }
+
+ @Override
+ public KeyPair loadClientIdentity(String location, FilePasswordProvider provider) throws IOException, GeneralSecurityException {
+ Path path = toPath(location);
+ try (InputStream inputStream = Files.newInputStream(path, IoUtils.EMPTY_OPEN_OPTIONS)) {
+ return SecurityUtils.loadKeyPairIdentity(path.toString(), inputStream, provider);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DEFAULT";
+ }
+
+ private Path toPath(String location) {
+ Path path = Paths.get(ValidateUtils.checkNotNullAndNotEmpty(location, "No location"));
+ path = path.toAbsolutePath();
+ path = path.normalize();
+ return path;
+ }
+ };
+
+ /**
+ * @param location The identity key-pair location - the actual meaning (file, URL, etc.)
+ * depends on the implementation.
+ * @return {@code true} if it represents a valid location - the actual meaning of
+ * the validity depends on the implementation
+ * @throws IOException If failed to validate the location
+ */
+ boolean isValidLocation(String location) throws IOException;
+
+ /**
+ * @param location The identity key-pair location - the actual meaning (file, URL, etc.)
+ * depends on the implementation.
+ * @param provider The {@link FilePasswordProvider} to consult if the location contains
+ * an encrypted identity
+ * @return The loaded {@link KeyPair} - {@code null} if location is empty
+ * and it is OK that it does not exist
+ * @throws IOException If failed to access / process the remote location
+ * @throws GeneralSecurityException If failed to convert the contents into
+ * a valid identity
+ */
+ KeyPair loadClientIdentity(String location, FilePasswordProvider provider) throws IOException, GeneralSecurityException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.java
new file mode 100644
index 0000000..bda4700
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.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.client.config.keys;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface ClientIdentityProvider {
+ /**
+ * Provides a {@link KeyPair} representing the client identity
+ *
+ * @return The client identity - may be {@code null} if no currently
+ * available identity from this provider. <B>Note:</B> the provider
+ * may return a <U>different</U> value every time this method is called
+ * - e.g., if it is (re-)loading contents from a file.
+ * @throws IOException If failed to load the identity
+ * @throws GeneralSecurityException If failed to parse the identity
+ */
+ KeyPair getClientIdentity() throws IOException, GeneralSecurityException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/client/config/keys/DefaultClientIdentitiesWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/DefaultClientIdentitiesWatcher.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/DefaultClientIdentitiesWatcher.java
new file mode 100644
index 0000000..3afa129
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/DefaultClientIdentitiesWatcher.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.client.config.keys;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultClientIdentitiesWatcher extends BuiltinClientIdentitiesWatcher {
+ public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, FilePasswordProvider provider) {
+ this(loader, provider, true);
+ }
+
+ public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
+ this(true, loader, provider, strict);
+ }
+
+ public DefaultClientIdentitiesWatcher(boolean supportedOnly, ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
+ this(supportedOnly,
+ GenericUtils.supplierOf(Objects.requireNonNull(loader, "No client identity loader")),
+ GenericUtils.supplierOf(Objects.requireNonNull(provider, "No password provider")),
+ strict);
+ }
+
+ public DefaultClientIdentitiesWatcher(Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider) {
+ this(loader, provider, true);
+ }
+
+ public DefaultClientIdentitiesWatcher(Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider, boolean strict) {
+ this(true, loader, provider, strict);
+ }
+
+ public DefaultClientIdentitiesWatcher(boolean supportedOnly,
+ Supplier<ClientIdentityLoader> loader, Supplier<FilePasswordProvider> provider, boolean strict) {
+ super(PublicKeyEntry.getDefaultKeysFolderPath(), supportedOnly, loader, provider, strict);
+ }
+
+ public static List<Path> getDefaultBuiltinIdentitiesPaths() {
+ return getDefaultBuiltinIdentitiesPaths(PublicKeyEntry.getDefaultKeysFolderPath());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/AttributeStore.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/AttributeStore.java b/sshd-common/src/main/java/org/apache/sshd/common/AttributeStore.java
new file mode 100644
index 0000000..96f6ab1
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/AttributeStore.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+/**
+ * Provides the capability to attach in-memory attributes to the entity
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface AttributeStore {
+ /**
+ * <P>
+ * Type safe key for storage of user attributes. Typically it is used as a static
+ * variable that is shared between the producer and the consumer. To further
+ * restrict access the setting or getting it from the store one can add static
+ * {@code get/set methods} e.g:
+ * </P>
+ *
+ * <pre>
+ * public static final AttributeKey<MyValue> MY_KEY = new AttributeKey<MyValue>();
+ *
+ * public static MyValue getMyValue(Session s) {
+ * return s.getAttribute(MY_KEY);
+ * }
+ *
+ * public static void setMyValue(Session s, MyValue value) {
+ * s.setAttribute(MY_KEY, value);
+ * }
+ * </pre>
+ *
+ * @param <T> type of value stored in the attribute.
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+ // CHECKSTYLE:OFF
+ class AttributeKey<T> {
+ public AttributeKey() {
+ super();
+ }
+ }
+ // CHECKSTYLE:ON
+
+ /**
+ * Returns the value of the user-defined attribute.
+ *
+ * @param <T> The generic attribute type
+ * @param key The key of the attribute; must not be {@code null}.
+ * @return {@code null} if there is no value associated with the specified key
+ */
+ <T> T getAttribute(AttributeKey<T> key);
+
+ /**
+ * Sets a user-defined attribute.
+ *
+ * @param <T> The generic attribute type
+ * @param key The key of the attribute; must not be {@code null}.
+ * @param value The value of the attribute; must not be {@code null}.
+ * @return The old value of the attribute; {@code null} if it is new.
+ */
+ <T> T setAttribute(AttributeKey<T> key, T value);
+
+ /**
+ * Removes the user-defined attribute
+ *
+ * @param <T> The generic attribute type
+ * @param key The key of the attribute; must not be {@code null}.
+ * @return The removed value; {@code null} if no previous value
+ */
+ <T> T removeAttribute(AttributeKey<T> key);
+
+ /**
+ * Attempts to resolve the associated value by going up the store's
+ * hierarchy (if any)
+ *
+ * @param <T> The generic attribute type
+ * @param key The key of the attribute; must not be {@code null}.
+ * @return {@code null} if there is no value associated with the specified key
+ */
+ <T> T resolveAttribute(AttributeKey<T> key);
+}
+
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/BuiltinFactory.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/BuiltinFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/BuiltinFactory.java
new file mode 100644
index 0000000..33b53a9
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/BuiltinFactory.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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * A named optional factory.
+ *
+ * @param <T> The create object instance type
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface BuiltinFactory<T> extends NamedFactory<T>, OptionalFeature {
+ static <T, E extends BuiltinFactory<T>> List<NamedFactory<T>> setUpFactories(
+ boolean ignoreUnsupported, Collection<? extends E> preferred) {
+ return GenericUtils.stream(preferred)
+ .filter(f -> ignoreUnsupported || f.isSupported())
+ .collect(Collectors.toList());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/Closeable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/Closeable.java b/sshd-common/src/main/java/org/apache/sshd/common/Closeable.java
new file mode 100644
index 0000000..6a6f622
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/Closeable.java
@@ -0,0 +1,126 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.channels.Channel;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+
+/**
+ * A {@code Closeable} is a resource that can be closed.
+ * The close method is invoked to release resources that the object is
+ * holding. The user can pre-register listeners to be notified
+ * when resource close is completed (successfully or otherwise)
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface Closeable extends Channel {
+
+ /**
+ * Timeout (milliseconds) for waiting on a {@link CloseFuture} to successfully
+ * complete its action.
+ * @see #DEFAULT_CLOSE_WAIT_TIMEOUT
+ */
+ String CLOSE_WAIT_TIMEOUT = "sshd-close-wait-time";
+
+ /**
+ * Default value for {@link #CLOSE_WAIT_TIMEOUT} if none specified
+ */
+ long DEFAULT_CLOSE_WAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(15L);
+
+ /**
+ * Close this resource asynchronously and return a future.
+ * Resources support two closing modes: a graceful mode
+ * which will cleanly close the resource and an immediate mode
+ * which will close the resources abruptly.
+ *
+ * @param immediately <code>true</code> if the resource should be shut down abruptly,
+ * <code>false</code> for a graceful close
+ * @return a {@link CloseFuture} representing the close request
+ */
+ CloseFuture close(boolean immediately);
+
+ /**
+ * Pre-register a listener to be informed when resource is closed. If
+ * resource is already closed, the listener will be invoked immediately
+ * and not registered for future notification
+ *
+ * @param listener The notification {@link SshFutureListener} - never {@code null}
+ */
+ void addCloseFutureListener(SshFutureListener<CloseFuture> listener);
+
+ /**
+ * Remove a pre-registered close event listener
+ *
+ * @param listener The register {@link SshFutureListener} - never {@code null}.
+ * Ignored if not registered or resource already closed
+ */
+ void removeCloseFutureListener(SshFutureListener<CloseFuture> listener);
+
+ /**
+ * Returns <code>true</code> if this object has been closed.
+ *
+ * @return <code>true</code> if closing
+ */
+ boolean isClosed();
+
+ /**
+ * Returns <code>true</code> if the {@link #close(boolean)} method
+ * has been called. Note that this method will return <code>true</code>
+ * even if this {@link #isClosed()} returns <code>true</code>.
+ *
+ * @return <code>true</code> if closing
+ */
+ boolean isClosing();
+
+ @Override
+ default boolean isOpen() {
+ return !(isClosed() || isClosing());
+ }
+
+ @Override
+ default void close() throws IOException {
+ Closeable.close(this);
+ }
+
+ static long getMaxCloseWaitTime(PropertyResolver resolver) {
+ return (resolver == null) ? DEFAULT_CLOSE_WAIT_TIMEOUT
+ : resolver.getLongProperty(CLOSE_WAIT_TIMEOUT, DEFAULT_CLOSE_WAIT_TIMEOUT);
+ }
+
+ static void close(Closeable closeable) throws IOException {
+ if (closeable == null) {
+ return;
+ }
+
+ if ((!closeable.isClosed()) && (!closeable.isClosing())) {
+ CloseFuture future = closeable.close(true);
+ long maxWait = (closeable instanceof PropertyResolver)
+ ? getMaxCloseWaitTime((PropertyResolver) closeable) : DEFAULT_CLOSE_WAIT_TIMEOUT;
+ boolean successful = future.await(maxWait);
+ if (!successful) {
+ throw new SocketTimeoutException("Failed to receive closure confirmation within " + maxWait + " millis");
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/Factory.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/Factory.java b/sshd-common/src/main/java/org/apache/sshd/common/Factory.java
new file mode 100644
index 0000000..93b351c
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/Factory.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import java.util.function.Supplier;
+
+/**
+ * Factory is a simple interface that is used to create other objects.
+ *
+ * @param <T> type of object this factory will create
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface Factory<T> extends Supplier<T> {
+
+ @Override
+ default T get() {
+ return create();
+ }
+
+ /**
+ * @return A new instance
+ */
+ T create();
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/NamedFactory.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/NamedFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/NamedFactory.java
new file mode 100644
index 0000000..5386447
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/NamedFactory.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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * A named factory is a factory identified by a name.
+ * Such names are used mainly in the algorithm negotiation at the beginning of the SSH connection.
+ *
+ * @param <T> The create object instance type
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface NamedFactory<T> extends Factory<T>, NamedResource {
+ /**
+ * Create an instance of the specified name by looking up the needed factory
+ * in the list.
+ *
+ * @param factories list of available factories
+ * @param name the factory name to use
+ * @param <T> type of object to create
+ * @return a newly created object or {@code null} if the factory is not in the list
+ */
+ static <T> T create(Collection<? extends NamedFactory<? extends T>> factories, String name) {
+ NamedFactory<? extends T> f = NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, factories);
+ if (f != null) {
+ return f.create();
+ } else {
+ return null;
+ }
+ }
+
+ static <S extends OptionalFeature, T, E extends NamedFactory<T>> List<NamedFactory<T>> setUpTransformedFactories(
+ boolean ignoreUnsupported, Collection<? extends S> preferred, Function<? super S, ? extends E> xform) {
+ return preferred.stream()
+ .filter(f -> ignoreUnsupported || f.isSupported())
+ .map(xform)
+ .collect(Collectors.toList());
+ }
+
+ static <T, E extends NamedFactory<T> & OptionalFeature> List<NamedFactory<T>> setUpBuiltinFactories(
+ boolean ignoreUnsupported, Collection<? extends E> preferred) {
+ return preferred.stream()
+ .filter(f -> ignoreUnsupported || f.isSupported())
+ .collect(Collectors.toList());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/NamedResource.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/NamedResource.java b/sshd-common/src/main/java/org/apache/sshd/common/NamedResource.java
new file mode 100644
index 0000000..813f53d
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/NamedResource.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface NamedResource {
+
+ /**
+ * Returns the value of {@link #getName()} - or {@code null} if argument is {@code null}
+ */
+ Function<NamedResource, String> NAME_EXTRACTOR = input -> input == null ? null : input.getName();
+
+ /**
+ * Compares 2 {@link NamedResource}s according to their {@link #getName()}
+ * value case <U>insensitive</U>
+ */
+ Comparator<NamedResource> BY_NAME_COMPARATOR = Comparator.comparing(NAME_EXTRACTOR, String.CASE_INSENSITIVE_ORDER);
+
+ /**
+ * @return The resource name
+ */
+ String getName();
+
+ /**
+ * @param resources The named resources
+ * @return A {@link List} of all the factories names - in same order
+ * as they appear in the input collection
+ */
+ static List<String> getNameList(Collection<? extends NamedResource> resources) {
+ return GenericUtils.map(resources, NamedResource::getName);
+ }
+
+ /**
+ * @param resources list of available resources
+ * @return A comma separated list of factory names
+ */
+ static String getNames(Collection<? extends NamedResource> resources) {
+ Collection<String> nameList = getNameList(resources);
+ return GenericUtils.join(nameList, ',');
+ }
+
+ /**
+ * Remove the resource identified by the name from the list.
+ *
+ * @param <R> The generic resource type
+ * @param name Name of the resource - ignored if {@code null}/empty
+ * @param c The {@link Comparator} to decide whether the {@link NamedResource#getName()}
+ * matches the <tt>name</tt> parameter
+ * @param resources The {@link NamedResource} to check - ignored if {@code null}/empty
+ * @return the removed resource from the list or {@code null} if not in the list
+ */
+ static <R extends NamedResource> R removeByName(String name, Comparator<? super String> c, Collection<? extends R> resources) {
+ R r = findByName(name, c, resources);
+ if (r != null) {
+ resources.remove(r);
+ }
+ return r;
+ }
+
+ /**
+ * @param <R> The generic resource type
+ * @param name Name of the resource - ignored if {@code null}/empty
+ * @param c The {@link Comparator} to decide whether the {@link NamedResource#getName()}
+ * matches the <tt>name</tt> parameter
+ * @param resources The {@link NamedResource} to check - ignored if {@code null}/empty
+ * @return The <U>first</U> resource whose name matches the parameter (by invoking
+ * {@link Comparator#compare(Object, Object)} - {@code null} if no match found
+ */
+ static <R extends NamedResource> R findByName(String name, Comparator<? super String> c, Collection<? extends R> resources) {
+ return GenericUtils.isEmpty(name)
+ ? null
+ : GenericUtils.stream(resources)
+ .filter(r -> c.compare(name, r.getName()) == 0)
+ .findFirst()
+ .orElse(null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/OptionalFeature.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/OptionalFeature.java b/sshd-common/src/main/java/org/apache/sshd/common/OptionalFeature.java
new file mode 100644
index 0000000..1afa864
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/OptionalFeature.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import java.util.Collection;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface OptionalFeature {
+ OptionalFeature TRUE = new OptionalFeature() {
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "TRUE";
+ }
+ };
+
+ OptionalFeature FALSE = new OptionalFeature() {
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "FALSE";
+ }
+ };
+
+ boolean isSupported();
+
+ static OptionalFeature of(boolean supported) {
+ return supported ? TRUE : FALSE;
+ }
+
+ static OptionalFeature all(Collection<? extends OptionalFeature> features) {
+ return () -> {
+ if (GenericUtils.isEmpty(features)) {
+ return false;
+ }
+
+ for (OptionalFeature f : features) {
+ if (!f.isSupported()) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+ }
+
+ static OptionalFeature any(Collection<? extends OptionalFeature> features) {
+ return () -> {
+ if (GenericUtils.isEmpty(features)) {
+ return false;
+ }
+
+ for (OptionalFeature f : features) {
+ if (f.isSupported()) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolver.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolver.java
new file mode 100644
index 0000000..c333c7f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolver.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Indicates an entity that can be configured using properties. The properties
+ * are simple name-value pairs where the actual value type depends on the
+ * property. Some automatic conversions may be available - e.g., from a string
+ * to a numeric or {@code boolean} value, or from {@code int} to {@code long},
+ * etc.. <B>Note:</B> implementations may decide to use case <U>insensitive</U>
+ * property names, therefore it is <U><B>highly discouraged</B></U> to use names
+ * that differ from each other only in case sensitivity. Also, implementations
+ * may choose to trim whitespaces, thus such are also highly discouraged.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PropertyResolver {
+ /**
+ * An "empty" resolver with no properties and no parent
+ */
+ PropertyResolver EMPTY = new PropertyResolver() {
+ @Override
+ public PropertyResolver getParentPropertyResolver() {
+ return null;
+ }
+
+ @Override
+ public Map<String, Object> getProperties() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public String toString() {
+ return "EMPTY";
+ }
+ };
+
+ /**
+ * @return The parent resolver that can be used to query for missing
+ * properties - {@code null} if no parent
+ */
+ PropertyResolver getParentPropertyResolver();
+
+ /**
+ * <P>
+ * A map of properties that can be used to configure the SSH server or
+ * client. This map will never be changed by either the server or client and
+ * is not supposed to be changed at runtime (changes are not bound to have
+ * any effect on a running client or server), though it may affect the
+ * creation of sessions later as these values are usually not cached.
+ * </P>
+ *
+ * <P>
+ * <B>Note:</B> the <U>type</U> of the mapped property should match the
+ * expected configuration value type - {@code Long, Integer, Boolean,
+ * String}, etc.... If it doesn't, the {@code toString()} result of the
+ * mapped value is used to convert it to the required type. E.g., if the
+ * mapped value is the <U>string</U> "1234" and the expected value
+ * is a {@code long} then it will be parsed into one. Also, if the mapped
+ * value is an {@code Integer} but a {@code long} is expected, then it will
+ * be converted into one.
+ * </P>
+ *
+ * @return a valid <code>Map</code> containing configuration values, never
+ * {@code null}
+ */
+ Map<String, Object> getProperties();
+
+ default long getLongProperty(String name, long def) {
+ return PropertyResolverUtils.getLongProperty(this, name, def);
+ }
+
+ default Long getLong(String name) {
+ return PropertyResolverUtils.getLong(this, name);
+ }
+
+ default int getIntProperty(String name, int def) {
+ return PropertyResolverUtils.getIntProperty(this, name, def);
+ }
+
+ default Integer getInteger(String name) {
+ return PropertyResolverUtils.getInteger(this, name);
+ }
+
+ default boolean getBooleanProperty(String name, boolean def) {
+ return PropertyResolverUtils.getBooleanProperty(this, name, def);
+ }
+
+ default Boolean getBoolean(String name) {
+ return PropertyResolverUtils.getBoolean(this, name);
+ }
+
+ default String getStringProperty(String name, String def) {
+ return PropertyResolverUtils.getStringProperty(this, name, def);
+ }
+
+ default String getString(String name) {
+ return PropertyResolverUtils.getString(this, name);
+ }
+
+ default Object getObject(String name) {
+ return PropertyResolverUtils.getObject(this, name);
+ }
+}