1 /*!\file clamfs.cxx
2 
3    \brief ClamFS main file
4 
5 *//*
6 
7    ClamFS - An user-space anti-virus protected file system
8    Copyright (C) 2007-2019 Krzysztof Burghardt
9 
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 
24 *//*
25 
26     Whole code of fusexmp_fh.c has been recopied (mainly for all hooks it contains).
27     File fusexmp_fh.c comes with FUSE source code. Original copyright is below.
28 
29     FUSE: Filesystem in Userspace
30     Copyright (C) 2001-2006  Miklos Szeredi <miklos@szeredi.hu>
31 
32     This program can be distributed under the terms of the GNU GPL.
33     See the file COPYING.
34 */
35 
36 #include "version.h"
37 #include "config.h"
38 
39 #include <iostream>
40 #include <fuse.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <fcntl.h>
45 #include <dirent.h>
46 #include <errno.h>
47 #ifdef HAVE_SETXATTR
48 #include <sys/xattr.h>
49 #endif
50 
51 #include <boost/shared_array.hpp>
52 
53 #include "clamfs.hxx"
54 #include "utils.hxx"
55 
56 using namespace std;
57 using namespace boost;
58 using namespace clamfs;
59 
60 /*!\namespace clamfs
61    \brief ClamFS own namespace
62 */
63 namespace clamfs {
64 
65 /*
66  * Things needed by all threads (thus global and threads safe)
67  */
68 
69 /*!\brief Saved file descriptor of our base directory */
70 static int savefd;
71 /*!\brief Stores all configuration options names and values */
72 config_t config;
73 /*!\brief ScanCache instance */
74 ScanCache *cache = NULL;
75 /*!\brief Stats instance */
76 Stats *stats = NULL;
77 /*!\brief Stores whitelisted and blacklisted file extensions */
78 extum_t *extensions = NULL;
79 /*!\brief Mutex need to serialize access to clamd */
80 FastMutex scanMutex;
81 
82 extern "C" {
83 
84 /*!\brief Fixes file path by prefixing it with "." (dot)
85    \param path path need to be fixed
86    \returns fixed path
87 */
fixpath(const char * path)88 static inline const char* fixpath(const char* path)
89 {
90     char* fixed = new char[strlen(path)+2];
91 
92     int res = fchdir(savefd);
93 
94     if (res < 0)
95     {
96         char* username = getusername();
97         char* callername = getcallername();
98         rLog(Warn, "(%s:%d) (%s:%d) %s: fchdir() failed: %s",
99                 callername, fuse_get_context()->pid, username, fuse_get_context()->uid,
100                 path, strerror(errno));
101         free(username);
102         free(callername);
103     }
104 
105     strcpy(fixed,".");
106     strcat(fixed,path);
107 
108     return fixed;
109 }
110 
111 /*!\brief FUSE getattr() callback
112    \param path file path
113    \param stbuf buffer to pass to lstat()
114    \returns 0 if lstat() returns without error on -errno otherwise
115 */
clamfs_getattr(const char * path,struct stat * stbuf)116 static int clamfs_getattr(const char *path, struct stat *stbuf)
117 {
118     int res;
119 
120     const char* fpath = fixpath(path);
121     res = lstat(fpath, stbuf);
122     delete[] fpath;
123     if (res == -1)
124         return -errno;
125 
126     return 0;
127 }
128 
129 /*!\brief FUSE fgetattr() callback
130    \param path file path
131    \param stbuf buffer to pass to lstat()
132    \param fi information about open files
133    \returns 0 if lstat() returns without error on -errno otherwise
134 */
clamfs_fgetattr(const char * path,struct stat * stbuf,struct fuse_file_info * fi)135 static int clamfs_fgetattr(const char *path, struct stat *stbuf,
136                         struct fuse_file_info *fi)
137 {
138     int res;
139 
140     (void) path;
141 
142     res = fstat((int)fi->fh, stbuf);
143     if (res == -1)
144         return -errno;
145 
146     return 0;
147 }
148 
149 /*!\brief FUSE access() callback
150    \param path file path
151    \param mask bit pattern
152    \returns 0 if access() returns without error on -errno otherwise
153 */
clamfs_access(const char * path,int mask)154 static int clamfs_access(const char *path, int mask)
155 {
156     int res;
157 
158     const char* fpath = fixpath(path);
159     res = access(fpath, mask);
160     delete[] fpath;
161     if (res == -1)
162         return -errno;
163 
164     return 0;
165 }
166 
167 /*!\brief FUSE readlink() callback
168    \param path file path
169    \param buf data buffer
170    \param size buffer size
171    \returns 0 if readlink() returns without error on -errno otherwise
172 */
clamfs_readlink(const char * path,char * buf,size_t size)173 static int clamfs_readlink(const char *path, char *buf, size_t size)
174 {
175     ssize_t res;
176 
177     const char* fpath = fixpath(path);
178     res = readlink(fpath, buf, size - 1);
179     delete[] fpath;
180     if (res == -1)
181         return -errno;
182 
183     buf[res] = '\0';
184     return 0;
185 }
186 
187 /*!\brief FUSE opendir() callback
188    \param path directory path
189    \param fi information about open files
190    \returns 0 if opendir() returns without error on -errno otherwise
191 */
clamfs_opendir(const char * path,struct fuse_file_info * fi)192 static int clamfs_opendir(const char *path, struct fuse_file_info *fi)
193 {
194     DIR *dp;
195 
196     const char* fpath = fixpath(path);
197     dp = opendir(fpath);
198     delete[] fpath;
199     if (dp == NULL)
200         return -errno;
201 
202     fi->fh = (unsigned long) dp;
203     return 0;
204 }
205 
206 /*!\brief Returns directory pointer from fuse_file_info
207    \param fi information about open files
208    \returns pointer to file handle
209 */
get_dirp(struct fuse_file_info * fi)210 static inline DIR *get_dirp(struct fuse_file_info *fi)
211 {
212     return (DIR *) (uintptr_t) fi->fh;
213 }
214 
215 /*!\brief FUSE readdir() callback
216    \param path directory path
217    \param buf data buffer
218    \param filler directory content filter
219    \param offset directory poninter offset
220    \param fi information about open files
221    \returns always 0
222 */
clamfs_readdir(const char * path,void * buf,fuse_fill_dir_t filler,off_t offset,struct fuse_file_info * fi)223 static int clamfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
224                        off_t offset, struct fuse_file_info *fi)
225 {
226     DIR *dp = get_dirp(fi);
227     struct dirent *de;
228 
229     (void) path;
230     seekdir(dp, offset);
231     while ((de = readdir(dp)) != NULL) {
232         struct stat st;
233         memset(&st, 0, sizeof(st));
234         st.st_ino = de->d_ino;
235         st.st_mode = (unsigned int)de->d_type << 12;
236         if (filler(buf, de->d_name, &st, telldir(dp)))
237             break;
238     }
239 
240     return 0;
241 }
242 
243 /*!\brief FUSE releasedir() callback
244    \param path directory path
245    \param fi information about open files
246    \returns always 0
247 */
clamfs_releasedir(const char * path,struct fuse_file_info * fi)248 static int clamfs_releasedir(const char *path, struct fuse_file_info *fi)
249 {
250     DIR *dp = get_dirp(fi);
251     (void) path;
252     closedir(dp);
253     return 0;
254 }
255 
256 /*!\brief FUSE mknod() callback
257    \param path file path
258    \param mode file permissions
259    \param rdev major and minor numbers of device special file
260    \returns 0 if mknod() returns without error on -errno otherwise
261 */
clamfs_mknod(const char * path,mode_t mode,dev_t rdev)262 static int clamfs_mknod(const char *path, mode_t mode, dev_t rdev)
263 {
264     int res;
265 
266     const char* fpath = fixpath(path);
267     if (S_ISFIFO(mode))
268         res = mkfifo(fpath, mode);
269     else
270         res = mknod(fpath, mode, rdev);
271     if (res == -1)
272     {
273         delete[] fpath;
274         return -errno;
275     }
276     else
277     res = lchown(fpath, fuse_get_context()->uid, fuse_get_context()->gid);
278     delete[] fpath;
279 
280     return 0;
281 }
282 
283 /*!\brief FUSE mkdir() callback
284    \param path file path
285    \param mode file permissions
286    \returns 0 if mkdir() returns without error on -errno otherwise
287 */
clamfs_mkdir(const char * path,mode_t mode)288 static int clamfs_mkdir(const char *path, mode_t mode)
289 {
290     int res;
291 
292     const char* fpath = fixpath(path);
293     res = mkdir(fpath, mode);
294     if (res == -1)
295     {
296         delete[] fpath;
297         return -errno;
298     }
299     else
300     res = lchown(fpath, fuse_get_context()->uid, fuse_get_context()->gid);
301     delete[] fpath;
302 
303     return 0;
304 }
305 
306 /*!\brief FUSE unlink() callback
307    \param path file path
308    \returns 0 if unlink() returns without error on -errno otherwise
309 */
clamfs_unlink(const char * path)310 static int clamfs_unlink(const char *path)
311 {
312     int res;
313 
314     const char* fpath = fixpath(path);
315     res = unlink(fpath);
316     delete[] fpath;
317     if (res == -1)
318         return -errno;
319 
320     return 0;
321 }
322 
323 /*!\brief FUSE rmdir() callback
324    \param path directory path
325    \returns 0 if rmdir() returns without error on -errno otherwise
326 */
clamfs_rmdir(const char * path)327 static int clamfs_rmdir(const char *path)
328 {
329     int res;
330 
331     const char* fpath = fixpath(path);
332     res = rmdir(fpath);
333     delete[] fpath;
334     if (res == -1)
335         return -errno;
336 
337     return 0;
338 }
339 
340 /*!\brief FUSE symlink() callback
341    \param from symlink name
342    \param to file path
343    \returns 0 if symlink() returns without error on -errno otherwise
344 */
clamfs_symlink(const char * from,const char * to)345 static int clamfs_symlink(const char *from, const char *to)
346 {
347     int res;
348 
349     const char* fto = fixpath(to);
350     res = symlink(from, fto);
351     delete[] fto;
352     if (res == -1)
353         return -errno;
354     else
355     res = lchown(from, fuse_get_context()->uid, fuse_get_context()->gid);
356 
357     return 0;
358 }
359 
360 /*!\brief FUSE rename() callback
361    \param from old file name
362    \param to new file name
363    \returns 0 if rename() returns without error on -errno otherwise
364 */
clamfs_rename(const char * from,const char * to)365 static int clamfs_rename(const char *from, const char *to)
366 {
367     int res;
368 
369     const char* ffrom = fixpath(from);
370     const char* fto = fixpath(to);
371     res = rename(ffrom, fto);
372     delete[] ffrom;
373     delete[] fto;
374     if (res == -1)
375         return -errno;
376 
377     return 0;
378 }
379 
380 /*!\brief FUSE link() callback
381    \param from link name
382    \param to file path
383    \returns 0 if link() returns without error on -errno otherwise
384 */
clamfs_link(const char * from,const char * to)385 static int clamfs_link(const char *from, const char *to)
386 {
387     int res;
388 
389     const char* ffrom = fixpath(from);
390     const char* fto = fixpath(to);
391     res = link(ffrom, fto);
392     delete[] fto;
393     if (res == -1)
394     {
395         delete[] ffrom;
396         return -errno;
397     }
398     else
399         res = lchown(ffrom, fuse_get_context()->uid, fuse_get_context()->gid);
400     delete[] ffrom;
401 
402     return 0;
403 }
404 
405 /*!\brief FUSE chmod() callback
406    \param path file path
407    \param mode file permissions
408    \returns 0 if chmod() returns without error on -errno otherwise
409 */
clamfs_chmod(const char * path,mode_t mode)410 static int clamfs_chmod(const char *path, mode_t mode)
411 {
412     int res;
413 
414     const char* fpath = fixpath(path);
415     res = chmod(fpath, mode);
416     delete[] fpath;
417     if (res == -1)
418         return -errno;
419 
420     return 0;
421 }
422 
423 /*!\brief FUSE chown() callback
424    \param path file path
425    \param uid user id
426    \param gid group id
427    \returns 0 if chown() returns without error on -errno otherwise
428 */
clamfs_chown(const char * path,uid_t uid,gid_t gid)429 static int clamfs_chown(const char *path, uid_t uid, gid_t gid)
430 {
431     int res;
432 
433     const char* fpath = fixpath(path);
434     res = lchown(fpath, uid, gid);
435     delete[] fpath;
436     if (res == -1)
437         return -errno;
438 
439     return 0;
440 }
441 
442 /*!\brief FUSE truncate() callback
443    \param path file path
444    \param size requested size
445    \returns 0 if truncate() returns without error on -errno otherwise
446 */
clamfs_truncate(const char * path,off_t size)447 static int clamfs_truncate(const char *path, off_t size)
448 {
449     int res;
450 
451     const char* fpath = fixpath(path);
452     res = truncate(fpath, size);
453     delete[] fpath;
454     if (res == -1)
455         return -errno;
456 
457     return 0;
458 }
459 
460 /*!\brief FUSE ftruncate() callback
461    \param path file path
462    \param size requested size
463    \param fi information about open files
464    \returns 0 if ftruncate() returns without error on -errno otherwise
465 */
clamfs_ftruncate(const char * path,off_t size,struct fuse_file_info * fi)466 static int clamfs_ftruncate(const char *path, off_t size,
467                          struct fuse_file_info *fi)
468 {
469     int res;
470 
471     (void) path;
472 
473     res = ftruncate((int)fi->fh, size);
474     if (res == -1)
475         return -errno;
476 
477     return 0;
478 }
479 
480 /*!\brief FUSE utime() callback
481    \param path file path
482    \param buf data buffer
483    \returns 0 if utime() returns without error on -errno otherwise
484 */
clamfs_utime(const char * path,struct utimbuf * buf)485 static int clamfs_utime(const char *path, struct utimbuf *buf)
486 {
487     int res;
488 
489     const char* fpath = fixpath(path);
490     res = utime(fpath, buf);
491     delete[] fpath;
492     if (res == -1)
493         return -errno;
494 
495     return 0;
496 }
497 
498 /*!\brief FUSE create() callback
499    \param path file path
500    \param mode file permissions
501    \param fi information about open files
502    \returns 0 if open() returns without error on -errno otherwise
503 */
clamfs_create(const char * path,mode_t mode,struct fuse_file_info * fi)504 static int clamfs_create(const char *path, mode_t mode, struct fuse_file_info *fi)
505 {
506     int res;
507     int fd;
508 
509     const char* fpath = fixpath(path);
510     fd = open(fpath, fi->flags, mode);
511     if (fd == -1)
512     {
513         delete[] fpath;
514         return -errno;
515     }
516     else
517        res = lchown(fpath, fuse_get_context()->uid, fuse_get_context()->gid);
518     delete[] fpath;
519 
520     if (res < 0)
521     {
522         char* username = getusername();
523         char* callername = getcallername();
524         rLog(Warn, "(%s:%d) (%s:%d) %s: lchown() failed: %s",
525                 callername, fuse_get_context()->pid, username, fuse_get_context()->uid,
526                 path, strerror(errno));
527         free(username);
528         free(callername);
529     }
530 
531     fi->fh = (unsigned long) fd;
532     return 0;
533 }
534 
535 /*!\brief Opens file and returns it file descriptor by fi->fh
536    \param path file path
537    \param fi information about open files
538    \returns 0 if open() returns without error on -errno otherwise
539 */
open_backend(const char * path,struct fuse_file_info * fi)540 static inline int open_backend(const char *path, struct fuse_file_info *fi)
541 {
542     int fd;
543 
544     const char* fpath = fixpath(path);
545     fd = open(fpath, fi->flags);
546     delete[] fpath;
547     if (fd == -1)
548         return -errno;
549 
550     fi->fh = (unsigned long) fd;
551     return 0;
552 }
553 
554 /*!\brief FUSE open() callback
555    \param path file path
556    \param fi information about open files
557    \returns result of open_backend() call or -EPERM if virus is detected
558 */
clamfs_open(const char * path,struct fuse_file_info * fi)559 static int clamfs_open(const char *path, struct fuse_file_info *fi)
560 {
561     int ret = 1;
562     bool file_is_blacklisted = false;
563     int scan_result;
564     struct stat file_stat;
565 
566     INC_STAT_COUNTER(openCalled);
567 
568     /*
569      * Dump stats to log periodically
570      */
571     if (stats) {
572         stats->periodicDumpToLog();
573     }
574 
575     /*
576      * Build file path in real filesystem tree
577      */
578     shared_array<char> real_path(new char[strlen(config["root"])+strlen(path)+1]);
579     strcpy(real_path.get(), config["root"]);
580     strcat(real_path.get(), path);
581 
582     /*
583      * Check extension ACL
584      */
585     if (extensions != NULL) {
586         const char *ext = rindex(path, '.'); /* find last dot */
587         if (ext != NULL) {
588             ++ext; /* omit dot */
589             extum_t::const_iterator extumConstIter;
590             extumConstIter = extensions->find(ext);
591             if (extumConstIter != extensions->end()) {
592                 switch (extumConstIter->second) {
593                     case whitelisted:
594                         {
595                             INC_STAT_COUNTER(whitelistHit);
596                             char* username = getusername();
597                             char* callername = getcallername();
598                             rLog(Warn, "(%s:%d) (%s:%d) %s: excluded from anti-virus scan because extension whitelisted ",
599                                     callername, fuse_get_context()->pid, username, fuse_get_context()->uid, path);
600                             free(username);
601                             free(callername);
602                             INC_STAT_COUNTER(openAllowed);
603                             return open_backend(path, fi);
604                         }
605                     case blacklisted:
606                         {
607                             INC_STAT_COUNTER(blacklistHit);
608                             file_is_blacklisted = true;
609                             char* username = getusername();
610                             char* callername = getcallername();
611                             rLog(Warn, "(%s:%d) (%s:%d) %s: forced anti-virus scan because extension blacklisted ",
612                                     callername, fuse_get_context()->pid, username, fuse_get_context()->uid, path);
613                             free(username);
614                             free(callername);
615                             break;
616                         }
617                     default:
618                         DEBUG("Extension found in unordered_map, but with unknown ACL type");
619                 }
620             } else {
621                 DEBUG("Extension not found in unordered_map");
622             }
623         }
624     }
625 
626     /*
627      * Check file size (if option defined)
628      */
629     if ((config["maximal-size"] != NULL) && (file_is_blacklisted == false)) {
630         ret = lstat(real_path.get(), &file_stat);
631         if (!ret) { /* got file stat without error */
632             if (file_stat.st_size > atoi(config["maximal-size"])) { /* file too big */
633                 INC_STAT_COUNTER(tooBigFile);
634                 char* username = getusername();
635                 char* callername = getcallername();
636                 rLog(Warn, "(%s:%d) (%s:%d) %s: excluded from anti-virus scan because file is too big (file size: %ld bytes)",
637                         callername, fuse_get_context()->pid, username, fuse_get_context()->uid, path, (long int)file_stat.st_size);
638                 free(username);
639                 free(callername);
640                 INC_STAT_COUNTER(openAllowed);
641                 return open_backend(path, fi);
642             }
643         }
644     }
645 
646     /*
647      * Check if file is in cache
648      */
649     if (cache != NULL) { /* only if cache initalized */
650         if (ret)
651             ret = lstat(real_path.get(), &file_stat);
652         if (!ret) { /* got file stat without error */
653 
654             Poco::SharedPtr<CachedResult> ptr_val;
655 
656             if ((ptr_val = cache->get(file_stat.st_ino))) {
657                 INC_STAT_COUNTER(earlyCacheHit);
658                 DEBUG("early cache hit for inode %ld", (unsigned long)file_stat.st_ino);
659 
660                 if (ptr_val->scanTimestamp == file_stat.st_mtime) {
661                     INC_STAT_COUNTER(lateCacheHit);
662                     DEBUG("late cache hit for inode %ld", (unsigned long)file_stat.st_ino);
663 
664                     /* file scanned and not changed, was it clean? */
665                     if (ptr_val->isClean) {
666                         INC_STAT_COUNTER(openAllowed);
667                         return open_backend(path, fi); /* Yes, it was */
668                     } else {
669                         INC_STAT_COUNTER(openDenied);
670                         return -EPERM; /* No, that file was infected */
671                     }
672                 } else {
673                     INC_STAT_COUNTER(lateCacheMiss);
674                     DEBUG("late cache miss for inode %ld", (unsigned long)file_stat.st_ino);
675 
676                     /*
677                      * Scan file when file it was changed
678                      */
679                     scan_result = ClamavScanFile(real_path.get());
680 
681                     /*
682                      * Check for scan results and update cache
683                      */
684                     ptr_val->scanTimestamp = file_stat.st_mtime;
685                     if (scan_result == 1) { /* virus found */
686                         ptr_val->isClean = false;
687                         INC_STAT_COUNTER(openDenied);
688                         return -EPERM;
689                     } else if(scan_result == 0) {
690                         ptr_val->isClean = true;
691                         INC_STAT_COUNTER(openAllowed);
692                         /* file is clean, open it */
693                         return open_backend(path, fi);
694                     } else {
695                         INC_STAT_COUNTER(scanFailed);
696                         INC_STAT_COUNTER(openDenied);
697                         cache->remove(file_stat.st_ino);
698                         return -EPERM;
699                     }
700                 }
701 
702             } else {
703                 INC_STAT_COUNTER(earlyCacheMiss);
704                 DEBUG("early cache miss for inode %ld", (unsigned long)file_stat.st_ino);
705 
706                 /*
707                  * Scan file when file is not in cache
708                  */
709                 scan_result = ClamavScanFile(real_path.get());
710 
711                 /*
712                  * Check for scan results
713                  */
714                 if (scan_result == 1) { /* virus found */
715                     CachedResult result(false, file_stat.st_mtime);
716                     cache->add(file_stat.st_ino, result);
717                     INC_STAT_COUNTER(openDenied);
718                     return -EPERM;
719                 } else if(scan_result == 0) {
720                     CachedResult result(true, file_stat.st_mtime);
721                     cache->add(file_stat.st_ino, result);
722                     INC_STAT_COUNTER(openAllowed);
723                     /* file is clean, open it */
724                     return open_backend(path, fi);
725                 } else {
726                     INC_STAT_COUNTER(scanFailed);
727                     INC_STAT_COUNTER(openDenied);
728                     cache->remove(file_stat.st_ino);
729                     return -EPERM;
730                 }
731 
732             }
733 
734         }
735     }
736 
737     /*
738      * Scan file when cache is not available
739      */
740     scan_result = ClamavScanFile(real_path.get());
741 
742     /*
743      * Check for scan results
744      */
745     if (scan_result == 1) { /* return -EPERM error if virus was found */
746         INC_STAT_COUNTER(openDenied);
747         return -EPERM;
748     } else if(scan_result != 0) {
749         INC_STAT_COUNTER(scanFailed);
750         INC_STAT_COUNTER(openDenied);
751         return -EPERM;
752     }
753 
754     /*
755      * If no virus detected continue as usual
756      */
757     INC_STAT_COUNTER(openAllowed);
758     return open_backend(path, fi);
759 }
760 
761 /*!\brief FUSE read() callback
762    \param path file path
763    \param buf data buffer
764    \param size buffer size
765    \param offset read offset
766    \param fi information about open files
767    \returns 0 if pread() returns without error on -errno otherwise
768 */
clamfs_read(const char * path,char * buf,size_t size,off_t offset,struct fuse_file_info * fi)769 static int clamfs_read(const char *path, char *buf, size_t size, off_t offset,
770                     struct fuse_file_info *fi)
771 {
772     ssize_t res;
773 
774     (void) path;
775     res = pread((int)fi->fh, buf, size, offset);
776     if (res == -1)
777         res = -errno;
778 
779     return (int)res;
780 }
781 
782 /*!\brief FUSE write() callback
783    \param path file path
784    \param buf data buffer
785    \param size buffer size
786    \param offset read offset
787    \param fi information about open files
788    \returns 0 if pwrite() returns without error on -errno otherwise
789 */
clamfs_write(const char * path,const char * buf,size_t size,off_t offset,struct fuse_file_info * fi)790 static int clamfs_write(const char *path, const char *buf, size_t size,
791                      off_t offset, struct fuse_file_info *fi)
792 {
793     ssize_t res;
794 
795     (void) path;
796     res = pwrite((int)fi->fh, buf, size, offset);
797     if (res == -1)
798         res = -errno;
799 
800     return (int)res;
801 }
802 
803 /*!\brief FUSE statfs() callback
804    \param path file path
805    \param stbuf data buffer
806    \returns 0 if statvfs() returns without error on -errno otherwise
807 */
clamfs_statfs(const char * path,struct statvfs * stbuf)808 static int clamfs_statfs(const char *path, struct statvfs *stbuf)
809 {
810     int res;
811 
812     const char* fpath = fixpath(path);
813     res = statvfs(fpath, stbuf);
814     delete[] fpath;
815     if (res == -1)
816         return -errno;
817 
818     return 0;
819 }
820 
821 /*!\brief FUSE release() callback
822    \param path file path
823    \param fi information about open files
824    \returns always 0
825 */
clamfs_release(const char * path,struct fuse_file_info * fi)826 static int clamfs_release(const char *path, struct fuse_file_info *fi)
827 {
828     (void) path;
829     close((int)fi->fh);
830 
831     return 0;
832 }
833 
834 /*!\brief FUSE fsync() callback
835    \param path file path
836    \param isdatasync data sync flag
837    \param fi information about open files
838    \returns 0 if f{data}sync() returns without error on -errno otherwise
839 */
clamfs_fsync(const char * path,int isdatasync,struct fuse_file_info * fi)840 static int clamfs_fsync(const char *path, int isdatasync,
841                      struct fuse_file_info *fi)
842 {
843     int res;
844     (void) path;
845 
846 #ifndef HAVE_FDATASYNC
847     (void) isdatasync;
848 #else
849     if (isdatasync)
850         res = fdatasync((int)fi->fh);
851     else
852 #endif
853         res = fsync((int)fi->fh);
854     if (res == -1)
855         return -errno;
856 
857     return 0;
858 }
859 
860 #ifdef HAVE_SETXATTR
861 /*!\brief FUSE setxattr() callback
862    \param path file path
863    \param name extended attribute name
864    \param value extended attribute value
865    \param size size of value
866    \param flags operation options
867    \returns 0 if lsetxattr() returns without error on -errno otherwise
868 */
clamfs_setxattr(const char * path,const char * name,const char * value,size_t size,int flags)869 static int clamfs_setxattr(const char *path, const char *name, const char *value,
870                         size_t size, int flags)
871 {
872     int res;
873     const char* fpath = fixpath(path);
874     res = lsetxattr(fpath, name, value, size, flags);
875     delete[] fpath;
876     if (res == -1)
877         return -errno;
878     return 0;
879 }
880 
881 /*!\brief FUSE getxattr() callback
882    \param path file path
883    \param name extended attribute name
884    \param value extended attribute value
885    \param size size of value
886    \returns 0 if lgetxattr() returns without error on -errno otherwise
887 */
clamfs_getxattr(const char * path,const char * name,char * value,size_t size)888 static int clamfs_getxattr(const char *path, const char *name, char *value,
889                     size_t size)
890 {
891     ssize_t res;
892     const char* fpath = fixpath(path);
893     res = lgetxattr(fpath, name, value, size);
894     delete[] fpath;
895     if (res == -1)
896         return -errno;
897     return (int)res;
898 }
899 
900 /*!\brief FUSE listxattr() callback
901    \param path file path
902    \param list list of extended attribute names
903    \param size size of list
904    \returns 0 if llistxattr() returns without error on -errno otherwise
905 */
clamfs_listxattr(const char * path,char * list,size_t size)906 static int clamfs_listxattr(const char *path, char *list, size_t size)
907 {
908     ssize_t res;
909     const char* fpath = fixpath(path);
910     res = llistxattr(fpath, list, size);
911     delete[] fpath;
912     if (res == -1)
913         return -errno;
914     return (int)res;
915 }
916 
917 /*!\brief FUSE removexattr() callback
918    \param path file path
919    \param name extended attribute name
920    \returns 0 if lremovexattr() returns without error on -errno otherwise
921 */
clamfs_removexattr(const char * path,const char * name)922 static int clamfs_removexattr(const char *path, const char *name)
923 {
924     int res;
925     const char* fpath = fixpath(path);
926     res = lremovexattr(fpath, name);
927     delete[] fpath;
928     if (res == -1)
929         return -errno;
930     return 0;
931 }
932 #endif /* HAVE_SETXATTR */
933 
934 /*!\brief ClamFS main()
935    \param argc arguments counter
936    \param argv arguments array
937    \returns 0 on success, error code otherwise
938 */
939 int main(int argc, char *argv[]);
main(int argc,char * argv[])940 int main(int argc, char *argv[])
941 {
942     int ret;
943     int fuse_argc;
944     char **fuse_argv;
945     fuse_operations clamfs_oper;
946 
947     /*
948      * Make sure all pointers are initialy set to NULL
949      */
950     memset(&clamfs_oper, 0, sizeof(fuse_operations));
951 
952     clamfs_oper.getattr     = clamfs_getattr;
953     clamfs_oper.fgetattr    = clamfs_fgetattr;
954     clamfs_oper.access      = clamfs_access;
955     clamfs_oper.readlink    = clamfs_readlink;
956     clamfs_oper.opendir     = clamfs_opendir;
957     clamfs_oper.readdir     = clamfs_readdir;
958     clamfs_oper.releasedir  = clamfs_releasedir;
959     clamfs_oper.mknod       = clamfs_mknod;
960     clamfs_oper.mkdir       = clamfs_mkdir;
961     clamfs_oper.symlink     = clamfs_symlink;
962     clamfs_oper.unlink      = clamfs_unlink;
963     clamfs_oper.rmdir       = clamfs_rmdir;
964     clamfs_oper.rename      = clamfs_rename;
965     clamfs_oper.link        = clamfs_link;
966     clamfs_oper.chmod       = clamfs_chmod;
967     clamfs_oper.chown       = clamfs_chown;
968     clamfs_oper.truncate    = clamfs_truncate;
969     clamfs_oper.ftruncate   = clamfs_ftruncate;
970     clamfs_oper.utime       = clamfs_utime;
971     clamfs_oper.create      = clamfs_create;
972     clamfs_oper.open        = clamfs_open;
973     clamfs_oper.read        = clamfs_read;
974     clamfs_oper.write       = clamfs_write;
975     clamfs_oper.statfs      = clamfs_statfs;
976     clamfs_oper.release     = clamfs_release;
977     clamfs_oper.fsync       = clamfs_fsync;
978 #ifdef HAVE_SETXATTR
979     clamfs_oper.setxattr    = clamfs_setxattr;
980     clamfs_oper.getxattr    = clamfs_getxattr;
981     clamfs_oper.listxattr   = clamfs_listxattr;
982     clamfs_oper.removexattr = clamfs_removexattr;
983 #endif
984 
985     umask(0);
986 
987     /*
988      * Open RLog
989      */
990     RLogInit(argc, argv);
991     RLogOpenStdio();
992 
993     rLog(Info, "ClamFS v" VERSION " (git-" PACKAGE_VERSION_GIT_DESCRIBE ")");
994     rLog(Info, "Copyright (c) 2007-2019 Krzysztof Burghardt <krzysztof@burghardt.pl>");
995     rLog(Info, "https://github.com/burghardt/clamfs");
996 
997     /*
998      * Check if we have one argument (other arguments are assumed RLog related)
999      */
1000     if ((argc < 2) ||
1001         ((argc > 1) &&
1002          ((strncmp(argv[1], "-h", strlen("-h")) == 0) ||
1003           (strncmp(argv[1], "--help", strlen("--help")) == 0)))) {
1004         rLog(Warn, "ClamFS need to be invoked with one parameter - location of configuration file");
1005         rLog(Warn, "Example: %s /etc/clamfs/home.xml", argv[0]);
1006         return EXIT_FAILURE;
1007     }
1008 
1009     /*
1010      * Load XML configuration file, parse it and fill in clamfs::config
1011      */
1012     ConfigParserXML cp(argv[1]);
1013     if (config.size() == 0) {
1014         rLog(Warn, "No configuration has been loaded");
1015         return EXIT_FAILURE;
1016     }
1017 
1018 #ifndef NDEBUG
1019     /*
1020      * Dump configuration form clamfs::config
1021      */
1022     cout << "--- begin of config dump ---" << endl;
1023     config_t::iterator m_begin = config.begin();
1024     config_t::iterator m_end   = config.end();
1025     while (m_begin != m_end) {
1026         cout << (*m_begin).first << ": " << (*m_begin).second << endl;
1027         ++m_begin;
1028     }
1029     cout << "--- end of config dump ---" << endl;
1030 #endif
1031 
1032     /*
1033      * Check if minimal set of configuration options has been defined
1034      * (any other option can be omitted but this three are mandatory)
1035      */
1036     if ((config["socket"] == NULL) ||
1037         (config["root"] == NULL) ||
1038         (config["mountpoint"] == NULL)) {
1039         rLog(Warn, "socket, root and mountpoint must be defined");
1040         return EXIT_FAILURE;
1041     }
1042 
1043     /*
1044      * Build argv for libFUSE
1045      */
1046     fuse_argv = new char *[FUSE_MAX_ARGS];
1047     memset(fuse_argv, 0, FUSE_MAX_ARGS * sizeof(char *)); /* set pointers to NULL */
1048     fuse_argc = 0;
1049     fuse_argv[fuse_argc++] = strdup(argv[0]); /* copy program name */
1050     fuse_argv[fuse_argc++] = strdup(config["mountpoint"]); /* set mountpoint */
1051 
1052     if ((config["public"] != NULL) && /* public */
1053         (strncmp(config["public"], "yes", 3) == 0)) {
1054         fuse_argv[fuse_argc++] = strdup("-o");
1055         if ((config["nonempty"] != NULL) && /* public and nonempty */
1056             (strncmp(config["nonempty"], "yes", 3) == 0)) {
1057 #ifdef HAVE_SETXATTR_WITH_ACL_PATCH
1058             fuse_argv[fuse_argc++] =
1059                 strdup("allow_other,default_permissions,acl,nonempty");
1060 #else
1061             fuse_argv[fuse_argc++] =
1062                 strdup("allow_other,default_permissions,nonempty");
1063 #endif
1064         } else { /* public without nonempty */
1065 #ifdef HAVE_SETXATTR_WITH_ACL_PATCH
1066             fuse_argv[fuse_argc++] = strdup("allow_other,default_permissions,acl");
1067 #else
1068             fuse_argv[fuse_argc++] = strdup("allow_other,default_permissions");
1069 #endif
1070         }
1071     } else if ((config["nonempty"] != NULL) && /* private and nonempty */
1072         (strncmp(config["nonempty"], "yes", 3) == 0)) {
1073         fuse_argv[fuse_argc++] = strdup("-o");
1074 #ifdef HAVE_SETXATTR_WITH_ACL_PATCH
1075         fuse_argv[fuse_argc++] = strdup("nonempty,default_permissions,acl");
1076 #else
1077         fuse_argv[fuse_argc++] = strdup("nonempty");
1078 #endif
1079 #ifdef HAVE_SETXATTR_WITH_ACL_PATCH
1080     } else {
1081         fuse_argv[fuse_argc++] = strdup("-o");
1082         fuse_argv[fuse_argc++] = strdup("default_permissions,acl");
1083 #endif
1084     }
1085 
1086     if ((config["readonly"] != NULL) &&
1087         (strncmp(config["readonly"], "yes", 3) == 0))
1088         fuse_argv[fuse_argc++] = strdup("-r");
1089 
1090     if ((config["threads"] != NULL) &&
1091         (strncmp(config["threads"], "no", 2) == 0))
1092         fuse_argv[fuse_argc++] = strdup("-s");
1093 
1094     if ((config["fork"] != NULL) &&
1095         (strncmp(config["fork"], "no", 2) == 0))
1096         fuse_argv[fuse_argc++] = strdup("-f");
1097 
1098     /*
1099      * Change our current directory to "root" of our filesystem
1100      */
1101     rLog(Info,"chdir to our 'root' (%s)",config["root"]);
1102     if (chdir(config["root"]) < 0) {
1103         int err = errno; /* copy errno, RLog can overwrite */
1104         rLog(Warn, "chdir failed: %s", strerror(err));
1105         return err;
1106     }
1107     savefd = open(".", 0);
1108 
1109     /*
1110      * Check if clamd is available for clamfs only if check option is not "no"
1111      */
1112     if ((config["check"] == NULL) ||
1113         (strncmp(config["check"], "no", 2) != 0)) {
1114         if ((ret = OpenClamav(config["socket"])) != 0) {
1115             rLog(Warn, "cannot start without running clamd, make sure it works");
1116             return ret;
1117         }
1118 
1119         if ((ret = PingClamav()) != 0) {
1120             rLog(Warn, "cannot start without running clamd, make sure it works");
1121             return ret;
1122         }
1123         CloseClamav();
1124     }
1125 
1126     /*
1127      * Initialize cache
1128      */
1129     if ((config["entries"] != NULL) &&
1130         (atol(config["entries"]) <= 0)) {
1131         rLog(Warn, "maximal cache entries count cannot be =< 0");
1132         return EXIT_FAILURE;
1133     }
1134     if ((config["expire"] != NULL) &&
1135         (atol(config["expire"]) <= 0)) {
1136         rLog(Warn, "maximal cache expire value cannot be =< 0");
1137         return EXIT_FAILURE;
1138     }
1139     if ((config["entries"] != NULL) &&
1140         (config["expire"] != NULL)) {
1141         rLog(Info, "ScanCache initialized, %s entries will be kept for %s ms max.",
1142             config["entries"], config["expire"]);
1143         cache = new ScanCache(atol(config["entries"]), atol(config["expire"]));
1144     } else {
1145         rLog(Warn, "ScanCache disabled, expect poor performance");
1146     }
1147 
1148     /*
1149      * Initialize stats
1150      */
1151     if ((config["every"] != NULL) &&
1152         (atol(config["every"]) != 0)) {
1153         rLog(Info, "Statistics module initialized");
1154         stats = new Stats(atol(config["every"]));
1155     } else if ((config["atexit"] != NULL) &&
1156         (strncmp(config["atexit"], "yes", 3) == 0)) {
1157         rLog(Info, "Statistics module initialized");
1158         stats = new Stats(0);
1159     } else {
1160         rLog(Info, "Statistics module disabled");
1161     }
1162 
1163     if ((config["memory"] != NULL) &&
1164         (strncmp(config["memory"], "yes", 3) == 0)) {
1165         stats->enableMemoryStats();
1166     }
1167 
1168     /*
1169      * Open configured logging target
1170      */
1171     if (config["method"] != NULL) {
1172         if (strncmp(config["method"], "syslog", 6) == 0) {
1173             RLogOpenSyslog();
1174             RLogCloseStdio();
1175         } else if (strncmp(config["method"], "file", 4) == 0) {
1176             if (config["filename"] != NULL) {
1177                 RLogOpenLogFile(config["filename"]);
1178                 RLogCloseStdio();
1179             } else {
1180                 rLog(Warn, "logging method 'file' chosen, but no log 'filename' given");
1181                 return EXIT_FAILURE;
1182             }
1183         }
1184     }
1185 
1186     /*
1187      * Print size of extensions ACL
1188      */
1189     if (extensions != NULL) {
1190         rLog(Info, "extension ACL size is %d entries", (int)extensions->size());
1191     }
1192 
1193     /*
1194      * Start FUSE
1195      */
1196     ret = fuse_main(fuse_argc, fuse_argv, &clamfs_oper, NULL);
1197 
1198     for (unsigned int i = 0; i < FUSE_MAX_ARGS; ++i)
1199         if (fuse_argv[i])
1200             free(fuse_argv[i]);
1201     delete[] fuse_argv;
1202 
1203     if (cache) {
1204         rLog(Info, "deleting cache");
1205         delete cache;
1206         cache = NULL;
1207     }
1208 
1209     if (stats) {
1210         if ((config["atexit"] != NULL) &&
1211             (strncmp(config["atexit"], "yes", 3) == 0)) {
1212             stats->dumpFilesystemStatsToLog();
1213             if (stats->memoryStats)
1214                 stats->dumpMemoryStatsToLog();
1215         }
1216 
1217         rLog(Info, "deleting stats");
1218         delete stats;
1219         stats = NULL;
1220     }
1221 
1222     if (extensions != NULL) {
1223         rLog(Info, "deleting extensions ACL");
1224         delete extensions;
1225         extensions = NULL;
1226     }
1227 
1228     rLog(Info, "closing logging targets");
1229     RLogCloseLogFile();
1230 
1231     rLog(Warn,"exiting");
1232 #ifdef DMALLOC
1233     dmalloc_verify(0L);
1234 #endif
1235     return ret;
1236 }
1237 
1238 } /* extern "C" */
1239 
1240 } /* namespace clamfs */
1241 
1242 /* EoF */
1243