Completed
Push — master ( 8082e9...516ce7 )
by
unknown
17s queued 13s
created

ospd.ospd.OSPDaemon.handle_delete_scan_command()   A

Complexity

Conditions 4

Size

Total Lines 18
Code Lines 12

Duplication

Lines 18
Ratio 100 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nop 2
dl 18
loc 18
rs 9.8
c 0
b 0
f 0
1
# Copyright (C) 2014-2018 Greenbone Networks GmbH
2
#
3
# SPDX-License-Identifier: GPL-2.0-or-later
4
#
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU General Public License
7
# as published by the Free Software Foundation; either version 2
8
# of the License, or (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18
19
# pylint: disable=too-many-lines
20
21
""" OSP Daemon core class.
22
"""
23
24
25
# This is needed for older pythons as our current module is called the same
26
# as the package we are in ...
27
# Another solution would be to rename that file.
28
from __future__ import absolute_import
29
30
import logging
31
import select
32
import socket
33
import ssl
34
import multiprocessing
35
import re
36
import time
37
import os
38
39
from xml.etree.ElementTree import Element, SubElement
40
41
import defusedxml.ElementTree as secET
42
43
from ospd import __version__
44
from ospd.vtfilter import VtsFilter
45
from ospd.misc import ScanCollection, ResultType, target_str_to_list
46
from ospd.misc import resolve_hostname, valid_uuid
47
from ospd.misc import ScanStatus
48
from ospd.xml import simple_response_str, get_result_xml
49
from ospd.error import OSPDError
50
51
logger = logging.getLogger(__name__)
52
53
PROTOCOL_VERSION = "1.2"
54
55
SCHEDULER_CHECK_PERIOD = 5  # in seconds
56
57
BASE_SCANNER_PARAMS = {
58
    'debug_mode': {
59
        'type': 'boolean',
60
        'name': 'Debug Mode',
61
        'default': 0,
62
        'mandatory': 0,
63
        'description': 'Whether to get extra scan debug information.',
64
    },
65
    'dry_run': {
66
        'type': 'boolean',
67
        'name': 'Dry Run',
68
        'default': 0,
69
        'mandatory': 0,
70
        'description': 'Whether to dry run scan.',
71
    },
72
}
73
74
COMMANDS_TABLE = {
75
    'start_scan': {
76
        'description': 'Start a new scan.',
77
        'attributes': {
78
            'target': 'Target host to scan',
79
            'ports': 'Ports list to scan',
80
            'scan_id': 'Optional UUID value to use as scan ID',
81
            'parallel': 'Optional nummer of parallel target to scan',
82
        },
83
        'elements': None,
84
    },
85
    'stop_scan': {
86
        'description': 'Stop a currently running scan.',
87
        'attributes': {'scan_id': 'ID of scan to stop.'},
88
        'elements': None,
89
    },
90
    'help': {
91
        'description': 'Print the commands help.',
92
        'attributes': {'format': 'Help format. Could be text or xml.'},
93
        'elements': None,
94
    },
95
    'get_scans': {
96
        'description': 'List the scans in buffer.',
97
        'attributes': {
98
            'scan_id': 'ID of a specific scan to get.',
99
            'details': 'Whether to return the full scan report.',
100
        },
101
        'elements': None,
102
    },
103
    'get_vts': {
104
        'description': 'List of available vulnerability tests.',
105
        'attributes': {
106
            'vt_id': 'ID of a specific vulnerability test to get.',
107
            'filter': 'Optional filter to get an specific vt collection.',
108
        },
109
        'elements': None,
110
    },
111
    'delete_scan': {
112
        'description': 'Delete a finished scan.',
113
        'attributes': {'scan_id': 'ID of scan to delete.'},
114
        'elements': None,
115
    },
116
    'get_version': {
117
        'description': 'Return various versions.',
118
        'attributes': None,
119
        'elements': None,
120
    },
121
    'get_scanner_details': {
122
        'description': 'Return scanner description and parameters',
123
        'attributes': None,
124
        'elements': None,
125
    },
126
}
127
128
129 View Code Duplication
def bind_socket(address, port):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
130
    """ Returns a socket bound on (address:port). """
131
132
    assert address
133
    assert port
134
    bindsocket = socket.socket()
135
    try:
136
        bindsocket.bind((address, port))
137
    except socket.error:
138
        logger.error("Couldn't bind socket on %s:%s", address, port)
139
        return None
140
141
    logger.info('Listening on %s:%s', address, port)
142
    bindsocket.listen(0)
143
    return bindsocket
144
145
146 View Code Duplication
def bind_unix_socket(path):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
147
    """ Returns a unix file socket bound on (path). """
148
149
    assert path
150
    bindsocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
151
    try:
152
        os.unlink(path)
153
    except OSError:
154
        if os.path.exists(path):
155
            raise
156
    try:
157
        bindsocket.bind(path)
158
    except socket.error:
159
        logger.error("Couldn't bind socket on %s", path)
160
        return None
161
162
    logger.info('Listening on %s', path)
163
    bindsocket.listen(0)
164
    return bindsocket
165
166
167 View Code Duplication
def close_client_stream(client_stream, unix_path):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
168
    """ Closes provided client stream """
169
    try:
170
        client_stream.shutdown(socket.SHUT_RDWR)
171
        if unix_path:
172
            logger.debug('%s: Connection closed', unix_path)
173
        else:
174
            peer = client_stream.getpeername()
175
            logger.debug('%s:%s: Connection closed', peer[0], peer[1])
176
    except (socket.error, OSError) as exception:
177
        logger.debug('Connection closing error: %s', exception)
178
    client_stream.close()
179
180
181 View Code Duplication
class OSPDaemon(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
182
183
    """ Daemon class for OSP traffic handling.
184
185
    Every scanner wrapper should subclass it and make necessary additions and
186
    changes.
187
    * Add any needed parameters in __init__.
188
    * Implement check() method which verifies scanner availability and other
189
      environment related conditions.
190
    * Implement process_scan_params and exec_scan methods which are
191
      specific to handling the <start_scan> command, executing the wrapped
192
      scanner and storing the results.
193
    * exec_scan() should return 0 if host is dead or not reached, 1 if host is
194
      alive and 2 if scan error or status is unknown.
195
    * Implement other methods that assert to False such as get_scanner_name,
196
      get_scanner_version.
197
    * Use Call set_command_attributes at init time to add scanner command
198
      specific options eg. the w3af profile for w3af wrapper.
199
    """
200
201
    def __init__(
202
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
203
        certfile,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
204
        keyfile,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
205
        cafile,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
206
        niceness=None,  # pylint: disable=unused-argument
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
207
        customvtfilter=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
208
        wrapper_logger=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
209
    ):
210
        """ Initializes the daemon's internal data. """
211
        # @todo: Actually it makes sense to move the certificate params to
212
        #        a separate function because it is not mandatory anymore to
213
        #        use a TLS setup (unix file socket is an alternative).
214
        #        However, changing this makes it mandatory for any ospd scanner
215
        #        to change the function calls as well. So this breaks the API
216
        #        and should only be done with a major release.
217
        self.certs = dict()
218
        self.certs['cert_file'] = certfile
219
        self.certs['key_file'] = keyfile
220
        self.certs['ca_file'] = cafile
221
        self.scan_collection = ScanCollection()
222
        self.scan_processes = dict()
223
        self.daemon_info = dict()
224
        self.daemon_info['name'] = "OSPd"
225
        self.daemon_info['version'] = __version__
226
        self.daemon_info['description'] = "No description"
227
        self.scanner_info = dict()
228
        self.scanner_info['name'] = 'No name'
229
        self.scanner_info['version'] = 'No version'
230
        self.scanner_info['description'] = 'No description'
231
        self.server_version = None  # Set by the subclass.
232
        self.protocol_version = PROTOCOL_VERSION
233
        self.commands = COMMANDS_TABLE
234
        self.scanner_params = dict()
235
        for name, param in BASE_SCANNER_PARAMS.items():
236
            self.add_scanner_param(name, param)
237
        self.vts = dict()
238
        self.vt_id_pattern = re.compile("[0-9a-zA-Z_\\-:.]{1,80}")
239
        self.vts_version = None
240
        if customvtfilter:
241
            self.vts_filter = customvtfilter
242
        else:
243
            self.vts_filter = VtsFilter()
244
        if wrapper_logger:
245
            global logger  # pylint: disable=global-statement
246
            logger = wrapper_logger
247
248
    def set_command_attributes(self, name, attributes):
249
        """ Sets the xml attributes of a specified command. """
250
        if self.command_exists(name):
251
            command = self.commands.get(name)
252
            command['attributes'] = attributes
253
254
    def add_scanner_param(self, name, scanner_param):
255
        """ Add a scanner parameter. """
256
257
        assert name
258
        assert scanner_param
259
        self.scanner_params[name] = scanner_param
260
        command = self.commands.get('start_scan')
261
        command['elements'] = {
262
            'scanner_params': {
263
                k: v['name'] for k, v in self.scanner_params.items()
264
            }
265
        }
266
267
    def add_vt(
268
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
269
        vt_id,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
270
        name=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
271
        vt_params=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
272
        vt_refs=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
273
        custom=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
274
        vt_creation_time=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
275
        vt_modification_time=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
276
        vt_dependencies=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
277
        summary=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
278
        impact=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
279
        affected=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
280
        insight=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
281
        solution=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
282
        solution_t=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
283
        detection=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
284
        qod_t=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
285
        qod_v=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
286
        severities=None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
287
    ):
288
        """ Add a vulnerability test information.
289
290
        Returns: The new number of stored VTs.
291
        -1 in case the VT ID was already present and thus the
292
        new VT was not considered.
293
        -2 in case the vt_id was invalid.
294
        """
295
296
        if not vt_id:
297
            return -2  # no valid vt_id
298
299
        if self.vt_id_pattern.fullmatch(vt_id) is None:
300
            return -2  # no valid vt_id
301
302
        if vt_id in self.vts:
303
            return -1  # The VT was already in the list.
304
305
        if name is None:
306
            name = ''
307
308
        self.vts[vt_id] = {'name': name}
309
        if custom is not None:
310
            self.vts[vt_id]["custom"] = custom
311
        if vt_params is not None:
312
            self.vts[vt_id]["vt_params"] = vt_params
313
        if vt_refs is not None:
314
            self.vts[vt_id]["vt_refs"] = vt_refs
315
        if vt_dependencies is not None:
316
            self.vts[vt_id]["vt_dependencies"] = vt_dependencies
317
        if vt_creation_time is not None:
318
            self.vts[vt_id]["creation_time"] = vt_creation_time
319
        if vt_modification_time is not None:
320
            self.vts[vt_id]["modification_time"] = vt_modification_time
321
        if summary is not None:
322
            self.vts[vt_id]["summary"] = summary
323
        if impact is not None:
324
            self.vts[vt_id]["impact"] = impact
325
        if affected is not None:
326
            self.vts[vt_id]["affected"] = affected
327
        if insight is not None:
328
            self.vts[vt_id]["insight"] = insight
329
        if solution is not None:
330
            self.vts[vt_id]["solution"] = solution
331
            if solution_t is not None:
332
                self.vts[vt_id]["solution_type"] = solution_t
333
        if detection is not None:
334
            self.vts[vt_id]["detection"] = detection
335
        if qod_t is not None:
336
            self.vts[vt_id]["qod_type"] = qod_t
337
        elif qod_v is not None:
338
            self.vts[vt_id]["qod"] = qod_v
339
        if severities is not None:
340
            self.vts[vt_id]["severities"] = severities
341
342
        return len(self.vts)
343
344
    def set_vts_version(self, vts_version):
345
        """ Add into the vts dictionary an entry to identify the
346
        vts version.
347
348
        Parameters:
349
            vts_version (str): Identifies a unique vts version.
350
        """
351
        if not vts_version:
352
            raise OSPDError(
353
                'A vts_version parameter is required', 'set_vts_version'
354
            )
355
        self.vts_version = vts_version
356
357
    def get_vts_version(self):
358
        """Return the vts version.
359
        """
360
        return self.vts_version
361
362
    def command_exists(self, name):
363
        """ Checks if a commands exists. """
364
        return name in self.commands.keys()
365
366
    def get_scanner_name(self):
367
        """ Gives the wrapped scanner's name. """
368
        return self.scanner_info['name']
369
370
    def get_scanner_version(self):
371
        """ Gives the wrapped scanner's version. """
372
        return self.scanner_info['version']
373
374
    def get_scanner_description(self):
375
        """ Gives the wrapped scanner's description. """
376
        return self.scanner_info['description']
377
378
    def get_server_version(self):
379
        """ Gives the specific OSP server's version. """
380
        assert self.server_version
381
        return self.server_version
382
383
    def get_protocol_version(self):
384
        """ Gives the OSP's version. """
385
        return self.protocol_version
386
387
    def _preprocess_scan_params(self, xml_params):
388
        """ Processes the scan parameters. """
389
        params = {}
390
        for param in xml_params:
391
            params[param.tag] = param.text or ''
392
        # Set default values.
393
        for key in self.scanner_params:
394
            if key not in params:
395
                params[key] = self.get_scanner_param_default(key)
396
                if self.get_scanner_param_type(key) == 'selection':
397
                    params[key] = params[key].split('|')[0]
398
        # Validate values.
399
        for key in params:
400
            param_type = self.get_scanner_param_type(key)
401
            if not param_type:
402
                continue
403
            if param_type in ['integer', 'boolean']:
404
                try:
405
                    params[key] = int(params[key])
406
                except ValueError:
407
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
408
            if param_type == 'boolean':
409
                if params[key] not in [0, 1]:
410
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
411
            elif param_type == 'selection':
412
                selection = self.get_scanner_param_default(key).split('|')
413
                if params[key] not in selection:
414
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
415
            if self.get_scanner_param_mandatory(key) and params[key] == '':
416
                raise OSPDError(
417
                    'Mandatory %s value is missing' % key, 'start_scan'
418
                )
419
        return params
420
421
    def process_scan_params(self, params):
422
        """ This method is to be overridden by the child classes if necessary
423
        """
424
        return params
425
426
    def process_vts_params(self, scanner_vts):
427
        """ Receive an XML object with the Vulnerability Tests an their
428
        parameters to be use in a scan and return a dictionary.
429
430
        @param: XML element with vt subelements. Each vt has an
431
                id attribute. Optional parameters can be included
432
                as vt child.
433
                Example form:
434
                <vt_selection>
435
                  <vt_single id='vt1' />
436
                  <vt_single id='vt2'>
437
                    <vt_value id='param1'>value</vt_value>
438
                  </vt_single>
439
                  <vt_group filter='family=debian'/>
440
                  <vt_group filter='family=general'/>
441
                </vt_selection>
442
443
        @return: Dictionary containing the vts attribute and subelements,
444
                 like the VT's id and VT's parameters.
445
                 Example form:
446
                 {'vt1': {},
447
                  'vt2': {'value_id': 'value'},
448
                  'vt_groups': ['family=debian', 'family=general']}
449
        """
450
        vt_selection = {}
451
        filters = list()
452
        for vt in scanner_vts:
453
            if vt.tag == 'vt_single':
454
                vt_id = vt.attrib.get('id')
455
                vt_selection[vt_id] = {}
456
                for vt_value in vt:
457
                    if not vt_value.attrib.get('id'):
458
                        raise OSPDError(
459
                            'Invalid VT preference. No attribute id',
460
                            'start_scan',
461
                        )
462
                    vt_value_id = vt_value.attrib.get('id')
463
                    vt_value_value = vt_value.text if vt_value.text else ''
464
                    vt_selection[vt_id][vt_value_id] = vt_value_value
465
            if vt.tag == 'vt_group':
466
                vts_filter = vt.attrib.get('filter', None)
467
                if vts_filter is None:
468
                    raise OSPDError(
469
                        'Invalid VT group. No filter given.', 'start_scan'
470
                    )
471
                filters.append(vts_filter)
472
        vt_selection['vt_groups'] = filters
473
        return vt_selection
474
475
    @staticmethod
476
    def process_credentials_elements(cred_tree):
477
        """ Receive an XML object with the credentials to run
478
        a scan against a given target.
479
480
        @param:
481
        <credentials>
482
          <credential type="up" service="ssh" port="22">
483
            <username>scanuser</username>
484
            <password>mypass</password>
485
          </credential>
486
          <credential type="up" service="smb">
487
            <username>smbuser</username>
488
            <password>mypass</password>
489
          </credential>
490
        </credentials>
491
492
        @return: Dictionary containing the credentials for a given target.
493
                 Example form:
494
                 {'ssh': {'type': type,
495
                          'port': port,
496
                          'username': username,
497
                          'password': pass,
498
                        },
499
                  'smb': {'type': type,
500
                          'username': username,
501
                          'password': pass,
502
                         },
503
                   }
504
        """
505
        credentials = {}
506
        for credential in cred_tree:
507
            service = credential.attrib.get('service')
508
            credentials[service] = {}
509
            credentials[service]['type'] = credential.attrib.get('type')
510
            if service == 'ssh':
511
                credentials[service]['port'] = credential.attrib.get('port')
512
            for param in credential:
513
                credentials[service][param.tag] = param.text
514
515
        return credentials
516
517
    @classmethod
518
    def process_targets_element(cls, scanner_target):
519
        """ Receive an XML object with the target, ports and credentials to run
520
        a scan against.
521
522
        @param: XML element with target subelements. Each target has <hosts>
523
        and <ports> subelements. Hosts can be a single host, a host range,
524
        a comma-separated host list or a network address.
525
        <ports> and  <credentials> are optional. Therefore each ospd-scanner
526
        should check for a valid ones if needed.
527
528
                Example form:
529
                <targets>
530
                  <target>
531
                    <hosts>localhosts</hosts>
532
                    <exclude_hosts>localhost1</exclude_hosts>
533
                    <ports>80,443</ports>
534
                  </target>
535
                  <target>
536
                    <hosts>192.168.0.0/24</hosts>
537
                    <ports>22</ports>
538
                    <credentials>
539
                      <credential type="up" service="ssh" port="22">
540
                        <username>scanuser</username>
541
                        <password>mypass</password>
542
                      </credential>
543
                      <credential type="up" service="smb">
544
                        <username>smbuser</username>
545
                        <password>mypass</password>
546
                      </credential>
547
                    </credentials>
548
                  </target>
549
                </targets>
550
551
        @return: A list of [hosts, port, {credentials}, exclude_hosts] list.
552
                 Example form:
553
                 [['localhosts', '80,43', '', 'localhosts1'],
554
                  ['192.168.0.0/24', '22', {'smb': {'type': type,
555
                                                    'port': port,
556
                                                    'username': username,
557
                                                    'password': pass,
558
                                                   }}], '']
559
        """
560
561
        target_list = []
562
        for target in scanner_target:
563
            exclude_hosts = ''
564
            ports = ''
565
            credentials = {}
566
            for child in target:
567
                if child.tag == 'hosts':
568
                    hosts = child.text
569
                if child.tag == 'exclude_hosts':
570
                    exclude_hosts = child.text
571
                if child.tag == 'ports':
572
                    ports = child.text
573
                if child.tag == 'credentials':
574
                    credentials = cls.process_credentials_elements(child)
575
            if hosts:
0 ignored issues
show
introduced by
The variable hosts does not seem to be defined for all execution paths.
Loading history...
576
                target_list.append([hosts, ports, credentials, exclude_hosts])
577
            else:
578
                raise OSPDError('No target to scan', 'start_scan')
579
580
        return target_list
581
582
    def handle_start_scan_command(self, scan_et):
583
        """ Handles <start_scan> command.
584
585
        @return: Response string for <start_scan> command.
586
        """
587
588
        target_str = scan_et.attrib.get('target')
589
        ports_str = scan_et.attrib.get('ports')
590
        # For backward compatibility, if target and ports attributes are set,
591
        # <targets> element is ignored.
592
        if target_str is None or ports_str is None:
593
            target_list = scan_et.find('targets')
594
            if target_list is None or not target_list:
595
                raise OSPDError('No targets or ports', 'start_scan')
596
            else:
597
                scan_targets = self.process_targets_element(target_list)
598
        else:
599
            scan_targets = []
600
            for single_target in target_str_to_list(target_str):
601
                scan_targets.append([single_target, ports_str, '', ''])
602
603
        scan_id = scan_et.attrib.get('scan_id')
604
        if scan_id is not None and scan_id != '' and not valid_uuid(scan_id):
605
            raise OSPDError('Invalid scan_id UUID', 'start_scan')
606
607
        try:
608
            parallel = int(scan_et.attrib.get('parallel', '1'))
609
            if parallel < 1 or parallel > 20:
610
                parallel = 1
611
        except ValueError:
612
            raise OSPDError(
613
                'Invalid value for parallel scans. ' 'It must be a number',
614
                'start_scan',
615
            )
616
617
        scanner_params = scan_et.find('scanner_params')
618
        if scanner_params is None:
619
            raise OSPDError('No scanner_params element', 'start_scan')
620
621
        params = self._preprocess_scan_params(scanner_params)
622
623
        # VTS is an optional element. If present should not be empty.
624
        vt_selection = {}
625
        scanner_vts = scan_et.find('vt_selection')
626
        if scanner_vts is not None:
627
            if not scanner_vts:
628
                raise OSPDError('VTs list is empty', 'start_scan')
629
            else:
630
                vt_selection = self.process_vts_params(scanner_vts)
631
632
        # Dry run case.
633
        if 'dry_run' in params and int(params['dry_run']):
634
            scan_func = self.dry_run_scan
635
            scan_params = None
636
        else:
637
            scan_func = self.start_scan
638
            scan_params = self.process_scan_params(params)
639
640
        scan_id = self.create_scan(
641
            scan_id, scan_targets, scan_params, vt_selection
642
        )
643
        scan_process = multiprocessing.Process(
644
            target=scan_func, args=(scan_id, scan_targets, parallel)
645
        )
646
        self.scan_processes[scan_id] = scan_process
647
        scan_process.start()
648
        id_ = Element('id')
649
        id_.text = scan_id
650
        return simple_response_str('start_scan', 200, 'OK', id_)
651
652
    def handle_stop_scan_command(self, scan_et):
653
        """ Handles <stop_scan> command.
654
655
        @return: Response string for <stop_scan> command.
656
        """
657
658
        scan_id = scan_et.attrib.get('scan_id')
659
        if scan_id is None or scan_id == '':
660
            raise OSPDError('No scan_id attribute', 'stop_scan')
661
        self.stop_scan(scan_id)
662
663
        return simple_response_str('stop_scan', 200, 'OK')
664
665
    def stop_scan(self, scan_id):
666
        scan_process = self.scan_processes.get(scan_id)
667
        if not scan_process:
668
            raise OSPDError('Scan not found {0}.'.format(scan_id), 'stop_scan')
669
        if not scan_process.is_alive():
670
            raise OSPDError('Scan already stopped or finished.', 'stop_scan')
671
672
        self.set_scan_status(scan_id, ScanStatus.STOPPED)
673
        logger.info('%s: Scan stopping %s.', scan_id, scan_process.ident)
674
        self.stop_scan_cleanup(scan_id)
675
        try:
676
            scan_process.terminate()
677
        except AttributeError:
678
            logger.debug('%s: The scanner task stopped unexpectedly.', scan_id)
679
            self.add_scan_log(scan_id, name='', host='', value='Scan stopped.')
680
681
        os.killpg(os.getpgid(scan_process.ident), 15)
682
        scan_process.join()
683
        self.add_scan_log(scan_id, name='', host='', value='Scan stopped.')
684
        logger.info('%s: Scan stopped.', scan_id)
685
686
    @staticmethod
687
    def stop_scan_cleanup(scan_id):
688
        """ Should be implemented by subclass in case of a clean up before
689
        terminating is needed. """
690
691
    def exec_scan(self, scan_id, target):
692
        """ Asserts to False. Should be implemented by subclass. """
693
        raise NotImplementedError
694
695
    def finish_scan(self, scan_id):
696
        """ Sets a scan as finished. """
697
        self.set_scan_progress(scan_id, 100)
698
        self.set_scan_status(scan_id, ScanStatus.FINISHED)
699
        logger.info("%s: Scan finished.", scan_id)
700
701
    def get_daemon_name(self):
702
        """ Gives osp daemon's name. """
703
        return self.daemon_info['name']
704
705
    def get_daemon_version(self):
706
        """ Gives osp daemon's version. """
707
        return self.daemon_info['version']
708
709
    def get_scanner_param_type(self, param):
710
        """ Returns type of a scanner parameter. """
711
        assert isinstance(param, str)
712
        entry = self.scanner_params.get(param)
713
        if not entry:
714
            return None
715
        return entry.get('type')
716
717
    def get_scanner_param_mandatory(self, param):
718
        """ Returns if a scanner parameter is mandatory. """
719
        assert isinstance(param, str)
720
        entry = self.scanner_params.get(param)
721
        if not entry:
722
            return False
723
        return entry.get('mandatory')
724
725
    def get_scanner_param_default(self, param):
726
        """ Returns default value of a scanner parameter. """
727
        assert isinstance(param, str)
728
        entry = self.scanner_params.get(param)
729
        if not entry:
730
            return None
731
        return entry.get('default')
732
733
    def get_scanner_params_xml(self):
734
        """ Returns the OSP Daemon's scanner params in xml format. """
735
        scanner_params = Element('scanner_params')
736
        for param_id, param in self.scanner_params.items():
737
            param_xml = SubElement(scanner_params, 'scanner_param')
738
            for name, value in [('id', param_id), ('type', param['type'])]:
739
                param_xml.set(name, value)
740
            for name, value in [
741
                ('name', param['name']),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
742
                ('description', param['description']),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
743
                ('default', param['default']),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
744
                ('mandatory', param['mandatory']),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
745
            ]:
746
                elem = SubElement(param_xml, name)
747
                elem.text = str(value)
748
        return scanner_params
749
750
    def new_client_stream(self, sock):
751
        """ Returns a new ssl client stream from bind_socket. """
752
753
        assert sock
754
        newsocket, fromaddr = sock.accept()
755
        logger.debug("New connection from" " %s:%s", fromaddr[0], fromaddr[1])
756
        # NB: Despite the name, ssl.PROTOCOL_SSLv23 selects the highest
757
        # protocol version that both the client and server support. In modern
758
        # Python versions (>= 3.4) it supports TLS >= 1.0 with SSLv2 and SSLv3
759
        # being disabled. For Python >=3.5, PROTOCOL_SSLv23 is an alias for
760
        # PROTOCOL_TLS which should be used once compatibility with Python 3.4
761
        # is no longer desired.
762
        try:
763
            ssl_socket = ssl.wrap_socket(
764
                newsocket,
765
                cert_reqs=ssl.CERT_REQUIRED,
766
                server_side=True,
767
                certfile=self.certs['cert_file'],
768
                keyfile=self.certs['key_file'],
769
                ca_certs=self.certs['ca_file'],
770
                ssl_version=ssl.PROTOCOL_SSLv23,
771
            )
772
        except (ssl.SSLError, socket.error) as message:
773
            logger.error(message)
774
            return None
775
        return ssl_socket
776
777
    @staticmethod
778
    def write_to_stream(stream, response, block_len=1024):
779
        """
780
        Send the response in blocks of the given len using the
781
        passed method dependending on the socket type.
782
        """
783
        try:
784
            i_start = 0
785
            i_end = block_len
786
            while True:
787
                if i_end > len(response):
788
                    stream(response[i_start:])
789
                    break
790
                stream(response[i_start:i_end])
791
                i_start = i_end
792
                i_end += block_len
793
        except (socket.timeout, socket.error) as exception:
794
            logger.debug('Error sending response to the client: %s', exception)
795
796
    def handle_client_stream(self, stream, is_unix=False):
797
        """ Handles stream of data received from client. """
798
799
        assert stream
800
        data = []
801
        stream.settimeout(2)
802
        while True:
803
            try:
804
                if is_unix:
805
                    buf = stream.recv(1024)
806
                else:
807
                    buf = stream.read(1024)
808
                if not buf:
809
                    break
810
                data.append(buf)
811
            except (AttributeError, ValueError) as message:
812
                logger.error(message)
813
                return
814
            except (ssl.SSLError) as exception:
815
                logger.debug('Error: %s', exception[0])
816
                break
817
            except (socket.timeout) as exception:
818
                logger.debug('Error: %s', exception)
819
                break
820
        data = b''.join(data)
821
        if len(data) <= 0:
822
            logger.debug("Empty client stream")
823
            return
824
        try:
825
            response = self.handle_command(data)
826
        except OSPDError as exception:
827
            response = exception.as_xml()
828
            logger.debug('Command error: %s', exception.message)
829
        except Exception:  # pylint: disable=broad-except
830
            logger.exception('While handling client command:')
831
            exception = OSPDError('Fatal error', 'error')
832
            response = exception.as_xml()
833
        if is_unix:
834
            send_method = stream.send
835
        else:
836
            send_method = stream.write
837
        self.write_to_stream(send_method, response)
838
839
    def parallel_scan(self, scan_id, target):
840
        """ Starts the scan with scan_id. """
841
        try:
842
            ret = self.exec_scan(scan_id, target)
843
            if ret == 0:
844
                self.add_scan_host_detail(
845
                    scan_id, name='host_status', host=target, value='0'
846
                )
847
            elif ret == 1:
848
                self.add_scan_host_detail(
849
                    scan_id, name='host_status', host=target, value='1'
850
                )
851
            elif ret == 2:
852
                self.add_scan_host_detail(
853
                    scan_id, name='host_status', host=target, value='2'
854
                )
855
            else:
856
                logger.debug('%s: No host status returned', target)
857
        except Exception as e:  # pylint: disable=broad-except
858
            self.add_scan_error(
859
                scan_id,
860
                name='',
861
                host=target,
862
                value='Host process failure (%s).' % e,
863
            )
864
            logger.exception('While scanning %s:', target)
865
        else:
866
            logger.info("%s: Host scan finished.", target)
867
868
    def check_pending_target(self, scan_id, multiscan_proc):
869
        """ Check if a scan process is still alive. In case the process
870
        finished or is stopped, removes the process from the multiscan
871
        _process list.
872
        Processes dead and with progress < 100% are considered stopped
873
        or with failures. Then will try to stop the other runnings (target)
874
        scans owned by the same task.
875
876
        @input scan_id        Scan_id of the whole scan.
877
        @input multiscan_proc A list with the scan process which
878
                              may still be alive.
879
880
        @return Actualized list with current running scan processes.
881
        """
882
        for running_target_proc, running_target_id in multiscan_proc:
883
            if not running_target_proc.is_alive():
884
                target_prog = self.get_scan_target_progress(
885
                    scan_id, running_target_id
886
                )
887
                if target_prog < 100:
888
                    self.stop_scan(scan_id)
889
                running_target = (running_target_proc, running_target_id)
890
                multiscan_proc.remove(running_target)
891
        return multiscan_proc
892
893
    def calculate_progress(self, scan_id):
894
        """ Calculate the total scan progress from the
895
        partial target progress. """
896
897
        t_prog = dict()
898
        for target in self.get_scan_target(scan_id):
899
            t_prog[target] = self.get_scan_target_progress(scan_id, target)
900
        return sum(t_prog.values()) / len(t_prog)
901
902
    def process_exclude_hosts(self, scan_id, target_list):
903
        """ Process the exclude hosts before launching the scans.
904
        Set exclude hosts as finished with 100% to calculate
905
        the scan progress."""
906
907
        for target, _, _, exclude_hosts in target_list:
908
            exc_hosts_list = ''
909
            if not exclude_hosts:
910
                continue
911
            exc_hosts_list = target_str_to_list(exclude_hosts)
912
            for host in exc_hosts_list:
913
                self.set_scan_host_finished(scan_id, target, host)
914
                self.set_scan_target_progress(scan_id, target, host, 100)
915
916
    def start_scan(self, scan_id, targets, parallel=1):
917
        """ Handle N parallel scans if 'parallel' is greater than 1. """
918
919
        os.setsid()
920
        multiscan_proc = []
921
        logger.info("%s: Scan started.", scan_id)
922
        target_list = targets
923
        if target_list is None or not target_list:
924
            raise OSPDError('Erroneous targets list', 'start_scan')
925
926
        self.process_exclude_hosts(scan_id, target_list)
927
928
        for _index, target in enumerate(target_list):
929
            while len(multiscan_proc) >= parallel:
930
                progress = self.calculate_progress(scan_id)
931
                self.set_scan_progress(scan_id, progress)
932
                multiscan_proc = self.check_pending_target(
933
                    scan_id, multiscan_proc
934
                )
935
                time.sleep(1)
936
937
            # If the scan status is stopped, does not launch anymore target
938
            # scans
939
            if self.get_scan_status(scan_id) == ScanStatus.STOPPED:
940
                return
941
942
            logger.info(
943
                "%s: Host scan started on ports %s.", target[0], target[1]
944
            )
945
            scan_process = multiprocessing.Process(
946
                target=self.parallel_scan, args=(scan_id, target[0])
947
            )
948
            multiscan_proc.append((scan_process, target[0]))
949
            scan_process.start()
950
            self.set_scan_status(scan_id, ScanStatus.RUNNING)
951
952
        # Wait until all single target were scanned
953
        while multiscan_proc:
954
            multiscan_proc = self.check_pending_target(scan_id, multiscan_proc)
955
            if multiscan_proc:
956
                progress = self.calculate_progress(scan_id)
957
                self.set_scan_progress(scan_id, progress)
958
            time.sleep(1)
959
960
        # Only set the scan as finished if the scan was not stopped.
961
        if self.get_scan_status(scan_id) != ScanStatus.STOPPED:
962
            self.finish_scan(scan_id)
963
964
    def dry_run_scan(self, scan_id, targets):
965
        """ Dry runs a scan. """
966
967
        os.setsid()
968
        for _, target in enumerate(targets):
969
            host = resolve_hostname(target[0])
970
            if host is None:
971
                logger.info("Couldn't resolve %s.", target[0])
972
                continue
973
            port = self.get_scan_ports(scan_id, target=target[0])
974
            logger.info("%s:%s: Dry run mode.", host, port)
975
            self.add_scan_log(
976
                scan_id, name='', host=host, value='Dry run result'
977
            )
978
        self.finish_scan(scan_id)
979
980
    def handle_timeout(self, scan_id, host):
981
        """ Handles scanner reaching timeout error. """
982
        self.add_scan_error(
983
            scan_id,
984
            host=host,
985
            name="Timeout",
986
            value="{0} exec timeout.".format(self.get_scanner_name()),
987
        )
988
989
    def set_scan_host_finished(self, scan_id, target, host):
990
        """ Add the host in a list of finished hosts """
991
        self.scan_collection.set_host_finished(scan_id, target, host)
992
993
    def set_scan_progress(self, scan_id, progress):
994
        """ Sets scan_id scan's progress which is a number
995
        between 0 and 100. """
996
        self.scan_collection.set_progress(scan_id, progress)
997
998
    def set_scan_target_progress(self, scan_id, target, host, progress):
999
        """ Sets host's progress which is part of target. """
1000
        self.scan_collection.set_target_progress(
1001
            scan_id, target, host, progress
1002
        )
1003
1004
    def set_scan_status(self, scan_id, status):
1005
        """ Set the scan's status."""
1006
        self.scan_collection.set_status(scan_id, status)
1007
1008
    def get_scan_status(self, scan_id):
1009
        """ Get scan_id scans's status."""
1010
        return self.scan_collection.get_status(scan_id)
1011
1012
    def scan_exists(self, scan_id):
1013
        """ Checks if a scan with ID scan_id is in collection.
1014
1015
        @return: 1 if scan exists, 0 otherwise.
1016
        """
1017
        return self.scan_collection.id_exists(scan_id)
1018
1019
    def handle_get_scans_command(self, scan_et):
1020
        """ Handles <get_scans> command.
1021
1022
        @return: Response string for <get_scans> command.
1023
        """
1024
1025
        scan_id = scan_et.attrib.get('scan_id')
1026
        details = scan_et.attrib.get('details')
1027
        pop_res = scan_et.attrib.get('pop_results')
1028
        if details and details == '0':
1029
            details = False
1030
        else:
1031
            details = True
1032
            if pop_res and pop_res == '1':
1033
                pop_res = True
1034
            else:
1035
                pop_res = False
1036
1037
        responses = []
1038
        if scan_id and scan_id in self.scan_collection.ids_iterator():
1039
            self.check_scan_process(scan_id)
1040
            scan = self.get_scan_xml(scan_id, details, pop_res)
1041
            responses.append(scan)
1042
        elif scan_id:
1043
            text = "Failed to find scan '{0}'".format(scan_id)
1044
            return simple_response_str('get_scans', 404, text)
1045
        else:
1046
            for scan_id in self.scan_collection.ids_iterator():
1047
                self.check_scan_process(scan_id)
1048
                scan = self.get_scan_xml(scan_id, details, pop_res)
1049
                responses.append(scan)
1050
        return simple_response_str('get_scans', 200, 'OK', responses)
1051
1052
    def handle_get_vts_command(self, vt_et):
1053
        """ Handles <get_vts> command.
1054
1055
        @return: Response string for <get_vts> command.
1056
        """
1057
1058
        vt_id = vt_et.attrib.get('vt_id')
1059
        vt_filter = vt_et.attrib.get('filter')
1060
1061
        if vt_id and vt_id not in self.vts:
1062
            text = "Failed to find vulnerability test '{0}'".format(vt_id)
1063
            return simple_response_str('get_vts', 404, text)
1064
1065
        filtered_vts = None
1066
        if vt_filter:
1067
            filtered_vts = self.vts_filter.get_filtered_vts_list(
1068
                self.vts, vt_filter
1069
            )
1070
1071
        responses = []
1072
1073
        vts_xml = self.get_vts_xml(vt_id, filtered_vts)
1074
1075
        responses.append(vts_xml)
1076
1077
        return simple_response_str('get_vts', 200, 'OK', responses)
1078
1079
    def handle_help_command(self, scan_et):
1080
        """ Handles <help> command.
1081
1082
        @return: Response string for <help> command.
1083
        """
1084
        help_format = scan_et.attrib.get('format')
1085
        if help_format is None or help_format == "text":
1086
            # Default help format is text.
1087
            return simple_response_str('help', 200, 'OK', self.get_help_text())
1088
        elif help_format == "xml":
1089
            text = self.get_xml_str(self.commands)
1090
            return simple_response_str('help', 200, 'OK', text)
1091
        raise OSPDError('Bogus help format', 'help')
1092
1093
    def get_help_text(self):
1094
        """ Returns the help output in plain text format."""
1095
1096
        txt = str('\n')
1097
        for name, info in self.commands.items():
1098
            command_txt = "\t{0: <22} {1}\n".format(name, info['description'])
1099
            if info['attributes']:
1100
                command_txt = ''.join([command_txt, "\t Attributes:\n"])
1101
                for attrname, attrdesc in info['attributes'].items():
1102
                    attr_txt = "\t  {0: <22} {1}\n".format(attrname, attrdesc)
1103
                    command_txt = ''.join([command_txt, attr_txt])
1104
            if info['elements']:
1105
                command_txt = ''.join(
1106
                    [
1107
                        command_txt,
1108
                        "\t Elements:\n",
1109
                        self.elements_as_text(info['elements']),
1110
                    ]
1111
                )
1112
            txt = ''.join([txt, command_txt])
1113
        return txt
1114
1115
    def elements_as_text(self, elems, indent=2):
1116
        """ Returns the elems dictionary as formatted plain text. """
1117
        assert elems
1118
        text = ""
1119
        for elename, eledesc in elems.items():
1120
            if isinstance(eledesc, dict):
1121
                desc_txt = self.elements_as_text(eledesc, indent + 2)
1122
                desc_txt = ''.join(['\n', desc_txt])
1123
            elif isinstance(eledesc, str):
1124
                desc_txt = ''.join([eledesc, '\n'])
1125
            else:
1126
                assert False, "Only string or dictionary"
1127
            ele_txt = "\t{0}{1: <22} {2}".format(
1128
                ' ' * indent, elename, desc_txt
0 ignored issues
show
introduced by
The variable desc_txt does not seem to be defined for all execution paths.
Loading history...
1129
            )
1130
            text = ''.join([text, ele_txt])
1131
        return text
1132
1133
    def handle_delete_scan_command(self, scan_et):
1134
        """ Handles <delete_scan> command.
1135
1136
        @return: Response string for <delete_scan> command.
1137
        """
1138
        scan_id = scan_et.attrib.get('scan_id')
1139
        if scan_id is None:
1140
            return simple_response_str(
1141
                'delete_scan', 404, 'No scan_id attribute'
1142
            )
1143
1144
        if not self.scan_exists(scan_id):
1145
            text = "Failed to find scan '{0}'".format(scan_id)
1146
            return simple_response_str('delete_scan', 404, text)
1147
        self.check_scan_process(scan_id)
1148
        if self.delete_scan(scan_id):
1149
            return simple_response_str('delete_scan', 200, 'OK')
1150
        raise OSPDError('Scan in progress', 'delete_scan')
1151
1152
    def delete_scan(self, scan_id):
1153
        """ Deletes scan_id scan from collection.
1154
1155
        @return: 1 if scan deleted, 0 otherwise.
1156
        """
1157
        if self.get_scan_status(scan_id) == ScanStatus.RUNNING:
1158
            return 0
1159
1160
        try:
1161
            del self.scan_processes[scan_id]
1162
        except KeyError:
1163
            logger.debug('Scan process for %s not found', scan_id)
1164
        return self.scan_collection.delete_scan(scan_id)
1165
1166
    def get_scan_results_xml(self, scan_id, pop_res):
1167
        """ Gets scan_id scan's results in XML format.
1168
1169
        @return: String of scan results in xml.
1170
        """
1171
        results = Element('results')
1172
        for result in self.scan_collection.results_iterator(scan_id, pop_res):
1173
            results.append(get_result_xml(result))
1174
1175
        logger.info('Returning %d results', len(results))
1176
        return results
1177
1178
    def get_xml_str(self, data):
1179
        """ Creates a string in XML Format using the provided data structure.
1180
1181
        @param: Dictionary of xml tags and their elements.
1182
1183
        @return: String of data in xml format.
1184
        """
1185
1186
        responses = []
1187
        for tag, value in data.items():
1188
            elem = Element(tag)
1189
            if isinstance(value, dict):
1190
                for val in self.get_xml_str(value):
1191
                    elem.append(val)
1192
            elif isinstance(value, list):
1193
                elem.text = ', '.join(value)
1194
            else:
1195
                elem.text = value
1196
            responses.append(elem)
1197
        return responses
1198
1199
    def get_scan_xml(self, scan_id, detailed=True, pop_res=False):
1200
        """ Gets scan in XML format.
1201
1202
        @return: String of scan in XML format.
1203
        """
1204
        if not scan_id:
1205
            return Element('scan')
1206
1207
        target = ','.join(self.get_scan_target(scan_id))
1208
        progress = self.get_scan_progress(scan_id)
1209
        status = self.get_scan_status(scan_id)
1210
        start_time = self.get_scan_start_time(scan_id)
1211
        end_time = self.get_scan_end_time(scan_id)
1212
        response = Element('scan')
1213
        for name, value in [
1214
            ('id', scan_id),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1215
            ('target', target),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1216
            ('progress', progress),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1217
            ('status', status.name.lower()),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1218
            ('start_time', start_time),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1219
            ('end_time', end_time),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1220
        ]:
1221
            response.set(name, str(value))
1222
        if detailed:
1223
            response.append(self.get_scan_results_xml(scan_id, pop_res))
1224
        return response
1225
1226
    @staticmethod
1227
    def get_custom_vt_as_xml_str(
1228
        vt_id, custom
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1229
    ):  # pylint: disable=unused-argument
1230
        """ Create a string representation of the XML object from the
1231
        custom data object.
1232
        This needs to be implemented by each ospd wrapper, in case
1233
        custom elements for VTs are used.
1234
1235
        The custom XML object which is returned will be embedded
1236
        into a <custom></custom> element.
1237
1238
        @return: XML object as string for custom data.
1239
        """
1240
        return ''
1241
1242
    @staticmethod
1243
    def get_params_vt_as_xml_str(
1244
        vt_id, vt_params
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1245
    ):  # pylint: disable=unused-argument
1246
        """ Create a string representation of the XML object from the
1247
        vt_params data object.
1248
        This needs to be implemented by each ospd wrapper, in case
1249
        vt_params elements for VTs are used.
1250
1251
        The params XML object which is returned will be embedded
1252
        into a <params></params> element.
1253
1254
        @return: XML object as string for vt parameters data.
1255
        """
1256
        return ''
1257
1258
    @staticmethod
1259
    def get_refs_vt_as_xml_str(
1260
        vt_id, vt_refs
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1261
    ):  # pylint: disable=unused-argument
1262
        """ Create a string representation of the XML object from the
1263
        refs data object.
1264
        This needs to be implemented by each ospd wrapper, in case
1265
        refs elements for VTs are used.
1266
1267
        The refs XML object which is returned will be embedded
1268
        into a <refs></refs> element.
1269
1270
        @return: XML object as string for vt references data.
1271
        """
1272
        return ''
1273
1274
    @staticmethod
1275
    def get_dependencies_vt_as_xml_str(
1276
        vt_id, vt_dependencies
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1277
    ):  # pylint: disable=unused-argument
1278
        """ Create a string representation of the XML object from the
1279
        vt_dependencies data object.
1280
        This needs to be implemented by each ospd wrapper, in case
1281
        vt_dependencies elements for VTs are used.
1282
1283
        The vt_dependencies XML object which is returned will be embedded
1284
        into a <dependencies></dependencies> element.
1285
1286
        @return: XML object as string for vt dependencies data.
1287
        """
1288
        return ''
1289
1290
    @staticmethod
1291
    def get_creation_time_vt_as_xml_str(
1292
        vt_id, vt_creation_time
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1293
    ):  # pylint: disable=unused-argument
1294
        """ Create a string representation of the XML object from the
1295
        vt_creation_time data object.
1296
        This needs to be implemented by each ospd wrapper, in case
1297
        vt_creation_time elements for VTs are used.
1298
1299
        The vt_creation_time XML object which is returned will be embedded
1300
        into a <vt_creation_time></vt_creation_time> element.
1301
1302
        @return: XML object as string for vt creation time data.
1303
        """
1304
        return ''
1305
1306
    @staticmethod
1307
    def get_modification_time_vt_as_xml_str(
1308
        vt_id, vt_modification_time
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1309
    ):  # pylint: disable=unused-argument
1310
        """ Create a string representation of the XML object from the
1311
        vt_modification_time data object.
1312
        This needs to be implemented by each ospd wrapper, in case
1313
        vt_modification_time elements for VTs are used.
1314
1315
        The vt_modification_time XML object which is returned will be embedded
1316
        into a <vt_modification_time></vt_modification_time> element.
1317
1318
        @return: XML object as string for vt references data.
1319
        """
1320
        return ''
1321
1322
    @staticmethod
1323
    def get_summary_vt_as_xml_str(
1324
        vt_id, summary
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1325
    ):  # pylint: disable=unused-argument
1326
        """ Create a string representation of the XML object from the
1327
        summary data object.
1328
        This needs to be implemented by each ospd wrapper, in case
1329
        summary elements for VTs are used.
1330
1331
        The summary XML object which is returned will be embedded
1332
        into a <summary></summary> element.
1333
1334
        @return: XML object as string for summary data.
1335
        """
1336
        return ''
1337
1338
    @staticmethod
1339
    def get_impact_vt_as_xml_str(
1340
        vt_id, impact
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1341
    ):  # pylint: disable=unused-argument
1342
        """ Create a string representation of the XML object from the
1343
        impact data object.
1344
        This needs to be implemented by each ospd wrapper, in case
1345
        impact elements for VTs are used.
1346
1347
        The impact XML object which is returned will be embedded
1348
        into a <impact></impact> element.
1349
1350
        @return: XML object as string for impact data.
1351
        """
1352
        return ''
1353
1354
    @staticmethod
1355
    def get_affected_vt_as_xml_str(
1356
        vt_id, affected
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1357
    ):  # pylint: disable=unused-argument
1358
        """ Create a string representation of the XML object from the
1359
        affected data object.
1360
        This needs to be implemented by each ospd wrapper, in case
1361
        affected elements for VTs are used.
1362
1363
        The affected XML object which is returned will be embedded
1364
        into a <affected></affected> element.
1365
1366
        @return: XML object as string for affected data.
1367
        """
1368
        return ''
1369
1370
    @staticmethod
1371
    def get_insight_vt_as_xml_str(
1372
        vt_id, insight
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1373
    ):  # pylint: disable=unused-argument
1374
        """ Create a string representation of the XML object from the
1375
        insight data object.
1376
        This needs to be implemented by each ospd wrapper, in case
1377
        insight elements for VTs are used.
1378
1379
        The insight XML object which is returned will be embedded
1380
        into a <insight></insight> element.
1381
1382
        @return: XML object as string for insight data.
1383
        """
1384
        return ''
1385
1386
    @staticmethod
1387
    def get_solution_vt_as_xml_str(
1388
        vt_id, solution, solution_type=None
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1389
    ):  # pylint: disable=unused-argument
1390
        """ Create a string representation of the XML object from the
1391
        solution data object.
1392
        This needs to be implemented by each ospd wrapper, in case
1393
        solution elements for VTs are used.
1394
1395
        The solution XML object which is returned will be embedded
1396
        into a <solution></solution> element.
1397
1398
        @return: XML object as string for solution data.
1399
        """
1400
        return ''
1401
1402
    @staticmethod
1403
    def get_detection_vt_as_xml_str(
1404
        vt_id, detection=None, qod_type=None, qod=None
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1405
    ):  # pylint: disable=unused-argument
1406
        """ Create a string representation of the XML object from the
1407
        detection data object.
1408
        This needs to be implemented by each ospd wrapper, in case
1409
        detection elements for VTs are used.
1410
1411
        The detection XML object which is returned is an element with
1412
        tag <detection></detection> element
1413
1414
        @return: XML object as string for detection data.
1415
        """
1416
        return ''
1417
1418
    @staticmethod
1419
    def get_severities_vt_as_xml_str(
1420
        vt_id, severities
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1421
    ):  # pylint: disable=unused-argument
1422
        """ Create a string representation of the XML object from the
1423
        severities data object.
1424
        This needs to be implemented by each ospd wrapper, in case
1425
        severities elements for VTs are used.
1426
1427
        The severities XML objects which are returned will be embedded
1428
        into a <severities></severities> element.
1429
1430
        @return: XML object as string for severities data.
1431
        """
1432
        return ''
1433
1434
    def get_vt_xml(self, vt_id):
1435
        """ Gets a single vulnerability test information in XML format.
1436
1437
        @return: String of single vulnerability test information in XML format.
1438
        """
1439
        if not vt_id:
1440
            return Element('vt')
1441
1442
        vt = self.vts.get(vt_id)
1443
1444
        name = vt.get('name')
1445
        vt_xml = Element('vt')
1446
        vt_xml.set('id', vt_id)
1447
1448
        for name, value in [('name', name)]:
1449
            elem = SubElement(vt_xml, name)
1450
            elem.text = str(value)
1451
1452
        if vt.get('vt_params'):
1453
            params_xml_str = self.get_params_vt_as_xml_str(
1454
                vt_id, vt.get('vt_params')
1455
            )
1456
            vt_xml.append(secET.fromstring(params_xml_str))
1457
1458
        if vt.get('vt_refs'):
1459
            refs_xml_str = self.get_refs_vt_as_xml_str(vt_id, vt.get('vt_refs'))
1460
            vt_xml.append(secET.fromstring(refs_xml_str))
1461
1462
        if vt.get('vt_dependencies'):
1463
            dependencies = self.get_dependencies_vt_as_xml_str(
1464
                vt_id, vt.get('vt_dependencies')
1465
            )
1466
            vt_xml.append(secET.fromstring(dependencies))
1467
1468
        if vt.get('creation_time'):
1469
            vt_ctime = self.get_creation_time_vt_as_xml_str(
1470
                vt_id, vt.get('creation_time')
1471
            )
1472
            vt_xml.append(secET.fromstring(vt_ctime))
1473
1474
        if vt.get('modification_time'):
1475
            vt_mtime = self.get_modification_time_vt_as_xml_str(
1476
                vt_id, vt.get('modification_time')
1477
            )
1478
            vt_xml.append(secET.fromstring(vt_mtime))
1479
1480
        if vt.get('summary'):
1481
            summary_xml_str = self.get_summary_vt_as_xml_str(
1482
                vt_id, vt.get('summary')
1483
            )
1484
            vt_xml.append(secET.fromstring(summary_xml_str))
1485
1486
        if vt.get('impact'):
1487
            impact_xml_str = self.get_impact_vt_as_xml_str(
1488
                vt_id, vt.get('impact')
1489
            )
1490
            vt_xml.append(secET.fromstring(impact_xml_str))
1491
1492
        if vt.get('affected'):
1493
            affected_xml_str = self.get_affected_vt_as_xml_str(
1494
                vt_id, vt.get('affected')
1495
            )
1496
            vt_xml.append(secET.fromstring(affected_xml_str))
1497
1498
        if vt.get('insight'):
1499
            insight_xml_str = self.get_insight_vt_as_xml_str(
1500
                vt_id, vt.get('insight')
1501
            )
1502
            vt_xml.append(secET.fromstring(insight_xml_str))
1503
1504
        if vt.get('solution'):
1505
            solution_xml_str = self.get_solution_vt_as_xml_str(
1506
                vt_id, vt.get('solution'), vt.get('solution_type')
1507
            )
1508
            vt_xml.append(secET.fromstring(solution_xml_str))
1509
1510
        if vt.get('detection') or vt.get('qod_type') or vt.get('qod'):
1511
            detection_xml_str = self.get_detection_vt_as_xml_str(
1512
                vt_id, vt.get('detection'), vt.get('qod_type'), vt.get('qod')
1513
            )
1514
            vt_xml.append(secET.fromstring(detection_xml_str))
1515
1516
        if vt.get('severities'):
1517
            severities_xml_str = self.get_severities_vt_as_xml_str(
1518
                vt_id, vt.get('severities')
1519
            )
1520
            vt_xml.append(secET.fromstring(severities_xml_str))
1521
1522
        if vt.get('custom'):
1523
            custom_xml_str = self.get_custom_vt_as_xml_str(
1524
                vt_id, vt.get('custom')
1525
            )
1526
            vt_xml.append(secET.fromstring(custom_xml_str))
1527
1528
        return vt_xml
1529
1530
    def get_vts_xml(self, vt_id=None, filtered_vts=None):
1531
        """ Gets collection of vulnerability test information in XML format.
1532
        If vt_id is specified, the collection will contain only this vt, if
1533
        found.
1534
        If no vt_id is specified, the collection will contain all vts or those
1535
        passed in filtered_vts.
1536
1537
        Arguments:
1538
            vt_id (vt_id, optional): ID of the vt to get.
1539
            filtered_vts (dict, optional): Filtered VTs collection.
1540
1541
        Return:
1542
            String of collection of vulnerability test information in
1543
            XML format.
1544
        """
1545
1546
        vts_xml = Element('vts')
1547
1548
        if vt_id:
1549
            vts_xml.append(self.get_vt_xml(vt_id))
1550
        elif filtered_vts:
1551
            for vt_id in filtered_vts:
1552
                vts_xml.append(self.get_vt_xml(vt_id))
1553
        else:
1554
            for vt_id in self.vts:
1555
                vts_xml.append(self.get_vt_xml(vt_id))
1556
1557
        return vts_xml
1558
1559
    def handle_get_scanner_details(self):
1560
        """ Handles <get_scanner_details> command.
1561
1562
        @return: Response string for <get_scanner_details> command.
1563
        """
1564
        desc_xml = Element('description')
1565
        desc_xml.text = self.get_scanner_description()
1566
        details = [desc_xml, self.get_scanner_params_xml()]
1567
        return simple_response_str('get_scanner_details', 200, 'OK', details)
1568
1569
    def handle_get_version_command(self):
1570
        """ Handles <get_version> command.
1571
1572
        @return: Response string for <get_version> command.
1573
        """
1574
        protocol = Element('protocol')
1575
        for name, value in [
1576
            ('name', 'OSP'),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1577
            ('version', self.get_protocol_version()),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1578
        ]:
1579
            elem = SubElement(protocol, name)
1580
            elem.text = value
1581
1582
        daemon = Element('daemon')
1583
        for name, value in [
1584
            ('name', self.get_daemon_name()),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1585
            ('version', self.get_daemon_version()),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1586
        ]:
1587
            elem = SubElement(daemon, name)
1588
            elem.text = value
1589
1590
        scanner = Element('scanner')
1591
        for name, value in [
1592
            ('name', self.get_scanner_name()),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1593
            ('version', self.get_scanner_version()),
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1594
        ]:
1595
            elem = SubElement(scanner, name)
1596
            elem.text = value
1597
1598
        content = [protocol, daemon, scanner]
1599
1600
        if self.get_vts_version():
1601
            vts = Element('vts')
1602
            elem = SubElement(vts, 'version')
1603
            elem.text = self.get_vts_version()
1604
            content.append(vts)
1605
1606
        return simple_response_str('get_version', 200, 'OK', content)
1607
1608
    def handle_command(self, command):
1609
        """ Handles an osp command in a string.
1610
1611
        @return: OSP Response to command.
1612
        """
1613
        try:
1614
            tree = secET.fromstring(command)
1615
        except secET.ParseError:
1616
            logger.debug("Erroneous client input: %s", command)
1617
            raise OSPDError('Invalid data')
1618
1619
        if not self.command_exists(tree.tag) and tree.tag != "authenticate":
1620
            raise OSPDError('Bogus command name')
1621
1622
        if tree.tag == "get_version":
1623
            return self.handle_get_version_command()
1624
        elif tree.tag == "start_scan":
1625
            return self.handle_start_scan_command(tree)
1626
        elif tree.tag == "stop_scan":
1627
            return self.handle_stop_scan_command(tree)
1628
        elif tree.tag == "get_scans":
1629
            return self.handle_get_scans_command(tree)
1630
        elif tree.tag == "get_vts":
1631
            return self.handle_get_vts_command(tree)
1632
        elif tree.tag == "delete_scan":
1633
            return self.handle_delete_scan_command(tree)
1634
        elif tree.tag == "help":
1635
            return self.handle_help_command(tree)
1636
        elif tree.tag == "get_scanner_details":
1637
            return self.handle_get_scanner_details()
1638
        else:
1639
            assert False, "Unhandled command: {0}".format(tree.tag)
1640
1641
    def check(self):
1642
        """ Asserts to False. Should be implemented by subclass. """
1643
        raise NotImplementedError
1644
1645
    def run(self, address, port, unix_path):
1646
        """ Starts the Daemon, handling commands until interrupted.
1647
1648
        @return False if error. Runs indefinitely otherwise.
1649
        """
1650
        assert address or unix_path
1651
        if unix_path:
1652
            sock = bind_unix_socket(unix_path)
1653
        else:
1654
            sock = bind_socket(address, port)
1655
        if sock is None:
1656
            return False
1657
1658
        sock.setblocking(False)
1659
        inputs = [sock]
1660
        outputs = []
1661
        try:
1662
            while True:
1663
                readable, _, _ = select.select(
1664
                    inputs, outputs, inputs, SCHEDULER_CHECK_PERIOD
1665
                )
1666
                for r_socket in readable:
1667
                    if unix_path and r_socket is sock:
1668
                        client_stream, _ = sock.accept()
1669
                        logger.debug("New connection from %s", unix_path)
1670
                        self.handle_client_stream(client_stream, True)
1671
                    else:
1672
                        client_stream = self.new_client_stream(sock)
1673
                        if client_stream is None:
1674
                            continue
1675
                        self.handle_client_stream(client_stream, False)
1676
                    close_client_stream(client_stream, unix_path)
1677
                self.scheduler()
1678
        except KeyboardInterrupt:
1679
            logger.info("Received Ctrl-C shutting-down ...")
1680
        finally:
1681
            sock.shutdown(socket.SHUT_RDWR)
1682
            sock.close()
1683
1684
    def scheduler(self):
1685
        """ Should be implemented by subclass in case of need
1686
        to run tasks periodically. """
1687
1688
    def create_scan(self, scan_id, targets, options, vts):
1689
        """ Creates a new scan.
1690
1691
        @target: Target to scan.
1692
        @options: Miscellaneous scan options.
1693
1694
        @return: New scan's ID.
1695
        """
1696
        if self.scan_exists(scan_id):
1697
            logger.info("Scan %s exists. Resuming scan.", scan_id)
1698
1699
        return self.scan_collection.create_scan(scan_id, targets, options, vts)
1700
1701
    def get_scan_options(self, scan_id):
1702
        """ Gives a scan's list of options. """
1703
        return self.scan_collection.get_options(scan_id)
1704
1705
    def set_scan_option(self, scan_id, name, value):
1706
        """ Sets a scan's option to a provided value. """
1707
        return self.scan_collection.set_option(scan_id, name, value)
1708
1709
    def check_scan_process(self, scan_id):
1710
        """ Check the scan's process, and terminate the scan if not alive. """
1711
        scan_process = self.scan_processes[scan_id]
1712
        progress = self.get_scan_progress(scan_id)
1713
        if progress < 100 and not scan_process.is_alive():
1714
            self.set_scan_status(scan_id, ScanStatus.STOPPED)
1715
            self.add_scan_error(
1716
                scan_id, name="", host="", value="Scan process failure."
1717
            )
1718
            logger.info("%s: Scan stopped with errors.", scan_id)
1719
        elif progress == 100:
1720
            scan_process.join()
1721
1722
    def get_scan_progress(self, scan_id):
1723
        """ Gives a scan's current progress value. """
1724
        return self.scan_collection.get_progress(scan_id)
1725
1726
    def get_scan_target_progress(self, scan_id, target):
1727
        """ Gives a list with scan's current progress value of each target. """
1728
        return self.scan_collection.get_target_progress(scan_id, target)
1729
1730
    def get_scan_target(self, scan_id):
1731
        """ Gives a scan's target. """
1732
        return self.scan_collection.get_target_list(scan_id)
1733
1734
    def get_scan_ports(self, scan_id, target=''):
1735
        """ Gives a scan's ports list. """
1736
        return self.scan_collection.get_ports(scan_id, target)
1737
1738
    def get_scan_exclude_hosts(self, scan_id, target=''):
1739
        """ Gives a scan's exclude host list. If a target is passed gives
1740
        the exclude host list for the given target. """
1741
        return self.scan_collection.get_exclude_hosts(scan_id, target)
1742
1743
    def get_scan_credentials(self, scan_id, target=''):
1744
        """ Gives a scan's credential list. If a target is passed gives
1745
        the credential list for the given target. """
1746
        return self.scan_collection.get_credentials(scan_id, target)
1747
1748
    def get_scan_vts(self, scan_id):
1749
        """ Gives a scan's vts list. """
1750
        return self.scan_collection.get_vts(scan_id)
1751
1752
    def get_scan_unfinished_hosts(self, scan_id):
1753
        """ Get a list of unfinished hosts."""
1754
        return self.scan_collection.get_hosts_unfinished(scan_id)
1755
1756
    def get_scan_finished_hosts(self, scan_id):
1757
        """ Get a list of unfinished hosts."""
1758
        return self.scan_collection.get_hosts_finished(scan_id)
1759
1760
    def get_scan_start_time(self, scan_id):
1761
        """ Gives a scan's start time. """
1762
        return self.scan_collection.get_start_time(scan_id)
1763
1764
    def get_scan_end_time(self, scan_id):
1765
        """ Gives a scan's end time. """
1766
        return self.scan_collection.get_end_time(scan_id)
1767
1768
    def add_scan_log(
1769
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1770
        scan_id,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1771
        host='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1772
        hostname='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1773
        name='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1774
        value='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1775
        port='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1776
        test_id='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1777
        qod='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1778
    ):
1779
        """ Adds a log result to scan_id scan. """
1780
        self.scan_collection.add_result(
1781
            scan_id,
1782
            ResultType.LOG,
1783
            host,
1784
            hostname,
1785
            name,
1786
            value,
1787
            port,
1788
            test_id,
1789
            0.0,
1790
            qod,
1791
        )
1792
1793
    def add_scan_error(
1794
        self, scan_id, host='', hostname='', name='', value='', port=''
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1795
    ):
1796
        """ Adds an error result to scan_id scan. """
1797
        self.scan_collection.add_result(
1798
            scan_id, ResultType.ERROR, host, hostname, name, value, port
1799
        )
1800
1801
    def add_scan_host_detail(
1802
        self, scan_id, host='', hostname='', name='', value=''
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1803
    ):
1804
        """ Adds a host detail result to scan_id scan. """
1805
        self.scan_collection.add_result(
1806
            scan_id, ResultType.HOST_DETAIL, host, hostname, name, value
1807
        )
1808
1809
    def add_scan_alarm(
1810
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1811
        scan_id,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1812
        host='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1813
        hostname='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1814
        name='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1815
        value='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1816
        port='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1817
        test_id='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1818
        severity='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1819
        qod='',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1820
    ):
1821
        """ Adds an alarm result to scan_id scan. """
1822
        self.scan_collection.add_result(
1823
            scan_id,
1824
            ResultType.ALARM,
1825
            host,
1826
            hostname,
1827
            name,
1828
            value,
1829
            port,
1830
            test_id,
1831
            severity,
1832
            qod,
1833
        )
1834