src/IkarusScanner.py
author ft
Tue, 04 Nov 2014 14:24:02 +0100
changeset 2 0c88ae943fa6
parent 1 57ad4aea86dd
permissions -rwxr-xr-x
Added licence things
     1 #!/usr/bin/python
     2 
     3 # ------------------------------------------------------------
     4 # opensecurity package file
     5 #
     6 # Autor:  Karlberger Christoph <Karlberger.C@ikarus.at>
     7 #         X-Net Services GmbH <office@x-net.at>
     8 #
     9 # Copyright 2013-2014 X-Net and AIT Austrian Institute of Technology
    10 #
    11 #     IKARUS Security Software GmbH
    12 #     Blechturmgasse 11
    13 #     1050 Wien
    14 #     AUSTRIA
    15 #     http://www.ikarussecurity.com
    16 #
    17 #     X-Net Technologies GmbH
    18 #     Elisabethstrasse 1
    19 #     4020 Linz
    20 #     AUSTRIA
    21 #     https://www.x-net.at
    22 #
    23 #     AIT Austrian Institute of Technology
    24 #     Donau City Strasse 1
    25 #     1220 Wien
    26 #     AUSTRIA
    27 #     http://www.ait.ac.at
    28 #
    29 #
    30 # Licensed under the Apache License, Version 2.0 (the "License");
    31 # you may not use this file except in compliance with the License.
    32 # You may obtain a copy of the License at
    33 #
    34 #    http://www.apache.org/licenses/LICENSE-2.0
    35 #
    36 # Unless required by applicable law or agreed to in writing, software
    37 # distributed under the License is distributed on an "AS IS" BASIS,
    38 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    39 # See the License for the specific language governing permissions and
    40 # limitations under the License.
    41 # ------------------------------------------------------------
    42 
    43 import ConfigParser
    44 
    45 import sys
    46 
    47 import logging
    48 import os
    49 import errno
    50 import time
    51 
    52 import urllib3
    53 import xml.etree.ElementTree as ET
    54 
    55 class IkarusScanner:
    56     
    57     # User the existing logger  instance
    58     __LOG = logging.getLogger("IkarusScanner")
    59     
    60     __MINOPTS = { "Main" : ["LocalScanserverURL", "RemoteScanserverURL", "MaxFileSize", "RetryTimeout"]}
    61     __CONFIG_NOT_READABLE = "Configfile is not readable"
    62     __CONFIG_WRONG = "Something is wrong with the config"
    63     __CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
    64     __LOCAL_SCANSERVER_URL = ""
    65     __REMOTE_SCANSERVER_URL = ""
    66     __STATUS_CODE_OK = 200
    67     __STATUS_CODE_INFECTED = 210
    68     __STATUS_CODE_NOT_FOUND = 404
    69     __MAX_SCAN_FILE_SIZE = 50 * 0x100000
    70     __SCANSERVER_RETRY_TIMEOUT = 60
    71     
    72     # Global http pool manager used to connect to the scan server
    73     __remoteScanserverReachable = True
    74     __scanserverTimestamp = 0
    75     __httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3)
    76     
    77     def __init__ (self, scanner_config_path):
    78         config = self.loadConfig (scanner_config_path)
    79     
    80         self.__scanserverTimestamp = time.time()
    81     
    82         self.__LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL")
    83         self.__REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL")
    84         self.__SCANSERVER_RETRY_TIMEOUT = int(config.get("Main", "RetryTimeout"))
    85     
    86         # Convert file size from MB to byte
    87         self.__MAX_SCAN_FILE_SIZE = int(config.get("Main", "MaxFileSize")) * 0x100000
    88     
    89 
    90     def checkMinimumOptions (self, config):
    91         for section, options in self.__MINOPTS.iteritems ():
    92             for option in options:
    93                 if (config.has_option(section, option) == False):
    94                     self.__LOG.error (self.__CONFIG_MISSING % (section, option))
    95                     exit (129)
    96 
    97     def loadConfig (self, scanner_config_path):
    98 
    99         configfile = scanner_config_path
   100         config = ConfigParser.SafeConfigParser ()
   101     
   102         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)):
   103             self.__LOG.error(self.__CONFIG_NOT_READABLE);
   104             raise SystemError(self.__CONFIG_NOT_READABLE)
   105     
   106         try:
   107             config.read (scanner_config_path)
   108         except Exception, e:
   109             self.__LOG.error("Error: %s" % (e));
   110             raise SystemError("Error: %s" % (e))
   111 
   112         self.checkMinimumOptions (config)
   113     
   114         return config
   115 
   116     def contactScanserver(self, url, fields):
   117         self.__LOG.debug("Contacting server %s" % url)
   118         return self.__httpPool.request_encode_body('POST', url, fields = fields, retries = 0)
   119     
   120     def scanFile (self, path, fileobject):
   121         return self.scanFileIkarus (path, fileobject)
   122 
   123     def scanFileIkarus (self, path, fileobject):
   124         retval = { "infected" : False, "virusname" : "Unknown" }
   125         self.__LOG.debug ("Scan File: %s" % (path))
   126     
   127         if (os.fstat(fileobject.fileno()).st_size > self.__MAX_SCAN_FILE_SIZE):
   128             self.__LOG.info("File max size exceeded. The file is not scanned.")
   129             retval["infected"] = False
   130             retval["virusname"] = "File is to big to be scanned."
   131             return retval
   132     
   133         fields = { 'up_file' : fileobject.read() }
   134     
   135         if (self.__remoteScanserverReachable == False) and ((self.__scanserverTimestamp + self.__SCANSERVER_RETRY_TIMEOUT) < time.time()):
   136             self.__remoteScanserverReachable = True
   137     
   138         if self.__remoteScanserverReachable:
   139             try:
   140                 response = self.contactScanserver(self.__REMOTE_SCANSERVER_URL, fields)
   141                 # We should catch socket.error here, but this does not work. Needs checking.
   142             except:
   143                 self.__LOG.info("Remote scan server unreachable, using local scan server.")
   144                 self.__LOG.debug("Exception: %s: %s" % (sys.exc_info()[0], sys.exc_info()[1]))
   145                 self.__LOG.info("Next check for remote server in %s seconds." % (self.__SCANSERVER_RETRY_TIMEOUT))
   146                 
   147                 self.__remoteScanserverReachable = False
   148                 self.__scanserverTimestamp = time.time()
   149     
   150                 try:
   151                     response = self.contactScanserver(self.__LOCAL_SCANSERVER_URL, fields)
   152                 except:
   153                     self.__LOG.error ("Connection to local scan server could not be established.")
   154                     self.__LOG.debug ("Exception: %s" % (sys.exc_info()[0]))
   155                     return retval
   156         else:
   157             try:
   158                 response = self.contactScanserver(self.__LOCAL_SCANSERVER_URL, fields)
   159             except:
   160                 self.__LOG.error ("Connection to local scan server could not be established.")
   161                 self.__LOG.error ("Exception: %s" %(sys.exc_info()[0]))
   162                 return retval
   163         
   164     
   165         if response.status == self.__STATUS_CODE_OK:
   166             retval["infected"] = False
   167         elif response.status == self.__STATUS_CODE_INFECTED:
   168             # Parse xml for info
   169             root = ET.fromstring(response.data)
   170             
   171             # this should be done in a more generic way
   172             retval["virusname"] = root[1][3][0].text
   173             retval["infected"] = True
   174         else:
   175             self.__LOG.error ("Connection error to scan server.")
   176     
   177         if (retval["infected"] == True):
   178             self.__LOG.error ("Virus found, denying access.")
   179         else:
   180             self.__LOG.debug ("No virus found.")
   181         
   182         return retval
   183 
   184     
   185