src/OsecFS.py
author ck
Tue, 26 Nov 2013 16:13:18 +0100
changeset 1 1f61fe50ab10
parent 0 e840b60f3ea3
child 2 d27473cf6a01
permissions -rwxr-xr-x
Added IKARUS scan server scan on file open.
     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"]}
    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" % (sys.argv[0]))
    46     exit (128)
    47 
    48 def loadConfig ():
    49     print ("load config")
    50 
    51     if (len (sys.argv) < 2):
    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     checkMinimumOptions (config)
    68 
    69     return config
    70 
    71 def initLog (config):
    72     print ("init log")
    73 
    74     global LOG
    75     logfile = config.get("Main", "Logfile")
    76 
    77     # ToDo move log level and maybe other things to config file
    78     logging.basicConfig(
    79                         level = logging.DEBUG,
    80                         format = "%(asctime)s %(name)-12s %(funcName)-15s %(levelname)-8s %(message)s",
    81                         datefmt = "%Y-%m-%d %H:%M:%S",
    82                         filename = logfile,
    83                         filemode = "a+",
    84     )
    85     LOG = logging.getLogger("fuse_main")
    86 
    87 
    88 def fixPath (path):
    89     return ".%s" % (path)
    90 
    91 def rootPath (rootpath, path):
    92     return "%s%s" % (rootpath, path)
    93 
    94 def flag2mode (flags):
    95     md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
    96     m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
    97 
    98     if flags | os.O_APPEND:
    99         m = m.replace('w', 'a', 1)
   100 
   101     return m
   102 
   103 def scanFileIkarus(path, fileobject):
   104     files = {'up_file': (path, fileobject)}
   105     
   106     try:
   107         #TODO: chance to remote server
   108         r = requests.post(LOCAL_SCANSERVER_URL, files=files)
   109     except requests.exceptions.ConnectionError:
   110         LOG.info("Remote scan server unreachable, using local scan server.")
   111 
   112         # TODO:
   113         # Here the local scan server should be contacted.
   114         # The requests package does not upload content in the second post request,
   115         # so no fallback server can be used right now (bug?)
   116         # I did not a find a solution yet, maybe another http package has to be used.
   117         # Disabled for now.
   118 
   119         #try:
   120         #    r = requests.post(LOCAL_SCANSERVER_URL, files=files)
   121         #except requests.exceptions.ConnectionError:
   122         #    return 2
   123         return 2
   124 
   125     if r.status_code == STATUS_CODE_OK:
   126         return 0
   127     elif r.status_code == STATUS_CODE_INFECTED:
   128         # Parse xml for info if desired
   129         #contentXML = r.content
   130         #root = ET.fromstring(contentXML)
   131         #status = root[1][2].text
   132         return 1
   133     else:
   134         return 2
   135 
   136 def scanFile (path, fileobject):
   137     infected = False
   138 
   139     LOG.debug ("Scan File: %s" % (path))
   140 
   141     # ToDo implement ikarus
   142     #result = pyclamav.scanfile (path)
   143     #LOG.debug ("Result of file \"%s\": %s" % (path, result))
   144     #if (result[0] != 0):
   145     #    infected = True
   146 
   147     #if (infected == True):
   148     #    LOG.error ("Virus found deny Access %s" % (result,))
   149 
   150     result = scanFileIkarus(path, fileobject)
   151     LOG.debug ("Result of file \"%s\": %s" % (path, result))
   152 
   153     if (result == 2):
   154         LOG.error ("Connection to scan server could not be established.")
   155 
   156     if (result == 1):
   157         infected = True
   158 
   159     if (infected == True):
   160         LOG.error ("Virus found deny Access %s" % (result))
   161 
   162     return infected
   163 
   164 def whitelistFile (path):
   165     whitelisted = False;
   166 
   167     LOG.debug ("Execute \"%s\" command on \"%s\"" %(SYSTEM_FILE_COMMAND, path))
   168     
   169     result = None
   170     try:
   171         result = subprocess.check_output ([SYSTEM_FILE_COMMAND, path]);
   172         # ToDo replace with real whitelist
   173         whitelisted = True
   174     except Exception as e:
   175         LOG.error ("Call returns with an error!")
   176         LOG.error (e)
   177 
   178     LOG.debug ("Type: %s" %(result))
   179 
   180     return whitelisted
   181 
   182 class OsecFS (Fuse):
   183 
   184     __rootpath = None
   185 
   186     # default fuse init
   187     def __init__(self, rootpath, *args, **kw):
   188         self.__rootpath = rootpath
   189         Fuse.__init__ (self, *args, **kw)
   190         LOG.debug ("Init complete.")
   191 
   192     # defines that our working directory will be the __rootpath
   193     def fsinit(self):
   194         os.chdir (self.__rootpath)
   195 
   196     def getattr(self, path):
   197         LOG.debug ("*** getattr (%s)" % (fixPath (path)))
   198         return os.lstat (fixPath (path));
   199 
   200     def getdir(self, path):
   201         LOG.debug ("*** getdir (%s)" % (path));
   202         return os.listdir (fixPath (path))
   203 
   204     def readdir(self, path, offset):
   205         LOG.debug ("*** readdir (%s %s)" % (path, offset));
   206         for e in os.listdir (fixPath (path)):
   207             yield fuse.Direntry(e)
   208 
   209     def chmod (self, path, mode):
   210         LOG.debug ("*** chmod %s %s" % (path, oct(mode)))
   211         os.chmod (fixPath (path), mode)
   212 
   213     def chown (self, path, uid, gid):
   214         LOG.debug ("*** chown %s %s %s" % (path, uid, gid))
   215         os.chown (fixPath (path), uid, gid)
   216 
   217     def link (self, targetPath, linkPath):
   218         LOG.debug ("*** link %s %s" % (targetPath, linkPath))
   219         os.link (fixPath (targetPath), fixPath (linkPath))
   220 
   221     def mkdir (self, path, mode):
   222         LOG.debug ("*** mkdir %s %s" % (path, oct(mode)))
   223         os.mkdir (fixPath (path), mode)
   224 
   225     def mknod (self, path, mode, dev):
   226         LOG.debug ("*** mknod %s %s %s" % (path, oct (mode), dev))
   227         os.mknod (fixPath (path), mode, dev)
   228 
   229     # to implement virus scan
   230     def open (self, path, flags):
   231         LOG.debug ("*** open %s %s" % (path, oct (flags)))
   232         self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode (flags))
   233         self.fd = self.file.fileno ()
   234 
   235         infected = scanFile (rootPath(self.__rootpath, path), self.file)
   236         if (infected == True):
   237             self.file.close ()
   238             return -errno.EACCES
   239         
   240         whitelisted = whitelistFile (rootPath(self.__rootpath, path))
   241         if (whitelisted == False):
   242             self.file.close ()
   243             return -errno.EACCES
   244 
   245     def read (self, path, length, offset):
   246         LOG.debug ("*** read %s %s %s" % (path, length, offset))
   247         self.file.seek (offset)
   248         return self.file.read (length)
   249 
   250     def readlink (self, path):
   251         LOG.debug ("*** readlink %s" % (path))
   252         return os.readlink (fixPath (path))
   253 
   254     def release (self, path, flags):
   255         LOG.debug ("*** release %s %s" % (path, oct (flags)))
   256         self.file.close ()
   257 
   258     def rename (self, oldPath, newPath):
   259         LOG.debug ("*** rename %s %s" % (oldPath, newPath))
   260         os.rename (fixPath (oldPath), fixPath (newPath))
   261 
   262     def rmdir (self, path):
   263         LOG.debug ("*** rmdir %s" % (path))
   264         os.rmdir (fixPath (path))
   265 
   266     def statfs (self):
   267         LOG.debug ("*** statfs")
   268         return os.statvfs(".")
   269 
   270     def symlink (self, targetPath, linkPath):
   271         LOG.debug ("*** symlink %s %s" % (targetPath, linkPath))
   272         os.symlink (fixPath (targetPath), fixPath (linkPath))
   273 
   274     def truncate (self, path, length):
   275         LOG.debug ("*** truncate %s %s" % (path, length))
   276         f = open (fixPath (path), "a")
   277         f.truncate (length)
   278         f.close ()
   279 
   280     def unlink (self, path):
   281         LOG.debug ("*** unlink %s" % (path))
   282         os.unlink (fixPath (path))
   283 
   284     def utime (self, path, times):
   285         LOG.debug ("*** utime %s %s" % (path, times))
   286         os.utime (fixPath (path), times)
   287 
   288     def write (self, path, buf, offset):
   289         LOG.debug ("*** write %s %s %s" % (path, buf, offset))
   290         self.file.seek (offset)
   291         self.file.write (buf)
   292         return len (buf)
   293 
   294     def access (self, path, mode):
   295         LOG.debug ("*** access %s %s" % (path, oct (mode)))
   296         if not os.access (fixPath (path), mode):
   297             return -errno.EACCES
   298 
   299     def create (self, path, flags, mode):
   300         LOG.debug ("*** create %s %s %s %s" % (fixPath (path), oct (flags), oct (mode), flag2mode (flags)))
   301         self.file = os.fdopen (os.open (fixPath (path), flags, mode), flag2mode (flags))
   302         self.fd = self.file.fileno ()
   303 
   304 
   305 if __name__ == "__main__":
   306     # Set api version
   307     fuse.fuse_python_api = (0, 2)
   308     fuse.feature_assert ('stateful_files', 'has_init')
   309 
   310     config = loadConfig ()
   311     initLog (config)
   312 
   313     LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL")
   314     REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL")
   315 
   316     osecfs = OsecFS (config.get ("Main", "Rootpath"))
   317     osecfs.flags = 0
   318     osecfs.multithreaded = 0
   319 
   320     # osecfs.parser.add_option (mountopt=config.get("Main", "Mountpoint"),
   321     #                      metavar="PATH",
   322     #                      default=config.get("Main", "Rootpath"),
   323     #                      help="mirror filesystem from under PATH [default: %default]")
   324     # osecfs.parse(values=osecfs, errex=1)
   325 
   326     fuse_args = [sys.argv[0], config.get ("Main", "Mountpoint")];
   327     osecfs.main (fuse_args)