Passed
Pull Request — master (#201)
by Juan José
01:30
created

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