Passed
Pull Request — master (#436)
by Jaspar
02:22
created

ospd_openvas.daemon.is_openvas_process_alive()   C

Complexity

Conditions 10

Size

Total Lines 29
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 24
nop 3
dl 0
loc 29
rs 5.9999
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like ospd_openvas.daemon.is_openvas_process_alive() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2014-2021 Greenbone Networks GmbH
3
#
4
# SPDX-License-Identifier: AGPL-3.0-or-later
5
#
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU Affero General Public License as
8
# published by the Free Software Foundation, either version 3 of the
9
# License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU Affero General Public License for more details.
15
#
16
# You should have received a copy of the GNU Affero General Public License
17
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20
# pylint: disable=too-many-lines
21
22
""" Setup for the OSP OpenVAS Server. """
23
24
import logging
25
import time
26
import copy
27
28
from typing import Optional, Dict, List, Tuple, Iterator
29
from datetime import datetime
30
31
from pathlib import Path
32
from os import geteuid, environ
33
from lxml.etree import tostring, SubElement, Element
34
35
import psutil
36
37
from ospd.ospd import OSPDaemon
38
from ospd.scan import ScanProgress
39
from ospd.server import BaseServer
40
from ospd.main import main as daemon_main
41
from ospd.vtfilter import VtsFilter
42
from ospd.resultlist import ResultList
43
44
from ospd_openvas import __version__
45
from ospd_openvas.errors import OspdOpenvasError
46
47
from ospd_openvas.dryrun import DryRun
48
from ospd_openvas.nvticache import NVTICache
49
from ospd_openvas.db import MainDB, BaseDB
50
from ospd_openvas.mqtt import OpenvasMQTTHandler
51
from ospd_openvas.lock import LockFile
52
from ospd_openvas.preferencehandler import PreferenceHandler
53
from ospd_openvas.openvas import Openvas
54
from ospd_openvas.vthelper import VtHelper
55
56
SENTRY_DSN_OSPD_OPENVAS = environ.get("SENTRY_DSN_OSPD_OPENVAS")
57
if SENTRY_DSN_OSPD_OPENVAS:
58
    import sentry_sdk
59
60
    sentry_sdk.init(
61
        SENTRY_DSN_OSPD_OPENVAS,
62
        traces_sample_rate=1.0,
63
        server_name=environ.get('SENTRY_SERVER_NAME'),
64
        environment=environ.get('SENTRY_ENVIRONMENT'),
65
    )
66
67
logger = logging.getLogger(__name__)
68
69
70
OSPD_DESC = """
71
This scanner runs OpenVAS to scan the target hosts.
72
73
OpenVAS (Open Vulnerability Assessment Scanner) is a powerful scanner
74
for vulnerabilities in IT infrastrucutres. The capabilities include
75
unauthenticated scanning as well as authenticated scanning for
76
various types of systems and services.
77
78
For more details about OpenVAS see:
79
http://www.openvas.org/
80
81
The current version of ospd-openvas is a simple frame, which sends
82
the server parameters to the Greenbone Vulnerability Manager daemon (GVMd) and
83
checks the existence of OpenVAS binary. But it can not run scans yet.
84
"""
85
86
OSPD_PARAMS = {
87
    'auto_enable_dependencies': {
88
        'type': 'boolean',
89
        'name': 'auto_enable_dependencies',
90
        'default': 1,
91
        'mandatory': 1,
92
        'visible_for_client': True,
93
        'description': 'Automatically enable the plugins that are depended on',
94
    },
95
    'cgi_path': {
96
        'type': 'string',
97
        'name': 'cgi_path',
98
        'default': '/cgi-bin:/scripts',
99
        'mandatory': 1,
100
        'visible_for_client': True,
101
        'description': 'Look for default CGIs in /cgi-bin and /scripts',
102
    },
103
    'checks_read_timeout': {
104
        'type': 'integer',
105
        'name': 'checks_read_timeout',
106
        'default': 5,
107
        'mandatory': 1,
108
        'visible_for_client': True,
109
        'description': (
110
            'Number  of seconds that the security checks will '
111
            + 'wait for when doing a recv()'
112
        ),
113
    },
114
    'non_simult_ports': {
115
        'type': 'string',
116
        'name': 'non_simult_ports',
117
        'default': '139, 445, 3389, Services/irc',
118
        'mandatory': 1,
119
        'visible_for_client': True,
120
        'description': (
121
            'Prevent to make two connections on the same given '
122
            + 'ports at the same time.'
123
        ),
124
    },
125
    'open_sock_max_attempts': {
126
        'type': 'integer',
127
        'name': 'open_sock_max_attempts',
128
        'default': 5,
129
        'mandatory': 0,
130
        'visible_for_client': True,
131
        'description': (
132
            'Number of unsuccessful retries to open the socket '
133
            + 'before to set the port as closed.'
134
        ),
135
    },
136
    'timeout_retry': {
137
        'type': 'integer',
138
        'name': 'timeout_retry',
139
        'default': 5,
140
        'mandatory': 0,
141
        'visible_for_client': True,
142
        'description': (
143
            'Number of retries when a socket connection attempt ' + 'timesout.'
144
        ),
145
    },
146
    'optimize_test': {
147
        'type': 'boolean',
148
        'name': 'optimize_test',
149
        'default': 1,
150
        'mandatory': 0,
151
        'visible_for_client': True,
152
        'description': (
153
            'By default, optimize_test is enabled which means openvas does '
154
            + 'trust the remote host banners and is only launching plugins '
155
            + 'against the services they have been designed to check. '
156
            + 'For example it will check a web server claiming to be IIS only '
157
            + 'for IIS related flaws but will skip plugins testing for Apache '
158
            + 'flaws, and so on. This default behavior is used to optimize '
159
            + 'the scanning performance and to avoid false positives. '
160
            + 'If you are not sure that the banners of the remote host '
161
            + 'have been tampered with, you can disable this option.'
162
        ),
163
    },
164
    'plugins_timeout': {
165
        'type': 'integer',
166
        'name': 'plugins_timeout',
167
        'default': 5,
168
        'mandatory': 0,
169
        'visible_for_client': True,
170
        'description': 'This is the maximum lifetime, in seconds of a plugin.',
171
    },
172
    'report_host_details': {
173
        'type': 'boolean',
174
        'name': 'report_host_details',
175
        'default': 1,
176
        'mandatory': 1,
177
        'visible_for_client': True,
178
        'description': '',
179
    },
180
    'safe_checks': {
181
        'type': 'boolean',
182
        'name': 'safe_checks',
183
        'default': 1,
184
        'mandatory': 1,
185
        'visible_for_client': True,
186
        'description': (
187
            'Disable the plugins with potential to crash '
188
            + 'the remote services'
189
        ),
190
    },
191
    'scanner_plugins_timeout': {
192
        'type': 'integer',
193
        'name': 'scanner_plugins_timeout',
194
        'default': 36000,
195
        'mandatory': 1,
196
        'visible_for_client': True,
197
        'description': 'Like plugins_timeout, but for ACT_SCANNER plugins.',
198
    },
199
    'time_between_request': {
200
        'type': 'integer',
201
        'name': 'time_between_request',
202
        'default': 0,
203
        'mandatory': 0,
204
        'visible_for_client': True,
205
        'description': (
206
            'Allow to set a wait time between two actions '
207
            + '(open, send, close).'
208
        ),
209
    },
210
    'unscanned_closed': {
211
        'type': 'boolean',
212
        'name': 'unscanned_closed',
213
        'default': 1,
214
        'mandatory': 1,
215
        'visible_for_client': True,
216
        'description': '',
217
    },
218
    'unscanned_closed_udp': {
219
        'type': 'boolean',
220
        'name': 'unscanned_closed_udp',
221
        'default': 1,
222
        'mandatory': 1,
223
        'visible_for_client': True,
224
        'description': '',
225
    },
226
    'expand_vhosts': {
227
        'type': 'boolean',
228
        'name': 'expand_vhosts',
229
        'default': 1,
230
        'mandatory': 0,
231
        'visible_for_client': True,
232
        'description': 'Whether to expand the target hosts '
233
        + 'list of vhosts with values gathered from sources '
234
        + 'such as reverse-lookup queries and VT checks '
235
        + 'for SSL/TLS certificates.',
236
    },
237
    'test_empty_vhost': {
238
        'type': 'boolean',
239
        'name': 'test_empty_vhost',
240
        'default': 0,
241
        'mandatory': 0,
242
        'visible_for_client': True,
243
        'description': 'If  set  to  yes, the scanner will '
244
        + 'also test the target by using empty vhost value '
245
        + 'in addition to the targets associated vhost values.',
246
    },
247
    'max_hosts': {
248
        'type': 'integer',
249
        'name': 'max_hosts',
250
        'default': 30,
251
        'mandatory': 0,
252
        'visible_for_client': False,
253
        'description': (
254
            'The maximum number of hosts to test at the same time which '
255
            + 'should be given to the client (which can override it). '
256
            + 'This value must be computed given your bandwidth, '
257
            + 'the number of hosts you want to test, your amount of '
258
            + 'memory and the performance of your processor(s).'
259
        ),
260
    },
261
    'max_checks': {
262
        'type': 'integer',
263
        'name': 'max_checks',
264
        'default': 10,
265
        'mandatory': 0,
266
        'visible_for_client': False,
267
        'description': (
268
            'The number of plugins that will run against each host being '
269
            + 'tested. Note that the total number of process will be max '
270
            + 'checks x max_hosts so you need to find a balance between '
271
            + 'these two options. Note that launching too many plugins at '
272
            + 'the same time may disable the remote host, either temporarily '
273
            + '(ie: inetd closes its ports) or definitely (the remote host '
274
            + 'crash because it is asked to do too many things at the '
275
            + 'same time), so be careful.'
276
        ),
277
    },
278
    'port_range': {
279
        'type': 'string',
280
        'name': 'port_range',
281
        'default': '',
282
        'mandatory': 0,
283
        'visible_for_client': False,
284
        'description': (
285
            'This is the default range of ports that the scanner plugins will '
286
            + 'probe. The syntax of this option is flexible, it can be a '
287
            + 'single range ("1-1500"), several ports ("21,23,80"), several '
288
            + 'ranges of ports ("1-1500,32000-33000"). Note that you can '
289
            + 'specify UDP and TCP ports by prefixing each range by T or U. '
290
            + 'For instance, the following range will make openvas scan UDP '
291
            + 'ports 1 to 1024 and TCP ports 1 to 65535 : '
292
            + '"T:1-65535,U:1-1024".'
293
        ),
294
    },
295
    'test_alive_hosts_only': {
296
        'type': 'boolean',
297
        'name': 'test_alive_hosts_only',
298
        'default': 0,
299
        'mandatory': 0,
300
        'visible_for_client': False,
301
        'description': (
302
            'If this option is set, openvas will scan the target list for '
303
            + 'alive hosts in a separate process while only testing those '
304
            + 'hosts which are identified as alive. This boosts the scan '
305
            + 'speed of target ranges with a high amount of dead hosts '
306
            + 'significantly.'
307
        ),
308
    },
309
    'hosts_allow': {
310
        'type': 'string',
311
        'name': 'hosts_allow',
312
        'default': '',
313
        'mandatory': 0,
314
        'visible_for_client': False,
315
        'description': (
316
            'Comma-separated list of the only targets that are authorized '
317
            + 'to be scanned. Supports the same syntax as the list targets. '
318
            + 'Both target hostnames and the address to which they resolve '
319
            + 'are checked. Hostnames in hosts_allow list are not resolved '
320
            + 'however.'
321
        ),
322
    },
323
    'hosts_deny': {
324
        'type': 'string',
325
        'name': 'hosts_deny',
326
        'default': '',
327
        'mandatory': 0,
328
        'visible_for_client': False,
329
        'description': (
330
            'Comma-separated list of targets that are not authorized to '
331
            + 'be scanned. Supports the same syntax as the list targets. '
332
            + 'Both target hostnames and the address to which they resolve '
333
            + 'are checked. Hostnames in hosts_deny list are not '
334
            + 'resolved however.'
335
        ),
336
    },
337
    'results_per_host': {
338
        'type': 'integer',
339
        'name': 'results_per_host',
340
        'default': 10,
341
        'mandatory': 0,
342
        'visible_for_client': True,
343
        'description': (
344
            'Amount of fake results generated per each host in the target '
345
            + 'list for a dry run scan.'
346
        ),
347
    },
348
}
349
350
VT_BASE_OID = "1.3.6.1.4.1.25623."
351
352
353
def safe_int(value: str) -> Optional[int]:
354
    """Convert a string into an integer and return None in case of errors
355
    during conversion
356
    """
357
    try:
358
        return int(value)
359
    except (ValueError, TypeError):
360
        return None
361
362
363
def is_openvas_process_alive(kbdb: BaseDB, ovas_pid: str, scan_id: str) -> bool:
364
    parent_exists = True
365
    parent = None
366
    try:
367
        parent = psutil.Process(int(ovas_pid))
368
    except psutil.NoSuchProcess:
369
        logger.debug('Process with pid %s already stopped', ovas_pid)
370
        parent_exists = False
371
    except TypeError:
372
        logger.debug(
373
            'Scan with ID %s never started and stopped unexpectedly',
374
            scan_id,
375
        )
376
        parent_exists = False
377
378
    is_zombie = False
379
    if parent and parent.status() == psutil.STATUS_ZOMBIE:
380
        logger.debug(
381
            ' %s: OpenVAS process is a zombie process',
382
            scan_id,
383
        )
384
        is_zombie = True
385
386
    if (not parent_exists or is_zombie) and kbdb:
387
        if kbdb and kbdb.scan_is_stopped(scan_id):
388
            return True
389
        return False
390
391
    return True
392
393
394
class OpenVasVtsFilter(VtsFilter):
395
396
    """Methods to overwrite the ones in the original class."""
397
398
    def __init__(self, nvticache: NVTICache) -> None:
399
        super().__init__()
400
401
        self.nvti = nvticache
402
403
    def format_vt_modification_time(self, value: str) -> str:
404
        """Convert the string seconds since epoch into a 19 character
405
        string representing YearMonthDayHourMinuteSecond,
406
        e.g. 20190319122532. This always refers to UTC.
407
        """
408
409
        return datetime.utcfromtimestamp(int(value)).strftime("%Y%m%d%H%M%S")
410
411
    def get_filtered_vts_list(self, vts, vt_filter: str) -> Optional[List[str]]:
412
        """Gets a collection of vulnerability test from the redis cache,
413
        which match the filter.
414
415
        Arguments:
416
            vt_filter: Filter to apply to the vts collection.
417
            vts: The complete vts collection.
418
419
        Returns:
420
            List with filtered vulnerability tests. The list can be empty.
421
            None in case of filter parse failure.
422
        """
423
        filters = self.parse_filters(vt_filter)
424
        if not filters:
425
            return None
426
427
        if not self.nvti:
428
            return None
429
430
        vt_oid_list = [vtlist[1] for vtlist in self.nvti.get_oids()]
431
        vt_oid_list_temp = copy.copy(vt_oid_list)
432
        vthelper = VtHelper(self.nvti)
433
434
        for element, oper, filter_val in filters:
435
            for vt_oid in vt_oid_list_temp:
436
                if vt_oid not in vt_oid_list:
437
                    continue
438
439
                vt = vthelper.get_single_vt(vt_oid)
440
                if vt is None or not vt.get(element):
441
                    vt_oid_list.remove(vt_oid)
442
                    continue
443
444
                elem_val = vt.get(element)
445
                val = self.format_filter_value(element, elem_val)
446
447
                if not self.filter_operator[oper](val, filter_val):
448
                    vt_oid_list.remove(vt_oid)
449
450
        return vt_oid_list
451
452
453
class OSPDopenvas(OSPDaemon):
454
455
    """Class for ospd-openvas daemon."""
456
457
    def __init__(
458
        self,
459
        *,
460
        niceness=None,
461
        mqtt=None,
462
        lock_file_dir='/var/run/ospd',
463
        **kwargs,
464
    ):
465
        """Initializes the ospd-openvas daemon's internal data."""
466
        self.main_db = MainDB()
467
        self.nvti = NVTICache(self.main_db)
468
469
        super().__init__(
470
            customvtfilter=OpenVasVtsFilter(self.nvti),
471
            storage=dict,
472
            file_storage_dir=lock_file_dir,
473
            **kwargs,
474
        )
475
476
        self.server_version = __version__
477
478
        self._niceness = str(niceness)
479
480
        self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock')
481
        self.daemon_info['name'] = 'OSPd OpenVAS'
482
        self.scanner_info['name'] = 'openvas'
483
        self.scanner_info['version'] = ''  # achieved during self.init()
484
        self.scanner_info['description'] = OSPD_DESC
485
486
        if mqtt:
487
            self._mqtt = str(mqtt)
488
        else:
489
            self._mqtt = None
490
491
        for name, param in OSPD_PARAMS.items():
492
            self.set_scanner_param(name, param)
493
494
        self._sudo_available = None
495
        self._is_running_as_root = None
496
497
        self.scan_only_params = dict()
498
499
    def init(self, server: BaseServer) -> None:
500
501
        self.scan_collection.init()
502
503
        server.start(self.handle_client_stream)
504
505
        self.scanner_info['version'] = Openvas.get_version()
506
507
        self.set_params_from_openvas_settings()
508
509
        with self.feed_lock.wait_for_lock():
510
            Openvas.load_vts_into_redis()
511
            current_feed = self.nvti.get_feed_version()
512
            self.set_vts_version(vts_version=current_feed)
513
514
            logger.debug("Calculating vts integrity check hash...")
515
            vthelper = VtHelper(self.nvti)
516
            self.vts.sha256_hash = vthelper.calculate_vts_collection_hash()
517
518
        if self._mqtt:
519
            try:
520
                OpenvasMQTTHandler(self._mqtt, self.report_openvas_results)
521
                logger.debug("MQTT Client running...")
522
            except ConnectionRefusedError:
523
                logger.error(
524
                    "%s: Connection to MQTT broker refused. MQTT disabled.",
525
                    self._mqtt,
526
                )
527
                self._mqtt = None
528
529
        self.initialized = True
530
531
    def set_params_from_openvas_settings(self):
532
        """Set OSPD_PARAMS with the params taken from the openvas executable."""
533
        param_list = Openvas.get_settings()
534
535
        for elem in param_list:
536
            if elem not in OSPD_PARAMS:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable OSPD_PARAMS does not seem to be defined.
Loading history...
537
                self.scan_only_params[elem] = param_list[elem]
538
            else:
539
                OSPD_PARAMS[elem]['default'] = param_list[elem]
540
541
    def feed_is_outdated(self, current_feed: str) -> Optional[bool]:
542
        """Compare the current feed with the one in the disk.
543
544
        Return:
545
            False if there is no new feed.
546
            True if the feed version in disk is newer than the feed in
547
                redis cache.
548
            None if there is no feed on the disk.
549
        """
550
        plugins_folder = self.scan_only_params.get('plugins_folder')
551
        if not plugins_folder:
552
            raise OspdOpenvasError("Error: Path to plugins folder not found.")
553
554
        feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc'
555
        if not feed_info_file.exists():
556
            self.set_params_from_openvas_settings()
557
            logger.debug('Plugins feed file %s not found.', feed_info_file)
558
            return None
559
560
        current_feed = safe_int(current_feed)
561
        if current_feed is None:
562
            logger.debug(
563
                "Wrong PLUGIN_SET format in plugins feed file %s. Format has to"
564
                " be yyyymmddhhmm. For example 'PLUGIN_SET = \"201910251033\"'",
565
                feed_info_file,
566
            )
567
568
        feed_date = None
569
        with feed_info_file.open() as fcontent:
570
            for line in fcontent:
571
                if "PLUGIN_SET" in line:
572
                    feed_date = line.split('=', 1)[1]
573
                    feed_date = feed_date.strip()
574
                    feed_date = feed_date.replace(';', '')
575
                    feed_date = feed_date.replace('"', '')
576
                    feed_date = safe_int(feed_date)
577
                    break
578
579
        logger.debug("Current feed version: %s", current_feed)
580
        logger.debug("Plugin feed version: %s", feed_date)
581
582
        return (
583
            (not feed_date) or (not current_feed) or (current_feed < feed_date)
584
        )
585
586
    def check_feed(self):
587
        """Check if there is a feed update.
588
589
        Wait until all the running scans finished. Set a flag to announce there
590
        is a pending feed update, which avoids to start a new scan.
591
        """
592
        if not self.vts.is_cache_available:
593
            return
594
595
        current_feed = self.nvti.get_feed_version()
596
        is_outdated = self.feed_is_outdated(current_feed)
597
598
        # Check if the nvticache in redis is outdated
599
        if not current_feed or is_outdated:
600
            with self.feed_lock as fl:
601
                if fl.has_lock():
602
                    self.initialized = False
603
                    Openvas.load_vts_into_redis()
604
                    current_feed = self.nvti.get_feed_version()
605
                    self.set_vts_version(vts_version=current_feed)
606
607
                    vthelper = VtHelper(self.nvti)
608
                    self.vts.sha256_hash = (
609
                        vthelper.calculate_vts_collection_hash()
610
                    )
611
                    self.initialized = True
612
                else:
613
                    logger.debug(
614
                        "The feed was not upload or it is outdated, "
615
                        "but other process is locking the update. "
616
                        "Trying again later..."
617
                    )
618
                    return
619
620
    def scheduler(self):
621
        """This method is called periodically to run tasks."""
622
        self.check_feed()
623
624
    def get_vt_iterator(
625
        self, vt_selection: List[str] = None, details: bool = True
626
    ) -> Iterator[Tuple[str, Dict]]:
627
        vthelper = VtHelper(self.nvti)
628
        return vthelper.get_vt_iterator(vt_selection, details)
629
630
    @staticmethod
631
    def get_custom_vt_as_xml_str(vt_id: str, custom: Dict) -> str:
632
        """Return an xml element with custom metadata formatted as string.
633
        Arguments:
634
            vt_id: VT OID. Only used for logging in error case.
635
            custom: Dictionary with the custom metadata.
636
        Return:
637
            Xml element as string.
638
        """
639
640
        _custom = Element('custom')
641
        for key, val in custom.items():
642
            xml_key = SubElement(_custom, key)
643
            try:
644
                xml_key.text = val
645
            except ValueError as e:
646
                logger.warning(
647
                    "Not possible to parse custom tag for VT %s: %s", vt_id, e
648
                )
649
        return tostring(_custom).decode('utf-8')
650
651
    @staticmethod
652
    def get_severities_vt_as_xml_str(vt_id: str, severities: Dict) -> str:
653
        """Return an xml element with severities as string.
654
        Arguments:
655
            vt_id: VT OID. Only used for logging in error case.
656
            severities: Dictionary with the severities.
657
        Return:
658
            Xml element as string.
659
        """
660
        _severities = Element('severities')
661
        _severity = SubElement(_severities, 'severity')
662
        if 'severity_base_vector' in severities:
663
            try:
664
                _value = SubElement(_severity, 'value')
665
                _value.text = severities.get('severity_base_vector')
666
            except ValueError as e:
667
                logger.warning(
668
                    "Not possible to parse severity tag for vt %s: %s", vt_id, e
669
                )
670
        if 'severity_origin' in severities:
671
            _origin = SubElement(_severity, 'origin')
672
            _origin.text = severities.get('severity_origin')
673
        if 'severity_date' in severities:
674
            _date = SubElement(_severity, 'date')
675
            _date.text = severities.get('severity_date')
676
        if 'severity_type' in severities:
677
            _severity.set('type', severities.get('severity_type'))
678
679
        return tostring(_severities).decode('utf-8')
680
681
    @staticmethod
682
    def get_params_vt_as_xml_str(vt_id: str, vt_params: Dict) -> str:
683
        """Return an xml element with params formatted as string.
684
        Arguments:
685
            vt_id: VT OID. Only used for logging in error case.
686
            vt_params: Dictionary with the VT parameters.
687
        Return:
688
            Xml element as string.
689
        """
690
        vt_params_xml = Element('params')
691
        for _pref_id, prefs in vt_params.items():
692
            vt_param = Element('param')
693
            vt_param.set('type', prefs['type'])
694
            vt_param.set('id', _pref_id)
695
            xml_name = SubElement(vt_param, 'name')
696
            try:
697
                xml_name.text = prefs['name']
698
            except ValueError as e:
699
                logger.warning(
700
                    "Not possible to parse parameter for VT %s: %s", vt_id, e
701
                )
702
            if prefs['default']:
703
                xml_def = SubElement(vt_param, 'default')
704
                try:
705
                    xml_def.text = prefs['default']
706
                except ValueError as e:
707
                    logger.warning(
708
                        "Not possible to parse default parameter for VT %s: %s",
709
                        vt_id,
710
                        e,
711
                    )
712
            vt_params_xml.append(vt_param)
713
714
        return tostring(vt_params_xml).decode('utf-8')
715
716
    @staticmethod
717
    def get_refs_vt_as_xml_str(vt_id: str, vt_refs: Dict) -> str:
718
        """Return an xml element with references formatted as string.
719
        Arguments:
720
            vt_id: VT OID. Only used for logging in error case.
721
            vt_refs: Dictionary with the VT references.
722
        Return:
723
            Xml element as string.
724
        """
725
        vt_refs_xml = Element('refs')
726
        for ref_type, ref_values in vt_refs.items():
727
            for value in ref_values:
728
                vt_ref = Element('ref')
729
                if ref_type == "xref" and value:
730
                    for xref in value.split(', '):
731
                        try:
732
                            _type, _id = xref.split(':', 1)
733
                        except ValueError as e:
734
                            logger.error(
735
                                'Not possible to parse xref "%s" for VT %s: %s',
736
                                xref,
737
                                vt_id,
738
                                e,
739
                            )
740
                            continue
741
                        vt_ref.set('type', _type.lower())
742
                        vt_ref.set('id', _id)
743
                elif value:
744
                    vt_ref.set('type', ref_type.lower())
745
                    vt_ref.set('id', value)
746
                else:
747
                    continue
748
                vt_refs_xml.append(vt_ref)
749
750
        return tostring(vt_refs_xml).decode('utf-8')
751
752
    @staticmethod
753
    def get_dependencies_vt_as_xml_str(
754
        vt_id: str, vt_dependencies: List
755
    ) -> str:
756
        """Return  an xml element with dependencies as string.
757
        Arguments:
758
            vt_id: VT OID. Only used for logging in error case.
759
            vt_dependencies: List with the VT dependencies.
760
        Return:
761
            Xml element as string.
762
        """
763
        vt_deps_xml = Element('dependencies')
764
        for dep in vt_dependencies:
765
            _vt_dep = Element('dependency')
766
            if VT_BASE_OID in dep:
767
                _vt_dep.set('vt_id', dep)
768
            else:
769
                logger.error(
770
                    'Not possible to add dependency %s for VT %s', dep, vt_id
771
                )
772
                continue
773
            vt_deps_xml.append(_vt_dep)
774
775
        return tostring(vt_deps_xml).decode('utf-8')
776
777
    @staticmethod
778
    def get_creation_time_vt_as_xml_str(
779
        vt_id: str, vt_creation_time: str
780
    ) -> str:
781
        """Return creation time as string.
782
        Arguments:
783
            vt_id: VT OID. Only used for logging in error case.
784
            vt_creation_time: String with the VT creation time.
785
        Return:
786
           Xml element as string.
787
        """
788
        _time = Element('creation_time')
789
        try:
790
            _time.text = vt_creation_time
791
        except ValueError as e:
792
            logger.warning(
793
                "Not possible to parse creation time for VT %s: %s", vt_id, e
794
            )
795
        return tostring(_time).decode('utf-8')
796
797
    @staticmethod
798
    def get_modification_time_vt_as_xml_str(
799
        vt_id: str, vt_modification_time: str
800
    ) -> str:
801
        """Return modification time as string.
802
        Arguments:
803
            vt_id: VT OID. Only used for logging in error case.
804
            vt_modification_time: String with the VT modification time.
805
        Return:
806
            Xml element as string.
807
        """
808
        _time = Element('modification_time')
809
        try:
810
            _time.text = vt_modification_time
811
        except ValueError as e:
812
            logger.warning(
813
                "Not possible to parse modification time for VT %s: %s",
814
                vt_id,
815
                e,
816
            )
817
        return tostring(_time).decode('utf-8')
818
819
    @staticmethod
820
    def get_summary_vt_as_xml_str(vt_id: str, summary: str) -> str:
821
        """Return summary as string.
822
        Arguments:
823
            vt_id: VT OID. Only used for logging in error case.
824
            summary: String with a VT summary.
825
        Return:
826
            Xml element as string.
827
        """
828
        _summary = Element('summary')
829
        try:
830
            _summary.text = summary
831
        except ValueError as e:
832
            logger.warning(
833
                "Not possible to parse summary tag for VT %s: %s", vt_id, e
834
            )
835
        return tostring(_summary).decode('utf-8')
836
837
    @staticmethod
838
    def get_impact_vt_as_xml_str(vt_id: str, impact) -> str:
839
        """Return impact as string.
840
841
        Arguments:
842
            vt_id (str): VT OID. Only used for logging in error case.
843
            impact (str): String which explain the vulneravility impact.
844
        Return:
845
            string: xml element as string.
846
        """
847
        _impact = Element('impact')
848
        try:
849
            _impact.text = impact
850
        except ValueError as e:
851
            logger.warning(
852
                "Not possible to parse impact tag for VT %s: %s", vt_id, e
853
            )
854
        return tostring(_impact).decode('utf-8')
855
856
    @staticmethod
857
    def get_affected_vt_as_xml_str(vt_id: str, affected: str) -> str:
858
        """Return affected as string.
859
        Arguments:
860
            vt_id: VT OID. Only used for logging in error case.
861
            affected: String which explain what is affected.
862
        Return:
863
            Xml element as string.
864
        """
865
        _affected = Element('affected')
866
        try:
867
            _affected.text = affected
868
        except ValueError as e:
869
            logger.warning(
870
                "Not possible to parse affected tag for VT %s: %s", vt_id, e
871
            )
872
        return tostring(_affected).decode('utf-8')
873
874
    @staticmethod
875
    def get_insight_vt_as_xml_str(vt_id: str, insight: str) -> str:
876
        """Return insight as string.
877
        Arguments:
878
            vt_id: VT OID. Only used for logging in error case.
879
            insight: String giving an insight of the vulnerability.
880
        Return:
881
            Xml element as string.
882
        """
883
        _insight = Element('insight')
884
        try:
885
            _insight.text = insight
886
        except ValueError as e:
887
            logger.warning(
888
                "Not possible to parse insight tag for VT %s: %s", vt_id, e
889
            )
890
        return tostring(_insight).decode('utf-8')
891
892
    @staticmethod
893
    def get_solution_vt_as_xml_str(
894
        vt_id: str,
895
        solution: str,
896
        solution_type: Optional[str] = None,
897
        solution_method: Optional[str] = None,
898
    ) -> str:
899
        """Return solution as string.
900
        Arguments:
901
            vt_id: VT OID. Only used for logging in error case.
902
            solution: String giving a possible solution.
903
            solution_type: A solution type
904
            solution_method: A solution method
905
        Return:
906
            Xml element as string.
907
        """
908
        _solution = Element('solution')
909
        try:
910
            _solution.text = solution
911
        except ValueError as e:
912
            logger.warning(
913
                "Not possible to parse solution tag for VT %s: %s", vt_id, e
914
            )
915
        if solution_type:
916
            _solution.set('type', solution_type)
917
        if solution_method:
918
            _solution.set('method', solution_method)
919
        return tostring(_solution).decode('utf-8')
920
921
    @staticmethod
922
    def get_detection_vt_as_xml_str(
923
        vt_id: str,
924
        detection: Optional[str] = None,
925
        qod_type: Optional[str] = None,
926
        qod: Optional[str] = None,
927
    ) -> str:
928
        """Return detection as string.
929
        Arguments:
930
            vt_id: VT OID. Only used for logging in error case.
931
            detection: String which explain how the vulnerability
932
              was detected.
933
            qod_type: qod type.
934
            qod: qod value.
935
        Return:
936
            Xml element as string.
937
        """
938
        _detection = Element('detection')
939
        if detection:
940
            try:
941
                _detection.text = detection
942
            except ValueError as e:
943
                logger.warning(
944
                    "Not possible to parse detection tag for VT %s: %s",
945
                    vt_id,
946
                    e,
947
                )
948
        if qod_type:
949
            _detection.set('qod_type', qod_type)
950
        elif qod:
951
            _detection.set('qod', qod)
952
953
        return tostring(_detection).decode('utf-8')
954
955
    @property
956
    def is_running_as_root(self) -> bool:
957
        """Check if it is running as root user."""
958
        if self._is_running_as_root is not None:
959
            return self._is_running_as_root
960
961
        self._is_running_as_root = False
962
        if geteuid() == 0:
963
            self._is_running_as_root = True
964
965
        return self._is_running_as_root
966
967
    @property
968
    def sudo_available(self) -> bool:
969
        """Checks that sudo is available"""
970
        if self._sudo_available is not None:
971
            return self._sudo_available
972
973
        if self.is_running_as_root:
974
            self._sudo_available = False
975
            return self._sudo_available
976
977
        self._sudo_available = Openvas.check_sudo()
978
979
        return self._sudo_available
980
981
    def check(self) -> bool:
982
        """Checks that openvas command line tool is found and
983
        is executable."""
984
        has_openvas = Openvas.check()
985
        if not has_openvas:
986
            logger.error(
987
                'openvas executable not available. Please install openvas'
988
                ' into your PATH.'
989
            )
990
        return has_openvas
991
992
    def report_openvas_scan_status(self, kbdb: BaseDB, scan_id: str):
993
        """Get all status entries from redis kb.
994
995
        Arguments:
996
            kbdb: KB context where to get the status from.
997
            scan_id: Scan ID to identify the current scan.
998
        """
999
        all_status = kbdb.get_scan_status()
1000
        all_hosts = dict()
1001
        finished_hosts = list()
1002
        for res in all_status:
1003
            try:
1004
                current_host, launched, total = res.split('/')
1005
            except ValueError:
1006
                continue
1007
1008
            try:
1009
                if float(total) == 0:
1010
                    continue
1011
                if float(total) == ScanProgress.DEAD_HOST:
1012
                    host_prog = ScanProgress.DEAD_HOST
1013
                else:
1014
                    host_prog = int((float(launched) / float(total)) * 100)
1015
            except TypeError:
1016
                continue
1017
1018
            all_hosts[current_host] = host_prog
1019
1020
            if host_prog in (ScanProgress.DEAD_HOST, ScanProgress.FINISHED):
1021
                finished_hosts.append(current_host)
1022
1023
            logger.debug(
1024
                '%s: Host %s has progress: %d', scan_id, current_host, host_prog
1025
            )
1026
1027
        self.set_scan_progress_batch(scan_id, host_progress=all_hosts)
1028
1029
        self.sort_host_finished(scan_id, finished_hosts)
1030
1031
    def report_openvas_results_redis(self, db: BaseDB, scan_id: str) -> bool:
1032
        """Get all results from redis kb and add them into the scan table"""
1033
        all_results = db.get_result()
1034
1035
        return self.report_openvas_results_redis_format_to_dict(
1036
            scan_id, all_results
1037
        )
1038
1039
    def report_openvas_results_redis_format_to_dict(
1040
        self, scan_id: str, all_results: list
1041
    ) -> bool:
1042
        """Transforms all Results from redis format into a dictionary format
1043
        and add them into the scan table
1044
        """
1045
        results = []
1046
        for res in all_results:
1047
            if res:
1048
                result = {}
1049
                msg = res.split('|||')
1050
                result["type"] = msg[0]
1051
                result["host_ip"] = msg[1]
1052
                result["hostname"] = msg[2]
1053
                result["port"] = msg[3]
1054
                result["OID"] = msg[4]
1055
                result["value"] = msg[5]
1056
                if len(msg) > 6:
1057
                    result["uri"] = msg[6]
1058
1059
                results.append(result)
1060
1061
        return self.report_openvas_results(results, scan_id)
1062
1063
    def report_openvas_results(
1064
        self,
1065
        results: list,
1066
        scan_id: str,
1067
    ) -> bool:
1068
        """Add results given as dictionaries into the scan table."""
1069
1070
        vthelper = VtHelper(self.nvti)
1071
        res_list = ResultList()
1072
        total_dead = 0
1073
1074
        for res in results:
1075
            if res:
1076
                roid = res["OID"].strip()
1077
                rqod = ''
1078
                rname = ''
1079
                current_host = res["host_ip"].strip() if res["host_ip"] else ''
1080
                rhostname = res["hostname"].strip() if res["hostname"] else ''
1081
                host_is_dead = (
1082
                    "Host dead" in res["value"] or res["type"] == "DEADHOST"
1083
                )
1084
                host_deny = "Host access denied" in res["value"]
1085
                start_end_msg = (
1086
                    res["type"] == "HOST_START" or res["type"] == "HOST_END"
1087
                )
1088
                host_count = res["type"] == "HOSTS_COUNT"
1089
                vt_aux = None
1090
1091
                # URI is optional and containing must be checked
1092
                ruri = res["uri"] if "uri" in res else ""
1093
1094
                if (
1095
                    not host_is_dead
1096
                    and not host_deny
1097
                    and not start_end_msg
1098
                    and not host_count
1099
                ):
1100
                    if roid:
1101
                        vt_aux = vthelper.get_single_vt(roid)
1102
                    if not vt_aux:
1103
                        logger.warning('Invalid VT oid %s for a result', roid)
1104
                    else:
1105
                        if vt_aux.get('qod_type'):
1106
                            rqod = self.nvti.QOD_TYPES[vt_aux.get('qod_type')]
1107
                        elif vt_aux.get('qod'):
1108
                            rqod = vt_aux.get('qod')
1109
                        rname = vt_aux.get('name')
1110
1111
                if res["type"] == 'ERRMSG':
1112
                    res_list.add_scan_error_to_list(
1113
                        host=current_host,
1114
                        hostname=rhostname,
1115
                        name=rname,
1116
                        value=res["value"],
1117
                        port=res["port"],
1118
                        test_id=roid,
1119
                        uri=ruri,
1120
                    )
1121
1122
                elif res["type"] in ['HOST_START', 'HOST_END']:
1123
                    res_list.add_scan_log_to_list(
1124
                        host=current_host,
1125
                        name=res["type"],
1126
                        value=res["value"],
1127
                    )
1128
1129
                elif res["type"] == 'LOG':
1130
                    res_list.add_scan_log_to_list(
1131
                        host=current_host,
1132
                        hostname=rhostname,
1133
                        name=rname,
1134
                        value=res["value"],
1135
                        port=res["port"],
1136
                        qod=rqod,
1137
                        test_id=roid,
1138
                        uri=ruri,
1139
                    )
1140
1141
                elif res["type"] == 'HOST_DETAIL':
1142
                    res_list.add_scan_host_detail_to_list(
1143
                        host=current_host,
1144
                        hostname=rhostname,
1145
                        name=rname,
1146
                        value=res["value"],
1147
                        uri=ruri,
1148
                    )
1149
1150
                elif res["type"] == 'ALARM':
1151
                    rseverity = vthelper.get_severity_score(vt_aux)
1152
                    res_list.add_scan_alarm_to_list(
1153
                        host=current_host,
1154
                        hostname=rhostname,
1155
                        name=rname,
1156
                        value=res["value"],
1157
                        port=res["port"],
1158
                        test_id=roid,
1159
                        severity=rseverity,
1160
                        qod=rqod,
1161
                        uri=ruri,
1162
                    )
1163
1164
                # To process non-scanned dead hosts when
1165
                # test_alive_host_only in openvas is enable
1166
                elif res["type"] == 'DEADHOST':
1167
                    try:
1168
                        total_dead = int(res["value"])
1169
                    except TypeError:
1170
                        logger.debug('Error processing dead host count')
1171
1172
                # To update total host count
1173
                if res["type"] == 'HOSTS_COUNT':
1174
                    try:
1175
                        count_total = int(res["value"])
1176
                        logger.debug(
1177
                            '%s: Set total hosts counted by OpenVAS: %d',
1178
                            scan_id,
1179
                            count_total,
1180
                        )
1181
                        self.set_scan_total_hosts(scan_id, count_total)
1182
                    except TypeError:
1183
                        logger.debug('Error processing total host count')
1184
1185
        # Insert result batch into the scan collection table.
1186
        if len(res_list):
1187
            self.scan_collection.add_result_list(scan_id, res_list)
1188
            logger.debug(
1189
                '%s: Inserting %d results into scan collection table',
1190
                scan_id,
1191
                len(res_list),
1192
            )
1193
        if total_dead:
1194
            logger.debug(
1195
                '%s: Set dead hosts counted by OpenVAS: %d',
1196
                scan_id,
1197
                total_dead,
1198
            )
1199
            self.scan_collection.set_amount_dead_hosts(
1200
                scan_id, total_dead=total_dead
1201
            )
1202
1203
        return len(res_list) > 0
1204
1205
    def stop_scan_cleanup(  # pylint: disable=arguments-differ
1206
        self, scan_id: str
1207
    ) -> None:
1208
        """Set a key in redis to indicate the wrapper is stopped.
1209
        It is done through redis because it is a new multiprocess
1210
        instance and it is not possible to reach the variables
1211
        of the grandchild process.
1212
        Indirectly sends SIGUSR1 to the running openvas scan process
1213
        via an invocation of openvas with the --scan-stop option to
1214
        stop it."""
1215
1216
        kbdb = self.main_db.find_kb_database_by_scan_id(scan_id)
1217
        if kbdb:
1218
            kbdb.stop_scan(scan_id)
1219
            ovas_pid = kbdb.get_scan_process_id()
1220
1221
            parent = None
1222
            try:
1223
                parent = psutil.Process(int(ovas_pid))
1224
            except psutil.NoSuchProcess:
1225
                logger.debug('Process with pid %s already stopped', ovas_pid)
1226
            except TypeError:
1227
                logger.debug(
1228
                    'Scan with ID %s never started and stopped unexpectedly',
1229
                    scan_id,
1230
                )
1231
1232
            if parent:
1233
                can_stop_scan = Openvas.stop_scan(
1234
                    scan_id,
1235
                    not self.is_running_as_root and self.sudo_available,
1236
                )
1237
                if not can_stop_scan:
1238
                    logger.debug(
1239
                        'Not possible to stop scan process: %s.',
1240
                        parent,
1241
                    )
1242
                    return
1243
1244
                logger.debug('Stopping process: %s', parent)
1245
1246
                while parent:
1247
                    if parent.is_running():
1248
                        time.sleep(0.1)
1249
                    else:
1250
                        parent = None
1251
1252
            for scan_db in kbdb.get_scan_databases():
1253
                self.main_db.release_database(scan_db)
1254
1255
    def exec_scan(self, scan_id: str):
1256
        """Starts the OpenVAS scanner for scan_id scan."""
1257
        params = self.scan_collection.get_options(scan_id)
1258
        if params.get("dry_run"):
1259
            dryrun = DryRun(self)
1260
            dryrun.exec_dry_run_scan(scan_id, self.nvti, OSPD_PARAMS)
1261
            return
1262
1263
        do_not_launch = False
1264
        kbdb = self.main_db.get_new_kb_database()
1265
1266
        scan_prefs = PreferenceHandler(
1267
            scan_id, kbdb, self.scan_collection, self.nvti
1268
        )
1269
        kbdb.add_scan_id(scan_id)
1270
        scan_prefs.prepare_target_for_openvas()
1271
1272
        if not scan_prefs.prepare_ports_for_openvas():
1273
            self.add_scan_error(
1274
                scan_id, name='', host='', value='Invalid port list.'
1275
            )
1276
            do_not_launch = True
1277
1278
        # Set credentials
1279
        if not scan_prefs.prepare_credentials_for_openvas():
1280
            error = (
1281
                'All authentifications contain errors.'
1282
                + 'Starting unauthenticated scan instead.'
1283
            )
1284
            self.add_scan_error(
1285
                scan_id,
1286
                name='',
1287
                host='',
1288
                value=error,
1289
            )
1290
            logger.error(error)
1291
        errors = scan_prefs.get_error_messages()
1292
        for e in errors:
1293
            error = 'Malformed credential. ' + e
1294
            self.add_scan_error(
1295
                scan_id,
1296
                name='',
1297
                host='',
1298
                value=error,
1299
            )
1300
            logger.error(error)
1301
1302
        if not scan_prefs.prepare_plugins_for_openvas():
1303
            self.add_scan_error(
1304
                scan_id, name='', host='', value='No VTS to run.'
1305
            )
1306
            do_not_launch = True
1307
1308
        scan_prefs.prepare_main_kbindex_for_openvas()
1309
        scan_prefs.prepare_host_options_for_openvas()
1310
        scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS)
1311
        scan_prefs.prepare_reverse_lookup_opt_for_openvas()
1312
        scan_prefs.prepare_alive_test_option_for_openvas()
1313
1314
        # VT preferences are stored after all preferences have been processed,
1315
        # since alive tests preferences have to be able to overwrite default
1316
        # preferences of ping_host.nasl for the classic method.
1317
        scan_prefs.prepare_nvt_preferences()
1318
        scan_prefs.prepare_boreas_alive_test()
1319
1320
        # Release memory used for scan preferences.
1321
        del scan_prefs
1322
1323
        if do_not_launch or kbdb.scan_is_stopped(scan_id):
1324
            self.main_db.release_database(kbdb)
1325
            return
1326
1327
        result = Openvas.start_scan(
1328
            scan_id,
1329
            not self.is_running_as_root and self.sudo_available,
1330
            self._niceness,
1331
        )
1332
1333
        if result is None:
1334
            self.main_db.release_database(kbdb)
1335
            return
1336
1337
        ovas_pid = result.pid
1338
        kbdb.add_scan_process_id(ovas_pid)
1339
        logger.debug('pid = %s', ovas_pid)
1340
1341
        # Wait until the scanner starts and loads all the preferences.
1342
        while kbdb.get_status(scan_id) == 'new':
1343
            res = result.poll()
1344
            if res and res < 0:
1345
                self.stop_scan_cleanup(scan_id)
1346
                logger.error(
1347
                    'It was not possible run the task %s, since openvas ended '
1348
                    'unexpectedly with errors during launching.',
1349
                    scan_id,
1350
                )
1351
                return
1352
1353
            time.sleep(1)
1354
1355
        got_results = False
1356
        while True:
1357
            target_is_finished = kbdb.target_is_finished(scan_id)
1358
            openvas_process_is_alive = is_openvas_process_alive(
1359
                kbdb, ovas_pid, scan_id
1360
            )
1361
            if not target_is_finished and not openvas_process_is_alive:
1362
                logger.error(
1363
                    'Task %s was unexpectedly stopped or killed.',
1364
                    scan_id,
1365
                )
1366
                self.add_scan_error(
1367
                    scan_id,
1368
                    name='',
1369
                    host='',
1370
                    value='Task was unexpectedly stopped or killed.',
1371
                )
1372
1373
                # check for scanner error messages before leaving.
1374
                if not self._mqtt:
1375
                    self.report_openvas_results_redis(kbdb, scan_id)
1376
1377
                kbdb.stop_scan(scan_id)
1378
1379
                for scan_db in kbdb.get_scan_databases():
1380
                    self.main_db.release_database(scan_db)
1381
                self.main_db.release_database(kbdb)
1382
                return
1383
1384
            # Wait a second before trying to get result from redis if there
1385
            # was no results before.
1386
            # Otherwise, wait 50 msec to give access other process to redis.
1387
            if not got_results:
1388
                time.sleep(1)
1389
            else:
1390
                time.sleep(0.05)
1391
            got_results = False
1392
1393
            # Check if the client stopped the whole scan
1394
            if kbdb.scan_is_stopped(scan_id):
1395
                logger.debug('%s: Scan stopped by the client', scan_id)
1396
1397
                # clean main_db, but wait for scanner to finish.
1398
                while not kbdb.target_is_finished(scan_id):
1399
                    logger.debug('%s: Waiting the scan to finish', scan_id)
1400
                    time.sleep(1)
1401
                self.main_db.release_database(kbdb)
1402
                return
1403
1404
            if not self._mqtt:
1405
                got_results = self.report_openvas_results_redis(kbdb, scan_id)
1406
            self.report_openvas_scan_status(kbdb, scan_id)
1407
1408
            # Scan end. No kb in use for this scan id
1409
            if kbdb.target_is_finished(scan_id):
1410
                if self._mqtt:
1411
                    time.sleep(0.3)
1412
                logger.debug('%s: Target is finished', scan_id)
1413
                break
1414
1415
        # Delete keys from KB related to this scan task.
1416
        logger.debug('%s: End Target. Release main database', scan_id)
1417
        self.main_db.release_database(kbdb)
1418
1419
1420
def main():
1421
    """OSP openvas main function."""
1422
    daemon_main('OSPD - openvas', OSPDopenvas)
1423
1424
1425
if __name__ == '__main__':
1426
    main()
1427