Completed
Push — master ( 8e2a39...26c1f2 )
by
unknown
17s queued 15s
created

ospd.ospd.OSPDaemon.get_scanner_name()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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