Passed
Pull Request — master (#138)
by Juan José
01:26
created

ospd_openvas.daemon.OSPDopenvas.sudo_available()   A

Complexity

Conditions 4

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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