mirror of
				https://github.com/jtesta/ssh-audit.git
				synced 2025-10-30 13:05:26 +01:00 
			
		
		
		
	Man Page on Windows (#93)
* Man Page on Windows * Corrected typo in update_windows_man_page.sh * Check that the 'sed' (stream editor) binary exists
This commit is contained in:
		| @@ -56,10 +56,11 @@ class AuditConf: | |||||||
|         self.threads = 32 |         self.threads = 32 | ||||||
|         self.list_policies = False |         self.list_policies = False | ||||||
|         self.lookup = '' |         self.lookup = '' | ||||||
|  |         self.manual = False | ||||||
|  |  | ||||||
|     def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None: |     def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None: | ||||||
|         valid = False |         valid = False | ||||||
|         if name in ['ssh1', 'ssh2', 'batch', 'client_audit', 'colors', 'verbose', 'timeout_set', 'json', 'make_policy', 'list_policies']: |         if name in ['ssh1', 'ssh2', 'batch', 'client_audit', 'colors', 'verbose', 'timeout_set', 'json', 'make_policy', 'list_policies', 'manual']: | ||||||
|             valid, value = True, bool(value) |             valid, value = True, bool(value) | ||||||
|         elif name in ['ipv4', 'ipv6']: |         elif name in ['ipv4', 'ipv6']: | ||||||
|             valid = False |             valid = False | ||||||
|   | |||||||
| @@ -24,3 +24,4 @@ | |||||||
| VERSION = 'v2.4.0-dev' | VERSION = 'v2.4.0-dev' | ||||||
| SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'  # SSH software to impersonate | SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'  # SSH software to impersonate | ||||||
| GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues'  # The URL to the Github issues tracker. | GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues'  # The URL to the Github issues tracker. | ||||||
|  | WINDOWS_MAN_PAGE = '' | ||||||
|   | |||||||
| @@ -36,6 +36,7 @@ from typing import Dict, List, Set, Sequence, Tuple, Iterable  # noqa: F401 | |||||||
| from typing import Callable, Optional, Union, Any  # noqa: F401 | from typing import Callable, Optional, Union, Any  # noqa: F401 | ||||||
|  |  | ||||||
| from ssh_audit.globals import VERSION | from ssh_audit.globals import VERSION | ||||||
|  | from ssh_audit.globals import WINDOWS_MAN_PAGE | ||||||
| from ssh_audit.algorithm import Algorithm | from ssh_audit.algorithm import Algorithm | ||||||
| from ssh_audit.algorithms import Algorithms | from ssh_audit.algorithms import Algorithms | ||||||
| from ssh_audit.auditconf import AuditConf | from ssh_audit.auditconf import AuditConf | ||||||
| @@ -86,6 +87,7 @@ def usage(err: Optional[str] = None) -> None: | |||||||
|     uout.info('   -L,  --list-policies    list all the official, built-in policies') |     uout.info('   -L,  --list-policies    list all the official, built-in policies') | ||||||
|     uout.info('        --lookup=<alg1,alg2,...>    looks up an algorithm(s) without\n                                    connecting to a server') |     uout.info('        --lookup=<alg1,alg2,...>    looks up an algorithm(s) without\n                                    connecting to a server') | ||||||
|     uout.info('   -M,  --make-policy=<policy.txt>  creates a policy based on the target server\n                                    (i.e.: the target server has the ideal\n                                    configuration that other servers should\n                                    adhere to)') |     uout.info('   -M,  --make-policy=<policy.txt>  creates a policy based on the target server\n                                    (i.e.: the target server has the ideal\n                                    configuration that other servers should\n                                    adhere to)') | ||||||
|  |     uout.info('   -m,  --manual           print the man page (Windows only)') | ||||||
|     uout.info('   -n,  --no-colors        disable colors') |     uout.info('   -n,  --no-colors        disable colors') | ||||||
|     uout.info('   -p,  --port=<port>      port to connect') |     uout.info('   -p,  --port=<port>      port to connect') | ||||||
|     uout.info('   -P,  --policy=<policy.txt>  run a policy test using the specified policy') |     uout.info('   -P,  --policy=<policy.txt>  run a policy test using the specified policy') | ||||||
| @@ -571,8 +573,8 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[. | |||||||
|     # pylint: disable=too-many-branches |     # pylint: disable=too-many-branches | ||||||
|     aconf = AuditConf() |     aconf = AuditConf() | ||||||
|     try: |     try: | ||||||
|         sopts = 'h1246M:p:P:jbcnvl:t:T:L' |         sopts = 'h1246M:p:P:jbcnvl:t:T:Lm' | ||||||
|         lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=', 'threads='] |         lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=', 'threads=', 'manual'] | ||||||
|         opts, args = getopt.gnu_getopt(args, sopts, lopts) |         opts, args = getopt.gnu_getopt(args, sopts, lopts) | ||||||
|     except getopt.GetoptError as err: |     except getopt.GetoptError as err: | ||||||
|         usage_cb(str(err)) |         usage_cb(str(err)) | ||||||
| @@ -625,10 +627,15 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[. | |||||||
|             aconf.list_policies = True |             aconf.list_policies = True | ||||||
|         elif o == '--lookup': |         elif o == '--lookup': | ||||||
|             aconf.lookup = a |             aconf.lookup = a | ||||||
|  |         elif o in ('-m', '--manual'): | ||||||
|  |             aconf.manual = True | ||||||
|  |  | ||||||
|     if len(args) == 0 and aconf.client_audit is False and aconf.target_file is None and aconf.list_policies is False and aconf.lookup == '': |     if len(args) == 0 and aconf.client_audit is False and aconf.target_file is None and aconf.list_policies is False and aconf.lookup == '' and aconf.manual is False: | ||||||
|         usage_cb() |         usage_cb() | ||||||
|  |  | ||||||
|  |     if aconf.manual: | ||||||
|  |         return aconf | ||||||
|  |  | ||||||
|     if aconf.lookup != '': |     if aconf.lookup != '': | ||||||
|         return aconf |         return aconf | ||||||
|  |  | ||||||
| @@ -989,6 +996,79 @@ def target_worker_thread(host: str, port: int, shared_aconf: AuditConf) -> Tuple | |||||||
|     return ret, string_output |     return ret, string_output | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def windows_manual(out: OutputBuffer) -> int: | ||||||
|  |     '''Prints the man page on Windows.  Returns an exitcodes.* flag.''' | ||||||
|  |     import os | ||||||
|  |     import ctypes | ||||||
|  |  | ||||||
|  |     retval = exitcodes.GOOD | ||||||
|  |  | ||||||
|  |     if sys.platform != 'win32': | ||||||
|  |         out.fail("The '-m' and '--manual' parameters are reserved for use on Windows only.\nUsers of other operating systems should read the man page.") | ||||||
|  |         retval = exitcodes.FAILURE | ||||||
|  |         return retval | ||||||
|  |  | ||||||
|  |     # Support for ANSI escape sequences was first introduced in Windows 10 | ||||||
|  |     # version 1511. | ||||||
|  |     # | ||||||
|  |     # Calling 'os.system' activates ANSI support if available. | ||||||
|  |     # | ||||||
|  |     # NB: If output is redirected to a file or piped to another program, ANSI | ||||||
|  |     #     support is suppressed. | ||||||
|  |     os.system("") | ||||||
|  |  | ||||||
|  |     STD_OUTPUT_HANDLE = -11 | ||||||
|  |     ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 | ||||||
|  |  | ||||||
|  |     kernel32 = ctypes.WinDLL('kernel32') | ||||||
|  |     hStdin = kernel32.GetStdHandle(STD_OUTPUT_HANDLE) | ||||||
|  |     consoleMode = ctypes.c_ulong() | ||||||
|  |  | ||||||
|  |     # GetConsoleMode | ||||||
|  |     # https://docs.microsoft.com/en-us/windows/console/getconsolemode | ||||||
|  |     # | ||||||
|  |     #   Parameters: | ||||||
|  |     #     1. hConsoleHandle [in] | ||||||
|  |     #     2. lpMode [out] | ||||||
|  |     # | ||||||
|  |     #   Return value: | ||||||
|  |     #     Success: A non-zero value. | ||||||
|  |     #     Fail:    A value of zero. | ||||||
|  |     kernel32.GetConsoleMode(hStdin, ctypes.byref(consoleMode)) | ||||||
|  |  | ||||||
|  |     # Use a bitwise and (&) between the console mode value and the flag. If the | ||||||
|  |     # console mode value contains the flag then ANSI is supported. | ||||||
|  |     ansi_supported = bool(consoleMode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) | ||||||
|  |  | ||||||
|  |     if ansi_supported: | ||||||
|  |         out.info(WINDOWS_MAN_PAGE) | ||||||
|  |     else: | ||||||
|  |         import io | ||||||
|  |         import re | ||||||
|  |  | ||||||
|  |         # If the text contains unicode characters this may result in a | ||||||
|  |         # "UnicodeEncodeError" error when printing depending on the active | ||||||
|  |         # console code page. Therefore the stdout's encoding is explicitly set | ||||||
|  |         # to utf8. | ||||||
|  |         # | ||||||
|  |         # NB: If ANSI support enabled then unicode is implicitly handled. | ||||||
|  |         new_stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf8', errors=sys.stdout.errors) | ||||||
|  |         old_stdout = sys.stdout | ||||||
|  |         sys.stdout = new_stdout | ||||||
|  |  | ||||||
|  |         # An ANSI escape sequence starts with an ESC character (033 in decimal | ||||||
|  |         # and 1b in hex), followed by an open bracket and terminates with 'm'. | ||||||
|  |         strip_ansi = re.compile(r'\x1b\[.*?m') | ||||||
|  |         man_plain_text = strip_ansi.sub('', WINDOWS_MAN_PAGE) | ||||||
|  |  | ||||||
|  |         out.info(man_plain_text) | ||||||
|  |  | ||||||
|  |         new_stdout.detach() | ||||||
|  |         sys.stdout = old_stdout | ||||||
|  |  | ||||||
|  |     return retval | ||||||
|  |  | ||||||
|  |  | ||||||
| def main() -> int: | def main() -> int: | ||||||
|     out = OutputBuffer() |     out = OutputBuffer() | ||||||
|     aconf = process_commandline(out, sys.argv[1:], usage) |     aconf = process_commandline(out, sys.argv[1:], usage) | ||||||
| @@ -998,6 +1078,11 @@ def main() -> int: | |||||||
|         out.json = True |         out.json = True | ||||||
|         out.use_colors = False |         out.use_colors = False | ||||||
|  |  | ||||||
|  |     if aconf.manual: | ||||||
|  |         retval = windows_manual(out) | ||||||
|  |         out.write() | ||||||
|  |         sys.exit(retval) | ||||||
|  |  | ||||||
|     if aconf.lookup != '': |     if aconf.lookup != '': | ||||||
|         retval = algorithm_lookup(out, aconf.lookup) |         retval = algorithm_lookup(out, aconf.lookup) | ||||||
|         out.write() |         out.write() | ||||||
|   | |||||||
| @@ -66,6 +66,11 @@ List all official, built-in policies for common systems.  Their full names can t | |||||||
| .br | .br | ||||||
| Look up the security information of an algorithm(s) in the internal database.  Does not connect to a server. | Look up the security information of an algorithm(s) in the internal database.  Does not connect to a server. | ||||||
|  |  | ||||||
|  | .TP | ||||||
|  | .B -m, \-\-manual | ||||||
|  | .br | ||||||
|  | Print the man page (Windows only). | ||||||
|  |  | ||||||
| .TP | .TP | ||||||
| .B -M, \-\-make-policy=<custom_policy.txt> | .B -M, \-\-make-policy=<custom_policy.txt> | ||||||
| .br | .br | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								update_windows_man_page.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								update_windows_man_page.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | ################################################################################ | ||||||
|  | # update_windows_man_page | ||||||
|  | # | ||||||
|  | # PURPOSE | ||||||
|  | #   Since Windows lacks a manual reader it's necessary to provide an alternative | ||||||
|  | #   means of reading the man page.  | ||||||
|  | # | ||||||
|  | #   This script should be run as part of the ssh-audit packaging process for  | ||||||
|  | #   Windows. It populates the 'WINDOWS_MAN_PAGE' variable in 'globals.py' with  | ||||||
|  | #   the contents of the man page. Windows users can then print the content of  | ||||||
|  | #   'WINDOWS_MAN_PAGE' by invoking ssh-audit with the manual parameters  | ||||||
|  | #   (--manual / -m). | ||||||
|  | # | ||||||
|  | # USAGE | ||||||
|  | #   update_windows_man_page.sh -m <path-to-man-page> -g <path-to-globals.py> | ||||||
|  | # | ||||||
|  | ################################################################################ | ||||||
|  |  | ||||||
|  | while getopts "m: g:" OPTION | ||||||
|  | do | ||||||
|  |     case "$OPTION" in | ||||||
|  |         m) | ||||||
|  |             MAN_PAGE="$OPTARG" | ||||||
|  |             ;; | ||||||
|  |         g) | ||||||
|  |             GLOBALS_PY="$OPTARG" | ||||||
|  |             ;; | ||||||
|  |         *) | ||||||
|  |             echo >&2 "Invalid parameter(s) provided" | ||||||
|  |             exit 1 | ||||||
|  |             ;; | ||||||
|  |     esac | ||||||
|  | done | ||||||
|  |  | ||||||
|  | if [[ -z "$MAN_PAGE" || -z "$GLOBALS_PY" ]]; then | ||||||
|  |     echo >&2 "Missing parameter(s)." | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # Check that the specified files exist. | ||||||
|  | [ -f "$MAN_PAGE" ] || { echo >&2 "man page file not found: $MAN_PAGE"; exit 1; } | ||||||
|  | [ -f "$GLOBALS_PY" ] || { echo >&2 "globals.py file not found: $GLOBALS_PY"; exit 1; } | ||||||
|  |  | ||||||
|  | # Check that the 'ul' (do underlining) binary exists. | ||||||
|  | command -v ul >/dev/null 2>&1 || { echo >&2 "ul not found."; exit 1; } | ||||||
|  |  | ||||||
|  | # Check that the 'sed' (stream editor) binary exists. | ||||||
|  | command -v sed >/dev/null 2>&1 || { echo >&2 "sed not found."; exit 1; } | ||||||
|  |  | ||||||
|  | # Remove the Windows man page placeholder from 'globals.py'. | ||||||
|  | sed -i '/^WINDOWS_MAN_PAGE/d' "$GLOBALS_PY" | ||||||
|  |  | ||||||
|  | # Append the man page content to 'globals.py'. | ||||||
|  | #   * man outputs a backspace-overwrite sequence rather than an ANSI escape  | ||||||
|  | #     sequence. | ||||||
|  | #   * 'MAN_KEEP_FORMATTING' preserves the backspace-overwrite sequence when  | ||||||
|  | #     redirected to a file or a pipe. | ||||||
|  | #   * The 'ul' command converts the backspace-overwrite sequence to an ANSI escape  | ||||||
|  | #     sequence. | ||||||
|  | echo WINDOWS_MAN_PAGE = '"""' >> "$GLOBALS_PY" | ||||||
|  | MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "$MAN_PAGE" | ul >> "$GLOBALS_PY" | ||||||
|  | echo '"""' >> "$GLOBALS_PY" | ||||||
		Reference in New Issue
	
	Block a user
	 thecliguy
					thecliguy