15 from importlib import import_module
26 sys.stderr = open('/var/log/osecfs_error.log', 'a+')
29 MINOPTS = { "Main" : ["Logfile", "LogLevel", "Mountpoint", "Rootpath", "ScannerPath", "ScannerModuleName", "ScannerClassName", "ScannerConfig", "ReadOnly"]}
31 CONFIG_NOT_READABLE = "Configfile is not readable"
32 CONFIG_WRONG = "Something is wrong with the config"
33 CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
34 SCAN_WRONG_RETURN_VALUE = "The return Value of the malware scanner is wrong. Has to be an dictionary"
35 SCAN_RETURN_VALUE_KEY_MISSING = "The dictionary has to include key \"infected\" (True, False) and \"virusname\" (String)"
36 VIRUS_FOUND = "Virus found. Access denied"
37 NOTIFICATION_CRITICAL = "critical"
38 NOTIFICATION_INFO = "info"
43 SYSTEM_FILE_COMMAND = "file"
44 httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3)
46 def checkMinimumOptions (config):
47 for section, options in MINOPTS.iteritems ():
48 for option in options:
49 if (config.has_option(section, option) == False):
50 print (CONFIG_MISSING % (section, option))
55 print ("%s configfile mountpath ro/rw" % (sys.argv[0]))
61 if (len (sys.argv) < 4):
64 configfile = sys.argv[1]
65 config = ConfigParser.SafeConfigParser ()
67 if ((os.path.exists (configfile) == False) or (os.path.isfile (configfile) == False) or (os.access (configfile, os.R_OK) == False)):
68 print (CONFIG_NOT_READABLE)
72 config.read (sys.argv[1])
75 print ("Error: %s" % (e))
78 config.set("Main", "Mountpoint", sys.argv[2])
79 if (sys.argv[3] == "rw"):
80 config.set("Main", "ReadOnly", "false")
82 config.set("Main", "ReadOnly", "true")
84 checkMinimumOptions (config)
92 logfile = config.get("Main", "Logfile")
94 numeric_level = getattr(logging, config.get("Main", "LogLevel").upper(), None)
95 if not isinstance(numeric_level, int):
96 raise ValueError('Invalid log level: %s' % loglevel)
98 # ToDo move log level and maybe other things to config file
100 level = numeric_level,
101 format = "%(asctime)s %(name)-12s %(funcName)-15s %(levelname)-8s %(message)s",
102 datefmt = "%Y-%m-%d %H:%M:%S",
106 LOG = logging.getLogger("fuse_main")
110 return ".%s" % (path)
112 def rootPath (rootpath, path):
113 return "%s%s" % (rootpath, path)
115 def flag2mode (flags):
116 md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
117 m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
119 # windows sets append even if it would overwrite the whole file (seek 0)
120 # so ignore append option
121 #if flags | os.O_APPEND:
122 # m = m.replace('w', 'a', 1)
126 def scanFile (path, fileobject):
127 LOG.debug ("Scan File \"%s\" with malware Scanner" %(path,) )
128 return MalwareScanner.scanFile (path, fileobject)
131 def scanFileClamAV (path):
134 LOG.debug ("Scan File: %s" % (path))
136 result = pyclamav.scanfile (path)
137 LOG.debug ("Result of file \"%s\": %s" % (path, result))
141 if (infected == True):
142 LOG.error ("Virus found, deny Access %s" % (result,))
146 def whitelistFile (path):
149 LOG.debug ("Execute \"%s\" command on \"%s\"" %(SYSTEM_FILE_COMMAND, path))
153 result = subprocess.check_output ([SYSTEM_FILE_COMMAND, path]);
154 # ToDo replace with real whitelist
156 except Exception as e:
157 LOG.error ("Call returns with an error!")
160 LOG.debug ("Type: %s" %(result))
164 def sendNotification (type, message):
165 netifaces.ifaddresses("eth0")[2][0]["addr"]
167 # Get first address in network (0 = network ip -> 192.168.0.0)
168 remote_ip = netaddr.IPNetwork("%s/%s" %(netifaces.ifaddresses("eth0")[2][0]["addr"], netifaces.ifaddresses("eth0")[2][0]["netmask"]))[1]
170 url_options = {"type" : type, "message" : message }
172 # BUG in urllib3. Starting / is missing -> workarround use 2 of them -.-
173 url = ("http://%s:8090//notification?%s" %(remote_ip, urllib.urlencode(url_options)))
175 LOG.debug ("Send notification to \"%s\"" %(url, ))
178 #response = httpPool.request_encode_body('GET', url, retries = 0)
179 response = httpPool.request("GET", url, retries = 0)
181 LOG.error("Remote host not reachable")
182 LOG.error ("Exception: %s" %(sys.exc_info()[0]))
185 if response.status == STATUS_CODE_OK:
186 LOG.info("Notification sent successfully")
188 LOG.error("Server returned errorcode: %s" %(response.status,))
190 def sendReadOnlyNotification():
191 sendNotification("critical", "Filesystem is in read only mode. If you want to export files please initialize an encrypted filesystem.")
198 def __init__(self, rootpath, *args, **kw):
199 self.__rootpath = rootpath
200 Fuse.__init__ (self, *args, **kw)
201 LOG.debug ("Init complete.")
202 sendNotification("information", "Filesystem successfully mounted.")
204 # defines that our working directory will be the __rootpath
206 os.chdir (self.__rootpath)
208 def getattr(self, path):
209 LOG.debug ("*** getattr (%s)" % (fixPath (path)))
210 return os.lstat (fixPath (path));
212 def getdir(self, path):
213 LOG.debug ("*** getdir (%s)" % (path));
214 return os.listdir (fixPath (path))
216 def readdir(self, path, offset):
217 LOG.debug ("*** readdir (%s %s)" % (path, offset));
218 for e in os.listdir (fixPath (path)):
219 yield fuse.Direntry(e)
221 def chmod (self, path, mode):
222 LOG.debug ("*** chmod %s %s" % (path, oct(mode)))
223 if (config.get("Main", "ReadOnly") == "true"):
224 sendReadOnlyNotification()
226 os.chmod (fixPath (path), mode)
228 def chown (self, path, uid, gid):
229 LOG.debug ("*** chown %s %s %s" % (path, uid, gid))
230 if (config.get("Main", "ReadOnly") == "true"):
231 sendReadOnlyNotification()
233 os.chown (fixPath (path), uid, gid)
235 def link (self, targetPath, linkPath):
236 LOG.debug ("*** link %s %s" % (targetPath, linkPath))
237 if (config.get("Main", "ReadOnly") == "true"):
238 sendReadOnlyNotification()
240 os.link (fixPath (targetPath), fixPath (linkPath))
242 def mkdir (self, path, mode):
243 LOG.debug ("*** mkdir %s %s" % (path, oct(mode)))
244 if (config.get("Main", "ReadOnly") == "true"):
245 sendReadOnlyNotification()
247 os.mkdir (fixPath (path), mode)
249 def mknod (self, path, mode, dev):
250 LOG.debug ("*** mknod %s %s %s" % (path, oct (mode), dev))
251 if (config.get("Main", "ReadOnly") == "true"):
252 sendReadOnlyNotification()
254 os.mknod (fixPath (path), mode, dev)
256 # to implement virus scan
257 def open (self, path, flags):
258 LOG.debug ("*** open %s %s" % (path, oct (flags)))
259 self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode (flags))
260 self.fd = self.file.fileno ()
262 LOG.debug(self.__rootpath)
265 retval = scanFile (rootPath(self.__rootpath, path), self.file)
267 #if type(retval) is not dict:
268 if (isinstance(retval, dict) == False):
269 LOG.error(SCAN_WRONG_RETURN_VALUE)
273 if ((retval.has_key("infected") == False) or (retval.has_key("virusname") == False)):
274 LOG.error(SCAN_RETURN_VALUE_KEY_MISSING)
279 if (retval.get("infected") == True):
281 sendNotification(NOTIFICATION_CRITICAL, "%s\nFile: %s\nVirus: %s" %(VIRUS_FOUND, path, retval.get("virusname")))
282 LOG.error("%s" %(VIRUS_FOUND,))
283 LOG.error("Virus: %s" %(retval.get("virusname"),))
286 whitelisted = whitelistFile (rootPath(self.__rootpath, path))
287 if (whitelisted == False):
289 sendNotification(NOTIFICATION_CRITICAL, "File not in whitelist. Access denied.")
292 def read (self, path, length, offset):
293 LOG.debug ("*** read %s %s %s" % (path, length, offset))
294 self.file.seek (offset)
295 return self.file.read (length)
297 def readlink (self, path):
298 LOG.debug ("*** readlink %s" % (path))
299 return os.readlink (fixPath (path))
301 def release (self, path, flags):
302 LOG.debug ("*** release %s %s" % (path, oct (flags)))
305 def rename (self, oldPath, newPath):
306 LOG.debug ("*** rename %s %s %s" % (oldPath, newPath, config.get("Main", "ReadOnly")))
307 if (config.get("Main", "ReadOnly") == "true"):
308 sendReadOnlyNotification()
310 os.rename (fixPath (oldPath), fixPath (newPath))
312 def rmdir (self, path):
313 LOG.debug ("*** rmdir %s %s" % (path, config.get("Main", "ReadOnly")))
314 if (config.get("Main", "ReadOnly") == "true"):
315 sendReadOnlyNotification()
317 os.rmdir (fixPath (path))
320 LOG.debug ("*** statfs")
321 return os.statvfs(".")
323 def symlink (self, targetPath, linkPath):
324 LOG.debug ("*** symlink %s %s %s" % (targetPath, linkPath, config.get("Main", "ReadOnly")))
325 if (config.get("Main", "ReadOnly") == "true"):
326 sendReadOnlyNotification()
328 os.symlink (fixPath (targetPath), fixPath (linkPath))
330 def truncate (self, path, length):
331 LOG.debug ("*** truncate %s %s %s" % (path, length, config.get("Main", "ReadOnly")))
332 if (config.get("Main", "ReadOnly") == "true"):
333 sendReadOnlyNotification()
335 f = open (fixPath (path), "w+")
339 def unlink (self, path):
340 LOG.debug ("*** unlink %s %s" % (path, config.get("Main", "ReadOnly")))
341 if (config.get("Main", "ReadOnly") == "true"):
342 sendReadOnlyNotification()
344 os.unlink (fixPath (path))
346 def utime (self, path, times):
347 LOG.debug ("*** utime %s %s" % (path, times))
348 os.utime (fixPath (path), times)
350 def write (self, path, buf, offset):
351 LOG.debug ("*** write %s %s %s %s" % (path, buf, offset, config.get("Main", "ReadOnly")))
352 if (config.get("Main", "ReadOnly") == "true"):
354 sendReadOnlyNotification()
356 self.file.seek (offset)
357 self.file.write (buf)
360 def access (self, path, mode):
361 LOG.debug ("*** access %s %s" % (path, oct (mode)))
362 if not os.access (fixPath (path), mode):
365 def create (self, path, flags, mode):
366 LOG.debug ("*** create %s %s %s %s %s" % (fixPath (path), oct (flags), oct (mode), flag2mode (flags), config.get("Main", "ReadOnly")))
367 if (config.get("Main", "ReadOnly") == "true"):
368 sendReadOnlyNotification()
370 #self.file = os.fdopen (os.open (fixPath (path), flags, mode), flag2mode (flags))
371 # fix strange Windows behaviour
372 self.file = os.fdopen (os.open (fixPath (path), flags, mode), "w+")
373 self.fd = self.file.fileno ()
376 if __name__ == "__main__":
378 fuse.fuse_python_api = (0, 2)
379 fuse.feature_assert ('stateful_files', 'has_init')
381 config = loadConfig ()
384 #sendNotification("Info", "OsecFS started")
386 # Import the Malware Scanner
387 sys.path.append(config.get("Main", "ScannerPath"))
389 MalwareModule = import_module(config.get("Main", "ScannerModuleName"))
390 MalwareClass = getattr(MalwareModule, config.get("Main", "ScannerClassName"))
392 MalwareScanner = MalwareClass (config.get("Main", "ScannerConfig"));
394 osecfs = OsecFS (config.get ("Main", "Rootpath"))
396 osecfs.multithreaded = 0
398 fuse_args = [sys.argv[0], config.get ("Main", "Mountpoint")];
399 osecfs.main (fuse_args)