KnownHostEntryReader.java
- /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
- * 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.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);
- }
- }