summaryrefslogtreecommitdiffstats
path: root/source3/modules/vfs_virusfilter.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/modules/vfs_virusfilter.c
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/modules/vfs_virusfilter.c')
-rw-r--r--source3/modules/vfs_virusfilter.c1677
1 files changed, 1677 insertions, 0 deletions
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
new file mode 100644
index 0000000..ea1886d
--- /dev/null
+++ b/source3/modules/vfs_virusfilter.c
@@ -0,0 +1,1677 @@
+/*
+ * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+ * Copyright (C) 2016-2017 Trever L. Adams
+ * Copyright (C) 2017 Ralph Boehme <slow@samba.org>
+ * Copyright (C) 2017 Jeremy Allison <jra@samba.org>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/*
+ * Default configuration values
+ * ======================================================================
+ */
+
+#define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX "virusfilter."
+#define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX ".infected"
+#define VIRUSFILTER_DEFAULT_RENAME_PREFIX "virusfilter."
+#define VIRUSFILTER_DEFAULT_RENAME_SUFFIX ".infected"
+
+/* ====================================================================== */
+
+enum virusfilter_scanner_enum {
+ VIRUSFILTER_SCANNER_CLAMAV,
+ VIRUSFILTER_SCANNER_DUMMY,
+ VIRUSFILTER_SCANNER_FSAV,
+ VIRUSFILTER_SCANNER_SOPHOS
+};
+
+static const struct enum_list scanner_list[] = {
+ { VIRUSFILTER_SCANNER_CLAMAV, "clamav" },
+ { VIRUSFILTER_SCANNER_DUMMY, "dummy" },
+ { VIRUSFILTER_SCANNER_FSAV, "fsav" },
+ { VIRUSFILTER_SCANNER_SOPHOS, "sophos" },
+ { -1, NULL }
+};
+
+static const struct enum_list virusfilter_actions[] = {
+ { VIRUSFILTER_ACTION_QUARANTINE, "quarantine" },
+ { VIRUSFILTER_ACTION_RENAME, "rename" },
+ { VIRUSFILTER_ACTION_DELETE, "delete" },
+
+ /* alias for "delete" */
+ { VIRUSFILTER_ACTION_DELETE, "remove" },
+
+ /* alias for "delete" */
+ { VIRUSFILTER_ACTION_DELETE, "unlink" },
+ { VIRUSFILTER_ACTION_DO_NOTHING, "nothing" },
+ { -1, NULL}
+};
+
+static int virusfilter_config_destructor(struct virusfilter_config *config)
+{
+ TALLOC_FREE(config->backend);
+ return 0;
+}
+
+/*
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_directory_exist(
+ struct vfs_handle_struct *handle,
+ const char *dname)
+{
+ int ret = -1;
+ struct smb_filename smb_fname = {
+ .base_name = discard_const_p(char, dname)
+ };
+
+ ret = SMB_VFS_STAT(handle->conn, &smb_fname);
+ if (ret == 0) {
+ return S_ISDIR(smb_fname.st.st_ex_mode);
+ }
+
+ return false;
+}
+
+/**
+ * Create directory tree
+ * @param conn connection
+ * @param dname Directory tree to be created
+ * @return Returns true for success
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_create_dir(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *dname)
+{
+ size_t len = 0;
+ size_t cat_len = 0;
+ char *new_dir = NULL;
+ char *tmp_str = NULL;
+ char *token = NULL;
+ char *tok_str = NULL;
+ bool status = false;
+ bool ok = false;
+ int ret = -1;
+ char *saveptr = NULL;
+
+ tmp_str = talloc_strdup(talloc_tos(), dname);
+ if (tmp_str == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ errno = ENOMEM;
+ goto done;
+ }
+ tok_str = tmp_str;
+
+ len = strlen(dname)+1;
+ new_dir = (char *)talloc_size(talloc_tos(), len + 1);
+ if (new_dir == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ errno = ENOMEM;
+ goto done;
+ }
+ *new_dir = '\0';
+ if (dname[0] == '/') {
+ /* Absolute path. */
+ cat_len = strlcat(new_dir, "/", len + 1);
+ if (cat_len >= len+1) {
+ goto done;
+ }
+ }
+
+ /* Create directory tree if necessary */
+ for (token = strtok_r(tok_str, "/", &saveptr);
+ token != NULL;
+ token = strtok_r(NULL, "/", &saveptr))
+ {
+ cat_len = strlcat(new_dir, token, len + 1);
+ if (cat_len >= len+1) {
+ goto done;
+ }
+ ok = quarantine_directory_exist(handle, new_dir);
+ if (ok == true) {
+ DBG_DEBUG("quarantine: dir %s already exists\n",
+ new_dir);
+ } else {
+ struct smb_filename *smb_fname = NULL;
+
+ DBG_INFO("quarantine: creating new dir %s\n", new_dir);
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ new_dir,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto done;
+ }
+
+ ret = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ smb_fname,
+ config->quarantine_dir_mode);
+ if (ret != 0) {
+ TALLOC_FREE(smb_fname);
+
+ DBG_WARNING("quarantine: mkdirat failed for %s "
+ "with error: %s\n", new_dir,
+ strerror(errno));
+ status = false;
+ goto done;
+ }
+ TALLOC_FREE(smb_fname);
+ }
+ cat_len = strlcat(new_dir, "/", len + 1);
+ if (cat_len >= len + 1) {
+ goto done;
+ }
+ }
+
+ status = true;
+done:
+ TALLOC_FREE(tmp_str);
+ TALLOC_FREE(new_dir);
+ return status;
+}
+
+static int virusfilter_vfs_connect(
+ struct vfs_handle_struct *handle,
+ const char *svc,
+ const char *user)
+{
+ int snum = SNUM(handle->conn);
+ struct virusfilter_config *config = NULL;
+ const char *exclude_files = NULL;
+ const char *infected_files = NULL;
+ const char *temp_quarantine_dir_mode = NULL;
+ const char *infected_file_command = NULL;
+ const char *scan_error_command = NULL;
+ const char *quarantine_dir = NULL;
+ const char *quarantine_prefix = NULL;
+ const char *quarantine_suffix = NULL;
+ const char *rename_prefix = NULL;
+ const char *rename_suffix = NULL;
+ const char *socket_path = NULL;
+ char *sret = NULL;
+ char *tmp = NULL;
+ enum virusfilter_scanner_enum backend;
+ int connect_timeout = 0;
+ int io_timeout = 0;
+ int ret = -1;
+
+ config = talloc_zero(handle, struct virusfilter_config);
+ if (config == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ return -1;
+ }
+ talloc_set_destructor(config, virusfilter_config_destructor);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config, NULL,
+ struct virusfilter_config, return -1);
+
+ config->scan_request_limit = lp_parm_int(
+ snum, "virusfilter", "scan request limit", 0);
+
+ config->scan_on_open = lp_parm_bool(
+ snum, "virusfilter", "scan on open", true);
+
+ config->scan_on_close = lp_parm_bool(
+ snum, "virusfilter", "scan on close", false);
+
+ config->max_nested_scan_archive = lp_parm_int(
+ snum, "virusfilter", "max nested scan archive", 1);
+
+ config->scan_archive = lp_parm_bool(
+ snum, "virusfilter", "scan archive", false);
+
+ config->scan_mime = lp_parm_bool(
+ snum, "virusfilter", "scan mime", false);
+
+ config->max_file_size = (ssize_t)lp_parm_ulong(
+ snum, "virusfilter", "max file size", 100000000L);
+
+ config->min_file_size = (ssize_t)lp_parm_ulong(
+ snum, "virusfilter", "min file size", 10);
+
+ exclude_files = lp_parm_const_string(
+ snum, "virusfilter", "exclude files", NULL);
+ if (exclude_files != NULL) {
+ set_namearray(&config->exclude_files, exclude_files);
+ }
+
+ infected_files = lp_parm_const_string(
+ snum, "virusfilter", "infected files", NULL);
+ if (infected_files != NULL) {
+ set_namearray(&config->infected_files, infected_files);
+ }
+
+ config->cache_entry_limit = lp_parm_int(
+ snum, "virusfilter", "cache entry limit", 100);
+
+ config->cache_time_limit = lp_parm_int(
+ snum, "virusfilter", "cache time limit", 10);
+
+ config->infected_file_action = lp_parm_enum(
+ snum, "virusfilter", "infected file action",
+ virusfilter_actions, VIRUSFILTER_ACTION_DO_NOTHING);
+
+ infected_file_command = lp_parm_const_string(
+ snum, "virusfilter", "infected file command", NULL);
+ if (infected_file_command != NULL) {
+ config->infected_file_command = talloc_strdup(
+ config,
+ infected_file_command);
+ if (config->infected_file_command == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+ scan_error_command = lp_parm_const_string(
+ snum, "virusfilter", "scan error command", NULL);
+ if (scan_error_command != NULL) {
+ config->scan_error_command = talloc_strdup(config,
+ scan_error_command);
+ if (config->scan_error_command == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ config->block_access_on_error = lp_parm_bool(
+ snum, "virusfilter", "block access on error", false);
+
+ tmp = talloc_asprintf(config, "%s/.quarantine",
+ handle->conn->connectpath);
+
+ quarantine_dir = lp_parm_const_string(
+ snum, "virusfilter", "quarantine directory",
+ tmp ? tmp : "/tmp/.quarantine");
+ if (quarantine_dir != NULL) {
+ config->quarantine_dir = talloc_strdup(config, quarantine_dir);
+ if (config->quarantine_dir == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ if (tmp != config->quarantine_dir) {
+ TALLOC_FREE(tmp);
+ }
+
+ temp_quarantine_dir_mode = lp_parm_const_string(
+ snum, "virusfilter", "quarantine directory mode", "0755");
+ if (temp_quarantine_dir_mode != NULL) {
+ unsigned int mode = 0;
+ sscanf(temp_quarantine_dir_mode, "%o", &mode);
+ config->quarantine_dir_mode = mode;
+ }
+
+ quarantine_prefix = lp_parm_const_string(
+ snum, "virusfilter", "quarantine prefix",
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+ if (quarantine_prefix != NULL) {
+ config->quarantine_prefix = talloc_strdup(config,
+ quarantine_prefix);
+ if (config->quarantine_prefix == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ quarantine_suffix = lp_parm_const_string(
+ snum, "virusfilter", "quarantine suffix",
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+ if (quarantine_suffix != NULL) {
+ config->quarantine_suffix = talloc_strdup(config,
+ quarantine_suffix);
+ if (config->quarantine_suffix == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ /*
+ * Make sure prefixes and suffixes do not contain directory
+ * delimiters
+ */
+ if (config->quarantine_prefix != NULL) {
+ sret = strstr(config->quarantine_prefix, "/");
+ if (sret != NULL) {
+ DBG_ERR("quarantine prefix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->quarantine_prefix,
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+ config->quarantine_prefix =
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX;
+ }
+ }
+ if (config->quarantine_suffix != NULL) {
+ sret = strstr(config->quarantine_suffix, "/");
+ if (sret != NULL) {
+ DBG_ERR("quarantine suffix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->quarantine_suffix,
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+ config->quarantine_suffix =
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX;
+ }
+ }
+
+ config->quarantine_keep_tree = lp_parm_bool(
+ snum, "virusfilter", "quarantine keep tree", true);
+
+ config->quarantine_keep_name = lp_parm_bool(
+ snum, "virusfilter", "quarantine keep name", true);
+
+ rename_prefix = lp_parm_const_string(
+ snum, "virusfilter", "rename prefix",
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+ if (rename_prefix != NULL) {
+ config->rename_prefix = talloc_strdup(config, rename_prefix);
+ if (config->rename_prefix == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ rename_suffix = lp_parm_const_string(
+ snum, "virusfilter", "rename suffix",
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+ if (rename_suffix != NULL) {
+ config->rename_suffix = talloc_strdup(config, rename_suffix);
+ if (config->rename_suffix == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ /*
+ * Make sure prefixes and suffixes do not contain directory
+ * delimiters
+ */
+ if (config->rename_prefix != NULL) {
+ sret = strstr(config->rename_prefix, "/");
+ if (sret != NULL) {
+ DBG_ERR("rename prefix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->rename_prefix,
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+ config->rename_prefix =
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX;
+ }
+ }
+ if (config->rename_suffix != NULL) {
+ sret = strstr(config->rename_suffix, "/");
+ if (sret != NULL) {
+ DBG_ERR("rename suffix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->rename_suffix,
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+ config->rename_suffix =
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX;
+ }
+ }
+
+ config->infected_open_errno = lp_parm_int(
+ snum, "virusfilter", "infected file errno on open", EACCES);
+
+ config->infected_close_errno = lp_parm_int(
+ snum, "virusfilter", "infected file errno on close", 0);
+
+ config->scan_error_open_errno = lp_parm_int(
+ snum, "virusfilter", "scan error errno on open", EACCES);
+
+ config->scan_error_close_errno = lp_parm_int(
+ snum, "virusfilter", "scan error errno on close", 0);
+
+ socket_path = lp_parm_const_string(
+ snum, "virusfilter", "socket path", NULL);
+ if (socket_path != NULL) {
+ config->socket_path = talloc_strdup(config, socket_path);
+ if (config->socket_path == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ /* canonicalize socket_path */
+ if (config->socket_path != NULL && config->socket_path[0] != '/') {
+ DBG_ERR("socket path must be an absolute path. "
+ "Using backend default\n");
+ config->socket_path = NULL;
+ }
+ if (config->socket_path != NULL) {
+ config->socket_path = canonicalize_absolute_path(
+ handle, config->socket_path);
+ if (config->socket_path == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ connect_timeout = lp_parm_int(snum, "virusfilter",
+ "connect timeout", 30000);
+
+ io_timeout = lp_parm_int(snum, "virusfilter", "io timeout", 60000);
+
+ config->io_h = virusfilter_io_new(config, connect_timeout, io_timeout);
+ if (config->io_h == NULL) {
+ DBG_ERR("virusfilter_io_new failed\n");
+ return -1;
+ }
+
+ if (config->cache_entry_limit > 0) {
+ config->cache = virusfilter_cache_new(handle,
+ config->cache_entry_limit,
+ config->cache_time_limit);
+ if (config->cache == NULL) {
+ DBG_ERR("Initializing cache failed: Cache disabled\n");
+ return -1;
+ }
+ }
+
+ /*
+ * Check quarantine directory now to save processing
+ * and becoming root over and over.
+ */
+ if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+ bool ok = true;
+ bool dir_exists;
+
+ /*
+ * Do SMB_VFS_NEXT_MKDIR(config->quarantine_dir)
+ * hierarchy
+ */
+ become_root();
+ dir_exists = quarantine_directory_exist(handle,
+ config->quarantine_dir);
+ if (!dir_exists) {
+ DBG_DEBUG("Creating quarantine directory: %s\n",
+ config->quarantine_dir);
+ ok = quarantine_create_dir(handle, config,
+ config->quarantine_dir);
+ }
+ unbecome_root();
+ if (!ok) {
+ DBG_ERR("Creating quarantine directory %s "
+ "failed with %s\n",
+ config->quarantine_dir,
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ /*
+ * Now that the frontend options are initialized, load the configured
+ * backend.
+ */
+
+ backend = (enum virusfilter_scanner_enum)lp_parm_enum(snum,
+ "virusfilter",
+ "scanner",
+ scanner_list,
+ -1);
+ if (backend == (enum virusfilter_scanner_enum)-1) {
+ DBG_ERR("No AV-Scanner configured, "
+ "please set \"virusfilter:scanner\"\n");
+ return -1;
+ }
+
+ switch (backend) {
+ case VIRUSFILTER_SCANNER_SOPHOS:
+ ret = virusfilter_sophos_init(config);
+ break;
+ case VIRUSFILTER_SCANNER_FSAV:
+ ret = virusfilter_fsav_init(config);
+ break;
+ case VIRUSFILTER_SCANNER_CLAMAV:
+ ret = virusfilter_clamav_init(config);
+ break;
+ case VIRUSFILTER_SCANNER_DUMMY:
+ ret = virusfilter_dummy_init(config);
+ break;
+ default:
+ DBG_ERR("Unhandled scanner %d\n", backend);
+ return -1;
+ }
+ if (ret != 0) {
+ DBG_ERR("Scanner backend init failed\n");
+ return -1;
+ }
+
+ if (config->backend->fns->connect != NULL) {
+ ret = config->backend->fns->connect(handle, config, svc, user);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+}
+
+static void virusfilter_vfs_disconnect(struct vfs_handle_struct *handle)
+{
+ struct virusfilter_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return);
+
+ if (config->backend->fns->disconnect != NULL) {
+ config->backend->fns->disconnect(handle);
+ }
+
+ free_namearray(config->exclude_files);
+ virusfilter_io_disconnect(config->io_h);
+
+ SMB_VFS_NEXT_DISCONNECT(handle);
+}
+
+static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx,
+ struct virusfilter_config *config,
+ char **env_list)
+{
+ int ret;
+
+ ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
+ VIRUSFILTER_VERSION);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
+ config->backend->name);
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (config->backend->version != 0) {
+ char *version = NULL;
+
+ version = talloc_asprintf(talloc_tos(), "%u",
+ config->backend->version);
+ if (version == NULL) {
+ return -1;
+ }
+ ret = virusfilter_env_set(mem_ctx, env_list,
+ "VIRUSFILTER_MODULE_VERSION",
+ version);
+ TALLOC_FREE(version);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static char *quarantine_check_tree(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct smb_filename *smb_fname,
+ char *q_dir_in,
+ char *cwd_fname)
+{
+ char *temp_path = NULL;
+ char *q_dir_out = NULL;
+ bool ok;
+
+ temp_path = talloc_asprintf(talloc_tos(), "%s/%s", q_dir_in, cwd_fname);
+ if (temp_path == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ goto out;
+ }
+
+ become_root();
+ ok = quarantine_directory_exist(handle, temp_path);
+ unbecome_root();
+ if (ok) {
+ DBG_DEBUG("quarantine: directory [%s] exists\n", temp_path);
+ q_dir_out = talloc_move(mem_ctx, &temp_path);
+ goto out;
+ }
+
+ DBG_DEBUG("quarantine: Creating directory %s\n", temp_path);
+
+ become_root();
+ ok = quarantine_create_dir(handle, config, temp_path);
+ unbecome_root();
+ if (!ok) {
+ DBG_NOTICE("Could not create quarantine directory [%s], "
+ "ignoring for [%s]\n",
+ temp_path, smb_fname_str_dbg(smb_fname));
+ goto out;
+ }
+
+ q_dir_out = talloc_move(mem_ctx, &temp_path);
+
+out:
+ TALLOC_FREE(temp_path);
+ return q_dir_out;
+}
+
+static virusfilter_action infected_file_action_quarantine(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ TALLOC_CTX *mem_ctx,
+ const struct files_struct *fsp,
+ const char **filepath_newp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ connection_struct *conn = handle->conn;
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ char *fname = fsp->fsp_name->base_name;
+ const struct smb_filename *smb_fname = fsp->fsp_name;
+ struct smb_filename *q_smb_fname = NULL;
+ char *q_dir = NULL;
+ char *q_prefix = NULL;
+ char *q_suffix = NULL;
+ char *q_filepath = NULL;
+ char *dir_name = NULL;
+ const char *base_name = NULL;
+ char *rand_filename_component = NULL;
+ virusfilter_action action = VIRUSFILTER_ACTION_QUARANTINE;
+ bool ok = false;
+ int ret = -1;
+ int saved_errno = 0;
+
+ q_dir = virusfilter_string_sub(frame, conn,
+ config->quarantine_dir);
+ q_prefix = virusfilter_string_sub(frame, conn,
+ config->quarantine_prefix);
+ q_suffix = virusfilter_string_sub(frame, conn,
+ config->quarantine_suffix);
+ if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
+ DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ if (config->quarantine_keep_name || config->quarantine_keep_tree) {
+ ok = parent_dirname(frame, smb_fname->base_name,
+ &dir_name, &base_name);
+ if (!ok) {
+ DBG_ERR("parent_dirname failed\n");
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ if (config->quarantine_keep_tree) {
+ char *tree = NULL;
+
+ tree = quarantine_check_tree(frame, handle, config,
+ smb_fname, q_dir,
+ cwd_fname);
+ if (tree == NULL) {
+ /*
+ * If we can't create the tree, just move it
+ * into the toplevel quarantine dir.
+ */
+ tree = q_dir;
+ }
+ q_dir = tree;
+ }
+ }
+
+ /* Get a 16 byte + \0 random filename component. */
+ rand_filename_component = generate_random_str(frame, 16);
+ if (rand_filename_component == NULL) {
+ DBG_ERR("generate_random_str failed\n");
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ if (config->quarantine_keep_name) {
+ q_filepath = talloc_asprintf(frame, "%s/%s%s%s-%s",
+ q_dir, q_prefix,
+ base_name, q_suffix,
+ rand_filename_component);
+ } else {
+ q_filepath = talloc_asprintf(frame, "%s/%s%s",
+ q_dir, q_prefix,
+ rand_filename_component);
+ }
+ if (q_filepath == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ q_smb_fname = synthetic_smb_fname(frame,
+ q_filepath,
+ smb_fname->stream_name,
+ NULL,
+ 0,
+ smb_fname->flags);
+ if (q_smb_fname == NULL) {
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ become_root();
+ ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+ if (ret == -1) {
+ DBG_ERR("Quarantine [%s/%s] rename to %s failed: %s\n",
+ cwd_fname, fname, q_filepath, strerror(saved_errno));
+ errno = saved_errno;
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ *filepath_newp = talloc_move(mem_ctx, &q_filepath);
+
+out:
+ TALLOC_FREE(frame);
+ return action;
+}
+
+static virusfilter_action infected_file_action_rename(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ TALLOC_CTX *mem_ctx,
+ const struct files_struct *fsp,
+ const char **filepath_newp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ connection_struct *conn = handle->conn;
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ char *fname = fsp->fsp_name->base_name;
+ const struct smb_filename *smb_fname = fsp->fsp_name;
+ struct smb_filename *q_smb_fname = NULL;
+ char *q_dir = NULL;
+ char *q_prefix = NULL;
+ char *q_suffix = NULL;
+ char *q_filepath = NULL;
+ const char *base_name = NULL;
+ virusfilter_action action = VIRUSFILTER_ACTION_RENAME;
+ bool ok = false;
+ int ret = -1;
+ int saved_errno = 0;
+
+ q_prefix = virusfilter_string_sub(frame, conn,
+ config->rename_prefix);
+ q_suffix = virusfilter_string_sub(frame, conn,
+ config->rename_suffix);
+ if (q_prefix == NULL || q_suffix == NULL) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ ok = parent_dirname(frame, fname, &q_dir, &base_name);
+ if (!ok) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ if (q_dir == NULL) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ q_filepath = talloc_asprintf(frame, "%s/%s%s%s", q_dir,
+ q_prefix, base_name, q_suffix);
+
+ q_smb_fname = synthetic_smb_fname(frame, q_filepath,
+ smb_fname->stream_name, NULL,
+ 0,
+ smb_fname->flags);
+ if (q_smb_fname == NULL) {
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ become_root();
+ ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+
+ if (ret == -1) {
+ DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
+ cwd_fname, fname, strerror(saved_errno));
+ errno = saved_errno;
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ *filepath_newp = talloc_move(mem_ctx, &q_filepath);
+
+out:
+ TALLOC_FREE(frame);
+ return action;
+}
+
+static virusfilter_action infected_file_action_delete(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *fsp)
+{
+ int ret;
+ int saved_errno = 0;
+
+ become_root();
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ handle->conn->cwd_fsp,
+ fsp->fsp_name,
+ 0);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+ if (ret == -1) {
+ DBG_ERR("Delete [%s/%s] failed: %s\n",
+ fsp->conn->cwd_fsp->fsp_name->base_name,
+ fsp->fsp_name->base_name,
+ strerror(saved_errno));
+ errno = saved_errno;
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ return VIRUSFILTER_ACTION_DELETE;
+}
+
+static virusfilter_action virusfilter_do_infected_file_action(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ TALLOC_CTX *mem_ctx,
+ const struct files_struct *fsp,
+ const char **filepath_newp)
+{
+ virusfilter_action action;
+
+ *filepath_newp = NULL;
+
+ switch (config->infected_file_action) {
+ case VIRUSFILTER_ACTION_RENAME:
+ action = infected_file_action_rename(handle, config, mem_ctx,
+ fsp, filepath_newp);
+ break;
+
+ case VIRUSFILTER_ACTION_QUARANTINE:
+ action = infected_file_action_quarantine(handle, config, mem_ctx,
+ fsp, filepath_newp);
+ break;
+
+ case VIRUSFILTER_ACTION_DELETE:
+ action = infected_file_action_delete(handle, fsp);
+ break;
+
+ case VIRUSFILTER_ACTION_DO_NOTHING:
+ default:
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ break;
+ }
+
+ return action;
+}
+
+static virusfilter_action virusfilter_treat_infected_file(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ const char *report,
+ bool is_cache)
+{
+ connection_struct *conn = handle->conn;
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ char *fname = fsp->fsp_name->base_name;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ int i;
+ virusfilter_action action;
+ const char *action_name = "UNKNOWN";
+ const char *filepath_q = NULL;
+ char *env_list = NULL;
+ char *command = NULL;
+ int command_result;
+ int ret;
+
+ action = virusfilter_do_infected_file_action(handle, config, mem_ctx,
+ fsp, &filepath_q);
+ for (i=0; virusfilter_actions[i].name; i++) {
+ if (virusfilter_actions[i].value == action) {
+ action_name = virusfilter_actions[i].name;
+ break;
+ }
+ }
+ DBG_WARNING("Infected file action: %s/%s: %s\n", cwd_fname,
+ fname, action_name);
+
+ if (!config->infected_file_command) {
+ return action;
+ }
+
+ ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+ if (ret == -1) {
+ goto done;
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
+ fname);
+ if (ret == -1) {
+ goto done;
+ }
+ if (report != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_FILE_REPORT",
+ report);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_FILE_ACTION",
+ action_name);
+ if (ret == -1) {
+ goto done;
+ }
+ if (filepath_q != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_QUARANTINED_FILE_PATH",
+ filepath_q);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ if (is_cache) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_RESULT_IS_CACHE", "yes");
+ if (ret == -1) {
+ goto done;
+ }
+ }
+
+ command = virusfilter_string_sub(mem_ctx, conn,
+ config->infected_file_command);
+ if (command == NULL) {
+ DBG_ERR("virusfilter_string_sub failed\n");
+ goto done;
+ }
+
+ DBG_NOTICE("Infected file command line: %s/%s: %s\n", cwd_fname,
+ fname, command);
+
+ command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+ conn, true);
+ if (command_result != 0) {
+ DBG_ERR("Infected file command failed: %d\n", command_result);
+ }
+
+ DBG_DEBUG("Infected file command finished: %d\n", command_result);
+
+done:
+ TALLOC_FREE(env_list);
+ TALLOC_FREE(command);
+
+ return action;
+}
+
+static void virusfilter_treat_scan_error(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ const char *report,
+ bool is_cache)
+{
+ connection_struct *conn = handle->conn;
+ const char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ char *env_list = NULL;
+ char *command = NULL;
+ int command_result;
+ int ret;
+
+ if (!config->scan_error_command) {
+ return;
+ }
+ ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+ if (ret == -1) {
+ goto done;
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
+ fname);
+ if (ret == -1) {
+ goto done;
+ }
+ if (report != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_SCAN_ERROR_REPORT",
+ report);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ if (is_cache) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_RESULT_IS_CACHE", "1");
+ if (ret == -1) {
+ goto done;
+ }
+ }
+
+ command = virusfilter_string_sub(mem_ctx, conn,
+ config->scan_error_command);
+ if (command == NULL) {
+ DBG_ERR("virusfilter_string_sub failed\n");
+ goto done;
+ }
+
+ DBG_NOTICE("Scan error command line: %s/%s: %s\n", cwd_fname,
+ fname, command);
+
+ command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+ conn, true);
+ if (command_result != 0) {
+ DBG_ERR("Scan error command failed: %d\n", command_result);
+ }
+
+done:
+ TALLOC_FREE(env_list);
+ TALLOC_FREE(command);
+}
+
+static virusfilter_result virusfilter_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp)
+{
+ virusfilter_result scan_result;
+ char *scan_report = NULL;
+ const char *fname = fsp->fsp_name->base_name;
+ const char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ struct virusfilter_cache_entry *scan_cache_e = NULL;
+ bool is_cache = false;
+ virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING;
+ bool add_scan_cache = true;
+ bool ok = false;
+
+ if (config->cache) {
+ DBG_DEBUG("Searching cache entry: fname: %s\n", fname);
+ scan_cache_e = virusfilter_cache_get(config->cache,
+ cwd_fname, fname);
+ if (scan_cache_e != NULL) {
+ DBG_DEBUG("Cache entry found: cached result: %d\n",
+ scan_cache_e->result);
+ is_cache = true;
+ scan_result = scan_cache_e->result;
+ scan_report = scan_cache_e->report;
+ goto virusfilter_scan_result_eval;
+ }
+ DBG_DEBUG("Cache entry not found\n");
+ }
+
+ if (config->backend->fns->scan_init != NULL) {
+ scan_result = config->backend->fns->scan_init(config);
+ if (scan_result != VIRUSFILTER_RESULT_OK) {
+ scan_result = VIRUSFILTER_RESULT_ERROR;
+ scan_report = talloc_asprintf(
+ talloc_tos(),
+ "Initializing scanner failed");
+ goto virusfilter_scan_result_eval;
+ }
+ }
+
+ scan_result = config->backend->fns->scan(handle, config, fsp,
+ &scan_report);
+
+ if (config->backend->fns->scan_end != NULL) {
+ bool scan_end = true;
+
+ if (config->scan_request_limit > 0) {
+ scan_end = false;
+ config->scan_request_count++;
+ if (config->scan_request_count >=
+ config->scan_request_limit)
+ {
+ scan_end = true;
+ config->scan_request_count = 0;
+ }
+ }
+ if (scan_end) {
+ config->backend->fns->scan_end(config);
+ }
+ }
+
+virusfilter_scan_result_eval:
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ DBG_INFO("Scan result: Clean: %s/%s\n", cwd_fname, fname);
+ break;
+
+ case VIRUSFILTER_RESULT_INFECTED:
+ DBG_ERR("Scan result: Infected: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "infected (memory error on report)");
+ file_action = virusfilter_treat_infected_file(handle,
+ config, fsp, scan_report, is_cache);
+ if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+ add_scan_cache = false;
+ }
+ break;
+
+ case VIRUSFILTER_RESULT_SUSPECTED:
+ if (!config->block_suspected_file) {
+ break;
+ }
+ DBG_ERR("Scan result: Suspected: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "suspected infection (memory error on report)");
+ file_action = virusfilter_treat_infected_file(handle,
+ config, fsp, scan_report, is_cache);
+ if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+ add_scan_cache = false;
+ }
+ break;
+
+ case VIRUSFILTER_RESULT_ERROR:
+ DBG_ERR("Scan result: Error: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "error (memory error on report)");
+ virusfilter_treat_scan_error(handle, config, fsp,
+ scan_report, is_cache);
+ add_scan_cache = false;
+ break;
+
+ default:
+ DBG_ERR("Scan result: Unknown result code %d: %s/%s: %s\n",
+ scan_result, cwd_fname, fname, scan_report ?
+ scan_report : "Unknown (memory error on report)");
+ virusfilter_treat_scan_error(handle, config, fsp,
+ scan_report, is_cache);
+ add_scan_cache = false;
+ break;
+ }
+
+ if (config->cache) {
+ if (!is_cache && add_scan_cache) {
+ DBG_DEBUG("Adding new cache entry: %s, %d\n", fname,
+ scan_result);
+ ok = virusfilter_cache_entry_add(
+ config->cache, cwd_fname, fname,
+ scan_result, scan_report);
+ if (!ok) {
+ DBG_ERR("Cannot create cache entry: "
+ "virusfilter_cache_entry_new failed\n");
+ goto virusfilter_scan_return;
+ }
+ } else if (is_cache) {
+ virusfilter_cache_entry_free(scan_cache_e);
+ }
+ }
+
+virusfilter_scan_return:
+ return scan_result;
+}
+
+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,
+ const struct vfs_open_how *how)
+{
+ 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;
+ SMB_STRUCT_STAT sbuf = smb_fname_in->st;
+
+ 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 (how->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_FSTAT(handle, fsp, &sbuf);
+ 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(sbuf.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 &&
+ sbuf.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 &&
+ sbuf.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, how);
+
+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)
+{
+ /*
+ * The name of this variable is for consistency. If API changes to
+ * match _open change to cwd_fname as in virusfilter_vfs_open.
+ */
+ const char *cwd_fname = handle->conn->connectpath;
+
+ struct virusfilter_config *config = NULL;
+ char *fname = fsp->fsp_name->base_name;
+ int close_result = -1;
+ int close_errno = 0;
+ virusfilter_result scan_result;
+ int scan_errno = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ /*
+ * Must close after scan? It appears not as the scanners are not
+ * internal and other modules such as greyhole seem to do
+ * SMB_VFS_NEXT_* functions before processing.
+ */
+ close_result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+ if (close_result == -1) {
+ close_errno = errno;
+ }
+
+ /*
+ * Return immediately if close_result == -1, and close_errno == EBADF.
+ * If close failed, file likely doesn't exist, do not try to scan.
+ */
+ if (close_result == -1 && close_errno == EBADF) {
+ if (fsp->fsp_flags.modified) {
+ DBG_DEBUG("Removing cache entry (if existent): "
+ "fname: %s\n", fname);
+ virusfilter_cache_remove(config->cache,
+ cwd_fname, fname);
+ }
+ goto virusfilter_vfs_close_fail;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ DBG_INFO("Not scanned: Directory: %s/\n", cwd_fname);
+ return close_result;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ if (config->scan_on_open && fsp->fsp_flags.modified) {
+ if (config->cache) {
+ DBG_DEBUG("Removing cache entry (if existent)"
+ ": fname: %s\n", fname);
+ virusfilter_cache_remove(
+ config->cache,
+ cwd_fname, fname);
+ }
+ }
+ DBG_INFO("Not scanned: only file backed streams can be scanned:"
+ " %s/%s\n", cwd_fname, fname);
+ return close_result;
+ }
+
+ if (!config->scan_on_close) {
+ if (config->scan_on_open && fsp->fsp_flags.modified) {
+ if (config->cache) {
+ DBG_DEBUG("Removing cache entry (if existent)"
+ ": fname: %s\n", fname);
+ virusfilter_cache_remove(
+ config->cache,
+ cwd_fname, fname);
+ }
+ }
+ DBG_INFO("Not scanned: scan on close is disabled: %s/%s\n",
+ cwd_fname, fname);
+ return close_result;
+ }
+
+ if (!fsp->fsp_flags.modified) {
+ DBG_NOTICE("Not scanned: File not modified: %s/%s\n",
+ cwd_fname, fname);
+
+ return close_result;
+ }
+
+ if (is_in_path(fname, config->exclude_files, false)) {
+ DBG_INFO("Not scanned: exclude files: %s/%s\n",
+ cwd_fname, fname);
+ return close_result;
+ }
+
+ scan_result = virusfilter_scan(handle, config, fsp);
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ break;
+ case VIRUSFILTER_RESULT_INFECTED:
+ scan_errno = config->infected_close_errno;
+ goto virusfilter_vfs_close_fail;
+ case VIRUSFILTER_RESULT_ERROR:
+ if (config->block_access_on_error) {
+ DBG_INFO("Block access\n");
+ scan_errno = config->scan_error_close_errno;
+ goto virusfilter_vfs_close_fail;
+ }
+ break;
+ default:
+ scan_errno = config->scan_error_close_errno;
+ goto virusfilter_vfs_close_fail;
+ }
+
+ if (close_errno != 0) {
+ errno = close_errno;
+ }
+
+ return close_result;
+
+virusfilter_vfs_close_fail:
+
+ errno = (scan_errno != 0) ? scan_errno : close_errno;
+
+ return close_result;
+}
+
+static int virusfilter_vfs_unlinkat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ struct virusfilter_config *config = NULL;
+ struct smb_filename *full_fname = NULL;
+ char *fname = NULL;
+ char *cwd_fname = dirfsp->fsp_name->base_name;
+
+ if (ret != 0 && errno != ENOENT) {
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ if (config->cache == NULL) {
+ return 0;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ fname = full_fname->base_name;
+
+ DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname);
+ virusfilter_cache_remove(config->cache, cwd_fname, fname);
+
+ TALLOC_FREE(full_fname);
+ return 0;
+}
+
+static int virusfilter_vfs_renameat(
+ struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int ret = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ struct virusfilter_config *config = NULL;
+ char *fname = NULL;
+ char *dst_fname = NULL;
+ char *cwd_fname = handle->conn->cwd_fsp->fsp_name->base_name;
+ struct smb_filename *full_src = NULL;
+ struct smb_filename *full_dst = NULL;
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ if (config->cache == NULL) {
+ return 0;
+ }
+
+ full_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_src == NULL) {
+ errno = ENOMEM;
+ ret = -1;
+ goto out;
+ }
+
+ full_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_dst == NULL) {
+ errno = ENOMEM;
+ ret = -1;
+ goto out;
+ }
+
+ fname = full_src->base_name;
+ dst_fname = full_dst->base_name;
+
+ DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n",
+ fname, dst_fname);
+ virusfilter_cache_entry_rename(config->cache,
+ cwd_fname,
+ fname,
+ dst_fname);
+
+ ret = 0;
+ out:
+ TALLOC_FREE(full_src);
+ TALLOC_FREE(full_dst);
+ return ret;
+}
+
+
+/* VFS operations */
+static struct vfs_fn_pointers vfs_virusfilter_fns = {
+ .connect_fn = virusfilter_vfs_connect,
+ .disconnect_fn = virusfilter_vfs_disconnect,
+ .openat_fn = virusfilter_vfs_openat,
+ .close_fn = virusfilter_vfs_close,
+ .unlinkat_fn = virusfilter_vfs_unlinkat,
+ .renameat_fn = virusfilter_vfs_renameat,
+};
+
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *);
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS status;
+
+ status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "virusfilter",
+ &vfs_virusfilter_fns);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ virusfilter_debug_class = debug_add_class("virusfilter");
+ if (virusfilter_debug_class == -1) {
+ virusfilter_debug_class = DBGC_VFS;
+ DBG_ERR("Couldn't register custom debugging class!\n");
+ } else {
+ DBG_DEBUG("Debug class number: %d\n", virusfilter_debug_class);
+ }
+
+ DBG_INFO("registered\n");
+
+ return status;
+}