| /* |
| * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> 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 |
| */ |
| package org.eclipse.jgit.internal.transport.sshd; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static java.text.MessageFormat.format; |
| import static org.apache.sshd.client.config.hosts.HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM; |
| import static org.apache.sshd.client.config.hosts.HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.stream.Collectors; |
| |
| import org.apache.sshd.client.config.hosts.HostPatternValue; |
| import org.apache.sshd.client.config.hosts.HostPatternsHolder; |
| import org.apache.sshd.client.config.hosts.KnownHostEntry; |
| import org.apache.sshd.client.config.hosts.KnownHostHashValue; |
| import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Apache MINA sshd 2.0.0 KnownHostEntry cannot read a host entry line like |
| * "host:port ssh-rsa <key>"; it complains about an illegal character in the |
| * host name (correct would be "[host]:port"). The default known_hosts reader |
| * also aborts reading on the first error. |
| * <p> |
| * This reader is a bit more robust and tries to handle this case if there is |
| * only one colon (otherwise it might be an IPv6 address (without port)), and it |
| * skips and logs invalid entries, but still returns all other valid entries |
| * from the file. |
| * </p> |
| */ |
| public class KnownHostEntryReader { |
| |
| private static final Logger LOG = LoggerFactory |
| .getLogger(KnownHostEntryReader.class); |
| |
| private KnownHostEntryReader() { |
| // No instantiation |
| } |
| |
| /** |
| * Reads a known_hosts file and returns all valid entries. Invalid entries |
| * are skipped (and a message is logged). |
| * |
| * @param path |
| * of the file to read |
| * @return a {@link List} of all valid entries read from the file |
| * @throws IOException |
| * if the file cannot be read. |
| */ |
| public static List<KnownHostEntry> readFromFile(Path path) |
| throws IOException { |
| List<KnownHostEntry> result = new LinkedList<>(); |
| try (BufferedReader r = Files.newBufferedReader(path, UTF_8)) { |
| r.lines().forEachOrdered(l -> { |
| if (l == null) { |
| return; |
| } |
| String line = clean(l); |
| if (line.isEmpty()) { |
| return; |
| } |
| try { |
| KnownHostEntry entry = parseHostEntry(line); |
| if (entry != null) { |
| result.add(entry); |
| } else { |
| LOG.warn(format(SshdText.get().knownHostsInvalidLine, |
| path, line)); |
| } |
| } catch (RuntimeException e) { |
| LOG.warn(format(SshdText.get().knownHostsInvalidLine, path, |
| line), e); |
| } |
| }); |
| } |
| return result; |
| } |
| |
| private static String clean(String line) { |
| int i = line.indexOf('#'); |
| return i < 0 ? line.trim() : line.substring(0, i).trim(); |
| } |
| |
| private static KnownHostEntry parseHostEntry(String line) { |
| KnownHostEntry entry = new KnownHostEntry(); |
| entry.setConfigLine(line); |
| String tmp = line; |
| int i = 0; |
| if (tmp.charAt(0) == KnownHostEntry.MARKER_INDICATOR) { |
| // A marker |
| i = tmp.indexOf(' ', 1); |
| if (i < 0) { |
| return null; |
| } |
| entry.setMarker(tmp.substring(1, i)); |
| tmp = tmp.substring(i + 1).trim(); |
| } |
| i = tmp.indexOf(' '); |
| if (i < 0) { |
| return null; |
| } |
| // Hash, or host patterns |
| if (tmp.charAt(0) == KnownHostHashValue.HASHED_HOST_DELIMITER) { |
| // Hashed host entry |
| KnownHostHashValue hash = KnownHostHashValue |
| .parse(tmp.substring(0, i)); |
| if (hash == null) { |
| return null; |
| } |
| entry.setHashedEntry(hash); |
| entry.setPatterns(null); |
| } else { |
| Collection<HostPatternValue> patterns = parsePatterns( |
| tmp.substring(0, i)); |
| if (patterns == null || patterns.isEmpty()) { |
| return null; |
| } |
| entry.setHashedEntry(null); |
| entry.setPatterns(patterns); |
| } |
| tmp = tmp.substring(i + 1).trim(); |
| AuthorizedKeyEntry key = AuthorizedKeyEntry |
| .parseAuthorizedKeyEntry(tmp); |
| if (key == null) { |
| return null; |
| } |
| entry.setKeyEntry(key); |
| return entry; |
| } |
| |
| private static Collection<HostPatternValue> parsePatterns(String text) { |
| if (text.isEmpty()) { |
| return null; |
| } |
| List<String> items = Arrays.stream(text.split(",")) //$NON-NLS-1$ |
| .filter(item -> item != null && !item.isEmpty()).map(item -> { |
| if (NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM == item |
| .charAt(0)) { |
| return item; |
| } |
| int firstColon = item.indexOf(':'); |
| if (firstColon < 0) { |
| return item; |
| } |
| int secondColon = item.indexOf(':', firstColon + 1); |
| if (secondColon > 0) { |
| // Assume an IPv6 address (without port). |
| return item; |
| } |
| // We have "host:port", should be "[host]:port" |
| return NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM |
| + item.substring(0, firstColon) |
| + NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM |
| + item.substring(firstColon); |
| }).collect(Collectors.toList()); |
| return items.isEmpty() ? null : HostPatternsHolder.parsePatterns(items); |
| } |
| } |