Shorter timeout for remote scan server.
Limit number of connection pools to 1.
Remote server is not contacted for a certain time when connection fails.
15 # ToDo replace with ikarus
22 MINOPTS = { "Main" : ["Logfile", "Mountpoint", "Rootpath", "LocalScanserverURL", "RemoteScanserverURL", "ReadOnly"]}
24 CONFIG_NOT_READABLE = "Configfile is not readable"
25 CONFIG_WRONG = "Something is wrong with the config"
26 CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
28 LOCAL_SCANSERVER_URL = ""
29 REMOTE_SCANSERVER_URL = ""
31 STATUS_CODE_INFECTED = 210
32 STATUS_CODE_NOT_FOUND = 404
34 SYSTEM_FILE_COMMAND = "file"
36 MAX_SCAN_FILE_SIZE = 50 * 0x100000
37 SCANSERVER_RETRY_TIMEOUT = 60
39 # Global http pool manager used to connect to the scan server
40 remoteScanserverReachable = True
41 scanserverTimestamp = 0
42 httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3)
44 def checkMinimumOptions (config):
45 for section, options in MINOPTS.iteritems ():
46 for option in options:
47 if (config.has_option(section, option) == False):
48 print (CONFIG_MISSING % (section, option))
53 print ("%s configfile mountpath ro/rw" % (sys.argv[0]))
59 if (len (sys.argv) < 4):
62 configfile = sys.argv[1]
63 config = ConfigParser.SafeConfigParser ()
65 if ((os.path.exists (configfile) == False) or (os.path.isfile (configfile) == False) or (os.access (configfile, os.R_OK) == False)):
66 print (CONFIG_NOT_READABLE)
70 config.read (sys.argv[1])
73 print ("Error: %s" % (e))
76 config.set("Main", "Mountpoint", sys.argv[2])
77 if (sys.argv[3] == "rw"):
78 config.set("Main", "ReadOnly", "false")
80 config.set("Main", "ReadOnly", "true")
82 checkMinimumOptions (config)
90 logfile = config.get("Main", "Logfile")
92 # ToDo move log level and maybe other things to config file
94 level = logging.DEBUG,
95 format = "%(asctime)s %(name)-12s %(funcName)-15s %(levelname)-8s %(message)s",
96 datefmt = "%Y-%m-%d %H:%M:%S",
100 LOG = logging.getLogger("fuse_main")
104 return ".%s" % (path)
106 def rootPath (rootpath, path):
107 return "%s%s" % (rootpath, path)
109 def flag2mode (flags):
110 md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
111 m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
113 if flags | os.O_APPEND:
114 m = m.replace('w', 'a', 1)
118 def contactScanserver(url, fields):
119 return httpPool.request_encode_body('POST', url, fields = fields, retries = 0)
122 def scanFileIkarus (path, fileobject):
123 global remoteScanserverReachable
124 global scanserverTimestamp
127 LOG.debug ("Scan File: %s" % (path))
129 if (os.fstat(fileobject.fileno()).st_size > MAX_SCAN_FILE_SIZE):
130 LOG.info("File max size exceeded. The file is not scanned.")
133 fields = { 'up_file' : fileobject.read() }
135 if (remoteScanserverReachable == False) and ((scanserverTimestamp + SCANSERVER_RETRY_TIMEOUT) < time.time()):
136 remoteScanserverReachable = True
138 if remoteScanserverReachable:
140 response = contactScanserver(REMOTE_SCANSERVER_URL, fields)
141 # We should catch socket.error here, but this does not work. Needs checking.
143 LOG.info("Remote scan server unreachable, using local scan server.")
144 LOG.info("Next check for remote server in %s seconds." % (SCANSERVER_RETRY_TIMEOUT))
146 remoteScanserverReachable = False
147 scanserverTimestamp = time.time()
150 response = contactScanserver(LOCAL_SCANSERVER_URL, fields)
152 LOG.error ("Connection to local scan server could not be established.")
153 LOG.error ("Exception: %s" %(sys.exc_info()[0]))
157 response = contactScanserver(LOCAL_SCANSERVER_URL, fields)
159 LOG.error ("Connection to local scan server could not be established.")
160 LOG.error ("Exception: %s" %(sys.exc_info()[0]))
164 if response.status == STATUS_CODE_OK:
166 elif response.status == STATUS_CODE_INFECTED:
167 # Parse xml for info if desired
168 #contentXML = r.content
169 #root = ET.fromstring(contentXML)
170 #status = root[1][2].text
173 LOG.error ("Connection error to scan server.")
175 if (infected == True):
176 LOG.error ("Virus found, denying access.")
178 LOG.debug ("No virus found.")
182 def scanFileClamAV (path):
185 LOG.debug ("Scan File: %s" % (path))
187 result = pyclamav.scanfile (path)
188 LOG.debug ("Result of file \"%s\": %s" % (path, result))
192 if (infected == True):
193 LOG.error ("Virus found, deny Access %s" % (result,))
197 def whitelistFile (path):
200 LOG.debug ("Execute \"%s\" command on \"%s\"" %(SYSTEM_FILE_COMMAND, path))
204 result = subprocess.check_output ([SYSTEM_FILE_COMMAND, path]);
205 # ToDo replace with real whitelist
207 except Exception as e:
208 LOG.error ("Call returns with an error!")
211 LOG.debug ("Type: %s" %(result))
220 def __init__(self, rootpath, *args, **kw):
221 self.__rootpath = rootpath
222 Fuse.__init__ (self, *args, **kw)
223 LOG.debug ("Init complete.")
225 # defines that our working directory will be the __rootpath
227 os.chdir (self.__rootpath)
229 def getattr(self, path):
230 LOG.debug ("*** getattr (%s)" % (fixPath (path)))
231 return os.lstat (fixPath (path));
233 def getdir(self, path):
234 LOG.debug ("*** getdir (%s)" % (path));
235 return os.listdir (fixPath (path))
237 def readdir(self, path, offset):
238 LOG.debug ("*** readdir (%s %s)" % (path, offset));
239 for e in os.listdir (fixPath (path)):
240 yield fuse.Direntry(e)
242 def chmod (self, path, mode):
243 LOG.debug ("*** chmod %s %s" % (path, oct(mode)))
244 if (config.get("Main", "ReadOnly") == "true"):
246 os.chmod (fixPath (path), mode)
248 def chown (self, path, uid, gid):
249 LOG.debug ("*** chown %s %s %s" % (path, uid, gid))
250 if (config.get("Main", "ReadOnly") == "true"):
252 os.chown (fixPath (path), uid, gid)
254 def link (self, targetPath, linkPath):
255 LOG.debug ("*** link %s %s" % (targetPath, linkPath))
256 if (config.get("Main", "ReadOnly") == "true"):
258 os.link (fixPath (targetPath), fixPath (linkPath))
260 def mkdir (self, path, mode):
261 LOG.debug ("*** mkdir %s %s" % (path, oct(mode)))
262 if (config.get("Main", "ReadOnly") == "true"):
264 os.mkdir (fixPath (path), mode)
266 def mknod (self, path, mode, dev):
267 LOG.debug ("*** mknod %s %s %s" % (path, oct (mode), dev))
268 if (config.get("Main", "ReadOnly") == "true"):
270 os.mknod (fixPath (path), mode, dev)
272 # to implement virus scan
273 def open (self, path, flags):
274 LOG.debug ("*** open %s %s" % (path, oct (flags)))
275 self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode (flags))
276 self.fd = self.file.fileno ()
278 infected = scanFileIkarus (rootPath(self.__rootpath, path), self.file)
279 #infected = scanFileClamAV (rootPath(self.__rootpath, path))
280 if (infected == True):
284 whitelisted = whitelistFile (rootPath(self.__rootpath, path))
285 if (whitelisted == False):
289 def read (self, path, length, offset):
290 LOG.debug ("*** read %s %s %s" % (path, length, offset))
291 self.file.seek (offset)
292 return self.file.read (length)
294 def readlink (self, path):
295 LOG.debug ("*** readlink %s" % (path))
296 return os.readlink (fixPath (path))
298 def release (self, path, flags):
299 LOG.debug ("*** release %s %s" % (path, oct (flags)))
302 def rename (self, oldPath, newPath):
303 LOG.debug ("*** rename %s %s %s" % (oldPath, newPath, config.get("Main", "ReadOnly")))
304 if (config.get("Main", "ReadOnly") == "true"):
306 os.rename (fixPath (oldPath), fixPath (newPath))
308 def rmdir (self, path):
309 LOG.debug ("*** rmdir %s %s" % (path, config.get("Main", "ReadOnly")))
310 if (config.get("Main", "ReadOnly") == "true"):
312 os.rmdir (fixPath (path))
315 LOG.debug ("*** statfs")
316 return os.statvfs(".")
318 def symlink (self, targetPath, linkPath):
319 LOG.debug ("*** symlink %s %s %s" % (targetPath, linkPath, config.get("Main", "ReadOnly")))
320 if (config.get("Main", "ReadOnly") == "true"):
322 os.symlink (fixPath (targetPath), fixPath (linkPath))
324 def truncate (self, path, length):
325 LOG.debug ("*** truncate %s %s %s" % (path, length, config.get("Main", "ReadOnly")))
326 if (config.get("Main", "ReadOnly") == "true"):
328 f = open (fixPath (path), "a")
332 def unlink (self, path):
333 LOG.debug ("*** unlink %s %s" % (path, config.get("Main", "ReadOnly")))
334 if (config.get("Main", "ReadOnly") == "true"):
336 os.unlink (fixPath (path))
338 def utime (self, path, times):
339 LOG.debug ("*** utime %s %s" % (path, times))
340 os.utime (fixPath (path), times)
342 def write (self, path, buf, offset):
343 LOG.debug ("*** write %s %s %s %s" % (path, buf, offset, config.get("Main", "ReadOnly")))
344 if (config.get("Main", "ReadOnly") == "true"):
347 self.file.seek (offset)
348 self.file.write (buf)
351 def access (self, path, mode):
352 LOG.debug ("*** access %s %s" % (path, oct (mode)))
353 if not os.access (fixPath (path), mode):
356 def create (self, path, flags, mode):
357 LOG.debug ("*** create %s %s %s %s %s" % (fixPath (path), oct (flags), oct (mode), flag2mode (flags), config.get("Main", "ReadOnly")))
358 if (config.get("Main", "ReadOnly") == "true"):
360 self.file = os.fdopen (os.open (fixPath (path), flags, mode), flag2mode (flags))
361 self.fd = self.file.fileno ()
364 if __name__ == "__main__":
366 fuse.fuse_python_api = (0, 2)
367 fuse.feature_assert ('stateful_files', 'has_init')
369 config = loadConfig ()
372 scanserverTimestamp = time.time()
374 LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL")
375 REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL")
376 SCANSERVER_RETRY_TIMEOUT = int(config.get("Main", "RetryTimeout"))
378 # Convert file size from MB to byte
379 MAX_SCAN_FILE_SIZE = int(config.get("Main", "MaxFileSize")) * 0x100000
381 osecfs = OsecFS (config.get ("Main", "Rootpath"))
383 osecfs.multithreaded = 0
385 # osecfs.parser.add_option (mountopt=config.get("Main", "Mountpoint"),
387 # default=config.get("Main", "Rootpath"),
388 # help="mirror filesystem from under PATH [default: %default]")
389 # osecfs.parse(values=osecfs, errex=1)
391 fuse_args = [sys.argv[0], config.get ("Main", "Mountpoint")];
392 osecfs.main (fuse_args)