Passed
Push — master ( 5898b3...6f2eae )
by Juan José
01:49
created

ospd.ospd.OSPDaemon.handle_stop_scan_command()   A

Complexity

Conditions 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 12
Ratio 100 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nop 2
dl 12
loc 12
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.xml import simple_response_str, get_result_xml
44
from ospd.error import OSPDError
45
46
logger = logging.getLogger(__name__)
47
48
PROTOCOL_VERSION = "1.2"
49
50
SCHEDULER_CHECK_PERIOD = 5  #in seconds
51
52
BASE_SCANNER_PARAMS = {
53
    'debug_mode': {
54
        'type': 'boolean',
55
        'name': 'Debug Mode',
56
        'default': 0,
57
        'mandatory': 0,
58
        'description': 'Whether to get extra scan debug information.',
59
    },
60
    'dry_run': {
61
        'type': 'boolean',
62
        'name': 'Dry Run',
63
        'default': 0,
64
        'mandatory': 0,
65
        'description': 'Whether to dry run scan.',
66
    },
67
}
68
69
COMMANDS_TABLE = {
70
    'start_scan': {
71
        'description': 'Start a new scan.',
72
        'attributes': {
73
            'target': 'Target host to scan',
74
            'ports': 'Ports list to scan',
75
            'scan_id': 'Optional UUID value to use as scan ID',
76
            'parallel': 'Optional nummer of parallel target to scan',
77
        },
78
        'elements': None
79
    },
80
    'stop_scan': {
81
        'description': 'Stop a currently running scan.',
82
        'attributes': {
83
            'scan_id': 'ID of scan to stop.'
84
        },
85
        'elements': None
86
    },
87
    'help': {
88
        'description': 'Print the commands help.',
89
        'attributes': {
90
            'format': 'Help format. Could be text or xml.'
91
        },
92
        'elements': None
93
    },
94
    'get_scans': {
95
        'description': 'List the scans in buffer.',
96
        'attributes': {
97
            'scan_id': 'ID of a specific scan to get.',
98
            'details': 'Whether to return the full scan report.'
99
        },
100
        'elements': None
101
    },
102
    'get_vts': {
103
        'description': 'List of available vulnerability tests.',
104
        'attributes': {
105
            'vt_id': 'ID of a specific vulnerability test to get.',
106
            'filter': 'Optional filter to get an specific vt collection.'
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
131 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...
132
    """ Returns a socket bound on (address:port). """
133
134
    assert address
135
    assert port
136
    bindsocket = socket.socket()
137
    try:
138
        bindsocket.bind((address, port))
139
    except socket.error:
140
        logger.error("Couldn't bind socket on %s:%s", address, port)
141
        return None
142
143
    logger.info('Listening on %s:%s', address, port)
144
    bindsocket.listen(0)
145
    return bindsocket
146
147 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...
148
    """ Returns a unix file socket bound on (path). """
149
150
    assert path
151
    bindsocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
152
    try:
153
        os.unlink(path)
154
    except OSError:
155
        if os.path.exists(path):
156
            raise
157
    try:
158
        bindsocket.bind(path)
159
    except socket.error:
160
        logger.error("Couldn't bind socket on %s", path)
161
        return None
162
163
    logger.info('Listening on %s', path)
164
    bindsocket.listen(0)
165
    return bindsocket
166
167
168 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...
169
    """ Closes provided client stream """
170
    try:
171
        client_stream.shutdown(socket.SHUT_RDWR)
172
        if unix_path:
173
            logger.debug('%s: Connection closed', unix_path)
174
        else:
175
            peer = client_stream.getpeername()
176
            logger.debug('%s:%s: Connection closed', peer[0], peer[1])
177
    except (socket.error, OSError) as exception:
178
        logger.debug('Connection closing error: %s', exception)
179
    client_stream.close()
180
181
182 View Code Duplication
class OSPDaemon(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
183
184
    """ Daemon class for OSP traffic handling.
185
186
    Every scanner wrapper should subclass it and make necessary additions and
187
    changes.
188
    * Add any needed parameters in __init__.
189
    * Implement check() method which verifies scanner availability and other
190
      environment related conditions.
191
    * Implement process_scan_params and exec_scan methods which are
192
      specific to handling the <start_scan> command, executing the wrapped
193
      scanner and storing the results.
194
    * exec_scan() should return 0 if host is dead or not reached, 1 if host is
195
      alive and 2 if scan error or status is unknown.
196
    * Implement other methods that assert to False such as get_scanner_name,
197
      get_scanner_version.
198
    * Use Call set_command_attributes at init time to add scanner command
199
      specific options eg. the w3af profile for w3af wrapper.
200
    """
201
202
    def __init__(self, certfile, keyfile, cafile,
203
                 customvtfilter=None, wrapper_logger=None):
204
        """ Initializes the daemon's internal data. """
205
        # @todo: Actually it makes sense to move the certificate params to
206
        #        a separate function because it is not mandatory anymore to
207
        #        use a TLS setup (unix file socket is an alternative).
208
        #        However, changing this makes it mandatory for any ospd scanner
209
        #        to change the function calls as well. So this breaks the API
210
        #        and should only be done with a major release.
211
        self.certs = dict()
212
        self.certs['cert_file'] = certfile
213
        self.certs['key_file'] = keyfile
214
        self.certs['ca_file'] = cafile
215
        self.scan_collection = ScanCollection()
216
        self.scan_processes = dict()
217
        self.daemon_info = dict()
218
        self.daemon_info['name'] = "OSPd"
219
        self.daemon_info['version'] = __version__
220
        self.daemon_info['description'] = "No description"
221
        self.scanner_info = dict()
222
        self.scanner_info['name'] = 'No name'
223
        self.scanner_info['version'] = 'No version'
224
        self.scanner_info['description'] = 'No description'
225
        self.server_version = None  # Set by the subclass.
226
        self.protocol_version = PROTOCOL_VERSION
227
        self.commands = COMMANDS_TABLE
228
        self.scanner_params = dict()
229
        for name, param in BASE_SCANNER_PARAMS.items():
230
            self.add_scanner_param(name, param)
231
        self.vts = dict()
232
        self.vt_id_pattern = re.compile("[0-9a-zA-Z_\-:.]{1,80}")
233
        self.vts_version = None
234
        if customvtfilter:
235
            self.vts_filter = customvtfilter
236
        else:
237
            self.vts_filter = VtsFilter()
238
        if wrapper_logger:
239
            global logger
240
            logger = wrapper_logger
241
242
    def set_command_attributes(self, name, attributes):
243
        """ Sets the xml attributes of a specified command. """
244
        if self.command_exists(name):
245
            command = self.commands.get(name)
246
            command['attributes'] = attributes
247
248
    def add_scanner_param(self, name, scanner_param):
249
        """ Add a scanner parameter. """
250
251
        assert name
252
        assert scanner_param
253
        self.scanner_params[name] = scanner_param
254
        command = self.commands.get('start_scan')
255
        command['elements'] = {
256
            'scanner_params':
257
                {k: v['name'] for k, v in self.scanner_params.items()}}
258
259
    def add_vt(self, vt_id, name=None, vt_params=None, vt_refs=None,
260
               custom=None, vt_creation_time=None, vt_modification_time=None,
261
               vt_dependencies=None, summary=None, impact=None, affected=None,
262
               insight=None, solution=None, solution_t=None, detection=None,
263
               qod_t=None, qod_v=None, severities=None):
264
        """ Add a vulnerability test information.
265
266
        Returns: The new number of stored VTs.
267
        -1 in case the VT ID was already present and thus the
268
        new VT was not considered.
269
        -2 in case the vt_id was invalid.
270
        """
271
272
        if not vt_id:
273
            return -2  # no valid vt_id
274
275
        if self.vt_id_pattern.fullmatch(vt_id) is None:
276
            return -2  # no valid vt_id
277
278
        if vt_id in self.vts:
279
            return -1  # The VT was already in the list.
280
281
        if name is None:
282
            name = ''
283
284
        self.vts[vt_id] = {'name': name}
285
        if custom is not None:
286
            self.vts[vt_id]["custom"] = custom
287
        if vt_params is not None:
288
            self.vts[vt_id]["vt_params"] = vt_params
289
        if vt_refs is not None:
290
            self.vts[vt_id]["vt_refs"] = vt_refs
291
        if vt_dependencies is not None:
292
            self.vts[vt_id]["vt_dependencies"] = vt_dependencies
293
        if vt_creation_time is not None:
294
            self.vts[vt_id]["creation_time"] = vt_creation_time
295
        if vt_modification_time is not None:
296
            self.vts[vt_id]["modification_time"] = vt_modification_time
297
        if summary is not None:
298
            self.vts[vt_id]["summary"] = summary
299
        if impact is not None:
300
            self.vts[vt_id]["impact"] = impact
301
        if affected is not None:
302
            self.vts[vt_id]["affected"] = affected
303
        if insight is not None:
304
            self.vts[vt_id]["insight"] = insight
305
        if solution is not None:
306
            self.vts[vt_id]["solution"] = solution
307
            if solution_t is not None:
308
                self.vts[vt_id]["solution_type"] = solution_t
309
        if detection is not None:
310
            self.vts[vt_id]["detection"] = detection
311
        if qod_t is not None:
312
            self.vts[vt_id]["qod_type"] = qod_t
313
        elif qod_v is not None:
314
            self.vts[vt_id]["qod"] = qod_v
315
        if severities is not None:
316
            self.vts[vt_id]["severities"] = severities
317
318
        return len(self.vts)
319
320
    def set_vts_version(self, vts_version):
321
        """ Add into the vts dictionary an entry to identify the
322
        vts version.
323
324
        Parameters:
325
            vts_version (str): Identifies a unique vts version.
326
        """
327
        if not vts_version:
328
            raise OSPDError('A vts_version parameter is required',
329
                            'set_vts_version')
330
        self.vts_version = vts_version
331
332
    def get_vts_version(self):
333
        """Return the vts version.
334
        """
335
        return self.vts_version
336
337
    def command_exists(self, name):
338
        """ Checks if a commands exists. """
339
        return name in self.commands.keys()
340
341
    def get_scanner_name(self):
342
        """ Gives the wrapped scanner's name. """
343
        return self.scanner_info['name']
344
345
    def get_scanner_version(self):
346
        """ Gives the wrapped scanner's version. """
347
        return self.scanner_info['version']
348
349
    def get_scanner_description(self):
350
        """ Gives the wrapped scanner's description. """
351
        return self.scanner_info['description']
352
353
    def get_server_version(self):
354
        """ Gives the specific OSP server's version. """
355
        assert self.server_version
356
        return self.server_version
357
358
    def get_protocol_version(self):
359
        """ Gives the OSP's version. """
360
        return self.protocol_version
361
362
    def _preprocess_scan_params(self, xml_params):
363
        """ Processes the scan parameters. """
364
        params = {}
365
        for param in xml_params:
366
            params[param.tag] = param.text or ''
367
        # Set default values.
368
        for key in self.scanner_params:
369
            if key not in params:
370
                params[key] = self.get_scanner_param_default(key)
371
                if self.get_scanner_param_type(key) == 'selection':
372
                    params[key] = params[key].split('|')[0]
373
        # Validate values.
374
        for key in params:
375
            param_type = self.get_scanner_param_type(key)
376
            if not param_type:
377
                continue
378
            if param_type in ['integer', 'boolean']:
379
                try:
380
                    params[key] = int(params[key])
381
                except ValueError:
382
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
383
            if param_type == 'boolean':
384
                if params[key] not in [0, 1]:
385
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
386
            elif param_type == 'selection':
387
                selection = self.get_scanner_param_default(key).split('|')
388
                if params[key] not in selection:
389
                    raise OSPDError('Invalid %s value' % key, 'start_scan')
390
            if self.get_scanner_param_mandatory(key) and params[key] == '':
391
                    raise OSPDError('Mandatory %s value is missing' % key,
392
                                    'start_scan')
393
        return params
394
395
    def process_scan_params(self, params):
396
        """ This method is to be overridden by the child classes if necessary
397
        """
398
        return params
399
400
    def process_vts_params(self, scanner_vts):
401
        """ Receive an XML object with the Vulnerability Tests an their
402
        parameters to be use in a scan and return a dictionary.
403
404
        @param: XML element with vt subelements. Each vt has an
405
                id attribute. Optional parameters can be included
406
                as vt child.
407
                Example form:
408
                <vt_selection>
409
                  <vt_single id='vt1' />
410
                  <vt_single id='vt2'>
411
                    <vt_value id='param1'>value</vt_value>
412
                  </vt_single>
413
                  <vt_group filter='family=debian'/>
414
                  <vt_group filter='family=general'/>
415
                </vt_selection>
416
417
        @return: Dictionary containing the vts attribute and subelements,
418
                 like the VT's id and VT's parameters.
419
                 Example form:
420
                 {'vt1': {},
421
                  'vt2': {'value_id': 'value'},
422
                  'vt_groups': ['family=debian', 'family=general']}
423
        """
424
        vt_selection = {}
425
        filters = list()
426
        for vt in scanner_vts:
427
            if vt.tag == 'vt_single':
428
                vt_id = vt.attrib.get('id')
429
                vt_selection[vt_id] = {}
430
                for vt_value in vt:
431
                    if not vt_value.attrib.get('id'):
432
                        raise OSPDError('Invalid VT preference. No attribute id',
433
                                        'start_scan')
434
                    vt_value_id = vt_value.attrib.get('id')
435
                    vt_value_value = vt_value.text if vt_value.text else ''
436
                    vt_selection[vt_id][vt_value_id] = vt_value_value
437
            if vt.tag == 'vt_group':
438
                vts_filter = vt.attrib.get('filter', None)
439
                if vts_filter is None:
440
                    raise OSPDError('Invalid VT group. No filter given.',
441
                                    'start_scan')
442
                filters.append(vts_filter)
443
        vt_selection['vt_groups'] = filters
444
        return vt_selection
445
446
    @staticmethod
447
    def process_credentials_elements(cred_tree):
448
        """ Receive an XML object with the credentials to run
449
        a scan against a given target.
450
451
        @param:
452
        <credentials>
453
          <credential type="up" service="ssh" port="22">
454
            <username>scanuser</username>
455
            <password>mypass</password>
456
          </credential>
457
          <credential type="up" service="smb">
458
            <username>smbuser</username>
459
            <password>mypass</password>
460
          </credential>
461
        </credentials>
462
463
        @return: Dictionary containing the credentials for a given target.
464
                 Example form:
465
                 {'ssh': {'type': type,
466
                          'port': port,
467
                          'username': username,
468
                          'password': pass,
469
                        },
470
                  'smb': {'type': type,
471
                          'username': username,
472
                          'password': pass,
473
                         },
474
                   }
475
        """
476
        credentials = {}
477
        for credential in cred_tree:
478
            service = credential.attrib.get('service')
479
            credentials[service] = {}
480
            credentials[service]['type'] = credential.attrib.get('type')
481
            if service == 'ssh':
482
                credentials[service]['port'] = credential.attrib.get('port')
483
            for param in credential:
484
                credentials[service][param.tag] = param.text
485
486
        return credentials
487
488
    @classmethod
489
    def process_targets_element(cls, scanner_target):
490
        """ Receive an XML object with the target, ports and credentials to run
491
        a scan against.
492
493
        @param: XML element with target subelements. Each target has <hosts>
494
        and <ports> subelements. Hosts can be a single host, a host range,
495
        a comma-separated host list or a network address.
496
        <ports> and  <credentials> are optional. Therefore each ospd-scanner
497
        should check for a valid ones if needed.
498
499
                Example form:
500
                <targets>
501
                  <target>
502
                    <hosts>localhosts</hosts>
503
                    <ports>80,443</ports>
504
                  </target>
505
                  <target>
506
                    <hosts>192.168.0.0/24</hosts>
507
                    <ports>22</ports>
508
                    <credentials>
509
                      <credential type="up" service="ssh" port="22">
510
                        <username>scanuser</username>
511
                        <password>mypass</password>
512
                      </credential>
513
                      <credential type="up" service="smb">
514
                        <username>smbuser</username>
515
                        <password>mypass</password>
516
                      </credential>
517
                    </credentials>
518
                  </target>
519
                </targets>
520
521
        @return: A list of (hosts, port) tuples.
522
                 Example form:
523
                 [['localhost', '80,43'],
524
                  ['192.168.0.0/24', '22', {'smb': {'type': type,
525
                                                    'port': port,
526
                                                    'username': username,
527
                                                    'password': pass,
528
                                                   }}]]
529
        """
530
531
        target_list = []
532
        for target in scanner_target:
533
            ports = ''
534
            credentials = {}
535
            for child in target:
536
                if child.tag == 'hosts':
537
                    hosts = child.text
538
                if child.tag == 'ports':
539
                    ports = child.text
540
                if child.tag == 'credentials':
541
                    credentials = cls.process_credentials_elements(child)
542
            if hosts:
0 ignored issues
show
introduced by
The variable hosts does not seem to be defined for all execution paths.
Loading history...
543
                target_list.append([hosts, ports, credentials])
544
            else:
545
                raise OSPDError('No target to scan', 'start_scan')
546
547
        return target_list
548
549
    def handle_start_scan_command(self, scan_et):
550
        """ Handles <start_scan> command.
551
552
        @return: Response string for <start_scan> command.
553
        """
554
555
        target_str = scan_et.attrib.get('target')
556
        ports_str = scan_et.attrib.get('ports')
557
        # For backward compatibility, if target and ports attributes are set,
558
        # <targets> element is ignored.
559
        if target_str is None or ports_str is None:
560
            target_list = scan_et.find('targets')
561
            if target_list is None or not target_list:
562
                raise OSPDError('No targets or ports', 'start_scan')
563
            else:
564
                scan_targets = self.process_targets_element(target_list)
565
        else:
566
            scan_targets = []
567
            for single_target in target_str_to_list(target_str):
568
                scan_targets.append([single_target, ports_str, ''])
569
570
        scan_id = scan_et.attrib.get('scan_id')
571
        if scan_id is not None and scan_id != '' and not valid_uuid(scan_id):
572
            raise OSPDError('Invalid scan_id UUID', 'start_scan')
573
574
        try:
575
            parallel = int(scan_et.attrib.get('parallel', '1'))
576
            if parallel < 1 or parallel > 20:
577
                parallel = 1
578
        except ValueError:
579
            raise OSPDError('Invalid value for parallel scans. '
580
                            'It must be a number', 'start_scan')
581
582
        scanner_params = scan_et.find('scanner_params')
583
        if scanner_params is None:
584
            raise OSPDError('No scanner_params element', 'start_scan')
585
586
        params = self._preprocess_scan_params(scanner_params)
587
588
        # VTS is an optional element. If present should not be empty.
589
        vt_selection = {}
590
        scanner_vts = scan_et.find('vt_selection')
591
        if scanner_vts is not None:
592
            if not scanner_vts:
593
                raise OSPDError('VTs list is empty', 'start_scan')
594
            else:
595
                vt_selection = self.process_vts_params(scanner_vts)
596
597
        # Dry run case.
598
        if 'dry_run' in params and int(params['dry_run']):
599
            scan_func = self.dry_run_scan
600
            scan_params = None
601
        else:
602
            scan_func = self.start_scan
603
            scan_params = self.process_scan_params(params)
604
605
        scan_id = self.create_scan(scan_id, scan_targets,
606
                                   target_str, scan_params,
607
                                   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, "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, '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(scan_id)
841
                if target_prog[running_target_id] < 100:
842
                    self.stop_scan(scan_id)
843
                running_target = (running_target_proc, running_target_id)
844
                multiscan_proc.remove(running_target)
845
        return multiscan_proc
846
847
    def calculate_progress(self, scan_id):
848
        """ Calculate the total scan progress from the
849
        partial target progress. """
850
851
        target_progress = self.get_scan_target_progress(scan_id)
852
        return sum(target_progress.values())/len(target_progress)
853
854
    def start_scan(self, scan_id, targets, parallel=1):
855
        """ Handle N parallel scans if 'parallel' is greater than 1. """
856
857
        os.setsid()
858
        multiscan_proc = []
859
        logger.info("%s: Scan started.", scan_id)
860
        target_list = targets
861
        if target_list is None or not target_list:
862
            raise OSPDError('Erroneous targets list', 'start_scan')
863
864
        for index, target in enumerate(target_list):
865
            while len(multiscan_proc) >= parallel:
866
                multiscan_proc = self.check_pending_target(scan_id,
867
                                                           multiscan_proc)
868
                progress = self.calculate_progress(scan_id)
869
                self.set_scan_progress(scan_id, progress)
870
                time.sleep(1)
871
872
            #If the scan status is stopped, does not launch anymore target scans
873
            if self.get_scan_status(scan_id) == "stopped":
874
                return
875
876
            logger.info("%s: Host scan started on ports %s.", target[0], target[1])
877
            scan_process = multiprocessing.Process(target=self.parallel_scan,
878
                                                   args=(scan_id, target[0]))
879
            multiscan_proc.append((scan_process, target[0]))
880
            scan_process.start()
881
            self.set_scan_status(scan_id, "running")
882
883
        # Wait until all single target were scanned
884
        while multiscan_proc:
885
            multiscan_proc = self.check_pending_target(scan_id, multiscan_proc)
886
            if multiscan_proc:
887
                progress = self.calculate_progress(scan_id)
888
                self.set_scan_progress(scan_id, progress)
889
            time.sleep(1)
890
891
        # Only set the scan as finished if the scan was not stopped.
892
        if self.get_scan_status(scan_id) != "stopped":
893
            self.finish_scan(scan_id)
894
895
    def dry_run_scan(self, scan_id, targets):
896
        """ Dry runs a scan. """
897
898
        os.setsid()
899
        #target_list = target_str_to_list(target_str)
900
        for _, target in enumerate(targets):
901
            host = resolve_hostname(target[0])
902
            if host is None:
903
                logger.info("Couldn't resolve %s.", target[0])
904
                continue
905
            port = self.get_scan_ports(scan_id, target=target[0])
906
            logger.info("%s:%s: Dry run mode.", host, port)
907
            self.add_scan_log(scan_id, name='', host=host,
908
                              value='Dry run result')
909
        self.finish_scan(scan_id)
910
911
    def handle_timeout(self, scan_id, host):
912
        """ Handles scanner reaching timeout error. """
913
        self.add_scan_error(scan_id, host=host, name="Timeout",
914
                            value="{0} exec timeout."
915
                            .format(self.get_scanner_name()))
916
917
    def set_scan_host_finished(self, scan_id, target, host):
918
        """ Add the host in a list of finished hosts """
919
        self.scan_collection.set_host_finished(scan_id, target, host)
920
921
    def set_scan_progress(self, scan_id, progress):
922
        """ Sets scan_id scan's progress which is a number between 0 and 100. """
923
        self.scan_collection.set_progress(scan_id, progress)
924
925
    def set_scan_target_progress(self, scan_id, target, progress):
926
        """ Sets target's progress. """
927
        self.scan_collection.set_target_progress(scan_id, target, progress)
928
929
    def set_scan_status(self, scan_id, status):
930
        """ Set the scan's status."""
931
        self.scan_collection.set_status(scan_id, status)
932
933
    def get_scan_status(self, scan_id):
934
        """ Get scan_id scans's status."""
935
        return self.scan_collection.get_status(scan_id)
936
937
    def scan_exists(self, scan_id):
938
        """ Checks if a scan with ID scan_id is in collection.
939
940
        @return: 1 if scan exists, 0 otherwise.
941
        """
942
        return self.scan_collection.id_exists(scan_id)
943
944
    def handle_get_scans_command(self, scan_et):
945
        """ Handles <get_scans> command.
946
947
        @return: Response string for <get_scans> command.
948
        """
949
950
        scan_id = scan_et.attrib.get('scan_id')
951
        details = scan_et.attrib.get('details')
952
        pop_res = scan_et.attrib.get('pop_results')
953
        if details and details == '0':
954
            details = False
955
        else:
956
            details = True
957
            if pop_res and pop_res == '1':
958
                pop_res = True
959
            else:
960
                pop_res = False
961
962
        responses = []
963
        if scan_id and scan_id in self.scan_collection.ids_iterator():
964
            self.check_scan_process(scan_id)
965
            scan = self.get_scan_xml(scan_id, details, pop_res)
966
            responses.append(scan)
967
        elif scan_id:
968
            text = "Failed to find scan '{0}'".format(scan_id)
969
            return simple_response_str('get_scans', 404, text)
970
        else:
971
            for scan_id in self.scan_collection.ids_iterator():
972
                self.check_scan_process(scan_id)
973
                scan = self.get_scan_xml(scan_id, details, pop_res)
974
                responses.append(scan)
975
        return simple_response_str('get_scans', 200, 'OK', responses)
976
977
    def handle_get_vts_command(self, vt_et):
978
        """ Handles <get_vts> command.
979
980
        @return: Response string for <get_vts> command.
981
        """
982
983
        vt_id = vt_et.attrib.get('vt_id')
984
        vt_filter = vt_et.attrib.get('filter')
985
986
        if vt_id and vt_id not in self.vts:
987
            text = "Failed to find vulnerability test '{0}'".format(vt_id)
988
            return simple_response_str('get_vts', 404, text)
989
990
        filtered_vts = None
991
        if vt_filter:
992
            filtered_vts = self.vts_filter.get_filtered_vts_list(
993
                self.vts, vt_filter)
994
995
        responses = []
996
997
        vts_xml = self.get_vts_xml(vt_id, filtered_vts)
998
999
        responses.append(vts_xml)
1000
1001
        return simple_response_str('get_vts', 200, 'OK', responses)
1002
1003
    def handle_help_command(self, scan_et):
1004
        """ Handles <help> command.
1005
1006
        @return: Response string for <help> command.
1007
        """
1008
        help_format = scan_et.attrib.get('format')
1009
        if help_format is None or help_format == "text":
1010
            # Default help format is text.
1011
            return simple_response_str('help', 200, 'OK',
1012
                                       self.get_help_text())
1013
        elif help_format == "xml":
1014
            text = self.get_xml_str(self.commands)
1015
            return simple_response_str('help', 200, 'OK', text)
1016
        raise OSPDError('Bogus help format', 'help')
1017
1018
    def get_help_text(self):
1019
        """ Returns the help output in plain text format."""
1020
1021
        txt = str('\n')
1022
        for name, info in self.commands.items():
1023
            command_txt = "\t{0: <22} {1}\n".format(name, info['description'])
1024
            if info['attributes']:
1025
                command_txt = ''.join([command_txt, "\t Attributes:\n"])
1026
                for attrname, attrdesc in info['attributes'].items():
1027
                    attr_txt = "\t  {0: <22} {1}\n".format(attrname, attrdesc)
1028
                    command_txt = ''.join([command_txt, attr_txt])
1029
            if info['elements']:
1030
                command_txt = ''.join([command_txt, "\t Elements:\n",
1031
                                       self.elements_as_text(info['elements'])])
1032
            txt = ''.join([txt, command_txt])
1033
        return txt
1034
1035
    def elements_as_text(self, elems, indent=2):
1036
        """ Returns the elems dictionary as formatted plain text. """
1037
        assert elems
1038
        text = ""
1039
        for elename, eledesc in elems.items():
1040
            if isinstance(eledesc, dict):
1041
                desc_txt = self.elements_as_text(eledesc, indent + 2)
1042
                desc_txt = ''.join(['\n', desc_txt])
1043
            elif isinstance(eledesc, str):
1044
                desc_txt = ''.join([eledesc, '\n'])
1045
            else:
1046
                assert False, "Only string or dictionary"
1047
            ele_txt = "\t{0}{1: <22} {2}".format(' ' * indent, elename,
1048
                                                 desc_txt)
0 ignored issues
show
introduced by
The variable desc_txt does not seem to be defined for all execution paths.
Loading history...
1049
            text = ''.join([text, ele_txt])
1050
        return text
1051
1052
    def handle_delete_scan_command(self, scan_et):
1053
        """ Handles <delete_scan> command.
1054
1055
        @return: Response string for <delete_scan> command.
1056
        """
1057
        scan_id = scan_et.attrib.get('scan_id')
1058
        if scan_id is None:
1059
            return simple_response_str('delete_scan', 404,
1060
                                       'No scan_id attribute')
1061
1062
        if not self.scan_exists(scan_id):
1063
            text = "Failed to find scan '{0}'".format(scan_id)
1064
            return simple_response_str('delete_scan', 404, text)
1065
        self.check_scan_process(scan_id)
1066
        if self.delete_scan(scan_id):
1067
            return simple_response_str('delete_scan', 200, 'OK')
1068
        raise OSPDError('Scan in progress', 'delete_scan')
1069
1070
    def delete_scan(self, scan_id):
1071
        """ Deletes scan_id scan from collection.
1072
1073
        @return: 1 if scan deleted, 0 otherwise.
1074
        """
1075
        try:
1076
            del self.scan_processes[scan_id]
1077
        except KeyError:
1078
            logger.debug('Scan process for %s not found', scan_id)
1079
        return self.scan_collection.delete_scan(scan_id)
1080
1081
    def get_scan_results_xml(self, scan_id, pop_res):
1082
        """ Gets scan_id scan's results in XML format.
1083
1084
        @return: String of scan results in xml.
1085
        """
1086
        results = Element('results')
1087
        for result in self.scan_collection.results_iterator(scan_id, pop_res):
1088
            results.append(get_result_xml(result))
1089
1090
        logger.info('Returning %d results', len(results))
1091
        return results
1092
1093
    def get_xml_str(self, data):
1094
        """ Creates a string in XML Format using the provided data structure.
1095
1096
        @param: Dictionary of xml tags and their elements.
1097
1098
        @return: String of data in xml format.
1099
        """
1100
1101
        responses = []
1102
        for tag, value in data.items():
1103
            elem = Element(tag)
1104
            if isinstance(value, dict):
1105
                for value in self.get_xml_str(value):
1106
                    elem.append(value)
1107
            elif isinstance(value, list):
1108
                value = ', '.join([m for m in value])
1109
                elem.text = value
1110
            else:
1111
                elem.text = value
1112
            responses.append(elem)
1113
        return responses
1114
1115
    def get_scan_xml(self, scan_id, detailed=True, pop_res=False):
1116
        """ Gets scan in XML format.
1117
1118
        @return: String of scan in XML format.
1119
        """
1120
        if not scan_id:
1121
            return Element('scan')
1122
1123
        target = self.get_scan_target(scan_id)
1124
        progress = self.get_scan_progress(scan_id)
1125
        status = self.get_scan_status(scan_id)
1126
        start_time = self.get_scan_start_time(scan_id)
1127
        end_time = self.get_scan_end_time(scan_id)
1128
        response = Element('scan')
1129
        for name, value in [('id', scan_id),
1130
                            ('target', target),
1131
                            ('progress', progress),
1132
                            ('status', status),
1133
                            ('start_time', start_time),
1134
                            ('end_time', end_time)]:
1135
            response.set(name, str(value))
1136
        if detailed:
1137
            response.append(self.get_scan_results_xml(scan_id, pop_res))
1138
        return response
1139
1140
    @staticmethod
1141
    def get_custom_vt_as_xml_str(vt_id, custom):
1142
        """ Create a string representation of the XML object from the
1143
        custom data object.
1144
        This needs to be implemented by each ospd wrapper, in case
1145
        custom elements for VTs are used.
1146
1147
        The custom XML object which is returned will be embedded
1148
        into a <custom></custom> element.
1149
1150
        @return: XML object as string for custom data.
1151
        """
1152
        return ''
1153
1154
    @staticmethod
1155
    def get_params_vt_as_xml_str(vt_id, vt_params):
1156
        """ Create a string representation of the XML object from the
1157
        vt_params data object.
1158
        This needs to be implemented by each ospd wrapper, in case
1159
        vt_params elements for VTs are used.
1160
1161
        The vt_params XML object which is returned will be embedded
1162
        into a <vt_params></vt_params> element.
1163
1164
        @return: XML object as string for vt parameters data.
1165
        """
1166
        return ''
1167
1168
    @staticmethod
1169
    def get_refs_vt_as_xml_str(vt_id, vt_refs):
1170
        """ Create a string representation of the XML object from the
1171
        refs data object.
1172
        This needs to be implemented by each ospd wrapper, in case
1173
        refs elements for VTs are used.
1174
1175
        The refs XML object which is returned will be embedded
1176
        into a <refs></refs> element.
1177
1178
        @return: XML object as string for vt references data.
1179
        """
1180
        return ''
1181
1182
    @staticmethod
1183
    def get_dependencies_vt_as_xml_str(vt_id, vt_dependencies):
1184
        """ Create a string representation of the XML object from the
1185
        vt_dependencies data object.
1186
        This needs to be implemented by each ospd wrapper, in case
1187
        vt_dependencies elements for VTs are used.
1188
1189
        The vt_dependencies XML object which is returned will be embedded
1190
        into a <dependencies></dependencies> element.
1191
1192
        @return: XML object as string for vt dependencies data.
1193
        """
1194
        return ''
1195
1196
    @staticmethod
1197
    def get_creation_time_vt_as_xml_str(vt_id, vt_creation_time):
1198
        """ Create a string representation of the XML object from the
1199
        vt_creation_time data object.
1200
        This needs to be implemented by each ospd wrapper, in case
1201
        vt_creation_time elements for VTs are used.
1202
1203
        The vt_creation_time XML object which is returned will be embedded
1204
        into a <vt_creation_time></vt_creation_time> element.
1205
1206
        @return: XML object as string for vt creation time data.
1207
        """
1208
        return ''
1209
1210
    @staticmethod
1211
    def get_modification_time_vt_as_xml_str(vt_id, vt_modification_time):
1212
        """ Create a string representation of the XML object from the
1213
        vt_modification_time data object.
1214
        This needs to be implemented by each ospd wrapper, in case
1215
        vt_modification_time elements for VTs are used.
1216
1217
        The vt_modification_time XML object which is returned will be embedded
1218
        into a <vt_modification_time></vt_modification_time> element.
1219
1220
        @return: XML object as string for vt references data.
1221
        """
1222
        return ''
1223
1224
    @staticmethod
1225
    def get_summary_vt_as_xml_str(vt_id, summary):
1226
        """ Create a string representation of the XML object from the
1227
        summary data object.
1228
        This needs to be implemented by each ospd wrapper, in case
1229
        summary elements for VTs are used.
1230
1231
        The summary XML object which is returned will be embedded
1232
        into a <summary></summary> element.
1233
1234
        @return: XML object as string for summary data.
1235
        """
1236
        return ''
1237
1238
    @staticmethod
1239
    def get_impact_vt_as_xml_str(vt_id, impact):
1240
        """ Create a string representation of the XML object from the
1241
        impact data object.
1242
        This needs to be implemented by each ospd wrapper, in case
1243
        impact elements for VTs are used.
1244
1245
        The impact XML object which is returned will be embedded
1246
        into a <impact></impact> element.
1247
1248
        @return: XML object as string for impact data.
1249
        """
1250
        return ''
1251
1252
    @staticmethod
1253
    def get_affected_vt_as_xml_str(vt_id, affected):
1254
        """ Create a string representation of the XML object from the
1255
        affected data object.
1256
        This needs to be implemented by each ospd wrapper, in case
1257
        affected elements for VTs are used.
1258
1259
        The affected XML object which is returned will be embedded
1260
        into a <affected></affected> element.
1261
1262
        @return: XML object as string for affected data.
1263
        """
1264
        return ''
1265
1266
    @staticmethod
1267
    def get_insight_vt_as_xml_str(vt_id, insight):
1268
        """ Create a string representation of the XML object from the
1269
        insight data object.
1270
        This needs to be implemented by each ospd wrapper, in case
1271
        insight elements for VTs are used.
1272
1273
        The insight XML object which is returned will be embedded
1274
        into a <insight></insight> element.
1275
1276
        @return: XML object as string for insight data.
1277
        """
1278
        return ''
1279
1280
    @staticmethod
1281
    def get_solution_vt_as_xml_str(vt_id, solution, solution_type=None):
1282
        """ Create a string representation of the XML object from the
1283
        solution data object.
1284
        This needs to be implemented by each ospd wrapper, in case
1285
        solution elements for VTs are used.
1286
1287
        The solution XML object which is returned will be embedded
1288
        into a <solution></solution> element.
1289
1290
        @return: XML object as string for solution data.
1291
        """
1292
        return ''
1293
1294
    @staticmethod
1295
    def get_detection_vt_as_xml_str(vt_id, detection=None, qod_type=None, qod=None):
1296
        """ Create a string representation of the XML object from the
1297
        detection data object.
1298
        This needs to be implemented by each ospd wrapper, in case
1299
        detection elements for VTs are used.
1300
1301
        The detection XML object which is returned is an element with
1302
        tag <detection></detection> element
1303
1304
        @return: XML object as string for detection data.
1305
        """
1306
        return ''
1307
1308
    @staticmethod
1309
    def get_severities_vt_as_xml_str(vt_id, severities):
1310
        """ Create a string representation of the XML object from the
1311
        severities data object.
1312
        This needs to be implemented by each ospd wrapper, in case
1313
        severities elements for VTs are used.
1314
1315
        The severities XML objects which are returned will be embedded
1316
        into a <severities></severities> element.
1317
1318
        @return: XML object as string for severities data.
1319
        """
1320
        return ''
1321
1322
    def get_vt_xml(self, vt_id):
1323
        """ Gets a single vulnerability test information in XML format.
1324
1325
        @return: String of single vulnerability test information in XML format.
1326
        """
1327
        if not vt_id:
1328
            return Element('vt')
1329
1330
        vt = self.vts.get(vt_id)
1331
1332
        name = vt.get('name')
1333
        vt_xml = Element('vt')
1334
        vt_xml.set('id', vt_id)
1335
1336
        for name, value in [('name', name)]:
1337
            elem = SubElement(vt_xml, name)
1338
            elem.text = str(value)
1339
1340
        if vt.get('vt_params'):
1341
            params_xml_str = self.get_params_vt_as_xml_str(
1342
                vt_id, vt.get('vt_params'))
1343
            vt_xml.append(secET.fromstring(params_xml_str))
1344
1345
        if vt.get('vt_refs'):
1346
            refs_xml_str = self.get_refs_vt_as_xml_str(
1347
                vt_id, vt.get('vt_refs'))
1348
            vt_xml.append(secET.fromstring(refs_xml_str))
1349
1350
        if vt.get('vt_dependencies'):
1351
            dependencies = self.get_dependencies_vt_as_xml_str(
1352
                vt_id, vt.get('vt_dependencies'))
1353
            vt_xml.append(secET.fromstring(dependencies))
1354
1355
        if vt.get('creation_time'):
1356
            vt_ctime = self.get_creation_time_vt_as_xml_str(
1357
                vt_id, vt.get('creation_time'))
1358
            vt_xml.append(secET.fromstring(vt_ctime))
1359
1360
        if vt.get('modification_time'):
1361
            vt_mtime = self.get_modification_time_vt_as_xml_str(
1362
                vt_id, vt.get('modification_time'))
1363
            vt_xml.append(secET.fromstring(vt_mtime))
1364
1365
        if vt.get('summary'):
1366
            summary_xml_str = self.get_summary_vt_as_xml_str(
1367
                vt_id, vt.get('summary'))
1368
            vt_xml.append(secET.fromstring(summary_xml_str))
1369
1370
        if vt.get('impact'):
1371
            impact_xml_str = self.get_impact_vt_as_xml_str(
1372
                vt_id, vt.get('impact'))
1373
            vt_xml.append(secET.fromstring(impact_xml_str))
1374
1375
        if vt.get('affected'):
1376
            affected_xml_str = self.get_affected_vt_as_xml_str(
1377
                vt_id, vt.get('affected'))
1378
            vt_xml.append(secET.fromstring(affected_xml_str))
1379
1380
        if vt.get('insight'):
1381
            insight_xml_str = self.get_insight_vt_as_xml_str(
1382
                vt_id, vt.get('insight'))
1383
            vt_xml.append(secET.fromstring(insight_xml_str))
1384
1385
        if vt.get('solution'):
1386
            solution_xml_str = self.get_solution_vt_as_xml_str(
1387
                vt_id, vt.get('solution'), vt.get('solution_type'))
1388
            vt_xml.append(secET.fromstring(solution_xml_str))
1389
1390
        if vt.get('detection') or vt.get('qod_type') or vt.get('qod'):
1391
            detection_xml_str = self.get_detection_vt_as_xml_str(
1392
                vt_id, vt.get('detection'), vt.get('qod_type'), vt.get('qod'))
1393
            vt_xml.append(secET.fromstring(detection_xml_str))
1394
1395
        if vt.get('severities'):
1396
            severities_xml_str = self.get_severities_vt_as_xml_str(
1397
                    vt_id, vt.get('severities'))
1398
            vt_xml.append(secET.fromstring(severities_xml_str))
1399
1400
        if vt.get('custom'):
1401
            custom_xml_str = self.get_custom_vt_as_xml_str(
1402
                vt_id, vt.get('custom'))
1403
            vt_xml.append(secET.fromstring(custom_xml_str))
1404
1405
        return vt_xml
1406
1407
    def get_vts_xml(self, vt_id=None, filtered_vts=None):
1408
        """ Gets collection of vulnerability test information in XML format.
1409
        If vt_id is specified, the collection will contain only this vt, if
1410
        found.
1411
        If no vt_id is specified, the collection will contain all vts or those
1412
        passed in filtered_vts.
1413
1414
        Arguments:
1415
            vt_id (vt_id, optional): ID of the vt to get.
1416
            filtered_vts (dict, optional): Filtered VTs collection.
1417
1418
        Return:
1419
            String of collection of vulnerability test information in
1420
            XML format.
1421
        """
1422
1423
        vts_xml = Element('vts')
1424
1425
        if vt_id:
1426
            vts_xml.append(self.get_vt_xml(vt_id))
1427
        elif filtered_vts:
1428
            for vt_id in filtered_vts:
1429
                vts_xml.append(self.get_vt_xml(vt_id))
1430
        else:
1431
            for vt_id in self.vts:
1432
                vts_xml.append(self.get_vt_xml(vt_id))
1433
1434
        return vts_xml
1435
1436
    def handle_get_scanner_details(self):
1437
        """ Handles <get_scanner_details> command.
1438
1439
        @return: Response string for <get_scanner_details> command.
1440
        """
1441
        desc_xml = Element('description')
1442
        desc_xml.text = self.get_scanner_description()
1443
        details = [
1444
            desc_xml,
1445
            self.get_scanner_params_xml()
1446
        ]
1447
        return simple_response_str('get_scanner_details', 200, 'OK', details)
1448
1449
    def handle_get_version_command(self):
1450
        """ Handles <get_version> command.
1451
1452
        @return: Response string for <get_version> command.
1453
        """
1454
        protocol = Element('protocol')
1455
        for name, value in [('name', 'OSP'), ('version', self.get_protocol_version())]:
1456
            elem = SubElement(protocol, name)
1457
            elem.text = value
1458
1459
        daemon = Element('daemon')
1460
        for name, value in [('name', self.get_daemon_name()), ('version', self.get_daemon_version())]:
1461
            elem = SubElement(daemon, name)
1462
            elem.text = value
1463
1464
        scanner = Element('scanner')
1465
        for name, value in [('name', self.get_scanner_name()), ('version', self.get_scanner_version())]:
1466
            elem = SubElement(scanner, name)
1467
            elem.text = value
1468
1469
        content = [protocol, daemon, scanner]
1470
1471
        if self.get_vts_version():
1472
            vts = Element('vts')
1473
            elem = SubElement(vts, 'version')
1474
            elem.text = self.get_vts_version()
1475
            content.append(vts)
1476
1477
        return simple_response_str('get_version', 200, 'OK', content)
1478
1479
    def handle_command(self, command):
1480
        """ Handles an osp command in a string.
1481
1482
        @return: OSP Response to command.
1483
        """
1484
        try:
1485
            tree = secET.fromstring(command)
1486
        except secET.ParseError:
1487
            logger.debug("Erroneous client input: %s", command)
1488
            raise OSPDError('Invalid data')
1489
1490
        if not self.command_exists(tree.tag) and tree.tag != "authenticate":
1491
            raise OSPDError('Bogus command name')
1492
1493
        if tree.tag == "get_version":
1494
            return self.handle_get_version_command()
1495
        elif tree.tag == "start_scan":
1496
            return self.handle_start_scan_command(tree)
1497
        elif tree.tag == "stop_scan":
1498
            return self.handle_stop_scan_command(tree)
1499
        elif tree.tag == "get_scans":
1500
            return self.handle_get_scans_command(tree)
1501
        elif tree.tag == "get_vts":
1502
            return self.handle_get_vts_command(tree)
1503
        elif tree.tag == "delete_scan":
1504
            return self.handle_delete_scan_command(tree)
1505
        elif tree.tag == "help":
1506
            return self.handle_help_command(tree)
1507
        elif tree.tag == "get_scanner_details":
1508
            return self.handle_get_scanner_details()
1509
        else:
1510
            assert False, "Unhandled command: {0}".format(tree.tag)
1511
1512
    def check(self):
1513
        """ Asserts to False. Should be implemented by subclass. """
1514
        raise NotImplementedError
1515
1516
    def run(self, address, port, unix_path):
1517
        """ Starts the Daemon, handling commands until interrupted.
1518
1519
        @return False if error. Runs indefinitely otherwise.
1520
        """
1521
        assert address or unix_path
1522
        if unix_path:
1523
            sock = bind_unix_socket(unix_path)
1524
        else:
1525
            sock = bind_socket(address, port)
1526
        if sock is None:
1527
            return False
1528
1529
        sock.setblocking(False)
1530
        inputs = [sock]
1531
        outputs = []
1532
        try:
1533
            while True:
1534
                readable, _, _ = select.select(
1535
                    inputs, outputs, inputs, SCHEDULER_CHECK_PERIOD)
1536
                for r_socket in readable:
1537
                    if unix_path and r_socket is sock:
1538
                        client_stream, _ = sock.accept()
1539
                        logger.debug("New connection from %s", unix_path)
1540
                        self.handle_client_stream(client_stream, True)
1541
                    else:
1542
                        client_stream = self.new_client_stream(sock)
1543
                        if client_stream is None:
1544
                            continue
1545
                        self.handle_client_stream(client_stream, False)
1546
                    close_client_stream(client_stream, unix_path)
1547
                self.scheduler()
1548
        except KeyboardInterrupt:
1549
            logger.info("Received Ctrl-C shutting-down ...")
1550
        finally:
1551
            sock.shutdown(socket.SHUT_RDWR)
1552
            sock.close()
1553
1554
    def scheduler(self):
1555
        """ Should be implemented by subclass in case of need
1556
        to run tasks periodically. """
1557
1558
    def create_scan(self, scan_id, targets, target_str, options, vts):
1559
        """ Creates a new scan.
1560
1561
        @target: Target to scan.
1562
        @options: Miscellaneous scan options.
1563
1564
        @return: New scan's ID.
1565
        """
1566
        return self.scan_collection.create_scan(scan_id, targets, target_str, options, vts)
1567
1568
    def get_scan_options(self, scan_id):
1569
        """ Gives a scan's list of options. """
1570
        return self.scan_collection.get_options(scan_id)
1571
1572
    def set_scan_option(self, scan_id, name, value):
1573
        """ Sets a scan's option to a provided value. """
1574
        return self.scan_collection.set_option(scan_id, name, value)
1575
1576
    def check_scan_process(self, scan_id):
1577
        """ Check the scan's process, and terminate the scan if not alive. """
1578
        scan_process = self.scan_processes[scan_id]
1579
        progress = self.get_scan_progress(scan_id)
1580
        if progress < 100 and not scan_process.is_alive():
1581
            self.set_scan_status(scan_id, 'stopped')
1582
            self.add_scan_error(scan_id, name="", host="",
1583
                                value="Scan process failure.")
1584
            logger.info("%s: Scan stopped with errors.", scan_id)
1585
        elif progress == 100:
1586
            scan_process.join()
1587
1588
    def get_scan_progress(self, scan_id):
1589
        """ Gives a scan's current progress value. """
1590
        return self.scan_collection.get_progress(scan_id)
1591
1592
    def get_scan_target_progress(self, scan_id):
1593
        """ Gives a list with scan's current progress value of each target. """
1594
        return self.scan_collection.get_target_progress(scan_id)
1595
1596
    def get_scan_target(self, scan_id):
1597
        """ Gives a scan's target. """
1598
        return self.scan_collection.get_target(scan_id)
1599
1600
    def get_scan_ports(self, scan_id, target=''):
1601
        """ Gives a scan's ports list. """
1602
        return self.scan_collection.get_ports(scan_id, target)
1603
1604
    def get_scan_credentials(self, scan_id, target=''):
1605
        """ Gives a scan's credential list. If a target is passed gives
1606
        the credential list for the given target. """
1607
        return self.scan_collection.get_credentials(scan_id, target)
1608
1609
    def get_scan_vts(self, scan_id):
1610
        """ Gives a scan's vts list. """
1611
        return self.scan_collection.get_vts(scan_id)
1612
1613
    def get_scan_start_time(self, scan_id):
1614
        """ Gives a scan's start time. """
1615
        return self.scan_collection.get_start_time(scan_id)
1616
1617
    def get_scan_end_time(self, scan_id):
1618
        """ Gives a scan's end time. """
1619
        return self.scan_collection.get_end_time(scan_id)
1620
1621
    def add_scan_log(self, scan_id, host='', name='', value='', port='',
1622
                     test_id='', qod=''):
1623
        """ Adds a log result to scan_id scan. """
1624
        self.scan_collection.add_result(scan_id, ResultType.LOG, host, name,
1625
                                        value, port, test_id, 0.0, qod)
1626
1627
    def add_scan_error(self, scan_id, host='', name='', value='', port=''):
1628
        """ Adds an error result to scan_id scan. """
1629
        self.scan_collection.add_result(scan_id, ResultType.ERROR, host, name,
1630
                                        value, port)
1631
1632
    def add_scan_host_detail(self, scan_id, host='', name='', value=''):
1633
        """ Adds a host detail result to scan_id scan. """
1634
        self.scan_collection.add_result(scan_id, ResultType.HOST_DETAIL, host,
1635
                                        name, value)
1636
1637
    def add_scan_alarm(self, scan_id, host='', name='', value='', port='',
1638
                       test_id='', severity='', qod=''):
1639
        """ Adds an alarm result to scan_id scan. """
1640
        self.scan_collection.add_result(scan_id, ResultType.ALARM, host, name,
1641
                                        value, port, test_id, severity, qod)
1642