class Output: LEVELS = ('info', 'warn', 'fail') # type: Sequence[str] COLORS = {'head': 36, 'good': 32, 'warn': 33, 'fail': 31} # Use brighter colors on Windows for better readability. if Utils.is_windows(): COLORS = {'head': 96, 'good': 92, 'warn': 93, 'fail': 91} def __init__(self) -> None: self.batch = False self.verbose = False self.use_colors = True self.json = False self.__level = 0 self.__colsupport = 'colorama' in sys.modules or os.name == 'posix' @property def level(self) -> str: if self.__level < len(self.LEVELS): return self.LEVELS[self.__level] return 'unknown' @level.setter def level(self, name: str) -> None: self.__level = self.get_level(name) def get_level(self, name: str) -> int: cname = 'info' if name == 'good' else name if cname not in self.LEVELS: return sys.maxsize return self.LEVELS.index(cname) def sep(self) -> None: if not self.batch: print() @property def colors_supported(self) -> bool: return self.__colsupport @staticmethod def _colorized(color: str) -> Callable[[str], None]: return lambda x: print(u'{}{}\033[0m'.format(color, x)) def __getattr__(self, name: str) -> Callable[[str], None]: if name == 'head' and self.batch: return lambda x: None if not self.get_level(name) >= self.__level: return lambda x: None if self.use_colors and self.colors_supported and name in self.COLORS: color = '\033[0;{}m'.format(self.COLORS[name]) return self._colorized(color) else: return lambda x: print(u'{}'.format(x))
def evaluate_policy(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!') 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)) else: spacing = '' if aconf.client_audit: print("Client IP: %s" % client_host) spacing = " " # So the fields below line up with 'Client IP: '. else: host = aconf.host if aconf.port != 22: # Check if this is an IPv6 address, as that is printed in a different format. if Utils.is_ipv6_address(aconf.host): host = '[%s]:%d' % (aconf.host, aconf.port) 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='') # 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 = "✔ " icon_fail = "❌ " if Utils.is_windows(): icon_good = "" icon_fail = "" if passed: out.good("%sPassed" % icon_good) else: out.fail("%sFailed!" % icon_fail) out.warn("\nErrors:\n%s" % error_str) return passed
class OutputBuffer: LEVELS: Sequence[str] = ('info', 'warn', 'fail') COLORS = {'head': 36, 'good': 32, 'warn': 33, 'fail': 31} # Use brighter colors on Windows for better readability. if Utils.is_windows(): COLORS = {'head': 96, 'good': 92, 'warn': 93, 'fail': 91} def __init__(self, buffer_output: bool = True) -> None: self.buffer_output = buffer_output self.buffer: List[str] = [] self.in_section = False self.section: List[str] = [] self.batch = False self.verbose = False self.debug = False self.use_colors = True self.json = False self.__level = 0 self.__is_color_supported = ('colorama' in sys.modules) or (os.name == 'posix') self.line_ended = True def _print(self, level: str, s: str = '', line_ended: bool = True) -> None: '''Saves output to buffer (if in buffered mode), or immediately prints to stdout otherwise.''' # If we're logging only 'warn' or above, and this is an 'info', ignore message. if self.get_level(level) < self.__level: return if self.use_colors and self.colors_supported and len( s) > 0 and level != 'info': s = "\033[0;%dm%s\033[0m" % (self.COLORS[level], s) if self.buffer_output: # Select which list to add to. If we are in a 'with' statement, then this goes in the section buffer, otherwise the general buffer. buf = self.section if self.in_section else self.buffer # Determine if a new line should be added, or if the last line should be appended. if not self.line_ended: last_entry = -1 if len(buf) > 0 else 0 buf[last_entry] = buf[last_entry] + s else: buf.append(s) # When False, this tells the next call to append to the last line we just added. self.line_ended = line_ended else: print(s) def get_buffer(self) -> str: '''Returns all buffered output, then clears the buffer.''' self.flush_section() buffer_str = "\n".join(self.buffer) self.buffer = [] return buffer_str def write(self) -> None: '''Writes the output to stdout.''' self.flush_section() print(self.get_buffer(), flush=True) def reset(self) -> None: self.flush_section() self.get_buffer() @property def level(self) -> str: '''Returns the minimum level for output.''' if self.__level < len(self.LEVELS): return self.LEVELS[self.__level] return 'unknown' @level.setter def level(self, name: str) -> None: '''Sets the minimum level for output (one of: 'info', 'warn', 'fail').''' self.__level = self.get_level(name) def get_level(self, name: str) -> int: cname = 'info' if name == 'good' else name if cname not in self.LEVELS: return sys.maxsize return self.LEVELS.index(cname) @property def colors_supported(self) -> bool: '''Returns True if the system supports color output.''' return self.__is_color_supported # When used in a 'with' block, the output to goes into a section; this can be sorted separately when add_section_to_buffer() is later called. def __enter__(self) -> 'OutputBuffer': self.in_section = True return self def __exit__(self, *args: Any) -> None: self.in_section = False def flush_section(self, sort_section: bool = False) -> None: '''Appends section output (optionally sorting it first) to the end of the buffer, then clears the section output.''' if sort_section: self.section.sort() self.buffer.extend(self.section) self.section = [] def is_section_empty(self) -> bool: '''Returns True if the section buffer is empty, otherwise False.''' return len(self.section) == 0 def head(self, s: str, line_ended: bool = True) -> 'OutputBuffer': if not self.batch: self._print('head', s, line_ended) return self def fail(self, s: str, line_ended: bool = True) -> 'OutputBuffer': self._print('fail', s, line_ended) return self def warn(self, s: str, line_ended: bool = True) -> 'OutputBuffer': self._print('warn', s, line_ended) return self def info(self, s: str, line_ended: bool = True) -> 'OutputBuffer': self._print('info', s, line_ended) return self def good(self, s: str, line_ended: bool = True) -> 'OutputBuffer': self._print('good', s, line_ended) return self def sep(self) -> 'OutputBuffer': if not self.batch: self._print('info') return self def v(self, s: str, write_now: bool = False) -> 'OutputBuffer': '''Prints a message if verbose output is enabled.''' if self.verbose or self.debug: self.info(s) if write_now: self.write() return self def d(self, s: str, write_now: bool = False) -> 'OutputBuffer': '''Prints a message if verbose output is enabled.''' if self.debug: self.info(s) if write_now: self.write() return self