http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/DefaultConfigFileHostEntryResolver.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/DefaultConfigFileHostEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/DefaultConfigFileHostEntryResolver.java deleted file mode 100644 index da5f687..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/DefaultConfigFileHostEntryResolver.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.File; -import java.io.IOException; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import org.apache.sshd.common.util.io.IoUtils; - -/** - * Monitors the {@code ~/.ssh/config} file of the user currently running - * the client, re-loading it if necessary. It also (optionally) enforces - * the same permissions regime as {@code OpenSSH} - * - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public class DefaultConfigFileHostEntryResolver extends ConfigFileHostEntryResolver { - /** - * The default instance that enforces the same permissions regime as {@code OpenSSH} - */ - public static final DefaultConfigFileHostEntryResolver INSTANCE = new DefaultConfigFileHostEntryResolver(true); - - private final boolean strict; - - /** - * @param strict If {@code true} then makes sure that the containing folder - * has 0700 access and the file 0644. <B>Note:</B> for <I>Windows</I> it - * does not check these permissions - * @see #validateStrictConfigFilePermissions(Path, LinkOption...) - */ - public DefaultConfigFileHostEntryResolver(boolean strict) { - this(HostConfigEntry.getDefaultHostConfigFile(), strict); - } - - public DefaultConfigFileHostEntryResolver(File file, boolean strict) { - this(Objects.requireNonNull(file, "No file provided").toPath(), strict, IoUtils.getLinkOptions(true)); - } - - public DefaultConfigFileHostEntryResolver(Path path, boolean strict, LinkOption... options) { - super(path, options); - this.strict = strict; - } - - /** - * @return If {@code true} then makes sure that the containing folder - * has 0700 access and the file 0644. <B>Note:</B> for <I>Windows</I> it - * does not check these permissions - * @see #validateStrictConfigFilePermissions(Path, LinkOption...) - */ - public final boolean isStrict() { - return strict; - } - - @Override - protected List<HostConfigEntry> reloadHostConfigEntries(Path path, String host, int port, String username) throws IOException { - if (isStrict()) { - if (log.isDebugEnabled()) { - log.debug("reloadHostConfigEntries({}@{}:{}) check permissions of {}", username, host, port, path); - } - - Map.Entry<String, ?> violation = validateStrictConfigFilePermissions(path); - if (violation != null) { - log.warn("reloadHostConfigEntries({}@{}:{}) invalid file={} permissions: {}", - username, host, port, path, violation.getKey()); - updateReloadAttributes(); - return Collections.emptyList(); - } - } - - return super.reloadHostConfigEntries(path, host, port, username); - } -}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java deleted file mode 100644 index f401cee..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java +++ /dev/null @@ -1,1169 +0,0 @@ -/* - * 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.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.StreamCorruptedException; -import java.io.Writer; -import java.net.InetAddress; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; - -import org.apache.sshd.common.auth.MutableUserHolder; -import org.apache.sshd.common.config.SshConfigFileReader; -import org.apache.sshd.common.config.keys.IdentityUtils; -import org.apache.sshd.common.config.keys.PublicKeyEntry; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.OsUtils; -import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.common.util.io.NoCloseInputStream; -import org.apache.sshd.common.util.io.NoCloseOutputStream; -import org.apache.sshd.common.util.io.NoCloseReader; - -/** - * Represents an entry in the client's configuration file as defined by - * the <A HREF="http://www.gsp.com/cgi-bin/man.cgi?topic=ssh_config">configuration - * file format</A> - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public class HostConfigEntry extends HostPatternsHolder implements MutableUserHolder { - /** - * Standard OpenSSH config file name - */ - public static final String STD_CONFIG_FILENAME = "config"; - - public static final String HOST_CONFIG_PROP = "Host"; - public static final String HOST_NAME_CONFIG_PROP = "HostName"; - public static final String PORT_CONFIG_PROP = SshConfigFileReader.PORT_CONFIG_PROP; - public static final String USER_CONFIG_PROP = "User"; - public static final String IDENTITY_FILE_CONFIG_PROP = "IdentityFile"; - /** - * Use only the identities specified in the host entry (if any) - */ - public static final String EXCLUSIVE_IDENTITIES_CONFIG_PROP = "IdentitiesOnly"; - public static final boolean DEFAULT_EXCLUSIVE_IDENTITIES = false; - - /** - * A case <U>insensitive</U> {@link Set} of the properties that receive special handling - */ - public static final Set<String> EXPLICIT_PROPERTIES = - Collections.unmodifiableSet( - GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, - HOST_CONFIG_PROP, HOST_NAME_CONFIG_PROP, PORT_CONFIG_PROP, - USER_CONFIG_PROP, IDENTITY_FILE_CONFIG_PROP, EXCLUSIVE_IDENTITIES_CONFIG_PROP - )); - - public static final String MULTI_VALUE_SEPARATORS = " ,"; - - public static final char HOME_TILDE_CHAR = '~'; - public static final char PATH_MACRO_CHAR = '%'; - public static final char LOCAL_HOME_MACRO = 'd'; - public static final char LOCAL_USER_MACRO = 'u'; - public static final char LOCAL_HOST_MACRO = 'l'; - public static final char REMOTE_HOST_MACRO = 'h'; - public static final char REMOTE_USER_MACRO = 'r'; - // Extra - not part of the standard - public static final char REMOTE_PORT_MACRO = 'p'; - - private static final class LazyDefaultConfigFileHolder { - private static final Path CONFIG_FILE = - PublicKeyEntry.getDefaultKeysFolderPath().resolve(STD_CONFIG_FILENAME); - - private LazyDefaultConfigFileHolder() { - throw new UnsupportedOperationException("No instance allowed"); - } - } - - private String host; - private String hostName; - private int port; - private String username; - private Boolean exclusiveIdentites; - private Collection<String> identities = Collections.emptyList(); - private Map<String, String> properties = Collections.emptyMap(); - - public HostConfigEntry() { - super(); - } - - public HostConfigEntry(String pattern, String host, int port, String username) { - setHost(pattern); - setHostName(host); - setPort(port); - setUsername(username); - } - - /** - * @return The <U>pattern(s)</U> represented by this entry - */ - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - setPatterns(parsePatterns(parseConfigValue(host))); - } - - public void setHost(Collection<String> patterns) { - this.host = GenericUtils.join(ValidateUtils.checkNotNullAndNotEmpty(patterns, "No patterns"), ','); - setPatterns(parsePatterns(patterns)); - } - - /** - * @return The effective host name to connect to if the pattern matches - */ - public String getHostName() { - return hostName; - } - - public void setHostName(String hostName) { - this.hostName = hostName; - } - - public String resolveHostName(String originalHost) { - return resolveHostName(originalHost, getHostName()); - } - - /** - * @return A port override - if positive - */ - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - /** - * Resolves the effective port to use - * - * @param originalPort The original requested port - * @return If the host entry port is positive, then it is used, otherwise - * the original requested port - * @see #resolvePort(int, int) - */ - public int resolvePort(int originalPort) { - return resolvePort(originalPort, getPort()); - } - - /** - * @return A username override - if not {@code null}/empty - */ - @Override - public String getUsername() { - return username; - } - - @Override - public void setUsername(String username) { - this.username = username; - } - - /** - * Resolves the effective username - * - * @param originalUser The original requested username - * @return If the configured host entry username is not {@code null}/empty - * then it is used, otherwise the original one. - * @see #resolveUsername(String) - */ - public String resolveUsername(String originalUser) { - return resolveUsername(originalUser, getUsername()); - } - - /** - * @return The current identities file paths - may be {@code null}/empty - */ - public Collection<String> getIdentities() { - return identities; - } - - /** - * @param file A {@link File} that contains an identity key - never {@code null} - */ - public void addIdentity(File file) { - addIdentity(Objects.requireNonNull(file, "No file").toPath()); - } - - /** - * @param path A {@link Path} to a file that contains an identity key - * - never {@code null} - */ - public void addIdentity(Path path) { - addIdentity(Objects.requireNonNull(path, "No path").toAbsolutePath().normalize().toString()); - } - - /** - * Adds a path to an identity file - * - * @param id The identity path to add - never {@code null} - */ - public void addIdentity(String id) { - String path = ValidateUtils.checkNotNullAndNotEmpty(id, "No identity provided"); - if (GenericUtils.isEmpty(identities)) { - identities = new LinkedList<>(); - } - identities.add(path); - } - - public void setIdentities(Collection<String> identities) { - this.identities = (identities == null) ? Collections.emptyList() : identities; - } - - /** - * @return {@code true} if must use only the identities in this entry - */ - public boolean isIdentitiesOnly() { - return (exclusiveIdentites == null) ? DEFAULT_EXCLUSIVE_IDENTITIES : exclusiveIdentites; - } - - public void setIdentitiesOnly(boolean identitiesOnly) { - exclusiveIdentites = identitiesOnly; - } - - /** - * @return A {@link Map} of extra properties that have been read - may be - * {@code null}/empty, or even contain some values that have been parsed - * and set as members of the entry (e.g., host, port, etc.). <B>Note:</B> - * multi-valued keys use a comma-separated list of values - */ - public Map<String, String> getProperties() { - return properties; - } - - /** - * @param name Property name - never {@code null}/empty - * @return Property value or {@code null} if no such property - * @see #getProperty(String, String) - */ - public String getProperty(String name) { - return getProperty(name, null); - } - - /** - * @param name Property name - never {@code null}/empty - * @param defaultValue Default value to return if no such property - * @return The property value or the default one if no such property - */ - public String getProperty(String name, String defaultValue) { - String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); - Map<String, String> props = getProperties(); - if (GenericUtils.isEmpty(props)) { - return null; - } - - String value = props.get(key); - if (GenericUtils.isEmpty(value)) { - return defaultValue; - } else { - return value; - } - } - - /** - * Updates the values that are <U>not</U> already configured with those - * from the global entry - * - * @param globalEntry The global entry - ignored if {@code null} or - * same reference as this entry - * @return {@code true} if anything updated - */ - public boolean processGlobalValues(HostConfigEntry globalEntry) { - if ((globalEntry == null) || (this == globalEntry)) { - return false; - } - - boolean modified = false; - /* - * NOTE !!! DO NOT TRY TO CHANGE THE ORDER OF THE OR-ing AS IT - * WOULD CAUSE INVALID CODE EXECUTION - */ - modified = updateGlobalPort(globalEntry.getPort()) || modified; - modified = updateGlobalHostName(globalEntry.getHostName()) || modified; - modified = updateGlobalUserName(globalEntry.getUsername()) || modified; - modified = updateGlobalIdentities(globalEntry.getIdentities()) || modified; - modified = updateGlobalIdentityOnly(globalEntry.isIdentitiesOnly()) || modified; - - Map<String, String> updated = updateGlobalProperties(globalEntry.getProperties()); - modified = (GenericUtils.size(updated) > 0) || modified; - - return modified; - } - - /** - * Sets all the properties for which no current value exists in the entry - * - * @param props The global properties - ignored if {@code null}/empty - * @return A {@link Map} of the <U>updated</U> properties - */ - public Map<String, String> updateGlobalProperties(Map<String, String> props) { - if (GenericUtils.isEmpty(props)) { - return Collections.emptyMap(); - } - - Map<String, String> updated = null; - // Cannot use forEach because of the modification of the updated map value (non-final) - for (Map.Entry<String, String> pe : props.entrySet()) { - String key = pe.getKey(); - String curValue = getProperty(key); - if (GenericUtils.length(curValue) > 0) { - continue; - } - - String newValue = pe.getValue(); - setProperty(key, newValue); - - if (updated == null) { - updated = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - } - - updated.put(key, newValue); - } - - if (updated == null) { - return Collections.emptyMap(); - } else { - return updated; - } - } - - /** - * @param ids Global identities - ignored if {@code null}/empty or already - * have configured identities - * @return {@code true} if updated identities - */ - public boolean updateGlobalIdentities(Collection<String> ids) { - if (GenericUtils.isEmpty(ids) || (GenericUtils.size(getIdentities()) > 0)) { - return false; - } - - for (String id : ids) { - addIdentity(id); - } - - return true; - } - - /** - * @param user The global user name - ignored if {@code null}/empty or - * already have a configured user - * @return {@code true} if updated the username - */ - public boolean updateGlobalUserName(String user) { - if (GenericUtils.isEmpty(user) || (GenericUtils.length(getUsername()) > 0)) { - return false; - } - - setUsername(user); - return true; - } - - /** - * @param name The global host name - ignored if {@code null}/empty or - * already have a configured target host - * @return {@code true} if updated the target host - */ - public boolean updateGlobalHostName(String name) { - if (GenericUtils.isEmpty(name) || (GenericUtils.length(getHostName()) > 0)) { - return false; - } - - setHostName(name); - return true; - } - - /** - * @param portValue The global port value - ignored if not positive - * or already have a configured port - * @return {@code true} if updated the port value - */ - public boolean updateGlobalPort(int portValue) { - if ((portValue <= 0) || (getPort() > 0)) { - return false; - } - - setPort(portValue); - return true; - } - - /** - * @param identitiesOnly Whether to use only the identities in this entry. - * Ignored if already set - * @return {@code true} if updated the option value - */ - public boolean updateGlobalIdentityOnly(boolean identitiesOnly) { - if (exclusiveIdentites != null) { - return false; - } - - setIdentitiesOnly(identitiesOnly); - return true; - } - - /** - * @param name Property name - never {@code null}/empty - * @param valsList The available values for the property - * @param ignoreAlreadyInitialized If {@code false} and one of the "known" - * properties is encountered then throws an exception - * @throws IllegalArgumentException If an existing value is overwritten and - * <tt>ignoreAlreadyInitialized</tt> is {@code false} (except for {@link #IDENTITY_FILE_CONFIG_PROP} - * which is <U>cumulative</U> - * @see #HOST_NAME_CONFIG_PROP - * @see #PORT_CONFIG_PROP - * @see #USER_CONFIG_PROP - * @see #IDENTITY_FILE_CONFIG_PROP - */ - public void processProperty(String name, Collection<String> valsList, boolean ignoreAlreadyInitialized) { - String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); - String joinedValue = GenericUtils.join(valsList, ','); - appendPropertyValue(key, joinedValue); - - if (HOST_NAME_CONFIG_PROP.equalsIgnoreCase(key)) { - ValidateUtils.checkTrue(GenericUtils.size(valsList) == 1, "Multiple target hosts N/A: %s", joinedValue); - - String curValue = getHostName(); - ValidateUtils.checkTrue(GenericUtils.isEmpty(curValue) || ignoreAlreadyInitialized, "Already initialized %s: %s", key, curValue); - setHostName(joinedValue); - } else if (PORT_CONFIG_PROP.equalsIgnoreCase(key)) { - ValidateUtils.checkTrue(GenericUtils.size(valsList) == 1, "Multiple target ports N/A: %s", joinedValue); - - int curValue = getPort(); - ValidateUtils.checkTrue((curValue <= 0) || ignoreAlreadyInitialized, "Already initialized %s: %d", key, curValue); - - int newValue = Integer.parseInt(joinedValue); - ValidateUtils.checkTrue(newValue > 0, "Bad new port value: %d", newValue); - setPort(newValue); - } else if (USER_CONFIG_PROP.equalsIgnoreCase(key)) { - ValidateUtils.checkTrue(GenericUtils.size(valsList) == 1, "Multiple target users N/A: %s", joinedValue); - - String curValue = getUsername(); - ValidateUtils.checkTrue(GenericUtils.isEmpty(curValue) || ignoreAlreadyInitialized, "Already initialized %s: %s", key, curValue); - setUsername(joinedValue); - } else if (IDENTITY_FILE_CONFIG_PROP.equalsIgnoreCase(key)) { - ValidateUtils.checkTrue(GenericUtils.size(valsList) > 0, "No identity files specified"); - for (String id : valsList) { - addIdentity(id); - } - } else if (EXCLUSIVE_IDENTITIES_CONFIG_PROP.equalsIgnoreCase(key)) { - setIdentitiesOnly( - SshConfigFileReader.parseBooleanValue( - ValidateUtils.checkNotNullAndNotEmpty(joinedValue, "No identities option value"))); - } - } - - /** - * Appends a value using a <U>comma</U> to an existing one. If no previous - * value then same as calling {@link #setProperty(String, String)}. - * - * @param name Property name - never {@code null}/empty - * @param value The value to be appended - ignored if {@code null}/empty - * @return The value <U>before</U> appending - {@code null} if no previous value - */ - public String appendPropertyValue(String name, String value) { - String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); - String curVal = getProperty(key); - if (GenericUtils.isEmpty(value)) { - return curVal; - } - - if (GenericUtils.isEmpty(curVal)) { - return setProperty(key, value); - } - - return setProperty(key, curVal + ',' + value); - } - - /** - * Sets / Replaces the property value - * - * @param name Property name - never {@code null}/empty - * @param value Property value - if {@code null}/empty then - * {@link #removeProperty(String)} is called - * @return The previous property value - {@code null} if no such name - */ - public String setProperty(String name, String value) { - if (GenericUtils.isEmpty(value)) { - return removeProperty(name); - } - - String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); - if (GenericUtils.isEmpty(properties)) { - properties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - } - - return properties.put(key, value); - } - - /** - * @param name Property name - never {@code null}/empty - * @return The removed property value - {@code null} if no such property name - */ - public String removeProperty(String name) { - String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); - Map<String, String> props = getProperties(); - if (GenericUtils.isEmpty(props)) { - return null; - } else { - return props.remove(key); - } - } - - /** - * @param properties The properties to set - if {@code null} then an empty - * map is effectively set. <B>Note:</B> it is highly recommended to use a - * <U>case insensitive</U> key mapper. - */ - public void setProperties(Map<String, String> properties) { - this.properties = (properties == null) ? Collections.emptyMap() : properties; - } - - public <A extends Appendable> A append(A sb) throws IOException { - sb.append(HOST_CONFIG_PROP).append(' ').append(ValidateUtils.checkNotNullAndNotEmpty(getHost(), "No host pattern")).append(IoUtils.EOL); - appendNonEmptyProperty(sb, HOST_NAME_CONFIG_PROP, getHostName()); - appendNonEmptyPort(sb, PORT_CONFIG_PROP, getPort()); - appendNonEmptyProperty(sb, USER_CONFIG_PROP, getUsername()); - appendNonEmptyValues(sb, IDENTITY_FILE_CONFIG_PROP, getIdentities()); - if (exclusiveIdentites != null) { - appendNonEmptyProperty(sb, EXCLUSIVE_IDENTITIES_CONFIG_PROP, SshConfigFileReader.yesNoValueOf(exclusiveIdentites)); - } - appendNonEmptyProperties(sb, getProperties()); - return sb; - } - - @Override - public String toString() { - return getHost() + ": " + getUsername() + "@" + getHostName() + ":" + getPort(); - } - - /** - * @param <A> The {@link Appendable} type - * @param sb The target appender - * @param name The property name - never {@code null}/empty - * @param port The port value - ignored if non-positive - * @return The target appender after having appended (or not) the value - * @throws IOException If failed to append the requested data - * @see #appendNonEmptyProperty(Appendable, String, Object) - */ - public static <A extends Appendable> A appendNonEmptyPort(A sb, String name, int port) throws IOException { - return appendNonEmptyProperty(sb, name, (port > 0) ? Integer.toString(port) : null); - } - - /** - * Appends the extra properties - while skipping the {@link #EXPLICIT_PROPERTIES} ones - * - * @param <A> The {@link Appendable} type - * @param sb The target appender - * @param props The {@link Map} of properties - ignored if {@code null}/empty - * @return The target appender after having appended (or not) the value - * @throws IOException If failed to append the requested data - * @see #appendNonEmptyProperty(Appendable, String, Object) - */ - public static <A extends Appendable> A appendNonEmptyProperties(A sb, Map<String, ?> props) throws IOException { - if (GenericUtils.isEmpty(props)) { - return sb; - } - - // Cannot use forEach because of the IOException being thrown by appendNonEmptyProperty - for (Map.Entry<String, ?> pe : props.entrySet()) { - String name = pe.getKey(); - if (EXPLICIT_PROPERTIES.contains(name)) { - continue; - } - - appendNonEmptyProperty(sb, name, pe.getValue()); - } - - return sb; - } - - /** - * @param <A> The {@link Appendable} type - * @param sb The target appender - * @param name The property name - never {@code null}/empty - * @param value The property value - ignored if {@code null}. <B>Note:</B> - * if the string representation of the value contains any commas, they are - * assumed to indicate a multi-valued property which is broken down to - * <U>individual</U> lines - one per value. - * @return The target appender after having appended (or not) the value - * @throws IOException If failed to append the requested data - * @see #appendNonEmptyValues(Appendable, String, Object...) - */ - public static <A extends Appendable> A appendNonEmptyProperty(A sb, String name, Object value) throws IOException { - String s = Objects.toString(value, null); - String[] vals = GenericUtils.split(s, ','); - return appendNonEmptyValues(sb, name, (Object[]) vals); - } - - /** - * @param <A> The {@link Appendable} type - * @param sb The target appender - * @param name The property name - never {@code null}/empty - * @param values The values to be added - one per line - ignored if {@code null}/empty - * @return The target appender after having appended (or not) the value - * @throws IOException If failed to append the requested data - * @see #appendNonEmptyValues(Appendable, String, Collection) - */ - public static <A extends Appendable> A appendNonEmptyValues(A sb, String name, Object... values) throws IOException { - return appendNonEmptyValues(sb, name, GenericUtils.isEmpty(values) ? Collections.emptyList() : Arrays.asList(values)); - } - - /** - * @param <A> The {@link Appendable} type - * @param sb The target appender - * @param name The property name - never {@code null}/empty - * @param values The values to be added - one per line - ignored if {@code null}/empty - * @return The target appender after having appended (or not) the value - * @throws IOException If failed to append the requested data - */ - public static <A extends Appendable> A appendNonEmptyValues(A sb, String name, Collection<?> values) throws IOException { - String k = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name"); - if (GenericUtils.isEmpty(values)) { - return sb; - } - - for (Object v : values) { - sb.append(" ").append(k).append(' ').append(Objects.toString(v)).append(IoUtils.EOL); - } - - return sb; - } - - /** - * @param entries The entries - ignored if {@code null}/empty - * @return A {@link HostConfigEntryResolver} wrapper using the entries - */ - public static HostConfigEntryResolver toHostConfigEntryResolver(final Collection<? extends HostConfigEntry> entries) { - if (GenericUtils.isEmpty(entries)) { - return HostConfigEntryResolver.EMPTY; - } else { - return (host1, port1, username1) -> { - List<HostConfigEntry> matches = findMatchingEntries(host1, entries); - int numMatches = GenericUtils.size(matches); - if (numMatches <= 0) { - return null; - } - - HostConfigEntry match = (numMatches == 1) ? matches.get(0) : findBestMatch(matches); - if (match == null) { - ValidateUtils.throwIllegalArgumentException("No best match found for %s@%s:%d out of %d matches", username1, host1, port1, numMatches); - } - - return normalizeEntry(match, host1, port1, username1); - }; - } - } - - /** - * @param entry The original entry - ignored if {@code null} - * @param host The original host name / address - * @param port The original port - * @param username The original user name - * @return A <U>cloned</U> entry whose values are resolved - including - * expanding macros in the identities files - * @throws IOException If failed to normalize the entry - * @see #resolveHostName(String) - * @see #resolvePort(int) - * @see #resolveUsername(String) - * @see #resolveIdentityFilePath(String, String, int, String) - */ - public static HostConfigEntry normalizeEntry(HostConfigEntry entry, String host, int port, String username) throws IOException { - if (entry == null) { - return null; - } - - HostConfigEntry normal = new HostConfigEntry(); - normal.setHost(host); - normal.setHostName(entry.resolveHostName(host)); - normal.setPort(entry.resolvePort(port)); - normal.setUsername(entry.resolveUsername(username)); - - Map<String, String> props = entry.getProperties(); - if (GenericUtils.size(props) > 0) { - normal.setProperties(new TreeMap<>(props)); - } - - Collection<String> ids = entry.getIdentities(); - if (GenericUtils.isEmpty(ids)) { - return normal; - } - - normal.setIdentities(Collections.emptyList()); // start fresh - for (String id : ids) { - String path = resolveIdentityFilePath(id, host, port, username); - normal.addIdentity(path); - } - - return normal; - } - - /** - * Resolves the effective target host - * - * @param originalName The original requested host - * @param entryName The configured host - * @return If the configured host entry is not {@code null}/empty - * then it is used, otherwise the original one. - */ - public static String resolveHostName(String originalName, String entryName) { - if (GenericUtils.isEmpty(entryName)) { - return originalName; - } else { - return entryName; - } - } - - /** - * Resolves the effective username - * - * @param originalUser The original requested username - * @param entryUser The configured host entry username - * @return If the configured host entry username is not {@code null}/empty - * then it is used, otherwise the original one. - */ - public static String resolveUsername(String originalUser, String entryUser) { - if (GenericUtils.isEmpty(entryUser)) { - return originalUser; - } else { - return entryUser; - } - } - - /** - * Resolves the effective port to use - * - * @param originalPort The original requested port - * @param entryPort The configured host entry port - * @return If the host entry port is positive, then it is used, otherwise - * the original requested port - */ - public static int resolvePort(int originalPort, int entryPort) { - if (entryPort <= 0) { - return originalPort; - } else { - return entryPort; - } - } - - public static List<HostConfigEntry> readHostConfigEntries(File file) throws IOException { - return readHostConfigEntries(file.toPath(), IoUtils.EMPTY_OPEN_OPTIONS); - } - - public static List<HostConfigEntry> readHostConfigEntries(Path path, OpenOption... options) throws IOException { - try (InputStream input = Files.newInputStream(path, options)) { - return readHostConfigEntries(input, true); - } - } - - public static List<HostConfigEntry> readHostConfigEntries(URL url) throws IOException { - try (InputStream input = url.openStream()) { - return readHostConfigEntries(input, true); - } - } - - public static List<HostConfigEntry> readHostConfigEntries(String filePath) throws IOException { - try (InputStream inStream = new FileInputStream(filePath)) { - return readHostConfigEntries(inStream, true); - } - } - - public static List<HostConfigEntry> readHostConfigEntries(InputStream inStream, boolean okToClose) throws IOException { - try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(inStream, okToClose), StandardCharsets.UTF_8)) { - return readHostConfigEntries(reader, true); - } - } - - public static List<HostConfigEntry> readHostConfigEntries(Reader rdr, boolean okToClose) throws IOException { - try (BufferedReader buf = new BufferedReader(NoCloseReader.resolveReader(rdr, okToClose))) { - return readHostConfigEntries(buf); - } - } - - /** - * Reads configuration entries - * - * @param rdr The {@link BufferedReader} to use - * @return The {@link List} of read {@link HostConfigEntry}-ies - * @throws IOException If failed to parse the read configuration - */ - public static List<HostConfigEntry> readHostConfigEntries(BufferedReader rdr) throws IOException { - HostConfigEntry curEntry = null; - HostConfigEntry globalEntry = null; - List<HostConfigEntry> entries = null; - - int lineNumber = 1; - for (String line = rdr.readLine(); line != null; line = rdr.readLine(), lineNumber++) { - line = GenericUtils.replaceWhitespaceAndTrim(line); - if (GenericUtils.isEmpty(line)) { - continue; - } - - int pos = line.indexOf(SshConfigFileReader.COMMENT_CHAR); - if (pos == 0) { - continue; - } - - if (pos > 0) { - line = line.substring(0, pos); - line = line.trim(); - } - - /* - * Some options use '=', others use ' ' - try both - * NOTE: we do not validate the format for each option separately - */ - pos = line.indexOf(' '); - if (pos < 0) { - pos = line.indexOf('='); - } - - if (pos < 0) { - throw new StreamCorruptedException("No configuration value delimiter at line " + lineNumber + ": " + line); - } - - String key = line.substring(0, pos); - String value = line.substring(pos + 1); - List<String> valsList = parseConfigValue(value); - - if (HOST_CONFIG_PROP.equalsIgnoreCase(key)) { - if (GenericUtils.isEmpty(valsList)) { - throw new StreamCorruptedException("Missing host pattern(s) at line " + lineNumber + ": " + line); - } - - // If the all-hosts pattern is used, make sure no global section already active - for (String name : valsList) { - if (ALL_HOSTS_PATTERN.equalsIgnoreCase(name) && (globalEntry != null)) { - throw new StreamCorruptedException("Overriding the global section with a specific one at line " + lineNumber + ": " + line); - } - } - - if (curEntry != null) { - curEntry.processGlobalValues(globalEntry); - } - - entries = updateEntriesList(entries, curEntry); - - curEntry = new HostConfigEntry(); - curEntry.setHost(valsList); - } else if (curEntry == null) { - // if 1st encountered property is NOT for a specific host, then configuration applies to ALL - curEntry = new HostConfigEntry(); - curEntry.setHost(Collections.singletonList(ALL_HOSTS_PATTERN)); - globalEntry = curEntry; - } - - try { - curEntry.processProperty(key, valsList, false); - } catch (RuntimeException e) { - throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")" - + " to process line #" + lineNumber + " (" + line + ")" - + ": " + e.getMessage()); - } - } - - if (curEntry != null) { - curEntry.processGlobalValues(globalEntry); - } - - entries = updateEntriesList(entries, curEntry); - if (entries == null) { - return Collections.emptyList(); - } else { - return entries; - } - } - - /** - * Finds the best match out of the given ones. - * - * @param matches The available matches - ignored if {@code null}/empty - * @return The best match or {@code null} if no matches or no best match found - * @see #findBestMatch(Iterator) - */ - public static HostConfigEntry findBestMatch(Collection<? extends HostConfigEntry> matches) { - if (GenericUtils.isEmpty(matches)) { - return null; - } else { - return findBestMatch(matches.iterator()); - } - } - - /** - * Finds the best match out of the given ones. - * - * @param matches The available matches - ignored if {@code null}/empty - * @return The best match or {@code null} if no matches or no best match found - * @see #findBestMatch(Iterator) - */ - public static HostConfigEntry findBestMatch(Iterable<? extends HostConfigEntry> matches) { - if (matches == null) { - return null; - } else { - return findBestMatch(matches.iterator()); - } - } - - /** - * Finds the best match out of the given ones. The best match is defined as one whose - * pattern is as <U>specific</U> as possible (if more than one match is available). - * I.e., a non-global match is preferred over global one, and a match with no wildcards - * is preferred over one with such a pattern. - * - * @param matches The available matches - ignored if {@code null}/empty - * @return The best match or {@code null} if no matches or no best match found - * @see #isSpecificHostPattern(String) - */ - public static HostConfigEntry findBestMatch(Iterator<? extends HostConfigEntry> matches) { - if ((matches == null) || (!matches.hasNext())) { - return null; - } - - HostConfigEntry candidate = matches.next(); - int wildcardMatches = 0; - while (matches.hasNext()) { - HostConfigEntry entry = matches.next(); - String entryPattern = entry.getHost(); - String candidatePattern = candidate.getHost(); - // prefer non-global entry over global entry - if (ALL_HOSTS_PATTERN.equalsIgnoreCase(candidatePattern)) { - // unlikely, but handle it - if (ALL_HOSTS_PATTERN.equalsIgnoreCase(entryPattern)) { - wildcardMatches++; - } else { - candidate = entry; - wildcardMatches = 0; - } - continue; - } - - if (isSpecificHostPattern(entryPattern)) { - // if both are specific then no best match - if (isSpecificHostPattern(candidatePattern)) { - return null; - } - - candidate = entry; - wildcardMatches = 0; - continue; - } - - wildcardMatches++; - } - - String candidatePattern = candidate.getHost(); - // best match either has specific host or no wildcard matches - if ((wildcardMatches <= 0) || (isSpecificHostPattern(candidatePattern))) { - return candidate; - } - - return null; - } - - public static List<HostConfigEntry> updateEntriesList(List<HostConfigEntry> entries, HostConfigEntry curEntry) { - if (curEntry == null) { - return entries; - } - - if (entries == null) { - entries = new ArrayList<>(); - } - - entries.add(curEntry); - return entries; - } - - public static void writeHostConfigEntries(File file, Collection<? extends HostConfigEntry> entries) throws IOException { - writeHostConfigEntries(Objects.requireNonNull(file, "No file").toPath(), entries, IoUtils.EMPTY_OPEN_OPTIONS); - } - - public static void writeHostConfigEntries(Path path, Collection<? extends HostConfigEntry> entries, OpenOption... options) throws IOException { - try (OutputStream outputStream = Files.newOutputStream(path, options)) { - writeHostConfigEntries(outputStream, true, entries); - } - } - - public static void writeHostConfigEntries(OutputStream outputStream, boolean okToClose, Collection<? extends HostConfigEntry> entries) throws IOException { - if (GenericUtils.isEmpty(entries)) { - return; - } - - try (Writer w = new OutputStreamWriter(NoCloseOutputStream.resolveOutputStream(outputStream, okToClose), StandardCharsets.UTF_8)) { - appendHostConfigEntries(w, entries); - } - } - - public static <A extends Appendable> A appendHostConfigEntries(A sb, Collection<? extends HostConfigEntry> entries) throws IOException { - if (GenericUtils.isEmpty(entries)) { - return sb; - } - - for (HostConfigEntry entry : entries) { - entry.append(sb); - } - - return sb; - } - - /** - * Checks if this is a multi-value - allow space and comma - * - * @param value The value - ignored if {@code null}/empty (after trimming) - * @return A {@link List} of the encountered values - */ - public static List<String> parseConfigValue(String value) { - String s = GenericUtils.replaceWhitespaceAndTrim(value); - if (GenericUtils.isEmpty(s)) { - return Collections.emptyList(); - } - - for (int index = 0; index < MULTI_VALUE_SEPARATORS.length(); index++) { - char sep = MULTI_VALUE_SEPARATORS.charAt(index); - int pos = s.indexOf(sep); - if (pos >= 0) { - String[] vals = GenericUtils.split(s, sep); - if (GenericUtils.isEmpty(vals)) { - return Collections.emptyList(); - } else { - return Arrays.asList(vals); - } - } - } - - // this point is reached if no separators found - return Collections.singletonList(s); - } - - // The file name may use the tilde syntax to refer to a userâs home directory or one of the following escape characters: - // '%d' (local user's home directory), '%u' (local user name), '%l' (local host name), '%h' (remote host name) or '%r' (remote user name). - public static String resolveIdentityFilePath(String id, String host, int port, String username) throws IOException { - if (GenericUtils.isEmpty(id)) { - return id; - } - - String path = id.replace('/', File.separatorChar); // make sure all separators are local - String[] elements = GenericUtils.split(path, File.separatorChar); - StringBuilder sb = new StringBuilder(path.length() + Long.SIZE); - for (int index = 0; index < elements.length; index++) { - String elem = elements[index]; - if (index > 0) { - sb.append(File.separatorChar); - } - - for (int curPos = 0; curPos < elem.length(); curPos++) { - char ch = elem.charAt(curPos); - if (ch == HOME_TILDE_CHAR) { - ValidateUtils.checkTrue((curPos == 0) && (index == 0), "Home tilde must be first: %s", id); - appendUserHome(sb); - } else if (ch == PATH_MACRO_CHAR) { - curPos++; - ValidateUtils.checkTrue(curPos < elem.length(), "Missing macro modifier in %s", id); - ch = elem.charAt(curPos); - switch(ch) { - case PATH_MACRO_CHAR: - sb.append(ch); - break; - case LOCAL_HOME_MACRO: - ValidateUtils.checkTrue((curPos == 1) && (index == 0), "Home macro must be first: %s", id); - appendUserHome(sb); - break; - case LOCAL_USER_MACRO: - sb.append(ValidateUtils.checkNotNullAndNotEmpty(OsUtils.getCurrentUser(), "No local user name value")); - break; - case LOCAL_HOST_MACRO: { - InetAddress address = Objects.requireNonNull(InetAddress.getLocalHost(), "No local address"); - sb.append(ValidateUtils.checkNotNullAndNotEmpty(address.getHostName(), "No local name")); - break; - } - case REMOTE_HOST_MACRO: - sb.append(ValidateUtils.checkNotNullAndNotEmpty(host, "No remote host provided")); - break; - case REMOTE_USER_MACRO: - sb.append(ValidateUtils.checkNotNullAndNotEmpty(username, "No remote user provided")); - break; - case REMOTE_PORT_MACRO: - ValidateUtils.checkTrue(port > 0, "Bad remote port value: %d", port); - sb.append(port); - break; - default: - ValidateUtils.throwIllegalArgumentException("Bad modifier '%s' in %s", String.valueOf(ch), id); - } - } else { - sb.append(ch); - } - } - } - - return sb.toString(); - } - - public static StringBuilder appendUserHome(StringBuilder sb) { - return appendUserHome(sb, IdentityUtils.getUserHomeFolder()); - } - - public static StringBuilder appendUserHome(StringBuilder sb, Path userHome) { - return appendUserHome(sb, Objects.requireNonNull(userHome, "No user home folder").toString()); - } - - public static StringBuilder appendUserHome(StringBuilder sb, String userHome) { - if (GenericUtils.isEmpty(userHome)) { - return sb; - } - - sb.append(userHome); - // strip any ending separator since we add our own - int len = sb.length(); - if (sb.charAt(len - 1) == File.separatorChar) { - sb.setLength(len - 1); - } - - return sb; - } - - /** - * @return The default {@link Path} location of the OpenSSH hosts entries configuration file - */ - @SuppressWarnings("synthetic-access") - public static Path getDefaultHostConfigFile() { - return LazyDefaultConfigFileHolder.CONFIG_FILE; - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java deleted file mode 100644 index a07cfcf..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -@FunctionalInterface -public interface HostConfigEntryResolver { - - /** - * An "empty" implementation that does not resolve any entry - */ - HostConfigEntryResolver EMPTY = new HostConfigEntryResolver() { - @Override - public HostConfigEntry resolveEffectiveHost(String host, int port, String username) throws IOException { - return null; - } - - @Override - public String toString() { - return "EMPTY"; - } - }; - - /** - * Invoked when creating a new client session in order to allow for overriding - * of the original parameters - * - * @param host The requested host - never {@code null}/empty - * @param port The requested port - * @param username The requested username - * @return A {@link HostConfigEntry} for the actual target - {@code null} if use - * original parameters. <B>Note:</B> if any identity files are attached to the - * configuration then they must point to <U>existing</U> locations. This means - * that any macros such as <code>~, %d, %h</code>, etc. must be resolved <U>prior</U> - * to returning the value - * @throws IOException If failed to resolve the configuration - */ - HostConfigEntry resolveEffectiveHost(String host, int port, String username) throws IOException; -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostPatternValue.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostPatternValue.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostPatternValue.java deleted file mode 100644 index 20d682f..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostPatternValue.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.util.regex.Pattern; - -import org.apache.sshd.common.util.GenericUtils; - -/** - * Represents a pattern definition in the <U>known_hosts</U> file - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - * @see <A HREF="https://en.wikibooks.org/wiki/OpenSSH/Client_Configuration_Files#About_the_Contents_of_the_known_hosts_Files"> - * OpenSSH cookbook - About the Contents of the known hosts Files</A> - */ -public class HostPatternValue { - private Pattern pattern; - private int port; - private boolean negated; - - public HostPatternValue() { - super(); - } - - public HostPatternValue(Pattern pattern, boolean negated) { - this(pattern, 0, negated); - } - - public HostPatternValue(Pattern pattern, int port, boolean negated) { - this.pattern = pattern; - this.port = port; - this.negated = negated; - } - - public Pattern getPattern() { - return pattern; - } - - public void setPattern(Pattern pattern) { - this.pattern = pattern; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public boolean isNegated() { - return negated; - } - - public void setNegated(boolean negated) { - this.negated = negated; - } - - @Override - public String toString() { - Pattern p = getPattern(); - String purePattern = (p == null) ? null : p.pattern(); - StringBuilder sb = new StringBuilder(GenericUtils.length(purePattern) + Short.SIZE); - if (isNegated()) { - sb.append(HostPatternsHolder.NEGATION_CHAR_PATTERN); - } - - int portValue = getPort(); - if (portValue > 0) { - sb.append(HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM); - } - sb.append(purePattern); - if (portValue > 0) { - sb.append(HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM); - sb.append(HostPatternsHolder.PORT_VALUE_DELIMITER); - sb.append(portValue); - } - - return sb.toString(); - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostPatternsHolder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostPatternsHolder.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostPatternsHolder.java deleted file mode 100644 index 9d90dac..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostPatternsHolder.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * 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.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.ValidateUtils; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public abstract class HostPatternsHolder { - - /** - * Used in a host pattern to denote zero or more consecutive characters - */ - public static final char WILDCARD_PATTERN = '*'; - public static final String ALL_HOSTS_PATTERN = String.valueOf(WILDCARD_PATTERN); - - /** - * Used in a host pattern to denote any <U>one</U> character - */ - public static final char SINGLE_CHAR_PATTERN = '?'; - - /** - * Used to negate a host pattern - */ - public static final char NEGATION_CHAR_PATTERN = '!'; - - /** - * The available pattern characters - */ - public static final String PATTERN_CHARS = new String(new char[]{WILDCARD_PATTERN, SINGLE_CHAR_PATTERN, NEGATION_CHAR_PATTERN}); - - /** Port value separator if non-standard port pattern used */ - public static final char PORT_VALUE_DELIMITER = ':'; - - /** Non-standard port specification host pattern enclosure start delimiter */ - public static final char NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM = '['; - - /** Non-standard port specification host pattern enclosure end delimiter */ - public static final char NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM = ']'; - - private Collection<HostPatternValue> patterns = new LinkedList<>(); - - protected HostPatternsHolder() { - super(); - } - - public Collection<HostPatternValue> getPatterns() { - return patterns; - } - - public void setPatterns(Collection<HostPatternValue> patterns) { - this.patterns = patterns; - } - - /** - * Checks if a given host name / address matches the entry's host pattern(s) - * - * @param host The host name / address - ignored if {@code null}/empty - * @param port The connection port - * @return {@code true} if the name / address matches the pattern(s) - * @see #isHostMatch(String, Pattern) - */ - public boolean isHostMatch(String host, int port) { - return isHostMatch(host, port, getPatterns()); - } - - /** - * @param pattern The pattern to check - ignored if {@code null}/empty - * @return {@code true} if the pattern is not empty and contains no wildcard characters - * @see #WILDCARD_PATTERN - * @see #SINGLE_CHAR_PATTERN - * @see #SINGLE_CHAR_PATTERN - */ - public static boolean isSpecificHostPattern(String pattern) { - if (GenericUtils.isEmpty(pattern)) { - return false; - } - - for (int index = 0; index < PATTERN_CHARS.length(); index++) { - char ch = PATTERN_CHARS.charAt(index); - if (pattern.indexOf(ch) >= 0) { - return false; - } - } - - return true; - } - - /** - * Locates all the matching entries for a give host name / address - * - * @param host The host name / address - ignored if {@code null}/empty - * @param entries The {@link HostConfigEntry}-ies to scan - ignored if {@code null}/empty - * @return A {@link List} of all the matching entries - * @see #isHostMatch(String, int) - */ - public static List<HostConfigEntry> findMatchingEntries(String host, HostConfigEntry... entries) { - // TODO in Java-8 use Stream(s) + predicate - if (GenericUtils.isEmpty(host) || GenericUtils.isEmpty(entries)) { - return Collections.emptyList(); - } else { - return findMatchingEntries(host, Arrays.asList(entries)); - } - } - - /** - * Locates all the matching entries for a give host name / address - * - * @param host The host name / address - ignored if {@code null}/empty - * @param entries The {@link HostConfigEntry}-ies to scan - ignored if {@code null}/empty - * @return A {@link List} of all the matching entries - * @see #isHostMatch(String, int) - */ - public static List<HostConfigEntry> findMatchingEntries(String host, Collection<? extends HostConfigEntry> entries) { - // TODO in Java-8 use Stream(s) + predicate - if (GenericUtils.isEmpty(host) || GenericUtils.isEmpty(entries)) { - return Collections.emptyList(); - } - - List<HostConfigEntry> matches = null; - for (HostConfigEntry entry : entries) { - if (!entry.isHostMatch(host, 0 /* any port */)) { - continue; // debug breakpoint - } - - if (matches == null) { - matches = new ArrayList<>(entries.size()); // in case ALL of them match - } - - matches.add(entry); - } - - if (matches == null) { - return Collections.emptyList(); - } else { - return matches; - } - } - - public static boolean isHostMatch(String host, int port, Collection<HostPatternValue> patterns) { - if (GenericUtils.isEmpty(patterns)) { - return false; - } - - boolean matchFound = false; - for (HostPatternValue pv : patterns) { - boolean negated = pv.isNegated(); - /* - * If already found a match we are interested only in negations - */ - if (matchFound && (!negated)) { - continue; - } - - if (!isHostMatch(host, pv.getPattern())) { - continue; - } - - /* - * According to https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5: - * - * If a negated entry is matched, then the Host entry is ignored, - * regardless of whether any other patterns on the line match. - */ - if (negated) { - return false; - } - - int pvPort = pv.getPort(); - if ((pvPort != 0) && (port != 0) && (pvPort != port)) { - continue; - } - - matchFound = true; - } - - return matchFound; - } - - /** - * Checks if a given host name / address matches a host pattern - * - * @param host The host name / address - ignored if {@code null}/empty - * @param pattern The host {@link Pattern} - ignored if {@code null} - * @return {@code true} if the name / address matches the pattern - */ - public static boolean isHostMatch(String host, Pattern pattern) { - if (GenericUtils.isEmpty(host) || (pattern == null)) { - return false; - } - - Matcher m = pattern.matcher(host); - return m.matches(); - } - - public static List<HostPatternValue> parsePatterns(CharSequence... patterns) { - return parsePatterns(GenericUtils.isEmpty(patterns) ? Collections.emptyList() : Arrays.asList(patterns)); - } - - public static List<HostPatternValue> parsePatterns(Collection<? extends CharSequence> patterns) { - if (GenericUtils.isEmpty(patterns)) { - return Collections.emptyList(); - } - - List<HostPatternValue> result = new ArrayList<>(patterns.size()); - for (CharSequence p : patterns) { - result.add(ValidateUtils.checkNotNull(toPattern(p), "No pattern for %s", p)); - } - - return result; - } - - /** - * Converts a host pattern string to a regular expression matcher. - * <B>Note:</B> pattern matching is <U>case insensitive</U> - * - * @param patternString The original pattern string - ignored if {@code null}/empty - * @return The regular expression matcher {@link Pattern} and the indication - * whether it is a negating pattern or not - {@code null} if no original string - * @see #NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM - * @see #NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM - * @see #WILDCARD_PATTERN - * @see #SINGLE_CHAR_PATTERN - * @see #NEGATION_CHAR_PATTERN - */ - public static HostPatternValue toPattern(CharSequence patternString) { - String pattern = GenericUtils.replaceWhitespaceAndTrim(Objects.toString(patternString, null)); - if (GenericUtils.isEmpty(pattern)) { - return null; - } - - int patternLen = pattern.length(); - int port = 0; - // Check if non-standard port value used - StringBuilder sb = new StringBuilder(patternLen); - if (pattern.charAt(0) == HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM) { - int pos = GenericUtils.lastIndexOf(pattern, HostPatternsHolder.PORT_VALUE_DELIMITER); - ValidateUtils.checkTrue(pos > 0, "Missing non-standard port value delimiter in %s", pattern); - ValidateUtils.checkTrue(pos < (patternLen - 1), "Missing non-standard port value number in %s", pattern); - ValidateUtils.checkTrue(pattern.charAt(pos - 1) == HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM, - "Invalid non-standard port value host pattern enclosure delimiters in %s", pattern); - - String csPort = pattern.substring(pos + 1, patternLen); - port = Integer.parseInt(csPort); - ValidateUtils.checkTrue((port > 0) && (port <= 0xFFFF), "Invalid non-start port value (%d) in %s", port, pattern); - - pattern = pattern.substring(1, pos - 1); - patternLen = pattern.length(); - } - - boolean negated = false; - for (int curPos = 0; curPos < patternLen; curPos++) { - char ch = pattern.charAt(curPos); - ValidateUtils.checkTrue(isValidPatternChar(ch), "Invalid host pattern char in %s", pattern); - - switch(ch) { - case '.': // need to escape it - sb.append('\\').append(ch); - break; - case SINGLE_CHAR_PATTERN: - sb.append('.'); - break; - case WILDCARD_PATTERN: - sb.append(".*"); - break; - case NEGATION_CHAR_PATTERN: - ValidateUtils.checkTrue(!negated, "Double negation in %s", pattern); - ValidateUtils.checkTrue(curPos == 0, "Negation must be 1st char: %s", pattern); - negated = true; - break; - default: - sb.append(ch); - } - } - - return new HostPatternValue(Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE), port, negated); - } - - /** - * Checks if the given character is valid for a host pattern. Valid - * characters are: - * <UL> - * <LI>A-Z</LI> - * <LI>a-z</LI> - * <LI>0-9</LI> - * <LI>Underscore (_)</LI> - * <LI>Hyphen (-)</LI> - * <LI>Dot (.)</LI> - * <LI>The {@link #WILDCARD_PATTERN}</LI> - * <LI>The {@link #SINGLE_CHAR_PATTERN}</LI> - * </UL> - * - * @param ch The character to validate - * @return {@code true} if valid pattern character - */ - public static boolean isValidPatternChar(char ch) { - if ((ch <= ' ') || (ch >= 0x7E)) { - return false; - } - if ((ch >= 'a') && (ch <= 'z')) { - return true; - } - if ((ch >= 'A') && (ch <= 'Z')) { - return true; - } - if ((ch >= '0') && (ch <= '9')) { - return true; - } - if ("-_.".indexOf(ch) >= 0) { - return true; - } - return PATTERN_CHARS.indexOf(ch) >= 0; - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/KnownHostDigest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/KnownHostDigest.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/KnownHostDigest.java deleted file mode 100644 index 2d9a322..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/KnownHostDigest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.util.Collections; -import java.util.EnumSet; -import java.util.Objects; -import java.util.Set; - -import org.apache.sshd.common.Factory; -import org.apache.sshd.common.NamedFactory; -import org.apache.sshd.common.NamedResource; -import org.apache.sshd.common.mac.BuiltinMacs; -import org.apache.sshd.common.mac.Mac; -import org.apache.sshd.common.util.ValidateUtils; - -/** - * Available digesters for known hosts entries - * - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public enum KnownHostDigest implements NamedFactory<Mac> { - SHA1("1", BuiltinMacs.hmacsha1); - - public static final Set<KnownHostDigest> VALUES = - Collections.unmodifiableSet(EnumSet.allOf(KnownHostDigest.class)); - - private final String name; - private final Factory<Mac> factory; - - KnownHostDigest(String name, Factory<Mac> factory) { - this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No name"); - this.factory = Objects.requireNonNull(factory, "No factory"); - } - - @Override - public String getName() { - return name; - } - - @Override - public Mac create() { - return factory.create(); - } - - public static KnownHostDigest fromName(String name) { - return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES); - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java deleted file mode 100644 index 4d4e97a..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * 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.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StreamCorruptedException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.apache.sshd.common.config.SshConfigFileReader; -import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; -import org.apache.sshd.common.config.keys.PublicKeyEntry; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.common.util.io.NoCloseInputStream; -import org.apache.sshd.common.util.io.NoCloseReader; - -/** - * Contains a representation of an entry in the <code>known_hosts</code> file - * - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - * @see <A HREF="http://www.manpagez.com/man/8/sshd/">sshd(8) man page</A> - */ -public class KnownHostEntry extends HostPatternsHolder { - /** - * Character that denotes that start of a marker - */ - public static final char MARKER_INDICATOR = '@'; - - /** - * Standard OpenSSH config file name - */ - public static final String STD_HOSTS_FILENAME = "known_hosts"; - - private static final class LazyDefaultConfigFileHolder { - private static final Path HOSTS_FILE = - PublicKeyEntry.getDefaultKeysFolderPath().resolve(STD_HOSTS_FILENAME); - - private LazyDefaultConfigFileHolder() { - throw new UnsupportedOperationException("No instance allowed"); - } - } - - private String line; - private String marker; - private AuthorizedKeyEntry keyEntry; - private KnownHostHashValue hashedEntry; - - public KnownHostEntry() { - super(); - } - - /** - * @param line The original line from which this entry was created - */ - public KnownHostEntry(String line) { - this.line = line; - } - - /** - * @return The original line from which this entry was created - */ - public String getConfigLine() { - return line; - } - - public void setConfigLine(String line) { - this.line = line; - } - - public String getMarker() { - return marker; - } - - public void setMarker(String marker) { - this.marker = marker; - } - - public AuthorizedKeyEntry getKeyEntry() { - return keyEntry; - } - - public void setKeyEntry(AuthorizedKeyEntry keyEntry) { - this.keyEntry = keyEntry; - } - - public KnownHostHashValue getHashedEntry() { - return hashedEntry; - } - - public void setHashedEntry(KnownHostHashValue hashedEntry) { - this.hashedEntry = hashedEntry; - } - - @Override - public boolean isHostMatch(String host, int port) { - if (super.isHostMatch(host, port)) { - return true; - } - - KnownHostHashValue hash = getHashedEntry(); - return (hash != null) && hash.isHostMatch(host); - } - - @Override - public String toString() { - return getConfigLine(); - } - - /** - * @return The default {@link Path} location of the OpenSSH known hosts file - */ - @SuppressWarnings("synthetic-access") - public static Path getDefaultKnownHostsFile() { - return LazyDefaultConfigFileHolder.HOSTS_FILE; - } - - public static List<KnownHostEntry> readKnownHostEntries(File file) throws IOException { - return readKnownHostEntries(file.toPath(), IoUtils.EMPTY_OPEN_OPTIONS); - } - - public static List<KnownHostEntry> readKnownHostEntries(Path path, OpenOption... options) throws IOException { - try (InputStream input = Files.newInputStream(path, options)) { - return readKnownHostEntries(input, true); - } - } - - public static List<KnownHostEntry> readKnownHostEntries(URL url) throws IOException { - try (InputStream input = url.openStream()) { - return readKnownHostEntries(input, true); - } - } - - public static List<KnownHostEntry> readKnownHostEntries(String filePath) throws IOException { - try (InputStream inStream = new FileInputStream(filePath)) { - return readKnownHostEntries(inStream, true); - } - } - - public static List<KnownHostEntry> readKnownHostEntries(InputStream inStream, boolean okToClose) throws IOException { - try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(inStream, okToClose), StandardCharsets.UTF_8)) { - return readKnownHostEntries(reader, true); - } - } - - public static List<KnownHostEntry> readKnownHostEntries(Reader rdr, boolean okToClose) throws IOException { - try (BufferedReader buf = new BufferedReader(NoCloseReader.resolveReader(rdr, okToClose))) { - return readKnownHostEntries(buf); - } - } - - /** - * Reads configuration entries - * - * @param rdr The {@link BufferedReader} to use - * @return The {@link List} of read {@link KnownHostEntry}-ies - * @throws IOException If failed to parse the read configuration - */ - public static List<KnownHostEntry> readKnownHostEntries(BufferedReader rdr) throws IOException { - List<KnownHostEntry> entries = null; - - int lineNumber = 1; - for (String line = rdr.readLine(); line != null; line = rdr.readLine(), lineNumber++) { - line = GenericUtils.trimToEmpty(line); - if (GenericUtils.isEmpty(line)) { - continue; - } - - int pos = line.indexOf(SshConfigFileReader.COMMENT_CHAR); - if (pos == 0) { - continue; - } - - if (pos > 0) { - line = line.substring(0, pos); - line = line.trim(); - } - - try { - KnownHostEntry entry = parseKnownHostEntry(line); - if (entry == null) { - continue; - } - - if (entries == null) { - entries = new ArrayList<>(); - } - entries.add(entry); - } catch (RuntimeException | Error e) { // TODO consider consulting a user callback - throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")" - + " to parse line #" + lineNumber + " '" + line + "': " + e.getMessage()); - } - } - - if (entries == null) { - return Collections.emptyList(); - } else { - return entries; - } - } - - public static KnownHostEntry parseKnownHostEntry(String line) { - return parseKnownHostEntry(GenericUtils.isEmpty(line) ? null : new KnownHostEntry(), line); - } - - public static <E extends KnownHostEntry> E parseKnownHostEntry(E entry, String data) { - String line = GenericUtils.replaceWhitespaceAndTrim(data); - if (GenericUtils.isEmpty(line) || (line.charAt(0) == PublicKeyEntry.COMMENT_CHAR)) { - return entry; - } - - entry.setConfigLine(line); - - if (line.charAt(0) == MARKER_INDICATOR) { - int pos = line.indexOf(' '); - ValidateUtils.checkTrue(pos > 0, "Missing marker name end delimiter in line=%s", data); - ValidateUtils.checkTrue(pos > 1, "No marker name after indicator in line=%s", data); - entry.setMarker(line.substring(1, pos)); - line = line.substring(pos + 1).trim(); - } else { - entry.setMarker(null); - } - - int pos = line.indexOf(' '); - ValidateUtils.checkTrue(pos > 0, "Missing host patterns end delimiter in line=%s", data); - String hostPattern = line.substring(0, pos); - line = line.substring(pos + 1).trim(); - - if (hostPattern.charAt(0) == KnownHostHashValue.HASHED_HOST_DELIMITER) { - KnownHostHashValue hash = - ValidateUtils.checkNotNull(KnownHostHashValue.parse(hostPattern), - "Failed to extract host hash value from line=%s", data); - entry.setHashedEntry(hash); - entry.setPatterns(null); - } else { - entry.setHashedEntry(null); - entry.setPatterns(parsePatterns(GenericUtils.split(hostPattern, ','))); - } - - AuthorizedKeyEntry key = - ValidateUtils.checkNotNull(AuthorizedKeyEntry.parseAuthorizedKeyEntry(line), - "No valid key entry recovered from line=%s", data); - entry.setKeyEntry(key); - return entry; - } -}
