def getUpdateCmd(self): """ Build update command @Returns Update command-line """ c = Command(self.tool_dir, self.update, None, self.toolbox_dir, None, None, None, None) return c.getParsedInstallCommandLine()
def getRunCmd(self, target, output_file, output_dir, specific_args): """ Build run command """ #print self.command c = Command(self.tool_dir, self.command, target, self.toolbox_dir, output_file, output_dir, self.service_name, specific_args) return c.getParsedRunCommandLine()
def getInstallCmd(self): """ Build install command @Returns Install command-line """ c = Command(self.tool_dir, self.install, None, self.toolbox_dir, None, None, None, None) return c.getParsedInstallCommandLine()
def run(self, target): try: self.command = Command(self.rawcmd, self.type) except CommandException as e: logger.error(e) return None # Build script to run if self.type == 'rce-blind': logger.warning('WARNING: This attack box must be reachable from the target !') logger.info('If target is vulnerable, exploit will try to ping local ' \ 'IP = {localip} from target'.format( localip=NetUtils.get_local_ip_address())) print(self.command.get_cmdline(target)) script = SCRIPT_RCE_BLIND.format( exploit_dir=self.directory, command=self.command.get_cmdline(target)) elif self.type == 'rce-standard': script = self.command.get_cmdline(target) else: logger.error('Unsupported exploit type') return None # Run subprocess try: logger.info('Exploit will be run from directory: {directory}'.format( directory=self.directory)) proc = subprocess.Popen(script, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Agressivelly get the output while True: out = proc.stdout.read(1) # We put that inside try block to avoid utf8 decoding error try: out = out.decode(sys.stdout.encoding) sys.stdout.write(out) self.output += out except: pass # Break if process has finished if out == '' and proc.poll() != None: break except Exception as e: logger.error('Error when trying to run command: {exception}'.format( exception=e)) return None return self.output
def __parse_tool_options(self, section, tool_config): """ Check and parse options from a given tool section :param section: Tool section into the toolbox settings file :param tool_config: A defaultdict(str) storing tool config which is updated into this method :return: Boolean indicating status """ log_prefix = '[{filename}{ext} | Section "{section}"]'.format( filename=TOOLBOX_CONF_FILE, ext=CONF_EXT, section=section) optparsed = self.config_parsers[TOOLBOX_CONF_FILE].options(section) for opt in TOOL_OPTIONS[MANDATORY]: if opt not in optparsed: logger.warning('{prefix} Missing mandatory option "{option}", tool is skipped'.format( prefix=log_prefix, option=opt)) return False tool_config['name_clean'] = section for opt in optparsed: if opt not in TOOL_OPTIONS[MANDATORY]+TOOL_OPTIONS[OPTIONAL]: logger.warning('{prefix} Option "{option}" is not supported, it will be ignored'.format( prefix=log_prefix, option=opt)) continue if opt in TOOL_OPTIONS[MANDATORY]: val = self.config_parsers[TOOLBOX_CONF_FILE].safe_get(section, opt, '', None) if opt == 'name': tool_config[opt]=StringUtils.clean(val, allowed_specials=['-', '_']) elif opt == 'description': tool_config[opt] = val elif opt == 'target_service': tool_config[opt] = val.lower() if tool_config[opt] not in self.services.list_services(multi=True): logger.warning('{prefix} Service specified in "target_service" is not supported, ' \ 'tool is skipped'.format(prefix=log_prefix)) return False if not tool_config[opt]: logger.warning('{prefix} Mandatory option "{option}" is empty, tool is skipped'.format( prefix=log_prefix, option=opt)) return False elif opt == 'install': tool_config[opt] = Command(cmdtype = CMD_INSTALL, cmdline = self.config_parsers[TOOLBOX_CONF_FILE].safe_get(section, opt, '', None)) elif opt == 'update': tool_config[opt] = Command(cmdtype = CMD_UPDATE, cmdline = self.config_parsers[TOOLBOX_CONF_FILE].safe_get(section, opt, '', None)) elif opt == 'check_command': tool_config[opt] = Command(cmdtype = CMD_CHECK, cmdline = self.config_parsers[TOOLBOX_CONF_FILE].safe_get(section, opt, '', None)) return True
def checkInstall(self, output): """ Check if the tool is correctly installed. Basically, it runs the installed tool without any option @Args output: CLIOutput instance @Returns Boolean indicating status """ # Directory where the tool should be installed if not FileUtils.is_dir(self.tool_dir): output.printFail( 'Directory where the tool should be installed (\'{0}\') does not exist !' .self.tool_dir) return False # Try to run the tool output.printInfo('Trying to run the tool {0}...'.format(self.name)) splitted = self.command.strip().split(' ') cmd = '' if splitted[0].lower() == 'sudo' and len(splitted) > 1: cmd = 'sudo ' splitted = splitted[1:] cmd += splitted[0] if splitted[0].lower() in ('python', 'python3', 'perl', 'ruby') and len(splitted) > 1: if splitted[1] != '-m': cmd += ' {0}'.format(splitted[1]) elif len(splitted) > 2: cmd += ' -m {0}'.format(splitted[2]) elif splitted[0].lower() == 'java' and len(splitted) > 2: if splitted[1].lower() == '-jar': cmd += ' -jar {0}'.format(splitted[2]) c = Command(self.tool_dir, cmd, None, self.toolbox_dir, None, None, None, None) cmd_check = c.getStandardCommandLine() cmd_check_print = cmd_check[cmd_check.index(';') + 1:].strip() output.printBeginCmd(cmd_check_print) process = ProcessLauncher(cmd_check, output, None) process.start() output.printEndCmd() # Prompt output.printPrompt( 'Does the tool {0} seem to be running correctly ? [Y/n]'.format( self.name)) return CLIUtils.promptYesNo(output, default='Y')
def __parse_commands(self, service, section): """ Create Commands object for a given tool. Each command is defined in settings file by: - command_<command_number> - context_<command_number> (optional) :param service: Service name :param section: Section name [check_(.+)] :return: List of Command instances """ log_prefix = '[{filename}{ext} | Section "{section}"]'.format( filename=service, ext=CONF_EXT, section=section) commands = list() cmdlines = self.config_parsers[service].safe_get_multi(section, 'command', default=None) i = 0 for cmd in cmdlines: context = self.config_parsers[service].safe_get(section, 'context_'+str(i+1), default=None) context = self.__parse_context(service, section, i, context) if context is None: logger.warning('{prefix} Context is invalid, the check is ignored'.format(prefix=log_prefix)) return None commands.append(Command(cmdtype=CMD_RUN, cmdline=cmdlines[i], context=context, services_config=self.services)) i += 1 if not commands: logger.warning('{prefix} No command is specified, the check is ignored'.format(prefix=log_prefix)) return commands
def __parse_commands(self, service, section): """ Parse commands for a given tool and create Commands object. Each command is defined in configuration file by: - command_<command_number> - context_<command_number> (optional) :param str service: Service name :param str section: Section name of the check :return: Created Command objects :rtype: list(Command) """ log_prefix = '[{filename}{ext} | Section "{section}"]'.format( filename=service, ext=CONF_EXT, section=section) commands = list() # Get command lines cmdlines = self.config_parsers[service].safe_get_multi(section, 'command', default=None) i = 0 # Loop over command lines for cmd in cmdlines: # Parse context requirements and create ContextRequirements object context = self.config_parsers[service].safe_get(section, 'context_' + str(i + 1), default=None) context_requirements = self.__parse_context_requirements( service, section, i + 1, context) if context_requirements is None: logger.warning('{prefix} Context requirements are invalid, the check ' \ 'is ignored'.format(prefix=log_prefix)) return None # Create the Command object command = Command(cmdtype=CmdType.RUN, cmdline=cmdlines[i], context_requirements=context_requirements, services_config=self.services) commands.append(command) i += 1 if not commands: logger.warning('{prefix} No command is specified, the check is ' \ 'ignored'.format(prefix=log_prefix)) return commands
def __init__( self, service_name, toolbox_dir, tooltype, # General tool options name, tool_ref_name, category, description, command, install, update, installed, last_update, # Specific tool options specific_options): """ Initialize a Tool object @Args service_name: service targeted by the tool toolbox_dir: toolbox directory tooltype: ToolType name: tool name as it will appear in the program category: tool category, must be one of the categories in "tools_categories" general setting description: tool description, it will be diplayed to user before running tool raw_command: command-line used to launched the tool (can use tags) install: command-line for tool install update: command-line for tool update installed: boolean indicating whether tool is installed or not last_update: date of last tool update specific_options: dictionary of specific tool options """ self.service_name = service_name self.toolbox_dir = toolbox_dir self.tooltype = tooltype self.name = name self.tool_ref_name = tool_ref_name self.category = category self.description = description self.last_update = last_update self.installed = installed if isinstance(installed, bool) else False self.specific_options = specific_options # Directory reserved for the tool if self.tooltype == ToolType.USE_MULTI: self.tool_dir = self.toolbox_dir + os.sep + Constants.MULTI_SERVICES_TOOLBOX_SUBDIR + os.sep + 'all' + os.sep + self.tool_ref_name else: self.tool_dir = self.toolbox_dir + os.sep + self.service_name + os.sep + self.category + os.sep + self.name # Commands instantiation self.command = Command(CommandType.RUN, command, self.tool_dir, self.toolbox_dir) if command else None self.install = Command(CommandType.INSTALL, install, self.tool_dir, self.toolbox_dir) if install else None self.update = Command(CommandType.UPDATE, update, self.tool_dir, self.toolbox_dir) if update else None
class Exploit: def __init__(self, name, description, type_, rawcmd, success=''): self.name = name self.description = description self.type = type_ self.rawcmd = rawcmd self.success = success self.directory = TOOL_BASEPATH + '/exploits/' + self.name.lower() self.output = '' def run(self, target): try: self.command = Command(self.rawcmd, self.type) except CommandException as e: logger.error(e) return None # Build script to run if self.type == 'rce-blind': logger.warning('WARNING: This attack box must be reachable from the target !') logger.info('If target is vulnerable, exploit will try to ping local ' \ 'IP = {localip} from target'.format( localip=NetUtils.get_local_ip_address())) print(self.command.get_cmdline(target)) script = SCRIPT_RCE_BLIND.format( exploit_dir=self.directory, command=self.command.get_cmdline(target)) elif self.type == 'rce-standard': script = self.command.get_cmdline(target) else: logger.error('Unsupported exploit type') return None # Run subprocess try: logger.info('Exploit will be run from directory: {directory}'.format( directory=self.directory)) proc = subprocess.Popen(script, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Agressivelly get the output while True: out = proc.stdout.read(1) # We put that inside try block to avoid utf8 decoding error try: out = out.decode(sys.stdout.encoding) sys.stdout.write(out) self.output += out except: pass # Break if process has finished if out == '' and proc.poll() != None: break except Exception as e: logger.error('Error when trying to run command: {exception}'.format( exception=e)) return None return self.output def check_success(self): if self.type == 'rce-blind': m = re.search(MATCHING_PATTERN_RCE_BLIND, self.output, re.IGNORECASE) elif self.type == 'rce-standard': m = re.search(self.success, self.output, re.IGNORECASE) return (m is not None)
def __parse_tool_options(self, section, tool_config): """ Check and parse options from a given tool section. :param str section: Section name corresponding to the tool in toolbox.conf :param defaultdict(str) tool_config: Tool configuration updated in this method :return: Status of parsing :rtype: bool """ log_prefix = '[{filename}{ext} | Section "{section}"]'.format( filename=TOOLBOX_CONF_FILE, ext=CONF_EXT, section=section) # Check presence of mandatory options optparsed = self.config_parsers[TOOLBOX_CONF_FILE].options(section) for opt in TOOL_OPTIONS[MANDATORY]: if opt not in optparsed: logger.warning('{prefix} Missing mandatory option "{option}", ' \ 'tool is skipped'.format(prefix=log_prefix, option=opt)) return False # Loop over options for opt in optparsed: # Check for unsupported options if opt not in TOOL_OPTIONS[MANDATORY] + TOOL_OPTIONS[OPTIONAL]: logger.warning('{prefix} Option "{option}" is not supported, ' \ 'it will be ignored'.format(prefix=log_prefix, option=opt)) continue # Add value val = self.config_parsers[TOOLBOX_CONF_FILE].safe_get( section, opt, '', None) if opt == 'name': tool_config[opt] = StringUtils.clean( val, allowed_specials=['-', '_']) elif opt == 'description': tool_config[opt] = val elif opt == 'target_service': tool_config[opt] = val.lower() if tool_config[opt] not in self.services.list_services( multi=True): logger.warning('{prefix} Service specified in "target_service" is ' \ 'not supported, tool is skipped'.format(prefix=log_prefix)) return False elif opt == 'install': tool_config[opt] = Command(cmdtype=CmdType.INSTALL, cmdline=val) elif opt == 'update': tool_config[opt] = Command(cmdtype=CmdType.UPDATE, cmdline=val) elif opt == 'check_command': tool_config[opt] = Command(cmdtype=CmdType.CHECK, cmdline=val) # Check for empty mandatory option if opt in TOOL_OPTIONS[MANDATORY] and not tool_config[opt]: logger.warning('{prefix} Mandatory option "{option}" is empty, tool ' \ 'is skipped'.format(prefix=log_prefix, option=opt)) return False return True
def __parse_tool_options(self, section, tool_config): """ Check and parse options from a given tool section. :param str section: Section name corresponding to the tool in toolbox.conf :param defaultdict(str) tool_config: Tool configuration updated in this method :return: Status of parsing :rtype: bool """ log_prefix = '[{filename}{ext} | Section "{section}"]'.format( filename=TOOLBOX_CONF_FILE, ext=CONF_EXT, section=section) # Check presence of mandatory options optparsed = self.config_parsers[TOOLBOX_CONF_FILE].options(section) for opt in TOOL_OPTIONS[MANDATORY]: if opt not in optparsed: logger.warning('{prefix} Missing mandatory option "{option}", ' \ 'tool is skipped'.format(prefix=log_prefix, option=opt)) return False # Loop over options for opt in optparsed: # Check for unsupported options if opt not in TOOL_OPTIONS[MANDATORY] + TOOL_OPTIONS[OPTIONAL]: logger.warning('{prefix} Option "{option}" is not supported, ' \ 'it will be ignored'.format(prefix=log_prefix, option=opt)) continue # Add value val = self.config_parsers[TOOLBOX_CONF_FILE].safe_get( section, opt, '', None) if opt == 'name': tool_config[opt] = StringUtils.clean( val, allowed_specials=['-', '_']) elif opt == 'description': tool_config[opt] = val elif opt == 'target_service': tool_config[opt] = val.lower() if tool_config[opt] not in self.services.list_services( multi=True): logger.warning('{prefix} Service specified in "target_service" is ' \ 'not supported, tool is skipped'.format(prefix=log_prefix)) return False elif opt == 'virtualenv': tool_config[opt] = val.lower() # For Python, format must be "python<version>" if tool_config[opt].startswith('python'): m = re.match('python(?P<version>[0-9](\.[0-9])*)', tool_config[opt]) if not m: logger.warning('{prefix} Invalid Python virtualenv, must be: ' \ 'virtualenv = python<version>. Tool is skipped'.format( prefix=log_prefix)) return False # For Ruby, make sure to have a format like "ruby-<version>" # Format "ruby<version>" is accepted and turned into "ruby-<version>" if tool_config[opt].startswith('ruby'): m1 = re.match('ruby(?P<version>[0-9](\.[0-9])*)', tool_config[opt]) m2 = re.match('ruby-(?P<version>[0-9](\.[0-9])*)', tool_config[opt]) if m1: tool_config[opt] = 'ruby-{version}'.format( version=m.group('version')) elif not m2: logger.warning('{prefix} Invalid Ruby virtualenv, must be: ' \ 'virtualenv = ruby-<version>. Tool is skipped'.format( prefix=log_prefix)) return False elif opt == 'install': tool_config[opt] = Command(cmdtype=CmdType.INSTALL, cmdline=val) elif opt == 'update': tool_config[opt] = Command(cmdtype=CmdType.UPDATE, cmdline=val) elif opt == 'check_command': tool_config[opt] = Command(cmdtype=CmdType.CHECK, cmdline=val) # Check for empty mandatory option if opt in TOOL_OPTIONS[MANDATORY] and not tool_config[opt]: logger.warning('{prefix} Mandatory option "{option}" is empty, tool ' \ 'is skipped'.format(prefix=log_prefix, option=opt)) return False return True
def run(self, target, mode, rce_command=''): """ :param Target targer: Target instance :param str mode: mode can be either "detect" or "exploit" :param str rce_command: RCE command to run when running exploit (requires mode=exploit) """ try: if mode == 'detect': self.command = Command(self.detection_rawcmd, self.type) elif mode == 'exploit': self.command = Command(self.exploit_rawcmd, self.type, self.exploit_rce_output) except CommandException as e: logger.error(e) return None # Build script to run if mode == 'exploit': if self.type == 'rce': if not self.exploit_rce_output: logger.warning('The exploit will attempt to execute command on remote system but no ' 'output will be available !') # For RCE exploit without command output in test mode (no rce_command provided): # Use script template that check for reverse connection with ICMP ping and HTTP requests if len(rce_command) == 0: logger.warning('WARNING: This attack box must be reachable from the target !') logger.info('No command supplied to run through RCE, automatic exploit test will be started...') logger.info('If target is vulnerable, exploit will try to ping (ICMP Echo request) and ' 'to send HTTP request to local IP = {localip} from target'.format( localip=NetUtils.get_local_ip_address())) cmdline = self.command.get_cmdline(target) print(cmdline) script = SCRIPT_RCE_BLIND.format( exploit_dir=self.directory, command=cmdline) else: cmdline = self.command.get_cmdline(target, rce_command) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: if len(rce_command) == 0: logger.info('No command supplied to run through RCE, automatic exploit test will be started...') logger.info('If target is vulnerable, exploit will try to run an echo command on target') cmdline = self.command.get_cmdline(target) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: cmdline = self.command.get_cmdline(target, rce_command) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: cmdline = self.command.get_cmdline(target) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: logger.warning('The script will attempt to detect if remote system is vulnerable without ' 'actually exploiting the vulnerability.') logger.warning('WARNING: False Positive is possible !') cmdline = self.command.get_cmdline(target) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline # Run subprocess try: logger.info('{script} will be run from directory: {directory}'.format( script='Exploit' if mode == 'exploit' else 'Detection script', directory=self.directory)) proc = subprocess.Popen(script, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Agressivelly get the output while True: out = proc.stdout.read(1) # We put that inside try block to avoid utf8 decoding error try: out = out.decode(sys.stdout.encoding) sys.stdout.write(out) self.output += out except: pass # Break if process has finished if out == '' and proc.poll() != None: break except Exception as e: logger.error('Error when trying to run command: {exception}'.format( exception=e)) return None return self.output
class Exploit: def __init__(self, name, product, description, type_, detection_rawcmd, detection_success, exploit_rawcmd, exploit_rce_output, exploit_success): self.name = name self.product = product self.description = description self.type = type_ self.detection_rawcmd = detection_rawcmd self.detection_success = detection_success self.exploit_rawcmd = exploit_rawcmd self.exploit_rce_output = exploit_rce_output self.exploit_success = exploit_success self.directory = TOOL_BASEPATH + '/exploits/' + self.name.lower() self.output = '' def is_mode_supported(self, mode): """ Check is specified mode is supported (either "detect" or "exploit") :param str mode: Requested mode """ if mode == 'detect': return (self.detection_rawcmd is not None and len(self.detection_rawcmd) > 0) elif mode == 'exploit': return (self.exploit_rawcmd is not None and len(self.exploit_rawcmd) > 0) else: return False def run(self, target, mode, rce_command=''): """ :param Target targer: Target instance :param str mode: mode can be either "detect" or "exploit" :param str rce_command: RCE command to run when running exploit (requires mode=exploit) """ try: if mode == 'detect': self.command = Command(self.detection_rawcmd, self.type) elif mode == 'exploit': self.command = Command(self.exploit_rawcmd, self.type, self.exploit_rce_output) except CommandException as e: logger.error(e) return None # Build script to run if mode == 'exploit': if self.type == 'rce': if not self.exploit_rce_output: logger.warning('The exploit will attempt to execute command on remote system but no ' 'output will be available !') # For RCE exploit without command output in test mode (no rce_command provided): # Use script template that check for reverse connection with ICMP ping and HTTP requests if len(rce_command) == 0: logger.warning('WARNING: This attack box must be reachable from the target !') logger.info('No command supplied to run through RCE, automatic exploit test will be started...') logger.info('If target is vulnerable, exploit will try to ping (ICMP Echo request) and ' 'to send HTTP request to local IP = {localip} from target'.format( localip=NetUtils.get_local_ip_address())) cmdline = self.command.get_cmdline(target) print(cmdline) script = SCRIPT_RCE_BLIND.format( exploit_dir=self.directory, command=cmdline) else: cmdline = self.command.get_cmdline(target, rce_command) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: if len(rce_command) == 0: logger.info('No command supplied to run through RCE, automatic exploit test will be started...') logger.info('If target is vulnerable, exploit will try to run an echo command on target') cmdline = self.command.get_cmdline(target) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: cmdline = self.command.get_cmdline(target, rce_command) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: cmdline = self.command.get_cmdline(target) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: logger.warning('The script will attempt to detect if remote system is vulnerable without ' 'actually exploiting the vulnerability.') logger.warning('WARNING: False Positive is possible !') cmdline = self.command.get_cmdline(target) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline # Run subprocess try: logger.info('{script} will be run from directory: {directory}'.format( script='Exploit' if mode == 'exploit' else 'Detection script', directory=self.directory)) proc = subprocess.Popen(script, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Agressivelly get the output while True: out = proc.stdout.read(1) # We put that inside try block to avoid utf8 decoding error try: out = out.decode(sys.stdout.encoding) sys.stdout.write(out) self.output += out except: pass # Break if process has finished if out == '' and proc.poll() != None: break except Exception as e: logger.error('Error when trying to run command: {exception}'.format( exception=e)) return None return self.output def check_success(self, mode): """ Check vuln detection success or exploit success when run in automatic test (i.e. no command provided via --cmd) :param str mode: mode can be either "detect" or "exploit" """ m = None if mode == 'detect': m = re.search(self.detection_success, self.output, re.IGNORECASE) if not m: if self.detection_success.lower() in self.output.lower(): m = self.detection_success elif mode == 'exploit': if self.type == 'rce': if self.exploit_rce_output: # RCE with command output: use success match string provided in settings m = re.search(self.exploit_success, self.output, re.IGNORECASE) if not m: if self.exploit_success.lower() in self.output.lower(): m = self.exploit_success else: # RCE without command output: use built-in match strings to detect either # ICMP echo reply or received HTTP request m = re.search(MATCHING_PATTERN_RCE_BLIND_ICMP, self.output, re.IGNORECASE) if not m: m = re.search(MATCHING_PATTERN_RCE_BLIND_HTTP, self.output, re.IGNORECASE) else: # Other exploit type (e.g. sqli) m = re.search(self.exploit_success, self.output, re.IGNORECASE) if not m: if self.exploit_success.lower() in self.output.lower(): m = self.exploit_success return (m is not None)
class Tool(object): def __init__(self, service_name, toolbox_dir, tooltype, # General tool options name, tool_ref_name, category, description, command, install, update, installed, last_update, # Specific tool options specific_options): """ Initialize a Tool object @Args service_name: service targeted by the tool toolbox_dir: toolbox directory tooltype: ToolType name: tool name as it will appear in the program category: tool category, must be one of the categories in "tools_categories" general setting description: tool description, it will be diplayed to user before running tool raw_command: command-line used to launched the tool (can use tags) install: command-line for tool install update: command-line for tool update installed: boolean indicating whether tool is installed or not last_update: date of last tool update specific_options: dictionary of specific tool options """ self.service_name = service_name self.toolbox_dir = toolbox_dir self.tooltype = tooltype self.name = name self.tool_ref_name = tool_ref_name self.category = category self.description = description self.last_update = last_update self.installed = installed if isinstance(installed, bool) else False self.specific_options = specific_options # Directory reserved for the tool if self.tooltype == ToolType.USE_MULTI: self.tool_dir = self.toolbox_dir + os.sep + Constants.MULTI_SERVICES_TOOLBOX_SUBDIR + os.sep + 'all' + os.sep + self.tool_ref_name else: self.tool_dir = self.toolbox_dir + os.sep + self.service_name + os.sep + self.category + os.sep + self.name # Commands instantiation self.command = Command(CommandType.RUN, command, self.tool_dir, self.toolbox_dir) if command else None self.install = Command(CommandType.INSTALL, install, self.tool_dir, self.toolbox_dir) if install else None self.update = Command(CommandType.UPDATE, update, self.tool_dir, self.toolbox_dir) if update else None def printToolSummary(self, output): """ Print tool info nicely @Args output: CLIOutput instance """ output.printTitle1(' {0} {1}'.format(self.name, '[-> {0}]'.format(self.tool_ref_name) if self.tool_ref_name else '')) output.printNewLine(' Description : {0}'.format(self.description)) #if self.command: output.printNewLine(' Command : {0}'.format(self.command.cmdline)) output.printRaw(' Installed : ') last_update = self.last_update if self.last_update else 'Unknown' output.printGreen('Yes [last update: {0}]\n'.format(last_update)) if self.installed else output.printRed('No\n') if self.installed: output.printNewLine(' Location : {0}'.format(self.tool_dir)) if self.specific_options: specific = '' for option in self.specific_options: type_opt = self.specific_options[option][0] value_opt = self.specific_options[option][1] if type_opt == bool and value_opt == True: specific += ' - {0}: {1}\n'.format(option, 'True') elif type_opt == list and value_opt: specific += ' - {0}: {1}\n'.format(option, ', '.join(value_opt)) if specific: output.printRaw(' Specific :\n{0}'.format(specific)) def printToolSummaryBrief(self, output): """ Print tool name + install status on one line @Args output: CLIOutput instance """ txt = ' - {0}{1}\n'.format(self.name, ' [-> {0}]'.format(self.tool_ref_name) if self.tool_ref_name else '') output.printGreen(txt) if self.installed else output.printRed(txt) def createToolDirectory(self, output): """ Create the tool directory if necessary @Args output: CLIOutput instance @Returns Boolean indicating operation status """ if FileUtils.is_dir(self.tool_dir): output.printInfo('Directory "{0}" already exists'.format(self.tool_dir)) return True try: FileUtils.create_directory(self.tool_dir) except Exception as e: output.printError('Unable to create new directory "{0}": {1}'.format(self.tool_dir, e)) return False output.printInfo('New directory "{0}" created'.format(self.tool_dir)) return True def runInstall(self, settings, output, fast_mode=False, referencing_tool=None): """ Install the tool @Args settings: Settings instance output: CLIOutput instance fast_mode: Boolean. If True, do not prompt confirm before install and do not check install after @Returns Boolean indicating status """ # Check for cases where no install will be run if self.installed: if self.tooltype == ToolType.USE_MULTI: output.printInfo('This is a reference to the tool "{0}" which is already installed, skipped.'.format(self.tool_ref_name)) else: output.printInfo('{0} is already installed (according to config), skipped.'.format(self.name)) print return False elif self.tooltype == ToolType.USE_MULTI: output.printInfo('This is a reference to the tool "{0}", which is not specific to the service {1}'.format(self.tool_ref_name, self.service_name)) ref_tool = settings.toolbox.searchInToolboxForService(self.tool_ref_name, Constants.MULTI_SERVICES_CONF_FILE) if ref_tool: return ref_tool.runInstall(settings, output, fast_mode=fast_mode, referencing_tool=self) else: output.printFail('The tool "{0}" has not been found inside the conf file "{1}{2}"'.format(self.tool_ref_name, \ Constants.MULTI_SERVICES_CONF_FILE, Constants.CONF_EXT)) return False elif not self.install: output.printWarning('No tool install command specified in config file, skipped') if not fast_mode: output.printPrompt('Do you want to mark this tool as installed ? [Y/n]') if fast_mode or CLIUtils.promptYesNo(output, default='Y'): try: if settings.changeInstalledOption(self.service_name, self.name, True): output.printSuccess('Tool {0} has been marked as installed. '.format(self.name)) else: output.printError('Error when saving "{0}{1}" configuration file'.format(Constants.INSTALL_STATUS_CONF_FILE, Constants.CONF_EXT)) except Exception as e: output.printError('An unexpected error occured when trying to mark the tool as installed') self.removeTool(settings, output) else: output.printInfo('Tool is still not marked as installed') print return False # Create directory for the tool if necessary if not self.createToolDirectory(output): output.printFail('Tool install skipped.') print return False # Print basic info and prompt confirmation cmd, cmd_short = self.install.getParsedCmdline() output.printInfo('Description : {0}'.format(self.description)) output.printInfo('Install command : {0}'.format(cmd_short)) if not fast_mode: output.printPrompt('Install ? [Y/n]') # Run install command if wanted if fast_mode or CLIUtils.promptYesNo(output, default='Y'): output.printBeginCmd(cmd_short) process = ProcessLauncher(cmd, output, None) process.start() output.printEndCmd() output.printSuccess('Tool installation has finished') # Check install ? install_ok = True if not (self.tooltype == ToolType.MULTI_SERVICES and not referencing_tool) and not fast_mode: output.printInfo('Now, checking if {0} has been installed correctly. Hit any key to run test...'.format(self.name)) CLIUtils.getch() try: install_ok = self.checkInstall(output, referencing_tool=referencing_tool) except Exception as e: install_ok = False output.printError('An unexpected error occured when checking install: {0}'.format(e)) # Change install status in configuration file if install_ok: try: if settings.changeInstalledOption(self.service_name, self.name, True): output.printSuccess('Tool {0} has been marked as installed. '.format(self.name)) else: output.printError('Error when saving "{0}{1}" configuration file'.format(Constants.INSTALL_STATUS_CONF_FILE, Constants.CONF_EXT)) except Exception as e: output.printError('An unexpected error occured when trying to mark the tool as installed: {0}'.format(e)) self.removeTool(settings, output) else: output.printFail('Tool {0} has not been marked as installed'.format(self.name)) self.removeTool(settings, output) else: output.printFail('Tool has not been installed') print def checkInstall(self, output, referencing_tool=None): """ Check if the tool is correctly installed. Basically, it runs the installed tool without any option @Args output: CLIOutput instance @Returns Boolean indicating status """ output.printInfo('Trying to run the tool {0} with no option...'.format(self.name)) if referencing_tool: cmd, cmd_short = referencing_tool.command.getParsedCmdline(remove_args=True) elif self.command: cmd, cmd_short = self.command.getParsedCmdline(remove_args=True) else: raise Exception output.printBeginCmd(cmd_short) process = ProcessLauncher(cmd, output, None) process.start() output.printEndCmd() output.printPrompt('Does the tool {0} seem to be running correctly ? [Y/n]'.format(self.name)) return CLIUtils.promptYesNo(output, default='Y') def runUpdate(self, settings, output, fast_mode=False, referencing_tool=None): """ Run the update for the tool @Args settings: Settings instance output: CLIOutput instance @Returns Boolean indicating status """ # Check for cases where no update will be run if not self.installed: output.printInfo('{0} is not installed yet (according to config), skipped.'.format(self.name)) print return False elif self.tooltype == ToolType.USE_MULTI: output.printInfo('This is a reference to the tool "{0}", which is not specific to the service {1}'.format(self.tool_ref_name, self.service_name)) ref_tool = settings.toolbox.searchInToolboxForService(self.tool_ref_name, Constants.MULTI_SERVICES_CONF_FILE) if ref_tool: return ref_tool.runUpdate(settings, output, fast_mode=fast_mode, referencing_tool=self) else: output.printFail('The tool "{0}" has not been found inside the conf file "{1}{2}"'.format(self.tool_ref_name, \ Constants.MULTI_SERVICES_CONF_FILE, Constants.CONF_EXT)) return False elif not self.update: output.printWarning('No tool update command specified in config file, skipped.') print return False # Create directory for the tool if necessary (should not be necessary because only update) if not FileUtils.is_dir(self.tool_dir): output.printFail('Tool directory does not exist but tool marked as installed. Trying to re-install it...') return self.runInstall(settings, output, fast_mode) # Print basic info and prompt confirmation cmd, cmd_short = self.update.getParsedCmdline() output.printInfo('Description : {0}'.format(self.description)) output.printInfo('Install command : {0}'.format(cmd_short)) if not fast_mode: output.printPrompt('Update ? [Y/n]') # Run update command if wanted if fast_mode or CLIUtils.promptYesNo(output, default='Y'): output.printBeginCmd(cmd_short) process = ProcessLauncher(cmd, output, None) process.start() output.printEndCmd() output.printSuccess('Tool update has finished') # Check install ? update_ok = True if not (self.tooltype == ToolType.MULTI_SERVICES and not referencing_tool) and not fast_mode: output.printInfo('Now, checking if {0} has been updateed correctly. Hit any key to run test...'.format(self.name)) CLIUtils.getch() try: update_ok = self.checkInstall(output, referencing_tool=referencing_tool) except Exception as e: update_ok = False output.printError('An unexpected error occured when checking install: {0}'.format(e)) # Change install status in configuration file if update_ok: try: if settings.changeInstalledOption(self.service_name, self.name, True): output.printSuccess('Tool {0} has been marked as successfully updated'.format(self.name)) else: output.printError('Error when saving "{0}{1}" configuration file'.format(Constants.INSTALL_STATUS_CONF_FILE, Constants.CONF_EXT)) except Exception as e: output.printError('An unexpected error occured when trying to change the last update date: {0}'.format(e)) #self.removeTool(settings, output) else: output.printFail('Tool {0} has not been marked as updated'.format(self.name)) #self.removeTool(settings, output) output.printPrompt('Do you want to try to re-install {0} ? [Y/n]'.format(self.name)) if CLIUtils.promptYesNo(output, default='Y'): self.reinstallTool(settings, output, referencing_tool=referencing_tool) else: output.printFail('Tool has not been updated') print def runTool(self, settings, output, output_dir, target, specific_args, ignore_specific=False, auto_yes=False): """ Run the tool @Args settings: instance of Settings output: instance of CLIOutput output_dir: directory where tool execution output will be saved target: instance of Target specific_args: specific arguments always_run: boolean indicating if tool should be always run (ignoring context specific options) auto_yes: boolean indicating if prompt should be displayed or not before running @Returns Boolean indicating status """ # Tool not installed yet if not self.installed: output.printInfo('{0} is not installed yet (according to config), skipped.'.format(self.name)) return False # If context specific if self.specific_options and not ignore_specific: for opt in self.specific_options.keys(): # Boolean option if self.specific_options[opt][0] == bool: if self.specific_options[opt][1] == True and (opt not in specific_args.keys() or specific_args[opt] == False): output.printInfo('Tool skipped. Specific to: {0} = True'.format(opt)) return False # List option elif self.specific_options[opt][0] == list and self.specific_options[opt][1]: if opt not in specific_args.keys() or \ specific_args[opt] != 'all' and specific_args[opt] not in self.specific_options[opt][1]: output.printInfo('Tool skipped. Specific to: {0} = {1}'.format(opt, ', '.join(self.specific_options[opt][1]))) return False # Print basic info and prompt confirmation cmd, cmd_short = self.command.getParsedCmdline(output_dir=output_dir, output_filename=self.name+'.txt', target=target, specific_args=specific_args) output.printInfo('Description : {0}'.format(self.description)) output.printInfo('Run command : {0}'.format(cmd_short)) if not auto_yes: output.printPrompt('Run tool ? [Y/n/t/w/q]'.format(self.category, self.name)) to_run = CLIUtils.promptRunMode(output, default='Y') else: to_run = 'Yes' # Run command if wanted if to_run == 'Quit': print output.printWarning('Exit !') sys.exit(0) elif to_run != 'No': output.printBeginCmd(cmd_short) process = ProcessLauncher(cmd, output, None) # Normal running if auto_yes or to_run == 'Yes': process.start() # Start in new tab elif to_run == 'Tab': # TODO output.printInfo('Not yet implemented') # Start in new window elif to_run == 'Window': process.startInNewWindow() print output.printInfo('Started in another window') output.printEndCmd() print def removeTool(self, settings, output): """ Remove the tool: - Remove tool directory into toolbox - Change install status to false @Args settings: Settings instance output: CLIOutput instance @Returns Boolean indicating operation status """ if self.tooltype == ToolType.USE_MULTI: output.printInfo('"{0}" is a reference to the tool "{1}" used for multi services. Not deleted'.format(\ self.name, self.tool_ref_name)) return False if not FileUtils.is_dir(self.tool_dir): output.printInfo('Directory "{0}" does not exist'.format(self.tool_dir)) else: if not FileUtils.remove_directory(self.tool_dir): output.printFail('Unable to delete directory "{0}". Check permissions and/or re-run with sudo'.format(self.tool_dir)) return False else: output.printInfo('Directory "{0}" deleted'.format(self.tool_dir)) # Make sure "installed" option in config file is set to False if not settings.changeInstalledOption(self.service_name, self.name, False): output.printError('An unexpected error occured when trying to mark the tool as uninstalled !') self.installed = False return True def reinstallTool(self, settings, output, referencing_tool=None): """ Try a tool re-install, i.e. remove and install @Args settings: Settings instance output: CLIOutput instance @Returns Boolean indicating operation status """ output.printInfo('First, the tool directory will be removed...') if not self.removeTool(settings, output): return False output.printInfo('Now, running a new install for {0}...'.format(self.name)) return self.runInstall(settings, output, referencing_tool=referencing_tool)