diff options
author | Trever L. Adams <trever.adams@gmail.com> | 2016-10-18 13:34:53 -0600 |
---|---|---|
committer | Ralph Boehme <slow@samba.org> | 2018-01-24 10:29:46 +0100 |
commit | b1e69edd0592d3b4b0f958792826a236dd3466e1 (patch) | |
tree | 726468d88e554c1ad9ef216f3ddce6bc123dfa27 /source3/modules | |
parent | 70d7f7d03c46c8727833f322bdc03da1b2aad720 (diff) | |
download | samba-b1e69edd0592d3b4b0f958792826a236dd3466e1.tar.gz |
Samba-VirusFilter: common headers and sources.
Samba-VirusFilter Contributors:
SATOH Fumiyasu @ OSS Technology Corp., Japan
Module creator/maintainer
Luke Dixon luke.dixon@zynstra.com
Samba 4 support
Trever L. Adams
Documentation
Code contributions
Samba-master merge work
With many thanks to the Samba Team.
Signed-off-by: Trever L. Adams <trever.adams@gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas@osstech.co.jp>
Reviewed-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
Diffstat (limited to 'source3/modules')
-rw-r--r-- | source3/modules/vfs_virusfilter.c | 1508 | ||||
-rw-r--r-- | source3/modules/vfs_virusfilter_common.h | 149 | ||||
-rw-r--r-- | source3/modules/vfs_virusfilter_utils.c | 1025 | ||||
-rw-r--r-- | source3/modules/vfs_virusfilter_utils.h | 177 | ||||
-rw-r--r-- | source3/modules/wscript_build | 13 |
5 files changed, 2872 insertions, 0 deletions
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c new file mode 100644 index 00000000000..a23d1f7c641 --- /dev/null +++ b/source3/modules/vfs_virusfilter.c @@ -0,0 +1,1508 @@ +/* + * 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_FSAV, + VIRUSFILTER_SCANNER_SOPHOS +}; + +static const struct enum_list scanner_list[] = { + { VIRUSFILTER_SCANNER_CLAMAV, "clamav" }, + { 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 neccessary */ + 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); + if (smb_fname == NULL) { + goto done; + } + + ret = SMB_VFS_NEXT_MKDIR(handle, + smb_fname, + config->quarantine_dir_mode); + if (ret != 0) { + TALLOC_FREE(smb_fname); + + DBG_WARNING("quarantine: mkdir 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 *temp_quarantine_dir_mode = 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); + } + + 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); + + config->infected_file_command = lp_parm_const_string( + snum, "virusfilter", "infected file command", NULL); + + config->scan_error_command = lp_parm_const_string( + snum, "virusfilter", "scan error command", NULL); + + config->block_access_on_error = lp_parm_bool( + snum, "virusfilter", "block access on error", false); + + tmp = talloc_asprintf(config, "%s/.quarantine", + handle->conn->connectpath); + + config->quarantine_dir = lp_parm_const_string( + snum, "virusfilter", "quarantine directory", + tmp ? tmp : "/tmp/.quarantine"); + + 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) { + sscanf(temp_quarantine_dir_mode, "%o", + &config->quarantine_dir_mode); + } + + config->quarantine_prefix = lp_parm_const_string( + snum, "virusfilter", "quarantine prefix", + VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX); + + config->quarantine_suffix = lp_parm_const_string( + snum, "virusfilter", "quarantine suffix", + VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX); + + /* + * Make sure prefixes and suffixes do not contain directory + * delimiters + */ + 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; + } + 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); + + config->rename_prefix = lp_parm_const_string( + snum, "virusfilter", "rename prefix", + VIRUSFILTER_DEFAULT_RENAME_PREFIX); + + config->rename_suffix = lp_parm_const_string( + snum, "virusfilter", "rename suffix", + VIRUSFILTER_DEFAULT_RENAME_SUFFIX); + + /* + * Make sure prefixes and suffixes do not contain directory + * delimiters + */ + 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; + } + 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); + + config->socket_path = lp_parm_const_string( + snum, "virusfilter", "socket path", NULL); + + /* 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) { + canonicalize_absolute_path(handle, + config->socket_path); + } + + 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"); + 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; + } + + /* This goes away as soon as the next commit adds an actual backend... */ + if (config->backend == NULL) { + DBG_INFO("Not implemented\n"); + return SMB_VFS_NEXT_CONNECT(handle, svc, user); + } + + 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_fname->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, 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_fname->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, + 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_UNLINK(handle, fsp->fsp_name); + if (ret == -1) { + saved_errno = errno; + } + unbecome_root(); + if (ret == -1) { + DBG_ERR("Delete [%s/%s] failed: %s\n", + fsp->conn->cwd_fname->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_fname->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_fname->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_fname->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"); + 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_open( + struct vfs_handle_struct *handle, + struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ + TALLOC_CTX *mem_ctx = talloc_tos(); + struct virusfilter_config *config; + const char *cwd_fname = fsp->conn->cwd_fname->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, ok2; + char *sret = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct virusfilter_config, return -1); + + 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++; + } + + ok1 = is_ntfs_stream_smb_fname(smb_fname); + ok2 = is_ntfs_default_stream_smb_fname(smb_fname); + if (ok1 && !ok2) { + 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; + } + +virusfilter_vfs_open_next: + return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode); + +virusfilter_vfs_open_fail: + 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 = NULL; + int close_result = -1; + int close_errno = 0; + virusfilter_result scan_result; + int scan_errno = 0; + bool ok1, ok2; + + 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->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->is_directory) { + DBG_INFO("Not scanned: Directory: %s/%s\n", cwd_fname, + fname); + return close_result; + } + + ok1 = is_ntfs_stream_smb_fname(fsp->fsp_name); + ok2 = is_ntfs_default_stream_smb_fname(fsp->fsp_name); + if (ok1 && !ok2) { + if (config->scan_on_open && fsp->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->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->modified) { + DBG_NOTICE("Not scanned: File not modified: %s/%s\n", + cwd_fname, fname); + + return close_result; + } + + if (config->exclude_files && 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_unlink( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname); + struct virusfilter_config *config = NULL; + char *fname = NULL; + char *cwd_fname = handle->conn->cwd_fname->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; + } + + fname = smb_fname->base_name; + + DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname); + virusfilter_cache_remove(config->cache, cwd_fname, fname); + + return 0; +} + +static int virusfilter_vfs_rename( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst) +{ + int ret = SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst); + struct virusfilter_config *config = NULL; + char *fname = NULL; + char *dst_fname = NULL; + char *cwd_fname = handle->conn->cwd_fname->base_name; + + if (ret != 0) { + return ret; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct virusfilter_config, return -1); + + if (config->cache == NULL) { + return 0; + } + + fname = smb_fname_src->base_name; + dst_fname = smb_fname_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); + + return 0; +} + +/* VFS operations */ +static struct vfs_fn_pointers vfs_virusfilter_fns = { + .connect_fn = virusfilter_vfs_connect, + .disconnect_fn = virusfilter_vfs_disconnect, + .open_fn = virusfilter_vfs_open, + .close_fn = virusfilter_vfs_close, + .unlink_fn = virusfilter_vfs_unlink, + .rename_fn = virusfilter_vfs_rename, +}; + +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; +} diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h new file mode 100644 index 00000000000..468883fdaf8 --- /dev/null +++ b/source3/modules/vfs_virusfilter_common.h @@ -0,0 +1,149 @@ +/* + Samba-VirusFilter VFS modules + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + + 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/>. +*/ + +#ifndef _VIRUSFILTER_COMMON_H +#define _VIRUSFILTER_COMMON_H + +#include <stdint.h> +#include <time.h> + +/* Samba common include file */ +#include "includes.h" + +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "system/filesys.h" +#include "transfer_file.h" +#include "auth.h" +#include "passdb.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include "../lib/tsocket/tsocket.h" + +/* Samba debug class for VIRUSFILTER */ +#undef DBGC_CLASS +#define DBGC_CLASS virusfilter_debug_class +extern int virusfilter_debug_class; + +/* Samba's global variable */ +extern userdom_struct current_user_info; + +#define VIRUSFILTER_VERSION "0.1.5" + +/* ====================================================================== */ + +typedef enum { + VIRUSFILTER_ACTION_DO_NOTHING, + VIRUSFILTER_ACTION_QUARANTINE, + VIRUSFILTER_ACTION_RENAME, + VIRUSFILTER_ACTION_DELETE, +} virusfilter_action; + +typedef enum { + VIRUSFILTER_RESULT_OK, + VIRUSFILTER_RESULT_CLEAN, + VIRUSFILTER_RESULT_ERROR, + VIRUSFILTER_RESULT_INFECTED, + VIRUSFILTER_RESULT_SUSPECTED, + /* FIXME: VIRUSFILTER_RESULT_RISKWARE, */ +} virusfilter_result; + +struct virusfilter_config { + int scan_request_count; + int scan_request_limit; + + /* Scan on file operations */ + bool scan_on_open; + bool scan_on_close; + + /* Special scan options */ + bool scan_archive; + int max_nested_scan_archive; + bool scan_mime; + bool block_suspected_file; + + /* Size limit */ + size_t max_file_size; + size_t min_file_size; + + /* Exclude files */ + name_compare_entry *exclude_files; + + /* Scan result cache */ + struct virusfilter_cache *cache; + int cache_entry_limit; + int cache_time_limit; + + /* Infected file options */ + virusfilter_action infected_file_action; + const char * infected_file_command; + int infected_open_errno; + int infected_close_errno; + + /* Scan error options */ + const char * scan_error_command; + int scan_error_open_errno; + int scan_error_close_errno; + bool block_access_on_error; + + /* Quarantine infected files */ + const char * quarantine_dir; + const char * quarantine_prefix; + const char * quarantine_suffix; + bool quarantine_keep_tree; + bool quarantine_keep_name; + mode_t quarantine_dir_mode; + + /* Rename infected files */ + const char * rename_prefix; + const char * rename_suffix; + + /* Network options */ + const char * socket_path; + struct virusfilter_io_handle *io_h; + + /* The backend AV engine */ + struct virusfilter_backend *backend; +}; + +struct virusfilter_backend_fns { + int (*connect)( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *svc, + const char *user); + void (*disconnect)( + struct vfs_handle_struct *handle); + virusfilter_result (*scan_init)( + struct virusfilter_config *config); + virusfilter_result (*scan)( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp); + void (*scan_end)( + struct virusfilter_config *config); +}; + +struct virusfilter_backend { + unsigned version; + const char *name; + const struct virusfilter_backend_fns *fns; + void *backend_private; +}; + +#endif /* _VIRUSFILTER_COMMON_H */ diff --git a/source3/modules/vfs_virusfilter_utils.c b/source3/modules/vfs_virusfilter_utils.c new file mode 100644 index 00000000000..628e0aef99a --- /dev/null +++ b/source3/modules/vfs_virusfilter_utils.c @@ -0,0 +1,1025 @@ +/* + Samba-VirusFilter VFS modules + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + Copyright (C) 2016-2017 Trever L. Adams + + 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 "modules/vfs_virusfilter_common.h" +#include "modules/vfs_virusfilter_utils.h" + +struct iovec; + +#include "lib/util/iov_buf.h" +#include <tevent.h> +#include "lib/tsocket/tsocket.h" + +int virusfilter_debug_class = DBGC_VFS; + +/* ====================================================================== */ + +char *virusfilter_string_sub( + TALLOC_CTX *mem_ctx, + connection_struct *conn, + const char *str) +{ + return talloc_sub_advanced(mem_ctx, + lp_servicename(mem_ctx, SNUM(conn)), + conn->session_info->unix_info->unix_name, + conn->connectpath, + conn->session_info->unix_token->gid, + conn->session_info->unix_info->sanitized_username, + conn->session_info->info->domain_name, + str); +} + +int virusfilter_vfs_next_move( + struct vfs_handle_struct *vfs_h, + const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst) +{ + int result; + + result = SMB_VFS_NEXT_RENAME(vfs_h, smb_fname_src, smb_fname_dst); + if (result == 0 || errno != EXDEV) { + return result; + } + + /* + * For now, do not handle EXDEV as poking around violates + * stackability. Return -1, simply refuse access. + */ + return -1; +} + +/* Line-based socket I/O + * ====================================================================== + */ + +struct virusfilter_io_handle *virusfilter_io_new( + TALLOC_CTX *mem_ctx, + int connect_timeout, + int io_timeout) +{ + struct virusfilter_io_handle *io_h = talloc_zero(mem_ctx, + struct virusfilter_io_handle); + + if (io_h == NULL) { + return NULL; + } + + io_h->stream = NULL; + io_h->r_len = 0; + + virusfilter_io_set_connect_timeout(io_h, connect_timeout); + virusfilter_io_set_io_timeout(io_h, io_timeout); + virusfilter_io_set_writel_eol(io_h, "\x0A", 1); + virusfilter_io_set_readl_eol(io_h, "\x0A", 1); + + return io_h; +} + +int virusfilter_io_set_connect_timeout( + struct virusfilter_io_handle *io_h, + int timeout) +{ + int timeout_old = io_h->connect_timeout; + + /* timeout <= 0 means infinite */ + io_h->connect_timeout = (timeout > 0) ? timeout : -1; + + return timeout_old; +} + +int virusfilter_io_set_io_timeout( + struct virusfilter_io_handle *io_h, + int timeout) +{ + int timeout_old = io_h->io_timeout; + + /* timeout <= 0 means infinite */ + io_h->io_timeout = (timeout > 0) ? timeout : -1; + + return timeout_old; +} + +void virusfilter_io_set_writel_eol( + struct virusfilter_io_handle *io_h, + const char *eol, + int eol_size) +{ + if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) { + return; + } + + memcpy(io_h->w_eol, eol, eol_size); + io_h->w_eol_size = eol_size; +} + +void virusfilter_io_set_readl_eol( + struct virusfilter_io_handle *io_h, + const char *eol, + int eol_size) +{ + if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) { + return; + } + + memcpy(io_h->r_eol, eol, eol_size); + io_h->r_eol_size = eol_size; +} + +bool virusfilter_io_connect_path( + struct virusfilter_io_handle *io_h, + const char *path) +{ + struct sockaddr_un addr; + NTSTATUS status; + int socket, bes_result, flags, ret; + + ZERO_STRUCT(addr); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path)); + + status = open_socket_out((struct sockaddr_storage *)&addr, 0, + io_h->connect_timeout, + &socket); + if (!NT_STATUS_IS_OK(status)) { + io_h->stream = NULL; + return false; + } + + /* We must not block */ + flags = fcntl(socket, F_GETFL); + if (flags <= 0) { + /* Handle error by ignoring */; + flags = 0; + DBG_WARNING("Could not get flags on socket (%s).\n", + strerror(errno)); + } + flags |= SOCK_NONBLOCK; + ret = fcntl(socket, F_SETFL, flags); + if (ret == -1) { + /* Handle error by ignoring for now */ + DBG_WARNING("Could not set flags on socket: %s.\n", + strerror(errno)); + } + + bes_result = tstream_bsd_existing_socket(io_h, socket, &io_h->stream); + if (bes_result < 0) { + DBG_ERR("Could not convert socket to tstream: %s.\n", + strerror(errno)); + io_h->stream = NULL; + return false; + } + + return true; +} + +static void disconnect_done(struct tevent_req *req) +{ + uint64_t *perr = tevent_req_callback_data(req, uint64_t); + int ret; + int err_ret; + + ret = tstream_disconnect_recv(req, &err_ret); + TALLOC_FREE(req); + if (ret == -1) { + *perr = err_ret; + } +} + +bool virusfilter_io_disconnect( + struct virusfilter_io_handle *io_h) +{ + struct tevent_req *req; + struct tevent_context *ev; + uint64_t *perror = NULL; + bool ok = true; + TALLOC_CTX *frame = talloc_stackframe(); + + if (io_h->stream == NULL) { + io_h->r_len = 0; + TALLOC_FREE(frame); + return VIRUSFILTER_RESULT_OK; + } + + ev = tevent_context_init(frame); + if (ev == NULL) { + DBG_ERR("Failed to setup event context.\n"); + ok = false; + goto fail; + } + + /* Error return - must be talloc'ed. */ + perror = talloc_zero(frame, uint64_t); + if (perror == NULL) { + goto fail; + } + + req = tstream_disconnect_send(io_h, ev, io_h->stream); + + /* Callback when disconnect is done. */ + tevent_req_set_callback(req, disconnect_done, perror); + + /* Set timeout. */ + ok = tevent_req_set_endtime(req, ev, timeval_current_ofs_msec( + io_h->connect_timeout)); + if (!ok) { + DBG_ERR("Can't set endtime\n"); + goto fail; + } + + /* Loop waiting for req to finish. */ + ok = tevent_req_poll(req, ev); + if (!ok) { + DBG_ERR("tevent_req_poll failed\n"); + goto fail; + } + + /* Emit debug error if failed. */ + if (*perror != 0) { + DBG_DEBUG("Error %s\n", strerror((int)*perror)); + goto fail; + } + + /* Here we know we disconnected. */ + + io_h->stream = NULL; + io_h->r_len = 0; + + fail: + TALLOC_FREE(frame); + return ok; +} + +static void writev_done(struct tevent_req *req) +{ + uint64_t *perr = tevent_req_callback_data(req, uint64_t); + int ret; + int err_ret; + + ret = tstream_writev_recv(req, &err_ret); + TALLOC_FREE(req); + if (ret == -1) { + *perr = err_ret; + } +} + +/**************************************************************************** + Write all data from an iov array, with msec timeout (per write) + NB. This can be called with a non-socket fd, don't add dependencies + on socket calls. +****************************************************************************/ + +bool write_data_iov_timeout( + struct tstream_context *stream, + const struct iovec *iov, + size_t iovcnt, + int ms_timeout) +{ + struct tevent_context *ev = NULL; + struct tevent_req *req = NULL; + uint64_t *perror = NULL; + bool ok = false; + TALLOC_CTX *frame = talloc_stackframe(); + + ev = tevent_context_init(frame); + if (ev == NULL) { + DBG_ERR("Failed to setup event context.\n"); + goto fail; + } + + /* Error return - must be talloc'ed. */ + perror = talloc_zero(frame, uint64_t); + if (perror == NULL) { + goto fail; + } + + /* Send the data. */ + req = tstream_writev_send(frame, ev, stream, iov, iovcnt); + if (req == NULL) { + DBG_ERR("Out of memory.\n"); + goto fail; + } + + /* Callback when *all* data sent. */ + tevent_req_set_callback(req, writev_done, perror); + + /* Set timeout. */ + ok = tevent_req_set_endtime(req, ev, + timeval_current_ofs_msec(ms_timeout)); + if (!ok) { + DBG_ERR("Can't set endtime\n"); + goto fail; + } + + /* Loop waiting for req to finish. */ + ok = tevent_req_poll(req, ev); + if (!ok) { + DBG_ERR("tevent_req_poll failed\n"); + goto fail; + } + + /* Done with req - freed by the callback. */ + req = NULL; + + /* Emit debug error if failed. */ + if (*perror != 0) { + DBG_DEBUG("Error %s\n", strerror((int)*perror)); + goto fail; + } + + /* Here we know we correctly wrote all data. */ + TALLOC_FREE(frame); + return true; + + fail: + TALLOC_FREE(frame); + return false; +} + +bool virusfilter_io_write( + struct virusfilter_io_handle *io_h, + const char *data, + size_t data_size) +{ + struct iovec iov; + + if (data_size == 0) { + return VIRUSFILTER_RESULT_OK; + } + + iov.iov_base = discard_const_p(void, data); + iov.iov_len = data_size; + + return write_data_iov_timeout(io_h->stream, &iov, 1, io_h->io_timeout); +} + +bool virusfilter_io_writel( + struct virusfilter_io_handle *io_h, + const char *data, + size_t data_size) +{ + bool ok; + + ok = virusfilter_io_write(io_h, data, data_size); + if (!ok) { + return ok; + } + + return virusfilter_io_write(io_h, io_h->w_eol, io_h->w_eol_size); +} + +bool virusfilter_io_writefl( + struct virusfilter_io_handle *io_h, + const char *data_fmt, ...) +{ + va_list ap; + char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE]; + size_t data_size; + + va_start(ap, data_fmt); + data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap); + va_end(ap); + + if (unlikely (data_size < 0)) { + DBG_ERR("vsnprintf failed: %s\n", strerror(errno)); + return false; + } + + memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size); + data_size += io_h->w_eol_size; + + return virusfilter_io_write(io_h, data, data_size); +} + +bool virusfilter_io_vwritefl( + struct virusfilter_io_handle *io_h, + const char *data_fmt, va_list ap) +{ + char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE]; + size_t data_size; + + data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap); + + if (unlikely (data_size < 0)) { + DBG_ERR("vsnprintf failed: %s\n", strerror(errno)); + return false; + } + + memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size); + data_size += io_h->w_eol_size; + + return virusfilter_io_write(io_h, data, data_size); +} + +bool virusfilter_io_writev( + struct virusfilter_io_handle *io_h, ...) +{ + va_list ap; + struct iovec iov[VIRUSFILTER_IO_IOV_MAX], *iov_p; + int iov_n; + + va_start(ap, io_h); + for (iov_p = iov, iov_n = 0; + iov_n < VIRUSFILTER_IO_IOV_MAX; + iov_p++, iov_n++) + { + iov_p->iov_base = va_arg(ap, void *); + if (iov_p->iov_base == NULL) { + break; + } + iov_p->iov_len = va_arg(ap, int); + } + va_end(ap); + + return write_data_iov_timeout(io_h->stream, iov, iov_n, + io_h->io_timeout); +} + +bool virusfilter_io_writevl( + struct virusfilter_io_handle *io_h, ...) +{ + va_list ap; + struct iovec iov[VIRUSFILTER_IO_IOV_MAX + 1], *iov_p; + int iov_n; + + va_start(ap, io_h); + for (iov_p = iov, iov_n = 0; iov_n < VIRUSFILTER_IO_IOV_MAX; + iov_p++, iov_n++) + { + iov_p->iov_base = va_arg(ap, void *); + if (iov_p->iov_base == NULL) { + break; + } + iov_p->iov_len = va_arg(ap, int); + } + va_end(ap); + + iov_p->iov_base = io_h->r_eol; + iov_p->iov_len = io_h->r_eol_size; + iov_n++; + + return write_data_iov_timeout(io_h->stream, iov, iov_n, + io_h->io_timeout); +} + +static bool return_existing_line(TALLOC_CTX *ctx, + struct virusfilter_io_handle *io_h, + char **read_line) +{ + size_t read_line_len = 0; + char *end_p = NULL; + char *eol = NULL; + + eol = memmem(io_h->r_buffer, io_h->r_len, + io_h->r_eol, io_h->r_eol_size); + if (eol == NULL) { + return false; + } + end_p = eol + io_h->r_eol_size; + + *eol = '\0'; + read_line_len = strlen(io_h->r_buffer) + 1; + *read_line = talloc_memdup(ctx, + io_h->r_buffer, + read_line_len); + if (*read_line == NULL) { + return false; + } + + /* + * Copy the remaining buffer over the line + * we returned. + */ + memmove(io_h->r_buffer, + end_p, + io_h->r_len - (end_p - io_h->r_buffer)); + + /* And reduce the size left in the buffer. */ + io_h->r_len -= (end_p - io_h->r_buffer); + return true; +} + +static void readv_done(struct tevent_req *req) +{ + uint64_t *perr = tevent_req_callback_data(req, uint64_t); + int ret; + int err_ret; + + ret = tstream_readv_recv(req, &err_ret); + TALLOC_FREE(req); + if (ret == -1) { + *perr = err_ret; + } +} + +bool virusfilter_io_readl(TALLOC_CTX *ctx, + struct virusfilter_io_handle *io_h, + char **read_line) +{ + struct tevent_context *ev = NULL; + bool ok = false; + uint64_t *perror = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + /* Search for an existing complete line. */ + ok = return_existing_line(ctx, io_h, read_line); + if (ok) { + goto finish; + } + + /* + * No complete line in the buffer. We must read more + * from the server. + */ + ev = tevent_context_init(frame); + if (ev == NULL) { + DBG_ERR("Failed to setup event context.\n"); + goto finish; + } + + /* Error return - must be talloc'ed. */ + perror = talloc_zero(frame, uint64_t); + if (perror == NULL) { + goto finish; + } + + for (;;) { + ssize_t pending = 0; + size_t read_size = 0; + struct iovec iov; + struct tevent_req *req = NULL; + + /* + * How much can we read ? + */ + pending = tstream_pending_bytes(io_h->stream); + if (pending < 0) { + DBG_ERR("tstream_pending_bytes failed (%s).\n", + strerror(errno)); + goto finish; + } + + read_size = pending; + /* Must read at least one byte. */ + read_size = MIN(read_size, 1); + + /* And max remaining buffer space. */ + read_size = MAX(read_size, + (sizeof(io_h->r_buffer) - io_h->r_len)); + + if (read_size == 0) { + /* Buffer is full with no EOL. Error out. */ + DBG_ERR("Line buffer full.\n"); + goto finish; + } + + iov.iov_base = io_h->r_buffer + io_h->r_len; + iov.iov_len = read_size; + + /* Read the data. */ + req = tstream_readv_send(frame, + ev, + io_h->stream, + &iov, + 1); + if (req == NULL) { + DBG_ERR("out of memory.\n"); + goto finish; + } + + /* Callback when *all* data read. */ + tevent_req_set_callback(req, readv_done, perror); + + /* Set timeout. */ + ok = tevent_req_set_endtime(req, ev, + timeval_current_ofs_msec(io_h->io_timeout)); + if (!ok) { + DBG_ERR("can't set endtime\n"); + goto finish; + } + + /* Loop waiting for req to finish. */ + ok = tevent_req_poll(req, ev); + if (!ok) { + DBG_ERR("tevent_req_poll failed\n"); + goto finish; + } + + /* Done with req - freed by the callback. */ + req = NULL; + + /* + * Emit debug error if failed. + * EPIPE may be success so, don't exit. + */ + if (*perror != 0 && *perror != EPIPE) { + DBG_DEBUG("Error %s\n", strerror((int)*perror)); + errno = (int)*perror; + goto finish; + } + + /* + * We read read_size bytes. Extend the useable + * buffer length. + */ + io_h->r_len += read_size; + + /* Paranoia... */ + SMB_ASSERT(io_h->r_len <= sizeof(io_h->r_buffer)); + + /* Exit if we have a line to return. */ + ok = return_existing_line(ctx, io_h, read_line); + if (ok) { + goto finish; + } + /* No eol - keep reading. */ + } + + finish: + + TALLOC_FREE(frame); + return ok; +} + +bool virusfilter_io_writefl_readl( + struct virusfilter_io_handle *io_h, + char **read_line, + const char *fmt, ...) +{ + bool ok; + + if (fmt) { + va_list ap; + + va_start(ap, fmt); + ok = virusfilter_io_vwritefl(io_h, fmt, ap); + va_end(ap); + + if (!ok) { + return ok; + } + } + + ok = virusfilter_io_readl(talloc_tos(), io_h, read_line); + if (!ok) { + DBG_ERR("virusfilter_io_readl not OK: %d\n", ok); + return false; + } + if (io_h->r_len == 0) { /* EOF */ + DBG_ERR("virusfilter_io_readl EOF\n"); + return false; + } + + return true; +} + +struct virusfilter_cache *virusfilter_cache_new( + TALLOC_CTX *ctx, + int entry_limit, + time_t time_limit) +{ + struct virusfilter_cache *cache; + + if (time_limit == 0) { + return NULL; + } + + cache = talloc_zero(ctx, struct virusfilter_cache); + if (cache == NULL) { + DBG_ERR("talloc_zero failed.\n"); + return NULL; + } + + cache->cache = memcache_init(cache->ctx, entry_limit * + (sizeof(struct virusfilter_cache_entry) + + VIRUSFILTER_CACHE_BUFFER_SIZE)); + if (cache->cache == NULL) { + DBG_ERR("memcache_init failed.\n"); + return NULL; + } + cache->ctx = ctx; + cache->time_limit = time_limit; + + return cache; +} + +bool virusfilter_cache_entry_add( + struct virusfilter_cache *cache, + const char *directory, + const char *fname, + virusfilter_result result, + char *report) +{ + int blob_size = sizeof(struct virusfilter_cache_entry); + struct virusfilter_cache_entry *cache_e = + talloc_zero_size(NULL, blob_size); + int fname_len = 0; + + if (fname == NULL || directory == NULL) { + TALLOC_FREE(report); + return false; + } + + fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname); + + if (fname == NULL) { + TALLOC_FREE(report); + return false; + } + + fname_len = strlen(fname); + + if (cache_e == NULL|| cache->time_limit == 0) { + TALLOC_FREE(report); + return false; + } + + cache_e->result = result; + if (report != NULL) { + cache_e->report = talloc_steal(cache_e, report); + } + if (cache->time_limit > 0) { + cache_e->time = time(NULL); + } + + memcache_add_talloc(cache->cache, + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(fname, fname_len), &cache_e); + + return true; +} + +bool virusfilter_cache_entry_rename( + struct virusfilter_cache *cache, + const char *directory, + char *old_fname, + char *new_fname) +{ + int old_fname_len = 0; + int new_fname_len = 0; + struct virusfilter_cache_entry *new_data = NULL; + struct virusfilter_cache_entry *old_data = NULL; + + if (old_fname == NULL || new_fname == NULL || directory == NULL) { + return false; + } + + old_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, old_fname); + new_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, new_fname); + + if (old_fname == NULL || new_fname == NULL) { + TALLOC_FREE(old_fname); + TALLOC_FREE(new_fname); + return false; + } + + old_fname_len = strlen(old_fname); + new_fname_len = strlen(new_fname); + + old_data = memcache_lookup_talloc( + cache->cache, + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(old_fname, old_fname_len)); + + if (old_data == NULL) { + return false; + } + + new_data = talloc_memdup(cache->ctx, old_data, + sizeof(struct virusfilter_cache_entry)); + if (new_data == NULL) { + return false; + } + new_data->report = talloc_strdup(new_data, old_data->report); + + memcache_add_talloc(cache->cache, + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(new_fname, new_fname_len), &new_data); + + memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(old_fname, old_fname_len)); + + return true; +} + +void virusfilter_cache_purge(struct virusfilter_cache *cache) +{ + memcache_flush(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC); +} + +struct virusfilter_cache_entry *virusfilter_cache_get( + struct virusfilter_cache *cache, + const char *directory, + const char *fname) +{ + int fname_len = 0; + struct virusfilter_cache_entry *cache_e = NULL; + struct virusfilter_cache_entry *data = NULL; + + if (fname == NULL || directory == NULL) { + return 0; + } + + fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname); + + if (fname == NULL) { + return 0; + } + + fname_len = strlen(fname); + + data = memcache_lookup_talloc(cache->cache, + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(fname, fname_len)); + + if (data == NULL) { + return cache_e; + } + + if (cache->time_limit > 0) { + if (time(NULL) - data->time > cache->time_limit) { + DBG_DEBUG("Cache entry is too old: %s\n", + fname); + virusfilter_cache_remove(cache, directory, fname); + return cache_e; + } + } + cache_e = talloc_memdup(cache->ctx, data, + sizeof(struct virusfilter_cache_entry)); + if (cache_e == NULL) { + return NULL; + } + if (data->report != NULL) { + cache_e->report = talloc_strdup(cache_e, data->report); + } else { + cache_e->report = NULL; + } + + return cache_e; +} + +void virusfilter_cache_remove(struct virusfilter_cache *cache, + const char *directory, + const char *fname) +{ + DBG_DEBUG("Purging cache entry: %s/%s\n", directory, fname); + + if (fname == NULL || directory == NULL) { + return; + } + + fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname); + + if (fname == NULL) { + return; + } + + memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(fname, strlen(fname))); +} + +void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e) +{ + if (cache_e != NULL) { + TALLOC_FREE(cache_e->report); + cache_e->report = NULL; + } + TALLOC_FREE(cache_e); +} + +/* Shell scripting + * ====================================================================== + */ + +int virusfilter_env_set( + TALLOC_CTX *mem_ctx, + char **env_list, + const char *name, + const char *value) +{ + char *env_new; + int ret; + + env_new = talloc_asprintf(mem_ctx, "%s=%s", name, value); + if (env_new == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + return -1; + } + + ret = strv_add(mem_ctx, env_list, env_new); + + TALLOC_FREE(env_new); + + return ret; +} + +/* virusfilter_env version Samba's *_sub_advanced() in substitute.c */ +int virusfilter_shell_set_conn_env( + TALLOC_CTX *mem_ctx, + char **env_list, + connection_struct *conn) +{ + int snum = SNUM(conn); + char *server_addr_p; + char *client_addr_p; + const char *local_machine_name = get_local_machine_name(); + fstring pidstr; + int ret; + + if (local_machine_name == NULL || *local_machine_name == '\0') { + local_machine_name = lp_netbios_name(); + } + + server_addr_p = tsocket_address_inet_addr_string( + conn->sconn->local_address, talloc_tos()); + + if (server_addr_p != NULL) { + ret = strncmp("::ffff:", server_addr_p, 7); + if (ret == 0) { + server_addr_p += 7; + } + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_IP", + server_addr_p); + } + TALLOC_FREE(server_addr_p); + + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_NAME", + myhostname()); + virusfilter_env_set(mem_ctx, env_list, + "VIRUSFILTER_SERVER_NETBIOS_NAME", + local_machine_name); + slprintf(pidstr,sizeof(pidstr)-1, "%ld", (long)getpid()); + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_PID", + pidstr); + + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_NAME", + lp_const_servicename(snum)); + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_PATH", + conn->cwd_fname->base_name); + + client_addr_p = tsocket_address_inet_addr_string( + conn->sconn->remote_address, talloc_tos()); + + if (client_addr_p != NULL) { + ret = strncmp("::ffff:", client_addr_p, 7); + if (ret == 0) { + client_addr_p += 7; + } + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_IP", + client_addr_p); + } + TALLOC_FREE(client_addr_p); + + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_NAME", + conn->sconn->remote_hostname); + virusfilter_env_set(mem_ctx, env_list, + "VIRUSFILTER_CLIENT_NETBIOS_NAME", + get_remote_machine_name()); + + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_NAME", + get_current_username()); + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_DOMAIN", + current_user_info.domain); + + return 0; +} + +/* Wrapper to Samba's smbrun() in smbrun.c */ +int virusfilter_shell_run( + TALLOC_CTX *mem_ctx, + const char *cmd, + char **env_list, + connection_struct *conn, + bool sanitize) +{ + int ret; + + if (conn != NULL) { + ret = virusfilter_shell_set_conn_env(mem_ctx, env_list, conn); + if (ret == -1) { + return -1; + } + } + + if (sanitize) { + return smbrun(cmd, NULL, strv_to_env(talloc_tos(), *env_list)); + } else { + return smbrun_no_sanitize(cmd, NULL, strv_to_env(talloc_tos(), + *env_list)); + } +} diff --git a/source3/modules/vfs_virusfilter_utils.h b/source3/modules/vfs_virusfilter_utils.h new file mode 100644 index 00000000000..69754aa6546 --- /dev/null +++ b/source3/modules/vfs_virusfilter_utils.h @@ -0,0 +1,177 @@ +/* + Samba-VirusFilter VFS modules + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + + 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/>. +*/ + +#ifndef _VIRUSFILTER_UTILS_H +#define _VIRUSFILTER_UTILS_H + +#include "modules/vfs_virusfilter_common.h" +#include "../lib/util/memcache.h" +#include "../lib/util/strv.h" + +/*#define str_eq(s1, s2) \ + ((strcmp((s1), (s2)) == 0) ? true : false) +#define strn_eq(s1, s2, n) \ + ((strncmp((s1), (s2), (n)) == 0) ? true : false) */ + +/* "* 3" is for %-encoding */ +#define VIRUSFILTER_IO_URL_MAX (PATH_MAX * 3) +#define VIRUSFILTER_IO_BUFFER_SIZE (VIRUSFILTER_IO_URL_MAX + 128) +#define VIRUSFILTER_IO_EOL_SIZE 1 +#define VIRUSFILTER_IO_IOV_MAX 16 +#define VIRUSFILTER_CACHE_BUFFER_SIZE (PATH_MAX + 128) + +struct virusfilter_io_handle { + struct tstream_context *stream; + int connect_timeout; /* msec */ + int io_timeout; /* msec */ + + /* end-of-line character(s) */ + char w_eol[VIRUSFILTER_IO_EOL_SIZE]; + int w_eol_size; + + /* end-of-line character(s) */ + char r_eol[VIRUSFILTER_IO_EOL_SIZE]; + int r_eol_size; + + /* buffer */ + char r_buffer[VIRUSFILTER_IO_BUFFER_SIZE]; + size_t r_len; +}; + +struct virusfilter_cache_entry { + time_t time; + virusfilter_result result; + char *report; +}; + +struct virusfilter_cache { + struct memcache *cache; + TALLOC_CTX *ctx; + time_t time_limit; +}; + +/* ====================================================================== */ + +char *virusfilter_string_sub( + TALLOC_CTX *mem_ctx, + connection_struct *conn, + const char *str); +int virusfilter_vfs_next_move( + vfs_handle_struct *handle, + const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst); + +/* Line-based socket I/O */ +struct virusfilter_io_handle *virusfilter_io_new( + TALLOC_CTX *mem_ctx, + int connect_timeout, + int timeout); +int virusfilter_io_set_connect_timeout( + struct virusfilter_io_handle *io_h, + int timeout); +int virusfilter_io_set_io_timeout( + struct virusfilter_io_handle *io_h, int timeout); +void virusfilter_io_set_writel_eol( + struct virusfilter_io_handle *io_h, + const char *eol, + int eol_size); +void virusfilter_io_set_readl_eol( + struct virusfilter_io_handle *io_h, + const char *eol, + int eol_size); +bool virusfilter_io_connect_path( + struct virusfilter_io_handle *io_h, + const char *path); +bool virusfilter_io_disconnect( + struct virusfilter_io_handle *io_h); +bool write_data_iov_timeout( + struct tstream_context *stream, + const struct iovec *iov, + size_t iovcnt, + int ms_timeout); +bool virusfilter_io_write( + struct virusfilter_io_handle *io_h, + const char *data, + size_t data_size); +bool virusfilter_io_writel( + struct virusfilter_io_handle *io_h, + const char *data, + size_t data_size); +bool virusfilter_io_writefl( + struct virusfilter_io_handle *io_h, + const char *data_fmt, ...); +bool virusfilter_io_vwritefl( + struct virusfilter_io_handle *io_h, + const char *data_fmt, va_list ap); +bool virusfilter_io_writev( + struct virusfilter_io_handle *io_h, ...); +bool virusfilter_io_writevl( + struct virusfilter_io_handle *io_h, ...); +bool virusfilter_io_readl(TALLOC_CTX *ctx, + struct virusfilter_io_handle *io_h, + char **read_line); +bool virusfilter_io_writefl_readl( + struct virusfilter_io_handle *io_h, + char **read_line, + const char *fmt, ...); + +/* Scan result cache */ +struct virusfilter_cache *virusfilter_cache_new( + TALLOC_CTX *ctx, + int entry_limit, + time_t time_limit); +bool virusfilter_cache_entry_add( + struct virusfilter_cache *cache, + const char *directory, + const char *fname, + virusfilter_result result, + char *report); +bool virusfilter_cache_entry_rename( + struct virusfilter_cache *cache, + const char *directory, + char *old_fname, + char *new_fname); +void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e); +struct virusfilter_cache_entry *virusfilter_cache_get( + struct virusfilter_cache *cache, + const char *directory, + const char *fname); +void virusfilter_cache_remove( + struct virusfilter_cache *cache, + const char *directory, + const char *fname); +void virusfilter_cache_purge(struct virusfilter_cache *cache); + +/* Shell scripting */ +int virusfilter_env_set( + TALLOC_CTX *mem_ctx, + char **env_list, + const char *name, + const char *value); +int virusfilter_shell_set_conn_env( + TALLOC_CTX *mem_ctx, + char **env_list, + connection_struct *conn); +int virusfilter_shell_run( + TALLOC_CTX *mem_ctx, + const char *cmd, + char **env_list, + connection_struct *conn, + bool sanitize); + +#endif /* _VIRUSFILTER_UTILS_H */ diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build index 079302cd584..f4179477376 100644 --- a/source3/modules/wscript_build +++ b/source3/modules/wscript_build @@ -17,6 +17,11 @@ bld.SAMBA3_LIBRARY('non_posix_acls', deps='samba-util vfs', private_library=True) +bld.SAMBA3_SUBSYSTEM('VFS_VIRUSFILTER_UTILS', + source='vfs_virusfilter_utils.c', + deps='strv', + enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter'))) + bld.SAMBA3_SUBSYSTEM('VFS_AIXACL_UTIL', source='vfs_aixacl_util.c', enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl2'))) @@ -505,6 +510,14 @@ bld.SAMBA3_MODULE('vfs_snapper', internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_snapper'), enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_snapper')) +bld.SAMBA3_MODULE('vfs_virusfilter', + subsystem='vfs', + source='vfs_virusfilter.c', + deps='samba-util VFS_VIRUSFILTER_UTILS', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter')) + bld.SAMBA3_MODULE('vfs_vxfs', subsystem='vfs', source='lib_vxfs.c vfs_vxfs.c', |