3 # ------------------------------------------------------------
4 # opensecurity package file
6 # Autor: Karlberger Christoph <Karlberger.C@ikarus.at>
7 # X-Net Services GmbH <office@x-net.at>
9 # Copyright 2013-2014 X-Net and AIT Austrian Institute of Technology
11 # IKARUS Security Software GmbH
15 # http://www.ikarussecurity.com
17 # X-Net Technologies GmbH
21 # https://www.x-net.at
23 # AIT Austrian Institute of Technology
24 # Donau City Strasse 1
27 # http://www.ait.ac.at
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
34 # http://www.apache.org/licenses/LICENSE-2.0
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 # ------------------------------------------------------------
53 import xml.etree.ElementTree as ET
57 # User the existing logger instance
58 __LOG = logging.getLogger("IkarusScanner")
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
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)
77 def __init__ (self, scanner_config_path):
78 config = self.loadConfig (scanner_config_path)
80 self.__scanserverTimestamp = time.time()
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"))
86 # Convert file size from MB to byte
87 self.__MAX_SCAN_FILE_SIZE = int(config.get("Main", "MaxFileSize")) * 0x100000
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))
97 def loadConfig (self, scanner_config_path):
99 configfile = scanner_config_path
100 config = ConfigParser.SafeConfigParser ()
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)
107 config.read (scanner_config_path)
109 self.__LOG.error("Error: %s" % (e));
110 raise SystemError("Error: %s" % (e))
112 self.checkMinimumOptions (config)
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)
120 def scanFile (self, path, fileobject):
121 return self.scanFileIkarus (path, fileobject)
123 def scanFileIkarus (self, path, fileobject):
124 retval = { "infected" : False, "virusname" : "Unknown" }
125 self.__LOG.debug ("Scan File: %s" % (path))
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."
133 fields = { 'up_file' : fileobject.read() }
135 if (self.__remoteScanserverReachable == False) and ((self.__scanserverTimestamp + self.__SCANSERVER_RETRY_TIMEOUT) < time.time()):
136 self.__remoteScanserverReachable = True
138 if self.__remoteScanserverReachable:
140 response = self.contactScanserver(self.__REMOTE_SCANSERVER_URL, fields)
141 # We should catch socket.error here, but this does not work. Needs checking.
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))
147 self.__remoteScanserverReachable = False
148 self.__scanserverTimestamp = time.time()
151 response = self.contactScanserver(self.__LOCAL_SCANSERVER_URL, fields)
153 self.__LOG.error ("Connection to local scan server could not be established.")
154 self.__LOG.debug ("Exception: %s" % (sys.exc_info()[0]))
158 response = self.contactScanserver(self.__LOCAL_SCANSERVER_URL, fields)
160 self.__LOG.error ("Connection to local scan server could not be established.")
161 self.__LOG.error ("Exception: %s" %(sys.exc_info()[0]))
165 if response.status == self.__STATUS_CODE_OK:
166 retval["infected"] = False
167 elif response.status == self.__STATUS_CODE_INFECTED:
169 root = ET.fromstring(response.data)
171 # this should be done in a more generic way
172 retval["virusname"] = root[1][3][0].text
173 retval["infected"] = True
175 self.__LOG.error ("Connection error to scan server.")
177 if (retval["infected"] == True):
178 self.__LOG.error ("Virus found, denying access.")
180 self.__LOG.debug ("No virus found.")