Passed
Pull Request — master (#198)
by
unknown
01:24
created

ospd.ospd.OSPDaemon.handle_get_performance()   C

Complexity

Conditions 9

Size

Total Lines 51
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 37
nop 2
dl 0
loc 51
rs 6.6586
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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