Passed
Pull Request — master (#402)
by Juan José
01:17
created

ospd.ospd.OSPDaemon.add_scan_log()   A

Complexity

Conditions 1

Size

Total Lines 26
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 23
nop 10
dl 0
loc 26
rs 9.328
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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