Completed
Push — master ( e0b344...410638 )
by
unknown
21s queued 12s
created

ospd.ospd.OSPDaemon.dry_run_scan()   A

Complexity

Conditions 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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