Passed
Pull Request — master (#422)
by
unknown
02:34
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
        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
        """Get all results from redis kb and add them into the scan table"""
1004
        all_results = db.get_result()
1005
1006
        return self.report_openvas_results_redis_format_to_dict(
1007
            scan_id, all_results
1008
        )
1009
1010
    def report_openvas_results_redis_format_to_dict(
1011
        self, scan_id: str, all_results: list
1012
    ) -> bool:
1013
        """Transforms all Results from redis format into a dictionary format
1014
        and add them into the scan table
1015
        """
1016
        results = []
1017
        for res in all_results:
1018
            if not res:
1019
                continue
1020
            result = {}
1021
            msg = res.split('|||')
1022
            result["type"] = msg[0]
1023
            result["host_ip"] = msg[1]
1024
            result["hostname"] = msg[2]
1025
            result["port"] = msg[3]
1026
            result["OID"] = msg[4]
1027
            result["value"] = msg[5]
1028
            if len(msg) > 6:
1029
                result["uri"] = msg[6]
1030
1031
            results.append(result)
1032
1033
        return self.report_openvas_results(results, scan_id)
1034
1035
    def report_openvas_results(
1036
        self,
1037
        results: list,
1038
        scan_id: str,
1039
    ) -> bool:
1040
        """Add results given as dictionaries into the scan table."""
1041
1042
        vthelper = VtHelper(self.nvti)
1043
1044
        res_list = ResultList()
1045
        total_dead = 0
1046
        for res in results:
1047
            if not res:
1048
                continue
1049
1050
            roid = res["OID"].strip()
1051
            rqod = ''
1052
            rname = ''
1053
            current_host = res["host_ip"].strip() if res["host_ip"] else ''
1054
            rhostname = res["hostname"].strip() if res["hostname"] else ''
1055
            host_is_dead = (
1056
                "Host dead" in res["value"] or res["type"] == "DEADHOST"
1057
            )
1058
            host_deny = "Host access denied" in res["value"]
1059
            start_end_msg = (
1060
                res["type"] == "HOST_START" or res["type"] == "HOST_END"
1061
            )
1062
            host_count = res["type"] == "HOSTS_COUNT"
1063
            vt_aux = None
1064
1065
            # URI is optional and containing must be checked
1066
            ruri = res["uri"] if "uri" in res else ""
1067
1068
            if (
1069
                roid
1070
                and not host_is_dead
1071
                and not host_deny
1072
                and not start_end_msg
1073
                and not host_count
1074
            ):
1075
                vt_aux = vthelper.get_single_vt(roid)
1076
1077
            if (
1078
                not vt_aux
1079
                and not host_is_dead
1080
                and not host_deny
1081
                and not start_end_msg
1082
                and not host_count
1083
            ):
1084
                logger.warning('Invalid VT oid %s for a result', roid)
1085
1086
            if vt_aux:
1087
                if vt_aux.get('qod_type'):
1088
                    qod_t = vt_aux.get('qod_type')
1089
                    rqod = self.nvti.QOD_TYPES[qod_t]
1090
                elif vt_aux.get('qod'):
1091
                    rqod = vt_aux.get('qod')
1092
1093
                rname = vt_aux.get('name')
1094
1095
            if res["type"] == 'ERRMSG':
1096
                res_list.add_scan_error_to_list(
1097
                    host=current_host,
1098
                    hostname=rhostname,
1099
                    name=rname,
1100
                    value=res["value"],
1101
                    port=res["port"],
1102
                    test_id=roid,
1103
                    uri=ruri,
1104
                )
1105
1106
            elif res["type"] == 'HOST_START' or res["type"] == 'HOST_END':
1107
                res_list.add_scan_log_to_list(
1108
                    host=current_host,
1109
                    name=res["type"],
1110
                    value=res["value"],
1111
                )
1112
1113
            elif res["type"] == 'LOG':
1114
                res_list.add_scan_log_to_list(
1115
                    host=current_host,
1116
                    hostname=rhostname,
1117
                    name=rname,
1118
                    value=res["value"],
1119
                    port=res["port"],
1120
                    qod=rqod,
1121
                    test_id=roid,
1122
                    uri=ruri,
1123
                )
1124
1125
            elif res["type"] == 'HOST_DETAIL':
1126
                res_list.add_scan_host_detail_to_list(
1127
                    host=current_host,
1128
                    hostname=rhostname,
1129
                    name=rname,
1130
                    value=res["value"],
1131
                    uri=ruri,
1132
                )
1133
1134
            elif res["type"] == 'ALARM':
1135
                rseverity = vthelper.get_severity_score(vt_aux)
1136
                res_list.add_scan_alarm_to_list(
1137
                    host=current_host,
1138
                    hostname=rhostname,
1139
                    name=rname,
1140
                    value=res["value"],
1141
                    port=res["port"],
1142
                    test_id=roid,
1143
                    severity=rseverity,
1144
                    qod=rqod,
1145
                    uri=ruri,
1146
                )
1147
1148
            # To process non-scanned dead hosts when
1149
            # test_alive_host_only in openvas is enable
1150
            elif res["type"] == 'DEADHOST':
1151
                try:
1152
                    total_dead = int(res["value"])
1153
                except TypeError:
1154
                    logger.debug('Error processing dead host count')
1155
1156
            # To update total host count
1157
            if res["type"] == 'HOSTS_COUNT':
1158
                try:
1159
                    count_total = int(res["value"])
1160
                    logger.debug(
1161
                        '%s: Set total hosts counted by OpenVAS: %d',
1162
                        scan_id,
1163
                        count_total,
1164
                    )
1165
                    self.set_scan_total_hosts(scan_id, count_total)
1166
                except TypeError:
1167
                    logger.debug('Error processing total host count')
1168
1169
        # Insert result batch into the scan collection table.
1170
        if len(res_list):
1171
            self.scan_collection.add_result_list(scan_id, res_list)
1172
            logger.debug(
1173
                '%s: Inserting %d results into scan collection table',
1174
                scan_id,
1175
                len(res_list),
1176
            )
1177
        if total_dead:
1178
            logger.debug(
1179
                '%s: Set dead hosts counted by OpenVAS: %d',
1180
                scan_id,
1181
                total_dead,
1182
            )
1183
            self.scan_collection.set_amount_dead_hosts(
1184
                scan_id, total_dead=total_dead
1185
            )
1186
1187
        return len(res_list) > 0
1188
1189
    def is_openvas_process_alive(
1190
        self, kbdb: BaseDB, ovas_pid: str, scan_id: str
1191
    ) -> bool:
1192
        parent_exists = True
1193
        parent = None
1194
        try:
1195
            parent = psutil.Process(int(ovas_pid))
1196
        except psutil.NoSuchProcess:
1197
            logger.debug('Process with pid %s already stopped', ovas_pid)
1198
            parent_exists = False
1199
        except TypeError:
1200
            logger.debug(
1201
                'Scan with ID %s never started and stopped unexpectedly',
1202
                scan_id,
1203
            )
1204
            parent_exists = False
1205
1206
        is_zombie = False
1207
        if parent and parent.status() == psutil.STATUS_ZOMBIE:
1208
            logger.debug(
1209
                ' %s: OpenVAS process is a zombie process',
1210
                scan_id,
1211
            )
1212
            is_zombie = True
1213
1214
        if (not parent_exists or is_zombie) and kbdb:
1215
            if kbdb and kbdb.scan_is_stopped(scan_id):
1216
                return True
1217
            return False
1218
1219
        return True
1220
1221
    def stop_scan_cleanup(  # pylint: disable=arguments-differ
1222
        self, scan_id: str
1223
    ):
1224
        """Set a key in redis to indicate the wrapper is stopped.
1225
        It is done through redis because it is a new multiprocess
1226
        instance and it is not possible to reach the variables
1227
        of the grandchild process.
1228
        Indirectly sends SIGUSR1 to the running openvas scan process
1229
        via an invocation of openvas with the --scan-stop option to
1230
        stop it."""
1231
1232
        kbdb = self.main_db.find_kb_database_by_scan_id(scan_id)
1233
        if kbdb:
1234
            kbdb.stop_scan(scan_id)
1235
            ovas_pid = kbdb.get_scan_process_id()
1236
1237
            parent = None
1238
            try:
1239
                parent = psutil.Process(int(ovas_pid))
1240
            except psutil.NoSuchProcess:
1241
                logger.debug('Process with pid %s already stopped', ovas_pid)
1242
            except TypeError:
1243
                logger.debug(
1244
                    'Scan with ID %s never started and stopped unexpectedly',
1245
                    scan_id,
1246
                )
1247
1248
            if parent:
1249
                can_stop_scan = Openvas.stop_scan(
1250
                    scan_id,
1251
                    not self.is_running_as_root and self.sudo_available,
1252
                )
1253
                if not can_stop_scan:
1254
                    logger.debug(
1255
                        'Not possible to stop scan process: %s.',
1256
                        parent,
1257
                    )
1258
                    return False
1259
1260
                logger.debug('Stopping process: %s', parent)
1261
1262
                while parent:
1263
                    if parent.is_running():
1264
                        time.sleep(0.1)
1265
                    else:
1266
                        parent = None
1267
1268
            for scan_db in kbdb.get_scan_databases():
1269
                self.main_db.release_database(scan_db)
1270
1271
    def exec_scan(self, scan_id: str):
1272
        """Starts the OpenVAS scanner for scan_id scan."""
1273
        params = self.scan_collection.get_options(scan_id)
1274
        if params.get("dry_run"):
1275
            dryrun = DryRun(self)
1276
            dryrun.exec_dry_run_scan(scan_id, self.nvti, OSPD_PARAMS)
1277
            return
1278
1279
        do_not_launch = False
1280
        kbdb = self.main_db.get_new_kb_database()
1281
1282
        scan_prefs = PreferenceHandler(
1283
            scan_id, kbdb, self.scan_collection, self.nvti
1284
        )
1285
        kbdb.add_scan_id(scan_id)
1286
        scan_prefs.prepare_target_for_openvas()
1287
1288
        if not scan_prefs.prepare_ports_for_openvas():
1289
            self.add_scan_error(
1290
                scan_id, name='', host='', value='Invalid port list.'
1291
            )
1292
            do_not_launch = True
1293
1294
        # Set credentials
1295
        if not scan_prefs.prepare_credentials_for_openvas():
1296
            error = (
1297
                'All authentifications contain errors.'
1298
                + 'Starting unauthenticated scan instead.'
1299
            )
1300
            self.add_scan_error(
1301
                scan_id,
1302
                name='',
1303
                host='',
1304
                value=error,
1305
            )
1306
            logger.error(error)
1307
        errors = scan_prefs.get_error_messages()
1308
        for e in errors:
1309
            error = 'Malformed credential. ' + e
1310
            self.add_scan_error(
1311
                scan_id,
1312
                name='',
1313
                host='',
1314
                value=error,
1315
            )
1316
            logger.error(error)
1317
1318
        if not scan_prefs.prepare_plugins_for_openvas():
1319
            self.add_scan_error(
1320
                scan_id, name='', host='', value='No VTS to run.'
1321
            )
1322
            do_not_launch = True
1323
1324
        scan_prefs.prepare_main_kbindex_for_openvas()
1325
        scan_prefs.prepare_host_options_for_openvas()
1326
        scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS)
1327
        scan_prefs.prepare_reverse_lookup_opt_for_openvas()
1328
        scan_prefs.prepare_alive_test_option_for_openvas()
1329
1330
        # VT preferences are stored after all preferences have been processed,
1331
        # since alive tests preferences have to be able to overwrite default
1332
        # preferences of ping_host.nasl for the classic method.
1333
        scan_prefs.prepare_nvt_preferences()
1334
        scan_prefs.prepare_boreas_alive_test()
1335
1336
        # Release memory used for scan preferences.
1337
        del scan_prefs
1338
1339
        if do_not_launch or kbdb.scan_is_stopped(scan_id):
1340
            self.main_db.release_database(kbdb)
1341
            return
1342
1343
        result = Openvas.start_scan(
1344
            scan_id,
1345
            not self.is_running_as_root and self.sudo_available,
1346
            self._niceness,
1347
        )
1348
1349
        if result is None:
1350
            self.main_db.release_database(kbdb)
1351
            return
1352
1353
        ovas_pid = result.pid
1354
        kbdb.add_scan_process_id(ovas_pid)
1355
        logger.debug('pid = %s', ovas_pid)
1356
1357
        # Wait until the scanner starts and loads all the preferences.
1358
        while kbdb.get_status(scan_id) == 'new':
1359
            res = result.poll()
1360
            if res and res < 0:
1361
                self.stop_scan_cleanup(scan_id)
1362
                logger.error(
1363
                    'It was not possible run the task %s, since openvas ended '
1364
                    'unexpectedly with errors during launching.',
1365
                    scan_id,
1366
                )
1367
                return
1368
1369
            time.sleep(1)
1370
1371
        got_results = False
1372
        while True:
1373
            target_is_finished = kbdb.target_is_finished(scan_id)
1374
            openvas_process_is_alive = self.is_openvas_process_alive(
1375
                kbdb, ovas_pid, scan_id
1376
            )
1377
            if not target_is_finished and not openvas_process_is_alive:
1378
                logger.error(
1379
                    'Task %s was unexpectedly stopped or killed.',
1380
                    scan_id,
1381
                )
1382
                self.add_scan_error(
1383
                    scan_id,
1384
                    name='',
1385
                    host='',
1386
                    value='Task was unexpectedly stopped or killed.',
1387
                )
1388
1389
                # check for scanner error messages before leaving.
1390
                if not self._mqtt:
1391
                    self.report_openvas_results_redis(kbdb, scan_id)
1392
1393
                kbdb.stop_scan(scan_id)
1394
1395
                for scan_db in kbdb.get_scan_databases():
1396
                    self.main_db.release_database(scan_db)
1397
                self.main_db.release_database(kbdb)
1398
                return
1399
1400
            # Wait a second before trying to get result from redis if there
1401
            # was no results before.
1402
            # Otherwise, wait 50 msec to give access other process to redis.
1403
            if not got_results:
1404
                time.sleep(1)
1405
            else:
1406
                time.sleep(0.05)
1407
            got_results = False
1408
1409
            # Check if the client stopped the whole scan
1410
            if kbdb.scan_is_stopped(scan_id):
1411
                logger.debug('%s: Scan stopped by the client', scan_id)
1412
1413
                # clean main_db, but wait for scanner to finish.
1414
                while not kbdb.target_is_finished(scan_id):
1415
                    logger.debug('%s: Waiting the scan to finish', scan_id)
1416
                    time.sleep(1)
1417
                self.main_db.release_database(kbdb)
1418
                return
1419
1420
            if not self._mqtt:
1421
                got_results = self.report_openvas_results_redis(kbdb, scan_id)
1422
            self.report_openvas_scan_status(kbdb, scan_id)
1423
1424
            # Scan end. No kb in use for this scan id
1425
            if kbdb.target_is_finished(scan_id):
1426
                logger.debug('%s: Target is finished', scan_id)
1427
                break
1428
1429
        # Delete keys from KB related to this scan task.
1430
        logger.debug('%s: End Target. Release main database', scan_id)
1431
        self.main_db.release_database(kbdb)
1432
1433
1434
def main():
1435
    """OSP openvas main function."""
1436
    daemon_main('OSPD - openvas', OSPDopenvas)
1437
1438
1439
if __name__ == '__main__':
1440
    main()
1441