Completed
Push — master ( 0dbbf7...dad66b )
by
unknown
12s queued 10s
created

ospd.ospd.OSPDaemon.add_scan_log()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 5
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nop 8
dl 5
loc 5
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
# -*- coding: utf-8 -*-
0 ignored issues
show
coding-style introduced by
Too many lines in module (1266/1000)
Loading history...
2
# $Id$
3
# Description:
4
# OSP Daemon core class.
5
#
6
# Authors:
7
# Hani Benhabiles <[email protected]>
8
# Benoît Allard <[email protected]>
9
# Jan-Oliver Wahmer <[email protected]>
10
#
11
# Copyright:
12
# Copyright (C) 2014, 2015, 2018 Greenbone Networks GmbH
13
#
14
# This program is free software; you can redistribute it and/or
15
# modify it under the terms of the GNU General Public License
16
# as published by the Free Software Foundation; either version 2
17
# of the License, or (at your option) any later version.
18
#
19
# This program is distributed in the hope that it will be useful,
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
# GNU General Public License for more details.
23
#
24
# You should have received a copy of the GNU General Public License
25
# along with this program; if not, write to the Free Software
26
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
27
28
""" OSP Daemon core class. """
29
30
# This is needed for older pythons as our current module is called the same
31
# as the package we are in ...
32
# Another solution would be to rename that file.
33
from __future__ import absolute_import
34
35
import logging
36
import socket
37
import ssl
38
import multiprocessing
39
import xml.etree.ElementTree as ET
40
import defusedxml.ElementTree as secET
41
import os
0 ignored issues
show
introduced by
standard import "import os" should be placed before "import defusedxml.ElementTree as secET"
Loading history...
42
import re
0 ignored issues
show
introduced by
standard import "import re" should be placed before "import defusedxml.ElementTree as secET"
Loading history...
43
44
from ospd import __version__
45
from ospd.misc import ScanCollection, ResultType, target_str_to_list
46
from ospd.misc import resolve_hostname, valid_uuid
47
48
logger = logging.getLogger(__name__)
0 ignored issues
show
Coding Style Naming introduced by
The name logger does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
49
50
PROTOCOL_VERSION = "1.2"
51
52
BASE_SCANNER_PARAMS = {
53
    'debug_mode': {
54
        'type': 'boolean',
55
        'name': 'Debug Mode',
56
        'default': 0,
57
        'mandatory': 0,
58
        'description': 'Whether to get extra scan debug information.',
59
    },
60
    'dry_run': {
61
        'type': 'boolean',
62
        'name': 'Dry Run',
63
        'default': 0,
64
        'mandatory': 0,
65
        'description': 'Whether to dry run scan.',
66
    },
67
}
68
69
COMMANDS_TABLE = {
70
    'start_scan': {
71
        'description': 'Start a new scan.',
72
        'attributes': {
73
            'target': 'Target host to scan',
74
            'ports': 'Ports list to scan',
75
            'scan_id': 'Optional UUID value to use as scan ID',
76
        },
77
        'elements': None
78
    },
79
    'stop_scan': {
80
        'description': 'Stop a currently running scan.',
81
        'attributes': {
82
            'scan_id': 'ID of scan to stop.'
83
        },
84
        'elements': None
85
    },
86
    'help': {
87
        'description': 'Print the commands help.',
88
        'attributes': {
89
            'format': 'Help format. Could be text or xml.'
90
        },
91
        'elements': None
92
    },
93
    'get_scans': {
94
        'description': 'List the scans in buffer.',
95
        'attributes': {
96
            'scan_id': 'ID of a specific scan to get.',
97
            'details': 'Whether to return the full scan report.'
98
        },
99
        'elements': None
100
    },
101
    'get_vts': {
102
        'description': 'List of available vulnerability tests.',
103
        'attributes': {
104
            'vt_id': 'ID of a specific vulnerability test to get.'
105
        },
106
        'elements': None
107
    },
108
    'delete_scan': {
109
        'description': 'Delete a finished scan.',
110
        'attributes': {
111
            'scan_id': 'ID of scan to delete.'
112
        },
113
        'elements': None
114
    },
115
    'get_version': {
116
        'description': 'Return various versions.',
117
        'attributes': None,
118
        'elements': None
119
    },
120
    'get_scanner_details': {
121
        'description': 'Return scanner description and parameters',
122
        'attributes': None,
123
        'elements': None
124
    }
125
}
126
127
128 View Code Duplication
def get_result_xml(result):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
129
    """ Formats a scan result to XML format. """
130
    result_xml = ET.Element('result')
131
    for name, value in [('name', result['name']),
132
                        ('type', ResultType.get_str(result['type'])),
133
                        ('severity', result['severity']),
134
                        ('host', result['host']),
135
                        ('test_id', result['test_id']),
136
                        ('port', result['port']),
137
                        ('qod', result['qod'])]:
138
        result_xml.set(name, str(value))
139
    result_xml.text = result['value']
140
    return result_xml
141
142
143 View Code Duplication
def simple_response_str(command, status, status_text, content=""):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
144
    """ Creates an OSP response XML string.
145
146
    @param: OSP Command to respond to.
147
    @param: Status of the response.
148
    @param: Status text of the response.
149
    @param: Text part of the response XML element.
150
151
    @return: String of response in xml format.
152
    """
153
    response = ET.Element('%s_response' % command)
154
    for name, value in [('status', str(status)), ('status_text', status_text)]:
155
        response.set(name, str(value))
156
    if isinstance(content, list):
157
        for elem in content:
158
            response.append(elem)
159
    elif isinstance(content, ET.Element):
160
        response.append(content)
161
    else:
162
        response.text = content
163
    return ET.tostring(response)
164
165
166 View Code Duplication
class OSPDError(Exception):
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...
167
168
    """ This is an exception that will result in an error message to the
169
    client """
170
171
    def __init__(self, message, command='osp', status=400):
172
        super(OSPDError, self).__init__()
173
        self.message = message
174
        self.command = command
175
        self.status = status
176
177
    def as_xml(self):
178
        """ Return the error in xml format. """
179
        return simple_response_str(self.command, self.status, self.message)
180
181
182 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...
183
    """ Returns a socket bound on (address:port). """
184
185
    assert address
186
    assert port
187
    bindsocket = socket.socket()
188
    try:
189
        bindsocket.bind((address, port))
190
    except socket.error:
191
        logger.error("Couldn't bind socket on {0}:{1}"
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
192
                     .format(address, port))
193
        return None
194
195
    logger.info('Listening on {0}:{1}'.format(address, port))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
196
    bindsocket.listen(0)
197
    return bindsocket
198
199 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...
200
    """ Returns a unix file socket bound on (path). """
201
202
    assert path
203
    bindsocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
204
    try:
205
        os.unlink(path)
206
    except OSError:
207
        if os.path.exists(path):
208
            raise
209
    try:
210
        bindsocket.bind(path)
211
    except socket.error:
212
        logger.error("Couldn't bind socket on {0}".format(path))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
213
        return None
214
215
    logger.info('Listening on {0}'.format(path))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
216
    bindsocket.listen(0)
217
    return bindsocket
218
219
220 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...
221
    """ Closes provided client stream """
222
    try:
223
        client_stream.shutdown(socket.SHUT_RDWR)
224
        if unix_path:
225
            logger.debug('{0}: Connection closed'.format(unix_path))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
226
        else:
227
            peer = client_stream.getpeername()
228
            logger.debug('{0}:{1}: Connection closed'.format(peer[0], peer[1]))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
229
    except (socket.error, OSError) as exception:
230
        logger.debug('Connection closing error: {0}'.format(exception))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
231
    client_stream.close()
232
233
234 View Code Duplication
class OSPDaemon(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
best-practice introduced by
Too many instance attributes (11/7)
Loading history...
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
best-practice introduced by
Too many public methods (66/20)
Loading history...
235
236
    """ Daemon class for OSP traffic handling.
237
238
    Every scanner wrapper should subclass it and make necessary additions and
239
    changes.
240
    * Add any needed parameters in __init__.
241
    * Implement check() method which verifies scanner availability and other
242
      environment related conditions.
243
    * Implement process_scan_params and exec_scan methods which are
244
      specific to handling the <start_scan> command, executing the wrapped
245
      scanner and storing the results.
246
    * exec_scan() should return 0 if host is dead or not reached, 1 if host is
247
      alive and 2 if scan error or status is unknown.
248
    * Implement other methods that assert to False such as get_scanner_name,
249
      get_scanner_version.
250
    * Use Call set_command_attributes at init time to add scanner command
251
      specific options eg. the w3af profile for w3af wrapper.
252
    """
253
254
    def __init__(self, certfile, keyfile, cafile):
255
        """ Initializes the daemon's internal data. """
256
        # @todo: Actually it makes sense to move the certificate params to
257
        #        a separate function because it is not mandatory anymore to
258
        #        use a TLS setup (unix file socket is an alternative).
259
        #        However, changing this makes it mandatory for any ospd scanner
260
        #        to change the function calls as well. So this breaks the API
261
        #        and should only be done with a major release.
262
        self.certs = dict()
263
        self.certs['cert_file'] = certfile
264
        self.certs['key_file'] = keyfile
265
        self.certs['ca_file'] = cafile
266
        self.scan_collection = ScanCollection()
267
        self.scan_processes = dict()
268
        self.daemon_info = dict()
269
        self.daemon_info['name'] = "OSPd"
270
        self.daemon_info['version'] = __version__
271
        self.daemon_info['description'] = "No description"
272
        self.scanner_info = dict()
273
        self.scanner_info['name'] = 'No name'
274
        self.scanner_info['version'] = 'No version'
275
        self.scanner_info['description'] = 'No description'
276
        self.server_version = None  # Set by the subclass.
277
        self.protocol_version = PROTOCOL_VERSION
278
        self.commands = COMMANDS_TABLE
279
        self.scanner_params = dict()
280
        for name, param in BASE_SCANNER_PARAMS.items():
281
            self.add_scanner_param(name, param)
282
        self.vts = dict()
283
        self.vt_id_pattern = re.compile("[0-9a-zA-Z_\-:.]{1,80}")
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \- was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
284
285
    def set_command_attributes(self, name, attributes):
286
        """ Sets the xml attributes of a specified command. """
287
        if self.command_exists(name):
288
            command = self.commands.get(name)
289
            command['attributes'] = attributes
290
291
    def add_scanner_param(self, name, scanner_param):
292
        """ Add a scanner parameter. """
293
294
        assert name
295
        assert scanner_param
296
        self.scanner_params[name] = scanner_param
297
        command = self.commands.get('start_scan')
298
        command['elements'] = {
299
            'scanner_params':
300
                {k: v['name'] for k, v in self.scanner_params.items()}}
301
302
    def add_vt(self, vt_id, name='', vt_params=None, custom=None):
303
        """ Add a vulnerability test information.
304
305
        Returns: The new number of stored VTs.
306
        -1 in case the VT ID was already present and thus the
307
        new VT was not considered.
308
        -2 in case the vt_id was invalid.
309
        """
310
311
        if not vt_id:
312
            return -2  # no valid vt_id
313
314
        if self.vt_id_pattern.fullmatch(vt_id) is None:
315
            return -2  # no valid vt_id
316
317
        if vt_id in self.vts:
318
            return -1  # The VT was already in the list.
319
320
        self.vts[vt_id] = {'name': name}
321
        if custom is not None:
322
            self.vts[vt_id]["custom"] = custom
323
        if vt_params is not None:
324
            self.vts[vt_id]["vt_params"] = vt_params
325
326
        return len(self.vts)
327
328
    def command_exists(self, name):
329
        """ Checks if a commands exists. """
330
        return name in self.commands.keys()
331
332
    def get_scanner_name(self):
333
        """ Gives the wrapped scanner's name. """
334
        return self.scanner_info['name']
335
336
    def get_scanner_version(self):
337
        """ Gives the wrapped scanner's version. """
338
        return self.scanner_info['version']
339
340
    def get_scanner_description(self):
341
        """ Gives the wrapped scanner's description. """
342
        return self.scanner_info['description']
343
344
    def get_server_version(self):
345
        """ Gives the specific OSP server's version. """
346
        assert self.server_version
347
        return self.server_version
348
349
    def get_protocol_version(self):
350
        """ Gives the OSP's version. """
351
        return self.protocol_version
352
353
    def _preprocess_scan_params(self, xml_params):
354
        """ Processes the scan parameters. """
355
        params = {}
356
        for param in xml_params:
357
            params[param.tag] = param.text or ''
358
        # Set default values.
359
        for key in self.scanner_params:
360
            if key not in params:
361
                params[key] = self.get_scanner_param_default(key)
362
                if self.get_scanner_param_type(key) == 'selection':
363
                    params[key] = params[key].split('|')[0]
364
        # Validate values.
365
        for key in params:
366
            param_type = self.get_scanner_param_type(key)
367
            if not param_type:
368
                continue
369
            if param_type in ['integer', 'boolean']:
370
                try:
371
                    params[key] = int(params[key])
372
                except ValueError:
373
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
374
            if param_type == 'boolean':
375
                if params[key] not in [0, 1]:
376
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
377
            elif param_type == 'selection':
378
                selection = self.get_scanner_param_default(key).split('|')
379
                if params[key] not in selection:
380
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
381
            if self.get_scanner_param_mandatory(key) and params[key] == '':
382
                    raise OSPDError('Mandatory %s value is missing' % key,
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 16 spaces were expected, but 20 were found.
Loading history...
383
                                    'start_scan')
384
        return params
385
386
    def process_scan_params(self, params):
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...
387
        """ This method is to be overridden by the child classes if necessary
388
        """
389
        return params
390
391
    def process_vts_params(self, scanner_vts):
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...
392
        """ Receive an XML object with the Vulnerability Tests an their
393
        parameters to be use in a scan and return a dictionary.
394
395
        @param: XML element with vt subelements. Each vt has an
396
                id attribute. Optinal parameters can be included
397
                as vt child.
398
                Example form:
399
                <vts>
400
                  <vt id='vt1' />
401
                  <vt id='vt2'>
402
                    <vt_param name='param1' type='type'>value</vt_param>
403
                  </vt>
404
                <vts>
405
406
        @return: Dictionary containing the vts attribute and subelements,
407
                 like the VT's id and VT's parameters.
408
                 Example form:
409
                 {v1, vt2: {param1: {'type': type', 'value': value}}}
410
        """
411
        vts = {}
412
        for vt in scanner_vts:
0 ignored issues
show
Coding Style Naming introduced by
The name vt does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
413
            vt_id = vt.attrib.get('id')
414
            vts[vt_id] = {}
415
            for param in vt:
416
                if not param.attrib.get('name'):
417
                    raise OSPDError('Invalid NVT parameter. No parameter name',
418
                                    'start_scan')
419
                ptype = param.attrib.get('type', 'entry')
420
                pvalue = param.text if param.text else ''
421
                pname = param.attrib.get('name')
422
                vts[vt_id][pname] = {'type': ptype, 'value': pvalue}
423
        return vts
424
425
    @staticmethod
426
    def process_credentials_elements(cred_tree):
427
        """ Receive an XML object with the credentials to run
428
        a scan against a given target.
429
430
        @param:
431
        <credentials>
432
          <credential type="up" service="ssh" port="22">
433
            <username>scanuser</username>
434
            <password>mypass</password>
435
          </credential>
436
          <credential type="up" service="smb">
437
            <username>smbuser</username>
438
            <password>mypass</password>
439
          </credential>
440
        </credentials>
441
442
        @return: Dictionary containing the credentials for a given target.
443
                 Example form:
444
                 {'ssh': {'type': type,
445
                          'port': port,
446
                          'username': username,
447
                          'password': pass,
448
                        },
449
                  'smb': {'type': type,
450
                          'username': username,
451
                          'password': pass,
452
                         },
453
                   }
454
        """
455
        credentials = {}
456
        for credential in cred_tree:
457
            service = credential.attrib.get('service')
458
            credentials[service] = {}
459
            credentials[service]['type'] = credential.attrib.get('type')
460
            if service == 'ssh':
461
                credentials[service]['port'] = credential.attrib.get('port')
462
            for param in credential:
463
                credentials[service][param.tag] = param.text
464
465
        return credentials
466
467
    @classmethod
468
    def process_targets_element(cls, scanner_target):
469
        """ Receive an XML object with the target, ports and credentials to run
470
        a scan against.
471
472
        @param: XML element with target subelements. Each target has <hosts>
473
        and <ports> subelements. Hosts can be a single host, a host range,
474
        a comma-separated host list or a network address.
475
        <ports> and  <credentials> are optional. Therefore each ospd-scanner
476
        should check for a valid ones if needed.
477
478
                Example form:
479
                <targets>
480
                  <target>
481
                    <hosts>localhosts</hosts>
482
                    <ports>80,443</ports>
483
                  </target>
484
                  <target>
485
                    <hosts>192.168.0.0/24</hosts>
486
                    <ports>22</ports>
487
                    <credentials>
488
                      <credential type="up" service="ssh" port="22">
489
                        <username>scanuser</username>
490
                        <password>mypass</password>
491
                      </credential>
492
                      <credential type="up" service="smb">
493
                        <username>smbuser</username>
494
                        <password>mypass</password>
495
                      </credential>
496
                    </credentials>
497
                  </target>
498
                </targets>
499
500
        @return: A list of (hosts, port) tuples.
501
                 Example form:
502
                 [['localhost', '80,43'],
503
                  ['192.168.0.0/24', '22', {'smb': {'type': type,
504
                                                    'port': port,
505
                                                    'username': username,
506
                                                    'password': pass,
507
                                                   }}]]
508
        """
509
510
        target_list = []
511
        for target in scanner_target:
512
            ports= ''
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
513
            credentials = {}
514
            for child in target:
515
                if child.tag == 'hosts':
516
                    hosts = child.text
517
                if child.tag == 'ports':
518
                    ports = child.text
519
                if child.tag == 'credentials':
520
                    credentials = cls.process_credentials_elements(child)
521
            if hosts:
0 ignored issues
show
introduced by
The variable hosts does not seem to be defined for all execution paths.
Loading history...
522
                target_list.append([hosts, ports, credentials])
523
            else:
524
                raise OSPDError('No target to scan', 'start_scan')
525
526
        return target_list
527
528
    def handle_start_scan_command(self, scan_et):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (17/15).
Loading history...
529
        """ Handles <start_scan> command.
530
531
        @return: Response string for <start_scan> command.
532
        """
533
534
        target_str = scan_et.attrib.get('target')
535
        ports_str = scan_et.attrib.get('ports')
536
        # For backward compatibility, if target and ports attributes are set,
537
        # <targets> element is ignored.
538
        if target_str is None or ports_str is None:
539
            target_list = scan_et.find('targets')
540
            if target_list is None or not target_list:
541
                raise OSPDError('No targets or ports', 'start_scan')
542
            else:
543
                scan_targets = self.process_targets_element(target_list)
544
        else:
545
            scan_targets = []
546
            for single_target in target_str_to_list(target_str):
547
                scan_targets.append([single_target, ports_str, ''])
548
549
        scan_id = scan_et.attrib.get('scan_id')
550
        if scan_id is not None and scan_id != '' and not valid_uuid(scan_id):
551
            raise OSPDError('Invalid scan_id UUID', 'start_scan')
552
553
        scanner_params = scan_et.find('scanner_params')
554
        if scanner_params is None:
555
            raise OSPDError('No scanner_params element', 'start_scan')
556
557
        params = self._preprocess_scan_params(scanner_params)
558
559
        # VTS is an optional element. If present should not be empty.
560
        vts = {}
561
        scanner_vts = scan_et.find('vts')
562
        if scanner_vts is not None:
563
            if not scanner_vts:
564
                raise OSPDError('VTs list is empty', 'start_scan')
565
            else:
566
                vts = self.process_vts_params(scanner_vts)
567
568
        # Dry run case.
569
        if 'dry_run' in params and int(params['dry_run']):
570
            scan_func = self.dry_run_scan
571
            scan_params = None
572
        else:
573
            scan_func = self.start_scan
574
            scan_params = self.process_scan_params(params)
575
576
        scan_id = self.create_scan(scan_id, scan_targets, target_str, scan_params, vts)
577
        scan_process = multiprocessing.Process(target=scan_func,
578
                                               args=(scan_id, scan_targets))
579
        self.scan_processes[scan_id] = scan_process
580
        scan_process.start()
581
        id_ = ET.Element('id')
582
        id_.text = scan_id
583
        return simple_response_str('start_scan', 200, 'OK', id_)
584
585
    def handle_stop_scan_command(self, scan_et):
586
        """ Handles <stop_scan> command.
587
588
        @return: Response string for <stop_scan> command.
589
        """
590
591
        scan_id = scan_et.attrib.get('scan_id')
592
        if scan_id is None or scan_id == '':
593
            raise OSPDError('No scan_id attribute', 'stop_scan')
594
        scan_process = self.scan_processes.get(scan_id)
595
        if not scan_process:
596
            raise OSPDError('Scan not found {0}.'.format(scan_id), 'stop_scan')
597
        if not scan_process.is_alive():
598
            raise OSPDError('Scan already stopped or finished.', 'stop_scan')
599
600
        logger.info('{0}: Scan stopping {1}.'.format(scan_id, scan_process.ident))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
601
        self.stop_scan(scan_id)
602
        scan_process.terminate()
603
        os.killpg(os.getpgid(scan_process.ident), 15)
604
        scan_process.join()
605
        self.set_scan_progress(scan_id, 100)
606
        self.add_scan_log(scan_id, name='', host='', value='Scan stopped.')
607
        logger.info('{0}: Scan stopped.'.format(scan_id))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
608
        return simple_response_str('stop_scan', 200, 'OK')
609
610
    def stop_scan(self, scan_id):
611
        """ Should be implemented by subclass in case of a clean up before
612
        terminating is needed. """
613
614
    def exec_scan(self, scan_id, target):
615
        """ Asserts to False. Should be implemented by subclass. """
616
        raise NotImplementedError
617
618
    def finish_scan(self, scan_id):
619
        """ Sets a scan as finished. """
620
        self.set_scan_progress(scan_id, 100)
621
        logger.info("{0}: Scan finished.".format(scan_id))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
622
623
    def get_daemon_name(self):
624
        """ Gives osp daemon's name. """
625
        return self.daemon_info['name']
626
627
    def get_daemon_version(self):
628
        """ Gives osp daemon's version. """
629
        return self.daemon_info['version']
630
631
    def get_scanner_param_type(self, param):
632
        """ Returns type of a scanner parameter. """
633
        assert isinstance(param, str)
634
        entry = self.scanner_params.get(param)
635
        if not entry:
636
            return None
637
        return entry.get('type')
638
639
    def get_scanner_param_mandatory(self, param):
640
        """ Returns if a scanner parameter is mandatory. """
641
        assert isinstance(param, str)
642
        entry = self.scanner_params.get(param)
643
        if not entry:
644
            return False
645
        return entry.get('mandatory')
646
647
    def get_scanner_param_default(self, param):
648
        """ Returns default value of a scanner parameter. """
649
        assert isinstance(param, str)
650
        entry = self.scanner_params.get(param)
651
        if not entry:
652
            return None
653
        return entry.get('default')
654
655
    def get_scanner_params_xml(self):
656
        """ Returns the OSP Daemon's scanner params in xml format. """
657
        scanner_params = ET.Element('scanner_params')
658
        for param_id, param in self.scanner_params.items():
659
            param_xml = ET.SubElement(scanner_params, 'scanner_param')
660
            for name, value in [('id', param_id),
661
                                ('type', param['type'])]:
662
                param_xml.set(name, value)
663
            for name, value in [('name', param['name']),
664
                                ('description', param['description']),
665
                                ('default', param['default']),
666
                                ('mandatory', param['mandatory'])]:
667
                elem = ET.SubElement(param_xml, name)
668
                elem.text = str(value)
669
        return scanner_params
670
671
    def new_client_stream(self, sock):
672
        """ Returns a new ssl client stream from bind_socket. """
673
674
        assert sock
675
        newsocket, fromaddr = sock.accept()
676
        logger.debug("New connection from"
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
677
                     " {0}:{1}".format(fromaddr[0], fromaddr[1]))
678
        # NB: Despite the name, ssl.PROTOCOL_SSLv23 selects the highest
679
        # protocol version that both the client and server support. In modern
680
        # Python versions (>= 3.4) it suppports TLS >= 1.0 with SSLv2 and SSLv3
681
        # being disabled. For Python >=3.5, PROTOCOL_SSLv23 is an alias for
682
        # PROTOCOL_TLS which should be used once compatibility with Python 3.4
683
        # is no longer desired.
684
        try:
685
            ssl_socket = ssl.wrap_socket(newsocket, cert_reqs=ssl.CERT_REQUIRED,
686
                                         server_side=True,
687
                                         certfile=self.certs['cert_file'],
688
                                         keyfile=self.certs['key_file'],
689
                                         ca_certs=self.certs['ca_file'],
690
                                         ssl_version=ssl.PROTOCOL_SSLv23)
691
        except (ssl.SSLError, socket.error) as message:
692
            logger.error(message)
693
            return None
694
        return ssl_socket
695
696
    @staticmethod
697
    def write_to_stream(stream, response, block_len=1024):
698
        """
699
        Send the response in blocks of the given len using the
700
        passed method dependending on the socket type.
701
        """
702
        try:
703
            i_start = 0
704
            i_end = block_len
705
            while True:
706
                if i_end > len(response):
707
                    stream(response[i_start:])
708
                    break
709
                stream(response[i_start:i_end])
710
                i_start = i_end
711
                i_end += block_len
712
        except (socket.timeout, socket.error) as exception:
713
            logger.debug('Error sending response to the client: {0}'.format(exception))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
714
715
    def handle_client_stream(self, stream, is_unix=False):
716
        """ Handles stream of data received from client. """
717
718
        assert stream
719
        data = []
720
        stream.settimeout(2)
721
        while True:
722
            try:
723
                if is_unix:
724
                    data.append(stream.recv(1024))
725
                else:
726
                    data.append(stream.read(1024))
727
                if len(data) == 0:
0 ignored issues
show
Unused Code introduced by
Do not use len(SEQUENCE) as condition value
Loading history...
728
                    logger.warning(
729
                        "Empty client stream (Connection unexpectedly closed)")
730
                    return
731
            except (AttributeError, ValueError) as message:
732
                logger.error(message)
733
                return
734
            except (ssl.SSLError) as exception:
735
                logger.debug('Error: {0}'.format(exception[0]))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
736
                break
737
            except (socket.timeout) as exception:
738
                logger.debug('Error: {0}'.format(exception))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
739
                break
740
        data = b''.join(data)
741
        if len(data) <= 0:
742
            logger.debug("Empty client stream")
743
            return
744
        try:
745
            response = self.handle_command(data)
746
        except OSPDError as exception:
747
            response = exception.as_xml()
748
            logger.debug('Command error: {0}'.format(exception.message))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
749
        except Exception:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
750
            logger.exception('While handling client command:')
751
            exception = OSPDError('Fatal error', 'error')
752
            response = exception.as_xml()
753
        if is_unix:
754
            send_method = stream.send
755
        else:
756
            send_method = stream.write
757
        self.write_to_stream(send_method, response)
758
759
    def start_scan(self, scan_id, targets):
760
        """ Starts the scan with scan_id. """
761
762
        os.setsid()
763
        logger.info("{0}: Scan started.".format(scan_id))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
764
        target_list = targets
765
        if target_list is None or not target_list:
766
            raise OSPDError('Erroneous targets list', 'start_scan')
767
        for index, target in enumerate(target_list):
768
            progress = float(index) * 100 / len(target_list)
769
            self.set_scan_progress(scan_id, int(progress))
770
            logger.info("{0}: Host scan started on ports {1}.".format(target[0],target[1]))
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
771
            try:
772
                ret = self.exec_scan(scan_id, target[0])
773
                if ret == 0:
774
                    self.add_scan_host_detail(scan_id, name='host_status',
775
                                              host=target[0], value='0')
776
                elif ret == 1:
777
                    self.add_scan_host_detail(scan_id, name='host_status',
778
                                              host=target[0], value='1')
779
                elif ret == 2:
780
                    self.add_scan_host_detail(scan_id, name='host_status',
781
                                              host=target[0], value='2')
782
                else:
783
                    logger.debug('{0}: No host status returned'.format(target[0]))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
784
            except Exception as e:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
785
                self.add_scan_error(scan_id, name='', host=target[0],
786
                                    value='Host process failure (%s).' % e)
787
                logger.exception('While scanning {0}:'.format(target[0]))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
788
            else:
789
                logger.info("{0}: Host scan finished.".format(target[0]))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
790
791
        self.finish_scan(scan_id)
792
793
    def dry_run_scan(self, scan_id, targets):
794
        """ Dry runs a scan. """
795
796
        os.setsid()
797
        #target_list = target_str_to_list(target_str)
798
        for _, target in enumerate(targets):
799
            host = resolve_hostname(target[0])
800
            if host is None:
801
                logger.info("Couldn't resolve {0}.".format(target[0]))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
802
                continue
803
            port = self.get_scan_ports(scan_id, target=target[0])
804
            logger.info("{0}:{1}: Dry run mode.".format(host, port))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
805
            self.add_scan_log(scan_id, name='', host=host,
806
                              value='Dry run result')
807
        self.finish_scan(scan_id)
808
809
    def handle_timeout(self, scan_id, host):
810
        """ Handles scanner reaching timeout error. """
811
        self.add_scan_error(scan_id, host=host, name="Timeout",
812
                            value="{0} exec timeout."
813
                            .format(self.get_scanner_name()))
814
815
    def set_scan_progress(self, scan_id, progress):
816
        """ Sets scan_id scan's progress which is a number between 0 and 100. """
817
        self.scan_collection.set_progress(scan_id, progress)
818
819
    def scan_exists(self, scan_id):
820
        """ Checks if a scan with ID scan_id is in collection.
821
822
        @return: 1 if scan exists, 0 otherwise.
823
        """
824
        return self.scan_collection.id_exists(scan_id)
825
826
    def handle_get_scans_command(self, scan_et):
827
        """ Handles <get_scans> command.
828
829
        @return: Response string for <get_scans> command.
830
        """
831
832
        scan_id = scan_et.attrib.get('scan_id')
833
        details = scan_et.attrib.get('details')
834
        if details and details == '0':
835
            details = False
836
        else:
837
            details = True
838
839
        responses = []
840
        if scan_id and scan_id in self.scan_collection.ids_iterator():
841
            self.check_scan_process(scan_id)
842
            scan = self.get_scan_xml(scan_id, details)
843
            responses.append(scan)
844
        elif scan_id:
845
            text = "Failed to find scan '{0}'".format(scan_id)
846
            return simple_response_str('get_scans', 404, text)
847
        else:
848
            for scan_id in self.scan_collection.ids_iterator():
849
                self.check_scan_process(scan_id)
850
                scan = self.get_scan_xml(scan_id, details)
851
                responses.append(scan)
852
        return simple_response_str('get_scans', 200, 'OK', responses)
853
854
    def handle_get_vts_command(self, vt_et):
855
        """ Handles <get_vts> command.
856
857
        @return: Response string for <get_vts> command.
858
        """
859
860
        vt_id = vt_et.attrib.get('vt_id')
861
862
        if vt_id and vt_id not in self.vts:
863
            text = "Failed to find vulnerability test '{0}'".format(vt_id)
864
            return simple_response_str('get_vts', 404, text)
865
866
        responses = []
867
868
        if vt_id:
869
            vts_xml = self.get_vts_xml(vt_id)
870
        else:
871
            vts_xml = self.get_vts_xml()
872
873
        responses.append(vts_xml)
874
875
        return simple_response_str('get_vts', 200, 'OK', responses)
876
877
    def handle_help_command(self, scan_et):
878
        """ Handles <help> command.
879
880
        @return: Response string for <help> command.
881
        """
882
        help_format = scan_et.attrib.get('format')
883
        if help_format is None or help_format == "text":
884
            # Default help format is text.
885
            return simple_response_str('help', 200, 'OK',
886
                                       self.get_help_text())
887
        elif help_format == "xml":
888
            text = self.get_xml_str(self.commands)
889
            return simple_response_str('help', 200, 'OK', text)
890
        raise OSPDError('Bogus help format', 'help')
891
892
    def get_help_text(self):
893
        """ Returns the help output in plain text format."""
894
895
        txt = str('\n')
896
        for name, info in self.commands.items():
897
            command_txt = "\t{0: <22} {1}\n".format(name, info['description'])
898
            if info['attributes']:
899
                command_txt = ''.join([command_txt, "\t Attributes:\n"])
900
                for attrname, attrdesc in info['attributes'].items():
901
                    attr_txt = "\t  {0: <22} {1}\n".format(attrname, attrdesc)
902
                    command_txt = ''.join([command_txt, attr_txt])
903
            if info['elements']:
904
                command_txt = ''.join([command_txt, "\t Elements:\n",
905
                                       self.elements_as_text(info['elements'])])
906
            txt = ''.join([txt, command_txt])
907
        return txt
908
909
    def elements_as_text(self, elems, indent=2):
910
        """ Returns the elems dictionary as formatted plain text. """
911
        assert elems
912
        text = ""
913
        for elename, eledesc in elems.items():
914
            if isinstance(eledesc, dict):
915
                desc_txt = self.elements_as_text(eledesc, indent + 2)
916
                desc_txt = ''.join(['\n', desc_txt])
917
            elif isinstance(eledesc, str):
918
                desc_txt = ''.join([eledesc, '\n'])
919
            else:
920
                assert False, "Only string or dictionary"
921
            ele_txt = "\t{0}{1: <22} {2}".format(' ' * indent, elename,
922
                                                 desc_txt)
0 ignored issues
show
introduced by
The variable desc_txt does not seem to be defined for all execution paths.
Loading history...
923
            text = ''.join([text, ele_txt])
924
        return text
925
926
    def handle_delete_scan_command(self, scan_et):
927
        """ Handles <delete_scan> command.
928
929
        @return: Response string for <delete_scan> command.
930
        """
931
        scan_id = scan_et.attrib.get('scan_id')
932
        if scan_id is None:
933
            return simple_response_str('delete_scan', 404,
934
                                       'No scan_id attribute')
935
936
        if not self.scan_exists(scan_id):
937
            text = "Failed to find scan '{0}'".format(scan_id)
938
            return simple_response_str('delete_scan', 404, text)
939
        self.check_scan_process(scan_id)
940
        if self.delete_scan(scan_id):
941
            return simple_response_str('delete_scan', 200, 'OK')
942
        raise OSPDError('Scan in progress', 'delete_scan')
943
944
    def delete_scan(self, scan_id):
945
        """ Deletes scan_id scan from collection.
946
947
        @return: 1 if scan deleted, 0 otherwise.
948
        """
949
        try:
950
            del self.scan_processes[scan_id]
951
        except KeyError:
952
            logger.debug('Scan process for {0} not found'.format(scan_id))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
953
        return self.scan_collection.delete_scan(scan_id)
954
955
    def get_scan_results_xml(self, scan_id):
956
        """ Gets scan_id scan's results in XML format.
957
958
        @return: String of scan results in xml.
959
        """
960
        results = ET.Element('results')
961
        for result in self.scan_collection.results_iterator(scan_id):
962
            results.append(get_result_xml(result))
963
964
        logger.info('Returning %d results', len(results))
965
        return results
966
967
    def get_xml_str(self, data):
968
        """ Creates a string in XML Format using the provided data structure.
969
970
        @param: Dictionary of xml tags and their elements.
971
972
        @return: String of data in xml format.
973
        """
974
975
        responses = []
976
        for tag, value in data.items():
977
            elem = ET.Element(tag)
978
            if isinstance(value, dict):
979
                for value in self.get_xml_str(value):
0 ignored issues
show
Comprehensibility Bug introduced by
value is re-defining a name which is already available in the outer-scope (previously defined on line 976).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
980
                    elem.append(value)
981
            elif isinstance(value, list):
982
                value = ', '.join([m for m in value])
983
                elem.text = value
984
            else:
985
                elem.text = value
986
            responses.append(elem)
987
        return responses
988
989
    def get_scan_xml(self, scan_id, detailed=True):
990
        """ Gets scan in XML format.
991
992
        @return: String of scan in XML format.
993
        """
994
        if not scan_id:
995
            return ET.Element('scan')
996
997
        target = self.get_scan_target(scan_id)
998
        progress = self.get_scan_progress(scan_id)
999
        start_time = self.get_scan_start_time(scan_id)
1000
        end_time = self.get_scan_end_time(scan_id)
1001
        response = ET.Element('scan')
1002
        for name, value in [('id', scan_id),
1003
                            ('target', target),
1004
                            ('progress', progress),
1005
                            ('start_time', start_time),
1006
                            ('end_time', end_time)]:
1007
            response.set(name, str(value))
1008
        if detailed:
1009
            response.append(self.get_scan_results_xml(scan_id))
1010
        return response
1011
1012
    def get_custom_vt_as_xml_str(self, custom):
0 ignored issues
show
Unused Code introduced by
The argument custom seems to be unused.
Loading history...
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...
1013
        """ Create a string representation of the XML object from the
1014
        custom data object.
1015
        This needs to be implemented by each ospd wrapper, in case
1016
        custom elements for VTs are used.
1017
1018
        The custom XML object which is returned will be embedded
1019
        into a <custom></custom> element.
1020
1021
        @return: XML object as string for custom data.
1022
        """
1023
        return ''
1024
1025
    def get_params_vt_as_xml_str(self, vt_params):
0 ignored issues
show
Unused Code introduced by
The argument vt_params seems to be unused.
Loading history...
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...
1026
        """ Create a string representation of the XML object from the
1027
        vt_params data object.
1028
        This needs to be implemented by each ospd wrapper, in case
1029
        vt_params elements for VTs are used.
1030
1031
        The vt_params XML object which is returned will be embedded
1032
        into a <vt_params></vt_params> element.
1033
1034
        @return: XML object as string for vt parameters data.
1035
        """
1036
        return ''
1037
1038
    def get_vt_xml(self, vt_id):
1039
        """ Gets a single vulnerability test information in XML format.
1040
1041
        @return: String of single vulnerability test information in XML format.
1042
        """
1043
        if not vt_id:
1044
            return ET.Element('vt')
1045
1046
        vt = self.vts.get(vt_id)
0 ignored issues
show
Coding Style Naming introduced by
The name vt does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
1047
1048
        name = vt.get('name')
1049
        vt_xml = ET.Element('vt')
1050
        vt_xml.set('id', vt_id)
1051
1052
        for name, value in [('name', name)]:
1053
            elem = ET.SubElement(vt_xml, name)
1054
            elem.text = str(value)
1055
1056
        if vt.get('custom'):
1057
            custom_xml_str = '<custom>%s</custom>' % self.get_custom_vt_as_xml_str(vt.get('custom'))
1058
            vt_xml.append(secET.fromstring(custom_xml_str))
1059
1060
        if vt.get('vt_params'):
1061
            params_xml_str = '<vt_params>%s</vt_params>' % self.get_params_vt_as_xml_str(vt.get('vt_params'))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (109/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1062
            vt_xml.append(secET.fromstring(params_xml_str))
1063
1064
        return vt_xml
1065
1066
    def get_vts_xml(self, vt_id=''):
1067
        """ Gets collection of vulnerability test information in XML format.
1068
        If vt_id is specified, the collection will contain only this vt, of found.
1069
        If no vt_id is specified, the collection will contain all vts.
1070
1071
        @return: String of collection of vulnerability test information in XML format.
1072
        """
1073
1074
        vts_xml = ET.Element('vts')
1075
1076
        if vt_id != '':
1077
            vts_xml.append(self.get_vt_xml(vt_id))
1078
        else:
1079
            for vt_id in self.vts:
0 ignored issues
show
unused-code introduced by
Redefining argument with the local name 'vt_id'
Loading history...
1080
                vts_xml.append(self.get_vt_xml(vt_id))
1081
1082
        return vts_xml
1083
1084
    def handle_get_scanner_details(self):
1085
        """ Handles <get_scanner_details> command.
1086
1087
        @return: Response string for <get_scanner_details> command.
1088
        """
1089
        desc_xml = ET.Element('description')
1090
        desc_xml.text = self.get_scanner_description()
1091
        details = [
1092
            desc_xml,
1093
            self.get_scanner_params_xml()
1094
        ]
1095
        return simple_response_str('get_scanner_details', 200, 'OK', details)
1096
1097
    def handle_get_version_command(self):
1098
        """ Handles <get_version> command.
1099
1100
        @return: Response string for <get_version> command.
1101
        """
1102
        protocol = ET.Element('protocol')
1103
        for name, value in [('name', 'OSP'), ('version', self.get_protocol_version())]:
1104
            elem = ET.SubElement(protocol, name)
1105
            elem.text = value
1106
1107
        daemon = ET.Element('daemon')
1108
        for name, value in [('name', self.get_daemon_name()), ('version', self.get_daemon_version())]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (102/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1109
            elem = ET.SubElement(daemon, name)
1110
            elem.text = value
1111
1112
        scanner = ET.Element('scanner')
1113
        for name, value in [('name', self.get_scanner_name()), ('version', self.get_scanner_version())]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1114
            elem = ET.SubElement(scanner, name)
1115
            elem.text = value
1116
1117
        return simple_response_str('get_version', 200, 'OK', [protocol, daemon, scanner])
1118
1119
    def handle_command(self, command):
0 ignored issues
show
best-practice introduced by
Too many return statements (8/6)
Loading history...
1120
        """ Handles an osp command in a string.
1121
1122
        @return: OSP Response to command.
1123
        """
1124
        try:
1125
            tree = secET.fromstring(command)
1126
        except secET.ParseError:
1127
            logger.debug("Erroneous client input: {0}".format(command))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
1128
            raise OSPDError('Invalid data')
1129
1130
        if not self.command_exists(tree.tag) and tree.tag != "authenticate":
1131
            raise OSPDError('Bogus command name')
1132
1133
        if tree.tag == "get_version":
1134
            return self.handle_get_version_command()
1135
        elif tree.tag == "start_scan":
1136
            return self.handle_start_scan_command(tree)
1137
        elif tree.tag == "stop_scan":
1138
            return self.handle_stop_scan_command(tree)
1139
        elif tree.tag == "get_scans":
1140
            return self.handle_get_scans_command(tree)
1141
        elif tree.tag == "get_vts":
1142
            return self.handle_get_vts_command(tree)
1143
        elif tree.tag == "delete_scan":
1144
            return self.handle_delete_scan_command(tree)
1145
        elif tree.tag == "help":
1146
            return self.handle_help_command(tree)
1147
        elif tree.tag == "get_scanner_details":
1148
            return self.handle_get_scanner_details()
1149
        else:
1150
            assert False, "Unhandled command: {0}".format(tree.tag)
1151
1152
    def check(self):
1153
        """ Asserts to False. Should be implemented by subclass. """
1154
        raise NotImplementedError
1155
1156
    def run(self, address, port, unix_path):
1157
        """ Starts the Daemon, handling commands until interrupted.
1158
1159
        @return False if error. Runs indefinitely otherwise.
1160
        """
1161
        assert address or unix_path
1162
        if unix_path:
1163
            sock = bind_unix_socket(unix_path)
1164
        else:
1165
            sock = bind_socket(address, port)
1166
        if sock is None:
1167
            return False
1168
1169
        try:
1170
            while True:
1171
                if unix_path:
1172
                    client_stream, _ = sock.accept()
1173
                    logger.debug("New connection from {0}".format(unix_path))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
1174
                    self.handle_client_stream(client_stream, True)
1175
                else:
1176
                    client_stream = self.new_client_stream(sock)
1177
                    if client_stream is None:
1178
                        continue
1179
                    self.handle_client_stream(client_stream, False)
1180
                close_client_stream(client_stream, unix_path)
1181
        except KeyboardInterrupt:
1182
            logger.info("Received Ctrl-C shutting-down ...")
1183
        finally:
1184
            sock.shutdown(socket.SHUT_RDWR)
1185
            sock.close()
1186
1187
    def create_scan(self, scan_id, targets, target_str, options, vts):
0 ignored issues
show
best-practice introduced by
Too many arguments (6/5)
Loading history...
1188
        """ Creates a new scan.
1189
1190
        @target: Target to scan.
1191
        @options: Miscellaneous scan options.
1192
1193
        @return: New scan's ID.
1194
        """
1195
        return self.scan_collection.create_scan(scan_id, targets, target_str, options, vts)
1196
1197
    def get_scan_options(self, scan_id):
1198
        """ Gives a scan's list of options. """
1199
        return self.scan_collection.get_options(scan_id)
1200
1201
    def set_scan_option(self, scan_id, name, value):
1202
        """ Sets a scan's option to a provided value. """
1203
        return self.scan_collection.set_option(scan_id, name, value)
1204
1205
    def check_scan_process(self, scan_id):
1206
        """ Check the scan's process, and terminate the scan if not alive. """
1207
        scan_process = self.scan_processes[scan_id]
1208
        progress = self.get_scan_progress(scan_id)
1209
        if progress < 100 and not scan_process.is_alive():
1210
            self.set_scan_progress(scan_id, 100)
1211
            self.add_scan_error(scan_id, name="", host="",
1212
                                value="Scan process failure.")
1213
            logger.info("{0}: Scan terminated.".format(scan_id))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
1214
        elif progress == 100:
1215
            scan_process.join()
1216
1217
    def get_scan_progress(self, scan_id):
1218
        """ Gives a scan's current progress value. """
1219
        return self.scan_collection.get_progress(scan_id)
1220
1221
    def get_scan_target(self, scan_id):
1222
        """ Gives a scan's target. """
1223
        return self.scan_collection.get_target(scan_id)
1224
1225
    def get_scan_ports(self, scan_id, target=''):
1226
        """ Gives a scan's ports list. """
1227
        return self.scan_collection.get_ports(scan_id, target)
1228
1229
    def get_scan_credentials(self, scan_id, target=''):
1230
        """ Gives a scan's credential list. If a target is passed gives
1231
        the credential list for the given target. """
1232
        return self.scan_collection.get_credentials(scan_id, target)
1233
1234
    def get_scan_vts(self, scan_id):
1235
        """ Gives a scan's vts list. """
1236
        return self.scan_collection.get_vts(scan_id)
1237
1238
    def get_scan_start_time(self, scan_id):
1239
        """ Gives a scan's start time. """
1240
        return self.scan_collection.get_start_time(scan_id)
1241
1242
    def get_scan_end_time(self, scan_id):
1243
        """ Gives a scan's end time. """
1244
        return self.scan_collection.get_end_time(scan_id)
1245
1246
    def add_scan_log(self, scan_id, host='', name='', value='', port='',
0 ignored issues
show
best-practice introduced by
Too many arguments (8/5)
Loading history...
1247
                     test_id='', qod=''):
1248
        """ Adds a log result to scan_id scan. """
1249
        self.scan_collection.add_result(scan_id, ResultType.LOG, host, name,
1250
                                        value, port, test_id, 0.0, qod)
1251
1252
    def add_scan_error(self, scan_id, host='', name='', value='', port=''):
0 ignored issues
show
best-practice introduced by
Too many arguments (6/5)
Loading history...
1253
        """ Adds an error result to scan_id scan. """
1254
        self.scan_collection.add_result(scan_id, ResultType.ERROR, host, name,
1255
                                        value, port)
1256
1257
    def add_scan_host_detail(self, scan_id, host='', name='', value=''):
1258
        """ Adds a host detail result to scan_id scan. """
1259
        self.scan_collection.add_result(scan_id, ResultType.HOST_DETAIL, host,
1260
                                        name, value)
1261
1262
    def add_scan_alarm(self, scan_id, host='', name='', value='', port='',
0 ignored issues
show
best-practice introduced by
Too many arguments (9/5)
Loading history...
1263
                       test_id='', severity='', qod=''):
1264
        """ Adds an alarm result to scan_id scan. """
1265
        self.scan_collection.add_result(scan_id, ResultType.ALARM, host, name,
1266
                                        value, port, test_id, severity, qod)
1267