Beispiel #1
0
    def detect_os(self):
        """
        Detect product from command output
        """
        for os in os_match.keys():
            if self.tool_name in os_match[os].keys():
                patterns = os_match[os][self.tool_name]

                if type(patterns) == str:
                    patterns = [patterns]

                for pattern in patterns:
                    logger.debug('Search for os pattern: {pattern}'.format(
                        pattern=pattern))

                    try:
                        m = re.search(pattern, self.cmd_output, re.IGNORECASE)
                    except Exception as e:
                        logger.warning('Error with matchstring [{pattern}], ' \
                            'you should review it. Exception: {exc}'.format(
                                pattern=pattern, exc=e))
                        break

                    # If pattern matches, add detected OS
                    if m:
                        logger.debug('OS pattern matches')

                        # Add detected OS to the context
                        self.cu.add_os(os)
                        return
Beispiel #2
0
    def check_args_attack(self):
        """Check arguments for subcommand Attack"""

        status = True
        if self.args.target_ip_or_url and self.args.mission:
            logger.error(
                '--target and --mission cannot be used at the same time')
            return False

        elif self.args.target_ip_or_url:
            status &= self.__check_args_attack_single_target()

        elif self.args.mission:
            status &= self.__check_args_attack_multi_targets()

        else:
            #logger.error('At least one target must be selected')
            self.subparser.print_help()
            return False

        if self.args.debug:
            logger.setLevel('DEBUG')
            logger.debug('Debug mode enabled')

        # status &= self.__check_args_attack_single_target()
        # status &= self.__check_args_attack_multi_targets()
        status &= self.__check_args_attack_bruteforce()
        status &= self.__check_args_attack_selection()
        status &= self.__check_args_attack_context()

        return status
Beispiel #3
0
    def __detect_specific_options(self):
        """Detect specific option update from command output"""
        if self.service.name in options_match.keys():

            if self.tool_name in options_match[self.service.name].keys():
                p = options_match[self.service.name][self.tool_name]

                for pattern in p.keys():
                    logger.debug('Search for option pattern: {pattern}'.format(
                        pattern=pattern))

                    try:
                        m = re.search(pattern, 
                                      self.cmd_output, 
                                      re.IGNORECASE|re.MULTILINE)
                    except Exception as e:
                        logger.warning('Error with matchstring [{pattern}], you should '\
                            'review it. Exception: {exception}'.format(
                                pattern=pattern, exception=e))
                        break


                    # If pattern matches cmd output, update specific option
                    if m:
                        logger.debug('Option pattern matches')
                        if 'name' in p[pattern]:
                            name = self.__replace_tokens_from_matchobj(
                                p[pattern]['name'], m)
                            if name is None:
                                continue
                        else:
                            logger.smarterror('Invalid matchstring for ' \
                                'service={service}, tool={tool}: Missing ' \
                                '"name" key'.format(
                                    service=self.service.name,
                                    tool=self.tool_name))
                            continue

                        if 'value' in p[pattern]:
                            value = self.__replace_tokens_from_matchobj(
                                p[pattern]['value'], m)
                            if value is None:
                                continue
                        else:
                            logger.smarterror('Invalid matchstring for ' \
                                'service={service}, tool={tool}: Missing ' \
                                '"value" key'.format(
                                    service=self.service.name,
                                    tool=self.tool_name))
                            continue 

                        # Add specific option to context
                        self.cu.add_option(name, value)                           
Beispiel #4
0
    def start_http(self):
        """Method run specifically for HTTP services"""

        # Autodetect HTTPS
        if self.service.url.lower().startswith('https://'):
            logger.smartinfo('HTTPS protocol detected from URL')
            self.cu.add_option('https', 'true')

        # Check if HTTP service is protected by .htaccess authentication
        if self.service.http_headers \
            and '401 Unauthorized'.lower() in self.service.http_headers.lower():

            logger.smartinfo('HTTP authentication (htaccess) detected ' \
                '(401 Unauthorized)')
            self.cu.add_option('htaccess', 'true')

        # Update context with web technologies
        if self.service.web_technos:
            # Detect OS
            if not self.service.host.os:
                processor = MatchstringsProcessor(self.service, 'wappalyzer',
                                                  self.service.host.os,
                                                  self.cu)
                processor.detect_os()

            # Detect products
            try:
                technos = ast.literal_eval(self.service.web_technos)
            except Exception as e:
                logger.debug('Error when retrieving "web_technos" field ' \
                    'from db: {}'.format(e))
                technos = list()

            for t in technos:
                for prodtype in products_match['http']:
                    p = products_match['http'][prodtype]
                    for prodname in p:
                        if 'wappalyzer' in p[prodname]:
                            pattern = p[prodname]['wappalyzer']

                            #m = re.search(pattern, t['name'], re.IGNORECASE|re.DOTALL)
                            if pattern.lower() == t['name'].lower():
                                version = t['version']
                                self.cu.add_product(prodtype, prodname,
                                                    version)

                                # Move to next product type if something found
                                break
Beispiel #5
0
    def check_target_compliance(self, target):
        """
        Check if target complies with any of the context requirements of the different 
        commands defined in the security check.

        :param Target target: Target
        :return: Check result
        :rtype: bool
        """
        i = 1
        for command in self.commands:
            logger.debug(
                '{check} - Command #{i} context requirements: {rawstr}'.format(
                    check=self.name, i=i, rawstr=command.context_requirements))
            i += 1
            if command.context_requirements.check_target_compliance(target):
                return True
        return False
Beispiel #6
0
    def start_http(self):

        # Autodetect HTTPS
        if self.service.url.lower().startswith('https://'):
            logger.smartinfo('HTTPS protocol detected from URL')
            self.cu.add_option('https', 'true')

        # Check if HTTP service is protected by .htaccess authentication
        if '401 Unauthorized'.lower() in self.service.http_headers.lower():
            logger.smartinfo('HTTP authentication (htaccess) detected ' \
                '(401 Unauthorized)')
            self.cu.add_option('htaccess', 'true')

        # Try to detect web server and/or appserver from Nmap banner
        self.__detect_product_from_banner('web-server')
        self.__detect_product_from_banner('web-appserver')

        # Try to detect supported products from web technologies
        if self.service.web_technos:
            try:
                technos = ast.literal_eval(self.service.web_technos)
            except Exception as e:
                logger.debug('Error when retrieving "web_technos" field ' \
                    'from db: {}'.format(e))
                technos = list()

            for t in technos:
                for prodtype in products_match['http']:
                    p = products_match['http'][prodtype]
                    for prodname in p:
                        if 'wappalyzer' in p[prodname]:
                            pattern = p[prodname]['wappalyzer']

                            #m = re.search(pattern, t['name'], re.IGNORECASE|re.DOTALL)
                            if pattern.lower() == t['name'].lower():
                                version = t['version']
                                self.cu.add_product(prodtype, prodname,
                                                    version)

                                # Move to next product type if something found
                                break
Beispiel #7
0
    def detect_vulns(self):
        """
        Detect vulnerability from command output
        Important: A command output might contain several vulnerabilities with the 
        same pattern.
        """
        if self.service.name in vulns_match.keys():

            if self.tool_name in vulns_match[self.service.name].keys():
                p = vulns_match[self.service.name][self.tool_name]

                for pattern in p.keys():

                    logger.debug('Search for vulns pattern: {pattern}'.format(
                        pattern=pattern))

                    # Important: Multiple search/match
                    #m = re.search(pattern, self.cmd_output, re.IGNORECASE)
                    try:
                        mall = re.finditer(pattern, self.cmd_output,
                                           re.IGNORECASE | re.MULTILINE)
                    except Exception as e:
                        logger.warning('Error with matchstring [{pattern}], you ' \
                            'should review it. Exception: {exception}'.format(
                                pattern=pattern, exception=e))
                        break

                    # Process each match
                    if mall:
                        for m in mall:
                            name = self.__replace_tokens_from_matchobj(
                                p[pattern], m)
                            if name is None:
                                continue

                            # Add vulnerability to context
                            logger.debug('Vuln pattern matches')
                            self.cu.add_vuln(
                                StringUtils.remove_non_printable_chars(name))
Beispiel #8
0
    def run(self):
        """Run the Attack Controller"""

        args = self.arguments.args
        logger.debug('CLI arguments:')
        logger.debug(args)

        # Attack configuration: Categories of checks to run
        categories = self.settings.services.list_all_categories() # default: all

        if args.cat_only:
            categories = [ cat for cat in categories if cat in args.cat_only ]
        elif args.cat_exclude:
            categories = [ cat for cat in categories if cat not in args.cat_exclude ]


        # Create the attack scope
        self.attack_scope = AttackScope(
            self.settings, 
            self.arguments,
            self.sqlsess,
            args.mission or args.add,
            filter_categories=categories, 
            filter_checks=args.checks, 
            attack_profile=args.profile,
            fast_mode=args.fast_mode)


        # Run the attack
        begin = datetime.datetime.now()
        if args.target_ip_or_url:
            self.__run_for_single_target(args)
        else:
            self.__run_for_multi_targets(args)
            
        print()
        duration = datetime.datetime.now() - begin
        logger.info('Finished. Duration: {}'.format(format_timespan(duration.seconds)))
Beispiel #9
0
    def run(self, target, arguments, sqlsession, fast_mode=False):
        """
        Run the security check.
        It consists in running commands with context requirements matching with the
        target's context.

        :param Target target: Target
        :param ArgumentsParser arguments: Arguments from command-line
        :param Session sqlsession: SQLAlchemy session
        :param SmartModulesLoader smartmodules_loader: Loader of SmartModules
        :param bool fast_mode: Set to true to disable prompts
        :return: Status
        :rtype: bool
        """
        if not self.tool.installed:
            return False

        i = 1
        command_outputs = list()
        for command in self.commands:
            if command.context_requirements.check_target_compliance(target):
                if not command.context_requirements.is_empty:
                    logger.info('Command #{num:02} matches requirements: ' \
                        '{context}'.format(num=i, context=command.context_requirements))

                cmdline = command.get_cmdline(self.tool.tool_dir, target,
                                              arguments)

                if fast_mode:
                    logger.info('Run command #{num:02}'.format(num=i))
                    mode = 'y'
                else:
                    mode = Output.prompt_choice(
                        'Run command {num}? [Y/n/f/q] '.format(
                            num='' if len(self.commands) == 1 else \
                                '#{num:02} '.format(num=i)),
                        choices={
                            'y': 'Yes',
                            'n': 'No',
                            #'t': 'New tab',
                            #'w': 'New window',
                            'f': 'Switch to fast mode (do not prompt anymore)',
                            'q': 'Quit the program',
                        },
                        default='y')

                if mode == 'q':
                    logger.warning('Exit !')
                    sys.exit(0)
                elif mode == 'n':
                    logger.info('Skipping this command')
                    continue
                else:
                    if mode == 'f':
                        logger.info('Switch to fast mode')
                        arguments.args.fast_mode = True

                    Output.begin_cmd(cmdline)
                    process = ProcessLauncher(cmdline)
                    if mode == 'y' or mode == 'f':
                        output = process.start()
                    # elif mode == 't':
                    #     output = process.start_in_new_tab()
                    #     logger.info('Command started in new tab')
                    # else:
                    #     output = process.start_in_new_window(self.name)
                    #     logger.info('Command started in new window')
                    Output.delimiter()
                    print()

                    output = StringUtils.interpret_ansi_escape_clear_lines(
                        output)
                    outputraw = StringUtils.remove_ansi_escape(output)
                    command_outputs.append(
                        CommandOutput(cmdline=cmdline,
                                      output=output,
                                      outputraw=outputraw))

                    # Run smartmodule method on output
                    postcheck = SmartPostcheck(
                        target.service, sqlsession, self.tool.name,
                        '{0}\n{1}'.format(cmdline, outputraw))
                    postcheck.run()

            else:
                logger.info('Command #{num:02} does not match requirements: ' \
                    '{context}'.format(num=i, context=command.context_requirements))
                logger.debug('Context string: {rawstr}'.format(
                    rawstr=command.context_requirements))

            i += 1

        # Add outputs in database
        if command_outputs:
            results_requester = ResultsRequester(sqlsession)
            results_requester.add_result(target.service.id, self.name,
                                         self.category, command_outputs)

        return True
Beispiel #10
0
    def __generate_table_web(self):
        """
        Generate the table with HTTP services registered in the mission
        """

        req = ServicesRequester(self.sqlsession)
        req.select_mission(self.mission)
        filter_ = Filter(FilterOperator.AND)
        filter_.add_condition(Condition('http', FilterData.SERVICE_EXACT))
        req.add_filter(filter_)
        services = req.get_results()

        if len(services) == 0:
            html = """
            <tr class="notfound">
                <td colspan="5">No record found</td>
            </tr>
            """
        else:
            html = ''

            # Unavailable thumbnail
            with open(REPORT_TPL_DIR + '/../img/unavailable.png', 'rb') as f:
                unavailable_b64 = base64.b64encode(f.read()).decode('ascii')

            for service in services:

                # Results HTML page name
                results = 'results-{ip}-{port}-{service}-{id}.html'.format(
                    ip=str(service.host.ip),
                    port=service.port,
                    service=service.name,
                    id=service.id)

                # Web technos
                try:
                    technos = ast.literal_eval(service.web_technos)
                except Exception as e:
                    logger.debug('Error when retrieving "web_technos" field ' \
                        'from db: {exc} for {service}'.format(
                            exc=e, service=service))
                    technos = list()

                tmp = list()
                for t in technos:
                    tmp.append('{}{}{}'.format(
                        t['name'], ' ' if t['version'] else '',
                        t['version'] if t['version'] else ''))
                webtechnos = ' | '.join(tmp)

                # Screenshot
                img_name = 'scren-{ip}-{port}-{id}'.format(ip=str(
                    service.host.ip),
                                                           port=service.port,
                                                           id=service.id)
                path = self.output_path + '/screenshots'

                if service.screenshot is not None \
                        and service.screenshot.status == ScreenStatus.OK \
                        and FileUtils.exists(path + '/' + img_name + '.png') \
                        and FileUtils.exists(path + '/' + img_name + '.thumb.png'):

                    screenshot = """
                    <a href="{screenlarge}" title="{title}" class="image-link">
                        <img src="{screenthumb}" class="border rounded">
                    </a>
                    """.format(screenlarge='screenshots/' + img_name + '.png',
                               title=service.html_title,
                               screenthumb='screenshots/' + img_name +
                               '.thumb.png')

                else:
                    screenshot = """
                    <img src="data:image/png;base64,{unavailable}">
                    """.format(unavailable=unavailable_b64)

                # HTML for table row
                html += """
                <tr{clickable}>
                    <td>{url}</td>
                    <td>{title}</td>
                    <td>{webtechnos}</td>
                    <td>{screenshot}</td>
                    <td>{checks}</td>
                </tr>
                """.format(
                    clickable=' class="clickable-row" data-href="{results}"'.format(
                        results=results) if len(service.results) > 0 else '',
                    url='<a href="{}" title="{}">{}</a>'.format(
                        service.url, service.url, StringUtils.shorten(service.url, 50)) \
                        if service.url else '',
                    title=StringUtils.shorten(service.html_title, 40),
                    webtechnos=webtechnos,
                    screenshot=screenshot,
                    checks=len(service.results))

        return html
Beispiel #11
0
    def __detect_credentials(self):
        """
        Detect usernames/credentials from command output
        Important: A command output might contain several usernames/passwords with the
        same pattern.

        Example method "search":

        >>> text = "
        ... Prefix
        ... Found credentials: 
        ...     admin:pass
        ...     toto:pwd
        ... lorem ipsum
        ... lorem ipsum"
        >>> import regex
        >>> m = regex.search('Pre[\s\S]*?Found credentials:(\s*(?P<m1>\S+):(?P<m2>\S+)\s*\n)+', text)
        >>> matchs = m.capturesdict()
        >>> matchs
        {'m1': ['admin', 'toto'], 'm2': ['pass', 'pwd']}

        >>> m = regex.search('(\[v\] Trying Credentials:\s*(?P<user>\S+)\s*(?P<password>\S+)\s*\n)+', text)
        >>> m.capturesdict()
        {'user': ['Miniwick', 'Miniwick', 'Miniwick', 'Miniwick', 'Miniwick'], 'password': ['password', 'admin', '123456', 'Password1', 'Miniwick']}
        >>> m = regex.search('WordPress[\s\S]*?(\[v\] Trying Credentials:\s*(?P<user>\S+)\s*(?P<password>\S+)\s*\n)+', text)
        >>> m.capturesdict()
        {'user': ['Miniwick', 'Miniwick', 'Miniwick', 'Miniwick', 'Miniwick'], 'password': ['password', 'admin', '123456', 'Password1', 'Miniwick']}

        """
        if self.service.name in creds_match.keys():

            if self.tool_name in creds_match[self.service.name].keys():
                p = creds_match[self.service.name][self.tool_name]

                for pattern in p.keys():

                    # Important: Multiple search/match
                    #m = re.search(pattern, self.cmd_output, re.IGNORECASE|re.DOTALL)
                    logger.debug('Search for creds pattern: {pattern}'.format(
                        pattern=pattern))

                    if 'user' not in p[pattern]:
                        logger.smarterror('Invalid matchstring for service={service}, ' \
                            ' tool={tool}: Missing "user" key'.format(
                                service=self.service.name,
                                tool=self.tool_name))
                        continue

                    # Matching method
                    if 'meth' in p[pattern] \
                            and p[pattern]['meth'] in ('finditer', 'search'):
                        method = p[pattern]['meth']
                    else:
                        method = 'finditer'

                    # Perform regexp matching
                    try:
                        if method == 'finditer':
                            m = re.finditer(pattern, self.cmd_output,
                                            re.IGNORECASE)
                        else:
                            m = regex.search(pattern, self.cmd_output,
                                             regex.IGNORECASE)
                    except Exception as e:
                        logger.warning('Error with matchstring [{pattern}], you should ' \
                            'review it. Exception: {exception}'.format(
                                pattern=pattern, exception=e))
                        break

                    if not m:
                        continue

                    pattern_match = False

                    # Method "finditer"
                    if method == 'finditer':
                        for match in m:
                            pattern_match = True
                            cred = dict()

                            # Replace tokens in user, pass, type
                            cred['user'] = self.__replace_tokens_from_matchobj(
                                p[pattern]['user'], match)
                            if cred['user'] is None:
                                continue

                            if 'pass' in p[pattern]:
                                cred[
                                    'pass'] = self.__replace_tokens_from_matchobj(
                                        p[pattern]['pass'], match)
                                if cred['pass'] is None:
                                    continue

                            if 'type' in p[pattern]:
                                cred[
                                    'type'] = self.__replace_tokens_from_matchobj(
                                        p[pattern]['type'], match)
                                if cred['type'] is None:
                                    continue

                            # Add username/cred to context
                            if 'pass' in cred:
                                self.cu.add_credentials(
                                    username=cred.get('user'),
                                    password=cred.get('pass'),
                                    auth_type=cred.get('type'))
                            elif 'user' in cred:
                                self.cu.add_username(
                                    username=cred.get('user'),
                                    auth_type=cred.get('type'))

                    # Method "search"
                    else:
                        pattern_match = True
                        matchs = m.capturesdict()
                        if 'm1' not in matchs:
                            logger.smarterror('Invalid matchstring for ' \
                                'service={service}, tool={tool}: Missing match ' \
                                'group'.format(
                                    service=self.service.name,
                                    tool=self.tool_name))
                            return

                        nb_groups = len(matchs['m1'])

                        for i in range(nb_groups):
                            cred = dict()

                            # Replace tokens in user, pass, type
                            cred['user'] = self.__replace_tokens_from_captdict(
                                p[pattern]['user'], matchs, i)
                            if cred['user'] is None:
                                continue

                            if 'pass' in p[pattern]:
                                cred[
                                    'pass'] = self.__replace_tokens_from_captdict(
                                        p[pattern]['pass'], matchs, i)
                                if cred['pass'] is None:
                                    continue

                            if 'type' in p[pattern]:
                                cred[
                                    'type'] = self.__replace_tokens_from_captdict(
                                        p[pattern]['type'], matchs, i)
                                if cred['type'] is None:
                                    continue

                            # Add username/cred to context
                            if 'pass' in cred:
                                self.cu.add_credentials(
                                    username=cred.get('user'),
                                    password=cred.get('pass'),
                                    auth_type=cred.get('type'))
                            elif 'user' in cred:
                                self.cu.add_username(
                                    username=cred.get('user'),
                                    auth_type=cred.get('type'))

                    # If a pattern has matched, skip the next patterns
                    if pattern_match:
                        logger.debug('Creds pattern matches (user only)')
                        return
Beispiel #12
0
    def __detect_products(self):
        """
        Detect product from command output
        
        For a given tool, and for a given product, if there are several matchstrings
        defined, their order is important because it stops after the first match.
        """
        if self.service.name in products_match.keys():

            for prodtype in products_match[self.service.name].keys():
                p = products_match[self.service.name][prodtype]
                break_prodnames = False

                for prodname in p.keys():

                    if self.tool_name in p[prodname].keys():
                        patterns = p[prodname][self.tool_name]

                        # List of patterns is supported (i.e. several different
                        # patterns for a given tool)
                        if type(patterns) == str:
                            patterns = [patterns]

                        for pattern in patterns:
                            version_detection = '[VERSION]' in pattern
                            pattern = pattern.replace('[VERSION]',
                                                      VERSION_REGEXP)

                            logger.debug(
                                'Search for products pattern: {pattern}'.
                                format(pattern=pattern))

                            try:
                                m = re.search(pattern, self.cmd_output,
                                              re.IGNORECASE)
                            except Exception as e:
                                logger.warning('Error with matchstring [{pattern}], ' \
                                    'you should review it. Exception: ' \
                                    '{exception}'.format(
                                        pattern=pattern, exception=e))
                                break

                            # If pattern matches cmd output, add detected product
                            # Note: For a given product type, only one name(+version)
                            # can be added.
                            if m:
                                logger.debug('Product pattern matches')
                                # Add version if present
                                if version_detection:
                                    try:
                                        if m.group('version') is not None:
                                            version = m.group('version')
                                        else:
                                            version = ''
                                        logger.debug(
                                            'Version detected: {version}'.
                                            format(version=version))
                                    except:
                                        version = ''
                                else:
                                    version = ''

                                # Add detected product to context
                                self.cu.add_product(prodtype, prodname,
                                                    version)

                                # Move to next product type because only one name
                                # (potentially with version) is supported per type.
                                # If name not found yet, give a try to next pattern
                                break_prodnames = True
                                #break

                        if break_prodnames:
                            break
Beispiel #13
0
    def __check_product(self, prodtype, prodname, prodversion):
        """
        Check if a product of a given type complies with the requirements.

        Requirements can be based on:
            - the product name only,
            - the product name and version.

        Compliance checks on product names are following these rules:
        Product name    Requirement     Result
        None            val             False
        val1            val1,val2       True
        val1            val2,val3       False
        any             None            True  
        any             'undefined'     False
        None            'undefined'     True

        Examples of possible context requirements on versions:
        any
        any|version_known
        vendor/product_name
        vendor/product_name|version_known
        vendor/product_name|7.*
        vendor/product_name|7.1.*
        vendor/product_name|>7.1
        vendor/product_name|<=7.0
        vendor/product_name|7.1.1   

        :param str prodtype: Product type 
        :param str prodname: Product name to check (can be None)
        :param str prodversion: Product version number to check (can be None)
        """
        requirement = self.products[prodtype]

        status = requirement is None
        status |= (requirement == ['undefined'] and prodname is None)
        if status:
            return True

        if prodname:
            for req_prod in requirement:
                req_prodname, req_prodvers = VersionUtils.extract_name_version(
                    req_prod)
                logger.debug(
                    'Required product: type={}, name={}, version={}'.format(
                        prodtype, req_prodname, req_prodvers))
                logger.debug(
                    'Target product: type={}, name={}, version={}'.format(
                        prodtype, prodname, prodversion))

                # Handle case where prefixed with "!" for inversion
                if len(req_prodname) > 0 and req_prodname[0] == '!':
                    inversion = True
                    req_prodname = req_prodname[1:]
                else:
                    inversion = False

                # When no special requirement on vendor/product_name but must be known
                if req_prodname.lower() == 'any':
                    # When version can be unknown
                    status = not req_prodvers

                    # When the version must be known (any value)
                    status |= (req_prodvers.lower() == 'version_known' and \
                        prodversion != '')

                    # When the version is unknown
                    status |= (req_prodvers.lower() == 'version_unknown' and \
                        prodversion == '')

                # When requirement on a defined vendor/product_name and it is matching
                elif req_prodname.lower() == prodname.lower():
                    # When no requirement on the version number
                    status = not req_prodvers

                    # When the version must be known but no requirement on its value
                    # status |= (req_prodvers.lower() == 'version_known' \
                    #     and prodversion != '')

                    # # When the version is unknown
                    # status |= (req_prodvers.lower() == 'version_unknown' and \
                    #     prodversion == '')

                    # When explicit requirement on the version number
                    status |= VersionUtils.check_version_requirement(
                        prodversion, req_prodvers)

                    if inversion and not status:
                        return True

                if status:
                    return True
        return False
    def __check_product(self, prodtype, prodname, prodversion):
        """
        Check if a product of a given type complies with the requirements.

        Requirements can be based on:
            - the product name only,
            - the product name and version.

        Compliance checks on product names are following these rules:
        Product name    Requirement     Result
        None            val             False
        val1            val1,val2       True
        val1            val2,val3       False
        any             None            True  
        any             'undefined'     False
        None            'undefined'     True

        Examples of possible context requirements on versions:
        any
        any|version_known
        vendor/product_name
        vendor/product_name|version_known
        vendor/product_name|7.*
        vendor/product_name|7.1.*
        vendor/product_name|>7.1
        vendor/product_name|<=7.0
        vendor/product_name|7.1.1   

        :param str prodtype: Product type 
        :param str prodname: Product name to check (can be None)
        :param str prodversion: Product version number to check (can be None)
        """
        requirement = self.products[prodtype]

        status  = requirement is None
        status |= (requirement == ['undefined'] and prodname is None)
        if status: 
            return True

        try:
            if prodname:
                for req_prod in requirement:
                    req_prodname, req_prodvers = VersionUtils.extract_name_version(req_prod)
                    logger.debug('Required product: type={}, name={}, version={}'.format(
                        prodtype, req_prodname, req_prodvers))
                    logger.debug('Target product: type={}, name={}, version={}'.format(
                        prodtype, prodname, prodversion))

                    # Handle case where prefixed with "!" for inversion
                    if len(req_prodname) > 0 and req_prodname[0] == '!':
                        inversion = True
                        req_prodname = req_prodname[1:]
                    else:
                        inversion = False

                    # When no special requirement on vendor/product_name but must be known
                    if req_prodname.lower() == 'any':
                        # When version can be unknown
                        status  = not req_prodvers

                        # When the version must be known (any value)
                        status |= (req_prodvers.lower() == 'version_known' and \
                            prodversion != '')

                        # When the version is unknown
                        status |= (req_prodvers.lower() == 'version_unknown' and \
                            prodversion == '')

                    # When requirement on a defined vendor/product_name and it is matching
                    elif req_prodname.lower() == prodname.lower():

                        # When no requirement on the version number
                        status  = not req_prodvers

                        # When the version must be known but no requirement on its value
                        # status |= (req_prodvers.lower() == 'version_known' \
                        #     and prodversion != '')

                        # # When the version is unknown
                        # status |= (req_prodvers.lower() == 'version_unknown' and \
                        #     prodversion == '')

                        # When explicit requirement on the version number 
                        # Perform version requirement check only if version of product
                        # has been detected. Otherwise, we condider it is better to 
                        # perform the check anyway in order to avoid to miss stuff
                        status |= (prodversion != '' and \
                            VersionUtils.check_version_requirement(
                                prodversion, req_prodvers))

                        if inversion and not status:
                            return True

                    if status:
                        return True
        except Exception as e:
            logger.error('An error occured when checking product requirements: {}'.format(e))
            logger.error('Following requirements syntax should be reviewed: {}'.format(
                requirement))
            logger.warning('Product requirements are ignored for this check')
            return True
        return False