"Fossies" - the Fresh Open Source Software Archive

Member "ospd-2.0.1/ospd/ospd.py" (12 May 2020, 67258 Bytes) of package /linux/misc/openvas/ospd-2.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "ospd.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.0.0_vs_2.0.1.

    1 # Copyright (C) 2014-2018 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 
   25 # This is needed for older pythons as our current module is called the same
   26 # as the package we are in ...
   27 # Another solution would be to rename that file.
   28 from __future__ import absolute_import
   29 
   30 import logging
   31 import socket
   32 import ssl
   33 import multiprocessing
   34 import re
   35 import time
   36 import os
   37 import subprocess
   38 
   39 from xml.etree.ElementTree import Element, SubElement
   40 
   41 import defusedxml.ElementTree as secET
   42 
   43 from ospd import __version__
   44 from ospd.errors import OspdCommandError, OspdError
   45 from ospd.misc import ScanCollection, ResultType, ScanStatus, valid_uuid
   46 from ospd.network import resolve_hostname, target_str_to_list
   47 from ospd.server import BaseServer
   48 from ospd.vtfilter import VtsFilter
   49 from ospd.xml import simple_response_str, get_result_xml, XmlStringHelper
   50 
   51 logger = logging.getLogger(__name__)
   52 
   53 PROTOCOL_VERSION = "1.2"
   54 
   55 SCHEDULER_CHECK_PERIOD = 5  # in seconds
   56 
   57 GVMCG_TITLES = [
   58     'cpu-*',
   59     'proc',
   60     'mem',
   61     'swap',
   62     'load',
   63     'df-*',
   64     'disk-sd[a-z][0-9]-rw',
   65     'disk-sd[a-z][0-9]-load',
   66     'disk-sd[a-z][0-9]-io-load',
   67     'interface-eth*-traffic',
   68     'interface-eth*-err-rate',
   69     'interface-eth*-err',
   70     'sensors-*_temperature-*',
   71     'sensors-*_fanspeed-*',
   72     'sensors-*_voltage-*',
   73     'titles',
   74 ]
   75 
   76 BASE_SCANNER_PARAMS = {
   77     'debug_mode': {
   78         'type': 'boolean',
   79         'name': 'Debug Mode',
   80         'default': 0,
   81         'mandatory': 0,
   82         'description': 'Whether to get extra scan debug information.',
   83     },
   84     'dry_run': {
   85         'type': 'boolean',
   86         'name': 'Dry Run',
   87         'default': 0,
   88         'mandatory': 0,
   89         'description': 'Whether to dry run scan.',
   90     },
   91 }
   92 
   93 COMMANDS_TABLE = {
   94     'start_scan': {
   95         'description': 'Start a new scan.',
   96         'attributes': {
   97             'target': 'Target host to scan',
   98             'ports': 'Ports list to scan',
   99             'scan_id': 'Optional UUID value to use as scan ID',
  100             'parallel': 'Optional nummer of parallel target to scan',
  101         },
  102         'elements': None,
  103     },
  104     'stop_scan': {
  105         'description': 'Stop a currently running scan.',
  106         'attributes': {'scan_id': 'ID of scan to stop.'},
  107         'elements': None,
  108     },
  109     'help': {
  110         'description': 'Print the commands help.',
  111         'attributes': {'format': 'Help format. Could be text or xml.'},
  112         'elements': None,
  113     },
  114     'get_scans': {
  115         'description': 'List the scans in buffer.',
  116         'attributes': {
  117             'scan_id': 'ID of a specific scan to get.',
  118             'details': 'Whether to return the full scan report.',
  119         },
  120         'elements': None,
  121     },
  122     'get_vts': {
  123         'description': 'List of available vulnerability tests.',
  124         'attributes': {
  125             'vt_id': 'ID of a specific vulnerability test to get.',
  126             'filter': 'Optional filter to get an specific vt collection.',
  127         },
  128         'elements': None,
  129     },
  130     'delete_scan': {
  131         'description': 'Delete a finished scan.',
  132         'attributes': {'scan_id': 'ID of scan to delete.'},
  133         'elements': None,
  134     },
  135     'get_version': {
  136         'description': 'Return various versions.',
  137         'attributes': None,
  138         'elements': None,
  139     },
  140     'get_scanner_details': {
  141         'description': 'Return scanner description and parameters',
  142         'attributes': None,
  143         'elements': None,
  144     },
  145     'get_performance': {
  146         'description': 'Return system report',
  147         'attributes': {
  148             'start': 'Time of first data point in report.',
  149             'end': 'Time of last data point in report.',
  150             'title': 'Name of report.',
  151         },
  152         'elements': None,
  153     },
  154 }
  155 
  156 
  157 class OSPDaemon:
  158 
  159     """ Daemon class for OSP traffic handling.
  160 
  161     Every scanner wrapper should subclass it and make necessary additions and
  162     changes.
  163 
  164     * Add any needed parameters in __init__.
  165     * Implement check() method which verifies scanner availability and other
  166       environment related conditions.
  167     * Implement process_scan_params and exec_scan methods which are
  168       specific to handling the <start_scan> command, executing the wrapped
  169       scanner and storing the results.
  170     * exec_scan() should return 0 if host is dead or not reached, 1 if host is
  171       alive and 2 if scan error or status is unknown.
  172     * Implement other methods that assert to False such as get_scanner_name,
  173       get_scanner_version.
  174     * Use Call set_command_attributes at init time to add scanner command
  175       specific options eg. the w3af profile for w3af wrapper.
  176     """
  177 
  178     def __init__(
  179         self, *, customvtfilter=None, **kwargs
  180     ):  # pylint: disable=unused-argument
  181         """ Initializes the daemon's internal data. """
  182         self.scan_collection = ScanCollection()
  183         self.scan_processes = dict()
  184 
  185         self.daemon_info = dict()
  186         self.daemon_info['name'] = "OSPd"
  187         self.daemon_info['version'] = __version__
  188         self.daemon_info['description'] = "No description"
  189 
  190         self.scanner_info = dict()
  191         self.scanner_info['name'] = 'No name'
  192         self.scanner_info['version'] = 'No version'
  193         self.scanner_info['description'] = 'No description'
  194 
  195         self.server_version = None  # Set by the subclass.
  196 
  197         self.scaninfo_store_time = kwargs.get('scaninfo_store_time')
  198 
  199         self.protocol_version = PROTOCOL_VERSION
  200 
  201         self.commands = COMMANDS_TABLE
  202 
  203         self.scanner_params = dict()
  204 
  205         for name, param in BASE_SCANNER_PARAMS.items():
  206             self.add_scanner_param(name, param)
  207 
  208         self.vts = dict()
  209         self.vt_id_pattern = re.compile("[0-9a-zA-Z_\\-:.]{1,80}")
  210         self.vts_version = None
  211 
  212         if customvtfilter:
  213             self.vts_filter = customvtfilter
  214         else:
  215             self.vts_filter = VtsFilter()
  216 
  217         self.is_cache_available = False
  218 
  219     def init(self):
  220         """ Should be overridden by a subclass if the initialization is costly.
  221 
  222             Will be called before check.
  223         """
  224         self.is_cache_available = True
  225 
  226     def set_command_attributes(self, name, attributes):
  227         """ Sets the xml attributes of a specified command. """
  228         if self.command_exists(name):
  229             command = self.commands.get(name)
  230             command['attributes'] = attributes
  231 
  232     def add_scanner_param(self, name, scanner_param):
  233         """ Add a scanner parameter. """
  234 
  235         assert name
  236         assert scanner_param
  237         self.scanner_params[name] = scanner_param
  238         command = self.commands.get('start_scan')
  239         command['elements'] = {
  240             'scanner_params': {
  241                 k: v['name'] for k, v in self.scanner_params.items()
  242             }
  243         }
  244 
  245     def add_vt(
  246         self,
  247         vt_id,
  248         name=None,
  249         vt_params=None,
  250         vt_refs=None,
  251         custom=None,
  252         vt_creation_time=None,
  253         vt_modification_time=None,
  254         vt_dependencies=None,
  255         summary=None,
  256         impact=None,
  257         affected=None,
  258         insight=None,
  259         solution=None,
  260         solution_t=None,
  261         detection=None,
  262         qod_t=None,
  263         qod_v=None,
  264         severities=None,
  265     ):
  266         """ Add a vulnerability test information.
  267         """
  268 
  269         if not vt_id:
  270             raise OspdError('Invalid vt_id {}'.format(vt_id))
  271 
  272         if self.vt_id_pattern.fullmatch(vt_id) is None:
  273             raise OspdError('Invalid vt_id {}'.format(vt_id))
  274 
  275         if vt_id in self.vts:
  276             raise OspdError('vt_id {} already exists'.format(vt_id))
  277 
  278         if name is None:
  279             name = ''
  280 
  281         vt = {'name': name}
  282         if custom is not None:
  283             vt["custom"] = custom
  284         if vt_params is not None:
  285             vt["vt_params"] = vt_params
  286         if vt_refs is not None:
  287             vt["vt_refs"] = vt_refs
  288         if vt_dependencies is not None:
  289             vt["vt_dependencies"] = vt_dependencies
  290         if vt_creation_time is not None:
  291             vt["creation_time"] = vt_creation_time
  292         if vt_modification_time is not None:
  293             vt["modification_time"] = vt_modification_time
  294         if summary is not None:
  295             vt["summary"] = summary
  296         if impact is not None:
  297             vt["impact"] = impact
  298         if affected is not None:
  299             vt["affected"] = affected
  300         if insight is not None:
  301             vt["insight"] = insight
  302 
  303         if solution is not None:
  304             vt["solution"] = solution
  305             if solution_t is not None:
  306                 vt["solution_type"] = solution_t
  307 
  308         if detection is not None:
  309             vt["detection"] = detection
  310 
  311         if qod_t is not None:
  312             vt["qod_type"] = qod_t
  313         elif qod_v is not None:
  314             vt["qod"] = qod_v
  315 
  316         if severities is not None:
  317             vt["severities"] = severities
  318 
  319         self.vts[vt_id] = vt
  320 
  321     def set_vts_version(self, vts_version):
  322         """ Add into the vts dictionary an entry to identify the
  323         vts version.
  324 
  325         Parameters:
  326             vts_version (str): Identifies a unique vts version.
  327         """
  328         if not vts_version:
  329             raise OspdCommandError(
  330                 'A vts_version parameter is required', 'set_vts_version'
  331             )
  332         self.vts_version = vts_version
  333 
  334     def get_vts_version(self):
  335         """Return the vts version.
  336         """
  337         return self.vts_version
  338 
  339     def command_exists(self, name):
  340         """ Checks if a commands exists. """
  341         return name in self.commands.keys()
  342 
  343     def get_scanner_name(self):
  344         """ Gives the wrapped scanner's name. """
  345         return self.scanner_info['name']
  346 
  347     def get_scanner_version(self):
  348         """ Gives the wrapped scanner's version. """
  349         return self.scanner_info['version']
  350 
  351     def get_scanner_description(self):
  352         """ Gives the wrapped scanner's description. """
  353         return self.scanner_info['description']
  354 
  355     def get_server_version(self):
  356         """ Gives the specific OSP server's version. """
  357         assert self.server_version
  358         return self.server_version
  359 
  360     def get_protocol_version(self):
  361         """ Gives the OSP's version. """
  362         return self.protocol_version
  363 
  364     def _preprocess_scan_params(self, xml_params):
  365         """ Processes the scan parameters. """
  366         params = {}
  367         for param in xml_params:
  368             params[param.tag] = param.text or ''
  369         # Set default values.
  370         for key in self.scanner_params:
  371             if key not in params:
  372                 params[key] = self.get_scanner_param_default(key)
  373                 if self.get_scanner_param_type(key) == 'selection':
  374                     params[key] = params[key].split('|')[0]
  375         # Validate values.
  376         for key in params:
  377             param_type = self.get_scanner_param_type(key)
  378             if not param_type:
  379                 continue
  380             if param_type in ['integer', 'boolean']:
  381                 try:
  382                     params[key] = int(params[key])
  383                 except ValueError:
  384                     raise OspdCommandError(
  385                         'Invalid %s value' % key, 'start_scan'
  386                     )
  387             if param_type == 'boolean':
  388                 if params[key] not in [0, 1]:
  389                     raise OspdCommandError(
  390                         'Invalid %s value' % key, 'start_scan'
  391                     )
  392             elif param_type == 'selection':
  393                 selection = self.get_scanner_param_default(key).split('|')
  394                 if params[key] not in selection:
  395                     raise OspdCommandError(
  396                         'Invalid %s value' % key, 'start_scan'
  397                     )
  398             if self.get_scanner_param_mandatory(key) and params[key] == '':
  399                 raise OspdCommandError(
  400                     'Mandatory %s value is missing' % key, 'start_scan'
  401                 )
  402         return params
  403 
  404     def process_scan_params(self, params):
  405         """ This method is to be overridden by the child classes if necessary
  406         """
  407         return params
  408 
  409     def process_vts_params(self, scanner_vts):
  410         """ Receive an XML object with the Vulnerability Tests an their
  411         parameters to be use in a scan and return a dictionary.
  412 
  413         @param: XML element with vt subelements. Each vt has an
  414                 id attribute. Optional parameters can be included
  415                 as vt child.
  416                 Example form:
  417                 <vt_selection>
  418                   <vt_single id='vt1' />
  419                   <vt_single id='vt2'>
  420                     <vt_value id='param1'>value</vt_value>
  421                   </vt_single>
  422                   <vt_group filter='family=debian'/>
  423                   <vt_group filter='family=general'/>
  424                 </vt_selection>
  425 
  426         @return: Dictionary containing the vts attribute and subelements,
  427                  like the VT's id and VT's parameters.
  428                  Example form:
  429                  {'vt1': {},
  430                   'vt2': {'value_id': 'value'},
  431                   'vt_groups': ['family=debian', 'family=general']}
  432         """
  433         vt_selection = {}
  434         filters = list()
  435         for vt in scanner_vts:
  436             if vt.tag == 'vt_single':
  437                 vt_id = vt.attrib.get('id')
  438                 vt_selection[vt_id] = {}
  439                 for vt_value in vt:
  440                     if not vt_value.attrib.get('id'):
  441                         raise OspdCommandError(
  442                             'Invalid VT preference. No attribute id',
  443                             'start_scan',
  444                         )
  445                     vt_value_id = vt_value.attrib.get('id')
  446                     vt_value_value = vt_value.text if vt_value.text else ''
  447                     vt_selection[vt_id][vt_value_id] = vt_value_value
  448             if vt.tag == 'vt_group':
  449                 vts_filter = vt.attrib.get('filter', None)
  450                 if vts_filter is None:
  451                     raise OspdCommandError(
  452                         'Invalid VT group. No filter given.', 'start_scan'
  453                     )
  454                 filters.append(vts_filter)
  455         vt_selection['vt_groups'] = filters
  456         return vt_selection
  457 
  458     @staticmethod
  459     def process_credentials_elements(cred_tree):
  460         """ Receive an XML object with the credentials to run
  461         a scan against a given target.
  462 
  463         @param:
  464         <credentials>
  465           <credential type="up" service="ssh" port="22">
  466             <username>scanuser</username>
  467             <password>mypass</password>
  468           </credential>
  469           <credential type="up" service="smb">
  470             <username>smbuser</username>
  471             <password>mypass</password>
  472           </credential>
  473         </credentials>
  474 
  475         @return: Dictionary containing the credentials for a given target.
  476                  Example form:
  477                  {'ssh': {'type': type,
  478                           'port': port,
  479                           'username': username,
  480                           'password': pass,
  481                         },
  482                   'smb': {'type': type,
  483                           'username': username,
  484                           'password': pass,
  485                          },
  486                    }
  487         """
  488         credentials = {}
  489         for credential in cred_tree:
  490             service = credential.attrib.get('service')
  491             credentials[service] = {}
  492             credentials[service]['type'] = credential.attrib.get('type')
  493             if service == 'ssh':
  494                 credentials[service]['port'] = credential.attrib.get('port')
  495             for param in credential:
  496                 credentials[service][param.tag] = param.text
  497 
  498         return credentials
  499 
  500     @classmethod
  501     def process_targets_element(cls, scanner_target):
  502         """ Receive an XML object with the target, ports and credentials to run
  503         a scan against.
  504 
  505         @param: XML element with target subelements. Each target has <hosts>
  506         and <ports> subelements. Hosts can be a single host, a host range,
  507         a comma-separated host list or a network address.
  508         <ports> and  <credentials> are optional. Therefore each ospd-scanner
  509         should check for a valid ones if needed.
  510 
  511                 Example form:
  512                 <targets>
  513                   <target>
  514                     <hosts>localhosts</hosts>
  515                     <exclude_hosts>localhost1</exclude_hosts>
  516                     <ports>80,443</ports>
  517                   </target>
  518                   <target>
  519                     <hosts>192.168.0.0/24</hosts>
  520                     <ports>22</ports>
  521                     <credentials>
  522                       <credential type="up" service="ssh" port="22">
  523                         <username>scanuser</username>
  524                         <password>mypass</password>
  525                       </credential>
  526                       <credential type="up" service="smb">
  527                         <username>smbuser</username>
  528                         <password>mypass</password>
  529                       </credential>
  530                     </credentials>
  531                   </target>
  532                 </targets>
  533 
  534         @return: A list of [hosts, port, {credentials}, exclude_hosts] list.
  535                  Example form:
  536                  [['localhosts', '80,43', '', 'localhosts1'],
  537                   ['192.168.0.0/24', '22', {'smb': {'type': type,
  538                                                     'port': port,
  539                                                     'username': username,
  540                                                     'password': pass,
  541                                                    }}], '']
  542         """
  543 
  544         target_list = []
  545         for target in scanner_target:
  546             exclude_hosts = ''
  547             finished_hosts = ''
  548             ports = ''
  549             credentials = {}
  550             for child in target:
  551                 if child.tag == 'hosts':
  552                     hosts = child.text
  553                 if child.tag == 'exclude_hosts':
  554                     exclude_hosts = child.text
  555                 if child.tag == 'finished_hosts':
  556                     finished_hosts = child.text
  557                 if child.tag == 'ports':
  558                     ports = child.text
  559                 if child.tag == 'credentials':
  560                     credentials = cls.process_credentials_elements(child)
  561             if hosts:
  562                 target_list.append(
  563                     [hosts, ports, credentials, exclude_hosts, finished_hosts]
  564                 )
  565             else:
  566                 raise OspdCommandError('No target to scan', 'start_scan')
  567 
  568         return target_list
  569 
  570     def handle_start_scan_command(self, scan_et):
  571         """ Handles <start_scan> command.
  572 
  573         @return: Response string for <start_scan> command.
  574         """
  575 
  576         target_str = scan_et.attrib.get('target')
  577         ports_str = scan_et.attrib.get('ports')
  578         # For backward compatibility, if target and ports attributes are set,
  579         # <targets> element is ignored.
  580         if target_str is None or ports_str is None:
  581             target_list = scan_et.find('targets')
  582             if target_list is None or len(target_list) == 0:
  583                 raise OspdCommandError('No targets or ports', 'start_scan')
  584             else:
  585                 scan_targets = self.process_targets_element(target_list)
  586         else:
  587             scan_targets = []
  588             for single_target in target_str_to_list(target_str):
  589                 scan_targets.append([single_target, ports_str, '', '', ''])
  590 
  591         scan_id = scan_et.attrib.get('scan_id')
  592         if scan_id is not None and scan_id != '' and not valid_uuid(scan_id):
  593             raise OspdCommandError('Invalid scan_id UUID', 'start_scan')
  594 
  595         try:
  596             parallel = int(scan_et.attrib.get('parallel', '1'))
  597             if parallel < 1 or parallel > 20:
  598                 parallel = 1
  599         except ValueError:
  600             raise OspdCommandError(
  601                 'Invalid value for parallel scans. ' 'It must be a number',
  602                 'start_scan',
  603             )
  604 
  605         scanner_params = scan_et.find('scanner_params')
  606         if scanner_params is None:
  607             raise OspdCommandError('No scanner_params element', 'start_scan')
  608 
  609         params = self._preprocess_scan_params(scanner_params)
  610 
  611         # VTS is an optional element. If present should not be empty.
  612         vt_selection = {}
  613         scanner_vts = scan_et.find('vt_selection')
  614         if scanner_vts is not None:
  615             if len(scanner_vts) == 0:
  616                 raise OspdCommandError('VTs list is empty', 'start_scan')
  617             else:
  618                 vt_selection = self.process_vts_params(scanner_vts)
  619 
  620         # Dry run case.
  621         if 'dry_run' in params and int(params['dry_run']):
  622             scan_func = self.dry_run_scan
  623             scan_params = None
  624         else:
  625             scan_func = self.start_scan
  626             scan_params = self.process_scan_params(params)
  627 
  628         scan_id_aux = scan_id
  629         scan_id = self.create_scan(
  630             scan_id, scan_targets, scan_params, vt_selection
  631         )
  632         if not scan_id:
  633             id_ = Element('id')
  634             id_.text = scan_id_aux
  635             return simple_response_str('start_scan', 100, 'Continue', id_)
  636 
  637         scan_process = multiprocessing.Process(
  638             target=scan_func, args=(scan_id, scan_targets, parallel)
  639         )
  640         self.scan_processes[scan_id] = scan_process
  641         scan_process.start()
  642         id_ = Element('id')
  643         id_.text = scan_id
  644         return simple_response_str('start_scan', 200, 'OK', id_)
  645 
  646     def handle_stop_scan_command(self, scan_et):
  647         """ Handles <stop_scan> command.
  648 
  649         @return: Response string for <stop_scan> command.
  650         """
  651 
  652         scan_id = scan_et.attrib.get('scan_id')
  653         if scan_id is None or scan_id == '':
  654             raise OspdCommandError('No scan_id attribute', 'stop_scan')
  655         self.stop_scan(scan_id)
  656 
  657         return simple_response_str('stop_scan', 200, 'OK')
  658 
  659     def stop_scan(self, scan_id):
  660         scan_process = self.scan_processes.get(scan_id)
  661         if not scan_process:
  662             raise OspdCommandError(
  663                 'Scan not found {0}.'.format(scan_id), 'stop_scan'
  664             )
  665         if not scan_process.is_alive():
  666             raise OspdCommandError(
  667                 'Scan already stopped or finished.', 'stop_scan'
  668             )
  669 
  670         self.set_scan_status(scan_id, ScanStatus.STOPPED)
  671         logger.info('%s: Scan stopping %s.', scan_id, scan_process.ident)
  672         self.stop_scan_cleanup(scan_id)
  673         try:
  674             scan_process.terminate()
  675         except AttributeError:
  676             logger.debug('%s: The scanner task stopped unexpectedly.', scan_id)
  677 
  678         try:
  679             os.killpg(os.getpgid(scan_process.ident), 15)
  680         except ProcessLookupError as e:
  681             logger.info(
  682                 '%s: Scan already stopped %s.', scan_id, scan_process.ident
  683             )
  684 
  685         if scan_process.ident != os.getpid():
  686             scan_process.join()
  687         logger.info('%s: Scan stopped.', scan_id)
  688 
  689     @staticmethod
  690     def stop_scan_cleanup(scan_id):
  691         """ Should be implemented by subclass in case of a clean up before
  692         terminating is needed. """
  693 
  694     @staticmethod
  695     def target_is_finished(scan_id):
  696         """ Should be implemented by subclass in case of a check before
  697         stopping is needed. """
  698 
  699     def exec_scan(self, scan_id, target):
  700         """ Asserts to False. Should be implemented by subclass. """
  701         raise NotImplementedError
  702 
  703     def finish_scan(self, scan_id):
  704         """ Sets a scan as finished. """
  705         self.set_scan_progress(scan_id, 100)
  706         self.set_scan_status(scan_id, ScanStatus.FINISHED)
  707         logger.info("%s: Scan finished.", scan_id)
  708 
  709     def get_daemon_name(self):
  710         """ Gives osp daemon's name. """
  711         return self.daemon_info['name']
  712 
  713     def get_daemon_version(self):
  714         """ Gives osp daemon's version. """
  715         return self.daemon_info['version']
  716 
  717     def get_scanner_param_type(self, param):
  718         """ Returns type of a scanner parameter. """
  719         assert isinstance(param, str)
  720         entry = self.scanner_params.get(param)
  721         if not entry:
  722             return None
  723         return entry.get('type')
  724 
  725     def get_scanner_param_mandatory(self, param):
  726         """ Returns if a scanner parameter is mandatory. """
  727         assert isinstance(param, str)
  728         entry = self.scanner_params.get(param)
  729         if not entry:
  730             return False
  731         return entry.get('mandatory')
  732 
  733     def get_scanner_param_default(self, param):
  734         """ Returns default value of a scanner parameter. """
  735         assert isinstance(param, str)
  736         entry = self.scanner_params.get(param)
  737         if not entry:
  738             return None
  739         return entry.get('default')
  740 
  741     def get_scanner_params_xml(self):
  742         """ Returns the OSP Daemon's scanner params in xml format. """
  743         scanner_params = Element('scanner_params')
  744         for param_id, param in self.scanner_params.items():
  745             param_xml = SubElement(scanner_params, 'scanner_param')
  746             for name, value in [('id', param_id), ('type', param['type'])]:
  747                 param_xml.set(name, value)
  748             for name, value in [
  749                 ('name', param['name']),
  750                 ('description', param['description']),
  751                 ('default', param['default']),
  752                 ('mandatory', param['mandatory']),
  753             ]:
  754                 elem = SubElement(param_xml, name)
  755                 elem.text = str(value)
  756         return scanner_params
  757 
  758     def handle_client_stream(self, stream):
  759         """ Handles stream of data received from client. """
  760 
  761         data = b''
  762 
  763         while True:
  764             try:
  765                 buf = stream.read()
  766                 if not buf:
  767                     break
  768 
  769                 data += buf
  770             except (AttributeError, ValueError) as message:
  771                 logger.error(message)
  772                 return
  773             except (ssl.SSLError) as exception:
  774                 logger.debug('Error: %s', exception)
  775                 break
  776             except (socket.timeout) as exception:
  777                 break
  778 
  779         if len(data) <= 0:
  780             logger.debug("Empty client stream")
  781             return
  782 
  783         response = None
  784         try:
  785             self.handle_command(data, stream)
  786         except OspdCommandError as exception:
  787             response = exception.as_xml()
  788             logger.debug('Command error: %s', exception.message)
  789         except Exception:  # pylint: disable=broad-except
  790             logger.exception('While handling client command:')
  791             exception = OspdCommandError('Fatal error', 'error')
  792             response = exception.as_xml()
  793         if response:
  794             stream.write(response)
  795         stream.close()
  796 
  797     def parallel_scan(self, scan_id, target):
  798         """ Starts the scan with scan_id. """
  799         try:
  800             ret = self.exec_scan(scan_id, target)
  801             if ret == 0:
  802                 logger.info("%s: Host scan dead.", target)
  803             elif ret == 1:
  804                 logger.info("%s: Host scan alived.", target)
  805             elif ret == 2:
  806                 logger.info("%s: Scan error or status unknown.", target)
  807             else:
  808                 logger.debug('%s: No host status returned', target)
  809         except Exception as e:  # pylint: disable=broad-except
  810             self.add_scan_error(
  811                 scan_id,
  812                 name='',
  813                 host=target,
  814                 value='Host process failure (%s).' % e,
  815             )
  816             logger.exception('While scanning %s:', target)
  817         else:
  818             logger.info("%s: Host scan finished.", target)
  819 
  820     def check_pending_target(self, scan_id, multiscan_proc):
  821         """ Check if a scan process is still alive. In case the process
  822         finished or is stopped, removes the process from the multiscan
  823         _process list.
  824         Processes dead and with progress < 100% are considered stopped
  825         or with failures. Then will try to stop the other runnings (target)
  826         scans owned by the same task.
  827 
  828         @input scan_id        Scan_id of the whole scan.
  829         @input multiscan_proc A list with the scan process which
  830                               may still be alive.
  831 
  832         @return Actualized list with current running scan processes.
  833         """
  834         for running_target_proc, running_target_id in multiscan_proc:
  835             if not running_target_proc.is_alive():
  836                 target_prog = self.get_scan_target_progress(
  837                     scan_id, running_target_id
  838                 )
  839 
  840                 _not_finished_clean = target_prog < 100
  841                 _not_stopped = (
  842                     self.get_scan_status(scan_id) != ScanStatus.STOPPED
  843                 )
  844 
  845                 if _not_finished_clean and _not_stopped:
  846                     if not self.target_is_finished(scan_id):
  847                         self.stop_scan(scan_id)
  848 
  849                 running_target = (running_target_proc, running_target_id)
  850                 multiscan_proc.remove(running_target)
  851 
  852         return multiscan_proc
  853 
  854     def calculate_progress(self, scan_id):
  855         """ Calculate the total scan progress from the
  856         partial target progress. """
  857 
  858         t_prog = dict()
  859         for target in self.get_scan_target(scan_id):
  860             t_prog[target] = self.get_scan_target_progress(scan_id, target)
  861         return sum(t_prog.values()) / len(t_prog)
  862 
  863     def process_exclude_hosts(self, scan_id, target_list):
  864         """ Process the exclude hosts before launching the scans."""
  865 
  866         for target, _, _, exclude_hosts, _ in target_list:
  867             exc_hosts_list = ''
  868             if not exclude_hosts:
  869                 continue
  870             exc_hosts_list = target_str_to_list(exclude_hosts)
  871             self.remove_scan_hosts_from_target_progress(
  872                 scan_id, target, exc_hosts_list
  873             )
  874 
  875     def process_finished_hosts(self, scan_id, target_list):
  876         """ Process the finished hosts before launching the scans.
  877         Set finished hosts as finished with 100% to calculate
  878         the scan progress."""
  879 
  880         for target, _, _, _, finished_hosts in target_list:
  881             exc_hosts_list = ''
  882             if not finished_hosts:
  883                 continue
  884             exc_hosts_list = target_str_to_list(finished_hosts)
  885 
  886             for host in exc_hosts_list:
  887                 self.set_scan_host_finished(scan_id, target, host)
  888                 self.set_scan_host_progress(scan_id, target, host, 100)
  889 
  890     def start_scan(self, scan_id, targets, parallel=1):
  891         """ Handle N parallel scans if 'parallel' is greater than 1. """
  892 
  893         os.setsid()
  894         multiscan_proc = []
  895         logger.info("%s: Scan started.", scan_id)
  896         target_list = targets
  897         if target_list is None or not target_list:
  898             raise OspdCommandError('Erroneous targets list', 'start_scan')
  899 
  900         self.process_exclude_hosts(scan_id, target_list)
  901         self.process_finished_hosts(scan_id, target_list)
  902 
  903         for _index, target in enumerate(target_list):
  904             while len(multiscan_proc) >= parallel:
  905                 progress = self.calculate_progress(scan_id)
  906                 self.set_scan_progress(scan_id, progress)
  907                 multiscan_proc = self.check_pending_target(
  908                     scan_id, multiscan_proc
  909                 )
  910                 time.sleep(1)
  911 
  912             # If the scan status is stopped, does not launch anymore target
  913             # scans
  914             if self.get_scan_status(scan_id) == ScanStatus.STOPPED:
  915                 return
  916 
  917             logger.debug(
  918                 "%s: Host scan started on ports %s.", target[0], target[1]
  919             )
  920             scan_process = multiprocessing.Process(
  921                 target=self.parallel_scan, args=(scan_id, target[0])
  922             )
  923             multiscan_proc.append((scan_process, target[0]))
  924             scan_process.start()
  925             self.set_scan_status(scan_id, ScanStatus.RUNNING)
  926 
  927         # Wait until all single target were scanned
  928         while multiscan_proc:
  929             multiscan_proc = self.check_pending_target(scan_id, multiscan_proc)
  930             if multiscan_proc:
  931                 progress = self.calculate_progress(scan_id)
  932                 self.set_scan_progress(scan_id, progress)
  933             time.sleep(1)
  934 
  935         # Only set the scan as finished if the scan was not stopped.
  936         if self.get_scan_status(scan_id) != ScanStatus.STOPPED:
  937             self.finish_scan(scan_id)
  938 
  939     def dry_run_scan(self, scan_id, targets, parallel):
  940         """ Dry runs a scan. """
  941 
  942         os.setsid()
  943         for _, target in enumerate(targets):
  944             host = resolve_hostname(target[0])
  945             if host is None:
  946                 logger.info("Couldn't resolve %s.", target[0])
  947                 continue
  948             port = self.get_scan_ports(scan_id, target=target[0])
  949             logger.info("%s:%s: Dry run mode.", host, port)
  950             self.add_scan_log(
  951                 scan_id, name='', host=host, value='Dry run result'
  952             )
  953         self.finish_scan(scan_id)
  954 
  955     def handle_timeout(self, scan_id, host):
  956         """ Handles scanner reaching timeout error. """
  957         self.add_scan_error(
  958             scan_id,
  959             host=host,
  960             name="Timeout",
  961             value="{0} exec timeout.".format(self.get_scanner_name()),
  962         )
  963 
  964     def remove_scan_hosts_from_target_progress(
  965         self, scan_id, target, exc_hosts_list
  966     ):
  967         """ Remove a list of hosts from the main scan progress table."""
  968         self.scan_collection.remove_hosts_from_target_progress(
  969             scan_id, target, exc_hosts_list
  970         )
  971 
  972     def set_scan_host_finished(self, scan_id, target, host):
  973         """ Add the host in a list of finished hosts """
  974         self.scan_collection.set_host_finished(scan_id, target, host)
  975 
  976     def set_scan_progress(self, scan_id, progress):
  977         """ Sets scan_id scan's progress which is a number
  978         between 0 and 100. """
  979         self.scan_collection.set_progress(scan_id, progress)
  980 
  981     def set_scan_host_progress(self, scan_id, target, host, progress):
  982         """ Sets host's progress which is part of target. """
  983         self.scan_collection.set_host_progress(scan_id, target, host, progress)
  984 
  985     def set_scan_status(self, scan_id, status):
  986         """ Set the scan's status."""
  987         self.scan_collection.set_status(scan_id, status)
  988 
  989     def get_scan_status(self, scan_id):
  990         """ Get scan_id scans's status."""
  991         return self.scan_collection.get_status(scan_id)
  992 
  993     def scan_exists(self, scan_id):
  994         """ Checks if a scan with ID scan_id is in collection.
  995 
  996         @return: 1 if scan exists, 0 otherwise.
  997         """
  998         return self.scan_collection.id_exists(scan_id)
  999 
 1000     def handle_get_scans_command(self, scan_et):
 1001         """ Handles <get_scans> command.
 1002 
 1003         @return: Response string for <get_scans> command.
 1004         """
 1005 
 1006         scan_id = scan_et.attrib.get('scan_id')
 1007         details = scan_et.attrib.get('details')
 1008         pop_res = scan_et.attrib.get('pop_results')
 1009         if details and details == '0':
 1010             details = False
 1011         else:
 1012             details = True
 1013             if pop_res and pop_res == '1':
 1014                 pop_res = True
 1015             else:
 1016                 pop_res = False
 1017 
 1018         responses = []
 1019         if scan_id and scan_id in self.scan_collection.ids_iterator():
 1020             self.check_scan_process(scan_id)
 1021             scan = self.get_scan_xml(scan_id, details, pop_res)
 1022             responses.append(scan)
 1023         elif scan_id:
 1024             text = "Failed to find scan '{0}'".format(scan_id)
 1025             return simple_response_str('get_scans', 404, text)
 1026         else:
 1027             for scan_id in self.scan_collection.ids_iterator():
 1028                 self.check_scan_process(scan_id)
 1029                 scan = self.get_scan_xml(scan_id, details, pop_res)
 1030                 responses.append(scan)
 1031         return simple_response_str('get_scans', 200, 'OK', responses)
 1032 
 1033     def handle_get_vts_command(self, vt_et):
 1034         """ Handles <get_vts> command.
 1035         The <get_vts> element accept two optional arguments.
 1036         vt_id argument receives a single vt id.
 1037         filter argument receives a filter selecting a sub set of vts.
 1038         If both arguments are given, the vts which match with the filter
 1039         are return.
 1040 
 1041         @return: Response string for <get_vts> command.
 1042         """
 1043         if not self.is_cache_available:
 1044             try:
 1045                 yield simple_response_str(
 1046                     'get_vts',
 1047                     409,
 1048                     'Conflict',
 1049                     'A vts update is being performed.',
 1050                 )
 1051             finally:
 1052                 return
 1053 
 1054         self.is_cache_available = False
 1055 
 1056         xml_helper = XmlStringHelper()
 1057 
 1058         vt_id = vt_et.attrib.get('vt_id')
 1059         vt_filter = vt_et.attrib.get('filter')
 1060 
 1061         if vt_id and vt_id not in self.vts:
 1062             try:
 1063                 text = "Failed to find vulnerability test '{0}'".format(vt_id)
 1064                 yield simple_response_str('get_vts', 404, text)
 1065             finally:
 1066                 self.is_cache_available = True
 1067                 return
 1068 
 1069         filtered_vts = None
 1070         if not vt_id and vt_filter:
 1071             try:
 1072                 filtered_vts = self.vts_filter.get_filtered_vts_list(
 1073                     self.vts, vt_filter
 1074                 )
 1075             except OspdCommandError as filter_error:
 1076                 self.is_cache_available = True
 1077                 raise OspdCommandError(filter_error)
 1078         elif vt_id:
 1079             filtered_vts = vt_id
 1080         else:
 1081             filtered_vts = self.vts.keys()
 1082 
 1083         yield xml_helper.create_response('get_vts')
 1084         yield xml_helper.create_element('vts')
 1085 
 1086         for vt in self.get_vt_iterator():
 1087             vt_id, _ = vt
 1088             if vt_id not in filtered_vts:
 1089                 continue
 1090             yield xml_helper.add_element(self.get_vt_xml(vt))
 1091 
 1092         yield xml_helper.create_element('vts', end=True)
 1093         yield xml_helper.create_response('get_vts', end=True)
 1094 
 1095         self.is_cache_available = True
 1096 
 1097     def handle_get_performance(self, scan_et):
 1098         """ Handles <get_performance> command.
 1099 
 1100         @return: Response string for <get_performance> command.
 1101         """
 1102         start = scan_et.attrib.get('start')
 1103         end = scan_et.attrib.get('end')
 1104         titles = scan_et.attrib.get('titles')
 1105 
 1106         cmd = ['gvmcg']
 1107         if start:
 1108             try:
 1109                 int(start)
 1110             except ValueError:
 1111                 raise OspdCommandError(
 1112                     'Start argument must be integer.', 'get_performance'
 1113                 )
 1114             cmd.append(start)
 1115 
 1116         if end:
 1117             try:
 1118                 int(end)
 1119             except ValueError:
 1120                 raise OspdCommandError(
 1121                     'End argument must be integer.', 'get_performance'
 1122                 )
 1123             cmd.append(end)
 1124 
 1125         if titles:
 1126             combined = "(" + ")|(".join(GVMCG_TITLES) + ")"
 1127             forbidden = "^[^|&;]+$"
 1128             if re.match(combined, titles) and re.match(forbidden, titles):
 1129                 cmd.append(titles)
 1130             else:
 1131                 raise OspdCommandError(
 1132                     'Arguments not allowed', 'get_performance'
 1133                 )
 1134 
 1135         try:
 1136             output = subprocess.check_output(cmd)
 1137         except (
 1138             subprocess.CalledProcessError,
 1139             PermissionError,
 1140             FileNotFoundError,
 1141         ) as e:
 1142             raise OspdCommandError(
 1143                 'Bogus get_performance format. %s' % e, 'get_performance'
 1144             )
 1145 
 1146         return simple_response_str(
 1147             'get_performance', 200, 'OK', output.decode()
 1148         )
 1149 
 1150     def handle_help_command(self, scan_et):
 1151         """ Handles <help> command.
 1152 
 1153         @return: Response string for <help> command.
 1154         """
 1155         help_format = scan_et.attrib.get('format')
 1156         if help_format is None or help_format == "text":
 1157             # Default help format is text.
 1158             return simple_response_str('help', 200, 'OK', self.get_help_text())
 1159         elif help_format == "xml":
 1160             text = self.get_xml_str(self.commands)
 1161             return simple_response_str('help', 200, 'OK', text)
 1162         raise OspdCommandError('Bogus help format', 'help')
 1163 
 1164     def get_help_text(self):
 1165         """ Returns the help output in plain text format."""
 1166 
 1167         txt = str('\n')
 1168         for name, info in self.commands.items():
 1169             command_txt = "\t{0: <22} {1}\n".format(name, info['description'])
 1170             if info['attributes']:
 1171                 command_txt = ''.join([command_txt, "\t Attributes:\n"])
 1172                 for attrname, attrdesc in info['attributes'].items():
 1173                     attr_txt = "\t  {0: <22} {1}\n".format(attrname, attrdesc)
 1174                     command_txt = ''.join([command_txt, attr_txt])
 1175             if info['elements']:
 1176                 command_txt = ''.join(
 1177                     [
 1178                         command_txt,
 1179                         "\t Elements:\n",
 1180                         self.elements_as_text(info['elements']),
 1181                     ]
 1182                 )
 1183             txt = ''.join([txt, command_txt])
 1184         return txt
 1185 
 1186     def elements_as_text(self, elems, indent=2):
 1187         """ Returns the elems dictionary as formatted plain text. """
 1188         assert elems
 1189         text = ""
 1190         for elename, eledesc in elems.items():
 1191             if isinstance(eledesc, dict):
 1192                 desc_txt = self.elements_as_text(eledesc, indent + 2)
 1193                 desc_txt = ''.join(['\n', desc_txt])
 1194             elif isinstance(eledesc, str):
 1195                 desc_txt = ''.join([eledesc, '\n'])
 1196             else:
 1197                 assert False, "Only string or dictionary"
 1198             ele_txt = "\t{0}{1: <22} {2}".format(
 1199                 ' ' * indent, elename, desc_txt
 1200             )
 1201             text = ''.join([text, ele_txt])
 1202         return text
 1203 
 1204     def handle_delete_scan_command(self, scan_et):
 1205         """ Handles <delete_scan> command.
 1206 
 1207         @return: Response string for <delete_scan> command.
 1208         """
 1209         scan_id = scan_et.attrib.get('scan_id')
 1210         if scan_id is None:
 1211             return simple_response_str(
 1212                 'delete_scan', 404, 'No scan_id attribute'
 1213             )
 1214 
 1215         if not self.scan_exists(scan_id):
 1216             text = "Failed to find scan '{0}'".format(scan_id)
 1217             return simple_response_str('delete_scan', 404, text)
 1218         self.check_scan_process(scan_id)
 1219         if self.delete_scan(scan_id):
 1220             return simple_response_str('delete_scan', 200, 'OK')
 1221         raise OspdCommandError('Scan in progress', 'delete_scan')
 1222 
 1223     def delete_scan(self, scan_id):
 1224         """ Deletes scan_id scan from collection.
 1225 
 1226         @return: 1 if scan deleted, 0 otherwise.
 1227         """
 1228         if self.get_scan_status(scan_id) == ScanStatus.RUNNING:
 1229             return 0
 1230 
 1231         try:
 1232             del self.scan_processes[scan_id]
 1233         except KeyError:
 1234             logger.debug('Scan process for %s not found', scan_id)
 1235         return self.scan_collection.delete_scan(scan_id)
 1236 
 1237     def get_scan_results_xml(self, scan_id, pop_res):
 1238         """ Gets scan_id scan's results in XML format.
 1239 
 1240         @return: String of scan results in xml.
 1241         """
 1242         results = Element('results')
 1243         for result in self.scan_collection.results_iterator(scan_id, pop_res):
 1244             results.append(get_result_xml(result))
 1245 
 1246         logger.debug('Returning %d results', len(results))
 1247         return results
 1248 
 1249     def get_xml_str(self, data):
 1250         """ Creates a string in XML Format using the provided data structure.
 1251 
 1252         @param: Dictionary of xml tags and their elements.
 1253 
 1254         @return: String of data in xml format.
 1255         """
 1256 
 1257         responses = []
 1258         for tag, value in data.items():
 1259             elem = Element(tag)
 1260             if isinstance(value, dict):
 1261                 for val in self.get_xml_str(value):
 1262                     elem.append(val)
 1263             elif isinstance(value, list):
 1264                 elem.text = ', '.join(value)
 1265             else:
 1266                 elem.text = value
 1267             responses.append(elem)
 1268         return responses
 1269 
 1270     def get_scan_xml(self, scan_id, detailed=True, pop_res=False):
 1271         """ Gets scan in XML format.
 1272 
 1273         @return: String of scan in XML format.
 1274         """
 1275         if not scan_id:
 1276             return Element('scan')
 1277 
 1278         target = ','.join(self.get_scan_target(scan_id))
 1279         progress = self.get_scan_progress(scan_id)
 1280         status = self.get_scan_status(scan_id)
 1281         start_time = self.get_scan_start_time(scan_id)
 1282         end_time = self.get_scan_end_time(scan_id)
 1283         response = Element('scan')
 1284         for name, value in [
 1285             ('id', scan_id),
 1286             ('target', target),
 1287             ('progress', progress),
 1288             ('status', status.name.lower()),
 1289             ('start_time', start_time),
 1290             ('end_time', end_time),
 1291         ]:
 1292             response.set(name, str(value))
 1293         if detailed:
 1294             response.append(self.get_scan_results_xml(scan_id, pop_res))
 1295         return response
 1296 
 1297     @staticmethod
 1298     def get_custom_vt_as_xml_str(
 1299         vt_id, custom
 1300     ):  # pylint: disable=unused-argument
 1301         """ Create a string representation of the XML object from the
 1302         custom data object.
 1303         This needs to be implemented by each ospd wrapper, in case
 1304         custom elements for VTs are used.
 1305 
 1306         The custom XML object which is returned will be embedded
 1307         into a <custom></custom> element.
 1308 
 1309         @return: XML object as string for custom data.
 1310         """
 1311         return ''
 1312 
 1313     @staticmethod
 1314     def get_params_vt_as_xml_str(
 1315         vt_id, vt_params
 1316     ):  # pylint: disable=unused-argument
 1317         """ Create a string representation of the XML object from the
 1318         vt_params data object.
 1319         This needs to be implemented by each ospd wrapper, in case
 1320         vt_params elements for VTs are used.
 1321 
 1322         The params XML object which is returned will be embedded
 1323         into a <params></params> element.
 1324 
 1325         @return: XML object as string for vt parameters data.
 1326         """
 1327         return ''
 1328 
 1329     @staticmethod
 1330     def get_refs_vt_as_xml_str(
 1331         vt_id, vt_refs
 1332     ):  # pylint: disable=unused-argument
 1333         """ Create a string representation of the XML object from the
 1334         refs data object.
 1335         This needs to be implemented by each ospd wrapper, in case
 1336         refs elements for VTs are used.
 1337 
 1338         The refs XML object which is returned will be embedded
 1339         into a <refs></refs> element.
 1340 
 1341         @return: XML object as string for vt references data.
 1342         """
 1343         return ''
 1344 
 1345     @staticmethod
 1346     def get_dependencies_vt_as_xml_str(
 1347         vt_id, vt_dependencies
 1348     ):  # pylint: disable=unused-argument
 1349         """ Create a string representation of the XML object from the
 1350         vt_dependencies data object.
 1351         This needs to be implemented by each ospd wrapper, in case
 1352         vt_dependencies elements for VTs are used.
 1353 
 1354         The vt_dependencies XML object which is returned will be embedded
 1355         into a <dependencies></dependencies> element.
 1356 
 1357         @return: XML object as string for vt dependencies data.
 1358         """
 1359         return ''
 1360 
 1361     @staticmethod
 1362     def get_creation_time_vt_as_xml_str(
 1363         vt_id, vt_creation_time
 1364     ):  # pylint: disable=unused-argument
 1365         """ Create a string representation of the XML object from the
 1366         vt_creation_time data object.
 1367         This needs to be implemented by each ospd wrapper, in case
 1368         vt_creation_time elements for VTs are used.
 1369 
 1370         The vt_creation_time XML object which is returned will be embedded
 1371         into a <vt_creation_time></vt_creation_time> element.
 1372 
 1373         @return: XML object as string for vt creation time data.
 1374         """
 1375         return ''
 1376 
 1377     @staticmethod
 1378     def get_modification_time_vt_as_xml_str(
 1379         vt_id, vt_modification_time
 1380     ):  # pylint: disable=unused-argument
 1381         """ Create a string representation of the XML object from the
 1382         vt_modification_time data object.
 1383         This needs to be implemented by each ospd wrapper, in case
 1384         vt_modification_time elements for VTs are used.
 1385 
 1386         The vt_modification_time XML object which is returned will be embedded
 1387         into a <vt_modification_time></vt_modification_time> element.
 1388 
 1389         @return: XML object as string for vt references data.
 1390         """
 1391         return ''
 1392 
 1393     @staticmethod
 1394     def get_summary_vt_as_xml_str(
 1395         vt_id, summary
 1396     ):  # pylint: disable=unused-argument
 1397         """ Create a string representation of the XML object from the
 1398         summary data object.
 1399         This needs to be implemented by each ospd wrapper, in case
 1400         summary elements for VTs are used.
 1401 
 1402         The summary XML object which is returned will be embedded
 1403         into a <summary></summary> element.
 1404 
 1405         @return: XML object as string for summary data.
 1406         """
 1407         return ''
 1408 
 1409     @staticmethod
 1410     def get_impact_vt_as_xml_str(
 1411         vt_id, impact
 1412     ):  # pylint: disable=unused-argument
 1413         """ Create a string representation of the XML object from the
 1414         impact data object.
 1415         This needs to be implemented by each ospd wrapper, in case
 1416         impact elements for VTs are used.
 1417 
 1418         The impact XML object which is returned will be embedded
 1419         into a <impact></impact> element.
 1420 
 1421         @return: XML object as string for impact data.
 1422         """
 1423         return ''
 1424 
 1425     @staticmethod
 1426     def get_affected_vt_as_xml_str(
 1427         vt_id, affected
 1428     ):  # pylint: disable=unused-argument
 1429         """ Create a string representation of the XML object from the
 1430         affected data object.
 1431         This needs to be implemented by each ospd wrapper, in case
 1432         affected elements for VTs are used.
 1433 
 1434         The affected XML object which is returned will be embedded
 1435         into a <affected></affected> element.
 1436 
 1437         @return: XML object as string for affected data.
 1438         """
 1439         return ''
 1440 
 1441     @staticmethod
 1442     def get_insight_vt_as_xml_str(
 1443         vt_id, insight
 1444     ):  # pylint: disable=unused-argument
 1445         """ Create a string representation of the XML object from the
 1446         insight data object.
 1447         This needs to be implemented by each ospd wrapper, in case
 1448         insight elements for VTs are used.
 1449 
 1450         The insight XML object which is returned will be embedded
 1451         into a <insight></insight> element.
 1452 
 1453         @return: XML object as string for insight data.
 1454         """
 1455         return ''
 1456 
 1457     @staticmethod
 1458     def get_solution_vt_as_xml_str(
 1459         vt_id, solution, solution_type=None
 1460     ):  # pylint: disable=unused-argument
 1461         """ Create a string representation of the XML object from the
 1462         solution data object.
 1463         This needs to be implemented by each ospd wrapper, in case
 1464         solution elements for VTs are used.
 1465 
 1466         The solution XML object which is returned will be embedded
 1467         into a <solution></solution> element.
 1468 
 1469         @return: XML object as string for solution data.
 1470         """
 1471         return ''
 1472 
 1473     @staticmethod
 1474     def get_detection_vt_as_xml_str(
 1475         vt_id, detection=None, qod_type=None, qod=None
 1476     ):  # pylint: disable=unused-argument
 1477         """ Create a string representation of the XML object from the
 1478         detection data object.
 1479         This needs to be implemented by each ospd wrapper, in case
 1480         detection elements for VTs are used.
 1481 
 1482         The detection XML object which is returned is an element with
 1483         tag <detection></detection> element
 1484 
 1485         @return: XML object as string for detection data.
 1486         """
 1487         return ''
 1488 
 1489     @staticmethod
 1490     def get_severities_vt_as_xml_str(
 1491         vt_id, severities
 1492     ):  # pylint: disable=unused-argument
 1493         """ Create a string representation of the XML object from the
 1494         severities data object.
 1495         This needs to be implemented by each ospd wrapper, in case
 1496         severities elements for VTs are used.
 1497 
 1498         The severities XML objects which are returned will be embedded
 1499         into a <severities></severities> element.
 1500 
 1501         @return: XML object as string for severities data.
 1502         """
 1503         return ''
 1504 
 1505     def get_vt_iterator(self):
 1506         for vt_id, val in self.vts.items():
 1507             yield (vt_id, val)
 1508 
 1509     def get_vt_xml(self, single_vt):
 1510         """ Gets a single vulnerability test information in XML format.
 1511 
 1512         @return: String of single vulnerability test information in XML format.
 1513         """
 1514         if not single_vt:
 1515             return Element('vt')
 1516 
 1517         vt_id, vt = single_vt
 1518         name = vt.get('name')
 1519         vt_xml = Element('vt')
 1520         vt_xml.set('id', vt_id)
 1521 
 1522         for name, value in [('name', name)]:
 1523             elem = SubElement(vt_xml, name)
 1524             elem.text = str(value)
 1525 
 1526         if vt.get('vt_params'):
 1527             params_xml_str = self.get_params_vt_as_xml_str(
 1528                 vt_id, vt.get('vt_params')
 1529             )
 1530             vt_xml.append(secET.fromstring(params_xml_str))
 1531 
 1532         if vt.get('vt_refs'):
 1533             refs_xml_str = self.get_refs_vt_as_xml_str(vt_id, vt.get('vt_refs'))
 1534             vt_xml.append(secET.fromstring(refs_xml_str))
 1535 
 1536         if vt.get('vt_dependencies'):
 1537             dependencies = self.get_dependencies_vt_as_xml_str(
 1538                 vt_id, vt.get('vt_dependencies')
 1539             )
 1540             vt_xml.append(secET.fromstring(dependencies))
 1541 
 1542         if vt.get('creation_time'):
 1543             vt_ctime = self.get_creation_time_vt_as_xml_str(
 1544                 vt_id, vt.get('creation_time')
 1545             )
 1546             vt_xml.append(secET.fromstring(vt_ctime))
 1547 
 1548         if vt.get('modification_time'):
 1549             vt_mtime = self.get_modification_time_vt_as_xml_str(
 1550                 vt_id, vt.get('modification_time')
 1551             )
 1552             vt_xml.append(secET.fromstring(vt_mtime))
 1553 
 1554         if vt.get('summary'):
 1555             summary_xml_str = self.get_summary_vt_as_xml_str(
 1556                 vt_id, vt.get('summary')
 1557             )
 1558             vt_xml.append(secET.fromstring(summary_xml_str))
 1559 
 1560         if vt.get('impact'):
 1561             impact_xml_str = self.get_impact_vt_as_xml_str(
 1562                 vt_id, vt.get('impact')
 1563             )
 1564             vt_xml.append(secET.fromstring(impact_xml_str))
 1565 
 1566         if vt.get('affected'):
 1567             affected_xml_str = self.get_affected_vt_as_xml_str(
 1568                 vt_id, vt.get('affected')
 1569             )
 1570             vt_xml.append(secET.fromstring(affected_xml_str))
 1571 
 1572         if vt.get('insight'):
 1573             insight_xml_str = self.get_insight_vt_as_xml_str(
 1574                 vt_id, vt.get('insight')
 1575             )
 1576             vt_xml.append(secET.fromstring(insight_xml_str))
 1577 
 1578         if vt.get('solution'):
 1579             solution_xml_str = self.get_solution_vt_as_xml_str(
 1580                 vt_id, vt.get('solution'), vt.get('solution_type')
 1581             )
 1582             vt_xml.append(secET.fromstring(solution_xml_str))
 1583 
 1584         if vt.get('detection') or vt.get('qod_type') or vt.get('qod'):
 1585             detection_xml_str = self.get_detection_vt_as_xml_str(
 1586                 vt_id, vt.get('detection'), vt.get('qod_type'), vt.get('qod')
 1587             )
 1588             vt_xml.append(secET.fromstring(detection_xml_str))
 1589 
 1590         if vt.get('severities'):
 1591             severities_xml_str = self.get_severities_vt_as_xml_str(
 1592                 vt_id, vt.get('severities')
 1593             )
 1594             vt_xml.append(secET.fromstring(severities_xml_str))
 1595 
 1596         if vt.get('custom'):
 1597             custom_xml_str = self.get_custom_vt_as_xml_str(
 1598                 vt_id, vt.get('custom')
 1599             )
 1600             vt_xml.append(secET.fromstring(custom_xml_str))
 1601 
 1602         return vt_xml
 1603 
 1604     def handle_get_scanner_details(self):
 1605         """ Handles <get_scanner_details> command.
 1606 
 1607         @return: Response string for <get_scanner_details> command.
 1608         """
 1609         desc_xml = Element('description')
 1610         desc_xml.text = self.get_scanner_description()
 1611         details = [desc_xml, self.get_scanner_params_xml()]
 1612         return simple_response_str('get_scanner_details', 200, 'OK', details)
 1613 
 1614     def handle_get_version_command(self):
 1615         """ Handles <get_version> command.
 1616 
 1617         @return: Response string for <get_version> command.
 1618         """
 1619         protocol = Element('protocol')
 1620         for name, value in [
 1621             ('name', 'OSP'),
 1622             ('version', self.get_protocol_version()),
 1623         ]:
 1624             elem = SubElement(protocol, name)
 1625             elem.text = value
 1626 
 1627         daemon = Element('daemon')
 1628         for name, value in [
 1629             ('name', self.get_daemon_name()),
 1630             ('version', self.get_daemon_version()),
 1631         ]:
 1632             elem = SubElement(daemon, name)
 1633             elem.text = value
 1634 
 1635         scanner = Element('scanner')
 1636         for name, value in [
 1637             ('name', self.get_scanner_name()),
 1638             ('version', self.get_scanner_version()),
 1639         ]:
 1640             elem = SubElement(scanner, name)
 1641             elem.text = value
 1642 
 1643         content = [protocol, daemon, scanner]
 1644 
 1645         if self.get_vts_version():
 1646             vts = Element('vts')
 1647             elem = SubElement(vts, 'version')
 1648             elem.text = self.get_vts_version()
 1649             content.append(vts)
 1650 
 1651         return simple_response_str('get_version', 200, 'OK', content)
 1652 
 1653     def handle_command(self, command, stream):
 1654         """ Handles an osp command in a string.
 1655 
 1656         @return: OSP Response to command.
 1657         """
 1658         try:
 1659             tree = secET.fromstring(command)
 1660         except secET.ParseError:
 1661             logger.debug("Erroneous client input: %s", command)
 1662             raise OspdCommandError('Invalid data')
 1663 
 1664         if not self.command_exists(tree.tag) and tree.tag != "authenticate":
 1665             raise OspdCommandError('Bogus command name')
 1666 
 1667         if tree.tag == "get_version":
 1668             stream.write(self.handle_get_version_command())
 1669         elif tree.tag == "start_scan":
 1670             stream.write(self.handle_start_scan_command(tree))
 1671         elif tree.tag == "stop_scan":
 1672             stream.write(self.handle_stop_scan_command(tree))
 1673         elif tree.tag == "get_scans":
 1674             stream.write(self.handle_get_scans_command(tree))
 1675         elif tree.tag == "get_vts":
 1676             response = self.handle_get_vts_command(tree)
 1677             for data in response:
 1678                 stream.write(data)
 1679             return
 1680         elif tree.tag == "delete_scan":
 1681             stream.write(self.handle_delete_scan_command(tree))
 1682         elif tree.tag == "help":
 1683             stream.write(self.handle_help_command(tree))
 1684         elif tree.tag == "get_scanner_details":
 1685             stream.write(self.handle_get_scanner_details())
 1686         elif tree.tag == "get_performance":
 1687             stream.write(self.handle_get_performance(tree))
 1688         else:
 1689             assert False, "Unhandled command: {0}".format(tree.tag)
 1690 
 1691     def check(self):
 1692         """ Asserts to False. Should be implemented by subclass. """
 1693         raise NotImplementedError
 1694 
 1695     def run(self, server: BaseServer):
 1696         """ Starts the Daemon, handling commands until interrupted.
 1697         """
 1698 
 1699         server.start(self.handle_client_stream)
 1700 
 1701         try:
 1702             while True:
 1703                 time.sleep(10)
 1704                 self.scheduler()
 1705                 self.clean_forgotten_scans()
 1706         except KeyboardInterrupt:
 1707             logger.info("Received Ctrl-C shutting-down ...")
 1708         finally:
 1709             logger.info("Shutting-down server ...")
 1710             server.close()
 1711 
 1712     def scheduler(self):
 1713         """ Should be implemented by subclass in case of need
 1714         to run tasks periodically. """
 1715 
 1716     def create_scan(self, scan_id, targets, options, vts):
 1717         """ Creates a new scan.
 1718 
 1719         @target: Target to scan.
 1720         @options: Miscellaneous scan options.
 1721 
 1722         @return: New scan's ID. None if the scan_id already exists and the
 1723                  scan status is RUNNING or FINISHED.
 1724         """
 1725         status = None
 1726         scan_exists = self.scan_exists(scan_id)
 1727         if scan_id and scan_exists:
 1728             status = self.get_scan_status(scan_id)
 1729 
 1730         if scan_exists and status == ScanStatus.STOPPED:
 1731             logger.info("Scan %s exists. Resuming scan.", scan_id)
 1732         elif scan_exists and (
 1733             status == ScanStatus.RUNNING or status == ScanStatus.FINISHED
 1734         ):
 1735             logger.info(
 1736                 "Scan %s exists with status %s.", scan_id, status.name.lower()
 1737             )
 1738             return
 1739         return self.scan_collection.create_scan(scan_id, targets, options, vts)
 1740 
 1741     def get_scan_options(self, scan_id):
 1742         """ Gives a scan's list of options. """
 1743         return self.scan_collection.get_options(scan_id)
 1744 
 1745     def set_scan_option(self, scan_id, name, value):
 1746         """ Sets a scan's option to a provided value. """
 1747         return self.scan_collection.set_option(scan_id, name, value)
 1748 
 1749     def clean_forgotten_scans(self):
 1750         """ Check for old stopped or finished scans which have not been
 1751         deleted and delete them if the are older than the set value."""
 1752 
 1753         if not self.scaninfo_store_time:
 1754             return
 1755 
 1756         for scan_id in list(self.scan_collection.ids_iterator()):
 1757             end_time = int(self.get_scan_end_time(scan_id))
 1758             scan_status = self.get_scan_status(scan_id)
 1759 
 1760             if (
 1761                 scan_status == ScanStatus.STOPPED
 1762                 or scan_status == ScanStatus.FINISHED
 1763             ) and end_time:
 1764                 stored_time = int(time.time()) - end_time
 1765                 if stored_time > self.scaninfo_store_time * 3600:
 1766                     logger.debug(
 1767                         'Scan %s is older than %d hours and seems have been '
 1768                         'forgotten. Scan info will be deleted from the '
 1769                         'scan table',
 1770                         scan_id,
 1771                         self.scaninfo_store_time,
 1772                     )
 1773                     self.delete_scan(scan_id)
 1774 
 1775     def check_scan_process(self, scan_id):
 1776         """ Check the scan's process, and terminate the scan if not alive. """
 1777         scan_process = self.scan_processes[scan_id]
 1778         progress = self.get_scan_progress(scan_id)
 1779         if progress < 100 and not scan_process.is_alive():
 1780             if not (self.get_scan_status(scan_id) == ScanStatus.STOPPED):
 1781                 self.set_scan_status(scan_id, ScanStatus.STOPPED)
 1782                 self.add_scan_error(
 1783                     scan_id, name="", host="", value="Scan process failure."
 1784                 )
 1785                 logger.info("%s: Scan stopped with errors.", scan_id)
 1786         elif progress == 100:
 1787             scan_process.join()
 1788 
 1789     def get_scan_progress(self, scan_id):
 1790         """ Gives a scan's current progress value. """
 1791         return self.scan_collection.get_progress(scan_id)
 1792 
 1793     def get_scan_target_progress(self, scan_id, target):
 1794         """ Gives a list with scan's current progress value of each target. """
 1795         return self.scan_collection.get_target_progress(scan_id, target)
 1796 
 1797     def get_scan_target(self, scan_id):
 1798         """ Gives a scan's target. """
 1799         return self.scan_collection.get_target_list(scan_id)
 1800 
 1801     def get_scan_ports(self, scan_id, target=''):
 1802         """ Gives a scan's ports list. """
 1803         return self.scan_collection.get_ports(scan_id, target)
 1804 
 1805     def get_scan_exclude_hosts(self, scan_id, target=''):
 1806         """ Gives a scan's exclude host list. If a target is passed gives
 1807         the exclude host list for the given target. """
 1808         return self.scan_collection.get_exclude_hosts(scan_id, target)
 1809 
 1810     def get_scan_credentials(self, scan_id, target=''):
 1811         """ Gives a scan's credential list. If a target is passed gives
 1812         the credential list for the given target. """
 1813         return self.scan_collection.get_credentials(scan_id, target)
 1814 
 1815     def get_scan_vts(self, scan_id):
 1816         """ Gives a scan's vts list. """
 1817         return self.scan_collection.get_vts(scan_id)
 1818 
 1819     def get_scan_unfinished_hosts(self, scan_id):
 1820         """ Get a list of unfinished hosts."""
 1821         return self.scan_collection.get_hosts_unfinished(scan_id)
 1822 
 1823     def get_scan_finished_hosts(self, scan_id):
 1824         """ Get a list of unfinished hosts."""
 1825         return self.scan_collection.get_hosts_finished(scan_id)
 1826 
 1827     def get_scan_start_time(self, scan_id):
 1828         """ Gives a scan's start time. """
 1829         return self.scan_collection.get_start_time(scan_id)
 1830 
 1831     def get_scan_end_time(self, scan_id):
 1832         """ Gives a scan's end time. """
 1833         return self.scan_collection.get_end_time(scan_id)
 1834 
 1835     def add_scan_log(
 1836         self,
 1837         scan_id,
 1838         host='',
 1839         hostname='',
 1840         name='',
 1841         value='',
 1842         port='',
 1843         test_id='',
 1844         qod='',
 1845     ):
 1846         """ Adds a log result to scan_id scan. """
 1847         self.scan_collection.add_result(
 1848             scan_id,
 1849             ResultType.LOG,
 1850             host,
 1851             hostname,
 1852             name,
 1853             value,
 1854             port,
 1855             test_id,
 1856             0.0,
 1857             qod,
 1858         )
 1859 
 1860     def add_scan_error(
 1861         self,
 1862         scan_id,
 1863         host='',
 1864         hostname='',
 1865         name='',
 1866         value='',
 1867         port='',
 1868         test_id='',
 1869     ):
 1870         """ Adds an error result to scan_id scan. """
 1871         self.scan_collection.add_result(
 1872             scan_id,
 1873             ResultType.ERROR,
 1874             host,
 1875             hostname,
 1876             name,
 1877             value,
 1878             port,
 1879             test_id,
 1880         )
 1881 
 1882     def add_scan_host_detail(
 1883         self, scan_id, host='', hostname='', name='', value=''
 1884     ):
 1885         """ Adds a host detail result to scan_id scan. """
 1886         self.scan_collection.add_result(
 1887             scan_id, ResultType.HOST_DETAIL, host, hostname, name, value
 1888         )
 1889 
 1890     def add_scan_alarm(
 1891         self,
 1892         scan_id,
 1893         host='',
 1894         hostname='',
 1895         name='',
 1896         value='',
 1897         port='',
 1898         test_id='',
 1899         severity='',
 1900         qod='',
 1901     ):
 1902         """ Adds an alarm result to scan_id scan. """
 1903         self.scan_collection.add_result(
 1904             scan_id,
 1905             ResultType.ALARM,
 1906             host,
 1907             hostname,
 1908             name,
 1909             value,
 1910             port,
 1911             test_id,
 1912             severity,
 1913             qod,
 1914         )