# Copyright (c) 2013-2019 Philip Hane # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # CLI python script interface for ipwhois.IPWhois lookups. import argparse import json from os import path from ipwhois import IPWhois from ipwhois.hr import (HR_ASN, HR_RDAP, HR_RDAP_COMMON, HR_WHOIS, HR_WHOIS_NIR) try: # pragma: no cover from urllib.request import (ProxyHandler, build_opener) except ImportError: # pragma: no cover from urllib2 import (ProxyHandler, build_opener) # CLI ANSI rendering ANSI = { 'end': '\033[0m', 'b': '\033[1m', 'ul': '\033[4m', 'red': '\033[31m', 'green': '\033[32m', 'yellow': '\033[33m', 'cyan': '\033[36m' } # Color definitions for sub lines COLOR_DEPTH = { '0': ANSI['green'], '1': ANSI['yellow'], '2': ANSI['red'], '3': ANSI['cyan'] } # Line formatting, keys ending in C are colorized versions. LINES = { '1': '>> ', '2': '>> >>> ', '3': '>> >>> >>>> ', '4': '>> >>> >>>> >>>>> ', '1C': '{0}>>{1} '.format(COLOR_DEPTH['0'], ANSI['end']), '2C': '{0}>>{1} >>>{2} '.format( COLOR_DEPTH['0'], COLOR_DEPTH['1'], ANSI['end'] ), '3C': '{0}>>{1} >>>{2} >>>>{3} '.format( COLOR_DEPTH['0'], COLOR_DEPTH['1'], COLOR_DEPTH['2'], ANSI['end'] ), '4C': '{0}>>{1} >>>{2} >>>>{3} >>>>>{4} '.format( COLOR_DEPTH['0'], COLOR_DEPTH['1'], COLOR_DEPTH['2'], COLOR_DEPTH['3'], ANSI['end'] ), } # Setup the arg parser. parser = argparse.ArgumentParser( description='ipwhois CLI interface' ) parser.add_argument( '--whois', action='store_true', help='Retrieve whois data via legacy Whois (port 43) instead of RDAP ' '(default).' ) parser.add_argument( '--exclude_nir', action='store_true', help='Disable NIR whois lookups (JPNIC, KRNIC). This is the opposite of ' 'the ipwhois inc_nir, in order to enable inc_nir by default in the ' 'CLI.', default=False ) parser.add_argument( '--json', action='store_true', help='Output results in JSON format.', default=False ) # Output options group = parser.add_argument_group('Output options') group.add_argument( '--hr', action='store_true', help='If set, returns results with human readable key translations.' ) group.add_argument( '--show_name', action='store_true', help='If this and --hr are set, the key name is shown in parentheses after' 'its short value' ) group.add_argument( '--colorize', action='store_true', help='If set, colorizes the output using ANSI. Should work in most ' 'platform consoles.' ) # IPWhois settings (common) group = parser.add_argument_group('IPWhois settings') group.add_argument( '--timeout', type=int, default=5, metavar='TIMEOUT', help='The default timeout for socket connections in seconds.' ) group.add_argument( '--proxy_http', type=str, nargs=1, default='', metavar='"PROXY_HTTP"', help='The proxy HTTP address passed to request.ProxyHandler. User auth ' 'can be passed like "http://user:pass@192.168.0.1:80"', required=False ) group.add_argument( '--proxy_https', type=str, nargs=1, default='', metavar='"PROXY_HTTPS"', help='The proxy HTTPS address passed to request.ProxyHandler. User auth' 'can be passed like "https://user:pass@192.168.0.1:443"', required=False ) # Common (RDAP & Legacy Whois) group = parser.add_argument_group('Common settings (RDAP & Legacy Whois)') group.add_argument( '--inc_raw', action='store_true', help='Include the raw whois results in the output.' ) group.add_argument( '--retry_count', type=int, default=3, metavar='RETRY_COUNT', help='The number of times to retry in case socket errors, timeouts, ' 'connection resets, etc. are encountered.' ) group.add_argument( '--asn_alts', type=str, nargs=1, default='whois,http', metavar='"ASN_ALTS"', help='A comma delimited list of additional lookup types to attempt if the ' 'ASN dns lookup fails. Allow permutations must be enabled. ' 'Defaults to all: "whois,http" *WARNING* deprecated in ' 'favor of new argument asn_methods.' ) group.add_argument( '--asn_methods', type=str, nargs=1, default='dns,whois,http', metavar='"ASN_METHODS"', help='List of ASN lookup types to attempt, in order. ' 'Defaults to all [\'dns\', \'whois\', \'http\'].' ) group.add_argument( '--extra_org_map', type=json.loads, nargs=1, default='{"DNIC": "arin"}', metavar='"EXTRA_ORG_MAP"', help='Dictionary mapping org handles to RIRs. This is for limited cases ' 'where ARIN REST (ASN fallback HTTP lookup) does not show an RIR as ' 'the org handle e.g., DNIC (which is now the built in ORG_MAP) e.g., ' '{\\"DNIC\\": \\"arin\\"}. Valid RIR values are (note the ' 'case-sensitive - this is meant to match the REST result): ' '\'ARIN\', \'RIPE\', \'apnic\', \'lacnic\', \'afrinic\'' ) group.add_argument( '--skip_asn_description', action='store_true', help='Don\'t run an additional query when pulling ASN information via dns ' '(to get the ASN description). This is the opposite of the ipwhois ' 'get_asn_description argument, in order to enable ' 'get_asn_description by default in the CLI.', default=False ) # RDAP group = parser.add_argument_group('RDAP settings') group.add_argument( '--depth', type=int, default=0, metavar='COLOR_DEPTH', help='If not --whois, how many levels deep to run RDAP queries when ' 'additional referenced objects are found.' ) group.add_argument( '--excluded_entities', type=str, nargs=1, default=None, metavar='"EXCLUDED_ENTITIES"', help='If not --whois, a comma delimited list of entity handles to not ' 'perform lookups.' ) group.add_argument( '--bootstrap', action='store_true', help='If not --whois, performs lookups via ARIN bootstrap rather than ' 'lookups based on ASN data. ASN lookups are not performed and no ' 'output for any of the asn* fields is provided.' ) group.add_argument( '--rate_limit_timeout', type=int, default=120, metavar='RATE_LIMIT_TIMEOUT', help='If not --whois, the number of seconds to wait before retrying when ' 'a rate limit notice is returned via rdap+json.' ) # Legacy Whois group = parser.add_argument_group('Legacy Whois settings') group.add_argument( '--get_referral', action='store_true', help='If --whois, retrieve referral whois information, if available.' ) group.add_argument( '--extra_blacklist', type=str, nargs=1, default='', metavar='"EXTRA_BLACKLIST"', help='If --whois, A list of blacklisted whois servers in addition to the ' 'global BLACKLIST.' ) group.add_argument( '--ignore_referral_errors', action='store_true', help='If --whois, ignore and continue when an exception is encountered on ' 'referral whois lookups.' ) group.add_argument( '--field_list', type=str, nargs=1, default='', metavar='"FIELD_LIST"', help='If --whois, a list of fields to parse: ' '[\'name\', \'handle\', \'description\', \'country\', \'state\', ' '\'city\', \'address\', \'postal_code\', \'emails\', \'created\', ' '\'updated\']' ) # NIR (National Internet Registry -- JPNIC, KRNIC) group = parser.add_argument_group('NIR (National Internet Registry) settings') group.add_argument( '--nir_field_list', type=str, nargs=1, default='', metavar='"NIR_FIELD_LIST"', help='If not --exclude_nir, a list of fields to parse: ' '[\'name\', \'handle\', \'country\', \'address\', \'postal_code\', ' '\'nameservers\', \'created\', \'updated\', \'contact_admin\', ' '\'contact_tech\']' ) # Input (required) group = parser.add_argument_group('Input (Required)') group.add_argument( '--addr', type=str, nargs=1, metavar='"IP"', help='An IPv4 or IPv6 address as a string.', required=True ) # Get the args script_args = parser.parse_args() # Get the current working directory. CUR_DIR = path.dirname(__file__) def generate_output(line='0', short=None, name=None, value=None, is_parent=False, colorize=True): """ The function for formatting CLI output results. Args: line (:obj:`str`): The line number (0-4). Determines indentation. Defaults to '0'. short (:obj:`str`): The optional abbreviated name for a field. See hr.py for values. name (:obj:`str`): The optional name for a field. See hr.py for values. value (:obj:`str`): The field data (required). is_parent (:obj:`bool`): Set to True if the field value has sub-items (dicts/lists). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ # TODO: so ugly output = '{0}{1}{2}{3}{4}{5}{6}{7}\n'.format( LINES['{0}{1}'.format(line, 'C' if colorize else '')] if ( line in LINES.keys()) else '', COLOR_DEPTH[line] if (colorize and line in COLOR_DEPTH) else '', ANSI['b'], short if short is not None else ( name if (name is not None) else '' ), '' if (name is None or short is None) else ' ({0})'.format( name), '' if (name is None and short is None) else ': ', ANSI['end'] if colorize else '', '' if is_parent else value ) return output class IPWhoisCLI: """ The CLI wrapper class for outputting formatted IPWhois results. Args: addr (:obj:`str`/:obj:`int`/:obj:`IPv4Address`/:obj:`IPv6Address`): An IPv4 or IPv6 address timeout (:obj:`int`): The default timeout for socket connections in seconds. Defaults to 5. proxy_http (:obj:`urllib.request.OpenerDirector`): The request for proxy HTTP support or None. proxy_https (:obj:`urllib.request.OpenerDirector`): The request for proxy HTTPS support or None. """ def __init__( self, addr, timeout, proxy_http, proxy_https ): self.addr = addr self.timeout = timeout handler_dict = None if proxy_http is not None: handler_dict = {'http': proxy_http} if proxy_https is not None: if handler_dict is None: handler_dict = {'https': proxy_https} else: handler_dict['https'] = proxy_https if handler_dict is None: self.opener = None else: handler = ProxyHandler(handler_dict) self.opener = build_opener(handler) self.obj = IPWhois(address=self.addr, timeout=self.timeout, proxy_opener=self.opener) def generate_output_header(self, query_type='RDAP'): """ The function for generating the CLI output header. Args: query_type (:obj:`str`): The IPWhois query type. Defaults to 'RDAP'. Returns: str: The generated output. """ output = '\n{0}{1}{2} query for {3}:{4}\n\n'.format( ANSI['ul'], ANSI['b'], query_type, self.obj.address_str, ANSI['end'] ) return output def generate_output_newline(self, line='0', colorize=True): """ The function for generating a CLI output new line. Args: line (:obj:`str`): The line number (0-4). Determines indentation. Defaults to '0'. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ return generate_output( line=line, is_parent=True, colorize=colorize ) def generate_output_asn(self, json_data=None, hr=True, show_name=False, colorize=True): """ The function for generating CLI output ASN results. Args: json_data (:obj:`dict`): The data to process. Defaults to None. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ if json_data is None: json_data = {} keys = {'asn', 'asn_cidr', 'asn_country_code', 'asn_date', 'asn_registry', 'asn_description'}.intersection(json_data) output = '' for key in keys: output += generate_output( line='0', short=HR_ASN[key]['_short'] if hr else key, name=HR_ASN[key]['_name'] if (hr and show_name) else None, value=(json_data[key] if ( json_data[key] is not None and len(json_data[key]) > 0 and json_data[key] != 'NA') else 'None'), colorize=colorize ) return output def generate_output_entities(self, json_data=None, hr=True, show_name=False, colorize=True): """ The function for generating CLI output RDAP entity results. Args: json_data (:obj:`dict`): The data to process. Defaults to None. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ output = '' short = HR_RDAP['entities']['_short'] if hr else 'entities' name = HR_RDAP['entities']['_name'] if (hr and show_name) else None output += generate_output( line='0', short=short, name=name, is_parent=False if (json_data is None or json_data['entities'] is None) else True, value='None' if (json_data is None or json_data['entities'] is None) else None, colorize=colorize ) if json_data is not None: for ent in json_data['entities']: output += generate_output( line='1', value=ent, colorize=colorize ) return output def generate_output_events(self, source, key, val, line='2', hr=True, show_name=False, colorize=True): """ The function for generating CLI output RDAP events results. Args: source (:obj:`str`): The parent key 'network' or 'objects' (required). key (:obj:`str`): The event key 'events' or 'events_actor' (required). val (:obj:`dict`): The event dictionary (required). line (:obj:`str`): The line number (0-4). Determines indentation. Defaults to '0'. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ output = generate_output( line=line, short=HR_RDAP[source][key]['_short'] if hr else key, name=HR_RDAP[source][key]['_name'] if (hr and show_name) else None, is_parent=False if (val is None or len(val) == 0) else True, value='None' if (val is None or len(val) == 0) else None, colorize=colorize ) if val is not None: count = 0 for item in val: try: action = item['action'] except KeyError: action = None try: timestamp = item['timestamp'] except KeyError: timestamp = None try: actor = item['actor'] except KeyError: actor = None if count > 0: output += generate_output( line=str(int(line)+1), is_parent=True, colorize=colorize ) output += generate_output( line=str(int(line)+1), short=HR_RDAP_COMMON[key]['action'][ '_short'] if hr else 'action', name=HR_RDAP_COMMON[key]['action'][ '_name'] if (hr and show_name) else None, value=action, colorize=colorize ) output += generate_output( line=str(int(line)+1), short=HR_RDAP_COMMON[key]['timestamp'][ '_short'] if hr else 'timestamp', name=HR_RDAP_COMMON[key]['timestamp'][ '_name'] if (hr and show_name) else None, value=timestamp, colorize=colorize ) output += generate_output( line=str(int(line)+1), short=HR_RDAP_COMMON[key]['actor'][ '_short'] if hr else 'actor', name=HR_RDAP_COMMON[key]['actor'][ '_name'] if (hr and show_name) else None, value=actor, colorize=colorize ) count += 1 return output def generate_output_list(self, source, key, val, line='2', hr=True, show_name=False, colorize=True): """ The function for generating CLI output RDAP list results. Args: source (:obj:`str`): The parent key 'network' or 'objects' (required). key (:obj:`str`): The event key 'events' or 'events_actor' (required). val (:obj:`dict`): The event dictionary (required). line (:obj:`str`): The line number (0-4). Determines indentation. Defaults to '0'. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ output = generate_output( line=line, short=HR_RDAP[source][key]['_short'] if hr else key, name=HR_RDAP[source][key]['_name'] if (hr and show_name) else None, is_parent=False if (val is None or len(val) == 0) else True, value='None' if (val is None or len(val) == 0) else None, colorize=colorize ) if val is not None: for item in val: output += generate_output( line=str(int(line)+1), value=item, colorize=colorize ) return output def generate_output_notices(self, source, key, val, line='1', hr=True, show_name=False, colorize=True): """ The function for generating CLI output RDAP notices results. Args: source (:obj:`str`): The parent key 'network' or 'objects' (required). key (:obj:`str`): The event key 'events' or 'events_actor' (required). val (:obj:`dict`): The event dictionary (required). line (:obj:`str`): The line number (0-4). Determines indentation. Defaults to '0'. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ output = generate_output( line=line, short=HR_RDAP[source][key]['_short'] if hr else key, name=HR_RDAP[source][key]['_name'] if (hr and show_name) else None, is_parent=False if (val is None or len(val) == 0) else True, value='None' if (val is None or len(val) == 0) else None, colorize=colorize ) if val is not None: count = 0 for item in val: title = item['title'] description = item['description'] links = item['links'] if count > 0: output += generate_output( line=str(int(line)+1), is_parent=True, colorize=colorize ) output += generate_output( line=str(int(line)+1), short=HR_RDAP_COMMON[key]['title']['_short'] if hr else ( 'title'), name=HR_RDAP_COMMON[key]['title']['_name'] if ( hr and show_name) else None, value=title, colorize=colorize ) output += generate_output( line=str(int(line)+1), short=HR_RDAP_COMMON[key]['description'][ '_short'] if hr else 'description', name=HR_RDAP_COMMON[key]['description'][ '_name'] if (hr and show_name) else None, value=description.replace( '\n', '\n{0}'.format(generate_output(line='3')) ), colorize=colorize ) output += self.generate_output_list( source=source, key='links', val=links, line=str(int(line)+1), hr=hr, show_name=show_name, colorize=colorize ) count += 1 return output def generate_output_network(self, json_data=None, hr=True, show_name=False, colorize=True): """ The function for generating CLI output RDAP network results. Args: json_data (:obj:`dict`): The data to process. Defaults to None. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ if json_data is None: json_data = {} output = generate_output( line='0', short=HR_RDAP['network']['_short'] if hr else 'network', name=HR_RDAP['network']['_name'] if (hr and show_name) else None, is_parent=True, colorize=colorize ) for key, val in json_data['network'].items(): if key in ['links', 'status']: output += self.generate_output_list( source='network', key=key, val=val, line='1', hr=hr, show_name=show_name, colorize=colorize ) elif key in ['notices', 'remarks']: output += self.generate_output_notices( source='network', key=key, val=val, line='1', hr=hr, show_name=show_name, colorize=colorize ) elif key == 'events': output += self.generate_output_events( source='network', key=key, val=val, line='1', hr=hr, show_name=show_name, colorize=colorize ) elif key not in ['raw']: output += generate_output( line='1', short=HR_RDAP['network'][key]['_short'] if hr else key, name=HR_RDAP['network'][key]['_name'] if ( hr and show_name) else None, value=val, colorize=colorize ) return output def generate_output_objects(self, json_data=None, hr=True, show_name=False, colorize=True): """ The function for generating CLI output RDAP object results. Args: json_data (:obj:`dict`): The data to process. Defaults to None. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ if json_data is None: json_data = {} output = generate_output( line='0', short=HR_RDAP['objects']['_short'] if hr else 'objects', name=HR_RDAP['objects']['_name'] if (hr and show_name) else None, is_parent=True, colorize=colorize ) count = 0 for obj_name, obj in json_data['objects'].items(): if count > 0: output += self.generate_output_newline( line='1', colorize=colorize ) count += 1 output += generate_output( line='1', short=obj_name, is_parent=True, colorize=colorize ) for key, val in obj.items(): if key in ['links', 'entities', 'roles', 'status']: output += self.generate_output_list( source='objects', key=key, val=val, line='2', hr=hr, show_name=show_name, colorize=colorize ) elif key in ['notices', 'remarks']: output += self.generate_output_notices( source='objects', key=key, val=val, line='2', hr=hr, show_name=show_name, colorize=colorize ) elif key == 'events': output += self.generate_output_events( source='objects', key=key, val=val, line='2', hr=hr, show_name=show_name, colorize=colorize ) elif key == 'contact': output += generate_output( line='2', short=HR_RDAP['objects']['contact'][ '_short'] if hr else 'contact', name=HR_RDAP['objects']['contact']['_name'] if ( hr and show_name) else None, is_parent=False if (val is None or len(val) == 0) else True, value='None' if (val is None or len(val) == 0) else None, colorize=colorize ) if val is not None: for k, v in val.items(): if k in ['phone', 'address', 'email']: output += generate_output( line='3', short=HR_RDAP['objects']['contact'][k][ '_short'] if hr else k, name=HR_RDAP['objects']['contact'][k][ '_name'] if ( hr and show_name) else None, is_parent=False if ( val is None or len(val) == 0 ) else True, value='None' if (val is None or len(val) == 0) else None, colorize=colorize ) if v is not None: for item in v: i_type = ', '.join(item['type']) if ( isinstance(item['type'], list) ) else item['type'] i_type = i_type if ( i_type is not None and len(i_type) > 0) else '' i_value = item['value'].replace( '\n', '\n{0}'.format( generate_output( line='4', is_parent=True, colorize=colorize ).replace('\n', '')) ) tmp_out = '{0}{1}{2}'.format( i_type, ': ' if i_type != '' else '', i_value ) output += generate_output( line='4', value=tmp_out, colorize=colorize ) else: output += generate_output( line='3', short=HR_RDAP['objects']['contact'][k][ '_short'] if hr else k, name=HR_RDAP['objects']['contact'][k][ '_name'] if ( hr and show_name) else None, value=v, colorize=colorize ) elif key not in ['raw']: output += generate_output( line='2', short=HR_RDAP['objects'][key]['_short'] if hr else key, name=HR_RDAP['objects'][key]['_name'] if ( hr and show_name) else None, value=val, colorize=colorize ) return output def lookup_rdap(self, hr=True, show_name=False, colorize=True, **kwargs): """ The function for wrapping IPWhois.lookup_rdap() and generating formatted CLI output. Args: hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. kwargs: Arguments to pass to IPWhois.lookup_rdap(). Returns: str: The generated output. """ # Perform the RDAP lookup ret = self.obj.lookup_rdap(**kwargs) if script_args.json: output = json.dumps(ret) else: # Header output = self.generate_output_header(query_type='RDAP') # ASN output += self.generate_output_asn( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) # Entities output += self.generate_output_entities( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) # Network output += self.generate_output_network( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) # Objects output += self.generate_output_objects( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) if 'nir' in ret: # NIR output += self.generate_output_nir( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) return output def generate_output_whois_nets(self, json_data=None, hr=True, show_name=False, colorize=True): """ The function for generating CLI output Legacy Whois networks results. Args: json_data (:obj:`dict`): The data to process. Defaults to None. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ if json_data is None: json_data = {} output = generate_output( line='0', short=HR_WHOIS['nets']['_short'] if hr else 'nets', name=HR_WHOIS['nets']['_name'] if (hr and show_name) else None, is_parent=True, colorize=colorize ) count = 0 for net in json_data['nets']: if count > 0: output += self.generate_output_newline( line='1', colorize=colorize ) count += 1 output += generate_output( line='1', short=net['handle'], is_parent=True, colorize=colorize ) for key, val in net.items(): if val and '\n' in val: output += generate_output( line='2', short=HR_WHOIS['nets'][key]['_short'] if hr else key, name=HR_WHOIS['nets'][key]['_name'] if ( hr and show_name) else None, is_parent=False if (val is None or len(val) == 0) else True, value='None' if (val is None or len(val) == 0) else None, colorize=colorize ) for v in val.split('\n'): output += generate_output( line='3', value=v, colorize=colorize ) else: output += generate_output( line='2', short=HR_WHOIS['nets'][key]['_short'] if hr else key, name=HR_WHOIS['nets'][key]['_name'] if ( hr and show_name) else None, value=val, colorize=colorize ) return output def generate_output_whois_referral(self, json_data=None, hr=True, show_name=False, colorize=True): """ The function for generating CLI output Legacy Whois referral results. Args: json_data (:obj:`dict`): The data to process. Defaults to None. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ if json_data is None: json_data = {} output = generate_output( line='0', short=HR_WHOIS['referral']['_short'] if hr else 'referral', name=HR_WHOIS['referral']['_name'] if (hr and show_name) else None, is_parent=False if json_data['referral'] is None else True, value='None' if json_data['referral'] is None else None, colorize=colorize ) if json_data['referral']: for key, val in json_data['referral'].items(): if val and '\n' in val: output += generate_output( line='1', short=HR_WHOIS['nets'][key]['_short'] if hr else key, name=HR_WHOIS['nets'][key]['_name'] if ( hr and show_name) else None, is_parent=False if (val is None or len(val) == 0) else True, value='None' if (val is None or len(val) == 0) else None, colorize=colorize ) for v in val.split('\n'): output += generate_output( line='2', value=v, colorize=colorize ) else: output += generate_output( line='1', short=HR_WHOIS['nets'][key]['_short'] if hr else key, name=HR_WHOIS['nets'][key]['_name'] if ( hr and show_name) else None, value=val, colorize=colorize ) return output def generate_output_nir(self, json_data=None, hr=True, show_name=False, colorize=True): """ The function for generating CLI output NIR network results. Args: json_data (:obj:`dict`): The data to process. Defaults to None. hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. Returns: str: The generated output. """ if json_data is None: json_data = {} output = generate_output( line='0', short=HR_WHOIS_NIR['nets']['_short'] if hr else 'nir_nets', name=HR_WHOIS_NIR['nets']['_name'] if (hr and show_name) else None, is_parent=True, colorize=colorize ) count = 0 if json_data['nir']: for net in json_data['nir']['nets']: if count > 0: output += self.generate_output_newline( line='1', colorize=colorize ) count += 1 output += generate_output( line='1', short=net['handle'], is_parent=True, colorize=colorize ) for key, val in net.items(): if val and (isinstance(val, dict) or '\n' in val or key == 'nameservers'): output += generate_output( line='2', short=( HR_WHOIS_NIR['nets'][key]['_short'] if ( hr) else key ), name=HR_WHOIS_NIR['nets'][key]['_name'] if ( hr and show_name) else None, is_parent=False if (val is None or len(val) == 0) else True, value='None' if (val is None or len(val) == 0) else None, colorize=colorize ) if key == 'contacts': for k, v in val.items(): if v: output += generate_output( line='3', is_parent=False if ( len(v) == 0) else True, name=k, colorize=colorize ) for contact_key, contact_val in v.items(): if v is not None: tmp_out = '{0}{1}{2}'.format( contact_key, ': ', contact_val ) output += generate_output( line='4', value=tmp_out, colorize=colorize ) elif key == 'nameservers': for v in val: output += generate_output( line='3', value=v, colorize=colorize ) else: for v in val.split('\n'): output += generate_output( line='3', value=v, colorize=colorize ) else: output += generate_output( line='2', short=( HR_WHOIS_NIR['nets'][key]['_short'] if ( hr) else key ), name=HR_WHOIS_NIR['nets'][key]['_name'] if ( hr and show_name) else None, value=val, colorize=colorize ) else: output += 'None' return output def lookup_whois(self, hr=True, show_name=False, colorize=True, **kwargs): """ The function for wrapping IPWhois.lookup_whois() and generating formatted CLI output. Args: hr (:obj:`bool`): Enable human readable key translations. Defaults to True. show_name (:obj:`bool`): Show human readable name (default is to only show short). Defaults to False. colorize (:obj:`bool`): Colorize the console output with ANSI colors. Defaults to True. kwargs: Arguments to pass to IPWhois.lookup_whois(). Returns: str: The generated output. """ # Perform the RDAP lookup ret = self.obj.lookup_whois(**kwargs) if script_args.json: output = json.dumps(ret) else: # Header output = self.generate_output_header(query_type='Legacy Whois') # ASN output += self.generate_output_asn( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) # Network output += self.generate_output_whois_nets( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) # Referral output += self.generate_output_whois_referral( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) if 'nir' in ret: # NIR output += self.generate_output_nir( json_data=ret, hr=hr, show_name=show_name, colorize=colorize ) output += self.generate_output_newline(colorize=colorize) return output if script_args.addr: results = IPWhoisCLI( addr=script_args.addr[0], timeout=script_args.timeout, proxy_http=script_args.proxy_http if ( script_args.proxy_http and len(script_args.proxy_http) > 0 ) else None, proxy_https=script_args.proxy_https if ( script_args.proxy_https and len(script_args.proxy_https) > 0 ) else None ) if script_args.whois: print(results.lookup_whois( hr=script_args.hr, show_name=script_args.show_name, colorize=script_args.colorize, inc_raw=script_args.inc_raw, retry_count=script_args.retry_count, get_referral=script_args.get_referral, extra_blacklist=script_args.extra_blacklist.split(',') if ( script_args.extra_blacklist and len(script_args.extra_blacklist) > 0) else None, ignore_referral_errors=script_args.ignore_referral_errors, field_list=script_args.field_list.split(',') if ( script_args.field_list and len(script_args.field_list) > 0) else None, asn_alts=script_args.asn_alts.split(',') if ( script_args.asn_alts and not script_args.asn_methods and len(script_args.asn_alts) > 0) else None, extra_org_map=script_args.extra_org_map, inc_nir=(not script_args.exclude_nir), nir_field_list=script_args.nir_field_list.split(',') if ( script_args.nir_field_list and len(script_args.nir_field_list) > 0) else None, asn_methods=script_args.asn_methods.split(',') if ( script_args.asn_methods and len(script_args.asn_methods) > 0) else None, get_asn_description=(not script_args.skip_asn_description) )) else: print(results.lookup_rdap( hr=script_args.hr, show_name=script_args.show_name, colorize=script_args.colorize, inc_raw=script_args.inc_raw, retry_count=script_args.retry_count, depth=script_args.depth, excluded_entities=script_args.excluded_entities.split(',') if ( script_args.excluded_entities and len(script_args.excluded_entities) > 0) else None, bootstrap=script_args.bootstrap, rate_limit_timeout=script_args.rate_limit_timeout, asn_alts=script_args.asn_alts.split(',') if ( script_args.asn_alts and not script_args.asn_methods and len(script_args.asn_alts) > 0) else None, extra_org_map=script_args.extra_org_map, inc_nir=(not script_args.exclude_nir), nir_field_list=script_args.nir_field_list.split(',') if ( script_args.nir_field_list and len(script_args.nir_field_list) > 0) else None, asn_methods=script_args.asn_methods.split(',') if ( script_args.asn_methods and len(script_args.asn_methods) > 0) else None, get_asn_description=(not script_args.skip_asn_description) ))