vfs_virusfilter: implement SMB_VFS_OPENAT()
authorRalph Boehme <slow@samba.org>
Wed, 20 May 2020 20:31:04 +0000 (22:31 +0200)
committerJeremy Allison <jra@samba.org>
Thu, 21 May 2020 20:38:34 +0000 (20:38 +0000)
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
source3/modules/vfs_virusfilter.c

index a9a3ac86409370a112eb0571cb243336be0e7ce6..bf7f25d181fa12383a5e3c5dd64ca8ccaf8cb87a 100644 (file)
@@ -1312,6 +1312,187 @@ virusfilter_vfs_open_fail:
        return -1;
 }
 
+static int virusfilter_vfs_openat(struct vfs_handle_struct *handle,
+                                 const struct files_struct *dirfsp,
+                                 const struct smb_filename *smb_fname_in,
+                                 struct files_struct *fsp,
+                                 int flags,
+                                 mode_t mode)
+{
+       TALLOC_CTX *mem_ctx = talloc_tos();
+       struct virusfilter_config *config = NULL;
+       const char *cwd_fname = dirfsp->fsp_name->base_name;
+       virusfilter_result scan_result;
+       const char *fname = fsp->fsp_name->base_name;
+       char *dir_name = NULL;
+       const char *base_name = NULL;
+       int scan_errno = 0;
+       size_t test_prefix;
+       size_t test_suffix;
+       int rename_trap_count = 0;
+       int ret;
+       bool ok1;
+       char *sret = NULL;
+       struct smb_filename *smb_fname = NULL;
+
+       /*
+        * For now assert this, so SMB_VFS_NEXT_STAT() below works.
+        */
+       SMB_ASSERT(dirfsp->fh->fd == AT_FDCWD);
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct virusfilter_config, return -1);
+
+       if (fsp->fsp_flags.is_directory) {
+               DBG_INFO("Not scanned: Directory: %s/\n", cwd_fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       test_prefix = strlen(config->rename_prefix);
+       test_suffix = strlen(config->rename_suffix);
+       if (test_prefix > 0) {
+               rename_trap_count++;
+       }
+       if (test_suffix > 0) {
+               rename_trap_count++;
+       }
+
+       smb_fname = cp_smb_filename(mem_ctx, smb_fname_in);
+       if (smb_fname == NULL) {
+               goto virusfilter_vfs_open_fail;
+       }
+
+       if (is_named_stream(smb_fname)) {
+               DBG_INFO("Not scanned: only file backed streams can be scanned:"
+                        " %s/%s\n", cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       if (!config->scan_on_open) {
+               DBG_INFO("Not scanned: scan on open is disabled: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       if (flags & O_TRUNC) {
+               DBG_INFO("Not scanned: Open flags have O_TRUNC: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+       if (ret != 0) {
+
+               /*
+                * Do not return immediately if !(flags & O_CREAT) &&
+                * errno != ENOENT.
+                * Do not do this here or anywhere else. The module is
+                * stackable and there may be modules below, such as audit
+                * modules, which should be handled.
+                */
+               goto virusfilter_vfs_open_next;
+       }
+       ret = S_ISREG(smb_fname->st.st_ex_mode);
+       if (ret == 0) {
+               DBG_INFO("Not scanned: Directory or special file: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+       if (config->max_file_size > 0 &&
+           smb_fname->st.st_ex_size > config->max_file_size)
+       {
+               DBG_INFO("Not scanned: file size > max file size: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+       if (config->min_file_size > 0 &&
+           smb_fname->st.st_ex_size < config->min_file_size)
+       {
+               DBG_INFO("Not scanned: file size < min file size: %s/%s\n",
+                     cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       ok1 = is_in_path(fname, config->exclude_files, false);
+       if (config->exclude_files && ok1)
+       {
+               DBG_INFO("Not scanned: exclude files: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+               sret = strstr_m(fname, config->quarantine_dir);
+               if (sret != NULL) {
+                       scan_errno = config->infected_open_errno;
+                       goto virusfilter_vfs_open_fail;
+               }
+       }
+
+       if (test_prefix > 0 || test_suffix > 0) {
+               ok1 = parent_dirname(mem_ctx, fname, &dir_name, &base_name);
+               if (ok1)
+               {
+                       if (test_prefix > 0) {
+                               ret = strncmp(base_name,
+                                   config->rename_prefix, test_prefix);
+                               if (ret != 0) {
+                                       test_prefix = 0;
+                               }
+                       }
+                       if (test_suffix > 0) {
+                               ret = strcmp(base_name + (strlen(base_name)
+                                                - test_suffix),
+                                                config->rename_suffix);
+                               if (ret != 0) {
+                                       test_suffix = 0;
+                               }
+                       }
+
+                       TALLOC_FREE(dir_name);
+
+                       if ((rename_trap_count == 2 && test_prefix &&
+                           test_suffix) || (rename_trap_count == 1 &&
+                           (test_prefix || test_suffix)))
+                       {
+                               scan_errno =
+                                       config->infected_open_errno;
+                               goto virusfilter_vfs_open_fail;
+                       }
+               }
+       }
+
+       scan_result = virusfilter_scan(handle, config, fsp);
+
+       switch (scan_result) {
+       case VIRUSFILTER_RESULT_CLEAN:
+               break;
+       case VIRUSFILTER_RESULT_INFECTED:
+               scan_errno = config->infected_open_errno;
+               goto virusfilter_vfs_open_fail;
+       case VIRUSFILTER_RESULT_ERROR:
+               if (config->block_access_on_error) {
+                       DBG_INFO("Block access\n");
+                       scan_errno = config->scan_error_open_errno;
+                       goto virusfilter_vfs_open_fail;
+               }
+               break;
+       default:
+               scan_errno = config->scan_error_open_errno;
+               goto virusfilter_vfs_open_fail;
+       }
+
+       TALLOC_FREE(smb_fname);
+
+virusfilter_vfs_open_next:
+       return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname_in, fsp, flags, mode);
+
+virusfilter_vfs_open_fail:
+       TALLOC_FREE(smb_fname);
+       errno = (scan_errno != 0) ? scan_errno : EACCES;
+       return -1;
+}
+
 static int virusfilter_vfs_close(
        struct vfs_handle_struct *handle,
        files_struct *fsp)
@@ -1517,6 +1698,7 @@ static struct vfs_fn_pointers vfs_virusfilter_fns = {
        .connect_fn     = virusfilter_vfs_connect,
        .disconnect_fn  = virusfilter_vfs_disconnect,
        .open_fn        = virusfilter_vfs_open,
+       .openat_fn      = virusfilter_vfs_openat,
        .close_fn       = virusfilter_vfs_close,
        .unlinkat_fn    = virusfilter_vfs_unlinkat,
        .renameat_fn    = virusfilter_vfs_renameat,