Ejemplo n.º 1
0
    def __init__(self,
                 *,
                 niceness=None,
                 lock_file_dir='/var/run/ospd',
                 **kwargs):
        """ Initializes the ospd-openvas daemon's internal data. """
        self.main_db = MainDB()
        self.nvti = NVTICache(self.main_db)

        super().__init__(
            customvtfilter=OpenVasVtsFilter(self.nvti),
            storage=dict,
            file_storage_dir=lock_file_dir,
            **kwargs,
        )

        self.server_version = __version__

        self._niceness = str(niceness)

        self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock')
        self.daemon_info['name'] = 'OSPd OpenVAS'
        self.scanner_info['name'] = 'openvas'
        self.scanner_info['version'] = ''  # achieved during self.init()
        self.scanner_info['description'] = OSPD_DESC

        for name, param in OSPD_PARAMS.items():
            self.set_scanner_param(name, param)

        self._sudo_available = None
        self._is_running_as_root = None

        self.scan_only_params = dict()
Ejemplo n.º 2
0
    def test_release(self, mock_redis):
        ctx = mock_redis.return_value

        maindb = MainDB(ctx)
        maindb.release()

        ctx.hdel.assert_called_with(DBINDEX_NAME, maindb.index)
        ctx.flushdb.assert_called_with()
Ejemplo n.º 3
0
    def test_try_db_index_error(self, mock_redis):
        ctx = mock_redis.return_value
        ctx.hsetnx.side_effect = Exception

        maindb = MainDB(ctx)

        with self.assertRaises(OspdOpenvasError):
            maindb.try_database(1)
Ejemplo n.º 4
0
    def test_release_database_by_index(self, mock_redis):
        ctx = mock_redis.return_value
        ctx.hdel.return_value = 1

        maindb = MainDB(ctx)

        maindb.release_database_by_index(3)

        ctx.hdel.assert_called_once_with(DBINDEX_NAME, 3)
Ejemplo n.º 5
0
    def test_try_database_false(self, mock_redis):
        ctx = mock_redis.return_value
        ctx.hsetnx.return_value = 0

        maindb = MainDB(ctx)

        ret = maindb.try_database(1)

        self.assertEqual(ret, False)
        ctx.hsetnx.assert_called_with(DBINDEX_NAME, 1, 1)
Ejemplo n.º 6
0
    def test_release_database(self, mock_redis):
        ctx = mock_redis.return_value
        ctx.hdel.return_value = 1

        db = MagicMock()
        db.index = 3
        maindb = MainDB(ctx)
        maindb.release_database(db)

        ctx.hdel.assert_called_once_with(DBINDEX_NAME, 3)
        db.flush.assert_called_with()
Ejemplo n.º 7
0
    def __init__(
        self,
        *,
        niceness=None,
        lock_file_dir='/var/lib/openvas',
        mqtt_broker_address="localhost",
        mqtt_broker_port=1883,
        disable_notus_hashsum_verification=False,
        **kwargs,
    ):
        """Initializes the ospd-openvas daemon's internal data."""

        self.main_db = MainDB()
        notus_dir = kwargs.get('notus_feed_dir')
        notus = None
        if notus_dir:
            ndir = Path(notus_dir)
            verifier = hashsum_verificator(ndir,
                                           disable_notus_hashsum_verification)
            notus = Notus(ndir, self.main_db.ctx, verifier)

        self.nvti = NVTICache(
            self.main_db,
            notus,
        )

        super().__init__(
            customvtfilter=OpenVasVtsFilter(self.nvti),
            storage=dict,
            file_storage_dir=lock_file_dir,
            **kwargs,
        )

        self.server_version = __version__

        self._niceness = str(niceness)

        self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock')
        self.daemon_info['name'] = 'OSPd OpenVAS'
        self.scanner_info['name'] = 'openvas'
        self.scanner_info['version'] = ''  # achieved during self.init()
        self.scanner_info['description'] = OSPD_DESC

        for name, param in OSPD_PARAMS.items():
            self.set_scanner_param(name, param)

        self._sudo_available = None
        self._is_running_as_root = None

        self.scan_only_params = dict()

        self._mqtt_broker_address = mqtt_broker_address
        self._mqtt_broker_port = mqtt_broker_port
Ejemplo n.º 8
0
    def test_get_new_kb_database(self, mock_redis):
        ctx = mock_redis.return_value

        maindb = MainDB(ctx)
        maindb._max_dbindex = 123  # pylint: disable=protected-access

        ctx.hsetnx.side_effect = [0, 0, 1]

        kbdb = maindb.get_new_kb_database()

        self.assertEqual(kbdb.index, 3)
        ctx.flushdb.assert_called_once_with()
Ejemplo n.º 9
0
    def test_get_new_kb_database_none(self, mock_redis):
        ctx = mock_redis.return_value

        maindb = MainDB(ctx)
        maindb._max_dbindex = 3  # pylint: disable=protected-access

        ctx.hsetnx.side_effect = [0, 0, 0]

        kbdb = maindb.get_new_kb_database()

        self.assertIsNone(kbdb)
        ctx.flushdb.assert_not_called()
Ejemplo n.º 10
0
    def test_find_kb_database_by_scan_id(self, mock_openvas_db, mock_redis):
        ctx = mock_redis.return_value

        new_ctx = 'foo'  # just some object to compare
        mock_openvas_db.create_context.return_value = new_ctx
        mock_openvas_db.get_key_count.side_effect = [0, 1]

        maindb = MainDB(ctx)
        maindb._max_dbindex = 3  # pylint: disable=protected-access

        kbdb = maindb.find_kb_database_by_scan_id('foo')

        mock_openvas_db.get_key_count.assert_called_with(
            new_ctx, 'internal/foo')
        self.assertEqual(kbdb.index, 2)
        self.assertIs(kbdb.ctx, new_ctx)
Ejemplo n.º 11
0
    def test_find_kb_database_by_scan_id(self, mock_openvas_db, mock_redis):
        ctx = mock_redis.return_value

        new_ctx = 'bar'  # just some object to compare
        mock_openvas_db.create_context.return_value = new_ctx
        mock_openvas_db.get_single_item.side_effect = [None, 'ipsum']

        maindb = MainDB(ctx)
        maindb._max_dbindex = 3  # pylint: disable=protected-access

        scan_id, kbdb = maindb.find_kb_database_by_scan_id('foo')

        mock_openvas_db.get_single_item.assert_called_with(
            new_ctx, 'internal/foo/globalscanid')
        self.assertEqual(scan_id, 'ipsum')
        self.assertEqual(kbdb.index, 2)
        self.assertIs(kbdb.ctx, new_ctx)
Ejemplo n.º 12
0
    def test_find_kb_database_by_scan_id_none(self, mock_openvas_db,
                                              mock_redis):
        ctx = mock_redis.return_value

        new_ctx = 'bar'  # just some object to compare
        mock_openvas_db.create_context.return_value = new_ctx
        mock_openvas_db.get_single_item.return_value = None

        maindb = MainDB(ctx)
        maindb._max_dbindex = 2  # pylint: disable=protected-access

        scan_id, kbdb = maindb.find_kb_database_by_scan_id('foo')

        mock_openvas_db.get_single_item.assert_called_once_with(
            new_ctx, 'internal/foo/globalscanid')
        self.assertIsNone(scan_id)
        self.assertIsNone(kbdb)
Ejemplo n.º 13
0
    def nvti(self) -> NVTICache:
        if self._nvti is None:
            try:
                maindb = MainDB()
                self._nvti = NVTICache(maindb)
            except SystemExit:
                raise OspdOpenvasError(
                    "Could not connect to the Redis KB") from None

        return self._nvti
Ejemplo n.º 14
0
    def test_max_database_index(self, mock_redis):
        ctx = mock_redis.return_value
        ctx.config_get.return_value = {'databases': '123'}

        maindb = MainDB(ctx)

        max_db = maindb.max_database_index

        self.assertEqual(max_db, 123)
        ctx.config_get.assert_called_with('databases')
Ejemplo n.º 15
0
    def test_max_database_index_fail(self, mock_redis):
        ctx = mock_redis.return_value
        ctx.config_get.return_value = {}

        maindb = MainDB(ctx)

        with self.assertRaises(OspdOpenvasError):
            max_db = (  # pylint: disable=unused-variable
                maindb.max_database_index)

        ctx.config_get.assert_called_with('databases')
Ejemplo n.º 16
0
class OSPDopenvas(OSPDaemon):
    """Class for ospd-openvas daemon."""
    def __init__(
        self,
        *,
        niceness=None,
        lock_file_dir='/var/lib/openvas',
        mqtt_broker_address="localhost",
        mqtt_broker_port=1883,
        disable_notus_hashsum_verification=False,
        **kwargs,
    ):
        """Initializes the ospd-openvas daemon's internal data."""

        self.main_db = MainDB()
        notus_dir = kwargs.get('notus_feed_dir')
        notus = None
        if notus_dir:
            ndir = Path(notus_dir)
            verifier = hashsum_verificator(ndir,
                                           disable_notus_hashsum_verification)
            notus = Notus(ndir, self.main_db.ctx, verifier)

        self.nvti = NVTICache(
            self.main_db,
            notus,
        )

        super().__init__(
            customvtfilter=OpenVasVtsFilter(self.nvti),
            storage=dict,
            file_storage_dir=lock_file_dir,
            **kwargs,
        )

        self.server_version = __version__

        self._niceness = str(niceness)

        self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock')
        self.daemon_info['name'] = 'OSPd OpenVAS'
        self.scanner_info['name'] = 'openvas'
        self.scanner_info['version'] = ''  # achieved during self.init()
        self.scanner_info['description'] = OSPD_DESC

        for name, param in OSPD_PARAMS.items():
            self.set_scanner_param(name, param)

        self._sudo_available = None
        self._is_running_as_root = None

        self.scan_only_params = dict()

        self._mqtt_broker_address = mqtt_broker_address
        self._mqtt_broker_port = mqtt_broker_port

    def init(self, server: BaseServer) -> None:

        notus_handler = NotusResultHandler(self.report_results)

        if self._mqtt_broker_address:
            try:
                client = MQTTClient(self._mqtt_broker_address,
                                    self._mqtt_broker_port, "ospd")
                daemon = MQTTDaemon(client)
                subscriber = MQTTSubscriber(client)

                subscriber.subscribe(ResultMessage,
                                     notus_handler.result_handler)
                daemon.run()
            except (ConnectionRefusedError, gaierror, ValueError) as e:
                logger.error(
                    "Could not connect to MQTT broker at %s, error was: %s."
                    " Unable to get results from Notus.",
                    self._mqtt_broker_address,
                    e,
                )
        else:
            logger.info(
                "MQTT Broker Adress empty. MQTT disabled. Unable to get Notus"
                " results.")

        self.scan_collection.init()

        server.start(self.handle_client_stream)

        self.scanner_info['version'] = Openvas.get_version()

        self.set_params_from_openvas_settings()

        with self.feed_lock.wait_for_lock():
            Openvas.load_vts_into_redis()
            self.set_feed_info()

            logger.debug("Calculating vts integrity check hash...")
            vthelper = VtHelper(self.nvti)
            self.vts.sha256_hash = vthelper.calculate_vts_collection_hash()

        self.initialized = True

    def set_params_from_openvas_settings(self):
        """Set OSPD_PARAMS with the params taken from the openvas executable."""
        param_list = Openvas.get_settings()

        for elem in param_list:  # pylint: disable=consider-using-dict-items
            if elem not in OSPD_PARAMS:
                self.scan_only_params[elem] = param_list[elem]
            else:
                OSPD_PARAMS[elem]['default'] = param_list[elem]

    def feed_is_outdated(self, current_feed: str) -> Optional[bool]:
        """Compare the current feed with the one in the disk.

        Return:
            False if there is no new feed.
            True if the feed version in disk is newer than the feed in
                redis cache.
            None if there is no feed on the disk.
        """
        current_feed = safe_int(current_feed)
        if current_feed is None:
            logger.debug(
                "Wrong PLUGIN_SET format in plugins feed file "
                "'plugin_feed_info.inc'. Format has to"
                " be yyyymmddhhmm. For example 'PLUGIN_SET = \"201910251033\"'"
            )

        feed_date = None
        feed_info = self.get_feed_info()
        if feed_info:
            feed_date = safe_int(feed_info.get("PLUGIN_SET"))

        logger.debug("Current feed version: %s", current_feed)
        logger.debug("Plugin feed version: %s", feed_date)

        return ((not feed_date) or (not current_feed)
                or (current_feed < feed_date))

    def get_feed_info(self) -> Dict[str, Any]:
        """Parses the current plugin_feed_info.inc file"""

        plugins_folder = self.scan_only_params.get('plugins_folder')
        if not plugins_folder:
            raise OspdOpenvasError("Error: Path to plugins folder not found.")

        feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc'
        if not feed_info_file.exists():
            self.set_params_from_openvas_settings()
            logger.debug('Plugins feed file %s not found.', feed_info_file)
            return {}

        feed_info = {}
        with feed_info_file.open(encoding='utf-8') as fcontent:
            for line in fcontent:

                try:
                    key, value = line.split('=', 1)
                except ValueError:
                    continue
                key = key.strip()
                value = value.strip()
                value = value.replace(';', '')
                value = value.replace('"', '')
                if value:
                    feed_info[key] = value

        return feed_info

    def set_feed_info(self):
        """Set feed current information to be included in the response of
        <get_version/> command
        """
        current_feed = self.nvti.get_feed_version()
        self.set_vts_version(vts_version=current_feed)

        feed_info = self.get_feed_info()
        self.set_feed_vendor(feed_info.get("FEED_VENDOR", "unknown"))
        self.set_feed_home(feed_info.get("FEED_HOME", "unknown"))
        self.set_feed_name(feed_info.get("PLUGIN_FEED", "unknown"))

    def check_feed_self_test(self) -> Dict:
        """Perform a feed sync self tests and check if the feed lock file is
        locked.
        """
        feed_status = dict()

        # It is locked by the current process
        if self.feed_lock.has_lock():
            feed_status["lockfile_in_use"] = '1'
        # Check if we can get the lock
        else:
            with self.feed_lock as fl:
                # It is available
                if fl.has_lock():
                    feed_status["lockfile_in_use"] = '0'
                # Locked by another process
                else:
                    feed_status["lockfile_in_use"] = '1'

        feed = Feed()
        _exit_error, _error_msg = feed.perform_feed_sync_self_test_success()
        feed_status["self_test_exit_error"] = str(_exit_error)
        feed_status["self_test_error_msg"] = _error_msg

        return feed_status

    def check_feed(self):
        """Check if there is a feed update.

        Wait until all the running scans finished. Set a flag to announce there
        is a pending feed update, which avoids to start a new scan.
        """
        if not self.vts.is_cache_available:
            return

        current_feed = self.nvti.get_feed_version()
        is_outdated = self.feed_is_outdated(current_feed)

        # Check if the nvticache in redis is outdated
        if not current_feed or is_outdated:
            with self.feed_lock as fl:
                if fl.has_lock():
                    self.initialized = False
                    Openvas.load_vts_into_redis()
                    self.set_feed_info()

                    vthelper = VtHelper(self.nvti)
                    self.vts.sha256_hash = (
                        vthelper.calculate_vts_collection_hash())
                    self.initialized = True
                else:
                    logger.debug("The feed was not upload or it is outdated, "
                                 "but other process is locking the update. "
                                 "Trying again later...")
                    return

    def scheduler(self):
        """This method is called periodically to run tasks."""
        self.check_feed()

    def get_vt_iterator(self,
                        vt_selection: List[str] = None,
                        details: bool = True) -> Iterator[Tuple[str, Dict]]:
        vthelper = VtHelper(self.nvti)
        return vthelper.get_vt_iterator(vt_selection, details)

    @property
    def is_running_as_root(self) -> bool:
        """Check if it is running as root user."""
        if self._is_running_as_root is not None:
            return self._is_running_as_root

        self._is_running_as_root = False
        if geteuid() == 0:
            self._is_running_as_root = True

        return self._is_running_as_root

    @property
    def sudo_available(self) -> bool:
        """Checks that sudo is available"""
        if self._sudo_available is not None:
            return self._sudo_available

        if self.is_running_as_root:
            self._sudo_available = False
            return self._sudo_available

        self._sudo_available = Openvas.check_sudo()

        return self._sudo_available

    def check(self) -> bool:
        """Checks that openvas command line tool is found and
        is executable."""
        has_openvas = Openvas.check()
        if not has_openvas:
            logger.error(
                'openvas executable not available. Please install openvas'
                ' into your PATH.')
        return has_openvas

    def report_openvas_scan_status(self, kbdb: BaseDB, scan_id: str):
        """Get all status entries from redis kb.

        Arguments:
            kbdb: KB context where to get the status from.
            scan_id: Scan ID to identify the current scan.
        """
        all_status = kbdb.get_scan_status()
        all_hosts = dict()
        finished_hosts = list()
        for res in all_status:
            try:
                current_host, launched, total = res.split('/')
            except ValueError:
                continue

            try:
                if float(total) == 0:
                    continue
                elif float(total) == ScanProgress.DEAD_HOST:
                    host_prog = ScanProgress.DEAD_HOST
                else:
                    host_prog = int((float(launched) / float(total)) * 100)
            except TypeError:
                continue

            all_hosts[current_host] = host_prog

            if (host_prog == ScanProgress.DEAD_HOST
                    or host_prog == ScanProgress.FINISHED):
                finished_hosts.append(current_host)

            logger.debug('%s: Host %s has progress: %d', scan_id, current_host,
                         host_prog)

        self.set_scan_progress_batch(scan_id, host_progress=all_hosts)

        self.sort_host_finished(scan_id, finished_hosts)

    def report_openvas_results(self, db: BaseDB, scan_id: str) -> bool:
        """Get all result entries from redis kb.

        Arguments:
            db: KB context where to get the results from.
            scan_id: Scan ID to identify the current scan.
        """

        # result_type|||host ip|||hostname|||port|||OID|||value[|||uri]
        all_results = db.get_result()
        results = []
        for res in all_results:
            if not res:
                continue
            msg = res.split('|||')
            result = {
                "result_type": msg[0],
                "host_ip": msg[1],
                "host_name": msg[2],
                "port": msg[3],
                "oid": msg[4],
                "value": msg[5],
            }
            if len(msg) > 6:
                result["uri"] = msg[6]

            results.append(result)

        return self.report_results(results, scan_id)

    def report_results(self, results: list, scan_id: str) -> bool:
        """Reports all results given in a list.

        Arguments:
            results: list of results each list item must contain a dictionary
            with following fields: result_type, host_ip, host_name, port, oid,
            value, uri (optional)

        """

        vthelper = VtHelper(self.nvti)

        res_list = ResultList()
        total_dead = 0
        for res in results:
            if not res:
                continue

            roid = res["oid"].strip()
            rqod = ''
            rname = ''
            current_host = res["host_ip"].strip() if res["host_ip"] else ''
            rhostname = res["host_name"].strip() if res["host_name"] else ''
            host_is_dead = ("Host dead" in res["value"]
                            or res["result_type"] == "DEADHOST")
            host_deny = "Host access denied" in res["value"]
            start_end_msg = (res["result_type"] == "HOST_START"
                             or res["result_type"] == "HOST_END")
            host_count = res["result_type"] == "HOSTS_COUNT"
            vt_aux = None

            # URI is optional and containing must be checked
            ruri = res["uri"] if "uri" in res else ""

            if (not host_is_dead and not host_deny and not start_end_msg
                    and not host_count):
                if not roid and res["result_type"] != 'ERRMSG':
                    logger.warning('Missing VT oid for a result')
                vt_aux = vthelper.get_single_vt(roid)
                if not vt_aux:
                    logger.warning('Invalid VT oid %s for a result', roid)
                else:
                    if vt_aux.get('qod_type'):
                        qod_t = vt_aux.get('qod_type')
                        rqod = self.nvti.QOD_TYPES[qod_t]
                    elif vt_aux.get('qod'):
                        rqod = vt_aux.get('qod')

                    rname = vt_aux.get('name')

            if res["result_type"] == 'ERRMSG':
                res_list.add_scan_error_to_list(
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=res["value"],
                    port=res["port"],
                    test_id=roid,
                    uri=ruri,
                )

            elif (res["result_type"] == 'HOST_START'
                  or res["result_type"] == 'HOST_END'):
                res_list.add_scan_log_to_list(
                    host=current_host,
                    name=res["result_type"],
                    value=res["value"],
                )

            elif res["result_type"] == 'LOG':
                res_list.add_scan_log_to_list(
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=res["value"],
                    port=res["port"],
                    qod=rqod,
                    test_id=roid,
                    uri=ruri,
                )

            elif res["result_type"] == 'HOST_DETAIL':
                res_list.add_scan_host_detail_to_list(
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=res["value"],
                    uri=ruri,
                )

            elif res["result_type"] == 'ALARM':
                rseverity = vthelper.get_severity_score(vt_aux)
                res_list.add_scan_alarm_to_list(
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=res["value"],
                    port=res["port"],
                    test_id=roid,
                    severity=rseverity,
                    qod=rqod,
                    uri=ruri,
                )

            # To process non-scanned dead hosts when
            # test_alive_host_only in openvas is enable
            elif res["result_type"] == 'DEADHOST':
                try:
                    total_dead = total_dead + int(res["value"])
                except TypeError:
                    logger.debug('Error processing dead host count')

            # To update total host count
            if res["result_type"] == 'HOSTS_COUNT':
                try:
                    count_total = int(res["value"])
                    logger.debug(
                        '%s: Set total hosts counted by OpenVAS: %d',
                        scan_id,
                        count_total,
                    )
                    self.set_scan_total_hosts(scan_id, count_total)
                except TypeError:
                    logger.debug('Error processing total host count')

        # Insert result batch into the scan collection table.
        if len(res_list):
            self.scan_collection.add_result_list(scan_id, res_list)
            logger.debug(
                '%s: Inserting %d results into scan collection table',
                scan_id,
                len(res_list),
            )
        if total_dead:
            logger.debug(
                '%s: Set dead hosts counted by OpenVAS: %d',
                scan_id,
                total_dead,
            )
            self.scan_collection.set_amount_dead_hosts(scan_id,
                                                       total_dead=total_dead)

        return len(res_list) > 0

    @staticmethod
    def is_openvas_process_alive(openvas_process: psutil.Popen) -> bool:

        try:
            if openvas_process.status() == psutil.STATUS_ZOMBIE:
                logger.debug("Process is a Zombie, waiting for it to clean up")
                openvas_process.wait()
        except psutil.NoSuchProcess:
            return False

        return openvas_process.is_running()

    def stop_scan_cleanup(
            self,
            kbdb: BaseDB,
            scan_id: str,
            ovas_process: psutil.Popen,  # pylint: disable=arguments-differ
    ):
        """Set a key in redis to indicate the wrapper is stopped.
        It is done through redis because it is a new multiprocess
        instance and it is not possible to reach the variables
        of the grandchild process.
        Indirectly sends SIGUSR1 to the running openvas scan process
        via an invocation of openvas with the --scan-stop option to
        stop it."""

        if kbdb:
            # Set stop flag in redis
            kbdb.stop_scan(scan_id)

            # Check if openvas is running
            if ovas_process.is_running():
                # Cleaning in case of Zombie Process
                if ovas_process.status() == psutil.STATUS_ZOMBIE:
                    logger.debug(
                        '%s: Process with PID %s is a Zombie process.'
                        ' Cleaning up...',
                        scan_id,
                        ovas_process.pid,
                    )
                    ovas_process.wait()
                # Stop openvas process and wait until it stopped
                else:
                    can_stop_scan = Openvas.stop_scan(
                        scan_id,
                        not self.is_running_as_root and self.sudo_available,
                    )
                    if not can_stop_scan:
                        logger.debug(
                            'Not possible to stop scan process: %s.',
                            ovas_process,
                        )
                        return

                    logger.debug('Stopping process: %s', ovas_process)

                    while ovas_process.is_running():
                        if ovas_process.status() == psutil.STATUS_ZOMBIE:
                            ovas_process.wait()
                        else:
                            time.sleep(0.1)
            else:
                logger.debug(
                    "%s: Process with PID %s already stopped",
                    scan_id,
                    ovas_process.pid,
                )

            # Clean redis db
            for scan_db in kbdb.get_scan_databases():
                self.main_db.release_database(scan_db)

    def exec_scan(self, scan_id: str):
        """Starts the OpenVAS scanner for scan_id scan."""
        params = self.scan_collection.get_options(scan_id)
        if params.get("dry_run"):
            dryrun = DryRun(self)
            dryrun.exec_dry_run_scan(scan_id, self.nvti, OSPD_PARAMS)
            return

        do_not_launch = False
        kbdb = self.main_db.get_new_kb_database()
        scan_prefs = PreferenceHandler(scan_id, kbdb, self.scan_collection,
                                       self.nvti)
        kbdb.add_scan_id(scan_id)
        scan_prefs.prepare_target_for_openvas()

        if not scan_prefs.prepare_ports_for_openvas():
            self.add_scan_error(scan_id,
                                name='',
                                host='',
                                value='Invalid port list.')
            do_not_launch = True

        # Set credentials
        if not scan_prefs.prepare_credentials_for_openvas():
            error = ('All authentifications contain errors.' +
                     'Starting unauthenticated scan instead.')
            self.add_scan_error(
                scan_id,
                name='',
                host='',
                value=error,
            )
            logger.error(error)
        errors = scan_prefs.get_error_messages()
        for e in errors:
            error = 'Malformed credential. ' + e
            self.add_scan_error(
                scan_id,
                name='',
                host='',
                value=error,
            )
            logger.error(error)

        if not scan_prefs.prepare_plugins_for_openvas():
            self.add_scan_error(scan_id,
                                name='',
                                host='',
                                value='No VTS to run.')
            do_not_launch = True

        scan_prefs.prepare_main_kbindex_for_openvas()
        scan_prefs.prepare_host_options_for_openvas()
        scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS)
        scan_prefs.prepare_reverse_lookup_opt_for_openvas()
        scan_prefs.prepare_alive_test_option_for_openvas()

        # VT preferences are stored after all preferences have been processed,
        # since alive tests preferences have to be able to overwrite default
        # preferences of ping_host.nasl for the classic method.
        scan_prefs.prepare_nvt_preferences()
        scan_prefs.prepare_boreas_alive_test()

        # Release memory used for scan preferences.
        del scan_prefs

        scan_stopped = self.get_scan_status(scan_id) == ScanStatus.STOPPED
        if do_not_launch or kbdb.scan_is_stopped(scan_id) or scan_stopped:
            self.main_db.release_database(kbdb)
            return

        openvas_process = Openvas.start_scan(
            scan_id,
            not self.is_running_as_root and self.sudo_available,
            self._niceness,
        )

        if openvas_process is None:
            self.main_db.release_database(kbdb)
            return

        kbdb.add_scan_process_id(openvas_process.pid)
        logger.debug('pid = %s', openvas_process.pid)

        # Wait until the scanner starts and loads all the preferences.
        while kbdb.get_status(scan_id) == 'new':
            res = openvas_process.poll()
            if res and res < 0:
                self.stop_scan_cleanup(kbdb, scan_id, openvas_process)
                logger.error(
                    'It was not possible run the task %s, since openvas ended '
                    'unexpectedly with errors during launching.',
                    scan_id,
                )
                return

            time.sleep(1)

        got_results = False
        while True:

            openvas_process_is_alive = self.is_openvas_process_alive(
                openvas_process)
            target_is_finished = kbdb.target_is_finished(scan_id)
            scan_stopped = self.get_scan_status(scan_id) == ScanStatus.STOPPED

            # Report new Results and update status
            got_results = self.report_openvas_results(kbdb, scan_id)
            self.report_openvas_scan_status(kbdb, scan_id)

            # Check if the client stopped the whole scan
            if scan_stopped:
                logger.debug('%s: Scan stopped by the client', scan_id)

                self.stop_scan_cleanup(kbdb, scan_id, openvas_process)

                # clean main_db, but wait for scanner to finish.
                while not kbdb.target_is_finished(scan_id):
                    if not self.is_openvas_process_alive(openvas_process):
                        break
                    logger.debug('%s: Waiting for openvas to finish', scan_id)
                    time.sleep(1)
                self.main_db.release_database(kbdb)
                return

            # Scan end. No kb in use for this scan id
            if target_is_finished:
                logger.debug('%s: Target is finished', scan_id)
                break

            if not openvas_process_is_alive:
                logger.error(
                    'Task %s was unexpectedly stopped or killed.',
                    scan_id,
                )
                self.add_scan_error(
                    scan_id,
                    name='',
                    host='',
                    value='Task was unexpectedly stopped or killed.',
                )

                # check for scanner error messages before leaving.
                self.report_openvas_results(kbdb, scan_id)

                kbdb.stop_scan(scan_id)

                for scan_db in kbdb.get_scan_databases():
                    self.main_db.release_database(scan_db)
                self.main_db.release_database(kbdb)
                return

            # Wait a second before trying to get result from redis if there
            # was no results before.
            # Otherwise, wait 50 msec to give access other process to redis.
            if not got_results:
                time.sleep(1)
            else:
                time.sleep(0.05)
            got_results = False

        # Sleep a second to be sure to get all notus results
        time.sleep(1)
        # Delete keys from KB related to this scan task.
        logger.debug('%s: End Target. Release main database', scan_id)
        self.main_db.release_database(kbdb)
Ejemplo n.º 17
0
class OSPDopenvas(OSPDaemon):

    """ Class for ospd-openvas daemon. """

    def __init__(
        self, *, niceness=None, lock_file_dir='/var/run/ospd', **kwargs
    ):
        """ Initializes the ospd-openvas daemon's internal data. """

        super().__init__(customvtfilter=OpenVasVtsFilter(), **kwargs)

        self.server_version = __version__

        self._niceness = str(niceness)

        self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock')
        self.daemon_info['name'] = 'OSPd OpenVAS'
        self.scanner_info['name'] = 'openvas'
        self.scanner_info['version'] = ''  # achieved during self.init()
        self.scanner_info['description'] = OSPD_DESC

        for name, param in OSPD_PARAMS.items():
            self.set_scanner_param(name, param)

        self._sudo_available = None
        self._is_running_as_root = None

        self.scan_only_params = dict()

        self.main_db = MainDB()

        self.nvti = NVTICache(self.main_db)

        self.pending_feed = None

    def init(self, server: BaseServer) -> None:

        server.start(self.handle_client_stream)

        self.scanner_info['version'] = Openvas.get_version()

        self.set_params_from_openvas_settings()

        if not self.nvti.ctx:
            with self.feed_lock.wait_for_lock():
                Openvas.load_vts_into_redis()

        self.load_vts()

        self.initialized = True

    def set_params_from_openvas_settings(self):
        """ Set OSPD_PARAMS with the params taken from the openvas executable.
        """
        param_list = Openvas.get_settings()

        for elem in param_list:
            if elem not in OSPD_PARAMS:
                self.scan_only_params[elem] = param_list[elem]
            else:
                OSPD_PARAMS[elem]['default'] = param_list[elem]

    def feed_is_outdated(self, current_feed: str) -> Optional[bool]:
        """ Compare the current feed with the one in the disk.

        Return:
            False if there is no new feed.
            True if the feed version in disk is newer than the feed in
                redis cache.
            None if there is no feed on the disk.
        """
        plugins_folder = self.scan_only_params.get('plugins_folder')
        if not plugins_folder:
            raise OspdOpenvasError("Error: Path to plugins folder not found.")

        feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc'
        if not feed_info_file.exists():
            self.set_params_from_openvas_settings()
            logger.debug('Plugins feed file %s not found.', feed_info_file)
            return None

        current_feed = safe_int(current_feed)

        feed_date = None
        with feed_info_file.open() as fcontent:
            for line in fcontent:
                if "PLUGIN_SET" in line:
                    feed_date = line.split('=', 1)[1]
                    feed_date = feed_date.strip()
                    feed_date = feed_date.replace(';', '')
                    feed_date = feed_date.replace('"', '')
                    feed_date = safe_int(feed_date)
                    break

        logger.debug("Current feed version: %s", current_feed)
        logger.debug("Plugin feed version: %s", feed_date)

        return (
            (not feed_date) or (not current_feed) or (current_feed < feed_date)
        )

    def feed_is_healthy(self):
        """ Compare the amount of filename keys and nvt keys in redis
        with the amount of oid loaded in memory.

        Return:
            True if the count is matching. False on failure.
        """
        filename_count = self.nvti.get_nvt_files_count()
        nvt_count = self.nvti.get_nvt_count()

        return len(self.vts) == filename_count == nvt_count

    def check_feed(self):
        """ Check if there is a feed update.

        Wait until all the running scans finished. Set a flag to announce there
        is a pending feed update, which avoids to start a new scan.
        """
        current_feed = self.nvti.get_feed_version()
        is_outdated = self.feed_is_outdated(current_feed)

        # Check if the feed is already accessible from the disk.
        if current_feed and is_outdated is None:
            self.pending_feed = True
            return

        # Check if the nvticache in redis is outdated
        if not current_feed or is_outdated:
            self.pending_feed = True

            with self.feed_lock as fl:
                if fl.has_lock():
                    Openvas.load_vts_into_redis()
                else:
                    logger.debug(
                        "The feed was not upload or it is outdated, "
                        "but other process is locking the update. "
                        "Trying again later..."
                    )
                    return

        _running_scan = False
        for scan_id in self.scan_processes:
            if self.scan_processes[scan_id].is_alive():
                _running_scan = True

        # Check if the NVT dict is outdated
        if self.pending_feed:
            _pending_feed = True
        else:
            _pending_feed = (
                self.get_vts_version() != self.nvti.get_feed_version()
            )

        _feed_is_healthy = self.feed_is_healthy()
        if _running_scan and not _feed_is_healthy:
            _pending_feed = True

            with self.feed_lock as fl:
                if fl.has_lock():
                    self.nvti.force_reload()
                    Openvas.load_vts_into_redis()
                else:
                    logger.debug(
                        "The VT Cache in memory is not healthy "
                        "and other process is locking the update. "
                        "Trying again later..."
                    )
                    return

        if _running_scan and _pending_feed:
            if not self.pending_feed:
                self.pending_feed = True
                logger.info(
                    'There is a running scan process locking the feed update. '
                    'Therefore the feed update will be performed later.'
                )
        elif (
            _pending_feed
            and not _running_scan
            and not self.feed_lock.is_locked()
        ):
            self.vts.clear()
            self.load_vts()

    def scheduler(self):
        """This method is called periodically to run tasks."""
        self.check_feed()

    def get_single_vt(self, vt_id, oids=None):
        _vt_params = self.nvti.get_nvt_params(vt_id)
        _vt_refs = self.nvti.get_nvt_refs(vt_id)
        _custom = self.nvti.get_nvt_metadata(vt_id)

        _name = _custom.pop('name')
        _vt_creation_time = _custom.pop('creation_date')
        _vt_modification_time = _custom.pop('last_modification')

        if oids:
            _vt_dependencies = list()
            if 'dependencies' in _custom:
                _deps = _custom.pop('dependencies')
                _deps_list = _deps.split(', ')
                for dep in _deps_list:
                    _vt_dependencies.append(oids.get(dep))
        else:
            _vt_dependencies = None

        _summary = None
        _impact = None
        _affected = None
        _insight = None
        _solution = None
        _solution_t = None
        _vuldetect = None
        _qod_t = None
        _qod_v = None

        if 'summary' in _custom:
            _summary = _custom.pop('summary')
        if 'impact' in _custom:
            _impact = _custom.pop('impact')
        if 'affected' in _custom:
            _affected = _custom.pop('affected')
        if 'insight' in _custom:
            _insight = _custom.pop('insight')
        if 'solution' in _custom:
            _solution = _custom.pop('solution')
            if 'solution_type' in _custom:
                _solution_t = _custom.pop('solution_type')

        if 'vuldetect' in _custom:
            _vuldetect = _custom.pop('vuldetect')
        if 'qod_type' in _custom:
            _qod_t = _custom.pop('qod_type')
        elif 'qod' in _custom:
            _qod_v = _custom.pop('qod')

        _severity = dict()
        if 'severity_base_vector' in _custom:
            _severity_vector = _custom.pop('severity_base_vector')
        else:
            _severity_vector = _custom.pop('cvss_base_vector')
        _severity['severity_base_vector'] = _severity_vector
        if 'severity_type' in _custom:
            _severity_type = _custom.pop('severity_type')
        else:
            _severity_type = 'cvss_base_v2'
        _severity['severity_type'] = _severity_type
        if 'severity_origin' in _custom:
            _severity['severity_origin'] = _custom.pop('severity_origin')

        if _name is None:
            _name = ''

        vt = {'name': _name}
        if _custom is not None:
            vt["custom"] = _custom
        if _vt_params is not None:
            vt["vt_params"] = _vt_params
        if _vt_refs is not None:
            vt["vt_refs"] = _vt_refs
        if _vt_dependencies is not None:
            vt["vt_dependencies"] = _vt_dependencies
        if _vt_creation_time is not None:
            vt["creation_time"] = _vt_creation_time
        if _vt_modification_time is not None:
            vt["modification_time"] = _vt_modification_time
        if _summary is not None:
            vt["summary"] = _summary
        if _impact is not None:
            vt["impact"] = _impact
        if _affected is not None:
            vt["affected"] = _affected
        if _insight is not None:
            vt["insight"] = _insight

        if _solution is not None:
            vt["solution"] = _solution
            if _solution_t is not None:
                vt["solution_type"] = _solution_t

        if _vuldetect is not None:
            vt["detection"] = _vuldetect

        if _qod_t is not None:
            vt["qod_type"] = _qod_t
        elif _qod_v is not None:
            vt["qod"] = _qod_v

        if _severity is not None:
            vt["severities"] = _severity

        return vt

    def get_vt_iterator(
        self, vt_selection: List[str] = None, details: bool = True
    ) -> Iterator[Tuple[str, Dict]]:
        """ Yield the vts from the Redis NVTicache. """

        oids = None
        if details:
            oids = dict(self.nvti.get_oids())

        for vt_id in vt_selection:
            vt = self.get_single_vt(vt_id, oids)
            yield (vt_id, vt)

    def load_vts(self):
        """ Load the VT's metadata into the vts global dictionary. """

        with self.feed_lock as fl:
            if not fl.has_lock():
                logger.warning(
                    'Error acquiring feed lock. Trying again later...'
                )
                return

            self.initialized = False
            logger.info('Loading VTs in memory.')

            oids = dict(self.nvti.get_oids())

            logger.debug('Found %s NVTs in redis.', len(oids))

            for _, vt_id in oids.items():
                vt = self.get_single_vt(vt_id, oids)

                if (
                    not vt
                    or vt.get('vt_params') is None
                    or vt.get('custom') is None
                ):
                    logger.warning(
                        'Error loading VTs in memory. Trying again later...'
                    )
                    return

                custom = {'family': vt['custom'].get('family')}
                try:
                    self.add_vt(
                        vt_id,
                        name=vt.get('name'),
                        qod_t=vt.get('qod_type'),
                        qod_v=vt.get('qod'),
                        severities=vt.get('severities'),
                        vt_modification_time=vt.get('modification_time'),
                        vt_params=vt.get('vt_params'),
                        custom=custom,
                    )
                except OspdError as e:
                    logger.warning("Error while adding VT %s. %s", vt_id, e)

            _feed_version = self.nvti.get_feed_version()

            self.set_vts_version(vts_version=_feed_version)
            self.vts.calculate_vts_collection_hash()
            self.pending_feed = False
            self.initialized = True

            logger.info('Finish loading up vts.')

            logger.debug('Loaded %s vts.', len(self.vts))

    @staticmethod
    def get_custom_vt_as_xml_str(vt_id: str, custom: Dict) -> str:
        """ Return an xml element with custom metadata formatted as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            custom: Dictionary with the custom metadata.
        Return:
            Xml element as string.
        """

        _custom = Element('custom')
        for key, val in custom.items():
            xml_key = SubElement(_custom, key)
            try:
                xml_key.text = val
            except ValueError as e:
                logger.warning(
                    "Not possible to parse custom tag for VT %s: %s", vt_id, e
                )
        return tostring(_custom).decode('utf-8')

    @staticmethod
    def get_severities_vt_as_xml_str(vt_id: str, severities: Dict) -> str:
        """ Return an xml element with severities as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            severities: Dictionary with the severities.
        Return:
            Xml element as string.
        """
        _severities = Element('severities')
        _severity = SubElement(_severities, 'severity')
        if 'severity_base_vector' in severities:
            try:
                _severity.text = severities.get('severity_base_vector')
            except ValueError as e:
                logger.warning(
                    "Not possible to parse severity tag for vt %s: %s", vt_id, e
                )
        if 'severity_origin' in severities:
            _severity.set('origin', severities.get('severity_origin'))
        if 'severity_type' in severities:
            _severity.set('type', severities.get('severity_type'))

        return tostring(_severities).decode('utf-8')

    @staticmethod
    def get_params_vt_as_xml_str(vt_id: str, vt_params: Dict) -> str:
        """ Return an xml element with params formatted as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_params: Dictionary with the VT parameters.
        Return:
            Xml element as string.
        """
        vt_params_xml = Element('params')
        for _pref_id, prefs in vt_params.items():
            vt_param = Element('param')
            vt_param.set('type', prefs['type'])
            vt_param.set('id', _pref_id)
            xml_name = SubElement(vt_param, 'name')
            try:
                xml_name.text = prefs['name']
            except ValueError as e:
                logger.warning(
                    "Not possible to parse parameter for VT %s: %s", vt_id, e
                )
            if prefs['default']:
                xml_def = SubElement(vt_param, 'default')
                try:
                    xml_def.text = prefs['default']
                except ValueError as e:
                    logger.warning(
                        "Not possible to parse default parameter for VT %s: %s",
                        vt_id,
                        e,
                    )
            vt_params_xml.append(vt_param)

        return tostring(vt_params_xml).decode('utf-8')

    @staticmethod
    def get_refs_vt_as_xml_str(vt_id: str, vt_refs: Dict) -> str:
        """ Return an xml element with references formatted as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_refs: Dictionary with the VT references.
        Return:
            Xml element as string.
        """
        vt_refs_xml = Element('refs')
        for ref_type, ref_values in vt_refs.items():
            for value in ref_values:
                vt_ref = Element('ref')
                if ref_type == "xref" and value:
                    for xref in value.split(', '):
                        try:
                            _type, _id = xref.split(':', 1)
                        except ValueError:
                            logger.error(
                                'Not possible to parse xref %s for VT %s',
                                xref,
                                vt_id,
                            )
                            continue
                        vt_ref.set('type', _type.lower())
                        vt_ref.set('id', _id)
                elif value:
                    vt_ref.set('type', ref_type.lower())
                    vt_ref.set('id', value)
                else:
                    continue
                vt_refs_xml.append(vt_ref)

        return tostring(vt_refs_xml).decode('utf-8')

    @staticmethod
    def get_dependencies_vt_as_xml_str(
        vt_id: str, vt_dependencies: List
    ) -> str:
        """ Return  an xml element with dependencies as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_dependencies: List with the VT dependencies.
        Return:
            Xml element as string.
        """
        vt_deps_xml = Element('dependencies')
        for dep in vt_dependencies:
            _vt_dep = Element('dependency')
            try:
                _vt_dep.set('vt_id', dep)
            except (ValueError, TypeError):
                logger.error(
                    'Not possible to add dependency %s for VT %s', dep, vt_id
                )
                continue
            vt_deps_xml.append(_vt_dep)

        return tostring(vt_deps_xml).decode('utf-8')

    @staticmethod
    def get_creation_time_vt_as_xml_str(
        vt_id: str, vt_creation_time: str
    ) -> str:
        """ Return creation time as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_creation_time: String with the VT creation time.
        Return:
           Xml element as string.
        """
        _time = Element('creation_time')
        try:
            _time.text = vt_creation_time
        except ValueError as e:
            logger.warning(
                "Not possible to parse creation time for VT %s: %s", vt_id, e
            )
        return tostring(_time).decode('utf-8')

    @staticmethod
    def get_modification_time_vt_as_xml_str(
        vt_id: str, vt_modification_time: str
    ) -> str:
        """ Return modification time as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_modification_time: String with the VT modification time.
        Return:
            Xml element as string.
        """
        _time = Element('modification_time')
        try:
            _time.text = vt_modification_time
        except ValueError as e:
            logger.warning(
                "Not possible to parse modification time for VT %s: %s",
                vt_id,
                e,
            )
        return tostring(_time).decode('utf-8')

    @staticmethod
    def get_summary_vt_as_xml_str(vt_id: str, summary: str) -> str:
        """ Return summary as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            summary: String with a VT summary.
        Return:
            Xml element as string.
        """
        _summary = Element('summary')
        try:
            _summary.text = summary
        except ValueError as e:
            logger.warning(
                "Not possible to parse summary tag for VT %s: %s", vt_id, e
            )
        return tostring(_summary).decode('utf-8')

    @staticmethod
    def get_impact_vt_as_xml_str(vt_id: str, impact) -> str:
        """ Return impact as string.

        Arguments:
            vt_id (str): VT OID. Only used for logging in error case.
            impact (str): String which explain the vulneravility impact.
        Return:
            string: xml element as string.
        """
        _impact = Element('impact')
        try:
            _impact.text = impact
        except ValueError as e:
            logger.warning(
                "Not possible to parse impact tag for VT %s: %s", vt_id, e
            )
        return tostring(_impact).decode('utf-8')

    @staticmethod
    def get_affected_vt_as_xml_str(vt_id: str, affected: str) -> str:
        """ Return affected as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            affected: String which explain what is affected.
        Return:
            Xml element as string.
        """
        _affected = Element('affected')
        try:
            _affected.text = affected
        except ValueError as e:
            logger.warning(
                "Not possible to parse affected tag for VT %s: %s", vt_id, e
            )
        return tostring(_affected).decode('utf-8')

    @staticmethod
    def get_insight_vt_as_xml_str(vt_id: str, insight: str) -> str:
        """ Return insight as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            insight: String giving an insight of the vulnerability.
        Return:
            Xml element as string.
        """
        _insight = Element('insight')
        try:
            _insight.text = insight
        except ValueError as e:
            logger.warning(
                "Not possible to parse insight tag for VT %s: %s", vt_id, e
            )
        return tostring(_insight).decode('utf-8')

    @staticmethod
    def get_solution_vt_as_xml_str(
        vt_id: str,
        solution: str,
        solution_type: Optional[str] = None,
        solution_method: Optional[str] = None,
    ) -> str:
        """ Return solution as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            solution: String giving a possible solution.
            solution_type: A solution type
            solution_method: A solution method
        Return:
            Xml element as string.
        """
        _solution = Element('solution')
        try:
            _solution.text = solution
        except ValueError as e:
            logger.warning(
                "Not possible to parse solution tag for VT %s: %s", vt_id, e
            )
        if solution_type:
            _solution.set('type', solution_type)
        if solution_method:
            _solution.set('method', solution_method)
        return tostring(_solution).decode('utf-8')

    @staticmethod
    def get_detection_vt_as_xml_str(
        vt_id: str,
        detection: Optional[str] = None,
        qod_type: Optional[str] = None,
        qod: Optional[str] = None,
    ) -> str:
        """ Return detection as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            detection: String which explain how the vulnerability
              was detected.
            qod_type: qod type.
            qod: qod value.
        Return:
            Xml element as string.
        """
        _detection = Element('detection')
        if detection:
            try:
                _detection.text = detection
            except ValueError as e:
                logger.warning(
                    "Not possible to parse detection tag for VT %s: %s",
                    vt_id,
                    e,
                )
        if qod_type:
            _detection.set('qod_type', qod_type)
        elif qod:
            _detection.set('qod', qod)

        return tostring(_detection).decode('utf-8')

    @property
    def is_running_as_root(self) -> bool:
        """ Check if it is running as root user."""
        if self._is_running_as_root is not None:
            return self._is_running_as_root

        self._is_running_as_root = False
        if geteuid() == 0:
            self._is_running_as_root = True

        return self._is_running_as_root

    @property
    def sudo_available(self) -> bool:
        """ Checks that sudo is available """
        if self._sudo_available is not None:
            return self._sudo_available

        if self.is_running_as_root:
            self._sudo_available = False
            return self._sudo_available

        self._sudo_available = Openvas.check_sudo()

        return self._sudo_available

    def check(self) -> bool:
        """ Checks that openvas command line tool is found and
        is executable. """
        has_openvas = Openvas.check()
        if not has_openvas:
            logger.error(
                'openvas executable not available. Please install openvas'
                ' into your PATH.'
            )
        return has_openvas

    def update_progress(self, scan_id: str, current_host: str, msg: str):
        """ Calculate percentage and update the scan status of a host
        for the progress bar.
        Arguments:
            scan_id: Scan ID to identify the current scan process.
            current_host: Host in the target to be updated.
            msg: String with launched and total plugins.
        """
        try:
            launched, total = msg.split('/')
        except ValueError:
            return
        if float(total) == 0:
            return
        elif float(total) == -1:
            host_prog = 100
        else:
            host_prog = (float(launched) / float(total)) * 100
        self.set_scan_host_progress(scan_id, current_host, host_prog)

    def report_openvas_scan_status(
        self, scan_db: ScanDB, scan_id: str, current_host: str
    ):
        """ Get all status entries from redis kb.

        Arguments:
            scan_id: Scan ID to identify the current scan.
            current_host: Host to be updated.
        """
        res = scan_db.get_scan_status()
        while res:
            self.update_progress(scan_id, current_host, res)
            res = scan_db.get_scan_status()

    def get_severity_score(self, vt_aux: dict) -> Optional[float]:
        """ Return the severity score for the given oid.
        Arguments:
            vt_aux: VT element from which to get the severity vector
        Returns:
            The calculated cvss base value. None if there is no severity
            vector or severity type is not cvss base version 2.
        """
        if vt_aux:
            severity_type = vt_aux['severities'].get('severity_type')
            severity_vector = vt_aux['severities'].get('severity_base_vector')

            if severity_type == "cvss_base_v2" and severity_vector:
                return CVSS.cvss_base_v2_value(severity_vector)

        return None

    def report_openvas_results(
        self, db: BaseDB, scan_id: str, current_host: str
    ):
        """ Get all result entries from redis kb. """
        res = db.get_result()
        while res:
            msg = res.split('|||')
            roid = msg[3].strip()
            rqod = ''
            rname = ''
            rhostname = msg[1].strip() if msg[1] else ''
            host_is_dead = "Host dead" in msg[4]
            vt_aux = None

            if roid and not host_is_dead:
                vt_aux = copy.deepcopy(self.vts.get(roid))

            if not vt_aux and not host_is_dead:
                logger.warning('Invalid VT oid %s for a result', roid)

            if vt_aux:
                if vt_aux.get('qod_type'):
                    qod_t = vt_aux.get('qod_type')
                    rqod = self.nvti.QOD_TYPES[qod_t]
                elif vt_aux.get('qod'):
                    rqod = vt_aux.get('qod')

                rname = vt_aux.get('name')

            if msg[0] == 'ERRMSG':
                self.add_scan_error(
                    scan_id,
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=msg[4],
                    port=msg[2],
                    test_id=roid,
                )

            if msg[0] == 'LOG':
                self.add_scan_log(
                    scan_id,
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=msg[4],
                    port=msg[2],
                    qod=rqod,
                    test_id=roid,
                )

            if msg[0] == 'HOST_DETAIL':
                self.add_scan_host_detail(
                    scan_id,
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=msg[4],
                )

            if msg[0] == 'ALARM':
                rseverity = self.get_severity_score(vt_aux)
                self.add_scan_alarm(
                    scan_id,
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=msg[4],
                    port=msg[2],
                    test_id=roid,
                    severity=rseverity,
                    qod=rqod,
                )

            vt_aux = None
            del vt_aux
            res = db.get_result()

    def report_openvas_timestamp_scan_host(
        self, scan_db: ScanDB, scan_id: str, host: str
    ):
        """ Get start and end timestamp of a host scan from redis kb. """
        timestamp = scan_db.get_host_scan_end_time()
        if timestamp:
            self.add_scan_log(
                scan_id, host=host, name='HOST_END', value=timestamp
            )
            return

        timestamp = scan_db.get_host_scan_start_time()
        if timestamp:
            self.add_scan_log(
                scan_id, host=host, name='HOST_START', value=timestamp
            )
            return

    def stop_scan_cleanup(  # pylint: disable=arguments-differ
        self, global_scan_id: str
    ):
        """ Set a key in redis to indicate the wrapper is stopped.
        It is done through redis because it is a new multiprocess
        instance and it is not possible to reach the variables
        of the grandchild process. Send SIGUSR2 to openvas to stop
        each running scan."""

        openvas_scan_id, kbdb = self.main_db.find_kb_database_by_scan_id(
            global_scan_id
        )
        if kbdb:
            kbdb.stop_scan(openvas_scan_id)
            ovas_pid = kbdb.get_scan_process_id()

            parent = None
            try:
                parent = psutil.Process(int(ovas_pid))
            except psutil.NoSuchProcess:
                logger.debug('Process with pid %s already stopped', ovas_pid)
            except TypeError:
                logger.debug(
                    'Scan with ID %s never started and stopped unexpectedly',
                    openvas_scan_id,
                )

            if parent:
                can_stop_scan = Openvas.stop_scan(
                    openvas_scan_id,
                    not self.is_running_as_root and self.sudo_available,
                )
                if not can_stop_scan:
                    logger.debug(
                        'Not possible to stop scan process: %s.', parent,
                    )
                    return False

                logger.debug('Stopping process: %s', parent)

                while parent:
                    try:
                        parent = psutil.Process(int(ovas_pid))
                    except psutil.NoSuchProcess:
                        parent = None

            for scan_db in kbdb.get_scan_databases():
                self.main_db.release_database(scan_db)

    def exec_scan(self, scan_id: str):
        """ Starts the OpenVAS scanner for scan_id scan. """
        if self.pending_feed:
            logger.info(
                '%s: There is a pending feed update. '
                'The scan can not be started.',
                scan_id,
            )
            self.add_scan_error(
                scan_id,
                name='',
                host='',
                value=(
                    'It was not possible to start the scan,'
                    'because a pending feed update. Please try later'
                ),
            )
            return 2

        do_not_launch = False

        # Set plugins to run.
        # Make a deepcopy of the vts dictionary. Otherwise, consulting the
        # DictProxy object of multiprocessing directly is to expensinve
        # (interprocess communication).
        temp_vts = self.vts.copy()

        kbdb = self.main_db.get_new_kb_database()
        scan_prefs = PreferenceHandler(
            scan_id, kbdb, self.scan_collection, temp_vts
        )
        openvas_scan_id = scan_prefs.prepare_openvas_scan_id_for_openvas()
        scan_prefs.prepare_target_for_openvas()

        if not scan_prefs.prepare_ports_for_openvas():
            self.add_scan_error(
                scan_id, name='', host='', value='No port list defined.'
            )
            do_not_launch = True

        # Set credentials
        if not scan_prefs.prepare_credentials_for_openvas():
            self.add_scan_error(
                scan_id, name='', host='', value='Malformed credential.'
            )
            do_not_launch = True

        if not scan_prefs.prepare_plugins_for_openvas():
            self.add_scan_error(
                scan_id, name='', host='', value='No VTS to run.'
            )
            do_not_launch = True

        temp_vts = None

        scan_prefs.prepare_main_kbindex_for_openvas()
        scan_prefs.prepare_host_options_for_openvas()
        scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS)
        scan_prefs.prepare_reverse_lookup_opt_for_openvas()
        scan_prefs.prepare_alive_test_option_for_openvas()

        # Release memory used for scan preferences.
        del scan_prefs

        if do_not_launch:
            self.main_db.release_database(kbdb)
            return 2

        result = Openvas.start_scan(
            openvas_scan_id,
            not self.is_running_as_root and self.sudo_available,
            self._niceness,
        )

        if result is None:
            self.main_db.release_database(kbdb)
            return False

        ovas_pid = result.pid
        kbdb.add_scan_process_id(ovas_pid)
        logger.debug('pid = %s', ovas_pid)

        # Wait until the scanner starts and loads all the preferences.
        while kbdb.get_status(openvas_scan_id) == 'new':
            res = result.poll()
            if res and res < 0:
                self.stop_scan_cleanup(scan_id)
                logger.error(
                    'It was not possible run the task %s, since openvas ended '
                    'unexpectedly with errors during launching.',
                    scan_id,
                )
                return 1

            time.sleep(1)

        no_id_found = False
        while True:
            time.sleep(3)
            # Check if the client stopped the whole scan
            if kbdb.scan_is_stopped(openvas_scan_id):
                return 1

            self.report_openvas_results(kbdb, scan_id, "")

            for scan_db in kbdb.get_scan_databases():

                id_aux = scan_db.get_scan_id()
                if not id_aux:
                    continue

                if id_aux == openvas_scan_id:
                    no_id_found = False
                    current_host = scan_db.get_host_ip()

                    self.report_openvas_results(scan_db, scan_id, current_host)
                    self.report_openvas_scan_status(
                        scan_db, scan_id, current_host
                    )
                    self.report_openvas_timestamp_scan_host(
                        scan_db, scan_id, current_host
                    )

                    if scan_db.host_is_finished(openvas_scan_id):
                        self.set_scan_host_finished(scan_id, current_host)
                        self.report_openvas_scan_status(
                            scan_db, scan_id, current_host
                        )
                        self.report_openvas_timestamp_scan_host(
                            scan_db, scan_id, current_host
                        )

                        kbdb.remove_scan_database(scan_db)
                        self.main_db.release_database(scan_db)

            # Scan end. No kb in use for this scan id
            if no_id_found and kbdb.target_is_finished(scan_id):
                break

            no_id_found = True

        # Delete keys from KB related to this scan task.
        self.main_db.release_database(kbdb)
Ejemplo n.º 18
0
class OSPDopenvas(OSPDaemon):
    """ Class for ospd-openvas daemon. """
    def __init__(self,
                 *,
                 niceness=None,
                 lock_file_dir='/var/run/ospd',
                 **kwargs):
        """ Initializes the ospd-openvas daemon's internal data. """
        self.main_db = MainDB()
        self.nvti = NVTICache(self.main_db)

        super().__init__(
            customvtfilter=OpenVasVtsFilter(self.nvti),
            storage=dict,
            file_storage_dir=lock_file_dir,
            **kwargs,
        )

        self.server_version = __version__

        self._niceness = str(niceness)

        self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock')
        self.daemon_info['name'] = 'OSPd OpenVAS'
        self.scanner_info['name'] = 'openvas'
        self.scanner_info['version'] = ''  # achieved during self.init()
        self.scanner_info['description'] = OSPD_DESC

        for name, param in OSPD_PARAMS.items():
            self.set_scanner_param(name, param)

        self._sudo_available = None
        self._is_running_as_root = None

        self.scan_only_params = dict()

    def init(self, server: BaseServer) -> None:

        self.scan_collection.init()

        server.start(self.handle_client_stream)

        self.scanner_info['version'] = Openvas.get_version()

        self.set_params_from_openvas_settings()

        with self.feed_lock.wait_for_lock():
            Openvas.load_vts_into_redis()
            notushandler = NotusMetadataHandler(nvti=self.nvti)
            notushandler.update_metadata()
            current_feed = self.nvti.get_feed_version()
            self.set_vts_version(vts_version=current_feed)

            logger.debug("Calculating vts integrity check hash...")
            vthelper = VtHelper(self.nvti)
            self.vts.sha256_hash = vthelper.calculate_vts_collection_hash()

        self.initialized = True

    def set_params_from_openvas_settings(self):
        """Set OSPD_PARAMS with the params taken from the openvas executable."""
        param_list = Openvas.get_settings()

        for elem in param_list:
            if elem not in OSPD_PARAMS:
                self.scan_only_params[elem] = param_list[elem]
            else:
                OSPD_PARAMS[elem]['default'] = param_list[elem]

    def feed_is_outdated(self, current_feed: str) -> Optional[bool]:
        """Compare the current feed with the one in the disk.

        Return:
            False if there is no new feed.
            True if the feed version in disk is newer than the feed in
                redis cache.
            None if there is no feed on the disk.
        """
        plugins_folder = self.scan_only_params.get('plugins_folder')
        if not plugins_folder:
            raise OspdOpenvasError("Error: Path to plugins folder not found.")

        feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc'
        if not feed_info_file.exists():
            self.set_params_from_openvas_settings()
            logger.debug('Plugins feed file %s not found.', feed_info_file)
            return None

        current_feed = safe_int(current_feed)
        if current_feed is None:
            logger.debug(
                "Wrong PLUGIN_SET format in plugins feed file %s. Format has to"
                " be yyyymmddhhmm. For example 'PLUGIN_SET = \"201910251033\"'",
                feed_info_file,
            )

        feed_date = None
        with feed_info_file.open() as fcontent:
            for line in fcontent:
                if "PLUGIN_SET" in line:
                    feed_date = line.split('=', 1)[1]
                    feed_date = feed_date.strip()
                    feed_date = feed_date.replace(';', '')
                    feed_date = feed_date.replace('"', '')
                    feed_date = safe_int(feed_date)
                    break

        logger.debug("Current feed version: %s", current_feed)
        logger.debug("Plugin feed version: %s", feed_date)

        return ((not feed_date) or (not current_feed)
                or (current_feed < feed_date))

    def check_feed(self):
        """Check if there is a feed update.

        Wait until all the running scans finished. Set a flag to announce there
        is a pending feed update, which avoids to start a new scan.
        """
        if not self.vts.is_cache_available:
            return

        current_feed = self.nvti.get_feed_version()
        is_outdated = self.feed_is_outdated(current_feed)

        # Check if the nvticache in redis is outdated
        if not current_feed or is_outdated:
            with self.feed_lock as fl:
                if fl.has_lock():
                    self.initialized = False
                    Openvas.load_vts_into_redis()
                    notushandler = NotusMetadataHandler(nvti=self.nvti)
                    notushandler.update_metadata()
                    current_feed = self.nvti.get_feed_version()
                    self.set_vts_version(vts_version=current_feed)

                    vthelper = VtHelper(self.nvti)
                    self.vts.sha256_hash = (
                        vthelper.calculate_vts_collection_hash())
                    self.initialized = True
                else:
                    logger.debug("The feed was not upload or it is outdated, "
                                 "but other process is locking the update. "
                                 "Trying again later...")
                    return

    def scheduler(self):
        """This method is called periodically to run tasks."""
        self.check_feed()

    def get_vt_iterator(self,
                        vt_selection: List[str] = None,
                        details: bool = True) -> Iterator[Tuple[str, Dict]]:
        vthelper = VtHelper(self.nvti)
        return vthelper.get_vt_iterator(vt_selection, details)

    @staticmethod
    def get_custom_vt_as_xml_str(vt_id: str, custom: Dict) -> str:
        """Return an xml element with custom metadata formatted as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            custom: Dictionary with the custom metadata.
        Return:
            Xml element as string.
        """

        _custom = Element('custom')
        for key, val in custom.items():
            xml_key = SubElement(_custom, key)
            try:
                xml_key.text = val
            except ValueError as e:
                logger.warning(
                    "Not possible to parse custom tag for VT %s: %s", vt_id, e)
        return tostring(_custom).decode('utf-8')

    @staticmethod
    def get_severities_vt_as_xml_str(vt_id: str, severities: Dict) -> str:
        """Return an xml element with severities as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            severities: Dictionary with the severities.
        Return:
            Xml element as string.
        """
        _severities = Element('severities')
        _severity = SubElement(_severities, 'severity')
        if 'severity_base_vector' in severities:
            try:
                _value = SubElement(_severity, 'value')
                _value.text = severities.get('severity_base_vector')
            except ValueError as e:
                logger.warning(
                    "Not possible to parse severity tag for vt %s: %s", vt_id,
                    e)
        if 'severity_origin' in severities:
            _origin = SubElement(_severity, 'origin')
            _origin.text = severities.get('severity_origin')
        if 'severity_date' in severities:
            _date = SubElement(_severity, 'date')
            _date.text = severities.get('severity_date')
        if 'severity_type' in severities:
            _severity.set('type', severities.get('severity_type'))

        return tostring(_severities).decode('utf-8')

    @staticmethod
    def get_params_vt_as_xml_str(vt_id: str, vt_params: Dict) -> str:
        """Return an xml element with params formatted as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_params: Dictionary with the VT parameters.
        Return:
            Xml element as string.
        """
        vt_params_xml = Element('params')
        for _pref_id, prefs in vt_params.items():
            vt_param = Element('param')
            vt_param.set('type', prefs['type'])
            vt_param.set('id', _pref_id)
            xml_name = SubElement(vt_param, 'name')
            try:
                xml_name.text = prefs['name']
            except ValueError as e:
                logger.warning("Not possible to parse parameter for VT %s: %s",
                               vt_id, e)
            if prefs['default']:
                xml_def = SubElement(vt_param, 'default')
                try:
                    xml_def.text = prefs['default']
                except ValueError as e:
                    logger.warning(
                        "Not possible to parse default parameter for VT %s: %s",
                        vt_id,
                        e,
                    )
            vt_params_xml.append(vt_param)

        return tostring(vt_params_xml).decode('utf-8')

    @staticmethod
    def get_refs_vt_as_xml_str(vt_id: str, vt_refs: Dict) -> str:
        """Return an xml element with references formatted as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_refs: Dictionary with the VT references.
        Return:
            Xml element as string.
        """
        vt_refs_xml = Element('refs')
        for ref_type, ref_values in vt_refs.items():
            for value in ref_values:
                vt_ref = Element('ref')
                if ref_type == "xref" and value:
                    for xref in value.split(', '):
                        try:
                            _type, _id = xref.split(':', 1)
                        except ValueError:
                            logger.error(
                                'Not possible to parse xref %s for VT %s',
                                xref,
                                vt_id,
                            )
                            continue
                        vt_ref.set('type', _type.lower())
                        vt_ref.set('id', _id)
                elif value:
                    vt_ref.set('type', ref_type.lower())
                    vt_ref.set('id', value)
                else:
                    continue
                vt_refs_xml.append(vt_ref)

        return tostring(vt_refs_xml).decode('utf-8')

    @staticmethod
    def get_dependencies_vt_as_xml_str(vt_id: str,
                                       vt_dependencies: List) -> str:
        """Return  an xml element with dependencies as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_dependencies: List with the VT dependencies.
        Return:
            Xml element as string.
        """
        vt_deps_xml = Element('dependencies')
        for dep in vt_dependencies:
            _vt_dep = Element('dependency')
            try:
                _vt_dep.set('vt_id', dep)
            except (ValueError, TypeError):
                logger.error('Not possible to add dependency %s for VT %s',
                             dep, vt_id)
                continue
            vt_deps_xml.append(_vt_dep)

        return tostring(vt_deps_xml).decode('utf-8')

    @staticmethod
    def get_creation_time_vt_as_xml_str(vt_id: str,
                                        vt_creation_time: str) -> str:
        """Return creation time as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_creation_time: String with the VT creation time.
        Return:
           Xml element as string.
        """
        _time = Element('creation_time')
        try:
            _time.text = vt_creation_time
        except ValueError as e:
            logger.warning("Not possible to parse creation time for VT %s: %s",
                           vt_id, e)
        return tostring(_time).decode('utf-8')

    @staticmethod
    def get_modification_time_vt_as_xml_str(vt_id: str,
                                            vt_modification_time: str) -> str:
        """Return modification time as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            vt_modification_time: String with the VT modification time.
        Return:
            Xml element as string.
        """
        _time = Element('modification_time')
        try:
            _time.text = vt_modification_time
        except ValueError as e:
            logger.warning(
                "Not possible to parse modification time for VT %s: %s",
                vt_id,
                e,
            )
        return tostring(_time).decode('utf-8')

    @staticmethod
    def get_summary_vt_as_xml_str(vt_id: str, summary: str) -> str:
        """Return summary as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            summary: String with a VT summary.
        Return:
            Xml element as string.
        """
        _summary = Element('summary')
        try:
            _summary.text = summary
        except ValueError as e:
            logger.warning("Not possible to parse summary tag for VT %s: %s",
                           vt_id, e)
        return tostring(_summary).decode('utf-8')

    @staticmethod
    def get_impact_vt_as_xml_str(vt_id: str, impact) -> str:
        """Return impact as string.

        Arguments:
            vt_id (str): VT OID. Only used for logging in error case.
            impact (str): String which explain the vulneravility impact.
        Return:
            string: xml element as string.
        """
        _impact = Element('impact')
        try:
            _impact.text = impact
        except ValueError as e:
            logger.warning("Not possible to parse impact tag for VT %s: %s",
                           vt_id, e)
        return tostring(_impact).decode('utf-8')

    @staticmethod
    def get_affected_vt_as_xml_str(vt_id: str, affected: str) -> str:
        """Return affected as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            affected: String which explain what is affected.
        Return:
            Xml element as string.
        """
        _affected = Element('affected')
        try:
            _affected.text = affected
        except ValueError as e:
            logger.warning("Not possible to parse affected tag for VT %s: %s",
                           vt_id, e)
        return tostring(_affected).decode('utf-8')

    @staticmethod
    def get_insight_vt_as_xml_str(vt_id: str, insight: str) -> str:
        """Return insight as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            insight: String giving an insight of the vulnerability.
        Return:
            Xml element as string.
        """
        _insight = Element('insight')
        try:
            _insight.text = insight
        except ValueError as e:
            logger.warning("Not possible to parse insight tag for VT %s: %s",
                           vt_id, e)
        return tostring(_insight).decode('utf-8')

    @staticmethod
    def get_solution_vt_as_xml_str(
        vt_id: str,
        solution: str,
        solution_type: Optional[str] = None,
        solution_method: Optional[str] = None,
    ) -> str:
        """Return solution as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            solution: String giving a possible solution.
            solution_type: A solution type
            solution_method: A solution method
        Return:
            Xml element as string.
        """
        _solution = Element('solution')
        try:
            _solution.text = solution
        except ValueError as e:
            logger.warning("Not possible to parse solution tag for VT %s: %s",
                           vt_id, e)
        if solution_type:
            _solution.set('type', solution_type)
        if solution_method:
            _solution.set('method', solution_method)
        return tostring(_solution).decode('utf-8')

    @staticmethod
    def get_detection_vt_as_xml_str(
        vt_id: str,
        detection: Optional[str] = None,
        qod_type: Optional[str] = None,
        qod: Optional[str] = None,
    ) -> str:
        """Return detection as string.
        Arguments:
            vt_id: VT OID. Only used for logging in error case.
            detection: String which explain how the vulnerability
              was detected.
            qod_type: qod type.
            qod: qod value.
        Return:
            Xml element as string.
        """
        _detection = Element('detection')
        if detection:
            try:
                _detection.text = detection
            except ValueError as e:
                logger.warning(
                    "Not possible to parse detection tag for VT %s: %s",
                    vt_id,
                    e,
                )
        if qod_type:
            _detection.set('qod_type', qod_type)
        elif qod:
            _detection.set('qod', qod)

        return tostring(_detection).decode('utf-8')

    @property
    def is_running_as_root(self) -> bool:
        """ Check if it is running as root user."""
        if self._is_running_as_root is not None:
            return self._is_running_as_root

        self._is_running_as_root = False
        if geteuid() == 0:
            self._is_running_as_root = True

        return self._is_running_as_root

    @property
    def sudo_available(self) -> bool:
        """ Checks that sudo is available """
        if self._sudo_available is not None:
            return self._sudo_available

        if self.is_running_as_root:
            self._sudo_available = False
            return self._sudo_available

        self._sudo_available = Openvas.check_sudo()

        return self._sudo_available

    def check(self) -> bool:
        """Checks that openvas command line tool is found and
        is executable."""
        has_openvas = Openvas.check()
        if not has_openvas:
            logger.error(
                'openvas executable not available. Please install openvas'
                ' into your PATH.')
        return has_openvas

    def report_openvas_scan_status(self, kbdb: BaseDB, scan_id: str):
        """Get all status entries from redis kb.

        Arguments:
            scan_id: Scan ID to identify the current scan.
            current_host: Host to be updated.
        """
        all_status = kbdb.get_scan_status()
        all_hosts = dict()
        finished_hosts = list()
        for res in all_status:
            try:
                current_host, launched, total = res.split('/')
            except ValueError:
                continue

            try:
                if float(total) == 0:
                    continue
                elif float(total) == ScanProgress.DEAD_HOST:
                    host_prog = ScanProgress.DEAD_HOST
                else:
                    host_prog = int((float(launched) / float(total)) * 100)
            except TypeError:
                continue

            all_hosts[current_host] = host_prog

            if (host_prog == ScanProgress.DEAD_HOST
                    or host_prog == ScanProgress.FINISHED):
                finished_hosts.append(current_host)

        self.set_scan_progress_batch(scan_id, host_progress=all_hosts)

        self.sort_host_finished(scan_id, finished_hosts)

    def get_severity_score(self, vt_aux: dict) -> Optional[float]:
        """Return the severity score for the given oid.
        Arguments:
            vt_aux: VT element from which to get the severity vector
        Returns:
            The calculated cvss base value. None if there is no severity
            vector or severity type is not cvss base version 2.
        """
        if vt_aux:
            severity_type = vt_aux['severities'].get('severity_type')
            severity_vector = vt_aux['severities'].get('severity_base_vector')

            if severity_type == "cvss_base_v2" and severity_vector:
                return CVSS.cvss_base_v2_value(severity_vector)
            elif severity_type == "cvss_base_v3" and severity_vector:
                return CVSS.cvss_base_v3_value(severity_vector)

        return None

    def report_openvas_results(self, db: BaseDB, scan_id: str) -> bool:
        """ Get all result entries from redis kb. """

        vthelper = VtHelper(self.nvti)

        # Result messages come in the next form, with optional uri field
        # type ||| host ip ||| hostname ||| port ||| OID ||| value [|||uri]
        all_results = db.get_result()
        res_list = ResultList()
        total_dead = 0
        for res in all_results:
            if not res:
                continue

            msg = res.split('|||')
            roid = msg[4].strip()
            rqod = ''
            rname = ''
            current_host = msg[1].strip() if msg[1] else ''
            rhostname = msg[2].strip() if msg[2] else ''
            host_is_dead = "Host dead" in msg[5] or msg[0] == "DEADHOST"
            host_deny = "Host access denied" in msg[5]
            start_end_msg = msg[0] == "HOST_START" or msg[0] == "HOST_END"
            vt_aux = None

            # URI is optional and msg list length must be checked
            ruri = ''
            if len(msg) > 6:
                ruri = msg[6]

            if (roid and not host_is_dead and not host_deny
                    and not start_end_msg):
                vt_aux = vthelper.get_single_vt(roid)

            if (not vt_aux and not host_is_dead and not host_deny
                    and not start_end_msg):
                logger.warning('Invalid VT oid %s for a result', roid)

            if vt_aux:
                if vt_aux.get('qod_type'):
                    qod_t = vt_aux.get('qod_type')
                    rqod = self.nvti.QOD_TYPES[qod_t]
                elif vt_aux.get('qod'):
                    rqod = vt_aux.get('qod')

                rname = vt_aux.get('name')

            if msg[0] == 'ERRMSG':
                res_list.add_scan_error_to_list(
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=msg[5],
                    port=msg[3],
                    test_id=roid,
                    uri=ruri,
                )

            elif msg[0] == 'HOST_START' or msg[0] == 'HOST_END':
                res_list.add_scan_log_to_list(
                    host=current_host,
                    name=msg[0],
                    value=msg[5],
                )

            elif msg[0] == 'LOG':
                res_list.add_scan_log_to_list(
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=msg[5],
                    port=msg[3],
                    qod=rqod,
                    test_id=roid,
                    uri=ruri,
                )

            elif msg[0] == 'HOST_DETAIL':
                res_list.add_scan_host_detail_to_list(
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=msg[5],
                    uri=ruri,
                )

            elif msg[0] == 'ALARM':
                rseverity = self.get_severity_score(vt_aux)
                res_list.add_scan_alarm_to_list(
                    host=current_host,
                    hostname=rhostname,
                    name=rname,
                    value=msg[5],
                    port=msg[3],
                    test_id=roid,
                    severity=rseverity,
                    qod=rqod,
                    uri=ruri,
                )

            # To process non-scanned dead hosts when
            # test_alive_host_only in openvas is enable
            elif msg[0] == 'DEADHOST':
                try:
                    total_dead = int(msg[5])
                except TypeError:
                    logger.debug('Error processing dead host count')

        # Insert result batch into the scan collection table.
        if len(res_list):
            self.scan_collection.add_result_list(scan_id, res_list)

        if total_dead:
            self.scan_collection.set_amount_dead_hosts(scan_id,
                                                       total_dead=total_dead)

        return len(res_list) > 0

    def is_openvas_process_alive(self, kbdb: BaseDB, ovas_pid: str,
                                 scan_id: str) -> bool:
        parent_exists = True
        parent = None
        try:
            parent = psutil.Process(int(ovas_pid))
        except psutil.NoSuchProcess:
            logger.debug('Process with pid %s already stopped', ovas_pid)
            parent_exists = False
        except TypeError:
            logger.debug(
                'Scan with ID %s never started and stopped unexpectedly',
                scan_id,
            )
            parent_exists = False

        is_zombie = False
        if parent and parent.status() == psutil.STATUS_ZOMBIE:
            is_zombie = True

        if (not parent_exists or is_zombie) and kbdb:
            if kbdb and kbdb.scan_is_stopped(scan_id):
                return True
            return False

        return True

    def stop_scan_cleanup(  # pylint: disable=arguments-differ
            self, scan_id: str):
        """Set a key in redis to indicate the wrapper is stopped.
        It is done through redis because it is a new multiprocess
        instance and it is not possible to reach the variables
        of the grandchild process. Send SIGUSR2 to openvas to stop
        each running scan."""

        kbdb = self.main_db.find_kb_database_by_scan_id(scan_id)
        if kbdb:
            kbdb.stop_scan(scan_id)
            ovas_pid = kbdb.get_scan_process_id()

            parent = None
            try:
                parent = psutil.Process(int(ovas_pid))
            except psutil.NoSuchProcess:
                logger.debug('Process with pid %s already stopped', ovas_pid)
            except TypeError:
                logger.debug(
                    'Scan with ID %s never started and stopped unexpectedly',
                    scan_id,
                )

            if parent:
                can_stop_scan = Openvas.stop_scan(
                    scan_id,
                    not self.is_running_as_root and self.sudo_available,
                )
                if not can_stop_scan:
                    logger.debug(
                        'Not possible to stop scan process: %s.',
                        parent,
                    )
                    return False

                logger.debug('Stopping process: %s', parent)

                while parent:
                    try:
                        parent = psutil.Process(int(ovas_pid))
                    except psutil.NoSuchProcess:
                        parent = None

            for scan_db in kbdb.get_scan_databases():
                self.main_db.release_database(scan_db)

    def exec_scan(self, scan_id: str):
        """ Starts the OpenVAS scanner for scan_id scan. """
        do_not_launch = False
        kbdb = self.main_db.get_new_kb_database()
        scan_prefs = PreferenceHandler(scan_id, kbdb, self.scan_collection,
                                       self.nvti)
        kbdb.add_scan_id(scan_id)
        scan_prefs.prepare_target_for_openvas()

        if not scan_prefs.prepare_ports_for_openvas():
            self.add_scan_error(scan_id,
                                name='',
                                host='',
                                value='No port list defined.')
            do_not_launch = True

        # Set credentials
        if not scan_prefs.prepare_credentials_for_openvas():
            self.add_scan_error(scan_id,
                                name='',
                                host='',
                                value='Malformed credential.')
            do_not_launch = True

        if not scan_prefs.prepare_plugins_for_openvas():
            self.add_scan_error(scan_id,
                                name='',
                                host='',
                                value='No VTS to run.')
            do_not_launch = True

        scan_prefs.prepare_main_kbindex_for_openvas()
        scan_prefs.prepare_host_options_for_openvas()
        scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS)
        scan_prefs.prepare_reverse_lookup_opt_for_openvas()
        scan_prefs.prepare_alive_test_option_for_openvas()
        scan_prefs.prepare_boreas_alive_test()

        # Release memory used for scan preferences.
        del scan_prefs

        if do_not_launch:
            self.main_db.release_database(kbdb)
            return

        result = Openvas.start_scan(
            scan_id,
            not self.is_running_as_root and self.sudo_available,
            self._niceness,
        )

        if result is None:
            self.main_db.release_database(kbdb)
            return

        ovas_pid = result.pid
        kbdb.add_scan_process_id(ovas_pid)
        logger.debug('pid = %s', ovas_pid)

        # Wait until the scanner starts and loads all the preferences.
        while kbdb.get_status(scan_id) == 'new':
            res = result.poll()
            if res and res < 0:
                self.stop_scan_cleanup(scan_id)
                logger.error(
                    'It was not possible run the task %s, since openvas ended '
                    'unexpectedly with errors during launching.',
                    scan_id,
                )
                return

            time.sleep(1)

        got_results = False
        while True:
            if not kbdb.target_is_finished(
                    scan_id) and not self.is_openvas_process_alive(
                        kbdb, ovas_pid, scan_id):
                logger.error(
                    'Task %s was unexpectedly stopped or killed.',
                    scan_id,
                )
                self.add_scan_error(
                    scan_id,
                    name='',
                    host='',
                    value='Task was unexpectedly stopped or killed.',
                )
                kbdb.stop_scan(scan_id)
                for scan_db in kbdb.get_scan_databases():
                    self.main_db.release_database(scan_db)
                self.main_db.release_database(kbdb)
                return

            # Wait a second before trying to get result from redis if there
            # was no results before.
            # Otherwise, wait 50 msec to give access other process to redis.
            if not got_results:
                time.sleep(1)
            else:
                time.sleep(0.05)
            got_results = False

            # Check if the client stopped the whole scan
            if kbdb.scan_is_stopped(scan_id):
                # clean main_db, but wait for scanner to finish.
                while not kbdb.target_is_finished(scan_id):
                    time.sleep(1)
                self.main_db.release_database(kbdb)
                return

            got_results = self.report_openvas_results(kbdb, scan_id)
            self.report_openvas_scan_status(kbdb, scan_id)

            # Scan end. No kb in use for this scan id
            if kbdb.target_is_finished(scan_id):
                break

        # Delete keys from KB related to this scan task.
        self.main_db.release_database(kbdb)