Passed
Pull Request — master (#448)
by
unknown
01:22
created

OSPDopenvas.stop_scan_cleanup()   B

Complexity

Conditions 8

Size

Total Lines 59
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 33
nop 4
dl 0
loc 59
rs 7.2213
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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