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