Latest AV code

Ralph Böhme slow at samba.org
Fri Dec 22 14:47:02 UTC 2017


Hi folks,

On Thu, Dec 21, 2017 at 03:06:32PM -0800, Jeremy Allison wrote:
> On Thu, Dec 21, 2017 at 11:23:01PM +0100, Ralph Böhme wrote:
> > 
> > The one I have is "[PATCHES] Samba-VirusFilter (version 6)". Is that the latest one? If
> > not, can you please bounce me their latest mail to s-t so I can follow up on the
> > list? Thanks!
> 
> Here's the latest version I have.

Jeremy, attached is the rewritten version as discussed.

Trevor, Satoh, a few days ago Jeremy asked me to look into the latest version of
the Virusfilter patchset (probably version 10 as posted on
https://lists.samba.org/archive/samba-technical/2017-July/121573.html), asking
for my opinion on the code.

I haven't been involved in the previous review rounds and somehow felt that the
current implementation using #includes of some generic code into three VFS
modules looked awkward.

I got bored the other day on a train from Frankenberg to Goslar, so I took the
liberty to bend the code to my will. Attached is a rewrite that removes the
#include magic by splitting the modules into one frontend VFS moulde
vfs_virusfilter and three scanner backends. The backend is then chosen by a new
option "virusfilter:scanner=clamav|fsav|sophos".

Trevor and Satoh, can you take a look and tell us if you agree with the rewrite?
This is the general design change that Jeremy and I agreed upon to be necessary
for the module to go upstream.

I tried to be careful when shuffling around the options and removing all the
macro stuff, so hopefully everything still works, but given the size of the
change this needs some testing I guess. The attached patchset compiles, but
hasn't seen any testing...

The attach patchset keeps the FIXUP patches as seperate patches on top of the
individual original patches. This is just to make it easier to see what I'm
doing. If you're agree with the new design, just fold the fixups into the
preceding patch.

If yes, then I guess there's little left that hinders final review by me and
possibly Jeremy. There are still lots of README.Coding violations, I've already
fixed many of them in the rewrite, I'd appreciate if you could fix the
remaining. Most of the violations are function calls in conditionals:

if (somefunc() COMPARISON EXPECTED_RESULT) { ... }

Please rewrite as:

VARIABLE = somefunc();
if (VARIABLE COMPARISON EXPECTED_RESULT) { ... }

-slow

-- 
Ralph Boehme, Samba Team       https://samba.org/
Samba Developer, SerNet GmbH   https://sernet.de/en/samba/
-------------- next part --------------
From 6aea5445ec2e289e85fdd0c169fe7f5c93313ea4 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:37:19 -0600
Subject: [PATCH 1/9] Samba-VirusFilter: memcache changes.

Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
---
 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 8eb1451e009a73fc2cd0e33a40528c54f38fbe14 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:34:53 -0600
Subject: [PATCH 2/9] Samba-VirusFilter: common headers and sources.

Samba-VirusFilter Contributors:

SATOH Fumiyasu @ OSS Technology Corp., Japan
Module creator/maintainer

Luke Dixon luke.dixon at zynstra.com
Samba 4 support

Trever L. Adams
Documentation
Code contributions
Samba-master merge work

With many thanks to the Samba Team.

Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas at osstech.co.jp>
---
 .../scripts/vfs/virusfilter/virusfilter-notify.ksh |  284 ++++
 source3/modules/vfs_virusfilter_common.h           |   67 +
 source3/modules/vfs_virusfilter_utils.c            | 1024 +++++++++++++
 source3/modules/vfs_virusfilter_utils.h            |  177 +++
 source3/modules/vfs_virusfilter_vfs.c              | 1524 ++++++++++++++++++++
 5 files changed, 3076 insertions(+)
 create mode 100644 examples/scripts/vfs/virusfilter/virusfilter-notify.ksh
 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
 create mode 100644 source3/modules/vfs_virusfilter_vfs.c

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 <http://www.gnu.org/licenses/>.
+##
+
+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:- at 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 >/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_common.h b/source3/modules/vfs_virusfilter_common.h
new file mode 100644
index 00000000000..b09753ca782
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -0,0 +1,67 @@
+/*
+   Samba-VirusFilter VFS modules
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_COMMON_H
+#define _VIRUSFILTER_COMMON_H
+
+#include <stdint.h>
+#include <time.h>
+
+/* Samba common include file */
+#include "includes.h"
+
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "system/filesys.h"
+#include "transfer_file.h"
+#include "auth.h"
+#include "passdb.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../lib/tsocket/tsocket.h"
+
+/* Samba debug class for VIRUSFILTER */
+#undef DBGC_CLASS
+#define DBGC_CLASS virusfilter_debug_level
+extern int virusfilter_debug_level;
+
+/* 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,
+#ifdef VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE
+	VIRUSFILTER_RESULT_SUSPECTED,
+#endif
+	/* FIXME: VIRUSFILTER_RESULT_RISKWARE, */
+} virusfilter_result;
+
+#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..359062c8545
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.c
@@ -0,0 +1,1024 @@
+/*
+   Samba-VirusFilter VFS modules
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+   Copyright (C) 2016 Trever L. Adams
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+struct iovec;
+
+#include "lib/util/iov_buf.h"
+#include <tevent.h>
+#include "lib/tsocket/tsocket.h"
+
+int virusfilter_debug_level = 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;
+}
+
+virusfilter_result virusfilter_io_connect_path(
+	struct virusfilter_io_handle *io_h,
+	const char *path)
+{
+	struct sockaddr_un addr;
+	NTSTATUS status;
+	int socket, bes_result, flags;
+
+	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 VIRUSFILTER_RESULT_ERROR;
+	}
+
+	/* 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;
+	if (fcntl(socket, F_SETFL, flags) == -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 VIRUSFILTER_RESULT_ERROR;
+	}
+
+	return VIRUSFILTER_RESULT_OK;
+}
+
+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;
+	}
+}
+
+virusfilter_result virusfilter_io_disconnect(
+	struct virusfilter_io_handle *io_h)
+{
+	struct tevent_req *req;
+	struct tevent_context *ev;
+	uint64_t *perror = NULL;
+	int ret;
+	int result = VIRUSFILTER_RESULT_OK;
+	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");
+		result = VIRUSFILTER_RESULT_ERROR;
+		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. */
+	if (!tevent_req_set_endtime(req, ev, timeval_current_ofs_msec(
+				    io_h->connect_timeout)))
+	{
+		DBG_ERR("Can't set endtime\n");
+		goto fail;
+	}
+
+	/* Loop waiting for req to finish. */
+	ret = tevent_req_poll(req, ev);
+	if (!ret) {
+		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 result;
+}
+
+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 ret = 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. */
+	if (!tevent_req_set_endtime(req, ev,
+				    timeval_current_ofs_msec(ms_timeout)))
+	{
+		DBG_ERR("Can't set endtime\n");
+		goto fail;
+	}
+
+	/* Loop waiting for req to finish. */
+	ret = tevent_req_poll(req, ev);
+	if (!ret) {
+		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;
+
+	switch (write_data_iov_timeout(io_h->stream, &iov, 1,
+		io_h->io_timeout)) {
+	case false:
+		return false;
+	default:
+		return true;
+	}
+}
+
+bool virusfilter_io_writel(
+	struct virusfilter_io_handle *io_h,
+	const char *data,
+	size_t data_size)
+{
+	virusfilter_result result;
+
+	result = virusfilter_io_write(io_h, data, data_size);
+	if (result != true) {
+		return result;
+	}
+
+	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) {
+			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) {
+			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 ret = VIRUSFILTER_RESULT_ERROR;
+	uint64_t *perror = NULL;
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	/* Search for an existing complete line. */
+	ret = return_existing_line(ctx, io_h, read_line);
+	if (ret == true) {
+		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. */
+		if (!tevent_req_set_endtime(req,
+				ev,
+				timeval_current_ofs_msec(io_h->io_timeout))) {
+			DBG_ERR("can't set endtime\n");
+			goto finish;
+		}
+
+		/* Loop waiting for req to finish. */
+		ret = tevent_req_poll(req, ev);
+		if (!ret) {
+			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. */
+		ret = return_existing_line(ctx, io_h, read_line);
+		if (ret == true) {
+			goto finish;
+		}
+		/* No eol - keep reading. */
+	}
+
+  finish:
+
+	TALLOC_FREE(frame);
+	return ret;
+}
+
+bool virusfilter_io_writefl_readl(
+	struct virusfilter_io_handle *io_h,
+	char **read_line,
+	const char *fmt, ...)
+{
+	bool result;
+
+	if (fmt) {
+		va_list ap;
+
+		va_start(ap, fmt);
+		result = virusfilter_io_vwritefl(io_h, fmt, ap);
+		va_end(ap);
+
+		if (result != true) {
+			return result;
+		}
+	}
+
+	result = virusfilter_io_readl(talloc_tos(), io_h, read_line);
+	if (result != true) {
+		DBG_ERR("virusfilter_io_readl not OK: %d\n", result);
+		return false;
+	}
+	if (io_h->r_len == 0) { /* EOF */
+		DBG_ERR("virusfilter_io_readl EOF\n");
+		return false;
+	}
+
+	return true;
+}
+
+struct virusfilter_cache_handle *virusfilter_cache_new(
+	TALLOC_CTX *ctx,
+	int entry_limit,
+	time_t time_limit)
+{
+	struct virusfilter_cache_handle *cache_h;
+
+	if (time_limit == 0) {
+		return NULL;
+	}
+
+	cache_h = talloc_zero(ctx, struct virusfilter_cache_handle);
+	if (cache_h == NULL) {
+		DBG_ERR("talloc_zero failed.\n");
+		return NULL;
+	}
+
+	cache_h->cache = memcache_init(cache_h->ctx, entry_limit *
+				       (sizeof(struct virusfilter_cache_entry)
+				       + VIRUSFILTER_CACHE_BUFFER_SIZE));
+	if (cache_h->cache == NULL) {
+		DBG_ERR("memcache_init failed.\n");
+		return NULL;
+	}
+	cache_h->ctx = ctx;
+	cache_h->time_limit = time_limit;
+
+	return cache_h;
+}
+
+int virusfilter_cache_entry_add(
+	struct virusfilter_cache_handle *cache_h,
+	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 0;
+	}
+
+	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+	if (fname == NULL) {
+		TALLOC_FREE(report);
+		return 0;
+	}
+
+	fname_len = strlen(fname);
+
+	if (cache_e == NULL|| cache_h->time_limit == 0) {
+		TALLOC_FREE(report);
+		return 0;
+	}
+
+	cache_e->result = result;
+	if (report != NULL) {
+		cache_e->report = talloc_steal(cache_e, report);
+	}
+	if (cache_h->time_limit > 0) {
+		cache_e->time = time(NULL);
+	}
+
+	memcache_add_talloc(cache_h->cache,
+			    VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+			    data_blob_const(fname, fname_len), &cache_e);
+
+	return 1;
+}
+
+int virusfilter_cache_entry_rename(
+	struct virusfilter_cache_handle *cache_h,
+	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 0;
+	}
+
+	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 0;
+	}
+
+	old_fname_len = strlen(old_fname);
+	new_fname_len = strlen(new_fname);
+
+	old_data = memcache_lookup_talloc(
+				cache_h->cache,
+				VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+				data_blob_const(old_fname, old_fname_len));
+
+	if (old_data == NULL) {
+		return 0;
+	}
+
+	new_data = talloc_memdup(cache_h->ctx, old_data,
+				 sizeof(struct virusfilter_cache_entry));
+	if (new_data == NULL) {
+		return 0;
+	}
+	new_data->report = talloc_strdup(new_data, old_data->report);
+
+	memcache_add_talloc(cache_h->cache,
+			VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+			data_blob_const(new_fname, new_fname_len), &new_data);
+
+	memcache_delete(cache_h->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+			data_blob_const(old_fname, old_fname_len));
+
+	return 1;
+}
+
+void virusfilter_cache_purge(struct virusfilter_cache_handle *cache_h)
+{
+	memcache_flush(cache_h->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
+}
+
+struct virusfilter_cache_entry *virusfilter_cache_get(
+	struct virusfilter_cache_handle *cache_h,
+	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_h->cache,
+				      VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+				      data_blob_const(fname, fname_len));
+
+	if (data == NULL) {
+		return cache_e;
+	}
+
+	if (cache_h->time_limit > 0) {
+		if (time(NULL) - data->time  > cache_h->time_limit) {
+			DBG_DEBUG("Cache entry is too old: %s\n",
+				  fname);
+			virusfilter_cache_remove(cache_h, directory, fname);
+			return cache_e;
+		}
+	}
+	cache_e = talloc_memdup(cache_h->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_handle *cache_h,
+	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_h->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;
+
+	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) {
+		if (strncmp("::ffff:", server_addr_p, 7) == 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) {
+		if (strncmp("::ffff:", client_addr_p, 7) == 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)
+{
+	if (conn != NULL && virusfilter_shell_set_conn_env(mem_ctx, env_list,
+							   conn) == -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..19a2fa14b7a
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.h
@@ -0,0 +1,177 @@
+/*
+   Samba-VirusFilter VFS modules
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_UTILS_H
+#define _VIRUSFILTER_UTILS_H
+
+#include "modules/vfs_virusfilter_common.h"
+#include "../lib/util/memcache.h"
+#include "../lib/util/strv.h"
+
+/*#define str_eq(s1, s2)		\
+	((strcmp((s1), (s2)) == 0) ? true : false)
+#define strn_eq(s1, s2, n)	\
+	((strncmp((s1), (s2), (n)) == 0) ? true : false) */
+
+/* "* 3" is for %-encoding */
+#define VIRUSFILTER_IO_URL_MAX		(PATH_MAX * 3)
+#define VIRUSFILTER_IO_BUFFER_SIZE	(VIRUSFILTER_IO_URL_MAX + 128)
+#define VIRUSFILTER_IO_EOL_SIZE		1
+#define VIRUSFILTER_IO_IOV_MAX		16
+#define VIRUSFILTER_CACHE_BUFFER_SIZE	(PATH_MAX + 128)
+
+struct virusfilter_io_handle {
+	struct tstream_context *stream;
+	int		connect_timeout;	/* msec */
+	int		io_timeout;		/* msec */
+
+	/* end-of-line character(s) */
+	char		w_eol[VIRUSFILTER_IO_EOL_SIZE];
+	int		w_eol_size;
+
+	/* end-of-line character(s) */
+	char		r_eol[VIRUSFILTER_IO_EOL_SIZE];
+	int		r_eol_size;
+
+	/* buffer */
+	char		r_buffer[VIRUSFILTER_IO_BUFFER_SIZE];
+	size_t		r_len;
+};
+
+struct virusfilter_cache_entry {
+	time_t time;
+	virusfilter_result result;
+	char *report;
+};
+
+struct virusfilter_cache_handle {
+	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);
+virusfilter_result virusfilter_io_connect_path(
+	struct virusfilter_io_handle *io_h,
+	const char *path);
+virusfilter_result 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_handle *virusfilter_cache_new(
+	TALLOC_CTX *ctx,
+	int entry_limit,
+	time_t time_limit);
+int virusfilter_cache_entry_add(
+	struct virusfilter_cache_handle *cache_h,
+	const char *directory,
+	const char *fname,
+	virusfilter_result result,
+	char *report);
+int virusfilter_cache_entry_rename(
+	struct virusfilter_cache_handle *cache_h,
+	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_handle *cache_h,
+	const char *directory,
+	const char *fname);
+void virusfilter_cache_remove(
+	struct virusfilter_cache_handle *cache_h,
+	const char *directory,
+	const char *fname);
+void virusfilter_cache_purge(struct virusfilter_cache_handle *cache_h);
+
+/* 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/vfs_virusfilter_vfs.c b/source3/modules/vfs_virusfilter_vfs.c
new file mode 100644
index 00000000000..16ca70d8381
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_vfs.c
@@ -0,0 +1,1524 @@
+/*
+ * Samba-VirusFilter VFS template
+ * #included into modules/vfs_virusfilter_clamav.c,
+ * modules/vfs_virusfilter_fsav.c and modules/vfs_virusfilter_sophos.c.
+ *
+ * The defines virusfilter_module_connect, virusfilter_module_scan_init,
+ * virusfilter_module_scan_end, virusfilter_module_scan must be defined, before
+ * including this file, as functions which implements those operations. The
+ * function names are normally the same as the define with the "module" part
+ * replaced with the module name. virusfilter_module_destruct_config may
+ * optionally be defined.
+ *
+ * The following must be defined before the include in every module:
+ * VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT, VIRUSFILTER_DEFAULT_SOCKET_PATH,
+ * VIRUSFILTER_DEFAULT_TIMEOUT, VIRUSFILTER_ENGINE (non-string module name),
+ * VIRUSFILTER_MODULE_ENGINE (string module name). See existing modules for
+ * examples.
+ *
+ * The following must be defined before the include if used in the module:
+ * VIRUSFILTER_DEFAULT_SCAN_ARCHIVE, VIRUSFILTER_DEFAULT_SCAN_MIME,
+ * VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE,
+ * VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT,
+ * VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE.
+ *
+ * Optionally, VIRUSFILTER_MODULE_CONFIG_MEMBERS may be defined
+ * (see modules/vfs_virusfilter_fsav.c for example). The configuration should
+ * be done in virusfilter_MODULENAME_connect inside of the virus scanning
+ * engine specific module.
+ *
+ * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+ * Copyright (C) 2016 Trever L. Adams
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+#define VIRUSFILTER_MODULE_NAME "virusfilter_" VIRUSFILTER_MODULE_ENGINE
+
+/*
+ * Default configuration values
+ * ======================================================================
+ */
+
+#define VIRUSFILTER_DEFAULT_SCAN_ON_OPEN		true
+#define VIRUSFILTER_DEFAULT_SCAN_ON_CLOSE		false
+#define VIRUSFILTER_DEFAULT_MAX_FILE_SIZE		100000000L /* 100MB */
+#define VIRUSFILTER_DEFAULT_MIN_FILE_SIZE		0
+#define VIRUSFILTER_DEFAULT_EXCLUDE_FILES		NULL
+
+#define VIRUSFILTER_DEFAULT_CACHE_ENTRY_LIMIT		100
+#define VIRUSFILTER_DEFAULT_CACHE_TIME_LIMIT		10
+
+#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ACTION	\
+	VIRUSFILTER_ACTION_DO_NOTHING
+#define VIRUSFILTER_DEFAULT_INFECTED_FILE_COMMAND	NULL
+#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_OPEN	EACCES
+#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_CLOSE 0
+
+#define VIRUSFILTER_DEFAULT_SCAN_ERROR_COMMAND		NULL
+#define VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_OPEN	EACCES
+#define VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_CLOSE	0
+#define VIRUSFILTER_DEFAULT_BLOCK_ACCESS_ON_ERROR	false
+
+#define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX		"virusfilter."
+#define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX		".infected"
+#define VIRUSFILTER_DEFAULT_QUARANTINE_KEEP_NAME	false
+
+#define VIRUSFILTER_DEFAULT_QUARANTINE_DIR		".quarantine"
+/* 700 = S_IRUSR | S_IWUSR | S_IXUSR */
+#define VIRUSFILTER_DEFAULT_QUARANTINE_DIR_MODE		"700"
+
+#define VIRUSFILTER_DEFAULT_RENAME_PREFIX		"virusfilter."
+#define VIRUSFILTER_DEFAULT_RENAME_SUFFIX		".infected"
+
+/* ====================================================================== */
+
+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}
+};
+
+struct virusfilter_handle {
+#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
+	int				scan_request_count;
+	int				scan_request_limit;
+#endif
+
+	/* Scan on file operations */
+	bool				scan_on_open;
+	bool				scan_on_close;
+
+	/* Special scan options */
+#ifdef VIRUSFILTER_DEFAULT_SCAN_ARCHIVE
+	bool				scan_archive;
+#endif
+#ifdef VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE
+	int				max_nested_scan_archive;
+#endif
+#ifdef VIRUSFILTER_DEFAULT_SCAN_MIME
+	bool				scan_mime;
+#endif
+#ifdef VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE
+	bool				block_suspected_file;
+#endif
+
+	/* 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_handle *cache_h;
+	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_name;
+	mode_t				quarantine_dir_mode;
+
+	/* Rename infected files */
+	const char *			rename_prefix;
+	const char *			rename_suffix;
+
+	/* Network options */
+#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
+	const char *			socket_path;
+	struct virusfilter_io_handle	*io_h;
+#endif
+
+	/* Module specific configuration options */
+#ifdef VIRUSFILTER_MODULE_CONFIG_MEMBERS
+	VIRUSFILTER_MODULE_CONFIG_MEMBERS
+#endif
+};
+
+/* ====================================================================== */
+
+#ifdef virusfilter_module_connect
+static int virusfilter_module_connect(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const char *svc,
+	const char *user);
+#endif
+
+#ifdef virusfilter_module_disconnect
+static int virusfilter_module_disconnect(struct vfs_handle_struct *vfs_h);
+#endif
+
+#ifdef virusfilter_module_destruct_config
+static int virusfilter_module_destruct_config(
+	struct virusfilter_handle *virusfilter_h);
+#endif
+
+#ifdef virusfilter_module_scan_init
+static virusfilter_result virusfilter_module_scan_init(
+	struct virusfilter_handle *virusfilter_h);
+#endif
+
+#ifdef virusfilter_module_scan_end
+static void virusfilter_module_scan_end(
+	struct virusfilter_handle *virusfilter_h);
+#endif
+
+static virusfilter_result virusfilter_module_scan(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const struct files_struct *fsp,
+	char **reportp);
+
+/* ====================================================================== */
+
+static int virusfilter_destruct_config(
+	struct virusfilter_handle *virusfilter_h)
+{
+#ifdef virusfilter_module_destruct_config
+	return virusfilter_module_destruct_config(virusfilter_h);
+#else
+	return 0;
+#endif
+}
+
+/*
+ * 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)
+{
+	struct smb_filename smb_fname = {
+		.base_name = discard_const_p(char, dname)
+	};
+
+	if (SMB_VFS_STAT(handle->conn, &smb_fname) == 0) {
+		if (S_ISDIR(smb_fname.st.st_ex_mode)) {
+			return true;
+		}
+	}
+
+	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_handle *virusfilter_h,
+	const char *dname)
+{
+	size_t len;
+	mode_t mode;
+	char *new_dir = NULL;
+	char *tmp_str = NULL;
+	char *token;
+	char *tok_str;
+	bool ret = false;
+	char *saveptr;
+
+	mode = virusfilter_h->quarantine_dir_mode;
+
+	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. */
+		if (strlcat(new_dir,"/",len+1) >= len+1) {
+			goto done;
+		}
+	}
+
+	/* Create directory tree if neccessary */
+	for (token = strtok_r(tok_str, "/", &saveptr); token;
+	     token = strtok_r(NULL, "/", &saveptr))
+	{
+		if (strlcat(new_dir, token, len+1) >= len+1) {
+			goto done;
+		}
+		if (quarantine_directory_exist(handle, new_dir)) {
+			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;
+			}
+
+			if (SMB_VFS_NEXT_MKDIR(handle, smb_fname, mode) != 0) {
+				TALLOC_FREE(smb_fname);
+
+				DBG_WARNING("quarantine: mkdir failed for %s "
+					    "with error: %s\n", new_dir,
+					    strerror(errno));
+				ret = false;
+				goto done;
+			}
+			TALLOC_FREE(smb_fname);
+		}
+		if (strlcat(new_dir, "/", len+1) >= len+1) {
+			goto done;
+		}
+		mode = virusfilter_h->quarantine_dir_mode;
+	}
+
+	ret = true;
+	done:
+		TALLOC_FREE(tmp_str);
+		TALLOC_FREE(new_dir);
+		return ret;
+}
+
+static int virusfilter_vfs_connect(
+	struct vfs_handle_struct *vfs_h,
+	const char *svc,
+	const char *user)
+{
+	int snum = SNUM(vfs_h->conn);
+	struct virusfilter_handle *virusfilter_h;
+	const char *exclude_files;
+	const char *temp_quarantine_dir_mode = NULL;
+#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
+	int connect_timeout, io_timeout;
+#endif
+
+
+	virusfilter_h = talloc_zero(vfs_h, struct virusfilter_handle);
+	if (!virusfilter_h) {
+		DBG_ERR("talloc_zero failed\n");
+		return -1;
+	}
+
+	talloc_set_destructor(virusfilter_h, virusfilter_destruct_config);
+
+	SMB_VFS_HANDLE_SET_DATA(vfs_h, virusfilter_h, NULL,
+				struct virusfilter_handle, return -1);
+
+#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
+	virusfilter_h->scan_request_limit = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "scan request limit",
+		VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT);
+#endif
+
+	virusfilter_h->scan_on_open = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "scan on open",
+		VIRUSFILTER_DEFAULT_SCAN_ON_OPEN);
+	virusfilter_h->scan_on_close = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "scan on close",
+		VIRUSFILTER_DEFAULT_SCAN_ON_CLOSE);
+#ifdef VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE
+	virusfilter_h->max_nested_scan_archive = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "max nested scan archive",
+		VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE);
+#endif
+#ifdef VIRUSFILTER_DEFAULT_SCAN_ARCHIVE
+	virusfilter_h->scan_archive = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "scan archive",
+		VIRUSFILTER_DEFAULT_SCAN_ARCHIVE);
+#endif
+#ifdef VIRUSFILTER_DEFAULT_MIME_SCAN
+	virusfilter_h->scan_mime = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "scan mime",
+		VIRUSFILTER_DEFAULT_SCAN_MIME);
+#endif
+
+	virusfilter_h->max_file_size = (ssize_t)lp_parm_ulong(snum,
+		VIRUSFILTER_MODULE_NAME, "max file size",
+		VIRUSFILTER_DEFAULT_MAX_FILE_SIZE);
+	virusfilter_h->min_file_size = (ssize_t)lp_parm_ulong(snum,
+		VIRUSFILTER_MODULE_NAME, "min file size",
+		VIRUSFILTER_DEFAULT_MIN_FILE_SIZE);
+
+	exclude_files = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "exclude files",
+		VIRUSFILTER_DEFAULT_EXCLUDE_FILES);
+	if (exclude_files) {
+		set_namearray(&virusfilter_h->exclude_files, exclude_files);
+	}
+
+	virusfilter_h->cache_entry_limit = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "cache entry limit",
+		VIRUSFILTER_DEFAULT_CACHE_ENTRY_LIMIT);
+	virusfilter_h->cache_time_limit = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "cache time limit",
+		VIRUSFILTER_DEFAULT_CACHE_TIME_LIMIT);
+
+	virusfilter_h->infected_file_action = lp_parm_enum(snum,
+		VIRUSFILTER_MODULE_NAME, "infected file action",
+		virusfilter_actions, VIRUSFILTER_DEFAULT_INFECTED_FILE_ACTION);
+	virusfilter_h->infected_file_command = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "infected file command",
+		VIRUSFILTER_DEFAULT_INFECTED_FILE_COMMAND);
+	virusfilter_h->scan_error_command = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "scan error command",
+		VIRUSFILTER_DEFAULT_SCAN_ERROR_COMMAND);
+	virusfilter_h->block_access_on_error = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "block access on error",
+		VIRUSFILTER_DEFAULT_BLOCK_ACCESS_ON_ERROR);
+
+	virusfilter_h->quarantine_dir = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "quarantine directory",
+		VIRUSFILTER_DEFAULT_QUARANTINE_DIR);
+
+	temp_quarantine_dir_mode = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "quarantine directory mode",
+		VIRUSFILTER_DEFAULT_QUARANTINE_DIR_MODE);
+	if (temp_quarantine_dir_mode != NULL) {
+		sscanf(temp_quarantine_dir_mode, "%o",
+		       &virusfilter_h->quarantine_dir_mode);
+	}
+	virusfilter_h->quarantine_prefix = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "quarantine prefix",
+		VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+	virusfilter_h->quarantine_suffix = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "quarantine suffix",
+		VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+
+	/*
+	 * Make sure prefixes and suffixes do not contain directory
+	 * delimiters
+	 */
+	if (strstr(virusfilter_h->quarantine_prefix, "/")) {
+		DBG_ERR("quarantine prefix must not contain directory "
+			"delimiter(s) such as '/' (%s replaced with %s)\n",
+			virusfilter_h->quarantine_prefix,
+			VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+		virusfilter_h->quarantine_prefix =
+			VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX;
+	}
+	if (strstr(virusfilter_h->quarantine_suffix, "/")) {
+		DBG_ERR("quarantine suffix must not contain directory "
+			"delimiter(s) such as '/' (%s replaced with %s)\n",
+			virusfilter_h->quarantine_suffix,
+			VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+		virusfilter_h->quarantine_suffix =
+			VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX;
+	}
+
+	virusfilter_h->quarantine_keep_name = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "quarantine keep name",
+		VIRUSFILTER_DEFAULT_QUARANTINE_KEEP_NAME);
+
+	virusfilter_h->rename_prefix = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "rename prefix",
+		VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+	virusfilter_h->rename_suffix = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "rename suffix",
+		VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+
+	/*
+	 * Make sure prefixes and suffixes do not contain directory
+	 * delimiters
+	 */
+	if (strstr(virusfilter_h->rename_prefix, "/")) {
+		DBG_ERR("rename prefix must not contain directory "
+			"delimiter(s) such as '/' (%s replaced with %s)\n",
+			virusfilter_h->rename_prefix,
+			VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+		virusfilter_h->rename_prefix =
+			VIRUSFILTER_DEFAULT_RENAME_PREFIX;
+	}
+	if (strstr(virusfilter_h->rename_suffix, "/")) {
+		DBG_ERR("rename suffix must not contain directory "
+			"delimiter(s) such as '/' (%s replaced with %s)\n",
+			virusfilter_h->rename_suffix,
+			VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+		virusfilter_h->rename_suffix =
+			VIRUSFILTER_DEFAULT_RENAME_SUFFIX;
+	}
+
+	virusfilter_h->infected_open_errno = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "infected file errno on open",
+		VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_OPEN);
+	virusfilter_h->infected_close_errno = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "infected file errno on close",
+		VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_CLOSE);
+	virusfilter_h->scan_error_open_errno = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "scan error errno on open",
+		VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_OPEN);
+	virusfilter_h->scan_error_close_errno = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "scan error errno on close",
+		VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_CLOSE);
+
+#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
+	virusfilter_h->socket_path = lp_parm_const_string(snum,
+		VIRUSFILTER_MODULE_NAME, "socket path",
+		VIRUSFILTER_DEFAULT_SOCKET_PATH);
+
+	/* canonicalize socket_path */
+	if(virusfilter_h->socket_path[0] != '/') {
+		DBG_ERR("socket path must be an absolute path. "
+			"Replacing %s with %s\n", virusfilter_h->socket_path,
+			VIRUSFILTER_DEFAULT_SOCKET_PATH);
+		virusfilter_h->socket_path =
+			VIRUSFILTER_DEFAULT_SOCKET_PATH;
+        }
+	virusfilter_h->socket_path =
+		canonicalize_absolute_path(vfs_h,
+					   virusfilter_h->socket_path);
+
+	connect_timeout = lp_parm_int(snum, VIRUSFILTER_MODULE_NAME,
+		"connect timeout", VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT);
+	io_timeout = lp_parm_int(snum, VIRUSFILTER_MODULE_NAME, "io timeout",
+		VIRUSFILTER_DEFAULT_TIMEOUT);
+
+	virusfilter_h->io_h =
+		virusfilter_io_new(virusfilter_h, connect_timeout, io_timeout);
+
+	if (!virusfilter_h->io_h) {
+		DBG_ERR("virusfilter_io_new failed");
+		return -1;
+	}
+#endif
+
+	if (virusfilter_h->cache_entry_limit > 0) {
+		virusfilter_h->cache_h = virusfilter_cache_new(vfs_h,
+					virusfilter_h->cache_entry_limit,
+					virusfilter_h->cache_time_limit);
+		if (!virusfilter_h->cache_h) {
+			DBG_ERR("Initializing cache failed: Cache disabled\n");
+		}
+	}
+
+#ifdef virusfilter_module_connect
+	if (virusfilter_module_connect(vfs_h, virusfilter_h, svc, user) == -1) {
+		return -1;
+	}
+#endif
+
+	/*
+	 * Check quarantine directory now to save processing
+	 * and becoming root over and over.
+	 */
+	if (virusfilter_h->infected_file_action ==
+	    VIRUSFILTER_ACTION_QUARANTINE)
+	{
+
+		/*
+		 * Do SMB_VFS_NEXT_MKDIR(virusfilter_h->quarantine_dir)
+		 * hierarchy
+		 */
+		become_root();
+		if (!quarantine_directory_exist(vfs_h,
+		    virusfilter_h->quarantine_dir))
+		{
+			DBG_DEBUG("Creating quarantine directory: %s\n",
+				  virusfilter_h->quarantine_dir);
+			quarantine_create_dir(vfs_h, virusfilter_h,
+					      virusfilter_h->quarantine_dir);
+		}
+		unbecome_root();
+	}
+
+	return SMB_VFS_NEXT_CONNECT(vfs_h, svc, user);
+}
+
+static void virusfilter_vfs_disconnect(struct vfs_handle_struct *vfs_h)
+{
+	struct virusfilter_handle *virusfilter_h;
+
+#ifdef virusfilter_module_disconnect
+	virusfilter_module_disconnect(vfs_h);
+#endif
+
+	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
+				struct virusfilter_handle, return);
+
+	free_namearray(virusfilter_h->exclude_files);
+#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
+	virusfilter_io_disconnect(virusfilter_h->io_h);
+#endif
+
+	SMB_VFS_NEXT_DISCONNECT(vfs_h);
+}
+
+static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx, char **env_list)
+{
+	if (virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
+	    VIRUSFILTER_VERSION) == -1)
+	{
+		return -1;
+	}
+	if (virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
+	    VIRUSFILTER_MODULE_NAME) == -1)
+	{
+		return -1;
+	}
+#ifdef VIRUSFILTER_MODULE_VERSION
+	if (virusfilter_env_set(mem_ctx, env_list,
+	    "VIRUSFILTER_MODULE_VERSION", VIRUSFILTER_MODULE_VERSION) == -1)
+	{
+		return -1;
+	}
+#endif
+
+	return 0;
+}
+
+static virusfilter_action virusfilter_do_infected_file_action(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const struct files_struct *fsp,
+	const char **filepath_newp)
+{
+	TALLOC_CTX *mem_ctx = talloc_tos();
+	connection_struct *conn = vfs_h->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;
+	char *q_prefix;
+	char *q_suffix;
+	char *q_filepath;
+	char *dir_name = NULL;
+	char *temp_path;
+	const char *base_name = NULL;
+	int q_fd;
+
+	*filepath_newp = NULL;
+
+	switch (virusfilter_h->infected_file_action) {
+	case VIRUSFILTER_ACTION_RENAME:
+		q_prefix = virusfilter_string_sub(mem_ctx, conn,
+						virusfilter_h->rename_prefix);
+		q_suffix = virusfilter_string_sub(mem_ctx, conn,
+						virusfilter_h->rename_suffix);
+		if (q_prefix == NULL || q_suffix == NULL) {
+			DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+				"memory\n", cwd_fname, fname);
+			TALLOC_FREE(q_prefix);
+			TALLOC_FREE(q_suffix);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		if (!parent_dirname(mem_ctx, fname, &q_dir,
+		    &base_name))
+		{
+			DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+				"memory\n", cwd_fname, fname);
+			TALLOC_FREE(q_prefix);
+			TALLOC_FREE(q_suffix);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		if (q_dir == NULL) {
+			DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+				"memory\n", cwd_fname, fname);
+			TALLOC_FREE(q_prefix);
+			TALLOC_FREE(q_suffix);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		q_filepath = talloc_asprintf(talloc_tos(), "%s/%s%s%s", q_dir,
+					     q_prefix, base_name, q_suffix);
+
+		TALLOC_FREE(q_dir);
+		TALLOC_FREE(q_prefix);
+		TALLOC_FREE(q_suffix);
+
+		become_root();
+
+		q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+						  smb_fname->stream_name, NULL,
+						  smb_fname->flags);
+		if (q_smb_fname == NULL) {
+			unlink(q_filepath);
+			unbecome_root();
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		if (virusfilter_vfs_next_move(vfs_h, smb_fname, q_smb_fname)
+		    == -1)
+		{
+			unbecome_root();
+			DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
+				cwd_fname, fname,
+				strerror(errno));
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+		unbecome_root();
+
+		*filepath_newp = q_filepath;
+
+		return VIRUSFILTER_ACTION_RENAME;
+
+	case VIRUSFILTER_ACTION_QUARANTINE:
+		q_dir = virusfilter_string_sub(mem_ctx, conn,
+					virusfilter_h->quarantine_dir);
+		q_prefix = virusfilter_string_sub(mem_ctx, conn,
+					virusfilter_h->quarantine_prefix);
+		q_suffix = virusfilter_string_sub(mem_ctx, conn,
+					virusfilter_h->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);
+			TALLOC_FREE(q_dir);
+			TALLOC_FREE(q_prefix);
+			TALLOC_FREE(q_suffix);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		if (!parent_dirname(mem_ctx, fname,
+		    &dir_name, &base_name))
+		{
+			DBG_ERR("Quarantine failed: %s/%s: Cannot "
+				"allocate memory\n", cwd_fname,
+				fname);
+			TALLOC_FREE(q_dir);
+			TALLOC_FREE(q_prefix);
+			TALLOC_FREE(q_suffix);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		temp_path = talloc_asprintf(mem_ctx, "%s/%s",
+						    cwd_fname, q_dir);
+
+		if (temp_path == NULL) {
+			DBG_ERR("Quarantine failed: %s/%s: Cannot "
+				"allocate memory\n", cwd_fname,
+				fname);
+			TALLOC_FREE(q_dir);
+			TALLOC_FREE(q_prefix);
+			TALLOC_FREE(q_suffix);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		become_root();
+		if (quarantine_directory_exist(vfs_h,
+		    temp_path))
+		{
+			DBG_DEBUG("quarantine: Directory already "
+				  "exists\n");
+			TALLOC_FREE(q_dir);
+			q_dir = temp_path;
+		} else {
+			DBG_DEBUG("quarantine: Creating "
+			      "directory %s\n", temp_path);
+			if (quarantine_create_dir(vfs_h,
+			    virusfilter_h, temp_path) == false)
+			{
+				DBG_NOTICE("quarantine: Could not "
+					"create directory ignoring"
+					"for %s...\n",
+					smb_fname_str_dbg(
+						smb_fname));
+				TALLOC_FREE(temp_path);
+			} else {
+				TALLOC_FREE(q_dir);
+				q_dir = temp_path;
+			}
+		}
+		unbecome_root();
+
+		if (virusfilter_h->quarantine_keep_name) {
+			q_filepath = talloc_asprintf(talloc_tos(),
+					"%s/%s%s%s-XXXXXX", q_dir, q_prefix,
+					base_name, q_suffix);
+		} else {
+			q_filepath = talloc_asprintf(talloc_tos(),
+					"%s/%sXXXXXX", q_dir, q_prefix);
+		}
+
+		TALLOC_FREE(dir_name);
+		TALLOC_FREE(q_dir);
+		TALLOC_FREE(q_prefix);
+		TALLOC_FREE(q_suffix);
+
+		if (q_filepath == NULL) {
+			DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+				"memory\n", cwd_fname, fname);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		become_root();
+
+		q_fd = mkstemp(q_filepath);
+		if (q_fd == -1) {
+			unbecome_root();
+			DBG_ERR("Quarantine failed: %s/%s: Cannot open "
+				"destination: %s: %s\n", cwd_fname,
+				fname, q_filepath,
+				strerror(errno));
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+		close(q_fd);
+
+		q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+			smb_fname->stream_name, NULL, smb_fname->flags);
+		if (q_smb_fname == NULL) {
+			unlink(q_filepath);
+			unbecome_root();
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		if (virusfilter_vfs_next_move(vfs_h, smb_fname, q_smb_fname)
+		    == -1)
+		{
+			unbecome_root();
+			DBG_ERR("Quarantine failed: %s/%s: Rename failed: "
+				"%s\n", cwd_fname, fname,
+				strerror(errno));
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+		unbecome_root();
+
+		*filepath_newp = q_filepath;
+
+		return VIRUSFILTER_ACTION_QUARANTINE;
+
+	case VIRUSFILTER_ACTION_DELETE:
+		become_root();
+		if (SMB_VFS_NEXT_UNLINK(vfs_h, smb_fname) == -1) {
+			unbecome_root();
+			DBG_ERR("Delete failed: %s/%s: Unlink failed: %s\n",
+				cwd_fname, fname,
+				strerror(errno));
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+		unbecome_root();
+		return VIRUSFILTER_ACTION_DELETE;
+
+	case VIRUSFILTER_ACTION_DO_NOTHING:
+	default:
+		return VIRUSFILTER_ACTION_DO_NOTHING;
+	}
+}
+
+static virusfilter_action virusfilter_treat_infected_file(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const struct files_struct *fsp,
+	const char *report,
+	bool is_cache)
+{
+	connection_struct *conn = vfs_h->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;
+
+	action = virusfilter_do_infected_file_action(vfs_h, virusfilter_h, 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 (!virusfilter_h->infected_file_command) {
+		return action;
+	}
+
+	if (virusfilter_set_module_env(mem_ctx, &env_list) == -1) {
+		goto done;
+	}
+	if (virusfilter_env_set(mem_ctx, &env_list,
+	    "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
+	    fname) == -1)
+	{
+		goto done;
+	}
+	if (report && virusfilter_env_set(mem_ctx, &env_list,
+	    "VIRUSFILTER_INFECTED_FILE_REPORT", report) == -1)
+	{
+		goto done;
+	}
+	if (virusfilter_env_set(mem_ctx, &env_list,
+	    "VIRUSFILTER_INFECTED_FILE_ACTION", action_name) == -1)
+	{
+		goto done;
+	}
+	if (filepath_q && virusfilter_env_set(mem_ctx, &env_list,
+	    "VIRUSFILTER_QUARANTINED_FILE_PATH", filepath_q) == -1)
+	{
+		goto done;
+	}
+	if (is_cache && virusfilter_env_set(mem_ctx, &env_list,
+	    "VIRUSFILTER_RESULT_IS_CACHE", "yes") == -1)
+	{
+		goto done;
+	}
+
+	command = virusfilter_string_sub(mem_ctx, conn,
+					 virusfilter_h->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 *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const struct files_struct *fsp,
+	const char *report,
+	bool is_cache)
+{
+	connection_struct *conn = vfs_h->conn;
+	char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	char *fname = fsp->fsp_name->base_name;
+	TALLOC_CTX *mem_ctx = talloc_tos();
+	char *env_list = NULL;
+	char *command = NULL;
+	int command_result;
+
+	if (!virusfilter_h->scan_error_command) {
+		return;
+	}
+	if (virusfilter_set_module_env(mem_ctx, &env_list) == -1) {
+		goto done;
+	}
+	if (virusfilter_env_set(mem_ctx, &env_list,
+	    "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
+	    fname) == -1)
+	{
+		goto done;
+	}
+	if (report && virusfilter_env_set(mem_ctx, &env_list,
+	    "VIRUSFILTER_SCAN_ERROR_REPORT", report) == -1)
+	{
+		goto done;
+	}
+	if (is_cache && virusfilter_env_set(mem_ctx, &env_list,
+	    "VIRUSFILTER_RESULT_IS_CACHE", "1") == -1)
+	{
+		goto done;
+	}
+
+	command = virusfilter_string_sub(mem_ctx, conn,
+					 virusfilter_h->scan_error_command);
+	if (!command) {
+		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 *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const struct files_struct *fsp)
+{
+	virusfilter_result scan_result;
+	char *scan_report = NULL;
+	char *fname = fsp->fsp_name->base_name;
+	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;
+
+	if (virusfilter_h->cache_h) {
+		DBG_DEBUG("Searching cache entry: fname: %s\n", fname);
+		scan_cache_e = virusfilter_cache_get(virusfilter_h->cache_h,
+						     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");
+	}
+
+#ifdef virusfilter_module_scan_init
+	if (virusfilter_module_scan_init(virusfilter_h) !=
+	    VIRUSFILTER_RESULT_OK)
+	{
+		scan_result = VIRUSFILTER_RESULT_ERROR;
+		scan_report = talloc_asprintf(talloc_tos(),
+						"Initializing scanner failed");
+		goto virusfilter_scan_result_eval;
+	}
+#endif
+
+	scan_result = virusfilter_module_scan(vfs_h, virusfilter_h, fsp,
+					      &scan_report);
+
+#ifdef virusfilter_module_scan_end
+#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
+	if (virusfilter_h->scan_request_limit > 0) {
+		virusfilter_h->scan_request_count++;
+		if (virusfilter_h->scan_request_count >=
+		    virusfilter_h->scan_request_limit)
+		{
+			virusfilter_module_scan_end(virusfilter_h);
+			virusfilter_h->scan_request_count = 0;
+		}
+	}
+#else
+	virusfilter_module_scan_end(virusfilter_h);
+#endif
+#endif
+
+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(vfs_h,
+					virusfilter_h, fsp,
+					scan_report, is_cache);
+		if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+			add_scan_cache = false;
+		}
+		break;
+#ifdef VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE
+	case VIRUSFILTER_RESULT_SUSPECTED:
+		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(vfs_h,
+					virusfilter_h, fsp, scan_report,
+					is_cache);
+		if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+			add_scan_cache = false;
+		}
+		break;
+#endif
+	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(vfs_h, virusfilter_h, 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(vfs_h, virusfilter_h, fsp,
+					     scan_report, is_cache);
+		add_scan_cache = false;
+		break;
+	}
+
+	if (virusfilter_h->cache_h) {
+		if (!is_cache && add_scan_cache) {
+			DBG_DEBUG("Adding new cache entry: %s, %d\n", fname,
+				  scan_result);
+			if (!virusfilter_cache_entry_add(
+			    virusfilter_h->cache_h, cwd_fname, fname,
+			    scan_result, scan_report))
+			{
+				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 *vfs_h,
+	struct smb_filename *smb_fname,
+	files_struct *fsp,
+	int flags,
+	mode_t mode)
+{
+	TALLOC_CTX *mem_ctx = talloc_stackframe();
+	struct virusfilter_handle *virusfilter_h;
+	char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	virusfilter_result scan_result;
+	char *fname = fsp->fsp_name->base_name;
+	char *dir_name = NULL;
+	const char *base_name = NULL;
+	int scan_errno = 0;
+	int test_prefix;
+	int test_suffix;
+	int rename_trap_count = 0;
+
+	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
+				struct virusfilter_handle, return -1);
+
+	test_prefix = strlen(virusfilter_h->rename_prefix);
+	test_suffix = strlen(virusfilter_h->rename_suffix);
+	if (test_prefix) {
+		rename_trap_count++;
+	}
+	if (test_suffix) {
+		rename_trap_count++;
+	}
+
+	if (is_ntfs_stream_smb_fname(smb_fname) &&
+	    !is_ntfs_default_stream_smb_fname(smb_fname))
+	{
+		DBG_INFO("Not scanned: only file backed streams can be scanned:"
+			 " %s/%s\n", cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+
+	if (!virusfilter_h->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;
+	}
+
+	if (SMB_VFS_NEXT_STAT(vfs_h, smb_fname) != 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;
+	}
+	if (!S_ISREG(smb_fname->st.st_ex_mode)) {
+		DBG_INFO("Not scanned: Directory or special file: %s/%s\n",
+			 cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+	if (virusfilter_h->max_file_size > 0 && smb_fname->st.st_ex_size >
+	    virusfilter_h->max_file_size)
+	{
+		DBG_INFO("Not scanned: file size > max file size: %s/%s\n",
+			 cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+	if (virusfilter_h->min_file_size > 0 && smb_fname->st.st_ex_size <
+	    virusfilter_h->min_file_size)
+	{
+		DBG_INFO("Not scanned: file size < min file size: %s/%s\n",
+		      cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+
+	if (virusfilter_h->exclude_files && is_in_path(fname,
+	    virusfilter_h->exclude_files, false))
+	{
+		DBG_INFO("Not scanned: exclude files: %s/%s\n",
+			 cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+
+	if (virusfilter_h->infected_file_action ==
+	    VIRUSFILTER_ACTION_QUARANTINE)
+	{
+		if (strstr(fname, virusfilter_h->quarantine_dir) != NULL) {
+			scan_errno = virusfilter_h->infected_open_errno;
+			goto virusfilter_vfs_open_fail;
+		}
+	}
+
+	if (test_prefix || test_suffix) {
+		if (parent_dirname(mem_ctx, fname, &dir_name, &base_name))
+		{
+			if (test_prefix) {
+				if (strncmp(base_name,
+				    virusfilter_h->rename_prefix,
+				    test_prefix) != 0)
+				{
+					test_prefix = 0;
+				}
+			}
+			if (test_suffix) {
+				if (strcmp(base_name + (strlen(base_name) -
+				    test_suffix),
+				    virusfilter_h->rename_suffix) != 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 =
+					virusfilter_h->infected_open_errno;
+				goto virusfilter_vfs_open_fail;
+			}
+		}
+	}
+
+	scan_result = virusfilter_scan(vfs_h, virusfilter_h, fsp);
+
+	switch (scan_result) {
+	case VIRUSFILTER_RESULT_CLEAN:
+		break;
+	case VIRUSFILTER_RESULT_INFECTED:
+		scan_errno = virusfilter_h->infected_open_errno;
+		goto virusfilter_vfs_open_fail;
+	case VIRUSFILTER_RESULT_ERROR:
+		if (virusfilter_h->block_access_on_error) {
+			DBG_INFO("Block access\n");
+			scan_errno = virusfilter_h->scan_error_open_errno;
+			goto virusfilter_vfs_open_fail;
+		}
+		break;
+	default:
+		scan_errno = virusfilter_h->scan_error_open_errno;
+		goto virusfilter_vfs_open_fail;
+	}
+
+virusfilter_vfs_open_next:
+	TALLOC_FREE(mem_ctx);
+	return SMB_VFS_NEXT_OPEN(vfs_h, smb_fname, fsp, flags, mode);
+
+virusfilter_vfs_open_fail:
+	TALLOC_FREE(mem_ctx);
+	errno = (scan_errno != 0) ? scan_errno : EACCES;
+	return -1;
+}
+
+static int virusfilter_vfs_close(
+	struct vfs_handle_struct *vfs_h,
+	files_struct *fsp)
+{
+	TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+	/*
+         * 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 = vfs_h->conn->connectpath;
+
+	struct virusfilter_handle *virusfilter_h;
+	char *fname = fsp->fsp_name->base_name;
+	int close_result, close_errno;
+	virusfilter_result scan_result;
+	int scan_errno = 0;
+
+	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
+				struct virusfilter_handle, 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(vfs_h, fsp);
+	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(virusfilter_h->cache_h,
+						 cwd_fname, fname);
+		}
+		goto virusfilter_vfs_close_fail;
+	}
+
+	if (fsp->is_directory) {
+		DBG_INFO("Not scanned: Directory: %s/%s\n", cwd_fname,
+			 fname);
+		TALLOC_FREE(mem_ctx);
+		return close_result;
+	}
+
+	if (is_ntfs_stream_smb_fname(fsp->fsp_name) &&
+	    !is_ntfs_default_stream_smb_fname(fsp->fsp_name))
+	{
+		if (virusfilter_h->scan_on_open && fsp->modified) {
+			if (virusfilter_h->cache_h) {
+				DBG_DEBUG("Removing cache entry (if existent)"
+					  ": fname: %s\n", fname);
+				virusfilter_cache_remove(
+						virusfilter_h->cache_h,
+						cwd_fname, fname);
+			}
+		}
+		DBG_INFO("Not scanned: only file backed streams can be scanned:"
+			 " %s/%s\n", cwd_fname, fname);
+		TALLOC_FREE(mem_ctx);
+		return close_result;
+	}
+
+	if (!virusfilter_h->scan_on_close) {
+		if (virusfilter_h->scan_on_open && fsp->modified) {
+			if (virusfilter_h->cache_h) {
+				DBG_DEBUG("Removing cache entry (if existent)"
+					  ": fname: %s\n", fname);
+				virusfilter_cache_remove(
+						virusfilter_h->cache_h,
+						cwd_fname, fname);
+			}
+		}
+		DBG_INFO("Not scanned: scan on close is disabled: %s/%s\n",
+			 cwd_fname, fname);
+		TALLOC_FREE(mem_ctx);
+		return close_result;
+	}
+
+	if (!fsp->modified) {
+		DBG_NOTICE("Not scanned: File not modified: %s/%s\n",
+			   cwd_fname, fname);
+
+		TALLOC_FREE(mem_ctx);
+		return close_result;
+	}
+
+	if (virusfilter_h->exclude_files && is_in_path(fname,
+	    virusfilter_h->exclude_files, false))
+	{
+		DBG_INFO("Not scanned: exclude files: %s/%s\n",
+			 cwd_fname, fname);
+		TALLOC_FREE(mem_ctx);
+		return close_result;
+	}
+
+	scan_result = virusfilter_scan(vfs_h, virusfilter_h, fsp);
+
+	switch (scan_result) {
+	case VIRUSFILTER_RESULT_CLEAN:
+		break;
+	case VIRUSFILTER_RESULT_INFECTED:
+		scan_errno = virusfilter_h->infected_close_errno;
+		goto virusfilter_vfs_close_fail;
+	case VIRUSFILTER_RESULT_ERROR:
+		if (virusfilter_h->block_access_on_error) {
+			DBG_INFO("Block access\n");
+			scan_errno = virusfilter_h->scan_error_close_errno;
+			goto virusfilter_vfs_close_fail;
+		}
+		break;
+	default:
+		scan_errno = virusfilter_h->scan_error_close_errno;
+		goto virusfilter_vfs_close_fail;
+	}
+
+	TALLOC_FREE(mem_ctx);
+	errno = close_errno;
+
+	return close_result;
+
+virusfilter_vfs_close_fail:
+
+	TALLOC_FREE(mem_ctx);
+	errno = (scan_errno != 0) ? scan_errno : close_errno;
+
+	return close_result;
+}
+
+static int virusfilter_vfs_unlink(
+	struct vfs_handle_struct *vfs_h,
+	const struct smb_filename *smb_fname)
+{
+	int ret = SMB_VFS_NEXT_UNLINK(vfs_h, smb_fname);
+	struct virusfilter_handle *virusfilter_h;
+	char *fname = NULL;
+	char *cwd_fname = vfs_h->conn->cwd_fname->base_name;
+
+	if (ret != 0 && errno != ENOENT) {
+		return ret;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
+				struct virusfilter_handle, return -1);
+
+	if (virusfilter_h->cache_h) {
+		fname = smb_fname->base_name;
+		DBG_DEBUG("Removing cache entry (if existent): fname: %s\n",
+			  fname);
+		virusfilter_cache_remove(virusfilter_h->cache_h, cwd_fname,
+					 fname);
+	}
+
+	return ret;
+}
+
+static int virusfilter_vfs_rename(
+	struct vfs_handle_struct *vfs_h,
+	const struct smb_filename *smb_fname_src,
+	const struct smb_filename *smb_fname_dst)
+{
+	int ret = SMB_VFS_NEXT_RENAME(vfs_h, smb_fname_src, smb_fname_dst);
+	struct virusfilter_handle *virusfilter_h;
+	char *fname = NULL;
+	char *cwd_fname = vfs_h->conn->cwd_fname->base_name;
+
+	if (ret != 0) {
+		return ret;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
+				struct virusfilter_handle, return -1);
+
+	if (virusfilter_h->cache_h) {
+		fname = smb_fname_dst->base_name;
+		DBG_DEBUG("Removing cache entry (if existent): fname: %s\n",
+			  fname);
+		virusfilter_cache_remove(virusfilter_h->cache_h, cwd_fname,
+					 fname);
+
+		fname = smb_fname_src->base_name;
+		DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n", fname,
+			  smb_fname_dst->base_name);
+		virusfilter_cache_entry_rename(virusfilter_h->cache_h,
+					       cwd_fname, fname,
+					       smb_fname_dst->base_name);
+	}
+
+	return ret;
+}
+
+/* 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,
+};
+
+#define MAKE_FN_NAME(x) NTSTATUS vfs_virusfilter_ ## x ## _init(TALLOC_CTX *ctx)
+#define VFS_VIRUSFILTER_INIT(ENGINE) MAKE_FN_NAME(ENGINE)
+
+VFS_VIRUSFILTER_INIT(VIRUSFILTER_ENGINE);
+VFS_VIRUSFILTER_INIT(VIRUSFILTER_ENGINE)
+{
+	NTSTATUS ret;
+
+	ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+			       VIRUSFILTER_MODULE_NAME, &vfs_virusfilter_fns);
+	if (!NT_STATUS_IS_OK(ret)) {
+		return ret;
+	}
+
+	virusfilter_debug_level = debug_add_class(VIRUSFILTER_MODULE_NAME);
+	if (virusfilter_debug_level == -1) {
+		virusfilter_debug_level = DBGC_VFS;
+		DBG_ERR("Couldn't register custom debugging class!\n");
+	} else {
+		DBG_DEBUG("Debug class number of '%s': %d\n",
+			  VIRUSFILTER_MODULE_NAME, virusfilter_debug_level);
+	}
+
+	DBG_INFO("%s registered\n", VIRUSFILTER_MODULE_NAME);
+
+	return ret;
+}
-- 
2.13.6


From a3df0e556d46947a175b72c167ce75f052e045a2 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Thu, 21 Dec 2017 16:43:30 +0100
Subject: [PATCH 3/9] FIXUP: remove include insanity, rework into proper
 frontend and backend

---
 docs-xml/manpages/vfs_virusfilter.8.xml            |  316 ++++++
 docs-xml/wscript_build                             |    1 +
 .../{vfs_virusfilter_vfs.c => vfs_virusfilter.c}   | 1037 +++++++++-----------
 source3/modules/vfs_virusfilter_common.h           |   89 +-
 source3/modules/vfs_virusfilter_utils.c            |   58 +-
 source3/modules/vfs_virusfilter_utils.h            |   14 +-
 source3/modules/wscript_build                      |   13 +
 source3/wscript                                    |    2 +-
 8 files changed, 911 insertions(+), 619 deletions(-)
 create mode 100644 docs-xml/manpages/vfs_virusfilter.8.xml
 rename source3/modules/{vfs_virusfilter_vfs.c => vfs_virusfilter.c} (50%)

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
new file mode 100644
index 00000000000..392c8f8028b
--- /dev/null
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//Samba-Team//DTD DocBook V4.2-Based Variant V1.0//EN" "http://www.samba.org/samba/DTD/samba-doc">
+<refentry id="vfs_virusfilter.8">
+
+<refmeta>
+	<refentrytitle>vfs_virusfilter</refentrytitle>
+	<manvolnum>8</manvolnum>
+	<refmiscinfo class="source">Samba</refmiscinfo>
+	<refmiscinfo class="manual">System Administration tools</refmiscinfo>
+	<refmiscinfo class="version">4.8</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+	<refname>vfs_virusfilter</refname>
+	<refpurpose>On access virus scanner</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+	<cmdsynopsis>
+		<command>vfs objects = virusfilter</command>
+	</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+	<title>DESCRIPTION</title>
+
+	<para>This is a set of various Samba VFS modules to scan and filter
+	virus files on Samba file services with an anti-virus scanner.</para>
+
+	<para>This module is stackable.</para>
+
+</refsect1>
+
+<refsect1>
+	<title>OPTIONS</title>
+
+	<variablelist>
+
+		<varlistentry>
+		<term>virusfilter:scanner</term>
+		<listitem>
+		<para>The antivirus scan-engine.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:socket path = PATH</term>
+		<listitem>
+		<para>Path of local socket for the virus scanner.
+		</para>
+		<para>If this option is not set, the default path depends on the
+		configured AV scanning engine.
+		</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:connect timeout = 30000</term>
+		<listitem>
+		<para>Controls how long to wait on connecting to the virus
+		scanning process before timing out. Value is in milliseconds.
+		</para>
+		<para>If this option is not set, the default is 30000.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:io timeout = 60000</term>
+		<listitem>
+		<para>Controls how long to wait on communications with the virus
+		scanning process before timing out. Value is in milliseconds.
+		</para>
+		<para>If this option is not set, the default is 60000.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:scan on open = yes</term>
+		<listitem>
+		<para>This option controls whether files are scanned on open.
+		</para>
+		<para>If this option is not set, the default is yes.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:scan on close = no</term>
+		<listitem>
+		<para>This option controls whether files are scanned on open.
+		</para>
+		<para>If this option is not set, the default is no.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:max file size = 100000000</term>
+		<listitem>
+		<para>This is the largest sized file, in bytes, which will be scanned.
+		</para>
+		<para>If this option is not set, the default is 100MB.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:min file size = 10</term>
+		<listitem>
+		<para>This is the largest sized file, in bytes, which will be scanned.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:infected file action = nothing</term>
+		<listitem>
+		<para>What to do with an infected file. The options are
+		nothing, quarantine, rename, delete.</para>
+		<para>If this option is not set, the default is nothing.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:infected file errno on close = 0</term>
+		<listitem>
+		<para>What errno to return on close if the file is infected.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:infected file errno on open = EACCES</term>
+		<listitem>
+		<para>What errno to return on close if the file is infected.
+		</para>
+		<para>If this option is not set, the default is EACCES.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:quarantine directory  = PATH</term>
+		<listitem>
+		<para>Where to move infected files. This is relative to, and
+		must be found in, the directory in which the file is found.</para>
+		<para>If this option is not set, the default is ".quarantine".
+		</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:quarantine prefix = virusfilter.</term>
+		<listitem>
+		<para>Prefix for quarantined files.</para>
+		<para>If this option is not set, the default is "virusfilter.".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:quarantine suffix = .infected</term>
+		<listitem>
+		<para>Suffix for quarantined files.
+		This option is only used if keep name is true. Otherwise it is ignored.</para>
+		<para>If this option is not set, the default is ".infected".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:rename prefix = virusfilter.</term>
+		<listitem>
+		<para>Prefix for infected files.</para>
+		<para>If this option is not set, the default is "virusfilter.".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:rename suffix = .infected</term>
+		<listitem>
+		<para>Suffix for infected files.</para>
+		<para>If this option is not set, the default is ".infected".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:quarantine keep name = yes</term>
+		<listitem>
+		<para>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.</para>
+		<para>If this option is not set, the default is no.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:infected file command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --cc "%U at example.com" --from samba at example.com --subject-prefix "Samba: Infected File: "</term>
+		<listitem>
+		<para>External command to run on an infected file is found.</para>
+		<para>If this option is not set, the default is none.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:scan archive = true</term>
+		<listitem>
+		<para>This defines whether or not to scan archives.</para>
+		<para>Sophos supports this and defaults to false.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:max nested scan archive = 1</term>
+		<listitem>
+		<para>This defines the maximum depth to search nested archives.</para>
+		<para>The Sophos module supports this and defaults to 1.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --from samba at example.com --subject-prefix "Samba: Scan Error: "</term>
+		<listitem>
+		<para>External command to run on scan error.</para>
+		<para>If this option is not set, the default is none.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:exclude files = empty</term>
+		<listitem>
+		<para>Files to exclude from scanning.</para>
+		<para>If this option is not set, the default is empty.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:block access on error = false</term>
+		<listitem>
+		<para>Controls whether or not access should be blocked on
+		a scanning error.</para>
+		<para>If this option is not set, the default is false.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:scan error errno on close = 0</term>
+		<listitem>
+		<para>What errno to return on close if there is an error in
+		scanning the file and block access on error is true.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:scan error errno on open = EACCES</term>
+		<listitem>
+		<para>What errno to return on open if there is an error in
+		scanning the file and block access on error is true.
+		</para>
+		<para>If this option is not set, the default is EACCES.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:cache entry limit = 100</term>
+		<listitem>
+		<para>The maximum number of entries in the scanning results
+		cache. Due to how Samba's memcache works, this is approximate.</para>
+		<para>If this option is not set, the default is 100.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:cache time limit = 10</term>
+		<listitem>
+		<para>The maximum number of seconds that a scanning result
+		will stay in the results cache. -1 disables the limit.
+		0 disables caching.</para>
+		<para>If this option is not set, the default is 10.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:quarantine directory mode = 700</term>
+		<listitem>
+		<para>This is the octet mode for the quarantine directory and
+		its subdirectories as they are created.</para>
+		<para>If this option is not set, the default is 700 or S_IRUSR | S_IWUSR | S_IXUSR.</para>
+		</listitem>
+		</varlistentry>
+
+	</variablelist>
+</refsect1>
+
+<refsect1>
+	<title>NOTES</title>
+
+	<para>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.</para>
+
+	<para>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).</para>
+</refsect1>
+
+<refsect1>
+	<title>AUTHOR</title>
+
+	<para>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.</para>
+
+</refsect1>
+
+</refentry>
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/source3/modules/vfs_virusfilter_vfs.c b/source3/modules/vfs_virusfilter.c
similarity index 50%
rename from source3/modules/vfs_virusfilter_vfs.c
rename to source3/modules/vfs_virusfilter.c
index 16ca70d8381..3d463a4502e 100644
--- a/source3/modules/vfs_virusfilter_vfs.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -1,34 +1,7 @@
 /*
- * Samba-VirusFilter VFS template
- * #included into modules/vfs_virusfilter_clamav.c,
- * modules/vfs_virusfilter_fsav.c and modules/vfs_virusfilter_sophos.c.
- *
- * The defines virusfilter_module_connect, virusfilter_module_scan_init,
- * virusfilter_module_scan_end, virusfilter_module_scan must be defined, before
- * including this file, as functions which implements those operations. The
- * function names are normally the same as the define with the "module" part
- * replaced with the module name. virusfilter_module_destruct_config may
- * optionally be defined.
- *
- * The following must be defined before the include in every module:
- * VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT, VIRUSFILTER_DEFAULT_SOCKET_PATH,
- * VIRUSFILTER_DEFAULT_TIMEOUT, VIRUSFILTER_ENGINE (non-string module name),
- * VIRUSFILTER_MODULE_ENGINE (string module name). See existing modules for
- * examples.
- *
- * The following must be defined before the include if used in the module:
- * VIRUSFILTER_DEFAULT_SCAN_ARCHIVE, VIRUSFILTER_DEFAULT_SCAN_MIME,
- * VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE,
- * VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT,
- * VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE.
- *
- * Optionally, VIRUSFILTER_MODULE_CONFIG_MEMBERS may be defined
- * (see modules/vfs_virusfilter_fsav.c for example). The configuration should
- * be done in virusfilter_MODULENAME_connect inside of the virus scanning
- * engine specific module.
- *
  * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
  * Copyright (C) 2016 Trever L. Adams
+ * Copyright (C) 2017 Ralph Boehme <slow at samba.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -44,49 +17,34 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "modules/vfs_virusfilter_common.h"
-#include "modules/vfs_virusfilter_utils.h"
-
-#define VIRUSFILTER_MODULE_NAME "virusfilter_" VIRUSFILTER_MODULE_ENGINE
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
 
 /*
  * Default configuration values
  * ======================================================================
  */
 
-#define VIRUSFILTER_DEFAULT_SCAN_ON_OPEN		true
-#define VIRUSFILTER_DEFAULT_SCAN_ON_CLOSE		false
-#define VIRUSFILTER_DEFAULT_MAX_FILE_SIZE		100000000L /* 100MB */
-#define VIRUSFILTER_DEFAULT_MIN_FILE_SIZE		0
-#define VIRUSFILTER_DEFAULT_EXCLUDE_FILES		NULL
-
-#define VIRUSFILTER_DEFAULT_CACHE_ENTRY_LIMIT		100
-#define VIRUSFILTER_DEFAULT_CACHE_TIME_LIMIT		10
-
-#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ACTION	\
-	VIRUSFILTER_ACTION_DO_NOTHING
-#define VIRUSFILTER_DEFAULT_INFECTED_FILE_COMMAND	NULL
-#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_OPEN	EACCES
-#define VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_CLOSE 0
-
-#define VIRUSFILTER_DEFAULT_SCAN_ERROR_COMMAND		NULL
-#define VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_OPEN	EACCES
-#define VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_CLOSE	0
-#define VIRUSFILTER_DEFAULT_BLOCK_ACCESS_ON_ERROR	false
-
 #define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX		"virusfilter."
 #define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX		".infected"
-#define VIRUSFILTER_DEFAULT_QUARANTINE_KEEP_NAME	false
-
-#define VIRUSFILTER_DEFAULT_QUARANTINE_DIR		".quarantine"
-/* 700 = S_IRUSR | S_IWUSR | S_IXUSR */
-#define VIRUSFILTER_DEFAULT_QUARANTINE_DIR_MODE		"700"
-
 #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" },
@@ -101,122 +59,10 @@ static const struct enum_list virusfilter_actions[] = {
 	{ -1,					NULL}
 };
 
-struct virusfilter_handle {
-#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
-	int				scan_request_count;
-	int				scan_request_limit;
-#endif
-
-	/* Scan on file operations */
-	bool				scan_on_open;
-	bool				scan_on_close;
-
-	/* Special scan options */
-#ifdef VIRUSFILTER_DEFAULT_SCAN_ARCHIVE
-	bool				scan_archive;
-#endif
-#ifdef VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE
-	int				max_nested_scan_archive;
-#endif
-#ifdef VIRUSFILTER_DEFAULT_SCAN_MIME
-	bool				scan_mime;
-#endif
-#ifdef VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE
-	bool				block_suspected_file;
-#endif
-
-	/* 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_handle *cache_h;
-	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_name;
-	mode_t				quarantine_dir_mode;
-
-	/* Rename infected files */
-	const char *			rename_prefix;
-	const char *			rename_suffix;
-
-	/* Network options */
-#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
-	const char *			socket_path;
-	struct virusfilter_io_handle	*io_h;
-#endif
-
-	/* Module specific configuration options */
-#ifdef VIRUSFILTER_MODULE_CONFIG_MEMBERS
-	VIRUSFILTER_MODULE_CONFIG_MEMBERS
-#endif
-};
-
-/* ====================================================================== */
-
-#ifdef virusfilter_module_connect
-static int virusfilter_module_connect(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
-	const char *svc,
-	const char *user);
-#endif
-
-#ifdef virusfilter_module_disconnect
-static int virusfilter_module_disconnect(struct vfs_handle_struct *vfs_h);
-#endif
-
-#ifdef virusfilter_module_destruct_config
-static int virusfilter_module_destruct_config(
-	struct virusfilter_handle *virusfilter_h);
-#endif
-
-#ifdef virusfilter_module_scan_init
-static virusfilter_result virusfilter_module_scan_init(
-	struct virusfilter_handle *virusfilter_h);
-#endif
-
-#ifdef virusfilter_module_scan_end
-static void virusfilter_module_scan_end(
-	struct virusfilter_handle *virusfilter_h);
-#endif
-
-static virusfilter_result virusfilter_module_scan(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
-	const struct files_struct *fsp,
-	char **reportp);
-
-/* ====================================================================== */
-
-static int virusfilter_destruct_config(
-	struct virusfilter_handle *virusfilter_h)
+static int virusfilter_config_destructor(struct virusfilter_config *config)
 {
-#ifdef virusfilter_module_destruct_config
-	return virusfilter_module_destruct_config(virusfilter_h);
-#else
+	TALLOC_FREE(config->backend);
 	return 0;
-#endif
 }
 
 /*
@@ -250,7 +96,7 @@ static bool quarantine_directory_exist(
  */
 static bool quarantine_create_dir(
 	struct vfs_handle_struct *handle,
-	struct virusfilter_handle *virusfilter_h,
+	struct virusfilter_config *config,
 	const char *dname)
 {
 	size_t len;
@@ -262,7 +108,7 @@ static bool quarantine_create_dir(
 	bool ret = false;
 	char *saveptr;
 
-	mode = virusfilter_h->quarantine_dir_mode;
+	mode = config->quarantine_dir_mode;
 
 	tmp_str = talloc_strdup(talloc_tos(), dname);
 	if (tmp_str == NULL) {
@@ -281,15 +127,15 @@ static bool quarantine_create_dir(
 	}
 	*new_dir = '\0';
 	if (dname[0] == '/') {
-
-	/* Absolute path. */
+		/* Absolute path. */
 		if (strlcat(new_dir,"/",len+1) >= len+1) {
 			goto done;
 		}
 	}
 
 	/* Create directory tree if neccessary */
-	for (token = strtok_r(tok_str, "/", &saveptr); token;
+	for (token = strtok_r(tok_str, "/", &saveptr);
+	     token != NULL;
 	     token = strtok_r(NULL, "/", &saveptr))
 	{
 		if (strlcat(new_dir, token, len+1) >= len+1) {
@@ -323,310 +169,327 @@ static bool quarantine_create_dir(
 		if (strlcat(new_dir, "/", len+1) >= len+1) {
 			goto done;
 		}
-		mode = virusfilter_h->quarantine_dir_mode;
+		mode = config->quarantine_dir_mode;
 	}
 
 	ret = true;
-	done:
-		TALLOC_FREE(tmp_str);
-		TALLOC_FREE(new_dir);
-		return ret;
+done:
+	TALLOC_FREE(tmp_str);
+	TALLOC_FREE(new_dir);
+	return ret;
 }
 
 static int virusfilter_vfs_connect(
-	struct vfs_handle_struct *vfs_h,
+	struct vfs_handle_struct *handle,
 	const char *svc,
 	const char *user)
 {
-	int snum = SNUM(vfs_h->conn);
-	struct virusfilter_handle *virusfilter_h;
+	int snum = SNUM(handle->conn);
+	struct virusfilter_config *config;
 	const char *exclude_files;
 	const char *temp_quarantine_dir_mode = NULL;
-#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
-	int connect_timeout, io_timeout;
-#endif
-
+	int backend;
+	int connect_timeout;
+	int io_timeout;
+	int ret;
 
-	virusfilter_h = talloc_zero(vfs_h, struct virusfilter_handle);
-	if (!virusfilter_h) {
+	config = talloc_zero(handle, struct virusfilter_config);
+	if (!config) {
 		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", false);
+
+	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", 0);
+
+	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", 0);
+
+	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);
 
-	talloc_set_destructor(virusfilter_h, virusfilter_destruct_config);
-
-	SMB_VFS_HANDLE_SET_DATA(vfs_h, virusfilter_h, NULL,
-				struct virusfilter_handle, return -1);
-
-#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
-	virusfilter_h->scan_request_limit = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "scan request limit",
-		VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT);
-#endif
-
-	virusfilter_h->scan_on_open = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "scan on open",
-		VIRUSFILTER_DEFAULT_SCAN_ON_OPEN);
-	virusfilter_h->scan_on_close = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "scan on close",
-		VIRUSFILTER_DEFAULT_SCAN_ON_CLOSE);
-#ifdef VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE
-	virusfilter_h->max_nested_scan_archive = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "max nested scan archive",
-		VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE);
-#endif
-#ifdef VIRUSFILTER_DEFAULT_SCAN_ARCHIVE
-	virusfilter_h->scan_archive = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "scan archive",
-		VIRUSFILTER_DEFAULT_SCAN_ARCHIVE);
-#endif
-#ifdef VIRUSFILTER_DEFAULT_MIME_SCAN
-	virusfilter_h->scan_mime = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "scan mime",
-		VIRUSFILTER_DEFAULT_SCAN_MIME);
-#endif
-
-	virusfilter_h->max_file_size = (ssize_t)lp_parm_ulong(snum,
-		VIRUSFILTER_MODULE_NAME, "max file size",
-		VIRUSFILTER_DEFAULT_MAX_FILE_SIZE);
-	virusfilter_h->min_file_size = (ssize_t)lp_parm_ulong(snum,
-		VIRUSFILTER_MODULE_NAME, "min file size",
-		VIRUSFILTER_DEFAULT_MIN_FILE_SIZE);
-
-	exclude_files = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "exclude files",
-		VIRUSFILTER_DEFAULT_EXCLUDE_FILES);
-	if (exclude_files) {
-		set_namearray(&virusfilter_h->exclude_files, exclude_files);
-	}
-
-	virusfilter_h->cache_entry_limit = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "cache entry limit",
-		VIRUSFILTER_DEFAULT_CACHE_ENTRY_LIMIT);
-	virusfilter_h->cache_time_limit = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "cache time limit",
-		VIRUSFILTER_DEFAULT_CACHE_TIME_LIMIT);
-
-	virusfilter_h->infected_file_action = lp_parm_enum(snum,
-		VIRUSFILTER_MODULE_NAME, "infected file action",
-		virusfilter_actions, VIRUSFILTER_DEFAULT_INFECTED_FILE_ACTION);
-	virusfilter_h->infected_file_command = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "infected file command",
-		VIRUSFILTER_DEFAULT_INFECTED_FILE_COMMAND);
-	virusfilter_h->scan_error_command = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "scan error command",
-		VIRUSFILTER_DEFAULT_SCAN_ERROR_COMMAND);
-	virusfilter_h->block_access_on_error = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "block access on error",
-		VIRUSFILTER_DEFAULT_BLOCK_ACCESS_ON_ERROR);
-
-	virusfilter_h->quarantine_dir = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "quarantine directory",
-		VIRUSFILTER_DEFAULT_QUARANTINE_DIR);
-
-	temp_quarantine_dir_mode = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "quarantine directory mode",
-		VIRUSFILTER_DEFAULT_QUARANTINE_DIR_MODE);
+	config->block_access_on_error = lp_parm_bool(
+		snum, "virusfilter", "block access on error", false);
+
+	config->quarantine_dir = lp_parm_const_string(
+		snum, "virusfilter", "quarantine directory", ".quarantine");
+
+	temp_quarantine_dir_mode = lp_parm_const_string(
+		snum, "virusfilter", "quarantine directory mode", "700");
 	if (temp_quarantine_dir_mode != NULL) {
 		sscanf(temp_quarantine_dir_mode, "%o",
-		       &virusfilter_h->quarantine_dir_mode);
+		       &config->quarantine_dir_mode);
 	}
-	virusfilter_h->quarantine_prefix = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "quarantine prefix",
+
+	config->quarantine_prefix = lp_parm_const_string(
+		snum, "virusfilter", "quarantine prefix",
 		VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
-	virusfilter_h->quarantine_suffix = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "quarantine suffix",
+
+	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
 	 */
-	if (strstr(virusfilter_h->quarantine_prefix, "/")) {
+	if (strstr(config->quarantine_prefix, "/")) {
 		DBG_ERR("quarantine prefix must not contain directory "
 			"delimiter(s) such as '/' (%s replaced with %s)\n",
-			virusfilter_h->quarantine_prefix,
+			config->quarantine_prefix,
 			VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
-		virusfilter_h->quarantine_prefix =
+		config->quarantine_prefix =
 			VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX;
 	}
-	if (strstr(virusfilter_h->quarantine_suffix, "/")) {
+	if (strstr(config->quarantine_suffix, "/")) {
 		DBG_ERR("quarantine suffix must not contain directory "
 			"delimiter(s) such as '/' (%s replaced with %s)\n",
-			virusfilter_h->quarantine_suffix,
+			config->quarantine_suffix,
 			VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
-		virusfilter_h->quarantine_suffix =
+		config->quarantine_suffix =
 			VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX;
 	}
 
-	virusfilter_h->quarantine_keep_name = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "quarantine keep name",
-		VIRUSFILTER_DEFAULT_QUARANTINE_KEEP_NAME);
+	config->quarantine_keep_name = lp_parm_bool(
+		snum, "virusfilter", "quarantine keep name", false);
 
-	virusfilter_h->rename_prefix = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "rename prefix",
+	config->rename_prefix = lp_parm_const_string(
+		snum, "virusfilter", "rename prefix",
 		VIRUSFILTER_DEFAULT_RENAME_PREFIX);
-	virusfilter_h->rename_suffix = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "rename suffix",
+
+	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
 	 */
-	if (strstr(virusfilter_h->rename_prefix, "/")) {
+	if (strstr(config->rename_prefix, "/")) {
 		DBG_ERR("rename prefix must not contain directory "
 			"delimiter(s) such as '/' (%s replaced with %s)\n",
-			virusfilter_h->rename_prefix,
+			config->rename_prefix,
 			VIRUSFILTER_DEFAULT_RENAME_PREFIX);
-		virusfilter_h->rename_prefix =
+		config->rename_prefix =
 			VIRUSFILTER_DEFAULT_RENAME_PREFIX;
 	}
-	if (strstr(virusfilter_h->rename_suffix, "/")) {
+	if (strstr(config->rename_suffix, "/")) {
 		DBG_ERR("rename suffix must not contain directory "
 			"delimiter(s) such as '/' (%s replaced with %s)\n",
-			virusfilter_h->rename_suffix,
+			config->rename_suffix,
 			VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
-		virusfilter_h->rename_suffix =
+		config->rename_suffix =
 			VIRUSFILTER_DEFAULT_RENAME_SUFFIX;
 	}
 
-	virusfilter_h->infected_open_errno = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "infected file errno on open",
-		VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_OPEN);
-	virusfilter_h->infected_close_errno = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "infected file errno on close",
-		VIRUSFILTER_DEFAULT_INFECTED_FILE_ERRNO_ON_CLOSE);
-	virusfilter_h->scan_error_open_errno = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "scan error errno on open",
-		VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_OPEN);
-	virusfilter_h->scan_error_close_errno = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "scan error errno on close",
-		VIRUSFILTER_DEFAULT_SCAN_ERROR_ERRNO_ON_CLOSE);
-
-#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
-	virusfilter_h->socket_path = lp_parm_const_string(snum,
-		VIRUSFILTER_MODULE_NAME, "socket path",
-		VIRUSFILTER_DEFAULT_SOCKET_PATH);
+	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(virusfilter_h->socket_path[0] != '/') {
+	if (config->socket_path[0] != '/') {
 		DBG_ERR("socket path must be an absolute path. "
-			"Replacing %s with %s\n", virusfilter_h->socket_path,
-			VIRUSFILTER_DEFAULT_SOCKET_PATH);
-		virusfilter_h->socket_path =
-			VIRUSFILTER_DEFAULT_SOCKET_PATH;
+			"Using backend default\n");
+		config->socket_path = NULL;
         }
-	virusfilter_h->socket_path =
-		canonicalize_absolute_path(vfs_h,
-					   virusfilter_h->socket_path);
+	if (config->socket_path != NULL) {
+		canonicalize_absolute_path(handle,
+					   config->socket_path);
+	}
 
-	connect_timeout = lp_parm_int(snum, VIRUSFILTER_MODULE_NAME,
-		"connect timeout", VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT);
-	io_timeout = lp_parm_int(snum, VIRUSFILTER_MODULE_NAME, "io timeout",
-		VIRUSFILTER_DEFAULT_TIMEOUT);
+	connect_timeout = lp_parm_int(snum, "virusfilter",
+				      "connect timeout", 30000);
 
-	virusfilter_h->io_h =
-		virusfilter_io_new(virusfilter_h, connect_timeout, io_timeout);
+	io_timeout = lp_parm_int(snum, "virusfilter", "io timeout", 60000);
 
-	if (!virusfilter_h->io_h) {
+	config->io_h = virusfilter_io_new(config, connect_timeout, io_timeout);
+	if (config->io_h == NULL) {
 		DBG_ERR("virusfilter_io_new failed");
 		return -1;
 	}
-#endif
 
-	if (virusfilter_h->cache_entry_limit > 0) {
-		virusfilter_h->cache_h = virusfilter_cache_new(vfs_h,
-					virusfilter_h->cache_entry_limit,
-					virusfilter_h->cache_time_limit);
-		if (!virusfilter_h->cache_h) {
+	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;
 		}
 	}
 
-#ifdef virusfilter_module_connect
-	if (virusfilter_module_connect(vfs_h, virusfilter_h, svc, user) == -1) {
-		return -1;
-	}
-#endif
+	config->socket_path = lp_parm_const_string(
+		snum, "virusfilter", "socket path", NULL);
 
 	/*
 	 * Check quarantine directory now to save processing
 	 * and becoming root over and over.
 	 */
-	if (virusfilter_h->infected_file_action ==
-	    VIRUSFILTER_ACTION_QUARANTINE)
-	{
+	if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+		bool dir_exists;
 
 		/*
-		 * Do SMB_VFS_NEXT_MKDIR(virusfilter_h->quarantine_dir)
+		 * Do SMB_VFS_NEXT_MKDIR(config->quarantine_dir)
 		 * hierarchy
 		 */
 		become_root();
-		if (!quarantine_directory_exist(vfs_h,
-		    virusfilter_h->quarantine_dir))
-		{
+		dir_exists = quarantine_directory_exist(
+			handle, config->quarantine_dir);
+		unbecome_root();
+
+		if (!dir_exists) {
 			DBG_DEBUG("Creating quarantine directory: %s\n",
-				  virusfilter_h->quarantine_dir);
-			quarantine_create_dir(vfs_h, virusfilter_h,
-					      virusfilter_h->quarantine_dir);
+				  config->quarantine_dir);
+
+			become_root();
+			quarantine_create_dir(handle, config,
+					      config->quarantine_dir);
+			unbecome_root();
 		}
-		unbecome_root();
 	}
 
-	return SMB_VFS_NEXT_CONNECT(vfs_h, svc, user);
+	/*
+	 * Now that the frontend options are initialized, load the configured
+	 * backend.
+	 */
+
+	backend = lp_parm_enum(snum, "virusfilter", "scanner", scanner_list, -1);
+	if (backend == -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 *vfs_h)
+static void virusfilter_vfs_disconnect(struct vfs_handle_struct *handle)
 {
-	struct virusfilter_handle *virusfilter_h;
+	struct virusfilter_config *config = NULL;
 
-#ifdef virusfilter_module_disconnect
-	virusfilter_module_disconnect(vfs_h);
-#endif
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return);
 
-	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
-				struct virusfilter_handle, return);
+	if (config->backend->fns->disconnect != NULL) {
+		config->backend->fns->disconnect(handle);
+	}
 
-	free_namearray(virusfilter_h->exclude_files);
-#ifdef VIRUSFILTER_DEFAULT_SOCKET_PATH
-	virusfilter_io_disconnect(virusfilter_h->io_h);
-#endif
+	free_namearray(config->exclude_files);
+	virusfilter_io_disconnect(config->io_h);
 
-	SMB_VFS_NEXT_DISCONNECT(vfs_h);
+	SMB_VFS_NEXT_DISCONNECT(handle);
 }
 
-static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx, char **env_list)
+static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx,
+				      struct virusfilter_config *config,
+				      char **env_list)
 {
-	if (virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
-	    VIRUSFILTER_VERSION) == -1)
-	{
+	int ret;
+
+	ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
+				  VIRUSFILTER_VERSION);
+	if (ret == -1) {
 		return -1;
 	}
-	if (virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
-	    VIRUSFILTER_MODULE_NAME) == -1)
-	{
+	ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
+				  config->backend->name);
+	if (ret == -1) {
 		return -1;
 	}
-#ifdef VIRUSFILTER_MODULE_VERSION
-	if (virusfilter_env_set(mem_ctx, env_list,
-	    "VIRUSFILTER_MODULE_VERSION", VIRUSFILTER_MODULE_VERSION) == -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;
+		}
 	}
-#endif
 
 	return 0;
 }
 
 static virusfilter_action virusfilter_do_infected_file_action(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
 	const struct files_struct *fsp,
 	const char **filepath_newp)
 {
 	TALLOC_CTX *mem_ctx = talloc_tos();
-	connection_struct *conn = vfs_h->conn;
+	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;
@@ -642,12 +505,12 @@ static virusfilter_action virusfilter_do_infected_file_action(
 
 	*filepath_newp = NULL;
 
-	switch (virusfilter_h->infected_file_action) {
+	switch (config->infected_file_action) {
 	case VIRUSFILTER_ACTION_RENAME:
 		q_prefix = virusfilter_string_sub(mem_ctx, conn,
-						virusfilter_h->rename_prefix);
+						config->rename_prefix);
 		q_suffix = virusfilter_string_sub(mem_ctx, conn,
-						virusfilter_h->rename_suffix);
+						config->rename_suffix);
 		if (q_prefix == NULL || q_suffix == NULL) {
 			DBG_ERR("Rename failed: %s/%s: Cannot allocate "
 				"memory\n", cwd_fname, fname);
@@ -692,7 +555,7 @@ static virusfilter_action virusfilter_do_infected_file_action(
 			return VIRUSFILTER_ACTION_DO_NOTHING;
 		}
 
-		if (virusfilter_vfs_next_move(vfs_h, smb_fname, q_smb_fname)
+		if (virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname)
 		    == -1)
 		{
 			unbecome_root();
@@ -709,11 +572,11 @@ static virusfilter_action virusfilter_do_infected_file_action(
 
 	case VIRUSFILTER_ACTION_QUARANTINE:
 		q_dir = virusfilter_string_sub(mem_ctx, conn,
-					virusfilter_h->quarantine_dir);
+					config->quarantine_dir);
 		q_prefix = virusfilter_string_sub(mem_ctx, conn,
-					virusfilter_h->quarantine_prefix);
+					config->quarantine_prefix);
 		q_suffix = virusfilter_string_sub(mem_ctx, conn,
-					virusfilter_h->quarantine_suffix);
+					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);
@@ -749,7 +612,7 @@ static virusfilter_action virusfilter_do_infected_file_action(
 		}
 
 		become_root();
-		if (quarantine_directory_exist(vfs_h,
+		if (quarantine_directory_exist(handle,
 		    temp_path))
 		{
 			DBG_DEBUG("quarantine: Directory already "
@@ -759,8 +622,8 @@ static virusfilter_action virusfilter_do_infected_file_action(
 		} else {
 			DBG_DEBUG("quarantine: Creating "
 			      "directory %s\n", temp_path);
-			if (quarantine_create_dir(vfs_h,
-			    virusfilter_h, temp_path) == false)
+			if (quarantine_create_dir(handle,
+			    config, temp_path) == false)
 			{
 				DBG_NOTICE("quarantine: Could not "
 					"create directory ignoring"
@@ -775,7 +638,7 @@ static virusfilter_action virusfilter_do_infected_file_action(
 		}
 		unbecome_root();
 
-		if (virusfilter_h->quarantine_keep_name) {
+		if (config->quarantine_keep_name) {
 			q_filepath = talloc_asprintf(talloc_tos(),
 					"%s/%s%s%s-XXXXXX", q_dir, q_prefix,
 					base_name, q_suffix);
@@ -816,7 +679,7 @@ static virusfilter_action virusfilter_do_infected_file_action(
 			return VIRUSFILTER_ACTION_DO_NOTHING;
 		}
 
-		if (virusfilter_vfs_next_move(vfs_h, smb_fname, q_smb_fname)
+		if (virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname)
 		    == -1)
 		{
 			unbecome_root();
@@ -833,7 +696,7 @@ static virusfilter_action virusfilter_do_infected_file_action(
 
 	case VIRUSFILTER_ACTION_DELETE:
 		become_root();
-		if (SMB_VFS_NEXT_UNLINK(vfs_h, smb_fname) == -1) {
+		if (SMB_VFS_NEXT_UNLINK(handle, smb_fname) == -1) {
 			unbecome_root();
 			DBG_ERR("Delete failed: %s/%s: Unlink failed: %s\n",
 				cwd_fname, fname,
@@ -850,13 +713,13 @@ static virusfilter_action virusfilter_do_infected_file_action(
 }
 
 static virusfilter_action virusfilter_treat_infected_file(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
 	const struct files_struct *fsp,
 	const char *report,
 	bool is_cache)
 {
-	connection_struct *conn = vfs_h->conn;
+	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();
@@ -867,8 +730,9 @@ static virusfilter_action virusfilter_treat_infected_file(
 	char *env_list = NULL;
 	char *command = NULL;
 	int command_result;
+	int ret;
 
-	action = virusfilter_do_infected_file_action(vfs_h, virusfilter_h, fsp,
+	action = virusfilter_do_infected_file_action(handle, config, fsp,
 						     &filepath_q);
 	for (i=0; virusfilter_actions[i].name; i++) {
 		if (virusfilter_actions[i].value == action) {
@@ -879,42 +743,52 @@ static virusfilter_action virusfilter_treat_infected_file(
 	DBG_WARNING("Infected file action: %s/%s: %s\n", cwd_fname,
 		    fname, action_name);
 
-	if (!virusfilter_h->infected_file_command) {
+	if (!config->infected_file_command) {
 		return action;
 	}
 
-	if (virusfilter_set_module_env(mem_ctx, &env_list) == -1) {
+	ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+	if (ret == -1) {
 		goto done;
 	}
-	if (virusfilter_env_set(mem_ctx, &env_list,
-	    "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
-	    fname) == -1)
-	{
+	ret = virusfilter_env_set(mem_ctx, &env_list,
+				  "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
+				  fname);
+	if (ret == -1) {
 		goto done;
 	}
-	if (report && virusfilter_env_set(mem_ctx, &env_list,
-	    "VIRUSFILTER_INFECTED_FILE_REPORT", report) == -1)
-	{
-		goto done;
+	if (report != NULL) {
+		ret = virusfilter_env_set(mem_ctx, &env_list,
+					  "VIRUSFILTER_INFECTED_FILE_REPORT",
+					  report);
+		if (ret == -1) {
+			goto done;
+		}
 	}
-	if (virusfilter_env_set(mem_ctx, &env_list,
-	    "VIRUSFILTER_INFECTED_FILE_ACTION", action_name) == -1)
-	{
+	ret = virusfilter_env_set(mem_ctx, &env_list,
+				  "VIRUSFILTER_INFECTED_FILE_ACTION",
+				  action_name);
+	if (ret == -1) {
 		goto done;
 	}
-	if (filepath_q && virusfilter_env_set(mem_ctx, &env_list,
-	    "VIRUSFILTER_QUARANTINED_FILE_PATH", filepath_q) == -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 && virusfilter_env_set(mem_ctx, &env_list,
-	    "VIRUSFILTER_RESULT_IS_CACHE", "yes") == -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,
-					 virusfilter_h->infected_file_command);
+					 config->infected_file_command);
 	if (command == NULL) {
 		DBG_ERR("virusfilter_string_sub failed\n");
 		goto done;
@@ -939,46 +813,53 @@ done:
 }
 
 static void virusfilter_treat_scan_error(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
 	const struct files_struct *fsp,
 	const char *report,
 	bool is_cache)
 {
-	connection_struct *conn = vfs_h->conn;
+	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();
 	char *env_list = NULL;
 	char *command = NULL;
 	int command_result;
+	int ret;
 
-	if (!virusfilter_h->scan_error_command) {
+	if (!config->scan_error_command) {
 		return;
 	}
-	if (virusfilter_set_module_env(mem_ctx, &env_list) == -1) {
+	ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+	if (ret == -1) {
 		goto done;
 	}
-	if (virusfilter_env_set(mem_ctx, &env_list,
-	    "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
-	    fname) == -1)
-	{
+	ret = virusfilter_env_set(mem_ctx, &env_list,
+				  "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
+				  fname);
+	if (ret == -1) {
 		goto done;
 	}
-	if (report && virusfilter_env_set(mem_ctx, &env_list,
-	    "VIRUSFILTER_SCAN_ERROR_REPORT", report) == -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 && virusfilter_env_set(mem_ctx, &env_list,
-	    "VIRUSFILTER_RESULT_IS_CACHE", "1") == -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,
-					 virusfilter_h->scan_error_command);
-	if (!command) {
+					 config->scan_error_command);
+	if (command == NULL) {
 		DBG_ERR("virusfilter_string_sub failed\n");
 		goto done;
 	}
@@ -998,8 +879,8 @@ done:
 }
 
 static virusfilter_result virusfilter_scan(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
 	const struct files_struct *fsp)
 {
 	virusfilter_result scan_result;
@@ -1011,9 +892,9 @@ static virusfilter_result virusfilter_scan(
 	virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING;
 	bool add_scan_cache = true;
 
-	if (virusfilter_h->cache_h) {
+	if (config->cache) {
 		DBG_DEBUG("Searching cache entry: fname: %s\n", fname);
-		scan_cache_e = virusfilter_cache_get(virusfilter_h->cache_h,
+		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",
@@ -1026,72 +907,77 @@ static virusfilter_result virusfilter_scan(
 		DBG_DEBUG("Cache entry not found\n");
 	}
 
-#ifdef virusfilter_module_scan_init
-	if (virusfilter_module_scan_init(virusfilter_h) !=
-	    VIRUSFILTER_RESULT_OK)
-	{
-		scan_result = VIRUSFILTER_RESULT_ERROR;
-		scan_report = talloc_asprintf(talloc_tos(),
-						"Initializing scanner failed");
-		goto virusfilter_scan_result_eval;
-	}
-#endif
-
-	scan_result = virusfilter_module_scan(vfs_h, virusfilter_h, fsp,
-					      &scan_report);
-
-#ifdef virusfilter_module_scan_end
-#ifdef VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT
-	if (virusfilter_h->scan_request_limit > 0) {
-		virusfilter_h->scan_request_count++;
-		if (virusfilter_h->scan_request_count >=
-		    virusfilter_h->scan_request_limit)
-		{
-			virusfilter_module_scan_end(virusfilter_h);
-			virusfilter_h->scan_request_count = 0;
+	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);
 		}
 	}
-#else
-	virusfilter_module_scan_end(virusfilter_h);
-#endif
-#endif
 
 virusfilter_scan_result_eval:
 
 	switch (scan_result) {
 	case VIRUSFILTER_RESULT_CLEAN:
-		DBG_INFO("Scan result: Clean: %s/%s\n",
-			 cwd_fname, fname);
+		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(vfs_h,
-					virusfilter_h, fsp,
+		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;
-#ifdef VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE
+
 	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(vfs_h,
-					virusfilter_h, fsp, scan_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;
-#endif
+
 	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(vfs_h, virusfilter_h, fsp,
+		virusfilter_treat_scan_error(handle, config, fsp,
 					     scan_report, is_cache);
 		add_scan_cache = false;
 		break;
@@ -1100,18 +986,18 @@ virusfilter_scan_result_eval:
 		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(vfs_h, virusfilter_h, fsp,
+		virusfilter_treat_scan_error(handle, config, fsp,
 					     scan_report, is_cache);
 		add_scan_cache = false;
 		break;
 	}
 
-	if (virusfilter_h->cache_h) {
+	if (config->cache) {
 		if (!is_cache && add_scan_cache) {
 			DBG_DEBUG("Adding new cache entry: %s, %d\n", fname,
 				  scan_result);
 			if (!virusfilter_cache_entry_add(
-			    virusfilter_h->cache_h, cwd_fname, fname,
+			    config->cache, cwd_fname, fname,
 			    scan_result, scan_report))
 			{
 				DBG_ERR("Cannot create cache entry: "
@@ -1128,14 +1014,14 @@ virusfilter_scan_return:
 }
 
 static int virusfilter_vfs_open(
-	struct vfs_handle_struct *vfs_h,
+	struct vfs_handle_struct *handle,
 	struct smb_filename *smb_fname,
 	files_struct *fsp,
 	int flags,
 	mode_t mode)
 {
 	TALLOC_CTX *mem_ctx = talloc_stackframe();
-	struct virusfilter_handle *virusfilter_h;
+	struct virusfilter_config *config;
 	char *cwd_fname = fsp->conn->cwd_fname->base_name;
 	virusfilter_result scan_result;
 	char *fname = fsp->fsp_name->base_name;
@@ -1146,11 +1032,11 @@ static int virusfilter_vfs_open(
 	int test_suffix;
 	int rename_trap_count = 0;
 
-	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
-				struct virusfilter_handle, return -1);
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return -1);
 
-	test_prefix = strlen(virusfilter_h->rename_prefix);
-	test_suffix = strlen(virusfilter_h->rename_suffix);
+	test_prefix = strlen(config->rename_prefix);
+	test_suffix = strlen(config->rename_suffix);
 	if (test_prefix) {
 		rename_trap_count++;
 	}
@@ -1166,7 +1052,7 @@ static int virusfilter_vfs_open(
 		goto virusfilter_vfs_open_next;
 	}
 
-	if (!virusfilter_h->scan_on_open) {
+	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;
@@ -1178,7 +1064,7 @@ static int virusfilter_vfs_open(
 		goto virusfilter_vfs_open_next;
 	}
 
-	if (SMB_VFS_NEXT_STAT(vfs_h, smb_fname) != 0) {
+	if (SMB_VFS_NEXT_STAT(handle, smb_fname) != 0) {
 
 		/*
 		 * Do not return immediately if !(flags & O_CREAT) &&
@@ -1194,34 +1080,32 @@ static int virusfilter_vfs_open(
 			 cwd_fname, fname);
 		goto virusfilter_vfs_open_next;
 	}
-	if (virusfilter_h->max_file_size > 0 && smb_fname->st.st_ex_size >
-	    virusfilter_h->max_file_size)
+	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 (virusfilter_h->min_file_size > 0 && smb_fname->st.st_ex_size <
-	    virusfilter_h->min_file_size)
+	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;
 	}
 
-	if (virusfilter_h->exclude_files && is_in_path(fname,
-	    virusfilter_h->exclude_files, false))
+	if (config->exclude_files &&
+	    is_in_path(fname, config->exclude_files, false))
 	{
 		DBG_INFO("Not scanned: exclude files: %s/%s\n",
 			 cwd_fname, fname);
 		goto virusfilter_vfs_open_next;
 	}
 
-	if (virusfilter_h->infected_file_action ==
-	    VIRUSFILTER_ACTION_QUARANTINE)
-	{
-		if (strstr(fname, virusfilter_h->quarantine_dir) != NULL) {
-			scan_errno = virusfilter_h->infected_open_errno;
+	if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+		if (strstr(fname, config->quarantine_dir) != NULL) {
+			scan_errno = config->infected_open_errno;
 			goto virusfilter_vfs_open_fail;
 		}
 	}
@@ -1231,7 +1115,7 @@ static int virusfilter_vfs_open(
 		{
 			if (test_prefix) {
 				if (strncmp(base_name,
-				    virusfilter_h->rename_prefix,
+				    config->rename_prefix,
 				    test_prefix) != 0)
 				{
 					test_prefix = 0;
@@ -1240,7 +1124,7 @@ static int virusfilter_vfs_open(
 			if (test_suffix) {
 				if (strcmp(base_name + (strlen(base_name) -
 				    test_suffix),
-				    virusfilter_h->rename_suffix) != 0)
+				    config->rename_suffix) != 0)
 				{
 					test_suffix = 0;
 				}
@@ -1253,35 +1137,35 @@ static int virusfilter_vfs_open(
 			    (test_prefix || test_suffix)))
 			{
 				scan_errno =
-					virusfilter_h->infected_open_errno;
+					config->infected_open_errno;
 				goto virusfilter_vfs_open_fail;
 			}
 		}
 	}
 
-	scan_result = virusfilter_scan(vfs_h, virusfilter_h, fsp);
+	scan_result = virusfilter_scan(handle, config, fsp);
 
 	switch (scan_result) {
 	case VIRUSFILTER_RESULT_CLEAN:
 		break;
 	case VIRUSFILTER_RESULT_INFECTED:
-		scan_errno = virusfilter_h->infected_open_errno;
+		scan_errno = config->infected_open_errno;
 		goto virusfilter_vfs_open_fail;
 	case VIRUSFILTER_RESULT_ERROR:
-		if (virusfilter_h->block_access_on_error) {
+		if (config->block_access_on_error) {
 			DBG_INFO("Block access\n");
-			scan_errno = virusfilter_h->scan_error_open_errno;
+			scan_errno = config->scan_error_open_errno;
 			goto virusfilter_vfs_open_fail;
 		}
 		break;
 	default:
-		scan_errno = virusfilter_h->scan_error_open_errno;
+		scan_errno = config->scan_error_open_errno;
 		goto virusfilter_vfs_open_fail;
 	}
 
 virusfilter_vfs_open_next:
 	TALLOC_FREE(mem_ctx);
-	return SMB_VFS_NEXT_OPEN(vfs_h, smb_fname, fsp, flags, mode);
+	return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
 
 virusfilter_vfs_open_fail:
 	TALLOC_FREE(mem_ctx);
@@ -1290,7 +1174,7 @@ virusfilter_vfs_open_fail:
 }
 
 static int virusfilter_vfs_close(
-	struct vfs_handle_struct *vfs_h,
+	struct vfs_handle_struct *handle,
 	files_struct *fsp)
 {
 	TALLOC_CTX *mem_ctx = talloc_stackframe();
@@ -1299,23 +1183,23 @@ static int virusfilter_vfs_close(
          * 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 = vfs_h->conn->connectpath;
+	const char *cwd_fname = handle->conn->connectpath;
 
-	struct virusfilter_handle *virusfilter_h;
+	struct virusfilter_config *config;
 	char *fname = fsp->fsp_name->base_name;
 	int close_result, close_errno;
 	virusfilter_result scan_result;
 	int scan_errno = 0;
 
-	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
-				struct virusfilter_handle, return -1);
+	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(vfs_h, fsp);
+	close_result = SMB_VFS_NEXT_CLOSE(handle, fsp);
 	close_errno = errno;
 
 	/*
@@ -1326,7 +1210,7 @@ static int virusfilter_vfs_close(
 		if (fsp->modified) {
 			DBG_DEBUG("Removing cache entry (if existent): "
 				  "fname: %s\n", fname);
-			virusfilter_cache_remove(virusfilter_h->cache_h,
+			virusfilter_cache_remove(config->cache,
 						 cwd_fname, fname);
 		}
 		goto virusfilter_vfs_close_fail;
@@ -1342,12 +1226,12 @@ static int virusfilter_vfs_close(
 	if (is_ntfs_stream_smb_fname(fsp->fsp_name) &&
 	    !is_ntfs_default_stream_smb_fname(fsp->fsp_name))
 	{
-		if (virusfilter_h->scan_on_open && fsp->modified) {
-			if (virusfilter_h->cache_h) {
+		if (config->scan_on_open && fsp->modified) {
+			if (config->cache) {
 				DBG_DEBUG("Removing cache entry (if existent)"
 					  ": fname: %s\n", fname);
 				virusfilter_cache_remove(
-						virusfilter_h->cache_h,
+						config->cache,
 						cwd_fname, fname);
 			}
 		}
@@ -1357,13 +1241,13 @@ static int virusfilter_vfs_close(
 		return close_result;
 	}
 
-	if (!virusfilter_h->scan_on_close) {
-		if (virusfilter_h->scan_on_open && fsp->modified) {
-			if (virusfilter_h->cache_h) {
+	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(
-						virusfilter_h->cache_h,
+						config->cache,
 						cwd_fname, fname);
 			}
 		}
@@ -1381,8 +1265,8 @@ static int virusfilter_vfs_close(
 		return close_result;
 	}
 
-	if (virusfilter_h->exclude_files && is_in_path(fname,
-	    virusfilter_h->exclude_files, false))
+	if (config->exclude_files && is_in_path(fname,
+	    config->exclude_files, false))
 	{
 		DBG_INFO("Not scanned: exclude files: %s/%s\n",
 			 cwd_fname, fname);
@@ -1390,23 +1274,23 @@ static int virusfilter_vfs_close(
 		return close_result;
 	}
 
-	scan_result = virusfilter_scan(vfs_h, virusfilter_h, fsp);
+	scan_result = virusfilter_scan(handle, config, fsp);
 
 	switch (scan_result) {
 	case VIRUSFILTER_RESULT_CLEAN:
 		break;
 	case VIRUSFILTER_RESULT_INFECTED:
-		scan_errno = virusfilter_h->infected_close_errno;
+		scan_errno = config->infected_close_errno;
 		goto virusfilter_vfs_close_fail;
 	case VIRUSFILTER_RESULT_ERROR:
-		if (virusfilter_h->block_access_on_error) {
+		if (config->block_access_on_error) {
 			DBG_INFO("Block access\n");
-			scan_errno = virusfilter_h->scan_error_close_errno;
+			scan_errno = config->scan_error_close_errno;
 			goto virusfilter_vfs_close_fail;
 		}
 		break;
 	default:
-		scan_errno = virusfilter_h->scan_error_close_errno;
+		scan_errno = config->scan_error_close_errno;
 		goto virusfilter_vfs_close_fail;
 	}
 
@@ -1424,101 +1308,98 @@ virusfilter_vfs_close_fail:
 }
 
 static int virusfilter_vfs_unlink(
-	struct vfs_handle_struct *vfs_h,
+	struct vfs_handle_struct *handle,
 	const struct smb_filename *smb_fname)
 {
-	int ret = SMB_VFS_NEXT_UNLINK(vfs_h, smb_fname);
-	struct virusfilter_handle *virusfilter_h;
+	int ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+	struct virusfilter_config *config;
 	char *fname = NULL;
-	char *cwd_fname = vfs_h->conn->cwd_fname->base_name;
+	char *cwd_fname = handle->conn->cwd_fname->base_name;
 
 	if (ret != 0 && errno != ENOENT) {
 		return ret;
 	}
 
-	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
-				struct virusfilter_handle, return -1);
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return -1);
 
-	if (virusfilter_h->cache_h) {
-		fname = smb_fname->base_name;
-		DBG_DEBUG("Removing cache entry (if existent): fname: %s\n",
-			  fname);
-		virusfilter_cache_remove(virusfilter_h->cache_h, cwd_fname,
-					 fname);
+	if (config->cache == NULL) {
+		return 0;
 	}
 
-	return ret;
+	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 *vfs_h,
+	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(vfs_h, smb_fname_src, smb_fname_dst);
-	struct virusfilter_handle *virusfilter_h;
+	int ret = SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
+	struct virusfilter_config *config;
 	char *fname = NULL;
-	char *cwd_fname = vfs_h->conn->cwd_fname->base_name;
+	char *cwd_fname = handle->conn->cwd_fname->base_name;
 
 	if (ret != 0) {
 		return ret;
 	}
 
-	SMB_VFS_HANDLE_GET_DATA(vfs_h, virusfilter_h,
-				struct virusfilter_handle, return -1);
-
-	if (virusfilter_h->cache_h) {
-		fname = smb_fname_dst->base_name;
-		DBG_DEBUG("Removing cache entry (if existent): fname: %s\n",
-			  fname);
-		virusfilter_cache_remove(virusfilter_h->cache_h, cwd_fname,
-					 fname);
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return -1);
 
-		fname = smb_fname_src->base_name;
-		DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n", fname,
-			  smb_fname_dst->base_name);
-		virusfilter_cache_entry_rename(virusfilter_h->cache_h,
-					       cwd_fname, fname,
-					       smb_fname_dst->base_name);
+	if (config->cache == NULL) {
+		return 0;
 	}
 
-	return ret;
+	fname = smb_fname_dst->base_name;
+	DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname);
+	virusfilter_cache_remove(config->cache, cwd_fname, fname);
+
+	fname = smb_fname_src->base_name;
+	DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n",
+		  fname, smb_fname_dst->base_name);
+	virusfilter_cache_entry_rename(config->cache,
+				       cwd_fname, fname,
+				       smb_fname_dst->base_name);
+
+	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,
+	.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,
 };
 
-#define MAKE_FN_NAME(x) NTSTATUS vfs_virusfilter_ ## x ## _init(TALLOC_CTX *ctx)
-#define VFS_VIRUSFILTER_INIT(ENGINE) MAKE_FN_NAME(ENGINE)
-
-VFS_VIRUSFILTER_INIT(VIRUSFILTER_ENGINE);
-VFS_VIRUSFILTER_INIT(VIRUSFILTER_ENGINE)
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *);
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *ctx)
 {
-	NTSTATUS ret;
+	NTSTATUS status;
 
-	ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
-			       VIRUSFILTER_MODULE_NAME, &vfs_virusfilter_fns);
-	if (!NT_STATUS_IS_OK(ret)) {
-		return ret;
+	status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+				  "virusfilter",
+				  &vfs_virusfilter_fns);
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
 	}
 
-	virusfilter_debug_level = debug_add_class(VIRUSFILTER_MODULE_NAME);
-	if (virusfilter_debug_level == -1) {
-		virusfilter_debug_level = DBGC_VFS;
+	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 of '%s': %d\n",
-			  VIRUSFILTER_MODULE_NAME, virusfilter_debug_level);
+		DBG_DEBUG("Debug class number: %d\n", virusfilter_debug_class);
 	}
 
-	DBG_INFO("%s registered\n", VIRUSFILTER_MODULE_NAME);
+	DBG_INFO("registered\n");
 
-	return ret;
+	return status;
 }
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
index b09753ca782..96d1662ecbd 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -36,8 +36,8 @@
 
 /* Samba debug class for VIRUSFILTER */
 #undef DBGC_CLASS
-#define DBGC_CLASS virusfilter_debug_level
-extern int virusfilter_debug_level;
+#define DBGC_CLASS virusfilter_debug_class
+extern int virusfilter_debug_class;
 
 /* Samba's global variable */
 extern userdom_struct current_user_info;
@@ -58,10 +58,91 @@ typedef enum {
 	VIRUSFILTER_RESULT_CLEAN,
 	VIRUSFILTER_RESULT_ERROR,
 	VIRUSFILTER_RESULT_INFECTED,
-#ifdef VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE
 	VIRUSFILTER_RESULT_SUSPECTED,
-#endif
 	/* 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_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
index 359062c8545..45454fc702a 100644
--- a/source3/modules/vfs_virusfilter_utils.c
+++ b/source3/modules/vfs_virusfilter_utils.c
@@ -26,7 +26,7 @@ struct iovec;
 #include <tevent.h>
 #include "lib/tsocket/tsocket.h"
 
-int virusfilter_debug_level = DBGC_VFS;
+int virusfilter_debug_class = DBGC_VFS;
 
 /* ====================================================================== */
 
@@ -694,38 +694,38 @@ bool virusfilter_io_writefl_readl(
 	return true;
 }
 
-struct virusfilter_cache_handle *virusfilter_cache_new(
+struct virusfilter_cache *virusfilter_cache_new(
 	TALLOC_CTX *ctx,
 	int entry_limit,
 	time_t time_limit)
 {
-	struct virusfilter_cache_handle *cache_h;
+	struct virusfilter_cache *cache;
 
 	if (time_limit == 0) {
 		return NULL;
 	}
 
-	cache_h = talloc_zero(ctx, struct virusfilter_cache_handle);
-	if (cache_h == NULL) {
+	cache = talloc_zero(ctx, struct virusfilter_cache);
+	if (cache == NULL) {
 		DBG_ERR("talloc_zero failed.\n");
 		return NULL;
 	}
 
-	cache_h->cache = memcache_init(cache_h->ctx, entry_limit *
+	cache->cache = memcache_init(cache->ctx, entry_limit *
 				       (sizeof(struct virusfilter_cache_entry)
 				       + VIRUSFILTER_CACHE_BUFFER_SIZE));
-	if (cache_h->cache == NULL) {
+	if (cache->cache == NULL) {
 		DBG_ERR("memcache_init failed.\n");
 		return NULL;
 	}
-	cache_h->ctx = ctx;
-	cache_h->time_limit = time_limit;
+	cache->ctx = ctx;
+	cache->time_limit = time_limit;
 
-	return cache_h;
+	return cache;
 }
 
 int virusfilter_cache_entry_add(
-	struct virusfilter_cache_handle *cache_h,
+	struct virusfilter_cache *cache,
 	const char *directory,
 	const char *fname,
 	virusfilter_result result,
@@ -750,7 +750,7 @@ int virusfilter_cache_entry_add(
 
 	fname_len = strlen(fname);
 
-	if (cache_e == NULL|| cache_h->time_limit == 0) {
+	if (cache_e == NULL|| cache->time_limit == 0) {
 		TALLOC_FREE(report);
 		return 0;
 	}
@@ -759,11 +759,11 @@ int virusfilter_cache_entry_add(
 	if (report != NULL) {
 		cache_e->report = talloc_steal(cache_e, report);
 	}
-	if (cache_h->time_limit > 0) {
+	if (cache->time_limit > 0) {
 		cache_e->time = time(NULL);
 	}
 
-	memcache_add_talloc(cache_h->cache,
+	memcache_add_talloc(cache->cache,
 			    VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
 			    data_blob_const(fname, fname_len), &cache_e);
 
@@ -771,7 +771,7 @@ int virusfilter_cache_entry_add(
 }
 
 int virusfilter_cache_entry_rename(
-	struct virusfilter_cache_handle *cache_h,
+	struct virusfilter_cache *cache,
 	const char *directory,
 	char *old_fname,
 	char *new_fname)
@@ -798,7 +798,7 @@ int virusfilter_cache_entry_rename(
 	new_fname_len = strlen(new_fname);
 
 	old_data = memcache_lookup_talloc(
-				cache_h->cache,
+				cache->cache,
 				VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
 				data_blob_const(old_fname, old_fname_len));
 
@@ -806,30 +806,30 @@ int virusfilter_cache_entry_rename(
 		return 0;
 	}
 
-	new_data = talloc_memdup(cache_h->ctx, old_data,
+	new_data = talloc_memdup(cache->ctx, old_data,
 				 sizeof(struct virusfilter_cache_entry));
 	if (new_data == NULL) {
 		return 0;
 	}
 	new_data->report = talloc_strdup(new_data, old_data->report);
 
-	memcache_add_talloc(cache_h->cache,
+	memcache_add_talloc(cache->cache,
 			VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
 			data_blob_const(new_fname, new_fname_len), &new_data);
 
-	memcache_delete(cache_h->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+	memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
 			data_blob_const(old_fname, old_fname_len));
 
 	return 1;
 }
 
-void virusfilter_cache_purge(struct virusfilter_cache_handle *cache_h)
+void virusfilter_cache_purge(struct virusfilter_cache *cache)
 {
-	memcache_flush(cache_h->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
+	memcache_flush(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
 }
 
 struct virusfilter_cache_entry *virusfilter_cache_get(
-	struct virusfilter_cache_handle *cache_h,
+	struct virusfilter_cache *cache,
 	const char *directory,
 	const char *fname)
 {
@@ -849,7 +849,7 @@ struct virusfilter_cache_entry *virusfilter_cache_get(
 
 	fname_len = strlen(fname);
 
-	data = memcache_lookup_talloc(cache_h->cache,
+	data = memcache_lookup_talloc(cache->cache,
 				      VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
 				      data_blob_const(fname, fname_len));
 
@@ -857,15 +857,15 @@ struct virusfilter_cache_entry *virusfilter_cache_get(
 		return cache_e;
 	}
 
-	if (cache_h->time_limit > 0) {
-		if (time(NULL) - data->time  > cache_h->time_limit) {
+	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_h, directory, fname);
+			virusfilter_cache_remove(cache, directory, fname);
 			return cache_e;
 		}
 	}
-	cache_e = talloc_memdup(cache_h->ctx, data,
+	cache_e = talloc_memdup(cache->ctx, data,
 			       sizeof(struct virusfilter_cache_entry));
 	if (cache_e == NULL) {
 		return NULL;
@@ -879,7 +879,7 @@ struct virusfilter_cache_entry *virusfilter_cache_get(
 	return cache_e;
 }
 
-void virusfilter_cache_remove(struct virusfilter_cache_handle *cache_h,
+void virusfilter_cache_remove(struct virusfilter_cache *cache,
 	const char *directory,
 	const char *fname)
 {
@@ -895,7 +895,7 @@ void virusfilter_cache_remove(struct virusfilter_cache_handle *cache_h,
 		return;
 	}
 
-	memcache_delete(cache_h->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+	memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
 			data_blob_const(fname, strlen(fname)));
 }
 
diff --git a/source3/modules/vfs_virusfilter_utils.h b/source3/modules/vfs_virusfilter_utils.h
index 19a2fa14b7a..9a82f316d19 100644
--- a/source3/modules/vfs_virusfilter_utils.h
+++ b/source3/modules/vfs_virusfilter_utils.h
@@ -59,7 +59,7 @@ struct virusfilter_cache_entry {
 	char *report;
 };
 
-struct virusfilter_cache_handle {
+struct virusfilter_cache {
 	struct memcache *cache;
 	TALLOC_CTX *ctx;
 	time_t time_limit;
@@ -131,31 +131,31 @@ bool virusfilter_io_writefl_readl(
 	const char *fmt, ...);
 
 /* Scan result cache */
-struct virusfilter_cache_handle *virusfilter_cache_new(
+struct virusfilter_cache *virusfilter_cache_new(
 	TALLOC_CTX *ctx,
 	int entry_limit,
 	time_t time_limit);
 int virusfilter_cache_entry_add(
-	struct virusfilter_cache_handle *cache_h,
+	struct virusfilter_cache *cache,
 	const char *directory,
 	const char *fname,
 	virusfilter_result result,
 	char *report);
 int virusfilter_cache_entry_rename(
-	struct virusfilter_cache_handle *cache_h,
+	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_handle *cache_h,
+	struct virusfilter_cache *cache,
 	const char *directory,
 	const char *fname);
 void virusfilter_cache_remove(
-	struct virusfilter_cache_handle *cache_h,
+	struct virusfilter_cache *cache,
 	const char *directory,
 	const char *fname);
-void virusfilter_cache_purge(struct virusfilter_cache_handle *cache_h);
+void virusfilter_cache_purge(struct virusfilter_cache *cache);
 
 /* Shell scripting */
 int virusfilter_env_set(
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index 4e4fdb98a1d..fdce73dc61d 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 d45a7d9ec60..cfd44a86e79 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 d1b16de1edb770a65ca18b48c02ad758924a1e03 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:38:14 -0600
Subject: [PATCH 4/9] Samba-VirusFilter: Sophos VFS backend.

Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas at osstech.co.jp>
---
 source3/modules/vfs_virusfilter_sophos.c | 361 +++++++++++++++++++++++++++++++
 1 file changed, 361 insertions(+)
 create mode 100644 source3/modules/vfs_virusfilter_sophos.c

diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c
new file mode 100644
index 00000000000..8c08194d0fb
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_sophos.c
@@ -0,0 +1,361 @@
+/*
+   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 <http://www.gnu.org/licenses/>.
+*/
+
+#define VIRUSFILTER_ENGINE sophos
+#define VIRUSFILTER_MODULE_ENGINE "sophos"
+
+/* 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
+#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT	30000 /* msec */
+#define VIRUSFILTER_DEFAULT_TIMEOUT		60000 /* msec */
+#define VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT	0
+#define VIRUSFILTER_DEFAULT_SCAN_ARCHIVE	false
+
+#define virusfilter_module_connect		virusfilter_sophos_connect
+#define virusfilter_module_scan_init		virusfilter_sophos_scan_init
+#define virusfilter_module_scan_end		virusfilter_sophos_scan_end
+#define virusfilter_module_scan			virusfilter_sophos_scan
+
+#include "modules/vfs_virusfilter_vfs.c"
+
+/* ====================================================================== */
+
+#include "modules/vfs_virusfilter_utils.h"
+
+/* ====================================================================== */
+
+/* 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 *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const char *svc,
+	const char *user)
+{
+	virusfilter_io_set_readl_eol(virusfilter_h->io_h, "\x0D\x0A", 2);
+
+	return 0;
+}
+
+static virusfilter_result virusfilter_sophos_scan_ping(
+	struct virusfilter_handle *virusfilter_h)
+{
+	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	char *reply = NULL;
+
+	/* SSSP/1.0 has no "PING" command */
+	if (virusfilter_io_writel(io_h, "SSSP/1.0 OPTIONS\n", 17) !=
+	    true)
+	{
+		return VIRUSFILTER_RESULT_ERROR;
+	}
+
+	for (;;) {
+		if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+			return VIRUSFILTER_RESULT_ERROR;
+		}
+		if (strcmp(reply, "") == 0) {
+			break;
+		}
+		TALLOC_FREE(reply);
+	}
+
+	TALLOC_FREE(reply);
+	return VIRUSFILTER_RESULT_OK;
+}
+
+static virusfilter_result virusfilter_sophos_scan_init(
+	struct virusfilter_handle *virusfilter_h)
+{
+	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	virusfilter_result result;
+	char *reply = NULL;
+
+	if (io_h->stream != NULL) {
+		DBG_DEBUG("SSSP: Checking if connection is alive\n");
+
+		if (virusfilter_sophos_scan_ping(virusfilter_h) ==
+		    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(virusfilter_h);
+	}
+
+
+	DBG_INFO("SSSP: Connecting to socket: %s\n",
+		virusfilter_h->socket_path);
+
+	become_root();
+	result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+	unbecome_root();
+
+	if (result != true) {
+		DBG_ERR("SSSP: Connecting to socket failed: %s: %s\n",
+			virusfilter_h->socket_path, strerror(errno));
+		return VIRUSFILTER_RESULT_ERROR;
+	}
+
+	if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+		DBG_ERR("SSSP: Reading greeting message failed: %s\n",
+			strerror(errno));
+		goto virusfilter_sophos_scan_init_failed;
+	}
+	if (strncmp(reply, "OK SSSP/1.0", 11) != 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);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply,
+	    "SSSP/1.0 OPTIONS\noutput:brief\nsavigrp:GrpArchiveUnpack %d\n",
+	    virusfilter_h->scan_archive ? 1 : 0) != true)
+	{
+		DBG_ERR("SSSP: OPTIONS: I/O error: %s\n", strerror(errno));
+		goto virusfilter_sophos_scan_init_failed;
+	}
+	if (strncmp(reply, "ACC ", 4) != 0) {
+		DBG_ERR("SSSP: OPTIONS: Not accepted: %s\n", reply);
+		goto virusfilter_sophos_scan_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+		DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+		goto virusfilter_sophos_scan_init_failed;
+	}
+	if (strncmp(reply, "DONE OK ", 8) != 0) {
+		DBG_ERR("SSSP: OPTIONS failed: %s\n", reply);
+		goto virusfilter_sophos_scan_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+		DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+		goto virusfilter_sophos_scan_init_failed;
+	}
+	if (strcmp(reply, "") != 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(virusfilter_h);
+
+	return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_sophos_scan_end(
+	struct virusfilter_handle *virusfilter_h)
+{
+	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+
+	DBG_INFO("SSSP: Disconnecting\n");
+
+	virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_sophos_scan(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	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 = virusfilter_h->io_h;
+	virusfilter_result result = VIRUSFILTER_RESULT_ERROR;
+	char *report = NULL;
+	char *reply = NULL;
+	char *reply_token, *reply_saveptr;
+
+	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;
+
+	if (virusfilter_io_writevl(io_h, "SSSP/1.0 SCANFILE ", 18, fileurl,
+	    fileurl_len, NULL) != true)
+	{
+		DBG_ERR("SSSP: SCANFILE: Write error: %s\n",
+		      strerror(errno));
+		goto virusfilter_sophos_scan_io_error;
+	}
+
+	if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+		DBG_ERR("SSSP: SCANFILE: Read error: %s\n", strerror(errno));
+		goto virusfilter_sophos_scan_io_error;
+	}
+	if (strncmp(reply, "ACC ", 4) != 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 (;;) {
+		if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+			DBG_ERR("SSSP: SCANFILE: Read error: %s\n",
+				strerror(errno));
+			goto virusfilter_sophos_scan_io_error;
+		}
+
+		if (strcmp(reply, "") == 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) {
+				  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 &&
+
+			    /* 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;
+}
-- 
2.13.6


From 9769060423d8a2e49cbc996139a0a37bfa306976 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Thu, 21 Dec 2017 17:19:28 +0100
Subject: [PATCH 5/9] FIXUP sophos

---
 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 | 87 ++++++++++++++++++--------------
 source3/modules/wscript_build            |  5 +-
 5 files changed, 73 insertions(+), 42 deletions(-)

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index 392c8f8028b..88738718250 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -41,6 +41,10 @@
 		<term>virusfilter:scanner</term>
 		<listitem>
 		<para>The antivirus scan-engine.</para>
+		<itemizedlist>
+		  <listitem><para><emphasis>sophos</emphasis>, the Sophos AV
+		  scanner</para></listitem>
+		</itemizedlist>
 		</listitem>
 		</varlistentry>
 
@@ -52,6 +56,8 @@
 		<para>If this option is not set, the default path depends on the
 		configured AV scanning engine.
 		</para>
+		<para>For the <emphasis>sophos</emphasis>backend the default is
+		<emphasis>/var/run/savdi/sssp.sock</emphasis>.</para>
 		</listitem>
 		</varlistentry>
 
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index 3d463a4502e..10462bd40e6 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -412,10 +412,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 96d1662ecbd..0278d42b710 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -145,4 +145,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
index 8c08194d0fb..5f1b990a214 100644
--- a/source3/modules/vfs_virusfilter_sophos.c
+++ b/source3/modules/vfs_virusfilter_sophos.c
@@ -17,8 +17,8 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#define VIRUSFILTER_ENGINE sophos
-#define VIRUSFILTER_MODULE_ENGINE "sophos"
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
 
 /* Default values for standard "extra" configuration variables */
 #ifdef SOPHOS_DEFAULT_SOCKET_PATH
@@ -26,23 +26,8 @@
 #else
 #  define VIRUSFILTER_DEFAULT_SOCKET_PATH	"/var/run/savdi/sssp.sock"
 #endif
-#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT	30000 /* msec */
-#define VIRUSFILTER_DEFAULT_TIMEOUT		60000 /* msec */
-#define VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT	0
-#define VIRUSFILTER_DEFAULT_SCAN_ARCHIVE	false
 
-#define virusfilter_module_connect		virusfilter_sophos_connect
-#define virusfilter_module_scan_init		virusfilter_sophos_scan_init
-#define virusfilter_module_scan_end		virusfilter_sophos_scan_end
-#define virusfilter_module_scan			virusfilter_sophos_scan
-
-#include "modules/vfs_virusfilter_vfs.c"
-
-/* ====================================================================== */
-
-#include "modules/vfs_virusfilter_utils.h"
-
-/* ====================================================================== */
+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)
@@ -78,20 +63,20 @@ static int virusfilter_url_quote(const char *src, char *dst, int dst_size)
 }
 
 static int virusfilter_sophos_connect(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
 	const char *svc,
 	const char *user)
 {
-	virusfilter_io_set_readl_eol(virusfilter_h->io_h, "\x0D\x0A", 2);
+	virusfilter_io_set_readl_eol(config->io_h, "\x0D\x0A", 2);
 
 	return 0;
 }
 
 static virusfilter_result virusfilter_sophos_scan_ping(
-	struct virusfilter_handle *virusfilter_h)
+	struct virusfilter_config *config)
 {
-	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	struct virusfilter_io_handle *io_h = config->io_h;
 	char *reply = NULL;
 
 	/* SSSP/1.0 has no "PING" command */
@@ -116,16 +101,16 @@ static virusfilter_result virusfilter_sophos_scan_ping(
 }
 
 static virusfilter_result virusfilter_sophos_scan_init(
-	struct virusfilter_handle *virusfilter_h)
+	struct virusfilter_config *config)
 {
-	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	struct virusfilter_io_handle *io_h = config->io_h;
 	virusfilter_result result;
 	char *reply = NULL;
 
 	if (io_h->stream != NULL) {
 		DBG_DEBUG("SSSP: Checking if connection is alive\n");
 
-		if (virusfilter_sophos_scan_ping(virusfilter_h) ==
+		if (virusfilter_sophos_scan_ping(config) ==
 		    VIRUSFILTER_RESULT_OK)
 		{
 			DBG_DEBUG("SSSP: Re-using existent connection\n");
@@ -133,20 +118,20 @@ static virusfilter_result virusfilter_sophos_scan_init(
 		}
 
 		DBG_INFO("SSSP: Closing dead connection\n");
-		virusfilter_sophos_scan_end(virusfilter_h);
+		virusfilter_sophos_scan_end(config);
 	}
 
 
 	DBG_INFO("SSSP: Connecting to socket: %s\n",
-		virusfilter_h->socket_path);
+		config->socket_path);
 
 	become_root();
-	result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+	result = virusfilter_io_connect_path(io_h, config->socket_path);
 	unbecome_root();
 
 	if (result != true) {
 		DBG_ERR("SSSP: Connecting to socket failed: %s: %s\n",
-			virusfilter_h->socket_path, strerror(errno));
+			config->socket_path, strerror(errno));
 		return VIRUSFILTER_RESULT_ERROR;
 	}
 
@@ -169,7 +154,7 @@ static virusfilter_result virusfilter_sophos_scan_init(
 
 	if (virusfilter_io_writefl_readl(io_h, &reply,
 	    "SSSP/1.0 OPTIONS\noutput:brief\nsavigrp:GrpArchiveUnpack %d\n",
-	    virusfilter_h->scan_archive ? 1 : 0) != true)
+	    config->scan_archive ? 1 : 0) != true)
 	{
 		DBG_ERR("SSSP: OPTIONS: I/O error: %s\n", strerror(errno));
 		goto virusfilter_sophos_scan_init_failed;
@@ -209,15 +194,15 @@ virusfilter_sophos_scan_init_failed:
 
 	TALLOC_FREE(reply);
 
-	virusfilter_sophos_scan_end(virusfilter_h);
+	virusfilter_sophos_scan_end(config);
 
 	return VIRUSFILTER_RESULT_ERROR;
 }
 
 static void virusfilter_sophos_scan_end(
-	struct virusfilter_handle *virusfilter_h)
+	struct virusfilter_config *config)
 {
-	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	struct virusfilter_io_handle *io_h = config->io_h;
 
 	DBG_INFO("SSSP: Disconnecting\n");
 
@@ -225,8 +210,8 @@ static void virusfilter_sophos_scan_end(
 }
 
 static virusfilter_result virusfilter_sophos_scan(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
 	const struct files_struct *fsp,
 	char **reportp)
 {
@@ -234,7 +219,7 @@ static virusfilter_result virusfilter_sophos_scan(
 	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 = virusfilter_h->io_h;
+	struct virusfilter_io_handle *io_h = config->io_h;
 	virusfilter_result result = VIRUSFILTER_RESULT_ERROR;
 	char *report = NULL;
 	char *reply = NULL;
@@ -359,3 +344,31 @@ virusfilter_sophos_scan_io_error:
 
 	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 fdce73dc61d..caaaa0c1730 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 2f4a18c51d0f6999f8a75d4e9d48d9bffce01b44 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:39:20 -0600
Subject: [PATCH 6/9] Samba-VirusFilter: F-Secure AntiVirus (fsav) VFS and man
 page.

Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas at osstech.co.jp>
---
 docs-xml/manpages/vfs_virusfilter_fsav.8.xml | 330 +++++++++++++++++++++
 source3/modules/vfs_virusfilter_fsav.c       | 413 +++++++++++++++++++++++++++
 2 files changed, 743 insertions(+)
 create mode 100644 docs-xml/manpages/vfs_virusfilter_fsav.8.xml
 create mode 100644 source3/modules/vfs_virusfilter_fsav.c

diff --git a/docs-xml/manpages/vfs_virusfilter_fsav.8.xml b/docs-xml/manpages/vfs_virusfilter_fsav.8.xml
new file mode 100644
index 00000000000..889709215f0
--- /dev/null
+++ b/docs-xml/manpages/vfs_virusfilter_fsav.8.xml
@@ -0,0 +1,330 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//Samba-Team//DTD DocBook V4.2-Based Variant V1.0//EN" "http://www.samba.org/samba/DTD/samba-doc">
+<refentry id="vfs_virusfilter_fsav.8">
+
+<refmeta>
+	<refentrytitle>vfs_virusfilter_fsav</refentrytitle>
+	<manvolnum>8</manvolnum>
+	<refmiscinfo class="source">Samba</refmiscinfo>
+	<refmiscinfo class="manual">System Administration tools</refmiscinfo>
+	<refmiscinfo class="version">4.6</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+	<refname>vfs_virusfilter_fsav</refname>
+	<refpurpose>On access virus scanner</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+	<cmdsynopsis>
+		<command>vfs objects = virusfilter_fsav</command>
+	</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+	<title>DESCRIPTION</title>
+
+	<para>This is a set of various Samba VFS modules to scan and filter
+	virus files on Samba file services with an anti-virus scanner.</para>
+
+	<para>This module is stackable.</para>
+
+</refsect1>
+
+<refsect1>
+	<title>OPTIONS</title>
+
+	<variablelist>
+
+		<varlistentry>
+		<term>virusfilter_fsav:socket path = PATH</term>
+		<listitem>
+		<para>Path of local socket for the virus scanner.
+		</para>
+		<para>If this option is not set, the default path /tmp/.fsav-0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:connect timeout = 30000</term>
+		<listitem>
+		<para>Controls how long to wait on connecting to the virus
+		scanning process before timing out. Value is in milliseconds.
+		</para>
+		<para>If this option is not set, the default is 30000.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:io timeout = 60000</term>
+		<listitem>
+		<para>Controls how long to wait on communications with the virus
+		scanning process before timing out. Value is in milliseconds.
+		</para>
+		<para>If this option is not set, the default is 60000.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:scan on open = yes</term>
+		<listitem>
+		<para>This option controls whether files are scanned on open.
+		</para>
+		<para>If this option is not set, the default is yes.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:scan on close = no</term>
+		<listitem>
+		<para>This option controls whether files are scanned on open.
+		</para>
+		<para>If this option is not set, the default is no.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:max file size = 100000000</term>
+		<listitem>
+		<para>This is the largest sized file, in bytes, which will be scanned.
+		</para>
+		<para>If this option is not set, the default is 100MB.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:min file size = 10</term>
+		<listitem>
+		<para>This is the largest sized file, in bytes, which will be scanned.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:infected file action = nothing</term>
+		<listitem>
+		<para>What to do with an infected file. The options are
+		nothing, quarantine, rename, delete.</para>
+		<para>If this option is not set, the default is nothing.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:infected file errno on close = 0</term>
+		<listitem>
+		<para>What errno to return on close if the file is infected.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:infected file errno on open = EACCES</term>
+		<listitem>
+		<para>What errno to return on close if the file is infected.
+		</para>
+		<para>If this option is not set, the default is EACCES.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:quarantine directory  = PATH</term>
+		<listitem>
+		<para>Where to move infected files. This is relative to, and
+		must be found in, the directory in which the file is found.</para>
+		<para>If this option is not set, the default is ".quarantine".
+		</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:quarantine prefix = virusfilter.</term>
+		<listitem>
+		<para>Prefix for quarantined files.</para>
+		<para>If this option is not set, the default is "virusfilter.".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:quarantine suffix = .infected</term>
+		<listitem>
+		<para>Suffix for quarantined files.
+		This option is only used if keep name is true. Otherwise it is ignored.</para>
+		<para>If this option is not set, the default is ".infected".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:rename prefix = virusfilter.</term>
+		<listitem>
+		<para>Prefix for infected files.</para>
+		<para>If this option is not set, the default is "virusfilter.".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:rename suffix = .infected</term>
+		<listitem>
+		<para>Suffix for infected files.</para>
+		<para>If this option is not set, the default is ".infected".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:quarantine keep name = yes</term>
+		<listitem>
+		<para>Should the file name be left unmodified other than adding a suffix
+		and/or prefix and a random suffix name as defined in virusfilter_fsav:rename prefix
+		and virusfilter_fsav:rename suffix.</para>
+		<para>If this option is not set, the default is no.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:infected file command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --cc "%U at example.com" --from samba at example.com --subject-prefix "Samba: Infected File: "</term>
+		<listitem>
+		<para>External command to run on an infected file is found.</para>
+		<para>If this option is not set, the default is none.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:scan archive = true</term>
+		<listitem>
+		<para>This defines whether or not to scan archives.</para>
+		<para>The f-sav module supports this and defaults to false.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:max nested scan archive = 1</term>
+		<listitem>
+		<para>This defines the maximum depth to search nested archives.</para>
+		<para>f-secure module supports this and defaults to 1.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:scan mime = true</term>
+		<listitem>
+		<para>This defines whether or not to scan mime files.</para>
+		<para>f-secure module supports this and defaults to false.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --from samba at example.com --subject-prefix "Samba: Scan Error: "</term>
+		<listitem>
+		<para>External command to run on scan error.</para>
+		<para>If this option is not set, the default is none.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:exclude files = empty</term>
+		<listitem>
+		<para>Files to exclude from scanning.</para>
+		<para>If this option is not set, the default is empty.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:block access on error = false</term>
+		<listitem>
+		<para>Controls whether or not access should be blocked on
+		a scanning error.</para>
+		<para>If this option is not set, the default is false.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:scan error errno on close = 0</term>
+		<listitem>
+		<para>What errno to return on close if there is an error in
+		scanning the file and block access on error is true.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:scan error errno on open = EACCES</term>
+		<listitem>
+		<para>What errno to return on open if there is an error in
+		scanning the file and block access on error is true.
+		</para>
+		<para>If this option is not set, the default is EACCES.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:cache entry limit = 100</term>
+		<listitem>
+		<para>The maximum number of entries in the scanning results
+		cache. Due to how Samba's memcache works, this is approximate.</para>
+		<para>If this option is not set, the default is 100.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:cache time limit = 10</term>
+		<listitem>
+		<para>The maximum number of seconds that a scanning result
+		will stay in the results cache. -1 disables the limit.
+		0 disables caching.</para>
+		<para>If this option is not set, the default is 10.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:quarantine directory mode = 700</term>
+		<listitem>
+		<para>This is the octet mode for the quarantine directory and
+		its subdirectories as they are created.</para>
+		<para>If this option is not set, the default is 700 or S_IRUSR | S_IWUSR | S_IXUSR.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_fsav:block suspected file = false</term>
+		<listitem>
+		<para>With this option on, suspected malware will be blocked as well.</para>
+		<para>If this option is not set, the default is false.</para>
+		</listitem>
+		</varlistentry>
+
+	</variablelist>
+</refsect1>
+
+<refsect1>
+	<title>NOTES</title>
+
+	<para>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.</para>
+
+	<para>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).</para>
+</refsect1>
+
+<refsect1>
+	<title>VERSION</title>
+
+	<para>This man page is correct for version 4.7 of the Samba suite.
+	</para>
+</refsect1>
+
+<refsect1>
+	<title>AUTHOR</title>
+
+	<para>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.</para>
+
+</refsect1>
+
+</refentry>
diff --git a/source3/modules/vfs_virusfilter_fsav.c b/source3/modules/vfs_virusfilter_fsav.c
new file mode 100644
index 00000000000..51f0036bb01
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_fsav.c
@@ -0,0 +1,413 @@
+/*
+   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 <http://www.gnu.org/licenses/>.
+*/
+
+#define VIRUSFILTER_ENGINE fsav
+#define VIRUSFILTER_MODULE_ENGINE "fsav"
+
+/* Default values for standard "extra" configuration variables */
+#define VIRUSFILTER_DEFAULT_SOCKET_PATH			"/tmp/.fsav-0"
+#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT		30000 /* msec */
+#define VIRUSFILTER_DEFAULT_TIMEOUT			60000 /* msec */
+#define VIRUSFILTER_DEFAULT_SCAN_ARCHIVE		false
+#define VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE	1
+#define VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT		0
+#define VIRUSFILTER_DEFAULT_SCAN_MIME			false
+#define VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE	false
+
+/* 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
+
+#define VIRUSFILTER_MODULE_CONFIG_MEMBERS \
+	int fsav_protocol; \
+	bool scan_riskware; \
+	bool stop_scan_on_first; \
+	bool filter_filename; \
+	/* End of VIRUSFILTER_MODULE_CONFIG_MEMBERS */
+
+#define virusfilter_module_connect		virusfilter_fsav_connect
+#define virusfilter_module_destruct_config	\
+	virusfilter_fsav_destruct_config
+#define virusfilter_module_scan_init		virusfilter_fsav_scan_init
+#define virusfilter_module_scan_end		virusfilter_fsav_scan_end
+#define virusfilter_module_scan			virusfilter_fsav_scan
+
+#include "vfs_virusfilter_vfs.c"
+
+/* ====================================================================== */
+
+#include "vfs_virusfilter_utils.h"
+
+/* ====================================================================== */
+
+static int virusfilter_fsav_connect(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const char *svc,
+	const char *user)
+{
+	int snum = SNUM(vfs_h->conn);
+
+	virusfilter_h->fsav_protocol = lp_parm_int(snum,
+		VIRUSFILTER_MODULE_NAME, "fsav protocol",
+		VIRUSFILTER_DEFAULT_FSAV_PROTOCOL);
+
+	virusfilter_h->scan_riskware = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "scan riskware",
+		VIRUSFILTER_DEFAULT_SCAN_RISKWARE);
+
+	virusfilter_h->stop_scan_on_first = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "stop scan on first",
+		VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST);
+
+	virusfilter_h->filter_filename = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "filter filename",
+		VIRUSFILTER_DEFAULT_FILTER_FILENAME);
+
+	virusfilter_h->block_suspected_file = lp_parm_bool(snum,
+		VIRUSFILTER_MODULE_NAME, "block suspected file",
+		VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE);
+
+	return 0;
+}
+
+static int virusfilter_fsav_destruct_config(
+	struct virusfilter_handle *virusfilter_h)
+{
+	virusfilter_fsav_scan_end(virusfilter_h);
+
+	return 0;
+}
+
+static virusfilter_result virusfilter_fsav_scan_init(
+	struct virusfilter_handle *virusfilter_h)
+{
+	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	virusfilter_result result;
+	char *reply = NULL;
+
+	if (io_h->stream != NULL) {
+		DBG_DEBUG("fsavd: Checking if connection is alive\n");
+
+		/* FIXME: I don't know the correct PING command format... */
+		if (virusfilter_io_writefl_readl(io_h, &reply, "PING") ==
+		    true)
+		{
+			if (strncmp(reply, "ERROR\t", 6) == 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(virusfilter_h);
+	}
+
+	DBG_INFO("fsavd: Connecting to socket: %s\n",
+		 virusfilter_h->socket_path);
+
+	become_root();
+	result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+	unbecome_root();
+
+	if (result != true) {
+		DBG_ERR("fsavd: Connecting to socket failed: %s: %s\n",
+			virusfilter_h->socket_path, strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+		DBG_ERR("fsavd: Reading greeting message failed: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	if (strncmp(reply, "DBVERSION\t", 10) != 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);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply,
+	    "PROTOCOL\t%d", virusfilter_h->fsav_protocol)
+	    != true)
+	{
+		DBG_ERR("fsavd: PROTOCOL: I/O error: %s\n", strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	if (strncmp(reply, "OK\t", 3) != 0) {
+		DBG_ERR("fsavd: PROTOCOL: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply,
+	    "CONFIGURE\tSTOPONFIRST\t%d", virusfilter_h->stop_scan_on_first ?
+	    1 : 0) != true)
+	{
+		DBG_ERR("fsavd: CONFIGURE STOPONFIRST: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	if (strncmp(reply, "OK\t", 3) != 0) {
+		DBG_ERR("fsavd: CONFIGURE STOPONFIRST: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply,
+	    "CONFIGURE\tFILTER\t%d", virusfilter_h->filter_filename ? 1 : 0)
+	    != true)
+	{
+		DBG_ERR("fsavd: CONFIGURE FILTER: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	if (strncmp(reply, "OK\t", 3) != 0) {
+		DBG_ERR("fsavd: CONFIGURE FILTER: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply,
+	    "CONFIGURE\tARCHIVE\t%d", virusfilter_h->scan_archive ? 1 : 0)
+	    != true)
+	{
+		DBG_ERR("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	if (strncmp(reply, "OK\t", 3) != 0) {
+		DBG_ERR("fsavd: CONFIGURE ARCHIVE: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply,
+	    "CONFIGURE\tMAXARCH\t%d", virusfilter_h->max_nested_scan_archive)
+	    != true)
+	{
+		DBG_ERR("fsavd: CONFIGURE MAXARCH: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	if (strncmp(reply, "OK\t", 3) != 0) {
+		DBG_ERR("fsavd: CONFIGURE MAXARCH: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply,
+	    "CONFIGURE\tMIME\t%d", virusfilter_h->scan_mime ? 1 : 0)
+	    != true)
+	{
+		DBG_ERR("fsavd: CONFIGURE MIME: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	if (strncmp(reply, "OK\t", 3) != 0) {
+		DBG_ERR("fsavd: CONFIGURE MIME: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply,
+	    "CONFIGURE\tRISKWARE\t%d", virusfilter_h->scan_riskware ? 1 : 0)
+	    != true)
+	{
+		DBG_ERR("fsavd: CONFIGURE RISKWARE: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	if (strncmp(reply, "OK\t", 3) != 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(virusfilter_h);
+
+	return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_fsav_scan_end(
+	struct virusfilter_handle *virusfilter_h)
+{
+	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+
+	DBG_INFO("fsavd: Disconnecting\n");
+	virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_fsav_scan(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	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 = virusfilter_h->io_h;
+	virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+	char *report = NULL;
+	char *reply = NULL;
+	char *reply_token, *reply_saveptr;
+
+	DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+	if (virusfilter_io_writevl(io_h, "SCAN\t", 5, cwd_fname,
+	    (int)strlen(cwd_fname), "/", 1, fname, (int)strlen(fname), NULL)
+	    != true)
+	{
+		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<FILEPATH> */
+			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<FILEPATH>\t<REPORT>\t<ENGINE> */
+			result = VIRUSFILTER_RESULT_INFECTED;
+			reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+			reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+			if (reply_token) {
+				  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) &&
+			   virusfilter_h->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) {
+				  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<FILEPATH>\t0x<CODE>\t<REPORT> [<ENGINE>] */
+			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;
+}
-- 
2.13.6


From ccb5e2afeb2d077eec4165a4eb1bba786dd64309 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 22 Dec 2017 13:30:44 +0100
Subject: [PATCH 7/9] FIXUP fsav

---
 docs-xml/manpages/vfs_virusfilter.8.xml      |  27 ++-
 docs-xml/manpages/vfs_virusfilter_fsav.8.xml | 330 ---------------------------
 source3/modules/vfs_virusfilter.c            |   3 +
 source3/modules/vfs_virusfilter_common.h     |   1 +
 source3/modules/vfs_virusfilter_fsav.c       | 194 +++++++++-------
 source3/modules/wscript_build                |   1 +
 6 files changed, 141 insertions(+), 415 deletions(-)
 delete mode 100644 docs-xml/manpages/vfs_virusfilter_fsav.8.xml

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index 88738718250..dd9f6dba46f 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -44,6 +44,8 @@
 		<itemizedlist>
 		  <listitem><para><emphasis>sophos</emphasis>, the Sophos AV
 		  scanner</para></listitem>
+		  <listitem><para><emphasis>fsav</emphasis>, the F-Secure AV
+		  scanner</para></listitem>
 		</itemizedlist>
 		</listitem>
 		</varlistentry>
@@ -58,6 +60,8 @@
 		</para>
 		<para>For the <emphasis>sophos</emphasis>backend the default is
 		<emphasis>/var/run/savdi/sssp.sock</emphasis>.</para>
+		<para>For the <emphasis>fsav</emphasis> backend the default is
+		<emphasis>/tmp/.fsav-0</emphasis>.</para>
 		</listitem>
 		</varlistentry>
 
@@ -209,7 +213,7 @@
 		<term>virusfilter:scan archive = true</term>
 		<listitem>
 		<para>This defines whether or not to scan archives.</para>
-		<para>Sophos supports this and defaults to false.</para>
+		<para>Sophos and F-Secure support this and it defaults to false.</para>
 		</listitem>
 		</varlistentry>
 
@@ -217,7 +221,16 @@
 		<term>virusfilter:max nested scan archive = 1</term>
 		<listitem>
 		<para>This defines the maximum depth to search nested archives.</para>
-		<para>The Sophos module supports this and defaults to 1.</para>
+		<para>The Sophos and F-Secure support this and it defaults to 1.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter:scan mime = true</term>
+		<listitem>
+		<para>This defines whether or not to scan mime files.</para>
+		<para>Only the <emphasis>fsav</emphasis>scanner supports this
+		option and defaults to false.</para>
 		</listitem>
 		</varlistentry>
 
@@ -294,6 +307,16 @@
 		</listitem>
 		</varlistentry>
 
+		<varlistentry>
+		<term>virusfilter:block suspected file = false</term>
+		<listitem>
+		<para>With this option on, suspected malware will be blocked as
+		well. Only the <emphasis>fsav</emphasis>scanner supports this
+		option.</para>
+		<para>If this option is not set, the default is false.</para>
+		</listitem>
+		</varlistentry>
+
 	</variablelist>
 </refsect1>
 
diff --git a/docs-xml/manpages/vfs_virusfilter_fsav.8.xml b/docs-xml/manpages/vfs_virusfilter_fsav.8.xml
deleted file mode 100644
index 889709215f0..00000000000
--- a/docs-xml/manpages/vfs_virusfilter_fsav.8.xml
+++ /dev/null
@@ -1,330 +0,0 @@
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!DOCTYPE refentry PUBLIC "-//Samba-Team//DTD DocBook V4.2-Based Variant V1.0//EN" "http://www.samba.org/samba/DTD/samba-doc">
-<refentry id="vfs_virusfilter_fsav.8">
-
-<refmeta>
-	<refentrytitle>vfs_virusfilter_fsav</refentrytitle>
-	<manvolnum>8</manvolnum>
-	<refmiscinfo class="source">Samba</refmiscinfo>
-	<refmiscinfo class="manual">System Administration tools</refmiscinfo>
-	<refmiscinfo class="version">4.6</refmiscinfo>
-</refmeta>
-
-
-<refnamediv>
-	<refname>vfs_virusfilter_fsav</refname>
-	<refpurpose>On access virus scanner</refpurpose>
-</refnamediv>
-
-<refsynopsisdiv>
-	<cmdsynopsis>
-		<command>vfs objects = virusfilter_fsav</command>
-	</cmdsynopsis>
-</refsynopsisdiv>
-
-<refsect1>
-	<title>DESCRIPTION</title>
-
-	<para>This is a set of various Samba VFS modules to scan and filter
-	virus files on Samba file services with an anti-virus scanner.</para>
-
-	<para>This module is stackable.</para>
-
-</refsect1>
-
-<refsect1>
-	<title>OPTIONS</title>
-
-	<variablelist>
-
-		<varlistentry>
-		<term>virusfilter_fsav:socket path = PATH</term>
-		<listitem>
-		<para>Path of local socket for the virus scanner.
-		</para>
-		<para>If this option is not set, the default path /tmp/.fsav-0.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:connect timeout = 30000</term>
-		<listitem>
-		<para>Controls how long to wait on connecting to the virus
-		scanning process before timing out. Value is in milliseconds.
-		</para>
-		<para>If this option is not set, the default is 30000.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:io timeout = 60000</term>
-		<listitem>
-		<para>Controls how long to wait on communications with the virus
-		scanning process before timing out. Value is in milliseconds.
-		</para>
-		<para>If this option is not set, the default is 60000.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:scan on open = yes</term>
-		<listitem>
-		<para>This option controls whether files are scanned on open.
-		</para>
-		<para>If this option is not set, the default is yes.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:scan on close = no</term>
-		<listitem>
-		<para>This option controls whether files are scanned on open.
-		</para>
-		<para>If this option is not set, the default is no.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:max file size = 100000000</term>
-		<listitem>
-		<para>This is the largest sized file, in bytes, which will be scanned.
-		</para>
-		<para>If this option is not set, the default is 100MB.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:min file size = 10</term>
-		<listitem>
-		<para>This is the largest sized file, in bytes, which will be scanned.
-		</para>
-		<para>If this option is not set, the default is 0.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:infected file action = nothing</term>
-		<listitem>
-		<para>What to do with an infected file. The options are
-		nothing, quarantine, rename, delete.</para>
-		<para>If this option is not set, the default is nothing.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:infected file errno on close = 0</term>
-		<listitem>
-		<para>What errno to return on close if the file is infected.
-		</para>
-		<para>If this option is not set, the default is 0.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:infected file errno on open = EACCES</term>
-		<listitem>
-		<para>What errno to return on close if the file is infected.
-		</para>
-		<para>If this option is not set, the default is EACCES.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:quarantine directory  = PATH</term>
-		<listitem>
-		<para>Where to move infected files. This is relative to, and
-		must be found in, the directory in which the file is found.</para>
-		<para>If this option is not set, the default is ".quarantine".
-		</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:quarantine prefix = virusfilter.</term>
-		<listitem>
-		<para>Prefix for quarantined files.</para>
-		<para>If this option is not set, the default is "virusfilter.".</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:quarantine suffix = .infected</term>
-		<listitem>
-		<para>Suffix for quarantined files.
-		This option is only used if keep name is true. Otherwise it is ignored.</para>
-		<para>If this option is not set, the default is ".infected".</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:rename prefix = virusfilter.</term>
-		<listitem>
-		<para>Prefix for infected files.</para>
-		<para>If this option is not set, the default is "virusfilter.".</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:rename suffix = .infected</term>
-		<listitem>
-		<para>Suffix for infected files.</para>
-		<para>If this option is not set, the default is ".infected".</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:quarantine keep name = yes</term>
-		<listitem>
-		<para>Should the file name be left unmodified other than adding a suffix
-		and/or prefix and a random suffix name as defined in virusfilter_fsav:rename prefix
-		and virusfilter_fsav:rename suffix.</para>
-		<para>If this option is not set, the default is no.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:infected file command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --cc "%U at example.com" --from samba at example.com --subject-prefix "Samba: Infected File: "</term>
-		<listitem>
-		<para>External command to run on an infected file is found.</para>
-		<para>If this option is not set, the default is none.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:scan archive = true</term>
-		<listitem>
-		<para>This defines whether or not to scan archives.</para>
-		<para>The f-sav module supports this and defaults to false.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:max nested scan archive = 1</term>
-		<listitem>
-		<para>This defines the maximum depth to search nested archives.</para>
-		<para>f-secure module supports this and defaults to 1.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:scan mime = true</term>
-		<listitem>
-		<para>This defines whether or not to scan mime files.</para>
-		<para>f-secure module supports this and defaults to false.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --from samba at example.com --subject-prefix "Samba: Scan Error: "</term>
-		<listitem>
-		<para>External command to run on scan error.</para>
-		<para>If this option is not set, the default is none.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:exclude files = empty</term>
-		<listitem>
-		<para>Files to exclude from scanning.</para>
-		<para>If this option is not set, the default is empty.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:block access on error = false</term>
-		<listitem>
-		<para>Controls whether or not access should be blocked on
-		a scanning error.</para>
-		<para>If this option is not set, the default is false.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:scan error errno on close = 0</term>
-		<listitem>
-		<para>What errno to return on close if there is an error in
-		scanning the file and block access on error is true.
-		</para>
-		<para>If this option is not set, the default is 0.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:scan error errno on open = EACCES</term>
-		<listitem>
-		<para>What errno to return on open if there is an error in
-		scanning the file and block access on error is true.
-		</para>
-		<para>If this option is not set, the default is EACCES.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:cache entry limit = 100</term>
-		<listitem>
-		<para>The maximum number of entries in the scanning results
-		cache. Due to how Samba's memcache works, this is approximate.</para>
-		<para>If this option is not set, the default is 100.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:cache time limit = 10</term>
-		<listitem>
-		<para>The maximum number of seconds that a scanning result
-		will stay in the results cache. -1 disables the limit.
-		0 disables caching.</para>
-		<para>If this option is not set, the default is 10.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:quarantine directory mode = 700</term>
-		<listitem>
-		<para>This is the octet mode for the quarantine directory and
-		its subdirectories as they are created.</para>
-		<para>If this option is not set, the default is 700 or S_IRUSR | S_IWUSR | S_IXUSR.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_fsav:block suspected file = false</term>
-		<listitem>
-		<para>With this option on, suspected malware will be blocked as well.</para>
-		<para>If this option is not set, the default is false.</para>
-		</listitem>
-		</varlistentry>
-
-	</variablelist>
-</refsect1>
-
-<refsect1>
-	<title>NOTES</title>
-
-	<para>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.</para>
-
-	<para>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).</para>
-</refsect1>
-
-<refsect1>
-	<title>VERSION</title>
-
-	<para>This man page is correct for version 4.7 of the Samba suite.
-	</para>
-</refsect1>
-
-<refsect1>
-	<title>AUTHOR</title>
-
-	<para>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.</para>
-
-</refsect1>
-
-</refentry>
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index 10462bd40e6..f7c78a6b715 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -416,6 +416,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 0278d42b710..48a2029703a 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -146,5 +146,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
index 51f0036bb01..d39e8a8d174 100644
--- a/source3/modules/vfs_virusfilter_fsav.c
+++ b/source3/modules/vfs_virusfilter_fsav.c
@@ -17,93 +17,96 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#define VIRUSFILTER_ENGINE fsav
-#define VIRUSFILTER_MODULE_ENGINE "fsav"
-
-/* Default values for standard "extra" configuration variables */
-#define VIRUSFILTER_DEFAULT_SOCKET_PATH			"/tmp/.fsav-0"
-#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT		30000 /* msec */
-#define VIRUSFILTER_DEFAULT_TIMEOUT			60000 /* msec */
-#define VIRUSFILTER_DEFAULT_SCAN_ARCHIVE		false
-#define VIRUSFILTER_DEFAULT_MAX_NESTED_SCAN_ARCHIVE	1
-#define VIRUSFILTER_DEFAULT_SCAN_REQUEST_LIMIT		0
-#define VIRUSFILTER_DEFAULT_SCAN_MIME			false
-#define VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE	false
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+#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
 
 /* 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
 
-#define VIRUSFILTER_MODULE_CONFIG_MEMBERS \
-	int fsav_protocol; \
-	bool scan_riskware; \
-	bool stop_scan_on_first; \
-	bool filter_filename; \
-	/* End of VIRUSFILTER_MODULE_CONFIG_MEMBERS */
-
-#define virusfilter_module_connect		virusfilter_fsav_connect
-#define virusfilter_module_destruct_config	\
-	virusfilter_fsav_destruct_config
-#define virusfilter_module_scan_init		virusfilter_fsav_scan_init
-#define virusfilter_module_scan_end		virusfilter_fsav_scan_end
-#define virusfilter_module_scan			virusfilter_fsav_scan
-
-#include "vfs_virusfilter_vfs.c"
+struct virusfilter_fsav_config {
+	/* Backpointer */
+	struct virusfilter_config *config;
 
-/* ====================================================================== */
+	int fsav_protocol;
+	bool scan_riskware;
+	bool stop_scan_on_first;
+	bool filter_filename;
+};
 
-#include "vfs_virusfilter_utils.h"
+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 *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
 	const char *svc,
 	const char *user)
 {
-	int snum = SNUM(vfs_h->conn);
+	int snum = SNUM(handle->conn);
+	struct virusfilter_fsav_config *fsav_config = NULL;
 
-	virusfilter_h->fsav_protocol = lp_parm_int(snum,
-		VIRUSFILTER_MODULE_NAME, "fsav protocol",
+	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);
 
-	virusfilter_h->scan_riskware = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "scan riskware",
+	fsav_config->scan_riskware = lp_parm_bool(
+		snum, "virusfilter", "scan riskware",
 		VIRUSFILTER_DEFAULT_SCAN_RISKWARE);
 
-	virusfilter_h->stop_scan_on_first = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "stop scan on first",
+	fsav_config->stop_scan_on_first = lp_parm_bool(
+		snum, "virusfilter", "stop scan on first",
 		VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST);
 
-	virusfilter_h->filter_filename = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "filter filename",
+	fsav_config->filter_filename = lp_parm_bool(
+		snum, "virusfilter", "filter filename",
 		VIRUSFILTER_DEFAULT_FILTER_FILENAME);
 
-	virusfilter_h->block_suspected_file = lp_parm_bool(snum,
-		VIRUSFILTER_MODULE_NAME, "block suspected file",
-		VIRUSFILTER_DEFAULT_BLOCK_SUSPECTED_FILE);
+	talloc_set_destructor(fsav_config, virusfilter_fsav_destruct_config);
 
-	return 0;
-}
+	config->backend->backend_private = fsav_config;
 
-static int virusfilter_fsav_destruct_config(
-	struct virusfilter_handle *virusfilter_h)
-{
-	virusfilter_fsav_scan_end(virusfilter_h);
+	config->block_suspected_file = lp_parm_bool(
+		snum, "virusfilter", "block suspected file", false);
 
 	return 0;
 }
 
 static virusfilter_result virusfilter_fsav_scan_init(
-	struct virusfilter_handle *virusfilter_h)
+	struct virusfilter_config *config)
 {
-	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	struct virusfilter_fsav_config *fsav_config = NULL;
+	struct virusfilter_io_handle *io_h = config->io_h;
 	virusfilter_result result;
 	char *reply = NULL;
+	bool ok;
+
+	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");
@@ -120,19 +123,19 @@ static virusfilter_result virusfilter_fsav_scan_init(
 		}
 
 		DBG_DEBUG("fsavd: Closing dead connection\n");
-		virusfilter_fsav_scan_end(virusfilter_h);
+		virusfilter_fsav_scan_end(config);
 	}
 
 	DBG_INFO("fsavd: Connecting to socket: %s\n",
-		 virusfilter_h->socket_path);
+		 config->socket_path);
 
 	become_root();
-	result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+	result = virusfilter_io_connect_path(io_h, config->socket_path);
 	unbecome_root();
 
 	if (result != true) {
 		DBG_ERR("fsavd: Connecting to socket failed: %s: %s\n",
-			virusfilter_h->socket_path, strerror(errno));
+			config->socket_path, strerror(errno));
 		goto virusfilter_fsav_init_failed;
 	}
 
@@ -155,10 +158,9 @@ static virusfilter_result virusfilter_fsav_scan_init(
 
 	TALLOC_FREE(reply);
 
-	if (virusfilter_io_writefl_readl(io_h, &reply,
-	    "PROTOCOL\t%d", virusfilter_h->fsav_protocol)
-	    != true)
-	{
+	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;
 	}
@@ -170,10 +172,11 @@ static virusfilter_result virusfilter_fsav_scan_init(
 
 	TALLOC_FREE(reply);
 
-	if (virusfilter_io_writefl_readl(io_h, &reply,
-	    "CONFIGURE\tSTOPONFIRST\t%d", virusfilter_h->stop_scan_on_first ?
-	    1 : 0) != true)
-	{
+	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;
@@ -186,10 +189,9 @@ static virusfilter_result virusfilter_fsav_scan_init(
 
 	TALLOC_FREE(reply);
 
-	if (virusfilter_io_writefl_readl(io_h, &reply,
-	    "CONFIGURE\tFILTER\t%d", virusfilter_h->filter_filename ? 1 : 0)
-	    != true)
-	{
+	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;
@@ -203,7 +205,7 @@ static virusfilter_result virusfilter_fsav_scan_init(
 	TALLOC_FREE(reply);
 
 	if (virusfilter_io_writefl_readl(io_h, &reply,
-	    "CONFIGURE\tARCHIVE\t%d", virusfilter_h->scan_archive ? 1 : 0)
+	    "CONFIGURE\tARCHIVE\t%d", config->scan_archive ? 1 : 0)
 	    != true)
 	{
 		DBG_ERR("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n",
@@ -219,7 +221,7 @@ static virusfilter_result virusfilter_fsav_scan_init(
 	TALLOC_FREE(reply);
 
 	if (virusfilter_io_writefl_readl(io_h, &reply,
-	    "CONFIGURE\tMAXARCH\t%d", virusfilter_h->max_nested_scan_archive)
+	    "CONFIGURE\tMAXARCH\t%d", config->max_nested_scan_archive)
 	    != true)
 	{
 		DBG_ERR("fsavd: CONFIGURE MAXARCH: I/O error: %s\n",
@@ -235,7 +237,7 @@ static virusfilter_result virusfilter_fsav_scan_init(
 	TALLOC_FREE(reply);
 
 	if (virusfilter_io_writefl_readl(io_h, &reply,
-	    "CONFIGURE\tMIME\t%d", virusfilter_h->scan_mime ? 1 : 0)
+	    "CONFIGURE\tMIME\t%d", config->scan_mime ? 1 : 0)
 	    != true)
 	{
 		DBG_ERR("fsavd: CONFIGURE MIME: I/O error: %s\n",
@@ -250,10 +252,9 @@ static virusfilter_result virusfilter_fsav_scan_init(
 
 	TALLOC_FREE(reply);
 
-	if (virusfilter_io_writefl_readl(io_h, &reply,
-	    "CONFIGURE\tRISKWARE\t%d", virusfilter_h->scan_riskware ? 1 : 0)
-	    != true)
-	{
+	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;
@@ -272,29 +273,28 @@ virusfilter_fsav_init_succeed:
 
 virusfilter_fsav_init_failed:
 	TALLOC_FREE(reply);
-	virusfilter_fsav_scan_end(virusfilter_h);
+	virusfilter_fsav_scan_end(config);
 
 	return VIRUSFILTER_RESULT_ERROR;
 }
 
-static void virusfilter_fsav_scan_end(
-	struct virusfilter_handle *virusfilter_h)
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config)
 {
-	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	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 *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	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 = virusfilter_h->io_h;
+	struct virusfilter_io_handle *io_h = config->io_h;
 	virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
 	char *report = NULL;
 	char *reply = NULL;
@@ -364,7 +364,7 @@ static virusfilter_result virusfilter_fsav_scan(
 		} else if ((strcmp(reply_token, "SUSPECTED") == 0 ||
 			   strcmp(reply_token, "ARCHIVE_SUSPECTED") == 0 ||
 			   strcmp(reply_token, "MIME_SUSPECTED") == 0) &&
-			   virusfilter_h->block_suspected_file)
+			   config->block_suspected_file)
 		{
 			result = VIRUSFILTER_RESULT_SUSPECTED;
 			reply_token = strtok_r(NULL, "\t", &reply_saveptr);
@@ -411,3 +411,31 @@ virusfilter_fsav_scan_return:
 
 	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 caaaa0c1730..10f90b0a6a5 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 fd27793924dc103659215b105df61738e5064ab0 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:40:01 -0600
Subject: [PATCH 8/9] Samba-VirusFilter: clamav VFS and man page.

Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas at osstech.co.jp>
---
 docs-xml/manpages/vfs_virusfilter_clamav.8.xml | 298 +++++++++++++++++++++++++
 source3/modules/vfs_virusfilter_clamav.c       | 181 +++++++++++++++
 2 files changed, 479 insertions(+)
 create mode 100644 docs-xml/manpages/vfs_virusfilter_clamav.8.xml
 create mode 100644 source3/modules/vfs_virusfilter_clamav.c

diff --git a/docs-xml/manpages/vfs_virusfilter_clamav.8.xml b/docs-xml/manpages/vfs_virusfilter_clamav.8.xml
new file mode 100644
index 00000000000..3fae9ee9471
--- /dev/null
+++ b/docs-xml/manpages/vfs_virusfilter_clamav.8.xml
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//Samba-Team//DTD DocBook V4.2-Based Variant V1.0//EN" "http://www.samba.org/samba/DTD/samba-doc">
+<refentry id="vfs_virusfilter_clamav.8">
+
+<refmeta>
+	<refentrytitle>vfs_virusfilter_clamav</refentrytitle>
+	<manvolnum>8</manvolnum>
+	<refmiscinfo class="source">Samba</refmiscinfo>
+	<refmiscinfo class="manual">System Administration tools</refmiscinfo>
+	<refmiscinfo class="version">4.6</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+	<refname>vfs_virusfilter_clamav</refname>
+	<refpurpose>On access virus scanner</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+	<cmdsynopsis>
+		<command>vfs objects = virusfilter_clamav</command>
+	</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+	<title>DESCRIPTION</title>
+
+	<para>This is a set of various Samba VFS modules to scan and filter
+	virus files on Samba file services with an anti-virus scanner.</para>
+
+	<para>This module is stackable.</para>
+
+</refsect1>
+
+<refsect1>
+	<title>OPTIONS</title>
+
+	<variablelist>
+
+		<varlistentry>
+		<term>virusfilter_clamav:socket path = PATH</term>
+		<listitem>
+		<para>Path of local socket for the virus scanner.
+		</para>
+		<para>If this option is not set, the default path /var/run/clamav/clamd.ctl.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:connect timeout = 30000</term>
+		<listitem>
+		<para>Controls how long to wait on connecting to the virus
+		scanning process before timing out. Value is in milliseconds.
+		</para>
+		<para>If this option is not set, the default is 30000.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:io timeout = 60000</term>
+		<listitem>
+		<para>Controls how long to wait on communications with the virus
+		scanning process before timing out. Value is in milliseconds.
+		</para>
+		<para>If this option is not set, the default is 60000.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:scan on open = yes</term>
+		<listitem>
+		<para>This option controls whether files are scanned on open.
+		</para>
+		<para>If this option is not set, the default is yes.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:scan on close = no</term>
+		<listitem>
+		<para>This option controls whether files are scanned on open.
+		</para>
+		<para>If this option is not set, the default is no.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:max file size = 100000000</term>
+		<listitem>
+		<para>This is the largest sized file, in bytes, which will be scanned.
+		</para>
+		<para>If this option is not set, the default is 100MB.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:min file size = 10</term>
+		<listitem>
+		<para>This is the largest sized file, in bytes, which will be scanned.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:infected file action = nothing</term>
+		<listitem>
+		<para>What to do with an infected file. The options are
+		nothing, quarantine, rename, delete.</para>
+		<para>If this option is not set, the default is nothing.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:infected file errno on close = 0</term>
+		<listitem>
+		<para>What errno to return on close if the file is infected.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:infected file errno on open = EACCES</term>
+		<listitem>
+		<para>What errno to return on close if the file is infected.
+		</para>
+		<para>If this option is not set, the default is EACCES.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:quarantine directory  = PATH</term>
+		<listitem>
+		<para>Where to move infected files. This is relative to, and
+		must be found in, the directory in which the file is found.</para>
+		<para>If this option is not set, the default is ".quarantine".
+		</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:quarantine prefix = virusfilter.</term>
+		<listitem>
+		<para>Prefix for quarantined files.</para>
+		<para>If this option is not set, the default is "virusfilter.".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:quarantine suffix = .infected</term>
+		<listitem>
+		<para>Suffix for quarantined files.
+		This option is only used if keep name is true. Otherwise it is ignored.</para>
+		<para>If this option is not set, the default is ".infected".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:rename prefix = virusfilter.</term>
+		<listitem>
+		<para>Prefix for infected files.</para>
+		<para>If this option is not set, the default is "virusfilter.".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:rename suffix = .infected</term>
+		<listitem>
+		<para>Suffix for infected files.</para>
+		<para>If this option is not set, the default is ".infected".</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:quarantine keep name = yes</term>
+		<listitem>
+		<para>Should the file name be left unmodified other than adding a suffix
+		and/or prefix and a random suffix name as defined in virusfilter_clamav:rename prefix
+		and virusfilter_clamv:rename suffix.</para>
+		<para>If this option is not set, the default is no.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:infected file command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --cc "%U at example.com" --from samba at example.com --subject-prefix "Samba: Infected File: "</term>
+		<listitem>
+		<para>External command to run on an infected file is found.</para>
+		<para>If this option is not set, the default is none.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --from samba at example.com --subject-prefix "Samba: Scan Error: "</term>
+		<listitem>
+		<para>External command to run on scan error.</para>
+		<para>If this option is not set, the default is none.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:exclude files = empty</term>
+		<listitem>
+		<para>Files to exclude from scanning.</para>
+		<para>If this option is not set, the default is empty.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:block access on error = false</term>
+		<listitem>
+		<para>Controls whether or not access should be blocked on
+		a scanning error.</para>
+		<para>If this option is not set, the default is false.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:scan error errno on close = 0</term>
+		<listitem>
+		<para>What errno to return on close if there is an error in
+		scanning the file and block access on error is true.
+		</para>
+		<para>If this option is not set, the default is 0.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:scan error errno on open = EACCES</term>
+		<listitem>
+		<para>What errno to return on open if there is an error in
+		scanning the file and block access on error is true.
+		</para>
+		<para>If this option is not set, the default is EACCES.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:cache entry limit = 100</term>
+		<listitem>
+		<para>The maximum number of entries in the scanning results
+		cache. Due to how Samba's memcache works, this is approximate.</para>
+		<para>If this option is not set, the default is 100.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:cache time limit = 10</term>
+		<listitem>
+		<para>The maximum number of seconds that a scanning result
+		will stay in the results cache. -1 disables the limit.
+		0 disables caching.</para>
+		<para>If this option is not set, the default is 10.</para>
+		</listitem>
+		</varlistentry>
+
+		<varlistentry>
+		<term>virusfilter_clamav:quarantine directory mode = 700</term>
+		<listitem>
+		<para>This is the octet mode for the quarantine directory and
+		its subdirectories as they are created.</para>
+		<para>If this option is not set, the default is 700 or S_IRUSR | S_IWUSR | S_IXUSR.</para>
+		</listitem>
+		</varlistentry>
+
+	</variablelist>
+</refsect1>
+
+<refsect1>
+	<title>NOTES</title>
+
+	<para>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.</para>
+
+	<para>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).</para>
+</refsect1>
+
+<refsect1>
+	<title>VERSION</title>
+
+	<para>This man page is correct for version 4.7 of the Samba suite.
+	</para>
+</refsect1>
+
+<refsect1>
+	<title>AUTHOR</title>
+
+	<para>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.</para>
+
+</refsect1>
+
+</refentry>
diff --git a/source3/modules/vfs_virusfilter_clamav.c b/source3/modules/vfs_virusfilter_clamav.c
new file mode 100644
index 00000000000..594b3f6e5c4
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_clamav.c
@@ -0,0 +1,181 @@
+/*
+   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 <http://www.gnu.org/licenses/>.
+*/
+
+#define VIRUSFILTER_ENGINE clamav
+#define VIRUSFILTER_MODULE_ENGINE "clamav"
+
+/* 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
+#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT	30000 /* msec */
+#define VIRUSFILTER_DEFAULT_TIMEOUT		60000 /* msec */
+
+#define virusfilter_module_connect		virusfilter_clamav_connect
+#define virusfilter_module_scan_init		virusfilter_clamav_scan_init
+#define virusfilter_module_scan_end		virusfilter_clamav_scan_end
+#define virusfilter_module_scan			virusfilter_clamav_scan
+
+#include "modules/vfs_virusfilter_vfs.c"
+
+/* ====================================================================== */
+
+#include "modules/vfs_virusfilter_utils.h"
+
+/* ====================================================================== */
+
+static int virusfilter_clamav_connect(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	const char *svc,
+	const char *user)
+{
+
+	/* To use clamd "zXXXX" commands */
+	virusfilter_io_set_writel_eol(virusfilter_h->io_h, "\0", 1);
+	virusfilter_io_set_readl_eol(virusfilter_h->io_h, "\0", 1);
+
+	return 0;
+}
+
+static virusfilter_result virusfilter_clamav_scan_init(
+	struct virusfilter_handle *virusfilter_h)
+{
+	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	virusfilter_result result;
+
+	DBG_INFO("clamd: Connecting to socket: %s\n",
+		 virusfilter_h->socket_path);
+
+	become_root();
+	result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+	unbecome_root();
+
+	if (result != VIRUSFILTER_RESULT_OK) {
+		DBG_ERR("clamd: Connecting to socket failed: %s: %s\n",
+			virusfilter_h->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_handle *virusfilter_h)
+{
+	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+
+	DBG_INFO("clamd: Disconnecting\n");
+
+	virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_clamav_scan(
+	struct vfs_handle_struct *vfs_h,
+	struct virusfilter_handle *virusfilter_h,
+	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 = virusfilter_h->io_h;
+	virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+	char *report = NULL;
+	char *reply = NULL;
+	char *reply_msg = NULL;
+	char *reply_token;
+
+	DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+	if (virusfilter_io_writefl_readl(io_h, &reply, "zSCAN %s/%s",
+	    cwd_fname, fname) != true)
+	{
+		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) {
+		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) {
+
+		/* <FILEPATH>: OK */
+		result = VIRUSFILTER_RESULT_CLEAN;
+		report = talloc_asprintf(talloc_tos(), "Clean");
+	} else if (strcmp(reply_token, "FOUND") == 0) {
+
+		/* <FILEPATH>: <REPORT> FOUND */
+		result = VIRUSFILTER_RESULT_INFECTED;
+		report = talloc_strdup(talloc_tos(), reply_msg);
+	} else if (strcmp(reply_token, "ERROR") == 0) {
+
+		/* <FILEPATH>: <REPORT> 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;
+}
-- 
2.13.6


From 9bafcc1b905e8e79850599f0e66eb4689b4943a9 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 22 Dec 2017 14:37:53 +0100
Subject: [PATCH 9/9] FIXUP clamav

---
 docs-xml/manpages/vfs_virusfilter.8.xml        |   4 +
 docs-xml/manpages/vfs_virusfilter_clamav.8.xml | 298 -------------------------
 source3/modules/vfs_virusfilter.c              |   3 +
 source3/modules/vfs_virusfilter_clamav.c       |  79 ++++---
 source3/modules/vfs_virusfilter_common.h       |   1 +
 source3/modules/wscript_build                  |   1 +
 6 files changed, 55 insertions(+), 331 deletions(-)
 delete mode 100644 docs-xml/manpages/vfs_virusfilter_clamav.8.xml

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index dd9f6dba46f..736792b0feb 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -46,6 +46,8 @@
 		  scanner</para></listitem>
 		  <listitem><para><emphasis>fsav</emphasis>, the F-Secure AV
 		  scanner</para></listitem>
+		  <listitem><para><emphasis>clamav</emphasis>, the ClamAV
+		  scanner</para></listitem>
 		</itemizedlist>
 		</listitem>
 		</varlistentry>
@@ -62,6 +64,8 @@
 		<emphasis>/var/run/savdi/sssp.sock</emphasis>.</para>
 		<para>For the <emphasis>fsav</emphasis> backend the default is
 		<emphasis>/tmp/.fsav-0</emphasis>.</para>
+		<para>For the <emphasis>fsav</emphasis> backend the default is
+		<emphasis>/var/run/clamav/clamd.ctl</emphasis>.</para>
 		</listitem>
 		</varlistentry>
 
diff --git a/docs-xml/manpages/vfs_virusfilter_clamav.8.xml b/docs-xml/manpages/vfs_virusfilter_clamav.8.xml
deleted file mode 100644
index 3fae9ee9471..00000000000
--- a/docs-xml/manpages/vfs_virusfilter_clamav.8.xml
+++ /dev/null
@@ -1,298 +0,0 @@
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!DOCTYPE refentry PUBLIC "-//Samba-Team//DTD DocBook V4.2-Based Variant V1.0//EN" "http://www.samba.org/samba/DTD/samba-doc">
-<refentry id="vfs_virusfilter_clamav.8">
-
-<refmeta>
-	<refentrytitle>vfs_virusfilter_clamav</refentrytitle>
-	<manvolnum>8</manvolnum>
-	<refmiscinfo class="source">Samba</refmiscinfo>
-	<refmiscinfo class="manual">System Administration tools</refmiscinfo>
-	<refmiscinfo class="version">4.6</refmiscinfo>
-</refmeta>
-
-
-<refnamediv>
-	<refname>vfs_virusfilter_clamav</refname>
-	<refpurpose>On access virus scanner</refpurpose>
-</refnamediv>
-
-<refsynopsisdiv>
-	<cmdsynopsis>
-		<command>vfs objects = virusfilter_clamav</command>
-	</cmdsynopsis>
-</refsynopsisdiv>
-
-<refsect1>
-	<title>DESCRIPTION</title>
-
-	<para>This is a set of various Samba VFS modules to scan and filter
-	virus files on Samba file services with an anti-virus scanner.</para>
-
-	<para>This module is stackable.</para>
-
-</refsect1>
-
-<refsect1>
-	<title>OPTIONS</title>
-
-	<variablelist>
-
-		<varlistentry>
-		<term>virusfilter_clamav:socket path = PATH</term>
-		<listitem>
-		<para>Path of local socket for the virus scanner.
-		</para>
-		<para>If this option is not set, the default path /var/run/clamav/clamd.ctl.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:connect timeout = 30000</term>
-		<listitem>
-		<para>Controls how long to wait on connecting to the virus
-		scanning process before timing out. Value is in milliseconds.
-		</para>
-		<para>If this option is not set, the default is 30000.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:io timeout = 60000</term>
-		<listitem>
-		<para>Controls how long to wait on communications with the virus
-		scanning process before timing out. Value is in milliseconds.
-		</para>
-		<para>If this option is not set, the default is 60000.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:scan on open = yes</term>
-		<listitem>
-		<para>This option controls whether files are scanned on open.
-		</para>
-		<para>If this option is not set, the default is yes.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:scan on close = no</term>
-		<listitem>
-		<para>This option controls whether files are scanned on open.
-		</para>
-		<para>If this option is not set, the default is no.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:max file size = 100000000</term>
-		<listitem>
-		<para>This is the largest sized file, in bytes, which will be scanned.
-		</para>
-		<para>If this option is not set, the default is 100MB.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:min file size = 10</term>
-		<listitem>
-		<para>This is the largest sized file, in bytes, which will be scanned.
-		</para>
-		<para>If this option is not set, the default is 0.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:infected file action = nothing</term>
-		<listitem>
-		<para>What to do with an infected file. The options are
-		nothing, quarantine, rename, delete.</para>
-		<para>If this option is not set, the default is nothing.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:infected file errno on close = 0</term>
-		<listitem>
-		<para>What errno to return on close if the file is infected.
-		</para>
-		<para>If this option is not set, the default is 0.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:infected file errno on open = EACCES</term>
-		<listitem>
-		<para>What errno to return on close if the file is infected.
-		</para>
-		<para>If this option is not set, the default is EACCES.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:quarantine directory  = PATH</term>
-		<listitem>
-		<para>Where to move infected files. This is relative to, and
-		must be found in, the directory in which the file is found.</para>
-		<para>If this option is not set, the default is ".quarantine".
-		</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:quarantine prefix = virusfilter.</term>
-		<listitem>
-		<para>Prefix for quarantined files.</para>
-		<para>If this option is not set, the default is "virusfilter.".</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:quarantine suffix = .infected</term>
-		<listitem>
-		<para>Suffix for quarantined files.
-		This option is only used if keep name is true. Otherwise it is ignored.</para>
-		<para>If this option is not set, the default is ".infected".</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:rename prefix = virusfilter.</term>
-		<listitem>
-		<para>Prefix for infected files.</para>
-		<para>If this option is not set, the default is "virusfilter.".</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:rename suffix = .infected</term>
-		<listitem>
-		<para>Suffix for infected files.</para>
-		<para>If this option is not set, the default is ".infected".</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:quarantine keep name = yes</term>
-		<listitem>
-		<para>Should the file name be left unmodified other than adding a suffix
-		and/or prefix and a random suffix name as defined in virusfilter_clamav:rename prefix
-		and virusfilter_clamv:rename suffix.</para>
-		<para>If this option is not set, the default is no.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:infected file command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --cc "%U at example.com" --from samba at example.com --subject-prefix "Samba: Infected File: "</term>
-		<listitem>
-		<para>External command to run on an infected file is found.</para>
-		<para>If this option is not set, the default is none.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --from samba at example.com --subject-prefix "Samba: Scan Error: "</term>
-		<listitem>
-		<para>External command to run on scan error.</para>
-		<para>If this option is not set, the default is none.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:exclude files = empty</term>
-		<listitem>
-		<para>Files to exclude from scanning.</para>
-		<para>If this option is not set, the default is empty.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:block access on error = false</term>
-		<listitem>
-		<para>Controls whether or not access should be blocked on
-		a scanning error.</para>
-		<para>If this option is not set, the default is false.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:scan error errno on close = 0</term>
-		<listitem>
-		<para>What errno to return on close if there is an error in
-		scanning the file and block access on error is true.
-		</para>
-		<para>If this option is not set, the default is 0.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:scan error errno on open = EACCES</term>
-		<listitem>
-		<para>What errno to return on open if there is an error in
-		scanning the file and block access on error is true.
-		</para>
-		<para>If this option is not set, the default is EACCES.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:cache entry limit = 100</term>
-		<listitem>
-		<para>The maximum number of entries in the scanning results
-		cache. Due to how Samba's memcache works, this is approximate.</para>
-		<para>If this option is not set, the default is 100.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:cache time limit = 10</term>
-		<listitem>
-		<para>The maximum number of seconds that a scanning result
-		will stay in the results cache. -1 disables the limit.
-		0 disables caching.</para>
-		<para>If this option is not set, the default is 10.</para>
-		</listitem>
-		</varlistentry>
-
-		<varlistentry>
-		<term>virusfilter_clamav:quarantine directory mode = 700</term>
-		<listitem>
-		<para>This is the octet mode for the quarantine directory and
-		its subdirectories as they are created.</para>
-		<para>If this option is not set, the default is 700 or S_IRUSR | S_IWUSR | S_IXUSR.</para>
-		</listitem>
-		</varlistentry>
-
-	</variablelist>
-</refsect1>
-
-<refsect1>
-	<title>NOTES</title>
-
-	<para>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.</para>
-
-	<para>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).</para>
-</refsect1>
-
-<refsect1>
-	<title>VERSION</title>
-
-	<para>This man page is correct for version 4.7 of the Samba suite.
-	</para>
-</refsect1>
-
-<refsect1>
-	<title>AUTHOR</title>
-
-	<para>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.</para>
-
-</refsect1>
-
-</refentry>
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index f7c78a6b715..ac242b4084c 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -419,6 +419,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
index 594b3f6e5c4..f4c8019eb16 100644
--- a/source3/modules/vfs_virusfilter_clamav.c
+++ b/source3/modules/vfs_virusfilter_clamav.c
@@ -17,61 +17,46 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#define VIRUSFILTER_ENGINE clamav
-#define VIRUSFILTER_MODULE_ENGINE "clamav"
-
 /* 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
-#define VIRUSFILTER_DEFAULT_CONNECT_TIMEOUT	30000 /* msec */
-#define VIRUSFILTER_DEFAULT_TIMEOUT		60000 /* msec */
-
-#define virusfilter_module_connect		virusfilter_clamav_connect
-#define virusfilter_module_scan_init		virusfilter_clamav_scan_init
-#define virusfilter_module_scan_end		virusfilter_clamav_scan_end
-#define virusfilter_module_scan			virusfilter_clamav_scan
-
-#include "modules/vfs_virusfilter_vfs.c"
-
-/* ====================================================================== */
 
+#include "modules/vfs_virusfilter_common.h"
 #include "modules/vfs_virusfilter_utils.h"
 
-/* ====================================================================== */
-
-static int virusfilter_clamav_connect(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
-	const char *svc,
-	const char *user)
+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(virusfilter_h->io_h, "\0", 1);
-	virusfilter_io_set_readl_eol(virusfilter_h->io_h, "\0", 1);
+	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_handle *virusfilter_h)
+	struct virusfilter_config *config)
 {
-	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	struct virusfilter_io_handle *io_h = config->io_h;
 	virusfilter_result result;
 
 	DBG_INFO("clamd: Connecting to socket: %s\n",
-		 virusfilter_h->socket_path);
+		 config->socket_path);
 
 	become_root();
-	result = virusfilter_io_connect_path(io_h, virusfilter_h->socket_path);
+	result = virusfilter_io_connect_path(io_h, config->socket_path);
 	unbecome_root();
 
 	if (result != VIRUSFILTER_RESULT_OK) {
 		DBG_ERR("clamd: Connecting to socket failed: %s: %s\n",
-			virusfilter_h->socket_path, strerror(errno));
+			config->socket_path, strerror(errno));
 		return VIRUSFILTER_RESULT_ERROR;
 	}
 
@@ -81,9 +66,9 @@ static virusfilter_result virusfilter_clamav_scan_init(
 }
 
 static void virusfilter_clamav_scan_end(
-	struct virusfilter_handle *virusfilter_h)
+	struct virusfilter_config *config)
 {
-	struct virusfilter_io_handle *io_h = virusfilter_h->io_h;
+	struct virusfilter_io_handle *io_h = config->io_h;
 
 	DBG_INFO("clamd: Disconnecting\n");
 
@@ -91,15 +76,15 @@ static void virusfilter_clamav_scan_end(
 }
 
 static virusfilter_result virusfilter_clamav_scan(
-	struct vfs_handle_struct *vfs_h,
-	struct virusfilter_handle *virusfilter_h,
+	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 = virusfilter_h->io_h;
+	struct virusfilter_io_handle *io_h = config->io_h;
 	virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
 	char *report = NULL;
 	char *reply = NULL;
@@ -179,3 +164,31 @@ virusfilter_clamav_scan_return:
 
 	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 48a2029703a..34206f9b94e 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);
+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 10f90b0a6a5..298088ce4a0 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



More information about the samba-technical mailing list