Merge pull request #60 from iamOgunyinka/flat

Add _block:_ and _blocker_ features as per #52 (flat implementation)
This commit is contained in:
Michael Skelton
2019-08-19 21:56:39 +10:00
committed by GitHub
8 changed files with 224 additions and 175 deletions

12
.github/FUNDING.yml vendored
View File

@@ -1,12 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: PayPal.Me/codingo

View File

@@ -1,16 +1,17 @@
#!/usr/bin/python3
import sys
from Interlace.lib.core.input import InputParser, InputHelper
from Interlace.lib.core.output import OutputHelper, Level
from Interlace.lib.threader import Pool
def build_queue(arguments, output):
queue = list()
for command in InputHelper.process_commands(arguments):
output.terminal(Level.THREAD, command, "Added to Queue")
queue.append(command)
return queue
task_list = InputHelper.process_commands(arguments)
for task in task_list:
output.terminal(Level.THREAD, task.name(), "Added to Queue")
return task_list
def main():

View File

@@ -1,12 +1,12 @@
from argparse import ArgumentParser
from netaddr import IPNetwork, IPRange, IPGlob
from Interlace.lib.core.output import OutputHelper, Level
import os.path
from os import access, W_OK
import sys
from re import compile
from random import sample, choice
from argparse import ArgumentParser
from math import ceil
from random import sample, choice
from netaddr import IPNetwork, IPRange, IPGlob
from Interlace.lib.threader import Task
class InputHelper(object):
@@ -78,83 +78,134 @@ class InputHelper(object):
return ips
@staticmethod
def _replace_variable_for_commands(commands, variable, replacements):
tmp_commands = set()
def _process_port(port_type):
if "," in port_type:
return port_type.split(",")
elif "-" in port_type:
tmp = port_type.split("-")
begin_range = int(tmp[0])
end_range = int(tmp[1])
if begin_range >= end_range:
raise Exception("Invalid range provided")
return list(range(begin_range, end_range + 1))
return [port_type]
test = list()
@staticmethod
def _pre_process_commands(command_list, task_name, is_global_task=True):
"""
:param command_list:
:param task_name: all tasks have 'scope' and all scopes have unique names, global scope defaults ''
:param is_global_task: when True, signifies that all global tasks are meant to be run concurrently
:return: list of possibly re-adjusted commands
"""
task_block = []
sibling = None
global_task = None
for command in command_list:
command = str(command).strip()
if not command:
continue
# the start or end of a command block
if command.startswith('_block:') and command.endswith('_'):
new_task_name = command.split('_block:')[1][:-1].strip()
# if this is the end of a block, then we're done
if task_name == new_task_name:
return task_block
# otherwise pre-process all the commands in this new `new_task_name` block
for task in InputHelper._pre_process_commands(command_list, new_task_name, False):
task_block.append(task)
sibling = task
continue
else:
# if a blocker is encountered, all commands following the blocker must wait until the last
# command in the block is executed. All block commands are synchronous
if command == '_blocker_':
global_task = sibling
continue
task = Task(command)
# if we're in the global scope and there was a previous _blocker_ encountered, we wait for the last
# child of the block
if is_global_task and global_task:
task.wait_for(global_task.get_lock())
# all but the first command in a block scope wait for its predecessor
elif sibling and not is_global_task:
task.wait_for(sibling.get_lock())
task_block.append(task)
sibling = task
return task_block
for replacement in replacements:
for command in commands:
test.append(str(command).replace(variable, str(replacement)))
@staticmethod
def _pre_process_hosts(host_ranges, destination_set, arguments):
for host in host_ranges:
host = host.replace(" ", "")
for ips in host.split(","):
# check if it is a domain name
if ips.split(".")[-1][0].isalpha():
destination_set.add(ips)
continue
# checking for CIDR
if not arguments.nocidr and "/" in ips:
destination_set.update(InputHelper._get_cidr_to_ips(ips))
# checking for IPs in a range
elif "-" in ips:
destination_set.update(InputHelper._get_ips_from_range(ips))
# checking for glob ranges
elif "*" in ips:
destination_set.update(InputHelper._get_ips_from_glob(ips))
else:
destination_set.add(ips)
@staticmethod
def _replace_variable_with_commands(commands, variable, replacements):
def add_task(t, item_list):
if t not in set(item_list):
item_list.append(t)
tasks = []
for command in commands:
for replacement in replacements:
if command.name().find(variable) != -1:
new_task = command.clone()
new_task.replace(variable, replacement)
add_task(new_task, tasks)
else:
add_task(command, tasks)
return tasks
tmp_commands.update(test)
return tmp_commands
@staticmethod
def _replace_variable_array(commands, variable, replacement):
tmp_commands = set()
counter = 0
test = list()
if not variable in sample(commands, 1)[0]:
return commands
for command in commands:
test.append(str(command).replace(variable, str(replacement[counter])))
counter += 1
tmp_commands.update(test)
return tmp_commands
if variable not in sample(commands, 1)[0]:
return
for counter, command in enumerate(commands):
command.replace(variable, str(replacement[counter]))
@staticmethod
def process_commands(arguments):
commands = set()
commands = list()
ranges = set()
targets = set()
exclusions_ranges = set()
exclusions = set()
final_commands = set()
output = OutputHelper(arguments)
# removing the trailing slash if any
if arguments.output:
if arguments.output[-1] == "/":
arguments.output = arguments.output[:-1]
if arguments.output and arguments.output[-1] == "/":
arguments.output = arguments.output[:-1]
if arguments.port:
if "," in arguments.port:
ports = arguments.port.split(",")
elif "-" in arguments.port:
tmp_ports = arguments.port.split("-")
if int(tmp_ports[0]) >= int(tmp_ports[1]):
raise Exception("Invalid range provided")
ports = list(range(int(tmp_ports[0]), int(tmp_ports[1]) + 1))
else:
ports = [arguments.port]
ports = InputHelper._process_port(arguments.port)
if arguments.realport:
if "," in arguments.realport:
real_ports = arguments.realport.split(",")
elif "-" in arguments.realport:
tmp_ports = arguments.realport.split("-")
if int(tmp_ports[0]) >= int(tmp_ports[1]):
raise Exception("Invalid range provided")
real_ports = list(range(int(tmp_ports[0]), int(tmp_ports[1]) + 1))
else:
real_ports = [arguments.realport]
real_ports = InputHelper._process_port(arguments.realport)
# process targets first
if arguments.target:
ranges.add(arguments.target)
else:
targetFile = arguments.target_list
target_file = arguments.target_list
if not sys.stdin.isatty():
targetFile = sys.stdin
for target in targetFile:
if target.strip():
ranges.add(target.strip())
target_file = sys.stdin
ranges.update([target.strip() for target in target_file if target.strip()])
# process exclusions first
if arguments.exclusions:
@@ -162,50 +213,13 @@ class InputHelper(object):
else:
if arguments.exclusions_list:
for exclusion in arguments.exclusions_list:
exclusions_ranges.add(target.strip())
exclusion = exclusion.strip()
if exclusion:
exclusions.add(exclusion)
# removing elements that may have spaces (helpful for easily processing comma notation)
for target in ranges:
target = target.replace(" ", "")
for ips in target.split(","):
# check if it is a domain name
if ips.split(".")[-1][0].isalpha():
targets.add(ips)
continue
# checking for CIDR
if not arguments.nocidr and "/" in ips:
targets.update(InputHelper._get_cidr_to_ips(ips))
# checking for IPs in a range
elif "-" in ips:
targets.update(InputHelper._get_ips_from_range(ips))
# checking for glob ranges
elif "*" in ips:
targets.update(InputHelper._get_ips_from_glob(ips))
else:
targets.add(ips)
# removing elements that may have spaces (helpful for easily processing comma notation)
for exclusion in exclusions_ranges:
exclusion = exclusion.replace(" ", "")
for ips in exclusion.split(","):
# check if it is a domain name
if ips.split(".")[-1][0].isalpha():
targets.add(ips)
continue
# checking for CIDR
if not arguments.nocidr and "/" in ips:
exclusions.update(InputHelper._get_cidr_to_ips(ips))
# checking for IPs in a range
elif "-" in ips:
exclusions.update(InputHelper._get_ips_from_range(ips))
# checking for glob ranges
elif "*" in ips:
exclusions.update(InputHelper._get_ips_from_glob(ips))
else:
exclusions.add(ips)
InputHelper._pre_process_hosts(ranges, targets, arguments)
InputHelper._pre_process_hosts(exclusions_ranges, exclusions, arguments)
# difference operation
targets -= exclusions
@@ -218,46 +232,40 @@ class InputHelper(object):
random_file = choice(files)
if arguments.command:
commands.add(arguments.command.rstrip('\n'))
commands.append(arguments.command.rstrip('\n'))
else:
for command in arguments.command_list:
commands.add(command.rstrip('\n'))
commands = InputHelper._pre_process_commands(arguments.command_list, '')
final_commands = InputHelper._replace_variable_for_commands(commands, "_target_", targets)
final_commands = InputHelper._replace_variable_for_commands(final_commands, "_host_", targets)
commands = InputHelper._replace_variable_with_commands(commands, "_target_", targets)
commands = InputHelper._replace_variable_with_commands(commands, "_host_", targets)
if arguments.port:
final_commands = InputHelper._replace_variable_for_commands(final_commands, "_port_", ports)
commands = InputHelper._replace_variable_with_commands(commands, "_port_", ports)
if arguments.realport:
final_commands = InputHelper._replace_variable_for_commands(final_commands, "_realport_", real_ports)
commands = InputHelper._replace_variable_with_commands(commands, "_realport_", real_ports)
if arguments.random:
final_commands = InputHelper._replace_variable_for_commands(final_commands, "_random_", [random_file])
commands = InputHelper._replace_variable_with_commands(commands, "_random_", [random_file])
if arguments.output:
final_commands = InputHelper._replace_variable_for_commands(final_commands, "_output_", [arguments.output])
commands = InputHelper._replace_variable_with_commands(commands, "_output_", [arguments.output])
if arguments.proto:
if "," in arguments.proto:
protocols = arguments.proto.split(",")
else:
protocols = arguments.proto
final_commands = InputHelper._replace_variable_for_commands(final_commands, "_proto_", protocols)
commands = InputHelper._replace_variable_with_commands(commands, "_proto_", protocols)
# process proxies
if arguments.proxy_list:
proxy_list = list()
for proxy in arguments.proxy_list:
if proxy.strip():
proxy_list.append(proxy.strip())
proxy_list = [proxy for proxy in arguments.proxy_list if proxy.strip()]
if len(proxy_list) < len(commands):
proxy_list = ceil(len(commands) / len(proxy_list)) * proxy_list
if len(proxy_list) < len(final_commands):
proxy_list = ceil(len(final_commands) / len(proxy_list)) * proxy_list
final_commands = InputHelper._replace_variable_array(final_commands, "_proxy_", proxy_list)
return final_commands
InputHelper._replace_variable_array(commands, "_proxy_", proxy_list)
return commands
class InputParser(object):

View File

@@ -1,7 +1,9 @@
from colorclass import Color
from colorclass import disable_all_colors, enable_all_colors, is_enabled
from time import localtime, strftime
from enum import IntEnum
from time import localtime, strftime
from colorclass import Color
from colorclass import disable_all_colors
from Interlace.lib.core.__version__ import __version__
@@ -40,7 +42,7 @@ class OutputHelper(object):
'target': target,
'command': command,
'message': message,
'leader':leader
'leader': leader
}
if not self.silent:

View File

@@ -1,12 +1,61 @@
import threading
import subprocess
import os
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Event
from tqdm import tqdm
class Task(object):
def __init__(self, command):
self.task = command
self.self_lock = None
self.sibling_lock = None
def __cmp__(self, other):
return self.name() == other.name()
def __hash__(self):
return self.task.__hash__()
def clone(self):
new_task = Task(self.task)
new_task.self_lock = self.self_lock
new_task.sibling_lock = self.sibling_lock
return new_task
def replace(self, old, new):
self.task = self.task.replace(old, new)
def run(self, t=False):
if self.sibling_lock:
self.sibling_lock.wait()
self._run_task(t)
if self.self_lock:
self.self_lock.set()
def wait_for(self, _lock):
self.sibling_lock = _lock
def name(self):
return self.task
def get_lock(self):
if not self.self_lock:
self.self_lock = Event()
self.self_lock.clear()
return self.self_lock
def _run_task(self, t=False):
if t:
s = subprocess.Popen(self.task, shell=True, stdout=subprocess.PIPE)
t.write(s.stdout.readline().decode("utf-8"))
else:
subprocess.Popen(self.task, shell=True)
class Worker(object):
def __init__(self, queue, timeout, output, tqdm):
self.queue = queue
def __init__(self, task_queue, timeout, output, tqdm):
self.queue = task_queue
self.timeout = timeout
self.output = output
self.tqdm = tqdm
@@ -19,24 +68,16 @@ class Worker(object):
if isinstance(self.tqdm, tqdm):
self.tqdm.update(1)
# run task
self.run_task(task, self.tqdm)
task.run(self.tqdm)
else:
self.run_task(task)
task.run()
except IndexError:
break
@staticmethod
def run_task(task, t=False):
if t:
s = subprocess.Popen(task, shell=True, stdout=subprocess.PIPE)
t.write(s.stdout.readline().decode("utf-8"))
else:
subprocess.Popen(task, shell=True)
class Pool(object):
def __init__(self, max_workers, queue, timeout, output, progress_bar):
def __init__(self, max_workers, task_queue, timeout, output, progress_bar):
# convert stdin input to integer
max_workers = int(max_workers)
@@ -45,34 +86,27 @@ class Pool(object):
raise ValueError("Workers must be >= 1")
# check if the queue is empty
if not queue:
if not task_queue:
raise ValueError("The queue is empty")
self.queue = queue
self.queue = task_queue
self.timeout = timeout
self.output = output
self.max_workers = max_workers
self.max_workers = min(len(task_queue), max_workers)
if not progress_bar:
self.tqdm = tqdm(total=len(queue))
self.tqdm = tqdm(total=len(task_queue))
else:
self.tqdm = True
def run(self):
workers = [Worker(self.queue, self.timeout, self.output, self.tqdm) for w in range(self.max_workers)]
threads = []
# run
for worker in workers:
thread = threading.Thread(target=worker)
thread.start()
threads.append(thread)
with ThreadPoolExecutor(self.max_workers) as executors:
for worker in workers:
executors.submit(worker)
# wait until all workers have completed their tasks
for thread in threads:
thread.join()
# test harness
if __name__ == "__main__":

2
bar.test Normal file
View File

@@ -0,0 +1,2 @@
localhost
localone

16
foo.test Normal file
View File

@@ -0,0 +1,16 @@
echo hello
_block:file-creation_
echo _target_
echo _target_/a/
echo _target_/output/scans
_block:file-creation_
echo "Doing this"
_blocker_
echo "Proceeding"
_block:file_
echo _target_/out/scans
echo _target_/b/
echo _target_/re/
_block:file_
_blocker_
echo "Done"

View File

@@ -10,7 +10,7 @@ def dependencies(imported_file):
with open("README.md") as file:
num_installed = False
num_installed = True
try:
import numpy
num_installed = True
@@ -35,7 +35,5 @@ with open("README.md") as file:
]
},
install_requires=dependencies('requirements.txt'),
setup_requires=['pytest-runner',
'' if num_installed else 'numpy==1.16.0'],
tests_require=dependencies('test-requirements.txt'),
include_package_data=True)