Completed
Push — master ( d6d671...21c533 )
by Juan José
13s
created

OSPDopenvas.get_dependencies_vt_as_xml_str()   A

Complexity

Conditions 4

Size

Total Lines 19
Code Lines 17

Duplication

Lines 19
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 17
dl 19
loc 19
rs 9.55
c 0
b 0
f 0
cc 4
nop 2
1
# -*- coding: utf-8 -*-
0 ignored issues
show
Coding Style introduced by
This module should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
2
# Description:
3
# Setup for the OSP OpenVAS Server
4
#
5
# Authors:
6
# Juan José Nicola <[email protected]>
7
#
8
# Copyright:
9
# Copyright (C) 2018 Greenbone Networks GmbH
10
#
11
# This program is free software; you can redistribute it and/or
12
# modify it under the terms of the GNU General Public License
13
# as published by the Free Software Foundation; either version 2
14
# of the License, or (at your option) any later version.
15
#
16
# This program is distributed in the hope that it will be useful,
17
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
# GNU General Public License for more details.
20
#
21
# You should have received a copy of the GNU General Public License
22
# along with this program; if not, write to the Free Software
23
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24
25
import subprocess
26
import time
27
import signal
28
import uuid
29
from lxml.etree import tostring, SubElement, Element
0 ignored issues
show
Bug introduced by
The name tostring does not seem to exist in module lxml.etree.
Loading history...
Bug introduced by
The name SubElement does not seem to exist in module lxml.etree.
Loading history...
Bug introduced by
The name Element does not seem to exist in module lxml.etree.
Loading history...
30
import psutil
31
32
from ospd.ospd import OSPDaemon, logger
33
from ospd.misc import main as daemon_main
34
from ospd.misc import target_str_to_list
35
from ospd_openvas import __version__
36
37
import ospd_openvas.nvticache as nvti
38
import ospd_openvas.openvas_db as openvas_db
39
40
OSPD_DESC = """
41
This scanner runs 'OpenVAS Scanner' to scan the target hosts.
42
43
OpenVAS (Open Vulnerability Assessment System) is a powerful scanner
44
for vulnerabilities in IT infrastrucutres. The capabilities include
45
unauthzenticated scanning as well as authneticated scanning for
46
various types of systems and services.
47
48
For more details about OpenVAS see the OpenVAS homepage:
49
http://www.openvas.org/
50
51
The current version of ospd-openvas is a simple frame, which sends
52
the server parameters to the Greenbone Vulnerability Manager (GVM) and checks
53
the existence of OpenVAS Scanner binary. But it can not run scans yet.
54
"""
55
56
MAIN_KBINDEX = None
57
58
OSPD_PARAMS = {
59
    'auto_enable_dependencies': {
60
        'type': 'boolean',
61
        'name': 'auto_enable_dependencies',
62
        'default': 1,
63
        'mandatory': 1,
64
        'description': 'Automatically enable the plugins that are depended on',
65
    },
66
    'cgi_path': {
67
        'type': 'string',
68
        'name': 'cgi_path',
69
        'default': '/cgi-bin:/scripts',
70
        'mandatory': 1,
71
        'description': 'Look for default CGIs in /cgi-bin and /scripts',
72
    },
73
    'checks_read_timeout': {
74
        'type': 'integer',
75
        'name': 'checks_read_timeout',
76
        'default': 5,
77
        'mandatory': 1,
78
        'description': ('Number  of seconds that the security checks will ' +
79
                        'wait for when doing a recv()'),
80
    },
81
    'drop_privileges': {
82
        'type': 'boolean',
83
        'name': 'drop_privileges',
84
        'default': 0,
85
        'mandatory': 1,
86
        'description': '',
87
    },
88
    'network_scan': {
89
        'type': 'boolean',
90
        'name': 'network_scan',
91
        'default': 0,
92
        'mandatory': 1,
93
        'description': '',
94
    },
95
    'non_simult_ports': {
96
        'type': 'string',
97
        'name': 'non_simult_ports',
98
        'default': '139, 445, 3389, Services/irc',
99
        'mandatory': 1,
100
        'description': ('Prevent to make two connections on the same given ' +
101
                        'ports at the same time.'),
102
    },
103
    'open_sock_max_attempts': {
104
        'type': 'integer',
105
        'name': 'open_sock_max_attempts',
106
        'default': 5,
107
        'mandatory': 0,
108
        'description': ('Number of unsuccessful retries to open the socket ' +
109
                        'before to set the port as closed.'),
110
    },
111
    'timeout_retry': {
112
        'type': 'integer',
113
        'name': 'timeout_retry',
114
        'default': 5,
115
        'mandatory': 0,
116
        'description': ('Number of retries when a socket connection attempt ' +
117
                        'timesout.'),
118
    },
119
    'optimize_test': {
120
        'type': 'integer',
121
        'name': 'optimize_test',
122
        'default': 5,
123
        'mandatory': 0,
124
        'description': ('By default, openvassd does not trust the remote ' +
125
                        'host banners.'),
126
    },
127
    'plugins_timeout': {
128
        'type': 'integer',
129
        'name': 'plugins_timeout',
130
        'default': 5,
131
        'mandatory': 0,
132
        'description': 'This is the maximum lifetime, in seconds of a plugin.',
133
    },
134
    'report_host_details': {
135
        'type': 'boolean',
136
        'name': 'report_host_details',
137
        'default': 1,
138
        'mandatory': 1,
139
        'description': '',
140
    },
141
    'safe_checks': {
142
        'type': 'boolean',
143
        'name': 'safe_checks',
144
        'default': 1,
145
        'mandatory': 1,
146
        'description': ('Disable the plugins with potential to crash ' +
147
                        'the remote services'),
148
    },
149
    'scanner_plugins_timeout': {
150
        'type': 'integer',
151
        'name': 'scanner_plugins_timeout',
152
        'default': 36000,
153
        'mandatory': 1,
154
        'description': 'Like plugins_timeout, but for ACT_SCANNER plugins.',
155
    },
156
    'time_between_request': {
157
        'type': 'integer',
158
        'name': 'time_between_request',
159
        'default': 0,
160
        'mandatory': 0,
161
        'description': ('Allow to set a wait time between two actions ' +
162
                        '(open, send, close).'),
163
    },
164
    'unscanned_closed': {
165
        'type': 'boolean',
166
        'name': 'unscanned_closed',
167
        'default': 1,
168
        'mandatory': 1,
169
        'description': '',
170
    },
171
    'unscanned_closed_udp': {
172
        'type': 'boolean',
173
        'name': 'unscanned_closed_udp',
174
        'default': 1,
175
        'mandatory': 1,
176
        'description': '',
177
    },
178
    'use_mac_addr': {
179
        'type': 'boolean',
180
        'name': 'use_mac_addr',
181
        'default': 0,
182
        'mandatory': 0,
183
        'description': 'To test the local network. ' +
184
                       'Hosts will be referred to by their MAC address.',
185
    },
186
    'vhosts': {
187
        'type': 'string',
188
        'name': 'vhosts',
189
        'default': '',
190
        'mandatory': 0,
191
        'description': '',
192
    },
193
    'vhosts_ip': {
194
        'type': 'string',
195
        'name': 'vhosts_ip',
196
        'default': '',
197
        'mandatory': 0,
198
        'description': '',
199
    },
200
}
201
202
203 View Code Duplication
class OSPDopenvas(OSPDaemon):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
best-practice introduced by
Too many public methods (32/20)
Loading history...
204
205
    """ Class for ospd-openvas daemon. """
206
207
    def __init__(self, certfile, keyfile, cafile):
208
        """ Initializes the ospd-openvas daemon's internal data. """
209
210
        super(OSPDopenvas, self).__init__(certfile=certfile, keyfile=keyfile,
211
                                          cafile=cafile)
212
        self.server_version = __version__
213
        self.scanner_info['name'] = 'openvassd'
214
        self.scanner_info['version'] = ''  # achieved during self.check()
215
        self.scanner_info['description'] = OSPD_DESC
216
        for name, param in OSPD_PARAMS.items():
217
            self.add_scanner_param(name, param)
218
219
        if openvas_db.db_init() is False:
220
            logger.error('OpenVAS Redis Error: Not possible '
221
                         'to find db_connection.')
222
            raise Exception
223
224
        self.pending_feed = None
225
        ctx = openvas_db.db_find(nvti.NVTICACHE_STR)
226
        if not ctx:
227
            self.redis_nvticache_init()
228
            ctx = openvas_db.db_find(nvti.NVTICACHE_STR)
229
        openvas_db.set_global_redisctx(ctx)
230
        self.load_vts()
231
232
    def parse_param(self):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
233
        """ Set OSPD_PARAMS with the params taken from the openvas_scanner. """
234
        global OSPD_PARAMS
0 ignored issues
show
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
235
        bool_dict = {'no': 0, 'yes': 1}
236
237
        result = subprocess.check_output(['openvassd', '-s'],
238
                                         stderr=subprocess.STDOUT)
239
        result = result.decode('ascii')
240
        param_list = dict()
241
        for conf in result.split('\n'):
242
            elem = conf.split('=')
243
            if len(elem) == 2:
244
                value = str.strip(elem[1])
245
                if str.strip(elem[1]) in bool_dict:
246
                    value = bool_dict[value]
247
                param_list[str.strip(elem[0])] = value
248
        for elem in OSPD_PARAMS:
249
            if elem in param_list:
250
                OSPD_PARAMS[elem]['default'] = param_list[elem]
251
252
    def redis_nvticache_init(self):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
253
        """ Loads NVT's metadata into Redis DB. """
254
        try:
255
            logger.debug('Loading NVTs in Redis DB')
256
            subprocess.check_call(['openvassd', '-C'])
257
        except subprocess.CalledProcessError as err:
258
            logger.error('OpenVAS Scanner failed to load NVTs.')
259
            raise err
260
261
    def check_feed(self):
262
        """ Check if there is a feed update. Wait until all the running
263
        scans finished. Set a flag to anounce there is a pending feed update,
264
        which avoid to start a new scan.
265
        """
266
        _running_scan = False
267
        for scan_id in self.scan_processes:
268
            if self.scan_processes[scan_id].is_alive():
269
                _running_scan = True
270
271
        if self.pending_feed:
272
            _pending_feed = True
273
        else:
274
            _pending_feed = self.get_vts_version() != nvti.get_feed_version()
0 ignored issues
show
Bug introduced by
The Instance of OSPDopenvas does not seem to have a member named get_vts_version.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
275
276
        if _running_scan and _pending_feed:
277
            if not self.pending_feed:
278
                self.pending_feed = True
279
                logger.debug(
280
                    'There is a running scan. Therefore the feed '
281
                    'update will be performed later.')
282
        elif not _running_scan and _pending_feed:
283
            self.vts = dict()
284
            self.load_vts()
285
286
    def scheduler(self):
287
        """This method is called periodically to run tasks."""
288
        self.check_feed()
289
290
    def load_vts(self):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (29/15).
Loading history...
291
        """ Load the NVT's metadata into the vts
292
        global  dictionary. """
293
        logger.debug('Loading vts in memory.')
294
        oids = dict(nvti.get_oids())
295
        for filename, vt_id in oids.items():
0 ignored issues
show
Unused Code introduced by
The variable filename seems to be unused.
Loading history...
296
            _vt_params = nvti.get_nvt_params(vt_id)
297
            _vt_refs = nvti.get_nvt_refs(vt_id)
298
            _custom = nvti.get_nvt_metadata(vt_id)
299
            _name = _custom.pop('name')
300
            _vt_creation_time = _custom.pop('creation_date')
301
            _vt_modification_time = _custom.pop('last_modification')
302
303
            _summary=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
304
            _impact=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
305
            _affected=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
306
            _insight=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
307
            _solution=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
308
            _solution_t=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
309
            _vuldetect=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
310
            _qod_t=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
311
            _qod_v=None
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
312
313
            if 'summary' in _custom:
314
                _summary  = _custom.pop('summary')
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
315
            if 'impact' in _custom:
316
                _impact   = _custom.pop('impact')
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
317
            if 'affected' in _custom:
318
                _affected = _custom.pop('affected')
319
            if 'insight' in _custom :
0 ignored issues
show
Coding Style introduced by
No space allowed before :
Loading history...
320
                _insight  = _custom.pop('insight')
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
321
            if 'solution' in _custom:
322
                _solution  = _custom.pop('solution')
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
323
                if 'solution_type' in _custom:
324
                    _solution_t  = _custom.pop('solution_type')
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
325
326
            if 'vuldetect' in _custom:
327
                _vuldetect  = _custom.pop('vuldetect')
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
328
            if 'qod_type' in _custom:
329
                _qod_t  = _custom.pop('qod_type')
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
330
            elif 'qod' in _custom:
331
                _qod_v  = _custom.pop('qod')
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
332
333
            _severity = dict()
334
            if 'severity_base_vector' in _custom:
335
                _severity_vector = _custom.pop('severity_base_vector')
336
            else:
337
                _severity_vector = _custom.pop('cvss_base_vector')
338
            _severity['severity_base_vector'] = _severity_vector
339
            if 'severity_type' in _custom:
340
                _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...
Comprehensibility Best Practice introduced by
Undefined variable 'custom'
Loading history...
341
            else:
342
                _severity_type = 'cvss_base_v2'
343
            _severity['severity_type'] = _severity_type
344
            if 'severity_origin' in _custom:
345
                _severity['severity_origin'] = _custom.pop('severity_origin')
346
347
            _vt_dependencies = list()
348
            if 'dependencies' in _custom:
349
                _deps = _custom.pop('dependencies')
350
                _deps_list = _deps.split(', ')
351
                for dep in _deps_list:
352
                    _vt_dependencies.append(oids.get('filename:' + dep))
353
354
            ret = self.add_vt(
355
                vt_id,
356
                name=_name,
357
                vt_params=_vt_params,
358
                vt_refs=_vt_refs,
359
                custom=_custom,
360
                vt_creation_time=_vt_creation_time,
361
                vt_modification_time=_vt_modification_time,
362
                vt_dependencies=_vt_dependencies,
363
                summary=_summary,
364
                impact=_impact,
365
                affected=_affected,
366
                insight=_insight,
367
                solution=_solution,
368
                solution_t=_solution_t,
369
                detection=_vuldetect,
370
                qod_t=_qod_t,
371
                qod_v=_qod_v,
372
                severities=_severity
373
            )
374
            if ret == -1:
375
                logger.info("Dupplicated VT with OID: {0}".format(vt_id))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
376
            if ret == -2:
377
                logger.info("{0}: Invalid OID.".format(vt_id))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
378
379
        _feed_version = nvti.get_feed_version()
380
        self.set_vts_version(vts_version=_feed_version)
0 ignored issues
show
Bug introduced by
The Instance of OSPDopenvas does not seem to have a member named set_vts_version.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
381
        self.pending_feed = False
382
        logger.debug('Finish loading up vts.')
383
384
    @staticmethod
385
    def get_custom_vt_as_xml_str(vt_id, custom):
386
        """ Return an xml element with custom metadata formatted as string."""
387
388
        nvt = Element('vt')
389
        for key, val in custom.items():
390
            xml_key = SubElement(nvt, key)
391
            xml_key.text = val
392
393
        itera = nvt.iter()
394
        metadata = ''
395
        for elem in itera:
396
            if elem.tag != 'vt':
397
                metadata += (tostring(elem).decode('utf-8'))
398
        return metadata
399
400
    @staticmethod
401
    def get_severities_vt_as_xml_str(vt_id, severities):
402
        """ Return an xml element with severities as string."""
403
404
        _severity = Element('severity')
405
        if 'severity_base_vector' in severities:
406
            _severity.text = severities.pop('severity_base_vector')
407
        if 'severity_origin' in severities:
408
            _severity.set('origin', severities.pop('severity_origin'))
409
        if 'severity_type' in severities:
410
            _severity.set('type', severities.pop('severity_type'))
411
412
        return tostring(_severity).decode('utf-8')
413
414
    @staticmethod
415
    def get_params_vt_as_xml_str(vt_id, vt_params):
416
        """ Return an xml element with params formatted as string."""
417
        vt_params_xml = Element('vt_params')
418
        for prefs in vt_params.items():
419
            vt_param = Element('vt_param')
420
            vt_param.set('type', prefs[1]['type'])
421
            vt_param.set('id', prefs[0])
422
            xml_name = SubElement(vt_param, 'name')
423
            xml_name.text = prefs[1]['name']
424
            if prefs[1]['default']:
425
                xml_def = SubElement(vt_param, 'default')
426
                xml_def.text = prefs[1]['default']
427
            vt_params_xml.append(vt_param)
428
429
        params_list = vt_params_xml.findall("vt_param")
430
        params = ''
431
        for param in params_list:
432
            params += (tostring(param).decode('utf-8'))
433
        return params
434
435
    @staticmethod
436
    def get_refs_vt_as_xml_str(vt_id, vt_refs):
437
        """ Return an xml element with references formatted as string."""
438
        vt_refs_xml = Element('vt_refs')
439
        for ref_type, ref_values in vt_refs.items():
440
            for value in ref_values:
441
                vt_ref = Element('ref')
442
                if ref_type == "xref" and value:
443
                    for xref in value.split(', '):
444
                        try:
445
                            _type, _id = xref.split(':', 1)
446
                        except ValueError:
447
                            logger.error(
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
448
                                'Not possible to parse xref %s for vt %s' % (
449
                                    xref, vt_id))
450
                            continue
451
                        vt_ref.set('type', _type.lower())
452
                        vt_ref.set('id', _id)
453
                elif value:
454
                    vt_ref.set('type', ref_type.lower())
455
                    vt_ref.set('id', value)
456
                else:
457
                    continue
458
                vt_refs_xml.append(vt_ref)
459
460
        refs_list = vt_refs_xml.findall("ref")
461
        refs = ''
462
        for ref in refs_list:
463
            refs += (tostring(ref).decode('utf-8'))
464
        return refs
465
466
    @staticmethod
467
    def get_dependencies_vt_as_xml_str(vt_id, dep_list):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'get_dependencies_vt_as_xml_str' method
Loading history...
468
        """ Return  an xml element with dependencies as string."""
469
        vt_deps_xml = Element('vt_deps')
470
        for dep in dep_list:
471
            _vt_dep = Element('dependency')
472
            try:
473
                _vt_dep.set('vt_id', dep)
474
            except TypeError:
475
                logger.error('Not possible to add dependency %s for vt %s' % (
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
476
                    dep, vt_id))
477
                continue
478
            vt_deps_xml.append(_vt_dep)
479
480
        _deps_list = vt_deps_xml.findall("dependency")
481
        deps = ''
482
        for _dep in _deps_list:
483
            deps += (tostring(_dep).decode('utf-8'))
484
        return deps
485
486
    @staticmethod
487
    def get_creation_time_vt_as_xml_str(vt_id, creation_time):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'get_creation_time_vt_as_xml_str' method
Loading history...
488
        """ Return creation time as string."""
489
        return creation_time
490
491
    @staticmethod
492
    def get_modification_time_vt_as_xml_str(vt_id, modification_time):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'get_modification_time_vt_as_xml_str' method
Loading history...
493
        """ Return modification time as string."""
494
        return modification_time
495
496
    @staticmethod
497
    def get_summary_vt_as_xml_str(vt_id, summary):
498
        """ Return summary as string."""
499
        _summary = Element('summary')
500
        _summary.text = summary
501
        return tostring(_summary).decode('utf-8')
502
503
    @staticmethod
504
    def get_impact_vt_as_xml_str(vt_id, impact):
505
        """ Return impact as string."""
506
        _impact = Element('impact')
507
        _impact.text = impact
508
        return tostring(_impact).decode('utf-8')
509
510
    @staticmethod
511
    def get_affected_vt_as_xml_str(vt_id, affected):
512
        """ Return affected as string."""
513
        _affected = Element('affected')
514
        _affected.text = affected
515
        return tostring(_affected).decode('utf-8')
516
517
    @staticmethod
518
    def get_insight_vt_as_xml_str(vt_id, insight):
519
        """ Return insight as string."""
520
        _insight = Element('insight')
521
        _insight.text = insight
522
        return tostring(_insight).decode('utf-8')
523
524
    @staticmethod
525
    def get_solution_vt_as_xml_str(vt_id, solution, solution_type=None):
526
        """ Return solution as string."""
527
        _solution = Element('solution')
528
        _solution.text = solution
529
        if solution_type:
530
            _solution.set('type', solution_type)
531
        return tostring(_solution).decode('utf-8')
532
533
    @staticmethod
534
    def get_detection_vt_as_xml_str(vt_id, vuldetect=None, qod_type=None, qod=None):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'get_detection_vt_as_xml_str' method
Loading history...
535
        """ Return detection as string."""
536
        _detection = Element('detection')
537
        if vuldetect:
538
            _detection.text = vuldetect
539
        if qod_type:
540
            _detection.set('qod_type', qod_type)
541
        elif qod:
542
            _detection.set('qod', qod)
543
544
        return tostring(_detection).decode('utf-8')
545
546
    def check(self):
547
        """ Checks that openvassd command line tool is found and
548
        is executable. """
549
        try:
550
            result = subprocess.check_output(['openvassd', '-V'],
551
                                             stderr=subprocess.STDOUT)
552
            result = result.decode('ascii')
553
        except OSError:
554
            # The command is not available
555
            return False
556
557
        if result is None:
558
            return False
559
560
        version = result.split('\n')
561
        if version[0].find('OpenVAS') < 0:
562
            return False
563
564
        self.parse_param()
565
        self.scanner_info['version'] = version[0]
566
567
        return True
568
569
    def update_progress(self, scan_id, target, msg):
570
        """ Calculate porcentage and update the scan status
571
        for the progress bar. """
572
        host_progress_dict = dict()
573
        prog = str.split(msg, '/')
574
        if float(prog[1]) == 0:
575
            return
576
        host_prog = (float(prog[0]) / float(prog[1])) * 100
577
        host_progress_dict[target] = host_prog
578
        total_host = len(target_str_to_list(target))
579
        self.set_scan_target_progress(scan_id, target,
580
                                      sum(host_progress_dict.values()) / total_host)
581
582
    def get_openvas_status(self, scan_id, target):
583
        """ Get all status entries from redis kb. """
584
        res = openvas_db.get_status()
585
        while res:
586
            self.update_progress(scan_id, target, res)
587
            res = openvas_db.get_status()
588
589
    def get_openvas_result(self, scan_id):
590
        """ Get all result entries from redis kb. """
591
        res = openvas_db.get_result()
592
        ctx = openvas_db.db_find(nvti.NVTICACHE_STR)
0 ignored issues
show
Unused Code introduced by
The variable ctx seems to be unused.
Loading history...
593
        while res:
594
            msg = res.split('|||')
595
            host_aux = openvas_db.item_get_single('internal/ip')
596
            roid = msg[3]
597
598
            rqod = ''
599
            if self.vts[roid].get('qod_type'):
600
                qod_t = self.vts[roid].get('qod_type')
601
                rqod = nvti.QoD_TYPES[qod_t]
602
            elif self.vts[roid].get('qod'):
603
                rqod = self.vts[roid].get('qod')
604
605
            rseverity = self.vts[roid]['severities'].get('cvss_base')
606
            rname = self.vts[roid].get('name')
607
608
            if msg[0] == 'ERRMSG':
609
                self.add_scan_error(scan_id, host=host_aux,
610
                                    name=rname, value=msg[4], port=msg[2])
611
            if msg[0] == 'LOG':
612
                self.add_scan_log(scan_id, host=host_aux, name=rname,
613
                                  value=msg[4], port=msg[2], qod=rqod,
614
                                  test_id=roid)
615
            if msg[0] == 'HOST_DETAIL':
616
                self.add_scan_log(scan_id, host=host_aux, name=rname,
617
                                  value=msg[4])
618
            if msg[0] == 'ALARM':
619
                self.add_scan_alarm(scan_id, host=host_aux, name=rname,
620
                                    value=msg[4], port=msg[2],
621
                                    test_id=roid, severity=rseverity,
622
                                    qod=rqod)
623
            res = openvas_db.get_result()
624
625
    def get_openvas_timestamp_scan_host(self, scan_id, target):
626
        """ Get start and end timestamp of a host scan from redis kb. """
627
        timestamp = openvas_db.get_host_scan_scan_end_time()
628
        if timestamp:
629
            self.add_scan_log(scan_id, host=target, name='HOST_END',
630
                              value=timestamp)
631
            return
632
        timestamp = openvas_db.get_host_scan_scan_start_time()
633
        if timestamp:
634
            self.add_scan_log(scan_id, host=target, name='HOST_START',
635
                              value=timestamp)
636
            return
637
638
    def scan_is_finished(self, scan_id):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
639
        """ Check if the scan has finished. """
640
        status = openvas_db.item_get_single(('internal/%s' % scan_id))
641
        return status == 'finished'
642
643
    def scan_is_stopped(self, scan_id):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
644
        """ Check if the parent process has received the stop_scan order.
645
        @in scan_id: ID to identify the scan to be stopped.
646
        @return 1 if yes, None in other case.
647
        """
648
        ctx = openvas_db.kb_connect(dbnum=MAIN_KBINDEX)
649
        openvas_db.set_global_redisctx(ctx)
650
        status = openvas_db.item_get_single(('internal/%s' % scan_id))
651
        return status == 'stop_all'
652
653
    @staticmethod
654
    def stop_scan(global_scan_id):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'stop_scan' method
Loading history...
655
        """ Set a key in redis to indicate the wrapper is stopped.
656
        It is done through redis because it is a new multiprocess
657
        instance and it is not possible to reach the variables
658
        of the grandchild process. Send SIGUSR2 to openvas to stop
659
        each running scan."""
660
        ctx = openvas_db.kb_connect()
661
        for current_kbi in range(0, openvas_db.MAX_DBINDEX):
662
            ctx.execute_command('SELECT '+ str(current_kbi))
663
            openvas_db.set_global_redisctx(ctx)
664
            scan_id = openvas_db.item_get_single(
665
                ('internal/%s/globalscanid' % global_scan_id))
666
            if scan_id:
667
                openvas_db.item_set_single(('internal/%s' % scan_id),
668
                                           ['stop_all', ])
669
                ovas_pid = openvas_db.item_get_single('internal/ovas_pid')
670
                parent = psutil.Process(int(ovas_pid))
671
                openvas_db.release_db(current_kbi)
672
                parent.send_signal(signal.SIGUSR2)
673
                logger.debug('Stopping process: {0}'.format(parent))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
674
675
    def get_vts_in_groups(self, ctx, filters):
0 ignored issues
show
Unused Code introduced by
The argument ctx seems to be unused.
Loading history...
676
        """ Return a list of vts which match with the given filter.
677
678
        @input filters A list of filters. Each filter has key, operator and
679
                       a value. They are separated by a space.
680
                       Supported keys: family
681
        @return Return a list of vts which match with the given filter.
682
        """
683
        vts_list = list()
684
        families = dict()
685
        oids = nvti.get_oids()
686
        for filename, oid in oids:
0 ignored issues
show
Unused Code introduced by
The variable filename seems to be unused.
Loading history...
687
            family = self.vts[oid]['custom'].get('family')
688
            if family not in families:
689
                families[family] = list()
690
            families[family].append(oid)
691
692
        for elem in filters:
693
            key, value = elem.split('=')
694
            if key == 'family' and value in families:
695
                vts_list.extend(families[value])
696
        return vts_list
697
698
    def get_vt_param_type(self, vtid, vt_param_id):
699
        """ Return the type of the vt parameter from the vts dictionary. """
700
        vt_params_list = self.vts[vtid].get("vt_params")
701
        return vt_params_list[vt_param_id]["type"]
702
703
    @staticmethod
704
    def check_param_type(vt_param_value, param_type):
705
        """ Check if the value of a vt parameter matches with
706
        the type founded.
707
        """
708
        if (param_type in ['entry',
709
                           'file',
710
                           'password',
711
                           'radio',
712
                           'sshlogin', ] and isinstance(vt_param_value, str)):
713
            return None
714
        elif (param_type == 'checkbox' and
715
              (vt_param_value == 'yes' or vt_param_value == 'no')):
716
            return None
717
        elif param_type == 'integer':
718
            try:
719
                int(vt_param_value)
720
            except ValueError:
721
                return 1
722
            return None
723
724
        return 1
725
726
    def process_vts(self, vts):
727
        """ Add single VTs and their parameters. """
728
        vts_list = []
729
        vts_params = []
730
        vtgroups = vts.pop('vt_groups')
731
732
        ctx = openvas_db.db_find(nvti.NVTICACHE_STR)
733
        openvas_db.set_global_redisctx(ctx)
734
        if vtgroups:
735
            vts_list = self.get_vts_in_groups(ctx, vtgroups)
736
737
        for vtid, vt_params in vts.items():
738
            vts_list.append(vtid)
739
            nvt_name = self.vts[vtid].get('name')
740
            for vt_param_id, vt_param_value in vt_params.items():
741
                param_type = self.get_vt_param_type(vtid, vt_param_id)
742
                if vt_param_id == 'timeout':
743
                    type_aux = 'integer'
744
                else:
745
                    type_aux = param_type
746
                if self.check_param_type(vt_param_value, type_aux):
747
                    logger.debug('Expected {} type for parameter value {}'
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
748
                                 .format(type_aux, str(vt_param_value)))
749
                param = ["{0}[{1}]:{2}".format(nvt_name, param_type,
750
                                               vt_param_id),
751
                         str(vt_param_value)]
752
                vts_params.append(param)
753
        return vts_list, vts_params
754
755
    @staticmethod
756
    def build_credentials_as_prefs(credentials):
757
        """ Parse the credential dictionary.
758
        @param credentials: Dictionary with the credentials.
759
760
        @return A list with the credentials in string format to be
761
                added to the redis KB.
762
        """
763
        cred_prefs_list = []
764
        for credential in credentials.items():
765
            service = credential[0]
766
            cred_params = credentials.get(service)
767
            cred_type = cred_params.get('type', '')
768
            username = cred_params.get('username', '')
769
            password = cred_params.get('password', '')
770
771
            if service == 'ssh':
772
                port = cred_params.get('port', '')
773
                cred_prefs_list.append('auth_port_ssh|||' +
774
                                       '{0}'.format(port))
775
                cred_prefs_list.append('SSH Authorization[entry]:SSH login ' +
776
                                       'name:|||{0}'.format(username))
777
                if cred_type == 'up':
778
                    cred_prefs_list.append('SSH Authorization[password]:' +
779
                                           'SSH password (unsafe!):|||' +
780
                                           '{0}'.format(password))
781
                else:
782
                    private = cred_params.get('private', '')
783
                    cred_prefs_list.append('SSH Authorization[password]:' +
784
                                           'SSH key passphrase:|||' +
785
                                           '{0}'.format(password))
786
                    cred_prefs_list.append('SSH Authorization[file]:' +
787
                                           'SSH private key:|||' +
788
                                           '{0}'.format(private))
789
            if service == 'smb':
790
                cred_prefs_list.append('SMB Authorization[entry]:SMB login:' +
791
                                       '|||{0}'.format(username))
792
                cred_prefs_list.append('SMB Authorization[password]:' +
793
                                       'SMB password :|||' +
794
                                       '{0}'.format(password))
795
            if service == 'esxi':
796
                cred_prefs_list.append('ESXi Authorization[entry]:ESXi login ' +
797
                                       'name:|||{0}'.format(username))
798
                cred_prefs_list.append('ESXi Authorization[password]:' +
799
                                       'ESXi login password:|||' +
800
                                       '{0}'.format(password))
801
802
            if service == 'snmp':
803
                community = cred_params.get('community', '')
804
                auth_algorithm = cred_params.get('auth_algorithm', '')
805
                privacy_password = cred_params.get('privacy_password', '')
806
                privacy_algorithm = cred_params.get('privacy_algorithm', '')
807
808
                cred_prefs_list.append('SNMP Authorization[password]:' +
809
                                       'SNMP Community:' +
810
                                       '{0}'.format(community))
811
                cred_prefs_list.append('SNMP Authorization[entry]:' +
812
                                       'SNMPv3 Username:' +
813
                                       '{0}'.format(username))
814
                cred_prefs_list.append('SNMP Authorization[password]:' +
815
                                       'SNMPv3 Password:' +
816
                                       '{0}'.format(password))
817
                cred_prefs_list.append('SNMP Authorization[radio]:' +
818
                                       'SNMPv3 Authentication Algorithm:' +
819
                                       '{0}'.format(auth_algorithm))
820
                cred_prefs_list.append('SNMP Authorization[password]:' +
821
                                       'SNMPv3 Privacy Password:' +
822
                                       '{0}'.format(privacy_password))
823
                cred_prefs_list.append('SNMP Authorization[radio]:' +
824
                                       'SNMPv3 Privacy Algorithm:' +
825
                                       '{0}'.format(privacy_algorithm))
826
827
        return cred_prefs_list
828
829
    def exec_scan(self, scan_id, target):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (30/15).
Loading history...
830
        """ Starts the OpenVAS scanner for scan_id scan. """
831
        if self.pending_feed:
832
            logger.info(
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
833
                '%s: There is a pending feed update. '
834
                'The scan can not be started.' % scan_id)
835
            self.add_scan_error(
836
                scan_id, name='', host=target,
837
                value=('It was not possible to start the scan,'
838
                'because a pending feed update. Please try later'))
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 7 spaces).
Loading history...
839
            return 2
840
841
        global MAIN_KBINDEX
0 ignored issues
show
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
842
        ports = self.get_scan_ports(scan_id, target)
843
        if not ports:
844
            self.add_scan_error(scan_id, name='', host=target,
845
                                value='No port list defined.')
846
            return 2
847
848
        # Get scan options
849
        options = self.get_scan_options(scan_id)
850
        prefs_val = []
851
        ctx = openvas_db.kb_new()
852
        openvas_db.set_global_redisctx(ctx)
853
        MAIN_KBINDEX = openvas_db.DB_INDEX
854
855
        # To avoid interference between scan process during a parallel scanning
856
        # new uuid is used internally for each scan.
857
        openvas_scan_id = str(uuid.uuid4())
858
        openvas_db.item_add_single(('internal/%s' % openvas_scan_id), ['new', ])
859
        openvas_db.item_add_single(('internal/%s/globalscanid' % scan_id), [openvas_scan_id, ])
860
861
        # Set scan preferences
862
        for item in options.items():
863
            item_type = ''
864
            if item[0] in OSPD_PARAMS:
865
                item_type = OSPD_PARAMS[item[0]].get('type')
866
            if item_type == 'boolean' and item[1] == 1:
867
                val = 'yes'
868
            else:
869
                val = str(item[1])
870
            prefs_val.append(item[0] + "|||" + val)
871
        openvas_db.item_add_single(str('internal/%s/scanprefs' % (openvas_scan_id)),
872
                                   prefs_val)
873
874
        # Store MAIN_KBINDEX as global preference
875
        ov_maindbid = ('ov_maindbid|||%d' % MAIN_KBINDEX)
876
        openvas_db.item_add_single(('internal/%s/scanprefs' % openvas_scan_id),
877
                                   [ov_maindbid, ])
878
879
        # Set target
880
        target_aux = ('TARGET|||%s' % target)
881
        openvas_db.item_add_single(('internal/%s/scanprefs' % openvas_scan_id),
882
                                   [target_aux, ])
883
        # Set port range
884
        port_range = ('port_range|||%s' % ports)
885
        openvas_db.item_add_single(('internal/%s/scanprefs' % openvas_scan_id),
886
                                   [port_range, ])
887
888
        # Set credentials
889
        credentials = self.get_scan_credentials(scan_id, target)
890
        if credentials:
891
            cred_prefs = self.build_credentials_as_prefs(credentials)
892
            openvas_db.item_add_single(str('internal/%s/scanprefs' % openvas_scan_id),
893
                                       cred_prefs)
894
895
        # Set plugins to run
896
        nvts = self.get_scan_vts(scan_id)
897
        if nvts != '':
898
            nvts_list, nvts_params = self.process_vts(nvts)
899
            # Select the scan KB again.
900
            ctx.execute_command('SELECT '+ str(MAIN_KBINDEX))
901
            openvas_db.set_global_redisctx(ctx)
902
            # Add nvts list
903
            separ = ';'
904
            plugin_list = ('plugin_set|||%s' % separ.join(nvts_list))
905
            openvas_db.item_add_single(('internal/%s/scanprefs' % openvas_scan_id),
906
                                       [plugin_list, ])
907
            # Add nvts parameters
908
            for elem in nvts_params:
909
                item = ('%s|||%s' % (elem[0], elem[1]))
910
                openvas_db.item_add_single(('internal/%s/scanprefs' % openvas_scan_id),
911
                                           [item, ])
912
        else:
913
            openvas_db.release_db(MAIN_KBINDEX)
914
            self.add_scan_error(scan_id, name='', host=target,
915
                                value='No VTS to run.')
916
            return 2
917
918
        # Create a general log entry about executing OpenVAS
919
        # It is important to send at least one result, otherwise
920
        # the host details won't be stored.
921
        self.add_scan_log(scan_id, host=target, name='OpenVAS summary',
922
                          value='An OpenVAS Scanner was started for %s.'
923
                          % target)
924
925
        self.add_scan_log(scan_id, host=target, name='KB location Found',
926
                          value='KB location path was found: %s.'
927
                          % openvas_db.DB_ADDRESS)
928
929
        self.add_scan_log(scan_id, host=target, name='Feed Update',
930
                          value='Feed version: %s.'
931
                          % nvti.get_feed_version())
932
933
        cmd = ['openvassd', '--scan-start', openvas_scan_id]
934
        try:
935
            result = subprocess.Popen(cmd, shell=False)
936
        except OSError:
937
            # the command is not available
938
            return False
939
940
        ovas_pid = result.pid
941
        logger.debug('pid = {0}'.format(ovas_pid))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
942
        openvas_db.item_add_single(('internal/ovas_pid'), [ovas_pid, ])
943
944
        # Wait until the scanner starts and loads all the preferences.
945
        while openvas_db.item_get_single('internal/'+ openvas_scan_id) == 'new':
946
            time.sleep(1)
947
948
        no_id_found = False
949
        while True:
950
            time.sleep(3)
951
952
            # Check if the client stopped the whole scan
953
            if self.scan_is_stopped(openvas_scan_id):
954
                return 1
955
956
            ctx = openvas_db.kb_connect(MAIN_KBINDEX)
957
            openvas_db.set_global_redisctx(ctx)
958
            dbs = openvas_db.item_get_set('internal/dbindex')
959
            for i in list(dbs):
960
                if i == MAIN_KBINDEX:
961
                    continue
962
                ctx.execute_command('SELECT '+ str(i))
963
                openvas_db.set_global_redisctx(ctx)
964
                id_aux = ctx.execute_command('srandmember internal/scan_id')
965
                if not id_aux:
966
                    continue
967
                if id_aux == openvas_scan_id:
968
                    no_id_found = False
969
                    self.get_openvas_timestamp_scan_host(scan_id, target)
970
                    self.get_openvas_result(scan_id)
971
                    self.get_openvas_status(scan_id, target)
972
                    if self.scan_is_finished(openvas_scan_id):
973
                        ctx.execute_command('SELECT '+ str(MAIN_KBINDEX))
974
                        openvas_db.remove_set_member('internal/dbindex', i)
975
                        openvas_db.release_db(i)
976
977
            # Scan end. No kb in use for this scan id
978
            if no_id_found:
979
                break
980
            no_id_found = True
981
982
        # Delete keys from KB related to this scan task.
983
        openvas_db.release_db(MAIN_KBINDEX)
984
        return 1
985
986
987
def main():
988
    """ OSP openvas main function. """
989
    daemon_main('OSPD - openvas wrapper', OSPDopenvas)
990