3 # ------------------------------------------------------------
4 # opensecurity package file
6 # Autor: X-Net Services GmbH <office@x-net.at>
8 # Copyright 2013-2014 X-Net and AIT Austrian Institute of Technology
11 # X-Net Technologies GmbH
15 # https://www.x-net.at
17 # AIT Austrian Institute of Technology
18 # Donau City Strasse 1
21 # http://www.ait.ac.at
24 # Licensed under the Apache License, Version 2.0 (the "License");
25 # you may not use this file except in compliance with the License.
26 # You may obtain a copy of the License at
28 # http://www.apache.org/licenses/LICENSE-2.0
30 # Unless required by applicable law or agreed to in writing, software
31 # distributed under the License is distributed on an "AS IS" BASIS,
32 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 # See the License for the specific language governing permissions and
34 # limitations under the License.
35 # ------------------------------------------------------------
50 from importlib import import_module
61 sys.stderr = open('/var/log/osecfs_error.log', 'a+')
64 MINOPTS = { "Main" : ["Logfile", "LogLevel", "Mountpoint", "Rootpath", "ScannerPath", "ScannerModuleName", "ScannerClassName", "ScannerConfig", "ReadOnly"]}
66 CONFIG_NOT_READABLE = "Configfile is not readable"
67 CONFIG_WRONG = "Something is wrong with the config"
68 CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
69 SCAN_WRONG_RETURN_VALUE = "The return Value of the malware scanner is wrong. Has to be an dictionary"
70 SCAN_RETURN_VALUE_KEY_MISSING = "The dictionary has to include key \"infected\" (True, False) and \"virusname\" (String)"
71 VIRUS_FOUND = "Virus found. Access denied"
72 NOTIFICATION_CRITICAL = "critical"
73 NOTIFICATION_INFO = "info"
78 SYSTEM_FILE_COMMAND = "file"
79 httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3)
81 def checkMinimumOptions (config):
82 for section, options in MINOPTS.iteritems ():
83 for option in options:
84 if (config.has_option(section, option) == False):
85 print (CONFIG_MISSING % (section, option))
90 print ("%s configfile mountpath ro/rw" % (sys.argv[0]))
96 if (len (sys.argv) < 4):
99 configfile = sys.argv[1]
100 config = ConfigParser.SafeConfigParser ()
102 if ((os.path.exists (configfile) == False) or (os.path.isfile (configfile) == False) or (os.access (configfile, os.R_OK) == False)):
103 print (CONFIG_NOT_READABLE)
107 config.read (sys.argv[1])
110 print ("Error: %s" % (e))
113 config.set("Main", "Mountpoint", sys.argv[2])
114 if (sys.argv[3] == "rw"):
115 config.set("Main", "ReadOnly", "false")
117 config.set("Main", "ReadOnly", "true")
119 checkMinimumOptions (config)
123 def initLog (config):
127 logfile = config.get("Main", "Logfile")
129 numeric_level = getattr(logging, config.get("Main", "LogLevel").upper(), None)
130 if not isinstance(numeric_level, int):
131 raise ValueError('Invalid log level: %s' % loglevel)
133 # ToDo move log level and maybe other things to config file
135 level = numeric_level,
136 format = "%(asctime)s %(name)-12s %(funcName)-15s %(levelname)-8s %(message)s",
137 datefmt = "%Y-%m-%d %H:%M:%S",
141 LOG = logging.getLogger("fuse_main")
145 return ".%s" % (path)
147 def rootPath (rootpath, path):
148 return "%s%s" % (rootpath, path)
150 def flag2mode (flags):
151 md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
152 m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
154 # windows sets append even if it would overwrite the whole file (seek 0)
155 # so ignore append option
156 #if flags | os.O_APPEND:
157 # m = m.replace('w', 'a', 1)
161 def scanFile (path, fileobject):
162 LOG.debug ("Scan File \"%s\" with malware Scanner" %(path,) )
163 return MalwareScanner.scanFile (path, fileobject)
166 def scanFileClamAV (path):
169 LOG.debug ("Scan File: %s" % (path))
171 result = pyclamav.scanfile (path)
172 LOG.debug ("Result of file \"%s\": %s" % (path, result))
176 if (infected == True):
177 LOG.error ("Virus found, deny Access %s" % (result,))
181 def whitelistFile (path):
184 LOG.debug ("Execute \"%s\" command on \"%s\"" %(SYSTEM_FILE_COMMAND, path))
188 result = subprocess.check_output ([SYSTEM_FILE_COMMAND, path]);
189 # ToDo replace with real whitelist
191 except Exception as e:
192 LOG.error ("Call returns with an error!")
195 LOG.debug ("Type: %s" %(result))
199 def sendDataToRest (urlpath, data):
200 netifaces.ifaddresses("eth0")[2][0]["addr"]
202 # Get first address in network (0 = network ip -> 192.168.0.0)
203 remote_ip = netaddr.IPNetwork("%s/%s" %(netifaces.ifaddresses("eth0")[2][0]["addr"], netifaces.ifaddresses("eth0")[2][0]["netmask"]))[1]
205 url = ("http://%s:8090//%s" %(remote_ip, urlpath))
207 LOG.debug ("Send data to \"%s\"" %(url, ))
208 LOG.debug ("Data: %s" %(data, ))
211 response = httpPool.request_encode_body("POST", url, fields=data, retries=0)
213 LOG.error("Remote host not reachable")
214 LOG.error ("Exception: %s" %(e,))
217 if response.status == STATUS_CODE_OK:
218 LOG.info("Data sent successfully to rest server")
221 LOG.error("Server returned errorcode: %s" %(response.status,))
225 def sendNotification (type, message):
226 data = {"msgtype" : type, "text" : message}
228 if (type == "information"):
229 sendDataToRest ("message", data)
231 sendDataToRest ("notification", data)
233 def sendReadOnlyNotification():
234 sendNotification("critical", "Filesystem is in read only mode. If you want to export files please initialize an encrypted filesystem.")
236 def sendLogNotPossibleNotification():
237 sendNotification("critical", "Send log entry to opensecurity rest server failed.")
239 def sendFileLog(filename, filesize, filehash, hashtype):
240 data = {"filename" : filename, "filesize" : "%s" %(filesize,), "filehash" : filehash, "hashtype" : hashtype}
241 retval = sendDataToRest ("log", data)
242 if (retval == False):
243 sendLogNotPossibleNotification()
245 def calcMD5 (path, block_size=256*128, hr=True):
247 with open(path,'rb') as f:
248 for chunk in iter(lambda: f.read(block_size), b''):
251 return md5.hexdigest()
259 def __init__(self, rootpath, *args, **kw):
260 self.__rootpath = rootpath
261 Fuse.__init__ (self, *args, **kw)
262 LOG.debug ("Init complete.")
263 sendNotification("information", "Filesystem successfully mounted.")
265 # defines that our working directory will be the __rootpath
267 os.chdir (self.__rootpath)
269 def getattr(self, path):
270 LOG.debug ("*** getattr (%s)" % (fixPath (path)))
271 return os.lstat (fixPath (path));
273 def getdir(self, path):
274 LOG.debug ("*** getdir (%s)" % (path));
275 return os.listdir (fixPath (path))
277 def readdir(self, path, offset):
278 LOG.debug ("*** readdir (%s %s)" % (path, offset));
279 for e in os.listdir (fixPath (path)):
280 yield fuse.Direntry(e)
282 def chmod (self, path, mode):
283 LOG.debug ("*** chmod %s %s" % (path, oct(mode)))
284 if (config.get("Main", "ReadOnly") == "true"):
285 sendReadOnlyNotification()
287 os.chmod (fixPath (path), mode)
289 def chown (self, path, uid, gid):
290 LOG.debug ("*** chown %s %s %s" % (path, uid, gid))
291 if (config.get("Main", "ReadOnly") == "true"):
292 sendReadOnlyNotification()
294 os.chown (fixPath (path), uid, gid)
296 def link (self, targetPath, linkPath):
297 LOG.debug ("*** link %s %s" % (targetPath, linkPath))
298 if (config.get("Main", "ReadOnly") == "true"):
299 sendReadOnlyNotification()
301 os.link (fixPath (targetPath), fixPath (linkPath))
303 def mkdir (self, path, mode):
304 LOG.debug ("*** mkdir %s %s" % (path, oct(mode)))
305 if (config.get("Main", "ReadOnly") == "true"):
306 sendReadOnlyNotification()
308 os.mkdir (fixPath (path), mode)
310 def mknod (self, path, mode, dev):
311 LOG.debug ("*** mknod %s %s %s" % (path, oct (mode), dev))
312 if (config.get("Main", "ReadOnly") == "true"):
313 sendReadOnlyNotification()
315 os.mknod (fixPath (path), mode, dev)
317 # to implement virus scan
318 def open (self, path, flags):
319 LOG.debug ("*** open %s %s" % (path, oct (flags)))
320 self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode (flags))
322 self.fd = self.file.fileno ()
324 LOG.debug(self.__rootpath)
327 retval = scanFile (rootPath(self.__rootpath, path), self.file)
329 #if type(retval) is not dict:
330 if (isinstance(retval, dict) == False):
331 LOG.error(SCAN_WRONG_RETURN_VALUE)
335 if ((retval.has_key("infected") == False) or (retval.has_key("virusname") == False)):
336 LOG.error(SCAN_RETURN_VALUE_KEY_MISSING)
341 if (retval.get("infected") == True):
343 sendNotification(NOTIFICATION_CRITICAL, "%s\nFile: %s\nVirus: %s" %(VIRUS_FOUND, path, retval.get("virusname")))
344 LOG.error("%s" %(VIRUS_FOUND,))
345 LOG.error("Virus: %s" %(retval.get("virusname"),))
348 whitelisted = whitelistFile (rootPath(self.__rootpath, path))
349 if (whitelisted == False):
351 sendNotification(NOTIFICATION_CRITICAL, "File not in whitelist. Access denied.")
354 def read (self, path, length, offset):
355 LOG.debug ("*** read %s %s %s" % (path, length, offset))
356 self.file.seek (offset)
357 return self.file.read (length)
359 def readlink (self, path):
360 LOG.debug ("*** readlink %s" % (path))
361 return os.readlink (fixPath (path))
363 def release (self, path, flags):
364 LOG.debug ("*** release %s %s" % (path, oct (flags)))
366 os.fsync(self.file.fileno())
369 if (self.written == True):
370 hashsum = calcMD5(fixPath(path))
371 filesize = os.path.getsize(fixPath(path))
372 sendFileLog(path, filesize, hashsum, "md5")
375 def rename (self, oldPath, newPath):
376 LOG.debug ("*** rename %s %s %s" % (oldPath, newPath, config.get("Main", "ReadOnly")))
377 if (config.get("Main", "ReadOnly") == "true"):
378 sendReadOnlyNotification()
380 os.rename (fixPath (oldPath), fixPath (newPath))
382 def rmdir (self, path):
383 LOG.debug ("*** rmdir %s %s" % (path, config.get("Main", "ReadOnly")))
384 if (config.get("Main", "ReadOnly") == "true"):
385 sendReadOnlyNotification()
387 os.rmdir (fixPath (path))
390 LOG.debug ("*** statfs")
391 return os.statvfs(".")
393 def symlink (self, targetPath, linkPath):
394 LOG.debug ("*** symlink %s %s %s" % (targetPath, linkPath, config.get("Main", "ReadOnly")))
395 if (config.get("Main", "ReadOnly") == "true"):
396 sendReadOnlyNotification()
398 os.symlink (fixPath (targetPath), fixPath (linkPath))
400 def truncate (self, path, length):
401 LOG.debug ("*** truncate %s %s %s" % (path, length, config.get("Main", "ReadOnly")))
402 if (config.get("Main", "ReadOnly") == "true"):
403 sendReadOnlyNotification()
405 f = open (fixPath (path), "w+")
409 def unlink (self, path):
410 LOG.debug ("*** unlink %s %s" % (path, config.get("Main", "ReadOnly")))
411 if (config.get("Main", "ReadOnly") == "true"):
412 sendReadOnlyNotification()
414 os.unlink (fixPath (path))
416 def utime (self, path, times):
417 LOG.debug ("*** utime %s %s" % (path, times))
418 os.utime (fixPath (path), times)
420 def write (self, path, buf, offset):
421 #LOG.debug ("*** write %s %s %s %s" % (path, buf, offset, config.get("Main", "ReadOnly")))
422 LOG.debug ("*** write %s %s %s %s" % (path, "filecontent", offset, config.get("Main", "ReadOnly")))
423 if (config.get("Main", "ReadOnly") == "true"):
425 sendReadOnlyNotification()
427 self.file.seek (offset)
428 self.file.write (buf)
432 def access (self, path, mode):
433 LOG.debug ("*** access %s %s" % (path, oct (mode)))
434 if not os.access (fixPath (path), mode):
437 def create (self, path, flags, mode):
438 LOG.debug ("*** create %s %s %s %s %s" % (fixPath (path), oct (flags), oct (mode), flag2mode (flags), config.get("Main", "ReadOnly")))
439 if (config.get("Main", "ReadOnly") == "true"):
440 sendReadOnlyNotification()
443 self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode(flags))
445 self.fd = self.file.fileno ()
448 if __name__ == "__main__":
450 fuse.fuse_python_api = (0, 2)
451 fuse.feature_assert ('stateful_files', 'has_init')
453 config = loadConfig ()
456 #sendNotification("Info", "OsecFS started")
458 # Import the Malware Scanner
459 sys.path.append(config.get("Main", "ScannerPath"))
461 MalwareModule = import_module(config.get("Main", "ScannerModuleName"))
462 MalwareClass = getattr(MalwareModule, config.get("Main", "ScannerClassName"))
464 MalwareScanner = MalwareClass (config.get("Main", "ScannerConfig"));
466 osecfs = OsecFS (config.get ("Main", "Rootpath"))
468 osecfs.multithreaded = 0
470 fuse_args = [sys.argv[0], config.get ("Main", "Mountpoint")];
471 osecfs.main (fuse_args)