Passed
Push — master ( 07626a...901da9 )
by
unknown
01:42
created

OSPDopenvas.get_openvas_result()   C

Complexity

Conditions 8

Size

Total Lines 59
Code Lines 47

Duplication

Lines 59
Ratio 100 %

Importance

Changes 0
Metric Value
cc 8
eloc 47
nop 2
dl 59
loc 59
rs 6.8678
c 0
b 0
f 0

How to fix   Long Method   

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:

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2018 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
""" Setup for the OSP OpenVAS Server. """
21
22
import subprocess
23
import time
24
import signal
25
import uuid
26
from lxml.etree import tostring, SubElement, Element
27
import psutil
28
29
from ospd.ospd import OSPDaemon, logger
30
from ospd.misc import main as daemon_main
31
from ospd.misc import target_str_to_list
32
from ospd.cvss import CVSS
33
from ospd_openvas import __version__
34
35
from ospd_openvas.nvticache import NVTICache
36
from ospd_openvas.db import OpenvasDB
37
38
OSPD_DESC = """
39
This scanner runs 'OpenVAS Scanner' to scan the target hosts.
40
41
OpenVAS (Open Vulnerability Assessment System) is a powerful scanner
42
for vulnerabilities in IT infrastrucutres. The capabilities include
43
unauthzenticated scanning as well as authneticated scanning for
44
various types of systems and services.
45
46
For more details about OpenVAS see the OpenVAS homepage:
47
http://www.openvas.org/
48
49
The current version of ospd-openvas is a simple frame, which sends
50
the server parameters to the Greenbone Vulnerability Manager (GVM) and checks
51
the existence of OpenVAS Scanner binary. But it can not run scans yet.
52
"""
53
54
OSPD_PARAMS = {
55
    'auto_enable_dependencies': {
56
        'type': 'boolean',
57
        'name': 'auto_enable_dependencies',
58
        'default': 1,
59
        'mandatory': 1,
60
        'description': 'Automatically enable the plugins that are depended on',
61
    },
62
    'cgi_path': {
63
        'type': 'string',
64
        'name': 'cgi_path',
65
        'default': '/cgi-bin:/scripts',
66
        'mandatory': 1,
67
        'description': 'Look for default CGIs in /cgi-bin and /scripts',
68
    },
69
    'checks_read_timeout': {
70
        'type': 'integer',
71
        'name': 'checks_read_timeout',
72
        'default': 5,
73
        'mandatory': 1,
74
        'description': ('Number  of seconds that the security checks will ' +
75
                        'wait for when doing a recv()'),
76
    },
77
    'drop_privileges': {
78
        'type': 'boolean',
79
        'name': 'drop_privileges',
80
        'default': 0,
81
        'mandatory': 1,
82
        'description': '',
83
    },
84
    'network_scan': {
85
        'type': 'boolean',
86
        'name': 'network_scan',
87
        'default': 0,
88
        'mandatory': 1,
89
        'description': '',
90
    },
91
    'non_simult_ports': {
92
        'type': 'string',
93
        'name': 'non_simult_ports',
94
        'default': '139, 445, 3389, Services/irc',
95
        'mandatory': 1,
96
        'description': ('Prevent to make two connections on the same given ' +
97
                        'ports at the same time.'),
98
    },
99
    'open_sock_max_attempts': {
100
        'type': 'integer',
101
        'name': 'open_sock_max_attempts',
102
        'default': 5,
103
        'mandatory': 0,
104
        'description': ('Number of unsuccessful retries to open the socket ' +
105
                        'before to set the port as closed.'),
106
    },
107
    'timeout_retry': {
108
        'type': 'integer',
109
        'name': 'timeout_retry',
110
        'default': 5,
111
        'mandatory': 0,
112
        'description': ('Number of retries when a socket connection attempt ' +
113
                        'timesout.'),
114
    },
115
    'optimize_test': {
116
        'type': 'integer',
117
        'name': 'optimize_test',
118
        'default': 5,
119
        'mandatory': 0,
120
        'description': ('By default, openvassd does not trust the remote ' +
121
                        'host banners.'),
122
    },
123
    'plugins_timeout': {
124
        'type': 'integer',
125
        'name': 'plugins_timeout',
126
        'default': 5,
127
        'mandatory': 0,
128
        'description': 'This is the maximum lifetime, in seconds of a plugin.',
129
    },
130
    'report_host_details': {
131
        'type': 'boolean',
132
        'name': 'report_host_details',
133
        'default': 1,
134
        'mandatory': 1,
135
        'description': '',
136
    },
137
    'safe_checks': {
138
        'type': 'boolean',
139
        'name': 'safe_checks',
140
        'default': 1,
141
        'mandatory': 1,
142
        'description': ('Disable the plugins with potential to crash ' +
143
                        'the remote services'),
144
    },
145
    'scanner_plugins_timeout': {
146
        'type': 'integer',
147
        'name': 'scanner_plugins_timeout',
148
        'default': 36000,
149
        'mandatory': 1,
150
        'description': 'Like plugins_timeout, but for ACT_SCANNER plugins.',
151
    },
152
    'time_between_request': {
153
        'type': 'integer',
154
        'name': 'time_between_request',
155
        'default': 0,
156
        'mandatory': 0,
157
        'description': ('Allow to set a wait time between two actions ' +
158
                        '(open, send, close).'),
159
    },
160
    'unscanned_closed': {
161
        'type': 'boolean',
162
        'name': 'unscanned_closed',
163
        'default': 1,
164
        'mandatory': 1,
165
        'description': '',
166
    },
167
    'unscanned_closed_udp': {
168
        'type': 'boolean',
169
        'name': 'unscanned_closed_udp',
170
        'default': 1,
171
        'mandatory': 1,
172
        'description': '',
173
    },
174
    'use_mac_addr': {
175
        'type': 'boolean',
176
        'name': 'use_mac_addr',
177
        'default': 0,
178
        'mandatory': 0,
179
        'description': 'To test the local network. ' +
180
                       'Hosts will be referred to by their MAC address.',
181
    },
182
    'vhosts': {
183
        'type': 'string',
184
        'name': 'vhosts',
185
        'default': '',
186
        'mandatory': 0,
187
        'description': '',
188
    },
189
    'vhosts_ip': {
190
        'type': 'string',
191
        'name': 'vhosts_ip',
192
        'default': '',
193
        'mandatory': 0,
194
        'description': '',
195
    },
196
}
197
198
199
def _from_bool_to_str(value):
200
    """ The OpenVAS scanner use yes and no as boolean values, whereas ospd
201
    uses 1 and 0."""
202
    return 'yes' if value == 1 else 'no'
203
204
205 View Code Duplication
class OSPDopenvas(OSPDaemon):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
206
207
    """ Class for ospd-openvas daemon. """
208
209
    def __init__(self, certfile, keyfile, cafile):
210
        """ Initializes the ospd-openvas daemon's internal data. """
211
212
        super(OSPDopenvas, self).__init__(certfile=certfile, keyfile=keyfile,
213
                                          cafile=cafile)
214
        self.server_version = __version__
215
        self.scanner_info['name'] = 'openvassd'
216
        self.scanner_info['version'] = ''  # achieved during self.check()
217
        self.scanner_info['description'] = OSPD_DESC
218
        for name, param in OSPD_PARAMS.items():
219
            self.add_scanner_param(name, param)
220
221
        self.main_kbindex = None
222
        self.openvas_db = OpenvasDB()
223
        self.nvti = NVTICache(self.openvas_db)
224
225
        self.openvas_db.db_init()
226
227
        self.pending_feed = None
228
        ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR)
229
        if not ctx:
230
            self.redis_nvticache_init()
231
            ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR)
232
        self.openvas_db.set_redisctx(ctx)
233
        self.load_vts()
234
235
    def parse_param(self):
236
        """ Set OSPD_PARAMS with the params taken from the openvas_scanner. """
237
        global OSPD_PARAMS
238
        bool_dict = {'no': 0, 'yes': 1}
239
240
        result = subprocess.check_output(['openvassd', '-s'],
241
                                         stderr=subprocess.STDOUT)
242
        result = result.decode('ascii')
243
        param_list = dict()
244
        for conf in result.split('\n'):
245
            elem = conf.split('=')
246
            if len(elem) == 2:
247
                value = str.strip(elem[1])
248
                if str.strip(elem[1]) in bool_dict:
249
                    value = bool_dict[value]
250
                param_list[str.strip(elem[0])] = value
251
        for elem in OSPD_PARAMS:
252
            if elem in param_list:
253
                OSPD_PARAMS[elem]['default'] = param_list[elem]
254
255
    def redis_nvticache_init(self):
256
        """ Loads NVT's metadata into Redis DB. """
257
        try:
258
            logger.debug('Loading NVTs in Redis DB')
259
            subprocess.check_call(['openvassd', '-C'])
260
        except subprocess.CalledProcessError as err:
261
            logger.error('OpenVAS Scanner failed to load NVTs.')
262
            raise err
263
264
    def check_feed(self):
265
        """ Check if there is a feed update. Wait until all the running
266
        scans finished. Set a flag to anounce there is a pending feed update,
267
        which avoid to start a new scan.
268
        """
269
        _running_scan = False
270
        for scan_id in self.scan_processes:
271
            if self.scan_processes[scan_id].is_alive():
272
                _running_scan = True
273
274
        if self.pending_feed:
275
            _pending_feed = True
276
        else:
277
            _pending_feed = self.get_vts_version() != self.nvti.get_feed_version()
278
279
        if _running_scan and _pending_feed:
280
            if not self.pending_feed:
281
                self.pending_feed = True
282
                logger.debug(
283
                    'There is a running scan. Therefore the feed '
284
                    'update will be performed later.')
285
        elif not _running_scan and _pending_feed:
286
            self.vts = dict()
287
            self.load_vts()
288
289
    def scheduler(self):
290
        """This method is called periodically to run tasks."""
291
        self.check_feed()
292
293
    def load_vts(self):
294
        """ Load the NVT's metadata into the vts
295
        global  dictionary. """
296
        logger.debug('Loading vts in memory.')
297
        oids = dict(self.nvti.get_oids())
298
        for filename, vt_id in oids.items():
299
            _vt_params = self.nvti.get_nvt_params(vt_id)
300
            _vt_refs = self.nvti.get_nvt_refs(vt_id)
301
            _custom = self.nvti.get_nvt_metadata(vt_id)
302
            _name = _custom.pop('name')
303
            _vt_creation_time = _custom.pop('creation_date')
304
            _vt_modification_time = _custom.pop('last_modification')
305
306
            _summary = None
307
            _impact = None
308
            _affected = None
309
            _insight = None
310
            _solution = None
311
            _solution_t = None
312
            _vuldetect = None
313
            _qod_t = None
314
            _qod_v = None
315
316
            if 'summary' in _custom:
317
                _summary = _custom.pop('summary')
318
            if 'impact' in _custom:
319
                _impact = _custom.pop('impact')
320
            if 'affected' in _custom:
321
                _affected = _custom.pop('affected')
322
            if 'insight' in _custom :
323
                _insight = _custom.pop('insight')
324
            if 'solution' in _custom:
325
                _solution = _custom.pop('solution')
326
                if 'solution_type' in _custom:
327
                    _solution_t = _custom.pop('solution_type')
328
329
            if 'vuldetect' in _custom:
330
                _vuldetect  = _custom.pop('vuldetect')
331
            if 'qod_type' in _custom:
332
                _qod_t  = _custom.pop('qod_type')
333
            elif 'qod' in _custom:
334
                _qod_v  = _custom.pop('qod')
335
336
            _severity = dict()
337
            if 'severity_base_vector' in _custom:
338
                _severity_vector = _custom.pop('severity_base_vector')
339
            else:
340
                _severity_vector = _custom.pop('cvss_base_vector')
341
            _severity['severity_base_vector'] = _severity_vector
342
            if 'severity_type' in _custom:
343
                _severity_type = custom.pop('severity_type')
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable custom does not seem to be defined.
Loading history...
344
            else:
345
                _severity_type = 'cvss_base_v2'
346
            _severity['severity_type'] = _severity_type
347
            if 'severity_origin' in _custom:
348
                _severity['severity_origin'] = _custom.pop('severity_origin')
349
350
            _vt_dependencies = list()
351
            if 'dependencies' in _custom:
352
                _deps = _custom.pop('dependencies')
353
                _deps_list = _deps.split(', ')
354
                for dep in _deps_list:
355
                    _vt_dependencies.append(oids.get('filename:' + dep))
356
357
            ret = self.add_vt(
358
                vt_id,
359
                name=_name,
360
                vt_params=_vt_params,
361
                vt_refs=_vt_refs,
362
                custom=_custom,
363
                vt_creation_time=_vt_creation_time,
364
                vt_modification_time=_vt_modification_time,
365
                vt_dependencies=_vt_dependencies,
366
                summary=_summary,
367
                impact=_impact,
368
                affected=_affected,
369
                insight=_insight,
370
                solution=_solution,
371
                solution_t=_solution_t,
372
                detection=_vuldetect,
373
                qod_t=_qod_t,
374
                qod_v=_qod_v,
375
                severities=_severity
376
            )
377
            if ret == -1:
378
                logger.info("Dupplicated VT with OID: {0}".format(vt_id))
379
            if ret == -2:
380
                logger.info("{0}: Invalid OID.".format(vt_id))
381
382
        _feed_version = self.nvti.get_feed_version()
383
        self.set_vts_version(vts_version=_feed_version)
384
        self.pending_feed = False
385
        logger.debug('Finish loading up vts.')
386
387
    @staticmethod
388
    def get_custom_vt_as_xml_str(vt_id, custom):
389
        """ Return an xml element with custom metadata formatted as string."""
390
391
        nvt = Element('vt')
392
        for key, val in custom.items():
393
            xml_key = SubElement(nvt, key)
394
            xml_key.text = val
395
396
        itera = nvt.iter()
397
        metadata = ''
398
        for elem in itera:
399
            if elem.tag != 'vt':
400
                metadata += (tostring(elem).decode('utf-8'))
401
        return metadata
402
403
    @staticmethod
404
    def get_severities_vt_as_xml_str(vt_id, severities):
405
        """ Return an xml element with severities as string."""
406
407
        _severity = Element('severity')
408
        if 'severity_base_vector' in severities:
409
            _severity.text = severities.pop('severity_base_vector')
410
        if 'severity_origin' in severities:
411
            _severity.set('origin', severities.pop('severity_origin'))
412
        if 'severity_type' in severities:
413
            _severity.set('type', severities.pop('severity_type'))
414
415
        return tostring(_severity).decode('utf-8')
416
417
    @staticmethod
418
    def get_params_vt_as_xml_str(vt_id, vt_params):
419
        """ Return an xml element with params formatted as string."""
420
        vt_params_xml = Element('vt_params')
421
        for prefs in vt_params.items():
422
            vt_param = Element('vt_param')
423
            vt_param.set('type', prefs[1]['type'])
424
            vt_param.set('id', prefs[0])
425
            xml_name = SubElement(vt_param, 'name')
426
            xml_name.text = prefs[1]['name']
427
            if prefs[1]['default']:
428
                xml_def = SubElement(vt_param, 'default')
429
                xml_def.text = prefs[1]['default']
430
            vt_params_xml.append(vt_param)
431
432
        params_list = vt_params_xml.findall("vt_param")
433
        params = ''
434
        for param in params_list:
435
            params += (tostring(param).decode('utf-8'))
436
        return params
437
438
    @staticmethod
439
    def get_refs_vt_as_xml_str(vt_id, vt_refs):
440
        """ Return an xml element with references formatted as string."""
441
        vt_refs_xml = Element('vt_refs')
442
        for ref_type, ref_values in vt_refs.items():
443
            for value in ref_values:
444
                vt_ref = Element('ref')
445
                if ref_type == "xref" and value:
446
                    for xref in value.split(', '):
447
                        try:
448
                            _type, _id = xref.split(':', 1)
449
                        except ValueError:
450
                            logger.error(
451
                                'Not possible to parse xref %s for vt %s' % (
452
                                    xref, vt_id))
453
                            continue
454
                        vt_ref.set('type', _type.lower())
455
                        vt_ref.set('id', _id)
456
                elif value:
457
                    vt_ref.set('type', ref_type.lower())
458
                    vt_ref.set('id', value)
459
                else:
460
                    continue
461
                vt_refs_xml.append(vt_ref)
462
463
        refs_list = vt_refs_xml.findall("ref")
464
        refs = ''
465
        for ref in refs_list:
466
            refs += (tostring(ref).decode('utf-8'))
467
        return refs
468
469
    @staticmethod
470
    def get_dependencies_vt_as_xml_str(vt_id, dep_list):
471
        """ Return  an xml element with dependencies as string."""
472
        vt_deps_xml = Element('vt_deps')
473
        for dep in dep_list:
474
            _vt_dep = Element('dependency')
475
            try:
476
                _vt_dep.set('vt_id', dep)
477
            except TypeError:
478
                logger.error('Not possible to add dependency %s for vt %s' % (
479
                    dep, vt_id))
480
                continue
481
            vt_deps_xml.append(_vt_dep)
482
483
        _deps_list = vt_deps_xml.findall("dependency")
484
        deps = ''
485
        for _dep in _deps_list:
486
            deps += (tostring(_dep).decode('utf-8'))
487
        return deps
488
489
    @staticmethod
490
    def get_creation_time_vt_as_xml_str(vt_id, creation_time):
491
        """ Return creation time as string."""
492
        return creation_time
493
494
    @staticmethod
495
    def get_modification_time_vt_as_xml_str(vt_id, modification_time):
496
        """ Return modification time as string."""
497
        return modification_time
498
499
    @staticmethod
500
    def get_summary_vt_as_xml_str(vt_id, summary):
501
        """ Return summary as string."""
502
        _summary = Element('summary')
503
        _summary.text = summary
504
        return tostring(_summary).decode('utf-8')
505
506
    @staticmethod
507
    def get_impact_vt_as_xml_str(vt_id, impact):
508
        """ Return impact as string."""
509
        _impact = Element('impact')
510
        _impact.text = impact
511
        return tostring(_impact).decode('utf-8')
512
513
    @staticmethod
514
    def get_affected_vt_as_xml_str(vt_id, affected):
515
        """ Return affected as string."""
516
        _affected = Element('affected')
517
        _affected.text = affected
518
        return tostring(_affected).decode('utf-8')
519
520
    @staticmethod
521
    def get_insight_vt_as_xml_str(vt_id, insight):
522
        """ Return insight as string."""
523
        _insight = Element('insight')
524
        _insight.text = insight
525
        return tostring(_insight).decode('utf-8')
526
527
    @staticmethod
528
    def get_solution_vt_as_xml_str(vt_id, solution, solution_type=None):
529
        """ Return solution as string."""
530
        _solution = Element('solution')
531
        _solution.text = solution
532
        if solution_type:
533
            _solution.set('type', solution_type)
534
        return tostring(_solution).decode('utf-8')
535
536
    @staticmethod
537
    def get_detection_vt_as_xml_str(vt_id, vuldetect=None, qod_type=None, qod=None):
538
        """ Return detection as string."""
539
        _detection = Element('detection')
540
        if vuldetect:
541
            _detection.text = vuldetect
542
        if qod_type:
543
            _detection.set('qod_type', qod_type)
544
        elif qod:
545
            _detection.set('qod', qod)
546
547
        return tostring(_detection).decode('utf-8')
548
549
    def check(self):
550
        """ Checks that openvassd command line tool is found and
551
        is executable. """
552
        try:
553
            result = subprocess.check_output(['openvassd', '-V'],
554
                                             stderr=subprocess.STDOUT)
555
            result = result.decode('ascii')
556
        except OSError:
557
            # The command is not available
558
            return False
559
560
        if result is None:
561
            return False
562
563
        version = result.split('\n')
564
        if version[0].find('OpenVAS') < 0:
565
            return False
566
567
        self.parse_param()
568
        self.scanner_info['version'] = version[0]
569
570
        return True
571
572
    def update_progress(self, scan_id, target, msg):
573
        """ Calculate porcentage and update the scan status
574
        for the progress bar. """
575
        host_progress_dict = dict()
576
        prog = str.split(msg, '/')
577
        if float(prog[1]) == 0:
578
            return
579
        host_prog = (float(prog[0]) / float(prog[1])) * 100
580
        host_progress_dict[target] = host_prog
581
        total_host = len(target_str_to_list(target))
582
        self.set_scan_target_progress(scan_id, target,
583
                                      sum(host_progress_dict.values()) / total_host)
584
585
    def get_openvas_status(self, scan_id, target):
586
        """ Get all status entries from redis kb. """
587
        res = self.openvas_db.get_status()
588
        while res:
589
            self.update_progress(scan_id, target, res)
590
            res = self.openvas_db.get_status()
591
592
593
    def get_severity_score(self, oid):
594
        """ Return the severity score for the given oid. """
595
        severity_type = (
596
            self.vts[oid]['severities'].get('severity_type'))
597
598
        severity_vector = (
599
            self.vts[oid]['severities'].get('severity_base_vector'))
600
601
        if severity_type == "cvss_base_v2" and severity_vector:
602
            return CVSS.cvss_base_v2_value(severity_vector)
603
604
        return None
605
606
    def get_openvas_result(self, scan_id):
607
        """ Get all result entries from redis kb. """
608
        res = self.openvas_db.get_result()
609
        while res:
610
            msg = res.split('|||')
611
            host_aux = self.openvas_db.get_single_item('internal/ip')
612
            roid = msg[3]
613
614
            rqod = ''
615
            if self.vts[roid].get('qod_type'):
616
                qod_t = self.vts[roid].get('qod_type')
617
                rqod = self.nvti.QoD_TYPES[qod_t]
618
            elif self.vts[roid].get('qod'):
619
                rqod = self.vts[roid].get('qod')
620
621
            rname = self.vts[roid].get('name')
622
623
            if msg[0] == 'ERRMSG':
624
                self.add_scan_error(
625
                    scan_id,
626
                    host=host_aux,
627
                    name=rname,
628
                    value=msg[4],
629
                    port=msg[2],
630
                )
631
632
            if msg[0] == 'LOG':
633
                self.add_scan_log(
634
                    scan_id,
635
                    host=host_aux,
636
                    name=rname,
637
                    value=msg[4],
638
                    port=msg[2],
639
                    qod=rqod,
640
                    test_id=roid,
641
                )
642
643
            if msg[0] == 'HOST_DETAIL':
644
                self.add_scan_log(
645
                    scan_id,
646
                    host=host_aux,
647
                    name=rname,
648
                    value=msg[4],
649
                )
650
651
            if msg[0] == 'ALARM':
652
                rseverity = self.get_severity_score(roid)
653
                self.add_scan_alarm(
654
                    scan_id,
655
                    host=host_aux,
656
                    name=rname,
657
                    value=msg[4],
658
                    port=msg[2],
659
                    test_id=roid,
660
                    severity=rseverity,
661
                    qod=rqod,
662
                )
663
664
            res = self.openvas_db.get_result()
665
666
    def get_openvas_timestamp_scan_host(self, scan_id, target):
667
        """ Get start and end timestamp of a host scan from redis kb. """
668
        timestamp = self.openvas_db.get_host_scan_scan_end_time()
669
        if timestamp:
670
            self.add_scan_log(scan_id, host=target, name='HOST_END',
671
                              value=timestamp)
672
            return
673
        timestamp = self.openvas_db.get_host_scan_scan_start_time()
674
        if timestamp:
675
            self.add_scan_log(scan_id, host=target, name='HOST_START',
676
                              value=timestamp)
677
            return
678
679
    def scan_is_finished(self, scan_id):
680
        """ Check if the scan has finished. """
681
        status = self.openvas_db.get_single_item('internal/%s' % scan_id)
682
        return status == 'finished'
683
684
    def scan_is_stopped(self, scan_id):
685
        """ Check if the parent process has received the stop_scan order.
686
        @in scan_id: ID to identify the scan to be stopped.
687
        @return 1 if yes, None in other case.
688
        """
689
        ctx = self.openvas_db.kb_connect(dbnum=self.main_kbindex)
690
        self.openvas_db.set_redisctx(ctx)
691
        status = self.openvas_db.get_single_item('internal/%s' % scan_id)
692
        return status == 'stop_all'
693
694
    def stop_scan(self, global_scan_id):
695
        """ Set a key in redis to indicate the wrapper is stopped.
696
        It is done through redis because it is a new multiprocess
697
        instance and it is not possible to reach the variables
698
        of the grandchild process. Send SIGUSR2 to openvas to stop
699
        each running scan."""
700
        ctx = self.openvas_db.kb_connect()
701
        for current_kbi in range(0, self.openvas_db.max_dbindex):
702
            self.openvas_db.select_kb(ctx, str(current_kbi), set_global=True)
703
            scan_id = self.openvas_db.get_single_item(
704
                'internal/%s/globalscanid' % global_scan_id)
705
            if scan_id:
706
                self.openvas_db.set_single_item('internal/%s' % scan_id,
707
                                           ['stop_all', ])
708
                ovas_pid = self.openvas_db.get_single_item('internal/ovas_pid')
709
                parent = psutil.Process(int(ovas_pid))
710
                self.openvas_db.release_db(current_kbi)
711
                parent.send_signal(signal.SIGUSR2)
712
                logger.debug('Stopping process: {0}'.format(parent))
713
714
    def get_vts_in_groups(self, filters):
715
        """ Return a list of vts which match with the given filter.
716
717
        @input filters A list of filters. Each filter has key, operator and
718
                       a value. They are separated by a space.
719
                       Supported keys: family
720
        @return Return a list of vts which match with the given filter.
721
        """
722
        vts_list = list()
723
        families = dict()
724
        for oid in self.vts:
725
            family = self.vts[oid]['custom'].get('family')
726
            if family not in families:
727
                families[family] = list()
728
            families[family].append(oid)
729
730
        for elem in filters:
731
            key, value = elem.split('=')
732
            if key == 'family' and value in families:
733
                vts_list.extend(families[value])
734
        return vts_list
735
736
    def get_vt_param_type(self, vtid, vt_param_id):
737
        """ Return the type of the vt parameter from the vts dictionary. """
738
        vt_params_list = self.vts[vtid].get("vt_params")
739
        return vt_params_list[vt_param_id]["type"]
740
741
    @staticmethod
742
    def check_param_type(vt_param_value, param_type):
743
        """ Check if the value of a vt parameter matches with
744
        the type founded.
745
        """
746
        if (param_type in ['entry',
747
                           'file',
748
                           'password',
749
                           'radio',
750
                           'sshlogin', ] and isinstance(vt_param_value, str)):
751
            return None
752
        elif (param_type == 'checkbox' and
753
              (vt_param_value == 'yes' or vt_param_value == 'no')):
754
            return None
755
        elif param_type == 'integer':
756
            try:
757
                int(vt_param_value)
758
            except ValueError:
759
                return 1
760
            return None
761
762
        return 1
763
764
    def process_vts(self, vts):
765
        """ Add single VTs and their parameters. """
766
        vts_list = []
767
        vts_params = []
768
        vtgroups = vts.pop('vt_groups')
769
770
        if vtgroups:
771
            vts_list = self.get_vts_in_groups(vtgroups)
772
773
        for vtid, vt_params in vts.items():
774
            vts_list.append(vtid)
775
            nvt_name = self.vts[vtid].get('name')
776
            for vt_param_id, vt_param_value in vt_params.items():
777
                param_type = self.get_vt_param_type(vtid, vt_param_id)
778
                if vt_param_id == 'timeout':
779
                    type_aux = 'integer'
780
                else:
781
                    type_aux = param_type
782
                if self.check_param_type(vt_param_value, type_aux):
783
                    logger.debug('Expected {} type for parameter value {}'
784
                                 .format(type_aux, str(vt_param_value)))
785
                param = ["{0}[{1}]:{2}".format(nvt_name, param_type,
786
                                               vt_param_id),
787
                         str(vt_param_value)]
788
                vts_params.append(param)
789
        return vts_list, vts_params
790
791
    @staticmethod
792
    def build_credentials_as_prefs(credentials):
793
        """ Parse the credential dictionary.
794
        @param credentials: Dictionary with the credentials.
795
796
        @return A list with the credentials in string format to be
797
                added to the redis KB.
798
        """
799
        cred_prefs_list = []
800
        for credential in credentials.items():
801
            service = credential[0]
802
            cred_params = credentials.get(service)
803
            cred_type = cred_params.get('type', '')
804
            username = cred_params.get('username', '')
805
            password = cred_params.get('password', '')
806
807
            if service == 'ssh':
808
                port = cred_params.get('port', '')
809
                cred_prefs_list.append('auth_port_ssh|||' +
810
                                       '{0}'.format(port))
811
                cred_prefs_list.append('SSH Authorization[entry]:SSH login ' +
812
                                       'name:|||{0}'.format(username))
813
                if cred_type == 'up':
814
                    cred_prefs_list.append('SSH Authorization[password]:' +
815
                                           'SSH password (unsafe!):|||' +
816
                                           '{0}'.format(password))
817
                else:
818
                    private = cred_params.get('private', '')
819
                    cred_prefs_list.append('SSH Authorization[password]:' +
820
                                           'SSH key passphrase:|||' +
821
                                           '{0}'.format(password))
822
                    cred_prefs_list.append('SSH Authorization[file]:' +
823
                                           'SSH private key:|||' +
824
                                           '{0}'.format(private))
825
            if service == 'smb':
826
                cred_prefs_list.append('SMB Authorization[entry]:SMB login:' +
827
                                       '|||{0}'.format(username))
828
                cred_prefs_list.append('SMB Authorization[password]:' +
829
                                       'SMB password :|||' +
830
                                       '{0}'.format(password))
831
            if service == 'esxi':
832
                cred_prefs_list.append('ESXi Authorization[entry]:ESXi login ' +
833
                                       'name:|||{0}'.format(username))
834
                cred_prefs_list.append('ESXi Authorization[password]:' +
835
                                       'ESXi login password:|||' +
836
                                       '{0}'.format(password))
837
838
            if service == 'snmp':
839
                community = cred_params.get('community', '')
840
                auth_algorithm = cred_params.get('auth_algorithm', '')
841
                privacy_password = cred_params.get('privacy_password', '')
842
                privacy_algorithm = cred_params.get('privacy_algorithm', '')
843
844
                cred_prefs_list.append('SNMP Authorization[password]:' +
845
                                       'SNMP Community:' +
846
                                       '{0}'.format(community))
847
                cred_prefs_list.append('SNMP Authorization[entry]:' +
848
                                       'SNMPv3 Username:' +
849
                                       '{0}'.format(username))
850
                cred_prefs_list.append('SNMP Authorization[password]:' +
851
                                       'SNMPv3 Password:' +
852
                                       '{0}'.format(password))
853
                cred_prefs_list.append('SNMP Authorization[radio]:' +
854
                                       'SNMPv3 Authentication Algorithm:' +
855
                                       '{0}'.format(auth_algorithm))
856
                cred_prefs_list.append('SNMP Authorization[password]:' +
857
                                       'SNMPv3 Privacy Password:' +
858
                                       '{0}'.format(privacy_password))
859
                cred_prefs_list.append('SNMP Authorization[radio]:' +
860
                                       'SNMPv3 Privacy Algorithm:' +
861
                                       '{0}'.format(privacy_algorithm))
862
863
        return cred_prefs_list
864
865
    def exec_scan(self, scan_id, target):
866
        """ Starts the OpenVAS scanner for scan_id scan. """
867
        if self.pending_feed:
868
            logger.info(
869
                '%s: There is a pending feed update. '
870
                'The scan can not be started.' % scan_id)
871
            self.add_scan_error(
872
                scan_id, name='', host=target,
873
                value=('It was not possible to start the scan,'
874
                'because a pending feed update. Please try later'))
875
            return 2
876
877
        ports = self.get_scan_ports(scan_id, target)
878
        if not ports:
879
            self.add_scan_error(scan_id, name='', host=target,
880
                                value='No port list defined.')
881
            return 2
882
883
        # Get scan options
884
        options = self.get_scan_options(scan_id)
885
        prefs_val = []
886
        ctx = self.openvas_db.kb_new()
887
        self.openvas_db.set_redisctx(ctx)
888
        self.main_kbindex = self.openvas_db.db_index
889
890
        # To avoid interference between scan process during a parallel scanning
891
        # new uuid is used internally for each scan.
892
        openvas_scan_id = str(uuid.uuid4())
893
        self.openvas_db.add_single_item(
894
            'internal/%s' % openvas_scan_id, ['new'])
895
        self.openvas_db.add_single_item(
896
            'internal/%s/globalscanid' % scan_id, [openvas_scan_id])
897
898
        # Set scan preferences
899
        for key, value in options.items():
900
            item_type = ''
901
            if key in OSPD_PARAMS:
902
                item_type = OSPD_PARAMS[key].get('type')
903
            if item_type == 'boolean':
904
                val =  _from_bool_to_str(value)
905
            else:
906
                val = str(value)
907
            prefs_val.append(key + "|||" + val)
908
        self.openvas_db.add_single_item(
909
            'internal/%s/scanprefs' % openvas_scan_id, prefs_val)
910
911
        # Store main_kbindex as global preference
912
        ov_maindbid = ('ov_maindbid|||%d' % self.main_kbindex)
913
        self.openvas_db.add_single_item(
914
            'internal/%s/scanprefs' % openvas_scan_id, [ov_maindbid])
915
916
        # Set target
917
        target_aux = ('TARGET|||%s' % target)
918
        self.openvas_db.add_single_item(
919
            'internal/%s/scanprefs' % openvas_scan_id, [target_aux])
920
        # Set port range
921
        port_range = ('port_range|||%s' % ports)
922
        self.openvas_db.add_single_item(
923
            'internal/%s/scanprefs' % openvas_scan_id, [port_range])
924
925
        # Set credentials
926
        credentials = self.get_scan_credentials(scan_id, target)
927
        if credentials:
928
            cred_prefs = self.build_credentials_as_prefs(credentials)
929
            self.openvas_db.add_single_item(
930
                'internal/%s/scanprefs' % openvas_scan_id, cred_prefs)
931
932
        # Set plugins to run
933
        nvts = self.get_scan_vts(scan_id)
934
        if nvts != '':
935
            nvts_list, nvts_params = self.process_vts(nvts)
936
           
937
            # Add nvts list
938
            separ = ';'
939
            plugin_list = 'plugin_set|||%s' % separ.join(nvts_list)
940
            self.openvas_db.add_single_item(
941
                'internal/%s/scanprefs' % openvas_scan_id, [plugin_list])
942
            # Add nvts parameters
943
            for elem in nvts_params:
944
                item = '%s|||%s' % (elem[0], elem[1])
945
                self.openvas_db.add_single_item(
946
                    'internal/%s/scanprefs' % openvas_scan_id, [item])
947
        else:
948
            self.openvas_db.release_db(self.main_kbindex)
949
            self.add_scan_error(scan_id, name='', host=target,
950
                                value='No VTS to run.')
951
            return 2
952
953
        # Create a general log entry about executing OpenVAS
954
        # It is important to send at least one result, otherwise
955
        # the host details won't be stored.
956
        self.add_scan_log(scan_id, host=target, name='OpenVAS summary',
957
                          value='An OpenVAS Scanner was started for %s.'
958
                          % target)
959
960
        self.add_scan_log(scan_id, host=target, name='KB location Found',
961
                          value='KB location path was found: %s.'
962
                          % self.openvas_db.db_address)
963
964
        self.add_scan_log(scan_id, host=target, name='Feed Update',
965
                          value='Feed version: %s.'
966
                          % self.nvti.get_feed_version())
967
968
        cmd = ['openvassd', '--scan-start', openvas_scan_id]
969
        try:
970
            result = subprocess.Popen(cmd, shell=False)
971
        except OSError:
972
            # the command is not available
973
            return False
974
975
        ovas_pid = result.pid
976
        logger.debug('pid = {0}'.format(ovas_pid))
977
        self.openvas_db.add_single_item('internal/ovas_pid', [ovas_pid])
978
979
        # Wait until the scanner starts and loads all the preferences.
980
        while self.openvas_db.get_single_item('internal/'+ openvas_scan_id) == 'new':
981
            time.sleep(1)
982
983
        no_id_found = False
984
        while True:
985
            time.sleep(3)
986
987
            # Check if the client stopped the whole scan
988
            if self.scan_is_stopped(openvas_scan_id):
989
                return 1
990
991
            ctx = self.openvas_db.kb_connect(self.main_kbindex)
992
            self.openvas_db.set_redisctx(ctx)
993
            dbs = self.openvas_db.get_list_item('internal/dbindex')
994
            for i in list(dbs):
995
                if i == self.main_kbindex:
996
                    continue
997
                self.openvas_db.select_kb(ctx, str(i), set_global=True)
998
                id_aux = self.openvas_db.get_single_item('internal/scan_id')
999
                if not id_aux:
1000
                    continue
1001
                if id_aux == openvas_scan_id:
1002
                    no_id_found = False
1003
                    self.get_openvas_timestamp_scan_host(scan_id, target)
1004
                    self.get_openvas_result(scan_id)
1005
                    self.get_openvas_status(scan_id, target)
1006
                    if self.scan_is_finished(openvas_scan_id):
1007
                        self.openvas_db.select_kb(
1008
                            ctx, str(self.main_kbindex), set_global=False)
1009
                        self.openvas_db.remove_list_item('internal/dbindex', i)
1010
                        self.openvas_db.release_db(i)
1011
1012
            # Scan end. No kb in use for this scan id
1013
            if no_id_found:
1014
                break
1015
            no_id_found = True
1016
1017
        # Delete keys from KB related to this scan task.
1018
        self.openvas_db.release_db(self.main_kbindex)
1019
        return 1
1020
1021
1022
def main():
1023
    """ OSP openvas main function. """
1024
    daemon_main('OSPD - openvas wrapper', OSPDopenvas)
1025