From e2f40ed62961bf032adf96979835b35da14e6d11 Mon Sep 17 00:00:00 2001 From: "Trever L. Adams" Date: Tue, 18 Oct 2016 13:37:19 -0600 Subject: [PATCH 1/5] Samba-VirusFilter: memcache changes. Signed-off-by: Trever L. Adams Reviewed-by: Jeremy Allison Reviewed-by: Ralph Boehme (cherry picked from commit 70d7f7d03c46c8727833f322bdc03da1b2aad720) --- lib/util/memcache.c | 1 + lib/util/memcache.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/util/memcache.c b/lib/util/memcache.c index acd663cd4e7..819ba44b443 100644 --- a/lib/util/memcache.c +++ b/lib/util/memcache.c @@ -55,6 +55,7 @@ static bool memcache_is_talloc(enum memcache_number n) case SINGLETON_CACHE_TALLOC: case SHARE_MODE_LOCK_CACHE: case GETWD_CACHE: + case VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC: result = true; break; default: diff --git a/lib/util/memcache.h b/lib/util/memcache.h index b87746bc928..670cbd6821f 100644 --- a/lib/util/memcache.h +++ b/lib/util/memcache.h @@ -44,7 +44,8 @@ enum memcache_number { SINGLETON_CACHE_TALLOC, /* talloc */ SINGLETON_CACHE, SMB1_SEARCH_OFFSET_MAP, - SHARE_MODE_LOCK_CACHE /* talloc */ + SHARE_MODE_LOCK_CACHE, /* talloc */ + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC /* talloc */ }; /* -- 2.13.6 From 38daef73c7f2e3b27714088f77096a0afc31391f Mon Sep 17 00:00:00 2001 From: "Trever L. Adams" Date: Tue, 18 Oct 2016 13:34:53 -0600 Subject: [PATCH 2/5] 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 Signed-off-by: SATOH Fumiyasu Reviewed-by: Jeremy Allison Reviewed-by: Ralph Boehme (cherry picked from commit b1e69edd0592d3b4b0f958792826a236dd3466e1) --- docs-xml/manpages/vfs_virusfilter.8.xml | 336 +++++ docs-xml/wscript_build | 1 + .../scripts/vfs/virusfilter/virusfilter-notify.ksh | 284 ++++ source3/modules/vfs_virusfilter.c | 1508 ++++++++++++++++++++ source3/modules/vfs_virusfilter_common.h | 149 ++ source3/modules/vfs_virusfilter_utils.c | 1025 +++++++++++++ source3/modules/vfs_virusfilter_utils.h | 177 +++ source3/modules/wscript_build | 13 + source3/wscript | 2 +- 9 files changed, 3494 insertions(+), 1 deletion(-) create mode 100644 docs-xml/manpages/vfs_virusfilter.8.xml create mode 100644 examples/scripts/vfs/virusfilter/virusfilter-notify.ksh create mode 100644 source3/modules/vfs_virusfilter.c create mode 100644 source3/modules/vfs_virusfilter_common.h create mode 100644 source3/modules/vfs_virusfilter_utils.c create mode 100644 source3/modules/vfs_virusfilter_utils.h diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml new file mode 100644 index 00000000000..eb6112e3827 --- /dev/null +++ b/docs-xml/manpages/vfs_virusfilter.8.xml @@ -0,0 +1,336 @@ + + + + + + vfs_virusfilter + 8 + Samba + System Administration tools + 4.8 + + + + + vfs_virusfilter + On access virus scanner + + + + + vfs objects = virusfilter + + + + + DESCRIPTION + + This is a set of various Samba VFS modules to scan and filter + virus files on Samba file services with an anti-virus scanner. + + This module is stackable. + + + + + OPTIONS + + + + + virusfilter:scanner + + The antivirus scan-engine. + + + + + virusfilter:socket path = PATH + + Path of local socket for the virus scanner. + + If this option is not set, the default path depends on the + configured AV scanning engine. + + + + + + virusfilter:connect timeout = 30000 + + Controls how long to wait on connecting to the virus + scanning process before timing out. Value is in milliseconds. + + If this option is not set, the default is 30000. + + + + + virusfilter:io timeout = 60000 + + Controls how long to wait on communications with the virus + scanning process before timing out. Value is in milliseconds. + + If this option is not set, the default is 60000. + + + + + virusfilter:scan on open = yes + + This option controls whether files are scanned on open. + + If this option is not set, the default is yes. + + + + + virusfilter:scan on close = no + + This option controls whether files are scanned on close. + + If this option is not set, the default is no. + + + + + virusfilter:max file size = 100000000 + + This is the largest sized file, in bytes, which will be scanned. + + If this option is not set, the default is 100MB. + + + + + virusfilter:min file size = 10 + + This is the smallest sized file, in bytes, which will be scanned. + + If this option is not set, the default is 10. + + + + + virusfilter:infected file action = nothing + + What to do with an infected file. The options are + nothing, quarantine, rename, delete. + If this option is not set, the default is nothing. + + + + + virusfilter:infected file errno on open = EACCES + + What errno to return on open if the file is infected. + + If this option is not set, the default is EACCES. + + + + + virusfilter:infected file errno on close = 0 + + What errno to return on close if the file is infected. + + If this option is not set, the default is 0. + + + + + virusfilter:quarantine directory = PATH + + Where to move infected files. This path must be an + absolute path. + If this option is not set, the default is ".quarantine" + relative to the share path. + + + + + virusfilter:quarantine prefix = virusfilter. + + Prefix for quarantined files. + If this option is not set, the default is "virusfilter.". + + + + + virusfilter:quarantine suffix = .infected + + Suffix for quarantined files. + This option is only used if keep name is true. Otherwise it is ignored. + If this option is not set, the default is ".infected". + + + + + virusfilter:rename prefix = virusfilter. + + Prefix for infected files. + If this option is not set, the default is "virusfilter.". + + + + + virusfilter:rename suffix = .infected + + Suffix for infected files. + If this option is not set, the default is ".infected". + + + + + virusfilter:quarantine keep tree = yes + + If keep tree is set, the directory structure relative + to the share is maintained in the quarantine directory. + + If this option is not set, the default is yes. + + + + + virusfilter:quarantine keep name = yes + + Should the file name be left unmodified other than adding a suffix + and/or prefix and a random suffix name as defined in virusfilter:rename prefix + and virusfilter:rename suffix. + If this option is not set, the default is yes. + + + + + virusfilter:infected file command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster@example.com --cc "%U@example.com" --from samba@example.com --subject-prefix "Samba: Infected File: " + + External command to run on an infected file is found. + If this option is not set, the default is none. + + + + + virusfilter:scan archive = true + + This defines whether or not to scan archives. + Sophos supports this and defaults to false. + + + + + virusfilter:max nested scan archive = 1 + + This defines the maximum depth to search nested archives. + The Sophos module supports this and defaults to 1. + + + + + virusfilter:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster@example.com --from samba@example.com --subject-prefix "Samba: Scan Error: " + + External command to run on scan error. + If this option is not set, the default is none. + + + + + virusfilter:exclude files = empty + + Files to exclude from scanning. + If this option is not set, the default is empty. + + + + + virusfilter:block access on error = false + + Controls whether or not access should be blocked on + a scanning error. + If this option is not set, the default is false. + + + + + virusfilter:scan error errno on open = EACCES + + What errno to return on open if there is an error in + scanning the file and block access on error is true. + + If this option is not set, the default is EACCES. + + + + + virusfilter:scan error errno on close = 0 + + What errno to return on close if there is an error in + scanning the file and block access on error is true. + + If this option is not set, the default is 0. + + + + + virusfilter:cache entry limit = 100 + + The maximum number of entries in the scanning results + cache. Due to how Samba's memcache works, this is approximate. + If this option is not set, the default is 100. + + + + + virusfilter:cache time limit = 10 + + The maximum number of seconds that a scanning result + will stay in the results cache. -1 disables the limit. + 0 disables caching. + If this option is not set, the default is 10. + + + + + virusfilter:quarantine directory mode = 0755 + + This is the octet mode for the quarantine directory and + its sub-directories as they are created. + If this option is not set, the default is 0755 or + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | + S_IXOTH. + Permissions must be such that all users can read and + search. I.E. don't mess with this unless you really know what + you are doing. + + + + + + + + NOTES + + This module can scan other than default streams, if the + alternative datastreams are each backed as separate files, such as with + the vfs module streams_depot. + + For proper operation the streams support module must be before + the virusfilter module in your vfs objects list (i.e. streams_depot + must be called before virusfilter module). + + This module is intended for security in depth by providing + virus scanning capability on the server. It is not intended to be used + in lieu of proper client based security. Other modules for security may + exist and may be desirable for security in depth on the server. + + + + AUTHOR + + The original Samba software and related utilities + were created by Andrew Tridgell. Samba is now developed + by the Samba Team as an Open Source project similar + to the way the Linux kernel is developed. + + + + diff --git a/docs-xml/wscript_build b/docs-xml/wscript_build index f586208b471..954c62a29bc 100644 --- a/docs-xml/wscript_build +++ b/docs-xml/wscript_build @@ -90,6 +90,7 @@ manpages=''' manpages/vfs_time_audit.8 manpages/vfs_tsmsm.8 manpages/vfs_unityed_media.8 + manpages/vfs_virusfilter.8 manpages/vfs_worm.8 manpages/vfs_xattr_tdb.8 manpages/vfstest.1 diff --git a/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh b/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh new file mode 100644 index 00000000000..a07b9148e83 --- /dev/null +++ b/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh @@ -0,0 +1,284 @@ +#!/bin/ksh +## +## 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 . +## + +set -u + +pdie() { echo "$0: ERROR: ${1-}" 1>&2; exit "${2-1}"; } + +## ====================================================================== + +sendmail="${VIRUSFILTER_NOTIFY_SENDMAIL_COMMAND:-/usr/sbin/sendmail}" +sendmail_opts="${VIRUSFILTER_NOTIFY_SENDMAIL_OPTIONS:-}" + +smbclient="${VIRUSFILTER_NOTIFY_SMBCLIENT_COMMAND:-@SAMBA_BINDIR@/smbclient}" +smbclient_opts="${VIRUSFILTER_NOTIFY_SMBCLIENT_OPTIONS:-}" + +## ====================================================================== + +if [ -n "${VIRUSFILTER_RESULT_IS_CACHE-}" ]; then + ## Result is cache. Ignore! + exit 0 +fi + +if [ ! -t 1 ] && [ -z "${VIRUSFILTER_NOTIFY_BG-}" ]; then + export VIRUSFILTER_NOTIFY_BG=1 + "$0" ${1+"$@"} /dev/null & + exit 0 +fi + +## ---------------------------------------------------------------------- + +if [ -n "${VIRUSFILTER_INFECTED_FILE_ACTION-}" ]; then + report="$VIRUSFILTER_INFECTED_FILE_REPORT" +else + report="$VIRUSFILTER_SCAN_ERROR_REPORT" +fi + +if [ X"$VIRUSFILTER_SERVER_NAME" != X"$VIRUSFILTER_SERVER_IP" ]; then + server_name="$VIRUSFILTER_SERVER_NAME" +else + server_name="$VIRUSFILTER_SERVER_NETBIOS_NAME" +fi + +if [ X"$VIRUSFILTER_CLIENT_NAME" != X"$VIRUSFILTER_CLIENT_IP" ]; then + client_name="$VIRUSFILTER_CLIENT_NAME" +else + client_name="$VIRUSFILTER_CLIENT_NETBIOS_NAME" +fi + +mail_to="" +winpopup_to="" +subject_prefix="" +sender="" +from="" +cc="" +bcc="" +content_type="text/plain" +content_encoding="UTF-8" + +cmd_usage="Usage: $0 [OPTIONS] + +Options: + --mail-to ADDRESS + Send a notice message to this e-mail address(es) + --winpopup-to NAME + Send a \"WinPopup\" message to this NetBIOS name + --sender ADDRESS + Envelope sender address for mail + --from ADDRESS + From: e-mail address for mail + --cc ADDRESS + Cc: e-mail address(es) for mail + --bcc ADDRESS + Bcc: e-mail address(es) for mail + --subject-prefix PREFIX + Subject: prefix string for mail + --content-type TYPE + --content-encoding ENCODING + Content-Type: TYPE; charset=\"ENCODING\" for mail [$content_type; charset=\"$content_encoding\"] + --header-file FILE + Prepend the content of FILE to the message + --footer-file FILE + Append the content of FILE to the message +" + +## ---------------------------------------------------------------------- + +getopts_want_arg() +{ + if [ "$#" -lt 2 ]; then + pdie "Option requires an argument: $1" + fi + if [ "$#" -ge 3 ]; then + if expr x"$2" : x"$3\$" >/dev/null; then + : OK + else + pdie "Invalid value for option: $1 $2" + fi + fi +} + +while [ "$#" -gt 0 ]; do + OPT="$1"; shift + case "$OPT" in + --help) + echo "$cmd_usage" + exit 0 + ;; + --mail-to) + getopts_want_arg "$OPT" ${1+"$1"} + mail_to="${mail_to:+$mail_to, }$1"; shift + ;; + --winpopup-to) + getopts_want_arg "$OPT" ${1+"$1"} + winpopup_to="$1"; shift + ;; + --sender) + getopts_want_arg "$OPT" ${1+"$1"} + sender="$1"; shift + ;; + --from) + getopts_want_arg "$OPT" ${1+"$1"} + from="$1"; shift + ;; + --cc) + getopts_want_arg "$OPT" ${1+"$1"} + cc="${cc:+$cc, }$1"; shift + ;; + --bcc) + getopts_want_arg "$OPT" ${1+"$1"} + bcc="${bcc:+$bcc, }$1"; shift + ;; + --subject-prefix) + getopts_want_arg "$OPT" ${1+"$1"} + subject_prefix="$1"; shift + ;; + --content-type) + getopts_want_arg "$OPT" ${1+"$1"} + content_type="$1"; shift + ;; + --content-encoding) + getopts_want_arg "$OPT" ${1+"$1"} + content_encoding="$1"; shift + ;; + --header-file) + getopts_want_arg "$OPT" ${1+"$1"} + header_file="$1"; shift + ;; + --footer-file) + getopts_want_arg "$OPT" ${1+"$1"} + footer_file="$1"; shift + ;; + --) + break + ;; + -*) + pdie "Invalid option: $OPT" + ;; + *) + set -- "$OPT" ${1+"$@"} + break + ;; + esac +done + +[ -z "$sender" ] && sender="$from" +subject="$subject_prefix$report" + +## ====================================================================== + +msg_header="\ +Subject: $subject +Content-Type: $content_type; charset=$content_encoding +X-VIRUSFILTER-Version: $VIRUSFILTER_VERSION +X-VIRUSFILTER-Module-Name: $VIRUSFILTER_MODULE_NAME +" + +if [ -n "${VIRUSFILTER_MODULE_VERSION-}" ]; then + msg_header="${msg_header}\ +X-VIRUSFILTER-Module-Version: $VIRUSFILTER_MODULE_VERSION +" +fi + +if [ -n "${from-}" ]; then + msg_header="${msg_header}\ +From: $from +" +fi + +if [ -n "${mail_to-}" ]; then + msg_header="${msg_header}\ +To: $mail_to +" +fi + +if [ -n "${cc-}" ]; then + msg_header="${msg_header}\ +Cc: $cc +" +fi + +if [ -n "${bcc-}" ]; then + msg_header="${msg_header}\ +Bcc: $bcc +" +fi + +## ---------------------------------------------------------------------- + +msg_body="" + +if [ -n "${header_file-}" ] && [ -f "$header_file" ]; then + msg_body="${msg_body}\ +`cat "$header_file"` +" +fi + +msg_body="${msg_body}\ +Server: $server_name ($VIRUSFILTER_SERVER_IP) +Server PID: $VIRUSFILTER_SERVER_PID +Service name: $VIRUSFILTER_SERVICE_NAME +Service path: $VIRUSFILTER_SERVICE_PATH +Client: $client_name ($VIRUSFILTER_CLIENT_IP) +User: $VIRUSFILTER_USER_DOMAIN\\$VIRUSFILTER_USER_NAME +" + +if [ -n "${VIRUSFILTER_INFECTED_FILE_ACTION-}" ]; then + msg_body="${msg_body}\ +Infected file report: $VIRUSFILTER_INFECTED_FILE_REPORT +" + msg_body="${msg_body}\ +Infected file path: $VIRUSFILTER_SERVICE_PATH/$VIRUSFILTER_INFECTED_SERVICE_FILE_PATH +" + msg_body="${msg_body}\ +Infected file action: $VIRUSFILTER_INFECTED_FILE_ACTION +" +else + msg_body="${msg_body}\ +Scan error report: $VIRUSFILTER_SCAN_ERROR_REPORT +Scan error file path: $VIRUSFILTER_SERVICE_PATH/$VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH +" +fi + +if [ -n "${VIRUSFILTER_QUARANTINED_FILE_PATH-}" ]; then + msg_body="${msg_body}\ +Quarantined/Renamed file path: ${VIRUSFILTER_QUARANTINED_FILE_PATH-} +" +fi + +if [ -n "${footer_file-}" ] && [ -f "$footer_file" ]; then + msg_body="${msg_body}\ +`cat "$footer_file"` +" +fi + +## ====================================================================== + +if [ -n "$mail_to" ]; then + (echo "$msg_header"; echo "$msg_body") \ + |"$sendmail" -t -i ${sender:+-f "$sender"} $sendmail_opts +fi + +if [ -n "$winpopup_to" ]; then + echo "$msg_body" \ + |"$smbclient" -M "$winpopup_to" -U% $smbclient_opts \ + >/dev/null +fi + +exit 0 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 + * Copyright (C) 2017 Jeremy Allison + * + * 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 . + */ + +#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 . +*/ + +#ifndef _VIRUSFILTER_COMMON_H +#define _VIRUSFILTER_COMMON_H + +#include +#include + +/* 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 . +*/ + +#include "modules/vfs_virusfilter_common.h" +#include "modules/vfs_virusfilter_utils.h" + +struct iovec; + +#include "lib/util/iov_buf.h" +#include +#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 . +*/ + +#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', diff --git a/source3/wscript b/source3/wscript index 0f8fe5452da..28c5ba4b231 100644 --- a/source3/wscript +++ b/source3/wscript @@ -1658,7 +1658,7 @@ main() { vfs_preopen vfs_catia vfs_media_harmony vfs_unityed_media vfs_fruit vfs_shell_snap vfs_commit vfs_worm vfs_crossrename vfs_linux_xfs_sgid - vfs_time_audit vfs_offline + vfs_time_audit vfs_offline vfs_virusfilter ''')) default_shared_modules.extend(TO_LIST('auth_script idmap_tdb2 idmap_script')) # these have broken dependencies -- 2.13.6 From 86662dae285acf330e38776ed5e70b3c8484a2a2 Mon Sep 17 00:00:00 2001 From: "Trever L. Adams" Date: Tue, 18 Oct 2016 13:38:14 -0600 Subject: [PATCH 3/5] Samba-VirusFilter: Sophos VFS backend. Signed-off-by: Trever L. Adams Signed-off-by: SATOH Fumiyasu Reviewed-by: Jeremy Allison Reviewed-by: Ralph Boehme (cherry picked from commit 0b25089edd453270e52f2d8e6858a9996bb29a0d) --- docs-xml/manpages/vfs_virusfilter.8.xml | 6 + source3/modules/vfs_virusfilter.c | 15 +- source3/modules/vfs_virusfilter_common.h | 2 + source3/modules/vfs_virusfilter_sophos.c | 391 +++++++++++++++++++++++++++++++ source3/modules/wscript_build | 5 +- 5 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 source3/modules/vfs_virusfilter_sophos.c diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml index eb6112e3827..c4bc8920043 100644 --- a/docs-xml/manpages/vfs_virusfilter.8.xml +++ b/docs-xml/manpages/vfs_virusfilter.8.xml @@ -41,6 +41,10 @@ virusfilter:scanner The antivirus scan-engine. + + sophos, the Sophos AV + scanner + @@ -52,6 +56,8 @@ If this option is not set, the default path depends on the configured AV scanning engine. + For the sophosbackend the default is + /var/run/savdi/sssp.sock. diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c index a23d1f7c641..8947e35b14b 100644 --- a/source3/modules/vfs_virusfilter.c +++ b/source3/modules/vfs_virusfilter.c @@ -441,10 +441,17 @@ static int virusfilter_vfs_connect( 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); + switch (backend) { + case VIRUSFILTER_SCANNER_SOPHOS: + ret = virusfilter_sophos_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) { diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h index 468883fdaf8..69519c9daa4 100644 --- a/source3/modules/vfs_virusfilter_common.h +++ b/source3/modules/vfs_virusfilter_common.h @@ -146,4 +146,6 @@ struct virusfilter_backend { void *backend_private; }; +int virusfilter_sophos_init(struct virusfilter_config *config); + #endif /* _VIRUSFILTER_COMMON_H */ diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c new file mode 100644 index 00000000000..72051cd64a2 --- /dev/null +++ b/source3/modules/vfs_virusfilter_sophos.c @@ -0,0 +1,391 @@ +/* + Samba-VirusFilter VFS modules + Sophos Anti-Virus savdid (SSSP/1.0) support + 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 . +*/ + +#include "vfs_virusfilter_common.h" +#include "vfs_virusfilter_utils.h" + +/* Default values for standard "extra" configuration variables */ +#ifdef SOPHOS_DEFAULT_SOCKET_PATH +# define VIRUSFILTER_DEFAULT_SOCKET_PATH SOPHOS_DEFAULT_SOCKET_PATH +#else +# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/var/run/savdi/sssp.sock" +#endif + +static void virusfilter_sophos_scan_end(struct virusfilter_config *config); + +/* Python's urllib.quote(string[, safe]) clone */ +static int virusfilter_url_quote(const char *src, char *dst, int dst_size) +{ + char *dst_c = dst; + static char hex[] = "0123456789ABCDEF"; + + for (; *src != '\0'; src++) { + if ((*src < '0' && *src != '-' && *src != '.' && *src != '/') || + (*src > '9' && *src < 'A') || + (*src > 'Z' && *src < 'a' && *src != '_') || + (*src > 'z')) + { + if (dst_size < 4) { + return -1; + } + *dst_c++ = '%'; + *dst_c++ = hex[(*src >> 4) & 0x0F]; + *dst_c++ = hex[*src & 0x0F]; + dst_size -= 3; + } else { + if (dst_size < 2) { + return -1; + } + *dst_c++ = *src; + dst_size--; + } + } + + *dst_c = '\0'; + + return (dst_c - dst); +} + +static int virusfilter_sophos_connect( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *svc, + const char *user) +{ + virusfilter_io_set_readl_eol(config->io_h, "\x0D\x0A", 2); + + return 0; +} + +static virusfilter_result virusfilter_sophos_scan_ping( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + char *reply = NULL; + bool ok; + int ret; + + /* SSSP/1.0 has no "PING" command */ + ok = virusfilter_io_writel(io_h, "SSSP/1.0 OPTIONS\n", 17); + if (!ok) { + return VIRUSFILTER_RESULT_ERROR; + } + + for (;;) { + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + return VIRUSFILTER_RESULT_ERROR; + } + ret = strcmp(reply, ""); + if (ret == 0) { + break; + } + TALLOC_FREE(reply); + } + + TALLOC_FREE(reply); + return VIRUSFILTER_RESULT_OK; +} + +static virusfilter_result virusfilter_sophos_scan_init( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + char *reply = NULL; + int ret; + bool ok; + + if (io_h->stream != NULL) { + DBG_DEBUG("SSSP: Checking if connection is alive\n"); + + ret = virusfilter_sophos_scan_ping(config); + if (ret == VIRUSFILTER_RESULT_OK) + { + DBG_DEBUG("SSSP: Re-using existent connection\n"); + return VIRUSFILTER_RESULT_OK; + } + + DBG_INFO("SSSP: Closing dead connection\n"); + virusfilter_sophos_scan_end(config); + } + + + DBG_INFO("SSSP: Connecting to socket: %s\n", + config->socket_path); + + become_root(); + ok = virusfilter_io_connect_path(io_h, config->socket_path); + unbecome_root(); + + if (!ok) { + DBG_ERR("SSSP: Connecting to socket failed: %s: %s\n", + config->socket_path, strerror(errno)); + return VIRUSFILTER_RESULT_ERROR; + } + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: Reading greeting message failed: %s\n", + strerror(errno)); + goto virusfilter_sophos_scan_init_failed; + } + ret = strncmp(reply, "OK SSSP/1.0", 11); + if (ret != 0) { + DBG_ERR("SSSP: Invalid greeting message: %s\n", + reply); + goto virusfilter_sophos_scan_init_failed; + } + + DBG_DEBUG("SSSP: Connected\n"); + + DBG_INFO("SSSP: Configuring\n"); + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "SSSP/1.0 OPTIONS\noutput:brief\nsavigrp:GrpArchiveUnpack %d\n", + config->scan_archive ? 1 : 0); + if (!ok) { + DBG_ERR("SSSP: OPTIONS: I/O error: %s\n", strerror(errno)); + goto virusfilter_sophos_scan_init_failed; + } + ret = strncmp(reply, "ACC ", 4); + if (ret != 0) { + DBG_ERR("SSSP: OPTIONS: Not accepted: %s\n", reply); + goto virusfilter_sophos_scan_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno)); + goto virusfilter_sophos_scan_init_failed; + } + ret = strncmp(reply, "DONE OK ", 8); + if (ret != 0) { + DBG_ERR("SSSP: OPTIONS failed: %s\n", reply); + goto virusfilter_sophos_scan_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno)); + goto virusfilter_sophos_scan_init_failed; + } + ret = strcmp(reply, ""); + if (ret != 0) { + DBG_ERR("SSSP: OPTIONS: Invalid reply: %s\n", reply); + goto virusfilter_sophos_scan_init_failed; + } + + DBG_DEBUG("SSSP: Configured\n"); + + return VIRUSFILTER_RESULT_OK; + +virusfilter_sophos_scan_init_failed: + + TALLOC_FREE(reply); + + virusfilter_sophos_scan_end(config); + + return VIRUSFILTER_RESULT_ERROR; +} + +static void virusfilter_sophos_scan_end( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + + DBG_INFO("SSSP: Disconnecting\n"); + + virusfilter_io_disconnect(io_h); +} + +static virusfilter_result virusfilter_sophos_scan( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp) +{ + char *cwd_fname = fsp->conn->cwd_fname->base_name; + const char *fname = fsp->fsp_name->base_name; + char fileurl[VIRUSFILTER_IO_URL_MAX+1]; + int fileurl_len, fileurl_len2; + struct virusfilter_io_handle *io_h = config->io_h; + virusfilter_result result = VIRUSFILTER_RESULT_ERROR; + char *report = NULL; + char *reply = NULL; + char *reply_token, *reply_saveptr; + int ret; + bool ok; + + DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname); + + fileurl_len = virusfilter_url_quote(cwd_fname, fileurl, + VIRUSFILTER_IO_URL_MAX); + if (fileurl_len < 0) { + DBG_ERR("virusfilter_url_quote failed: File path too long: " + "%s/%s\n", cwd_fname, fname); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), "File path too long"); + goto virusfilter_sophos_scan_return; + } + fileurl[fileurl_len] = '/'; + fileurl_len++; + + fileurl_len += fileurl_len2 = virusfilter_url_quote(fname, + fileurl + fileurl_len, VIRUSFILTER_IO_URL_MAX - fileurl_len); + if (fileurl_len2 < 0) { + DBG_ERR("virusfilter_url_quote failed: File path too long: " + "%s/%s\n", cwd_fname, fname); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), "File path too long"); + goto virusfilter_sophos_scan_return; + } + fileurl_len += fileurl_len2; + + ok = virusfilter_io_writevl(io_h, "SSSP/1.0 SCANFILE ", 18, fileurl, + fileurl_len, NULL); + if (!ok) { + DBG_ERR("SSSP: SCANFILE: Write error: %s\n", + strerror(errno)); + goto virusfilter_sophos_scan_io_error; + } + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: SCANFILE: Read error: %s\n", strerror(errno)); + goto virusfilter_sophos_scan_io_error; + } + ret = strncmp(reply, "ACC ", 4); + if (ret != 0) { + DBG_ERR("SSSP: SCANFILE: Not accepted: %s\n", + reply); + result = VIRUSFILTER_RESULT_ERROR; + goto virusfilter_sophos_scan_return; + } + + TALLOC_FREE(reply); + + result = VIRUSFILTER_RESULT_CLEAN; + for (;;) { + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: SCANFILE: Read error: %s\n", + strerror(errno)); + goto virusfilter_sophos_scan_io_error; + } + + ret = strcmp(reply, ""); + if (ret == 0) { + break; + } + + reply_token = strtok_r(reply, " ", &reply_saveptr); + + if (strcmp(reply_token, "VIRUS") == 0) { + result = VIRUSFILTER_RESULT_INFECTED; + reply_token = strtok_r(NULL, " ", &reply_saveptr); + if (reply_token != NULL) { + report = talloc_strdup(talloc_tos(), + reply_token); + } else { + report = talloc_asprintf(talloc_tos(), + "UNKNOWN INFECTION"); + } + } else if (strcmp(reply_token, "OK") == 0) { + + /* Ignore */ + } else if (strcmp(reply_token, "DONE") == 0) { + reply_token = strtok_r(NULL, "", &reply_saveptr); + if (reply_token != NULL && + + /* Succeed */ + strncmp(reply_token, "OK 0000 ", 8) != 0 && + + /* Infected */ + strncmp(reply_token, "OK 0203 ", 8) != 0) + { + DBG_ERR("SSSP: SCANFILE: Error: %s\n", + reply_token); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner error: %s\n", + reply_token); + } + } else { + DBG_ERR("SSSP: SCANFILE: Invalid reply: %s\n", + reply_token); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), "Scanner " + "communication error"); + } + + TALLOC_FREE(reply); + } + +virusfilter_sophos_scan_return: + TALLOC_FREE(reply); + + if (report == NULL) { + *reportp = talloc_asprintf(talloc_tos(), + "Scanner report memory error"); + } else { + *reportp = report; + } + + return result; + +virusfilter_sophos_scan_io_error: + *reportp = talloc_asprintf(talloc_tos(), + "Scanner I/O error: %s\n", strerror(errno)); + + return result; +} + +static struct virusfilter_backend_fns virusfilter_backend_sophos ={ + .connect = virusfilter_sophos_connect, + .disconnect = NULL, + .scan_init = virusfilter_sophos_scan_init, + .scan = virusfilter_sophos_scan, + .scan_end = virusfilter_sophos_scan_end, +}; + +int virusfilter_sophos_init(struct virusfilter_config *config) +{ + struct virusfilter_backend *backend = NULL; + + if (config->socket_path == NULL) { + config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH; + } + + backend = talloc_zero(config, struct virusfilter_backend); + if (backend == NULL) { + return -1; + } + + backend->fns = &virusfilter_backend_sophos; + backend->name = "sophos"; + + config->backend = backend; + return 0; +} diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build index f4179477376..14fddb3b30e 100644 --- a/source3/modules/wscript_build +++ b/source3/modules/wscript_build @@ -512,7 +512,10 @@ bld.SAMBA3_MODULE('vfs_snapper', bld.SAMBA3_MODULE('vfs_virusfilter', subsystem='vfs', - source='vfs_virusfilter.c', + source=''' + vfs_virusfilter.c + vfs_virusfilter_sophos.c + ''', deps='samba-util VFS_VIRUSFILTER_UTILS', init_function='', internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'), -- 2.13.6 From 5b5c1dabd06c0bb1382ba60b237ad5bcd488e340 Mon Sep 17 00:00:00 2001 From: "Trever L. Adams" Date: Tue, 18 Oct 2016 13:39:20 -0600 Subject: [PATCH 4/5] Samba-VirusFilter: F-Secure AntiVirus (fsav) VFS and man page. Signed-off-by: Trever L. Adams Signed-off-by: SATOH Fumiyasu Reviewed-by: Jeremy Allison Reviewed-by: Ralph Boehme (cherry picked from commit 5970d68bf651fb8dbf1ac4e79d8f2e9467154870) --- docs-xml/manpages/vfs_virusfilter.8.xml | 27 +- source3/modules/vfs_virusfilter.c | 3 + source3/modules/vfs_virusfilter_common.h | 1 + source3/modules/vfs_virusfilter_fsav.c | 451 +++++++++++++++++++++++++++++++ source3/modules/wscript_build | 1 + 5 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 source3/modules/vfs_virusfilter_fsav.c diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml index c4bc8920043..2e70ab0b553 100644 --- a/docs-xml/manpages/vfs_virusfilter.8.xml +++ b/docs-xml/manpages/vfs_virusfilter.8.xml @@ -44,6 +44,8 @@ sophos, the Sophos AV scanner + fsav, the F-Secure AV + scanner @@ -58,6 +60,8 @@ For the sophosbackend the default is /var/run/savdi/sssp.sock. + For the fsav backend the default is + /tmp/.fsav-0. @@ -219,7 +223,7 @@ virusfilter:scan archive = true This defines whether or not to scan archives. - Sophos supports this and defaults to false. + Sophos and F-Secure support this and it defaults to false. @@ -227,7 +231,16 @@ virusfilter:max nested scan archive = 1 This defines the maximum depth to search nested archives. - The Sophos module supports this and defaults to 1. + The Sophos and F-Secure support this and it defaults to 1. + + + + + virusfilter:scan mime = true + + This defines whether or not to scan mime files. + Only the fsavscanner supports this + option and defaults to false. @@ -309,6 +322,16 @@ + + virusfilter:block suspected file = false + + With this option on, suspected malware will be blocked as + well. Only the fsavscanner supports this + option. + If this option is not set, the default is false. + + + diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c index 8947e35b14b..338b4fc899c 100644 --- a/source3/modules/vfs_virusfilter.c +++ b/source3/modules/vfs_virusfilter.c @@ -445,6 +445,9 @@ static int virusfilter_vfs_connect( case VIRUSFILTER_SCANNER_SOPHOS: ret = virusfilter_sophos_init(config); break; + case VIRUSFILTER_SCANNER_FSAV: + ret = virusfilter_fsav_init(config); + break; default: DBG_ERR("Unhandled scanner %d\n", backend); return -1; diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h index 69519c9daa4..a28ce2978fa 100644 --- a/source3/modules/vfs_virusfilter_common.h +++ b/source3/modules/vfs_virusfilter_common.h @@ -147,5 +147,6 @@ struct virusfilter_backend { }; int virusfilter_sophos_init(struct virusfilter_config *config); +int virusfilter_fsav_init(struct virusfilter_config *config); #endif /* _VIRUSFILTER_COMMON_H */ diff --git a/source3/modules/vfs_virusfilter_fsav.c b/source3/modules/vfs_virusfilter_fsav.c new file mode 100644 index 00000000000..2b874d7db43 --- /dev/null +++ b/source3/modules/vfs_virusfilter_fsav.c @@ -0,0 +1,451 @@ +/* + Samba-VirusFilter VFS modules + F-Secure Anti-Virus fsavd support + 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 . +*/ + +#include "vfs_virusfilter_common.h" +#include "vfs_virusfilter_utils.h" + +#ifdef FSAV_DEFAULT_SOCKET_PATH +# define VIRUSFILTER_DEFAULT_SOCKET_PATH FSAV_DEFAULT_SOCKET_PATH +#else +# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/tmp/.fsav-0" +#endif + +/* Default values for module-specific configuration variables */ +/* 5 = F-Secure Linux 7 or later? */ + +#define VIRUSFILTER_DEFAULT_FSAV_PROTOCOL 5 +#define VIRUSFILTER_DEFAULT_SCAN_RISKWARE false +#define VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST true +#define VIRUSFILTER_DEFAULT_FILTER_FILENAME false + +struct virusfilter_fsav_config { + /* Backpointer */ + struct virusfilter_config *config; + + int fsav_protocol; + bool scan_riskware; + bool stop_scan_on_first; + bool filter_filename; +}; + +static void virusfilter_fsav_scan_end(struct virusfilter_config *config); + +static int virusfilter_fsav_destruct_config( + struct virusfilter_fsav_config *fsav_config) +{ + virusfilter_fsav_scan_end(fsav_config->config); + return 0; +} + +static int virusfilter_fsav_connect( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *svc, + const char *user) +{ + int snum = SNUM(handle->conn); + struct virusfilter_fsav_config *fsav_config = NULL; + + fsav_config = talloc_zero(config->backend, + struct virusfilter_fsav_config); + if (fsav_config == NULL) { + return -1; + } + + fsav_config->config = config; + + fsav_config->fsav_protocol = lp_parm_int( + snum, "virusfilter", "fsav protocol", + VIRUSFILTER_DEFAULT_FSAV_PROTOCOL); + + fsav_config->scan_riskware = lp_parm_bool( + snum, "virusfilter", "scan riskware", + VIRUSFILTER_DEFAULT_SCAN_RISKWARE); + + fsav_config->stop_scan_on_first = lp_parm_bool( + snum, "virusfilter", "stop scan on first", + VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST); + + fsav_config->filter_filename = lp_parm_bool( + snum, "virusfilter", "filter filename", + VIRUSFILTER_DEFAULT_FILTER_FILENAME); + + talloc_set_destructor(fsav_config, virusfilter_fsav_destruct_config); + + config->backend->backend_private = fsav_config; + + config->block_suspected_file = lp_parm_bool( + snum, "virusfilter", "block suspected file", false); + + return 0; +} + +static virusfilter_result virusfilter_fsav_scan_init( + struct virusfilter_config *config) +{ + struct virusfilter_fsav_config *fsav_config = NULL; + struct virusfilter_io_handle *io_h = config->io_h; + char *reply = NULL; + bool ok; + int ret; + + fsav_config = talloc_get_type_abort(config->backend->backend_private, + struct virusfilter_fsav_config); + + if (io_h->stream != NULL) { + DBG_DEBUG("fsavd: Checking if connection is alive\n"); + + /* FIXME: I don't know the correct PING command format... */ + ok = virusfilter_io_writefl_readl(io_h, &reply, "PING"); + if (ok) { + ret = strncmp(reply, "ERROR\t", 6); + if (ret == 0) { + DBG_DEBUG("fsavd: Re-using existent " + "connection\n"); + goto virusfilter_fsav_init_succeed; + } + } + + DBG_DEBUG("fsavd: Closing dead connection\n"); + virusfilter_fsav_scan_end(config); + } + + DBG_INFO("fsavd: Connecting to socket: %s\n", + config->socket_path); + + become_root(); + ok = virusfilter_io_connect_path(io_h, config->socket_path); + unbecome_root(); + + if (!ok) { + DBG_ERR("fsavd: Connecting to socket failed: %s: %s\n", + config->socket_path, strerror(errno)); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("fsavd: Reading greeting message failed: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "DBVERSION\t", 10); + if (ret != 0) { + DBG_ERR("fsavd: Invalid greeting message: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + DBG_DEBUG("fsavd: Connected\n"); + + DBG_INFO("fsavd: Configuring\n"); + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, "PROTOCOL\t%d", + fsav_config->fsav_protocol); + if (!ok) { + DBG_ERR("fsavd: PROTOCOL: I/O error: %s\n", strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: PROTOCOL: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "CONFIGURE\tSTOPONFIRST\t%d", + fsav_config->stop_scan_on_first ? + 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE STOPONFIRST: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE STOPONFIRST: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tFILTER\t%d", + fsav_config->filter_filename ? 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE FILTER: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE FILTER: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "CONFIGURE\tARCHIVE\t%d", + config->scan_archive ? 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE ARCHIVE: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "CONFIGURE\tMAXARCH\t%d", + config->max_nested_scan_archive); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE MAXARCH: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE MAXARCH: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "CONFIGURE\tMIME\t%d", + config->scan_mime ? 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE MIME: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE MIME: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tRISKWARE\t%d", + fsav_config->scan_riskware ? 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE RISKWARE: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE RISKWARE: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + DBG_DEBUG("fsavd: Configured\n"); + +virusfilter_fsav_init_succeed: + TALLOC_FREE(reply); + return VIRUSFILTER_RESULT_OK; + +virusfilter_fsav_init_failed: + TALLOC_FREE(reply); + virusfilter_fsav_scan_end(config); + + return VIRUSFILTER_RESULT_ERROR; +} + +static void virusfilter_fsav_scan_end(struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + + DBG_INFO("fsavd: Disconnecting\n"); + virusfilter_io_disconnect(io_h); +} + +static virusfilter_result virusfilter_fsav_scan( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp) +{ + char *cwd_fname = fsp->conn->cwd_fname->base_name; + const char *fname = fsp->fsp_name->base_name; + struct virusfilter_io_handle *io_h = config->io_h; + virusfilter_result result = VIRUSFILTER_RESULT_CLEAN; + char *report = NULL; + char *reply = NULL; + char *reply_token, *reply_saveptr; + bool ok; + + DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname); + + ok = virusfilter_io_writevl(io_h, "SCAN\t", 5, cwd_fname, + (int)strlen(cwd_fname), "/", 1, fname, + (int)strlen(fname), NULL); + if (!ok) { + DBG_ERR("fsavd: SCAN: Write error: %s\n", strerror(errno)); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_scan_return; + } + + TALLOC_FREE(reply); + + for (;;) { + if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) { + DBG_ERR("fsavd: SCANFILE: Read error: %s\n", + strerror(errno)); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner I/O error: %s\n", + strerror(errno)); + break; + } + + reply_token = strtok_r(reply, "\t", &reply_saveptr); + + if (strcmp(reply_token, "OK") == 0) { + break; + } else if (strcmp(reply_token, "CLEAN") == 0) { + + /* CLEAN\t */ + result = VIRUSFILTER_RESULT_CLEAN; + report = talloc_asprintf(talloc_tos(), "Clean"); + } else if (strcmp(reply_token, "INFECTED") == 0 || + strcmp(reply_token, "ARCHIVE_INFECTED") == 0 || + strcmp(reply_token, "MIME_INFECTED") == 0 || + strcmp(reply_token, "RISKWARE") == 0 || + strcmp(reply_token, "ARCHIVE_RISKWARE") == 0 || + strcmp(reply_token, "MIME_RISKWARE") == 0) + { + + /* INFECTED\t\t\t */ + result = VIRUSFILTER_RESULT_INFECTED; + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + if (reply_token != NULL) { + report = talloc_strdup(talloc_tos(), + reply_token); + } else { + report = talloc_asprintf(talloc_tos(), + "UNKNOWN INFECTION"); + } + } else if (strcmp(reply_token, "OPEN_ARCHIVE") == 0) { + + /* Ignore */ + } else if (strcmp(reply_token, "CLOSE_ARCHIVE") == 0) { + + /* Ignore */ + } else if ((strcmp(reply_token, "SUSPECTED") == 0 || + strcmp(reply_token, "ARCHIVE_SUSPECTED") == 0 || + strcmp(reply_token, "MIME_SUSPECTED") == 0) && + config->block_suspected_file) + { + result = VIRUSFILTER_RESULT_SUSPECTED; + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + if (reply_token != NULL) { + report = talloc_strdup(talloc_tos(), + reply_token); + } else { + report = talloc_asprintf(talloc_tos(), + "UNKNOWN REASON SUSPECTED"); + } + } else if (strcmp(reply_token, "SCAN_FAILURE") == 0) { + + /* SCAN_FAILURE\t\t0x\t [] */ + result = VIRUSFILTER_RESULT_ERROR; + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + DBG_ERR("fsavd: SCANFILE: Scaner error: %s\n", + reply_token ? reply_token : "UNKNOWN ERROR"); + report = talloc_asprintf(talloc_tos(), + "Scanner error: %s", + reply_token ? reply_token : + "UNKNOWN ERROR"); + } else { + result = VIRUSFILTER_RESULT_ERROR; + DBG_ERR("fsavd: SCANFILE: Invalid reply: %s\t", + reply_token); + report = talloc_asprintf(talloc_tos(), + "Scanner communication error"); + } + + TALLOC_FREE(reply); + } + +virusfilter_fsav_scan_return: + TALLOC_FREE(reply); + + if (report == NULL) { + *reportp = talloc_asprintf(talloc_tos(), "Scanner report memory " + "error"); + } else { + *reportp = report; + } + + return result; +} + +static struct virusfilter_backend_fns virusfilter_backend_fsav ={ + .connect = virusfilter_fsav_connect, + .disconnect = NULL, + .scan_init = virusfilter_fsav_scan_init, + .scan = virusfilter_fsav_scan, + .scan_end = virusfilter_fsav_scan_end, +}; + +int virusfilter_fsav_init(struct virusfilter_config *config) +{ + struct virusfilter_backend *backend = NULL; + + if (config->socket_path == NULL) { + config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH; + } + + backend = talloc_zero(config, struct virusfilter_backend); + if (backend == NULL) { + return -1; + } + + backend->fns = &virusfilter_backend_fsav; + backend->name = "fsav"; + + config->backend = backend; + return 0; +} diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build index 14fddb3b30e..f63c00a9955 100644 --- a/source3/modules/wscript_build +++ b/source3/modules/wscript_build @@ -515,6 +515,7 @@ bld.SAMBA3_MODULE('vfs_virusfilter', source=''' vfs_virusfilter.c vfs_virusfilter_sophos.c + vfs_virusfilter_fsav.c ''', deps='samba-util VFS_VIRUSFILTER_UTILS', init_function='', -- 2.13.6 From b55d4bfdd503c523ce5e9e6434780fc0be615aa0 Mon Sep 17 00:00:00 2001 From: "Trever L. Adams" Date: Tue, 18 Oct 2016 13:40:01 -0600 Subject: [PATCH 5/5] Samba-VirusFilter: clamav VFS and man page. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Trever L. Adams Signed-off-by: SATOH Fumiyasu Reviewed-by: Jeremy Allison Reviewed-by: Ralph Boehme Autobuild-User(master): Ralph Böhme Autobuild-Date(master): Wed Jan 24 15:08:59 CET 2018 on sn-devel-144 (cherry picked from commit cbf743d329730387ede92a9d329893d1c651e97a) --- docs-xml/manpages/vfs_virusfilter.8.xml | 4 + source3/modules/vfs_virusfilter.c | 3 + source3/modules/vfs_virusfilter_clamav.c | 195 +++++++++++++++++++++++++++++++ source3/modules/vfs_virusfilter_common.h | 1 + source3/modules/wscript_build | 1 + 5 files changed, 204 insertions(+) create mode 100644 source3/modules/vfs_virusfilter_clamav.c diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml index 2e70ab0b553..ee49df11575 100644 --- a/docs-xml/manpages/vfs_virusfilter.8.xml +++ b/docs-xml/manpages/vfs_virusfilter.8.xml @@ -46,6 +46,8 @@ scanner fsav, the F-Secure AV scanner + clamav, the ClamAV + scanner @@ -62,6 +64,8 @@ /var/run/savdi/sssp.sock. For the fsav backend the default is /tmp/.fsav-0. + For the fsav backend the default is + /var/run/clamav/clamd.ctl. diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c index 338b4fc899c..9b29923110d 100644 --- a/source3/modules/vfs_virusfilter.c +++ b/source3/modules/vfs_virusfilter.c @@ -448,6 +448,9 @@ static int virusfilter_vfs_connect( case VIRUSFILTER_SCANNER_FSAV: ret = virusfilter_fsav_init(config); break; + case VIRUSFILTER_SCANNER_CLAMAV: + ret = virusfilter_clamav_init(config); + break; default: DBG_ERR("Unhandled scanner %d\n", backend); return -1; diff --git a/source3/modules/vfs_virusfilter_clamav.c b/source3/modules/vfs_virusfilter_clamav.c new file mode 100644 index 00000000000..d0e1fc081b7 --- /dev/null +++ b/source3/modules/vfs_virusfilter_clamav.c @@ -0,0 +1,195 @@ +/* + Samba-VirusFilter VFS modules + ClamAV clamd support + 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 . +*/ + +/* Default values for standard "extra" configuration variables */ + +#ifdef CLAMAV_DEFAULT_SOCKET_PATH +# define VIRUSFILTER_DEFAULT_SOCKET_PATH CLAMAV_DEFAULT_SOCKET_PATH +#else +# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/var/run/clamav/clamd.ctl" +#endif + +#include "modules/vfs_virusfilter_common.h" +#include "modules/vfs_virusfilter_utils.h" + +static int virusfilter_clamav_connect(struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *svc, + const char *user) +{ + + /* To use clamd "zXXXX" commands */ + virusfilter_io_set_writel_eol(config->io_h, "\0", 1); + virusfilter_io_set_readl_eol(config->io_h, "\0", 1); + + return 0; +} + +static virusfilter_result virusfilter_clamav_scan_init( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + bool ok; + + DBG_INFO("clamd: Connecting to socket: %s\n", + config->socket_path); + + become_root(); + ok = virusfilter_io_connect_path(io_h, config->socket_path); + unbecome_root(); + + if (!ok) { + DBG_ERR("clamd: Connecting to socket failed: %s: %s\n", + config->socket_path, strerror(errno)); + return VIRUSFILTER_RESULT_ERROR; + } + + DBG_INFO("clamd: Connected\n"); + + return VIRUSFILTER_RESULT_OK; +} + +static void virusfilter_clamav_scan_end( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + + DBG_INFO("clamd: Disconnecting\n"); + + virusfilter_io_disconnect(io_h); +} + +static virusfilter_result virusfilter_clamav_scan( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp) +{ + char *cwd_fname = fsp->conn->cwd_fname->base_name; + const char *fname = fsp->fsp_name->base_name; + size_t filepath_len = strlen(cwd_fname) + 1 /* slash */ + strlen(fname); + struct virusfilter_io_handle *io_h = config->io_h; + virusfilter_result result = VIRUSFILTER_RESULT_CLEAN; + char *report = NULL; + char *reply = NULL; + char *reply_msg = NULL; + char *reply_token; + bool ok; + + DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname); + + ok = virusfilter_io_writefl_readl(io_h, &reply, "zSCAN %s/%s", + cwd_fname, fname); + if (!ok) { + DBG_ERR("clamd: zSCAN: I/O error: %s\n", strerror(errno)); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner I/O error: %s\n", + strerror(errno)); + goto virusfilter_clamav_scan_return; + } + + if (reply[filepath_len] != ':' || + reply[filepath_len+1] != ' ') + { + DBG_ERR("clamd: zSCAN: Invalid reply: %s\n", + reply); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner communication error"); + goto virusfilter_clamav_scan_return; + } + reply_msg = reply + filepath_len + 2; + + reply_token = strrchr(reply, ' '); + + if (reply_token == NULL) { + DBG_ERR("clamd: zSCAN: Invalid reply: %s\n", + reply); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner communication error"); + goto virusfilter_clamav_scan_return; + } + *reply_token = '\0'; + reply_token++; + + if (strcmp(reply_token, "OK") == 0) { + + /* : OK */ + result = VIRUSFILTER_RESULT_CLEAN; + report = talloc_asprintf(talloc_tos(), "Clean"); + } else if (strcmp(reply_token, "FOUND") == 0) { + + /* : FOUND */ + result = VIRUSFILTER_RESULT_INFECTED; + report = talloc_strdup(talloc_tos(), reply_msg); + } else if (strcmp(reply_token, "ERROR") == 0) { + + /* : ERROR */ + DBG_ERR("clamd: zSCAN: Error: %s\n", reply_msg); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner error: %s\t", reply_msg); + } else { + DBG_ERR("clamd: zSCAN: Invalid reply: %s\n", reply_token); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner communication error"); + } + +virusfilter_clamav_scan_return: + TALLOC_FREE(reply); + if (report == NULL) { + *reportp = talloc_asprintf(talloc_tos(), + "Scanner report memory error"); + } else { + *reportp = report; + } + + return result; +} + +static struct virusfilter_backend_fns virusfilter_backend_clamav = { + .connect = virusfilter_clamav_connect, + .disconnect = NULL, + .scan_init = virusfilter_clamav_scan_init, + .scan = virusfilter_clamav_scan, + .scan_end = virusfilter_clamav_scan_end, +}; + +int virusfilter_clamav_init(struct virusfilter_config *config) +{ + struct virusfilter_backend *backend = NULL; + + if (config->socket_path == NULL) { + config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH; + } + + backend = talloc_zero(config, struct virusfilter_backend); + if (backend == NULL) { + return -1; + } + + backend->fns = &virusfilter_backend_clamav; + backend->name = "clamav"; + + config->backend = backend; + return 0; +} diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h index a28ce2978fa..f71b0b949a7 100644 --- a/source3/modules/vfs_virusfilter_common.h +++ b/source3/modules/vfs_virusfilter_common.h @@ -148,5 +148,6 @@ struct virusfilter_backend { int virusfilter_sophos_init(struct virusfilter_config *config); int virusfilter_fsav_init(struct virusfilter_config *config); +int virusfilter_clamav_init(struct virusfilter_config *config); #endif /* _VIRUSFILTER_COMMON_H */ diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build index f63c00a9955..5c529890470 100644 --- a/source3/modules/wscript_build +++ b/source3/modules/wscript_build @@ -516,6 +516,7 @@ bld.SAMBA3_MODULE('vfs_virusfilter', vfs_virusfilter.c vfs_virusfilter_sophos.c vfs_virusfilter_fsav.c + vfs_virusfilter_clamav.c ''', deps='samba-util VFS_VIRUSFILTER_UTILS', init_function='', -- 2.13.6