src/IkarusScanner.py
author ft
Tue, 18 Feb 2014 15:38:00 +0100
changeset 0 ca2023eb2463
child 1 57ad4aea86dd
permissions -rwxr-xr-x
Initial commit
working ikarus scanner engine
     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 
    14 class IkarusScanner:
    15     
    16     # User the existing logger  instance
    17     __LOG = logging.getLogger("IkarusScanner")
    18     
    19     __MINOPTS = { "Main" : ["LocalScanserverURL", "RemoteScanserverURL", "MaxFileSize", "RetryTimeout"]}
    20     __CONFIG_NOT_READABLE = "Configfile is not readable"
    21     __CONFIG_WRONG = "Something is wrong with the config"
    22     __CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
    23     __LOCAL_SCANSERVER_URL = ""
    24     __REMOTE_SCANSERVER_URL = ""
    25     __STATUS_CODE_OK = 200
    26     __STATUS_CODE_INFECTED = 210
    27     __STATUS_CODE_NOT_FOUND = 404
    28     __MAX_SCAN_FILE_SIZE = 50 * 0x100000
    29     __SCANSERVER_RETRY_TIMEOUT = 60
    30     
    31     # Global http pool manager used to connect to the scan server
    32     __remoteScanserverReachable = True
    33     __scanserverTimestamp = 0
    34     __httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3)
    35     
    36     def __init__ (self, scanner_config_path):
    37         config = self.loadConfig (scanner_config_path)
    38     
    39         self.__scanserverTimestamp = time.time()
    40     
    41         __LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL")
    42         __REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL")
    43         __SCANSERVER_RETRY_TIMEOUT = int(config.get("Main", "RetryTimeout"))
    44     
    45         # Convert file size from MB to byte
    46         __MAX_SCAN_FILE_SIZE = int(config.get("Main", "MaxFileSize")) * 0x100000
    47     
    48 
    49     def checkMinimumOptions (self, config):
    50         for section, options in self.__MINOPTS.iteritems ():
    51             for option in options:
    52                 if (config.has_option(section, option) == False):
    53                     self.__LOG.error (self.__CONFIG_MISSING % (section, option))
    54                     exit (129)
    55 
    56     def loadConfig (self, scanner_config_path):
    57 
    58         configfile = scanner_config_path
    59         config = ConfigParser.SafeConfigParser ()
    60     
    61         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)):
    62             self.__LOG.error(self.__CONFIG_NOT_READABLE);
    63             raise SystemError(self.__CONFIG_NOT_READABLE)
    64     
    65         try:
    66             config.read (scanner_config_path)
    67         except Exception, e:
    68             self.__LOG.error("Error: %s" % (e));
    69             raise SystemError("Error: %s" % (e))
    70 
    71         self.checkMinimumOptions (config)
    72     
    73         return config
    74 
    75     def contactScanserver(self, url, fields):
    76         return httpPool.request_encode_body('POST', url, fields = fields, retries = 0)
    77     
    78     def scanFile (self, path, fileobject):
    79         return self.scanFileIkarus (path, fileobject)
    80 
    81     def scanFileIkarus (self, path, fileobject):
    82         retval = { "infected" : False, "virusname" : "Unknown" }
    83         self.__LOG.debug ("Scan File: %s" % (path))
    84         
    85         
    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"] = True
    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 = 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.info("Next check for remote server in %s seconds." % (self.__SCANSERVER_RETRY_TIMEOUT))
   105                 
   106                 self.__remoteScanserverReachable = False
   107                 self.__scanserverTimestamp = time.time()
   108     
   109                 try:
   110                     response = contactScanserver(self.__LOCAL_SCANSERVER_URL, fields)
   111                 except:
   112                     self.__LOG.error ("Connection to local scan server could not be established.")
   113                     self.__LOG.error ("Exception: %s" %(sys.exc_info()[0]))
   114                     return retval
   115         else:
   116             try:
   117                 response = contactScanserver(self.__LOCAL_SCANSERVER_URL, fields)
   118             except:
   119                 self.__LOG.error ("Connection to local scan server could not be established.")
   120                 self.__LOG.error ("Exception: %s" %(sys.exc_info()[0]))
   121                 return retval
   122         
   123     
   124         if response.status == self.__STATUS_CODE_OK:
   125             retval["infected"] = False
   126         elif response.status == self.__STATUS_CODE_INFECTED:
   127             # Parse xml for info if desired
   128             #contentXML = r.content
   129             #root = ET.fromstring(contentXML)
   130             #status = root[1][2].text
   131             retval["infected"] = True
   132         else:
   133             self.__LOG.error ("Connection error to scan server.")
   134     
   135         if (retval["infected"] == True):
   136             self.__LOG.error ("Virus found, denying access.")
   137         else:
   138             self.__LOG.debug ("No virus found.")
   139         
   140         return retval
   141 
   142     
   143