Completed
Push — master ( 745830...54fe4d )
by Juan José
17s queued 13s
created

ospd.ospd   F

Complexity

Total Complexity 202

Size/Duplication

Total Lines 1416
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 804
dl 0
loc 1416
rs 1.796
c 0
b 0
f 0
wmc 202

89 Methods

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