Completed
Push — master ( 45c408...8672a7 )
by Juan José
15s queued 13s
created

ospd.ospd.OSPDaemon.handle_command()   D

Complexity

Conditions 12

Size

Total Lines 42
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 30
nop 3
dl 0
loc 42
rs 4.8
c 0
b 0
f 0

How to fix   Complexity   

Complexity

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

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

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