Completed
Push — master ( cef773...93c828 )
by Juan José
13s queued 11s
created

ospd.ospd.OSPDaemon.get_summary_vt_as_xml_str()   A

Complexity

Conditions 1

Size

Total Lines 13
Code Lines 3

Duplication

Lines 13
Ratio 100 %

Importance

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