diff options
author | Thomas Wolf <thomas.wolf@paranor.ch> | 2018-09-23 16:45:45 +0200 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2018-11-13 10:49:26 -0800 |
commit | 488d95571fbe5b896c929dc3f65dc0c0a7161d00 (patch) | |
tree | 4da31f2a4513e5eb20d65d6e71bc5a311510be4a /org.eclipse.jgit.ssh.apache | |
parent | 0173b25415fb334490396a2fa4150db888c56947 (diff) | |
download | jgit-488d95571fbe5b896c929dc3f65dc0c0a7161d00.tar.gz jgit-488d95571fbe5b896c929dc3f65dc0c0a7161d00.zip |
Apache MINA sshd client
Add a new ssh client implementation based on Apach MINA sshd 2.0.0.
This implementation uses JGit's own config file parser and host entry
resolver. Code inspection of the Apache MINA implementation revealed
a few bugs or idiosyncrasies that immediately would re-introduce bugs
already fixed in the past in JGit.
Apache MINA sshd is not without quirks either, and I had to configure
and override more than I had expected. But at least it was all doable
in clean ways.
Apache MINA boasts support for Bouncy Castle, so in theory this should
open the way to using more ssh key algorithms, such as ed25519.
The implementation is in a separate bundle and is still not used in
the core org.eclipse.jgit bundle. The tests re-use the ssh tests from
the core test bundle.
Bug: 520927
Change-Id: Ib35e73c35799140fe050d1ff4fb18d0d3596580e
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.jgit.ssh.apache')
36 files changed, 4685 insertions, 0 deletions
diff --git a/org.eclipse.jgit.ssh.apache/.classpath b/org.eclipse.jgit.ssh.apache/.classpath new file mode 100644 index 0000000000..110168ffa1 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.classpath @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="resources"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/org.eclipse.jgit.ssh.apache/.fbprefs b/org.eclipse.jgit.ssh.apache/.fbprefs new file mode 100644 index 0000000000..81a0767ff6 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.fbprefs @@ -0,0 +1,125 @@ +#FindBugs User Preferences +#Mon May 04 16:24:13 PDT 2009 +detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true +detectorBadAppletConstructor=BadAppletConstructor|false +detectorBadResultSetAccess=BadResultSetAccess|true +detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true +detectorBadUseOfReturnValue=BadUseOfReturnValue|true +detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true +detectorBooleanReturnNull=BooleanReturnNull|true +detectorCallToUnsupportedMethod=CallToUnsupportedMethod|true +detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true +detectorCheckTypeQualifiers=CheckTypeQualifiers|true +detectorCloneIdiom=CloneIdiom|false +detectorComparatorIdiom=ComparatorIdiom|true +detectorConfusedInheritance=ConfusedInheritance|true +detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true +detectorCrossSiteScripting=CrossSiteScripting|true +detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true +detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true +detectorDontUseEnum=DontUseEnum|true +detectorDroppedException=DroppedException|true +detectorDumbMethodInvocations=DumbMethodInvocations|true +detectorDumbMethods=DumbMethods|true +detectorDuplicateBranches=DuplicateBranches|true +detectorEmptyZipFileEntry=EmptyZipFileEntry|true +detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true +detectorFinalizerNullsFields=FinalizerNullsFields|true +detectorFindBadCast2=FindBadCast2|true +detectorFindBadForLoop=FindBadForLoop|true +detectorFindCircularDependencies=FindCircularDependencies|false +detectorFindDeadLocalStores=FindDeadLocalStores|true +detectorFindDoubleCheck=FindDoubleCheck|true +detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true +detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true +detectorFindFinalizeInvocations=FindFinalizeInvocations|true +detectorFindFloatEquality=FindFloatEquality|true +detectorFindHEmismatch=FindHEmismatch|true +detectorFindInconsistentSync2=FindInconsistentSync2|true +detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true +detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true +detectorFindMaskedFields=FindMaskedFields|true +detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true +detectorFindNakedNotify=FindNakedNotify|true +detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true +detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true +detectorFindNonShortCircuit=FindNonShortCircuit|true +detectorFindNullDeref=FindNullDeref|true +detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true +detectorFindOpenStream=FindOpenStream|true +detectorFindPuzzlers=FindPuzzlers|true +detectorFindRefComparison=FindRefComparison|true +detectorFindReturnRef=FindReturnRef|true +detectorFindRunInvocations=FindRunInvocations|true +detectorFindSelfComparison=FindSelfComparison|true +detectorFindSelfComparison2=FindSelfComparison2|true +detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true +detectorFindSpinLoop=FindSpinLoop|true +detectorFindSqlInjection=FindSqlInjection|true +detectorFindTwoLockWait=FindTwoLockWait|true +detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true +detectorFindUnconditionalWait=FindUnconditionalWait|true +detectorFindUninitializedGet=FindUninitializedGet|true +detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true +detectorFindUnreleasedLock=FindUnreleasedLock|true +detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true +detectorFindUnsyncGet=FindUnsyncGet|true +detectorFindUselessControlFlow=FindUselessControlFlow|true +detectorFormatStringChecker=FormatStringChecker|true +detectorHugeSharedStringConstants=HugeSharedStringConstants|true +detectorIDivResultCastToDouble=IDivResultCastToDouble|true +detectorIncompatMask=IncompatMask|true +detectorInconsistentAnnotations=InconsistentAnnotations|true +detectorInefficientMemberAccess=InefficientMemberAccess|false +detectorInefficientToArray=InefficientToArray|true +detectorInfiniteLoop=InfiniteLoop|true +detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true +detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false +detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true +detectorInitializationChain=InitializationChain|true +detectorInstantiateStaticClass=InstantiateStaticClass|true +detectorInvalidJUnitTest=InvalidJUnitTest|true +detectorIteratorIdioms=IteratorIdioms|true +detectorLazyInit=LazyInit|true +detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true +detectorMethodReturnCheck=MethodReturnCheck|true +detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true +detectorMutableLock=MutableLock|true +detectorMutableStaticFields=MutableStaticFields|true +detectorNaming=Naming|true +detectorNumberConstructor=NumberConstructor|true +detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true +detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true +detectorPublicSemaphores=PublicSemaphores|false +detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true +detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true +detectorRedundantInterfaces=RedundantInterfaces|true +detectorRepeatedConditionals=RepeatedConditionals|true +detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true +detectorSerializableIdiom=SerializableIdiom|true +detectorStartInConstructor=StartInConstructor|true +detectorStaticCalendarDetector=StaticCalendarDetector|true +detectorStringConcatenation=StringConcatenation|true +detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true +detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true +detectorSwitchFallthrough=SwitchFallthrough|true +detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true +detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true +detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true +detectorURLProblems=URLProblems|true +detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true +detectorUnnecessaryMath=UnnecessaryMath|true +detectorUnreadFields=UnreadFields|true +detectorUseObjectEquals=UseObjectEquals|false +detectorUselessSubclassMethod=UselessSubclassMethod|false +detectorVarArgsProblems=VarArgsProblems|true +detectorVolatileUsage=VolatileUsage|true +detectorWaitInLoop=WaitInLoop|true +detectorWrongMapIterator=WrongMapIterator|true +detectorXMLFactoryBypass=XMLFactoryBypass|true +detector_threshold=2 +effort=default +excludefilter0=findBugs/FindBugsExcludeFilter.xml +filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false +filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL| +run_at_full_build=true diff --git a/org.eclipse.jgit.ssh.apache/.gitignore b/org.eclipse.jgit.ssh.apache/.gitignore new file mode 100644 index 0000000000..934e0e06ff --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.gitignore @@ -0,0 +1,2 @@ +/bin +/target diff --git a/org.eclipse.jgit.ssh.apache/.project b/org.eclipse.jgit.ssh.apache/.project new file mode 100644 index 0000000000..a7bbd6bafd --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.project @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.jgit.ssh.apache</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + <nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature> + </natures> +</projectDescription> diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..66ac15c47c --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Mon Aug 11 16:46:12 PDT 2008 +eclipse.preferences.version=1 +encoding/<project>=UTF-8 diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000000..006e07ede5 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,3 @@ +#Mon Mar 24 18:55:50 EDT 2008 +eclipse.preferences.version=1 +line.separator=\n diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..13c32a6d94 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,399 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.comparingIdentical=error +org.eclipse.jdt.core.compiler.problem.deadCode=error +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error +org.eclipse.jdt.core.compiler.problem.potentialNullReference=error +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=error +org.eclipse.jdt.core.compiler.problem.unusedLocal=error +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error +org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=80 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000000..fef3713825 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,66 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_JGit Format +formatter_settings_version=12 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/> +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.tasks.ui.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.tasks.ui.prefs new file mode 100644 index 0000000000..823c0f56ae --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.tasks.ui.prefs @@ -0,0 +1,4 @@ +#Tue Jul 19 20:11:28 CEST 2011 +eclipse.preferences.version=1 +project.repository.kind=bugzilla +project.repository.url=https\://bugs.eclipse.org/bugs diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.team.ui.prefs new file mode 100644 index 0000000000..0cba949fb7 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.team.ui.prefs @@ -0,0 +1,3 @@ +#Tue Jul 19 20:11:28 CEST 2011 +commit.comment.template=${task.description} \n\nBug\: ${task.key} +eclipse.preferences.version=1 diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.api.tools.prefs new file mode 100644 index 0000000000..c0030ded71 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.api.tools.prefs @@ -0,0 +1,104 @@ +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error +ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error +CLASS_ELEMENT_TYPE_ADDED_METHOD=Error +CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error +CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error +CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error +CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error +ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error +ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error +ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +FIELD_ELEMENT_TYPE_ADDED_VALUE=Error +FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error +FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error +FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error +FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error +ILLEGAL_EXTEND=Warning +ILLEGAL_IMPLEMENT=Warning +ILLEGAL_INSTANTIATE=Warning +ILLEGAL_OVERRIDE=Warning +ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error +INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error +INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore +INVALID_JAVADOC_TAG=Ignore +INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error +LEAK_EXTEND=Warning +LEAK_FIELD_DECL=Warning +LEAK_IMPLEMENT=Warning +LEAK_METHOD_PARAM=Warning +LEAK_METHOD_RETURN_TYPE=Warning +METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error +METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error +UNUSED_PROBLEM_FILTERS=Warning +automatically_removed_unused_problem_filters=false +changed_execution_env=Error +eclipse.preferences.version=1 +incompatible_api_component_version=Error +incompatible_api_component_version_include_major_without_breaking_change=Disabled +incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore +invalid_since_tag_version=Error +malformed_since_tag=Error +missing_since_tag=Error +report_api_breakage_when_major_version_incremented=Disabled +report_resolution_errors_api_component=Warning diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.core.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 0000000000..82793f2d27 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +#Thu Jan 14 14:34:32 CST 2010 +eclipse.preferences.version=1 +resolve.requirebundle=false diff --git a/org.eclipse.jgit.ssh.apache/BUILD b/org.eclipse.jgit.ssh.apache/BUILD new file mode 100644 index 0000000000..d6a145381c --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/BUILD @@ -0,0 +1,18 @@ +package(default_visibility = ["//visibility:public"]) + +SRCS = glob(["src/**/*.java"]) + +RESOURCES = glob(["resources/**"]) + +java_library( + name = "ssh-apache", + srcs = SRCS, + resource_strip_prefix = "org.eclipse.jgit.ssh.apache/resources", + resources = RESOURCES, + deps = [ + "//lib:slf4j-api", + "//lib:sshd-core", + "//lib:sshd-sftp", + "//org.eclipse.jgit:jgit", + ], +) diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..acc81018e8 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF @@ -0,0 +1,76 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %Bundle-Name +Automatic-Module-Name: org.eclipse.jgit.ssh.apache +Bundle-SymbolicName: org.eclipse.jgit.ssh.apache +Bundle-Vendor: %Provider-Name +Bundle-ActivationPolicy: lazy +Bundle-Version: 5.2.0.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Export-Package: org.eclipse.jgit.internal.transport.sshd;version="5.2.0";x-internal:=true; + uses:="org.apache.sshd.client, + org.apache.sshd.client.auth, + org.apache.sshd.client.auth.keyboard, + org.apache.sshd.client.auth.pubkey, + org.apache.sshd.client.config.hosts, + org.apache.sshd.client.future, + org.apache.sshd.client.keyverifier, + org.apache.sshd.client.session, + org.apache.sshd.common.config.keys, + org.apache.sshd.common.io, + org.apache.sshd.common.keyprovider, + org.apache.sshd.common.signature, + org.apache.sshd.common.util.buffer, + org.eclipse.jgit.transport", + org.eclipse.jgit.transport.sshd;version="5.2.0"; + uses:="org.apache.sshd.client, + org.apache.sshd.client.config.hosts, + org.apache.sshd.common.keyprovider, + org.apache.sshd.client.keyverifier, + org.eclipse.jgit.internal.transport.sshd, + org.eclipse.jgit.transport, + org.eclipse.jgit.util" +Import-Package: org.apache.sshd.agent;version="[2.0.0,2.1.0)", + org.apache.sshd.client;version="[2.0.0,2.1.0)", + org.apache.sshd.client.auth;version="[2.0.0,2.1.0)", + org.apache.sshd.client.auth.keyboard;version="[2.0.0,2.1.0)", + org.apache.sshd.client.auth.password;version="[2.0.0,2.1.0)", + org.apache.sshd.client.auth.pubkey;version="[2.0.0,2.1.0)", + org.apache.sshd.client.channel;version="[2.0.0,2.1.0)", + org.apache.sshd.client.config.hosts;version="[2.0.0,2.1.0)", + org.apache.sshd.client.config.keys;version="[2.0.0,2.1.0)", + org.apache.sshd.client.future;version="[2.0.0,2.1.0)", + org.apache.sshd.client.keyverifier;version="[2.0.0,2.1.0)", + org.apache.sshd.client.session;version="[2.0.0,2.1.0)", + org.apache.sshd.client.subsystem.sftp;version="[2.0.0,2.1.0)", + org.apache.sshd.common;version="[2.0.0,2.1.0)", + org.apache.sshd.common.auth;version="[2.0.0,2.1.0)", + org.apache.sshd.common.channel;version="[2.0.0,2.1.0)", + org.apache.sshd.common.compression;version="[2.0.0,2.1.0)", + org.apache.sshd.common.config.keys;version="[2.0.0,2.1.0)", + org.apache.sshd.common.digest;version="[2.0.0,2.1.0)", + org.apache.sshd.common.forward;version="[2.0.0,2.1.0)", + org.apache.sshd.common.future;version="[2.0.0,2.1.0)", + org.apache.sshd.common.io;version="[2.0.0,2.1.0)", + org.apache.sshd.common.kex;version="[2.0.0,2.1.0)", + org.apache.sshd.common.keyprovider;version="[2.0.0,2.1.0)", + org.apache.sshd.common.mac;version="[2.0.0,2.1.0)", + org.apache.sshd.common.random;version="[2.0.0,2.1.0)", + org.apache.sshd.common.session;version="[2.0.0,2.1.0)", + org.apache.sshd.common.session.helpers;version="[2.0.0,2.1.0)", + org.apache.sshd.common.signature;version="[2.0.0,2.1.0)", + org.apache.sshd.common.subsystem.sftp;version="[2.0.0,2.1.0)", + org.apache.sshd.common.util;version="[2.0.0,2.1.0)", + org.apache.sshd.common.util.buffer;version="[2.0.0,2.1.0)", + org.apache.sshd.common.util.io;version="[2.0.0,2.1.0)", + org.apache.sshd.common.util.logging;version="[2.0.0,2.1.0)", + org.apache.sshd.common.util.net;version="[2.0.0,2.1.0)", + org.apache.sshd.server.auth;version="[2.0.0,2.1.0)", + org.eclipse.jgit.annotations;version="[5.2.0,5.3.0)", + org.eclipse.jgit.errors;version="[5.2.0,5.3.0)", + org.eclipse.jgit.internal.storage.file;version="[5.2.0,5.3.0)", + org.eclipse.jgit.internal.transport.ssh;version="[5.2.0,5.3.0)", + org.eclipse.jgit.nls;version="[5.2.0,5.3.0)", + org.eclipse.jgit.transport;version="[5.2.0,5.3.0)", + org.eclipse.jgit.util;version="[5.2.0,5.3.0)", + org.slf4j;version="[1.7.0,2.0.0)" diff --git a/org.eclipse.jgit.ssh.apache/about.html b/org.eclipse.jgit.ssh.apache/about.html new file mode 100644 index 0000000000..f971af18d0 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/about.html @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> + +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" /> +<title>Eclipse Distribution License - Version 1.0</title> +<style type="text/css"> + body { + size: 8.5in 11.0in; + margin: 0.25in 0.5in 0.25in 0.5in; + tab-interval: 0.5in; + } + p { + margin-left: auto; + margin-top: 0.5em; + margin-bottom: 0.5em; + } + p.list { + margin-left: 0.5in; + margin-top: 0.05em; + margin-bottom: 0.05em; + } + .ubc-name { + margin-left: 0.5in; + white-space: pre; + } + </style> + +</head> + +<body lang="EN-US"> + +<p><b>Eclipse Distribution License - v 1.0</b></p> + +<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p> + +<p>All rights reserved.</p> +<p>Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: +<ul><li>Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. </li> +<li>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. </li> +<li>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. </li></ul> +</p> +<p>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.</p> + +<hr> +<p><b>SHA-1 UbcCheck - MIT</b></p> + +<p>Copyright (c) 2017:</p> +<div class="ubc-name"> +Marc Stevens +Cryptology Group +Centrum Wiskunde & Informatica +P.O. Box 94079, 1090 GB Amsterdam, Netherlands +marc@marc-stevens.nl +</div> +<div class="ubc-name"> +Dan Shumow +Microsoft Research +danshu@microsoft.com +</div> +<p>Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +</p> +<ul><li>The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software.</li></ul> +<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.</p> + +</body> + +</html> diff --git a/org.eclipse.jgit.ssh.apache/build.properties b/org.eclipse.jgit.ssh.apache/build.properties new file mode 100644 index 0000000000..8148271ef3 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/build.properties @@ -0,0 +1,7 @@ +source.. = src/,\ + resources/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties,\ + about.html diff --git a/org.eclipse.jgit.ssh.apache/plugin.properties b/org.eclipse.jgit.ssh.apache/plugin.properties new file mode 100644 index 0000000000..8f3540c0f0 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/plugin.properties @@ -0,0 +1,2 @@ +plugin_name=JGit SSH support based on Apache MINA sshd +provider_name=Eclipse JGit diff --git a/org.eclipse.jgit.ssh.apache/pom.xml b/org.eclipse.jgit.ssh.apache/pom.xml new file mode 100644 index 0000000000..7c62320a29 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/pom.xml @@ -0,0 +1,206 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.eclipse.jgit</groupId> + <artifactId>org.eclipse.jgit-parent</artifactId> + <version>5.2.0-SNAPSHOT</version> + </parent> + + <artifactId>org.eclipse.jgit.ssh.apache</artifactId> + <name>JGit - Apache sshd-based SSH support</name> + + <description> + SSH support for JGit based on Apache MINA sshd + </description> + + <properties> + <translate-qualifier/> + </properties> + + <dependencies> + <dependency> + <groupId>org.eclipse.jgit</groupId> + <artifactId>org.eclipse.jgit</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.sshd</groupId> + <artifactId>sshd-core</artifactId> + <version>${apache-sshd-version}</version> + </dependency> + + <dependency> + <groupId>org.apache.sshd</groupId> + <artifactId>sshd-sftp</artifactId> + <version>${apache-sshd-version}</version> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + </dependencies> + + <build> + <sourceDirectory>src/</sourceDirectory> + + <resources> + <resource> + <directory>.</directory> + <includes> + <include>plugin.properties</include> + <include>about.html</include> + </includes> + </resource> + <resource> + <directory>resources/</directory> + </resource> + </resources> + + <plugins> + <plugin> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestFile>${bundle-manifest}</manifestFile> + </archive> + </configuration> + </plugin> + + <plugin> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>true</skip><!-- TODO: Enable after the first release --> + </configuration> + <executions> + <execution> + <phase>verify</phase> + <goals> + <goal>cmp</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <reporting> + <plugins> + <plugin> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <reportSets> + <reportSet> + <reports> + <report>cmp-report</report> + </reports> + </reportSet> + </reportSets> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>true</skip><!-- TODO: Enable after the first release --> + </configuration> + </plugin> + </plugins> + </reporting> +</project> diff --git a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties new file mode 100644 index 0000000000..72bca6a975 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties @@ -0,0 +1,36 @@ +authenticationCanceled=Authentication canceled: no password +closeListenerFailed=Ssh session close listener failed +configInvalidPath=Invalid path in ssh config key {0}: {1} +ftpCloseFailed=Closing the SFTP channel failed +keyEncryptedMsg=Key ''{0}'' is encrypted. Enter the passphrase to decrypt it. +keyEncryptedPrompt=Passphrase +keyLoadFailed=Could not load key ''{0}'' +knownHostsCouldNotUpdate=Could not update known hosts file {0} +knownHostsFileLockedRead=Could not read known hosts file (locked) {0} +knownHostsFileLockedUpdate=Could not update known hosts file (locked) {0} +knownHostsFileReadFailed=Failed to read known hosts file {0} +knownHostsInvalidLine=Known hosts file {0} contains invalid line {1} +knownHostsInvalidPath=Invalid path for known hosts file {0} +knownHostsKeyFingerprints=The {0} key''s fingerprints are: +knownHostsModifiedKeyAcceptPrompt=Accept this key and continue connecting all the same? +knownHostsModifiedKeyDenyMsg=To resolve this add the correct host key to your known hosts file {0} +knownHostsModifiedKeyStorePrompt=If so, also store the new key? +knownHostsModifiedKeyWarning=WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!\n\ +The connection might be compromised (man-in-the-middle attack).\n\ +It is also possible that the {0} key of the host has just been changed.\n\ +The expected {1} key for host ''{2}'' has the fingerprints:\n\ +{3}\n\ +{4}\n\ +The {0} key actually received has the fingerprints:\n\ +{5}\n\ +{6} +knownHostsRevokedKeyMsg=Host ''{0}'' sent a key that is marked as revoked in the known hosts file {1}. +knownHostsUnknownKeyMsg=The authenticity of host ''{0}'' cannot be established. +knownHostsUnknownKeyPrompt=Accept and store this key, and continue connecting? +knownHostsUnknownKeyType=Cannot read server key from known hosts file {0}; line {1} +knownHostsUserAskCreationMsg=File {0} does not exist. +knownHostsUserAskCreationPrompt=Create file {0} ? +sessionCloseFailed=Closing the session failed +sshClosingDown=Apache MINA sshd session factory is closing down; cannot create new ssh sessions on this factory +sshCommandTimeout={0} timed out after {1} seconds while opening the channel +sshProcessStillRunning={0} is not yet completed, cannot get exit code
\ No newline at end of file diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/CachingKeyPairProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/CachingKeyPairProvider.java new file mode 100644 index 0000000000..dcd17af2ff --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/CachingKeyPairProvider.java @@ -0,0 +1,168 @@ +/* + * 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.text.MessageFormat.format; + +import java.io.IOException; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; + +import org.apache.sshd.common.keyprovider.FileKeyPairProvider; +import org.eclipse.jgit.transport.sshd.KeyCache; + +/** + * A {@link FileKeyPairProvider} that uses an external {@link KeyCache}. + */ +public class CachingKeyPairProvider extends FileKeyPairProvider { + + private final KeyCache cache; + + /** + * Creates a new {@link CachingKeyPairProvider} using the given + * {@link KeyCache}. If the cache is {@code null}, this is a simple + * {@link FileKeyPairProvider}. + * + * @param paths + * to load keys from + * @param cache + * to use, may be {@code null} if no external caching is desired + */ + public CachingKeyPairProvider(List<Path> paths, KeyCache cache) { + super(paths); + this.cache = cache; + } + + @Override + protected Iterable<KeyPair> loadKeys(Collection<? extends Path> resources) { + if (resources.isEmpty()) { + return Collections.emptyList(); + } + return () -> new CancellingKeyPairIterator(resources); + } + + @Override + protected KeyPair doLoadKey(Path resource) + throws IOException, GeneralSecurityException { + // By calling doLoadKey(String, Path, FilePasswordProvider) instead of + // super.doLoadKey(Path) we can bypass the key caching in + // AbstractResourceKeyPairProvider, over which we have no real control. + String resourceId = resource.toString(); + if (cache == null) { + return doLoadKey(resourceId, resource, getPasswordFinder()); + } + Throwable t[] = { null }; + KeyPair key = cache.get(resource, p -> { + try { + return doLoadKey(resourceId, p, getPasswordFinder()); + } catch (IOException | GeneralSecurityException e) { + t[0] = e; + return null; + } + }); + if (t[0] != null) { + if (t[0] instanceof CancellationException) { + throw (CancellationException) t[0]; + } + throw new IOException( + format(SshdText.get().keyLoadFailed, resource), t[0]); + } + return key; + } + + private class CancellingKeyPairIterator implements Iterator<KeyPair> { + + private final Iterator<Path> paths; + + private KeyPair nextItem; + + private boolean nextSet; + + public CancellingKeyPairIterator(Collection<? extends Path> resources) { + List<Path> copy = new ArrayList<>(resources.size()); + copy.addAll(resources); + paths = copy.iterator(); + } + + @Override + public boolean hasNext() { + if (nextSet) { + return nextItem != null; + } + nextSet = true; + while (nextItem == null && paths.hasNext()) { + try { + nextItem = doLoadKey(paths.next()); + } catch (CancellationException cancelled) { + throw cancelled; + } catch (Exception other) { + log.warn(other.toString()); + } + } + return nextItem != null; + } + + @Override + public KeyPair next() { + if (!nextSet && !hasNext()) { + throw new NoSuchElementException(); + } + KeyPair result = nextItem; + nextItem = null; + nextSet = false; + if (result == null) { + throw new NoSuchElementException(); + } + return result; + } + + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java new file mode 100644 index 0000000000..2bde7e711d --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java @@ -0,0 +1,114 @@ +/* + * 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 org.apache.sshd.client.ClientFactoryManager; +import org.apache.sshd.client.config.hosts.HostConfigEntry; +import org.apache.sshd.client.session.ClientSessionImpl; +import org.apache.sshd.common.io.IoSession; +import org.eclipse.jgit.transport.CredentialsProvider; + +/** + * A {@link org.apache.sshd.client.session.ClientSession ClientSession} that can + * be associated with the {@link HostConfigEntry} the session was created for. + * The {@link JGitSshClient} creates such sessions and sets this association. + * <p> + * Also provides for associating a JGit {@link CredentialsProvider} with a + * session. + * </p> + */ +public class JGitClientSession extends ClientSessionImpl { + + private HostConfigEntry hostConfig; + + private CredentialsProvider credentialsProvider; + + /** + * @param manager + * @param session + * @throws Exception + */ + public JGitClientSession(ClientFactoryManager manager, IoSession session) + throws Exception { + super(manager, session); + } + + /** + * Retrieves the {@link HostConfigEntry} this session was created for. + * + * @return the {@link HostConfigEntry}, or {@code null} if none set + */ + public HostConfigEntry getHostConfigEntry() { + return hostConfig; + } + + /** + * Sets the {@link HostConfigEntry} this session was created for. + * + * @param hostConfig + * the {@link HostConfigEntry} + */ + public void setHostConfigEntry(HostConfigEntry hostConfig) { + this.hostConfig = hostConfig; + } + + /** + * Sets the {@link CredentialsProvider} for this session. + * + * @param provider + * to set + */ + public void setCredentialsProvider(CredentialsProvider provider) { + credentialsProvider = provider; + } + + /** + * Retrieves the {@link CredentialsProvider} set for this session. + * + * @return the provider, or {@code null} if none is set. + */ + public CredentialsProvider getCredentialsProvider() { + return credentialsProvider; + } + +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java new file mode 100644 index 0000000000..0b3de4ace3 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java @@ -0,0 +1,83 @@ +/* + * 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 java.util.List; + +import org.apache.sshd.client.auth.AbstractUserAuthFactory; +import org.apache.sshd.client.auth.UserAuth; +import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.signature.SignatureFactoriesManager; + +/** + * A customized authentication factory for public key user authentication. The + * default implementation {@link UserAuthPublicKeyFactory} ends up doing some + * crazy stream "magic" that loads too many keys too early. + */ +public class JGitPublicKeyAuthFactory extends AbstractUserAuthFactory + implements SignatureFactoriesManager { + + /** The singleton {@link JGitPublicKeyAuthFactory}. */ + public static final JGitPublicKeyAuthFactory INSTANCE = new JGitPublicKeyAuthFactory(); + + private JGitPublicKeyAuthFactory() { + super(UserAuthPublicKeyFactory.NAME); + } + + @Override + public UserAuth create() { + return new JGitPublicKeyAuthentication(getSignatureFactories()); + } + + @Override + public List<NamedFactory<Signature>> getSignatureFactories() { + return null; + } + + @Override + public void setSignatureFactories(List<NamedFactory<Signature>> factories) { + throw new UnsupportedOperationException(); + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java new file mode 100644 index 0000000000..63b3990b13 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java @@ -0,0 +1,103 @@ +/* + * 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 java.util.List; + +import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.signature.Signature; + +/** + * A specialized public key authentication handler that uses our own + * {@link JGitPublicKeyIterator}. The super class creates in + * {@link #init(ClientSession, String)} a + * {@link org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyIterator}, which + * in its constructor does some strange {@link java.util.stream.Stream} "magic" + * that ends up loading keys prematurely. + */ +public class JGitPublicKeyAuthentication extends UserAuthPublicKey { + + private ClientSession clientSession; + + private String serviceName; + + /** + * Creates a new {@link JGitPublicKeyAuthentication}. + * + * @param factories + * signature factories to use + */ + public JGitPublicKeyAuthentication( + List<NamedFactory<Signature>> factories) { + super(factories); + } + + @Override + public void init(ClientSession session, String service) throws Exception { + // Do *not* call super.init(); it'll create a UserAuthPublicKeyIterator + // and that's where things then go wrong. Instead, do the whole + // initialization directly here. + clientSession = session; + serviceName = service; + releaseKeys(); + // Use our own iterator! + keys = new JGitPublicKeyIterator(session, this); + } + + @Override + public ClientSession getClientSession() { + return clientSession; + } + + @Override + public ClientSession getSession() { + return clientSession; + } + + @Override + public String getService() { + return serviceName; + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyIterator.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyIterator.java new file mode 100644 index 0000000000..cda12623d8 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyIterator.java @@ -0,0 +1,275 @@ +/* + * 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 java.io.IOException; +import java.nio.channels.Channel; +import java.security.KeyPair; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.sshd.agent.SshAgent; +import org.apache.sshd.agent.SshAgentFactory; +import org.apache.sshd.client.auth.pubkey.AbstractKeyPairIterator; +import org.apache.sshd.client.auth.pubkey.KeyAgentIdentity; +import org.apache.sshd.client.auth.pubkey.KeyPairIdentity; +import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity; +import org.apache.sshd.client.config.hosts.HostConfigEntry; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.FactoryManager; +import org.apache.sshd.common.keyprovider.KeyIdentityProvider; +import org.apache.sshd.common.signature.SignatureFactoriesManager; + +/** + * A new iterator over key pairs that we use instead of the default + * {@link org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyIterator}, which + * in its constructor does some strange {@link java.util.stream.Stream} "magic" + * that ends up loading keys prematurely. This class uses plain + * {@link Iterator}s instead to avoid that problem. Used in + * {@link JGitPublicKeyAuthentication}. + * + * @see <a href= + * "https://issues.apache.org/jira/projects/SSHD/issues/SSHD-860">Upstream + * issue SSHD-860</a> + */ +public class JGitPublicKeyIterator + extends AbstractKeyPairIterator<PublicKeyIdentity> implements Channel { + + // Re: the cause for the problem mentioned above has not been determined. + // It looks as if either the Apache code inadvertently calls + // GenericUtils.isEmpty() on all streams (which would load the first key + // of each stream), or the Java stream implementation does some prefetching. + // It's not entirely clear. Using Iterators we have more control over + // what happens when. + + private final AtomicBoolean open = new AtomicBoolean(true); + + private SshAgent agent; + + private final List<Iterator<PublicKeyIdentity>> keys = new ArrayList<>(3); + + private final Iterator<Iterator<PublicKeyIdentity>> keyIter; + + private Iterator<PublicKeyIdentity> current; + + private Boolean hasElement; + + /** + * Creates a new {@link JGitPublicKeyIterator}. + * + * @param session + * we're trying to authenticate + * @param signatureFactories + * to use + * @throws Exception + * if an {@link SshAgentFactory} is configured and getting + * identities from the agent fails + */ + public JGitPublicKeyIterator(ClientSession session, + SignatureFactoriesManager signatureFactories) throws Exception { + super(session); + boolean useAgent = true; + if (session instanceof JGitClientSession) { + HostConfigEntry config = ((JGitClientSession) session) + .getHostConfigEntry(); + useAgent = !config.isIdentitiesOnly(); + } + if (useAgent) { + FactoryManager manager = session.getFactoryManager(); + SshAgentFactory factory = manager == null ? null + : manager.getAgentFactory(); + if (factory != null) { + try { + agent = factory.createClient(manager); + keys.add(new AgentIdentityIterator(agent)); + } catch (IOException e) { + try { + closeAgent(); + } catch (IOException err) { + e.addSuppressed(err); + } + throw e; + } + } + } + keys.add( + new KeyPairIdentityIterator(session.getRegisteredIdentities(), + session, signatureFactories)); + keys.add(new KeyPairIdentityIterator(session.getKeyPairProvider(), + session, signatureFactories)); + keyIter = keys.iterator(); + } + + @Override + public boolean isOpen() { + return open.get(); + } + + @Override + public void close() throws IOException { + if (open.getAndSet(false)) { + closeAgent(); + } + } + + @Override + public boolean hasNext() { + if (!isOpen()) { + return false; + } + if (hasElement != null) { + return hasElement.booleanValue(); + } + while (current == null || !current.hasNext()) { + if (keyIter.hasNext()) { + current = keyIter.next(); + } else { + current = null; + hasElement = Boolean.FALSE; + return false; + } + } + hasElement = Boolean.TRUE; + return true; + } + + @Override + public PublicKeyIdentity next() { + if (!isOpen() || hasElement == null && !hasNext() + || !hasElement.booleanValue()) { + throw new NoSuchElementException(); + } + hasElement = null; + PublicKeyIdentity result; + try { + result = current.next(); + } catch (NoSuchElementException e) { + result = null; + } + return result; + } + + private void closeAgent() throws IOException { + if (agent == null) { + return; + } + try { + agent.close(); + } finally { + agent = null; + } + } + + /** + * An {@link Iterator} that maps the data obtained from an agent to + * {@link PublicKeyIdentity}. + */ + private static class AgentIdentityIterator + implements Iterator<PublicKeyIdentity> { + + private final SshAgent agent; + + private final Iterator<? extends Map.Entry<PublicKey, String>> iter; + + public AgentIdentityIterator(SshAgent agent) throws IOException { + this.agent = agent; + iter = agent == null ? null : agent.getIdentities().iterator(); + } + + @Override + public boolean hasNext() { + return iter != null && iter.hasNext(); + } + + @Override + public PublicKeyIdentity next() { + if (iter == null) { + throw new NoSuchElementException(); + } + Map.Entry<PublicKey, String> entry = iter.next(); + return new KeyAgentIdentity(agent, entry.getKey(), + entry.getValue()); + } + } + + /** + * An {@link Iterator} that maps {@link KeyPair} to + * {@link PublicKeyIdentity}. + */ + private static class KeyPairIdentityIterator + implements Iterator<PublicKeyIdentity> { + + private final Iterator<KeyPair> keyPairs; + + private final ClientSession session; + + private final SignatureFactoriesManager signatureFactories; + + public KeyPairIdentityIterator(KeyIdentityProvider provider, + ClientSession session, + SignatureFactoriesManager signatureFactories) { + this.session = session; + this.signatureFactories = signatureFactories; + keyPairs = provider == null ? null : provider.loadKeys().iterator(); + } + + @Override + public boolean hasNext() { + return keyPairs != null && keyPairs.hasNext(); + } + + @Override + public PublicKeyIdentity next() { + if (keyPairs == null) { + throw new NoSuchElementException(); + } + KeyPair key = keyPairs.next(); + return new KeyPairIdentity(signatureFactories, session, key); + } + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java new file mode 100644 index 0000000000..98d4e3e5e5 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java @@ -0,0 +1,320 @@ +/* + * 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.text.MessageFormat.format; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.config.hosts.HostConfigEntry; +import org.apache.sshd.client.future.ConnectFuture; +import org.apache.sshd.client.future.DefaultConnectFuture; +import org.apache.sshd.client.session.ClientSessionImpl; +import org.apache.sshd.client.session.SessionFactory; +import org.apache.sshd.common.future.SshFutureListener; +import org.apache.sshd.common.io.IoConnectFuture; +import org.apache.sshd.common.io.IoSession; +import org.apache.sshd.common.keyprovider.FileKeyPairProvider; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.helpers.AbstractSession; +import org.apache.sshd.common.util.ValidateUtils; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.SshConstants; +import org.eclipse.jgit.transport.sshd.KeyCache; + +/** + * Customized {@link SshClient} for JGit. It creates specialized + * {@link JGitClientSession}s that know about the {@link HostConfigEntry} they + * were created for, and it loads all KeyPair identities lazily. + */ +public class JGitSshClient extends SshClient { + + private KeyCache keyCache; + + private CredentialsProvider credentialsProvider; + + @Override + protected SessionFactory createSessionFactory() { + // Override the parent's default + return new JGitSessionFactory(this); + } + + @Override + public ConnectFuture connect(HostConfigEntry hostConfig) + throws IOException { + if (connector == null) { + throw new IllegalStateException("SshClient not started."); //$NON-NLS-1$ + } + Objects.requireNonNull(hostConfig, "No host configuration"); //$NON-NLS-1$ + String host = ValidateUtils.checkNotNullAndNotEmpty( + hostConfig.getHostName(), "No target host"); //$NON-NLS-1$ + int port = hostConfig.getPort(); + ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port); //$NON-NLS-1$ + String userName = hostConfig.getUsername(); + InetSocketAddress address = new InetSocketAddress(host, port); + ConnectFuture connectFuture = new DefaultConnectFuture( + userName + '@' + address, null); + SshFutureListener<IoConnectFuture> listener = createConnectCompletionListener( + connectFuture, userName, address, hostConfig); + connector.connect(address).addListener(listener); + return connectFuture; + } + + private SshFutureListener<IoConnectFuture> createConnectCompletionListener( + ConnectFuture connectFuture, String username, + InetSocketAddress address, HostConfigEntry hostConfig) { + return new SshFutureListener<IoConnectFuture>() { + + @Override + public void operationComplete(IoConnectFuture future) { + if (future.isCanceled()) { + connectFuture.cancel(); + return; + } + Throwable t = future.getException(); + if (t != null) { + connectFuture.setException(t); + return; + } + IoSession ioSession = future.getSession(); + try { + JGitClientSession session = createSession(ioSession, + username, address, hostConfig); + connectFuture.setSession(session); + } catch (RuntimeException e) { + connectFuture.setException(e); + ioSession.close(true); + } + } + + @Override + public String toString() { + return "JGitSshClient$ConnectCompletionListener[" + username //$NON-NLS-1$ + + '@' + address + ']'; + } + }; + } + + private JGitClientSession createSession(IoSession ioSession, + String username, InetSocketAddress address, + HostConfigEntry hostConfig) { + AbstractSession rawSession = AbstractSession.getSession(ioSession); + if (!(rawSession instanceof JGitClientSession)) { + throw new IllegalStateException("Wrong session type: " //$NON-NLS-1$ + + rawSession.getClass().getCanonicalName()); + } + JGitClientSession session = (JGitClientSession) rawSession; + session.setUsername(username); + session.setConnectAddress(address); + session.setHostConfigEntry(hostConfig); + if (session.getCredentialsProvider() == null) { + session.setCredentialsProvider(getCredentialsProvider()); + } + FileKeyPairProvider ourConfiguredKeysProvider = null; + List<Path> identities = hostConfig.getIdentities().stream() + .map(s -> { + try { + return Paths.get(s); + } catch (InvalidPathException e) { + log.warn(format(SshdText.get().configInvalidPath, + SshConstants.IDENTITY_FILE, s), e); + return null; + } + }).filter(p -> p != null && Files.exists(p)) + .collect(Collectors.toList()); + ourConfiguredKeysProvider = new CachingKeyPairProvider(identities, + keyCache); + ourConfiguredKeysProvider.setPasswordFinder(getFilePasswordProvider()); + if (hostConfig.isIdentitiesOnly()) { + session.setKeyPairProvider(ourConfiguredKeysProvider); + } else { + KeyPairProvider defaultKeysProvider = getKeyPairProvider(); + if (defaultKeysProvider instanceof FileKeyPairProvider) { + ((FileKeyPairProvider) defaultKeysProvider) + .setPasswordFinder(getFilePasswordProvider()); + } + KeyPairProvider combinedProvider = new CombinedKeyPairProvider( + ourConfiguredKeysProvider, defaultKeysProvider); + session.setKeyPairProvider(combinedProvider); + } + return session; + } + + /** + * Set a cache for loaded keys. Newly discovered keys will be added when + * IdentityFile host entries from the ssh config file are used during + * session authentication. + * + * @param cache + * to use + */ + public void setKeyCache(KeyCache cache) { + keyCache = cache; + } + + /** + * Sets the {@link CredentialsProvider} for this client. + * + * @param provider + * to set + */ + public void setCredentialsProvider(CredentialsProvider provider) { + credentialsProvider = provider; + } + + /** + * Retrieves the {@link CredentialsProvider} set for this client. + * + * @return the provider, or {@code null} if none is set. + */ + public CredentialsProvider getCredentialsProvider() { + return credentialsProvider; + } + + /** + * A {@link SessionFactory} to create our own specialized + * {@link JGitClientSession}s. + */ + private static class JGitSessionFactory extends SessionFactory { + + public JGitSessionFactory(JGitSshClient client) { + super(client); + } + + @Override + protected ClientSessionImpl doCreateSession(IoSession ioSession) + throws Exception { + return new JGitClientSession(getClient(), ioSession); + } + } + + /** + * A {@link KeyPairProvider} that iterates over the {@link Iterable}s + * returned by other {@link KeyPairProvider}s. + */ + private static class CombinedKeyPairProvider implements KeyPairProvider { + + private final List<KeyPairProvider> providers; + + public CombinedKeyPairProvider(KeyPairProvider... providers) { + this(Arrays.stream(providers).filter(Objects::nonNull) + .collect(Collectors.toList())); + } + + public CombinedKeyPairProvider(List<KeyPairProvider> providers) { + this.providers = providers; + } + + @Override + public Iterable<String> getKeyTypes() { + throw new UnsupportedOperationException( + "Should not have been called in a ssh client"); //$NON-NLS-1$ + } + + @Override + public KeyPair loadKey(String type) { + throw new UnsupportedOperationException( + "Should not have been called in a ssh client"); //$NON-NLS-1$ + } + + @Override + public Iterable<KeyPair> loadKeys() { + return () -> new Iterator<KeyPair>() { + + private Iterator<KeyPairProvider> factories = providers.iterator(); + private Iterator<KeyPair> current; + + private Boolean hasElement; + + @Override + public boolean hasNext() { + if (hasElement != null) { + return hasElement.booleanValue(); + } + while (current == null || !current.hasNext()) { + if (factories.hasNext()) { + current = factories.next().loadKeys().iterator(); + } else { + current = null; + hasElement = Boolean.FALSE; + return false; + } + } + hasElement = Boolean.TRUE; + return true; + } + + @Override + public KeyPair next() { + if (hasElement == null && !hasNext() + || !hasElement.booleanValue()) { + throw new NoSuchElementException(); + } + hasElement = null; + KeyPair result; + try { + result = current.next(); + } catch (NoSuchElementException e) { + result = null; + } + return result; + } + + }; + } + + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitUserInteraction.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitUserInteraction.java new file mode 100644 index 0000000000..a96a6962cc --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitUserInteraction.java @@ -0,0 +1,169 @@ +/* + * 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 java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +import org.apache.sshd.client.auth.keyboard.UserInteraction; +import org.apache.sshd.client.session.ClientSession; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.SshConstants; +import org.eclipse.jgit.transport.URIish; + +/** + * A {@link UserInteraction} callback implementation based on a + * {@link CredentialsProvider}. + */ +public class JGitUserInteraction implements UserInteraction { + + private final CredentialsProvider provider; + + /** + * Creates a new {@link JGitUserInteraction} for interactive password input + * based on the given {@link CredentialsProvider}. + * + * @param provider + * to use + */ + public JGitUserInteraction(CredentialsProvider provider) { + this.provider = provider; + } + + @Override + public boolean isInteractionAllowed(ClientSession session) { + return provider.isInteractive(); + } + + @Override + public String[] interactive(ClientSession session, String name, + String instruction, String lang, String[] prompt, boolean[] echo) { + // This is keyboard-interactive authentication + List<CredentialItem> items = new ArrayList<>(); + int numberOfHiddenInputs = 0; + for (int i = 0; i < prompt.length; i++) { + boolean hidden = i < echo.length && !echo[i]; + if (hidden) { + numberOfHiddenInputs++; + } + } + // RFC 4256 (SSH_MSG_USERAUTH_INFO_REQUEST) says: "The language tag is + // deprecated and SHOULD be the empty string." and "[If there are no + // prompts] the client SHOULD still display the name and instruction + // fields" and "[The] client SHOULD print the name and instruction (if + // non-empty)" + if (name != null && !name.isEmpty()) { + items.add(new CredentialItem.InformationalMessage(name)); + } + if (instruction != null && !instruction.isEmpty()) { + items.add(new CredentialItem.InformationalMessage(instruction)); + } + for (int i = 0; i < prompt.length; i++) { + boolean hidden = i < echo.length && !echo[i]; + if (hidden && numberOfHiddenInputs == 1) { + // We need to somehow trigger storing the password in the + // Eclipse secure storage in EGit. Currently, this is done only + // for password fields. + items.add(new CredentialItem.Password()); + // TODO Possibly change EGit to store all hidden strings + // (keyed by the URI and the prompt?) so that we don't have to + // use this kludge here. + } else { + items.add(new CredentialItem.StringType(prompt[i], hidden)); + } + } + if (items.isEmpty()) { + // Huh? No info, no prompts? + return prompt; // Is known to have length zero here + } + URIish uri = toURI(session.getUsername(), + (InetSocketAddress) session.getIoSession().getRemoteAddress()); + if (provider.get(uri, items)) { + return items.stream().map(i -> { + if (i instanceof CredentialItem.Password) { + return new String(((CredentialItem.Password) i).getValue()); + } else if (i instanceof CredentialItem.StringType) { + return ((CredentialItem.StringType) i).getValue(); + } + return null; + }).filter(s -> s != null).toArray(String[]::new); + } + // TODO What to throw to abort the connection/authentication process? + // In UserAuthKeyboardInteractive.getUserResponses() it's clear that + // returning null is valid and signifies "an error"; we'll try the + // next authentication method. But if the user explicitly canceled, + // then we don't want to try the next methods... + // + // Probably not a serious issue with the typical order of public-key, + // keyboard-interactive, password. + return null; + } + + @Override + public String getUpdatedPassword(ClientSession session, String prompt, + String lang) { + // TODO Implement password update in password authentication? + return null; + } + + /** + * Creates a {@link URIish} from the given remote address and user name. + * + * @param userName + * for the uri + * @param remote + * address of the remote host + * @return the uri, with {@link SshConstants#SSH_SCHEME} as scheme + */ + public static URIish toURI(String userName, InetSocketAddress remote) { + String host = remote.getHostString(); + int port = remote.getPort(); + return new URIish() // + .setScheme(SshConstants.SSH_SCHEME) // + .setHost(host) // + .setPort(port) // + .setUser(userName); + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java new file mode 100644 index 0000000000..a20ee6bb84 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java @@ -0,0 +1,718 @@ +/* + * 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.text.MessageFormat.format; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import org.apache.sshd.client.config.hosts.HostConfigEntry; +import org.apache.sshd.client.config.hosts.KnownHostEntry; +import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier; +import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair; +import org.apache.sshd.client.keyverifier.ServerKeyVerifier; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; +import org.apache.sshd.common.digest.BuiltinDigests; +import org.apache.sshd.common.util.io.ModifiableFileWatcher; +import org.apache.sshd.common.util.net.SshdSocketAddress; +import org.eclipse.jgit.internal.storage.file.LockFile; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.SshConstants; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.sshd.JGitHostConfigEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A sever host key verifier that honors the {@code StrictHostKeyChecking} and + * {@code UserKnownHostsFile} values from the ssh configuration. + * <p> + * The verifier can be given default known_hosts files in the constructor, which + * will be used if the ssh config does not specify a {@code UserKnownHostsFile}. + * If the ssh config <em>does</em> set {@code UserKnownHostsFile}, the verifier + * uses the given files in the order given. Non-existing or unreadable files are + * ignored. + * <p> + * {@code StrictHostKeyChecking} accepts the following values: + * </p> + * <dl> + * <dt>ask</dt> + * <dd>Ask the user whether new or changed keys shall be accepted and be added + * to the known_hosts file.</dd> + * <dt>yes/true</dt> + * <dd>Accept only keys listed in the known_hosts file.</dd> + * <dt>no/false</dt> + * <dd>Silently accept all new or changed keys, add new keys to the known_hosts + * file.</dd> + * <dt>accept-new</dt> + * <dd>Silently accept keys for new hosts and add them to the known_hosts + * file.</dd> + * </dl> + * <p> + * If {@code StrictHostKeyChecking} is not set, or set to any other value, the + * default value <b>ask</b> is active. + * </p> + * <p> + * This implementation relies on the {@link ClientSession} being a + * {@link JGitClientSession}. By default Apache MINA sshd does not forward the + * config file host entry to the session, so it would be unknown here which + * entry it was and what setting of {@code StrictHostKeyChecking} should be + * used. If used with some other session type, the implementation assumes + * "<b>ask</b>". + * <p> + * <p> + * Asking the user is done via a {@link CredentialsProvider} obtained from the + * session. If none is set, the implementation falls back to strict host key + * checking ("<b>yes</b>"). + * </p> + * <p> + * Note that adding a key to the known hosts file may create the file. You can + * specify in the constructor whether the user shall be asked about that, too. + * If the the user declines updating the file, but the key was otherwise + * accepted (user confirmed for "<b>ask</b>", or "no" or "accept-new" are + * active), the key is accepted for this session only. + * </p> + * <p> + * If several known hosts files are specified, a new key is always added to the + * first file (even if it doesn't exist yet; see the note about file creation + * above). + * </p> + * + * @see <a href="http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5">man + * ssh-config</a> + */ +public class OpenSshServerKeyVerifier implements ServerKeyVerifier { + + // TODO: GlobalKnownHostsFile? May need some kind of LRU caching; these + // files may be large! + + private static final Logger LOG = LoggerFactory + .getLogger(OpenSshServerKeyVerifier.class); + + /** Can be used to mark revoked known host lines. */ + private static final String MARKER_REVOKED = "revoked"; //$NON-NLS-1$ + + private final boolean askAboutNewFile; + + private final Map<Path, HostKeyFile> knownHostsFiles = new ConcurrentHashMap<>(); + + private final List<HostKeyFile> defaultFiles = new ArrayList<>(); + + private enum ModifiedKeyHandling { + DENY, ALLOW, ALLOW_AND_STORE + } + + /** + * Creates a new {@link OpenSshServerKeyVerifier}. + * + * @param askAboutNewFile + * whether to ask the user, if possible, about creating a new + * non-existing known_hosts file + * @param defaultFiles + * typically ~/.ssh/known_hosts and ~/.ssh/known_hosts2. May be + * empty or {@code null}, in which case no default files are + * installed. The files need not exist. + */ + public OpenSshServerKeyVerifier(boolean askAboutNewFile, List<File> defaultFiles) { + if (defaultFiles != null) { + for (File file : defaultFiles) { + Path p = file.toPath(); + HostKeyFile newFile = new HostKeyFile(p); + knownHostsFiles.put(p, newFile); + this.defaultFiles.add(newFile); + } + } + this.askAboutNewFile = askAboutNewFile; + } + + @Override + public boolean verifyServerKey(ClientSession clientSession, + SocketAddress remoteAddress, PublicKey serverKey) { + List<HostKeyFile> filesToUse = defaultFiles; + if (clientSession instanceof JGitClientSession) { + HostConfigEntry entry = ((JGitClientSession) clientSession) + .getHostConfigEntry(); + if (entry instanceof JGitHostConfigEntry) { + // Always true! + List<HostKeyFile> userFiles = addUserHostKeyFiles( + ((JGitHostConfigEntry) entry).getMultiValuedOptions() + .get(SshConstants.USER_KNOWN_HOSTS_FILE)); + if (!userFiles.isEmpty()) { + filesToUse = userFiles; + } + } + } + AskUser ask = new AskUser(); + HostEntryPair[] modified = { null }; + Path path = null; + HostKeyHelper helper = new HostKeyHelper(); + for (HostKeyFile file : filesToUse) { + try { + if (find(clientSession, remoteAddress, serverKey, file.get(), + modified, helper)) { + return true; + } + } catch (RevokedKeyException e) { + ask.revokedKey(clientSession, remoteAddress, serverKey, + file.getPath()); + return false; + } + if (path == null && modified[0] != null) { + // Remember the file in which we might need to update the + // entry + path = file.getPath(); + } + } + if (modified[0] != null) { + // We found an entry, but with a different key + ModifiedKeyHandling toDo = ask.acceptModifiedServerKey( + clientSession, remoteAddress, modified[0].getServerKey(), + serverKey, path); + if (toDo == ModifiedKeyHandling.ALLOW_AND_STORE) { + try { + updateModifiedServerKey(clientSession, remoteAddress, + serverKey, modified[0], path, helper); + knownHostsFiles.get(path).resetReloadAttributes(); + } catch (IOException e) { + LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate, + path)); + } + } + if (toDo == ModifiedKeyHandling.DENY) { + return false; + } + // TODO: OpenSsh disables password and keyboard-interactive + // authentication in this case. Also agent and local port forwarding + // are switched off. (Plus a few other things such as X11 forwarding + // that are of no interest to a git client.) + return true; + } else if (ask.acceptUnknownKey(clientSession, remoteAddress, + serverKey)) { + if (!filesToUse.isEmpty()) { + HostKeyFile toUpdate = filesToUse.get(0); + path = toUpdate.getPath(); + try { + updateKnownHostsFile(clientSession, remoteAddress, + serverKey, path, helper); + toUpdate.resetReloadAttributes(); + } catch (IOException e) { + LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate, + path)); + } + } + return true; + } + return false; + } + + private static class RevokedKeyException extends Exception { + private static final long serialVersionUID = 1L; + } + + private boolean find(ClientSession clientSession, + SocketAddress remoteAddress, PublicKey serverKey, + List<HostEntryPair> entries, HostEntryPair[] modified, + HostKeyHelper helper) throws RevokedKeyException { + Collection<SshdSocketAddress> candidates = helper + .resolveHostNetworkIdentities(clientSession, remoteAddress); + for (HostEntryPair current : entries) { + KnownHostEntry entry = current.getHostEntry(); + for (SshdSocketAddress host : candidates) { + if (entry.isHostMatch(host.getHostName(), host.getPort())) { + boolean isRevoked = MARKER_REVOKED + .equals(entry.getMarker()); + if (KeyUtils.compareKeys(serverKey, + current.getServerKey())) { + // Exact match + if (isRevoked) { + throw new RevokedKeyException(); + } + modified[0] = null; + return true; + } else if (!isRevoked) { + // Server sent a different key + modified[0] = current; + // Keep going -- maybe there's another entry for this + // host + } + } + } + } + return false; + } + + private List<HostKeyFile> addUserHostKeyFiles(List<String> fileNames) { + if (fileNames == null || fileNames.isEmpty()) { + return Collections.emptyList(); + } + List<HostKeyFile> userFiles = new ArrayList<>(); + for (String name : fileNames) { + try { + Path path = Paths.get(name); + HostKeyFile file = knownHostsFiles.computeIfAbsent(path, + p -> new HostKeyFile(path)); + userFiles.add(file); + } catch (InvalidPathException e) { + LOG.warn(format(SshdText.get().knownHostsInvalidPath, + name)); + } + } + return userFiles; + } + + private void updateKnownHostsFile(ClientSession clientSession, + SocketAddress remoteAddress, PublicKey serverKey, Path path, + HostKeyHelper updater) + throws IOException { + KnownHostEntry entry = updater.prepareKnownHostEntry(clientSession, + remoteAddress, serverKey); + if (entry == null) { + return; + } + if (!Files.exists(path)) { + if (askAboutNewFile) { + CredentialsProvider provider = getCredentialsProvider( + clientSession); + if (provider == null) { + // We can't ask, so don't create the file + return; + } + URIish uri = new URIish().setPath(path.toString()); + if (!askUser(provider, uri, // + format(SshdText.get().knownHostsUserAskCreationPrompt, + path), // + format(SshdText.get().knownHostsUserAskCreationMsg, + path))) { + return; + } + } + } + LockFile lock = new LockFile(path.toFile()); + if (lock.lockForAppend()) { + try { + try (BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(lock.getOutputStream(), + StandardCharsets.UTF_8))) { + writer.newLine(); + writer.write(entry.getConfigLine()); + writer.newLine(); + } + lock.commit(); + } catch (IOException e) { + lock.unlock(); + throw e; + } + } else { + LOG.warn(format(SshdText.get().knownHostsFileLockedUpdate, + path)); + } + } + + private void updateModifiedServerKey(ClientSession clientSession, + SocketAddress remoteAddress, PublicKey serverKey, + HostEntryPair entry, Path path, HostKeyHelper helper) + throws IOException { + KnownHostEntry hostEntry = entry.getHostEntry(); + String oldLine = hostEntry.getConfigLine(); + String newLine = helper.prepareModifiedServerKeyLine(clientSession, + remoteAddress, hostEntry, oldLine, entry.getServerKey(), + serverKey); + if (newLine == null || newLine.isEmpty()) { + return; + } + if (oldLine == null || oldLine.isEmpty() || newLine.equals(oldLine)) { + // Shouldn't happen. + return; + } + LockFile lock = new LockFile(path.toFile()); + if (lock.lock()) { + try { + try (BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(lock.getOutputStream(), + StandardCharsets.UTF_8)); + BufferedReader reader = Files.newBufferedReader(path, + StandardCharsets.UTF_8)) { + boolean done = false; + String line; + while ((line = reader.readLine()) != null) { + String toWrite = line; + if (!done) { + int pos = line.indexOf('#'); + String toTest = pos < 0 ? line + : line.substring(0, pos); + if (toTest.trim().equals(oldLine)) { + toWrite = newLine; + done = true; + } + } + writer.write(toWrite); + writer.newLine(); + } + } + lock.commit(); + } catch (IOException e) { + lock.unlock(); + throw e; + } + } else { + LOG.warn(format(SshdText.get().knownHostsFileLockedUpdate, + path)); + } + } + + private static CredentialsProvider getCredentialsProvider( + ClientSession session) { + if (session instanceof JGitClientSession) { + return ((JGitClientSession) session).getCredentialsProvider(); + } + return null; + } + + private static boolean askUser(CredentialsProvider provider, URIish uri, + String prompt, String... messages) { + List<CredentialItem> items = new ArrayList<>(messages.length + 1); + for (String message : messages) { + items.add(new CredentialItem.InformationalMessage(message)); + } + if (prompt != null) { + CredentialItem.YesNoType answer = new CredentialItem.YesNoType( + prompt); + items.add(answer); + return provider.get(uri, items) && answer.getValue(); + } else { + return provider.get(uri, items); + } + } + + private static class AskUser { + + private enum Check { + ASK, DENY, ALLOW; + } + + @SuppressWarnings("nls") + private Check checkMode(ClientSession session, + SocketAddress remoteAddress, boolean changed) { + if (!(remoteAddress instanceof InetSocketAddress)) { + return Check.DENY; + } + if (session instanceof JGitClientSession) { + HostConfigEntry entry = ((JGitClientSession) session) + .getHostConfigEntry(); + String value = entry.getProperty( + SshConstants.STRICT_HOST_KEY_CHECKING, "ask"); + switch (value.toLowerCase(Locale.ROOT)) { + case SshConstants.YES: + case SshConstants.ON: + return Check.DENY; + case SshConstants.NO: + case SshConstants.OFF: + return Check.ALLOW; + case "accept-new": + return changed ? Check.DENY : Check.ALLOW; + default: + break; + } + } + if (getCredentialsProvider(session) == null) { + // This is called only for new, unknown hosts. If we have no way + // to interact with the user, the fallback mode is to deny the + // key. + return Check.DENY; + } + return Check.ASK; + } + + public void revokedKey(ClientSession clientSession, + SocketAddress remoteAddress, PublicKey serverKey, Path path) { + CredentialsProvider provider = getCredentialsProvider( + clientSession); + if (provider == null) { + return; + } + InetSocketAddress remote = (InetSocketAddress) remoteAddress; + URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(), + remote); + String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256, + serverKey); + String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey); + String keyAlgorithm = serverKey.getAlgorithm(); + askUser(provider, uri, null, // + format(SshdText.get().knownHostsRevokedKeyMsg, + remote.getHostString(), path), + format(SshdText.get().knownHostsKeyFingerprints, + keyAlgorithm), + md5, sha256); + } + + public boolean acceptUnknownKey(ClientSession clientSession, + SocketAddress remoteAddress, PublicKey serverKey) { + Check check = checkMode(clientSession, remoteAddress, false); + if (check != Check.ASK) { + return check == Check.ALLOW; + } + CredentialsProvider provider = getCredentialsProvider( + clientSession); + InetSocketAddress remote = (InetSocketAddress) remoteAddress; + // Ask the user + String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256, + serverKey); + String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey); + String keyAlgorithm = serverKey.getAlgorithm(); + String remoteHost = remote.getHostString(); + URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(), + remote); + String prompt = SshdText.get().knownHostsUnknownKeyPrompt; + return askUser(provider, uri, prompt, // + format(SshdText.get().knownHostsUnknownKeyMsg, + remoteHost), + format(SshdText.get().knownHostsKeyFingerprints, + keyAlgorithm), + md5, sha256); + } + + public ModifiedKeyHandling acceptModifiedServerKey( + ClientSession clientSession, + SocketAddress remoteAddress, PublicKey expected, + PublicKey actual, Path path) { + Check check = checkMode(clientSession, remoteAddress, true); + if (check == Check.ALLOW) { + // Never auto-store on CHECK.ALLOW + return ModifiedKeyHandling.ALLOW; + } + InetSocketAddress remote = (InetSocketAddress) remoteAddress; + String keyAlgorithm = actual.getAlgorithm(); + String remoteHost = remote.getHostString(); + URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(), + remote); + List<String> messages = new ArrayList<>(); + String warning = format( + SshdText.get().knownHostsModifiedKeyWarning, + keyAlgorithm, expected.getAlgorithm(), remoteHost, + KeyUtils.getFingerPrint(BuiltinDigests.md5, expected), + KeyUtils.getFingerPrint(BuiltinDigests.sha256, expected), + KeyUtils.getFingerPrint(BuiltinDigests.md5, actual), + KeyUtils.getFingerPrint(BuiltinDigests.sha256, actual)); + for (String line : warning.split("\n")) { //$NON-NLS-1$ + messages.add(line); + } + + CredentialsProvider provider = getCredentialsProvider( + clientSession); + if (check == Check.DENY) { + if (provider != null) { + messages.add(format( + SshdText.get().knownHostsModifiedKeyDenyMsg, path)); + askUser(provider, uri, null, + messages.toArray(new String[0])); + } + return ModifiedKeyHandling.DENY; + } + // ASK -- two questions: procceed? and store? + List<CredentialItem> items = new ArrayList<>(messages.size() + 2); + for (String message : messages) { + items.add(new CredentialItem.InformationalMessage(message)); + } + CredentialItem.YesNoType proceed = new CredentialItem.YesNoType( + SshdText.get().knownHostsModifiedKeyAcceptPrompt); + CredentialItem.YesNoType store = new CredentialItem.YesNoType( + SshdText.get().knownHostsModifiedKeyStorePrompt); + items.add(proceed); + items.add(store); + if (provider.get(uri, items) && proceed.getValue()) { + return store.getValue() ? ModifiedKeyHandling.ALLOW_AND_STORE + : ModifiedKeyHandling.ALLOW; + } + return ModifiedKeyHandling.DENY; + } + + } + + private static class HostKeyFile extends ModifiableFileWatcher + implements Supplier<List<HostEntryPair>> { + + private List<HostEntryPair> entries = Collections.emptyList(); + + public HostKeyFile(Path path) { + super(path); + } + + @Override + public List<HostEntryPair> get() { + Path path = getPath(); + try { + if (checkReloadRequired()) { + if (!Files.exists(path)) { + // Has disappeared. + resetReloadAttributes(); + return Collections.emptyList(); + } + LockFile lock = new LockFile(path.toFile()); + if (lock.lock()) { + try { + entries = reload(getPath()); + } finally { + lock.unlock(); + } + } else { + LOG.warn(format(SshdText.get().knownHostsFileLockedRead, + path)); + } + } + } catch (IOException e) { + LOG.warn(format(SshdText.get().knownHostsFileReadFailed, path)); + } + return Collections.unmodifiableList(entries); + } + + private List<HostEntryPair> reload(Path path) throws IOException { + try { + List<KnownHostEntry> rawEntries = KnownHostEntry + .readKnownHostEntries(path); + updateReloadAttributes(); + if (rawEntries == null || rawEntries.isEmpty()) { + return Collections.emptyList(); + } + List<HostEntryPair> newEntries = new LinkedList<>(); + for (KnownHostEntry entry : rawEntries) { + AuthorizedKeyEntry keyPart = entry.getKeyEntry(); + if (keyPart == null) { + continue; + } + try { + PublicKey serverKey = keyPart.resolvePublicKey( + PublicKeyEntryResolver.IGNORING); + if (serverKey == null) { + LOG.warn(format( + SshdText.get().knownHostsUnknownKeyType, + getPath(), entry.getConfigLine())); + } else { + newEntries.add(new HostEntryPair(entry, serverKey)); + } + } catch (GeneralSecurityException e) { + LOG.warn(format(SshdText.get().knownHostsInvalidLine, + getPath(), entry.getConfigLine())); + } + } + return newEntries; + } catch (FileNotFoundException e) { + resetReloadAttributes(); + return Collections.emptyList(); + } + } + } + + // The stuff below is just a hack to avoid having to copy a lot of code from + // KnownHostsServerKeyVerifier + + private static class HostKeyHelper extends KnownHostsServerKeyVerifier { + + public HostKeyHelper() { + // These two arguments will never be used in any way. + super((c, r, s) -> false, new File(".").toPath()); //$NON-NLS-1$ + } + + @Override + protected KnownHostEntry prepareKnownHostEntry( + ClientSession clientSession, SocketAddress remoteAddress, + PublicKey serverKey) throws IOException { + // Make this method accessible + try { + return super.prepareKnownHostEntry(clientSession, remoteAddress, + serverKey); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + @Override + protected String prepareModifiedServerKeyLine( + ClientSession clientSession, SocketAddress remoteAddress, + KnownHostEntry entry, String curLine, PublicKey expected, + PublicKey actual) throws IOException { + // Make this method accessible + try { + return super.prepareModifiedServerKeyLine(clientSession, + remoteAddress, entry, curLine, expected, actual); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + @Override + protected Collection<SshdSocketAddress> resolveHostNetworkIdentities( + ClientSession clientSession, SocketAddress remoteAddress) { + // Make this method accessible + return super.resolveHostNetworkIdentities(clientSession, + remoteAddress); + } + } + +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java new file mode 100644 index 0000000000..7f08f72f25 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java @@ -0,0 +1,50 @@ +package org.eclipse.jgit.internal.transport.sshd; + +import org.eclipse.jgit.nls.NLS; +import org.eclipse.jgit.nls.TranslationBundle; + +/** + * Externalized text messages for localization. + */ +public final class SshdText extends TranslationBundle { + + /** + * Get an instance of this translation bundle. + * + * @return an instance of this translation bundle + */ + public static SshdText get() { + return NLS.getBundleFor(SshdText.class); + } + + // @formatter:off + /***/ public String authenticationCanceled; + /***/ public String closeListenerFailed; + /***/ public String configInvalidPath; + /***/ public String ftpCloseFailed; + /***/ public String keyEncryptedMsg; + /***/ public String keyEncryptedPrompt; + /***/ public String keyLoadFailed; + /***/ public String knownHostsCouldNotUpdate; + /***/ public String knownHostsFileLockedRead; + /***/ public String knownHostsFileLockedUpdate; + /***/ public String knownHostsFileReadFailed; + /***/ public String knownHostsInvalidLine; + /***/ public String knownHostsInvalidPath; + /***/ public String knownHostsKeyFingerprints; + /***/ public String knownHostsModifiedKeyAcceptPrompt; + /***/ public String knownHostsModifiedKeyDenyMsg; + /***/ public String knownHostsModifiedKeyStorePrompt; + /***/ public String knownHostsModifiedKeyWarning; + /***/ public String knownHostsRevokedKeyMsg; + /***/ public String knownHostsUnknownKeyMsg; + /***/ public String knownHostsUnknownKeyPrompt; + /***/ public String knownHostsUnknownKeyType; + /***/ public String knownHostsUserAskCreationMsg; + /***/ public String knownHostsUserAskCreationPrompt; + /***/ public String sessionCloseFailed; + /***/ public String sshClosingDown; + /***/ public String sshCommandTimeout; + /***/ public String sshProcessStillRunning; + +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/IdentityPasswordProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/IdentityPasswordProvider.java new file mode 100644 index 0000000000..5a0bd7fdc8 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/IdentityPasswordProvider.java @@ -0,0 +1,120 @@ +/* + * 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.transport.sshd; + +import static java.text.MessageFormat.format; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; + +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.eclipse.jgit.internal.transport.sshd.SshdText; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.URIish; + +/** + * A {@link FilePasswordProvider} based on a {@link CredentialsProvider}. + * + * @since 5.2 + */ +public class IdentityPasswordProvider implements FilePasswordProvider { + + private CredentialsProvider provider; + + /** + * Creates a new {@link IdentityPasswordProvider} to get the passphrase for + * an encrypted identity. + * + * @param provider + * to use + */ + public IdentityPasswordProvider(CredentialsProvider provider) { + this.provider = provider; + } + + /** + * Creates a {@link URIish} from a given string. The + * {@link CredentialsProvider} uses uris as resource identifications. + * + * @param resourceKey + * to convert + * @return the uri + */ + protected URIish toUri(String resourceKey) { + try { + return new URIish(resourceKey); + } catch (URISyntaxException e) { + return new URIish().setPath(resourceKey); // Doesn't check!! + } + } + + @Override + public String getPassword(String resourceKey) throws IOException { + if (provider == null) { + return null; + } + URIish file = toUri(resourceKey); + List<CredentialItem> items = new ArrayList<>(2); + items.add(new CredentialItem.InformationalMessage( + format(SshdText.get().keyEncryptedMsg, resourceKey))); + CredentialItem.Password password = new CredentialItem.Password( + SshdText.get().keyEncryptedPrompt); + items.add(password); + try { + provider.get(file, items); + char[] pass = password.getValue(); + if (pass == null) { + throw new CancellationException( + SshdText.get().authenticationCanceled); + } + return new String(pass); + } finally { + password.clear(); + } + } + +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitHostConfigEntry.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitHostConfigEntry.java new file mode 100644 index 0000000000..6bffa2e3ee --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitHostConfigEntry.java @@ -0,0 +1,87 @@ +/* + * 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.transport.sshd; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.sshd.client.config.hosts.HostConfigEntry; +import org.eclipse.jgit.annotations.NonNull; + +/** + * A {@link HostConfigEntry} that provides access to the multi-valued keys as + * lists of strings. The super class treats them as single strings containing + * comma-separated lists. + * + * @since 5.2 + */ +public class JGitHostConfigEntry extends HostConfigEntry { + + private Map<String, List<String>> multiValuedOptions; + + /** + * Sets the multi-valued options. + * + * @param options + * to set, may be {@code null} to set an empty map + */ + public void setMultiValuedOptions(Map<String, List<String>> options) { + multiValuedOptions = options; + } + + /** + * Retrieves all multi-valued options. + * + * @return an unmodifiable map + */ + @NonNull + public Map<String, List<String>> getMultiValuedOptions() { + Map<String, List<String>> options = multiValuedOptions; + if (options == null) { + return Collections.emptyMap(); + } + return Collections.unmodifiableMap(options); + } + +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitKeyCache.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitKeyCache.java new file mode 100644 index 0000000000..52325c6780 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitKeyCache.java @@ -0,0 +1,88 @@ +/* + * 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.transport.sshd; + +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import javax.security.auth.DestroyFailedException; + +/** + * A simple {@link KeyCache}. JGit uses one such cache in its + * {@link SshdSessionFactory} to avoid loading keys multiple times. + * + * @since 5.2 + */ +public class JGitKeyCache implements KeyCache { + + private AtomicReference<Map<Path, KeyPair>> cache = new AtomicReference<>( + new ConcurrentHashMap<>()); + + @Override + public KeyPair get(Path path, + Function<? super Path, ? extends KeyPair> loader) { + return cache.get().computeIfAbsent(path, loader); + } + + @Override + public void close() { + Map<Path, KeyPair> map = cache.getAndSet(null); + if (map == null) { + return; + } + for (KeyPair k : map.values()) { + PrivateKey p = k.getPrivate(); + try { + p.destroy(); + } catch (DestroyFailedException e) { + // Ignore here. We did our best. + } + } + map.clear(); + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitSshConfig.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitSshConfig.java new file mode 100644 index 0000000000..9638374251 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitSshConfig.java @@ -0,0 +1,141 @@ +/* + * 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.transport.sshd; + +import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.flag; +import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.positive; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.sshd.client.config.hosts.HostConfigEntry; +import org.apache.sshd.client.config.hosts.HostConfigEntryResolver; +import org.apache.sshd.common.util.net.SshdSocketAddress; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile; +import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.HostEntry; +import org.eclipse.jgit.transport.SshConstants; + +/** + * A {@link HostConfigEntryResolver} adapted specifically for JGit. + * <p> + * We use our own config file parser and entry resolution since the default + * {@link org.apache.sshd.client.config.hosts.ConfigFileHostEntryResolver + * ConfigFileHostEntryResolver} has a number of problems: + * </p> + * <ul> + * <li>It does case-insensitive pattern matching. Matching in OpenSsh is + * case-sensitive! Compare also bug 531118.</li> + * <li>It only merges values from the global items (before the first "Host" + * line) into the host entries. Otherwise it selects the most specific match. + * OpenSsh processes <em>all</em> entries in the order they appear in the file + * and whenever one matches, it updates values as appropriate.</li> + * <li>We have to ensure that ~ replacement uses the same HOME directory as + * JGit. Compare bug bug 526175.</li> + * </ul> + * Therefore, this re-uses the parsing and caching from + * {@link OpenSshConfigFile}. + * + * @since 5.2 + */ +public class JGitSshConfig implements HostConfigEntryResolver { + + private OpenSshConfigFile configFile; + + /** + * Creates a new {@link OpenSshConfigFile} that will read the config from + * file {@code config} use the given file {@code home} as "home" directory. + * + * @param home + * user's home directory for the purpose of ~ replacement + * @param config + * file to load. + * @param localUserName + * user name of the current user on the local host OS + */ + public JGitSshConfig(@NonNull File home, @NonNull File config, + @NonNull String localUserName) { + configFile = new OpenSshConfigFile(home, config, localUserName); + } + + @Override + public HostConfigEntry resolveEffectiveHost(String host, int port, + String username) throws IOException { + HostEntry entry = configFile.lookup(host, port, username); + JGitHostConfigEntry config = new JGitHostConfigEntry(); + String hostName = entry.getValue(SshConstants.HOST_NAME); + if (hostName == null || hostName.isEmpty()) { + hostName = host; + } + config.setHostName(hostName); + config.setHost(SshdSocketAddress.isIPv6Address(hostName) ? "" : hostName); //$NON-NLS-1$ + String user = username != null && !username.isEmpty() ? username + : entry.getValue(SshConstants.USER); + if (user == null || user.isEmpty()) { + user = configFile.getLocalUserName(); + } + config.setUsername(user); + int p = port >= 0 ? port : positive(entry.getValue(SshConstants.PORT)); + config.setPort(p >= 0 ? p : SshConstants.SSH_DEFAULT_PORT); + config.setIdentities(entry.getValues(SshConstants.IDENTITY_FILE)); + config.setIdentitiesOnly( + flag(entry.getValue(SshConstants.IDENTITIES_ONLY))); + // Apache MINA conflates all keys, even multi-valued ones, in one map + // and puts multiple values separated by commas in one string. See + // the javadoc on HostConfigEntry. + Map<String, String> allOptions = new TreeMap<>( + String.CASE_INSENSITIVE_ORDER); + allOptions.putAll(entry.getOptions()); + // And what if a value contains a comma?? + entry.getMultiValuedOptions().entrySet().stream() + .forEach(e -> allOptions.put(e.getKey(), + String.join(",", e.getValue()))); //$NON-NLS-1$ + config.setProperties(allOptions); + // The following is an extension from JGitHostConfigEntry + config.setMultiValuedOptions(entry.getMultiValuedOptions()); + return config; + } + +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyCache.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyCache.java new file mode 100644 index 0000000000..d1865d9f73 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyCache.java @@ -0,0 +1,74 @@ +/* + * 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.transport.sshd; + +import java.nio.file.Path; +import java.security.KeyPair; +import java.util.function.Function; + +/** + * A cache for {@link KeyPair}s. + * + * @since 5.2 + */ +public interface KeyCache { + + /** + * Obtains a {@link KeyPair} from the cache. Implementations must be + * thread-safe. + * + * @param path + * of the key + * @param loader + * to load the key if it isn't present in the cache yet + * @return the {@link KeyPair}, or {@code null} if not present and could not + * be loaded + */ + KeyPair get(Path path, Function<? super Path, ? extends KeyPair> loader); + + /** + * Removes all {@link KeyPair} from this cache and destroys their private + * keys. This cache instance must not be used anymore thereafter. + */ + public void close(); +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SessionCloseListener.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SessionCloseListener.java new file mode 100644 index 0000000000..1707c7079d --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SessionCloseListener.java @@ -0,0 +1,59 @@ +/* + * 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.transport.sshd; + +/** + * A {@code SessionCloseListener} is invoked when a {@link SshdSession} is + * closed. + */ +@FunctionalInterface +public interface SessionCloseListener { + + /** + * Invoked when a {@link SshdSession} has been closed. + * + * @param session + * that was closed. + */ + void sessionClosed(SshdSession session); +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java new file mode 100644 index 0000000000..7d0e686a28 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java @@ -0,0 +1,485 @@ +/* + * 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.transport.sshd; + +import static java.text.MessageFormat.format; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.channel.ChannelExec; +import org.apache.sshd.client.channel.ClientChannelEvent; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.client.subsystem.sftp.SftpClient; +import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle; +import org.apache.sshd.client.subsystem.sftp.SftpClient.CopyMode; +import org.apache.sshd.client.subsystem.sftp.SftpClientFactory; +import org.apache.sshd.common.session.Session; +import org.apache.sshd.common.session.SessionListener; +import org.apache.sshd.common.subsystem.sftp.SftpException; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.internal.transport.sshd.SshdText; +import org.eclipse.jgit.transport.FtpChannel; +import org.eclipse.jgit.transport.RemoteSession; +import org.eclipse.jgit.transport.URIish; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An implementation of {@link RemoteSession} based on Apache MINA sshd. + * + * @since 5.2 + */ +public class SshdSession implements RemoteSession { + + private static final Logger LOG = LoggerFactory + .getLogger(SshdSession.class); + + private final CopyOnWriteArrayList<SessionCloseListener> listeners = new CopyOnWriteArrayList<>(); + + private final URIish uri; + + private SshClient client; + + private ClientSession session; + + SshdSession(URIish uri, Supplier<SshClient> clientFactory) { + this.uri = uri; + this.client = clientFactory.get(); + } + + void connect(Duration timeout) throws IOException { + if (!client.isStarted()) { + client.start(); + } + try { + String username = uri.getUser(); + String host = uri.getHost(); + int port = uri.getPort(); + long t = timeout.toMillis(); + if (t <= 0) { + session = client.connect(username, host, port).verify() + .getSession(); + } else { + session = client.connect(username, host, port) + .verify(timeout.toMillis()).getSession(); + } + session.addSessionListener(new SessionListener() { + + @Override + public void sessionClosed(Session s) { + notifyCloseListeners(); + } + }); + // Authentication timeout is by default 2 minutes. + session.auth().verify(session.getAuthTimeout()); + } catch (IOException e) { + disconnect(e); + throw e; + } + } + + /** + * Adds a {@link SessionCloseListener} to this session. Has no effect if the + * given {@code listener} is already registered with this session. + * + * @param listener + * to add + */ + public void addCloseListener(@NonNull SessionCloseListener listener) { + listeners.addIfAbsent(listener); + } + + /** + * Removes the given {@code listener}; has no effect if the listener is not + * currently registered with this session. + * + * @param listener + * to remove + */ + public void removeCloseListener(@NonNull SessionCloseListener listener) { + listeners.remove(listener); + } + + private void notifyCloseListeners() { + for (SessionCloseListener l : listeners) { + try { + l.sessionClosed(this); + } catch (RuntimeException e) { + LOG.warn(SshdText.get().closeListenerFailed, e); + } + } + } + + @Override + public Process exec(String commandName, int timeout) throws IOException { + @SuppressWarnings("resource") + ChannelExec exec = session.createExecChannel(commandName); + long timeoutMillis = TimeUnit.SECONDS.toMillis(timeout); + try { + if (timeout <= 0) { + exec.open().verify(); + } else { + long start = System.nanoTime(); + exec.open().verify(timeoutMillis); + timeoutMillis -= TimeUnit.NANOSECONDS + .toMillis(System.nanoTime() - start); + } + } catch (IOException e) { + exec.close(true); + throw e; + } catch (RuntimeException e) { + exec.close(true); + throw e; + } + if (timeout > 0 && timeoutMillis <= 0) { + // We have used up the whole timeout for opening the channel + exec.close(true); + throw new InterruptedIOException( + format(SshdText.get().sshCommandTimeout, commandName, + Integer.valueOf(timeout))); + } + return new SshdExecProcess(exec, commandName, timeoutMillis); + } + + /** + * Obtain an {@link FtpChannel} to perform SFTP operations in this + * {@link SshdSession}. + */ + @Override + @NonNull + public FtpChannel getFtpChannel() { + return new SshdFtpChannel(); + } + + @Override + public void disconnect() { + disconnect(null); + } + + private void disconnect(Throwable reason) { + try { + if (session != null) { + session.close(); + session = null; + } + } catch (IOException e) { + if (reason != null) { + reason.addSuppressed(e); + } else { + LOG.error(SshdText.get().sessionCloseFailed, e); + } + } finally { + client.stop(); + client = null; + } + } + + private static class SshdExecProcess extends Process { + + private final ChannelExec channel; + + private final long timeoutMillis; + + private final String commandName; + + public SshdExecProcess(ChannelExec channel, String commandName, + long timeoutMillis) { + this.channel = channel; + this.timeoutMillis = timeoutMillis > 0 ? timeoutMillis : -1L; + this.commandName = commandName; + } + + @Override + public OutputStream getOutputStream() { + return channel.getInvertedIn(); + } + + @Override + public InputStream getInputStream() { + return channel.getInvertedOut(); + } + + @Override + public InputStream getErrorStream() { + return channel.getInvertedErr(); + } + + @Override + public int waitFor() throws InterruptedException { + if (waitFor(timeoutMillis, TimeUnit.MILLISECONDS)) { + return exitValue(); + } + return -1; + } + + @Override + public boolean waitFor(long timeout, TimeUnit unit) + throws InterruptedException { + long millis = timeout >= 0 ? unit.toMillis(timeout) : -1L; + return channel + .waitFor(EnumSet.of(ClientChannelEvent.CLOSED), millis) + .contains(ClientChannelEvent.CLOSED); + } + + @Override + public int exitValue() { + Integer exitCode = channel.getExitStatus(); + if (exitCode == null) { + throw new IllegalThreadStateException( + format(SshdText.get().sshProcessStillRunning, + commandName)); + } + return exitCode.intValue(); + } + + @Override + public void destroy() { + if (channel.isOpen()) { + channel.close(true); + } + } + } + + /** + * Helper interface like {@link Supplier}, but possibly raising an + * {@link IOException}. + * + * @param <T> + * return type + */ + @FunctionalInterface + private interface FtpOperation<T> { + + T call() throws IOException; + + } + + private class SshdFtpChannel implements FtpChannel { + + private SftpClient ftp; + + /** Current working directory. */ + private String cwd = ""; //$NON-NLS-1$ + + @Override + public void connect(int timeout, TimeUnit unit) throws IOException { + if (timeout <= 0) { + session.getProperties().put( + SftpClient.SFTP_CHANNEL_OPEN_TIMEOUT, + Long.valueOf(Long.MAX_VALUE)); + } else { + session.getProperties().put( + SftpClient.SFTP_CHANNEL_OPEN_TIMEOUT, + Long.valueOf(unit.toMillis(timeout))); + } + ftp = SftpClientFactory.instance().createSftpClient(session); + try { + cd(cwd); + } catch (IOException e) { + ftp.close(); + } + } + + @Override + public void disconnect() { + try { + ftp.close(); + } catch (IOException e) { + LOG.error(SshdText.get().ftpCloseFailed, e); + } + } + + @Override + public boolean isConnected() { + return session.isAuthenticated() && ftp.isOpen(); + } + + private String absolute(String path) { + if (path.isEmpty()) { + return cwd; + } + // Note: there is no path injection vulnerability here. If + // path has too many ".." components, we rely on the server + // catching it and returning an error. + if (path.charAt(0) != '/') { + if (cwd.charAt(cwd.length() - 1) == '/') { + return cwd + path; + } else { + return cwd + '/' + path; + } + } + return path; + } + + private <T> T map(FtpOperation<T> op) throws IOException { + try { + return op.call(); + } catch (IOException e) { + if (e instanceof SftpException) { + throw new FtpChannel.FtpException(e.getLocalizedMessage(), + ((SftpException) e).getStatus(), e); + } + throw e; + } + } + + @Override + public void cd(String path) throws IOException { + cwd = map(() -> ftp.canonicalPath(absolute(path))); + if (cwd.isEmpty()) { + cwd += '/'; + } + } + + @Override + public String pwd() throws IOException { + return cwd; + } + + @Override + public Collection<DirEntry> ls(String path) throws IOException { + return map(() -> { + List<DirEntry> result = new ArrayList<>(); + try (CloseableHandle handle = ftp.openDir(absolute(path))) { + AtomicReference<Boolean> atEnd = new AtomicReference<>( + Boolean.FALSE); + while (!atEnd.get().booleanValue()) { + List<SftpClient.DirEntry> chunk = ftp.readDir(handle, + atEnd); + if (chunk == null) { + break; + } + for (SftpClient.DirEntry remote : chunk) { + result.add(new DirEntry() { + + @Override + public String getFilename() { + return remote.getFilename(); + } + + @Override + public long getModifiedTime() { + return remote.getAttributes() + .getModifyTime().toMillis(); + } + + @Override + public boolean isDirectory() { + return remote.getAttributes().isDirectory(); + } + + }); + } + } + } + return result; + }); + } + + @Override + public void rmdir(String path) throws IOException { + map(() -> { + ftp.rmdir(absolute(path)); + return null; + }); + + } + + @Override + public void mkdir(String path) throws IOException { + map(() -> { + ftp.mkdir(absolute(path)); + return null; + }); + } + + @Override + public InputStream get(String path) throws IOException { + return map(() -> ftp.read(absolute(path))); + } + + @Override + public OutputStream put(String path) throws IOException { + return map(() -> ftp.write(absolute(path))); + } + + @Override + public void rm(String path) throws IOException { + map(() -> { + ftp.remove(absolute(path)); + return null; + }); + } + + @Override + public void rename(String from, String to) throws IOException { + map(() -> { + String src = absolute(from); + String dest = absolute(to); + try { + ftp.rename(src, dest, CopyMode.Atomic, CopyMode.Overwrite); + } catch (UnsupportedOperationException e) { + // Older server cannot do POSIX rename... + if (!src.equals(dest)) { + delete(dest); + ftp.rename(src, dest); + } + } + return null; + }); + } + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java new file mode 100644 index 0000000000..862b5590e9 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java @@ -0,0 +1,436 @@ +/* + * 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.transport.sshd; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import org.apache.sshd.client.ClientBuilder; +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.auth.UserAuth; +import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory; +import org.apache.sshd.client.auth.password.UserAuthPasswordFactory; +import org.apache.sshd.client.config.hosts.HostConfigEntryResolver; +import org.apache.sshd.client.keyverifier.ServerKeyVerifier; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.compression.BuiltinCompressions; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.keyprovider.FileKeyPairProvider; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider; +import org.eclipse.jgit.internal.transport.sshd.JGitPublicKeyAuthFactory; +import org.eclipse.jgit.internal.transport.sshd.JGitSshClient; +import org.eclipse.jgit.internal.transport.sshd.JGitUserInteraction; +import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyVerifier; +import org.eclipse.jgit.internal.transport.sshd.SshdText; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.SshConstants; +import org.eclipse.jgit.transport.SshSessionFactory; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.FS; + +/** + * A {@link SshSessionFactory} that uses Apache MINA sshd. + * + * @since 5.2 + */ +public class SshdSessionFactory extends SshSessionFactory implements Closeable { + + private final AtomicBoolean closing = new AtomicBoolean(); + + private final Set<SshdSession> sessions = new HashSet<>(); + + private final Map<Tuple, HostConfigEntryResolver> defaultHostConfigEntryResolver = new ConcurrentHashMap<>(); + + private final Map<Tuple, ServerKeyVerifier> defaultServerKeyVerifier = new ConcurrentHashMap<>(); + + private final Map<Tuple, FileKeyPairProvider> defaultKeys = new ConcurrentHashMap<>(); + + private final KeyCache keyCache; + + private File sshDirectory; + + private File homeDirectory; + + /** + * Creates a new {@link SshdSessionFactory} without {@link KeyCache}. + */ + public SshdSessionFactory() { + this(null); + } + + /** + * Creates a new {@link SshdSessionFactory} using the given + * {@link KeyCache}. The {@code keyCache} is used for all sessions created + * through this session factory; cached keys are destroyed when the session + * factory is {@link #close() closed}. + * <p> + * Caching ssh keys in memory for an extended period of time is generally + * considered bad practice, but there may be circumstances where using a + * {@link KeyCache} is still the right choice, for instance to avoid that a + * user gets prompted several times for the same password for the same key. + * In general, however, it is preferable <em>not</em> to use a key cache but + * to use a {@link #createFilePasswordProvider(CredentialsProvider) + * FilePasswordProvider} that has access to some secure storage and can save + * and retrieve passwords from there without user interaction. Another + * approach is to use an ssh agent. + * </p> + * <p> + * Note that the underlying ssh library (Apache MINA sshd) may or may not + * keep ssh keys in memory for unspecified periods of time irrespective of + * the use of a {@link KeyCache}. + * </p> + * + * @param keyCache + * {@link KeyCache} to use for caching ssh keys, or {@code null} + * to not use a key cache + */ + public SshdSessionFactory(KeyCache keyCache) { + super(); + this.keyCache = keyCache; + } + + /** A simple general map key. */ + private static final class Tuple { + private Object[] objects; + + public Tuple(Object... objects) { + this.objects = objects; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == Tuple.class) { + Tuple other = (Tuple) obj; + return Arrays.equals(objects, other.objects); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(objects); + } + } + + // We can't really use a single client. Clients need to be stopped + // properly, and we don't really know when to do that. Instead we use + // a dedicated SshClient instance per session. We need a bit of caching to + // avoid re-loading the ssh config and keys repeatedly. + + @Override + public SshdSession getSession(URIish uri, + CredentialsProvider credentialsProvider, FS fs, int tms) + throws TransportException { + SshdSession session = null; + try { + session = new SshdSession(uri, () -> { + File home = getHomeDirectory(); + if (home == null) { + // Always use the detected filesystem for the user home! + // It makes no sense to have different "user home" + // directories depending on what file system a repository + // is. + home = FS.DETECTED.userHome(); + } + File sshDir = getSshDirectory(); + if (sshDir == null) { + sshDir = new File(home, SshConstants.SSH_DIR); + } + HostConfigEntryResolver configFile = getHostConfigEntryResolver( + home, sshDir); + KeyPairProvider defaultKeysProvider = getDefaultKeysProvider( + sshDir); + SshClient client = ClientBuilder.builder() + .factory(JGitSshClient::new) + .filePasswordProvider( + createFilePasswordProvider(credentialsProvider)) + .hostConfigEntryResolver(configFile) + .serverKeyVerifier(getServerKeyVerifier(home, sshDir)) + .compressionFactories( + new ArrayList<>(BuiltinCompressions.VALUES)) + .build(); + client.setUserInteraction( + new JGitUserInteraction(credentialsProvider)); + client.setUserAuthFactories(getUserAuthFactories()); + client.setKeyPairProvider(defaultKeysProvider); + // JGit-specific things: + JGitSshClient jgitClient = (JGitSshClient) client; + jgitClient.setKeyCache(getKeyCache()); + jgitClient.setCredentialsProvider(credentialsProvider); + // Other things? + return client; + }); + session.addCloseListener(s -> unregister(s)); + register(session); + session.connect(Duration.ofMillis(tms)); + return session; + } catch (Exception e) { + unregister(session); + throw new TransportException(uri, e.getMessage(), e); + } + } + + @Override + public void close() { + closing.set(true); + boolean cleanKeys = false; + synchronized (this) { + cleanKeys = sessions.isEmpty(); + } + if (cleanKeys) { + KeyCache cache = getKeyCache(); + if (cache != null) { + cache.close(); + } + } + } + + private void register(SshdSession newSession) throws IOException { + if (newSession == null) { + return; + } + if (closing.get()) { + throw new IOException(SshdText.get().sshClosingDown); + } + synchronized (this) { + sessions.add(newSession); + } + } + + private void unregister(SshdSession oldSession) { + boolean cleanKeys = false; + synchronized (this) { + sessions.remove(oldSession); + cleanKeys = closing.get() && sessions.isEmpty(); + } + if (cleanKeys) { + KeyCache cache = getKeyCache(); + if (cache != null) { + cache.close(); + } + } + } + + /** + * Set a global directory to use as the user's home directory + * + * @param homeDir + * to use + */ + public void setHomeDirectory(@NonNull File homeDir) { + if (homeDir.isAbsolute()) { + homeDirectory = homeDir; + } else { + homeDirectory = homeDir.getAbsoluteFile(); + } + } + + /** + * Retrieves the global user home directory + * + * @return the directory, or {@code null} if not set + */ + public File getHomeDirectory() { + return homeDirectory; + } + + /** + * Set a global directory to use as the .ssh directory + * + * @param sshDir + * to use + */ + public void setSshDirectory(@NonNull File sshDir) { + if (sshDir.isAbsolute()) { + sshDirectory = sshDir; + } else { + sshDirectory = sshDir.getAbsoluteFile(); + } + } + + /** + * Retrieves the global .ssh directory + * + * @return the directory, or {@code null} if not set + */ + public File getSshDirectory() { + return sshDirectory; + } + + /** + * Obtain a {@link HostConfigEntryResolver} to read the ssh config file and + * to determine host entries for connections. + * + * @param homeDir + * home directory to use for ~ replacement + * @param sshDir + * to use for looking for the config file + * @return the resolver + */ + @NonNull + protected HostConfigEntryResolver getHostConfigEntryResolver( + @NonNull File homeDir, @NonNull File sshDir) { + return defaultHostConfigEntryResolver.computeIfAbsent( + new Tuple(homeDir, sshDir), + t -> new JGitSshConfig(homeDir, + new File(sshDir, SshConstants.CONFIG), + getLocalUserName())); + } + + /** + * Obtain a {@link ServerKeyVerifier} to read known_hosts files and to + * verify server host keys. The default implementation returns a + * {@link ServerKeyVerifier} that recognizes the two openssh standard files + * {@code ~/.ssh/known_hosts} and {@code ~/.ssh/known_hosts2} as well as any + * files configured via the {@code UserKnownHostsFile} option in the ssh + * config file. + * + * @param homeDir + * home directory to use for ~ replacement + * @param sshDir + * representing ~/.ssh/ + * @return the resolver + */ + @NonNull + protected ServerKeyVerifier getServerKeyVerifier(@NonNull File homeDir, + @NonNull File sshDir) { + return defaultServerKeyVerifier.computeIfAbsent( + new Tuple(homeDir, sshDir), + t -> new OpenSshServerKeyVerifier(true, + Arrays.asList( + new File(sshDir, SshConstants.KNOWN_HOSTS), + new File(sshDir, + SshConstants.KNOWN_HOSTS + '2')))); + } + + /** + * Determines a {@link KeyPairProvider} to use to load the default keys. + * + * @param sshDir + * to look in for keys + * @return the {@link KeyPairProvider} + */ + @NonNull + protected KeyPairProvider getDefaultKeysProvider(@NonNull File sshDir) { + return defaultKeys.computeIfAbsent(new Tuple(sshDir), + t -> new CachingKeyPairProvider(getDefaultIdentities(sshDir), + getKeyCache())); + } + + /** + * Gets a list of default identities, i.e., private key files that shall + * always be tried for public key authentication. Typically those are + * ~/.ssh/id_dsa, ~/.ssh/id_rsa, and so on. The default implementation + * returns the files defined in {@link SshConstants#DEFAULT_IDENTITIES}. + * + * @param sshDir + * the directory that represents ~/.ssh/ + * @return a possibly empty list of paths containing default identities + * (private keys) + */ + @NonNull + protected List<Path> getDefaultIdentities(@NonNull File sshDir) { + return Arrays + .asList(SshConstants.DEFAULT_IDENTITIES).stream() + .map(s -> new File(sshDir, s).toPath()).filter(Files::exists) + .collect(Collectors.toList()); + } + + /** + * Obtains the {@link KeyCache} to use to cache loaded keys. + * + * @return the {@link KeyCache}, or {@code null} if none. + */ + protected final KeyCache getKeyCache() { + return keyCache; + } + + /** + * Creates a {@link FilePasswordProvider} for a new session. + * + * @param provider + * the {@link CredentialsProvider} to delegate for for user + * interactions + * @return a new {@link FilePasswordProvider} + */ + @NonNull + protected FilePasswordProvider createFilePasswordProvider( + CredentialsProvider provider) { + return new IdentityPasswordProvider(provider); + } + + /** + * Gets the user authentication mechanisms (or rather, factories for them). + * By default this returns public-key, keyboard-interactive, and password, + * in that order. (I.e., we don't do gssapi-with-mic or hostbased (yet)). + * + * @return the non-empty list of factories. + */ + @NonNull + protected List<NamedFactory<UserAuth>> getUserAuthFactories() { + return Collections.unmodifiableList( + Arrays.asList(JGitPublicKeyAuthFactory.INSTANCE, + UserAuthKeyboardInteractiveFactory.INSTANCE, + UserAuthPasswordFactory.INSTANCE)); + } +} |