You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

OpenSshConfig.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /*
  2. * Copyright (C) 2008, 2018, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.transport;
  44. import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.positive;
  45. import java.io.File;
  46. import java.util.List;
  47. import java.util.Map;
  48. import java.util.TreeMap;
  49. import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
  50. import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.HostEntry;
  51. import org.eclipse.jgit.util.FS;
  52. import com.jcraft.jsch.ConfigRepository;
  53. /**
  54. * Fairly complete configuration parser for the OpenSSH ~/.ssh/config file.
  55. * <p>
  56. * JSch does have its own config file parser
  57. * {@link com.jcraft.jsch.OpenSSHConfig} since version 0.1.50, but it has a
  58. * number of problems:
  59. * <ul>
  60. * <li>it splits lines of the format "keyword = value" wrongly: you'd end up
  61. * with the value "= value".
  62. * <li>its "Host" keyword is not case insensitive.
  63. * <li>it doesn't handle quoted values.
  64. * <li>JSch's OpenSSHConfig doesn't monitor for config file changes.
  65. * </ul>
  66. * <p>
  67. * This parser makes the critical options available to
  68. * {@link org.eclipse.jgit.transport.SshSessionFactory} via
  69. * {@link org.eclipse.jgit.transport.OpenSshConfig.Host} objects returned by
  70. * {@link #lookup(String)}, and implements a fully conforming
  71. * {@link com.jcraft.jsch.ConfigRepository} providing
  72. * {@link com.jcraft.jsch.ConfigRepository.Config}s via
  73. * {@link #getConfig(String)}.
  74. * </p>
  75. *
  76. * @see OpenSshConfigFile
  77. */
  78. public class OpenSshConfig implements ConfigRepository {
  79. /**
  80. * Obtain the user's configuration data.
  81. * <p>
  82. * The configuration file is always returned to the caller, even if no file
  83. * exists in the user's home directory at the time the call was made. Lookup
  84. * requests are cached and are automatically updated if the user modifies
  85. * the configuration file since the last time it was cached.
  86. *
  87. * @param fs
  88. * the file system abstraction which will be necessary to
  89. * perform certain file system operations.
  90. * @return a caching reader of the user's configuration file.
  91. */
  92. public static OpenSshConfig get(FS fs) {
  93. File home = fs.userHome();
  94. if (home == null)
  95. home = new File(".").getAbsoluteFile(); //$NON-NLS-1$
  96. final File config = new File(new File(home, SshConstants.SSH_DIR),
  97. SshConstants.CONFIG);
  98. return new OpenSshConfig(home, config);
  99. }
  100. /** The base file. */
  101. private OpenSshConfigFile configFile;
  102. OpenSshConfig(File h, File cfg) {
  103. configFile = new OpenSshConfigFile(h, cfg,
  104. SshSessionFactory.getLocalUserName());
  105. }
  106. /**
  107. * Locate the configuration for a specific host request.
  108. *
  109. * @param hostName
  110. * the name the user has supplied to the SSH tool. This may be a
  111. * real host name, or it may just be a "Host" block in the
  112. * configuration file.
  113. * @return r configuration for the requested name. Never null.
  114. */
  115. public Host lookup(String hostName) {
  116. HostEntry entry = configFile.lookup(hostName, -1, null);
  117. return new Host(entry, hostName, configFile.getLocalUserName());
  118. }
  119. /**
  120. * Configuration of one "Host" block in the configuration file.
  121. * <p>
  122. * If returned from {@link OpenSshConfig#lookup(String)} some or all of the
  123. * properties may not be populated. The properties which are not populated
  124. * should be defaulted by the caller.
  125. * <p>
  126. * When returned from {@link OpenSshConfig#lookup(String)} any wildcard
  127. * entries which appear later in the configuration file will have been
  128. * already merged into this block.
  129. */
  130. public static class Host {
  131. String hostName;
  132. int port;
  133. File identityFile;
  134. String user;
  135. String preferredAuthentications;
  136. Boolean batchMode;
  137. String strictHostKeyChecking;
  138. int connectionAttempts;
  139. private HostEntry entry;
  140. private Config config;
  141. // See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys
  142. // to ssh-config keys.
  143. private static final Map<String, String> KEY_MAP = new TreeMap<>(
  144. String.CASE_INSENSITIVE_ORDER);
  145. static {
  146. KEY_MAP.put("kex", SshConstants.KEX_ALGORITHMS); //$NON-NLS-1$
  147. KEY_MAP.put("server_host_key", SshConstants.HOST_KEY_ALGORITHMS); //$NON-NLS-1$
  148. KEY_MAP.put("cipher.c2s", SshConstants.CIPHERS); //$NON-NLS-1$
  149. KEY_MAP.put("cipher.s2c", SshConstants.CIPHERS); //$NON-NLS-1$
  150. KEY_MAP.put("mac.c2s", SshConstants.MACS); //$NON-NLS-1$
  151. KEY_MAP.put("mac.s2c", SshConstants.MACS); //$NON-NLS-1$
  152. KEY_MAP.put("compression.s2c", SshConstants.COMPRESSION); //$NON-NLS-1$
  153. KEY_MAP.put("compression.c2s", SshConstants.COMPRESSION); //$NON-NLS-1$
  154. KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$
  155. KEY_MAP.put("MaxAuthTries", //$NON-NLS-1$
  156. SshConstants.NUMBER_OF_PASSWORD_PROMPTS);
  157. }
  158. private static String mapKey(String key) {
  159. String k = KEY_MAP.get(key);
  160. return k != null ? k : key;
  161. }
  162. /**
  163. * Creates a new uninitialized {@link Host}.
  164. */
  165. public Host() {
  166. // For API backwards compatibility with pre-4.9 JGit
  167. }
  168. Host(HostEntry entry, String hostName, String localUserName) {
  169. this.entry = entry;
  170. complete(hostName, localUserName);
  171. }
  172. /**
  173. * @return the value StrictHostKeyChecking property, the valid values
  174. * are "yes" (unknown hosts are not accepted), "no" (unknown
  175. * hosts are always accepted), and "ask" (user should be asked
  176. * before accepting the host)
  177. */
  178. public String getStrictHostKeyChecking() {
  179. return strictHostKeyChecking;
  180. }
  181. /**
  182. * @return the real IP address or host name to connect to; never null.
  183. */
  184. public String getHostName() {
  185. return hostName;
  186. }
  187. /**
  188. * @return the real port number to connect to; never 0.
  189. */
  190. public int getPort() {
  191. return port;
  192. }
  193. /**
  194. * @return path of the private key file to use for authentication; null
  195. * if the caller should use default authentication strategies.
  196. */
  197. public File getIdentityFile() {
  198. return identityFile;
  199. }
  200. /**
  201. * @return the real user name to connect as; never null.
  202. */
  203. public String getUser() {
  204. return user;
  205. }
  206. /**
  207. * @return the preferred authentication methods, separated by commas if
  208. * more than one authentication method is preferred.
  209. */
  210. public String getPreferredAuthentications() {
  211. return preferredAuthentications;
  212. }
  213. /**
  214. * @return true if batch (non-interactive) mode is preferred for this
  215. * host connection.
  216. */
  217. public boolean isBatchMode() {
  218. return batchMode != null && batchMode.booleanValue();
  219. }
  220. /**
  221. * @return the number of tries (one per second) to connect before
  222. * exiting. The argument must be an integer. This may be useful
  223. * in scripts if the connection sometimes fails. The default is
  224. * 1.
  225. * @since 3.4
  226. */
  227. public int getConnectionAttempts() {
  228. return connectionAttempts;
  229. }
  230. private void complete(String initialHostName, String localUserName) {
  231. // Try to set values from the options.
  232. hostName = entry.getValue(SshConstants.HOST_NAME);
  233. user = entry.getValue(SshConstants.USER);
  234. port = positive(entry.getValue(SshConstants.PORT));
  235. connectionAttempts = positive(
  236. entry.getValue(SshConstants.CONNECTION_ATTEMPTS));
  237. strictHostKeyChecking = entry
  238. .getValue(SshConstants.STRICT_HOST_KEY_CHECKING);
  239. batchMode = Boolean.valueOf(OpenSshConfigFile
  240. .flag(entry.getValue(SshConstants.BATCH_MODE)));
  241. preferredAuthentications = entry
  242. .getValue(SshConstants.PREFERRED_AUTHENTICATIONS);
  243. // Fill in defaults if still not set
  244. if (hostName == null || hostName.isEmpty()) {
  245. hostName = initialHostName;
  246. }
  247. if (user == null || user.isEmpty()) {
  248. user = localUserName;
  249. }
  250. if (port <= 0) {
  251. port = SshConstants.SSH_DEFAULT_PORT;
  252. }
  253. if (connectionAttempts <= 0) {
  254. connectionAttempts = 1;
  255. }
  256. List<String> identityFiles = entry
  257. .getValues(SshConstants.IDENTITY_FILE);
  258. if (identityFiles != null && !identityFiles.isEmpty()) {
  259. identityFile = new File(identityFiles.get(0));
  260. }
  261. }
  262. Config getConfig() {
  263. if (config == null) {
  264. config = new Config() {
  265. @Override
  266. public String getHostname() {
  267. return Host.this.getHostName();
  268. }
  269. @Override
  270. public String getUser() {
  271. return Host.this.getUser();
  272. }
  273. @Override
  274. public int getPort() {
  275. return Host.this.getPort();
  276. }
  277. @Override
  278. public String getValue(String key) {
  279. // See com.jcraft.jsch.OpenSSHConfig.MyConfig.getValue()
  280. // for this special case.
  281. if (key.equals("compression.s2c") //$NON-NLS-1$
  282. || key.equals("compression.c2s")) { //$NON-NLS-1$
  283. if (!OpenSshConfigFile.flag(
  284. Host.this.entry.getValue(mapKey(key)))) {
  285. return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$
  286. }
  287. return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$
  288. }
  289. return Host.this.entry.getValue(mapKey(key));
  290. }
  291. @Override
  292. public String[] getValues(String key) {
  293. List<String> values = Host.this.entry
  294. .getValues(mapKey(key));
  295. if (values == null) {
  296. return new String[0];
  297. }
  298. return values.toArray(new String[0]);
  299. }
  300. };
  301. }
  302. return config;
  303. }
  304. @Override
  305. @SuppressWarnings("nls")
  306. public String toString() {
  307. return "Host [hostName=" + hostName + ", port=" + port
  308. + ", identityFile=" + identityFile + ", user=" + user
  309. + ", preferredAuthentications=" + preferredAuthentications
  310. + ", batchMode=" + batchMode + ", strictHostKeyChecking="
  311. + strictHostKeyChecking + ", connectionAttempts="
  312. + connectionAttempts + ", entry=" + entry + "]";
  313. }
  314. }
  315. /**
  316. * {@inheritDoc}
  317. * <p>
  318. * Retrieves the full {@link com.jcraft.jsch.ConfigRepository.Config Config}
  319. * for the given host name. Should be called only by Jsch and tests.
  320. *
  321. * @since 4.9
  322. */
  323. @Override
  324. public Config getConfig(String hostName) {
  325. Host host = lookup(hostName);
  326. return host.getConfig();
  327. }
  328. /** {@inheritDoc} */
  329. @Override
  330. public String toString() {
  331. return "OpenSshConfig [configFile=" + configFile + ']'; //$NON-NLS-1$
  332. }
  333. }