| /* |
| * Copyright (C) 2008, 2018, Google Inc. and others |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| //TODO(ms): move to org.eclipse.jgit.ssh.jsch in 6.0 |
| package org.eclipse.jgit.transport; |
| |
| import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.positive; |
| |
| import java.io.File; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile; |
| import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.HostEntry; |
| import org.eclipse.jgit.util.FS; |
| |
| import com.jcraft.jsch.ConfigRepository; |
| |
| /** |
| * Fairly complete configuration parser for the OpenSSH ~/.ssh/config file. |
| * <p> |
| * 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: |
| * <ul> |
| * <li>it splits lines of the format "keyword = value" wrongly: you'd end up |
| * with the value "= value". |
| * <li>its "Host" keyword is not case insensitive. |
| * <li>it doesn't handle quoted values. |
| * <li>JSch's OpenSSHConfig doesn't monitor for config file changes. |
| * </ul> |
| * <p> |
| * This parser makes the critical options available to |
| * {@link org.eclipse.jgit.transport.SshSessionFactory} via |
| * {@link org.eclipse.jgit.transport.OpenSshConfig.Host} objects returned |
| * by {@link #lookup(String)}, and implements a fully conforming |
| * {@link com.jcraft.jsch.ConfigRepository} providing |
| * {@link com.jcraft.jsch.ConfigRepository.Config}s via |
| * {@link #getConfig(String)}. |
| * </p> |
| * |
| * @see OpenSshConfigFile |
| */ |
| public class OpenSshConfig implements ConfigRepository { |
| |
| /** |
| * Obtain the user's configuration data. |
| * <p> |
| * 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, SshConstants.SSH_DIR), |
| SshConstants.CONFIG); |
| return new OpenSshConfig(home, config); |
| } |
| |
| /** The base file. */ |
| private OpenSshConfigFile configFile; |
| |
| OpenSshConfig(File h, File cfg) { |
| configFile = new OpenSshConfigFile(h, cfg, |
| SshSessionFactory.getLocalUserName()); |
| } |
| |
| /** |
| * 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(String hostName) { |
| HostEntry entry = configFile.lookup(hostName, -1, null); |
| return new Host(entry, hostName, configFile.getLocalUserName()); |
| } |
| |
| /** |
| * Configuration of one "Host" block in the configuration file. |
| * <p> |
| * 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. |
| * <p> |
| * 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 HostEntry entry; |
| |
| private Config config; |
| |
| // See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys |
| // to ssh-config keys. |
| private static final Map<String, String> KEY_MAP = new TreeMap<>( |
| String.CASE_INSENSITIVE_ORDER); |
| |
| static { |
| KEY_MAP.put("kex", SshConstants.KEX_ALGORITHMS); //$NON-NLS-1$ |
| KEY_MAP.put("server_host_key", SshConstants.HOST_KEY_ALGORITHMS); //$NON-NLS-1$ |
| KEY_MAP.put("cipher.c2s", SshConstants.CIPHERS); //$NON-NLS-1$ |
| KEY_MAP.put("cipher.s2c", SshConstants.CIPHERS); //$NON-NLS-1$ |
| KEY_MAP.put("mac.c2s", SshConstants.MACS); //$NON-NLS-1$ |
| KEY_MAP.put("mac.s2c", SshConstants.MACS); //$NON-NLS-1$ |
| KEY_MAP.put("compression.s2c", SshConstants.COMPRESSION); //$NON-NLS-1$ |
| KEY_MAP.put("compression.c2s", SshConstants.COMPRESSION); //$NON-NLS-1$ |
| KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$ |
| KEY_MAP.put("MaxAuthTries", //$NON-NLS-1$ |
| SshConstants.NUMBER_OF_PASSWORD_PROMPTS); |
| } |
| |
| private static String mapKey(String key) { |
| String k = KEY_MAP.get(key); |
| return k != null ? k : key; |
| } |
| |
| /** |
| * Creates a new uninitialized {@link Host}. |
| */ |
| public Host() { |
| // For API backwards compatibility with pre-4.9 JGit |
| } |
| |
| Host(HostEntry entry, String hostName, String localUserName) { |
| this.entry = entry; |
| complete(hostName, localUserName); |
| } |
| |
| /** |
| * @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, String localUserName) { |
| // Try to set values from the options. |
| hostName = entry.getValue(SshConstants.HOST_NAME); |
| user = entry.getValue(SshConstants.USER); |
| port = positive(entry.getValue(SshConstants.PORT)); |
| connectionAttempts = positive( |
| entry.getValue(SshConstants.CONNECTION_ATTEMPTS)); |
| strictHostKeyChecking = entry |
| .getValue(SshConstants.STRICT_HOST_KEY_CHECKING); |
| batchMode = Boolean.valueOf(OpenSshConfigFile |
| .flag(entry.getValue(SshConstants.BATCH_MODE))); |
| preferredAuthentications = entry |
| .getValue(SshConstants.PREFERRED_AUTHENTICATIONS); |
| // Fill in defaults if still not set |
| if (hostName == null || hostName.isEmpty()) { |
| hostName = initialHostName; |
| } |
| if (user == null || user.isEmpty()) { |
| user = localUserName; |
| } |
| if (port <= 0) { |
| port = SshConstants.SSH_DEFAULT_PORT; |
| } |
| if (connectionAttempts <= 0) { |
| connectionAttempts = 1; |
| } |
| List<String> identityFiles = entry |
| .getValues(SshConstants.IDENTITY_FILE); |
| if (identityFiles != null && !identityFiles.isEmpty()) { |
| identityFile = new File(identityFiles.get(0)); |
| } |
| } |
| |
| Config getConfig() { |
| if (config == null) { |
| config = new Config() { |
| |
| @Override |
| public String getHostname() { |
| return Host.this.getHostName(); |
| } |
| |
| @Override |
| public String getUser() { |
| return Host.this.getUser(); |
| } |
| |
| @Override |
| public int getPort() { |
| return Host.this.getPort(); |
| } |
| |
| @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$ |
| if (!OpenSshConfigFile.flag( |
| Host.this.entry.getValue(mapKey(key)))) { |
| return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$ |
| } |
| return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$ |
| } |
| return Host.this.entry.getValue(mapKey(key)); |
| } |
| |
| @Override |
| public String[] getValues(String key) { |
| List<String> values = Host.this.entry |
| .getValues(mapKey(key)); |
| if (values == null) { |
| return new String[0]; |
| } |
| return values.toArray(new String[0]); |
| } |
| }; |
| } |
| 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 + ", entry=" + entry + "]"; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * <p> |
| * Retrieves the full {@link com.jcraft.jsch.ConfigRepository.Config Config} |
| * for the given host name. Should be called only by Jsch and tests. |
| * |
| * @since 4.9 |
| */ |
| @Override |
| public Config getConfig(String hostName) { |
| Host host = lookup(hostName); |
| return host.getConfig(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| return "OpenSshConfig [configFile=" + configFile + ']'; //$NON-NLS-1$ |
| } |
| } |