src/IkarusScanner.py
author ck
Mon, 24 Feb 2014 16:47:02 +0100
changeset 1 57ad4aea86dd
parent 0 ca2023eb2463
child 2 0c88ae943fa6
permissions -rwxr-xr-x
Added virus name to the return value of the scan function.
Fixed errors.
     1 #!/usr/bin/python
     2 
     3 import ConfigParser
     4 
     5 import sys
     6 
     7 import logging
     8 import os
     9 import errno
    10 import time
    11 
    12 import urllib3
    13 import xml.etree.ElementTree as ET
    14 
    15 class IkarusScanner:
    16     
    17     # User the existing logger  instance
    18     __LOG = logging.getLogger("IkarusScanner")
    19     
    20     __MINOPTS = { "Main" : ["LocalScanserverURL", "RemoteScanserverURL", "MaxFileSize", "RetryTimeout"]}
    21     __CONFIG_NOT_READABLE = "Configfile is not readable"
    22     __CONFIG_WRONG = "Something is wrong with the config"
    23     __CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
    24     __LOCAL_SCANSERVER_URL = ""
    25     __REMOTE_SCANSERVER_URL = ""
    26     __STATUS_CODE_OK = 200
    27     __STATUS_CODE_INFECTED = 210
    28     __STATUS_CODE_NOT_FOUND = 404
    29     __MAX_SCAN_FILE_SIZE = 50 * 0x100000
    30     __SCANSERVER_RETRY_TIMEOUT = 60
    31     
    32     # Global http pool manager used to connect to the scan server
    33     __remoteScanserverReachable = True
    34     __scanserverTimestamp = 0
    35     __httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3)
    36     
    37     def __init__ (self, scanner_config_path):
    38         config = self.loadConfig (scanner_config_path)
    39     
    40         self.__scanserverTimestamp = time.time()
    41     
    42         self.__LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL")
    43         self.__REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL")
    44         self.__SCANSERVER_RETRY_TIMEOUT = int(config.get("Main", "RetryTimeout"))
    45     
    46         # Convert file size from MB to byte
    47         self.__MAX_SCAN_FILE_SIZE = int(config.get("Main", "MaxFileSize")) * 0x100000
    48     
    49 
    50     def checkMinimumOptions (self, config):
    51         for section, options in self.__MINOPTS.iteritems ():
    52             for option in options:
    53                 if (config.has_option(section, option) == False):
    54                     self.__LOG.error (self.__CONFIG_MISSING % (section, option))
    55                     exit (129)
    56 
    57     def loadConfig (self, scanner_config_path):
    58 
    59         configfile = scanner_config_path
    60         config = ConfigParser.SafeConfigParser ()
    61     
    62         if ((os.path.exists (scanner_config_path) == False) or (os.path.isfile (scanner_config_path) == False) or (os.access (scanner_config_path, os.R_OK) == False)):
    63             self.__LOG.error(self.__CONFIG_NOT_READABLE);
    64             raise SystemError(self.__CONFIG_NOT_READABLE)
    65     
    66         try:
    67             config.read (scanner_config_path)
    68         except Exception, e:
    69             self.__LOG.error("Error: %s" % (e));
    70             raise SystemError("Error: %s" % (e))
    71 
    72         self.checkMinimumOptions (config)
    73     
    74         return config
    75 
    76     def contactScanserver(self, url, fields):
    77         self.__LOG.debug("Contacting server %s" % url)
    78         return self.__httpPool.request_encode_body('POST', url, fields = fields, retries = 0)
    79     
    80     def scanFile (self, path, fileobject):
    81         return self.scanFileIkarus (path, fileobject)
    82 
    83     def scanFileIkarus (self, path, fileobject):
    84         retval = { "infected" : False, "virusname" : "Unknown" }
    85         self.__LOG.debug ("Scan File: %s" % (path))
    86     
    87         if (os.fstat(fileobject.fileno()).st_size > self.__MAX_SCAN_FILE_SIZE):
    88             self.__LOG.info("File max size exceeded. The file is not scanned.")
    89             retval["infected"] = False
    90             retval["virusname"] = "File is to big to be scanned."
    91             return retval
    92     
    93         fields = { 'up_file' : fileobject.read() }
    94     
    95         if (self.__remoteScanserverReachable == False) and ((self.__scanserverTimestamp + self.__SCANSERVER_RETRY_TIMEOUT) < time.time()):
    96             self.__remoteScanserverReachable = True
    97     
    98         if self.__remoteScanserverReachable:
    99             try:
   100                 response = self.contactScanserver(self.__REMOTE_SCANSERVER_URL, fields)
   101                 # We should catch socket.error here, but this does not work. Needs checking.
   102             except:
   103                 self.__LOG.info("Remote scan server unreachable, using local scan server.")
   104                 self.__LOG.debug("Exception: %s: %s" % (sys.exc_info()[0], sys.exc_info()[1]))
   105                 self.__LOG.info("Next check for remote server in %s seconds." % (self.__SCANSERVER_RETRY_TIMEOUT))
   106                 
   107                 self.__remoteScanserverReachable = False
   108                 self.__scanserverTimestamp = time.time()
   109     
   110                 try:
   111                     response = self.contactScanserver(self.__LOCAL_SCANSERVER_URL, fields)
   112                 except:
   113                     self.__LOG.error ("Connection to local scan server could not be established.")
   114                     self.__LOG.debug ("Exception: %s" % (sys.exc_info()[0]))
   115                     return retval
   116         else:
   117             try:
   118                 response = self.contactScanserver(self.__LOCAL_SCANSERVER_URL, fields)
   119             except:
   120                 self.__LOG.error ("Connection to local scan server could not be established.")
   121                 self.__LOG.error ("Exception: %s" %(sys.exc_info()[0]))
   122                 return retval
   123         
   124     
   125         if response.status == self.__STATUS_CODE_OK:
   126             retval["infected"] = False
   127         elif response.status == self.__STATUS_CODE_INFECTED:
   128             # Parse xml for info
   129             root = ET.fromstring(response.data)
   130             
   131             # this should be done in a more generic way
   132             retval["virusname"] = root[1][3][0].text
   133             retval["infected"] = True
   134         else:
   135             self.__LOG.error ("Connection error to scan server.")
   136     
   137         if (retval["infected"] == True):
   138             self.__LOG.error ("Virus found, denying access.")
   139         else:
   140             self.__LOG.debug ("No virus found.")
   141         
   142         return retval
   143 
   144     
   145