Passed
Pull Request — master (#435)
by Jaspar
01:39
created

ospd_openvas.daemon.is_openvas_process_alive()   C

Complexity

Conditions 10

Size

Total Lines 31
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 25
nop 3
dl 0
loc 31
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
def is_openvas_process_alive(
363
    kbdb: BaseDB, ovas_pid: str, scan_id: str
364
) -> bool:
365
    parent_exists = True
366
    parent = None
367
    try:
368
        parent = psutil.Process(int(ovas_pid))
369
    except psutil.NoSuchProcess:
370
        logger.debug('Process with pid %s already stopped', ovas_pid)
371
        parent_exists = False
372
    except TypeError:
373
        logger.debug(
374
            'Scan with ID %s never started and stopped unexpectedly',
375
            scan_id,
376
        )
377
        parent_exists = False
378
379
    is_zombie = False
380
    if parent and parent.status() == psutil.STATUS_ZOMBIE:
381
        logger.debug(
382
            ' %s: OpenVAS process is a zombie process',
383
            scan_id,
384
        )
385
        is_zombie = True
386
387
    if (not parent_exists or is_zombie) and kbdb:
388
        if kbdb and kbdb.scan_is_stopped(scan_id):
389
            return True
390
        return False
391
392
    return True
393
394
395
class OpenVasVtsFilter(VtsFilter):
396
397
    """Methods to overwrite the ones in the original class."""
398
399
    def __init__(self, nvticache: NVTICache) -> None:
400
        super().__init__()
401
402
        self.nvti = nvticache
403
404
    def format_vt_modification_time(self, value: str) -> str:
405
        """Convert the string seconds since epoch into a 19 character
406
        string representing YearMonthDayHourMinuteSecond,
407
        e.g. 20190319122532. This always refers to UTC.
408
        """
409
410
        return datetime.utcfromtimestamp(int(value)).strftime("%Y%m%d%H%M%S")
411
412
    def get_filtered_vts_list(self, vts, vt_filter: str) -> Optional[List[str]]:
413
        """Gets a collection of vulnerability test from the redis cache,
414
        which match the filter.
415
416
        Arguments:
417
            vt_filter: Filter to apply to the vts collection.
418
            vts: The complete vts collection.
419
420
        Returns:
421
            List with filtered vulnerability tests. The list can be empty.
422
            None in case of filter parse failure.
423
        """
424
        filters = self.parse_filters(vt_filter)
425
        if not filters:
426
            return None
427
428
        if not self.nvti:
429
            return None
430
431
        vt_oid_list = [vtlist[1] for vtlist in self.nvti.get_oids()]
432
        vt_oid_list_temp = copy.copy(vt_oid_list)
433
        vthelper = VtHelper(self.nvti)
434
435
        for element, oper, filter_val in filters:
436
            for vt_oid in vt_oid_list_temp:
437
                if vt_oid not in vt_oid_list:
438
                    continue
439
440
                vt = vthelper.get_single_vt(vt_oid)
441
                if vt is None or not vt.get(element):
442
                    vt_oid_list.remove(vt_oid)
443
                    continue
444
445
                elem_val = vt.get(element)
446
                val = self.format_filter_value(element, elem_val)
447
448
                if not self.filter_operator[oper](val, filter_val):
449
                    vt_oid_list.remove(vt_oid)
450
451
        return vt_oid_list
452
453
454
class OSPDopenvas(OSPDaemon):
455
456
    """Class for ospd-openvas daemon."""
457
458
    def __init__(
459
        self,
460
        *,
461
        niceness=None,
462
        mqtt=None,
463
        lock_file_dir='/var/run/ospd',
464
        **kwargs,
465
    ):
466
        """Initializes the ospd-openvas daemon's internal data."""
467
        self.main_db = MainDB()
468
        self.nvti = NVTICache(self.main_db)
469
470
        super().__init__(
471
            customvtfilter=OpenVasVtsFilter(self.nvti),
472
            storage=dict,
473
            file_storage_dir=lock_file_dir,
474
            **kwargs,
475
        )
476
477
        self.server_version = __version__
478
479
        self._niceness = str(niceness)
480
481
        self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock')
482
        self.daemon_info['name'] = 'OSPd OpenVAS'
483
        self.scanner_info['name'] = 'openvas'
484
        self.scanner_info['version'] = ''  # achieved during self.init()
485
        self.scanner_info['description'] = OSPD_DESC
486
487
        if mqtt:
488
            self._mqtt = str(mqtt)
489
        else:
490
            self._mqtt = None
491
492
        for name, param in OSPD_PARAMS.items():
493
            self.set_scanner_param(name, param)
494
495
        self._sudo_available = None
496
        self._is_running_as_root = None
497
498
        self.scan_only_params = dict()
499
500
    def init(self, server: BaseServer) -> None:
501
502
        self.scan_collection.init()
503
504
        server.start(self.handle_client_stream)
505
506
        self.scanner_info['version'] = Openvas.get_version()
507
508
        self.set_params_from_openvas_settings()
509
510
        with self.feed_lock.wait_for_lock():
511
            Openvas.load_vts_into_redis()
512
            current_feed = self.nvti.get_feed_version()
513
            self.set_vts_version(vts_version=current_feed)
514
515
            logger.debug("Calculating vts integrity check hash...")
516
            vthelper = VtHelper(self.nvti)
517
            self.vts.sha256_hash = vthelper.calculate_vts_collection_hash()
518
519
        if self._mqtt:
520
            try:
521
                OpenvasMQTTHandler(self._mqtt, self.report_openvas_results)
522
                logger.debug("MQTT Client running...")
523
            except ConnectionRefusedError:
524
                logger.error(
525
                    "%s: Connection to MQTT broker refused. MQTT disabled.",
526
                    self._mqtt,
527
                )
528
                self._mqtt = None
529
530
        self.initialized = True
531
532
    def set_params_from_openvas_settings(self):
533
        """Set OSPD_PARAMS with the params taken from the openvas executable."""
534
        param_list = Openvas.get_settings()
535
536
        for elem in param_list:
537
            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...
538
                self.scan_only_params[elem] = param_list[elem]
539
            else:
540
                OSPD_PARAMS[elem]['default'] = param_list[elem]
541
542
    def feed_is_outdated(self, current_feed: str) -> Optional[bool]:
543
        """Compare the current feed with the one in the disk.
544
545
        Return:
546
            False if there is no new feed.
547
            True if the feed version in disk is newer than the feed in
548
                redis cache.
549
            None if there is no feed on the disk.
550
        """
551
        plugins_folder = self.scan_only_params.get('plugins_folder')
552
        if not plugins_folder:
553
            raise OspdOpenvasError("Error: Path to plugins folder not found.")
554
555
        feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc'
556
        if not feed_info_file.exists():
557
            self.set_params_from_openvas_settings()
558
            logger.debug('Plugins feed file %s not found.', feed_info_file)
559
            return None
560
561
        current_feed = safe_int(current_feed)
562
        if current_feed is None:
563
            logger.debug(
564
                "Wrong PLUGIN_SET format in plugins feed file %s. Format has to"
565
                " be yyyymmddhhmm. For example 'PLUGIN_SET = \"201910251033\"'",
566
                feed_info_file,
567
            )
568
569
        feed_date = None
570
        with feed_info_file.open() as fcontent:
571
            for line in fcontent:
572
                if "PLUGIN_SET" in line:
573
                    feed_date = line.split('=', 1)[1]
574
                    feed_date = feed_date.strip()
575
                    feed_date = feed_date.replace(';', '')
576
                    feed_date = feed_date.replace('"', '')
577
                    feed_date = safe_int(feed_date)
578
                    break
579
580
        logger.debug("Current feed version: %s", current_feed)
581
        logger.debug("Plugin feed version: %s", feed_date)
582
583
        return (
584
            (not feed_date) or (not current_feed) or (current_feed < feed_date)
585
        )
586
587
    def check_feed(self):
588
        """Check if there is a feed update.
589
590
        Wait until all the running scans finished. Set a flag to announce there
591
        is a pending feed update, which avoids to start a new scan.
592
        """
593
        if not self.vts.is_cache_available:
594
            return
595
596
        current_feed = self.nvti.get_feed_version()
597
        is_outdated = self.feed_is_outdated(current_feed)
598
599
        # Check if the nvticache in redis is outdated
600
        if not current_feed or is_outdated:
601
            with self.feed_lock as fl:
602
                if fl.has_lock():
603
                    self.initialized = False
604
                    Openvas.load_vts_into_redis()
605
                    current_feed = self.nvti.get_feed_version()
606
                    self.set_vts_version(vts_version=current_feed)
607
608
                    vthelper = VtHelper(self.nvti)
609
                    self.vts.sha256_hash = (
610
                        vthelper.calculate_vts_collection_hash()
611
                    )
612
                    self.initialized = True
613
                else:
614
                    logger.debug(
615
                        "The feed was not upload or it is outdated, "
616
                        "but other process is locking the update. "
617
                        "Trying again later..."
618
                    )
619
                    return
620
621
    def scheduler(self):
622
        """This method is called periodically to run tasks."""
623
        self.check_feed()
624
625
    def get_vt_iterator(
626
        self, vt_selection: List[str] = None, details: bool = True
627
    ) -> Iterator[Tuple[str, Dict]]:
628
        vthelper = VtHelper(self.nvti)
629
        return vthelper.get_vt_iterator(vt_selection, details)
630
631
    @staticmethod
632
    def get_custom_vt_as_xml_str(vt_id: str, custom: Dict) -> str:
633
        """Return an xml element with custom metadata formatted as string.
634
        Arguments:
635
            vt_id: VT OID. Only used for logging in error case.
636
            custom: Dictionary with the custom metadata.
637
        Return:
638
            Xml element as string.
639
        """
640
641
        _custom = Element('custom')
642
        for key, val in custom.items():
643
            xml_key = SubElement(_custom, key)
644
            try:
645
                xml_key.text = val
646
            except ValueError as e:
647
                logger.warning(
648
                    "Not possible to parse custom tag for VT %s: %s", vt_id, e
649
                )
650
        return tostring(_custom).decode('utf-8')
651
652
    @staticmethod
653
    def get_severities_vt_as_xml_str(vt_id: str, severities: Dict) -> str:
654
        """Return an xml element with severities as string.
655
        Arguments:
656
            vt_id: VT OID. Only used for logging in error case.
657
            severities: Dictionary with the severities.
658
        Return:
659
            Xml element as string.
660
        """
661
        _severities = Element('severities')
662
        _severity = SubElement(_severities, 'severity')
663
        if 'severity_base_vector' in severities:
664
            try:
665
                _value = SubElement(_severity, 'value')
666
                _value.text = severities.get('severity_base_vector')
667
            except ValueError as e:
668
                logger.warning(
669
                    "Not possible to parse severity tag for vt %s: %s", vt_id, e
670
                )
671
        if 'severity_origin' in severities:
672
            _origin = SubElement(_severity, 'origin')
673
            _origin.text = severities.get('severity_origin')
674
        if 'severity_date' in severities:
675
            _date = SubElement(_severity, 'date')
676
            _date.text = severities.get('severity_date')
677
        if 'severity_type' in severities:
678
            _severity.set('type', severities.get('severity_type'))
679
680
        return tostring(_severities).decode('utf-8')
681
682
    @staticmethod
683
    def get_params_vt_as_xml_str(vt_id: str, vt_params: Dict) -> str:
684
        """Return an xml element with params formatted as string.
685
        Arguments:
686
            vt_id: VT OID. Only used for logging in error case.
687
            vt_params: Dictionary with the VT parameters.
688
        Return:
689
            Xml element as string.
690
        """
691
        vt_params_xml = Element('params')
692
        for _pref_id, prefs in vt_params.items():
693
            vt_param = Element('param')
694
            vt_param.set('type', prefs['type'])
695
            vt_param.set('id', _pref_id)
696
            xml_name = SubElement(vt_param, 'name')
697
            try:
698
                xml_name.text = prefs['name']
699
            except ValueError as e:
700
                logger.warning(
701
                    "Not possible to parse parameter for VT %s: %s", vt_id, e
702
                )
703
            if prefs['default']:
704
                xml_def = SubElement(vt_param, 'default')
705
                try:
706
                    xml_def.text = prefs['default']
707
                except ValueError as e:
708
                    logger.warning(
709
                        "Not possible to parse default parameter for VT %s: %s",
710
                        vt_id,
711
                        e,
712
                    )
713
            vt_params_xml.append(vt_param)
714
715
        return tostring(vt_params_xml).decode('utf-8')
716
717
    @staticmethod
718
    def get_refs_vt_as_xml_str(vt_id: str, vt_refs: Dict) -> str:
719
        """Return an xml element with references formatted as string.
720
        Arguments:
721
            vt_id: VT OID. Only used for logging in error case.
722
            vt_refs: Dictionary with the VT references.
723
        Return:
724
            Xml element as string.
725
        """
726
        vt_refs_xml = Element('refs')
727
        for ref_type, ref_values in vt_refs.items():
728
            for value in ref_values:
729
                vt_ref = Element('ref')
730
                if ref_type == "xref" and value:
731
                    for xref in value.split(', '):
732
                        try:
733
                            _type, _id = xref.split(':', 1)
734
                        except ValueError as e:
735
                            logger.error(
736
                                'Not possible to parse xref "%s" for VT %s: %s',
737
                                xref,
738
                                vt_id,
739
                                e,
740
                            )
741
                            continue
742
                        vt_ref.set('type', _type.lower())
743
                        vt_ref.set('id', _id)
744
                elif value:
745
                    vt_ref.set('type', ref_type.lower())
746
                    vt_ref.set('id', value)
747
                else:
748
                    continue
749
                vt_refs_xml.append(vt_ref)
750
751
        return tostring(vt_refs_xml).decode('utf-8')
752
753
    @staticmethod
754
    def get_dependencies_vt_as_xml_str(
755
        vt_id: str, vt_dependencies: List
756
    ) -> str:
757
        """Return  an xml element with dependencies as string.
758
        Arguments:
759
            vt_id: VT OID. Only used for logging in error case.
760
            vt_dependencies: List with the VT dependencies.
761
        Return:
762
            Xml element as string.
763
        """
764
        vt_deps_xml = Element('dependencies')
765
        for dep in vt_dependencies:
766
            _vt_dep = Element('dependency')
767
            if VT_BASE_OID in dep:
768
                _vt_dep.set('vt_id', dep)
769
            else:
770
                logger.error(
771
                    'Not possible to add dependency %s for VT %s', dep, vt_id
772
                )
773
                continue
774
            vt_deps_xml.append(_vt_dep)
775
776
        return tostring(vt_deps_xml).decode('utf-8')
777
778
    @staticmethod
779
    def get_creation_time_vt_as_xml_str(
780
        vt_id: str, vt_creation_time: str
781
    ) -> str:
782
        """Return creation time as string.
783
        Arguments:
784
            vt_id: VT OID. Only used for logging in error case.
785
            vt_creation_time: String with the VT creation time.
786
        Return:
787
           Xml element as string.
788
        """
789
        _time = Element('creation_time')
790
        try:
791
            _time.text = vt_creation_time
792
        except ValueError as e:
793
            logger.warning(
794
                "Not possible to parse creation time for VT %s: %s", vt_id, e
795
            )
796
        return tostring(_time).decode('utf-8')
797
798
    @staticmethod
799
    def get_modification_time_vt_as_xml_str(
800
        vt_id: str, vt_modification_time: str
801
    ) -> str:
802
        """Return modification time as string.
803
        Arguments:
804
            vt_id: VT OID. Only used for logging in error case.
805
            vt_modification_time: String with the VT modification time.
806
        Return:
807
            Xml element as string.
808
        """
809
        _time = Element('modification_time')
810
        try:
811
            _time.text = vt_modification_time
812
        except ValueError as e:
813
            logger.warning(
814
                "Not possible to parse modification time for VT %s: %s",
815
                vt_id,
816
                e,
817
            )
818
        return tostring(_time).decode('utf-8')
819
820
    @staticmethod
821
    def get_summary_vt_as_xml_str(vt_id: str, summary: str) -> str:
822
        """Return summary as string.
823
        Arguments:
824
            vt_id: VT OID. Only used for logging in error case.
825
            summary: String with a VT summary.
826
        Return:
827
            Xml element as string.
828
        """
829
        _summary = Element('summary')
830
        try:
831
            _summary.text = summary
832
        except ValueError as e:
833
            logger.warning(
834
                "Not possible to parse summary tag for VT %s: %s", vt_id, e
835
            )
836
        return tostring(_summary).decode('utf-8')
837
838
    @staticmethod
839
    def get_impact_vt_as_xml_str(vt_id: str, impact) -> str:
840
        """Return impact as string.
841
842
        Arguments:
843
            vt_id (str): VT OID. Only used for logging in error case.
844
            impact (str): String which explain the vulneravility impact.
845
        Return:
846
            string: xml element as string.
847
        """
848
        _impact = Element('impact')
849
        try:
850
            _impact.text = impact
851
        except ValueError as e:
852
            logger.warning(
853
                "Not possible to parse impact tag for VT %s: %s", vt_id, e
854
            )
855
        return tostring(_impact).decode('utf-8')
856
857
    @staticmethod
858
    def get_affected_vt_as_xml_str(vt_id: str, affected: str) -> str:
859
        """Return affected as string.
860
        Arguments:
861
            vt_id: VT OID. Only used for logging in error case.
862
            affected: String which explain what is affected.
863
        Return:
864
            Xml element as string.
865
        """
866
        _affected = Element('affected')
867
        try:
868
            _affected.text = affected
869
        except ValueError as e:
870
            logger.warning(
871
                "Not possible to parse affected tag for VT %s: %s", vt_id, e
872
            )
873
        return tostring(_affected).decode('utf-8')
874
875
    @staticmethod
876
    def get_insight_vt_as_xml_str(vt_id: str, insight: str) -> str:
877
        """Return insight as string.
878
        Arguments:
879
            vt_id: VT OID. Only used for logging in error case.
880
            insight: String giving an insight of the vulnerability.
881
        Return:
882
            Xml element as string.
883
        """
884
        _insight = Element('insight')
885
        try:
886
            _insight.text = insight
887
        except ValueError as e:
888
            logger.warning(
889
                "Not possible to parse insight tag for VT %s: %s", vt_id, e
890
            )
891
        return tostring(_insight).decode('utf-8')
892
893
    @staticmethod
894
    def get_solution_vt_as_xml_str(
895
        vt_id: str,
896
        solution: str,
897
        solution_type: Optional[str] = None,
898
        solution_method: Optional[str] = None,
899
    ) -> str:
900
        """Return solution as string.
901
        Arguments:
902
            vt_id: VT OID. Only used for logging in error case.
903
            solution: String giving a possible solution.
904
            solution_type: A solution type
905
            solution_method: A solution method
906
        Return:
907
            Xml element as string.
908
        """
909
        _solution = Element('solution')
910
        try:
911
            _solution.text = solution
912
        except ValueError as e:
913
            logger.warning(
914
                "Not possible to parse solution tag for VT %s: %s", vt_id, e
915
            )
916
        if solution_type:
917
            _solution.set('type', solution_type)
918
        if solution_method:
919
            _solution.set('method', solution_method)
920
        return tostring(_solution).decode('utf-8')
921
922
    @staticmethod
923
    def get_detection_vt_as_xml_str(
924
        vt_id: str,
925
        detection: Optional[str] = None,
926
        qod_type: Optional[str] = None,
927
        qod: Optional[str] = None,
928
    ) -> str:
929
        """Return detection as string.
930
        Arguments:
931
            vt_id: VT OID. Only used for logging in error case.
932
            detection: String which explain how the vulnerability
933
              was detected.
934
            qod_type: qod type.
935
            qod: qod value.
936
        Return:
937
            Xml element as string.
938
        """
939
        _detection = Element('detection')
940
        if detection:
941
            try:
942
                _detection.text = detection
943
            except ValueError as e:
944
                logger.warning(
945
                    "Not possible to parse detection tag for VT %s: %s",
946
                    vt_id,
947
                    e,
948
                )
949
        if qod_type:
950
            _detection.set('qod_type', qod_type)
951
        elif qod:
952
            _detection.set('qod', qod)
953
954
        return tostring(_detection).decode('utf-8')
955
956
    @property
957
    def is_running_as_root(self) -> bool:
958
        """Check if it is running as root user."""
959
        if self._is_running_as_root is not None:
960
            return self._is_running_as_root
961
962
        self._is_running_as_root = False
963
        if geteuid() == 0:
964
            self._is_running_as_root = True
965
966
        return self._is_running_as_root
967
968
    @property
969
    def sudo_available(self) -> bool:
970
        """Checks that sudo is available"""
971
        if self._sudo_available is not None:
972
            return self._sudo_available
973
974
        if self.is_running_as_root:
975
            self._sudo_available = False
976
            return self._sudo_available
977
978
        self._sudo_available = Openvas.check_sudo()
979
980
        return self._sudo_available
981
982
    def check(self) -> bool:
983
        """Checks that openvas command line tool is found and
984
        is executable."""
985
        has_openvas = Openvas.check()
986
        if not has_openvas:
987
            logger.error(
988
                'openvas executable not available. Please install openvas'
989
                ' into your PATH.'
990
            )
991
        return has_openvas
992
993
    def report_openvas_scan_status(self, kbdb: BaseDB, scan_id: str):
994
        """Get all status entries from redis kb.
995
996
        Arguments:
997
            kbdb: KB context where to get the status from.
998
            scan_id: Scan ID to identify the current scan.
999
        """
1000
        all_status = kbdb.get_scan_status()
1001
        all_hosts = dict()
1002
        finished_hosts = list()
1003
        for res in all_status:
1004
            try:
1005
                current_host, launched, total = res.split('/')
1006
            except ValueError:
1007
                continue
1008
1009
            try:
1010
                if float(total) == 0:
1011
                    continue
1012
                if float(total) == ScanProgress.DEAD_HOST:
1013
                    host_prog = ScanProgress.DEAD_HOST
1014
                else:
1015
                    host_prog = int((float(launched) / float(total)) * 100)
1016
            except TypeError:
1017
                continue
1018
1019
            all_hosts[current_host] = host_prog
1020
1021
            if (
1022
                host_prog in (ScanProgress.DEAD_HOST, ScanProgress.FINISHED)
1023
            ):
1024
                finished_hosts.append(current_host)
1025
1026
            logger.debug(
1027
                '%s: Host %s has progress: %d', scan_id, current_host, host_prog
1028
            )
1029
1030
        self.set_scan_progress_batch(scan_id, host_progress=all_hosts)
1031
1032
        self.sort_host_finished(scan_id, finished_hosts)
1033
1034
    def report_openvas_results_redis(self, db: BaseDB, scan_id: str) -> bool:
1035
        """Get all results from redis kb and add them into the scan table"""
1036
        all_results = db.get_result()
1037
1038
        return self.report_openvas_results_redis_format_to_dict(
1039
            scan_id, all_results
1040
        )
1041
1042
    def report_openvas_results_redis_format_to_dict(
1043
        self, scan_id: str, all_results: list
1044
    ) -> bool:
1045
        """Transforms all Results from redis format into a dictionary format
1046
        and add them into the scan table
1047
        """
1048
        results = []
1049
        for res in all_results:
1050
            if res:
1051
                result = {}
1052
                msg = res.split('|||')
1053
                result["type"] = msg[0]
1054
                result["host_ip"] = msg[1]
1055
                result["hostname"] = msg[2]
1056
                result["port"] = msg[3]
1057
                result["OID"] = msg[4]
1058
                result["value"] = msg[5]
1059
                if len(msg) > 6:
1060
                    result["uri"] = msg[6]
1061
1062
                results.append(result)
1063
1064
        return self.report_openvas_results(results, scan_id)
1065
1066
    def report_openvas_results(
1067
        self,
1068
        results: list,
1069
        scan_id: str,
1070
    ) -> bool:
1071
        """Add results given as dictionaries into the scan table."""
1072
1073
        vthelper = VtHelper(self.nvti)
1074
        res_list = ResultList()
1075
        total_dead = 0
1076
1077
        for res in results:
1078
            if res:
1079
                roid = res["OID"].strip()
1080
                rqod = ''
1081
                rname = ''
1082
                current_host = res["host_ip"].strip() if res["host_ip"] else ''
1083
                rhostname = res["hostname"].strip() if res["hostname"] else ''
1084
                host_is_dead = (
1085
                    "Host dead" in res["value"] or res["type"] == "DEADHOST"
1086
                )
1087
                host_deny = "Host access denied" in res["value"]
1088
                start_end_msg = (
1089
                    res["type"] == "HOST_START" or res["type"] == "HOST_END"
1090
                )
1091
                host_count = res["type"] == "HOSTS_COUNT"
1092
                vt_aux = None
1093
1094
                # URI is optional and containing must be checked
1095
                ruri = res["uri"] if "uri" in res else ""
1096
1097
                if (
1098
                    not host_is_dead
1099
                    and not host_deny
1100
                    and not start_end_msg
1101
                    and not host_count
1102
                ):
1103
                    if roid:
1104
                        vt_aux = vthelper.get_single_vt(roid)
1105
                    if not vt_aux:
1106
                        logger.warning('Invalid VT oid %s for a result', roid)
1107
                    else:
1108
                        if vt_aux.get('qod_type'):
1109
                            rqod = self.nvti.QOD_TYPES[vt_aux.get('qod_type')]
1110
                        elif vt_aux.get('qod'):
1111
                            rqod = vt_aux.get('qod')
1112
                        rname = vt_aux.get('name')
1113
1114
                if res["type"] == 'ERRMSG':
1115
                    res_list.add_scan_error_to_list(
1116
                        host=current_host,
1117
                        hostname=rhostname,
1118
                        name=rname,
1119
                        value=res["value"],
1120
                        port=res["port"],
1121
                        test_id=roid,
1122
                        uri=ruri,
1123
                    )
1124
1125
                elif res["type"] in ['HOST_START', 'HOST_END']:
1126
                    res_list.add_scan_log_to_list(
1127
                        host=current_host,
1128
                        name=res["type"],
1129
                        value=res["value"],
1130
                    )
1131
1132
                elif res["type"] == 'LOG':
1133
                    res_list.add_scan_log_to_list(
1134
                        host=current_host,
1135
                        hostname=rhostname,
1136
                        name=rname,
1137
                        value=res["value"],
1138
                        port=res["port"],
1139
                        qod=rqod,
1140
                        test_id=roid,
1141
                        uri=ruri,
1142
                    )
1143
1144
                elif res["type"] == 'HOST_DETAIL':
1145
                    res_list.add_scan_host_detail_to_list(
1146
                        host=current_host,
1147
                        hostname=rhostname,
1148
                        name=rname,
1149
                        value=res["value"],
1150
                        uri=ruri,
1151
                    )
1152
1153
                elif res["type"] == 'ALARM':
1154
                    rseverity = vthelper.get_severity_score(vt_aux)
1155
                    res_list.add_scan_alarm_to_list(
1156
                        host=current_host,
1157
                        hostname=rhostname,
1158
                        name=rname,
1159
                        value=res["value"],
1160
                        port=res["port"],
1161
                        test_id=roid,
1162
                        severity=rseverity,
1163
                        qod=rqod,
1164
                        uri=ruri,
1165
                    )
1166
1167
                # To process non-scanned dead hosts when
1168
                # test_alive_host_only in openvas is enable
1169
                elif res["type"] == 'DEADHOST':
1170
                    try:
1171
                        total_dead = int(res["value"])
1172
                    except TypeError:
1173
                        logger.debug('Error processing dead host count')
1174
1175
                # To update total host count
1176
                if res["type"] == 'HOSTS_COUNT':
1177
                    try:
1178
                        count_total = int(res["value"])
1179
                        logger.debug(
1180
                            '%s: Set total hosts counted by OpenVAS: %d',
1181
                            scan_id,
1182
                            count_total,
1183
                        )
1184
                        self.set_scan_total_hosts(scan_id, count_total)
1185
                    except TypeError:
1186
                        logger.debug('Error processing total host count')
1187
1188
        # Insert result batch into the scan collection table.
1189
        if len(res_list):
1190
            self.scan_collection.add_result_list(scan_id, res_list)
1191
            logger.debug(
1192
                '%s: Inserting %d results into scan collection table',
1193
                scan_id,
1194
                len(res_list),
1195
            )
1196
        if total_dead:
1197
            logger.debug(
1198
                '%s: Set dead hosts counted by OpenVAS: %d',
1199
                scan_id,
1200
                total_dead,
1201
            )
1202
            self.scan_collection.set_amount_dead_hosts(
1203
                scan_id, total_dead=total_dead
1204
            )
1205
1206
        return len(res_list) > 0
1207
1208
1209
1210
    def stop_scan_cleanup(  # pylint: disable=arguments-differ
1211
        self, scan_id: str
1212
    ) -> None:
1213
        """Set a key in redis to indicate the wrapper is stopped.
1214
        It is done through redis because it is a new multiprocess
1215
        instance and it is not possible to reach the variables
1216
        of the grandchild process.
1217
        Indirectly sends SIGUSR1 to the running openvas scan process
1218
        via an invocation of openvas with the --scan-stop option to
1219
        stop it."""
1220
1221
        kbdb = self.main_db.find_kb_database_by_scan_id(scan_id)
1222
        if kbdb:
1223
            kbdb.stop_scan(scan_id)
1224
            ovas_pid = kbdb.get_scan_process_id()
1225
1226
            parent = None
1227
            try:
1228
                parent = psutil.Process(int(ovas_pid))
1229
            except psutil.NoSuchProcess:
1230
                logger.debug('Process with pid %s already stopped', ovas_pid)
1231
            except TypeError:
1232
                logger.debug(
1233
                    'Scan with ID %s never started and stopped unexpectedly',
1234
                    scan_id,
1235
                )
1236
1237
            if parent:
1238
                can_stop_scan = Openvas.stop_scan(
1239
                    scan_id,
1240
                    not self.is_running_as_root and self.sudo_available,
1241
                )
1242
                if not can_stop_scan:
1243
                    logger.debug(
1244
                        'Not possible to stop scan process: %s.',
1245
                        parent,
1246
                    )
1247
                    return
1248
1249
                logger.debug('Stopping process: %s', parent)
1250
1251
                while parent:
1252
                    if parent.is_running():
1253
                        time.sleep(0.1)
1254
                    else:
1255
                        parent = None
1256
1257
            for scan_db in kbdb.get_scan_databases():
1258
                self.main_db.release_database(scan_db)
1259
1260
    def exec_scan(self, scan_id: str):
1261
        """Starts the OpenVAS scanner for scan_id scan."""
1262
        params = self.scan_collection.get_options(scan_id)
1263
        if params.get("dry_run"):
1264
            dryrun = DryRun(self)
1265
            dryrun.exec_dry_run_scan(scan_id, self.nvti, OSPD_PARAMS)
1266
            return
1267
1268
        do_not_launch = False
1269
        kbdb = self.main_db.get_new_kb_database()
1270
1271
        scan_prefs = PreferenceHandler(
1272
            scan_id, kbdb, self.scan_collection, self.nvti
1273
        )
1274
        kbdb.add_scan_id(scan_id)
1275
        scan_prefs.prepare_target_for_openvas()
1276
1277
        if not scan_prefs.prepare_ports_for_openvas():
1278
            self.add_scan_error(
1279
                scan_id, name='', host='', value='Invalid port list.'
1280
            )
1281
            do_not_launch = True
1282
1283
        # Set credentials
1284
        if not scan_prefs.prepare_credentials_for_openvas():
1285
            error = (
1286
                'All authentifications contain errors.'
1287
                + 'Starting unauthenticated scan instead.'
1288
            )
1289
            self.add_scan_error(
1290
                scan_id,
1291
                name='',
1292
                host='',
1293
                value=error,
1294
            )
1295
            logger.error(error)
1296
        errors = scan_prefs.get_error_messages()
1297
        for e in errors:
1298
            error = 'Malformed credential. ' + e
1299
            self.add_scan_error(
1300
                scan_id,
1301
                name='',
1302
                host='',
1303
                value=error,
1304
            )
1305
            logger.error(error)
1306
1307
        if not scan_prefs.prepare_plugins_for_openvas():
1308
            self.add_scan_error(
1309
                scan_id, name='', host='', value='No VTS to run.'
1310
            )
1311
            do_not_launch = True
1312
1313
        scan_prefs.prepare_main_kbindex_for_openvas()
1314
        scan_prefs.prepare_host_options_for_openvas()
1315
        scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS)
1316
        scan_prefs.prepare_reverse_lookup_opt_for_openvas()
1317
        scan_prefs.prepare_alive_test_option_for_openvas()
1318
1319
        # VT preferences are stored after all preferences have been processed,
1320
        # since alive tests preferences have to be able to overwrite default
1321
        # preferences of ping_host.nasl for the classic method.
1322
        scan_prefs.prepare_nvt_preferences()
1323
        scan_prefs.prepare_boreas_alive_test()
1324
1325
        # Release memory used for scan preferences.
1326
        del scan_prefs
1327
1328
        if do_not_launch or kbdb.scan_is_stopped(scan_id):
1329
            self.main_db.release_database(kbdb)
1330
            return
1331
1332
        result = Openvas.start_scan(
1333
            scan_id,
1334
            not self.is_running_as_root and self.sudo_available,
1335
            self._niceness,
1336
        )
1337
1338
        if result is None:
1339
            self.main_db.release_database(kbdb)
1340
            return
1341
1342
        ovas_pid = result.pid
1343
        kbdb.add_scan_process_id(ovas_pid)
1344
        logger.debug('pid = %s', ovas_pid)
1345
1346
        # Wait until the scanner starts and loads all the preferences.
1347
        while kbdb.get_status(scan_id) == 'new':
1348
            res = result.poll()
1349
            if res and res < 0:
1350
                self.stop_scan_cleanup(scan_id)
1351
                logger.error(
1352
                    'It was not possible run the task %s, since openvas ended '
1353
                    'unexpectedly with errors during launching.',
1354
                    scan_id,
1355
                )
1356
                return
1357
1358
            time.sleep(1)
1359
1360
        got_results = False
1361
        while True:
1362
            target_is_finished = kbdb.target_is_finished(scan_id)
1363
            openvas_process_is_alive = is_openvas_process_alive(
1364
                kbdb, ovas_pid, scan_id
1365
            )
1366
            if not target_is_finished and not openvas_process_is_alive:
1367
                logger.error(
1368
                    'Task %s was unexpectedly stopped or killed.',
1369
                    scan_id,
1370
                )
1371
                self.add_scan_error(
1372
                    scan_id,
1373
                    name='',
1374
                    host='',
1375
                    value='Task was unexpectedly stopped or killed.',
1376
                )
1377
1378
                # check for scanner error messages before leaving.
1379
                if not self._mqtt:
1380
                    self.report_openvas_results_redis(kbdb, scan_id)
1381
1382
                kbdb.stop_scan(scan_id)
1383
1384
                for scan_db in kbdb.get_scan_databases():
1385
                    self.main_db.release_database(scan_db)
1386
                self.main_db.release_database(kbdb)
1387
                return
1388
1389
            # Wait a second before trying to get result from redis if there
1390
            # was no results before.
1391
            # Otherwise, wait 50 msec to give access other process to redis.
1392
            if not got_results:
1393
                time.sleep(1)
1394
            else:
1395
                time.sleep(0.05)
1396
            got_results = False
1397
1398
            # Check if the client stopped the whole scan
1399
            if kbdb.scan_is_stopped(scan_id):
1400
                logger.debug('%s: Scan stopped by the client', scan_id)
1401
1402
                # clean main_db, but wait for scanner to finish.
1403
                while not kbdb.target_is_finished(scan_id):
1404
                    logger.debug('%s: Waiting the scan to finish', scan_id)
1405
                    time.sleep(1)
1406
                self.main_db.release_database(kbdb)
1407
                return
1408
1409
            if not self._mqtt:
1410
                got_results = self.report_openvas_results_redis(kbdb, scan_id)
1411
            self.report_openvas_scan_status(kbdb, scan_id)
1412
1413
            # Scan end. No kb in use for this scan id
1414
            if kbdb.target_is_finished(scan_id):
1415
                if self._mqtt:
1416
                    time.sleep(0.3)
1417
                logger.debug('%s: Target is finished', scan_id)
1418
                break
1419
1420
        # Delete keys from KB related to this scan task.
1421
        logger.debug('%s: End Target. Release main database', scan_id)
1422
        self.main_db.release_database(kbdb)
1423
1424
1425
def main():
1426
    """OSP openvas main function."""
1427
    daemon_main('OSPD - openvas', OSPDopenvas)
1428
1429
1430
if __name__ == '__main__':
1431
    main()
1432