Пример #1
0
    def generate_report(self, output_file):
        Logger.log_verbose("Writing OpenOffice report to file: %s" %
                           output_file)

        # Load docutils.
        with catch_warnings(record=True):
            from docutils.core import publish_file
            from docutils.writers.odf_odt import Writer, Reader

        # Create a temporary file for the reStructured Text report.
        with tempfile(suffix=".rst") as filename:

            # Generate the report in reStructured Text format.
            Logger.log_more_verbose("Writing temporary file in rST format...")
            with open(filename, "w") as source:
                self.write_report_to_open_file(source)

            # Convert to OpenOffice format.
            Logger.log_more_verbose("Converting to OpenOffice format...")
            with open(filename, "rU") as source:
                writer = Writer()
                reader = Reader()
                with catch_warnings(record=True):
                    with open(output_file, "wb") as destination:
                        publish_file(
                            source=source,
                            destination=destination,
                            destination_path=output_file,
                            reader=reader,
                            writer=writer,
                        )
Пример #2
0
    def __generate_report(self, output_file):
        Logger.log_verbose(
            "Writing LaTeX report to file: %s" % output_file)

        # Load docutils.
        with warnings.catch_warnings(record=True):
            from docutils.core import publish_file

        # Create a temporary file for the reStructured Text report.
        with tempfile(suffix=".rst") as filename:

            # Generate the report in reStructured Text format.
            Logger.log_more_verbose("Writing temporary file in rST format...")
            with open(filename, "w") as source:
                self.write_report_to_open_file(source)

            # Convert to LaTeX format.
            Logger.log_more_verbose("Converting to LaTeX format...")
            with open(filename, "rU") as source:
                with warnings.catch_warnings(record=True):
                    with open(output_file, "wb") as destination:
                        publish_file(
                            source = source,
                            destination = destination,
                            destination_path = output_file,
                            writer_name = "latex",
                        )
Пример #3
0
    def run(self, info):

        # Build the command line arguments for Nmap.
        args = shlex.split(Config.plugin_args["args"])
        if info.version == 6 and "-6" not in args:
            args.append("-6")
        args.append(info.address)

        # The Nmap output will be saved in XML format in a temporary file.
        with tempfile(suffix=".xml") as output:
            args.append("-oX")
            args.append(output)

            # Run Nmap and capture the text output.
            Logger.log("Launching Nmap against: %s" % info.address)
            Logger.log_more_verbose("Nmap arguments: %s" % " ".join(args))
            with ConnectionSlot(info.address):
                t1 = time()
                code = run_external_tool("nmap", args, callback=Logger.log_verbose)
                t2 = time()

            # Log the output in extra verbose mode.
            if code:
                Logger.log_error("Nmap execution failed, status code: %d" % code)
            else:
                Logger.log("Nmap scan finished in %s seconds for target: %s" % (t2 - t1, info.address))

            # Parse and return the results.
            return self.parse_nmap_results(info, output)
Пример #4
0
    def generate_report(self, output_file):
        Logger.log_verbose(
            "Writing OpenOffice report to file: %s" % output_file)

        # Load docutils.
        with catch_warnings(record=True):
            from docutils.core import publish_file
            from docutils.writers.odf_odt import Writer, Reader

        # Create a temporary file for the reStructured Text report.
        with tempfile(suffix=".rst") as filename:

            # Generate the report in reStructured Text format.
            Logger.log_more_verbose("Writing temporary file in rST format...")
            with open(filename, "w") as source:
                self.write_report_to_open_file(source)

            # Convert to OpenOffice format.
            Logger.log_more_verbose("Converting to OpenOffice format...")
            with open(filename, "rU") as source:
                writer = Writer()
                reader = Reader()
                with catch_warnings(record=True):
                    with open(output_file, "wb") as destination:
                        publish_file(
                            source = source,
                            destination = destination,
                            destination_path = output_file,
                            reader = reader,
                            writer = writer,
                        )
Пример #5
0
    def run(self, info):

        # Build the command line arguments for Nmap.
        args = shlex.split(Config.plugin_args["args"])
        if info.version == 6 and "-6" not in args:
            args.append("-6")
        args.append(info.address)

        # The Nmap output will be saved in XML format in a temporary file.
        with tempfile(suffix=".xml") as output:
            args.append("-oX")
            args.append(output)

            # Run Nmap and capture the text output.
            Logger.log("Launching Nmap against: %s" % info.address)
            Logger.log_more_verbose("Nmap arguments: %s" % " ".join(args))
            with ConnectionSlot(info.address):
                t1 = time()
                code = run_external_tool("nmap",
                                         args,
                                         callback=Logger.log_verbose)
                t2 = time()

            # Log the output in extra verbose mode.
            if code:
                Logger.log_error("Nmap execution failed, status code: %d" %
                                 code)
            else:
                Logger.log("Nmap scan finished in %s seconds for target: %s" %
                           (t2 - t1, info.address))

            # Parse and return the results.
            return self.parse_nmap_results(info, output)
Пример #6
0
    def recv_info(self, info):

        # Get the hostname to test.
        m_host = info.hostname

        # Workaround for a bug in SSLScan: if the target port doesn't answer
        # back the SSL handshake (i.e. if port 443 is open but another protocol
        # is being used) then SSLScan just blocks indefinitely.
        try:
            with ConnectionSlot(m_host):
                s = socket(AF_INET, SOCK_STREAM)
                try:
                    s.settimeout(4.0)
                    s.connect( (m_host, 443) )
                    s = wrap_socket(s)
                    s.shutdown(2)
                finally:
                    s.close()
        except Exception:
            Logger.log_error_more_verbose(
                "Host %r doesn't seem to support SSL, aborting." % m_host)
            return

        # Create a temporary output file.
        with tempfile(suffix = ".xml") as output:

            # Build the command line arguments.
            args = [
                "--no-failed",
                "--xml=" + output,  # non standard cmdline parsing :(
                m_host
            ]

            # Run SSLScan and capture the text output.
            Logger.log("Launching SSLScan against: %s" % m_host)
            Logger.log_more_verbose("SSLScan arguments: %s" % " ".join(args))
            with ConnectionSlot(m_host):
                t1 = time()
                code = run_external_tool("sslscan", args, callback=Logger.log_verbose)
                t2 = time()
            if code:
                Logger.log_error(
                    "SSLScan execution failed, status code: %d" % code)
            else:
                Logger.log("SSLScan scan finished in %s seconds for target: %s"
                           % (t2 - t1, m_host))

            # Parse and return the results.
            r, v = self.parse_sslscan_results(output)
            if v:
                Logger.log("Found %s SSL vulnerabilities." % v)
            else:
                Logger.log("No SSL vulnerabilities found.")
            return r
Пример #7
0
    def recv_info(self, info):
        if not isinstance(info, Url):
            return
        
        if not info.has_url_params and not info.has_post_params:
             Logger.log("URL '%s' has not parameters" % info.url)
             return
         
        # Get xss script executable
        xsser_script = self.get_xsser()

        results = []

        args = [
                "-u",
                info.url,
                ]
        with tempfile(prefix="tmpxss", suffix=".xml") as filename:
            args.extend([
                         "--xml=%s" % filename
                        ])
            if info.has_url_params:
                if self.run_xsser(info.url,xsser_script, args):
                    results.extend(self.parse_xsser_result(info,filename))
        
            if info.has_post_params:
                args.extend([
                             "-p",
                             "&".join([ "%s=%s" % (k, v) for k, v in info.post_params.iteritems()])
                             ])
                if self.run_xsser(info.url,xsser_script, args):
                    results.extend(self.parse_xsser_result(info,filename))        
        
        if results:
            Logger.log("Found %s xss vulns." % len(results))
        else:
            Logger.log("No xss vulns found.")
            

        return results
Пример #8
0
    def recv_info(self, info):

        m_host = info.hostname

        # Create a temporary output file.
        with tempfile(suffix = ".xml") as output:

            # Build the command line arguments.
            args = [
                "--no-failed",
                "--xml=%s" % output,
                m_host
            ]

            # Run Nmap and capture the text output.
            Logger.log("Launching SSLscan against: %s" % m_host)
            Logger.log_more_verbose("SSLscan arguments: %s" % " ".join(args))

            t1 = time()
            code = run_external_tool("sslscan", args, callback=Logger.log_verbose)
            t2 = time()

            # Log the output in extra verbose mode.
            if code:
                Logger.log_error(
                    "SSLscan execution failed, status code: %d" % code)
            else:
                Logger.log("SSLscan scan finished in %s seconds for target: %s"
                           % (t2 - t1, m_host))

            # Parse and return the results.
            r =  self.parse_sslscan_results(info, output)

            if r:
                Logger.log("Found %s SSL vulns." % len(r))
            else:
                Logger.log("No SSL vulns found.")

            return r
Пример #9
0
    def run(self, info):

        if not isinstance(info, URL):
            return

        if not info.has_url_params and not info.has_post_params:
            return

        # Get user args
        user_args = shlex.split(Config.plugin_args.get("args", []))

        # Result info
        results = []

        with tempfile(prefix="tmpxss", suffix=".xml") as filename:

            args = ["--xml=%s" % filename, "--no-head", "--threads", "1"]

            # Add the user args
            args.extend(user_args)

            if info.has_url_params:

                # Get payload for config injection point
                args.extend([
                    "-u",
                    "%s://%s" % (info.parsed_url.scheme, info.parsed_url.host),
                ])

                # When we want to try GET parameters, we must pass to xsser one by one.
                for param, value in info.parsed_url.query_params.iteritems():

                    # Not evaluate web server params
                    if param in WEB_SERVERS_VARS:
                        continue

                    # Prepare and reorder params
                    fixed_params = "&".join([
                        "%s=%s" % (x, y)
                        for x, y in info.parsed_url.query_params.iteritems()
                        if x != param
                    ])

                    # Add param to text + fixed params
                    if fixed_params:  # -> empty fixed params
                        params = "%s?%s&%s=" % (info.parsed_url.path,
                                                fixed_params, param)
                    else:
                        params = "%s?%s=" % (info.parsed_url.path, param)

                    # Prepary args for xsser
                    args.extend(["-g", params])

                    # Run xsser
                    if self.run_xsser(info.hostname, info.url, args):
                        results.extend(self.parse_xsser_result(info, filename))

            if info.has_post_params:
                args.extend([
                    "-u",
                    info.url,
                    "-p",
                    "&".join([
                        "%s=%s" % (k, v)
                        for k, v in info.post_params.iteritems()
                        if k not in WEB_SERVERS_VARS
                    ]),
                ])
                if self.run_xsser(info.hostname, info.url, args):
                    results.extend(self.parse_xsser_result(info, filename))

        if results:
            Logger.log("Found %s XSS vulnerabilities." % len(results))
        else:
            Logger.log_verbose("No XSS vulnerabilities found.")

        return results
Пример #10
0
    def run(self, info):

        # Get the path to the Nikto scanner and the configuration file.
        nikto_script, config = self.get_nikto()

        # Build the command line arguments.
        # The -output argument will be filled by run_nikto.
        args = [
            "-host", info.hostname,
            "-ssl" if info.is_https else "-nossl",
            "-port", str(info.parsed_url.port),
            "-Format", "csv",
            "-ask", "no",
            "-nointeractive",
            ##"-useproxy",
        ]
        for option in ("Pause", "timeout", "Tuning", "Plugins"):
            value = Config.plugin_args.get(option.lower(), "")
            value = value.replace("\r", "")
            value = value.replace("\n", "")
            value = value.replace("\t", "")
            value = value.replace(" ", "")
            if value:
                args.extend(["-" + option, value])

        # Create a temporary output file.
        with tempfile(suffix = ".csv") as output:

            # Append the output file name to the arguments.
            args.append("-output")
            args.append(output)

            # If we need to set the proxy or the cookies, we'll have to create
            # a temporary config file with the modified settings, since there's
            # no way of passing these options through the command line.
            if Config.audit_config.proxy_addr or Config.audit_config.cookie:

                # Make sure we have a config file.
                if not config:
                    raise ValueError("Missing configuration file!")

                # Create a temporary config file.
                with tempfile(suffix = ".conf") as tmp_config:

                    # Open the original config file.
                    with open(config, "rU") as src:

                        # Open the new config file.
                        with open(tmp_config, "w") as dst:

                            # Copy the contents of the original config file.
                            dst.write( src.read() )

                            # Append the new settings.
                            proxy_addr = Config.audit_config.proxy_addr
                            if proxy_addr:
                                parsed = parse_url(proxy_addr)
                                dst.write("PROXYHOST=%s\n" % parsed.host)
                                dst.write("PROXYPORT=%s\n" % parsed.port)
                                if Config.audit_config.proxy_user:
                                    dst.write("PROXYUSER=%s\n" %
                                              Config.audit_config.proxy_user)
                                if Config.audit_config.proxy_pass:
                                    dst.write("PROXYPASS=%s\n" %
                                              Config.audit_config.proxy_pass)
                            cookie_dict = Config.audit_config.cookie
                            if cookie_dict:
                                cookie = ";".join(
                                    '"%s=%s"' % x
                                    for x in cookie_dict.iteritems()
                                )
                                dst.write("STATIC-COOKIE=%s\n" % cookie)

                    # Set the new config file.
                    args = ["-config", tmp_config] + args

                    # Run Nikto and parse the output.
                    return self.run_nikto(info, output, nikto_script, args)

            # Otherwise, just use the supplied config file.
            else:
                if config:
                    args = ["-config", config] + args

                # Run Nikto and parse the output.
                return self.run_nikto(info, output, nikto_script, args)
Пример #11
0
    def launch_sslscan(self, hostname, port):
        """
        Launch SSLScan against the specified hostname and port.

        :param hostname: Hostname to test.
        :type hostname: str

        :param port: TCP port to test.
        :type port: int
        """

        # Don't scan the same host and port twice.
        if self.state.put("%s:%d" % (hostname, port), True):
            Logger.log_more_verbose(
                "Host %s:%d already scanned, skipped."
                % (hostname, port))
            return

        # Workaround for a bug in SSLScan: if the target port doesn't
        # answer back the SSL handshake (i.e. if port 443 is open but
        # another protocol is being used) then SSLScan just blocks
        # indefinitely.
        try:
            with ConnectionSlot(hostname):
                s = socket(AF_INET, SOCK_STREAM)
                try:
                    s.settimeout(4.0)
                    s.connect( (hostname, port) )
                    s = wrap_socket(s)
                    s.shutdown(2)
                finally:
                    s.close()
        except Exception:
            Logger.log_error_more_verbose(
                "Host %s:%d doesn't seem to support SSL, aborting."
                % (hostname, port))
            return

        # Create a temporary output file.
        with tempfile(suffix = ".xml") as output:

            # Build the command line arguments.
            args = [
                "--no-failed",
                "--xml=" + output,  # non standard cmdline parsing :(
                "%s:%d" % (hostname, port),
            ]

            # Run SSLScan and capture the text output.
            Logger.log("Launching SSLScan against: %s" % hostname)
            Logger.log_more_verbose("SSLScan arguments: %s" % " ".join(args))
            with ConnectionSlot(hostname):
                t1 = time()
                code = run_external_tool("sslscan", args,
                                         callback=Logger.log_verbose)
                t2 = time()
            if code:
                if code < 0 and sep == "\\":
                    asc_code = "0x%.8X" % code
                else:
                    asc_code = str(code)
                Logger.log_error(
                    "SSLScan execution failed, status code: %s" % asc_code)
            else:
                Logger.log("SSLScan scan finished in %s seconds for target: %s"
                           % (t2 - t1, hostname))

            # Parse and return the results.
            r, v = self.parse_sslscan_results(output)
            if v:
                Logger.log("Found %s SSL vulnerabilities." % v)
            else:
                Logger.log("No SSL vulnerabilities found.")
            return r
Пример #12
0
    def run(self, info):

        if not isinstance(info, URL):
            return

        if not info.has_url_params and not info.has_post_params:
            return

        # Get user args
        user_args = shlex.split(Config.plugin_args.get("args", []))

        # Result info
        results = []

        with tempfile(prefix="tmpxss", suffix=".xml") as filename:

            args = [
                "--xml=%s" % filename,
                "--no-head",
                "--threads",
                "1"
            ]

            # Add the user args
            args.extend(user_args)

            if info.has_url_params:

                # Get payload for config injection point
                args.extend([
                    "-u",
                    "%s://%s" % (info.parsed_url.scheme, info.parsed_url.host),
                ])

                # When we want to try GET parameters, we must pass to xsser one by one.
                for param, value in info.parsed_url.query_params.iteritems():

                    # Not evaluate web server params
                    if param in WEB_SERVERS_VARS:
                        continue

                    # Prepare and reorder params
                    fixed_params = "&".join(["%s=%s" % (x, y) for x, y in info.parsed_url.query_params.iteritems() if x != param])

                    # Add param to text + fixed params
                    if fixed_params: # -> empty fixed params
                        params = "%s?%s&%s=" % (info.parsed_url.path, fixed_params, param)
                    else:
                        params = "%s?%s=" % (info.parsed_url.path, param)

                    # Prepary args for xsser
                    args.extend([
                        "-g",
                        params
                    ])

                    # Run xsser
                    if self.run_xsser(info.hostname, info.url, args):
                        results.extend(self.parse_xsser_result(info, filename))

            if info.has_post_params:
                args.extend([
                    "-u",
                    info.url,
                    "-p",
                    "&".join(
                        ["%s=%s" % (k, v)
                            for k, v in info.post_params.iteritems() if k not in WEB_SERVERS_VARS]
                    ),
                ])
                if self.run_xsser(info.hostname, info.url, args):
                    results.extend(self.parse_xsser_result(info, filename))

        if results:
            Logger.log("Found %s XSS vulnerabilities." % len(results))
        else:
            Logger.log_verbose("No XSS vulnerabilities found.")

        return results
Пример #13
0
    def recv_info(self, info):

        # Get the path to the Nikto scanner.
        nikto_script = Config.plugin_args["exec"]
        if nikto_script and exists(nikto_script):
            nikto_dir = split(nikto_script)[0]
            nikto_dir = abspath(nikto_dir)
        else:
            nikto_dir = split(__file__)[0]
            nikto_dir = join(nikto_dir, "nikto")
            nikto_dir = abspath(nikto_dir)
            nikto_script = join(nikto_dir, "nikto.pl")
            if not nikto_script or not exists(nikto_script):
                nikto_script = "/usr/bin/nikto"
                if not exists(nikto_script):
                    nikto_script = Config.plugin_args["exec"]
                    if nikto_script:
                        Logger.log_error("Nikto not found! File: %s" %
                                         nikto_script)
                    else:
                        Logger.log_error("Nikto not found!")
                    return

        # Get the path to the configuration file.
        config = Config.plugin_args["config"]
        if config:
            config = join(nikto_dir, config)
            config = abspath(config)
            if not exists(config):
                config = "/etc/nikto.conf"
                if not exists(config):
                    config = Config.plugin_args["config"]
                    if config and exists(config):
                        config = abspath(config)
                    else:
                        if config:
                            Logger.log_error(
                                "Nikto config file not found! File: %s" %
                                config)
                        else:
                            Logger.log_error("Nikto config file not found!")
                        return

        # Build the command line arguments.
        # The -output argument will be filled by run_nikto.
        args = [
            "-host",
            info.hostname,
            "-ssl" if info.is_https else "-nossl",
            "-port",
            str(info.parsed_url.port),
            "-Format",
            "csv",
            "-ask",
            "no",
            "-nointeractive",
            ##"-useproxy",
        ]
        if config:
            args = ["-config", config] + args
        for option in ("Pause", "timeout", "Tuning"):
            value = Config.plugin_args.get(option.lower(), None)
            if value:
                args.extend(["-" + option, value])

        # Create a temporary output file.
        with tempfile(suffix=".csv") as output:

            # Append the output file name to the arguments.
            args.append("-output")
            args.append(output)

            # Run Nikto and parse the output.
            return self.run_nikto(info, output, nikto_script, args)
Пример #14
0
    def recv_info(self, info):

        # Get the path to the Nikto scanner.
        nikto_script = Config.plugin_args["exec"]
        if nikto_script and exists(nikto_script):
            nikto_dir = split(nikto_script)[0]
            nikto_dir = abspath(nikto_dir)
        else:
            nikto_dir = split(__file__)[0]
            nikto_dir = join(nikto_dir, "nikto")
            nikto_dir = abspath(nikto_dir)
            nikto_script = join(nikto_dir, "nikto.pl")
            if not nikto_script or not exists(nikto_script):
                nikto_script = "/usr/bin/nikto"
                if not exists(nikto_script):
                    nikto_script = Config.plugin_args["exec"]
                    if nikto_script:
                        Logger.log_error(
                            "Nikto not found! File: %s" % nikto_script)
                    else:
                        Logger.log_error("Nikto not found!")
                    return

        # Get the path to the configuration file.
        config = Config.plugin_args["config"]
        if config:
            config = join(nikto_dir, config)
            config = abspath(config)
            if not exists(config):
                config = "/etc/nikto.conf"
                if not exists(config):
                    config = Config.plugin_args["config"]
                    if config and exists(config):
                        config = abspath(config)
                    else:
                        if config:
                            Logger.log_error(
                                "Nikto config file not found! File: %s"
                                % config
                            )
                        else:
                            Logger.log_error("Nikto config file not found!")
                        return

        # Build the command line arguments.
        # The -output argument will be filled by run_nikto.
        args = [
            "-host", info.hostname,
            "-ssl" if info.is_https else "-nossl",
            "-port", str(info.parsed_url.port),
            "-Format", "csv",
            "-ask", "no",
            "-nointeractive",
            ##"-useproxy",
        ]
        if config:
            args = ["-config", config] + args
        for option in ("Pause", "timeout", "Tuning"):
            value = Config.plugin_args.get(option.lower(), None)
            if value:
                args.extend(["-" + option, value])

        # Create a temporary output file.
        with tempfile(suffix = ".csv") as output:

            # Append the output file name to the arguments.
            args.append("-output")
            args.append(output)

            # Run Nikto and parse the output.
            return self.run_nikto(info, output, nikto_script, args)
Пример #15
0
    def run(self, info):

        # Get the path to the Nikto scanner and the configuration file.
        nikto_script, config = self.get_nikto()

        # Build the command line arguments.
        # The -output argument will be filled by run_nikto.
        args = [
            "-host",
            info.hostname,
            "-ssl" if info.is_https else "-nossl",
            "-port",
            str(info.parsed_url.port),
            "-Format",
            "csv",
            "-ask",
            "no",
            "-nointeractive",
            ##"-useproxy",
        ]
        for option in ("Pause", "timeout", "Tuning", "Plugins"):
            value = Config.plugin_args.get(option.lower(), "")
            value = value.replace("\r", "")
            value = value.replace("\n", "")
            value = value.replace("\t", "")
            value = value.replace(" ", "")
            if value:
                args.extend(["-" + option, value])

        # Create a temporary output file.
        with tempfile(suffix=".csv") as output:

            # Append the output file name to the arguments.
            args.append("-output")
            args.append(output)

            # If we need to set the proxy or the cookies, we'll have to create
            # a temporary config file with the modified settings, since there's
            # no way of passing these options through the command line.
            if Config.audit_config.proxy_addr or Config.audit_config.cookie:

                # Make sure we have a config file.
                if not config:
                    raise ValueError("Missing configuration file!")

                # Create a temporary config file.
                with tempfile(suffix=".conf") as tmp_config:

                    # Open the original config file.
                    with open(config, "rU") as src:

                        # Open the new config file.
                        with open(tmp_config, "w") as dst:

                            # Copy the contents of the original config file.
                            dst.write(src.read())

                            # Append the new settings.
                            proxy_addr = Config.audit_config.proxy_addr
                            if proxy_addr:
                                parsed = parse_url(proxy_addr)
                                dst.write("PROXYHOST=%s\n" % parsed.host)
                                dst.write("PROXYPORT=%s\n" % parsed.port)
                                if Config.audit_config.proxy_user:
                                    dst.write("PROXYUSER=%s\n" %
                                              Config.audit_config.proxy_user)
                                if Config.audit_config.proxy_pass:
                                    dst.write("PROXYPASS=%s\n" %
                                              Config.audit_config.proxy_pass)
                            cookie_dict = Config.audit_config.cookie
                            if cookie_dict:
                                cookie = ";".join(
                                    '"%s=%s"' % x
                                    for x in cookie_dict.iteritems())
                                dst.write("STATIC-COOKIE=%s\n" % cookie)

                    # Set the new config file.
                    args = ["-config", tmp_config] + args

                    # Run Nikto and parse the output.
                    return self.run_nikto(info, output, nikto_script, args)

            # Otherwise, just use the supplied config file.
            else:
                if config:
                    args = ["-config", config] + args

                # Run Nikto and parse the output.
                return self.run_nikto(info, output, nikto_script, args)
Пример #16
0
    def launch_sslscan(self, hostname, port):
        """
        Launch SSLScan against the specified hostname and port.

        :param hostname: Hostname to test.
        :type hostname: str

        :param port: TCP port to test.
        :type port: int
        """

        # Don't scan the same host and port twice.
        if self.state.put("%s:%d" % (hostname, port), True):
            Logger.log_more_verbose(
                "Host %s:%d already scanned, skipped."
                % (hostname, port))
            return

        # Workaround for a bug in SSLScan: if the target port doesn't
        # answer back the SSL handshake (i.e. if port 443 is open but
        # another protocol is being used) then SSLScan just blocks
        # indefinitely.
        try:
            with ConnectionSlot(hostname):
                s = socket(AF_INET, SOCK_STREAM)
                try:
                    s.settimeout(4.0)
                    s.connect( (hostname, port) )
                    s = wrap_socket(s)
                    s.shutdown(2)
                finally:
                    s.close()
        except Exception:
            Logger.log_error_more_verbose(
                "Host %s:%d doesn't seem to support SSL, aborting."
                % (hostname, port))
            return

        # Create a temporary output file.
        with tempfile(suffix = ".xml") as output:

            # Build the command line arguments.
            args = [
                "--no-failed",
                "--xml=" + output,  # non standard cmdline parsing :(
                "%s:%d" % (hostname, port),
            ]

            # Run SSLScan and capture the text output.
            Logger.log("Launching SSLScan against: %s" % hostname)
            Logger.log_more_verbose("SSLScan arguments: %s" % " ".join(args))
            with ConnectionSlot(hostname):
                t1 = time()
                code = run_external_tool("sslscan", args,
                                         callback=Logger.log_verbose)
                t2 = time()
            if code:
                if code < 0 and sep == "\\":
                    asc_code = "0x%.8X" % code
                else:
                    asc_code = str(code)
                Logger.log_error(
                    "SSLScan execution failed, status code: %s" % asc_code)
            else:
                Logger.log("SSLScan scan finished in %s seconds for target: %s"
                           % (t2 - t1, hostname))

            # Parse and return the results.
            r, v = self.parse_sslscan_results(output)
            if v:
                Logger.log("Found %s SSL vulnerabilities." % v)
            else:
                Logger.log("No SSL vulnerabilities found.")
            return r