src/OsecFS.py
author ft
Tue, 03 Dec 2013 14:53:22 +0100
changeset 4 114537186d9e
parent 3 5c45c43de56e
child 5 5b7c05fc9a5e
permissions -rwxr-xr-x
Catch all Exceptions in the scanning part
     1 #!/usr/bin/python
     2 
     3 from fuse import Fuse
     4 import fuse
     5 
     6 import ConfigParser
     7 
     8 import sys
     9 
    10 import logging
    11 import os
    12 import errno
    13 
    14 # ToDo replace with ikarus
    15 #import pyclamav
    16 import subprocess
    17 
    18 import requests
    19 
    20 
    21 MINOPTS = { "Main" : ["Logfile", "Mountpoint", "Rootpath", "LocalScanserverURL", "RemoteScanserverURL", "ReadOnly"]}
    22 
    23 CONFIG_NOT_READABLE = "Configfile is not readable"
    24 CONFIG_WRONG = "Something is wrong with the config"
    25 CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
    26 LOG = None
    27 LOCAL_SCANSERVER_URL = ""
    28 REMOTE_SCANSERVER_URL = ""
    29 STATUS_CODE_OK = 200
    30 STATUS_CODE_INFECTED = 210
    31 STATUS_CODE_NOT_FOUND = 404
    32 
    33 SYSTEM_FILE_COMMAND = "file"
    34 
    35 
    36 def checkMinimumOptions (config):
    37     for section, options in MINOPTS.iteritems ():
    38         for option in options:
    39             if (config.has_option(section, option) == False):
    40                 print (CONFIG_MISSING % (section, option))
    41                 exit (129)
    42 
    43 def printUsage ():
    44     print ("Usage:")
    45     print ("%s configfile mountpath ro/rw" % (sys.argv[0]))
    46     exit (128)
    47 
    48 def loadConfig ():
    49     print ("load config")
    50 
    51     if (len (sys.argv) < 4):
    52         printUsage ()
    53 
    54     configfile = sys.argv[1]
    55     config = ConfigParser.SafeConfigParser ()
    56 
    57     if ((os.path.exists (configfile) == False) or (os.path.isfile (configfile) == False) or (os.access (configfile, os.R_OK) == False)):
    58         print (CONFIG_NOT_READABLE)
    59         printUsage ()
    60 
    61     try:
    62         config.read (sys.argv[1])
    63     except Exception, e:
    64         print (CONFIG_WRONG)
    65         print ("Error: %s" % (e))
    66 
    67 
    68     config.set("Main", "Mountpoint", sys.argv[2])
    69     if (sys.argv[3] == "rw"):
    70         config.set("Main", "ReadOnly", "false")
    71     else:
    72         config.set("Main", "ReadOnly", "true")
    73 
    74     checkMinimumOptions (config)
    75 
    76     return config
    77 
    78 def initLog (config):
    79     print ("init log")
    80 
    81     global LOG
    82     logfile = config.get("Main", "Logfile")
    83 
    84     # ToDo move log level and maybe other things to config file
    85     logging.basicConfig(
    86                         level = logging.DEBUG,
    87                         format = "%(asctime)s %(name)-12s %(funcName)-15s %(levelname)-8s %(message)s",
    88                         datefmt = "%Y-%m-%d %H:%M:%S",
    89                         filename = logfile,
    90                         filemode = "a+",
    91     )
    92     LOG = logging.getLogger("fuse_main")
    93 
    94 
    95 def fixPath (path):
    96     return ".%s" % (path)
    97 
    98 def rootPath (rootpath, path):
    99     return "%s%s" % (rootpath, path)
   100 
   101 def flag2mode (flags):
   102     md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
   103     m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
   104 
   105     if flags | os.O_APPEND:
   106         m = m.replace('w', 'a', 1)
   107 
   108     return m
   109 
   110 def scanFileIkarus (path, fileobject):
   111     infected = False
   112     LOG.debug ("Scan File: %s" % (path))
   113     
   114     files = {'up_file': (path, fileobject)}
   115     
   116     try:
   117         #TODO: change to remote server
   118         r = requests.post(LOCAL_SCANSERVER_URL, files=files)
   119     except requests.exceptions.ConnectionError:
   120         #LOG.info("Remote scan server unreachable, using local scan server.")
   121 
   122         # TODO:
   123         # Here the local scan server should be contacted.
   124         # The requests package does not upload content in the second post request,
   125         # so no fallback server can be used right now (bug?)
   126         # I did not a find a solution yet, maybe another http package has to be used.
   127         # Disabled for now.
   128 
   129         #try:
   130         #    r = requests.post(LOCAL_SCANSERVER_URL, files=files)
   131         #except requests.exceptions.ConnectionError:
   132         #    return 2
   133         LOG.error ("Connection to scan server could not be established.")
   134         return False
   135     except:
   136         LOG.error ("Something went wrong at scanning.")
   137         LOG.error ("Exception: %s" %(sys.exc_info()[0],))
   138         return False
   139 
   140 
   141     if r.status_code == STATUS_CODE_OK:
   142         infected = False
   143     elif r.status_code == STATUS_CODE_INFECTED:
   144         # Parse xml for info if desired
   145         #contentXML = r.content
   146         #root = ET.fromstring(contentXML)
   147         #status = root[1][2].text
   148         infected = True
   149     else:
   150         LOG.error ("Connection error to scan server.")
   151 
   152     if (infected == True):
   153         LOG.error ("Virus found, denying access.")
   154     else:
   155         LOG.debug ("No virus found.")
   156 
   157     return infected
   158 
   159 def scanFileClamAV (path):
   160     infected = False
   161 
   162     LOG.debug ("Scan File: %s" % (path))
   163 
   164     # ToDo implement ikarus
   165     result = pyclamav.scanfile (path)
   166     LOG.debug ("Result of file \"%s\": %s" % (path, result))
   167     if (result[0] != 0):
   168         infected = True
   169 
   170     if (infected == True):
   171         LOG.error ("Virus found, deny Access %s" % (result,))
   172 
   173     return infected
   174 
   175 def whitelistFile (path):
   176     whitelisted = False;
   177 
   178     LOG.debug ("Execute \"%s\" command on \"%s\"" %(SYSTEM_FILE_COMMAND, path))
   179     
   180     result = None
   181     try:
   182         result = subprocess.check_output ([SYSTEM_FILE_COMMAND, path]);
   183         # ToDo replace with real whitelist
   184         whitelisted = True
   185     except Exception as e:
   186         LOG.error ("Call returns with an error!")
   187         LOG.error (e)
   188 
   189     LOG.debug ("Type: %s" %(result))
   190 
   191     return whitelisted
   192 
   193 class OsecFS (Fuse):
   194 
   195     __rootpath = None
   196 
   197     # default fuse init
   198     def __init__(self, rootpath, *args, **kw):
   199         self.__rootpath = rootpath
   200         Fuse.__init__ (self, *args, **kw)
   201         LOG.debug ("Init complete.")
   202 
   203     # defines that our working directory will be the __rootpath
   204     def fsinit(self):
   205         os.chdir (self.__rootpath)
   206 
   207     def getattr(self, path):
   208         LOG.debug ("*** getattr (%s)" % (fixPath (path)))
   209         return os.lstat (fixPath (path));
   210 
   211     def getdir(self, path):
   212         LOG.debug ("*** getdir (%s)" % (path));
   213         return os.listdir (fixPath (path))
   214 
   215     def readdir(self, path, offset):
   216         LOG.debug ("*** readdir (%s %s)" % (path, offset));
   217         for e in os.listdir (fixPath (path)):
   218             yield fuse.Direntry(e)
   219 
   220     def chmod (self, path, mode):
   221         LOG.debug ("*** chmod %s %s" % (path, oct(mode)))
   222         if (config.get("Main", "ReadOnly") == "true"):
   223             return -errno.EACCES
   224         os.chmod (fixPath (path), mode)
   225 
   226     def chown (self, path, uid, gid):
   227         LOG.debug ("*** chown %s %s %s" % (path, uid, gid))
   228         if (config.get("Main", "ReadOnly") == "true"):
   229             return -errno.EACCES
   230         os.chown (fixPath (path), uid, gid)
   231 
   232     def link (self, targetPath, linkPath):
   233         LOG.debug ("*** link %s %s" % (targetPath, linkPath))
   234         if (config.get("Main", "ReadOnly") == "true"):
   235             return -errno.EACCES
   236         os.link (fixPath (targetPath), fixPath (linkPath))
   237 
   238     def mkdir (self, path, mode):
   239         LOG.debug ("*** mkdir %s %s" % (path, oct(mode)))
   240         if (config.get("Main", "ReadOnly") == "true"):
   241             return -errno.EACCES
   242         os.mkdir (fixPath (path), mode)
   243 
   244     def mknod (self, path, mode, dev):
   245         LOG.debug ("*** mknod %s %s %s" % (path, oct (mode), dev))
   246         if (config.get("Main", "ReadOnly") == "true"):
   247             return -errno.EACCES
   248         os.mknod (fixPath (path), mode, dev)
   249 
   250     # to implement virus scan
   251     def open (self, path, flags):
   252         LOG.debug ("*** open %s %s" % (path, oct (flags)))
   253         self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode (flags))
   254         self.fd = self.file.fileno ()
   255 
   256         infected = scanFileIkarus (rootPath(self.__rootpath, path), self.file)
   257         #infected = scanFileClamAV (rootPath(self.__rootpath, path))
   258         if (infected == True):
   259             self.file.close ()
   260             return -errno.EACCES
   261         
   262         whitelisted = whitelistFile (rootPath(self.__rootpath, path))
   263         if (whitelisted == False):
   264             self.file.close ()
   265             return -errno.EACCES
   266 
   267     def read (self, path, length, offset):
   268         LOG.debug ("*** read %s %s %s" % (path, length, offset))
   269         self.file.seek (offset)
   270         return self.file.read (length)
   271 
   272     def readlink (self, path):
   273         LOG.debug ("*** readlink %s" % (path))
   274         return os.readlink (fixPath (path))
   275 
   276     def release (self, path, flags):
   277         LOG.debug ("*** release %s %s" % (path, oct (flags)))
   278         self.file.close ()
   279 
   280     def rename (self, oldPath, newPath):
   281         LOG.debug ("*** rename %s %s %s" % (oldPath, newPath, config.get("Main", "ReadOnly")))
   282         if (config.get("Main", "ReadOnly") == "true"):
   283             return -errno.EACCES
   284         os.rename (fixPath (oldPath), fixPath (newPath))
   285 
   286     def rmdir (self, path):
   287         LOG.debug ("*** rmdir %s %s" % (path, config.get("Main", "ReadOnly")))
   288         if (config.get("Main", "ReadOnly") == "true"):
   289             return -errno.EACCES
   290         os.rmdir (fixPath (path))
   291 
   292     def statfs (self):
   293         LOG.debug ("*** statfs")
   294         return os.statvfs(".")
   295 
   296     def symlink (self, targetPath, linkPath):
   297         LOG.debug ("*** symlink %s %s %s" % (targetPath, linkPath, config.get("Main", "ReadOnly")))
   298         if (config.get("Main", "ReadOnly") == "true"):
   299             return -errno.EACCES
   300         os.symlink (fixPath (targetPath), fixPath (linkPath))
   301 
   302     def truncate (self, path, length):
   303         LOG.debug ("*** truncate %s %s %s" % (path, length, config.get("Main", "ReadOnly")))
   304         if (config.get("Main", "ReadOnly") == "true"):
   305             return -errno.EACCES
   306         f = open (fixPath (path), "a")
   307         f.truncate (length)
   308         f.close ()
   309 
   310     def unlink (self, path):
   311         LOG.debug ("*** unlink %s %s" % (path, config.get("Main", "ReadOnly")))
   312         if (config.get("Main", "ReadOnly") == "true"):
   313             return -errno.EACCES
   314         os.unlink (fixPath (path))
   315 
   316     def utime (self, path, times):
   317         LOG.debug ("*** utime %s %s" % (path, times))
   318         os.utime (fixPath (path), times)
   319 
   320     def write (self, path, buf, offset):
   321         LOG.debug ("*** write %s %s %s %s" % (path, buf, offset, config.get("Main", "ReadOnly")))
   322         if (config.get("Main", "ReadOnly") == "true"):
   323             self.file.close()
   324             return -errno.EACCES
   325         self.file.seek (offset)
   326         self.file.write (buf)
   327         return len (buf)
   328 
   329     def access (self, path, mode):
   330         LOG.debug ("*** access %s %s" % (path, oct (mode)))
   331         if not os.access (fixPath (path), mode):
   332             return -errno.EACCES
   333 
   334     def create (self, path, flags, mode):
   335         LOG.debug ("*** create %s %s %s %s %s" % (fixPath (path), oct (flags), oct (mode), flag2mode (flags), config.get("Main", "ReadOnly")))
   336         if (config.get("Main", "ReadOnly") == "true"):
   337             return -errno.EACCES
   338         self.file = os.fdopen (os.open (fixPath (path), flags, mode), flag2mode (flags))
   339         self.fd = self.file.fileno ()
   340 
   341 
   342 if __name__ == "__main__":
   343     # Set api version
   344     fuse.fuse_python_api = (0, 2)
   345     fuse.feature_assert ('stateful_files', 'has_init')
   346 
   347     config = loadConfig ()
   348     initLog (config)
   349 
   350     LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL")
   351     REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL")
   352 
   353     osecfs = OsecFS (config.get ("Main", "Rootpath"))
   354     osecfs.flags = 0
   355     osecfs.multithreaded = 0
   356 
   357     # osecfs.parser.add_option (mountopt=config.get("Main", "Mountpoint"),
   358     #                      metavar="PATH",
   359     #                      default=config.get("Main", "Rootpath"),
   360     #                      help="mirror filesystem from under PATH [default: %default]")
   361     # osecfs.parse(values=osecfs, errex=1)
   362 
   363     fuse_args = [sys.argv[0], config.get ("Main", "Mountpoint")];
   364     osecfs.main (fuse_args)