Completed
Push — master ( 8945f9...62acd1 )
by Juan José
22s queued 14s
created

ospd.ospd   F

Complexity

Total Complexity 216

Size/Duplication

Total Lines 1417
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 216
eloc 784
dl 0
loc 1417
rs 1.816
c 0
b 0
f 0

94 Methods

Rating   Name   Duplication   Size   Complexity  
A OSPDaemon.init() 0 2 1
A OSPDaemon.add_scanner_param() 0 4 1
A OSPDaemon.set_command_attributes() 0 5 2
A OSPDaemon.set_scanner_param() 0 7 1
A OSPDaemon.get_scanner_name() 0 3 1
A OSPDaemon.process_exclude_hosts() 0 10 3
A OSPDaemon.elements_as_text() 0 4 1
A OSPDaemon.calculate_progress() 0 8 2
A OSPDaemon.finish_scan() 0 5 1
A OSPDaemon.remove_scan_hosts_from_target_progress() 0 6 1
A OSPDaemon.get_vts_version() 0 4 1
A OSPDaemon.scan_exists() 0 6 1
A OSPDaemon.process_vts_params() 0 7 1
A OSPDaemon.process_targets_element() 0 7 1
A OSPDaemon.dry_run_scan() 0 22 3
B OSPDaemon.parallel_scan() 0 22 6
A OSPDaemon.process_finished_hosts() 0 14 4
A OSPDaemon.get_protocol_version() 0 3 1
A OSPDaemon.target_is_finished() 0 3 1
A OSPDaemon.get_scanner_param_mandatory() 0 7 2
A OSPDaemon.set_scan_host_finished() 0 5 1
A OSPDaemon.get_scanner_description() 0 3 1
A OSPDaemon.command_exists() 0 3 1
A OSPDaemon.get_scanner_param_default() 0 7 2
A OSPDaemon.get_scan_status() 0 3 1
A OSPDaemon.get_scanner_params() 0 2 1
A OSPDaemon.process_credentials_elements() 0 7 1
A OSPDaemon.get_daemon_name() 0 3 1
A OSPDaemon.set_vts_version() 0 12 2
A OSPDaemon.get_scanner_param_type() 0 7 2
C OSPDaemon.start_scan() 0 49 9
F OSPDaemon.preprocess_scan_params() 0 45 15
A OSPDaemon.exec_scan() 0 3 1
A OSPDaemon.__init__() 0 41 4
A OSPDaemon.set_scan_host_progress() 0 5 1
A OSPDaemon.get_help_text() 0 26 5
A OSPDaemon.stop_scan_cleanup() 0 3 1
A OSPDaemon.get_server_version() 0 4 1
A OSPDaemon.set_scan_progress() 0 4 1
B OSPDaemon.stop_scan() 0 33 6
A OSPDaemon.get_scanner_params_xml() 0 7 1
B OSPDaemon.add_vt() 0 51 1
C OSPDaemon.handle_client_stream() 0 39 10
A OSPDaemon.handle_timeout() 0 7 1
A OSPDaemon.get_scanner_version() 0 3 1
A OSPDaemon.process_scan_params() 0 4 1
B OSPDaemon.check_pending_target() 0 33 6
A OSPDaemon.set_scan_status() 0 3 1
A OSPDaemon.get_daemon_version() 0 3 1
A OSPDaemon.get_scan_finished_hosts() 0 3 1
B OSPDaemon.clean_forgotten_scans() 0 25 7
A OSPDaemon.get_scan_options() 0 3 1
B OSPDaemon.create_scan() 0 31 8
A OSPDaemon.get_xml_str() 0 12 1
A OSPDaemon.get_modification_time_vt_as_xml_str() 0 15 1
A OSPDaemon.get_custom_vt_as_xml_str() 0 15 1
A OSPDaemon.add_scan_error() 0 12 1
A OSPDaemon.get_scan_start_time() 0 3 1
A OSPDaemon.get_params_vt_as_xml_str() 0 15 1
A OSPDaemon.add_scan_alarm() 0 24 1
A OSPDaemon.get_scan_vts() 0 3 1
A OSPDaemon.get_scan_progress() 0 3 1
A OSPDaemon.get_severities_vt_as_xml_str() 0 15 1
A OSPDaemon.get_scan_target_options() 0 4 1
A OSPDaemon.get_affected_vt_as_xml_str() 0 15 1
A OSPDaemon.get_scan_xml() 0 34 4
A OSPDaemon.get_detection_vt_as_xml_str() 0 15 1
A OSPDaemon.check() 0 3 1
A OSPDaemon.get_scan_unfinished_hosts() 0 3 1
A OSPDaemon.get_insight_vt_as_xml_str() 0 15 1
A OSPDaemon.set_scan_option() 0 3 1
A OSPDaemon.scheduler() 0 2 1
A OSPDaemon.get_summary_vt_as_xml_str() 0 15 1
A OSPDaemon.wait_for_children() 0 4 2
A OSPDaemon.get_dependencies_vt_as_xml_str() 0 15 1
A OSPDaemon.get_impact_vt_as_xml_str() 0 15 1
A OSPDaemon.add_scan_log() 0 23 1
A OSPDaemon.get_refs_vt_as_xml_str() 0 15 1
A OSPDaemon.get_scan_target() 0 3 1
A OSPDaemon.get_scan_ports() 0 3 1
A OSPDaemon.check_scan_process() 0 16 5
F OSPDaemon.get_vt_xml() 0 98 18
A OSPDaemon.get_solution_vt_as_xml_str() 0 15 1
B OSPDaemon.get_vts_selection_list() 0 35 6
A OSPDaemon.get_scan_exclude_hosts() 0 4 1
A OSPDaemon.delete_scan() 0 20 5
A OSPDaemon.get_scan_end_time() 0 3 1
A OSPDaemon.get_scan_credentials() 0 4 1
A OSPDaemon.add_scan_host_detail() 0 11 1
A OSPDaemon.get_creation_time_vt_as_xml_str() 0 15 1
A OSPDaemon.get_scan_results_xml() 0 15 2
A OSPDaemon.get_scan_target_progress() 0 3 1
B OSPDaemon.handle_command() 0 22 6
A OSPDaemon.run() 0 17 3

1 Function

Rating   Name   Duplication   Size   Complexity  
A _terminate_process_group() 0 2 1

How to fix   Complexity   

Complexity

Complex classes like ospd.ospd often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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