Passed
Pull Request — master (#422)
by
unknown
02:18
created

OSPDopenvas.report_openvas_results_redis()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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