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.

OpenSshConfigFile.java 27KB

Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Merge branch 'stable-5.1' into stable-5.2 * stable-5.1: Fix OpenSshConfigTest#config FileSnapshot: fix bug with timestamp thresholding In LockFile#waitForStatChange wait in units of file time resolution Cache FileStoreAttributeCache per directory Fix FileSnapshot#save(long) and FileSnapshot#save(Instant) Persist minimal racy threshold and allow manual configuration Measure minimum racy interval to auto-configure FileSnapshot Reuse FileUtils to recursively delete files created by tests Fix FileAttributeCache.toString() Add test for racy git detection in FileSnapshot Repeat RefDirectoryTest.testGetRef_DiscoversModifiedLoose 100 times Fix org.eclipse.jdt.core.prefs of org.eclipse.jgit.junit Add missing javadoc in org.eclipse.jgit.junit Enhance RepeatRule to report number of failures at the end Fix FileSnapshotTests for filesystem with high timestamp resolution Retry deleting test files in FileBasedConfigTest Measure filesystem timestamp resolution already in test setup Refactor FileSnapshotTest to use NIO APIs Measure stored timestamp resolution instead of time to touch file Handle CancellationException in FileStoreAttributeCache Fix FileSnapshot#saveNoConfig Use Instant for smudge time in DirCache and DirCacheEntry Use Instant instead of milliseconds for filesystem timestamp handling Workaround SecurityException in FS#getFsTimestampResolution Fix NPE in FS$FileStoreAttributeCache.getFsTimestampResolution FS: ignore AccessDeniedException when measuring timestamp resolution Add debug trace for FileSnapshot Use FileChannel.open to touch file and set mtime to now Persist filesystem timestamp resolution and allow manual configuration Increase bazel timeout for long running tests Bazel: Fix lint warning flagged by buildifier Update bazlets to latest version Bazel: Add missing dependencies for ArchiveCommandTest Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file Add support for nanoseconds and microseconds for Config#getTimeUnit Optionally measure filesystem timestamp resolution asynchronously Delete unused FileTreeIteratorWithTimeControl FileSnapshot#equals: consider UNKNOWN_SIZE Timeout measuring file timestamp resolution after 2 seconds Fix RacyGitTests#testRacyGitDetection Change RacyGitTests to create a racy git situation in a stable way Deprecate Constants.CHARACTER_ENCODING in favor of StandardCharsets.UTF_8 Fix non-deterministic hash of archives created by ArchiveCommand Update Maven plugins ecj, plexus, error-prone Update Maven plugins and cleanup Maven warnings Make inner classes static where possible Fix API problem filters Change-Id: Ia57385b2a60f48a5317c8d723721c235d7043a84 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
4 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Merge branch 'stable-5.1' into stable-5.2 * stable-5.1: Fix OpenSshConfigTest#config FileSnapshot: fix bug with timestamp thresholding In LockFile#waitForStatChange wait in units of file time resolution Cache FileStoreAttributeCache per directory Fix FileSnapshot#save(long) and FileSnapshot#save(Instant) Persist minimal racy threshold and allow manual configuration Measure minimum racy interval to auto-configure FileSnapshot Reuse FileUtils to recursively delete files created by tests Fix FileAttributeCache.toString() Add test for racy git detection in FileSnapshot Repeat RefDirectoryTest.testGetRef_DiscoversModifiedLoose 100 times Fix org.eclipse.jdt.core.prefs of org.eclipse.jgit.junit Add missing javadoc in org.eclipse.jgit.junit Enhance RepeatRule to report number of failures at the end Fix FileSnapshotTests for filesystem with high timestamp resolution Retry deleting test files in FileBasedConfigTest Measure filesystem timestamp resolution already in test setup Refactor FileSnapshotTest to use NIO APIs Measure stored timestamp resolution instead of time to touch file Handle CancellationException in FileStoreAttributeCache Fix FileSnapshot#saveNoConfig Use Instant for smudge time in DirCache and DirCacheEntry Use Instant instead of milliseconds for filesystem timestamp handling Workaround SecurityException in FS#getFsTimestampResolution Fix NPE in FS$FileStoreAttributeCache.getFsTimestampResolution FS: ignore AccessDeniedException when measuring timestamp resolution Add debug trace for FileSnapshot Use FileChannel.open to touch file and set mtime to now Persist filesystem timestamp resolution and allow manual configuration Increase bazel timeout for long running tests Bazel: Fix lint warning flagged by buildifier Update bazlets to latest version Bazel: Add missing dependencies for ArchiveCommandTest Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file Add support for nanoseconds and microseconds for Config#getTimeUnit Optionally measure filesystem timestamp resolution asynchronously Delete unused FileTreeIteratorWithTimeControl FileSnapshot#equals: consider UNKNOWN_SIZE Timeout measuring file timestamp resolution after 2 seconds Fix RacyGitTests#testRacyGitDetection Change RacyGitTests to create a racy git situation in a stable way Deprecate Constants.CHARACTER_ENCODING in favor of StandardCharsets.UTF_8 Fix non-deterministic hash of archives created by ArchiveCommand Update Maven plugins ecj, plexus, error-prone Update Maven plugins and cleanup Maven warnings Make inner classes static where possible Fix API problem filters Change-Id: Ia57385b2a60f48a5317c8d723721c235d7043a84 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
4 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Merge branch 'stable-5.1' into stable-5.2 * stable-5.1: Fix OpenSshConfigTest#config FileSnapshot: fix bug with timestamp thresholding In LockFile#waitForStatChange wait in units of file time resolution Cache FileStoreAttributeCache per directory Fix FileSnapshot#save(long) and FileSnapshot#save(Instant) Persist minimal racy threshold and allow manual configuration Measure minimum racy interval to auto-configure FileSnapshot Reuse FileUtils to recursively delete files created by tests Fix FileAttributeCache.toString() Add test for racy git detection in FileSnapshot Repeat RefDirectoryTest.testGetRef_DiscoversModifiedLoose 100 times Fix org.eclipse.jdt.core.prefs of org.eclipse.jgit.junit Add missing javadoc in org.eclipse.jgit.junit Enhance RepeatRule to report number of failures at the end Fix FileSnapshotTests for filesystem with high timestamp resolution Retry deleting test files in FileBasedConfigTest Measure filesystem timestamp resolution already in test setup Refactor FileSnapshotTest to use NIO APIs Measure stored timestamp resolution instead of time to touch file Handle CancellationException in FileStoreAttributeCache Fix FileSnapshot#saveNoConfig Use Instant for smudge time in DirCache and DirCacheEntry Use Instant instead of milliseconds for filesystem timestamp handling Workaround SecurityException in FS#getFsTimestampResolution Fix NPE in FS$FileStoreAttributeCache.getFsTimestampResolution FS: ignore AccessDeniedException when measuring timestamp resolution Add debug trace for FileSnapshot Use FileChannel.open to touch file and set mtime to now Persist filesystem timestamp resolution and allow manual configuration Increase bazel timeout for long running tests Bazel: Fix lint warning flagged by buildifier Update bazlets to latest version Bazel: Add missing dependencies for ArchiveCommandTest Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file Add support for nanoseconds and microseconds for Config#getTimeUnit Optionally measure filesystem timestamp resolution asynchronously Delete unused FileTreeIteratorWithTimeControl FileSnapshot#equals: consider UNKNOWN_SIZE Timeout measuring file timestamp resolution after 2 seconds Fix RacyGitTests#testRacyGitDetection Change RacyGitTests to create a racy git situation in a stable way Deprecate Constants.CHARACTER_ENCODING in favor of StandardCharsets.UTF_8 Fix non-deterministic hash of archives created by ArchiveCommand Update Maven plugins ecj, plexus, error-prone Update Maven plugins and cleanup Maven warnings Make inner classes static where possible Fix API problem filters Change-Id: Ia57385b2a60f48a5317c8d723721c235d7043a84 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
4 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Merge branch 'stable-5.1' into stable-5.2 * stable-5.1: Fix OpenSshConfigTest#config FileSnapshot: fix bug with timestamp thresholding In LockFile#waitForStatChange wait in units of file time resolution Cache FileStoreAttributeCache per directory Fix FileSnapshot#save(long) and FileSnapshot#save(Instant) Persist minimal racy threshold and allow manual configuration Measure minimum racy interval to auto-configure FileSnapshot Reuse FileUtils to recursively delete files created by tests Fix FileAttributeCache.toString() Add test for racy git detection in FileSnapshot Repeat RefDirectoryTest.testGetRef_DiscoversModifiedLoose 100 times Fix org.eclipse.jdt.core.prefs of org.eclipse.jgit.junit Add missing javadoc in org.eclipse.jgit.junit Enhance RepeatRule to report number of failures at the end Fix FileSnapshotTests for filesystem with high timestamp resolution Retry deleting test files in FileBasedConfigTest Measure filesystem timestamp resolution already in test setup Refactor FileSnapshotTest to use NIO APIs Measure stored timestamp resolution instead of time to touch file Handle CancellationException in FileStoreAttributeCache Fix FileSnapshot#saveNoConfig Use Instant for smudge time in DirCache and DirCacheEntry Use Instant instead of milliseconds for filesystem timestamp handling Workaround SecurityException in FS#getFsTimestampResolution Fix NPE in FS$FileStoreAttributeCache.getFsTimestampResolution FS: ignore AccessDeniedException when measuring timestamp resolution Add debug trace for FileSnapshot Use FileChannel.open to touch file and set mtime to now Persist filesystem timestamp resolution and allow manual configuration Increase bazel timeout for long running tests Bazel: Fix lint warning flagged by buildifier Update bazlets to latest version Bazel: Add missing dependencies for ArchiveCommandTest Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file Add support for nanoseconds and microseconds for Config#getTimeUnit Optionally measure filesystem timestamp resolution asynchronously Delete unused FileTreeIteratorWithTimeControl FileSnapshot#equals: consider UNKNOWN_SIZE Timeout measuring file timestamp resolution after 2 seconds Fix RacyGitTests#testRacyGitDetection Change RacyGitTests to create a racy git situation in a stable way Deprecate Constants.CHARACTER_ENCODING in favor of StandardCharsets.UTF_8 Fix non-deterministic hash of archives created by ArchiveCommand Update Maven plugins ecj, plexus, error-prone Update Maven plugins and cleanup Maven warnings Make inner classes static where possible Fix API problem filters Change-Id: Ia57385b2a60f48a5317c8d723721c235d7043a84 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
4 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
Factor out a JSch-independent ssh config parser Move the bulk of the basic parsing and host entry handling into a new class OpenSshConfigFile that has no dependencies on any concrete ssh implementation. Make the existing OpenSshConfig use the new parser. Introduce a new class SshConstants collecting all the various ssh- related string literals. Also use TreeMaps with a case-insensitive key comparator instead of converting keys to uppercase. Add a test to verify that keys are matched case-insensitively. Most of the parsing code was simply moved, except that the new parser supports looking up entries given host name, port, and user name, and can thus handle more %-substitutions correctly. This feature is not yet used and cannot be used with JSch since JSch only has a ConfigRepository.getConfig(String) interface. The split is still worth the trouble as it opens the way to using another ssh client altogether. Apache MINA sshd, for instance, resolves host entries giving host name, port, and user name. (Apache MINA has a built-in ssh config handling, but that has problems, too: its pattern matching is case-insensitive, and its merging of host entries if several match is not the same as in OpenSsh. But with this refactoring, it will be possible to plug in OpenSshConfigFile into an Apache MINA sshd client without dragging along JSch.) One test case that doesn't make sense anymore has been removed. It tested that repeatedly querying for a host entry returned the same object. That is no longer true since the caching has been moved to a deeper level. Bug: 520927 Change-Id: I6381d52b29099595e6eaf8b05c786aeeaefbf9cc Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895
  1. /*
  2. * Copyright (C) 2008, 2017, Google Inc.
  3. * Copyright (C) 2017, 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.internal.transport.ssh;
  12. import static java.nio.charset.StandardCharsets.UTF_8;
  13. import java.io.BufferedReader;
  14. import java.io.File;
  15. import java.io.IOException;
  16. import java.nio.file.Files;
  17. import java.time.Instant;
  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.LinkedHashMap;
  22. import java.util.List;
  23. import java.util.Locale;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.TreeMap;
  27. import java.util.TreeSet;
  28. import org.eclipse.jgit.annotations.NonNull;
  29. import org.eclipse.jgit.errors.InvalidPatternException;
  30. import org.eclipse.jgit.fnmatch.FileNameMatcher;
  31. import org.eclipse.jgit.transport.SshConfigStore;
  32. import org.eclipse.jgit.transport.SshConstants;
  33. import org.eclipse.jgit.util.FS;
  34. import org.eclipse.jgit.util.StringUtils;
  35. import org.eclipse.jgit.util.SystemReader;
  36. /**
  37. * Fairly complete configuration parser for the openssh ~/.ssh/config file.
  38. * <p>
  39. * Both JSch 0.1.54 and Apache MINA sshd 2.1.0 have parsers for this, but both
  40. * are buggy. Therefore we implement our own parser to read an openssh
  41. * configuration file.
  42. * </p>
  43. * <p>
  44. * Limitations compared to the full openssh 7.5 parser:
  45. * </p>
  46. * <ul>
  47. * <li>This parser does not handle Match or Include keywords.
  48. * <li>This parser does not do host name canonicalization.
  49. * </ul>
  50. * <p>
  51. * Note that openssh's readconf.c is a validating parser; this parser does not
  52. * validate entries.
  53. * </p>
  54. * <p>
  55. * This config does %-substitutions for the following tokens:
  56. * </p>
  57. * <ul>
  58. * <li>%% - single %
  59. * <li>%C - short-hand for %l%h%p%r.
  60. * <li>%d - home directory path
  61. * <li>%h - remote host name
  62. * <li>%L - local host name without domain
  63. * <li>%l - FQDN of the local host
  64. * <li>%n - host name as specified in {@link #lookup(String, int, String)}
  65. * <li>%p - port number; if not given in {@link #lookup(String, int, String)}
  66. * replaced only if set in the config
  67. * <li>%r - remote user name; if not given in
  68. * {@link #lookup(String, int, String)} replaced only if set in the config
  69. * <li>%u - local user name
  70. * </ul>
  71. * <p>
  72. * %i is not handled; Java has no concept of a "user ID". %T is always replaced
  73. * by NONE.
  74. * </p>
  75. *
  76. * @see <a href="http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5">man
  77. * ssh-config</a>
  78. */
  79. public class OpenSshConfigFile implements SshConfigStore {
  80. /**
  81. * "Host" name of the HostEntry for the default options before the first
  82. * host block in a config file.
  83. */
  84. private static final String DEFAULT_NAME = ""; //$NON-NLS-1$
  85. /** The user's home directory, as key files may be relative to here. */
  86. private final File home;
  87. /** The .ssh/config file we read and monitor for updates. */
  88. private final File configFile;
  89. /** User name of the user on the host OS. */
  90. private final String localUserName;
  91. /** Modification time of {@link #configFile} when it was last loaded. */
  92. private Instant lastModified;
  93. /**
  94. * Encapsulates entries read out of the configuration file, and a cache of
  95. * fully resolved entries created from that.
  96. */
  97. private static class State {
  98. // Keyed by pattern; if a "Host" line has multiple patterns, we generate
  99. // duplicate HostEntry objects
  100. Map<String, HostEntry> entries = new LinkedHashMap<>();
  101. // Keyed by user@hostname:port
  102. Map<String, HostEntry> hosts = new HashMap<>();
  103. @Override
  104. @SuppressWarnings("nls")
  105. public String toString() {
  106. return "State [entries=" + entries + ", hosts=" + hosts + "]";
  107. }
  108. }
  109. /** State read from the config file, plus the cache. */
  110. private State state;
  111. /**
  112. * Creates a new {@link OpenSshConfigFile} that will read the config from
  113. * file {@code config} use the given file {@code home} as "home" directory.
  114. *
  115. * @param home
  116. * user's home directory for the purpose of ~ replacement
  117. * @param config
  118. * file to load.
  119. * @param localUserName
  120. * user name of the current user on the local host OS
  121. */
  122. public OpenSshConfigFile(@NonNull File home, @NonNull File config,
  123. @NonNull String localUserName) {
  124. this.home = home;
  125. this.configFile = config;
  126. this.localUserName = localUserName;
  127. state = new State();
  128. }
  129. /**
  130. * Locate the configuration for a specific host request.
  131. *
  132. * @param hostName
  133. * the name the user has supplied to the SSH tool. This may be a
  134. * real host name, or it may just be a "Host" block in the
  135. * configuration file.
  136. * @param port
  137. * the user supplied; <= 0 if none
  138. * @param userName
  139. * the user supplied, may be {@code null} or empty if none given
  140. * @return the configuration for the requested name.
  141. */
  142. @Override
  143. @NonNull
  144. public HostEntry lookup(@NonNull String hostName, int port,
  145. String userName) {
  146. final State cache = refresh();
  147. String cacheKey = toCacheKey(hostName, port, userName);
  148. HostEntry h = cache.hosts.get(cacheKey);
  149. if (h != null) {
  150. return h;
  151. }
  152. HostEntry fullConfig = new HostEntry();
  153. // Initialize with default entries at the top of the file, before the
  154. // first Host block.
  155. fullConfig.merge(cache.entries.get(DEFAULT_NAME));
  156. for (Map.Entry<String, HostEntry> e : cache.entries.entrySet()) {
  157. String pattern = e.getKey();
  158. if (isHostMatch(pattern, hostName)) {
  159. fullConfig.merge(e.getValue());
  160. }
  161. }
  162. fullConfig.substitute(hostName, port, userName, localUserName, home);
  163. cache.hosts.put(cacheKey, fullConfig);
  164. return fullConfig;
  165. }
  166. @NonNull
  167. private String toCacheKey(@NonNull String hostName, int port,
  168. String userName) {
  169. String key = hostName;
  170. if (port > 0) {
  171. key = key + ':' + Integer.toString(port);
  172. }
  173. if (userName != null && !userName.isEmpty()) {
  174. key = userName + '@' + key;
  175. }
  176. return key;
  177. }
  178. private synchronized State refresh() {
  179. final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
  180. if (!mtime.equals(lastModified)) {
  181. State newState = new State();
  182. try (BufferedReader br = Files
  183. .newBufferedReader(configFile.toPath(), UTF_8)) {
  184. newState.entries = parse(br);
  185. } catch (IOException | RuntimeException none) {
  186. // Ignore -- we'll set and return an empty state
  187. }
  188. lastModified = mtime;
  189. state = newState;
  190. }
  191. return state;
  192. }
  193. private Map<String, HostEntry> parse(BufferedReader reader)
  194. throws IOException {
  195. final Map<String, HostEntry> entries = new LinkedHashMap<>();
  196. final List<HostEntry> current = new ArrayList<>(4);
  197. String line;
  198. // The man page doesn't say so, but the openssh parser (readconf.c)
  199. // starts out in active mode and thus always applies any lines that
  200. // occur before the first host block. We gather those options in a
  201. // HostEntry for DEFAULT_NAME.
  202. HostEntry defaults = new HostEntry();
  203. current.add(defaults);
  204. entries.put(DEFAULT_NAME, defaults);
  205. while ((line = reader.readLine()) != null) {
  206. line = line.trim();
  207. if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$
  208. continue;
  209. }
  210. String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
  211. // Although the ssh-config man page doesn't say so, the openssh
  212. // parser does allow quoted keywords.
  213. String keyword = dequote(parts[0].trim());
  214. // man 5 ssh-config says lines had the format "keyword arguments",
  215. // with no indication that arguments were optional. However, let's
  216. // not crap out on missing arguments. See bug 444319.
  217. String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$
  218. if (StringUtils.equalsIgnoreCase(SshConstants.HOST, keyword)) {
  219. current.clear();
  220. for (String name : parseList(argValue)) {
  221. if (name == null || name.isEmpty()) {
  222. // null should not occur, but better be safe than sorry.
  223. continue;
  224. }
  225. HostEntry c = entries.get(name);
  226. if (c == null) {
  227. c = new HostEntry();
  228. entries.put(name, c);
  229. }
  230. current.add(c);
  231. }
  232. continue;
  233. }
  234. if (current.isEmpty()) {
  235. // We received an option outside of a Host block. We
  236. // don't know who this should match against, so skip.
  237. continue;
  238. }
  239. if (HostEntry.isListKey(keyword)) {
  240. List<String> args = validate(keyword, parseList(argValue));
  241. for (HostEntry entry : current) {
  242. entry.setValue(keyword, args);
  243. }
  244. } else if (!argValue.isEmpty()) {
  245. argValue = validate(keyword, dequote(argValue));
  246. for (HostEntry entry : current) {
  247. entry.setValue(keyword, argValue);
  248. }
  249. }
  250. }
  251. return entries;
  252. }
  253. /**
  254. * Splits the argument into a list of whitespace-separated elements.
  255. * Elements containing whitespace must be quoted and will be de-quoted.
  256. *
  257. * @param argument
  258. * argument part of the configuration line as read from the
  259. * config file
  260. * @return a {@link List} of elements, possibly empty and possibly
  261. * containing empty elements, but not containing {@code null}
  262. */
  263. private List<String> parseList(String argument) {
  264. List<String> result = new ArrayList<>(4);
  265. int start = 0;
  266. int length = argument.length();
  267. while (start < length) {
  268. // Skip whitespace
  269. if (Character.isSpaceChar(argument.charAt(start))) {
  270. start++;
  271. continue;
  272. }
  273. if (argument.charAt(start) == '"') {
  274. int stop = argument.indexOf('"', ++start);
  275. if (stop < start) {
  276. // No closing double quote: skip
  277. break;
  278. }
  279. result.add(argument.substring(start, stop));
  280. start = stop + 1;
  281. } else {
  282. int stop = start + 1;
  283. while (stop < length
  284. && !Character.isSpaceChar(argument.charAt(stop))) {
  285. stop++;
  286. }
  287. result.add(argument.substring(start, stop));
  288. start = stop + 1;
  289. }
  290. }
  291. return result;
  292. }
  293. /**
  294. * Hook to perform validation on a single value, or to sanitize it. If this
  295. * throws an (unchecked) exception, parsing of the file is abandoned.
  296. *
  297. * @param key
  298. * of the entry
  299. * @param value
  300. * as read from the config file
  301. * @return the validated and possibly sanitized value
  302. */
  303. protected String validate(String key, String value) {
  304. if (String.CASE_INSENSITIVE_ORDER.compare(key,
  305. SshConstants.PREFERRED_AUTHENTICATIONS) == 0) {
  306. return stripWhitespace(value);
  307. }
  308. return value;
  309. }
  310. /**
  311. * Hook to perform validation on values, or to sanitize them. If this throws
  312. * an (unchecked) exception, parsing of the file is abandoned.
  313. *
  314. * @param key
  315. * of the entry
  316. * @param value
  317. * list of arguments as read from the config file
  318. * @return a {@link List} of values, possibly empty and possibly containing
  319. * empty elements, but not containing {@code null}
  320. */
  321. protected List<String> validate(String key, List<String> value) {
  322. return value;
  323. }
  324. private static boolean isHostMatch(String pattern, String name) {
  325. if (pattern.startsWith("!")) { //$NON-NLS-1$
  326. return !patternMatchesHost(pattern.substring(1), name);
  327. }
  328. return patternMatchesHost(pattern, name);
  329. }
  330. private static boolean patternMatchesHost(String pattern, String name) {
  331. if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) {
  332. final FileNameMatcher fn;
  333. try {
  334. fn = new FileNameMatcher(pattern, null);
  335. } catch (InvalidPatternException e) {
  336. return false;
  337. }
  338. fn.append(name);
  339. return fn.isMatch();
  340. }
  341. // Not a pattern but a full host name
  342. return pattern.equals(name);
  343. }
  344. private static String dequote(String value) {
  345. if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$
  346. && value.length() > 1)
  347. return value.substring(1, value.length() - 1);
  348. return value;
  349. }
  350. private static String stripWhitespace(String value) {
  351. final StringBuilder b = new StringBuilder();
  352. for (int i = 0; i < value.length(); i++) {
  353. if (!Character.isSpaceChar(value.charAt(i)))
  354. b.append(value.charAt(i));
  355. }
  356. return b.toString();
  357. }
  358. private static File toFile(String path, File home) {
  359. if (path.startsWith("~/") || path.startsWith("~" + File.separator)) { //$NON-NLS-1$ //$NON-NLS-2$
  360. return new File(home, path.substring(2));
  361. }
  362. File ret = new File(path);
  363. if (ret.isAbsolute()) {
  364. return ret;
  365. }
  366. return new File(home, path);
  367. }
  368. /**
  369. * Converts a positive value into an {@code int}.
  370. *
  371. * @param value
  372. * to convert
  373. * @return the value, or -1 if it wasn't a positive integral value
  374. */
  375. public static int positive(String value) {
  376. if (value != null) {
  377. try {
  378. return Integer.parseUnsignedInt(value);
  379. } catch (NumberFormatException e) {
  380. // Ignore
  381. }
  382. }
  383. return -1;
  384. }
  385. /**
  386. * Converts a ssh config flag value (yes/true/on - no/false/off) into an
  387. * {@code boolean}.
  388. *
  389. * @param value
  390. * to convert
  391. * @return {@code true} if {@code value} is "yes", "on", or "true";
  392. * {@code false} otherwise
  393. */
  394. public static boolean flag(String value) {
  395. if (value == null) {
  396. return false;
  397. }
  398. return SshConstants.YES.equals(value) || SshConstants.ON.equals(value)
  399. || SshConstants.TRUE.equals(value);
  400. }
  401. /**
  402. * Retrieves the local user name as given in the constructor.
  403. *
  404. * @return the user name
  405. */
  406. public String getLocalUserName() {
  407. return localUserName;
  408. }
  409. /**
  410. * A host entry from the ssh config file. Any merging of global values and
  411. * of several matching host entries, %-substitutions, and ~ replacement have
  412. * all been done.
  413. */
  414. public static class HostEntry implements SshConfigStore.HostConfig {
  415. /**
  416. * Keys that can be specified multiple times, building up a list. (I.e.,
  417. * those are the keys that do not follow the general rule of "first
  418. * occurrence wins".)
  419. */
  420. private static final Set<String> MULTI_KEYS = new TreeSet<>(
  421. String.CASE_INSENSITIVE_ORDER);
  422. static {
  423. MULTI_KEYS.add(SshConstants.CERTIFICATE_FILE);
  424. MULTI_KEYS.add(SshConstants.IDENTITY_FILE);
  425. MULTI_KEYS.add(SshConstants.LOCAL_FORWARD);
  426. MULTI_KEYS.add(SshConstants.REMOTE_FORWARD);
  427. MULTI_KEYS.add(SshConstants.SEND_ENV);
  428. }
  429. /**
  430. * Keys that take a whitespace-separated list of elements as argument.
  431. * Because the dequote-handling is different, we must handle those in
  432. * the parser. There are a few other keys that take comma-separated
  433. * lists as arguments, but for the parser those are single arguments
  434. * that must be quoted if they contain whitespace, and taking them apart
  435. * is the responsibility of the user of those keys.
  436. */
  437. private static final Set<String> LIST_KEYS = new TreeSet<>(
  438. String.CASE_INSENSITIVE_ORDER);
  439. static {
  440. LIST_KEYS.add(SshConstants.CANONICAL_DOMAINS);
  441. LIST_KEYS.add(SshConstants.GLOBAL_KNOWN_HOSTS_FILE);
  442. LIST_KEYS.add(SshConstants.SEND_ENV);
  443. LIST_KEYS.add(SshConstants.USER_KNOWN_HOSTS_FILE);
  444. }
  445. private Map<String, String> options;
  446. private Map<String, List<String>> multiOptions;
  447. private Map<String, List<String>> listOptions;
  448. /**
  449. * Retrieves the value of a single-valued key, or the first if the key
  450. * has multiple values. Keys are case-insensitive, so
  451. * {@code getValue("HostName") == getValue("HOSTNAME")}.
  452. *
  453. * @param key
  454. * to get the value of
  455. * @return the value, or {@code null} if none
  456. */
  457. @Override
  458. public String getValue(String key) {
  459. String result = options != null ? options.get(key) : null;
  460. if (result == null) {
  461. // Let's be lenient and return at least the first value from
  462. // a list-valued or multi-valued key.
  463. List<String> values = listOptions != null ? listOptions.get(key)
  464. : null;
  465. if (values == null) {
  466. values = multiOptions != null ? multiOptions.get(key)
  467. : null;
  468. }
  469. if (values != null && !values.isEmpty()) {
  470. result = values.get(0);
  471. }
  472. }
  473. return result;
  474. }
  475. /**
  476. * Retrieves the values of a multi or list-valued key. Keys are
  477. * case-insensitive, so
  478. * {@code getValue("HostName") == getValue("HOSTNAME")}.
  479. *
  480. * @param key
  481. * to get the values of
  482. * @return a possibly empty list of values
  483. */
  484. @Override
  485. public List<String> getValues(String key) {
  486. List<String> values = listOptions != null ? listOptions.get(key)
  487. : null;
  488. if (values == null) {
  489. values = multiOptions != null ? multiOptions.get(key) : null;
  490. }
  491. if (values == null || values.isEmpty()) {
  492. return new ArrayList<>();
  493. }
  494. return new ArrayList<>(values);
  495. }
  496. /**
  497. * Sets the value of a single-valued key if it not set yet, or adds a
  498. * value to a multi-valued key. If the value is {@code null}, the key is
  499. * removed altogether, whether it is single-, list-, or multi-valued.
  500. *
  501. * @param key
  502. * to modify
  503. * @param value
  504. * to set or add
  505. */
  506. public void setValue(String key, String value) {
  507. if (value == null) {
  508. if (multiOptions != null) {
  509. multiOptions.remove(key);
  510. }
  511. if (listOptions != null) {
  512. listOptions.remove(key);
  513. }
  514. if (options != null) {
  515. options.remove(key);
  516. }
  517. return;
  518. }
  519. if (MULTI_KEYS.contains(key)) {
  520. if (multiOptions == null) {
  521. multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  522. }
  523. List<String> values = multiOptions.get(key);
  524. if (values == null) {
  525. values = new ArrayList<>(4);
  526. multiOptions.put(key, values);
  527. }
  528. values.add(value);
  529. } else {
  530. if (options == null) {
  531. options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  532. }
  533. if (!options.containsKey(key)) {
  534. options.put(key, value);
  535. }
  536. }
  537. }
  538. /**
  539. * Sets the values of a multi- or list-valued key.
  540. *
  541. * @param key
  542. * to set
  543. * @param values
  544. * a non-empty list of values
  545. */
  546. public void setValue(String key, List<String> values) {
  547. if (values.isEmpty()) {
  548. return;
  549. }
  550. // Check multi-valued keys first; because of the replacement
  551. // strategy, they must take precedence over list-valued keys
  552. // which always follow the "first occurrence wins" strategy.
  553. //
  554. // Note that SendEnv is a multi-valued list-valued key. (It's
  555. // rather immaterial for JGit, though.)
  556. if (MULTI_KEYS.contains(key)) {
  557. if (multiOptions == null) {
  558. multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  559. }
  560. List<String> items = multiOptions.get(key);
  561. if (items == null) {
  562. items = new ArrayList<>(values);
  563. multiOptions.put(key, items);
  564. } else {
  565. items.addAll(values);
  566. }
  567. } else {
  568. if (listOptions == null) {
  569. listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  570. }
  571. if (!listOptions.containsKey(key)) {
  572. listOptions.put(key, values);
  573. }
  574. }
  575. }
  576. /**
  577. * Does the key take a whitespace-separated list of values?
  578. *
  579. * @param key
  580. * to check
  581. * @return {@code true} if the key is a list-valued key.
  582. */
  583. public static boolean isListKey(String key) {
  584. return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT));
  585. }
  586. void merge(HostEntry entry) {
  587. if (entry == null) {
  588. // Can occur if we could not read the config file
  589. return;
  590. }
  591. if (entry.options != null) {
  592. if (options == null) {
  593. options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  594. }
  595. for (Map.Entry<String, String> item : entry.options
  596. .entrySet()) {
  597. if (!options.containsKey(item.getKey())) {
  598. options.put(item.getKey(), item.getValue());
  599. }
  600. }
  601. }
  602. if (entry.listOptions != null) {
  603. if (listOptions == null) {
  604. listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  605. }
  606. for (Map.Entry<String, List<String>> item : entry.listOptions
  607. .entrySet()) {
  608. if (!listOptions.containsKey(item.getKey())) {
  609. listOptions.put(item.getKey(), item.getValue());
  610. }
  611. }
  612. }
  613. if (entry.multiOptions != null) {
  614. if (multiOptions == null) {
  615. multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  616. }
  617. for (Map.Entry<String, List<String>> item : entry.multiOptions
  618. .entrySet()) {
  619. List<String> values = multiOptions.get(item.getKey());
  620. if (values == null) {
  621. values = new ArrayList<>(item.getValue());
  622. multiOptions.put(item.getKey(), values);
  623. } else {
  624. values.addAll(item.getValue());
  625. }
  626. }
  627. }
  628. }
  629. private List<String> substitute(List<String> values, String allowed,
  630. Replacer r) {
  631. List<String> result = new ArrayList<>(values.size());
  632. for (String value : values) {
  633. result.add(r.substitute(value, allowed));
  634. }
  635. return result;
  636. }
  637. private List<String> replaceTilde(List<String> values, File home) {
  638. List<String> result = new ArrayList<>(values.size());
  639. for (String value : values) {
  640. result.add(toFile(value, home).getPath());
  641. }
  642. return result;
  643. }
  644. void substitute(String originalHostName, int port, String userName,
  645. String localUserName, File home) {
  646. int p = port >= 0 ? port : positive(getValue(SshConstants.PORT));
  647. if (p < 0) {
  648. p = SshConstants.SSH_DEFAULT_PORT;
  649. }
  650. String u = userName != null && !userName.isEmpty() ? userName
  651. : getValue(SshConstants.USER);
  652. if (u == null || u.isEmpty()) {
  653. u = localUserName;
  654. }
  655. Replacer r = new Replacer(originalHostName, p, u, localUserName,
  656. home);
  657. if (options != null) {
  658. // HOSTNAME first
  659. String hostName = options.get(SshConstants.HOST_NAME);
  660. if (hostName == null || hostName.isEmpty()) {
  661. options.put(SshConstants.HOST_NAME, originalHostName);
  662. } else {
  663. hostName = r.substitute(hostName, "h"); //$NON-NLS-1$
  664. options.put(SshConstants.HOST_NAME, hostName);
  665. r.update('h', hostName);
  666. }
  667. }
  668. if (multiOptions != null) {
  669. List<String> values = multiOptions
  670. .get(SshConstants.IDENTITY_FILE);
  671. if (values != null) {
  672. values = substitute(values, "dhlru", r); //$NON-NLS-1$
  673. values = replaceTilde(values, home);
  674. multiOptions.put(SshConstants.IDENTITY_FILE, values);
  675. }
  676. values = multiOptions.get(SshConstants.CERTIFICATE_FILE);
  677. if (values != null) {
  678. values = substitute(values, "dhlru", r); //$NON-NLS-1$
  679. values = replaceTilde(values, home);
  680. multiOptions.put(SshConstants.CERTIFICATE_FILE, values);
  681. }
  682. }
  683. if (listOptions != null) {
  684. List<String> values = listOptions
  685. .get(SshConstants.USER_KNOWN_HOSTS_FILE);
  686. if (values != null) {
  687. values = replaceTilde(values, home);
  688. listOptions.put(SshConstants.USER_KNOWN_HOSTS_FILE, values);
  689. }
  690. }
  691. if (options != null) {
  692. // HOSTNAME already done above
  693. String value = options.get(SshConstants.IDENTITY_AGENT);
  694. if (value != null) {
  695. value = r.substitute(value, "dhlru"); //$NON-NLS-1$
  696. value = toFile(value, home).getPath();
  697. options.put(SshConstants.IDENTITY_AGENT, value);
  698. }
  699. value = options.get(SshConstants.CONTROL_PATH);
  700. if (value != null) {
  701. value = r.substitute(value, "ChLlnpru"); //$NON-NLS-1$
  702. value = toFile(value, home).getPath();
  703. options.put(SshConstants.CONTROL_PATH, value);
  704. }
  705. value = options.get(SshConstants.LOCAL_COMMAND);
  706. if (value != null) {
  707. value = r.substitute(value, "CdhlnprTu"); //$NON-NLS-1$
  708. options.put(SshConstants.LOCAL_COMMAND, value);
  709. }
  710. value = options.get(SshConstants.REMOTE_COMMAND);
  711. if (value != null) {
  712. value = r.substitute(value, "Cdhlnpru"); //$NON-NLS-1$
  713. options.put(SshConstants.REMOTE_COMMAND, value);
  714. }
  715. value = options.get(SshConstants.PROXY_COMMAND);
  716. if (value != null) {
  717. value = r.substitute(value, "hpr"); //$NON-NLS-1$
  718. options.put(SshConstants.PROXY_COMMAND, value);
  719. }
  720. }
  721. // Match is not implemented and would need to be done elsewhere
  722. // anyway.
  723. }
  724. /**
  725. * Retrieves an unmodifiable map of all single-valued options, with
  726. * case-insensitive lookup by keys.
  727. *
  728. * @return all single-valued options
  729. */
  730. @Override
  731. @NonNull
  732. public Map<String, String> getOptions() {
  733. if (options == null) {
  734. return Collections.emptyMap();
  735. }
  736. return Collections.unmodifiableMap(options);
  737. }
  738. /**
  739. * Retrieves an unmodifiable map of all multi-valued options, with
  740. * case-insensitive lookup by keys.
  741. *
  742. * @return all multi-valued options
  743. */
  744. @Override
  745. @NonNull
  746. public Map<String, List<String>> getMultiValuedOptions() {
  747. if (listOptions == null && multiOptions == null) {
  748. return Collections.emptyMap();
  749. }
  750. Map<String, List<String>> allValues = new TreeMap<>(
  751. String.CASE_INSENSITIVE_ORDER);
  752. if (multiOptions != null) {
  753. allValues.putAll(multiOptions);
  754. }
  755. if (listOptions != null) {
  756. allValues.putAll(listOptions);
  757. }
  758. return Collections.unmodifiableMap(allValues);
  759. }
  760. @Override
  761. @SuppressWarnings("nls")
  762. public String toString() {
  763. return "HostEntry [options=" + options + ", multiOptions="
  764. + multiOptions + ", listOptions=" + listOptions + "]";
  765. }
  766. }
  767. private static class Replacer {
  768. private final Map<Character, String> replacements = new HashMap<>();
  769. public Replacer(String host, int port, String user,
  770. String localUserName, File home) {
  771. replacements.put(Character.valueOf('%'), "%"); //$NON-NLS-1$
  772. replacements.put(Character.valueOf('d'), home.getPath());
  773. replacements.put(Character.valueOf('h'), host);
  774. String localhost = SystemReader.getInstance().getHostname();
  775. replacements.put(Character.valueOf('l'), localhost);
  776. int period = localhost.indexOf('.');
  777. if (period > 0) {
  778. localhost = localhost.substring(0, period);
  779. }
  780. replacements.put(Character.valueOf('L'), localhost);
  781. replacements.put(Character.valueOf('n'), host);
  782. replacements.put(Character.valueOf('p'), Integer.toString(port));
  783. replacements.put(Character.valueOf('r'), user == null ? "" : user); //$NON-NLS-1$
  784. replacements.put(Character.valueOf('u'), localUserName);
  785. replacements.put(Character.valueOf('C'),
  786. substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
  787. replacements.put(Character.valueOf('T'), "NONE"); //$NON-NLS-1$
  788. }
  789. public void update(char key, String value) {
  790. replacements.put(Character.valueOf(key), value);
  791. if ("lhpr".indexOf(key) >= 0) { //$NON-NLS-1$
  792. replacements.put(Character.valueOf('C'),
  793. substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
  794. }
  795. }
  796. public String substitute(String input, String allowed) {
  797. if (input == null || input.length() <= 1
  798. || input.indexOf('%') < 0) {
  799. return input;
  800. }
  801. StringBuilder builder = new StringBuilder();
  802. int start = 0;
  803. int length = input.length();
  804. while (start < length) {
  805. int percent = input.indexOf('%', start);
  806. if (percent < 0 || percent + 1 >= length) {
  807. builder.append(input.substring(start));
  808. break;
  809. }
  810. String replacement = null;
  811. char ch = input.charAt(percent + 1);
  812. if (ch == '%' || allowed.indexOf(ch) >= 0) {
  813. replacement = replacements.get(Character.valueOf(ch));
  814. }
  815. if (replacement == null) {
  816. builder.append(input.substring(start, percent + 2));
  817. } else {
  818. builder.append(input.substring(start, percent))
  819. .append(replacement);
  820. }
  821. start = percent + 2;
  822. }
  823. return builder.toString();
  824. }
  825. }
  826. /** {@inheritDoc} */
  827. @Override
  828. @SuppressWarnings("nls")
  829. public String toString() {
  830. return "OpenSshConfig [home=" + home + ", configFile=" + configFile
  831. + ", lastModified=" + lastModified + ", state=" + state + "]";
  832. }
  833. }