diff options
Diffstat (limited to 'org.eclipse.jgit.ssh.apache')
30 files changed, 1212 insertions, 118 deletions
diff --git a/org.eclipse.jgit.ssh.apache/.classpath b/org.eclipse.jgit.ssh.apache/.classpath index 110168ffa1..1fde318a04 100644 --- a/org.eclipse.jgit.ssh.apache/.classpath +++ b/org.eclipse.jgit.ssh.apache/.classpath @@ -1,6 +1,10 @@ <?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.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"> + <attributes> + <attribute name="module" value="true"/> + </attributes> + </classpathentry> <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="resources"/> 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 index 15ef2aad5d..d1f54bbe65 100644 --- a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.core.prefs @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.N 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.targetPlatform=11 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.compliance=11 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -24,6 +24,7 @@ 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.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning @@ -80,6 +81,7 @@ org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warn 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.reportPreviewFeatures=warning org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled @@ -112,34 +114,66 @@ 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.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false +org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false +org.eclipse.jdt.core.formatter.align_with_spaces=false +org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=0 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49 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_assertion_message=0 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_bitwise_operator=16 org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16 org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0 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_expressions_in_for_loop_header=0 +org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 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_record_components=16 +org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0 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_shift_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=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_record_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_type_annotations=0 +org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 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_last_class_body_declaration=0 org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=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 @@ -148,6 +182,7 @@ 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_statement_group_in_switch=0 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 @@ -157,12 +192,18 @@ 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_lambda_body=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_record_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.align_tags_descriptions_grouped=false +org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false 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.count_line_length_from_starting_position=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 @@ -172,7 +213,9 @@ 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.indent_tag_description=false org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not 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 @@ -184,10 +227,11 @@ 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.format_line_comment_starting_on_first_column=false 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_record_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 @@ -197,15 +241,17 @@ 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_enum_constant=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_parameter=do not 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_after_type_annotation=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 @@ -219,11 +265,15 @@ 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_additive_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=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_bitwise_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 @@ -249,10 +299,16 @@ org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arg 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_record_components=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=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_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not 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 @@ -269,6 +325,7 @@ org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not ins 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_record_declaration=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 @@ -277,13 +334,20 @@ 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_relational_operator=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_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=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_bitwise_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 @@ -300,6 +364,7 @@ org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not in 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_record_declaration=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 @@ -326,10 +391,15 @@ org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_ar 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_record_components=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_switch_case_expressions=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_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=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 @@ -341,6 +411,8 @@ org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_ 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_record_constructor=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_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 @@ -356,6 +428,7 @@ 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_record_declaration=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 @@ -366,9 +439,12 @@ org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not inser 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_relational_operator=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_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=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 @@ -380,20 +456,63 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_decla 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_annotation_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never 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_enum_constant_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false +org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never 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_after_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0 org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines 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.text_block_indentation=0 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_additive_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true +org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true +org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true +org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true +org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=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 index fef3713825..5cfb8b6ac6 100644 --- a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.ui.prefs +++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.ui.prefs @@ -1,7 +1,7 @@ eclipse.preferences.version=1 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true formatter_profile=_JGit Format -formatter_settings_version=12 +formatter_settings_version=21 org.eclipse.jdt.ui.ignorelowercasenames=true org.eclipse.jdt.ui.importorder=java;javax;org;com; org.eclipse.jdt.ui.ondemandthreshold=99 diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF index e8bb662812..81e01ccadc 100644 --- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF @@ -6,9 +6,9 @@ Bundle-SymbolicName: org.eclipse.jgit.ssh.apache Bundle-Vendor: %Bundle-Vendor Bundle-Localization: plugin Bundle-ActivationPolicy: lazy -Bundle-Version: 5.13.3.202401111512-r -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Export-Package: org.eclipse.jgit.internal.transport.sshd;version="5.13.3";x-internal:=true; +Bundle-Version: 6.0.1.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-11 +Export-Package: org.eclipse.jgit.internal.transport.sshd;version="6.0.1";x-internal:=true; uses:="org.apache.sshd.client, org.apache.sshd.client.auth, org.apache.sshd.client.auth.keyboard, @@ -23,15 +23,17 @@ Export-Package: org.eclipse.jgit.internal.transport.sshd;version="5.13.3";x-inte org.apache.sshd.common.signature, org.apache.sshd.common.util.buffer, org.eclipse.jgit.transport", - org.eclipse.jgit.internal.transport.sshd.auth;version="5.13.3";x-internal:=true, - org.eclipse.jgit.internal.transport.sshd.proxy;version="5.13.3";x-friends:="org.eclipse.jgit.ssh.apache.test", - org.eclipse.jgit.transport.sshd;version="5.13.3"; + org.eclipse.jgit.internal.transport.sshd.agent;version="6.0.1";x-internal:=true, + org.eclipse.jgit.internal.transport.sshd.auth;version="6.0.1";x-internal:=true, + org.eclipse.jgit.internal.transport.sshd.proxy;version="6.0.1";x-friends:="org.eclipse.jgit.ssh.apache.test", + org.eclipse.jgit.transport.sshd;version="6.0.1"; uses:="org.eclipse.jgit.transport, org.apache.sshd.client.config.hosts, org.apache.sshd.common.keyprovider, org.eclipse.jgit.util, org.apache.sshd.client.session, - org.apache.sshd.client.keyverifier" + org.apache.sshd.client.keyverifier", + org.eclipse.jgit.transport.sshd.agent;version="6.0.1" Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)", org.apache.sshd.agent;version="[2.7.0,2.8.0)", org.apache.sshd.client;version="[2.7.0,2.8.0)", @@ -71,6 +73,7 @@ Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)", org.apache.sshd.common.util.buffer;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.closeable;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.io;version="[2.7.0,2.8.0)", + org.apache.sshd.common.util.io.functors;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.io.resource;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.logging;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.net;version="[2.7.0,2.8.0)", @@ -80,12 +83,12 @@ Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)", org.apache.sshd.sftp;version="[2.7.0,2.8.0)", org.apache.sshd.sftp.client;version="[2.7.0,2.8.0)", org.apache.sshd.sftp.common;version="[2.7.0,2.8.0)", - org.eclipse.jgit.annotations;version="[5.13.3,5.14.0)", - org.eclipse.jgit.errors;version="[5.13.3,5.14.0)", - org.eclipse.jgit.fnmatch;version="[5.13.3,5.14.0)", - org.eclipse.jgit.internal.storage.file;version="[5.13.3,5.14.0)", - org.eclipse.jgit.internal.transport.ssh;version="[5.13.3,5.14.0)", - org.eclipse.jgit.nls;version="[5.13.3,5.14.0)", - org.eclipse.jgit.transport;version="[5.13.3,5.14.0)", - org.eclipse.jgit.util;version="[5.13.3,5.14.0)", + org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)", + org.eclipse.jgit.errors;version="[6.0.1,6.1.0)", + org.eclipse.jgit.fnmatch;version="[6.0.1,6.1.0)", + org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)", + org.eclipse.jgit.internal.transport.ssh;version="[6.0.1,6.1.0)", + org.eclipse.jgit.nls;version="[6.0.1,6.1.0)", + org.eclipse.jgit.transport;version="[6.0.1,6.1.0)", + org.eclipse.jgit.util;version="[6.0.1,6.1.0)", org.slf4j;version="[1.7.0,2.0.0)" diff --git a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF index c64f658c16..b770b90344 100644 --- a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF +++ b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF @@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2 Bundle-Name: org.eclipse.jgit.ssh.apache - Sources Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 5.13.3.202401111512-r -Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="5.13.3.202401111512-r";roots="." +Bundle-Version: 6.0.1.qualifier +Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="6.0.1.qualifier";roots="." diff --git a/org.eclipse.jgit.ssh.apache/README.md b/org.eclipse.jgit.ssh.apache/README.md new file mode 100644 index 0000000000..cba87ac9cc --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/README.md @@ -0,0 +1,61 @@ +# JGit SSH support via Apache MINA sshd + +This bundle provides an implementation of git transport over SSH implemented via +[Apache MINA sshd](https://mina.apache.org/sshd-project/). + +## Service registration + +This bundle declares a service for the `java.util.ServiceLoader` for interface +`org.eclipse.jgit.transport.ssh.SshSessionFactory`. The core JGit bundle uses the service +loader to pick up an implementation of that interface. + +Note that JGit simply uses the first `SshSessionFactory` provided by the `ServiceLoader`. + +If the service loader cannot find the session factory, either ensure that the service +declaration is on the Classpath of bundle `org.eclipse.jgit`, or set the factory explicitly +(see below). + +In an OSGi environment, one might need a service loader bridge, or have a little OSGi +fragment for bundle `org.eclipse.jgit` that puts the right service declaration onto the +Classpath of that bundle. (OSGi fragments become part of the Classpath of their host +bundle.) + +## Configuring an SSH implementation for JGit + +The simplest way to set an SSH implementation for JGit is to install it globally via +`SshSessionFactory.setInstance()`. This instance will be used by JGit for all SSH +connections by default. + +It is also possible to set the SSH implementation individually for any git command +that needs a transport (`TransportCommand`) via a `org.eclipse.jgit.api.TransportConfigCallback`. + +To do so, set the wanted `SshSessionFactory` on the SSH transport, like: + +```java +SshSessionFactory customFactory = ...; // Get it from wherever +FetchCommand fetch = git.fetch() + .setTransportConfigCallback(transport -> { + if (transport instanceof SshTransport) { + ((SshTransport) transport).setSshSessionFactory(customFactory); + } + }) + ... + .call(); +``` + +## Using a different SSH implementation + +To use a different SSH implementation: + +* Do not include this bundle in your product. +* Include the bundle of the alternate implementation. + * If the service loader finds the alternate implementation, nothing more is needed. + * Otherwise ensure the service declaration from the other bundle is on the Classpath of bundle `org.eclipse.jgit`, + * or set the `SshSessionFactory` for JGit explicitly (see above). + +## Using an external SSH executable + +JGit has built-in support for not using any Java SSH implementation but an external SSH +executable. To use an external SSH executable, set environment variable **GIT_SSH** to +the path of the executable. JGit will create a sub-process to run the executable and +communicate with this sub-process to perform the git operation. diff --git a/org.eclipse.jgit.ssh.apache/pom.xml b/org.eclipse.jgit.ssh.apache/pom.xml index 074998a203..340cca158d 100644 --- a/org.eclipse.jgit.ssh.apache/pom.xml +++ b/org.eclipse.jgit.ssh.apache/pom.xml @@ -17,7 +17,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>5.13.3.202401111512-r</version> + <version>6.0.1-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.ssh.apache</artifactId> @@ -105,15 +105,15 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-source-plugin</artifactId> - <inherited>true</inherited> - <executions> - <execution> - <id>attach-sources</id> - <phase>process-classes</phase> - <goals> - <goal>jar</goal> - </goals> + <artifactId>maven-source-plugin</artifactId> + <inherited>true</inherited> + <executions> + <execution> + <id>attach-sources</id> + <phase>process-classes</phase> + <goals> + <goal>jar</goal> + </goals> <configuration> <archive> <manifestFile>${source-bundle-manifest}</manifestFile> 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 index defcbdcfc1..2bba736aad 100644 --- 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 @@ -19,6 +19,7 @@ identityFileNoKey=No keys found in identity {0} identityFileMultipleKeys=Multiple key pairs found in identity {0} identityFileNotFound=Skipping identity ''{0}'': file not found identityFileUnsupportedFormat=Unsupported format in identity {0} +invalidSignatureAlgorithm=Signature algorithm ''{0}'' is not valid for a key of type ''{1}'' kexServerKeyInvalid=Server key did not validate keyEncryptedMsg=Key ''{0}'' is encrypted. Enter the passphrase to decrypt it. keyEncryptedPrompt=Passphrase @@ -84,6 +85,10 @@ serverIdTooLong=Server identification is longer than 255 characters (including l serverIdWithNul=Server identification contains a NUL character: {0} sessionCloseFailed=Closing the session failed sessionWithoutUsername=SSH session created without user name; cannot authenticate +sshAgentReplyLengthError=Invalid SSH agent reply message length {0} after command {1} +sshAgentReplyUnexpected=Unexpected reply from ssh-agent: {0} +sshAgentShortReadBuffer=Short read from SSH agent +sshAgentWrongNumberOfKeys=Invalid number of SSH agent keys: {0} 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 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 index f7b37d7816..e2dbb4c466 100644 --- 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 @@ -71,9 +71,9 @@ public class JGitClientSession extends ClientSessionImpl { /** * Default setting for the maximum number of bytes to read in the initial - * protocol version exchange. 64kb is what OpenSSH < 8.0 read; OpenSSH 8.0 - * changed it to 8Mb, but that seems excessive for the purpose stated in RFC - * 4253. The Apache MINA sshd default in + * protocol version exchange. 64kb is what OpenSSH < 8.0 read; OpenSSH + * 8.0 changed it to 8Mb, but that seems excessive for the purpose stated in + * RFC 4253. The Apache MINA sshd default in * {@link org.apache.sshd.core.CoreModuleProperties#MAX_IDENTIFICATION_SIZE} * is 16kb. */ 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 index 08da18f5aa..c082a9a963 100644 --- 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 @@ -12,8 +12,12 @@ package org.eclipse.jgit.internal.transport.sshd; import static java.text.MessageFormat.format; import static org.eclipse.jgit.transport.SshConstants.PUBKEY_ACCEPTED_ALGORITHMS; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; +import org.apache.sshd.client.auth.pubkey.KeyAgentIdentity; +import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity; import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey; import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.session.ClientSession; @@ -38,7 +42,7 @@ public class JGitPublicKeyAuthentication extends UserAuthPublicKey { throw new IllegalStateException("Wrong session type: " //$NON-NLS-1$ + rawSession.getClass().getCanonicalName()); } - JGitClientSession session = ((JGitClientSession) rawSession); + JGitClientSession session = (JGitClientSession) rawSession; HostConfigEntry hostConfig = session.getHostConfigEntry(); // Set signature algorithms for public key authentication String pubkeyAlgos = hostConfig.getProperty(PUBKEY_ACCEPTED_ALGORITHMS); @@ -60,5 +64,48 @@ public class JGitPublicKeyAuthentication extends UserAuthPublicKey { // If we don't set signature factories here, the default ones from the // session will be used. super.init(session, service); + // In sshd 2.7.0, we end up now with a key iterator that uses keys + // provided by an ssh-agent even if IdentitiesOnly is true. So if + // needed, filter out any KeyAgentIdentity. + if (hostConfig.isIdentitiesOnly()) { + Iterator<PublicKeyIdentity> original = keys; + // The original iterator will already have gotten the identities + // from the agent. Unfortunately there's nothing we can do about + // that; it'll have to be fixed upstream. (As will, ultimately, + // respecting isIdentitiesOnly().) At least we can simply not + // use the keys the agent provided. + // + // See https://issues.apache.org/jira/browse/SSHD-1218 + keys = new Iterator<>() { + + private PublicKeyIdentity value; + + @Override + public boolean hasNext() { + if (value != null) { + return true; + } + PublicKeyIdentity next = null; + while (original.hasNext()) { + next = original.next(); + if (!(next instanceof KeyAgentIdentity)) { + value = next; + return true; + } + } + return false; + } + + @Override + public PublicKeyIdentity next() { + if (hasNext()) { + PublicKeyIdentity result = value; + value = null; + return result; + } + throw new NoSuchElementException(); + } + }; + } } } 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 index ae12c2028d..71e8e61585 100644 --- 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 @@ -32,8 +32,10 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.Supplier; import java.util.stream.Collectors; +import org.apache.sshd.agent.SshAgentFactory; import org.apache.sshd.client.SshClient; import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.future.ConnectFuture; @@ -100,6 +102,8 @@ public class JGitSshClient extends SshClient { private ProxyDataFactory proxyDatabase; + private Supplier<SshAgentFactory> agentFactorySupplier = () -> null; + @Override protected SessionFactory createSessionFactory() { // Override the parent's default @@ -223,7 +227,7 @@ public class JGitSshClient extends SshClient { private SshFutureListener<IoConnectFuture> createConnectCompletionListener( ConnectFuture connectFuture, String username, InetSocketAddress address, HostConfigEntry hostConfig) { - return new SshFutureListener<IoConnectFuture>() { + return new SshFutureListener<>() { @Override public void operationComplete(IoConnectFuture future) { @@ -368,6 +372,22 @@ public class JGitSshClient extends SshClient { return credentialsProvider; } + @Override + public SshAgentFactory getAgentFactory() { + return agentFactorySupplier.get(); + } + + @Override + protected void checkConfig() { + // The super class requires channel factories for agent forwarding if a + // factory for an SSH agent is set. We haven't implemented this yet, and + // we don't do SSH agent forwarding for now. Unfortunately, there is no + // way to bypass this check in the super class except making + // getAgentFactory() return null until after the check. + super.checkConfig(); + agentFactorySupplier = super::getAgentFactory; + } + /** * A {@link SessionFactory} to create our own specialized * {@link JGitClientSession}s. @@ -406,7 +426,7 @@ public class JGitSshClient extends SshClient { @Override public Iterable<KeyPair> loadKeys(SessionContext context) { - return () -> new Iterator<KeyPair>() { + return () -> new Iterator<>() { private Iterator<KeyIdentityProvider> factories = providers .iterator(); @@ -439,7 +459,7 @@ public class JGitSshClient extends SshClient { @Override public KeyPair next() { - if (hasElement == null && !hasNext() + if ((hasElement == null && !hasNext()) || !hasElement.booleanValue()) { throw new NoSuchElementException(); } diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java index 85e406f422..d8bf449acf 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java @@ -34,6 +34,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; @@ -138,6 +139,8 @@ public class OpenSshServerKeyDatabase private final List<HostKeyFile> defaultFiles = new ArrayList<>(); + private Random prng; + /** * Creates a new {@link OpenSshServerKeyDatabase}. * @@ -680,7 +683,9 @@ public class OpenSshServerKeyDatabase // or to Apache MINA sshd. NamedFactory<Mac> digester = KnownHostDigest.SHA1; Mac mac = digester.create(); - SecureRandom prng = new SecureRandom(); + if (prng == null) { + prng = new SecureRandom(); + } byte[] salt = new byte[mac.getDefaultBlockSize()]; for (SshdSocketAddress address : patterns) { if (result.length() > 0) { 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 index c0f5719629..00ee62d6dd 100644 --- 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 @@ -1,3 +1,12 @@ +/* + * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ package org.eclipse.jgit.internal.transport.sshd; import org.eclipse.jgit.nls.NLS; @@ -39,6 +48,7 @@ public final class SshdText extends TranslationBundle { /***/ public String identityFileMultipleKeys; /***/ public String identityFileNotFound; /***/ public String identityFileUnsupportedFormat; + /***/ public String invalidSignatureAlgorithm; /***/ public String kexServerKeyInvalid; /***/ public String keyEncryptedMsg; /***/ public String keyEncryptedPrompt; @@ -96,6 +106,10 @@ public final class SshdText extends TranslationBundle { /***/ public String serverIdWithNul; /***/ public String sessionCloseFailed; /***/ public String sessionWithoutUsername; + /***/ public String sshAgentReplyLengthError; + /***/ public String sshAgentReplyUnexpected; + /***/ public String sshAgentShortReadBuffer; + /***/ public String sshAgentWrongNumberOfKeys; /***/ public String sshClosingDown; /***/ public String sshCommandTimeout; /***/ public String sshProcessStillRunning; diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java new file mode 100644 index 0000000000..aba7a76459 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.internal.transport.sshd.agent; + +import java.util.Iterator; +import java.util.ServiceLoader; + +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; + +/** + * Provides a {@link ConnectorFactory} obtained via the {@link ServiceLoader}. + */ +public final class ConnectorFactoryProvider { + + private static volatile ConnectorFactory INSTANCE = loadDefaultFactory(); + + private static ConnectorFactory loadDefaultFactory() { + ServiceLoader<ConnectorFactory> loader = ServiceLoader + .load(ConnectorFactory.class); + Iterator<ConnectorFactory> iter = loader.iterator(); + while (iter.hasNext()) { + ConnectorFactory candidate = iter.next(); + if (candidate.isSupported()) { + return candidate; + } + } + return null; + + } + + /** + * Retrieves the currently set default {@link ConnectorFactory}. + * + * @return the {@link ConnectorFactory}, or {@code null} if none. + */ + public static ConnectorFactory getDefaultFactory() { + return INSTANCE; + } + + /** + * Sets the default {@link ConnectorFactory}. + * + * @param factory + * {@link ConnectorFactory} to use, or {@code null} to use the + * factory discovered via the {@link ServiceLoader}. + */ + public static void setDefaultFactory(ConnectorFactory factory) { + INSTANCE = factory == null ? loadDefaultFactory() : factory; + } + + private ConnectorFactoryProvider() { + // No instantiation + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java new file mode 100644 index 0000000000..1ed2ab9d78 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.internal.transport.sshd.agent; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.apache.sshd.agent.SshAgent; +import org.apache.sshd.agent.SshAgentFactory; +import org.apache.sshd.agent.SshAgentServer; +import org.apache.sshd.common.FactoryManager; +import org.apache.sshd.common.channel.ChannelFactory; +import org.apache.sshd.common.session.ConnectionService; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; + +/** + * A factory for creating {@link SshAgentClient}s. + */ +public class JGitSshAgentFactory implements SshAgentFactory { + + private final @NonNull ConnectorFactory factory; + + private final File homeDir; + + /** + * Creates a new {@link JGitSshAgentFactory}. + * + * @param factory + * {@link JGitSshAgentFactory} to wrap + * @param homeDir + * for obtaining the current local user's home directory + */ + public JGitSshAgentFactory(@NonNull ConnectorFactory factory, + File homeDir) { + this.factory = factory; + this.homeDir = homeDir; + } + + @Override + public List<ChannelFactory> getChannelForwardingFactories( + FactoryManager manager) { + // No agent forwarding supported yet. + return Collections.emptyList(); + } + + @Override + public SshAgent createClient(FactoryManager manager) throws IOException { + // sshd 2.8.0 will pass us the session here. At that point, we can get + // the HostConfigEntry and extract and handle the IdentityAgent setting. + // For now, pass null to let the ConnectorFactory do its default + // behavior (Pageant on Windows, SSH_AUTH_SOCK on Unixes with the + // jgit-builtin factory). + return new SshAgentClient(factory.create(null, homeDir)); + } + + @Override + public SshAgentServer createServer(ConnectionService service) + throws IOException { + // This should be called in a server only. + return null; + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java new file mode 100644 index 0000000000..08483e4c20 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.internal.transport.sshd.agent; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.PublicKey; +import java.text.MessageFormat; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.sshd.agent.SshAgent; +import org.apache.sshd.agent.SshAgentConstants; +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.buffer.BufferException; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.apache.sshd.common.util.buffer.ByteArrayBuffer; +import org.eclipse.jgit.internal.transport.sshd.SshdText; +import org.eclipse.jgit.transport.sshd.agent.Connector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A client for an SSH2 agent. This client supports only querying identities and + * signature requests. + * + * @see <a href="https://tools.ietf.org/html/draft-miller-ssh-agent-04">SSH + * Agent Protocol, RFC draft</a> + */ +public class SshAgentClient implements SshAgent { + + private static final Logger LOG = LoggerFactory + .getLogger(SshAgentClient.class); + + // OpenSSH limit + private static final int MAX_NUMBER_OF_KEYS = 2048; + + private final AtomicBoolean closed = new AtomicBoolean(); + + private final Connector connector; + + /** + * Creates a new {@link SshAgentClient} implementing the SSH2 ssh agent + * protocol, using the given {@link Connector} to connect to the SSH agent + * and to exchange messages. + * + * @param connector + * {@link Connector} to use + */ + public SshAgentClient(Connector connector) { + this.connector = connector; + } + + private boolean open(boolean debugging) throws IOException { + if (closed.get()) { + if (debugging) { + LOG.debug("SSH agent connection already closed"); //$NON-NLS-1$ + } + return false; + } + boolean connected = connector != null && connector.connect(); + if (!connected) { + if (debugging) { + LOG.debug("No SSH agent (SSH_AUTH_SOCK not set)"); //$NON-NLS-1$ + } + } + return connected; + } + + @Override + public void close() throws IOException { + if (!closed.getAndSet(true) && connector != null) { + connector.close(); + } + } + + @Override + public Iterable<? extends Map.Entry<PublicKey, String>> getIdentities() + throws IOException { + boolean debugging = LOG.isDebugEnabled(); + if (!open(debugging)) { + return Collections.emptyList(); + } + if (debugging) { + LOG.debug("Requesting identities from SSH agent"); //$NON-NLS-1$ + } + try { + Buffer reply = rpc( + SshAgentConstants.SSH2_AGENTC_REQUEST_IDENTITIES); + byte cmd = reply.getByte(); + if (cmd != SshAgentConstants.SSH2_AGENT_IDENTITIES_ANSWER) { + throw new SshException(MessageFormat.format( + SshdText.get().sshAgentReplyUnexpected, + SshAgentConstants.getCommandMessageName(cmd))); + } + int numberOfKeys = reply.getInt(); + if (numberOfKeys < 0 || numberOfKeys > MAX_NUMBER_OF_KEYS) { + throw new SshException(MessageFormat.format( + SshdText.get().sshAgentWrongNumberOfKeys, + Integer.toString(numberOfKeys))); + } + if (numberOfKeys == 0) { + if (debugging) { + LOG.debug("SSH agent has no keys"); //$NON-NLS-1$ + } + return Collections.emptyList(); + } + if (debugging) { + LOG.debug("Got {} key(s) from the SSH agent", //$NON-NLS-1$ + Integer.toString(numberOfKeys)); + } + boolean tracing = LOG.isTraceEnabled(); + List<Map.Entry<PublicKey, String>> keys = new ArrayList<>( + numberOfKeys); + for (int i = 0; i < numberOfKeys; i++) { + PublicKey key = reply.getPublicKey(); + String comment = reply.getString(); + if (tracing) { + LOG.trace("Got SSH agent {} key: {} {}", //$NON-NLS-1$ + KeyUtils.getKeyType(key), + KeyUtils.getFingerPrint(key), comment); + } + keys.add(new AbstractMap.SimpleImmutableEntry<>(key, comment)); + } + return keys; + } catch (BufferException e) { + throw new SshException(SshdText.get().sshAgentShortReadBuffer, e); + } + } + + @Override + public Map.Entry<String, byte[]> sign(SessionContext session, PublicKey key, + String algorithm, byte[] data) throws IOException { + boolean debugging = LOG.isDebugEnabled(); + String keyType = KeyUtils.getKeyType(key); + String signatureAlgorithm; + if (algorithm != null) { + if (!KeyUtils.getCanonicalKeyType(algorithm).equals(keyType)) { + throw new IllegalArgumentException(MessageFormat.format( + SshdText.get().invalidSignatureAlgorithm, algorithm, + keyType)); + } + signatureAlgorithm = algorithm; + } else { + signatureAlgorithm = keyType; + } + if (!open(debugging)) { + return null; + } + int flags = 0; + switch (signatureAlgorithm) { + case KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS: + case KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS: + flags = 4; + break; + case KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS: + case KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS: + flags = 2; + break; + default: + break; + } + ByteArrayBuffer msg = new ByteArrayBuffer(); + msg.putInt(0); + msg.putByte(SshAgentConstants.SSH2_AGENTC_SIGN_REQUEST); + msg.putPublicKey(key); + msg.putBytes(data); + msg.putInt(flags); + if (debugging) { + LOG.debug( + "sign({}): signing request to SSH agent for {} key, {} signature; flags={}", //$NON-NLS-1$ + session, keyType, signatureAlgorithm, + Integer.toString(flags)); + } + Buffer reply = rpc(SshAgentConstants.SSH2_AGENTC_SIGN_REQUEST, + msg.getCompactData()); + byte cmd = reply.getByte(); + if (cmd != SshAgentConstants.SSH2_AGENT_SIGN_RESPONSE) { + throw new SshException( + MessageFormat.format(SshdText.get().sshAgentReplyUnexpected, + SshAgentConstants.getCommandMessageName(cmd))); + } + try { + Buffer signatureReply = new ByteArrayBuffer(reply.getBytes()); + String actualAlgorithm = signatureReply.getString(); + byte[] signature = signatureReply.getBytes(); + if (LOG.isTraceEnabled()) { + LOG.trace( + "sign({}): signature reply from SSH agent for {} key: {} signature={}", //$NON-NLS-1$ + session, keyType, actualAlgorithm, + BufferUtils.toHex(':', signature)); + + } else if (LOG.isDebugEnabled()) { + LOG.debug( + "sign({}): signature reply from SSH agent for {} key, {} signature", //$NON-NLS-1$ + session, keyType, actualAlgorithm); + } + return new AbstractMap.SimpleImmutableEntry<>(actualAlgorithm, + signature); + } catch (BufferException e) { + throw new SshException(SshdText.get().sshAgentShortReadBuffer, e); + } + } + + private Buffer rpc(byte command, byte[] message) throws IOException { + return new ByteArrayBuffer(connector.rpc(command, message)); + } + + private Buffer rpc(byte command) throws IOException { + return new ByteArrayBuffer(connector.rpc(command)); + } + + @Override + public boolean isOpen() { + return !closed.get(); + } + + @Override + public void addIdentity(KeyPair key, String comment) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void removeIdentity(PublicKey key) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAllIdentities() throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java index eae0d75355..e5f884e299 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java @@ -95,8 +95,8 @@ public abstract class BasicAuthentication<ParameterType, TokenType> @Override public final void start() throws Exception { - if (user != null && !user.isEmpty() - || password != null && password.length > 0) { + if ((user != null && !user.isEmpty()) + || (password != null && password.length > 0)) { return; } askCredentials(); diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java index 54e2cbcebf..a8e33af35e 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java @@ -31,7 +31,7 @@ public abstract class AbstractClientProxyConnector .toMillis(30L); /** Guards {@link #done} and {@link #bufferedCommands}. */ - private Object lock = new Object(); + private final Object lock = new Object(); private boolean done; @@ -105,7 +105,7 @@ public abstract class AbstractClientProxyConnector /** * Obtains the timeout for the whole rest of the proxy connection protocol. * - * @return the timeout in milliseconds, always > 0L + * @return the timeout in milliseconds, always > 0L */ protected long getTimeout() { long last = lastProxyOperationTime; diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java index e5d1e80f74..b7deb29dc4 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java @@ -113,8 +113,8 @@ public class HttpClientConnector extends AbstractClientProxyConnector { IoSession session = sshSession.getIoSession(); session.addCloseFutureListener(f -> close()); StringBuilder msg = connect(); - if (proxyUser != null && !proxyUser.isEmpty() - || proxyPassword != null && proxyPassword.length > 0) { + if ((proxyUser != null && !proxyUser.isEmpty()) + || (proxyPassword != null && proxyPassword.length > 0)) { authenticator = basic; basic.setParams(null); basic.start(); @@ -232,7 +232,8 @@ public class HttpClientConnector extends AbstractClientProxyConnector { } catch (HttpParser.ParseException e) { throw new IOException( format(SshdText.get().proxyHttpUnexpectedReply, - proxyAddress, reply.get(0))); + proxyAddress, reply.get(0)), + e); } } diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java index 0500a63428..ece22af1ce 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java @@ -31,6 +31,23 @@ public final class HttpParser { private static final long serialVersionUID = -1634090143702048640L; + /** + * Creates a new {@link ParseException} without cause. + */ + public ParseException() { + super(); + } + + /** + * Creates a new {@link ParseException} with the given {@code cause}. + * + * @param cause + * {@link Throwable} that caused this exception, or + * {@code null} if none + */ + public ParseException(Throwable cause) { + super(cause); + } } private HttpParser() { @@ -64,7 +81,7 @@ public final class HttpParser { resultCode = Integer.parseUnsignedInt( line.substring(firstBlank + 1, secondBlank)); } catch (NumberFormatException e) { - throw new ParseException(); + throw new ParseException(e); } // Again, accept even if the reason is missing String reason = ""; //$NON-NLS-1$ diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java index 8844efa6b7..bb227bbac8 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java @@ -94,7 +94,7 @@ public class Socks5ClientConnector extends AbstractClientProxyConnector { // JSON(9), NONE_ACCEPTABLE(0xFF); - private byte value; + private final byte value; SocksAuthenticationMethod(int value) { this.value = (byte) value; diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProvider.java index acc11cefb1..ed9fe37d5c 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProvider.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProvider.java @@ -31,7 +31,7 @@ public interface KeyPasswordProvider { * identifying the key resource that is being attempted to be * loaded * @param attempt - * the number of previous attempts to get a passphrase; >= 0 + * the number of previous attempts to get a passphrase; >= 0 * @return the passphrase * @throws IOException * if no password can be obtained @@ -44,7 +44,7 @@ public interface KeyPasswordProvider { * * @param maxNumberOfAttempts * number of times to ask for a passphrase; - * {@link IllegalArgumentException} may be thrown if <= 0 + * {@link IllegalArgumentException} may be thrown if <= 0 */ void setAttempts(int maxNumberOfAttempts); @@ -53,7 +53,7 @@ public interface KeyPasswordProvider { * attempted for one identity resource through this provider. The default * return 1. * - * @return the number of times to ask for a passphrase; should be >= 1. + * @return the number of times to ask for a passphrase; should be >= 1. */ default int getAttempts() { return 1; 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 index 33b234b1f1..c270b44956 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others + * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -28,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -44,9 +43,9 @@ import org.apache.sshd.common.SshException; import org.apache.sshd.common.future.CloseFuture; import org.apache.sshd.common.future.SshFutureListener; import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.common.util.io.functors.IOFunction; import org.apache.sshd.common.util.net.SshdSocketAddress; import org.apache.sshd.sftp.client.SftpClient; -import org.apache.sshd.sftp.client.SftpClient.CloseableHandle; import org.apache.sshd.sftp.client.SftpClient.CopyMode; import org.apache.sshd.sftp.client.SftpClientFactory; import org.apache.sshd.sftp.common.SftpException; @@ -220,7 +219,8 @@ public class SshdSession implements RemoteSession2 { HostConfigEntry hostConfig, String host) throws IOException { if (currentHops.isEmpty()) { String jumpHosts = hostConfig.getProperty(SshConstants.PROXY_JUMP); - if (!StringUtils.isEmptyOrNull(jumpHosts)) { + if (!StringUtils.isEmptyOrNull(jumpHosts) + && !SshConstants.NONE.equals(jumpHosts)) { try { return parseProxyJump(jumpHosts); } catch (URISyntaxException e) { @@ -416,20 +416,6 @@ public class SshdSession implements RemoteSession2 { } } - /** - * 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; @@ -485,9 +471,9 @@ public class SshdSession implements RemoteSession2 { return path; } - private <T> T map(FtpOperation<T> op) throws IOException { + private <T> T map(IOFunction<Void, T> op) throws IOException { try { - return op.call(); + return op.apply(null); } catch (IOException e) { if (e instanceof SftpException) { throw new FtpChannel.FtpException(e.getLocalizedMessage(), @@ -499,7 +485,7 @@ public class SshdSession implements RemoteSession2 { @Override public void cd(String path) throws IOException { - cwd = map(() -> ftp.canonicalPath(absolute(path))); + cwd = map(x -> ftp.canonicalPath(absolute(path))); if (cwd.isEmpty()) { cwd += '/'; } @@ -512,39 +498,28 @@ public class SshdSession implements RemoteSession2 { @Override public Collection<DirEntry> ls(String path) throws IOException { - return map(() -> { + return map(x -> { 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 : ftp.readDir(absolute(path))) { + result.add(new DirEntry() { + + @Override + public String getFilename() { + return remote.getFilename(); } - 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(); - } - - }); + + @Override + public long getModifiedTime() { + return remote.getAttributes().getModifyTime() + .toMillis(); } - } + + @Override + public boolean isDirectory() { + return remote.getAttributes().isDirectory(); + } + + }); } return result; }); @@ -552,7 +527,7 @@ public class SshdSession implements RemoteSession2 { @Override public void rmdir(String path) throws IOException { - map(() -> { + map(x -> { ftp.rmdir(absolute(path)); return null; }); @@ -561,7 +536,7 @@ public class SshdSession implements RemoteSession2 { @Override public void mkdir(String path) throws IOException { - map(() -> { + map(x -> { ftp.mkdir(absolute(path)); return null; }); @@ -569,17 +544,17 @@ public class SshdSession implements RemoteSession2 { @Override public InputStream get(String path) throws IOException { - return map(() -> ftp.read(absolute(path))); + return map(x -> ftp.read(absolute(path))); } @Override public OutputStream put(String path) throws IOException { - return map(() -> ftp.write(absolute(path))); + return map(x -> ftp.write(absolute(path))); } @Override public void rm(String path) throws IOException { - map(() -> { + map(x -> { ftp.remove(absolute(path)); return null; }); @@ -587,7 +562,7 @@ public class SshdSession implements RemoteSession2 { @Override public void rename(String from, String to) throws IOException { - map(() -> { + map(x -> { String src = absolute(from); String dest = absolute(to); try { 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 index cad959c904..58cf8e1ddd 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others + * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -56,11 +56,14 @@ import org.eclipse.jgit.internal.transport.sshd.JGitUserInteraction; import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase; import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper; import org.eclipse.jgit.internal.transport.sshd.SshdText; +import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.SshConfigStore; import org.eclipse.jgit.transport.SshConstants; import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.sshd.agent.Connector; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; import org.eclipse.jgit.util.FS; /** @@ -102,9 +105,9 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable { /** * Creates a new {@link SshdSessionFactory} using the given {@link KeyCache} - * and {@link ProxyDataFactory}. 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}. + * and {@link ProxyDataFactory}. 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 @@ -114,13 +117,21 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable { * to use a {@link #createKeyPasswordProvider(CredentialsProvider) * KeyPasswordProvider} 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. + * 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> + * <p> + * By default, the factory uses the {@link java.util.ServiceLoader} to find + * a {@link ConnectorFactory} for creating a {@link Connector} to connect to + * a running SSH agent. If it finds one, the SSH agent is used in publickey + * authentication. If there is none, no SSH agent will ever be contacted. + * Note that one can define {@code IdentitiesOnly yes} for a host entry in + * the {@code ~/.ssh/config} file to bypass the SSH agent in any case. + * </p> * * @param keyCache * {@link KeyCache} to use for caching ssh keys, or {@code null} @@ -216,6 +227,11 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable { new JGitUserInteraction(credentialsProvider)); client.setUserAuthFactories(getUserAuthFactories()); client.setKeyIdentityProvider(defaultKeysProvider); + ConnectorFactory connectors = getConnectorFactory(); + if (connectors != null) { + client.setAgentFactory( + new JGitSshAgentFactory(connectors, home)); + } // JGit-specific things: JGitSshClient jgitClient = (JGitSshClient) client; jgitClient.setKeyCache(getKeyCache()); @@ -437,6 +453,20 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable { } /** + * Gets a {@link ConnectorFactory}. If this returns {@code null}, SSH agents + * are not supported. + * <p> + * The default implementation uses {@link ConnectorFactory#getDefault()} + * </p> + * + * @return the factory, or {@code null} if no SSH agent support is desired + * @since 6.0 + */ + protected ConnectorFactory getConnectorFactory() { + return ConnectorFactory.getDefault(); + } + + /** * Gets the list of default user known hosts files. The default returns * ~/.ssh/known_hosts and ~/.ssh/known_hosts2. The ssh config * {@code UserKnownHostsFile} overrides this default. diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactoryBuilder.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactoryBuilder.java index 2147c2bd58..7ed9b5ea3b 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactoryBuilder.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactoryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others + * Copyright (C) 2020, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -20,6 +20,7 @@ import java.util.function.Function; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.SshConfigStore; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; import org.eclipse.jgit.util.StringUtils; /** @@ -114,7 +115,7 @@ public final class SshdSessionFactoryBuilder { } /** - * A factory interface for creating a @link SshConfigStore}. + * A factory interface for creating a {@link SshConfigStore}. */ @FunctionalInterface public interface ConfigStoreFactory { @@ -233,6 +234,41 @@ public final class SshdSessionFactoryBuilder { } /** + * Sets an explicit {@link ConnectorFactory}. If {@code null}, there will be + * no support for SSH agents. + * <p> + * If not set, the created {@link SshdSessionFactory} will use the + * {@link java.util.ServiceLoader} to find an {@link ConnectorFactory}. + * </p> + * + * @param factory + * {@link ConnectorFactory} to use + * @return this {@link SshdSessionFactoryBuilder} + * @since 6.0 + */ + public SshdSessionFactoryBuilder setConnectorFactory( + ConnectorFactory factory) { + this.state.connectorFactory = factory; + this.state.connectorFactorySet = true; + return this; + } + + /** + * Removes a previously set {@link ConnectorFactory}. The created + * {@link SshdSessionFactory} will use the {@link java.util.ServiceLoader} + * to find an {@link ConnectorFactory}. This is also the default if + * {@link #setConnectorFactory(ConnectorFactory)} isn't called at all. + * + * @return this {@link SshdSessionFactoryBuilder} + * @since 6.0 + */ + public SshdSessionFactoryBuilder withDefaultConnectorFactory() { + this.state.connectorFactory = null; + this.state.connectorFactorySet = false; + return this; + } + + /** * Builds a {@link SshdSessionFactory} as configured, using the given * {@link KeyCache} for caching keys. * <p> @@ -277,6 +313,10 @@ public final class SshdSessionFactoryBuilder { BiFunction<File, File, ServerKeyDatabase> serverKeyDatabaseCreator; + ConnectorFactory connectorFactory; + + boolean connectorFactorySet; + State copy() { State c = new State(); c.proxyDataFactory = proxyDataFactory; @@ -290,6 +330,8 @@ public final class SshdSessionFactoryBuilder { c.defaultKeyFileFinder = defaultKeyFileFinder; c.defaultKeysProvider = defaultKeysProvider; c.serverKeyDatabaseCreator = serverKeyDatabaseCreator; + c.connectorFactory = connectorFactory; + c.connectorFactorySet = connectorFactorySet; return c; } @@ -388,6 +430,15 @@ public final class SshdSessionFactoryBuilder { return super.createSshConfigStore(homeDir, configFile, localUserName); } + + @Override + protected ConnectorFactory getConnectorFactory() { + if (connectorFactorySet) { + return connectorFactory; + } + // Use default via ServiceLoader + return super.getConnectorFactory(); + } } } } diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/AbstractConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/AbstractConnector.java new file mode 100644 index 0000000000..71ddc3b003 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/AbstractConnector.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.transport.sshd.agent; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Objects; + +import org.apache.sshd.agent.SshAgentConstants; +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.eclipse.jgit.internal.transport.sshd.SshdText; + +/** + * Provides some utility methods for implementing {@link Connector}s. + * + * @since 6.0 + */ +public abstract class AbstractConnector implements Connector { + + // A somewhat sane lower bound for the maximum reply length + private static final int MIN_REPLY_LENGTH = 8 * 1024; + + /** + * Default maximum reply length. 256kB is the OpenSSH limit. + */ + protected static final int DEFAULT_MAX_REPLY_LENGTH = 256 * 1024; + + private final int maxReplyLength; + + /** + * Creates a new instance using the {@link #DEFAULT_MAX_REPLY_LENGTH}. + */ + protected AbstractConnector() { + this(DEFAULT_MAX_REPLY_LENGTH); + } + + /** + * Creates a new instance. + * + * @param maxReplyLength + * maximum number of payload bytes we're ready to accept + */ + protected AbstractConnector(int maxReplyLength) { + if (maxReplyLength < MIN_REPLY_LENGTH) { + throw new IllegalArgumentException( + "Maximum payload length too small"); //$NON-NLS-1$ + } + this.maxReplyLength = maxReplyLength; + } + + /** + * Retrieves the maximum message length this {@link AbstractConnector} is + * configured for. + * + * @return the maximum message length + */ + protected int getMaximumMessageLength() { + return this.maxReplyLength; + } + + /** + * Prepares a message for sending by inserting the command and message + * length. + * + * @param command + * SSH agent command the request is for + * @param message + * about to be sent, including the 5 spare bytes at the front + * @throws IllegalArgumentException + * if {@code message} has less than 5 bytes + */ + protected void prepareMessage(byte command, byte[] message) + throws IllegalArgumentException { + Objects.requireNonNull(message); + if (message.length < 5) { + // No translation; internal error + throw new IllegalArgumentException("Message buffer for " //$NON-NLS-1$ + + SshAgentConstants.getCommandMessageName(command) + + " must have at least 5 bytes; have only " //$NON-NLS-1$ + + message.length); + } + BufferUtils.putUInt(message.length - 4, message); + message[4] = command; + } + + /** + * Checks the received length of a reply. + * + * @param command + * SSH agent command the reply is for + * @param length + * length as received: number of payload bytes + * @return the length as an {@code int} + * @throws IOException + * if the length is invalid + */ + protected int toLength(byte command, byte[] length) + throws IOException { + long l = BufferUtils.getUInt(length); + if (l <= 0 || l > maxReplyLength - 4) { + throw new SshException(MessageFormat.format( + SshdText.get().sshAgentReplyLengthError, + Long.toString(l), + SshAgentConstants.getCommandMessageName(command))); + } + return (int) l; + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java new file mode 100644 index 0000000000..d8dfbfc94d --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.transport.sshd.agent; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Simple interface for connecting to something and making RPC-style + * request-reply calls. + * + * @see ConnectorFactory + * @since 6.0 + */ +public interface Connector extends Closeable { + + /** + * Connects to an SSH agent if there is one running. If called when already + * connected just returns {@code true}. + * + * @return {@code true} if an SSH agent is available and connected, + * {@false} if no SSH agent is available + * @throws IOException + * if connecting to the SSH agent failed + */ + boolean connect() throws IOException; + + /** + * Performs a remote call to the SSH agent and returns the result. + * + * @param command + * to send + * @param message + * to send; must have at least 5 bytes, and must have 5 unused + * bytes at the front. + * @return the result received + * @throws IOException + * if an error occurs + */ + byte[] rpc(byte command, byte[] message) throws IOException; + + /** + * Performs a remote call sending only a command without any parameters to + * the SSH agent and returns the result. + * + * @param command + * to send + * @return the result received + * @throws IOException + * if an error occurs + */ + default byte[] rpc(byte command) throws IOException { + return rpc(command, new byte[5]); + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java new file mode 100644 index 0000000000..da98ea7fe0 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.transport.sshd.agent; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.internal.transport.sshd.agent.ConnectorFactoryProvider; + +/** + * A factory for creating {@link Connector}s. This is a service provider + * interface; implementations are discovered via the + * {@link java.util.ServiceLoader}, or can be set explicitly on a + * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}. + * + * @since 6.0 + */ +public interface ConnectorFactory { + + /** + * Retrieves the currently set default {@link ConnectorFactory}. This is the + * factory that is used unless overridden by the + * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}. + * + * @return the current default factory; may be {@code null} if none is set + * and the {@link java.util.ServiceLoader} cannot find any suitable + * implementation + */ + static ConnectorFactory getDefault() { + return ConnectorFactoryProvider.getDefaultFactory(); + } + + /** + * Sets a default {@link ConnectorFactory}. This is the factory that is used + * unless overridden by the + * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}. + * <p> + * If no default factory is set programmatically, an implementation is + * discovered via the {@link java.util.ServiceLoader}. + * </p> + * + * @param factory + * {@link ConnectorFactory} to set, or {@code null} to revert to + * the default behavior of using the + * {@link java.util.ServiceLoader}. + */ + static void setDefault(ConnectorFactory factory) { + ConnectorFactoryProvider.setDefaultFactory(factory); + } + + /** + * Creates a new {@link Connector}. + * + * @param identityAgent + * identifies the wanted agent connection; if {@code null}, the + * factory is free to provide a {@link Connector} to a default + * agent. The value will typically come from the + * {@code IdentityAgent} setting in {@code ~/.ssh/config}. + * @param homeDir + * the current local user's home directory as configured in the + * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory} + * @return a new {@link Connector} + * @throws IOException + * if no connector can be created + */ + @NonNull + Connector create(String identityAgent, File homeDir) + throws IOException; + + /** + * Tells whether this {@link ConnectorFactory} is applicable on the + * currently running platform. + * + * @return {@code true} if the factory can be used, {@code false} otherwise + */ + boolean isSupported(); + + /** + * Retrieves a name for this factory. + * + * @return the name + */ + String getName(); + + /** + * {@link ConnectorDescriptor}s describe available {@link Connector}s a + * {@link ConnectorFactory} may provide. + * <p> + * A {@link ConnectorFactory} may support connecting to different SSH + * agents. Agents are identified by name; a user can choose a specific agent + * for instance via the {@code IdentityAgent} setting in + * {@code ~/.ssh/config}. + * </p> + * <p> + * OpenSSH knows two built-in names: "none" for not using any agent, and + * "SSH_AUTH_SOCK" for using an agent that communicates over a Unix domain + * socket given by the value of environment variable {@code SSH_AUTH_SOCK}. + * Other agents can be specified in OpenSSH by specifying the socket file + * directly. (The "standard" OpenBSD OpenSSH knows only this communication + * mechanism.) "SSH_AUTH_SOCK" is also the default in OpenBSD OpenSSH if + * nothing is configured. + * </p> + * <p> + * A particular {@link ConnectorFactory} may support more communication + * mechanisms or different agents. For instance, a factory on Windows might + * support Pageant, Win32-OpenSSH, or even git bash ssh-agent, and might + * accept internal names like "pageant", "openssh", "SSH_AUTH_SOCK" in + * {@link ConnectorFactory#create(String, File)} to choose among them. + * </p> + * The {@link ConnectorDescriptor} interface and the + * {@link ConnectorFactory#getSupportedConnectors()} and + * {@link ConnectorFactory#getDefaultConnector()} methods provide a way for + * code using a {@link ConnectorFactory} to learn what the factory supports + * and thus implement some way by which a user can influence the default + * behavior if {@code IdentityAgent} is not set or + * {@link ConnectorFactory#create(String, File)} is called with + * {@code identityAgent == null}. + */ + interface ConnectorDescriptor { + + /** + * Retrieves the internal name of a supported {@link Connector}. The + * internal name is the one a user can specify for instance in the + * {@code IdentityAgent} setting in {@code ~/.ssh/config} to select the + * connector. + * + * @return the internal name; not empty + */ + @NonNull + String getIdentityAgent(); + + /** + * Retrieves a display name for a {@link Connector}, suitable for + * showing in a UI. + * + * @return the display name; properly localized and not empty + */ + @NonNull + String getDisplayName(); + } + + /** + * Tells which kinds of SSH agents this {@link ConnectorFactory} supports. + * <p> + * An implementation of this method should document the possible values it + * returns. + * </p> + * + * @return an immutable collection of {@link ConnectorDescriptor}s, + * including {@link #getDefaultConnector()} and not including a + * descriptor for internal name "none" + */ + @NonNull + Collection<ConnectorDescriptor> getSupportedConnectors(); + + /** + * Tells what kind of {@link Connector} this {@link ConnectorFactory} + * creates if {@link ConnectorFactory#create(String, File)} is called with + * {@code identityAgent == null}. + * + * @return a {@link ConnectorDescriptor} for the default connector + */ + ConnectorDescriptor getDefaultConnector(); +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/package-info.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/package-info.java new file mode 100644 index 0000000000..71ca43f3d5 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/package-info.java @@ -0,0 +1,6 @@ +/** + * Service provider interfaces for connecting to an SSH agent. Implementations + * are discovered via the {@link java.util.ServiceLoader}, or can be set + * explicitly on a {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}. + */ +package org.eclipse.jgit.transport.sshd.agent; diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/package-info.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/package-info.java new file mode 100644 index 0000000000..926234a3bd --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides a JGit {@link org.eclipse.jgit.transport.SshSessionFactory} + * implemented via <a href="https://mina.apache.org/sshd-project/">Apache MINA + * sshd</a>. + */ +package org.eclipse.jgit.transport.sshd; |