Passed
Pull Request — master (#424)
by Juan José
01:39
created

ospd_openvas.daemon   F

Complexity

Total Complexity 168

Size/Duplication

Total Lines 1362
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 892
dl 0
loc 1362
rs 1.708
c 0
b 0
f 0
wmc 168

31 Methods

Rating   Name   Duplication   Size   Complexity  
A OSPDopenvas.get_custom_vt_as_xml_str() 0 20 3
A OSPDopenvas.get_creation_time_vt_as_xml_str() 0 19 2
A OSPDopenvas.sudo_available() 0 13 3
A OSPDopenvas.scheduler() 0 3 1
B OSPDopenvas.get_params_vt_as_xml_str() 0 34 5
B OSPDopenvas.check_feed() 0 33 6
C OpenVasVtsFilter.get_filtered_vts_list() 0 42 9
A OSPDopenvas.check() 0 10 2
A OSPDopenvas.get_solution_vt_as_xml_str() 0 28 4
B OSPDopenvas.get_refs_vt_as_xml_str() 0 35 8
A OSPDopenvas.get_insight_vt_as_xml_str() 0 17 2
A OpenVasVtsFilter.format_vt_modification_time() 0 7 1
B OSPDopenvas.report_openvas_scan_status() 0 41 8
A OSPDopenvas.set_params_from_openvas_settings() 0 9 3
A OpenVasVtsFilter.__init__() 0 4 1
A OSPDopenvas.get_modification_time_vt_as_xml_str() 0 21 2
A OSPDopenvas.get_affected_vt_as_xml_str() 0 17 2
A OSPDopenvas.get_summary_vt_as_xml_str() 0 17 2
A OSPDopenvas.__init__() 0 31 2
A OSPDopenvas.get_dependencies_vt_as_xml_str() 0 24 3
A OSPDopenvas.get_vt_iterator() 0 5 1
A OSPDopenvas.init() 0 20 2
A OSPDopenvas.get_impact_vt_as_xml_str() 0 18 2
B OSPDopenvas.get_severities_vt_as_xml_str() 0 29 6
B OSPDopenvas.feed_is_outdated() 0 43 7
B OSPDopenvas.get_detection_vt_as_xml_str() 0 33 5
A OSPDopenvas.is_running_as_root() 0 11 3
C OSPDopenvas.stop_scan_cleanup() 0 49 9
C OSPDopenvas.is_openvas_process_alive() 0 31 10
F OSPDopenvas.exec_scan() 0 158 20
F OSPDopenvas.report_openvas_results() 0 151 31

2 Functions

Rating   Name   Duplication   Size   Complexity  
A main() 0 3 1
A safe_int() 0 8 2

How to fix   Complexity   

Complexity

Complex classes like ospd_openvas.daemon often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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