Completed
Push — master ( 46baa9...f16cc7 )
by Juan José
14s queued 12s
created

ospd.ospd   F

Complexity

Total Complexity 200

Size/Duplication

Total Lines 1407
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 799
dl 0
loc 1407
rs 1.801
c 0
b 0
f 0
wmc 200

89 Methods

Rating   Name   Duplication   Size   Complexity  
A OSPDaemon.scheduler() 0 2 1
A OSPDaemon.wait_for_children() 0 4 2
A OSPDaemon.get_scan_progress() 0 3 1
A OSPDaemon.add_scan_error() 0 20 1
A OSPDaemon.elements_as_text() 0 4 1
A OSPDaemon.scan_exists() 0 6 1
A OSPDaemon.get_scan_status() 0 3 1
A OSPDaemon.delete_scan() 0 20 5
A OSPDaemon.get_help_text() 0 26 5
A OSPDaemon.get_scan_results_xml() 0 15 2
A OSPDaemon.set_scan_status() 0 3 1
A OSPDaemon.__init__() 0 52 4
A OSPDaemon.get_scanner_name() 0 3 1
A OSPDaemon.finish_scan() 0 5 1
A OSPDaemon.get_vts_version() 0 4 1
A OSPDaemon.process_vts_params() 0 7 1
A OSPDaemon.process_targets_element() 0 7 1
A OSPDaemon.get_protocol_version() 0 3 1
A OSPDaemon.target_is_finished() 0 3 1
A OSPDaemon.get_scanner_param_mandatory() 0 7 2
A OSPDaemon.init() 0 8 1
A OSPDaemon.get_scanner_description() 0 3 1
A OSPDaemon.command_exists() 0 3 1
A OSPDaemon.get_scanner_param_default() 0 7 2
A OSPDaemon.get_scanner_params() 0 2 1
A OSPDaemon.process_credentials_elements() 0 7 1
A OSPDaemon.get_daemon_name() 0 3 1
A OSPDaemon.set_vts_version() 0 12 2
A OSPDaemon.get_scanner_param_type() 0 7 2
F OSPDaemon.preprocess_scan_params() 0 45 15
A OSPDaemon.exec_scan() 0 3 1
A OSPDaemon.add_scanner_param() 0 4 1
A OSPDaemon.set_command_attributes() 0 5 2
A OSPDaemon.stop_scan_cleanup() 0 3 1
A OSPDaemon.get_server_version() 0 4 1
B OSPDaemon.stop_scan() 0 33 6
A OSPDaemon.get_scanner_params_xml() 0 7 1
B OSPDaemon.add_vt() 0 51 1
C OSPDaemon.handle_client_stream() 0 45 11
A OSPDaemon.set_scanner_param() 0 7 1
A OSPDaemon.get_scanner_version() 0 3 1
A OSPDaemon.process_scan_params() 0 4 1
A OSPDaemon.get_daemon_version() 0 3 1
A OSPDaemon.get_xml_str() 0 12 1
A OSPDaemon.get_modification_time_vt_as_xml_str() 0 15 1
A OSPDaemon.get_custom_vt_as_xml_str() 0 15 1
A OSPDaemon.get_params_vt_as_xml_str() 0 15 1
A OSPDaemon.dry_run_scan() 0 16 2
A OSPDaemon.process_finished_hosts() 0 9 2
A OSPDaemon.get_severities_vt_as_xml_str() 0 15 1
A OSPDaemon.get_affected_vt_as_xml_str() 0 15 1
B OSPDaemon.get_scan_xml() 0 38 5
A OSPDaemon.get_detection_vt_as_xml_str() 0 15 1
A OSPDaemon.check() 0 3 1
A OSPDaemon._get_scan_progress_xml() 0 25 1
A OSPDaemon.get_insight_vt_as_xml_str() 0 15 1
A OSPDaemon.get_summary_vt_as_xml_str() 0 15 1
A OSPDaemon.get_dependencies_vt_as_xml_str() 0 15 1
A OSPDaemon.get_impact_vt_as_xml_str() 0 15 1
A OSPDaemon.get_vt_iterator() 0 6 1
A OSPDaemon.get_refs_vt_as_xml_str() 0 15 1
A OSPDaemon.start_scan() 0 22 4
F OSPDaemon.get_vt_xml() 0 98 19
A OSPDaemon.set_scan_host_progress() 0 18 5
A OSPDaemon.get_solution_vt_as_xml_str() 0 15 1
A OSPDaemon.get_vts_selection_list() 0 33 5
A OSPDaemon.set_scan_progress_batch() 0 7 1
A OSPDaemon.get_creation_time_vt_as_xml_str() 0 15 1
A OSPDaemon.handle_timeout() 0 7 1
A OSPDaemon.sort_host_finished() 0 27 5
B OSPDaemon.handle_command() 0 32 8
A OSPDaemon.run() 0 13 3
B OSPDaemon.clean_forgotten_scans() 0 25 7
A OSPDaemon.get_scan_options() 0 3 1
A OSPDaemon.create_scan() 0 25 3
A OSPDaemon.start_pending_scans() 0 8 3
A OSPDaemon.get_scan_host() 0 3 1
A OSPDaemon.get_scan_start_time() 0 3 1
A OSPDaemon.add_scan_alarm() 0 24 1
A OSPDaemon.get_scan_vts() 0 3 1
A OSPDaemon.get_scan_target_options() 0 4 1
A OSPDaemon.set_scan_option() 0 3 1
A OSPDaemon.add_scan_log() 0 24 1
A OSPDaemon.get_scan_ports() 0 3 1
B OSPDaemon.check_scan_process() 0 19 6
A OSPDaemon.get_scan_exclude_hosts() 0 4 1
A OSPDaemon.get_scan_end_time() 0 3 1
A OSPDaemon.get_scan_credentials() 0 4 1
A OSPDaemon.add_scan_host_detail() 0 11 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A _terminate_process_group() 0 2 1

How to fix   Complexity   

Complexity

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

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

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