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.

KnownHostEntryReader.java 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*
  2. * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.transport.sshd;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static java.text.MessageFormat.format;
  13. import static org.apache.sshd.client.config.hosts.HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM;
  14. import static org.apache.sshd.client.config.hosts.HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM;
  15. import java.io.BufferedReader;
  16. import java.io.IOException;
  17. import java.nio.file.Files;
  18. import java.nio.file.Path;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import java.util.stream.Collectors;
  24. import org.apache.sshd.client.config.hosts.HostPatternValue;
  25. import org.apache.sshd.client.config.hosts.HostPatternsHolder;
  26. import org.apache.sshd.client.config.hosts.KnownHostEntry;
  27. import org.apache.sshd.client.config.hosts.KnownHostHashValue;
  28. import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
  29. import org.slf4j.Logger;
  30. import org.slf4j.LoggerFactory;
  31. /**
  32. * Apache MINA sshd 2.0.0 KnownHostEntry cannot read a host entry line like
  33. * "host:port ssh-rsa <key>"; it complains about an illegal character in the
  34. * host name (correct would be "[host]:port"). The default known_hosts reader
  35. * also aborts reading on the first error.
  36. * <p>
  37. * This reader is a bit more robust and tries to handle this case if there is
  38. * only one colon (otherwise it might be an IPv6 address (without port)), and it
  39. * skips and logs invalid entries, but still returns all other valid entries
  40. * from the file.
  41. * </p>
  42. */
  43. public class KnownHostEntryReader {
  44. private static final Logger LOG = LoggerFactory
  45. .getLogger(KnownHostEntryReader.class);
  46. private KnownHostEntryReader() {
  47. // No instantiation
  48. }
  49. /**
  50. * Reads a known_hosts file and returns all valid entries. Invalid entries
  51. * are skipped (and a message is logged).
  52. *
  53. * @param path
  54. * of the file to read
  55. * @return a {@link List} of all valid entries read from the file
  56. * @throws IOException
  57. * if the file cannot be read.
  58. */
  59. public static List<KnownHostEntry> readFromFile(Path path)
  60. throws IOException {
  61. List<KnownHostEntry> result = new LinkedList<>();
  62. try (BufferedReader r = Files.newBufferedReader(path, UTF_8)) {
  63. r.lines().forEachOrdered(l -> {
  64. if (l == null) {
  65. return;
  66. }
  67. String line = clean(l);
  68. if (line.isEmpty()) {
  69. return;
  70. }
  71. try {
  72. KnownHostEntry entry = parseHostEntry(line);
  73. if (entry != null) {
  74. result.add(entry);
  75. } else {
  76. LOG.warn(format(SshdText.get().knownHostsInvalidLine,
  77. path, line));
  78. }
  79. } catch (RuntimeException e) {
  80. LOG.warn(format(SshdText.get().knownHostsInvalidLine, path,
  81. line), e);
  82. }
  83. });
  84. }
  85. return result;
  86. }
  87. private static String clean(String line) {
  88. int i = line.indexOf('#');
  89. return i < 0 ? line.trim() : line.substring(0, i).trim();
  90. }
  91. private static KnownHostEntry parseHostEntry(String line) {
  92. KnownHostEntry entry = new KnownHostEntry();
  93. entry.setConfigLine(line);
  94. String tmp = line;
  95. int i = 0;
  96. if (tmp.charAt(0) == KnownHostEntry.MARKER_INDICATOR) {
  97. // A marker
  98. i = tmp.indexOf(' ', 1);
  99. if (i < 0) {
  100. return null;
  101. }
  102. entry.setMarker(tmp.substring(1, i));
  103. tmp = tmp.substring(i + 1).trim();
  104. }
  105. i = tmp.indexOf(' ');
  106. if (i < 0) {
  107. return null;
  108. }
  109. // Hash, or host patterns
  110. if (tmp.charAt(0) == KnownHostHashValue.HASHED_HOST_DELIMITER) {
  111. // Hashed host entry
  112. KnownHostHashValue hash = KnownHostHashValue
  113. .parse(tmp.substring(0, i));
  114. if (hash == null) {
  115. return null;
  116. }
  117. entry.setHashedEntry(hash);
  118. entry.setPatterns(null);
  119. } else {
  120. Collection<HostPatternValue> patterns = parsePatterns(
  121. tmp.substring(0, i));
  122. if (patterns == null || patterns.isEmpty()) {
  123. return null;
  124. }
  125. entry.setHashedEntry(null);
  126. entry.setPatterns(patterns);
  127. }
  128. tmp = tmp.substring(i + 1).trim();
  129. AuthorizedKeyEntry key = AuthorizedKeyEntry
  130. .parseAuthorizedKeyEntry(tmp);
  131. if (key == null) {
  132. return null;
  133. }
  134. entry.setKeyEntry(key);
  135. return entry;
  136. }
  137. private static Collection<HostPatternValue> parsePatterns(String text) {
  138. if (text.isEmpty()) {
  139. return null;
  140. }
  141. List<String> items = Arrays.stream(text.split(",")) //$NON-NLS-1$
  142. .filter(item -> item != null && !item.isEmpty()).map(item -> {
  143. if (NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM == item
  144. .charAt(0)) {
  145. return item;
  146. }
  147. int firstColon = item.indexOf(':');
  148. if (firstColon < 0) {
  149. return item;
  150. }
  151. int secondColon = item.indexOf(':', firstColon + 1);
  152. if (secondColon > 0) {
  153. // Assume an IPv6 address (without port).
  154. return item;
  155. }
  156. // We have "host:port", should be "[host]:port"
  157. return NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM
  158. + item.substring(0, firstColon)
  159. + NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM
  160. + item.substring(firstColon);
  161. }).collect(Collectors.toList());
  162. return items.isEmpty() ? null : HostPatternsHolder.parsePatterns(items);
  163. }
  164. }