15 # ToDo replace with ikarus
25 MINOPTS = { "Main" : ["Logfile", "Mountpoint", "Rootpath", "LocalScanserverURL", "RemoteScanserverURL", "ReadOnly"]}
27 CONFIG_NOT_READABLE = "Configfile is not readable"
28 CONFIG_WRONG = "Something is wrong with the config"
29 CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
31 LOCAL_SCANSERVER_URL = ""
32 REMOTE_SCANSERVER_URL = ""
34 STATUS_CODE_INFECTED = 210
35 STATUS_CODE_NOT_FOUND = 404
37 SYSTEM_FILE_COMMAND = "file"
39 MAX_SCAN_FILE_SIZE = 50 * 0x100000
40 SCANSERVER_RETRY_TIMEOUT = 60
42 # Global http pool manager used to connect to the scan server
43 remoteScanserverReachable = True
44 scanserverTimestamp = 0
45 httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3)
47 def checkMinimumOptions (config):
48 for section, options in MINOPTS.iteritems ():
49 for option in options:
50 if (config.has_option(section, option) == False):
51 print (CONFIG_MISSING % (section, option))
56 print ("%s configfile mountpath ro/rw" % (sys.argv[0]))
62 if (len (sys.argv) < 4):
65 configfile = sys.argv[1]
66 config = ConfigParser.SafeConfigParser ()
68 if ((os.path.exists (configfile) == False) or (os.path.isfile (configfile) == False) or (os.access (configfile, os.R_OK) == False)):
69 print (CONFIG_NOT_READABLE)
73 config.read (sys.argv[1])
76 print ("Error: %s" % (e))
79 config.set("Main", "Mountpoint", sys.argv[2])
80 if (sys.argv[3] == "rw"):
81 config.set("Main", "ReadOnly", "false")
83 config.set("Main", "ReadOnly", "true")
85 checkMinimumOptions (config)
93 logfile = config.get("Main", "Logfile")
95 # ToDo move log level and maybe other things to config file
97 level = logging.DEBUG,
98 format = "%(asctime)s %(name)-12s %(funcName)-15s %(levelname)-8s %(message)s",
99 datefmt = "%Y-%m-%d %H:%M:%S",
103 LOG = logging.getLogger("fuse_main")
107 return ".%s" % (path)
109 def rootPath (rootpath, path):
110 return "%s%s" % (rootpath, path)
112 def flag2mode (flags):
113 md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
114 m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
116 if flags | os.O_APPEND:
117 m = m.replace('w', 'a', 1)
121 def contactScanserver(url, fields):
122 return httpPool.request_encode_body('POST', url, fields = fields, retries = 0)
125 def scanFileIkarus (path, fileobject):
126 global remoteScanserverReachable
127 global scanserverTimestamp
130 LOG.debug ("Scan File: %s" % (path))
132 if (os.fstat(fileobject.fileno()).st_size > MAX_SCAN_FILE_SIZE):
133 LOG.info("File max size exceeded. The file is not scanned.")
136 fields = { 'up_file' : fileobject.read() }
138 if (remoteScanserverReachable == False) and ((scanserverTimestamp + SCANSERVER_RETRY_TIMEOUT) < time.time()):
139 remoteScanserverReachable = True
141 if remoteScanserverReachable:
143 response = contactScanserver(REMOTE_SCANSERVER_URL, fields)
144 # We should catch socket.error here, but this does not work. Needs checking.
146 LOG.info("Remote scan server unreachable, using local scan server.")
147 LOG.info("Next check for remote server in %s seconds." % (SCANSERVER_RETRY_TIMEOUT))
149 remoteScanserverReachable = False
150 scanserverTimestamp = time.time()
153 response = contactScanserver(LOCAL_SCANSERVER_URL, fields)
155 LOG.error ("Connection to local scan server could not be established.")
156 LOG.error ("Exception: %s" %(sys.exc_info()[0]))
160 response = contactScanserver(LOCAL_SCANSERVER_URL, fields)
162 LOG.error ("Connection to local scan server could not be established.")
163 LOG.error ("Exception: %s" %(sys.exc_info()[0]))
167 if response.status == STATUS_CODE_OK:
169 elif response.status == STATUS_CODE_INFECTED:
170 # Parse xml for info if desired
171 #contentXML = r.content
172 #root = ET.fromstring(contentXML)
173 #status = root[1][2].text
176 LOG.error ("Connection error to scan server.")
178 if (infected == True):
179 LOG.error ("Virus found, denying access.")
181 LOG.debug ("No virus found.")
185 def scanFileClamAV (path):
188 LOG.debug ("Scan File: %s" % (path))
190 result = pyclamav.scanfile (path)
191 LOG.debug ("Result of file \"%s\": %s" % (path, result))
195 if (infected == True):
196 LOG.error ("Virus found, deny Access %s" % (result,))
200 def whitelistFile (path):
203 LOG.debug ("Execute \"%s\" command on \"%s\"" %(SYSTEM_FILE_COMMAND, path))
207 result = subprocess.check_output ([SYSTEM_FILE_COMMAND, path]);
208 # ToDo replace with real whitelist
210 except Exception as e:
211 LOG.error ("Call returns with an error!")
214 LOG.debug ("Type: %s" %(result))
218 def sendNotification (type, message):
219 netifaces.ifaddresses("eth0")[2][0]["addr"]
221 # Get first address in network (0 = network ip -> 192.168.0.0)
222 remote_ip = netaddr.IPNetwork("%s/%s" %(netifaces.ifaddresses("eth0")[2][0]["addr"], netifaces.ifaddresses("eth0")[2][0]["netmask"]))[1]
224 url_options = {"type" : type, "message" : message }
225 url = ("http://%s/notification?%s" %(remote_ip, urllib.urlencode(url_options)))
227 LOG.debug ("Send notification to \"%s\"" %(url, ))
230 response = httpPool.request_encode_body('GET', url, retries = 0)
232 LOG.error("Remote host not reachable")
233 LOG.error ("Exception: %s" %(sys.exc_info()[0]))
236 if response.status == STATUS_CODE_OK:
237 LOG.info("Notification sent successfully")
239 LOG.error("Server returned errorcode: %s" %(response.status,))
246 def __init__(self, rootpath, *args, **kw):
247 self.__rootpath = rootpath
248 Fuse.__init__ (self, *args, **kw)
249 LOG.debug ("Init complete.")
251 # defines that our working directory will be the __rootpath
253 os.chdir (self.__rootpath)
255 def getattr(self, path):
256 LOG.debug ("*** getattr (%s)" % (fixPath (path)))
257 return os.lstat (fixPath (path));
259 def getdir(self, path):
260 LOG.debug ("*** getdir (%s)" % (path));
261 return os.listdir (fixPath (path))
263 def readdir(self, path, offset):
264 LOG.debug ("*** readdir (%s %s)" % (path, offset));
265 for e in os.listdir (fixPath (path)):
266 yield fuse.Direntry(e)
268 def chmod (self, path, mode):
269 LOG.debug ("*** chmod %s %s" % (path, oct(mode)))
270 if (config.get("Main", "ReadOnly") == "true"):
272 os.chmod (fixPath (path), mode)
274 def chown (self, path, uid, gid):
275 LOG.debug ("*** chown %s %s %s" % (path, uid, gid))
276 if (config.get("Main", "ReadOnly") == "true"):
278 os.chown (fixPath (path), uid, gid)
280 def link (self, targetPath, linkPath):
281 LOG.debug ("*** link %s %s" % (targetPath, linkPath))
282 if (config.get("Main", "ReadOnly") == "true"):
284 os.link (fixPath (targetPath), fixPath (linkPath))
286 def mkdir (self, path, mode):
287 LOG.debug ("*** mkdir %s %s" % (path, oct(mode)))
288 if (config.get("Main", "ReadOnly") == "true"):
290 os.mkdir (fixPath (path), mode)
292 def mknod (self, path, mode, dev):
293 LOG.debug ("*** mknod %s %s %s" % (path, oct (mode), dev))
294 if (config.get("Main", "ReadOnly") == "true"):
296 os.mknod (fixPath (path), mode, dev)
298 # to implement virus scan
299 def open (self, path, flags):
300 LOG.debug ("*** open %s %s" % (path, oct (flags)))
301 self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode (flags))
302 self.fd = self.file.fileno ()
304 infected = scanFileIkarus (rootPath(self.__rootpath, path), self.file)
305 #infected = scanFileClamAV (rootPath(self.__rootpath, path))
306 if (infected == True):
310 whitelisted = whitelistFile (rootPath(self.__rootpath, path))
311 if (whitelisted == False):
315 def read (self, path, length, offset):
316 LOG.debug ("*** read %s %s %s" % (path, length, offset))
317 self.file.seek (offset)
318 return self.file.read (length)
320 def readlink (self, path):
321 LOG.debug ("*** readlink %s" % (path))
322 return os.readlink (fixPath (path))
324 def release (self, path, flags):
325 LOG.debug ("*** release %s %s" % (path, oct (flags)))
328 def rename (self, oldPath, newPath):
329 LOG.debug ("*** rename %s %s %s" % (oldPath, newPath, config.get("Main", "ReadOnly")))
330 if (config.get("Main", "ReadOnly") == "true"):
332 os.rename (fixPath (oldPath), fixPath (newPath))
334 def rmdir (self, path):
335 LOG.debug ("*** rmdir %s %s" % (path, config.get("Main", "ReadOnly")))
336 if (config.get("Main", "ReadOnly") == "true"):
338 os.rmdir (fixPath (path))
341 LOG.debug ("*** statfs")
342 return os.statvfs(".")
344 def symlink (self, targetPath, linkPath):
345 LOG.debug ("*** symlink %s %s %s" % (targetPath, linkPath, config.get("Main", "ReadOnly")))
346 if (config.get("Main", "ReadOnly") == "true"):
348 os.symlink (fixPath (targetPath), fixPath (linkPath))
350 def truncate (self, path, length):
351 LOG.debug ("*** truncate %s %s %s" % (path, length, config.get("Main", "ReadOnly")))
352 if (config.get("Main", "ReadOnly") == "true"):
354 f = open (fixPath (path), "a")
358 def unlink (self, path):
359 LOG.debug ("*** unlink %s %s" % (path, config.get("Main", "ReadOnly")))
360 if (config.get("Main", "ReadOnly") == "true"):
362 os.unlink (fixPath (path))
364 def utime (self, path, times):
365 LOG.debug ("*** utime %s %s" % (path, times))
366 os.utime (fixPath (path), times)
368 def write (self, path, buf, offset):
369 LOG.debug ("*** write %s %s %s %s" % (path, buf, offset, config.get("Main", "ReadOnly")))
370 if (config.get("Main", "ReadOnly") == "true"):
373 self.file.seek (offset)
374 self.file.write (buf)
377 def access (self, path, mode):
378 LOG.debug ("*** access %s %s" % (path, oct (mode)))
379 if not os.access (fixPath (path), mode):
382 def create (self, path, flags, mode):
383 LOG.debug ("*** create %s %s %s %s %s" % (fixPath (path), oct (flags), oct (mode), flag2mode (flags), config.get("Main", "ReadOnly")))
384 if (config.get("Main", "ReadOnly") == "true"):
386 self.file = os.fdopen (os.open (fixPath (path), flags, mode), flag2mode (flags))
387 self.fd = self.file.fileno ()
390 if __name__ == "__main__":
392 fuse.fuse_python_api = (0, 2)
393 fuse.feature_assert ('stateful_files', 'has_init')
395 config = loadConfig ()
398 #sendNotification("Info", "OsecFS started")
400 scanserverTimestamp = time.time()
402 LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL")
403 REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL")
404 SCANSERVER_RETRY_TIMEOUT = int(config.get("Main", "RetryTimeout"))
406 # Convert file size from MB to byte
407 MAX_SCAN_FILE_SIZE = int(config.get("Main", "MaxFileSize")) * 0x100000
409 osecfs = OsecFS (config.get ("Main", "Rootpath"))
411 osecfs.multithreaded = 0
413 # osecfs.parser.add_option (mountopt=config.get("Main", "Mountpoint"),
415 # default=config.get("Main", "Rootpath"),
416 # help="mirror filesystem from under PATH [default: %default]")
417 # osecfs.parse(values=osecfs, errex=1)
419 fuse_args = [sys.argv[0], config.get ("Main", "Mountpoint")];
420 osecfs.main (fuse_args)