# HG changeset patch # User ft # Date 1392734230 -3600 # Node ID dc877520743bf621fd4e67c650527fd7825b7a61 # Parent b97aad470500f4c850bfd6365ab19cc703b54175 moved scanner engines to extra projects its now possible to configure the scanner engine with the config file diff -r b97aad470500 -r dc877520743b config/OsecFS.cfg --- a/config/OsecFS.cfg Mon Dec 09 16:38:20 2013 +0100 +++ b/config/OsecFS.cfg Tue Feb 18 15:37:10 2014 +0100 @@ -2,17 +2,32 @@ # make sure this file is writeable Logfile: /var/log/fuse_test.log +# DEBUG, INFO, WARNING, ERROR, CRITICAL +LogLevel: debug + # where the files really are on the filesystem Rootpath: /tmp/root_fuse -# the maximum file size in MB that is scanned -MaxFileSize: 50 -# the URL of the local scan server -LocalScanserverURL: http://localhost/virusscan +# path to scanner class +ScannerPath: /path/to/ikarusscanner/src/ -# the URL of the remote scan server -RemoteScanserverURL: http://192.168.63.129/virusscan +# scanner module name +ScannerModuleName: IkarusScanner +ScannerClassName: IkarusScanner -# wait time in seconds until a new connection attempt to remote server is made -RetryTimeout: 600 +# config file for scanner (path will be in the constructor) +ScannerConfig: /path/to/IkarusScanner.cfg + + + + +# path to scanner class +#ScannerPath: /path/to/clamavscanner/src + +# scanner module name +#ScannerModuleName: ClamAVScanner +#ScannerClassName: ClamAVScanner + +# config file for scanner (path will be in the constructor) +#ScannerConfig: /path/to/ClamAVScanner.cfg \ No newline at end of file diff -r b97aad470500 -r dc877520743b src/OsecFS.py --- a/src/OsecFS.py Mon Dec 09 16:38:20 2013 +0100 +++ b/src/OsecFS.py Tue Feb 18 15:37:10 2014 +0100 @@ -12,8 +12,9 @@ import errno import time -# ToDo replace with ikarus -#import pyclamav +from importlib import import_module + + import subprocess import urllib3 @@ -22,26 +23,23 @@ import netaddr -MINOPTS = { "Main" : ["Logfile", "Mountpoint", "Rootpath", "LocalScanserverURL", "RemoteScanserverURL", "ReadOnly"]} +sys.stderr = open('/var/log/osecfs_error.log', 'a+') + + +MINOPTS = { "Main" : ["Logfile", "LogLevel", "Mountpoint", "Rootpath", "ScannerPath", "ScannerModuleName", "ScannerClassName", "ScannerConfig", "ReadOnly"]} CONFIG_NOT_READABLE = "Configfile is not readable" CONFIG_WRONG = "Something is wrong with the config" CONFIG_MISSING = "Section: \"%s\" Option: \"%s\" in configfile is missing" +SCAN_WRONG_RETURN_VALUE = "The return Value of the malware scanner is wrong. Has to be an dictionary" +SCAN_RETURN_VALUE_KEY_MISSING = "The dictionary has to include key \"infected\" (True, False) and \"virusname\" (String)" +VIRUS_FOUND = "Virus found. Access denied" +NOTIFICATION_CRITICAL = "critical" +NOTIFICATION_INFO = "info" LOG = None -LOCAL_SCANSERVER_URL = "" -REMOTE_SCANSERVER_URL = "" -STATUS_CODE_OK = 200 -STATUS_CODE_INFECTED = 210 -STATUS_CODE_NOT_FOUND = 404 +MalwareScanner = None SYSTEM_FILE_COMMAND = "file" - -MAX_SCAN_FILE_SIZE = 50 * 0x100000 -SCANSERVER_RETRY_TIMEOUT = 60 - -# Global http pool manager used to connect to the scan server -remoteScanserverReachable = True -scanserverTimestamp = 0 httpPool = urllib3.PoolManager(num_pools = 1, timeout = 3) def checkMinimumOptions (config): @@ -91,10 +89,14 @@ global LOG logfile = config.get("Main", "Logfile") + + numeric_level = getattr(logging, config.get("Main", "LogLevel").upper(), None) + if not isinstance(numeric_level, int): + raise ValueError('Invalid log level: %s' % loglevel) # ToDo move log level and maybe other things to config file logging.basicConfig( - level = logging.DEBUG, + level = numeric_level, format = "%(asctime)s %(name)-12s %(funcName)-15s %(levelname)-8s %(message)s", datefmt = "%Y-%m-%d %H:%M:%S", filename = logfile, @@ -117,70 +119,11 @@ m = m.replace('w', 'a', 1) return m - -def contactScanserver(url, fields): - return httpPool.request_encode_body('POST', url, fields = fields, retries = 0) - -def scanFileIkarus (path, fileobject): - global remoteScanserverReachable - global scanserverTimestamp +def scanFile (path, fileobject): + LOG.debug ("Scan File \"%s\" with malware Scanner" %(path,) ) + return MalwareScanner.scanFile (path, fileobject) - infected = False - LOG.debug ("Scan File: %s" % (path)) - - if (os.fstat(fileobject.fileno()).st_size > MAX_SCAN_FILE_SIZE): - LOG.info("File max size exceeded. The file is not scanned.") - return False - - fields = { 'up_file' : fileobject.read() } - - if (remoteScanserverReachable == False) and ((scanserverTimestamp + SCANSERVER_RETRY_TIMEOUT) < time.time()): - remoteScanserverReachable = True - - if remoteScanserverReachable: - try: - response = contactScanserver(REMOTE_SCANSERVER_URL, fields) - # We should catch socket.error here, but this does not work. Needs checking. - except: - LOG.info("Remote scan server unreachable, using local scan server.") - LOG.info("Next check for remote server in %s seconds." % (SCANSERVER_RETRY_TIMEOUT)) - - remoteScanserverReachable = False - scanserverTimestamp = time.time() - - try: - response = contactScanserver(LOCAL_SCANSERVER_URL, fields) - except: - LOG.error ("Connection to local scan server could not be established.") - LOG.error ("Exception: %s" %(sys.exc_info()[0])) - return False - else: - try: - response = contactScanserver(LOCAL_SCANSERVER_URL, fields) - except: - LOG.error ("Connection to local scan server could not be established.") - LOG.error ("Exception: %s" %(sys.exc_info()[0])) - return False - - - if response.status == STATUS_CODE_OK: - infected = False - elif response.status == STATUS_CODE_INFECTED: - # Parse xml for info if desired - #contentXML = r.content - #root = ET.fromstring(contentXML) - #status = root[1][2].text - infected = True - else: - LOG.error ("Connection error to scan server.") - - if (infected == True): - LOG.error ("Virus found, denying access.") - else: - LOG.debug ("No virus found.") - - return infected def scanFileClamAV (path): infected = False @@ -312,18 +255,35 @@ LOG.debug ("*** open %s %s" % (path, oct (flags))) self.file = os.fdopen (os.open (fixPath (path), flags), flag2mode (flags)) self.fd = self.file.fileno () - - infected = scanFileIkarus (rootPath(self.__rootpath, path), self.file) - #infected = scanFileClamAV (rootPath(self.__rootpath, path)) - if (infected == True): + + LOG.debug(self.__rootpath) + LOG.debug(path) + + retval = scanFile (rootPath(self.__rootpath, path), self.file) + + #if type(retval) is not dict: + if (isinstance(retval, dict) == False): + LOG.error(SCAN_WRONG_RETURN_VALUE) self.file.close () - sendNotification("critical", "Virus found. Access denied.") + return -errno.EACCES + + if ((retval.has_key("infected") == False) or (retval.has_key("virusname") == False)): + LOG.error(SCAN_RETURN_VALUE_KEY_MISSING) + self.file.close () + return -errno.EACCES + + + if (retval.get("infected") == True): + self.file.close () + sendNotification(NOTIFICATION_CRITICAL, "%s\nFile: %s\nVirus: %s" %(VIRUS_FOUND, path, retval.get("virusname"))) + LOG.error("%s" %(VIRUS_FOUND,)) + LOG.error("Virus: %s" %(retval.get("virusname"),)) return -errno.EACCES whitelisted = whitelistFile (rootPath(self.__rootpath, path)) if (whitelisted == False): self.file.close () - sendNotification("critical", "File not in whitelist. Access denied.") + sendNotification(NOTIFICATION_CRITICAL, "File not in whitelist. Access denied.") return -errno.EACCES def read (self, path, length, offset): @@ -417,15 +377,14 @@ initLog (config) #sendNotification("Info", "OsecFS started") - - scanserverTimestamp = time.time() - - LOCAL_SCANSERVER_URL = config.get("Main", "LocalScanserverURL") - REMOTE_SCANSERVER_URL = config.get("Main", "RemoteScanserverURL") - SCANSERVER_RETRY_TIMEOUT = int(config.get("Main", "RetryTimeout")) - - # Convert file size from MB to byte - MAX_SCAN_FILE_SIZE = int(config.get("Main", "MaxFileSize")) * 0x100000 + + # Import the Malware Scanner + sys.path.append(config.get("Main", "ScannerPath")) + + MalwareModule = import_module(config.get("Main", "ScannerModuleName")) + MalwareClass = getattr(MalwareModule, config.get("Main", "ScannerClassName")) + + MalwareScanner = MalwareClass (config.get("Main", "ScannerConfig")); osecfs = OsecFS (config.get ("Main", "Rootpath")) osecfs.flags = 0