From commits-return-11512-archive-asf-public=cust-asf.ponee.io@mina.apache.org Thu Sep 6 18:03:21 2018 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx-eu-01.ponee.io (Postfix) with SMTP id 3ABCF180789 for ; Thu, 6 Sep 2018 18:03:16 +0200 (CEST) Received: (qmail 19846 invoked by uid 500); 6 Sep 2018 16:03:15 -0000 Mailing-List: contact commits-help@mina.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@mina.apache.org Delivered-To: mailing list commits@mina.apache.org Received: (qmail 18005 invoked by uid 99); 6 Sep 2018 16:03:13 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 06 Sep 2018 16:03:13 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 99739E11C8; Thu, 6 Sep 2018 16:03:12 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: lgoldstein@apache.org To: commits@mina.apache.org Date: Thu, 06 Sep 2018 16:03:59 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [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 Apache MINA SSHD Project + */ +public class KnownHostHashValue { + /** + * Character used to indicate a hashed host pattern + */ + public static final char HASHED_HOST_DELIMITER = '|'; + + public static final NamedFactory DEFAULT_DIGEST = KnownHostDigest.SHA1; + + private NamedFactory digester = DEFAULT_DIGEST; + private byte[] saltValue; + private byte[] digestValue; + + public KnownHostHashValue() { + super(); + } + + public NamedFactory getDigester() { + return digester; + } + + public void setDigester(NamedFactory 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 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 append(A sb, KnownHostHashValue hashValue) throws IOException { + return (hashValue == null) ? sb : append(sb, hashValue.getDigester(), hashValue.getSaltValue(), hashValue.getDigestValue()); + } + + public static 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 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 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 Apache MINA SSHD Project + */ +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 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 loader, Supplier provider, boolean strict) { + this(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES), supportedOnly, loader, provider, strict); + } + + public BuiltinClientIdentitiesWatcher(Path keysFolder, Collection ids, boolean supportedOnly, + Supplier loader, Supplier provider, boolean strict) { + super(getBuiltinIdentitiesPaths(keysFolder, ids), loader, provider, strict); + this.supportedOnly = supportedOnly; + } + + public final boolean isSupportedOnly() { + return supportedOnly; + } + + @Override + public Iterable 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 getDefaultBuiltinIdentitiesPaths(Path keysFolder) { + return getBuiltinIdentitiesPaths(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES)); + } + + public static List getBuiltinIdentitiesPaths(Path keysFolder, Collection ids) { + Objects.requireNonNull(keysFolder, "No keys folder"); + if (GenericUtils.isEmpty(ids)) { + return Collections.emptyList(); + } + + List 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 Apache MINA SSHD Project + */ +public class ClientIdentitiesWatcher extends AbstractKeyPairProvider implements KeyPairProvider { + private final Collection providers; + + public ClientIdentitiesWatcher(Collection paths, + ClientIdentityLoader loader, FilePasswordProvider provider) { + this(paths, loader, provider, true); + } + + public ClientIdentitiesWatcher(Collection 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 paths, + Supplier loader, Supplier provider) { + this(paths, loader, provider, true); + } + + public ClientIdentitiesWatcher(Collection paths, + Supplier loader, Supplier provider, boolean strict) { + this(buildProviders(paths, loader, provider, strict)); + } + + public ClientIdentitiesWatcher(Collection providers) { + this.providers = providers; + } + + @Override + public Iterable loadKeys() { + return loadKeys(null); + } + + protected Iterable loadKeys(Predicate filter) { + return () -> { + Stream 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 Original type + * @param Mapped type + * @param stream Original values stream + * @param mapper Mapper to target type + * @return Mapped stream + */ + protected Stream safeMap(Stream stream, Function 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 buildProviders( + Collection 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 buildProviders( + Collection paths, Supplier loader, Supplier 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 Apache MINA SSHD Project + * @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 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 guaranteed 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 guaranteed 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 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 guaranteed 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 + * insensitive), 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 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 guaranteed 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 + * insensitive), 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 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 guaranteed 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 + * insensitive), 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 loadIdentities( + Path dir, boolean strict, Collection types, Function idGenerator, FilePasswordProvider provider, LinkOption... options) + throws IOException, GeneralSecurityException { + Map 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 + * insensitive), 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 scanIdentitiesFolder( + Path dir, boolean strict, Collection types, Function 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 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 Apache MINA SSHD Project + */ +public class ClientIdentityFileWatcher extends ModifiableFileWatcher implements ClientIdentityProvider { + private final AtomicReference identityHolder = new AtomicReference<>(null); + private final Supplier loaderHolder; + private final Supplier 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 loader, Supplier provider) { + this(path, loader, provider, true); + } + + public ClientIdentityFileWatcher(Path path, Supplier loader, Supplier 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 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 Apache MINA SSHD Project + */ +public interface ClientIdentityLoader { + /** + *

A default implementation that assumes a file location that must exist.

+ * + *

+ * Note: It calls {@link SecurityUtils#loadKeyPairIdentity(String, InputStream, FilePasswordProvider)} + *

+ */ + 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 Apache MINA SSHD Project + */ +@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. Note: the provider + * may return a different 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 Apache MINA SSHD Project + */ +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 loader, Supplier provider) { + this(loader, provider, true); + } + + public DefaultClientIdentitiesWatcher(Supplier loader, Supplier provider, boolean strict) { + this(true, loader, provider, strict); + } + + public DefaultClientIdentitiesWatcher(boolean supportedOnly, + Supplier loader, Supplier provider, boolean strict) { + super(PublicKeyEntry.getDefaultKeysFolderPath(), supportedOnly, loader, provider, strict); + } + + public static List 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 Apache MINA SSHD Project + */ +public interface AttributeStore { + /** + *

+ * 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: + *

+ * + *
+     * 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);
+     * }
+     * 
+ * + * @param type of value stored in the attribute. + * @author Apache MINA SSHD Project + */ + // CHECKSTYLE:OFF + class AttributeKey { + public AttributeKey() { + super(); + } + } + // CHECKSTYLE:ON + + /** + * Returns the value of the user-defined attribute. + * + * @param 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 getAttribute(AttributeKey key); + + /** + * Sets a user-defined attribute. + * + * @param 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 setAttribute(AttributeKey key, T value); + + /** + * Removes the user-defined attribute + * + * @param 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 removeAttribute(AttributeKey key); + + /** + * Attempts to resolve the associated value by going up the store's + * hierarchy (if any) + * + * @param 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 resolveAttribute(AttributeKey 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 The create object instance type + * @author Apache MINA SSHD Project + */ +public interface BuiltinFactory extends NamedFactory, OptionalFeature { + static > List> setUpFactories( + boolean ignoreUnsupported, Collection 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 Apache MINA SSHD Project + */ +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 true if the resource should be shut down abruptly, + * false 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 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 listener); + + /** + * Returns true if this object has been closed. + * + * @return true if closing + */ + boolean isClosed(); + + /** + * Returns true if the {@link #close(boolean)} method + * has been called. Note that this method will return true + * even if this {@link #isClosed()} returns true. + * + * @return true 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 type of object this factory will create + * @author Apache MINA SSHD Project + */ +@FunctionalInterface +public interface Factory extends Supplier { + + @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 The create object instance type + * @author Apache MINA SSHD Project + */ +public interface NamedFactory extends Factory, 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 type of object to create + * @return a newly created object or {@code null} if the factory is not in the list + */ + static T create(Collection> factories, String name) { + NamedFactory f = NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, factories); + if (f != null) { + return f.create(); + } else { + return null; + } + } + + static > List> setUpTransformedFactories( + boolean ignoreUnsupported, Collection preferred, Function xform) { + return preferred.stream() + .filter(f -> ignoreUnsupported || f.isSupported()) + .map(xform) + .collect(Collectors.toList()); + } + + static & OptionalFeature> List> setUpBuiltinFactories( + boolean ignoreUnsupported, Collection 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 Apache MINA SSHD Project + */ +@FunctionalInterface +public interface NamedResource { + + /** + * Returns the value of {@link #getName()} - or {@code null} if argument is {@code null} + */ + Function NAME_EXTRACTOR = input -> input == null ? null : input.getName(); + + /** + * Compares 2 {@link NamedResource}s according to their {@link #getName()} + * value case insensitive + */ + Comparator 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 getNameList(Collection 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 resources) { + Collection nameList = getNameList(resources); + return GenericUtils.join(nameList, ','); + } + + /** + * Remove the resource identified by the name from the list. + * + * @param 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 name 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 removeByName(String name, Comparator c, Collection resources) { + R r = findByName(name, c, resources); + if (r != null) { + resources.remove(r); + } + return r; + } + + /** + * @param 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 name parameter + * @param resources The {@link NamedResource} to check - ignored if {@code null}/empty + * @return The first resource whose name matches the parameter (by invoking + * {@link Comparator#compare(Object, Object)} - {@code null} if no match found + */ + static R findByName(String name, Comparator c, Collection 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 Apache MINA SSHD Project + */ +@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 features) { + return () -> { + if (GenericUtils.isEmpty(features)) { + return false; + } + + for (OptionalFeature f : features) { + if (!f.isSupported()) { + return false; + } + } + + return true; + }; + } + + static OptionalFeature any(Collection 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.. Note: implementations may decide to use case insensitive + * property names, therefore it is highly discouraged 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 Apache MINA SSHD Project + */ +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 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(); + + /** + *

+ * 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. + *

+ * + *

+ * Note: the type 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 string "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. + *

+ * + * @return a valid Map containing configuration values, never + * {@code null} + */ + Map 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); + } +}