/* * Copyright (C) 2008, 2017, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.transport; 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.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.fnmatch.FileNameMatcher; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; import com.jcraft.jsch.ConfigRepository; /** * Fairly complete configuration parser for the OpenSSH ~/.ssh/config file. *

* JSch does have its own config file parser * {@link com.jcraft.jsch.OpenSSHConfig} since version 0.1.50, but it has a * number of problems: *

*

* Therefore implement our own parser to read an OpenSSH configuration file. It * makes the critical options available to {@link SshSessionFactory} via * {@link Host} objects returned by {@link #lookup(String)}, and implements a * fully conforming {@link ConfigRepository} providing * {@link com.jcraft.jsch.ConfigRepository.Config}s via * {@link #getConfig(String)}. *

*

* Limitations compared to the full OpenSSH 7.5 parser: *

* *

* Note that OpenSSH's readconf.c is a validating parser; Jsch's * ConfigRepository OTOH treats all option values as plain strings, so any * validation must happen in Jsch outside of the parser. Thus this parser does * not validate option values, except for a few options when constructing a * {@link Host} object. *

*

* This config does %-substitutions for the following tokens: *

* *

* If the config doesn't set the port or the remote user name, %p and %r remain * un-substituted. It's the caller's responsibility to replace them with values * obtained from the connection URI. %i is not handled; Java has no concept of a * "user ID". *

*/ public class OpenSshConfig implements ConfigRepository { /** IANA assigned port number for SSH. */ static final int SSH_PORT = 22; /** * Obtain the user's configuration data. *

* The configuration file is always returned to the caller, even if no file * exists in the user's home directory at the time the call was made. Lookup * requests are cached and are automatically updated if the user modifies * the configuration file since the last time it was cached. * * @param fs * the file system abstraction which will be necessary to * perform certain file system operations. * @return a caching reader of the user's configuration file. */ public static OpenSshConfig get(FS fs) { File home = fs.userHome(); if (home == null) home = new File(".").getAbsoluteFile(); //$NON-NLS-1$ final File config = new File(new File(home, ".ssh"), Constants.CONFIG); //$NON-NLS-1$ final OpenSshConfig osc = new OpenSshConfig(home, config); osc.refresh(); return osc; } /** The user's home directory, as key files may be relative to here. */ private final File home; /** The .ssh/config file we read and monitor for updates. */ private final File configFile; /** Modification time of {@link #configFile} when it was last loaded. */ private long lastModified; /** * Encapsulates entries read out of the configuration file, and * {@link Host}s created from that. */ private static class State { Map entries = new LinkedHashMap<>(); Map hosts = new HashMap<>(); @Override @SuppressWarnings("nls") public String toString() { return "State [entries=" + entries + ", hosts=" + hosts + "]"; } } /** State read from the config file, plus {@link Host}s created from it. */ private State state; OpenSshConfig(final File h, final File cfg) { home = h; configFile = cfg; state = new State(); } /** * Locate the configuration for a specific host request. * * @param hostName * the name the user has supplied to the SSH tool. This may be a * real host name, or it may just be a "Host" block in the * configuration file. * @return r configuration for the requested name. Never null. */ public Host lookup(final String hostName) { final State cache = refresh(); Host h = cache.hosts.get(hostName); if (h != null) { return h; } HostEntry fullConfig = new HostEntry(); // Initialize with default entries at the top of the file, before the // first Host block. fullConfig.merge(cache.entries.get(HostEntry.DEFAULT_NAME)); for (final Map.Entry e : cache.entries.entrySet()) { String key = e.getKey(); if (isHostMatch(key, hostName)) { fullConfig.merge(e.getValue()); } } fullConfig.substitute(hostName, home); h = new Host(fullConfig, hostName, home); cache.hosts.put(hostName, h); return h; } private synchronized State refresh() { final long mtime = configFile.lastModified(); if (mtime != lastModified) { State newState = new State(); try (FileInputStream in = new FileInputStream(configFile)) { newState.entries = parse(in); } catch (IOException none) { // Ignore -- we'll set and return an empty state } lastModified = mtime; state = newState; } return state; } private Map parse(final InputStream in) throws IOException { final Map m = new LinkedHashMap<>(); final BufferedReader br = new BufferedReader(new InputStreamReader(in)); final List current = new ArrayList<>(4); String line; // The man page doesn't say so, but the OpenSSH parser (readconf.c) // starts out in active mode and thus always applies any lines that // occur before the first host block. We gather those options in a // HostEntry for DEFAULT_NAME. HostEntry defaults = new HostEntry(); current.add(defaults); m.put(HostEntry.DEFAULT_NAME, defaults); while ((line = br.readLine()) != null) { line = line.trim(); if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$ continue; } String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$ // Although the ssh-config man page doesn't say so, the OpenSSH // parser does allow quoted keywords. String keyword = dequote(parts[0].trim()); // man 5 ssh-config says lines had the format "keyword arguments", // with no indication that arguments were optional. However, let's // not crap out on missing arguments. See bug 444319. String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$ if (StringUtils.equalsIgnoreCase("Host", keyword)) { //$NON-NLS-1$ current.clear(); for (String name : HostEntry.parseList(argValue)) { if (name == null || name.isEmpty()) { // null should not occur, but better be safe than sorry. continue; } HostEntry c = m.get(name); if (c == null) { c = new HostEntry(); m.put(name, c); } current.add(c); } continue; } if (current.isEmpty()) { // We received an option outside of a Host block. We // don't know who this should match against, so skip. continue; } if (HostEntry.isListKey(keyword)) { List args = HostEntry.parseList(argValue); for (HostEntry entry : current) { entry.setValue(keyword, args); } } else if (!argValue.isEmpty()) { argValue = dequote(argValue); for (HostEntry entry : current) { entry.setValue(keyword, argValue); } } } return m; } private static boolean isHostMatch(final String pattern, final String name) { if (pattern.startsWith("!")) { //$NON-NLS-1$ return !patternMatchesHost(pattern.substring(1), name); } else { return patternMatchesHost(pattern, name); } } private static boolean patternMatchesHost(final String pattern, final String name) { if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) { final FileNameMatcher fn; try { fn = new FileNameMatcher(pattern, null); } catch (InvalidPatternException e) { return false; } fn.append(name); return fn.isMatch(); } else { // Not a pattern but a full host name return pattern.equals(name); } } private static String dequote(final String value) { if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$ && value.length() > 1) return value.substring(1, value.length() - 1); return value; } private static String nows(final String value) { final StringBuilder b = new StringBuilder(); for (int i = 0; i < value.length(); i++) { if (!Character.isSpaceChar(value.charAt(i))) b.append(value.charAt(i)); } return b.toString(); } private static Boolean yesno(final String value) { if (StringUtils.equalsIgnoreCase("yes", value)) //$NON-NLS-1$ return Boolean.TRUE; return Boolean.FALSE; } private static File toFile(String path, File home) { if (path.startsWith("~/")) { //$NON-NLS-1$ return new File(home, path.substring(2)); } File ret = new File(path); if (ret.isAbsolute()) { return ret; } return new File(home, path); } private static int positive(final String value) { if (value != null) { try { return Integer.parseUnsignedInt(value); } catch (NumberFormatException e) { // Ignore } } return -1; } static String userName() { return AccessController.doPrivileged(new PrivilegedAction() { @Override public String run() { return SystemReader.getInstance() .getProperty(Constants.OS_USER_NAME_KEY); } }); } private static class HostEntry implements ConfigRepository.Config { /** * "Host name" of the HostEntry for the default options before the first * host block in a config file. */ public static final String DEFAULT_NAME = ""; //$NON-NLS-1$ // See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys // to ssh-config keys. private static final Map KEY_MAP = new HashMap<>(); static { KEY_MAP.put("kex", "KexAlgorithms"); //$NON-NLS-1$//$NON-NLS-2$ KEY_MAP.put("server_host_key", "HostKeyAlgorithms"); //$NON-NLS-1$ //$NON-NLS-2$ KEY_MAP.put("cipher.c2s", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$ KEY_MAP.put("cipher.s2c", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$ KEY_MAP.put("mac.c2s", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$ KEY_MAP.put("mac.s2c", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$ KEY_MAP.put("compression.s2c", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$ KEY_MAP.put("compression.c2s", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$ KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$ KEY_MAP.put("MaxAuthTries", "NumberOfPasswordPrompts"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Keys that can be specified multiple times, building up a list. (I.e., * those are the keys that do not follow the general rule of "first * occurrence wins".) */ private static final Set MULTI_KEYS = new HashSet<>(); static { MULTI_KEYS.add("CERTIFICATEFILE"); //$NON-NLS-1$ MULTI_KEYS.add("IDENTITYFILE"); //$NON-NLS-1$ MULTI_KEYS.add("LOCALFORWARD"); //$NON-NLS-1$ MULTI_KEYS.add("REMOTEFORWARD"); //$NON-NLS-1$ MULTI_KEYS.add("SENDENV"); //$NON-NLS-1$ } /** * Keys that take a whitespace-separated list of elements as argument. * Because the dequote-handling is different, we must handle those in * the parser. There are a few other keys that take comma-separated * lists as arguments, but for the parser those are single arguments * that must be quoted if they contain whitespace, and taking them apart * is the responsibility of the user of those keys. */ private static final Set LIST_KEYS = new HashSet<>(); static { LIST_KEYS.add("CANONICALDOMAINS"); //$NON-NLS-1$ LIST_KEYS.add("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$ LIST_KEYS.add("SENDENV"); //$NON-NLS-1$ LIST_KEYS.add("USERKNOWNHOSTSFILE"); //$NON-NLS-1$ } private Map options; private Map> multiOptions; private Map> listOptions; @Override public String getHostname() { return getValue("HOSTNAME"); //$NON-NLS-1$ } @Override public String getUser() { return getValue("USER"); //$NON-NLS-1$ } @Override public int getPort() { return positive(getValue("PORT")); //$NON-NLS-1$ } private static String mapKey(String key) { String k = KEY_MAP.get(key); if (k == null) { k = key; } return k.toUpperCase(Locale.ROOT); } private String findValue(String key) { String k = mapKey(key); String result = options != null ? options.get(k) : null; if (result == null) { // Also check the list and multi options. Modern OpenSSH treats // UserKnownHostsFile and GlobalKnownHostsFile as list-valued, // and so does this parser. Jsch 0.1.54 in general doesn't know // about list-valued options (it _does_ know multi-valued // options, though), and will ask for a single value for such // options. // // Let's be lenient and return at least the first value from // a list-valued or multi-valued key for which Jsch asks for a // single value. List values = listOptions != null ? listOptions.get(k) : null; if (values == null) { values = multiOptions != null ? multiOptions.get(k) : null; } if (values != null && !values.isEmpty()) { result = values.get(0); } } return result; } @Override public String getValue(String key) { // See com.jcraft.jsch.OpenSSHConfig.MyConfig.getValue() for this // special case. if (key.equals("compression.s2c") //$NON-NLS-1$ || key.equals("compression.c2s")) { //$NON-NLS-1$ String foo = findValue(key); if (foo == null || foo.equals("no")) { //$NON-NLS-1$ return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$ } return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$ } return findValue(key); } @Override public String[] getValues(String key) { String k = mapKey(key); List values = listOptions != null ? listOptions.get(k) : null; if (values == null) { values = multiOptions != null ? multiOptions.get(k) : null; } if (values == null || values.isEmpty()) { return new String[0]; } return values.toArray(new String[values.size()]); } public void setValue(String key, String value) { String k = key.toUpperCase(Locale.ROOT); if (MULTI_KEYS.contains(k)) { if (multiOptions == null) { multiOptions = new HashMap<>(); } List values = multiOptions.get(k); if (values == null) { values = new ArrayList<>(4); multiOptions.put(k, values); } values.add(value); } else { if (options == null) { options = new HashMap<>(); } if (!options.containsKey(k)) { options.put(k, value); } } } public void setValue(String key, List values) { if (values.isEmpty()) { // Can occur only on a missing argument: ignore. return; } String k = key.toUpperCase(Locale.ROOT); // Check multi-valued keys first; because of the replacement // strategy, they must take precedence over list-valued keys // which always follow the "first occurrence wins" strategy. // // Note that SendEnv is a multi-valued list-valued key. (It's // rather immaterial for JGit, though.) if (MULTI_KEYS.contains(k)) { if (multiOptions == null) { multiOptions = new HashMap<>(2 * MULTI_KEYS.size()); } List items = multiOptions.get(k); if (items == null) { items = new ArrayList<>(values); multiOptions.put(k, items); } else { items.addAll(values); } } else { if (listOptions == null) { listOptions = new HashMap<>(2 * LIST_KEYS.size()); } if (!listOptions.containsKey(k)) { listOptions.put(k, values); } } } public static boolean isListKey(String key) { return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT)); } /** * Splits the argument into a list of whitespace-separated elements. * Elements containing whitespace must be quoted and will be de-quoted. * * @param argument * argument part of the configuration line as read from the * config file * @return a {@link List} of elements, possibly empty and possibly * containing empty elements */ public static List parseList(String argument) { List result = new ArrayList<>(4); int start = 0; int length = argument.length(); while (start < length) { // Skip whitespace if (Character.isSpaceChar(argument.charAt(start))) { start++; continue; } if (argument.charAt(start) == '"') { int stop = argument.indexOf('"', ++start); if (stop < start) { // No closing double quote: skip break; } result.add(argument.substring(start, stop)); start = stop + 1; } else { int stop = start + 1; while (stop < length && !Character.isSpaceChar(argument.charAt(stop))) { stop++; } result.add(argument.substring(start, stop)); start = stop + 1; } } return result; } protected void merge(HostEntry entry) { if (entry == null) { // Can occur if we could not read the config file return; } if (entry.options != null) { if (options == null) { options = new HashMap<>(); } for (Map.Entry item : entry.options .entrySet()) { if (!options.containsKey(item.getKey())) { options.put(item.getKey(), item.getValue()); } } } if (entry.listOptions != null) { if (listOptions == null) { listOptions = new HashMap<>(2 * LIST_KEYS.size()); } for (Map.Entry> item : entry.listOptions .entrySet()) { if (!listOptions.containsKey(item.getKey())) { listOptions.put(item.getKey(), item.getValue()); } } } if (entry.multiOptions != null) { if (multiOptions == null) { multiOptions = new HashMap<>(2 * MULTI_KEYS.size()); } for (Map.Entry> item : entry.multiOptions .entrySet()) { List values = multiOptions.get(item.getKey()); if (values == null) { values = new ArrayList<>(item.getValue()); multiOptions.put(item.getKey(), values); } else { values.addAll(item.getValue()); } } } } private class Replacer { private final Map replacements = new HashMap<>(); public Replacer(String originalHostName, File home) { replacements.put(Character.valueOf('%'), "%"); //$NON-NLS-1$ replacements.put(Character.valueOf('d'), home.getPath()); // Needs special treatment... String host = getValue("HOSTNAME"); //$NON-NLS-1$ replacements.put(Character.valueOf('h'), originalHostName); if (host != null && host.indexOf('%') >= 0) { host = substitute(host, "h"); //$NON-NLS-1$ options.put("HOSTNAME", host); //$NON-NLS-1$ } if (host != null) { replacements.put(Character.valueOf('h'), host); } String localhost = SystemReader.getInstance().getHostname(); replacements.put(Character.valueOf('l'), localhost); int period = localhost.indexOf('.'); if (period > 0) { localhost = localhost.substring(0, period); } replacements.put(Character.valueOf('L'), localhost); replacements.put(Character.valueOf('n'), originalHostName); replacements.put(Character.valueOf('p'), getValue("PORT")); //$NON-NLS-1$ replacements.put(Character.valueOf('r'), getValue("USER")); //$NON-NLS-1$ replacements.put(Character.valueOf('u'), userName()); replacements.put(Character.valueOf('C'), substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$ } public String substitute(String input, String allowed) { if (input == null || input.length() <= 1 || input.indexOf('%') < 0) { return input; } StringBuilder builder = new StringBuilder(); int start = 0; int length = input.length(); while (start < length) { int percent = input.indexOf('%', start); if (percent < 0 || percent + 1 >= length) { builder.append(input.substring(start)); break; } String replacement = null; char ch = input.charAt(percent + 1); if (ch == '%' || allowed.indexOf(ch) >= 0) { replacement = replacements.get(Character.valueOf(ch)); } if (replacement == null) { builder.append(input.substring(start, percent + 2)); } else { builder.append(input.substring(start, percent)) .append(replacement); } start = percent + 2; } return builder.toString(); } } private List substitute(List values, String allowed, Replacer r) { List result = new ArrayList<>(values.size()); for (String value : values) { result.add(r.substitute(value, allowed)); } return result; } private List replaceTilde(List values, File home) { List result = new ArrayList<>(values.size()); for (String value : values) { result.add(toFile(value, home).getPath()); } return result; } protected void substitute(String originalHostName, File home) { Replacer r = new Replacer(originalHostName, home); if (multiOptions != null) { List values = multiOptions.get("IDENTITYFILE"); //$NON-NLS-1$ if (values != null) { values = substitute(values, "dhlru", r); //$NON-NLS-1$ values = replaceTilde(values, home); multiOptions.put("IDENTITYFILE", values); //$NON-NLS-1$ } values = multiOptions.get("CERTIFICATEFILE"); //$NON-NLS-1$ if (values != null) { values = substitute(values, "dhlru", r); //$NON-NLS-1$ values = replaceTilde(values, home); multiOptions.put("CERTIFICATEFILE", values); //$NON-NLS-1$ } } if (listOptions != null) { List values = listOptions.get("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$ if (values != null) { values = replaceTilde(values, home); listOptions.put("GLOBALKNOWNHOSTSFILE", values); //$NON-NLS-1$ } values = listOptions.get("USERKNOWNHOSTSFILE"); //$NON-NLS-1$ if (values != null) { values = replaceTilde(values, home); listOptions.put("USERKNOWNHOSTSFILE", values); //$NON-NLS-1$ } } if (options != null) { // HOSTNAME already done in Replacer constructor String value = options.get("IDENTITYAGENT"); //$NON-NLS-1$ if (value != null) { value = r.substitute(value, "dhlru"); //$NON-NLS-1$ value = toFile(value, home).getPath(); options.put("IDENTITYAGENT", value); //$NON-NLS-1$ } } // Match is not implemented and would need to be done elsewhere // anyway. ControlPath, LocalCommand, ProxyCommand, and // RemoteCommand are not used by Jsch. } @Override @SuppressWarnings("nls") public String toString() { return "HostEntry [options=" + options + ", multiOptions=" + multiOptions + ", listOptions=" + listOptions + "]"; } } /** * Configuration of one "Host" block in the configuration file. *

* If returned from {@link OpenSshConfig#lookup(String)} some or all of the * properties may not be populated. The properties which are not populated * should be defaulted by the caller. *

* When returned from {@link OpenSshConfig#lookup(String)} any wildcard * entries which appear later in the configuration file will have been * already merged into this block. */ public static class Host { String hostName; int port; File identityFile; String user; String preferredAuthentications; Boolean batchMode; String strictHostKeyChecking; int connectionAttempts; private Config config; /** * Creates a new uninitialized {@link Host}. */ public Host() { // For API backwards compatibility with pre-4.9 JGit } Host(Config config, String hostName, File homeDir) { this.config = config; complete(hostName, homeDir); } /** * @return the value StrictHostKeyChecking property, the valid values * are "yes" (unknown hosts are not accepted), "no" (unknown * hosts are always accepted), and "ask" (user should be asked * before accepting the host) */ public String getStrictHostKeyChecking() { return strictHostKeyChecking; } /** * @return the real IP address or host name to connect to; never null. */ public String getHostName() { return hostName; } /** * @return the real port number to connect to; never 0. */ public int getPort() { return port; } /** * @return path of the private key file to use for authentication; null * if the caller should use default authentication strategies. */ public File getIdentityFile() { return identityFile; } /** * @return the real user name to connect as; never null. */ public String getUser() { return user; } /** * @return the preferred authentication methods, separated by commas if * more than one authentication method is preferred. */ public String getPreferredAuthentications() { return preferredAuthentications; } /** * @return true if batch (non-interactive) mode is preferred for this * host connection. */ public boolean isBatchMode() { return batchMode != null && batchMode.booleanValue(); } /** * @return the number of tries (one per second) to connect before * exiting. The argument must be an integer. This may be useful * in scripts if the connection sometimes fails. The default is * 1. * @since 3.4 */ public int getConnectionAttempts() { return connectionAttempts; } private void complete(String initialHostName, File homeDir) { // Try to set values from the options. hostName = config.getHostname(); user = config.getUser(); port = config.getPort(); connectionAttempts = positive( config.getValue("ConnectionAttempts")); //$NON-NLS-1$ strictHostKeyChecking = config.getValue("StrictHostKeyChecking"); //$NON-NLS-1$ String value = config.getValue("BatchMode"); //$NON-NLS-1$ if (value != null) { batchMode = yesno(value); } value = config.getValue("PreferredAuthentications"); //$NON-NLS-1$ if (value != null) { preferredAuthentications = nows(value); } // Fill in defaults if still not set if (hostName == null) { hostName = initialHostName; } if (user == null) { user = OpenSshConfig.userName(); } if (port <= 0) { port = OpenSshConfig.SSH_PORT; } if (connectionAttempts <= 0) { connectionAttempts = 1; } String[] identityFiles = config.getValues("IdentityFile"); //$NON-NLS-1$ if (identityFiles != null && identityFiles.length > 0) { identityFile = toFile(identityFiles[0], homeDir); } } Config getConfig() { return config; } @Override @SuppressWarnings("nls") public String toString() { return "Host [hostName=" + hostName + ", port=" + port + ", identityFile=" + identityFile + ", user=" + user + ", preferredAuthentications=" + preferredAuthentications + ", batchMode=" + batchMode + ", strictHostKeyChecking=" + strictHostKeyChecking + ", connectionAttempts=" + connectionAttempts + ", config=" + config + "]"; } } /** * Retrieves the full {@link com.jcraft.jsch.ConfigRepository.Config Config} * for the given host name. Should be called only by Jsch and tests. * * @param hostName * to get the config for * @return the configuration for the host * @since 4.9 */ @Override public Config getConfig(String hostName) { Host host = lookup(hostName); return host.getConfig(); } @Override @SuppressWarnings("nls") public String toString() { return "OpenSshConfig [home=" + home + ", configFile=" + configFile + ", lastModified=" + lastModified + ", state=" + state + "]"; } }