Completed
Push — master ( 784599...e17a09 )
by
unknown
18s queued 12s
created

OSPDopenvas.report_openvas_results_redis()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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