Passed
Pull Request — master (#42)
by
unknown
01:26
created

ospd.ospd.OSPDaemon.get_scanner_name()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 3
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 3
loc 3
rs 10
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
0 ignored issues
show
coding-style introduced by
Too many lines in module (1309/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
import time
0 ignored issues
show
introduced by
standard import "import time" should be placed before "import defusedxml.ElementTree as secET"
Loading history...
44
45
from ospd import __version__
46
from ospd.misc import ScanCollection, ResultType, target_str_to_list
47
from ospd.misc import resolve_hostname, valid_uuid
48
49
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...
50
51
PROTOCOL_VERSION = "1.2"
52
53
BASE_SCANNER_PARAMS = {
54
    'debug_mode': {
55
        'type': 'boolean',
56
        'name': 'Debug Mode',
57
        'default': 0,
58
        'mandatory': 0,
59
        'description': 'Whether to get extra scan debug information.',
60
    },
61
    'dry_run': {
62
        'type': 'boolean',
63
        'name': 'Dry Run',
64
        'default': 0,
65
        'mandatory': 0,
66
        'description': 'Whether to dry run scan.',
67
    },
68
}
69
70
COMMANDS_TABLE = {
71
    'start_scan': {
72
        'description': 'Start a new scan.',
73
        'attributes': {
74
            'target': 'Target host to scan',
75
            'ports': 'Ports list to scan',
76
            'scan_id': 'Optional UUID value to use as scan ID',
77
            'parallel': 'Optional nummer of parallel target to scan',
78
        },
79
        'elements': None
80
    },
81
    'stop_scan': {
82
        'description': 'Stop a currently running scan.',
83
        'attributes': {
84
            'scan_id': 'ID of scan to stop.'
85
        },
86
        'elements': None
87
    },
88
    'help': {
89
        'description': 'Print the commands help.',
90
        'attributes': {
91
            'format': 'Help format. Could be text or xml.'
92
        },
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
        },
108
        'elements': None
109
    },
110
    'delete_scan': {
111
        'description': 'Delete a finished scan.',
112
        'attributes': {
113
            'scan_id': 'ID of scan to delete.'
114
        },
115
        'elements': None
116
    },
117
    'get_version': {
118
        'description': 'Return various versions.',
119
        'attributes': None,
120
        'elements': None
121
    },
122
    'get_scanner_details': {
123
        'description': 'Return scanner description and parameters',
124
        'attributes': None,
125
        'elements': None
126
    }
127
}
128
129
130 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...
131
    """ Formats a scan result to XML format. """
132
    result_xml = ET.Element('result')
133
    for name, value in [('name', result['name']),
134
                        ('type', ResultType.get_str(result['type'])),
135
                        ('severity', result['severity']),
136
                        ('host', result['host']),
137
                        ('test_id', result['test_id']),
138
                        ('port', result['port']),
139
                        ('qod', result['qod'])]:
140
        result_xml.set(name, str(value))
141
    result_xml.text = result['value']
142
    return result_xml
143
144
145 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...
146
    """ Creates an OSP response XML string.
147
148
    @param: OSP Command to respond to.
149
    @param: Status of the response.
150
    @param: Status text of the response.
151
    @param: Text part of the response XML element.
152
153
    @return: String of response in xml format.
154
    """
155
    response = ET.Element('%s_response' % command)
156
    for name, value in [('status', str(status)), ('status_text', status_text)]:
157
        response.set(name, str(value))
158
    if isinstance(content, list):
159
        for elem in content:
160
            response.append(elem)
161
    elif isinstance(content, ET.Element):
162
        response.append(content)
163
    else:
164
        response.text = content
165
    return ET.tostring(response)
166
167
168 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...
169
170
    """ This is an exception that will result in an error message to the
171
    client """
172
173
    def __init__(self, message, command='osp', status=400):
174
        super(OSPDError, self).__init__()
175
        self.message = message
176
        self.command = command
177
        self.status = status
178
179
    def as_xml(self):
180
        """ Return the error in xml format. """
181
        return simple_response_str(self.command, self.status, self.message)
182
183
184 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...
185
    """ Returns a socket bound on (address:port). """
186
187
    assert address
188
    assert port
189
    bindsocket = socket.socket()
190
    try:
191
        bindsocket.bind((address, port))
192
    except socket.error:
193
        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...
194
                     .format(address, port))
195
        return None
196
197
    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...
198
    bindsocket.listen(0)
199
    return bindsocket
200
201 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...
202
    """ Returns a unix file socket bound on (path). """
203
204
    assert path
205
    bindsocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
206
    try:
207
        os.unlink(path)
208
    except OSError:
209
        if os.path.exists(path):
210
            raise
211
    try:
212
        bindsocket.bind(path)
213
    except socket.error:
214
        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...
215
        return None
216
217
    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...
218
    bindsocket.listen(0)
219
    return bindsocket
220
221
222 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...
223
    """ Closes provided client stream """
224
    try:
225
        client_stream.shutdown(socket.SHUT_RDWR)
226
        if unix_path:
227
            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...
228
        else:
229
            peer = client_stream.getpeername()
230
            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...
231
    except (socket.error, OSError) as exception:
232
        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...
233
    client_stream.close()
234
235
236 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 (68/20)
Loading history...
237
238
    """ Daemon class for OSP traffic handling.
239
240
    Every scanner wrapper should subclass it and make necessary additions and
241
    changes.
242
    * Add any needed parameters in __init__.
243
    * Implement check() method which verifies scanner availability and other
244
      environment related conditions.
245
    * Implement process_scan_params and exec_scan methods which are
246
      specific to handling the <start_scan> command, executing the wrapped
247
      scanner and storing the results.
248
    * exec_scan() should return 0 if host is dead or not reached, 1 if host is
249
      alive and 2 if scan error or status is unknown.
250
    * Implement other methods that assert to False such as get_scanner_name,
251
      get_scanner_version.
252
    * Use Call set_command_attributes at init time to add scanner command
253
      specific options eg. the w3af profile for w3af wrapper.
254
    """
255
256
    def __init__(self, certfile, keyfile, cafile):
257
        """ Initializes the daemon's internal data. """
258
        # @todo: Actually it makes sense to move the certificate params to
259
        #        a separate function because it is not mandatory anymore to
260
        #        use a TLS setup (unix file socket is an alternative).
261
        #        However, changing this makes it mandatory for any ospd scanner
262
        #        to change the function calls as well. So this breaks the API
263
        #        and should only be done with a major release.
264
        self.certs = dict()
265
        self.certs['cert_file'] = certfile
266
        self.certs['key_file'] = keyfile
267
        self.certs['ca_file'] = cafile
268
        self.scan_collection = ScanCollection()
269
        self.scan_processes = dict()
270
        self.daemon_info = dict()
271
        self.daemon_info['name'] = "OSPd"
272
        self.daemon_info['version'] = __version__
273
        self.daemon_info['description'] = "No description"
274
        self.scanner_info = dict()
275
        self.scanner_info['name'] = 'No name'
276
        self.scanner_info['version'] = 'No version'
277
        self.scanner_info['description'] = 'No description'
278
        self.server_version = None  # Set by the subclass.
279
        self.protocol_version = PROTOCOL_VERSION
280
        self.commands = COMMANDS_TABLE
281
        self.scanner_params = dict()
282
        for name, param in BASE_SCANNER_PARAMS.items():
283
            self.add_scanner_param(name, param)
284
        self.vts = dict()
285
        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...
286
287
    def set_command_attributes(self, name, attributes):
288
        """ Sets the xml attributes of a specified command. """
289
        if self.command_exists(name):
290
            command = self.commands.get(name)
291
            command['attributes'] = attributes
292
293
    def add_scanner_param(self, name, scanner_param):
294
        """ Add a scanner parameter. """
295
296
        assert name
297
        assert scanner_param
298
        self.scanner_params[name] = scanner_param
299
        command = self.commands.get('start_scan')
300
        command['elements'] = {
301
            'scanner_params':
302
                {k: v['name'] for k, v in self.scanner_params.items()}}
303
304
    def add_vt(self, vt_id, name='', vt_params=None, custom=None):
305
        """ Add a vulnerability test information.
306
307
        Returns: The new number of stored VTs.
308
        -1 in case the VT ID was already present and thus the
309
        new VT was not considered.
310
        -2 in case the vt_id was invalid.
311
        """
312
313
        if not vt_id:
314
            return -2  # no valid vt_id
315
316
        if self.vt_id_pattern.fullmatch(vt_id) is None:
317
            return -2  # no valid vt_id
318
319
        if vt_id in self.vts:
320
            return -1  # The VT was already in the list.
321
322
        self.vts[vt_id] = {'name': name}
323
        if custom is not None:
324
            self.vts[vt_id]["custom"] = custom
325
        if vt_params is not None:
326
            self.vts[vt_id]["vt_params"] = vt_params
327
328
        return len(self.vts)
329
330
    def command_exists(self, name):
331
        """ Checks if a commands exists. """
332
        return name in self.commands.keys()
333
334
    def get_scanner_name(self):
335
        """ Gives the wrapped scanner's name. """
336
        return self.scanner_info['name']
337
338
    def get_scanner_version(self):
339
        """ Gives the wrapped scanner's version. """
340
        return self.scanner_info['version']
341
342
    def get_scanner_description(self):
343
        """ Gives the wrapped scanner's description. """
344
        return self.scanner_info['description']
345
346
    def get_server_version(self):
347
        """ Gives the specific OSP server's version. """
348
        assert self.server_version
349
        return self.server_version
350
351
    def get_protocol_version(self):
352
        """ Gives the OSP's version. """
353
        return self.protocol_version
354
355
    def _preprocess_scan_params(self, xml_params):
356
        """ Processes the scan parameters. """
357
        params = {}
358
        for param in xml_params:
359
            params[param.tag] = param.text or ''
360
        # Set default values.
361
        for key in self.scanner_params:
362
            if key not in params:
363
                params[key] = self.get_scanner_param_default(key)
364
                if self.get_scanner_param_type(key) == 'selection':
365
                    params[key] = params[key].split('|')[0]
366
        # Validate values.
367
        for key in params:
368
            param_type = self.get_scanner_param_type(key)
369
            if not param_type:
370
                continue
371
            if param_type in ['integer', 'boolean']:
372
                try:
373
                    params[key] = int(params[key])
374
                except ValueError:
375
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
376
            if param_type == 'boolean':
377
                if params[key] not in [0, 1]:
378
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
379
            elif param_type == 'selection':
380
                selection = self.get_scanner_param_default(key).split('|')
381
                if params[key] not in selection:
382
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
383
            if self.get_scanner_param_mandatory(key) and params[key] == '':
384
                    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...
385
                                    'start_scan')
386
        return params
387
388
    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...
389
        """ This method is to be overridden by the child classes if necessary
390
        """
391
        return params
392
393
    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...
394
        """ Receive an XML object with the Vulnerability Tests an their
395
        parameters to be use in a scan and return a dictionary.
396
397
        @param: XML element with vt subelements. Each vt has an
398
                id attribute. Optinal parameters can be included
399
                as vt child.
400
                Example form:
401
                <vts>
402
                  <vt id='vt1' />
403
                  <vt id='vt2'>
404
                    <vt_param name='param1' type='type'>value</vt_param>
405
                  </vt>
406
                <vts>
407
408
        @return: Dictionary containing the vts attribute and subelements,
409
                 like the VT's id and VT's parameters.
410
                 Example form:
411
                 {v1, vt2: {param1: {'type': type', 'value': value}}}
412
        """
413
        vts = {}
414
        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...
415
            vt_id = vt.attrib.get('id')
416
            vts[vt_id] = {}
417
            for param in vt:
418
                if not param.attrib.get('name'):
419
                    raise OSPDError('Invalid NVT parameter. No parameter name',
420
                                    'start_scan')
421
                ptype = param.attrib.get('type', 'entry')
422
                pvalue = param.text if param.text else ''
423
                pname = param.attrib.get('name')
424
                vts[vt_id][pname] = {'type': ptype, 'value': pvalue}
425
        return vts
426
427
    @staticmethod
428
    def process_credentials_elements(cred_tree):
429
        """ Receive an XML object with the credentials to run
430
        a scan against a given target.
431
432
        @param:
433
        <credentials>
434
          <credential type="up" service="ssh" port="22">
435
            <username>scanuser</username>
436
            <password>mypass</password>
437
          </credential>
438
          <credential type="up" service="smb">
439
            <username>smbuser</username>
440
            <password>mypass</password>
441
          </credential>
442
        </credentials>
443
444
        @return: Dictionary containing the credentials for a given target.
445
                 Example form:
446
                 {'ssh': {'type': type,
447
                          'port': port,
448
                          'username': username,
449
                          'password': pass,
450
                        },
451
                  'smb': {'type': type,
452
                          'username': username,
453
                          'password': pass,
454
                         },
455
                   }
456
        """
457
        credentials = {}
458
        for credential in cred_tree:
459
            service = credential.attrib.get('service')
460
            credentials[service] = {}
461
            credentials[service]['type'] = credential.attrib.get('type')
462
            if service == 'ssh':
463
                credentials[service]['port'] = credential.attrib.get('port')
464
            for param in credential:
465
                credentials[service][param.tag] = param.text
466
467
        return credentials
468
469
    @classmethod
470
    def process_targets_element(cls, scanner_target):
471
        """ Receive an XML object with the target, ports and credentials to run
472
        a scan against.
473
474
        @param: XML element with target subelements. Each target has <hosts>
475
        and <ports> subelements. Hosts can be a single host, a host range,
476
        a comma-separated host list or a network address.
477
        <ports> and  <credentials> are optional. Therefore each ospd-scanner
478
        should check for a valid ones if needed.
479
480
                Example form:
481
                <targets>
482
                  <target>
483
                    <hosts>localhosts</hosts>
484
                    <ports>80,443</ports>
485
                  </target>
486
                  <target>
487
                    <hosts>192.168.0.0/24</hosts>
488
                    <ports>22</ports>
489
                    <credentials>
490
                      <credential type="up" service="ssh" port="22">
491
                        <username>scanuser</username>
492
                        <password>mypass</password>
493
                      </credential>
494
                      <credential type="up" service="smb">
495
                        <username>smbuser</username>
496
                        <password>mypass</password>
497
                      </credential>
498
                    </credentials>
499
                  </target>
500
                </targets>
501
502
        @return: A list of (hosts, port) tuples.
503
                 Example form:
504
                 [['localhost', '80,43'],
505
                  ['192.168.0.0/24', '22', {'smb': {'type': type,
506
                                                    'port': port,
507
                                                    'username': username,
508
                                                    'password': pass,
509
                                                   }}]]
510
        """
511
512
        target_list = []
513
        for target in scanner_target:
514
            ports= ''
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
515
            credentials = {}
516
            for child in target:
517
                if child.tag == 'hosts':
518
                    hosts = child.text
519
                if child.tag == 'ports':
520
                    ports = child.text
521
                if child.tag == 'credentials':
522
                    credentials = cls.process_credentials_elements(child)
523
            if hosts:
0 ignored issues
show
introduced by
The variable hosts does not seem to be defined for all execution paths.
Loading history...
524
                target_list.append([hosts, ports, credentials])
525
            else:
526
                raise OSPDError('No target to scan', 'start_scan')
527
528
        return target_list
529
530
    def handle_start_scan_command(self, scan_et):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (18/15).
Loading history...
531
        """ Handles <start_scan> command.
532
533
        @return: Response string for <start_scan> command.
534
        """
535
536
        target_str = scan_et.attrib.get('target')
537
        ports_str = scan_et.attrib.get('ports')
538
        # For backward compatibility, if target and ports attributes are set,
539
        # <targets> element is ignored.
540
        if target_str is None or ports_str is None:
541
            target_list = scan_et.find('targets')
542
            if target_list is None or not target_list:
543
                raise OSPDError('No targets or ports', 'start_scan')
544
            else:
545
                scan_targets = self.process_targets_element(target_list)
546
        else:
547
            scan_targets = []
548
            for single_target in target_str_to_list(target_str):
549
                scan_targets.append([single_target, ports_str, ''])
550
551
        scan_id = scan_et.attrib.get('scan_id')
552
        if scan_id is not None and scan_id != '' and not valid_uuid(scan_id):
553
            raise OSPDError('Invalid scan_id UUID', 'start_scan')
554
555
        try:
556
            parallel = int(scan_et.attrib.get('parallel', '1'))
557
            if parallel < 1 or parallel > 20:
558
                parallel = 1
559
        except ValueError:
560
            raise OSPDError('Invalid value for parallel scans. '
561
                            'It must be a number', 'start_scan')
562
563
        scanner_params = scan_et.find('scanner_params')
564
        if scanner_params is None:
565
            raise OSPDError('No scanner_params element', 'start_scan')
566
567
        params = self._preprocess_scan_params(scanner_params)
568
569
        # VTS is an optional element. If present should not be empty.
570
        vts = {}
571
        scanner_vts = scan_et.find('vts')
572
        if scanner_vts is not None:
573
            if not scanner_vts:
574
                raise OSPDError('VTs list is empty', 'start_scan')
575
            else:
576
                vts = self.process_vts_params(scanner_vts)
577
578
        # Dry run case.
579
        if 'dry_run' in params and int(params['dry_run']):
580
            scan_func = self.dry_run_scan
581
            scan_params = None
582
        else:
583
            scan_func = self.start_scan
584
            scan_params = self.process_scan_params(params)
585
586
        scan_id = self.create_scan(scan_id, scan_targets, target_str, scan_params, vts)
587
        scan_process = multiprocessing.Process(target=scan_func,
588
                                               args=(scan_id,
589
                                                     scan_targets,
590
                                                     parallel))
591
        self.scan_processes[scan_id] = scan_process
592
        scan_process.start()
593
        id_ = ET.Element('id')
594
        id_.text = scan_id
595
        return simple_response_str('start_scan', 200, 'OK', id_)
596
597
    def handle_stop_scan_command(self, scan_et):
598
        """ Handles <stop_scan> command.
599
600
        @return: Response string for <stop_scan> command.
601
        """
602
603
        scan_id = scan_et.attrib.get('scan_id')
604
        if scan_id is None or scan_id == '':
605
            raise OSPDError('No scan_id attribute', 'stop_scan')
606
        scan_process = self.scan_processes.get(scan_id)
607
        if not scan_process:
608
            raise OSPDError('Scan not found {0}.'.format(scan_id), 'stop_scan')
609
        if not scan_process.is_alive():
610
            raise OSPDError('Scan already stopped or finished.', 'stop_scan')
611
612
        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...
613
        self.stop_scan(scan_id)
614
        scan_process.terminate()
615
        os.killpg(os.getpgid(scan_process.ident), 15)
616
        scan_process.join()
617
        self.set_scan_progress(scan_id, 100)
618
        self.add_scan_log(scan_id, name='', host='', value='Scan stopped.')
619
        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...
620
        return simple_response_str('stop_scan', 200, 'OK')
621
622
    def stop_scan(self, scan_id):
623
        """ Should be implemented by subclass in case of a clean up before
624
        terminating is needed. """
625
626
    def exec_scan(self, scan_id, target):
627
        """ Asserts to False. Should be implemented by subclass. """
628
        raise NotImplementedError
629
630
    def finish_scan(self, scan_id):
631
        """ Sets a scan as finished. """
632
        self.set_scan_progress(scan_id, 100)
633
        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...
634
635
    def get_daemon_name(self):
636
        """ Gives osp daemon's name. """
637
        return self.daemon_info['name']
638
639
    def get_daemon_version(self):
640
        """ Gives osp daemon's version. """
641
        return self.daemon_info['version']
642
643
    def get_scanner_param_type(self, param):
644
        """ Returns type of a scanner parameter. """
645
        assert isinstance(param, str)
646
        entry = self.scanner_params.get(param)
647
        if not entry:
648
            return None
649
        return entry.get('type')
650
651
    def get_scanner_param_mandatory(self, param):
652
        """ Returns if a scanner parameter is mandatory. """
653
        assert isinstance(param, str)
654
        entry = self.scanner_params.get(param)
655
        if not entry:
656
            return False
657
        return entry.get('mandatory')
658
659
    def get_scanner_param_default(self, param):
660
        """ Returns default value of a scanner parameter. """
661
        assert isinstance(param, str)
662
        entry = self.scanner_params.get(param)
663
        if not entry:
664
            return None
665
        return entry.get('default')
666
667
    def get_scanner_params_xml(self):
668
        """ Returns the OSP Daemon's scanner params in xml format. """
669
        scanner_params = ET.Element('scanner_params')
670
        for param_id, param in self.scanner_params.items():
671
            param_xml = ET.SubElement(scanner_params, 'scanner_param')
672
            for name, value in [('id', param_id),
673
                                ('type', param['type'])]:
674
                param_xml.set(name, value)
675
            for name, value in [('name', param['name']),
676
                                ('description', param['description']),
677
                                ('default', param['default']),
678
                                ('mandatory', param['mandatory'])]:
679
                elem = ET.SubElement(param_xml, name)
680
                elem.text = str(value)
681
        return scanner_params
682
683
    def new_client_stream(self, sock):
684
        """ Returns a new ssl client stream from bind_socket. """
685
686
        assert sock
687
        newsocket, fromaddr = sock.accept()
688
        logger.debug("New connection from"
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
689
                     " {0}:{1}".format(fromaddr[0], fromaddr[1]))
690
        # NB: Despite the name, ssl.PROTOCOL_SSLv23 selects the highest
691
        # protocol version that both the client and server support. In modern
692
        # Python versions (>= 3.4) it suppports TLS >= 1.0 with SSLv2 and SSLv3
693
        # being disabled. For Python >=3.5, PROTOCOL_SSLv23 is an alias for
694
        # PROTOCOL_TLS which should be used once compatibility with Python 3.4
695
        # is no longer desired.
696
        try:
697
            ssl_socket = ssl.wrap_socket(newsocket, cert_reqs=ssl.CERT_REQUIRED,
698
                                         server_side=True,
699
                                         certfile=self.certs['cert_file'],
700
                                         keyfile=self.certs['key_file'],
701
                                         ca_certs=self.certs['ca_file'],
702
                                         ssl_version=ssl.PROTOCOL_SSLv23)
703
        except (ssl.SSLError, socket.error) as message:
704
            logger.error(message)
705
            return None
706
        return ssl_socket
707
708
    @staticmethod
709
    def write_to_stream(stream, response, block_len=1024):
710
        """
711
        Send the response in blocks of the given len using the
712
        passed method dependending on the socket type.
713
        """
714
        try:
715
            i_start = 0
716
            i_end = block_len
717
            while True:
718
                if i_end > len(response):
719
                    stream(response[i_start:])
720
                    break
721
                stream(response[i_start:i_end])
722
                i_start = i_end
723
                i_end += block_len
724
        except (socket.timeout, socket.error) as exception:
725
            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...
726
727
    def handle_client_stream(self, stream, is_unix=False):
728
        """ Handles stream of data received from client. """
729
730
        assert stream
731
        data = []
732
        stream.settimeout(2)
733
        while True:
734
            try:
735
                if is_unix:
736
                    data.append(stream.recv(1024))
737
                else:
738
                    data.append(stream.read(1024))
739
                if len(data) == 0:
0 ignored issues
show
Unused Code introduced by
Do not use len(SEQUENCE) as condition value
Loading history...
740
                    logger.warning(
741
                        "Empty client stream (Connection unexpectedly closed)")
742
                    return
743
            except (AttributeError, ValueError) as message:
744
                logger.error(message)
745
                return
746
            except (ssl.SSLError) as exception:
747
                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...
748
                break
749
            except (socket.timeout) as exception:
750
                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...
751
                break
752
        data = b''.join(data)
753
        if len(data) <= 0:
754
            logger.debug("Empty client stream")
755
            return
756
        try:
757
            response = self.handle_command(data)
758
        except OSPDError as exception:
759
            response = exception.as_xml()
760
            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...
761
        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...
762
            logger.exception('While handling client command:')
763
            exception = OSPDError('Fatal error', 'error')
764
            response = exception.as_xml()
765
        if is_unix:
766
            send_method = stream.send
767
        else:
768
            send_method = stream.write
769
        self.write_to_stream(send_method, response)
770
771
    def parallel_scan(self,scan_id, target):
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
772
        """ Starts the scan with scan_id. """
773
        try:
774
            ret = self.exec_scan(scan_id, target)
775
            if ret == 0:
776
                self.add_scan_host_detail(scan_id, name='host_status',
777
                                          host=target, value='0')
778
            elif ret == 1:
779
                self.add_scan_host_detail(scan_id, name='host_status',
780
                                          host=target, value='1')
781
            elif ret == 2:
782
                self.add_scan_host_detail(scan_id, name='host_status',
783
                                          host=target, value='2')
784
            else:
785
                logger.debug('{0}: No host status returned'.format(target))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
786
        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...
787
            self.add_scan_error(scan_id, name='', host=target,
788
                                value='Host process failure (%s).' % e)
789
            logger.exception('While scanning {0}:'.format(target))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
790
        else:
791
            logger.info("{0}: Host scan finished.".format(target))
0 ignored issues
show
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
792
793
    @staticmethod
794
    def check_pending_target(multiscan_proc):
795
        """ Check if a scan process is still alive. In case the process
796
        finished, removes the process from the multiscan_process list
797
798
        @input multiscan_proc A list with the scan process which
799
        may still be alive.
800
        @return Actualized list with current runnging scan processes
801
        """
802
        for run_target in multiscan_proc:
803
            if not run_target.is_alive():
804
                multiscan_proc.remove(run_target)
805
        return multiscan_proc
806
807
    def start_scan(self, scan_id, targets, parallel=1):
808
        """ Handle N parallel scans if 'parallel' is greater than 1. """
809
        os.setsid()
810
        multiscan_proc = []
811
        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...
812
        target_list = targets
813
        if target_list is None or not target_list:
814
            raise OSPDError('Erroneous targets list', 'start_scan')
815
816
        for index, target in enumerate(target_list):
817
            while len(multiscan_proc) >= parallel:
818
               multiscan_proc = self.check_pending_target(multiscan_proc)
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 16 spaces were expected, but 15 were found.
Loading history...
819
               time.sleep(1)
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 16 spaces were expected, but 15 were found.
Loading history...
820
821
            progress = float(index) * 100 / len(target_list)
822
            self.set_scan_progress(scan_id, int(progress))
823
            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...
824
825
            scan_process = multiprocessing.Process(target=self.parallel_scan,
826
                                               args=(scan_id, target[0]))
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 4 spaces).
Loading history...
827
            multiscan_proc.append(scan_process)
828
            scan_process.start()
829
830
        # Wait until all single target were scanned
831
        while multiscan_proc:
832
            multiscan_proc = self.check_pending_target(multiscan_proc)
833
            time.sleep(1)
834
        self.finish_scan(scan_id)
835
836
    def dry_run_scan(self, scan_id, targets):
837
        """ Dry runs a scan. """
838
839
        os.setsid()
840
        #target_list = target_str_to_list(target_str)
841
        for _, target in enumerate(targets):
842
            host = resolve_hostname(target[0])
843
            if host is None:
844
                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...
845
                continue
846
            port = self.get_scan_ports(scan_id, target=target[0])
847
            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...
848
            self.add_scan_log(scan_id, name='', host=host,
849
                              value='Dry run result')
850
        self.finish_scan(scan_id)
851
852
    def handle_timeout(self, scan_id, host):
853
        """ Handles scanner reaching timeout error. """
854
        self.add_scan_error(scan_id, host=host, name="Timeout",
855
                            value="{0} exec timeout."
856
                            .format(self.get_scanner_name()))
857
858
    def set_scan_progress(self, scan_id, progress):
859
        """ Sets scan_id scan's progress which is a number between 0 and 100. """
860
        self.scan_collection.set_progress(scan_id, progress)
861
862
    def scan_exists(self, scan_id):
863
        """ Checks if a scan with ID scan_id is in collection.
864
865
        @return: 1 if scan exists, 0 otherwise.
866
        """
867
        return self.scan_collection.id_exists(scan_id)
868
869
    def handle_get_scans_command(self, scan_et):
870
        """ Handles <get_scans> command.
871
872
        @return: Response string for <get_scans> command.
873
        """
874
875
        scan_id = scan_et.attrib.get('scan_id')
876
        details = scan_et.attrib.get('details')
877
        if details and details == '0':
878
            details = False
879
        else:
880
            details = True
881
882
        responses = []
883
        if scan_id and scan_id in self.scan_collection.ids_iterator():
884
            self.check_scan_process(scan_id)
885
            scan = self.get_scan_xml(scan_id, details)
886
            responses.append(scan)
887
        elif scan_id:
888
            text = "Failed to find scan '{0}'".format(scan_id)
889
            return simple_response_str('get_scans', 404, text)
890
        else:
891
            for scan_id in self.scan_collection.ids_iterator():
892
                self.check_scan_process(scan_id)
893
                scan = self.get_scan_xml(scan_id, details)
894
                responses.append(scan)
895
        return simple_response_str('get_scans', 200, 'OK', responses)
896
897
    def handle_get_vts_command(self, vt_et):
898
        """ Handles <get_vts> command.
899
900
        @return: Response string for <get_vts> command.
901
        """
902
903
        vt_id = vt_et.attrib.get('vt_id')
904
905
        if vt_id and vt_id not in self.vts:
906
            text = "Failed to find vulnerability test '{0}'".format(vt_id)
907
            return simple_response_str('get_vts', 404, text)
908
909
        responses = []
910
911
        if vt_id:
912
            vts_xml = self.get_vts_xml(vt_id)
913
        else:
914
            vts_xml = self.get_vts_xml()
915
916
        responses.append(vts_xml)
917
918
        return simple_response_str('get_vts', 200, 'OK', responses)
919
920
    def handle_help_command(self, scan_et):
921
        """ Handles <help> command.
922
923
        @return: Response string for <help> command.
924
        """
925
        help_format = scan_et.attrib.get('format')
926
        if help_format is None or help_format == "text":
927
            # Default help format is text.
928
            return simple_response_str('help', 200, 'OK',
929
                                       self.get_help_text())
930
        elif help_format == "xml":
931
            text = self.get_xml_str(self.commands)
932
            return simple_response_str('help', 200, 'OK', text)
933
        raise OSPDError('Bogus help format', 'help')
934
935
    def get_help_text(self):
936
        """ Returns the help output in plain text format."""
937
938
        txt = str('\n')
939
        for name, info in self.commands.items():
940
            command_txt = "\t{0: <22} {1}\n".format(name, info['description'])
941
            if info['attributes']:
942
                command_txt = ''.join([command_txt, "\t Attributes:\n"])
943
                for attrname, attrdesc in info['attributes'].items():
944
                    attr_txt = "\t  {0: <22} {1}\n".format(attrname, attrdesc)
945
                    command_txt = ''.join([command_txt, attr_txt])
946
            if info['elements']:
947
                command_txt = ''.join([command_txt, "\t Elements:\n",
948
                                       self.elements_as_text(info['elements'])])
949
            txt = ''.join([txt, command_txt])
950
        return txt
951
952
    def elements_as_text(self, elems, indent=2):
953
        """ Returns the elems dictionary as formatted plain text. """
954
        assert elems
955
        text = ""
956
        for elename, eledesc in elems.items():
957
            if isinstance(eledesc, dict):
958
                desc_txt = self.elements_as_text(eledesc, indent + 2)
959
                desc_txt = ''.join(['\n', desc_txt])
960
            elif isinstance(eledesc, str):
961
                desc_txt = ''.join([eledesc, '\n'])
962
            else:
963
                assert False, "Only string or dictionary"
964
            ele_txt = "\t{0}{1: <22} {2}".format(' ' * indent, elename,
965
                                                 desc_txt)
0 ignored issues
show
introduced by
The variable desc_txt does not seem to be defined for all execution paths.
Loading history...
966
            text = ''.join([text, ele_txt])
967
        return text
968
969
    def handle_delete_scan_command(self, scan_et):
970
        """ Handles <delete_scan> command.
971
972
        @return: Response string for <delete_scan> command.
973
        """
974
        scan_id = scan_et.attrib.get('scan_id')
975
        if scan_id is None:
976
            return simple_response_str('delete_scan', 404,
977
                                       'No scan_id attribute')
978
979
        if not self.scan_exists(scan_id):
980
            text = "Failed to find scan '{0}'".format(scan_id)
981
            return simple_response_str('delete_scan', 404, text)
982
        self.check_scan_process(scan_id)
983
        if self.delete_scan(scan_id):
984
            return simple_response_str('delete_scan', 200, 'OK')
985
        raise OSPDError('Scan in progress', 'delete_scan')
986
987
    def delete_scan(self, scan_id):
988
        """ Deletes scan_id scan from collection.
989
990
        @return: 1 if scan deleted, 0 otherwise.
991
        """
992
        try:
993
            del self.scan_processes[scan_id]
994
        except KeyError:
995
            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...
996
        return self.scan_collection.delete_scan(scan_id)
997
998
    def get_scan_results_xml(self, scan_id):
999
        """ Gets scan_id scan's results in XML format.
1000
1001
        @return: String of scan results in xml.
1002
        """
1003
        results = ET.Element('results')
1004
        for result in self.scan_collection.results_iterator(scan_id):
1005
            results.append(get_result_xml(result))
1006
1007
        logger.info('Returning %d results', len(results))
1008
        return results
1009
1010
    def get_xml_str(self, data):
1011
        """ Creates a string in XML Format using the provided data structure.
1012
1013
        @param: Dictionary of xml tags and their elements.
1014
1015
        @return: String of data in xml format.
1016
        """
1017
1018
        responses = []
1019
        for tag, value in data.items():
1020
            elem = ET.Element(tag)
1021
            if isinstance(value, dict):
1022
                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 1019).

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...
1023
                    elem.append(value)
1024
            elif isinstance(value, list):
1025
                value = ', '.join([m for m in value])
1026
                elem.text = value
1027
            else:
1028
                elem.text = value
1029
            responses.append(elem)
1030
        return responses
1031
1032
    def get_scan_xml(self, scan_id, detailed=True):
1033
        """ Gets scan in XML format.
1034
1035
        @return: String of scan in XML format.
1036
        """
1037
        if not scan_id:
1038
            return ET.Element('scan')
1039
1040
        target = self.get_scan_target(scan_id)
1041
        progress = self.get_scan_progress(scan_id)
1042
        start_time = self.get_scan_start_time(scan_id)
1043
        end_time = self.get_scan_end_time(scan_id)
1044
        response = ET.Element('scan')
1045
        for name, value in [('id', scan_id),
1046
                            ('target', target),
1047
                            ('progress', progress),
1048
                            ('start_time', start_time),
1049
                            ('end_time', end_time)]:
1050
            response.set(name, str(value))
1051
        if detailed:
1052
            response.append(self.get_scan_results_xml(scan_id))
1053
        return response
1054
1055
    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...
1056
        """ Create a string representation of the XML object from the
1057
        custom data object.
1058
        This needs to be implemented by each ospd wrapper, in case
1059
        custom elements for VTs are used.
1060
1061
        The custom XML object which is returned will be embedded
1062
        into a <custom></custom> element.
1063
1064
        @return: XML object as string for custom data.
1065
        """
1066
        return ''
1067
1068
    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...
1069
        """ Create a string representation of the XML object from the
1070
        vt_params data object.
1071
        This needs to be implemented by each ospd wrapper, in case
1072
        vt_params elements for VTs are used.
1073
1074
        The vt_params XML object which is returned will be embedded
1075
        into a <vt_params></vt_params> element.
1076
1077
        @return: XML object as string for vt parameters data.
1078
        """
1079
        return ''
1080
1081
    def get_vt_xml(self, vt_id):
1082
        """ Gets a single vulnerability test information in XML format.
1083
1084
        @return: String of single vulnerability test information in XML format.
1085
        """
1086
        if not vt_id:
1087
            return ET.Element('vt')
1088
1089
        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...
1090
1091
        name = vt.get('name')
1092
        vt_xml = ET.Element('vt')
1093
        vt_xml.set('id', vt_id)
1094
1095
        for name, value in [('name', name)]:
1096
            elem = ET.SubElement(vt_xml, name)
1097
            elem.text = str(value)
1098
1099
        if vt.get('custom'):
1100
            custom_xml_str = '<custom>%s</custom>' % self.get_custom_vt_as_xml_str(vt.get('custom'))
1101
            vt_xml.append(secET.fromstring(custom_xml_str))
1102
1103
        if vt.get('vt_params'):
1104
            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...
1105
            vt_xml.append(secET.fromstring(params_xml_str))
1106
1107
        return vt_xml
1108
1109
    def get_vts_xml(self, vt_id=''):
1110
        """ Gets collection of vulnerability test information in XML format.
1111
        If vt_id is specified, the collection will contain only this vt, of found.
1112
        If no vt_id is specified, the collection will contain all vts.
1113
1114
        @return: String of collection of vulnerability test information in XML format.
1115
        """
1116
1117
        vts_xml = ET.Element('vts')
1118
1119
        if vt_id != '':
1120
            vts_xml.append(self.get_vt_xml(vt_id))
1121
        else:
1122
            for vt_id in self.vts:
0 ignored issues
show
unused-code introduced by
Redefining argument with the local name 'vt_id'
Loading history...
1123
                vts_xml.append(self.get_vt_xml(vt_id))
1124
1125
        return vts_xml
1126
1127
    def handle_get_scanner_details(self):
1128
        """ Handles <get_scanner_details> command.
1129
1130
        @return: Response string for <get_scanner_details> command.
1131
        """
1132
        desc_xml = ET.Element('description')
1133
        desc_xml.text = self.get_scanner_description()
1134
        details = [
1135
            desc_xml,
1136
            self.get_scanner_params_xml()
1137
        ]
1138
        return simple_response_str('get_scanner_details', 200, 'OK', details)
1139
1140
    def handle_get_version_command(self):
1141
        """ Handles <get_version> command.
1142
1143
        @return: Response string for <get_version> command.
1144
        """
1145
        protocol = ET.Element('protocol')
1146
        for name, value in [('name', 'OSP'), ('version', self.get_protocol_version())]:
1147
            elem = ET.SubElement(protocol, name)
1148
            elem.text = value
1149
1150
        daemon = ET.Element('daemon')
1151
        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...
1152
            elem = ET.SubElement(daemon, name)
1153
            elem.text = value
1154
1155
        scanner = ET.Element('scanner')
1156
        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...
1157
            elem = ET.SubElement(scanner, name)
1158
            elem.text = value
1159
1160
        return simple_response_str('get_version', 200, 'OK', [protocol, daemon, scanner])
1161
1162
    def handle_command(self, command):
0 ignored issues
show
best-practice introduced by
Too many return statements (8/6)
Loading history...
1163
        """ Handles an osp command in a string.
1164
1165
        @return: OSP Response to command.
1166
        """
1167
        try:
1168
            tree = secET.fromstring(command)
1169
        except secET.ParseError:
1170
            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...
1171
            raise OSPDError('Invalid data')
1172
1173
        if not self.command_exists(tree.tag) and tree.tag != "authenticate":
1174
            raise OSPDError('Bogus command name')
1175
1176
        if tree.tag == "get_version":
1177
            return self.handle_get_version_command()
1178
        elif tree.tag == "start_scan":
1179
            return self.handle_start_scan_command(tree)
1180
        elif tree.tag == "stop_scan":
1181
            return self.handle_stop_scan_command(tree)
1182
        elif tree.tag == "get_scans":
1183
            return self.handle_get_scans_command(tree)
1184
        elif tree.tag == "get_vts":
1185
            return self.handle_get_vts_command(tree)
1186
        elif tree.tag == "delete_scan":
1187
            return self.handle_delete_scan_command(tree)
1188
        elif tree.tag == "help":
1189
            return self.handle_help_command(tree)
1190
        elif tree.tag == "get_scanner_details":
1191
            return self.handle_get_scanner_details()
1192
        else:
1193
            assert False, "Unhandled command: {0}".format(tree.tag)
1194
1195
    def check(self):
1196
        """ Asserts to False. Should be implemented by subclass. """
1197
        raise NotImplementedError
1198
1199
    def run(self, address, port, unix_path):
1200
        """ Starts the Daemon, handling commands until interrupted.
1201
1202
        @return False if error. Runs indefinitely otherwise.
1203
        """
1204
        assert address or unix_path
1205
        if unix_path:
1206
            sock = bind_unix_socket(unix_path)
1207
        else:
1208
            sock = bind_socket(address, port)
1209
        if sock is None:
1210
            return False
1211
1212
        try:
1213
            while True:
1214
                if unix_path:
1215
                    client_stream, _ = sock.accept()
1216
                    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...
1217
                    self.handle_client_stream(client_stream, True)
1218
                else:
1219
                    client_stream = self.new_client_stream(sock)
1220
                    if client_stream is None:
1221
                        continue
1222
                    self.handle_client_stream(client_stream, False)
1223
                close_client_stream(client_stream, unix_path)
1224
        except KeyboardInterrupt:
1225
            logger.info("Received Ctrl-C shutting-down ...")
1226
        finally:
1227
            sock.shutdown(socket.SHUT_RDWR)
1228
            sock.close()
1229
1230
    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...
1231
        """ Creates a new scan.
1232
1233
        @target: Target to scan.
1234
        @options: Miscellaneous scan options.
1235
1236
        @return: New scan's ID.
1237
        """
1238
        return self.scan_collection.create_scan(scan_id, targets, target_str, options, vts)
1239
1240
    def get_scan_options(self, scan_id):
1241
        """ Gives a scan's list of options. """
1242
        return self.scan_collection.get_options(scan_id)
1243
1244
    def set_scan_option(self, scan_id, name, value):
1245
        """ Sets a scan's option to a provided value. """
1246
        return self.scan_collection.set_option(scan_id, name, value)
1247
1248
    def check_scan_process(self, scan_id):
1249
        """ Check the scan's process, and terminate the scan if not alive. """
1250
        scan_process = self.scan_processes[scan_id]
1251
        progress = self.get_scan_progress(scan_id)
1252
        if progress < 100 and not scan_process.is_alive():
1253
            self.set_scan_progress(scan_id, 100)
1254
            self.add_scan_error(scan_id, name="", host="",
1255
                                value="Scan process failure.")
1256
            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...
1257
        elif progress == 100:
1258
            scan_process.join()
1259
1260
    def get_scan_progress(self, scan_id):
1261
        """ Gives a scan's current progress value. """
1262
        return self.scan_collection.get_progress(scan_id)
1263
1264
    def get_scan_target(self, scan_id):
1265
        """ Gives a scan's target. """
1266
        return self.scan_collection.get_target(scan_id)
1267
1268
    def get_scan_ports(self, scan_id, target=''):
1269
        """ Gives a scan's ports list. """
1270
        return self.scan_collection.get_ports(scan_id, target)
1271
1272
    def get_scan_credentials(self, scan_id, target=''):
1273
        """ Gives a scan's credential list. If a target is passed gives
1274
        the credential list for the given target. """
1275
        return self.scan_collection.get_credentials(scan_id, target)
1276
1277
    def get_scan_vts(self, scan_id):
1278
        """ Gives a scan's vts list. """
1279
        return self.scan_collection.get_vts(scan_id)
1280
1281
    def get_scan_start_time(self, scan_id):
1282
        """ Gives a scan's start time. """
1283
        return self.scan_collection.get_start_time(scan_id)
1284
1285
    def get_scan_end_time(self, scan_id):
1286
        """ Gives a scan's end time. """
1287
        return self.scan_collection.get_end_time(scan_id)
1288
1289
    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...
1290
                     test_id='', qod=''):
1291
        """ Adds a log result to scan_id scan. """
1292
        self.scan_collection.add_result(scan_id, ResultType.LOG, host, name,
1293
                                        value, port, test_id, 0.0, qod)
1294
1295
    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...
1296
        """ Adds an error result to scan_id scan. """
1297
        self.scan_collection.add_result(scan_id, ResultType.ERROR, host, name,
1298
                                        value, port)
1299
1300
    def add_scan_host_detail(self, scan_id, host='', name='', value=''):
1301
        """ Adds a host detail result to scan_id scan. """
1302
        self.scan_collection.add_result(scan_id, ResultType.HOST_DETAIL, host,
1303
                                        name, value)
1304
1305
    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...
1306
                       test_id='', severity='', qod=''):
1307
        """ Adds an alarm result to scan_id scan. """
1308
        self.scan_collection.add_result(scan_id, ResultType.ALARM, host, name,
1309
                                        value, port, test_id, severity, qod)
1310