Passed
Pull Request — master (#177)
by Juan José
01:29
created

OSPDopenvas.get_openvas_result()   F

Complexity

Conditions 14

Size

Total Lines 71
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 59
nop 3
dl 0
loc 71
rs 3.6
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

Complexity

Complex classes like ospd_openvas.daemon.OSPDopenvas.get_openvas_result() 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) 2019 Greenbone Networks GmbH
3
#
4
# SPDX-License-Identifier: GPL-2.0-or-later
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the 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 General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
20
# pylint: disable=too-many-lines
21
22
""" Setup for the OSP OpenVAS Server. """
23
24
import logging
25
import subprocess
26
import time
27
import uuid
28
import binascii
29
import copy
30
31
from typing import Optional, Dict, List, Tuple
32
from datetime import datetime
33
from base64 import b64decode
34
35
from pathlib import Path
36
from os import geteuid
37
from lxml.etree import tostring, SubElement, Element
38
39
import psutil
40
41
from ospd.errors import OspdError
42
from ospd.ospd import OSPDaemon
43
from ospd.main import main as daemon_main
44
from ospd.cvss import CVSS
45
from ospd.vtfilter import VtsFilter
46
47
from ospd_openvas import __version__
48
from ospd_openvas.errors import OspdOpenvasError
49
50
from ospd_openvas.nvticache import NVTICache
51
from ospd_openvas.db import OpenvasDB
52
53
logger = logging.getLogger(__name__)
54
55
56
OSPD_DESC = """
57
This scanner runs OpenVAS to scan the target hosts.
58
59
OpenVAS (Open Vulnerability Assessment Scanner) is a powerful scanner
60
for vulnerabilities in IT infrastrucutres. The capabilities include
61
unauthenticated scanning as well as authenticated scanning for
62
various types of systems and services.
63
64
For more details about OpenVAS see:
65
http://www.openvas.org/
66
67
The current version of ospd-openvas is a simple frame, which sends
68
the server parameters to the Greenbone Vulnerability Manager daemon (GVMd) and
69
checks the existence of OpenVAS binary. But it can not run scans yet.
70
"""
71
72
OSPD_PARAMS = {
73
    'auto_enable_dependencies': {
74
        'type': 'boolean',
75
        'name': 'auto_enable_dependencies',
76
        'default': 1,
77
        'mandatory': 1,
78
        'description': 'Automatically enable the plugins that are depended on',
79
    },
80
    'cgi_path': {
81
        'type': 'string',
82
        'name': 'cgi_path',
83
        'default': '/cgi-bin:/scripts',
84
        'mandatory': 1,
85
        'description': 'Look for default CGIs in /cgi-bin and /scripts',
86
    },
87
    'checks_read_timeout': {
88
        'type': 'integer',
89
        'name': 'checks_read_timeout',
90
        'default': 5,
91
        'mandatory': 1,
92
        'description': (
93
            'Number  of seconds that the security checks will '
94
            + 'wait for when doing a recv()'
95
        ),
96
    },
97
    'drop_privileges': {
98
        'type': 'boolean',
99
        'name': 'drop_privileges',
100
        'default': 0,
101
        'mandatory': 1,
102
        'description': '',
103
    },
104
    'network_scan': {
105
        'type': 'boolean',
106
        'name': 'network_scan',
107
        'default': 0,
108
        'mandatory': 1,
109
        'description': '',
110
    },
111
    'non_simult_ports': {
112
        'type': 'string',
113
        'name': 'non_simult_ports',
114
        'default': '139, 445, 3389, Services/irc',
115
        'mandatory': 1,
116
        'description': (
117
            'Prevent to make two connections on the same given '
118
            + 'ports at the same time.'
119
        ),
120
    },
121
    'open_sock_max_attempts': {
122
        'type': 'integer',
123
        'name': 'open_sock_max_attempts',
124
        'default': 5,
125
        'mandatory': 0,
126
        'description': (
127
            'Number of unsuccessful retries to open the socket '
128
            + 'before to set the port as closed.'
129
        ),
130
    },
131
    'timeout_retry': {
132
        'type': 'integer',
133
        'name': 'timeout_retry',
134
        'default': 5,
135
        'mandatory': 0,
136
        'description': (
137
            'Number of retries when a socket connection attempt ' + 'timesout.'
138
        ),
139
    },
140
    'optimize_test': {
141
        'type': 'integer',
142
        'name': 'optimize_test',
143
        'default': 5,
144
        'mandatory': 0,
145
        'description': (
146
            'By default, openvas does not trust the remote ' + 'host banners.'
147
        ),
148
    },
149
    'plugins_timeout': {
150
        'type': 'integer',
151
        'name': 'plugins_timeout',
152
        'default': 5,
153
        'mandatory': 0,
154
        'description': 'This is the maximum lifetime, in seconds of a plugin.',
155
    },
156
    'report_host_details': {
157
        'type': 'boolean',
158
        'name': 'report_host_details',
159
        'default': 1,
160
        'mandatory': 1,
161
        'description': '',
162
    },
163
    'safe_checks': {
164
        'type': 'boolean',
165
        'name': 'safe_checks',
166
        'default': 1,
167
        'mandatory': 1,
168
        'description': (
169
            'Disable the plugins with potential to crash '
170
            + 'the remote services'
171
        ),
172
    },
173
    'scanner_plugins_timeout': {
174
        'type': 'integer',
175
        'name': 'scanner_plugins_timeout',
176
        'default': 36000,
177
        'mandatory': 1,
178
        'description': 'Like plugins_timeout, but for ACT_SCANNER plugins.',
179
    },
180
    'time_between_request': {
181
        'type': 'integer',
182
        'name': 'time_between_request',
183
        'default': 0,
184
        'mandatory': 0,
185
        'description': (
186
            'Allow to set a wait time between two actions '
187
            + '(open, send, close).'
188
        ),
189
    },
190
    'unscanned_closed': {
191
        'type': 'boolean',
192
        'name': 'unscanned_closed',
193
        'default': 1,
194
        'mandatory': 1,
195
        'description': '',
196
    },
197
    'unscanned_closed_udp': {
198
        'type': 'boolean',
199
        'name': 'unscanned_closed_udp',
200
        'default': 1,
201
        'mandatory': 1,
202
        'description': '',
203
    },
204
    'use_mac_addr': {
205
        'type': 'boolean',
206
        'name': 'use_mac_addr',
207
        'default': 0,
208
        'mandatory': 0,
209
        'description': 'To test the local network. '
210
        + 'Hosts will be referred to by their MAC address.',
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 15 spaces).
Loading history...
211
    },
212
    'vhosts': {
213
        'type': 'string',
214
        'name': 'vhosts',
215
        'default': '',
216
        'mandatory': 0,
217
        'description': '',
218
    },
219
    'vhosts_ip': {
220
        'type': 'string',
221
        'name': 'vhosts_ip',
222
        'default': '',
223
        'mandatory': 0,
224
        'description': '',
225
    },
226
}
227
228
OID_SSH_AUTH = "1.3.6.1.4.1.25623.1.0.103591"
229
OID_SMB_AUTH = "1.3.6.1.4.1.25623.1.0.90023"
230
OID_ESXI_AUTH = "1.3.6.1.4.1.25623.1.0.105058"
231
OID_SNMP_AUTH = "1.3.6.1.4.1.25623.1.0.105076"
232
233
234
def _from_bool_to_str(value: int) -> str:
235
    """ The OpenVAS scanner use yes and no as boolean values, whereas ospd
236
    uses 1 and 0."""
237
    return 'yes' if value == 1 else 'no'
238
239
240
class OpenVasVtsFilter(VtsFilter):
241
    """ Methods to overwrite the ones in the original class.
242
    Each method formats the value to be compatible with the filter
243
    """
244
245
    def format_vt_modification_time(self, value: str) -> str:
246
        """ Convert the string seconds since epoch into a 19 character
247
        string representing YearMonthDayHourMinuteSecond,
248
        e.g. 20190319122532. This always refers to UTC.
249
        """
250
251
        return datetime.utcfromtimestamp(int(value)).strftime("%Y%m%d%H%M%S")
252
253
254
class OSPDopenvas(OSPDaemon):
255
256
    """ Class for ospd-openvas daemon. """
257
258
    def __init__(self, *, niceness=None, **kwargs):
259
        """ Initializes the ospd-openvas daemon's internal data. """
260
261
        super().__init__(customvtfilter=OpenVasVtsFilter(), **kwargs)
262
263
        self.server_version = __version__
264
265
        self._niceness = str(niceness)
266
267
        self.scanner_info['name'] = 'openvas'
268
        self.scanner_info['version'] = ''  # achieved during self.check()
269
        self.scanner_info['description'] = OSPD_DESC
270
271
        for name, param in OSPD_PARAMS.items():
272
            self.add_scanner_param(name, param)
273
274
        self._sudo_available = None
275
        self._is_running_as_root = None
276
277
        self.scan_only_params = dict()
278
279
        self.main_kbindex = None
280
281
        self.openvas_db = OpenvasDB()
282
283
        self.nvti = NVTICache(self.openvas_db)
284
285
        self.pending_feed = None
286
287
        self.temp_vts_dict = None
288
289
    def init(self):
290
        self.openvas_db.db_init()
291
292
        ctx = self.nvti.get_redis_context()
293
294
        if not ctx:
295
            self.redis_nvticache_init()
296
            ctx = self.nvti.get_redis_context()
297
298
        self.openvas_db.set_redisctx(ctx)
299
300
        self.load_vts()
301
302
    def parse_param(self):
303
        """ Set OSPD_PARAMS with the params taken from the openvas_scanner. """
304
        bool_dict = {'no': 0, 'yes': 1}
305
306
        result = subprocess.check_output(
307
            ['openvas', '-s'], stderr=subprocess.STDOUT
308
        )
309
        result = result.decode('ascii')
310
        param_list = dict()
311
        for conf in result.split('\n'):
312
            elem = conf.split('=')
313
            if len(elem) == 2:
314
                value = str.strip(elem[1])
315
                if str.strip(elem[1]) in bool_dict:
316
                    value = bool_dict[value]
317
                param_list[str.strip(elem[0])] = value
318
        for elem in OSPD_PARAMS:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable OSPD_PARAMS does not seem to be defined.
Loading history...
319
            if elem in param_list:
320
                OSPD_PARAMS[elem]['default'] = param_list[elem]
321
        for elem in param_list:
322
            if elem not in OSPD_PARAMS:
323
                self.scan_only_params[elem] = param_list[elem]
324
325
    def redis_nvticache_init(self):
326
        """ Loads NVT's metadata into Redis DB. """
327
        try:
328
            logger.debug('Loading NVTs in Redis DB')
329
            subprocess.check_call(['openvas', '--update-vt-info'])
330
        except subprocess.CalledProcessError as err:
331
            logger.error('OpenVAS Scanner failed to load NVTs. %s', err)
332
333
    def feed_is_outdated(self, current_feed: str) -> Optional[bool]:
334
        """ Compare the current feed with the one in the disk.
335
336
        Return:
337
            False if there is no new feed.
338
            True if the feed version in disk is newer than the feed in
339
            redis cache.
340
            None if there is no feed
341
            the disk.
342
        """
343
        plugins_folder = self.scan_only_params.get('plugins_folder')
344
        if not plugins_folder:
345
            raise OspdOpenvasError("Error: Path to plugins folder not found.")
346
347
        feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc'
348
        if not feed_info_file.exists():
349
            self.parse_param()
350
            msg = 'Plugins feed file %s not found.' % feed_info_file
351
            logger.debug(msg)
352
            return None
353
354
        date = 0
355
        with open(str(feed_info_file)) as fcontent:
356
            for line in fcontent:
357
                if "PLUGIN_SET" in line:
358
                    date = line.split(' = ')[1]
359
                    date = date.replace(';', '')
360
                    date = date.replace('"', '')
361
        if int(current_feed) < int(date) or int(date) == 0:
362
            return True
363
        return False
364
365
    def check_feed(self):
366
        """ Check if there is a feed update. Wait until all the running
367
        scans finished. Set a flag to anounce there is a pending feed update,
368
        which avoid to start a new scan.
369
        """
370
        current_feed = self.nvti.get_feed_version()
371
        # Check if the feed is already accessible in the disk.
372
        if current_feed and self.feed_is_outdated(current_feed) is None:
373
            self.pending_feed = True
374
            return
375
376
        # Check if the nvticache in redis is outdated
377
        if not current_feed or self.feed_is_outdated(current_feed):
378
            self.redis_nvticache_init()
379
            ctx = self.nvti.get_redis_context()
380
            self.openvas_db.set_redisctx(ctx)
381
            self.pending_feed = True
382
383
        _running_scan = False
384
        for scan_id in self.scan_processes:
385
            if self.scan_processes[scan_id].is_alive():
386
                _running_scan = True
387
388
        # Check if the NVT dict is outdated
389
        if self.pending_feed:
390
            _pending_feed = True
391
        else:
392
            _pending_feed = (
393
                self.get_vts_version() != self.nvti.get_feed_version()
394
            )
395
396
        if _running_scan and _pending_feed:
397
            if not self.pending_feed:
398
                self.pending_feed = True
399
                logger.debug(
400
                    'There is a running scan. Therefore the feed '
401
                    'update will be performed later.'
402
                )
403
        elif not _running_scan and _pending_feed:
404
            self.vts = None
405
            self.load_vts()
406
407
    def scheduler(self):
408
        """This method is called periodically to run tasks."""
409
        self.check_feed()
410
411
    def load_vts(self):
412
        """ Load the NVT's metadata into the vts
413
        global  dictionary. """
414
        logger.debug('Loading vts in memory.')
415
        oids = dict(self.nvti.get_oids())
416
        for _filename, vt_id in oids.items():
417
            _vt_params = self.nvti.get_nvt_params(vt_id)
418
            _vt_refs = self.nvti.get_nvt_refs(vt_id)
419
            _custom = self.nvti.get_nvt_metadata(vt_id)
420
421
            if _vt_params is None or _custom is None:
422
                logger.warning('Error loading VTs in memory. Trying again...')
423
                return
424
425
            _name = _custom.pop('name')
426
            _vt_creation_time = _custom.pop('creation_date')
427
            _vt_modification_time = _custom.pop('last_modification')
428
429
            _summary = None
430
            _impact = None
431
            _affected = None
432
            _insight = None
433
            _solution = None
434
            _solution_t = None
435
            _solution_m = None
436
            _vuldetect = None
437
            _qod_t = None
438
            _qod_v = None
439
440
            if 'summary' in _custom:
441
                _summary = _custom.pop('summary')
442
            if 'impact' in _custom:
443
                _impact = _custom.pop('impact')
444
            if 'affected' in _custom:
445
                _affected = _custom.pop('affected')
446
            if 'insight' in _custom:
447
                _insight = _custom.pop('insight')
448
            if 'solution' in _custom:
449
                _solution = _custom.pop('solution')
450
                if 'solution_type' in _custom:
451
                    _solution_t = _custom.pop('solution_type')
452
                if 'solution_method' in _custom:
453
                    _solution_m = _custom.pop('solution_method')
454
455
            if 'vuldetect' in _custom:
456
                _vuldetect = _custom.pop('vuldetect')
457
            if 'qod_type' in _custom:
458
                _qod_t = _custom.pop('qod_type')
459
            elif 'qod' in _custom:
460
                _qod_v = _custom.pop('qod')
461
462
            _severity = dict()
463
            if 'severity_base_vector' in _custom:
464
                _severity_vector = _custom.pop('severity_base_vector')
465
            else:
466
                _severity_vector = _custom.pop('cvss_base_vector')
467
            _severity['severity_base_vector'] = _severity_vector
468
            if 'severity_type' in _custom:
469
                _severity_type = _custom.pop('severity_type')
470
            else:
471
                _severity_type = 'cvss_base_v2'
472
            _severity['severity_type'] = _severity_type
473
            if 'severity_origin' in _custom:
474
                _severity['severity_origin'] = _custom.pop('severity_origin')
475
476
            _vt_dependencies = list()
477
            if 'dependencies' in _custom:
478
                _deps = _custom.pop('dependencies')
479
                _deps_list = _deps.split(', ')
480
                for dep in _deps_list:
481
                    _vt_dependencies.append(oids.get('filename:' + dep))
482
483
            try:
484
                self.add_vt(
485
                    vt_id,
486
                    name=_name,
487
                    vt_params=_vt_params,
488
                    vt_refs=_vt_refs,
489
                    custom=_custom,
490
                    vt_creation_time=_vt_creation_time,
491
                    vt_modification_time=_vt_modification_time,
492
                    vt_dependencies=_vt_dependencies,
493
                    summary=_summary,
494
                    impact=_impact,
495
                    affected=_affected,
496
                    insight=_insight,
497
                    solution=_solution,
498
                    solution_t=_solution_t,
499
                    solution_m=_solution_m,
500
                    detection=_vuldetect,
501
                    qod_t=_qod_t,
502
                    qod_v=_qod_v,
503
                    severities=_severity,
504
                )
505
            except OspdError as e:
506
                logger.info("Error while adding vt. %s", e)
507
508
        _feed_version = self.nvti.get_feed_version()
509
        self.set_vts_version(vts_version=_feed_version)
510
        self.pending_feed = False
511
        logger.debug('Finish loading up vts.')
512
513
    @staticmethod
514
    def get_custom_vt_as_xml_str(vt_id: str, custom: Dict) -> str:
515
        """ Return an xml element with custom metadata formatted as string.
516
        Arguments:
517
            vt_id: VT OID. Only used for logging in error case.
518
            custom: Dictionary with the custom metadata.
519
        Return:
520
            Xml element as string.
521
        """
522
523
        _custom = Element('custom')
524
        for key, val in custom.items():
525
            xml_key = SubElement(_custom, key)
526
            try:
527
                xml_key.text = val
528
            except ValueError as e:
529
                logger.warning(
530
                    "Not possible to parse custom tag for vt %s: %s", vt_id, e
531
                )
532
        return tostring(_custom).decode('utf-8')
533
534
    @staticmethod
535
    def get_severities_vt_as_xml_str(vt_id: str, severities: Dict) -> str:
536
        """ Return an xml element with severities as string.
537
        Arguments:
538
            vt_id: VT OID. Only used for logging in error case.
539
            severities: Dictionary with the severities.
540
        Return:
541
            Xml element as string.
542
        """
543
        _severities = Element('severities')
544
        _severity = SubElement(_severities, 'severity')
545
        if 'severity_base_vector' in severities:
546
            try:
547
                _severity.text = severities.get('severity_base_vector')
548
            except ValueError as e:
549
                logger.warning(
550
                    "Not possible to parse severity tag for vt %s: %s", vt_id, e
551
                )
552
        if 'severity_origin' in severities:
553
            _severity.set('origin', severities.get('severity_origin'))
554
        if 'severity_type' in severities:
555
            _severity.set('type', severities.get('severity_type'))
556
557
        return tostring(_severities).decode('utf-8')
558
559
    @staticmethod
560
    def get_params_vt_as_xml_str(vt_id: str, vt_params: Dict) -> str:
561
        """ Return an xml element with params formatted as string.
562
        Arguments:
563
            vt_id: VT OID. Only used for logging in error case.
564
            vt_params: Dictionary with the VT parameters.
565
        Return:
566
            Xml element as string.
567
        """
568
        vt_params_xml = Element('params')
569
        for _pref_id, prefs in vt_params.items():
570
            vt_param = Element('param')
571
            vt_param.set('type', prefs['type'])
572
            vt_param.set('id', _pref_id)
573
            xml_name = SubElement(vt_param, 'name')
574
            try:
575
                xml_name.text = prefs['name']
576
            except ValueError as e:
577
                logger.warning(
578
                    "Not possible to parse parameter for vt %s: %s", vt_id, e
579
                )
580
            if prefs['default']:
581
                xml_def = SubElement(vt_param, 'default')
582
                try:
583
                    xml_def.text = prefs['default']
584
                except ValueError as e:
585
                    logger.warning(
586
                        "Not possible to parse default parameter for vt %s: %s",
587
                        vt_id,
588
                        e,
589
                    )
590
            vt_params_xml.append(vt_param)
591
592
        return tostring(vt_params_xml).decode('utf-8')
593
594
    @staticmethod
595
    def get_refs_vt_as_xml_str(vt_id: str, vt_refs: Dict) -> str:
596
        """ Return an xml element with references formatted as string.
597
        Arguments:
598
            vt_id: VT OID. Only used for logging in error case.
599
            vt_refs: Dictionary with the VT references.
600
        Return:
601
            Xml element as string.
602
        """
603
        vt_refs_xml = Element('refs')
604
        for ref_type, ref_values in vt_refs.items():
605
            for value in ref_values:
606
                vt_ref = Element('ref')
607
                if ref_type == "xref" and value:
608
                    for xref in value.split(', '):
609
                        try:
610
                            _type, _id = xref.split(':', 1)
611
                        except ValueError:
612
                            logger.error(
613
                                'Not possible to parse xref %s for vt %s',
614
                                xref,
615
                                vt_id,
616
                            )
617
                            continue
618
                        vt_ref.set('type', _type.lower())
619
                        vt_ref.set('id', _id)
620
                elif value:
621
                    vt_ref.set('type', ref_type.lower())
622
                    vt_ref.set('id', value)
623
                else:
624
                    continue
625
                vt_refs_xml.append(vt_ref)
626
627
        return tostring(vt_refs_xml).decode('utf-8')
628
629
    @staticmethod
630
    def get_dependencies_vt_as_xml_str(
631
        vt_id: str, vt_dependencies: List
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
632
    ) -> str:
633
        """ Return  an xml element with dependencies as string.
634
        Arguments:
635
            vt_id: VT OID. Only used for logging in error case.
636
            vt_dependencies: List with the VT dependencies.
637
        Return:
638
            Xml element as string.
639
        """
640
        vt_deps_xml = Element('dependencies')
641
        for dep in vt_dependencies:
642
            _vt_dep = Element('dependency')
643
            try:
644
                _vt_dep.set('vt_id', dep)
645
            except (ValueError, TypeError):
646
                logger.error(
647
                    'Not possible to add dependency %s for vt %s', dep, vt_id
648
                )
649
                continue
650
            vt_deps_xml.append(_vt_dep)
651
652
        return tostring(vt_deps_xml).decode('utf-8')
653
654
    @staticmethod
655
    def get_creation_time_vt_as_xml_str(
656
        vt_id: str, vt_creation_time: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
657
    ) -> str:
658
        """ Return creation time as string.
659
        Arguments:
660
            vt_id: VT OID. Only used for logging in error case.
661
            vt_creation_time: String with the VT creation time.
662
        Return:
663
           Xml element as string.
664
        """
665
        _time = Element('creation_time')
666
        try:
667
            _time.text = vt_creation_time
668
        except ValueError as e:
669
            logger.warning(
670
                "Not possible to parse creation time for vt %s: %s", vt_id, e
671
            )
672
        return tostring(_time).decode('utf-8')
673
674
    @staticmethod
675
    def get_modification_time_vt_as_xml_str(
676
        vt_id: str, vt_modification_time: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
677
    ) -> str:
678
        """ Return modification time as string.
679
        Arguments:
680
            vt_id: VT OID. Only used for logging in error case.
681
            vt_modification_time: String with the VT modification time.
682
        Return:
683
            Xml element as string.
684
        """
685
        _time = Element('modification_time')
686
        try:
687
            _time.text = vt_modification_time
688
        except ValueError as e:
689
            logger.warning(
690
                "Not possible to parse modification time for vt %s: %s",
691
                vt_id,
692
                e,
693
            )
694
        return tostring(_time).decode('utf-8')
695
696
    @staticmethod
697
    def get_summary_vt_as_xml_str(vt_id: str, summary: str) -> str:
698
        """ Return summary as string.
699
        Arguments:
700
            vt_id: VT OID. Only used for logging in error case.
701
            summary: String with a VT summary.
702
        Return:
703
            Xml element as string.
704
        """
705
        _summary = Element('summary')
706
        try:
707
            _summary.text = summary
708
        except ValueError as e:
709
            logger.warning(
710
                "Not possible to parse summary tag for vt %s: %s", vt_id, e
711
            )
712
        return tostring(_summary).decode('utf-8')
713
714
    @staticmethod
715
    def get_impact_vt_as_xml_str(vt_id: str, impact) -> str:
716
        """ Return impact as string.
717
718
        Arguments:
719
            vt_id (str): VT OID. Only used for logging in error case.
720
            impact (str): String which explain the vulneravility impact.
721
        Return:
722
            string: xml element as string.
723
        """
724
        _impact = Element('impact')
725
        try:
726
            _impact.text = impact
727
        except ValueError as e:
728
            logger.warning(
729
                "Not possible to parse impact tag for vt %s: %s", vt_id, e
730
            )
731
        return tostring(_impact).decode('utf-8')
732
733
    @staticmethod
734
    def get_affected_vt_as_xml_str(vt_id: str, affected: str) -> str:
735
        """ Return affected as string.
736
        Arguments:
737
            vt_id: VT OID. Only used for logging in error case.
738
            affected: String which explain what is affected.
739
        Return:
740
            Xml element as string.
741
        """
742
        _affected = Element('affected')
743
        try:
744
            _affected.text = affected
745
        except ValueError as e:
746
            logger.warning(
747
                "Not possible to parse affected tag for vt %s: %s", vt_id, e
748
            )
749
        return tostring(_affected).decode('utf-8')
750
751
    @staticmethod
752
    def get_insight_vt_as_xml_str(vt_id: str, insight: str) -> str:
753
        """ Return insight as string.
754
        Arguments:
755
            vt_id: VT OID. Only used for logging in error case.
756
            insight: String giving an insight of the vulnerability.
757
        Return:
758
            Xml element as string.
759
        """
760
        _insight = Element('insight')
761
        try:
762
            _insight.text = insight
763
        except ValueError as e:
764
            logger.warning(
765
                "Not possible to parse insight tag for vt %s: %s", vt_id, e
766
            )
767
        return tostring(_insight).decode('utf-8')
768
769
    @staticmethod
770
    def get_solution_vt_as_xml_str(
771
        vt_id: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
772
        solution: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
773
        solution_type: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
774
        solution_method: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
775
    ) -> str:
776
        """ Return solution as string.
777
        Arguments:
778
            vt_id: VT OID. Only used for logging in error case.
779
            solution: String giving a possible solution.
780
            solution_type: A solution type
781
            solution_method: A solution method
782
        Return:
783
            Xml element as string.
784
        """
785
        _solution = Element('solution')
786
        try:
787
            _solution.text = solution
788
        except ValueError as e:
789
            logger.warning(
790
                "Not possible to parse solution tag for vt %s: %s", vt_id, e
791
            )
792
        if solution_type:
793
            _solution.set('type', solution_type)
794
        if solution_method:
795
            _solution.set('method', solution_method)
796
        return tostring(_solution).decode('utf-8')
797
798
    @staticmethod
799
    def get_detection_vt_as_xml_str(
800
        vt_id: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
801
        detection: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
802
        qod_type: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
803
        qod: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
804
    ) -> str:
805
        """ Return detection as string.
806
        Arguments:
807
            vt_id: VT OID. Only used for logging in error case.
808
            detection: String which explain how the vulnerability
809
              was detected.
810
            qod_type: qod type.
811
            qod: qod value.
812
        Return:
813
            Xml element as string.
814
        """
815
        _detection = Element('detection')
816
        if detection:
817
            try:
818
                _detection.text = detection
819
            except ValueError as e:
820
                logger.warning(
821
                    "Not possible to parse detection tag for vt %s: %s",
822
                    vt_id,
823
                    e,
824
                )
825
        if qod_type:
826
            _detection.set('qod_type', qod_type)
827
        elif qod:
828
            _detection.set('qod', qod)
829
830
        return tostring(_detection).decode('utf-8')
831
832
    @property
833
    def is_running_as_root(self) -> bool:
834
        """ Check if it is running as root user."""
835
        if self._is_running_as_root is not None:
836
            return self._is_running_as_root
837
838
        self._is_running_as_root = False
839
        if geteuid() == 0:
840
            self._is_running_as_root = True
841
842
        return self._is_running_as_root
843
844
    @property
845
    def sudo_available(self) -> bool:
846
        """ Checks that sudo is available """
847
        if self._sudo_available is not None:
848
            return self._sudo_available
849
850
        if self.is_running_as_root:
851
            self._sudo_available = False
852
            return self._sudo_available
853
854
        try:
855
            subprocess.check_call(
856
                ['sudo', '-n', 'openvas', '-s'], stdout=subprocess.PIPE
857
            )
858
            self._sudo_available = True
859
        except (subprocess.SubprocessError, OSError) as e:
860
            logger.debug(
861
                'It was not possible to call openvas with sudo. '
862
                'The scanner will run as non-root user. Reason %s',
863
                e,
864
            )
865
            self._sudo_available = False
866
867
        return self._sudo_available
868
869
    def check(self) -> bool:
870
        """ Checks that openvas command line tool is found and
871
        is executable. """
872
        try:
873
            result = subprocess.check_output(
874
                ['openvas', '-V'], stderr=subprocess.STDOUT
875
            )
876
            result = result.decode('ascii')
877
        except OSError:
878
            # The command is not available
879
            return False
880
881
        if result is None:
882
            return False
883
884
        version = result.split('\n')
885
        if version[0].find('OpenVAS') < 0:
886
            return False
887
888
        self.parse_param()
889
        self.scanner_info['version'] = version[0]
890
891
        return True
892
893
    def update_progress(
894
        self, scan_id: str, target: str, current_host: str, msg: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
895
    ):
896
        """ Calculate percentage and update the scan status of a host
897
        for the progress bar.
898
        Arguments:
899
            scan_id: Scan ID to identify the current scan process.
900
            target: Target to be updated with the calculated
901
                          scan progress.
902
            current_host: Host in the target to be updated.
903
            msg: String with launched and total plugins.
904
        """
905
        try:
906
            launched, total = msg.split('/')
907
        except ValueError:
908
            return
909
        if float(total) == 0:
910
            return
911
        elif float(total) == -1:
912
            host_prog = 100
913
        else:
914
            host_prog = (float(launched) / float(total)) * 100
915
        self.set_scan_host_progress(scan_id, target, current_host, host_prog)
916
917
    def get_openvas_status(self, scan_id: str, target: str, current_host: str):
918
        """ Get all status entries from redis kb.
919
        Arguments:
920
            scan_id: Scan ID to identify the current scan.
921
            target: Target progress to be updated.
922
            current_host: Host to be updated.
923
        """
924
        res = self.openvas_db.get_status()
925
        while res:
926
            self.update_progress(scan_id, target, current_host, res)
927
            res = self.openvas_db.get_status()
928
929
    def get_severity_score(self, vt_aux: dict) -> Optional[float]:
930
        """ Return the severity score for the given oid.
931
        Arguments:
932
            vt_aux: VT element from which to get the severity vector
933
        Returns:
934
            The calculated cvss base value. None if there is no severity
935
            vector or severity type is not cvss base version 2.
936
        """
937
        if vt_aux:
938
            severity_type = vt_aux['severities'].get('severity_type')
939
            severity_vector = vt_aux['severities'].get('severity_base_vector')
940
941
            if severity_type == "cvss_base_v2" and severity_vector:
942
                return CVSS.cvss_base_v2_value(severity_vector)
943
944
        return None
945
946
    def get_openvas_result(self, scan_id: str, current_host: str):
947
        """ Get all result entries from redis kb. """
948
        res = self.openvas_db.get_result()
949
        while res:
950
            msg = res.split('|||')
951
            roid = msg[3].strip()
952
            rqod = ''
953
            rname = ''
954
            rhostname = msg[1].strip() if msg[1] else ''
955
            host_is_dead = "Host dead" in msg[4]
956
            vt_aux = None
957
958
            if roid and not host_is_dead:
959
                vt_aux = copy.deepcopy(self.vts.get(roid))
960
                if vt_aux and vt_aux.get('qod_type'):
961
                    qod_t = vt_aux.get('qod_type')
962
                    rqod = self.nvti.QOD_TYPES[qod_t]
963
                elif vt_aux and vt_aux.get('qod'):
964
                    rqod = vt_aux.get('qod')
965
966
                if vt_aux:
967
                    rname = vt_aux.get('name')
968
969
            if msg[0] == 'ERRMSG':
970
                self.add_scan_error(
971
                    scan_id,
972
                    host=current_host,
973
                    hostname=rhostname,
974
                    name=rname,
975
                    value=msg[4],
976
                    port=msg[2],
977
                )
978
979
            if msg[0] == 'LOG':
980
                self.add_scan_log(
981
                    scan_id,
982
                    host=current_host,
983
                    hostname=rhostname,
984
                    name=rname,
985
                    value=msg[4],
986
                    port=msg[2],
987
                    qod=rqod,
988
                    test_id=roid,
989
                )
990
991
            if msg[0] == 'HOST_DETAIL':
992
                self.add_scan_host_detail(
993
                    scan_id,
994
                    host=current_host,
995
                    hostname=rhostname,
996
                    name=rname,
997
                    value=msg[4],
998
                )
999
1000
            if msg[0] == 'ALARM':
1001
                rseverity = self.get_severity_score(vt_aux)
1002
                self.add_scan_alarm(
1003
                    scan_id,
1004
                    host=current_host,
1005
                    hostname=rhostname,
1006
                    name=rname,
1007
                    value=msg[4],
1008
                    port=msg[2],
1009
                    test_id=roid,
1010
                    severity=rseverity,
1011
                    qod=rqod,
1012
                )
1013
1014
            vt_aux = None
1015
            del vt_aux
1016
            res = self.openvas_db.get_result()
1017
1018
    def get_openvas_timestamp_scan_host(self, scan_id: str, target: str):
1019
        """ Get start and end timestamp of a host scan from redis kb. """
1020
        timestamp = self.openvas_db.get_host_scan_scan_end_time()
1021
        if timestamp:
1022
            self.add_scan_log(
1023
                scan_id, host=target, name='HOST_END', value=timestamp
1024
            )
1025
            return
1026
        timestamp = self.openvas_db.get_host_scan_scan_start_time()
1027
        if timestamp:
1028
            self.add_scan_log(
1029
                scan_id, host=target, name='HOST_START', value=timestamp
1030
            )
1031
            return
1032
1033
    def host_is_finished(self, scan_id: str) -> bool:
1034
        """ Check if the host has finished. """
1035
        status = self.openvas_db.get_single_item('internal/%s' % scan_id)
1036
        return status == 'finished'
1037
1038
    def target_is_finished(self, scan_id: str) -> bool:
1039
        """ Check if a target has finished. The scan id to be used is
1040
        the scan id passed to the openvas, is not the global scan id."""
1041
        ctx = self.openvas_db.kb_connect(dbnum=self.main_kbindex)
1042
        scan_id = self.openvas_db.get_single_item(
1043
            'internal/%s/globalscanid' % scan_id, ctx=ctx
1044
        )
1045
        status = self.openvas_db.get_single_item(
1046
            'internal/%s' % scan_id, ctx=ctx
1047
        )
1048
1049
        return status == 'finished' or status is None
1050
1051
    def scan_is_stopped(self, scan_id: str) -> bool:
1052
        """ Check if the parent process has received the stop_scan order.
1053
        @in scan_id: ID to identify the scan to be stopped.
1054
        @return 1 if yes, None in other case.
1055
        """
1056
        ctx = self.openvas_db.kb_connect(dbnum=self.main_kbindex)
1057
        self.openvas_db.set_redisctx(ctx)
1058
        status = self.openvas_db.get_single_item('internal/%s' % scan_id)
1059
        return status == 'stop_all'
1060
1061
    def stop_scan_cleanup(
1062
        self, global_scan_id: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1063
    ):  # pylint: disable=arguments-differ
1064
        """ Set a key in redis to indicate the wrapper is stopped.
1065
        It is done through redis because it is a new multiprocess
1066
        instance and it is not possible to reach the variables
1067
        of the grandchild process. Send SIGUSR2 to openvas to stop
1068
        each running scan."""
1069
        ctx = self.openvas_db.kb_connect()
1070
        for current_kbi in range(0, self.openvas_db.max_dbindex):
1071
            self.openvas_db.select_kb(ctx, str(current_kbi), set_global=True)
1072
            scan_id = self.openvas_db.get_single_item(
1073
                'internal/%s/globalscanid' % global_scan_id
1074
            )
1075
            if scan_id:
1076
                self.openvas_db.set_single_item(
1077
                    'internal/%s' % scan_id, ['stop_all']
1078
                )
1079
                ovas_pid = self.openvas_db.get_single_item('internal/ovas_pid')
1080
                parent = None
1081
                try:
1082
                    parent = psutil.Process(int(ovas_pid))
1083
                except psutil.NoSuchProcess:
1084
                    logger.debug(
1085
                        'Process with pid %s already stopped', ovas_pid
1086
                    )
1087
                except TypeError:
1088
                    logger.debug(
1089
                        'Scan with ID %s never started and stopped '
1090
                        'unexpectedly',
1091
                        scan_id,
1092
                    )
1093
1094
                if parent:
1095
                    cmd = ['openvas', '--scan-stop', scan_id]
1096
                    if not self.is_running_as_root and self.sudo_available:
1097
                        cmd = ['sudo', '-n'] + cmd
1098
1099
                    try:
1100
                        subprocess.Popen(cmd, shell=False)
1101
                    except OSError as e:
1102
                        # the command is not available
1103
                        logger.debug(
1104
                            'Not possible to Stopping process: %s.' 'Reason %s',
1105
                            parent,
1106
                            e,
1107
                        )
1108
                        return False
1109
1110
                    logger.debug('Stopping process: %s', parent)
1111
                    while parent:
1112
                        try:
1113
                            parent = psutil.Process(int(ovas_pid))
1114
                        except psutil.NoSuchProcess:
1115
                            parent = None
1116
1117
                self.openvas_db.release_db(current_kbi)
1118
                for host_kb in range(0, self.openvas_db.max_dbindex):
1119
                    self.openvas_db.select_kb(
1120
                        ctx, str(host_kb), set_global=True
1121
                    )
1122
                    if self.openvas_db.get_single_item('internal/%s' % scan_id):
1123
                        self.openvas_db.release_db(host_kb)
1124
1125
    def get_vts_in_groups(self, filters: List) -> List:
1126
        """ Return a list of vts which match with the given filter.
1127
1128
        @input filters A list of filters. Each filter has key, operator and
1129
                       a value. They are separated by a space.
1130
                       Supported keys: family
1131
        @return Return a list of vts which match with the given filter.
1132
        """
1133
        vts_list = list()
1134
        families = dict()
1135
1136
        # Because DictProxy for python3.5 doesn't support iterkeys(),
1137
        # itervalues(), or iteritems() either, the iteration must be
1138
        # done as follow with iter().
1139
        for oid in iter(self.temp_vts_dict.keys()):
1140
            family = self.temp_vts_dict[oid]['custom'].get('family')
1141
            if family not in families:
1142
                families[family] = list()
1143
            families[family].append(oid)
1144
1145
        for elem in filters:
1146
            key, value = elem.split('=')
1147
            if key == 'family' and value in families:
1148
                vts_list.extend(families[value])
1149
        return vts_list
1150
1151
    def get_vt_param_type(self, vtid: str, vt_param_id: str) -> Optional[str]:
1152
        """ Return the type of the vt parameter from the vts dictionary. """
1153
1154
        vt_params_list = self.temp_vts_dict[vtid].get("vt_params")
1155
        if vt_params_list.get(vt_param_id):
1156
            return vt_params_list[vt_param_id]["type"]
1157
        return None
1158
1159
    def get_vt_param_name(self, vtid: str, vt_param_id: str) -> Optional[str]:
1160
        """ Return the type of the vt parameter from the vts dictionary. """
1161
1162
        vt_params_list = self.temp_vts_dict[vtid].get("vt_params")
1163
        if vt_params_list.get(vt_param_id):
1164
            return vt_params_list[vt_param_id]["name"]
1165
        return None
1166
1167
    @staticmethod
1168
    def check_param_type(vt_param_value: str, param_type: str) -> Optional[int]:
1169
        """ Check if the value of a vt parameter matches with
1170
        the type founded.
1171
        """
1172
        if param_type in [
1173
            'entry',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1174
            'password',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1175
            'radio',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1176
            'sshlogin',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1177
        ] and isinstance(vt_param_value, str):
1178
            return None
1179
        elif param_type == 'checkbox' and (
1180
            vt_param_value == '0' or vt_param_value == '1'
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1181
        ):
1182
            return None
1183
        elif param_type == 'file':
1184
            try:
1185
                b64decode(vt_param_value.encode())
1186
            except (binascii.Error, AttributeError, TypeError):
1187
                return 1
1188
            return None
1189
        elif param_type == 'integer':
1190
            try:
1191
                int(vt_param_value)
1192
            except ValueError:
1193
                return 1
1194
            return None
1195
1196
        return 1
1197
1198
    def process_vts(self, vts: List) -> Tuple[list, list]:
1199
        """ Add single VTs and their parameters. """
1200
        vts_list = []
1201
        vts_params = []
1202
        vtgroups = vts.pop('vt_groups')
1203
1204
        if vtgroups:
1205
            vts_list = self.get_vts_in_groups(vtgroups)
1206
1207
        for vtid, vt_params in vts.items():
1208
            if vtid not in self.temp_vts_dict.keys():
1209
                logger.warning(
1210
                    'The vt %s was not found and it will not be loaded.', vtid
1211
                )
1212
                continue
1213
            vts_list.append(vtid)
1214
            for vt_param_id, vt_param_value in vt_params.items():
1215
                param_type = self.get_vt_param_type(vtid, vt_param_id)
1216
                param_name = self.get_vt_param_name(vtid, vt_param_id)
1217
                if not param_type or not param_name:
1218
                    logger.debug(
1219
                        'Missing type or name for vt parameter %s of %s. '
1220
                        'It could not be loaded.',
1221
                        vt_param_id,
1222
                        vtid,
1223
                    )
1224
                    continue
1225
                if vt_param_id == '0':
1226
                    type_aux = 'integer'
1227
                else:
1228
                    type_aux = param_type
1229
                if self.check_param_type(vt_param_value, type_aux):
1230
                    logger.debug(
1231
                        'The vt parameter %s for %s could not be loaded. '
1232
                        'Expected %s type for parameter value %s',
1233
                        vt_param_id,
1234
                        vtid,
1235
                        type_aux,
1236
                        str(vt_param_value),
1237
                    )
1238
                    continue
1239
                if type_aux == 'checkbox':
1240
                    vt_param_value = _from_bool_to_str(int(vt_param_value))
1241
                param = [
1242
                    "{0}:{1}:{2}:{3}".format(
1243
                        vtid, vt_param_id, param_type, param_name
1244
                    ),
1245
                    str(vt_param_value),
1246
                ]
1247
                vts_params.append(param)
1248
        return vts_list, vts_params
1249
1250
    @staticmethod
1251
    def build_credentials_as_prefs(credentials: Dict) -> List[str]:
1252
        """ Parse the credential dictionary.
1253
        @param credentials: Dictionary with the credentials.
1254
1255
        @return A list with the credentials in string format to be
1256
                added to the redis KB.
1257
        """
1258
        cred_prefs_list = []
1259
        for credential in credentials.items():
1260
            service = credential[0]
1261
            cred_params = credentials.get(service)
1262
            cred_type = cred_params.get('type', '')
1263
            username = cred_params.get('username', '')
1264
            password = cred_params.get('password', '')
1265
1266
            if service == 'ssh':
1267
                port = cred_params.get('port', '')
1268
                cred_prefs_list.append('auth_port_ssh|||' + '{0}'.format(port))
1269
                cred_prefs_list.append(
1270
                    OID_SSH_AUTH
1271
                    + ':1:'
1272
                    + 'entry:SSH login '
1273
                    + 'name:|||{0}'.format(username)
1274
                )
1275
                if cred_type == 'up':
1276
                    cred_prefs_list.append(
1277
                        OID_SSH_AUTH
1278
                        + ':3:'
1279
                        + 'password:SSH password '
1280
                        + '(unsafe!):|||{0}'.format(password)
1281
                    )
1282
                else:
1283
                    private = cred_params.get('private', '')
1284
                    cred_prefs_list.append(
1285
                        OID_SSH_AUTH
1286
                        + ':2:'
1287
                        + 'password:SSH key passphrase:|||'
1288
                        + '{0}'.format(password)
1289
                    )
1290
                    cred_prefs_list.append(
1291
                        OID_SSH_AUTH
1292
                        + ':4:'
1293
                        + 'file:SSH private key:|||'
1294
                        + '{0}'.format(private)
1295
                    )
1296
            if service == 'smb':
1297
                cred_prefs_list.append(
1298
                    OID_SMB_AUTH
1299
                    + ':1:entry'
1300
                    + ':SMB login:|||{0}'.format(username)
1301
                )
1302
                cred_prefs_list.append(
1303
                    OID_SMB_AUTH
1304
                    + ':2:'
1305
                    + 'password:SMB password:|||'
1306
                    + '{0}'.format(password)
1307
                )
1308
            if service == 'esxi':
1309
                cred_prefs_list.append(
1310
                    OID_ESXI_AUTH
1311
                    + ':1:entry:'
1312
                    + 'ESXi login name:|||'
1313
                    + '{0}'.format(username)
1314
                )
1315
                cred_prefs_list.append(
1316
                    OID_ESXI_AUTH
1317
                    + ':2:'
1318
                    + 'password:ESXi login password:|||'
1319
                    + '{0}'.format(password)
1320
                )
1321
1322
            if service == 'snmp':
1323
                community = cred_params.get('community', '')
1324
                auth_algorithm = cred_params.get('auth_algorithm', '')
1325
                privacy_password = cred_params.get('privacy_password', '')
1326
                privacy_algorithm = cred_params.get('privacy_algorithm', '')
1327
1328
                cred_prefs_list.append(
1329
                    OID_SNMP_AUTH
1330
                    + ':1:'
1331
                    + 'password:SNMP Community:'
1332
                    + '{0}'.format(community)
1333
                )
1334
                cred_prefs_list.append(
1335
                    OID_SNMP_AUTH
1336
                    + ':2:'
1337
                    + 'entry:SNMPv3 Username:'
1338
                    + '{0}'.format(username)
1339
                )
1340
                cred_prefs_list.append(
1341
                    OID_SNMP_AUTH + ':3:'
1342
                    'password:SNMPv3 Password:' + '{0}'.format(password)
1343
                )
1344
                cred_prefs_list.append(
1345
                    OID_SNMP_AUTH
1346
                    + ':4:'
1347
                    + 'radio:SNMPv3 Authentication '
1348
                    + 'Algorithm:{0}'.format(auth_algorithm)
1349
                )
1350
                cred_prefs_list.append(
1351
                    OID_SNMP_AUTH
1352
                    + ':5:'
1353
                    + 'password:SNMPv3 Privacy Password:'
1354
                    + '{0}'.format(privacy_password)
1355
                )
1356
                cred_prefs_list.append(
1357
                    OID_SNMP_AUTH
1358
                    + ':6:'
1359
                    + 'radio:SNMPv3 Privacy Algorithm:'
1360
                    + '{0}'.format(privacy_algorithm)
1361
                )
1362
1363
        return cred_prefs_list
1364
1365
    def exec_scan(self, scan_id: str, target: str):
1366
        """ Starts the OpenVAS scanner for scan_id scan. """
1367
        if self.pending_feed:
1368
            logger.info(
1369
                '%s: There is a pending feed update. '
1370
                'The scan can not be started.',
1371
                scan_id,
1372
            )
1373
            self.add_scan_error(
1374
                scan_id,
1375
                name='',
1376
                host=target,
1377
                value=(
1378
                    'It was not possible to start the scan,'
1379
                    'because a pending feed update. Please try later'
1380
                ),
1381
            )
1382
            return 2
1383
1384
        ports = self.get_scan_ports(scan_id, target)
1385
        if not ports:
1386
            self.add_scan_error(
1387
                scan_id, name='', host=target, value='No port list defined.'
1388
            )
1389
            return 2
1390
1391
        # Get scan options
1392
        options = self.get_scan_options(scan_id)
1393
        prefs_val = []
1394
        ctx = self.openvas_db.kb_new()
1395
        self.openvas_db.set_redisctx(ctx)
1396
        self.main_kbindex = self.openvas_db.db_index
1397
1398
        # To avoid interference between scan process during a parallel scanning
1399
        # new uuid is used internally for each scan.
1400
        openvas_scan_id = str(uuid.uuid4())
1401
        self.openvas_db.add_single_item(
1402
            'internal/%s' % openvas_scan_id, ['new']
1403
        )
1404
        self.openvas_db.add_single_item(
1405
            'internal/%s/globalscanid' % scan_id, [openvas_scan_id]
1406
        )
1407
        self.openvas_db.add_single_item('internal/scanid', [openvas_scan_id])
1408
1409
        exclude_hosts = self.get_scan_exclude_hosts(scan_id, target)
1410
        if exclude_hosts:
1411
            options['exclude_hosts'] = exclude_hosts
1412
1413
        # Get unfinished hosts, in case it is a resumed scan. And added
1414
        # into exclude_hosts scan preference. Set progress for the finished ones
1415
        # to 100%.
1416
        finished_hosts = self.get_scan_finished_hosts(scan_id)
1417
        if finished_hosts:
1418
            if exclude_hosts:
1419
                finished_hosts_str = ','.join(finished_hosts)
1420
                exclude_hosts = exclude_hosts + ',' + finished_hosts_str
1421
                options['exclude_hosts'] = exclude_hosts
1422
            else:
1423
                options['exclude_hosts'] = ','.join(finished_hosts)
1424
1425
        # Set scan preferences
1426
        for key, value in options.items():
1427
            item_type = ''
1428
            if key in OSPD_PARAMS:
1429
                item_type = OSPD_PARAMS[key].get('type')
1430
            if item_type == 'boolean':
1431
                val = _from_bool_to_str(value)
1432
            else:
1433
                val = str(value)
1434
            prefs_val.append(key + "|||" + val)
1435
        self.openvas_db.add_single_item(
1436
            'internal/%s/scanprefs' % openvas_scan_id, prefs_val
1437
        )
1438
1439
        # Store main_kbindex as global preference
1440
        ov_maindbid = 'ov_maindbid|||%d' % self.main_kbindex
1441
        self.openvas_db.add_single_item(
1442
            'internal/%s/scanprefs' % openvas_scan_id, [ov_maindbid]
1443
        )
1444
1445
        # Set target
1446
        target_aux = 'TARGET|||%s' % target
1447
        self.openvas_db.add_single_item(
1448
            'internal/%s/scanprefs' % openvas_scan_id, [target_aux]
1449
        )
1450
        # Set port range
1451
        port_range = 'port_range|||%s' % ports
1452
        self.openvas_db.add_single_item(
1453
            'internal/%s/scanprefs' % openvas_scan_id, [port_range]
1454
        )
1455
1456
        # If credentials or vts fail, set this variable.
1457
        do_not_launch = False
1458
1459
        # Set credentials
1460
        credentials = self.get_scan_credentials(scan_id, target)
1461
        if credentials:
1462
            cred_prefs = self.build_credentials_as_prefs(credentials)
1463
            if cred_prefs:
1464
                self.openvas_db.add_single_item(
1465
                    'internal/%s/scanprefs' % openvas_scan_id, cred_prefs
1466
                )
1467
            else:
1468
                self.add_scan_error(
1469
                    scan_id, name='', host=target, value='Malformed credential.'
1470
                )
1471
                do_not_launch = True
1472
1473
        # Set plugins to run.
1474
        # Make a deepcopy of the vts dictionary. Otherwise, consulting the
1475
        # DictProxy object of multiprocessing directly is to expensinve
1476
        # (interprocess communication).
1477
        self.temp_vts_dict = copy.deepcopy(self.vts)
1478
        nvts = self.get_scan_vts(scan_id)
1479
        if nvts != '':
1480
            nvts_list, nvts_params = self.process_vts(nvts)
1481
            # Add nvts list
1482
            separ = ';'
1483
            plugin_list = 'plugin_set|||%s' % separ.join(nvts_list)
1484
            self.openvas_db.add_single_item(
1485
                'internal/%s/scanprefs' % openvas_scan_id, [plugin_list]
1486
            )
1487
            # Add nvts parameters
1488
            for elem in nvts_params:
1489
                item = '%s|||%s' % (elem[0], elem[1])
1490
                self.openvas_db.add_single_item(
1491
                    'internal/%s/scanprefs' % openvas_scan_id, [item]
1492
                )
1493
            # Release temp vts dict memory.
1494
            self.temp_vts_dict = None
1495
        else:
1496
            self.add_scan_error(
1497
                scan_id, name='', host=target, value='No VTS to run.'
1498
            )
1499
            do_not_launch = True
1500
1501
        if do_not_launch:
1502
            self.openvas_db.release_db(self.main_kbindex)
1503
            return 2
1504
1505
        cmd = ['openvas', '--scan-start', openvas_scan_id]
1506
        if not self.is_running_as_root and self.sudo_available:
1507
            cmd = ['sudo', '-n'] + cmd
1508
1509
        if self._niceness is not None:
1510
            cmd = ['nice', '-n', self._niceness] + cmd
1511
1512
        logger.debug("Running scan with niceness %s", self._niceness)
1513
        try:
1514
            result = subprocess.Popen(cmd, shell=False)
1515
        except OSError:
1516
            # the command is not available
1517
            return False
1518
1519
        ovas_pid = result.pid
1520
        logger.debug('pid = %s', ovas_pid)
1521
        self.openvas_db.add_single_item('internal/ovas_pid', [ovas_pid])
1522
1523
        # Wait until the scanner starts and loads all the preferences.
1524
        while (
1525
            self.openvas_db.get_single_item('internal/' + openvas_scan_id)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1526
            == 'new'
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1527
        ):
1528
            res = result.poll()
1529
            if res and res < 0:
1530
                self.stop_scan_cleanup(scan_id)
1531
                msg = (
1532
                    'It was not possible run the task %s, since openvas ended '
1533
                    'unexpectedly with errors during launching.' % scan_id
1534
                )
1535
                logger.error(msg)
1536
                return 1
1537
            time.sleep(1)
1538
1539
        no_id_found = False
1540
        while True:
1541
            time.sleep(3)
1542
            # Check if the client stopped the whole scan
1543
            if self.scan_is_stopped(openvas_scan_id):
1544
                return 1
1545
1546
            ctx = self.openvas_db.kb_connect(self.main_kbindex)
1547
            self.openvas_db.set_redisctx(ctx)
1548
            self.get_openvas_result(scan_id, "")
1549
            dbs = self.openvas_db.get_list_item('internal/dbindex')
1550
            for i in list(dbs):
1551
                if i == self.main_kbindex:
1552
                    continue
1553
                self.openvas_db.select_kb(ctx, str(i), set_global=True)
1554
                id_aux = self.openvas_db.get_single_item('internal/scan_id')
1555
                if not id_aux:
1556
                    continue
1557
                if id_aux == openvas_scan_id:
1558
                    no_id_found = False
1559
                    current_host = self.openvas_db.get_host_ip()
1560
                    self.get_openvas_result(scan_id, current_host)
1561
                    self.get_openvas_status(scan_id, target, current_host)
1562
                    self.get_openvas_timestamp_scan_host(scan_id, current_host)
1563
                    if self.host_is_finished(openvas_scan_id):
1564
                        self.set_scan_host_finished(
1565
                            scan_id, target, current_host
1566
                        )
1567
                        self.get_openvas_status(scan_id, target, current_host)
1568
                        self.get_openvas_timestamp_scan_host(
1569
                            scan_id, current_host
1570
                        )
1571
                        self.openvas_db.select_kb(
1572
                            ctx, str(self.main_kbindex), set_global=False
1573
                        )
1574
                        self.openvas_db.remove_list_item('internal/dbindex', i)
1575
                        self.openvas_db.release_db(i)
1576
1577
            # Scan end. No kb in use for this scan id
1578
            if no_id_found and self.target_is_finished(scan_id):
1579
                break
1580
            no_id_found = True
1581
1582
        # Delete keys from KB related to this scan task.
1583
        self.openvas_db.release_db(self.main_kbindex)
1584
1585
1586
def main():
1587
    """ OSP openvas main function. """
1588
    daemon_main('OSPD - openvas', OSPDopenvas)
1589
1590
1591
if __name__ == '__main__':
1592
    main()
1593