''' Helper methods used across the CLI commands. ''' import click import datetime import gzip import itertools import os import sys from ipaddress import ip_network, ip_address from .settings import SHODAN_CONFIG_DIR try: basestring # Python 2 except NameError: basestring = (str, ) # Python 3 def get_api_key(): '''Returns the API key of the current logged-in user.''' shodan_dir = os.path.expanduser(SHODAN_CONFIG_DIR) keyfile = shodan_dir + '/api_key' # If the file doesn't yet exist let the user know that they need to # initialize the shodan cli if not os.path.exists(keyfile): raise click.ClickException('Please run "shodan init " before using this command') # Make sure it is a read-only file if not oct(os.stat(keyfile).st_mode).endswith("600"): os.chmod(keyfile, 0o600) with open(keyfile, 'r') as fin: return fin.read().strip() def escape_data(args): # Make sure the string is unicode so the terminal can properly display it # We do it using format() so it works across Python 2 and 3 args = u'{}'.format(args) return args.replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t') def timestr(): return datetime.datetime.utcnow().strftime('%Y-%m-%d') def open_streaming_file(directory, timestr, compresslevel=9): return gzip.open('{}/{}.json.gz'.format(directory, timestr), 'a', compresslevel) def get_banner_field(banner, flat_field): # The provided field is a collapsed form of the actual field fields = flat_field.split('.') try: current_obj = banner for field in fields: current_obj = current_obj[field] return current_obj except Exception: pass return None def filter_with_netmask(banner, netmask): # filtering based on netmask is a more abstract concept than # a mere check for a specific field and thus needs its own mechanism # this will enable users to use the net:10.0.0.0/8 syntax they are used to # to find specific networks from a big shodan download. network = ip_network(netmask) ip_field = get_banner_field(banner, 'ip') if not ip_field: return False banner_ip_address = ip_address(ip_field) return banner_ip_address in network def match_filters(banner, filters): for args in filters: flat_field, check = args.split(':', 1) if flat_field == 'net': return filter_with_netmask(banner, check) value = get_banner_field(banner, flat_field) # If the field doesn't exist on the banner then ignore the record if not value: return False # It must match all filters to be allowed field_type = type(value) # For lists of strings we see whether the desired value is contained in the field if field_type == list or isinstance(value, basestring): if check not in value: return False elif field_type == int: if int(check) != value: return False elif field_type == float: if float(check) != value: return False else: # Ignore unknown types pass return True def async_spinner(finished): spinner = itertools.cycle(['-', '/', '|', '\\']) while not finished.is_set(): sys.stdout.write('\b{}'.format(next(spinner))) sys.stdout.flush() finished.wait(0.2) def humanize_api_plan(plan): return { 'oss': 'Free', 'dev': 'Membership', 'basic': 'Freelancer API', 'plus': 'Small Business API', 'corp': 'Corporate API', 'stream-100': 'Enterprise', }[plan]