| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -2,7 +2,7 @@
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				"""
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   The MIT License (MIT)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -23,6 +23,8 @@
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   THE SOFTWARE.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				"""
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import concurrent.futures
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import copy
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import getopt
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import json
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import os
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -42,7 +44,6 @@ from ssh_audit import exitcodes
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from ssh_audit.fingerprint import Fingerprint
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from ssh_audit.gextest import GEXTest
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from ssh_audit.hostkeytest import HostKeyTest
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from ssh_audit.output import Output
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from ssh_audit.outputbuffer import OutputBuffer
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from ssh_audit.policy import Policy
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				from ssh_audit.product import Product
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -66,7 +67,7 @@ except ImportError:  # pragma: nocover
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def usage(err: Optional[str] = None) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    retval = exitcodes.GOOD
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout = Output()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout = OutputBuffer()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    p = os.path.basename(sys.argv[0])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.head('# {} {}, https://github.com/jtesta/ssh-audit\n'.format(p, VERSION))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if err is not None and len(err) > 0:
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -89,25 +90,27 @@ def usage(err: Optional[str] = None) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.info('   -p,  --port=<port>      port to connect')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.info('   -P,  --policy=<policy.txt>  run a policy test using the specified policy')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.info('   -t,  --timeout=<secs>   timeout (in seconds) for connection and reading\n                               (default: 5)')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.info('   -T,  --targets=<hosts.txt>  a file containing a list of target hosts (one\n                                   per line, format HOST[:PORT])')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.info('   -T,  --targets=<hosts.txt>  a file containing a list of target hosts (one\n                                   per line, format HOST[:PORT]).  Use --threads\n                                   to control concurrent scans.')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.info('        --threads=<threads>    number of threads to use when scanning multiple\n                                   targets (-T/--targets) (default: 32)')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.info('   -v,  --verbose          verbose output')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    uout.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    sys.exit(retval)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_algorithms(title: str, alg_db: Dict[str, Dict[str, List[List[Optional[str]]]]], alg_type: str, algorithms: List[str], unknown_algs: List[str], is_json_output: bool, program_retval: int, maxlen: int = 0, alg_sizes: Optional[Dict[str, Tuple[int, int]]] = None) -> int:  # pylint: disable=too-many-arguments
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with OutputBuffer() as obuf:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_algorithms(out: OutputBuffer, title: str, alg_db: Dict[str, Dict[str, List[List[Optional[str]]]]], alg_type: str, algorithms: List[str], unknown_algs: List[str], is_json_output: bool, program_retval: int, maxlen: int = 0, alg_sizes: Optional[Dict[str, Tuple[int, int]]] = None) -> int:  # pylint: disable=too-many-arguments
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with out:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for algorithm in algorithms:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            program_retval = output_algorithm(alg_db, alg_type, algorithm, unknown_algs, program_retval, maxlen, alg_sizes)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(obuf) > 0 and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            program_retval = output_algorithm(out, alg_db, alg_type, algorithm, unknown_algs, program_retval, maxlen, alg_sizes)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if not out.is_section_empty() and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.head('# ' + title)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        obuf.flush()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.flush_section()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return program_retval
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_algorithm(alg_db: Dict[str, Dict[str, List[List[Optional[str]]]]], alg_type: str, alg_name: str, unknown_algs: List[str], program_retval: int, alg_max_len: int = 0, alg_sizes: Optional[Dict[str, Tuple[int, int]]] = None) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_algorithm(out: OutputBuffer, alg_db: Dict[str, Dict[str, List[List[Optional[str]]]]], alg_type: str, alg_name: str, unknown_algs: List[str], program_retval: int, alg_max_len: int = 0, alg_sizes: Optional[Dict[str, Tuple[int, int]]] = None) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    prefix = '(' + alg_type + ') '
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if alg_max_len == 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        alg_max_len = len(alg_name)
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -175,7 +178,7 @@ def output_algorithm(alg_db: Dict[str, Dict[str, List[List[Optional[str]]]]], al
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return program_retval
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_compatibility(algs: Algorithms, client_audit: bool, for_server: bool = True) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_compatibility(out: OutputBuffer, algs: Algorithms, client_audit: bool, for_server: bool = True) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # Don't output any compatibility info if we're doing a client audit.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if client_audit:
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -205,7 +208,7 @@ def output_compatibility(algs: Algorithms, client_audit: bool, for_server: bool
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.good('(gen) compatibility: ' + ', '.join(comp_text))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_security_sub(sub: str, software: Optional[Software], client_audit: bool, padlen: int) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_security_sub(out: OutputBuffer, sub: str, software: Optional[Software], client_audit: bool, padlen: int) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    secdb = VersionVulnerabilityDB.CVE if sub == 'cve' else VersionVulnerabilityDB.TXT
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if software is None or software.product not in secdb:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -241,20 +244,20 @@ def output_security_sub(sub: str, software: Optional[Software], client_audit: bo
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.fail('(sec) {}{} -- {}'.format(name, p, descr))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_security(banner: Optional[Banner], client_audit: bool, padlen: int, is_json_output: bool) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with OutputBuffer() as obuf:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_security(out: OutputBuffer, banner: Optional[Banner], client_audit: bool, padlen: int, is_json_output: bool) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with out:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if banner is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            software = Software.parse(banner)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            output_security_sub('cve', software, client_audit, padlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            output_security_sub('txt', software, client_audit, padlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(obuf) > 0 and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            output_security_sub(out, 'cve', software, client_audit, padlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            output_security_sub(out, 'txt', software, client_audit, padlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if not out.is_section_empty() and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.head('# security')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        obuf.flush()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.flush_section()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_fingerprints(algs: Algorithms, is_json_output: bool, sha256: bool = True) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with OutputBuffer() as obuf:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: bool, sha256: bool = True) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with out:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        fps = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if algs.ssh1kex is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            name = 'ssh-rsa1'
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -284,14 +287,14 @@ def output_fingerprints(algs: Algorithms, is_json_output: bool, sha256: bool = T
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # p = '' if out.batch else ' ' * (padlen - len(name))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # out.good('(fin) {0}{1} -- {2} {3}'.format(name, p, bits, fpo))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.good('(fin) {}: {}'.format(name, fpo))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(obuf) > 0 and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if not out.is_section_empty() and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.head('# fingerprints')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        obuf.flush()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.flush_section()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				# Returns True if no warnings or failures encountered in configuration.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_recommendations(algs: Algorithms, software: Optional[Software], is_json_output: bool, padlen: int = 0) -> bool:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_recommendations(out: OutputBuffer, algs: Algorithms, software: Optional[Software], is_json_output: bool, padlen: int = 0) -> bool:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    ret = True
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # PuTTY's algorithms cannot be modified, so there's no point in issuing recommendations.
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -323,7 +326,7 @@ def output_recommendations(algs: Algorithms, software: Optional[Software], is_js
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return ret
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    for_server = True
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with OutputBuffer() as obuf:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with out:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        software, alg_rec = algs.get_recommendations(software, for_server)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for sshv in range(2, 0, -1):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if sshv not in alg_rec:
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -351,20 +354,20 @@ def output_recommendations(algs: Algorithms, software: Optional[Software], is_js
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        b = '(SSH{})'.format(sshv) if sshv == 1 else ''
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        fm = '(rec) {0}{1}{2}-- {3} algorithm to {4}{5} {6}'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        fn(fm.format(sg, name, p, alg_type, an, chg_additional_info, b))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(obuf) > 0 and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if not out.is_section_empty() and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if software is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            title = '(for {})'.format(software.display(False))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            title = ''
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.head('# algorithm recommendations {}'.format(title))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        obuf.flush(True)  # Sort the output so that it is always stable (needed for repeatable testing).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.flush_section(sort_section=True)  # Sort the output so that it is always stable (needed for repeatable testing).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return ret
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				# Output additional information & notes.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_info(software: Optional['Software'], client_audit: bool, any_problems: bool, is_json_output: bool) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with OutputBuffer() as obuf:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output_info(out: OutputBuffer, software: Optional['Software'], client_audit: bool, any_problems: bool, is_json_output: bool) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with out:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Tell user that PuTTY cannot be hardened at the protocol-level.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if client_audit and (software is not None) and (software.product == Product.PuTTY):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.warn('(nfo) PuTTY does not have the option of restricting any algorithms during the SSH handshake.')
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -373,20 +376,20 @@ def output_info(software: Optional['Software'], client_audit: bool, any_problems
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if any_problems:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.warn('(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(obuf) > 0 and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if not out.is_section_empty() and not is_json_output:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.head('# additional info')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        obuf.flush()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.flush_section()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				# Returns a exitcodes.* flag to denote if any failures or warnings were encountered.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output(aconf: AuditConf, banner: Optional[Banner], header: List[str], client_host: Optional[str] = None, kex: Optional[SSH2_Kex] = None, pkm: Optional[SSH1_PublicKeyMessage] = None, print_target: bool = False) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header: List[str], client_host: Optional[str] = None, kex: Optional[SSH2_Kex] = None, pkm: Optional[SSH1_PublicKeyMessage] = None, print_target: bool = False) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    program_retval = exitcodes.GOOD
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    client_audit = client_host is not None  # If set, this is a client audit.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    sshv = 1 if pkm is not None else 2
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    algs = Algorithms(pkm, kex)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with OutputBuffer() as obuf:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    with out:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if print_target:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            host = aconf.host
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -416,7 +419,7 @@ def output(aconf: AuditConf, banner: Optional[Banner], header: List[str], client
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                out.good('(gen) software: {}'.format(software))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            software = None
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        output_compatibility(algs, client_audit)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        output_compatibility(out, algs, client_audit)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if kex is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            compressions = [x for x in kex.server.compression if x != 'none']
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if len(compressions) > 0:
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -424,12 +427,12 @@ def output(aconf: AuditConf, banner: Optional[Banner], header: List[str], client
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                cmptxt = 'disabled'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.good('(gen) compression: {}'.format(cmptxt))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(obuf) > 0 and not aconf.json:  # Print output when it exists and JSON output isn't requested.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if not out.is_section_empty() and not aconf.json:  # Print output when it exists and JSON output isn't requested.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.head('# general')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        obuf.flush()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.flush_section()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    maxlen = algs.maxlen + 1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    output_security(banner, client_audit, maxlen, aconf.json)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    output_security(out, banner, client_audit, maxlen, aconf.json)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # Filled in by output_algorithms() with unidentified algs.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    unknown_algorithms: List[str] = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if pkm is not None:
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -437,34 +440,36 @@ def output(aconf: AuditConf, banner: Optional[Banner], header: List[str], client
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ciphers = pkm.supported_ciphers
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        auths = pkm.supported_authentications
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        title, atype = 'SSH1 host-key algorithms', 'key'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(title, adb, atype, ['ssh-rsa1'], unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(out, title, adb, atype, ['ssh-rsa1'], unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        title, atype = 'SSH1 encryption algorithms (ciphers)', 'enc'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(title, adb, atype, ciphers, unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(out, title, adb, atype, ciphers, unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        title, atype = 'SSH1 authentication types', 'aut'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(title, adb, atype, auths, unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(out, title, adb, atype, auths, unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if kex is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        adb = SSH2_KexDB.ALGORITHMS
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        title, atype = 'key exchange algorithms', 'kex'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(title, adb, atype, kex.kex_algorithms, unknown_algorithms, aconf.json, program_retval, maxlen, kex.dh_modulus_sizes())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(out, title, adb, atype, kex.kex_algorithms, unknown_algorithms, aconf.json, program_retval, maxlen, kex.dh_modulus_sizes())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        title, atype = 'host-key algorithms', 'key'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(title, adb, atype, kex.key_algorithms, unknown_algorithms, aconf.json, program_retval, maxlen, kex.rsa_key_sizes())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(out, title, adb, atype, kex.key_algorithms, unknown_algorithms, aconf.json, program_retval, maxlen, kex.rsa_key_sizes())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        title, atype = 'encryption algorithms (ciphers)', 'enc'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(title, adb, atype, kex.server.encryption, unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(out, title, adb, atype, kex.server.encryption, unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        title, atype = 'message authentication code algorithms', 'mac'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(title, adb, atype, kex.server.mac, unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    output_fingerprints(algs, aconf.json, True)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    perfect_config = output_recommendations(algs, software, aconf.json, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    output_info(software, client_audit, not perfect_config, aconf.json)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output_algorithms(out, title, adb, atype, kex.server.mac, unknown_algorithms, aconf.json, program_retval, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    output_fingerprints(out, algs, aconf.json, True)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    perfect_config = output_recommendations(out, algs, software, aconf.json, maxlen)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    output_info(out, software, client_audit, not perfect_config, aconf.json)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if aconf.json:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print(json.dumps(build_struct(banner, kex=kex, client_host=client_host), sort_keys=True), end='' if len(aconf.target_list) > 0 else "\n")  # Print the JSON of the audit info.  Skip the newline at the end if multiple targets were given (since each audit dump will go into its own list entry).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.reset()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Build & write the JSON struct.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.info(json.dumps(build_struct(aconf.host, banner, kex=kex, client_host=client_host), sort_keys=True))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    elif len(unknown_algorithms) > 0:  # If we encountered any unknown algorithms, ask the user to report them.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.warn("\n\n!!! WARNING: unknown algorithm(s) found!: %s.  Please email the full output above to the maintainer (jtesta@positronsecurity.com), or create a Github issue at <https://github.com/jtesta/ssh-audit/issues>.\n" % ','.join(unknown_algorithms))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return program_retval
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def evaluate_policy(aconf: AuditConf, banner: Optional['Banner'], client_host: Optional[str], kex: Optional['SSH2_Kex'] = None) -> bool:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def evaluate_policy(out: OutputBuffer, aconf: AuditConf, banner: Optional['Banner'], client_host: Optional[str], kex: Optional['SSH2_Kex'] = None) -> bool:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if aconf.policy is None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        raise RuntimeError('Internal error: cannot evaluate against null Policy!')
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -472,11 +477,11 @@ def evaluate_policy(aconf: AuditConf, banner: Optional['Banner'], client_host: O
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    passed, error_struct, error_str = aconf.policy.evaluate(banner, kex)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if aconf.json:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        json_struct = {'host': aconf.host, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print(json.dumps(json_struct, sort_keys=True))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.info(json.dumps(json_struct, sort_keys=True))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        spacing = ''
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if aconf.client_audit:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            print("Client IP: %s" % client_host)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.info("Client IP: %s" % client_host)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            spacing = "   "  # So the fields below line up with 'Client IP: '.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            host = aconf.host
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -487,9 +492,9 @@ def evaluate_policy(aconf: AuditConf, banner: Optional['Banner'], client_host: O
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    host = '%s:%d' % (aconf.host, aconf.port)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            print("Host:   %s" % host)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("Policy: %s%s" % (spacing, aconf.policy.get_name_and_version()))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("Result: %s" % spacing, end='')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.info("Host:   %s" % host)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.info("Policy: %s%s" % (spacing, aconf.policy.get_name_and_version()))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.info("Result: %s" % spacing, line_ended=False)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Use these nice unicode characters in the result message, unless we're on Windows (the cmd.exe terminal doesn't display them properly).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        icon_good = "✔ "
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -507,23 +512,25 @@ def evaluate_policy(aconf: AuditConf, banner: Optional['Banner'], client_host: O
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return passed
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def list_policies() -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def list_policies(out: OutputBuffer) -> None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    '''Prints a list of server & client policies.'''
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    server_policy_names, client_policy_names = Policy.list_builtin_policies()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(server_policy_names) > 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.head('\nServer policies:\n')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("  * \"%s\"" % "\"\n  * \"".join(server_policy_names))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.info("  * \"%s\"" % "\"\n  * \"".join(server_policy_names))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(client_policy_names) > 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.head('\nClient policies:\n')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("  * \"%s\"" % "\"\n  * \"".join(client_policy_names))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.info("  * \"%s\"" % "\"\n  * \"".join(client_policy_names))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(server_policy_names) == 0 and len(client_policy_names) == 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("Error: no built-in policies found!")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.fail("Error: no built-in policies found!")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.info("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH2_Kex'], client_host: Optional[str]) -> None:
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -552,12 +559,12 @@ def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        print("Error: file already exists: %s" % aconf.policy_file)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def process_commandline(args: List[str], usage_cb: Callable[..., None]) -> 'AuditConf':  # pylint: disable=too-many-statements
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[..., None]) -> 'AuditConf':  # pylint: disable=too-many-statements
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # pylint: disable=too-many-branches
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    aconf = AuditConf()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        sopts = 'h1246M:p:P:jbcnvl:t:T:L'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=']
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=', 'threads=']
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        opts, args = getopt.gnu_getopt(args, sopts, lopts)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    except getopt.GetoptError as err:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        usage_cb(str(err))
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -589,6 +596,7 @@ def process_commandline(args: List[str], usage_cb: Callable[..., None]) -> 'Audi
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            aconf.json = True
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif o in ('-v', '--verbose'):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            aconf.verbose = True
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.verbose = True
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif o in ('-l', '--level'):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if a not in ('info', 'warn', 'fail'):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                usage_cb('level {} is not valid'.format(a))
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -603,6 +611,8 @@ def process_commandline(args: List[str], usage_cb: Callable[..., None]) -> 'Audi
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            aconf.policy_file = a
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif o in ('-T', '--targets'):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            aconf.target_file = a
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif o == '--threads':
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            aconf.threads = int(a)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif o in ('-L', '--list-policies'):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            aconf.list_policies = True
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif o == '--lookup':
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -615,7 +625,7 @@ def process_commandline(args: List[str], usage_cb: Callable[..., None]) -> 'Audi
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return aconf
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if aconf.list_policies:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        list_policies()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        list_policies(out)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        sys.exit(exitcodes.GOOD)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if aconf.client_audit is False and aconf.target_file is None:
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -659,23 +669,26 @@ def process_commandline(args: List[str], usage_cb: Callable[..., None]) -> 'Audi
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                aconf.policy = Policy(policy_file=aconf.policy_file)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            except Exception as e:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                print("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc()))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                out.fail("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc()))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                out.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                sys.exit(exitcodes.UNKNOWN_ERROR)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # If the user wants to do a client audit, but provided a server policy, terminate.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if aconf.client_audit and aconf.policy.is_server_policy():
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            print("Error: client audit selected, but server policy provided.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.fail("Error: client audit selected, but server policy provided.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            sys.exit(exitcodes.UNKNOWN_ERROR)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # If the user wants to do a server audit, but provided a client policy, terminate.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if aconf.client_audit is False and aconf.policy.is_server_policy() is False:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            print("Error: server audit selected, but client policy provided.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.fail("Error: server audit selected, but client policy provided.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            sys.exit(exitcodes.UNKNOWN_ERROR)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return aconf
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def build_struct(banner: Optional['Banner'], kex: Optional['SSH2_Kex'] = None, pkm: Optional['SSH1_PublicKeyMessage'] = None, client_host: Optional[str] = None) -> Any:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def build_struct(target_host: str, banner: Optional['Banner'], kex: Optional['SSH2_Kex'] = None, pkm: Optional['SSH1_PublicKeyMessage'] = None, client_host: Optional[str] = None) -> Any:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    banner_str = ''
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    banner_protocol = None
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -695,8 +708,13 @@ def build_struct(banner: Optional['Banner'], kex: Optional['SSH2_Kex'] = None, p
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            "comments": banner_comments,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # If we're scanning a client host, put the client's IP into the results.  Otherwise, include the target host.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if client_host is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        res['client_ip'] = client_host
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        res['target'] = target_host
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if kex is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        res['compression'] = kex.server.compression
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -773,7 +791,7 @@ def build_struct(banner: Optional['Banner'], kex: Optional['SSH2_Kex'] = None, p
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				# Returns one of the exitcodes.* flags.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def audit(aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = False) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = False) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    program_retval = exitcodes.GOOD
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out.batch = aconf.batch
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out.verbose = aconf.verbose
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -781,12 +799,20 @@ def audit(aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = Fal
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out.use_colors = aconf.colors
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    s = SSH_Socket(aconf.host, aconf.port, aconf.ipvo, aconf.timeout, aconf.timeout_set)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if aconf.client_audit:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.v("Listening for client connection on port %d..." % aconf.port, write_now=True)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        s.listen_and_accept()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.v("Connecting to %s:%d..." % ('[%s]' % aconf.host if Utils.is_ipv6_address(aconf.host) else aconf.host, aconf.port), write_now=True)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        err = s.connect()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if err is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.fail(err)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            sys.exit(exitcodes.CONNECTION_ERROR)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # If we're running against multiple targets, return a connection error to the calling worker thread.  Otherwise, write the error message to the console and exit.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if len(aconf.target_list) > 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                return exitcodes.CONNECTION_ERROR
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                out.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                sys.exit(exitcodes.CONNECTION_ERROR)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if sshv is None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        sshv = 2 if aconf.ssh2 else 1
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -811,7 +837,9 @@ def audit(aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = Fal
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                payload_txt = u'"{}"'.format(repr(payload).lstrip('b')[1:-1])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if payload_txt == u'Protocol major versions differ.':
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if sshv == 2 and aconf.ssh1:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    return audit(aconf, 1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    ret = audit(out, aconf, 1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    out.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    return ret
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            err = '[exception] error reading packet ({})'.format(payload_txt)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            err_pair = None
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -824,11 +852,11 @@ def audit(aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = Fal
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                      'instead received unknown message ({2})'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                err = fmt.format(err_pair[0], err_pair[1], packet_type)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if err is not None:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        output(aconf, banner, header)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        output(out, aconf, banner, header)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.fail(err)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return exitcodes.CONNECTION_ERROR
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if sshv == 1:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output(aconf, banner, header, pkm=SSH1_PublicKeyMessage.parse(payload))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        program_retval = output(out, aconf, banner, header, pkm=SSH1_PublicKeyMessage.parse(payload))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    elif sshv == 2:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        kex = SSH2_Kex.parse(payload)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if aconf.client_audit is False:
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -837,11 +865,11 @@ def audit(aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = Fal
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # This is a standard audit scan.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (aconf.policy is None) and (aconf.make_policy is False):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            program_retval = output(aconf, banner, header, client_host=s.client_host, kex=kex, print_target=print_target)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            program_retval = output(out, aconf, banner, header, client_host=s.client_host, kex=kex, print_target=print_target)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # This is a policy test.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif (aconf.policy is not None) and (aconf.make_policy is False):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            program_retval = exitcodes.GOOD if evaluate_policy(aconf, banner, s.client_host, kex=kex) else exitcodes.FAILURE
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            program_retval = exitcodes.GOOD if evaluate_policy(out, aconf, banner, s.client_host, kex=kex) else exitcodes.FAILURE
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # A new policy should be made from this scan.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        elif (aconf.policy is None) and (aconf.make_policy is True):
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -853,7 +881,7 @@ def audit(aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = Fal
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return program_retval
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def algorithm_lookup(alg_names: str) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def algorithm_lookup(out: OutputBuffer, alg_names: str) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    '''Looks up a comma-separated list of algorithms and outputs their security properties.  Returns an exitcodes.* flag.'''
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    retval = exitcodes.GOOD
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    alg_types = {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -885,7 +913,7 @@ def algorithm_lookup(alg_names: str) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    for alg_type in alg_types:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if len(algorithms_dict[alg_type]) > 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            title = str(alg_types.get(alg_type))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            retval = output_algorithms(title, adb, alg_type, list(algorithms_dict[alg_type]), unknown_algorithms, False, retval, padding)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            retval = output_algorithms(out, title, adb, alg_type, list(algorithms_dict[alg_type]), unknown_algorithms, False, retval, padding)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    algorithms_dict_flattened = [
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        alg_name
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -915,7 +943,7 @@ def algorithm_lookup(alg_names: str) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for algorithm_not_found in algorithms_not_found:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out.fail(algorithm_not_found)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    print()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out.sep()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if len(similar_algorithms) > 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        retval = exitcodes.FAILURE
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -926,14 +954,45 @@ def algorithm_lookup(alg_names: str) -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return retval
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				out = Output()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				# Worker thread for scanning multiple targets concurrently.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def target_worker_thread(host: str, port: int, shared_aconf: AuditConf) -> Tuple[int, str]:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    ret = -1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    string_output = ''
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out = OutputBuffer()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out.verbose = shared_aconf.verbose
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    my_aconf = copy.deepcopy(shared_aconf)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    my_aconf.host = host
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    my_aconf.port = port
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # If we're outputting JSON, turn off colors and ensure 'info' level messages go through.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if my_aconf.json:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.json = True
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.use_colors = False
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out.v("Running against: %s:%d..." % (my_aconf.host, my_aconf.port), write_now=True)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    try:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ret = audit(out, my_aconf, print_target=True)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        string_output = out.get_buffer()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    except Exception:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ret = -1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        string_output = "An exception occurred while scanning %s:%d:\n%s" % (host, port, str(traceback.format_exc()))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return ret, string_output
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				def main() -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    aconf = process_commandline(sys.argv[1:], usage)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    out = OutputBuffer()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    aconf = process_commandline(out, sys.argv[1:], usage)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # If we're outputting JSON, turn off colors and ensure 'info' level messages go through.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if aconf.json:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.json = True
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.use_colors = False
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if aconf.lookup != '':
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        retval = algorithm_lookup(aconf.lookup)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        retval = algorithm_lookup(out, aconf.lookup)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        sys.exit(retval)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    # If multiple targets were specified...
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -945,31 +1004,46 @@ def main() -> int:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            print('[', end='')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Loop through each target in the list.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for i, target in enumerate(aconf.target_list):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            aconf.host, port = Utils.parse_host_and_port(target)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if port == 0:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                port = 22
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            aconf.port = port
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        target_servers = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for _, target in enumerate(aconf.target_list):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            host, port = Utils.parse_host_and_port(target, default_port=22)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            target_servers.append((host, port))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            new_ret = audit(aconf, print_target=True)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # A ranked list of return codes.  Those with higher indices will take precendence over lower ones.  For example, if three servers are scanned, yielding WARNING, GOOD, and UNKNOWN_ERROR, the overall result will be UNKNOWN_ERROR, since its index is the highest.  Errors have highest priority, followed by failures, then warnings.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ranked_return_codes = [exitcodes.GOOD, exitcodes.WARNING, exitcodes.FAILURE, exitcodes.CONNECTION_ERROR, exitcodes.UNKNOWN_ERROR]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # Set the return value only if an unknown error occurred, a failure occurred, or if a warning occurred and the previous value was good.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (new_ret == exitcodes.UNKNOWN_ERROR) or (new_ret == exitcodes.FAILURE) or ((new_ret == exitcodes.WARNING) and (ret == exitcodes.GOOD)):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                ret = new_ret
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        # Queue all worker threads.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        num_target_servers = len(target_servers)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        num_processed = 0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.v("Scanning %u targets with %s%u threads..." % (num_target_servers, '(at most) ' if aconf.threads > num_target_servers else '',  aconf.threads), write_now=True)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        with concurrent.futures.ThreadPoolExecutor(max_workers=aconf.threads) as executor:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            future_to_server = {executor.submit(target_worker_thread, target_server[0], target_server[1], aconf): target_server for target_server in target_servers}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            for future in concurrent.futures.as_completed(future_to_server):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                worker_ret, worker_output = future.result()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            # Don't print a delimiter after the last target was handled.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if i + 1 != len(aconf.target_list):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if aconf.json:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    print(", ", end='')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    print(("-" * 80) + "\n")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # If this worker's return code is ranked higher that what we've cached so far, update our cache.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if ranked_return_codes.index(worker_ret) > ranked_return_codes.index(ret):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    ret = worker_ret
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # print("Worker for %s:%d returned %d: [%s]" % (target_server[0], target_server[1], worker_ret, worker_output))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                print(worker_output, end='' if aconf.json else "\n")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                # Don't print a delimiter after the last target was handled.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                num_processed += 1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if num_processed < num_target_servers:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    if aconf.json:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        print(", ", end='')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        print(("-" * 80) + "\n")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if aconf.json:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            print(']')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return ret
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return audit(aconf)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    else:  # Just a scan against a single target.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ret = audit(out, aconf)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out.write()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return ret
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				if __name__ == '__main__':  # pragma: nocover
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				 
 |