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