From ee47cac4ab0f0bd9116ce925b20a78617d035649 Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Wed, 14 Aug 2019 20:28:35 +0100 Subject: [PATCH 01/10] Fixed issue 52 --- Interlace/interlace.py | 21 +++- Interlace/lib/core/input.py | 214 ++++++++++++++++------------------- Interlace/lib/core/output.py | 10 +- Interlace/lib/threader.py | 117 +++++++++++++++---- 4 files changed, 213 insertions(+), 149 deletions(-) diff --git a/Interlace/interlace.py b/Interlace/interlace.py index 4a0b75e..104b43b 100644 --- a/Interlace/interlace.py +++ b/Interlace/interlace.py @@ -2,15 +2,24 @@ import sys from Interlace.lib.core.input import InputParser, InputHelper from Interlace.lib.core.output import OutputHelper, Level -from Interlace.lib.threader import Pool +from Interlace.lib.threader import Pool, TaskBlock + + +def print_command(level, command, message, output): + if isinstance(command, TaskBlock): + for c in command: + print_command(level, c, message, output) + else: + output.terminal(Level.THREAD, command.name(), "Added to Queue") 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_queue = InputHelper.process_commands(arguments) + task_list = [] + for task in task_queue: + print_command(Level.THREAD, task, "Added to Queue", output) + task_list.append(task) + return task_list def main(): diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index 22823a3..e744845 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -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 +from argparse import ArgumentParser from math import ceil +from os import access, W_OK +from random import sample + +from netaddr import IPNetwork, IPRange, IPGlob +from Interlace.lib.threader import TaskBlock, Task class InputHelper(object): @@ -62,35 +62,83 @@ def _get_cidr_to_ips(cidr_range): 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): + task_block = TaskBlock(task_name) + parent_task = None + for command in command_list: + command = str(command).strip() + if not command: + continue + if command.startswith('_block:') and command.endswith('_'): + new_task_name = command.split('_block:')[1][:-1].strip() + if task_name == new_task_name: + return task_block + task = InputHelper._pre_process_commands(command_list, new_task_name) + else: + task = Task(command) + if command == '_blocker_': + parent_task = task_block.last() + parent_task.set_lock() + continue + if parent_task: + task.wait_for(parent_task.get_lock()) + task_block.add_task(task) + return task_block + + @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): for replacement in replacements: for command in commands: - test.append(str(command).replace(variable, str(replacement))) + if isinstance(command, TaskBlock): + InputHelper._replace_variable_with_commands(command, variable, replacements) + else: + command.replace(variable, str(replacement)) - 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 + # TODO + if variable not in sample(commands, 1)[0]: + return + for counter, command in enumerate(commands): + if isinstance(command, TaskBlock): + InputHelper._replace_variable_array(command, variable, replacement) + else: + command.replace(variable, str(replacement[counter])) @staticmethod def process_commands(arguments): @@ -99,98 +147,37 @@ def process_commands(arguments): targets = set() exclusions_ranges = set() exclusions = set() - final_commands = set() - output = OutputHelper(arguments) - # checking for whether output is writable and whether it exists + # checking if output is writable and exists if arguments.output: if not access(arguments.output, W_OK): raise Exception("Directory provided isn't writable") 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 for target in target_file if target.strip()]) # process exclusions first if arguments.exclusions: exclusions_ranges.add(arguments.exclusions) else: if arguments.exclusions_list: - for exclusion in arguments.exclusions_list: - exclusions_ranges.add(target.strip()) + exclusions_ranges.update([exclusion for exclusion in arguments.exclusions_list if exclusion.strip()]) # 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 @@ -201,41 +188,36 @@ def process_commands(arguments): if arguments.command: commands.add(arguments.command.rstrip('\n')) else: - for command in arguments.command_list: - commands.add(command.rstrip('\n')) + tasks = InputHelper._pre_process_commands(arguments.command_list, '') + commands.update(tasks.get_tasks()) - final_commands = InputHelper._replace_variable_for_commands(commands, "_target_", targets) - final_commands = InputHelper._replace_variable_for_commands(final_commands, "_host_", targets) + InputHelper._replace_variable_with_commands(commands, "_target_", targets) + InputHelper._replace_variable_with_commands(commands, "_host_", targets) if arguments.port: - final_commands = InputHelper._replace_variable_for_commands(final_commands, "_port_", ports) + InputHelper._replace_variable_with_commands(commands, "_port_", ports) if arguments.realport: - final_commands = InputHelper._replace_variable_for_commands(final_commands, "_realport_", real_ports) + InputHelper._replace_variable_with_commands(commands, "_realport_", real_ports) if arguments.output: - final_commands = InputHelper._replace_variable_for_commands(final_commands, "_output_", [arguments.output]) + 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) - + 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()) - - 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) + 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 - return final_commands + InputHelper._replace_variable_array(commands, "_proxy_", proxy_list) + return commands class InputParser(object): diff --git a/Interlace/lib/core/output.py b/Interlace/lib/core/output.py index a5caf5d..9e5d46d 100644 --- a/Interlace/lib/core/output.py +++ b/Interlace/lib/core/output.py @@ -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 @@ def terminal(self, level, target, command, message=""): 'target': target, 'command': command, 'message': message, - 'leader':leader + 'leader': leader } if not self.silent: diff --git a/Interlace/lib/threader.py b/Interlace/lib/threader.py index f33b882..1378925 100644 --- a/Interlace/lib/threader.py +++ b/Interlace/lib/threader.py @@ -1,12 +1,92 @@ -import threading import subprocess -import os +from multiprocessing import Event +from threading import Thread + from tqdm import tqdm +class Task(object): + def __init__(self, command): + self.task = command + self._lock = None + self._waiting_for_task = False + + def __hash__(self): + return self.task.__hash__() + + def replace(self, old, new): + self.task = self.task.replace(old, new) + + def run(self, t=False): + if not self._waiting_for_task: + self._run_task(t) + if self._lock: + self._lock.set() + else: + self._lock.wait() + self._run_task(t) + + def wait_for(self, lock): + self._lock = lock + self._waiting_for_task = True + + def set_lock(self): + if not self._lock: + self._lock = Event() + self._lock.clear() + + def name(self): + return self.task + + def get_lock(self): + return 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 TaskBlock(Task): + def __init__(self, name): + super().__init__('') + self._name = name + self.tasks = [] + + def name(self): + return self._name + + def add_task(self, task): + self.tasks.append(task) + + def __len__(self): + return len(self.tasks) + + def __hash__(self): + hash_value = 0 + for t in self.tasks: + hash_value ^= t.__hash__() + return hash_value + + def __iter__(self): + return self.tasks.__iter__() + + def last(self): + return self.tasks[-1] + + def _run_task(self, t=False): + for task in self.tasks: + task._run_task(t) + + def get_tasks(self): + return self.tasks + + 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 +99,16 @@ def __call__(self): 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,28 +117,26 @@ def __init__(self, max_workers, queue, timeout, output, progress_bar): 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 = Thread(target=worker) thread.start() threads.append(thread) @@ -74,6 +144,7 @@ def run(self): for thread in threads: thread.join() + # test harness if __name__ == "__main__": tasks = ["sleep 1", From ab34aca3b2c7cff070e2c5194e8a6634fc9c3196 Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Fri, 16 Aug 2019 03:37:50 +0100 Subject: [PATCH 02/10] Testing implementation --- .github/FUNDING.yml | 12 ----------- .gitignore | 1 + Interlace/interlace.py | 2 +- Interlace/lib/core/input.py | 42 ++++++++++++++++++++++++------------- Interlace/lib/threader.py | 9 ++++++++ setup.py | 4 +--- 6 files changed, 40 insertions(+), 30 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 33a48a1..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -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 diff --git a/.gitignore b/.gitignore index 3ebf85d..884dd95 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ wheels/ *.egg-info/ .installed.cfg *.egg +*.test MANIFEST # PyInstaller diff --git a/Interlace/interlace.py b/Interlace/interlace.py index 104b43b..84a5953 100644 --- a/Interlace/interlace.py +++ b/Interlace/interlace.py @@ -31,7 +31,7 @@ def main(): output.print_banner() pool = Pool(arguments.threads, build_queue(arguments, output), arguments.timeout, output, arguments.sober) - pool.run() + # pool.run() if __name__ == "__main__": diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index e744845..b9c1a67 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -121,12 +121,23 @@ def _pre_process_hosts(host_ranges, destination_set, arguments): @staticmethod def _replace_variable_with_commands(commands, variable, replacements): - for replacement in replacements: - for command in commands: - if isinstance(command, TaskBlock): - InputHelper._replace_variable_with_commands(command, variable, replacements) - else: - command.replace(variable, str(replacement)) + data = set() + for command in commands: + is_task_block = isinstance(command, TaskBlock) + for replacement in replacements: + if not is_task_block and command.name().find(variable) != -1: + new_task = command.clone() + new_task.replace(variable, replacement) + data.add(new_task) + elif not is_task_block and command not in data: + data.add(command) + elif is_task_block: + new_task_block = TaskBlock(command.name()) + result = InputHelper._replace_variable_with_commands(command.get_tasks(), variable, replacements) + for r in result: + new_task_block.add_task(r) + data.add(new_task_block) + return set(data) @staticmethod def _replace_variable_array(commands, variable, replacement): @@ -166,14 +177,17 @@ def process_commands(arguments): target_file = arguments.target_list if not sys.stdin.isatty(): target_file = sys.stdin - ranges.update([target for target in target_file if target.strip()]) + ranges.update([target.strip() for target in target_file if target.strip()]) # process exclusions first if arguments.exclusions: exclusions_ranges.add(arguments.exclusions) else: if arguments.exclusions_list: - exclusions_ranges.update([exclusion for exclusion in arguments.exclusions_list if exclusion.strip()]) + for exclusion in arguments.exclusions_list: + exclusion = exclusion.strip() + if exclusion: + exclusions.add(exclusion) # removing elements that may have spaces (helpful for easily processing comma notation) InputHelper._pre_process_hosts(ranges, targets, arguments) @@ -191,24 +205,24 @@ def process_commands(arguments): tasks = InputHelper._pre_process_commands(arguments.command_list, '') commands.update(tasks.get_tasks()) - InputHelper._replace_variable_with_commands(commands, "_target_", targets) - InputHelper._replace_variable_with_commands(commands, "_host_", targets) + commands = InputHelper._replace_variable_with_commands(commands, "_target_", targets) + commands = InputHelper._replace_variable_with_commands(commands, "_host_", targets) if arguments.port: - InputHelper._replace_variable_with_commands(commands, "_port_", ports) + commands = InputHelper._replace_variable_with_commands(commands, "_port_", ports) if arguments.realport: - InputHelper._replace_variable_with_commands(commands, "_realport_", real_ports) + commands = InputHelper._replace_variable_with_commands(commands, "_realport_", real_ports) if arguments.output: - InputHelper._replace_variable_with_commands(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 - InputHelper._replace_variable_with_commands(commands, "_proto_", protocols) + commands = InputHelper._replace_variable_with_commands(commands, "_proto_", protocols) # process proxies if arguments.proxy_list: diff --git a/Interlace/lib/threader.py b/Interlace/lib/threader.py index 1378925..97abb39 100644 --- a/Interlace/lib/threader.py +++ b/Interlace/lib/threader.py @@ -11,9 +11,18 @@ def __init__(self, command): self._lock = None self._waiting_for_task = False + 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._lock = self._lock + new_task._waiting_for_task = self._waiting_for_task + return new_task + def replace(self, old, new): self.task = self.task.replace(old, new) diff --git a/setup.py b/setup.py index a8ace8b..00041d2 100644 --- a/setup.py +++ b/setup.py @@ -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 @@ def dependencies(imported_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) From d416e5a6892001f2304803f0dbdbff5d2d6d359c Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Fri, 16 Aug 2019 04:06:28 +0100 Subject: [PATCH 03/10] Fixed bug when using target_list --- Interlace/interlace.py | 2 +- Interlace/lib/core/input.py | 22 +++++++++++----------- Interlace/lib/threader.py | 3 +++ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Interlace/interlace.py b/Interlace/interlace.py index 84a5953..104b43b 100644 --- a/Interlace/interlace.py +++ b/Interlace/interlace.py @@ -31,7 +31,7 @@ def main(): output.print_banner() pool = Pool(arguments.threads, build_queue(arguments, output), arguments.timeout, output, arguments.sober) - # pool.run() + pool.run() if __name__ == "__main__": diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index b9c1a67..039d05a 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -123,20 +123,20 @@ def _pre_process_hosts(host_ranges, destination_set, arguments): def _replace_variable_with_commands(commands, variable, replacements): data = set() for command in commands: - is_task_block = isinstance(command, TaskBlock) + is_task = not isinstance(command, TaskBlock) for replacement in replacements: - if not is_task_block and command.name().find(variable) != -1: + if is_task and command.name().find(variable) != -1: new_task = command.clone() new_task.replace(variable, replacement) data.add(new_task) - elif not is_task_block and command not in data: + elif is_task and command not in data: + data.add(command) + elif not is_task: + tasks = [task for task in command.get_tasks()] + command.clear_tasks() + for r in InputHelper._replace_variable_with_commands(tasks, variable, replacements): + command.add_task(r) data.add(command) - elif is_task_block: - new_task_block = TaskBlock(command.name()) - result = InputHelper._replace_variable_with_commands(command.get_tasks(), variable, replacements) - for r in result: - new_task_block.add_task(r) - data.add(new_task_block) return set(data) @staticmethod @@ -175,8 +175,8 @@ def process_commands(arguments): ranges.add(arguments.target) else: target_file = arguments.target_list - if not sys.stdin.isatty(): - target_file = sys.stdin + # if not sys.stdin.isatty(): + # target_file = sys.stdin ranges.update([target.strip() for target in target_file if target.strip()]) # process exclusions first diff --git a/Interlace/lib/threader.py b/Interlace/lib/threader.py index 97abb39..a88cbf9 100644 --- a/Interlace/lib/threader.py +++ b/Interlace/lib/threader.py @@ -70,6 +70,9 @@ def name(self): def add_task(self, task): self.tasks.append(task) + def clear_tasks(self): + self.tasks.clear() + def __len__(self): return len(self.tasks) From 1c778c9b99a96da580e0ff86ab48dc141e708e95 Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Fri, 16 Aug 2019 05:06:45 +0100 Subject: [PATCH 04/10] Removed commented code introduced when debugging --- Interlace/lib/core/input.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index cbae85f..92f5313 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -173,8 +173,8 @@ def process_commands(arguments): ranges.add(arguments.target) else: target_file = arguments.target_list - # if not sys.stdin.isatty(): - # target_file = sys.stdin + if not sys.stdin.isatty(): + target_file = sys.stdin ranges.update([target.strip() for target in target_file if target.strip()]) # process exclusions first From 20af7466db02c732e1f84b087e37e1adc8f80dcd Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Fri, 16 Aug 2019 09:39:10 +0100 Subject: [PATCH 05/10] Implemented sequential command processing --- Interlace/interlace.py | 7 +++---- Interlace/lib/core/input.py | 29 +++++++++++++++++------------ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Interlace/interlace.py b/Interlace/interlace.py index 104b43b..a112786 100644 --- a/Interlace/interlace.py +++ b/Interlace/interlace.py @@ -1,5 +1,6 @@ #!/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, TaskBlock @@ -14,11 +15,9 @@ def print_command(level, command, message, output): def build_queue(arguments, output): - task_queue = InputHelper.process_commands(arguments) - task_list = [] - for task in task_queue: + task_list = InputHelper.process_commands(arguments) + for task in task_list: print_command(Level.THREAD, task, "Added to Queue", output) - task_list.append(task) return task_list diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index cbae85f..e6566fb 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -121,23 +121,28 @@ def _pre_process_hosts(host_ranges, destination_set, arguments): @staticmethod def _replace_variable_with_commands(commands, variable, replacements): - data = set() + foo = [] + + def add_task(t): + if t not in set(foo): + foo.append(t) + for command in commands: is_task = not isinstance(command, TaskBlock) for replacement in replacements: if is_task and command.name().find(variable) != -1: new_task = command.clone() new_task.replace(variable, replacement) - data.add(new_task) - elif is_task and command not in data: - data.add(command) + add_task(new_task) + elif is_task and command not in set(foo): + add_task(command) elif not is_task: tasks = [task for task in command.get_tasks()] command.clear_tasks() for r in InputHelper._replace_variable_with_commands(tasks, variable, replacements): command.add_task(r) - data.add(command) - return set(data) + add_task(command) + return foo @staticmethod def _replace_variable_array(commands, variable, replacement): @@ -153,13 +158,13 @@ def _replace_variable_array(commands, variable, replacement): @staticmethod def process_commands(arguments): - commands = set() + commands = list() ranges = set() targets = set() exclusions_ranges = set() exclusions = set() - if arguments.output[-1] == "/": + if arguments.output and arguments.output[-1] == "/": arguments.output = arguments.output[:-1] if arguments.port: @@ -173,8 +178,8 @@ def process_commands(arguments): ranges.add(arguments.target) else: target_file = arguments.target_list - # if not sys.stdin.isatty(): - # target_file = sys.stdin + if not sys.stdin.isatty(): + target_file = sys.stdin ranges.update([target.strip() for target in target_file if target.strip()]) # process exclusions first @@ -198,10 +203,10 @@ def process_commands(arguments): raise Exception("No target provided, or empty target list") if arguments.command: - commands.add(arguments.command.rstrip('\n')) + commands.append(arguments.command.rstrip('\n')) else: tasks = InputHelper._pre_process_commands(arguments.command_list, '') - commands.update(tasks.get_tasks()) + commands = tasks.get_tasks() commands = InputHelper._replace_variable_with_commands(commands, "_target_", targets) commands = InputHelper._replace_variable_with_commands(commands, "_host_", targets) From b9487fec9c326ddb9f9040f7f1d4988038df2d49 Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Fri, 16 Aug 2019 14:31:39 +0100 Subject: [PATCH 06/10] Flattened the commands hierarchy --- Interlace/interlace.py | 18 +++----- Interlace/lib/core/input.py | 70 ++++++++++++++--------------- Interlace/lib/threader.py | 87 ++++++++----------------------------- 3 files changed, 58 insertions(+), 117 deletions(-) diff --git a/Interlace/interlace.py b/Interlace/interlace.py index a112786..77cff21 100644 --- a/Interlace/interlace.py +++ b/Interlace/interlace.py @@ -1,29 +1,23 @@ #!/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, TaskBlock - - -def print_command(level, command, message, output): - if isinstance(command, TaskBlock): - for c in command: - print_command(level, c, message, output) - else: - output.terminal(Level.THREAD, command.name(), "Added to Queue") +from Interlace.lib.threader import Pool def build_queue(arguments, output): task_list = InputHelper.process_commands(arguments) for task in task_list: - print_command(Level.THREAD, task, "Added to Queue", output) + output.terminal(Level.THREAD, task.name(), "Added to Queue") return task_list def main(): parser = InputParser() - arguments = parser.parse(sys.argv[1:]) + args = ["-cL", "C:\\Users\\user\\Documents\\PythonProjects\\Interlace\\foo.test", + "-tL", "C:\\Users\\user\\Documents\\PythonProjects\\Interlace\\bar.test"] + arguments = parser.parse(args) + # arguments = parser.parse(sys.argv[1:]) output = OutputHelper(arguments) diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index e6566fb..6c55098 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -2,11 +2,11 @@ import sys from argparse import ArgumentParser from math import ceil -from os import access, W_OK from random import sample from netaddr import IPNetwork, IPRange, IPGlob -from Interlace.lib.threader import TaskBlock, Task + +from Interlace.lib.threader import Task class InputHelper(object): @@ -75,9 +75,10 @@ def _process_port(port_type): return [port_type] @staticmethod - def _pre_process_commands(command_list, task_name): - task_block = TaskBlock(task_name) - parent_task = None + def _pre_process_commands(command_list, task_name, is_global_task=True): + task_block = [] + sibling = None + global_task = None for command in command_list: command = str(command).strip() if not command: @@ -86,16 +87,23 @@ def _pre_process_commands(command_list, task_name): new_task_name = command.split('_block:')[1][:-1].strip() if task_name == new_task_name: return task_block - task = InputHelper._pre_process_commands(command_list, new_task_name) + for task in InputHelper._pre_process_commands(command_list, new_task_name, False): + task_block.append(task) + sibling = task + continue else: - task = Task(command) if command == '_blocker_': - parent_task = task_block.last() - parent_task.set_lock() + global_task = sibling continue - if parent_task: - task.wait_for(parent_task.get_lock()) - task_block.add_task(task) + task = Task(command) + if is_global_task and global_task: + print('{} must wait for GLOBAL {}'.format(task.name(), global_task.name())) + task.wait_for(global_task.get_lock()) + elif sibling: + task.wait_for(sibling.get_lock()) + print('{} is waiting for {}'.format(task.name(), sibling.name())) + task_block.append(task) + sibling = task return task_block @staticmethod @@ -121,28 +129,20 @@ def _pre_process_hosts(host_ranges, destination_set, arguments): @staticmethod def _replace_variable_with_commands(commands, variable, replacements): - foo = [] - - def add_task(t): - if t not in set(foo): - foo.append(t) + def add_task(t, item_list): + if t not in set(item_list): + item_list.append(t) + tasks = [] for command in commands: - is_task = not isinstance(command, TaskBlock) for replacement in replacements: - if is_task and command.name().find(variable) != -1: + if command.name().find(variable) != -1: new_task = command.clone() new_task.replace(variable, replacement) - add_task(new_task) - elif is_task and command not in set(foo): - add_task(command) - elif not is_task: - tasks = [task for task in command.get_tasks()] - command.clear_tasks() - for r in InputHelper._replace_variable_with_commands(tasks, variable, replacements): - command.add_task(r) - add_task(command) - return foo + add_task(new_task, tasks) + else: + add_task(command, tasks) + return tasks @staticmethod def _replace_variable_array(commands, variable, replacement): @@ -151,10 +151,7 @@ def _replace_variable_array(commands, variable, replacement): return for counter, command in enumerate(commands): - if isinstance(command, TaskBlock): - InputHelper._replace_variable_array(command, variable, replacement) - else: - command.replace(variable, str(replacement[counter])) + command.replace(variable, str(replacement[counter])) @staticmethod def process_commands(arguments): @@ -178,8 +175,8 @@ def process_commands(arguments): ranges.add(arguments.target) else: target_file = arguments.target_list - if not sys.stdin.isatty(): - target_file = sys.stdin + # if not sys.stdin.isatty(): + # target_file = sys.stdin ranges.update([target.strip() for target in target_file if target.strip()]) # process exclusions first @@ -205,8 +202,7 @@ def process_commands(arguments): if arguments.command: commands.append(arguments.command.rstrip('\n')) else: - tasks = InputHelper._pre_process_commands(arguments.command_list, '') - commands = tasks.get_tasks() + commands = InputHelper._pre_process_commands(arguments.command_list, '') commands = InputHelper._replace_variable_with_commands(commands, "_target_", targets) commands = InputHelper._replace_variable_with_commands(commands, "_host_", targets) diff --git a/Interlace/lib/threader.py b/Interlace/lib/threader.py index a88cbf9..26d11df 100644 --- a/Interlace/lib/threader.py +++ b/Interlace/lib/threader.py @@ -1,6 +1,6 @@ import subprocess +from concurrent.futures import ThreadPoolExecutor from multiprocessing import Event -from threading import Thread from tqdm import tqdm @@ -8,8 +8,8 @@ class Task(object): def __init__(self, command): self.task = command - self._lock = None - self._waiting_for_task = False + self.self_lock = None + self.sibling_lock = None def __cmp__(self, other): return self.name() == other.name() @@ -19,36 +19,31 @@ def __hash__(self): def clone(self): new_task = Task(self.task) - new_task._lock = self._lock - new_task._waiting_for_task = self._waiting_for_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 not self._waiting_for_task: - self._run_task(t) - if self._lock: - self._lock.set() - else: - self._lock.wait() - self._run_task(t) - - def wait_for(self, lock): - self._lock = lock - self._waiting_for_task = True + if self.sibling_lock: + self.sibling_lock.wait() + self._run_task(t) + if self.self_lock: + self.self_lock.set() - def set_lock(self): - if not self._lock: - self._lock = Event() - self._lock.clear() + def wait_for(self, _lock): + self.sibling_lock = _lock def name(self): return self.task def get_lock(self): - return self._lock + 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: @@ -58,44 +53,6 @@ def _run_task(self, t=False): subprocess.Popen(self.task, shell=True) -class TaskBlock(Task): - def __init__(self, name): - super().__init__('') - self._name = name - self.tasks = [] - - def name(self): - return self._name - - def add_task(self, task): - self.tasks.append(task) - - def clear_tasks(self): - self.tasks.clear() - - def __len__(self): - return len(self.tasks) - - def __hash__(self): - hash_value = 0 - for t in self.tasks: - hash_value ^= t.__hash__() - return hash_value - - def __iter__(self): - return self.tasks.__iter__() - - def last(self): - return self.tasks[-1] - - def _run_task(self, t=False): - for task in self.tasks: - task._run_task(t) - - def get_tasks(self): - return self.tasks - - class Worker(object): def __init__(self, task_queue, timeout, output, tqdm): self.queue = task_queue @@ -144,17 +101,11 @@ def __init__(self, max_workers, task_queue, timeout, output, progress_bar): 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 = Thread(target=worker) - thread.start() - threads.append(thread) - - # wait until all workers have completed their tasks - for thread in threads: - thread.join() + with ThreadPoolExecutor(self.max_workers) as executors: + for worker in workers: + executors.submit(worker) # test harness From 33a5a6880362eefb81e082942a3491e4d1594e73 Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Fri, 16 Aug 2019 17:34:27 +0100 Subject: [PATCH 07/10] Perfectting synchronization --- Interlace/lib/core/input.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index 6c55098..2b02ea3 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -97,11 +97,9 @@ def _pre_process_commands(command_list, task_name, is_global_task=True): continue task = Task(command) if is_global_task and global_task: - print('{} must wait for GLOBAL {}'.format(task.name(), global_task.name())) task.wait_for(global_task.get_lock()) - elif sibling: + elif sibling and not is_global_task: task.wait_for(sibling.get_lock()) - print('{} is waiting for {}'.format(task.name(), sibling.name())) task_block.append(task) sibling = task return task_block From c9521e6ae0f3f47b7229bd0d2c6c5e49aed14a25 Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Fri, 16 Aug 2019 17:39:12 +0100 Subject: [PATCH 08/10] Added test files --- .gitignore | 1 - bar.test | 2 ++ foo.test | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 bar.test create mode 100644 foo.test diff --git a/.gitignore b/.gitignore index 884dd95..3ebf85d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ wheels/ *.egg-info/ .installed.cfg *.egg -*.test MANIFEST # PyInstaller diff --git a/bar.test b/bar.test new file mode 100644 index 0000000..b8f5fbd --- /dev/null +++ b/bar.test @@ -0,0 +1,2 @@ +localhost +localone diff --git a/foo.test b/foo.test new file mode 100644 index 0000000..441c494 --- /dev/null +++ b/foo.test @@ -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" From 885019276f5266660de3d316fb46082af74a78f5 Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Fri, 16 Aug 2019 19:04:35 +0100 Subject: [PATCH 09/10] Cleaned up debug prints --- Interlace/interlace.py | 7 +++---- Interlace/lib/core/input.py | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Interlace/interlace.py b/Interlace/interlace.py index 77cff21..4f84115 100644 --- a/Interlace/interlace.py +++ b/Interlace/interlace.py @@ -1,5 +1,7 @@ #!/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 @@ -14,10 +16,7 @@ def build_queue(arguments, output): def main(): parser = InputParser() - args = ["-cL", "C:\\Users\\user\\Documents\\PythonProjects\\Interlace\\foo.test", - "-tL", "C:\\Users\\user\\Documents\\PythonProjects\\Interlace\\bar.test"] - arguments = parser.parse(args) - # arguments = parser.parse(sys.argv[1:]) + arguments = parser.parse(sys.argv[1:]) output = OutputHelper(arguments) diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index 2b02ea3..4701a22 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -76,6 +76,12 @@ def _process_port(port_type): @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: + """ task_block = [] sibling = None global_task = None @@ -83,21 +89,29 @@ def _pre_process_commands(command_list, task_name, is_global_task=True): 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) @@ -144,7 +158,6 @@ def add_task(t, item_list): @staticmethod def _replace_variable_array(commands, variable, replacement): - # TODO if variable not in sample(commands, 1)[0]: return @@ -173,8 +186,8 @@ def process_commands(arguments): ranges.add(arguments.target) else: target_file = arguments.target_list - # if not sys.stdin.isatty(): - # target_file = sys.stdin + if not sys.stdin.isatty(): + target_file = sys.stdin ranges.update([target.strip() for target in target_file if target.strip()]) # process exclusions first From 3673de2fccaf397d4945f2cf9b0b2659b24362cb Mon Sep 17 00:00:00 2001 From: Joshua Ogunyinka Date: Sat, 17 Aug 2019 05:24:13 +0100 Subject: [PATCH 10/10] Fixed typo from base.master typo --- Interlace/lib/core/input.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Interlace/lib/core/input.py b/Interlace/lib/core/input.py index 7cfbe4d..21b36dd 100644 --- a/Interlace/lib/core/input.py +++ b/Interlace/lib/core/input.py @@ -1,10 +1,9 @@ import os.path import sys - from argparse import ArgumentParser -from re import compile -from random import sample, choice from math import ceil +from random import sample, choice + from netaddr import IPNetwork, IPRange, IPGlob from Interlace.lib.threader import Task @@ -247,7 +246,7 @@ def process_commands(arguments): commands = InputHelper._replace_variable_with_commands(commands, "_realport_", real_ports) if arguments.random: - commands = InputHelper._replace_variable_for_commands(commands, "_random_", [random_file]) + commands = InputHelper._replace_variable_with_commands(commands, "_random_", [random_file]) if arguments.output: commands = InputHelper._replace_variable_with_commands(commands, "_output_", [arguments.output])