diff options
Diffstat (limited to 'source3/modules')
-rw-r--r-- | source3/modules/vfs_scannedonly.c | 995 |
1 files changed, 995 insertions, 0 deletions
diff --git a/source3/modules/vfs_scannedonly.c b/source3/modules/vfs_scannedonly.c new file mode 100644 index 0000000000..8699bff162 --- /dev/null +++ b/source3/modules/vfs_scannedonly.c @@ -0,0 +1,995 @@ +/* + * scannedonly VFS module for Samba 3.5 + * + * Copyright 2007,2008,2009 (C) Olivier Sessink + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * ABOUT SCANNEDONLY + * + * scannedonly implements a 'filter' like vfs module that talks over a + * unix domain socket or over UDP to a anti-virus engine. + * + * files that are clean have a corresponding .scanned:{filename} file + * in the same directory. So why the .scanned: files? They take up + * only an inode, because they are 0 bytes. To test if the file is + * scanned only a stat() call on the filesystem is needed which is + * very quick compared to a database lookup. All modern filesystems + * use database technology such as balanced trees for lookups anyway. + * The number of inodes in modern filesystems is also not limiting + * anymore. The .scanned: files are also easy scriptable. You can + * remove them with a simple find command or create them with a + * simple touch command. Extended filesystem attributes have similar + * properties, but are not supported on all filesystems, so that + * would limit the usage of the module (and attributes are not as + * easily scriptable) + * + * files that are not clean are sent to the AV-engine. Only the + * filename is sent over the socket. The protocol is very simple: + * a newline separated list of filenames inside each datagram. + * + * a file AV-scan may be requested multiple times, the AV-engine + * should also check if the file has been scanned already. Requests + * can also be dropped by the AV-engine (and we thus don't need the + * reliability of TCP). + * + */ + +#include "includes.h" + +#include "config.h" + +#define SENDBUFFERSIZE 1450 + +struct Tscannedonly { + int socket; + int domain_socket; + int portnum; + int scanning_message_len; + int recheck_time_open; + int recheck_tries_open; + int recheck_size_open; + int recheck_time_readdir; + int recheck_tries_readdir; + bool show_special_files; + bool rm_hidden_files_on_rmdir; + bool hide_nonscanned_files; + bool allow_nonscanned_files; + char *socketname; + char *scanhost; + char *scanning_message; + char *p_scanned; /* prefix for scanned files */ + char *p_virus; /* prefix for virus containing files */ + char *p_failed; /* prefix for failed to scan files */ + char gsendbuffer[SENDBUFFERSIZE + 1]; +}; + +#define STRUCTSCANO(var) ((struct Tscannedonly *)var) + +struct scannedonly_DIR { + char *base; + int notify_loop_done; + SMB_STRUCT_DIR *DIR; +}; +#define SCANNEDONLY_DEBUG 9 +/*********************/ +/* utility functions */ +/*********************/ + +static char *real_path_from_notify_path(TALLOC_CTX *ctx, + struct Tscannedonly *so, + const char *path) +{ + char *name; + int len, pathlen; + + name = strrchr(path, '/'); + if (!name) { + return NULL; + } + pathlen = name - path; + name++; + len = strlen(name); + if (len <= so->scanning_message_len) { + return NULL; + } + + if (strcmp(name + (len - so->scanning_message_len), + so->scanning_message) != 0) { + return NULL; + } + + return talloc_strndup(ctx,path, + pathlen + len - so->scanning_message_len); +} + +static char *cachefile_name(TALLOC_CTX *ctx, + const char *shortname, + const char *base, + const char *p_scanned) +{ + return talloc_asprintf(ctx, "%s%s%s", base, p_scanned, shortname); +} + +static char *name_w_ending_slash(TALLOC_CTX *ctx, const char *name) +{ + int len = strlen(name); + if (name[len - 1] == '/') { + return talloc_strdup(ctx,name); + } else { + return talloc_asprintf(ctx, "%s/", name); + } +} + +static char *cachefile_name_f_fullpath(TALLOC_CTX *ctx, + const char *fullpath, + const char *p_scanned) +{ + const char *base; + char *tmp, *cachefile, *shortname; + tmp = strrchr(fullpath, '/'); + if (tmp) { + base = talloc_strndup(ctx, fullpath, (tmp - fullpath) + 1); + shortname = tmp + 1; + } else { + base = ""; + shortname = (char *)fullpath; + } + cachefile = cachefile_name(ctx, shortname, base, p_scanned); + DEBUG(SCANNEDONLY_DEBUG, + ("cachefile_name_f_fullpath cachefile=%s\n", cachefile)); + return cachefile; +} + +static char *path_plus_name(TALLOC_CTX *ctx, const char *base, + const char *filename) +{ + return talloc_asprintf(ctx, "%s%s", base,filename); +} + +static char *construct_full_path(TALLOC_CTX *ctx, vfs_handle_struct * handle, + const char *somepath, bool ending_slash) +{ + char *tmp; + + if (!somepath) { + return NULL; + } + if (somepath[0] == '/') { + if (ending_slash) { + return name_w_ending_slash(ctx,somepath); + } + return talloc_strdup(ctx,somepath); + } + tmp=(char *)somepath; + if (tmp[0]=='.'&&tmp[1]=='/') { + tmp+=2; + } + /* vfs_GetWd() seems to return a path with a slash */ + if (ending_slash) { + return talloc_asprintf(ctx, "%s%s/", + vfs_GetWd(ctx, handle->conn),tmp); + } + return talloc_asprintf(ctx, "%s%s", + vfs_GetWd(ctx, handle->conn),tmp); +} + +static int connect_to_scanner(vfs_handle_struct * handle) +{ + struct Tscannedonly *so = (struct Tscannedonly *)handle->data; + + if (so->domain_socket) { + struct sockaddr_un saun; + DEBUG(SCANNEDONLY_DEBUG, ("socket=%s\n", so->socketname)); + if ((so->socket = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { + DEBUG(2, ("failed to create socket %s\n", + so->socketname)); + return -1; + } + saun.sun_family = AF_UNIX; + strncpy(saun.sun_path, so->socketname, + strlen(so->socketname) + 1); + if (connect(so->socket, (struct sockaddr *)(void *)&saun, + SUN_LEN(&saun)) < 0) { + DEBUG(2, ("failed to connect to socket %s\n", + so->socketname)); + return -1; + } + DEBUG(SCANNEDONLY_DEBUG,("bound %s to socket %d\n", + saun.sun_path, so->socket)); + + } else { + so->socket = open_udp_socket(so->scanhost, so->portnum); + if (so->socket < 0) { + DEBUG(2,("failed to open UDP socket to %s:%d\n", + so->scanhost,so->portnum)); + return -1; + } + } + + {/* increasing the socket buffer is done because we have large bursts + of UDP packets or DGRAM's on a domain socket whenever we hit a + large directory with lots of unscanned files. */ + int sndsize; + socklen_t size = sizeof(int); + getsockopt(so->socket, SOL_SOCKET, SO_RCVBUF, + (char *)&sndsize, &size); + DEBUG(SCANNEDONLY_DEBUG, ("current socket buffer size=%d\n", + sndsize)); + sndsize = 262144; + if (setsockopt(so->socket, SOL_SOCKET, SO_RCVBUF, + (char *)&sndsize, + (int)sizeof(sndsize)) != 0) { + DEBUG(SCANNEDONLY_DEBUG, + ("error setting socket buffer %s (%d)\n", + strerror(errno), errno)); + } + } + set_blocking(so->socket, false); + return 0; +} + +static void flush_sendbuffer(vfs_handle_struct * handle) +{ + struct Tscannedonly *so = (struct Tscannedonly *)handle->data; + int ret, len, loop = 10; + if (so->gsendbuffer[0] == '\0') { + return; + } + + do { + loop--; + len = strlen(so->gsendbuffer); + ret = send(so->socket, so->gsendbuffer, len, MSG_DONTWAIT); + if (ret == len) { + so->gsendbuffer[0] = '\0'; + break; + } + if (ret == -1) { + DEBUG(3,("scannedonly flush_sendbuffer: " + "error sending on socket %d to scanner:" + " %s (%d)\n", + so->socket, strerror(errno), errno)); + if (errno == ECONNREFUSED || errno == ENOTCONN + || errno == ECONNRESET) { + if (connect_to_scanner(handle) == -1) + break; /* connecting fails, abort */ + /* try again */ + } else if (errno != EINTR) { + /* on EINTR we just try again, all remaining + other errors we log the error + and try again ONCE */ + loop = 1; + DEBUG(3,("scannedonly flush_sendbuffer: " + "error sending data to scanner: %s " + "(%d)\n", strerror(errno), errno)); + } + } else { + /* --> partial write: Resend all filenames that were + not or not completely written. a partial filename + written means the filename will not arrive correctly, + so resend it completely */ + int pos = 0; + while (pos < len) { + char *tmp = strchr(so->gsendbuffer+pos, '\n'); + if (tmp && tmp - so->gsendbuffer < ret) + pos = tmp - so->gsendbuffer + 1; + else + break; + } + memmove(so->gsendbuffer, so->gsendbuffer + pos, + SENDBUFFERSIZE - ret); + /* now try again */ + } + } while (loop > 0); + + if (so->gsendbuffer[0] != '\0') { + DEBUG(2, + ("scannedonly flush_sendbuffer: " + "failed to send files to AV scanner, " + "discarding files.")); + so->gsendbuffer[0] = '\0'; + } +} + +static void notify_scanner(vfs_handle_struct * handle, const char *scanfile) +{ + char *tmp; + int tmplen, gsendlen; + struct Tscannedonly *so = (struct Tscannedonly *)handle->data; + TALLOC_CTX *ctx=talloc_tos(); + if (scanfile[0] != '/') { + tmp = construct_full_path(ctx,handle, scanfile, false); + } else { + tmp = (char *)scanfile; + } + tmplen = strlen(tmp); + gsendlen = strlen(so->gsendbuffer); + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly notify_scanner: tmp=%s, tmplen=%d, gsendlen=%d\n", + tmp, tmplen, gsendlen)); + if (gsendlen + tmplen >= SENDBUFFERSIZE) { + flush_sendbuffer(handle); + } + strncat(so->gsendbuffer, tmp, tmplen); + strncat(so->gsendbuffer, "\n", 1); +} + +static bool is_scannedonly_file(struct Tscannedonly *so, const char *shortname) +{ + if (shortname[0]!='.') { + return false; + } + if (strncmp(shortname, so->p_scanned, strlen(so->p_scanned)) == 0) { + return true; + } + if (strncmp(shortname, so->p_virus, strlen(so->p_virus)) == 0) { + return true; + } + if (strncmp(shortname, so->p_failed, strlen(so->p_failed)) == 0) { + return true; + } + return false; +} + +static bool timespec_is_newer(struct timespec *base, struct timespec *test) +{ + return timespec_compare(base,test) < 0; +} + +/* +vfs_handle_struct *handle the scannedonly handle +scannedonly_DIR * sDIR the scannedonly struct if called from _readdir() +or NULL +fullpath is a full path starting from / or a relative path to the +current working directory +shortname is the filename without directory components +basename, is the directory without file name component +allow_nonexistant return TRUE if stat() on the requested file fails +recheck_time, the time in milliseconds to wait for the daemon to +create a .scanned file +recheck_tries, the number of tries to wait +recheck_size, size in Kb of files that should not be waited for +loop : boolean if we should try to loop over all files in the directory +and send a notify to the scanner for all files that need scanning +*/ +static bool scannedonly_allow_access(vfs_handle_struct * handle, + struct scannedonly_DIR *sDIR, + struct smb_filename *smb_fname, + const char *shortname, + const char *base_name, + int allow_nonexistant, + int recheck_time, int recheck_tries, + int recheck_size, int loop) +{ + struct smb_filename *cache_smb_fname; + TALLOC_CTX *ctx=talloc_tos(); + char *cachefile; + int retval; + int didloop; + DEBUG(SCANNEDONLY_DEBUG, + ("smb_fname->base_name=%s, shortname=%s, base_name=%s\n" + ,smb_fname->base_name,shortname,base_name)); + + if (ISDOT(shortname) || ISDOTDOT(shortname)) { + return true; + } + if (is_scannedonly_file(STRUCTSCANO(handle->data), shortname)) { + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_allow_access, %s is a scannedonly file, " + "return 0\n", shortname)); + return false; + } + + if (!VALID_STAT(smb_fname->st)) { + DEBUG(SCANNEDONLY_DEBUG,("stat %s\n",smb_fname->base_name)); + retval = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (retval != 0) { + /* failed to stat this file?!? --> hide it */ + DEBUG(SCANNEDONLY_DEBUG,("no valid stat, return" + " allow_nonexistant=%d\n", + allow_nonexistant)); + return allow_nonexistant; + } + } + if (!S_ISREG(smb_fname->st.st_ex_mode)) { + DEBUG(SCANNEDONLY_DEBUG, + ("%s is not a regular file, ISDIR=%d\n", + smb_fname->base_name, + S_ISDIR(smb_fname->st.st_ex_mode))); + return (STRUCTSCANO(handle->data)-> + show_special_files || + S_ISDIR(smb_fname->st.st_ex_mode)); + } + if (smb_fname->st.st_ex_size == 0) { + DEBUG(SCANNEDONLY_DEBUG,("empty file, return 1\n")); + return true; /* empty files cannot contain viruses ! */ + } + cachefile = cachefile_name(ctx, + shortname, + base_name, + STRUCTSCANO(handle->data)->p_scanned); + create_synthetic_smb_fname(ctx, cachefile,NULL,NULL,&cache_smb_fname); + if (!VALID_STAT(cache_smb_fname->st)) { + retval = SMB_VFS_NEXT_STAT(handle, cache_smb_fname); + } + if (retval == 0 && VALID_STAT(cache_smb_fname->st)) { + if (timespec_is_newer(&smb_fname->st.st_ex_mtime, + &cache_smb_fname->st.st_ex_mtime)) { + talloc_free(cache_smb_fname); + return true; + } + /* no cachefile or too old */ + SMB_VFS_NEXT_UNLINK(handle, cache_smb_fname); + retval = -1; + } + + notify_scanner(handle, smb_fname->base_name); + + didloop = 0; + if (loop && sDIR && !sDIR->notify_loop_done) { + /* check the rest of the directory and notify the + scanner if some file needs scanning */ + long offset; + SMB_STRUCT_DIRENT *dire; + + offset = SMB_VFS_NEXT_TELLDIR(handle, sDIR->DIR); + dire = SMB_VFS_NEXT_READDIR(handle, sDIR->DIR, NULL); + while (dire) { + char *fpath2; + struct smb_filename *smb_fname2; + fpath2 = path_plus_name(ctx,base_name, dire->d_name); + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_allow_access in loop, " + "found %s\n", fpath2)); + create_synthetic_smb_fname(ctx, fpath2,NULL,NULL, + &smb_fname2); + scannedonly_allow_access(handle, NULL, + smb_fname2, + dire->d_name, + base_name, 0, 0, 0, 0, 0); + talloc_free(fpath2); + talloc_free(smb_fname2); + dire = SMB_VFS_NEXT_READDIR(handle, sDIR->DIR,NULL); + } + sDIR->notify_loop_done = 1; + didloop = 1; + SMB_VFS_NEXT_SEEKDIR(handle, sDIR->DIR, offset); + } + if (recheck_time > 0 + && ((recheck_size > 0 + && smb_fname->st.st_ex_size < (1024 * recheck_size)) + || didloop)) { + int i = 0; + flush_sendbuffer(handle); + while (retval != 0 /*&& errno == ENOENT */ + && i < recheck_tries) { + struct timespec req = { 0, recheck_time * 10000 }; + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_allow_access, wait (try=%d " + "(max %d), %d ms) for %s\n", + i, recheck_tries, + recheck_time, cache_smb_fname->base_name)); + nanosleep(&req, NULL); + retval = SMB_VFS_NEXT_STAT(handle, cache_smb_fname); + i++; + } + } + /* still no cachefile, or still too old, return 0 */ + if (retval != 0 + || !timespec_is_newer(&smb_fname->st.st_ex_mtime, + &cache_smb_fname->st.st_ex_mtime)) { + DEBUG(SCANNEDONLY_DEBUG, + ("retval=%d, return 0\n",retval)); + return false; + } + return true; +} + +/*********************/ +/* VFS functions */ +/*********************/ + +static SMB_STRUCT_DIR *scannedonly_opendir(vfs_handle_struct * handle, + const char *fname, + const char *mask, uint32 attr) +{ + SMB_STRUCT_DIR *DIRp; + struct scannedonly_DIR *sDIR; + + DIRp = SMB_VFS_NEXT_OPENDIR(handle, fname, mask, attr); + if (!DIRp) { + return NULL; + } + + sDIR = TALLOC_P(NULL, struct scannedonly_DIR); + if (fname[0] != '/') { + sDIR->base = construct_full_path(sDIR,handle, fname, true); + } else { + sDIR->base = name_w_ending_slash(sDIR, fname); + } + sDIR->DIR = DIRp; + sDIR->notify_loop_done = 0; + return (SMB_STRUCT_DIR *) sDIR; +} + +static SMB_STRUCT_DIRENT *scannedonly_readdir(vfs_handle_struct *handle, + SMB_STRUCT_DIR * dirp, + SMB_STRUCT_STAT *sbuf) +{ + SMB_STRUCT_DIRENT *result; + int allowed = 0; + char *tmp; + struct smb_filename *smb_fname; + char *notify_name; + int namelen; + SMB_STRUCT_DIRENT *newdirent; + TALLOC_CTX *ctx=talloc_tos(); + + struct scannedonly_DIR *sDIR = (struct scannedonly_DIR *)dirp; + if (!dirp) { + return NULL; + } + + result = SMB_VFS_NEXT_READDIR(handle, sDIR->DIR, sbuf); + + if (!result) + return NULL; + + if (is_scannedonly_file(STRUCTSCANO(handle->data), result->d_name)) { + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_readdir, %s is a scannedonly file, " + "skip to next entry\n", result->d_name)); + return scannedonly_readdir(handle, dirp, NULL); + } + + tmp = path_plus_name(ctx,sDIR->base, result->d_name); + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_readdir, check access to %s (sbuf=%p)\n", + tmp,sbuf)); + + /* even if we don't hide nonscanned files or we allow non scanned + files we call allow_access because it will notify the daemon to + scan these files */ + create_synthetic_smb_fname(ctx, tmp,NULL, + sbuf?VALID_STAT(*sbuf)?sbuf:NULL:NULL, + &smb_fname); + allowed = scannedonly_allow_access( + handle, sDIR, smb_fname, + result->d_name, + sDIR->base, 0, + STRUCTSCANO(handle->data)->hide_nonscanned_files + ? STRUCTSCANO(handle->data)->recheck_time_readdir + : 0, + STRUCTSCANO(handle->data)->recheck_tries_readdir, + -1, + 1); + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_readdir access to %s (%s) = %d\n", tmp, + result->d_name, allowed)); + if (allowed) { + return result; + } + DEBUG(SCANNEDONLY_DEBUG, + ("hide_nonscanned_files=%d, allow_nonscanned_files=%d\n", + STRUCTSCANO(handle->data)->hide_nonscanned_files, + STRUCTSCANO(handle->data)->allow_nonscanned_files + )); + + if (!STRUCTSCANO(handle->data)->hide_nonscanned_files + || STRUCTSCANO(handle->data)->allow_nonscanned_files) { + return result; + } + + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_readdir, readdir listing for %s not " + "allowed, notify user\n", result->d_name)); + notify_name = talloc_asprintf( + ctx,"%s %s",result->d_name, + STRUCTSCANO(handle->data)->scanning_message); + namelen = strlen(notify_name); + newdirent = (SMB_STRUCT_DIRENT *)TALLOC_ARRAY( + ctx, char, sizeof(SMB_STRUCT_DIRENT) + namelen + 1); + if (!newdirent) { + return NULL; + } + memcpy(newdirent, result, sizeof(SMB_STRUCT_DIRENT)); + memcpy(&newdirent->d_name, notify_name, namelen + 1); + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_readdir, return newdirent at %p with " + "notification %s\n", newdirent, newdirent->d_name)); + return newdirent; +} + +static void scannedonly_seekdir(struct vfs_handle_struct *handle, + SMB_STRUCT_DIR * dirp, long offset) +{ + struct scannedonly_DIR *sDIR = (struct scannedonly_DIR *)dirp; + SMB_VFS_NEXT_SEEKDIR(handle, sDIR->DIR, offset); +} + +static long scannedonly_telldir(struct vfs_handle_struct *handle, + SMB_STRUCT_DIR * dirp) +{ + struct scannedonly_DIR *sDIR = (struct scannedonly_DIR *)dirp; + return SMB_VFS_NEXT_TELLDIR(handle, sDIR->DIR); +} + +static void scannedonly_rewinddir(struct vfs_handle_struct *handle, + SMB_STRUCT_DIR * dirp) +{ + struct scannedonly_DIR *sDIR = (struct scannedonly_DIR *)dirp; + SMB_VFS_NEXT_REWINDDIR(handle, sDIR->DIR); +} + +static int scannedonly_closedir(vfs_handle_struct * handle, + SMB_STRUCT_DIR * dirp) +{ + int retval; + struct scannedonly_DIR *sDIR = (struct scannedonly_DIR *)dirp; + flush_sendbuffer(handle); + retval = SMB_VFS_NEXT_CLOSEDIR(handle, sDIR->DIR); + TALLOC_FREE(sDIR); + return retval; +} + +static int scannedonly_stat(vfs_handle_struct * handle, + struct smb_filename *smb_fname) +{ + int ret; + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + DEBUG(SCANNEDONLY_DEBUG, ("scannedonly_stat: %s returned %d\n", + smb_fname->base_name, ret)); + if (ret != 0 && errno == ENOENT) { + TALLOC_CTX *ctx=talloc_tos(); + char *test_base_name, *tmp_base_name = smb_fname->base_name; + /* possibly this was a fake name (file is being scanned for + viruses.txt): check for that and create the real name and + stat the real name */ + test_base_name = real_path_from_notify_path( + ctx, + STRUCTSCANO(handle->data), + smb_fname->base_name); + if (test_base_name) { + smb_fname->base_name = test_base_name; + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + DEBUG(5, ("_stat: %s returned %d\n", + test_base_name, ret)); + smb_fname->base_name = tmp_base_name; + } + } + return ret; +} + +static int scannedonly_lstat(vfs_handle_struct * handle, + struct smb_filename *smb_fname) +{ + int ret; + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + DEBUG(SCANNEDONLY_DEBUG, ("scannedonly_lstat: %s returned %d\n", + smb_fname->base_name, ret)); + if (ret != 0 && errno == ENOENT) { + TALLOC_CTX *ctx=talloc_tos(); + char *test_base_name, *tmp_base_name = smb_fname->base_name; + /* possibly this was a fake name (file is being scanned for + viruses.txt): check for that and create the real name and + stat the real name */ + test_base_name = real_path_from_notify_path( + ctx, STRUCTSCANO(handle->data), smb_fname->base_name); + if (test_base_name) { + smb_fname->base_name = test_base_name; + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + DEBUG(5, ("_lstat: %s returned %d\n", + test_base_name, ret)); + smb_fname->base_name = tmp_base_name; + } + } + return ret; +} + +static int scannedonly_open(vfs_handle_struct * handle, + struct smb_filename *smb_fname, + files_struct * fsp, int flags, mode_t mode) +{ + const char *base; + char *tmp, *shortname; + int allowed, write_access = 0; + TALLOC_CTX *ctx=talloc_tos(); + /* if open for writing ignore it */ + if ((flags & O_ACCMODE) == O_WRONLY) { + return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode); + } + if ((flags & O_ACCMODE) == O_RDWR) { + write_access = 1; + } + /* check if this file is scanned already */ + tmp = strrchr(smb_fname->base_name, '/'); + if (tmp) { + base = talloc_strndup(ctx,smb_fname->base_name, + (tmp - smb_fname->base_name) + 1); + shortname = tmp + 1; + } else { + base = ""; + shortname = (char *)smb_fname->base_name; + } + allowed = scannedonly_allow_access( + handle, NULL, smb_fname, shortname, + base, + write_access, + STRUCTSCANO(handle->data)->recheck_time_open, + STRUCTSCANO(handle->data)->recheck_tries_open, + STRUCTSCANO(handle->data)->recheck_size_open, + 0); + flush_sendbuffer(handle); + DEBUG(SCANNEDONLY_DEBUG, ("scannedonly_open: allow=%d for %s\n", + allowed, smb_fname->base_name)); + if (allowed + || STRUCTSCANO(handle->data)->allow_nonscanned_files) { + return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode); + } + errno = EACCES; + return -1; +} + +static int scannedonly_close(vfs_handle_struct * handle, files_struct * fsp) +{ + /* we only have to notify the scanner + for files that were open readwrite or writable. */ + if (fsp->can_write) { + TALLOC_CTX *ctx = talloc_tos(); + notify_scanner(handle, construct_full_path( + ctx,handle, + fsp->fsp_name->base_name,false)); + flush_sendbuffer(handle); + } + return SMB_VFS_NEXT_CLOSE(handle, fsp); +} + +static int scannedonly_rename(vfs_handle_struct * handle, + const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst) +{ + /* rename the cache file before we pass the actual rename on */ + struct smb_filename *smb_fname_src_tmp = NULL; + struct smb_filename *smb_fname_dst_tmp = NULL; + char *cachefile_src, *cachefile_dst; + TALLOC_CTX *ctx = talloc_tos(); + + /* Setup temporary smb_filename structs. */ + cachefile_src = cachefile_name_f_fullpath( + ctx, + smb_fname_src->base_name, + STRUCTSCANO(handle->data)->p_scanned); + cachefile_dst = cachefile_name_f_fullpath( + ctx, + smb_fname_dst->base_name, + STRUCTSCANO(handle->data)->p_scanned); + + create_synthetic_smb_fname(ctx, cachefile_src,NULL,NULL, + &smb_fname_src_tmp); + create_synthetic_smb_fname(ctx, cachefile_dst,NULL,NULL, + &smb_fname_dst_tmp); + + if (SMB_VFS_NEXT_RENAME(handle, smb_fname_src_tmp, smb_fname_dst_tmp) + != 0) { + DEBUG(SCANNEDONLY_DEBUG, + ("failed to rename %s into %s\n", cachefile_src, + cachefile_dst)); + } + return SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst); +} + +static int scannedonly_unlink(vfs_handle_struct * handle, + const struct smb_filename *smb_fname) +{ + /* unlink the 'scanned' file too */ + struct smb_filename *smb_fname_cache = NULL; + char * cachefile; + TALLOC_CTX *ctx = talloc_tos(); + + cachefile = cachefile_name_f_fullpath( + ctx, + smb_fname->base_name, + STRUCTSCANO(handle->data)->p_scanned); + create_synthetic_smb_fname(ctx, cachefile,NULL,NULL, + &smb_fname_cache); + if (SMB_VFS_NEXT_UNLINK(handle, smb_fname_cache) != 0) { + DEBUG(SCANNEDONLY_DEBUG, ("_unlink: failed to unlink %s\n", + smb_fname_cache->base_name)); + } + return SMB_VFS_NEXT_UNLINK(handle, smb_fname); +} + +static int scannedonly_rmdir(vfs_handle_struct * handle, const char *path) +{ + /* if there are only .scanned: .virus: or .failed: files, we delete + those, because the client cannot see them */ + DIR *dirp; + SMB_STRUCT_DIRENT *dire; + TALLOC_CTX *ctx = talloc_tos(); + bool only_deletable_files = true, have_files = false; + char *path_w_slash; + + if (!STRUCTSCANO(handle->data)->rm_hidden_files_on_rmdir) + return SMB_VFS_NEXT_RMDIR(handle, path); + + path_w_slash = name_w_ending_slash(ctx,path); + dirp = SMB_VFS_NEXT_OPENDIR(handle, path, NULL, 0); + while ((dire = SMB_VFS_NEXT_READDIR(handle, dirp, NULL)) != NULL) { + if (ISDOT(dire->d_name) || ISDOTDOT(dire->d_name)) { + continue; + } + have_files = true; + if (!is_scannedonly_file(STRUCTSCANO(handle->data), + dire->d_name)) { + struct smb_filename *smb_fname = NULL; + char *fullpath; + int retval; + + if (STRUCTSCANO(handle->data)->show_special_files) { + only_deletable_files = false; + break; + } + /* stat the file and see if it is a + special file */ + fullpath = path_plus_name(ctx,path_w_slash, + dire->d_name); + create_synthetic_smb_fname(ctx, fullpath,NULL,NULL, + &smb_fname); + retval = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (retval == 0 + && S_ISREG(smb_fname->st.st_ex_mode)) { + only_deletable_files = false; + } + TALLOC_FREE(fullpath); + TALLOC_FREE(smb_fname); + break; + } + } + DEBUG(SCANNEDONLY_DEBUG, + ("path=%s, have_files=%d, only_deletable_files=%d\n", + path, have_files, only_deletable_files)); + if (have_files && only_deletable_files) { + DEBUG(SCANNEDONLY_DEBUG, + ("scannedonly_rmdir, remove leftover scannedonly " + "files from %s\n", path_w_slash)); + SMB_VFS_NEXT_REWINDDIR(handle, dirp); + while ((dire = SMB_VFS_NEXT_READDIR(handle, dirp, NULL)) + != NULL) { + char *fullpath; + struct smb_filename *smb_fname = NULL; + if (ISDOT(dire->d_name) || ISDOTDOT(dire->d_name)) { + continue; + } + fullpath = path_plus_name(ctx,path_w_slash, + dire->d_name); + create_synthetic_smb_fname(ctx, fullpath,NULL,NULL, + &smb_fname); + DEBUG(SCANNEDONLY_DEBUG, ("unlink %s\n", fullpath)); + SMB_VFS_NEXT_UNLINK(handle, smb_fname); + TALLOC_FREE(fullpath); + TALLOC_FREE(smb_fname); + } + } + return SMB_VFS_NEXT_CLOSEDIR(handle, dirp); +} + +static void free_scannedonly_data(void **data) +{ + SAFE_FREE(*data); +} + +static int scannedonly_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + + struct Tscannedonly *so; + + so = SMB_MALLOC_P(struct Tscannedonly); + handle->data = (void *)so; + handle->free_data = free_scannedonly_data; + so->gsendbuffer[0]='\0'; + so->domain_socket = + lp_parm_bool(SNUM(handle->conn), "scannedonly", + "domain_socket", True); + so->socketname = + (char *)lp_parm_const_string(SNUM(handle->conn), + "scannedonly", "socketname", + "/var/lib/scannedonly/scan"); + so->portnum = + lp_parm_int(SNUM(handle->conn), "scannedonly", "portnum", + 2020); + so->scanhost = + (char *)lp_parm_const_string(SNUM(handle->conn), + "scannedonly", "scanhost", + "localhost"); + + so->show_special_files = + lp_parm_bool(SNUM(handle->conn), "scannedonly", + "show_special_files", True); + so->rm_hidden_files_on_rmdir = + lp_parm_bool(SNUM(handle->conn), "scannedonly", + "rm_hidden_files_on_rmdir", True); + so->hide_nonscanned_files = + lp_parm_bool(SNUM(handle->conn), "scannedonly", + "hide_nonscanned_files", False); + so->allow_nonscanned_files = + lp_parm_bool(SNUM(handle->conn), "scannedonly", + "allow_nonscanned_files", False); + so->scanning_message = + (char *)lp_parm_const_string(SNUM(handle->conn), + "scannedonly", + "scanning_message", + "is being scanned for viruses"); + so->scanning_message_len = strlen(so->scanning_message); + so->recheck_time_open = + lp_parm_int(SNUM(handle->conn), "scannedonly", + "recheck_time_open", 50); + so->recheck_tries_open = + lp_parm_int(SNUM(handle->conn), "scannedonly", + "recheck_tries_open", 100); + so->recheck_size_open = + lp_parm_int(SNUM(handle->conn), "scannedonly", + "recheck_size_open", 100); + so->recheck_time_readdir = + lp_parm_int(SNUM(handle->conn), "scannedonly", + "recheck_time_readdir", 50); + so->recheck_tries_readdir = + lp_parm_int(SNUM(handle->conn), "scannedonly", + "recheck_tries_readdir", 20); + + so->p_scanned = + (char *)lp_parm_const_string(SNUM(handle->conn), + "scannedonly", + "pref_scanned", + ".scanned:"); + so->p_virus = + (char *)lp_parm_const_string(SNUM(handle->conn), + "scannedonly", + "pref_virus", + ".virus:"); + so->p_failed = + (char *)lp_parm_const_string(SNUM(handle->conn), + "scannedonly", + "pref_failed", + ".failed:"); + connect_to_scanner(handle); + + return SMB_VFS_NEXT_CONNECT(handle, service, user); +} + +/* VFS operations structure */ +static struct vfs_fn_pointers vfs_scannedonly_fns = { + .opendir = scannedonly_opendir, + .readdir = scannedonly_readdir, + .seekdir = scannedonly_seekdir, + .telldir = scannedonly_telldir, + .rewind_dir = scannedonly_rewinddir, + .closedir = scannedonly_closedir, + .rmdir = scannedonly_rmdir, + .stat = scannedonly_stat, + .lstat = scannedonly_lstat, + .open = scannedonly_open, + .close_fn = scannedonly_close, + .rename = scannedonly_rename, + .unlink = scannedonly_unlink, + .connect_fn = scannedonly_connect +}; + +NTSTATUS vfs_scannedonly_init(void) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "scannedonly", + &vfs_scannedonly_fns); +} |