1.1 --- a/config/OsecFS.cfg Mon Dec 09 16:38:20 2013 +0100
1.2 +++ b/config/OsecFS.cfg Tue Feb 18 15:37:10 2014 +0100
1.3 @@ -2,17 +2,32 @@
1.4 # make sure this file is writeable
1.5 Logfile: /var/log/fuse_test.log
1.6
1.7 +# DEBUG, INFO, WARNING, ERROR, CRITICAL
1.8 +LogLevel: debug
1.9 +
1.10 # where the files really are on the filesystem
1.11 Rootpath: /tmp/root_fuse
1.12
1.13 -# the maximum file size in MB that is scanned
1.14 -MaxFileSize: 50
1.15
1.16 -# the URL of the local scan server
1.17 -LocalScanserverURL: http://localhost/virusscan
1.18 +# path to scanner class
1.19 +ScannerPath: /path/to/ikarusscanner/src/
1.20
1.21 -# the URL of the remote scan server
1.22 -RemoteScanserverURL: http://192.168.63.129/virusscan
1.23 +# scanner module name
1.24 +ScannerModuleName: IkarusScanner
1.25 +ScannerClassName: IkarusScanner
1.26
1.27 -# wait time in seconds until a new connection attempt to remote server is made
1.28 -RetryTimeout: 600
1.29 +# config file for scanner (path will be in the constructor)
1.30 +ScannerConfig: /path/to/IkarusScanner.cfg
1.31 +
1.32 +
1.33 +
1.34 +
1.35 +# path to scanner class
1.36 +#ScannerPath: /path/to/clamavscanner/src
1.37 +
1.38 +# scanner module name
1.39 +#ScannerModuleName: ClamAVScanner
1.40 +#ScannerClassName: ClamAVScanner
1.41 +
1.42 +# config file for scanner (path will be in the constructor)
1.43 +#ScannerConfig: /path/to/ClamAVScanner.cfg
1.44 \ No newline at end of file
2.1 --- a/src/OsecFS.py Mon Dec 09 16:38:20 2013 +0100
2.2 +++ b/src/OsecFS.py Tue Feb 18 15:37:10 2014 +0100
2.3 @@ -12,8 +12,9 @@
2.4 import errno
2.5 import time
2.6
2.7 -# ToDo replace with ikarus
2.8 -#import pyclamav
2.9 +from importlib import import_module
2.10 +
2.11 +
2.12 import subprocess
2.13
2.14 import urllib3
2.15 @@ -22,26 +23,23 @@
2.16 import netaddr
2.17
2.18
2.19 -MINOPTS = { "Main" : ["Logfile", "Mountpoint", "Rootpath", "LocalScanserverURL", "RemoteScanserverURL", "ReadOnly"]}
2.20 +sys.stderr = open('/var/log/osecfs_error.log', 'a+')
2.21 +
2.22 +
2.23 +MINOPTS = { "Main" : ["Logfile", "LogLevel", "Mountpoint", "Rootpath", "ScannerPath", "ScannerModuleName", "ScannerClassName", "ScannerConfig", "ReadOnly"]}
2.24
2.25 CONFIG_NOT_READABLE = "Configfile is not readable"
2.26 CONFIG_WRONG = "Something is wrong with the config"
2.27 CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing"
2.28 +SCAN_WRONG_RETURN_VALUE = "The return Value of the malware scanner is wrong. Has to be an dictionary"
2.29 +SCAN_RETURN_VALUE_KEY_MISSING = "The dictionary has to include key \"infected\" (True, False) and \"virusname\" (String)"
2.30 +VIRUS_FOUND = "Virus found. Access denied"
2.31 +NOTIFICATION_CRITICAL = "critical"
2.32 +NOTIFICATION_INFO = "info"
2.33 LOG = None
2.34 -LOCAL_SCANSERVER_URL = ""
2.35 -REMOTE_SCANSERVER_URL = ""
2.36 -STATUS_CODE_OK = 200
2.37 -STATUS_CODE_INFECTED = 210
2.38 -STATUS_CODE_NOT_FOUND = 404
2.39 +MalwareScanner = None
2.40
2.41 SYSTEM_FILE_COMMAND = "file"
2.42 -
2.43 -MAX_SCAN_FILE_SIZE = 50 * 0x100000
2.44 -SCANSERVER_RETRY_TIMEOUT = 60
2.45 -
2.46 -# Global http pool manager used to connect to the scan server
2.47 -remoteScanserverReachable = True
2.48 -scanserverTimestamp = 0
2.49 httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3)
2.50
2.51 def checkMinimumOptions (config):
2.52 @@ -91,10 +89,14 @@
2.53
2.54 global LOG
2.55 logfile = config.get("Main", "Logfile")
2.56 +
2.57 + numeric_level = getattr(logging, config.get("Main", "LogLevel").upper(), None)
2.58 + if not isinstance(numeric_level, int):
2.59 + raise ValueError('Invalid log level: %s' % loglevel)
2.60
2.61 # ToDo move log level and maybe other things to config file
2.62 logging.basicConfig(
2.63 - level = logging.DEBUG,
2.64 + level = numeric_level,
2.65 format = "%(asctime)s %(name)-12s %(funcName)-15s %(levelname)-8s %(message)s",
2.66 datefmt = "%Y-%m-%d %H:%M:%S",
2.67 filename = logfile,
2.68 @@ -117,70 +119,11 @@
2.69 m = m.replace('w', 'a', 1)
2.70
2.71 return m
2.72 -
2.73 -def contactScanserver(url, fields):
2.74 - return httpPool.request_encode_body('POST', url, fields = fields, retries = 0)
2.75 -
2.76
2.77 -def scanFileIkarus (path, fileobject):
2.78 - global remoteScanserverReachable
2.79 - global scanserverTimestamp
2.80 +def scanFile (path, fileobject):
2.81 + LOG.debug ("Scan File \"%s\" with malware Scanner" %(path,) )
2.82 + return MalwareScanner.scanFile (path, fileobject)
2.83
2.84 - infected = False
2.85 - LOG.debug ("Scan File: %s" % (path))
2.86 -
2.87 - if (os.fstat(fileobject.fileno()).st_size > MAX_SCAN_FILE_SIZE):
2.88 - LOG.info("File max size exceeded. The file is not scanned.")
2.89 - return False
2.90 -
2.91 - fields = { 'up_file' : fileobject.read() }
2.92 -
2.93 - if (remoteScanserverReachable == False) and ((scanserverTimestamp + SCANSERVER_RETRY_TIMEOUT) < time.time()):
2.94 - remoteScanserverReachable = True
2.95 -
2.96 - if remoteScanserverReachable:
2.97 - try:
2.98 - response = contactScanserver(REMOTE_SCANSERVER_URL, fields)
2.99 - # We should catch socket.error here, but this does not work. Needs checking.
2.100 - except:
2.101 - LOG.info("Remote scan server unreachable, using local scan server.")
2.102 - LOG.info("Next check for remote server in %s seconds." % (SCANSERVER_RETRY_TIMEOUT))
2.103 -
2.104 - remoteScanserverReachable = False
2.105 - scanserverTimestamp = time.time()
2.106 -
2.107 - try:
2.108 - response = contactScanserver(LOCAL_SCANSERVER_URL, fields)
2.109 - except:
2.110 - LOG.error ("Connection to local scan server could not be established.")
2.111 - LOG.error ("Exception: %s" %(sys.exc_info()[0]))
2.112 - return False
2.113 - else:
2.114 - try:
2.115 - response = contactScanserver(LOCAL_SCANSERVER_URL, fields)
2.116 - except:
2.117 - LOG.error ("Connection to local scan server could not be established.")
2.118 - LOG.error ("Exception: %s" %(sys.exc_info()[0]))
2.119 - return False
2.120 -
2.121 -
2.122 - if response.status == STATUS_CODE_OK:
2.123 - infected = False
2.124 - elif response.status == STATUS_CODE_INFECTED:
2.125 - # Parse xml for info if desired
2.126 - #contentXML = r.content
2.127 - #root = ET.fromstring(contentXML)
2.128 - #status = root[1][2].text
2.129 - infected = True
2.130 - else:
2.131 - LOG.error ("Connection error to scan server.")
2.132 -
2.133 - if (infected == True):
2.134 - LOG.error ("Virus found, denying access.")
2.135 - else:
2.136 - LOG.debug ("No virus found.")
2.137 -
2.138 - return infected
2.139
2.140 def scanFileClamAV (path):
2.141 infected = False
2.142 @@ -312,18 +255,35 @@
2.143 LOG.debug ("*** open %s %s" % (path, oct (flags)))
2.144 self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode (flags))
2.145 self.fd = self.file.fileno ()
2.146 -
2.147 - infected = scanFileIkarus (rootPath(self.__rootpath, path), self.file)
2.148 - #infected = scanFileClamAV (rootPath(self.__rootpath, path))
2.149 - if (infected == True):
2.150 +
2.151 + LOG.debug(self.__rootpath)
2.152 + LOG.debug(path)
2.153 +
2.154 + retval = scanFile (rootPath(self.__rootpath, path), self.file)
2.155 +
2.156 + #if type(retval) is not dict:
2.157 + if (isinstance(retval, dict) == False):
2.158 + LOG.error(SCAN_WRONG_RETURN_VALUE)
2.159 self.file.close ()
2.160 - sendNotification("critical", "Virus found. Access denied.")
2.161 + return -errno.EACCES
2.162 +
2.163 + if ((retval.has_key("infected") == False) or (retval.has_key("virusname") == False)):
2.164 + LOG.error(SCAN_RETURN_VALUE_KEY_MISSING)
2.165 + self.file.close ()
2.166 + return -errno.EACCES
2.167 +
2.168 +
2.169 + if (retval.get("infected") == True):
2.170 + self.file.close ()
2.171 + sendNotification(NOTIFICATION_CRITICAL, "%s\nFile: %s\nVirus: %s" %(VIRUS_FOUND, path, retval.get("virusname")))
2.172 + LOG.error("%s" %(VIRUS_FOUND,))
2.173 + LOG.error("Virus: %s" %(retval.get("virusname"),))
2.174 return -errno.EACCES
2.175
2.176 whitelisted = whitelistFile (rootPath(self.__rootpath, path))
2.177 if (whitelisted == False):
2.178 self.file.close ()
2.179 - sendNotification("critical", "File not in whitelist. Access denied.")
2.180 + sendNotification(NOTIFICATION_CRITICAL, "File not in whitelist. Access denied.")
2.181 return -errno.EACCES
2.182
2.183 def read (self, path, length, offset):
2.184 @@ -417,15 +377,14 @@
2.185 initLog (config)
2.186
2.187 #sendNotification("Info", "OsecFS started")
2.188 -
2.189 - scanserverTimestamp = time.time()
2.190 -
2.191 - LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL")
2.192 - REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL")
2.193 - SCANSERVER_RETRY_TIMEOUT = int(config.get("Main", "RetryTimeout"))
2.194 -
2.195 - # Convert file size from MB to byte
2.196 - MAX_SCAN_FILE_SIZE = int(config.get("Main", "MaxFileSize")) * 0x100000
2.197 +
2.198 + # Import the Malware Scanner
2.199 + sys.path.append(config.get("Main", "ScannerPath"))
2.200 +
2.201 + MalwareModule = import_module(config.get("Main", "ScannerModuleName"))
2.202 + MalwareClass = getattr(MalwareModule, config.get("Main", "ScannerClassName"))
2.203 +
2.204 + MalwareScanner = MalwareClass (config.get("Main", "ScannerConfig"));
2.205
2.206 osecfs = OsecFS (config.get ("Main", "Rootpath"))
2.207 osecfs.flags = 0