[PATCH] Samba VirusFilter (version 12)

Ralph Böhme slow at samba.org
Tue Jan 23 19:01:28 UTC 2018


On Tue, Jan 23, 2018 at 09:10:46AM -0800, Jeremy Allison wrote:
> LGTM. Ralph, can I get a second Team reviewer ?

I'm really sorry, but virusfilter_do_infected_file_action() was in bad need of
some refactoring.

The attached patchset has a fixup commit where I essentially split
virusfilter_do_infected_file_action() into smaller functions and made use of
talloc_stackframe()s where appropriate for sane cleanup of temporary
objects. Where needed out-arguments are talloc_moved from the temporary memory
context to the memory context the caller provides.

Trever, please check, fixup, test and resubmit. Sorry. The good news is, this
seems to be the last issue that needs addressing. :)

-slow

-- 
Ralph Boehme, Samba Team       https://samba.org/
Samba Developer, SerNet GmbH   https://sernet.de/en/samba/
-------------- next part --------------
From 74274e453ed4111f949f823bd2a894b3ca8886e6 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/6] Samba-VirusFilter: memcache changes.

Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 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 027c8ce6c9a96e93df08db19d8dff296eba2e807 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/6] 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>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 docs-xml/manpages/vfs_virusfilter.8.xml            |  336 +++++
 docs-xml/wscript_build                             |    1 +
 .../scripts/vfs/virusfilter/virusfilter-notify.ksh |  284 ++++
 source3/modules/vfs_virusfilter.c                  | 1454 ++++++++++++++++++++
 source3/modules/vfs_virusfilter_common.h           |  149 ++
 source3/modules/vfs_virusfilter_utils.c            | 1025 ++++++++++++++
 source3/modules/vfs_virusfilter_utils.h            |  177 +++
 source3/modules/wscript_build                      |   13 +
 source3/wscript                                    |    2 +-
 9 files changed, 3440 insertions(+), 1 deletion(-)
 create mode 100644 docs-xml/manpages/vfs_virusfilter.8.xml
 create mode 100644 examples/scripts/vfs/virusfilter/virusfilter-notify.ksh
 create mode 100644 source3/modules/vfs_virusfilter.c
 create mode 100644 source3/modules/vfs_virusfilter_common.h
 create mode 100644 source3/modules/vfs_virusfilter_utils.c
 create mode 100644 source3/modules/vfs_virusfilter_utils.h

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
new file mode 100644
index 00000000000..eb6112e3827
--- /dev/null
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -0,0 +1,336 @@
+<?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 close.
+		</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 smallest sized file, in bytes, which will be scanned.
+		</para>
+		<para>If this option is not set, the default is 10.</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 open = EACCES</term>
+		<listitem>
+		<para>What errno to return on open if the file is infected.
+		</para>
+		<para>If this option is not set, the default is EACCES.</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:quarantine directory = PATH</term>
+		<listitem>
+		<para>Where to move infected files. This path must be an
+		absolute path.</para>
+		<para>If this option is not set, the default is ".quarantine"
+		relative to the share path. </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 tree = yes</term>
+		<listitem>
+		<para>If keep tree is set, the directory structure relative
+		to the share is maintained in the quarantine directory.
+		</para>
+		<para>If this option is not set, the default is yes.</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 yes.</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 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: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: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 = 0755</term>
+		<listitem>
+		<para>This is the octet mode for the quarantine directory and
+		its sub-directories as they are created.</para>
+		<para>If this option is not set, the default is 0755 or
+		S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH |
+		S_IXOTH.</para>
+		<para>Permissions must be such that all users can read and
+		search. I.E. don't mess with this unless you really know what
+		you are doing.</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>
+
+	<para>This module is intended for security in depth by providing
+	virus scanning capability on the server. It is not intended to be used
+	in lieu of proper client based security. Other modules for security may
+	exist and may be desirable for security in depth on the server.</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/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.c b/source3/modules/vfs_virusfilter.c
new file mode 100644
index 00000000000..8703f2d303b
--- /dev/null
+++ b/source3/modules/vfs_virusfilter.c
@@ -0,0 +1,1454 @@
+/*
+ * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+ * Copyright (C) 2016-2017 Trever L. Adams
+ * Copyright (C) 2017 Ralph Boehme <slow at samba.org>
+ * Copyright (C) 2017 Jeremy Allison <jra 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
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/*
+ * Default configuration values
+ * ======================================================================
+ */
+
+#define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX		"virusfilter."
+#define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX		".infected"
+#define VIRUSFILTER_DEFAULT_RENAME_PREFIX		"virusfilter."
+#define VIRUSFILTER_DEFAULT_RENAME_SUFFIX		".infected"
+
+/* ====================================================================== */
+
+enum virusfilter_scanner_enum {
+	VIRUSFILTER_SCANNER_CLAMAV,
+	VIRUSFILTER_SCANNER_FSAV,
+	VIRUSFILTER_SCANNER_SOPHOS
+};
+
+static const struct enum_list scanner_list[] = {
+	{ VIRUSFILTER_SCANNER_CLAMAV,	"clamav" },
+	{ VIRUSFILTER_SCANNER_FSAV,	"fsav" },
+	{ VIRUSFILTER_SCANNER_SOPHOS,	"sophos" },
+	{ -1,				NULL }
+};
+
+static const struct enum_list virusfilter_actions[] = {
+	{ VIRUSFILTER_ACTION_QUARANTINE,	"quarantine" },
+	{ VIRUSFILTER_ACTION_RENAME,		"rename" },
+	{ VIRUSFILTER_ACTION_DELETE,		"delete" },
+
+	/* alias for "delete" */
+	{ VIRUSFILTER_ACTION_DELETE,		"remove" },
+
+	/* alias for "delete" */
+	{ VIRUSFILTER_ACTION_DELETE,		"unlink" },
+	{ VIRUSFILTER_ACTION_DO_NOTHING,	"nothing" },
+	{ -1,					NULL}
+};
+
+static int virusfilter_config_destructor(struct virusfilter_config *config)
+{
+	TALLOC_FREE(config->backend);
+	return 0;
+}
+
+/*
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_directory_exist(
+	struct vfs_handle_struct *handle,
+	const char *dname)
+{
+	int ret = -1;
+	struct smb_filename smb_fname = {
+		.base_name = discard_const_p(char, dname)
+	};
+
+	ret = SMB_VFS_STAT(handle->conn, &smb_fname);
+	if (ret == 0) {
+		return S_ISDIR(smb_fname.st.st_ex_mode);
+	}
+
+	return false;
+}
+
+/**
+ * Create directory tree
+ * @param conn connection
+ * @param dname Directory tree to be created
+ * @return Returns true for success
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_create_dir(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	const char *dname)
+{
+	size_t len = 0;
+	size_t cat_len = 0;
+	char *new_dir = NULL;
+	char *tmp_str = NULL;
+	char *token = NULL;
+	char *tok_str = NULL;
+	bool status = false;
+	bool ok = false;
+	int ret = -1;
+	char *saveptr = NULL;
+
+	tmp_str = talloc_strdup(talloc_tos(), dname);
+	if (tmp_str == NULL) {
+		DBG_ERR("virusfilter-vfs: out of memory!\n");
+		errno = ENOMEM;
+		goto done;
+	}
+	tok_str = tmp_str;
+
+	len = strlen(dname)+1;
+	new_dir = (char *)talloc_size(talloc_tos(), len + 1);
+	if (new_dir == NULL) {
+		DBG_ERR("virusfilter-vfs: out of memory!\n");
+		errno = ENOMEM;
+		goto done;
+	}
+	*new_dir = '\0';
+	if (dname[0] == '/') {
+		/* Absolute path. */
+		cat_len = strlcat(new_dir, "/", len + 1);
+		if (cat_len >= len+1) {
+			goto done;
+		}
+	}
+
+	/* Create directory tree if neccessary */
+	for (token = strtok_r(tok_str, "/", &saveptr);
+	     token != NULL;
+	     token = strtok_r(NULL, "/", &saveptr))
+	{
+		cat_len = strlcat(new_dir, token, len + 1);
+		if (cat_len >= len+1) {
+			goto done;
+		}
+		ok = quarantine_directory_exist(handle, new_dir);
+		if (ok == true) {
+			DBG_DEBUG("quarantine: dir %s already exists\n",
+				  new_dir);
+		} else {
+			struct smb_filename *smb_fname = NULL;
+
+			DBG_INFO("quarantine: creating new dir %s\n", new_dir);
+
+			smb_fname = synthetic_smb_fname(talloc_tos(), new_dir,
+							NULL, NULL, 0);
+			if (smb_fname == NULL) {
+				goto done;
+			}
+
+			ret = SMB_VFS_NEXT_MKDIR(handle,
+					smb_fname,
+					config->quarantine_dir_mode);
+			if (ret != 0) {
+				TALLOC_FREE(smb_fname);
+
+				DBG_WARNING("quarantine: mkdir failed for %s "
+					    "with error: %s\n", new_dir,
+					    strerror(errno));
+				status = false;
+				goto done;
+			}
+			TALLOC_FREE(smb_fname);
+		}
+		cat_len = strlcat(new_dir, "/", len + 1);
+		if (cat_len >= len + 1) {
+			goto done;
+		}
+	}
+
+	status = true;
+done:
+	TALLOC_FREE(tmp_str);
+	TALLOC_FREE(new_dir);
+	return status;
+}
+
+static int virusfilter_vfs_connect(
+	struct vfs_handle_struct *handle,
+	const char *svc,
+	const char *user)
+{
+	int snum = SNUM(handle->conn);
+	struct virusfilter_config *config = NULL;
+	const char *exclude_files = NULL;
+	const char *temp_quarantine_dir_mode = NULL;
+	char *sret = NULL;
+	char *tmp = NULL;
+	enum virusfilter_scanner_enum backend;
+	int connect_timeout = 0;
+	int io_timeout = 0;
+	int ret = -1;
+
+	config = talloc_zero(handle, struct virusfilter_config);
+	if (config == NULL) {
+		DBG_ERR("talloc_zero failed\n");
+		return -1;
+	}
+	talloc_set_destructor(config, virusfilter_config_destructor);
+
+	SMB_VFS_HANDLE_SET_DATA(handle, config, NULL,
+				struct virusfilter_config, return -1);
+
+	config->scan_request_limit = lp_parm_int(
+		snum, "virusfilter", "scan request limit", 0);
+
+	config->scan_on_open = lp_parm_bool(
+		snum, "virusfilter", "scan on open", true);
+
+	config->scan_on_close = lp_parm_bool(
+		snum, "virusfilter", "scan on close", false);
+
+	config->max_nested_scan_archive = lp_parm_int(
+		snum, "virusfilter", "max nested scan archive", 1);
+
+	config->scan_archive = lp_parm_bool(
+		snum, "virusfilter", "scan archive", false);
+
+	config->scan_mime = lp_parm_bool(
+		snum, "virusfilter", "scan mime", false);
+
+	config->max_file_size = (ssize_t)lp_parm_ulong(
+		snum, "virusfilter", "max file size", 100000000L);
+
+	config->min_file_size = (ssize_t)lp_parm_ulong(
+		snum, "virusfilter", "min file size", 10);
+
+	exclude_files = lp_parm_const_string(
+		snum, "virusfilter", "exclude files", NULL);
+	if (exclude_files != NULL) {
+		set_namearray(&config->exclude_files, exclude_files);
+	}
+
+	config->cache_entry_limit = lp_parm_int(
+		snum, "virusfilter", "cache entry limit", 100);
+
+	config->cache_time_limit = lp_parm_int(
+		snum, "virusfilter", "cache time limit", 10);
+
+	config->infected_file_action = lp_parm_enum(
+		snum, "virusfilter", "infected file action",
+		virusfilter_actions, VIRUSFILTER_ACTION_DO_NOTHING);
+
+	config->infected_file_command = lp_parm_const_string(
+		snum, "virusfilter", "infected file command", NULL);
+
+	config->scan_error_command = lp_parm_const_string(
+		snum, "virusfilter", "scan error command", NULL);
+
+	config->block_access_on_error = lp_parm_bool(
+		snum, "virusfilter", "block access on error", false);
+
+	tmp = talloc_asprintf(config, "%s/.quarantine",
+		handle->conn->connectpath);
+
+	config->quarantine_dir = lp_parm_const_string(
+		snum, "virusfilter", "quarantine directory",
+		tmp ? tmp : "/tmp/.quarantine");
+
+	if (tmp != config->quarantine_dir) {
+		TALLOC_FREE(tmp);
+	}
+
+	temp_quarantine_dir_mode = lp_parm_const_string(
+		snum, "virusfilter", "quarantine directory mode", "0755");
+	if (temp_quarantine_dir_mode != NULL) {
+		sscanf(temp_quarantine_dir_mode, "%o",
+		       &config->quarantine_dir_mode);
+	}
+
+	config->quarantine_prefix = lp_parm_const_string(
+		snum, "virusfilter", "quarantine prefix",
+		VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+
+	config->quarantine_suffix = lp_parm_const_string(
+		snum, "virusfilter", "quarantine suffix",
+		VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+
+	/*
+	 * Make sure prefixes and suffixes do not contain directory
+	 * delimiters
+	 */
+	sret = strstr(config->quarantine_prefix, "/");
+	if (sret != NULL) {
+		DBG_ERR("quarantine prefix must not contain directory "
+			"delimiter(s) such as '/' (%s replaced with %s)\n",
+			config->quarantine_prefix,
+			VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+		config->quarantine_prefix =
+			VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX;
+	}
+	sret = strstr(config->quarantine_suffix, "/");
+	if (sret != NULL) {
+		DBG_ERR("quarantine suffix must not contain directory "
+			"delimiter(s) such as '/' (%s replaced with %s)\n",
+			config->quarantine_suffix,
+			VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+		config->quarantine_suffix =
+			VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX;
+	}
+
+	config->quarantine_keep_tree = lp_parm_bool(
+		snum, "virusfilter", "quarantine keep tree", true);
+
+	config->quarantine_keep_name = lp_parm_bool(
+		snum, "virusfilter", "quarantine keep name", true);
+
+	config->rename_prefix = lp_parm_const_string(
+		snum, "virusfilter", "rename prefix",
+		VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+
+	config->rename_suffix = lp_parm_const_string(
+		snum, "virusfilter", "rename suffix",
+		VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+
+	/*
+	 * Make sure prefixes and suffixes do not contain directory
+	 * delimiters
+	 */
+	sret = strstr(config->rename_prefix, "/");
+	if (sret != NULL) {
+		DBG_ERR("rename prefix must not contain directory "
+			"delimiter(s) such as '/' (%s replaced with %s)\n",
+			config->rename_prefix,
+			VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+		config->rename_prefix =
+			VIRUSFILTER_DEFAULT_RENAME_PREFIX;
+	}
+	sret = strstr(config->rename_suffix, "/");
+	if (sret != NULL) {
+		DBG_ERR("rename suffix must not contain directory "
+			"delimiter(s) such as '/' (%s replaced with %s)\n",
+			config->rename_suffix,
+			VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+		config->rename_suffix =
+			VIRUSFILTER_DEFAULT_RENAME_SUFFIX;
+	}
+
+	config->infected_open_errno = lp_parm_int(
+		snum, "virusfilter", "infected file errno on open", EACCES);
+
+	config->infected_close_errno = lp_parm_int(
+		snum, "virusfilter", "infected file errno on close", 0);
+
+	config->scan_error_open_errno = lp_parm_int(
+		snum, "virusfilter", "scan error errno on open", EACCES);
+
+	config->scan_error_close_errno = lp_parm_int(
+		snum, "virusfilter", "scan error errno on close", 0);
+
+	config->socket_path = lp_parm_const_string(
+		snum, "virusfilter", "socket path", NULL);
+
+	/* canonicalize socket_path */
+	if (config->socket_path != NULL && config->socket_path[0] != '/') {
+		DBG_ERR("socket path must be an absolute path. "
+			"Using backend default\n");
+		config->socket_path = NULL;
+        }
+	if (config->socket_path != NULL) {
+		canonicalize_absolute_path(handle,
+					   config->socket_path);
+	}
+
+	connect_timeout = lp_parm_int(snum, "virusfilter",
+				      "connect timeout", 30000);
+
+	io_timeout = lp_parm_int(snum, "virusfilter", "io timeout", 60000);
+
+	config->io_h = virusfilter_io_new(config, connect_timeout, io_timeout);
+	if (config->io_h == NULL) {
+		DBG_ERR("virusfilter_io_new failed");
+		return -1;
+	}
+
+	if (config->cache_entry_limit > 0) {
+		config->cache = virusfilter_cache_new(handle,
+					config->cache_entry_limit,
+					config->cache_time_limit);
+		if (config->cache == NULL) {
+			DBG_ERR("Initializing cache failed: Cache disabled\n");
+			return -1;
+		}
+	}
+
+	/*
+	 * Check quarantine directory now to save processing
+	 * and becoming root over and over.
+	 */
+	if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+		bool ok = true;
+		bool dir_exists;
+
+		/*
+		 * Do SMB_VFS_NEXT_MKDIR(config->quarantine_dir)
+		 * hierarchy
+		 */
+		become_root();
+		dir_exists = quarantine_directory_exist(handle,
+						config->quarantine_dir);
+		if (!dir_exists) {
+			DBG_DEBUG("Creating quarantine directory: %s\n",
+				  config->quarantine_dir);
+			ok = quarantine_create_dir(handle, config,
+					      config->quarantine_dir);
+		}
+		unbecome_root();
+		if (!ok) {
+			DBG_ERR("Creating quarantine directory %s "
+				"failed with %s\n",
+				config->quarantine_dir,
+				strerror(errno));
+			return -1;
+		}
+	}
+
+	/*
+	 * Now that the frontend options are initialized, load the configured
+	 * backend.
+	 */
+
+	backend = (enum virusfilter_scanner_enum)lp_parm_enum(snum,
+				"virusfilter",
+				"scanner",
+				scanner_list,
+			       -1);
+	if (backend == (enum virusfilter_scanner_enum)-1) {
+		DBG_ERR("No AV-Scanner configured, "
+			"please set \"virusfilter:scanner\"\n");
+		return -1;
+	}
+
+	/* This goes away as soon as the next commit adds an actual backend... */
+	if (config->backend == NULL) {
+		DBG_INFO("Not implemented\n");
+		return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+	}
+
+	if (config->backend->fns->connect != NULL) {
+		ret = config->backend->fns->connect(handle, config, svc, user);
+		if (ret == -1) {
+			return -1;
+		}
+	}
+
+	return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+}
+
+static void virusfilter_vfs_disconnect(struct vfs_handle_struct *handle)
+{
+	struct virusfilter_config *config = NULL;
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return);
+
+	if (config->backend->fns->disconnect != NULL) {
+		config->backend->fns->disconnect(handle);
+	}
+
+	free_namearray(config->exclude_files);
+	virusfilter_io_disconnect(config->io_h);
+
+	SMB_VFS_NEXT_DISCONNECT(handle);
+}
+
+static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx,
+				      struct virusfilter_config *config,
+				      char **env_list)
+{
+	int ret;
+
+	ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
+				  VIRUSFILTER_VERSION);
+	if (ret == -1) {
+		return -1;
+	}
+	ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
+				  config->backend->name);
+	if (ret == -1) {
+		return -1;
+	}
+
+	if (config->backend->version != 0) {
+		char *version = NULL;
+
+		version = talloc_asprintf(talloc_tos(), "%u",
+					  config->backend->version);
+		if (version == NULL) {
+			return -1;
+		}
+		ret = virusfilter_env_set(mem_ctx, env_list,
+					  "VIRUSFILTER_MODULE_VERSION",
+					  version);
+		TALLOC_FREE(version);
+		if (ret == -1) {
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static virusfilter_action virusfilter_do_infected_file_action(
+	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 = handle->conn;
+	char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	char *fname = fsp->fsp_name->base_name;
+	const struct smb_filename *smb_fname = fsp->fsp_name;
+	struct smb_filename *q_smb_fname = NULL;
+	char *q_dir = NULL;
+	char *q_prefix = NULL;
+	char *q_suffix = NULL;
+	char *q_filepath = NULL;
+	char *dir_name = NULL;
+	char *temp_path = NULL;
+	const char *base_name = NULL;
+	char *rand_filename_component = NULL;
+	bool ok = false;
+	int ret = -1;
+	int saved_errno = 0;
+
+	*filepath_newp = NULL;
+
+	switch (config->infected_file_action) {
+	case VIRUSFILTER_ACTION_RENAME:
+		q_prefix = virusfilter_string_sub(mem_ctx, conn,
+						config->rename_prefix);
+		q_suffix = virusfilter_string_sub(mem_ctx, conn,
+						config->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;
+		}
+
+		ok = parent_dirname(mem_ctx, fname, &q_dir, &base_name);
+		if (!ok) {
+			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);
+
+		q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+						  smb_fname->stream_name, NULL,
+						  smb_fname->flags);
+		if (q_smb_fname == NULL) {
+			TALLOC_FREE(q_filepath);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		become_root();
+		ret = virusfilter_vfs_next_move(handle, smb_fname,
+						   q_smb_fname);
+		if (ret == -1) {
+			saved_errno = errno;
+		}
+		unbecome_root();
+
+		if (ret == -1) {
+			DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
+				cwd_fname, fname,
+				strerror(saved_errno));
+			errno = saved_errno;
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		*filepath_newp = q_filepath;
+
+		return VIRUSFILTER_ACTION_RENAME;
+
+	case VIRUSFILTER_ACTION_QUARANTINE:
+		q_dir = virusfilter_string_sub(mem_ctx, conn,
+					config->quarantine_dir);
+		q_prefix = virusfilter_string_sub(mem_ctx, conn,
+					config->quarantine_prefix);
+		q_suffix = virusfilter_string_sub(mem_ctx, conn,
+					config->quarantine_suffix);
+		if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
+			DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+				"memory\n", cwd_fname, fname);
+			TALLOC_FREE(q_dir);
+			TALLOC_FREE(q_prefix);
+			TALLOC_FREE(q_suffix);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		if (config->quarantine_keep_name ||
+		    config->quarantine_keep_tree)
+		{
+			ok = parent_dirname(mem_ctx, smb_fname->base_name,
+					    &dir_name, &base_name);
+			if (!ok) {
+				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);
+				TALLOC_FREE(rand_filename_component);
+				return VIRUSFILTER_ACTION_DO_NOTHING;
+			}
+
+			if (config->quarantine_keep_tree) {
+				temp_path = talloc_asprintf(mem_ctx,
+						"%s/%s", q_dir, cwd_fname);
+				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);
+					TALLOC_FREE(rand_filename_component);
+					return VIRUSFILTER_ACTION_DO_NOTHING;
+				}
+
+				become_root();
+				ok = quarantine_directory_exist(handle,
+								temp_path);
+				if (ok) {
+					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);
+					ok = quarantine_create_dir(handle,
+							config, temp_path);
+					if (!ok) {
+						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();
+			}
+		}
+
+		/* Get a 16 byte + \0 random filename component. */
+		rand_filename_component = generate_random_str(talloc_tos(), 16);
+		if (rand_filename_component == NULL) {
+			DBG_ERR("Quarantine failed: %s/%s: Cannot "
+				"allocate memory\n", cwd_fname, fname);
+			TALLOC_FREE(dir_name);
+			TALLOC_FREE(q_dir);
+			TALLOC_FREE(q_prefix);
+			TALLOC_FREE(q_suffix);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+		if (config->quarantine_keep_name) {
+			q_filepath = talloc_asprintf(talloc_tos(),
+					"%s/%s%s%s-%s",
+					q_dir, q_prefix, base_name, q_suffix,
+					rand_filename_component);
+		} else {
+			q_filepath = talloc_asprintf(talloc_tos(),
+				"%s/%s%s", q_dir, q_prefix,
+				rand_filename_component);
+		}
+
+		TALLOC_FREE(dir_name);
+		TALLOC_FREE(q_dir);
+		TALLOC_FREE(q_prefix);
+		TALLOC_FREE(q_suffix);
+		TALLOC_FREE(rand_filename_component);
+
+		if (q_filepath == NULL) {
+			DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+				"memory\n", cwd_fname, fname);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+			smb_fname->stream_name, NULL, smb_fname->flags);
+		if (q_smb_fname == NULL) {
+			TALLOC_FREE(q_filepath);
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		become_root();
+		ret = virusfilter_vfs_next_move(handle, smb_fname,
+						   q_smb_fname);
+		if (ret == -1) {
+			saved_errno = errno;
+		}
+		unbecome_root();
+		if (ret == -1) {
+			DBG_ERR("Quarantine failed: %s/%s: Rename to %s "
+				"failed: %s\n",
+				cwd_fname, fname,
+				q_filepath,
+				strerror(saved_errno));
+			errno = saved_errno;
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+
+		*filepath_newp = q_filepath;
+
+		return VIRUSFILTER_ACTION_QUARANTINE;
+
+	case VIRUSFILTER_ACTION_DELETE:
+		become_root();
+		ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+		if (ret == -1) {
+			saved_errno = errno;
+		}
+		unbecome_root();
+		if (ret == -1) {
+			DBG_ERR("Delete failed: %s/%s: Unlink failed: %s\n",
+				cwd_fname, fname,
+				strerror(saved_errno));
+			errno = saved_errno;
+			return VIRUSFILTER_ACTION_DO_NOTHING;
+		}
+		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 *handle,
+	struct virusfilter_config *config,
+	const struct files_struct *fsp,
+	const char *report,
+	bool is_cache)
+{
+	connection_struct *conn = handle->conn;
+	char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	char *fname = fsp->fsp_name->base_name;
+	TALLOC_CTX *mem_ctx = talloc_tos();
+	int i;
+	virusfilter_action action;
+	const char *action_name = "UNKNOWN";
+	const char *filepath_q = NULL;
+	char *env_list = NULL;
+	char *command = NULL;
+	int command_result;
+	int ret;
+
+	action = virusfilter_do_infected_file_action(handle, config, fsp,
+						     &filepath_q);
+	for (i=0; virusfilter_actions[i].name; i++) {
+		if (virusfilter_actions[i].value == action) {
+			action_name = virusfilter_actions[i].name;
+			break;
+		}
+	}
+	DBG_WARNING("Infected file action: %s/%s: %s\n", cwd_fname,
+		    fname, action_name);
+
+	if (!config->infected_file_command) {
+		return action;
+	}
+
+	ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+	if (ret == -1) {
+		goto done;
+	}
+	ret = virusfilter_env_set(mem_ctx, &env_list,
+				  "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
+				  fname);
+	if (ret == -1) {
+		goto done;
+	}
+	if (report != NULL) {
+		ret = virusfilter_env_set(mem_ctx, &env_list,
+					  "VIRUSFILTER_INFECTED_FILE_REPORT",
+					  report);
+		if (ret == -1) {
+			goto done;
+		}
+	}
+	ret = virusfilter_env_set(mem_ctx, &env_list,
+				  "VIRUSFILTER_INFECTED_FILE_ACTION",
+				  action_name);
+	if (ret == -1) {
+		goto done;
+	}
+	if (filepath_q != NULL) {
+		ret = virusfilter_env_set(mem_ctx, &env_list,
+					  "VIRUSFILTER_QUARANTINED_FILE_PATH",
+					  filepath_q);
+		if (ret == -1) {
+			goto done;
+		}
+	}
+	if (is_cache) {
+		ret = virusfilter_env_set(mem_ctx, &env_list,
+					  "VIRUSFILTER_RESULT_IS_CACHE", "yes");
+		if (ret == -1) {
+			goto done;
+		}
+	}
+
+	command = virusfilter_string_sub(mem_ctx, conn,
+					 config->infected_file_command);
+	if (command == NULL) {
+		DBG_ERR("virusfilter_string_sub failed\n");
+		goto done;
+	}
+
+	DBG_NOTICE("Infected file command line: %s/%s: %s\n", cwd_fname,
+		   fname, command);
+
+	command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+					       conn, true);
+	if (command_result != 0) {
+		DBG_ERR("Infected file command failed: %d\n", command_result);
+	}
+
+	DBG_DEBUG("Infected file command finished: %d\n", command_result);
+
+done:
+	TALLOC_FREE(env_list);
+	TALLOC_FREE(command);
+
+	return action;
+}
+
+static void virusfilter_treat_scan_error(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	const struct files_struct *fsp,
+	const char *report,
+	bool is_cache)
+{
+	connection_struct *conn = handle->conn;
+	const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	const char *fname = fsp->fsp_name->base_name;
+	TALLOC_CTX *mem_ctx = talloc_tos();
+	char *env_list = NULL;
+	char *command = NULL;
+	int command_result;
+	int ret;
+
+	if (!config->scan_error_command) {
+		return;
+	}
+	ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+	if (ret == -1) {
+		goto done;
+	}
+	ret = virusfilter_env_set(mem_ctx, &env_list,
+				  "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
+				  fname);
+	if (ret == -1) {
+		goto done;
+	}
+	if (report != NULL) {
+		ret = virusfilter_env_set(mem_ctx, &env_list,
+					  "VIRUSFILTER_SCAN_ERROR_REPORT",
+					  report);
+		if (ret == -1) {
+			goto done;
+		}
+	}
+	if (is_cache) {
+		ret = virusfilter_env_set(mem_ctx, &env_list,
+					  "VIRUSFILTER_RESULT_IS_CACHE", "1");
+		if (ret == -1) {
+			goto done;
+		}
+	}
+
+	command = virusfilter_string_sub(mem_ctx, conn,
+					 config->scan_error_command);
+	if (command == NULL) {
+		DBG_ERR("virusfilter_string_sub failed\n");
+		goto done;
+	}
+
+	DBG_NOTICE("Scan error command line: %s/%s: %s\n", cwd_fname,
+		   fname, command);
+
+	command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+					       conn, true);
+	if (command_result != 0) {
+		DBG_ERR("Scan error command failed: %d\n", command_result);
+	}
+
+done:
+	TALLOC_FREE(env_list);
+	TALLOC_FREE(command);
+}
+
+static virusfilter_result virusfilter_scan(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	const struct files_struct *fsp)
+{
+	virusfilter_result scan_result;
+	char *scan_report = NULL;
+	const char *fname = fsp->fsp_name->base_name;
+	const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	struct virusfilter_cache_entry *scan_cache_e = NULL;
+	bool is_cache = false;
+	virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING;
+	bool add_scan_cache = true;
+	bool ok = false;
+
+	if (config->cache) {
+		DBG_DEBUG("Searching cache entry: fname: %s\n", fname);
+		scan_cache_e = virusfilter_cache_get(config->cache,
+						     cwd_fname, fname);
+		if (scan_cache_e != NULL) {
+			DBG_DEBUG("Cache entry found: cached result: %d\n",
+			      scan_cache_e->result);
+			is_cache = true;
+			scan_result = scan_cache_e->result;
+			scan_report = scan_cache_e->report;
+			goto virusfilter_scan_result_eval;
+		}
+		DBG_DEBUG("Cache entry not found\n");
+	}
+
+	if (config->backend->fns->scan_init != NULL) {
+		scan_result = config->backend->fns->scan_init(config);
+		if (scan_result != VIRUSFILTER_RESULT_OK) {
+			scan_result = VIRUSFILTER_RESULT_ERROR;
+			scan_report = talloc_asprintf(
+				talloc_tos(),
+				"Initializing scanner failed");
+			goto virusfilter_scan_result_eval;
+		}
+	}
+
+	scan_result = config->backend->fns->scan(handle, config, fsp,
+						 &scan_report);
+
+	if (config->backend->fns->scan_end != NULL) {
+		bool scan_end = true;
+
+		if (config->scan_request_limit > 0) {
+			scan_end = false;
+			config->scan_request_count++;
+			if (config->scan_request_count >=
+			    config->scan_request_limit)
+			{
+				scan_end = true;
+				config->scan_request_count = 0;
+			}
+		}
+		if (scan_end) {
+			config->backend->fns->scan_end(config);
+		}
+	}
+
+virusfilter_scan_result_eval:
+
+	switch (scan_result) {
+	case VIRUSFILTER_RESULT_CLEAN:
+		DBG_INFO("Scan result: Clean: %s/%s\n", cwd_fname, fname);
+		break;
+
+	case VIRUSFILTER_RESULT_INFECTED:
+		DBG_ERR("Scan result: Infected: %s/%s: %s\n",
+			cwd_fname, fname, scan_report ? scan_report :
+			"infected (memory error on report)");
+		file_action = virusfilter_treat_infected_file(handle,
+					config, fsp, scan_report, is_cache);
+		if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+			add_scan_cache = false;
+		}
+		break;
+
+	case VIRUSFILTER_RESULT_SUSPECTED:
+		if (!config->block_suspected_file) {
+			break;
+		}
+		DBG_ERR("Scan result: Suspected: %s/%s: %s\n",
+			cwd_fname, fname, scan_report ? scan_report :
+			"suspected infection (memory error on report)");
+		file_action = virusfilter_treat_infected_file(handle,
+					config, fsp, scan_report, is_cache);
+		if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+			add_scan_cache = false;
+		}
+		break;
+
+	case VIRUSFILTER_RESULT_ERROR:
+		DBG_ERR("Scan result: Error: %s/%s: %s\n",
+			cwd_fname, fname, scan_report ? scan_report :
+			"error (memory error on report)");
+		virusfilter_treat_scan_error(handle, config, fsp,
+					     scan_report, is_cache);
+		add_scan_cache = false;
+		break;
+
+	default:
+		DBG_ERR("Scan result: Unknown result code %d: %s/%s: %s\n",
+			scan_result, cwd_fname, fname, scan_report ?
+			scan_report : "Unknown (memory error on report)");
+		virusfilter_treat_scan_error(handle, config, fsp,
+					     scan_report, is_cache);
+		add_scan_cache = false;
+		break;
+	}
+
+	if (config->cache) {
+		if (!is_cache && add_scan_cache) {
+			DBG_DEBUG("Adding new cache entry: %s, %d\n", fname,
+				  scan_result);
+			ok = virusfilter_cache_entry_add(
+					config->cache, cwd_fname, fname,
+					scan_result, scan_report);
+			if (!ok) {
+				DBG_ERR("Cannot create cache entry: "
+					"virusfilter_cache_entry_new failed");
+				goto virusfilter_scan_return;
+			}
+		} else if (is_cache) {
+			virusfilter_cache_entry_free(scan_cache_e);
+		}
+	}
+
+virusfilter_scan_return:
+	return scan_result;
+}
+
+static int virusfilter_vfs_open(
+	struct vfs_handle_struct *handle,
+	struct smb_filename *smb_fname,
+	files_struct *fsp,
+	int flags,
+	mode_t mode)
+{
+	TALLOC_CTX *mem_ctx = talloc_tos();
+	struct virusfilter_config *config;
+	const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	virusfilter_result scan_result;
+	const char *fname = fsp->fsp_name->base_name;
+	char *dir_name = NULL;
+	const char *base_name = NULL;
+	int scan_errno = 0;
+	size_t test_prefix;
+	size_t test_suffix;
+	int rename_trap_count = 0;
+	int ret;
+	bool ok1, ok2;
+	char *sret = NULL;
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return -1);
+
+	test_prefix = strlen(config->rename_prefix);
+	test_suffix = strlen(config->rename_suffix);
+	if (test_prefix > 0) {
+		rename_trap_count++;
+	}
+	if (test_suffix > 0) {
+		rename_trap_count++;
+	}
+
+	ok1 = is_ntfs_stream_smb_fname(smb_fname);
+	ok2 = is_ntfs_default_stream_smb_fname(smb_fname);
+	if (ok1 && !ok2) {
+		DBG_INFO("Not scanned: only file backed streams can be scanned:"
+			 " %s/%s\n", cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+
+	if (!config->scan_on_open) {
+		DBG_INFO("Not scanned: scan on open is disabled: %s/%s\n",
+			 cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+
+	if (flags & O_TRUNC) {
+		DBG_INFO("Not scanned: Open flags have O_TRUNC: %s/%s\n",
+			 cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+
+	ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+	if (ret != 0) {
+
+		/*
+		 * Do not return immediately if !(flags & O_CREAT) &&
+		 * errno != ENOENT.
+		 * Do not do this here or anywhere else. The module is
+		 * stackable and there may be modules below, such as audit
+		 * modules, which should be handled.
+		 */
+		goto virusfilter_vfs_open_next;
+	}
+	ret = S_ISREG(smb_fname->st.st_ex_mode);
+	if (ret == 0) {
+		DBG_INFO("Not scanned: Directory or special file: %s/%s\n",
+			 cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+	if (config->max_file_size > 0 &&
+	    smb_fname->st.st_ex_size > config->max_file_size)
+	{
+		DBG_INFO("Not scanned: file size > max file size: %s/%s\n",
+			 cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+	if (config->min_file_size > 0 &&
+	    smb_fname->st.st_ex_size < config->min_file_size)
+	{
+		DBG_INFO("Not scanned: file size < min file size: %s/%s\n",
+		      cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+
+	ok1 = is_in_path(fname, config->exclude_files, false);
+	if (config->exclude_files && ok1)
+	{
+		DBG_INFO("Not scanned: exclude files: %s/%s\n",
+			 cwd_fname, fname);
+		goto virusfilter_vfs_open_next;
+	}
+
+	if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+		sret = strstr_m(fname, config->quarantine_dir);
+		if (sret != NULL) {
+			scan_errno = config->infected_open_errno;
+			goto virusfilter_vfs_open_fail;
+		}
+	}
+
+	if (test_prefix > 0 || test_suffix > 0) {
+		ok1 = parent_dirname(mem_ctx, fname, &dir_name, &base_name);
+		if (ok1)
+		{
+			if (test_prefix > 0) {
+				ret = strncmp(base_name,
+				    config->rename_prefix, test_prefix);
+				if (ret != 0) {
+					test_prefix = 0;
+				}
+			}
+			if (test_suffix > 0) {
+				ret = strcmp(base_name + (strlen(base_name)
+						 - test_suffix),
+						 config->rename_suffix);
+				if (ret != 0) {
+					test_suffix = 0;
+				}
+			}
+
+			TALLOC_FREE(dir_name);
+
+			if ((rename_trap_count == 2 && test_prefix &&
+			    test_suffix) || (rename_trap_count == 1 &&
+			    (test_prefix || test_suffix)))
+			{
+				scan_errno =
+					config->infected_open_errno;
+				goto virusfilter_vfs_open_fail;
+			}
+		}
+	}
+
+	scan_result = virusfilter_scan(handle, config, fsp);
+
+	switch (scan_result) {
+	case VIRUSFILTER_RESULT_CLEAN:
+		break;
+	case VIRUSFILTER_RESULT_INFECTED:
+		scan_errno = config->infected_open_errno;
+		goto virusfilter_vfs_open_fail;
+	case VIRUSFILTER_RESULT_ERROR:
+		if (config->block_access_on_error) {
+			DBG_INFO("Block access\n");
+			scan_errno = config->scan_error_open_errno;
+			goto virusfilter_vfs_open_fail;
+		}
+		break;
+	default:
+		scan_errno = config->scan_error_open_errno;
+		goto virusfilter_vfs_open_fail;
+	}
+
+virusfilter_vfs_open_next:
+	return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
+
+virusfilter_vfs_open_fail:
+	errno = (scan_errno != 0) ? scan_errno : EACCES;
+	return -1;
+}
+
+static int virusfilter_vfs_close(
+	struct vfs_handle_struct *handle,
+	files_struct *fsp)
+{
+	/*
+         * The name of this variable is for consistency. If API changes to
+         * match _open change to cwd_fname as in virusfilter_vfs_open.
+         */
+	const char *cwd_fname = handle->conn->connectpath;
+
+	struct virusfilter_config *config = NULL;
+	char *fname = fsp->fsp_name->base_name = NULL;
+	int close_result = -1;
+	int close_errno = 0;
+	virusfilter_result scan_result;
+	int scan_errno = 0;
+	bool ok1, ok2;
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return -1);
+
+	/*
+	 * Must close after scan? It appears not as the scanners are not
+	 * internal and other modules such as greyhole seem to do
+	 * SMB_VFS_NEXT_* functions before processing.
+	 */
+	close_result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+	if (close_result == -1) {
+		close_errno = errno;
+	}
+
+	/*
+	 * Return immediately if close_result == -1, and close_errno == EBADF.
+	 * If close failed, file likely doesn't exist, do not try to scan.
+	 */
+	if (close_result == -1 && close_errno == EBADF) {
+		if (fsp->modified) {
+			DBG_DEBUG("Removing cache entry (if existent): "
+				  "fname: %s\n", fname);
+			virusfilter_cache_remove(config->cache,
+						 cwd_fname, fname);
+		}
+		goto virusfilter_vfs_close_fail;
+	}
+
+	if (fsp->is_directory) {
+		DBG_INFO("Not scanned: Directory: %s/%s\n", cwd_fname,
+			 fname);
+		return close_result;
+	}
+
+	ok1 = is_ntfs_stream_smb_fname(fsp->fsp_name);
+	ok2 = is_ntfs_default_stream_smb_fname(fsp->fsp_name);
+	if (ok1 && !ok2) {
+		if (config->scan_on_open && fsp->modified) {
+			if (config->cache) {
+				DBG_DEBUG("Removing cache entry (if existent)"
+					  ": fname: %s\n", fname);
+				virusfilter_cache_remove(
+						config->cache,
+						cwd_fname, fname);
+			}
+		}
+		DBG_INFO("Not scanned: only file backed streams can be scanned:"
+			 " %s/%s\n", cwd_fname, fname);
+		return close_result;
+	}
+
+	if (!config->scan_on_close) {
+		if (config->scan_on_open && fsp->modified) {
+			if (config->cache) {
+				DBG_DEBUG("Removing cache entry (if existent)"
+					  ": fname: %s\n", fname);
+				virusfilter_cache_remove(
+						config->cache,
+						cwd_fname, fname);
+			}
+		}
+		DBG_INFO("Not scanned: scan on close is disabled: %s/%s\n",
+			 cwd_fname, fname);
+		return close_result;
+	}
+
+	if (!fsp->modified) {
+		DBG_NOTICE("Not scanned: File not modified: %s/%s\n",
+			   cwd_fname, fname);
+
+		return close_result;
+	}
+
+	if (config->exclude_files && is_in_path(fname,
+	    config->exclude_files, false))
+	{
+		DBG_INFO("Not scanned: exclude files: %s/%s\n",
+			 cwd_fname, fname);
+		return close_result;
+	}
+
+	scan_result = virusfilter_scan(handle, config, fsp);
+
+	switch (scan_result) {
+	case VIRUSFILTER_RESULT_CLEAN:
+		break;
+	case VIRUSFILTER_RESULT_INFECTED:
+		scan_errno = config->infected_close_errno;
+		goto virusfilter_vfs_close_fail;
+	case VIRUSFILTER_RESULT_ERROR:
+		if (config->block_access_on_error) {
+			DBG_INFO("Block access\n");
+			scan_errno = config->scan_error_close_errno;
+			goto virusfilter_vfs_close_fail;
+		}
+		break;
+	default:
+		scan_errno = config->scan_error_close_errno;
+		goto virusfilter_vfs_close_fail;
+	}
+
+	if (close_errno != 0) {
+		errno = close_errno;
+	}
+
+	return close_result;
+
+virusfilter_vfs_close_fail:
+
+	errno = (scan_errno != 0) ? scan_errno : close_errno;
+
+	return close_result;
+}
+
+static int virusfilter_vfs_unlink(
+	struct vfs_handle_struct *handle,
+	const struct smb_filename *smb_fname)
+{
+	int ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+	struct virusfilter_config *config = NULL;
+	char *fname = NULL;
+	char *cwd_fname = handle->conn->cwd_fname->base_name;
+
+	if (ret != 0 && errno != ENOENT) {
+		return ret;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return -1);
+
+	if (config->cache == NULL) {
+		return 0;
+	}
+
+	fname = smb_fname->base_name;
+
+	DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname);
+	virusfilter_cache_remove(config->cache, cwd_fname, fname);
+
+	return 0;
+}
+
+static int virusfilter_vfs_rename(
+	struct vfs_handle_struct *handle,
+	const struct smb_filename *smb_fname_src,
+	const struct smb_filename *smb_fname_dst)
+{
+	int ret = SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
+	struct virusfilter_config *config = NULL;
+	char *fname = NULL;
+	char *dst_fname = NULL;
+	char *cwd_fname = handle->conn->cwd_fname->base_name;
+
+	if (ret != 0) {
+		return ret;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct virusfilter_config, return -1);
+
+	if (config->cache == NULL) {
+		return 0;
+	}
+
+	fname = smb_fname_src->base_name;
+	dst_fname = smb_fname_dst->base_name;
+
+	DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n",
+		  fname, dst_fname);
+	virusfilter_cache_entry_rename(config->cache,
+				       cwd_fname, fname,
+				       dst_fname);
+
+	return 0;
+}
+
+/* VFS operations */
+static struct vfs_fn_pointers vfs_virusfilter_fns = {
+	.connect_fn	= virusfilter_vfs_connect,
+	.disconnect_fn	= virusfilter_vfs_disconnect,
+	.open_fn	= virusfilter_vfs_open,
+	.close_fn	= virusfilter_vfs_close,
+	.unlink_fn	= virusfilter_vfs_unlink,
+	.rename_fn	= virusfilter_vfs_rename,
+};
+
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *);
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *ctx)
+{
+	NTSTATUS status;
+
+	status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+				  "virusfilter",
+				  &vfs_virusfilter_fns);
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
+	}
+
+	virusfilter_debug_class = debug_add_class("virusfilter");
+	if (virusfilter_debug_class == -1) {
+		virusfilter_debug_class = DBGC_VFS;
+		DBG_ERR("Couldn't register custom debugging class!\n");
+	} else {
+		DBG_DEBUG("Debug class number: %d\n", virusfilter_debug_class);
+	}
+
+	DBG_INFO("registered\n");
+
+	return status;
+}
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
new file mode 100644
index 00000000000..468883fdaf8
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -0,0 +1,149 @@
+/*
+   Samba-VirusFilter VFS modules
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_COMMON_H
+#define _VIRUSFILTER_COMMON_H
+
+#include <stdint.h>
+#include <time.h>
+
+/* Samba common include file */
+#include "includes.h"
+
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "system/filesys.h"
+#include "transfer_file.h"
+#include "auth.h"
+#include "passdb.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../lib/tsocket/tsocket.h"
+
+/* Samba debug class for VIRUSFILTER */
+#undef DBGC_CLASS
+#define DBGC_CLASS virusfilter_debug_class
+extern int virusfilter_debug_class;
+
+/* Samba's global variable */
+extern userdom_struct current_user_info;
+
+#define VIRUSFILTER_VERSION "0.1.5"
+
+/* ====================================================================== */
+
+typedef enum {
+	VIRUSFILTER_ACTION_DO_NOTHING,
+	VIRUSFILTER_ACTION_QUARANTINE,
+	VIRUSFILTER_ACTION_RENAME,
+	VIRUSFILTER_ACTION_DELETE,
+} virusfilter_action;
+
+typedef enum {
+	VIRUSFILTER_RESULT_OK,
+	VIRUSFILTER_RESULT_CLEAN,
+	VIRUSFILTER_RESULT_ERROR,
+	VIRUSFILTER_RESULT_INFECTED,
+	VIRUSFILTER_RESULT_SUSPECTED,
+	/* FIXME: VIRUSFILTER_RESULT_RISKWARE, */
+} virusfilter_result;
+
+struct virusfilter_config {
+	int				scan_request_count;
+	int				scan_request_limit;
+
+	/* Scan on file operations */
+	bool				scan_on_open;
+	bool				scan_on_close;
+
+	/* Special scan options */
+	bool				scan_archive;
+	int				max_nested_scan_archive;
+	bool				scan_mime;
+	bool				block_suspected_file;
+
+	/* Size limit */
+	size_t				max_file_size;
+	size_t				min_file_size;
+
+	/* Exclude files */
+	name_compare_entry		*exclude_files;
+
+	/* Scan result cache */
+	struct virusfilter_cache	*cache;
+	int				cache_entry_limit;
+	int				cache_time_limit;
+
+	/* Infected file options */
+	virusfilter_action		infected_file_action;
+	const char *			infected_file_command;
+	int				infected_open_errno;
+	int				infected_close_errno;
+
+	/* Scan error options */
+	const char *			scan_error_command;
+	int				scan_error_open_errno;
+	int				scan_error_close_errno;
+	bool				block_access_on_error;
+
+	/* Quarantine infected files */
+	const char *			quarantine_dir;
+	const char *			quarantine_prefix;
+	const char *			quarantine_suffix;
+	bool				quarantine_keep_tree;
+	bool				quarantine_keep_name;
+	mode_t				quarantine_dir_mode;
+
+	/* Rename infected files */
+	const char *			rename_prefix;
+	const char *			rename_suffix;
+
+	/* Network options */
+	const char *			socket_path;
+	struct virusfilter_io_handle	*io_h;
+
+	/* The backend AV engine */
+	struct virusfilter_backend	*backend;
+};
+
+struct virusfilter_backend_fns {
+	int (*connect)(
+		struct vfs_handle_struct *handle,
+		struct virusfilter_config *config,
+		const char *svc,
+		const char *user);
+	void (*disconnect)(
+		struct vfs_handle_struct *handle);
+	virusfilter_result (*scan_init)(
+		struct virusfilter_config *config);
+	virusfilter_result (*scan)(
+		struct vfs_handle_struct *handle,
+		struct virusfilter_config *config,
+		const struct files_struct *fsp,
+		char **reportp);
+	void (*scan_end)(
+		struct virusfilter_config *config);
+};
+
+struct virusfilter_backend {
+	unsigned version;
+	const char *name;
+	const struct virusfilter_backend_fns *fns;
+	void *backend_private;
+};
+
+#endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_utils.c b/source3/modules/vfs_virusfilter_utils.c
new file mode 100644
index 00000000000..628e0aef99a
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.c
@@ -0,0 +1,1025 @@
+/*
+   Samba-VirusFilter VFS modules
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+   Copyright (C) 2016-2017 Trever L. Adams
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+struct iovec;
+
+#include "lib/util/iov_buf.h"
+#include <tevent.h>
+#include "lib/tsocket/tsocket.h"
+
+int virusfilter_debug_class = DBGC_VFS;
+
+/* ====================================================================== */
+
+char *virusfilter_string_sub(
+	TALLOC_CTX *mem_ctx,
+	connection_struct *conn,
+	const char *str)
+{
+	return talloc_sub_advanced(mem_ctx,
+		lp_servicename(mem_ctx, SNUM(conn)),
+		conn->session_info->unix_info->unix_name,
+		conn->connectpath,
+		conn->session_info->unix_token->gid,
+		conn->session_info->unix_info->sanitized_username,
+		conn->session_info->info->domain_name,
+		str);
+}
+
+int virusfilter_vfs_next_move(
+	struct vfs_handle_struct *vfs_h,
+	const struct smb_filename *smb_fname_src,
+	const struct smb_filename *smb_fname_dst)
+{
+	int result;
+
+	result = SMB_VFS_NEXT_RENAME(vfs_h, smb_fname_src, smb_fname_dst);
+	if (result == 0 || errno != EXDEV) {
+		return result;
+	}
+
+	/*
+	 * For now, do not handle EXDEV as poking around violates
+	 * stackability. Return -1, simply refuse access.
+	 */
+	return -1;
+}
+
+/* Line-based socket I/O
+ * ======================================================================
+ */
+
+struct virusfilter_io_handle *virusfilter_io_new(
+	TALLOC_CTX *mem_ctx,
+	int connect_timeout,
+	int io_timeout)
+{
+	struct virusfilter_io_handle *io_h = talloc_zero(mem_ctx,
+						struct virusfilter_io_handle);
+
+	if (io_h == NULL) {
+		return NULL;
+	}
+
+	io_h->stream = NULL;
+	io_h->r_len = 0;
+
+	virusfilter_io_set_connect_timeout(io_h, connect_timeout);
+	virusfilter_io_set_io_timeout(io_h, io_timeout);
+	virusfilter_io_set_writel_eol(io_h, "\x0A", 1);
+	virusfilter_io_set_readl_eol(io_h, "\x0A", 1);
+
+	return io_h;
+}
+
+int virusfilter_io_set_connect_timeout(
+	struct virusfilter_io_handle *io_h,
+	int timeout)
+{
+	int timeout_old = io_h->connect_timeout;
+
+	/* timeout <= 0 means infinite */
+	io_h->connect_timeout = (timeout > 0) ? timeout : -1;
+
+	return timeout_old;
+}
+
+int virusfilter_io_set_io_timeout(
+	struct virusfilter_io_handle *io_h,
+	int timeout)
+{
+	int timeout_old = io_h->io_timeout;
+
+	/* timeout <= 0 means infinite */
+	io_h->io_timeout = (timeout > 0) ? timeout : -1;
+
+	return timeout_old;
+}
+
+void virusfilter_io_set_writel_eol(
+	struct virusfilter_io_handle *io_h,
+	const char *eol,
+	int eol_size)
+{
+	if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) {
+		return;
+	}
+
+	memcpy(io_h->w_eol, eol, eol_size);
+	io_h->w_eol_size = eol_size;
+}
+
+void virusfilter_io_set_readl_eol(
+	struct virusfilter_io_handle *io_h,
+	const char *eol,
+	int eol_size)
+{
+	if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) {
+		return;
+	}
+
+	memcpy(io_h->r_eol, eol, eol_size);
+	io_h->r_eol_size = eol_size;
+}
+
+bool virusfilter_io_connect_path(
+	struct virusfilter_io_handle *io_h,
+	const char *path)
+{
+	struct sockaddr_un addr;
+	NTSTATUS status;
+	int socket, bes_result, flags, ret;
+
+	ZERO_STRUCT(addr);
+	addr.sun_family = AF_UNIX;
+	strncpy(addr.sun_path, path, sizeof(addr.sun_path));
+
+	status = open_socket_out((struct sockaddr_storage *)&addr, 0,
+				 io_h->connect_timeout,
+				 &socket);
+	if (!NT_STATUS_IS_OK(status)) {
+		io_h->stream = NULL;
+		return false;
+	}
+
+	/* We must not block */
+	flags = fcntl(socket, F_GETFL);
+	if (flags <= 0) {
+		/* Handle error by ignoring */;
+		flags = 0;
+		DBG_WARNING("Could not get flags on socket (%s).\n",
+			    strerror(errno));
+	}
+	flags |= SOCK_NONBLOCK;
+	ret = fcntl(socket, F_SETFL, flags);
+	if (ret == -1) {
+		/* Handle error by ignoring for now */
+		DBG_WARNING("Could not set flags on socket: %s.\n",
+			    strerror(errno));
+	}
+
+	bes_result = tstream_bsd_existing_socket(io_h, socket, &io_h->stream);
+	if (bes_result < 0) {
+		DBG_ERR("Could not convert socket to tstream: %s.\n",
+			strerror(errno));
+		io_h->stream = NULL;
+		return false;
+	}
+
+	return true;
+}
+
+static void disconnect_done(struct tevent_req *req)
+{
+	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+	int ret;
+	int err_ret;
+
+	ret = tstream_disconnect_recv(req, &err_ret);
+	TALLOC_FREE(req);
+	if (ret == -1) {
+		*perr = err_ret;
+	}
+}
+
+bool virusfilter_io_disconnect(
+	struct virusfilter_io_handle *io_h)
+{
+	struct tevent_req *req;
+	struct tevent_context *ev;
+	uint64_t *perror = NULL;
+	bool ok = true;
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	if (io_h->stream == NULL) {
+		io_h->r_len = 0;
+		TALLOC_FREE(frame);
+		return VIRUSFILTER_RESULT_OK;
+	}
+
+	ev = tevent_context_init(frame);
+	if (ev == NULL) {
+		DBG_ERR("Failed to setup event context.\n");
+		ok = false;
+		goto fail;
+	}
+
+	/* Error return - must be talloc'ed. */
+	perror = talloc_zero(frame, uint64_t);
+	if (perror == NULL) {
+		goto fail;
+	}
+
+	req = tstream_disconnect_send(io_h, ev, io_h->stream);
+
+	/* Callback when disconnect is done. */
+	tevent_req_set_callback(req, disconnect_done, perror);
+
+	/* Set timeout. */
+	ok = tevent_req_set_endtime(req, ev, timeval_current_ofs_msec(
+				    io_h->connect_timeout));
+	if (!ok) {
+		DBG_ERR("Can't set endtime\n");
+		goto fail;
+	}
+
+	/* Loop waiting for req to finish. */
+	ok = tevent_req_poll(req, ev);
+	if (!ok) {
+		DBG_ERR("tevent_req_poll failed\n");
+		goto fail;
+	}
+
+	/* Emit debug error if failed. */
+	if (*perror != 0) {
+		DBG_DEBUG("Error %s\n", strerror((int)*perror));
+		goto fail;
+	}
+
+	/* Here we know we disconnected. */
+
+	io_h->stream = NULL;
+	io_h->r_len = 0;
+
+	fail:
+		TALLOC_FREE(frame);
+		return ok;
+}
+
+static void writev_done(struct tevent_req *req)
+{
+	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+	int ret;
+	int err_ret;
+
+	ret = tstream_writev_recv(req, &err_ret);
+	TALLOC_FREE(req);
+	if (ret == -1) {
+		*perr = err_ret;
+	}
+}
+
+/****************************************************************************
+ Write all data from an iov array, with msec timeout (per write)
+ NB. This can be called with a non-socket fd, don't add dependencies
+ on socket calls.
+****************************************************************************/
+
+bool write_data_iov_timeout(
+	struct tstream_context *stream,
+	const struct iovec *iov,
+	size_t iovcnt,
+	int ms_timeout)
+{
+	struct tevent_context *ev = NULL;
+	struct tevent_req *req = NULL;
+	uint64_t *perror = NULL;
+	bool ok = false;
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	ev = tevent_context_init(frame);
+	if (ev == NULL) {
+		DBG_ERR("Failed to setup event context.\n");
+		goto fail;
+	}
+
+	/* Error return - must be talloc'ed. */
+	perror = talloc_zero(frame, uint64_t);
+	if (perror == NULL) {
+		goto fail;
+	}
+
+	/* Send the data. */
+	req = tstream_writev_send(frame, ev, stream, iov, iovcnt);
+	if (req == NULL) {
+		DBG_ERR("Out of memory.\n");
+		goto fail;
+	}
+
+	/* Callback when *all* data sent. */
+	tevent_req_set_callback(req, writev_done, perror);
+
+	/* Set timeout. */
+	ok = tevent_req_set_endtime(req, ev,
+				    timeval_current_ofs_msec(ms_timeout));
+	if (!ok) {
+		DBG_ERR("Can't set endtime\n");
+		goto fail;
+	}
+
+	/* Loop waiting for req to finish. */
+	ok = tevent_req_poll(req, ev);
+	if (!ok) {
+		DBG_ERR("tevent_req_poll failed\n");
+		goto fail;
+	}
+
+	/* Done with req - freed by the callback. */
+	req = NULL;
+
+	/* Emit debug error if failed. */
+	if (*perror != 0) {
+		DBG_DEBUG("Error %s\n", strerror((int)*perror));
+		goto fail;
+	}
+
+	/* Here we know we correctly wrote all data. */
+	TALLOC_FREE(frame);
+	return true;
+
+  fail:
+	TALLOC_FREE(frame);
+	return false;
+}
+
+bool virusfilter_io_write(
+	struct virusfilter_io_handle *io_h,
+	const char *data,
+	size_t data_size)
+{
+	struct iovec iov;
+
+	if (data_size == 0) {
+		return VIRUSFILTER_RESULT_OK;
+	}
+
+	iov.iov_base = discard_const_p(void, data);
+	iov.iov_len = data_size;
+
+	return write_data_iov_timeout(io_h->stream, &iov, 1, io_h->io_timeout);
+}
+
+bool virusfilter_io_writel(
+	struct virusfilter_io_handle *io_h,
+	const char *data,
+	size_t data_size)
+{
+	bool ok;
+
+	ok = virusfilter_io_write(io_h, data, data_size);
+	if (!ok) {
+		return ok;
+	}
+
+	return virusfilter_io_write(io_h, io_h->w_eol, io_h->w_eol_size);
+}
+
+bool virusfilter_io_writefl(
+	struct virusfilter_io_handle *io_h,
+	const char *data_fmt, ...)
+{
+	va_list ap;
+	char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
+	size_t data_size;
+
+	va_start(ap, data_fmt);
+	data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
+	va_end(ap);
+
+	if (unlikely (data_size < 0)) {
+		DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
+		return false;
+	}
+
+	memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size);
+	data_size += io_h->w_eol_size;
+
+	return virusfilter_io_write(io_h, data, data_size);
+}
+
+bool virusfilter_io_vwritefl(
+	struct virusfilter_io_handle *io_h,
+	const char *data_fmt, va_list ap)
+{
+	char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
+	size_t data_size;
+
+	data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
+
+	if (unlikely (data_size < 0)) {
+		DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
+		return false;
+	}
+
+	memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size);
+	data_size += io_h->w_eol_size;
+
+	return virusfilter_io_write(io_h, data, data_size);
+}
+
+bool virusfilter_io_writev(
+	struct virusfilter_io_handle *io_h, ...)
+{
+	va_list ap;
+	struct iovec iov[VIRUSFILTER_IO_IOV_MAX], *iov_p;
+	int iov_n;
+
+	va_start(ap, io_h);
+	for (iov_p = iov, iov_n = 0;
+	     iov_n < VIRUSFILTER_IO_IOV_MAX;
+	     iov_p++, iov_n++)
+	{
+		iov_p->iov_base = va_arg(ap, void *);
+		if (iov_p->iov_base == NULL) {
+			break;
+		}
+		iov_p->iov_len = va_arg(ap, int);
+	}
+	va_end(ap);
+
+	return write_data_iov_timeout(io_h->stream, iov, iov_n,
+		io_h->io_timeout);
+}
+
+bool virusfilter_io_writevl(
+	struct virusfilter_io_handle *io_h, ...)
+{
+	va_list ap;
+	struct iovec iov[VIRUSFILTER_IO_IOV_MAX + 1], *iov_p;
+	int iov_n;
+
+	va_start(ap, io_h);
+	for (iov_p = iov, iov_n = 0; iov_n < VIRUSFILTER_IO_IOV_MAX;
+	     iov_p++, iov_n++)
+	{
+		iov_p->iov_base = va_arg(ap, void *);
+		if (iov_p->iov_base == NULL) {
+			break;
+		}
+		iov_p->iov_len = va_arg(ap, int);
+	}
+	va_end(ap);
+
+	iov_p->iov_base = io_h->r_eol;
+	iov_p->iov_len = io_h->r_eol_size;
+	iov_n++;
+
+	return write_data_iov_timeout(io_h->stream, iov, iov_n,
+		io_h->io_timeout);
+}
+
+static bool return_existing_line(TALLOC_CTX *ctx,
+				struct virusfilter_io_handle *io_h,
+				char **read_line)
+{
+	size_t read_line_len = 0;
+	char *end_p = NULL;
+	char *eol = NULL;
+
+	eol = memmem(io_h->r_buffer, io_h->r_len,
+			io_h->r_eol, io_h->r_eol_size);
+	if (eol == NULL) {
+		return false;
+	}
+	end_p = eol + io_h->r_eol_size;
+
+	*eol = '\0';
+	read_line_len = strlen(io_h->r_buffer) + 1;
+	*read_line = talloc_memdup(ctx,
+				io_h->r_buffer,
+				read_line_len);
+	if (*read_line == NULL) {
+		return false;
+	}
+
+	/*
+	 * Copy the remaining buffer over the line
+	 * we returned.
+	 */
+	memmove(io_h->r_buffer,
+		end_p,
+		io_h->r_len - (end_p - io_h->r_buffer));
+
+	/* And reduce the size left in the buffer. */
+	io_h->r_len -= (end_p - io_h->r_buffer);
+	return true;
+}
+
+static void readv_done(struct tevent_req *req)
+{
+	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+	int ret;
+	int err_ret;
+
+	ret = tstream_readv_recv(req, &err_ret);
+	TALLOC_FREE(req);
+	if (ret == -1) {
+		*perr = err_ret;
+	}
+}
+
+bool virusfilter_io_readl(TALLOC_CTX *ctx,
+			struct virusfilter_io_handle *io_h,
+			char **read_line)
+{
+	struct tevent_context *ev = NULL;
+	bool ok = false;
+	uint64_t *perror = NULL;
+	TALLOC_CTX *frame = talloc_stackframe();
+
+	/* Search for an existing complete line. */
+	ok = return_existing_line(ctx, io_h, read_line);
+	if (ok) {
+		goto finish;
+	}
+
+	/*
+	 * No complete line in the buffer. We must read more
+	 * from the server.
+	 */
+	ev = tevent_context_init(frame);
+	if (ev == NULL) {
+		DBG_ERR("Failed to setup event context.\n");
+		goto finish;
+	}
+
+	/* Error return - must be talloc'ed. */
+	perror = talloc_zero(frame, uint64_t);
+	if (perror == NULL) {
+		goto finish;
+	}
+
+	for (;;) {
+		ssize_t pending = 0;
+		size_t read_size = 0;
+		struct iovec iov;
+		struct tevent_req *req = NULL;
+
+		/*
+		 * How much can we read ?
+		 */
+		pending = tstream_pending_bytes(io_h->stream);
+		if (pending < 0) {
+			DBG_ERR("tstream_pending_bytes failed (%s).\n",
+				strerror(errno));
+			goto finish;
+		}
+
+		read_size = pending;
+		/* Must read at least one byte. */
+		read_size = MIN(read_size, 1);
+
+		/* And max remaining buffer space. */
+		read_size = MAX(read_size,
+				(sizeof(io_h->r_buffer) - io_h->r_len));
+
+		if (read_size == 0) {
+			/* Buffer is full with no EOL. Error out. */
+			DBG_ERR("Line buffer full.\n");
+			goto finish;
+		}
+
+		iov.iov_base = io_h->r_buffer + io_h->r_len;
+		iov.iov_len = read_size;
+
+		/* Read the data. */
+		req = tstream_readv_send(frame,
+					ev,
+					io_h->stream,
+					&iov,
+					1);
+		if (req == NULL) {
+			DBG_ERR("out of memory.\n");
+			goto finish;
+		}
+
+		/* Callback when *all* data read. */
+		tevent_req_set_callback(req, readv_done, perror);
+
+		/* Set timeout. */
+		ok = tevent_req_set_endtime(req, ev,
+				timeval_current_ofs_msec(io_h->io_timeout));
+		if (!ok) {
+			DBG_ERR("can't set endtime\n");
+			goto finish;
+		}
+
+		/* Loop waiting for req to finish. */
+		ok = tevent_req_poll(req, ev);
+		if (!ok) {
+			DBG_ERR("tevent_req_poll failed\n");
+			goto finish;
+		}
+
+		/* Done with req - freed by the callback. */
+		req = NULL;
+
+		/*
+		 * Emit debug error if failed.
+		 * EPIPE may be success so, don't exit.
+		 */
+		if (*perror != 0 && *perror != EPIPE) {
+			DBG_DEBUG("Error %s\n", strerror((int)*perror));
+			errno = (int)*perror;
+			goto finish;
+		}
+
+		/*
+		 * We read read_size bytes. Extend the useable
+		 * buffer length.
+		 */
+		io_h->r_len += read_size;
+
+		/* Paranoia... */
+		SMB_ASSERT(io_h->r_len <= sizeof(io_h->r_buffer));
+
+		/* Exit if we have a line to return. */
+		ok = return_existing_line(ctx, io_h, read_line);
+		if (ok) {
+			goto finish;
+		}
+		/* No eol - keep reading. */
+	}
+
+  finish:
+
+	TALLOC_FREE(frame);
+	return ok;
+}
+
+bool virusfilter_io_writefl_readl(
+	struct virusfilter_io_handle *io_h,
+	char **read_line,
+	const char *fmt, ...)
+{
+	bool ok;
+
+	if (fmt) {
+		va_list ap;
+
+		va_start(ap, fmt);
+		ok = virusfilter_io_vwritefl(io_h, fmt, ap);
+		va_end(ap);
+
+		if (!ok) {
+			return ok;
+		}
+	}
+
+	ok = virusfilter_io_readl(talloc_tos(), io_h, read_line);
+	if (!ok) {
+		DBG_ERR("virusfilter_io_readl not OK: %d\n", ok);
+		return false;
+	}
+	if (io_h->r_len == 0) { /* EOF */
+		DBG_ERR("virusfilter_io_readl EOF\n");
+		return false;
+	}
+
+	return true;
+}
+
+struct virusfilter_cache *virusfilter_cache_new(
+	TALLOC_CTX *ctx,
+	int entry_limit,
+	time_t time_limit)
+{
+	struct virusfilter_cache *cache;
+
+	if (time_limit == 0) {
+		return NULL;
+	}
+
+	cache = talloc_zero(ctx, struct virusfilter_cache);
+	if (cache == NULL) {
+		DBG_ERR("talloc_zero failed.\n");
+		return NULL;
+	}
+
+	cache->cache = memcache_init(cache->ctx, entry_limit *
+				       (sizeof(struct virusfilter_cache_entry)
+				       + VIRUSFILTER_CACHE_BUFFER_SIZE));
+	if (cache->cache == NULL) {
+		DBG_ERR("memcache_init failed.\n");
+		return NULL;
+	}
+	cache->ctx = ctx;
+	cache->time_limit = time_limit;
+
+	return cache;
+}
+
+bool virusfilter_cache_entry_add(
+	struct virusfilter_cache *cache,
+	const char *directory,
+	const char *fname,
+	virusfilter_result result,
+	char *report)
+{
+	int blob_size = sizeof(struct virusfilter_cache_entry);
+	struct virusfilter_cache_entry *cache_e =
+					talloc_zero_size(NULL, blob_size);
+	int fname_len = 0;
+
+	if (fname == NULL || directory == NULL) {
+		TALLOC_FREE(report);
+		return false;
+	}
+
+	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+	if (fname == NULL) {
+		TALLOC_FREE(report);
+		return false;
+	}
+
+	fname_len = strlen(fname);
+
+	if (cache_e == NULL|| cache->time_limit == 0) {
+		TALLOC_FREE(report);
+		return false;
+	}
+
+	cache_e->result = result;
+	if (report != NULL) {
+		cache_e->report = talloc_steal(cache_e, report);
+	}
+	if (cache->time_limit > 0) {
+		cache_e->time = time(NULL);
+	}
+
+	memcache_add_talloc(cache->cache,
+			    VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+			    data_blob_const(fname, fname_len), &cache_e);
+
+	return true;
+}
+
+bool virusfilter_cache_entry_rename(
+	struct virusfilter_cache *cache,
+	const char *directory,
+	char *old_fname,
+	char *new_fname)
+{
+	int old_fname_len = 0;
+	int new_fname_len = 0;
+	struct virusfilter_cache_entry *new_data = NULL;
+	struct virusfilter_cache_entry *old_data = NULL;
+
+	if (old_fname == NULL || new_fname == NULL || directory == NULL) {
+		return false;
+	}
+
+	old_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, old_fname);
+	new_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, new_fname);
+
+	if (old_fname == NULL || new_fname == NULL) {
+		TALLOC_FREE(old_fname);
+		TALLOC_FREE(new_fname);
+		return false;
+	}
+
+	old_fname_len = strlen(old_fname);
+	new_fname_len = strlen(new_fname);
+
+	old_data = memcache_lookup_talloc(
+				cache->cache,
+				VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+				data_blob_const(old_fname, old_fname_len));
+
+	if (old_data == NULL) {
+		return false;
+	}
+
+	new_data = talloc_memdup(cache->ctx, old_data,
+				 sizeof(struct virusfilter_cache_entry));
+	if (new_data == NULL) {
+		return false;
+	}
+	new_data->report = talloc_strdup(new_data, old_data->report);
+
+	memcache_add_talloc(cache->cache,
+			VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+			data_blob_const(new_fname, new_fname_len), &new_data);
+
+	memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+			data_blob_const(old_fname, old_fname_len));
+
+	return true;
+}
+
+void virusfilter_cache_purge(struct virusfilter_cache *cache)
+{
+	memcache_flush(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
+}
+
+struct virusfilter_cache_entry *virusfilter_cache_get(
+	struct virusfilter_cache *cache,
+	const char *directory,
+	const char *fname)
+{
+	int fname_len = 0;
+	struct virusfilter_cache_entry *cache_e = NULL;
+	struct virusfilter_cache_entry *data = NULL;
+
+	if (fname == NULL || directory == NULL) {
+		return 0;
+	}
+
+	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+	if (fname == NULL) {
+		return 0;
+	}
+
+	fname_len = strlen(fname);
+
+	data = memcache_lookup_talloc(cache->cache,
+				      VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+				      data_blob_const(fname, fname_len));
+
+	if (data == NULL) {
+		return cache_e;
+	}
+
+	if (cache->time_limit > 0) {
+		if (time(NULL) - data->time  > cache->time_limit) {
+			DBG_DEBUG("Cache entry is too old: %s\n",
+				  fname);
+			virusfilter_cache_remove(cache, directory, fname);
+			return cache_e;
+		}
+	}
+	cache_e = talloc_memdup(cache->ctx, data,
+			       sizeof(struct virusfilter_cache_entry));
+	if (cache_e == NULL) {
+		return NULL;
+	}
+	if (data->report != NULL) {
+		cache_e->report = talloc_strdup(cache_e, data->report);
+	} else {
+		cache_e->report = NULL;
+	}
+
+	return cache_e;
+}
+
+void virusfilter_cache_remove(struct virusfilter_cache *cache,
+	const char *directory,
+	const char *fname)
+{
+	DBG_DEBUG("Purging cache entry: %s/%s\n", directory, fname);
+
+	if (fname == NULL || directory == NULL) {
+		return;
+	}
+
+	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+	if (fname == NULL) {
+		return;
+	}
+
+	memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+			data_blob_const(fname, strlen(fname)));
+}
+
+void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e)
+{
+	if (cache_e != NULL) {
+		TALLOC_FREE(cache_e->report);
+		cache_e->report = NULL;
+	}
+	TALLOC_FREE(cache_e);
+}
+
+/* Shell scripting
+ * ======================================================================
+ */
+
+int virusfilter_env_set(
+	TALLOC_CTX *mem_ctx,
+	char **env_list,
+	const char *name,
+	const char *value)
+{
+	char *env_new;
+	int ret;
+
+	env_new = talloc_asprintf(mem_ctx, "%s=%s", name, value);
+	if (env_new == NULL) {
+		DBG_ERR("talloc_asprintf failed\n");
+		return -1;
+	}
+
+	ret = strv_add(mem_ctx, env_list, env_new);
+
+	TALLOC_FREE(env_new);
+
+	return ret;
+}
+
+/* virusfilter_env version Samba's *_sub_advanced() in substitute.c */
+int virusfilter_shell_set_conn_env(
+	TALLOC_CTX *mem_ctx,
+	char **env_list,
+	connection_struct *conn)
+{
+	int snum = SNUM(conn);
+	char *server_addr_p;
+	char *client_addr_p;
+	const char *local_machine_name = get_local_machine_name();
+	fstring pidstr;
+	int ret;
+
+	if (local_machine_name == NULL || *local_machine_name == '\0') {
+		local_machine_name = lp_netbios_name();
+	}
+
+	server_addr_p = tsocket_address_inet_addr_string(
+				conn->sconn->local_address, talloc_tos());
+
+	if (server_addr_p != NULL) {
+		ret = strncmp("::ffff:", server_addr_p, 7);
+		if (ret == 0) {
+			server_addr_p += 7;
+		}
+		virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_IP",
+				    server_addr_p);
+	}
+	TALLOC_FREE(server_addr_p);
+
+	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_NAME",
+			    myhostname());
+	virusfilter_env_set(mem_ctx, env_list,
+			    "VIRUSFILTER_SERVER_NETBIOS_NAME",
+			    local_machine_name);
+	slprintf(pidstr,sizeof(pidstr)-1, "%ld", (long)getpid());
+	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_PID",
+			    pidstr);
+
+	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_NAME",
+			    lp_const_servicename(snum));
+	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_PATH",
+			    conn->cwd_fname->base_name);
+
+	client_addr_p = tsocket_address_inet_addr_string(
+				conn->sconn->remote_address, talloc_tos());
+
+	if (client_addr_p != NULL) {
+		ret = strncmp("::ffff:", client_addr_p, 7);
+		if (ret == 0) {
+			client_addr_p += 7;
+		}
+		virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_IP",
+				    client_addr_p);
+	}
+	TALLOC_FREE(client_addr_p);
+
+	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_NAME",
+			    conn->sconn->remote_hostname);
+	virusfilter_env_set(mem_ctx, env_list,
+			    "VIRUSFILTER_CLIENT_NETBIOS_NAME",
+			    get_remote_machine_name());
+
+	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_NAME",
+			    get_current_username());
+	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_DOMAIN",
+			    current_user_info.domain);
+
+	return 0;
+}
+
+/* Wrapper to Samba's smbrun() in smbrun.c */
+int virusfilter_shell_run(
+	TALLOC_CTX *mem_ctx,
+	const char *cmd,
+	char **env_list,
+	connection_struct *conn,
+	bool sanitize)
+{
+	int ret;
+
+	if (conn != NULL) {
+		ret = virusfilter_shell_set_conn_env(mem_ctx, env_list, conn);
+		if (ret == -1) {
+			return -1;
+		}
+	}
+
+	if (sanitize) {
+		return smbrun(cmd, NULL, strv_to_env(talloc_tos(), *env_list));
+	} else {
+		return smbrun_no_sanitize(cmd, NULL, strv_to_env(talloc_tos(),
+					  *env_list));
+	}
+}
diff --git a/source3/modules/vfs_virusfilter_utils.h b/source3/modules/vfs_virusfilter_utils.h
new file mode 100644
index 00000000000..69754aa6546
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.h
@@ -0,0 +1,177 @@
+/*
+   Samba-VirusFilter VFS modules
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_UTILS_H
+#define _VIRUSFILTER_UTILS_H
+
+#include "modules/vfs_virusfilter_common.h"
+#include "../lib/util/memcache.h"
+#include "../lib/util/strv.h"
+
+/*#define str_eq(s1, s2)		\
+	((strcmp((s1), (s2)) == 0) ? true : false)
+#define strn_eq(s1, s2, n)	\
+	((strncmp((s1), (s2), (n)) == 0) ? true : false) */
+
+/* "* 3" is for %-encoding */
+#define VIRUSFILTER_IO_URL_MAX		(PATH_MAX * 3)
+#define VIRUSFILTER_IO_BUFFER_SIZE	(VIRUSFILTER_IO_URL_MAX + 128)
+#define VIRUSFILTER_IO_EOL_SIZE		1
+#define VIRUSFILTER_IO_IOV_MAX		16
+#define VIRUSFILTER_CACHE_BUFFER_SIZE	(PATH_MAX + 128)
+
+struct virusfilter_io_handle {
+	struct tstream_context *stream;
+	int		connect_timeout;	/* msec */
+	int		io_timeout;		/* msec */
+
+	/* end-of-line character(s) */
+	char		w_eol[VIRUSFILTER_IO_EOL_SIZE];
+	int		w_eol_size;
+
+	/* end-of-line character(s) */
+	char		r_eol[VIRUSFILTER_IO_EOL_SIZE];
+	int		r_eol_size;
+
+	/* buffer */
+	char		r_buffer[VIRUSFILTER_IO_BUFFER_SIZE];
+	size_t		r_len;
+};
+
+struct virusfilter_cache_entry {
+	time_t time;
+	virusfilter_result result;
+	char *report;
+};
+
+struct virusfilter_cache {
+	struct memcache *cache;
+	TALLOC_CTX *ctx;
+	time_t time_limit;
+};
+
+/* ====================================================================== */
+
+char *virusfilter_string_sub(
+	TALLOC_CTX *mem_ctx,
+	connection_struct *conn,
+	const char *str);
+int virusfilter_vfs_next_move(
+	vfs_handle_struct *handle,
+	const struct smb_filename *smb_fname_src,
+	const struct smb_filename *smb_fname_dst);
+
+/* Line-based socket I/O */
+struct virusfilter_io_handle *virusfilter_io_new(
+	TALLOC_CTX *mem_ctx,
+	int connect_timeout,
+	int timeout);
+int virusfilter_io_set_connect_timeout(
+	struct virusfilter_io_handle *io_h,
+	int timeout);
+int virusfilter_io_set_io_timeout(
+	struct virusfilter_io_handle *io_h, int timeout);
+void virusfilter_io_set_writel_eol(
+	struct virusfilter_io_handle *io_h,
+	const char *eol,
+	int eol_size);
+void virusfilter_io_set_readl_eol(
+	struct virusfilter_io_handle *io_h,
+	const char *eol,
+	int eol_size);
+bool virusfilter_io_connect_path(
+	struct virusfilter_io_handle *io_h,
+	const char *path);
+bool virusfilter_io_disconnect(
+	struct virusfilter_io_handle *io_h);
+bool write_data_iov_timeout(
+	struct tstream_context *stream,
+	const struct iovec *iov,
+	size_t iovcnt,
+	int ms_timeout);
+bool virusfilter_io_write(
+	struct virusfilter_io_handle *io_h,
+	const char *data,
+	size_t data_size);
+bool virusfilter_io_writel(
+	struct virusfilter_io_handle *io_h,
+	const char *data,
+	size_t data_size);
+bool virusfilter_io_writefl(
+	struct virusfilter_io_handle *io_h,
+	const char *data_fmt, ...);
+bool virusfilter_io_vwritefl(
+	struct virusfilter_io_handle *io_h,
+	const char *data_fmt, va_list ap);
+bool virusfilter_io_writev(
+	struct virusfilter_io_handle *io_h, ...);
+bool virusfilter_io_writevl(
+	struct virusfilter_io_handle *io_h, ...);
+bool virusfilter_io_readl(TALLOC_CTX *ctx,
+			struct virusfilter_io_handle *io_h,
+			char **read_line);
+bool virusfilter_io_writefl_readl(
+	struct virusfilter_io_handle *io_h,
+	char **read_line,
+	const char *fmt, ...);
+
+/* Scan result cache */
+struct virusfilter_cache *virusfilter_cache_new(
+	TALLOC_CTX *ctx,
+	int entry_limit,
+	time_t time_limit);
+bool virusfilter_cache_entry_add(
+	struct virusfilter_cache *cache,
+	const char *directory,
+	const char *fname,
+	virusfilter_result result,
+	char *report);
+bool virusfilter_cache_entry_rename(
+	struct virusfilter_cache *cache,
+	const char *directory,
+	char *old_fname,
+	char *new_fname);
+void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e);
+struct virusfilter_cache_entry *virusfilter_cache_get(
+	struct virusfilter_cache *cache,
+	const char *directory,
+	const char *fname);
+void virusfilter_cache_remove(
+	struct virusfilter_cache *cache,
+	const char *directory,
+	const char *fname);
+void virusfilter_cache_purge(struct virusfilter_cache *cache);
+
+/* Shell scripting */
+int virusfilter_env_set(
+	TALLOC_CTX *mem_ctx,
+	char **env_list,
+	const char *name,
+	const char *value);
+int virusfilter_shell_set_conn_env(
+	TALLOC_CTX *mem_ctx,
+	char **env_list,
+	connection_struct *conn);
+int virusfilter_shell_run(
+	TALLOC_CTX *mem_ctx,
+	const char *cmd,
+	char **env_list,
+	connection_struct *conn,
+	bool sanitize);
+
+#endif /* _VIRUSFILTER_UTILS_H */
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index 079302cd584..f4179477376 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -17,6 +17,11 @@ bld.SAMBA3_LIBRARY('non_posix_acls',
                    deps='samba-util vfs',
                    private_library=True)
 
+bld.SAMBA3_SUBSYSTEM('VFS_VIRUSFILTER_UTILS',
+                   source='vfs_virusfilter_utils.c',
+                   deps='strv',
+                   enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter')))
+
 bld.SAMBA3_SUBSYSTEM('VFS_AIXACL_UTIL',
                     source='vfs_aixacl_util.c',
                     enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl2')))
@@ -505,6 +510,14 @@ bld.SAMBA3_MODULE('vfs_snapper',
                  internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_snapper'),
                  enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_snapper'))
 
+bld.SAMBA3_MODULE('vfs_virusfilter',
+                 subsystem='vfs',
+                 source='vfs_virusfilter.c',
+                 deps='samba-util VFS_VIRUSFILTER_UTILS',
+                 init_function='',
+                 internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'),
+                 enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter'))
+
 bld.SAMBA3_MODULE('vfs_vxfs',
                  subsystem='vfs',
                  source='lib_vxfs.c vfs_vxfs.c',
diff --git a/source3/wscript b/source3/wscript
index 8751833b221..6d2d94bae87 100644
--- a/source3/wscript
+++ b/source3/wscript
@@ -1669,7 +1669,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 6e58bbd544cd6e075ba59f3602f7b5bb4de230b3 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 23 Jan 2018 19:37:14 +0100
Subject: [PATCH 3/6] !fixup Samba-VirusFilter: common headers and sources.

---
 source3/modules/vfs_virusfilter.c | 457 +++++++++++++++++++++-----------------
 1 file changed, 256 insertions(+), 201 deletions(-)

diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index 8703f2d303b..4f0f9f88922 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -511,13 +511,59 @@ static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx,
 	return 0;
 }
 
-static virusfilter_action virusfilter_do_infected_file_action(
+static char *quarantine_check_tree(TALLOC_CTX *mem_ctx,
+				   struct vfs_handle_struct *handle,
+				   struct virusfilter_config *config,
+				   const struct smb_filename *smb_fname,
+				   char *q_dir_in,
+				   char *cwd_fname)
+{
+	char *temp_path = NULL;
+	char *q_dir_out = NULL;
+	bool ok;
+
+	temp_path = talloc_asprintf(talloc_tos(), "%s/%s", q_dir_in, cwd_fname);
+	if (temp_path == NULL) {
+		DBG_ERR("talloc_asprintf failed\n");
+		goto out;
+	}
+
+	become_root();
+	ok = quarantine_directory_exist(handle,	temp_path);
+	unbecome_root();
+	if (ok) {
+		DBG_DEBUG("quarantine: directory [%s] exists\n", temp_path);
+		q_dir_out = talloc_move(mem_ctx, &temp_path);
+		goto out;
+	}
+
+	DBG_DEBUG("quarantine: Creating directory %s\n", temp_path);
+
+	become_root();
+	ok = quarantine_create_dir(handle, config, temp_path);
+	unbecome_root();
+	if (!ok) {
+		DBG_NOTICE("Could not create quarantine directory [%s], "
+			   "ignoring for [%s]\n",
+			   temp_path, smb_fname_str_dbg(smb_fname));
+		goto out;
+	}
+
+	q_dir_out = talloc_move(mem_ctx, &temp_path);
+
+out:
+	TALLOC_FREE(temp_path);
+    	return q_dir_out;
+}
+
+static virusfilter_action infected_file_action_quarantine(
 	struct vfs_handle_struct *handle,
 	struct virusfilter_config *config,
+	TALLOC_CTX *mem_ctx,
 	const struct files_struct *fsp,
 	const char **filepath_newp)
 {
-	TALLOC_CTX *mem_ctx = talloc_tos();
+	TALLOC_CTX *frame = talloc_stackframe();
 	connection_struct *conn = handle->conn;
 	char *cwd_fname = fsp->conn->cwd_fname->base_name;
 	char *fname = fsp->fsp_name->base_name;
@@ -528,237 +574,246 @@ static virusfilter_action virusfilter_do_infected_file_action(
 	char *q_suffix = NULL;
 	char *q_filepath = NULL;
 	char *dir_name = NULL;
-	char *temp_path = NULL;
 	const char *base_name = NULL;
 	char *rand_filename_component = NULL;
+	virusfilter_action action;
 	bool ok = false;
 	int ret = -1;
 	int saved_errno = 0;
 
-	*filepath_newp = NULL;
-
-	switch (config->infected_file_action) {
-	case VIRUSFILTER_ACTION_RENAME:
-		q_prefix = virusfilter_string_sub(mem_ctx, conn,
-						config->rename_prefix);
-		q_suffix = virusfilter_string_sub(mem_ctx, conn,
-						config->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;
-		}
-
-		ok = parent_dirname(mem_ctx, fname, &q_dir, &base_name);
+	q_dir = virusfilter_string_sub(frame, conn,
+				       config->quarantine_dir);
+	q_prefix = virusfilter_string_sub(frame, conn,
+					  config->quarantine_prefix);
+	q_suffix = virusfilter_string_sub(frame, conn,
+					  config->quarantine_suffix);
+	if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
+		DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+			"memory\n", cwd_fname, fname);
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
+
+	if (config->quarantine_keep_name || config->quarantine_keep_tree) {
+		ok = parent_dirname(frame, smb_fname->base_name,
+				    &dir_name, &base_name);
 		if (!ok) {
-			DBG_ERR("Rename failed: %s/%s: Cannot allocate "
-				"memory\n", cwd_fname, fname);
-			TALLOC_FREE(q_prefix);
-			TALLOC_FREE(q_suffix);
-			return VIRUSFILTER_ACTION_DO_NOTHING;
+			DBG_ERR("parent_dirname failed\n");
+			action = VIRUSFILTER_ACTION_DO_NOTHING;
+			goto out;
 		}
 
-		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;
+		if (config->quarantine_keep_tree) {
+			char *tree = NULL;
+
+			tree = quarantine_check_tree(frame, handle, config,
+						     smb_fname, q_dir,
+						     cwd_fname);
+			if (tree == NULL) {
+				/*
+				 * If we can't create the tree, just move it
+				 * into the toplevel quarantine dir.
+				 */
+				tree = q_dir;
+			}
+			q_dir = tree;
 		}
+	}
 
-		q_filepath = talloc_asprintf(talloc_tos(), "%s/%s%s%s", q_dir,
-					     q_prefix, base_name, q_suffix);
+	/* Get a 16 byte + \0 random filename component. */
+	rand_filename_component = generate_random_str(frame, 16);
+	if (rand_filename_component == NULL) {
+		DBG_ERR("generate_random_str failed\n");
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-		TALLOC_FREE(q_dir);
-		TALLOC_FREE(q_prefix);
-		TALLOC_FREE(q_suffix);
+	if (config->quarantine_keep_name) {
+		q_filepath = talloc_asprintf(frame, "%s/%s%s%s-%s",
+					     q_dir, q_prefix,
+					     base_name, q_suffix,
+					     rand_filename_component);
+	} else {
+		q_filepath = talloc_asprintf(frame, "%s/%s%s",
+					     q_dir, q_prefix,
+					     rand_filename_component);
+	}
+	if (q_filepath == NULL) {
+		DBG_ERR("talloc_asprintf failed\n");
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-		q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
-						  smb_fname->stream_name, NULL,
-						  smb_fname->flags);
-		if (q_smb_fname == NULL) {
-			TALLOC_FREE(q_filepath);
-			return VIRUSFILTER_ACTION_DO_NOTHING;
-		}
+	q_smb_fname = synthetic_smb_fname(frame, q_filepath,
+					  smb_fname->stream_name,
+					  NULL, smb_fname->flags);
+	if (q_smb_fname == NULL) {
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-		become_root();
-		ret = virusfilter_vfs_next_move(handle, smb_fname,
-						   q_smb_fname);
-		if (ret == -1) {
-			saved_errno = errno;
-		}
-		unbecome_root();
+	become_root();
+	ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
+	if (ret == -1) {
+		saved_errno = errno;
+	}
+	unbecome_root();
+	if (ret == -1) {
+		DBG_ERR("Quarantine [%s/%s] rename to %s failed: %s\n",
+			cwd_fname, fname, q_filepath, strerror(saved_errno));
+		errno = saved_errno;
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-		if (ret == -1) {
-			DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
-				cwd_fname, fname,
-				strerror(saved_errno));
-			errno = saved_errno;
-			return VIRUSFILTER_ACTION_DO_NOTHING;
-		}
+	*filepath_newp = talloc_move(mem_ctx, &q_filepath);
+	action = VIRUSFILTER_ACTION_QUARANTINE;
 
-		*filepath_newp = q_filepath;
+out:
+	TALLOC_FREE(frame);
+	return action;
+}
 
-		return VIRUSFILTER_ACTION_RENAME;
+static virusfilter_action infected_file_action_rename(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	TALLOC_CTX *mem_ctx,
+	const struct files_struct *fsp,
+	const char **filepath_newp)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	connection_struct *conn = handle->conn;
+	char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	char *fname = fsp->fsp_name->base_name;
+	const struct smb_filename *smb_fname = fsp->fsp_name;
+	struct smb_filename *q_smb_fname = NULL;
+	char *q_dir = NULL;
+	char *q_prefix = NULL;
+	char *q_suffix = NULL;
+	char *q_filepath = NULL;
+	const char *base_name = NULL;
+	virusfilter_action action;
+	bool ok = false;
+	int ret = -1;
+	int saved_errno = 0;
 
-	case VIRUSFILTER_ACTION_QUARANTINE:
-		q_dir = virusfilter_string_sub(mem_ctx, conn,
-					config->quarantine_dir);
-		q_prefix = virusfilter_string_sub(mem_ctx, conn,
-					config->quarantine_prefix);
-		q_suffix = virusfilter_string_sub(mem_ctx, conn,
-					config->quarantine_suffix);
-		if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
-			DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
-				"memory\n", cwd_fname, fname);
-			TALLOC_FREE(q_dir);
-			TALLOC_FREE(q_prefix);
-			TALLOC_FREE(q_suffix);
-			return VIRUSFILTER_ACTION_DO_NOTHING;
-		}
+	q_prefix = virusfilter_string_sub(frame, conn,
+					  config->rename_prefix);
+	q_suffix = virusfilter_string_sub(frame, conn,
+					  config->rename_suffix);
+	if (q_prefix == NULL || q_suffix == NULL) {
+		DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+			"memory\n", cwd_fname, fname);
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-		if (config->quarantine_keep_name ||
-		    config->quarantine_keep_tree)
-		{
-			ok = parent_dirname(mem_ctx, smb_fname->base_name,
-					    &dir_name, &base_name);
-			if (!ok) {
-				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);
-				TALLOC_FREE(rand_filename_component);
-				return VIRUSFILTER_ACTION_DO_NOTHING;
-			}
+	ok = parent_dirname(frame, fname, &q_dir, &base_name);
+	if (!ok) {
+		DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+			"memory\n", cwd_fname, fname);
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-			if (config->quarantine_keep_tree) {
-				temp_path = talloc_asprintf(mem_ctx,
-						"%s/%s", q_dir, cwd_fname);
-				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);
-					TALLOC_FREE(rand_filename_component);
-					return VIRUSFILTER_ACTION_DO_NOTHING;
-				}
+	if (q_dir == NULL) {
+		DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+			"memory\n", cwd_fname, fname);
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-				become_root();
-				ok = quarantine_directory_exist(handle,
-								temp_path);
-				if (ok) {
-					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);
-					ok = quarantine_create_dir(handle,
-							config, temp_path);
-					if (!ok) {
-						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();
-			}
-		}
+	q_filepath = talloc_asprintf(frame, "%s/%s%s%s", q_dir,
+				     q_prefix, base_name, q_suffix);
 
-		/* Get a 16 byte + \0 random filename component. */
-		rand_filename_component = generate_random_str(talloc_tos(), 16);
-		if (rand_filename_component == NULL) {
-			DBG_ERR("Quarantine failed: %s/%s: Cannot "
-				"allocate memory\n", cwd_fname, fname);
-			TALLOC_FREE(dir_name);
-			TALLOC_FREE(q_dir);
-			TALLOC_FREE(q_prefix);
-			TALLOC_FREE(q_suffix);
-			return VIRUSFILTER_ACTION_DO_NOTHING;
-		}
-		if (config->quarantine_keep_name) {
-			q_filepath = talloc_asprintf(talloc_tos(),
-					"%s/%s%s%s-%s",
-					q_dir, q_prefix, base_name, q_suffix,
-					rand_filename_component);
-		} else {
-			q_filepath = talloc_asprintf(talloc_tos(),
-				"%s/%s%s", q_dir, q_prefix,
-				rand_filename_component);
-		}
+	q_smb_fname = synthetic_smb_fname(frame, q_filepath,
+					  smb_fname->stream_name, NULL,
+					  smb_fname->flags);
+	if (q_smb_fname == NULL) {
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-		TALLOC_FREE(dir_name);
-		TALLOC_FREE(q_dir);
-		TALLOC_FREE(q_prefix);
-		TALLOC_FREE(q_suffix);
-		TALLOC_FREE(rand_filename_component);
+	become_root();
+	ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
+	if (ret == -1) {
+		saved_errno = errno;
+	}
+	unbecome_root();
 
-		if (q_filepath == NULL) {
-			DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
-				"memory\n", cwd_fname, fname);
-			return VIRUSFILTER_ACTION_DO_NOTHING;
-		}
+	if (ret == -1) {
+		DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
+			cwd_fname, fname, strerror(saved_errno));
+		errno = saved_errno;
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		goto out;
+	}
 
-		q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
-			smb_fname->stream_name, NULL, smb_fname->flags);
-		if (q_smb_fname == NULL) {
-			TALLOC_FREE(q_filepath);
-			return VIRUSFILTER_ACTION_DO_NOTHING;
-		}
+	*filepath_newp = talloc_move(mem_ctx, &q_filepath);
 
-		become_root();
-		ret = virusfilter_vfs_next_move(handle, smb_fname,
-						   q_smb_fname);
-		if (ret == -1) {
-			saved_errno = errno;
-		}
-		unbecome_root();
-		if (ret == -1) {
-			DBG_ERR("Quarantine failed: %s/%s: Rename to %s "
-				"failed: %s\n",
-				cwd_fname, fname,
-				q_filepath,
-				strerror(saved_errno));
-			errno = saved_errno;
-			return VIRUSFILTER_ACTION_DO_NOTHING;
-		}
+out:
+	TALLOC_FREE(frame);
+	return VIRUSFILTER_ACTION_RENAME;
+}
+
+static virusfilter_action infected_file_action_delete(
+	struct vfs_handle_struct *handle,
+	const struct files_struct *fsp)
+{
+	int ret;
+	int saved_errno = 0;
+
+	become_root();
+	ret = SMB_VFS_NEXT_UNLINK(handle, fsp->fsp_name);
+	if (ret == -1) {
+		saved_errno = errno;
+	}
+	unbecome_root();
+	if (ret == -1) {
+		DBG_ERR("Delete [%s/%s] failed: %s\n",
+			fsp->conn->cwd_fname->base_name,
+			fsp->fsp_name->base_name,
+			strerror(saved_errno));
+		errno = saved_errno;
+		return VIRUSFILTER_ACTION_DO_NOTHING;
+	}
 
-		*filepath_newp = q_filepath;
+	return VIRUSFILTER_ACTION_DELETE;
+}
 
-		return VIRUSFILTER_ACTION_QUARANTINE;
+static virusfilter_action virusfilter_do_infected_file_action(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	TALLOC_CTX *mem_ctx,
+	const struct files_struct *fsp,
+	const char **filepath_newp)
+{
+	virusfilter_action action;
+
+	*filepath_newp = NULL;
+
+	switch (config->infected_file_action) {
+	case VIRUSFILTER_ACTION_RENAME:
+		action = infected_file_action_rename(handle, config, mem_ctx,
+						     fsp, filepath_newp);
+		break;
+
+	case VIRUSFILTER_ACTION_QUARANTINE:
+		action = infected_file_action_quarantine(handle, config, mem_ctx,
+							 fsp, filepath_newp);
+		break;
 
 	case VIRUSFILTER_ACTION_DELETE:
-		become_root();
-		ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
-		if (ret == -1) {
-			saved_errno = errno;
-		}
-		unbecome_root();
-		if (ret == -1) {
-			DBG_ERR("Delete failed: %s/%s: Unlink failed: %s\n",
-				cwd_fname, fname,
-				strerror(saved_errno));
-			errno = saved_errno;
-			return VIRUSFILTER_ACTION_DO_NOTHING;
-		}
-		return VIRUSFILTER_ACTION_DELETE;
+		action = infected_file_action_delete(handle, fsp);
+		break;
 
 	case VIRUSFILTER_ACTION_DO_NOTHING:
 	default:
-		return VIRUSFILTER_ACTION_DO_NOTHING;
+		action = VIRUSFILTER_ACTION_DO_NOTHING;
+		break;
 	}
+
+	return action;
 }
 
 static virusfilter_action virusfilter_treat_infected_file(
@@ -781,8 +836,8 @@ static virusfilter_action virusfilter_treat_infected_file(
 	int command_result;
 	int ret;
 
-	action = virusfilter_do_infected_file_action(handle, config, fsp,
-						     &filepath_q);
+	action = virusfilter_do_infected_file_action(handle, config, mem_ctx,
+						     fsp, &filepath_q);
 	for (i=0; virusfilter_actions[i].name; i++) {
 		if (virusfilter_actions[i].value == action) {
 			action_name = virusfilter_actions[i].name;
-- 
2.13.6


From 5e757e49ceb2831bcd75d538f5d3d3597099bacf 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/6] 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>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 docs-xml/manpages/vfs_virusfilter.8.xml  |   6 +
 source3/modules/vfs_virusfilter.c        |  15 +-
 source3/modules/vfs_virusfilter_common.h |   2 +
 source3/modules/vfs_virusfilter_sophos.c | 391 +++++++++++++++++++++++++++++++
 source3/modules/wscript_build            |   5 +-
 5 files changed, 414 insertions(+), 5 deletions(-)
 create mode 100644 source3/modules/vfs_virusfilter_sophos.c

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index eb6112e3827..c4bc8920043 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -41,6 +41,10 @@
 		<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 4f0f9f88922..7c563c3105b 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -441,10 +441,17 @@ static int virusfilter_vfs_connect(
 		return -1;
 	}
 
-	/* This goes away as soon as the next commit adds an actual backend... */
-	if (config->backend == NULL) {
-		DBG_INFO("Not implemented\n");
-		return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+	switch (backend) {
+	case VIRUSFILTER_SCANNER_SOPHOS:
+		ret = virusfilter_sophos_init(config);
+		break;
+	default:
+		DBG_ERR("Unhandled scanner %d\n", backend);
+		return -1;
+	}
+	if (ret != 0) {
+		DBG_ERR("Scanner backend init failed\n");
+		return -1;
 	}
 
 	if (config->backend->fns->connect != NULL) {
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
index 468883fdaf8..69519c9daa4 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -146,4 +146,6 @@ struct virusfilter_backend {
 	void *backend_private;
 };
 
+int virusfilter_sophos_init(struct virusfilter_config *config);
+
 #endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c
new file mode 100644
index 00000000000..72051cd64a2
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_sophos.c
@@ -0,0 +1,391 @@
+/*
+   Samba-VirusFilter VFS modules
+   Sophos Anti-Virus savdid (SSSP/1.0) support
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/* Default values for standard "extra" configuration variables */
+#ifdef SOPHOS_DEFAULT_SOCKET_PATH
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH	SOPHOS_DEFAULT_SOCKET_PATH
+#else
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH	"/var/run/savdi/sssp.sock"
+#endif
+
+static void virusfilter_sophos_scan_end(struct virusfilter_config *config);
+
+/* Python's urllib.quote(string[, safe]) clone */
+static int virusfilter_url_quote(const char *src, char *dst, int dst_size)
+{
+	char *dst_c = dst;
+	static char hex[] = "0123456789ABCDEF";
+
+	for (; *src != '\0'; src++) {
+		if ((*src < '0' && *src != '-' && *src != '.' && *src != '/') ||
+		    (*src > '9' && *src < 'A') ||
+		    (*src > 'Z' && *src < 'a' && *src != '_') ||
+		    (*src > 'z'))
+		{
+			if (dst_size < 4) {
+				return -1;
+			}
+			*dst_c++ = '%';
+			*dst_c++ = hex[(*src >> 4) & 0x0F];
+			*dst_c++ = hex[*src & 0x0F];
+			dst_size -= 3;
+		} else {
+			if (dst_size < 2) {
+				return -1;
+			}
+			*dst_c++ = *src;
+			dst_size--;
+		}
+	}
+
+	*dst_c = '\0';
+
+	return (dst_c - dst);
+}
+
+static int virusfilter_sophos_connect(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	const char *svc,
+	const char *user)
+{
+	virusfilter_io_set_readl_eol(config->io_h, "\x0D\x0A", 2);
+
+	return 0;
+}
+
+static virusfilter_result virusfilter_sophos_scan_ping(
+	struct virusfilter_config *config)
+{
+	struct virusfilter_io_handle *io_h = config->io_h;
+	char *reply = NULL;
+	bool ok;
+	int ret;
+
+	/* SSSP/1.0 has no "PING" command */
+	ok = virusfilter_io_writel(io_h, "SSSP/1.0 OPTIONS\n", 17);
+	if (!ok) {
+		return VIRUSFILTER_RESULT_ERROR;
+	}
+
+	for (;;) {
+		ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+		if (!ok) {
+			return VIRUSFILTER_RESULT_ERROR;
+		}
+		ret = strcmp(reply, "");
+		if (ret == 0) {
+			break;
+		}
+		TALLOC_FREE(reply);
+	}
+
+	TALLOC_FREE(reply);
+	return VIRUSFILTER_RESULT_OK;
+}
+
+static virusfilter_result virusfilter_sophos_scan_init(
+	struct virusfilter_config *config)
+{
+	struct virusfilter_io_handle *io_h = config->io_h;
+	char *reply = NULL;
+	int ret;
+	bool ok;
+
+	if (io_h->stream != NULL) {
+		DBG_DEBUG("SSSP: Checking if connection is alive\n");
+
+		ret = virusfilter_sophos_scan_ping(config);
+		if (ret == VIRUSFILTER_RESULT_OK)
+		{
+			DBG_DEBUG("SSSP: Re-using existent connection\n");
+			return VIRUSFILTER_RESULT_OK;
+		}
+
+		DBG_INFO("SSSP: Closing dead connection\n");
+		virusfilter_sophos_scan_end(config);
+	}
+
+
+	DBG_INFO("SSSP: Connecting to socket: %s\n",
+		config->socket_path);
+
+	become_root();
+	ok = virusfilter_io_connect_path(io_h, config->socket_path);
+	unbecome_root();
+
+	if (!ok) {
+		DBG_ERR("SSSP: Connecting to socket failed: %s: %s\n",
+			config->socket_path, strerror(errno));
+		return VIRUSFILTER_RESULT_ERROR;
+	}
+
+	ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+	if (!ok) {
+		DBG_ERR("SSSP: Reading greeting message failed: %s\n",
+			strerror(errno));
+		goto virusfilter_sophos_scan_init_failed;
+	}
+	ret = strncmp(reply, "OK SSSP/1.0", 11);
+	if (ret != 0) {
+		DBG_ERR("SSSP: Invalid greeting message: %s\n",
+			reply);
+		goto virusfilter_sophos_scan_init_failed;
+	}
+
+	DBG_DEBUG("SSSP: Connected\n");
+
+	DBG_INFO("SSSP: Configuring\n");
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply,
+	    "SSSP/1.0 OPTIONS\noutput:brief\nsavigrp:GrpArchiveUnpack %d\n",
+	    config->scan_archive ? 1 : 0);
+	if (!ok) {
+		DBG_ERR("SSSP: OPTIONS: I/O error: %s\n", strerror(errno));
+		goto virusfilter_sophos_scan_init_failed;
+	}
+	ret = strncmp(reply, "ACC ", 4);
+	if (ret != 0) {
+		DBG_ERR("SSSP: OPTIONS: Not accepted: %s\n", reply);
+		goto virusfilter_sophos_scan_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+	if (!ok) {
+		DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+		goto virusfilter_sophos_scan_init_failed;
+	}
+	ret = strncmp(reply, "DONE OK ", 8);
+	if (ret != 0) {
+		DBG_ERR("SSSP: OPTIONS failed: %s\n", reply);
+		goto virusfilter_sophos_scan_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+	if (!ok) {
+		DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+		goto virusfilter_sophos_scan_init_failed;
+	}
+	ret = strcmp(reply, "");
+	if (ret != 0) {
+		DBG_ERR("SSSP: OPTIONS: Invalid reply: %s\n", reply);
+		goto virusfilter_sophos_scan_init_failed;
+	}
+
+	DBG_DEBUG("SSSP: Configured\n");
+
+	return VIRUSFILTER_RESULT_OK;
+
+virusfilter_sophos_scan_init_failed:
+
+	TALLOC_FREE(reply);
+
+	virusfilter_sophos_scan_end(config);
+
+	return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_sophos_scan_end(
+	struct virusfilter_config *config)
+{
+	struct virusfilter_io_handle *io_h = config->io_h;
+
+	DBG_INFO("SSSP: Disconnecting\n");
+
+	virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_sophos_scan(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	const struct files_struct *fsp,
+	char **reportp)
+{
+	char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	const char *fname = fsp->fsp_name->base_name;
+	char fileurl[VIRUSFILTER_IO_URL_MAX+1];
+	int fileurl_len, fileurl_len2;
+	struct virusfilter_io_handle *io_h = config->io_h;
+	virusfilter_result result = VIRUSFILTER_RESULT_ERROR;
+	char *report = NULL;
+	char *reply = NULL;
+	char *reply_token, *reply_saveptr;
+	int ret;
+	bool ok;
+
+	DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+	fileurl_len = virusfilter_url_quote(cwd_fname, fileurl,
+					    VIRUSFILTER_IO_URL_MAX);
+	if (fileurl_len < 0) {
+		DBG_ERR("virusfilter_url_quote failed: File path too long: "
+			"%s/%s\n", cwd_fname, fname);
+		result = VIRUSFILTER_RESULT_ERROR;
+		report = talloc_asprintf(talloc_tos(), "File path too long");
+		goto virusfilter_sophos_scan_return;
+	}
+	fileurl[fileurl_len] = '/';
+	fileurl_len++;
+
+	fileurl_len += fileurl_len2 = virusfilter_url_quote(fname,
+		fileurl + fileurl_len, VIRUSFILTER_IO_URL_MAX - fileurl_len);
+	if (fileurl_len2 < 0) {
+		DBG_ERR("virusfilter_url_quote failed: File path too long: "
+			"%s/%s\n", cwd_fname, fname);
+		result = VIRUSFILTER_RESULT_ERROR;
+		report = talloc_asprintf(talloc_tos(), "File path too long");
+		goto virusfilter_sophos_scan_return;
+	}
+	fileurl_len += fileurl_len2;
+
+	ok = virusfilter_io_writevl(io_h, "SSSP/1.0 SCANFILE ", 18, fileurl,
+				    fileurl_len, NULL);
+	if (!ok) {
+		DBG_ERR("SSSP: SCANFILE: Write error: %s\n",
+		      strerror(errno));
+		goto virusfilter_sophos_scan_io_error;
+	}
+
+	ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+	if (!ok) {
+		DBG_ERR("SSSP: SCANFILE: Read error: %s\n", strerror(errno));
+		goto virusfilter_sophos_scan_io_error;
+	}
+	ret = strncmp(reply, "ACC ", 4);
+	if (ret != 0) {
+		DBG_ERR("SSSP: SCANFILE: Not accepted: %s\n",
+			reply);
+		result = VIRUSFILTER_RESULT_ERROR;
+		goto virusfilter_sophos_scan_return;
+	}
+
+	TALLOC_FREE(reply);
+
+	result = VIRUSFILTER_RESULT_CLEAN;
+	for (;;) {
+		ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+		if (!ok) {
+			DBG_ERR("SSSP: SCANFILE: Read error: %s\n",
+				strerror(errno));
+			goto virusfilter_sophos_scan_io_error;
+		}
+
+		ret = strcmp(reply, "");
+		if (ret == 0) {
+			break;
+		}
+
+		reply_token = strtok_r(reply, " ", &reply_saveptr);
+
+		if (strcmp(reply_token, "VIRUS") == 0) {
+			result = VIRUSFILTER_RESULT_INFECTED;
+			reply_token = strtok_r(NULL, " ", &reply_saveptr);
+			if (reply_token != NULL) {
+				  report = talloc_strdup(talloc_tos(),
+							 reply_token);
+			} else {
+				  report = talloc_asprintf(talloc_tos(),
+							"UNKNOWN INFECTION");
+			}
+		} else if (strcmp(reply_token, "OK") == 0) {
+
+			/* Ignore */
+		} else if (strcmp(reply_token, "DONE") == 0) {
+			reply_token = strtok_r(NULL, "", &reply_saveptr);
+			if (reply_token != NULL &&
+
+			    /* Succeed */
+			    strncmp(reply_token, "OK 0000 ", 8) != 0 &&
+
+			    /* Infected */
+			    strncmp(reply_token, "OK 0203 ", 8) != 0)
+			{
+				DBG_ERR("SSSP: SCANFILE: Error: %s\n",
+					reply_token);
+				result = VIRUSFILTER_RESULT_ERROR;
+				report = talloc_asprintf(talloc_tos(),
+							 "Scanner error: %s\n",
+							 reply_token);
+			}
+		} else {
+			DBG_ERR("SSSP: SCANFILE: Invalid reply: %s\n",
+				reply_token);
+			result = VIRUSFILTER_RESULT_ERROR;
+			report = talloc_asprintf(talloc_tos(), "Scanner "
+						 "communication error");
+		}
+
+		TALLOC_FREE(reply);
+	}
+
+virusfilter_sophos_scan_return:
+	TALLOC_FREE(reply);
+
+	if (report == NULL) {
+		*reportp = talloc_asprintf(talloc_tos(),
+					   "Scanner report memory error");
+	} else {
+		*reportp = report;
+	}
+
+	return result;
+
+virusfilter_sophos_scan_io_error:
+	*reportp = talloc_asprintf(talloc_tos(),
+				   "Scanner I/O error: %s\n", strerror(errno));
+
+	return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_sophos ={
+	.connect = virusfilter_sophos_connect,
+	.disconnect = NULL,
+	.scan_init = virusfilter_sophos_scan_init,
+	.scan = virusfilter_sophos_scan,
+	.scan_end = virusfilter_sophos_scan_end,
+};
+
+int virusfilter_sophos_init(struct virusfilter_config *config)
+{
+	struct virusfilter_backend *backend = NULL;
+
+	if (config->socket_path == NULL) {
+		config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+	}
+
+	backend = talloc_zero(config, struct virusfilter_backend);
+	if (backend == NULL) {
+		return -1;
+	}
+
+	backend->fns = &virusfilter_backend_sophos;
+	backend->name = "sophos";
+
+	config->backend = backend;
+	return 0;
+}
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index f4179477376..14fddb3b30e 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -512,7 +512,10 @@ bld.SAMBA3_MODULE('vfs_snapper',
 
 bld.SAMBA3_MODULE('vfs_virusfilter',
                  subsystem='vfs',
-                 source='vfs_virusfilter.c',
+                 source='''
+                 vfs_virusfilter.c
+                 vfs_virusfilter_sophos.c
+                 ''',
                  deps='samba-util VFS_VIRUSFILTER_UTILS',
                  init_function='',
                  internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'),
-- 
2.13.6


From 07f5191bd974d7aa0c5e5e122f5be52f71821652 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 5/6] 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>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 docs-xml/manpages/vfs_virusfilter.8.xml  |  27 +-
 source3/modules/vfs_virusfilter.c        |   3 +
 source3/modules/vfs_virusfilter_common.h |   1 +
 source3/modules/vfs_virusfilter_fsav.c   | 451 +++++++++++++++++++++++++++++++
 source3/modules/wscript_build            |   1 +
 5 files changed, 481 insertions(+), 2 deletions(-)
 create mode 100644 source3/modules/vfs_virusfilter_fsav.c

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index c4bc8920043..2e70ab0b553 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -44,6 +44,8 @@
 		<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>
 
@@ -219,7 +223,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>
 
@@ -227,7 +231,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>
 
@@ -309,6 +322,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/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index 7c563c3105b..8b4141592fb 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -445,6 +445,9 @@ static int virusfilter_vfs_connect(
 	case VIRUSFILTER_SCANNER_SOPHOS:
 		ret = virusfilter_sophos_init(config);
 		break;
+	case VIRUSFILTER_SCANNER_FSAV:
+		ret = virusfilter_fsav_init(config);
+		break;
 	default:
 		DBG_ERR("Unhandled scanner %d\n", backend);
 		return -1;
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
index 69519c9daa4..a28ce2978fa 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -147,5 +147,6 @@ struct virusfilter_backend {
 };
 
 int virusfilter_sophos_init(struct virusfilter_config *config);
+int virusfilter_fsav_init(struct virusfilter_config *config);
 
 #endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_fsav.c b/source3/modules/vfs_virusfilter_fsav.c
new file mode 100644
index 00000000000..2b874d7db43
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_fsav.c
@@ -0,0 +1,451 @@
+/*
+   Samba-VirusFilter VFS modules
+   F-Secure Anti-Virus fsavd support
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+#ifdef FSAV_DEFAULT_SOCKET_PATH
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH	FSAV_DEFAULT_SOCKET_PATH
+#else
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH	"/tmp/.fsav-0"
+#endif
+
+/* Default values for module-specific configuration variables */
+/* 5 = F-Secure Linux 7 or later? */
+
+#define VIRUSFILTER_DEFAULT_FSAV_PROTOCOL		5
+#define VIRUSFILTER_DEFAULT_SCAN_RISKWARE		false
+#define VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST		true
+#define VIRUSFILTER_DEFAULT_FILTER_FILENAME		false
+
+struct virusfilter_fsav_config {
+	/* Backpointer */
+	struct virusfilter_config *config;
+
+	int fsav_protocol;
+	bool scan_riskware;
+	bool stop_scan_on_first;
+	bool filter_filename;
+};
+
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config);
+
+static int virusfilter_fsav_destruct_config(
+	struct virusfilter_fsav_config *fsav_config)
+{
+	virusfilter_fsav_scan_end(fsav_config->config);
+	return 0;
+}
+
+static int virusfilter_fsav_connect(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	const char *svc,
+	const char *user)
+{
+	int snum = SNUM(handle->conn);
+	struct virusfilter_fsav_config *fsav_config = NULL;
+
+	fsav_config = talloc_zero(config->backend,
+				  struct virusfilter_fsav_config);
+	if (fsav_config == NULL) {
+		return -1;
+	}
+
+	fsav_config->config = config;
+
+	fsav_config->fsav_protocol = lp_parm_int(
+		snum, "virusfilter", "fsav protocol",
+		VIRUSFILTER_DEFAULT_FSAV_PROTOCOL);
+
+	fsav_config->scan_riskware = lp_parm_bool(
+		snum, "virusfilter", "scan riskware",
+		VIRUSFILTER_DEFAULT_SCAN_RISKWARE);
+
+	fsav_config->stop_scan_on_first = lp_parm_bool(
+		snum, "virusfilter", "stop scan on first",
+		VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST);
+
+	fsav_config->filter_filename = lp_parm_bool(
+		snum, "virusfilter", "filter filename",
+		VIRUSFILTER_DEFAULT_FILTER_FILENAME);
+
+	talloc_set_destructor(fsav_config, virusfilter_fsav_destruct_config);
+
+	config->backend->backend_private = fsav_config;
+
+	config->block_suspected_file = lp_parm_bool(
+		snum, "virusfilter", "block suspected file", false);
+
+	return 0;
+}
+
+static virusfilter_result virusfilter_fsav_scan_init(
+	struct virusfilter_config *config)
+{
+	struct virusfilter_fsav_config *fsav_config = NULL;
+	struct virusfilter_io_handle *io_h = config->io_h;
+	char *reply = NULL;
+	bool ok;
+	int ret;
+
+	fsav_config = talloc_get_type_abort(config->backend->backend_private,
+					    struct virusfilter_fsav_config);
+
+	if (io_h->stream != NULL) {
+		DBG_DEBUG("fsavd: Checking if connection is alive\n");
+
+		/* FIXME: I don't know the correct PING command format... */
+		ok = virusfilter_io_writefl_readl(io_h, &reply, "PING");
+		if (ok)	{
+			ret = strncmp(reply, "ERROR\t", 6);
+			if (ret == 0) {
+				DBG_DEBUG("fsavd: Re-using existent "
+					  "connection\n");
+				goto virusfilter_fsav_init_succeed;
+			}
+		}
+
+		DBG_DEBUG("fsavd: Closing dead connection\n");
+		virusfilter_fsav_scan_end(config);
+	}
+
+	DBG_INFO("fsavd: Connecting to socket: %s\n",
+		 config->socket_path);
+
+	become_root();
+	ok = virusfilter_io_connect_path(io_h, config->socket_path);
+	unbecome_root();
+
+	if (!ok) {
+		DBG_ERR("fsavd: Connecting to socket failed: %s: %s\n",
+			config->socket_path, strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+	if (!ok) {
+		DBG_ERR("fsavd: Reading greeting message failed: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	ret = strncmp(reply, "DBVERSION\t", 10);
+	if (ret != 0) {
+		DBG_ERR("fsavd: Invalid greeting message: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	DBG_DEBUG("fsavd: Connected\n");
+
+	DBG_INFO("fsavd: Configuring\n");
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply, "PROTOCOL\t%d",
+					  fsav_config->fsav_protocol);
+	if (!ok) {
+		DBG_ERR("fsavd: PROTOCOL: I/O error: %s\n", strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	ret = strncmp(reply, "OK\t", 3);
+	if (ret != 0) {
+		DBG_ERR("fsavd: PROTOCOL: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply,
+					  "CONFIGURE\tSTOPONFIRST\t%d",
+					  fsav_config->stop_scan_on_first ?
+					  1 : 0);
+	if (!ok) {
+		DBG_ERR("fsavd: CONFIGURE STOPONFIRST: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	ret = strncmp(reply, "OK\t", 3);
+	if (ret != 0) {
+		DBG_ERR("fsavd: CONFIGURE STOPONFIRST: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tFILTER\t%d",
+					  fsav_config->filter_filename ? 1 : 0);
+	if (!ok) {
+		DBG_ERR("fsavd: CONFIGURE FILTER: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	ret = strncmp(reply, "OK\t", 3);
+	if (ret != 0) {
+		DBG_ERR("fsavd: CONFIGURE FILTER: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply,
+					  "CONFIGURE\tARCHIVE\t%d",
+					  config->scan_archive ? 1 : 0);
+	if (!ok) {
+		DBG_ERR("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	ret = strncmp(reply, "OK\t", 3);
+	if (ret != 0) {
+		DBG_ERR("fsavd: CONFIGURE ARCHIVE: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply,
+					  "CONFIGURE\tMAXARCH\t%d",
+					  config->max_nested_scan_archive);
+	if (!ok) {
+		DBG_ERR("fsavd: CONFIGURE MAXARCH: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	ret = strncmp(reply, "OK\t", 3);
+	if (ret != 0) {
+		DBG_ERR("fsavd: CONFIGURE MAXARCH: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply,
+					  "CONFIGURE\tMIME\t%d",
+					  config->scan_mime ? 1 : 0);
+	if (!ok) {
+		DBG_ERR("fsavd: CONFIGURE MIME: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	ret = strncmp(reply, "OK\t", 3);
+	if (ret != 0) {
+		DBG_ERR("fsavd: CONFIGURE MIME: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	TALLOC_FREE(reply);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tRISKWARE\t%d",
+					  fsav_config->scan_riskware ? 1 : 0);
+	if (!ok) {
+		DBG_ERR("fsavd: CONFIGURE RISKWARE: I/O error: %s\n",
+			strerror(errno));
+		goto virusfilter_fsav_init_failed;
+	}
+	ret = strncmp(reply, "OK\t", 3);
+	if (ret != 0) {
+		DBG_ERR("fsavd: CONFIGURE RISKWARE: Not accepted: %s\n",
+			reply);
+		goto virusfilter_fsav_init_failed;
+	}
+
+	DBG_DEBUG("fsavd: Configured\n");
+
+virusfilter_fsav_init_succeed:
+	TALLOC_FREE(reply);
+	return VIRUSFILTER_RESULT_OK;
+
+virusfilter_fsav_init_failed:
+	TALLOC_FREE(reply);
+	virusfilter_fsav_scan_end(config);
+
+	return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config)
+{
+	struct virusfilter_io_handle *io_h = config->io_h;
+
+	DBG_INFO("fsavd: Disconnecting\n");
+	virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_fsav_scan(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	const struct files_struct *fsp,
+	char **reportp)
+{
+	char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	const char *fname = fsp->fsp_name->base_name;
+	struct virusfilter_io_handle *io_h = config->io_h;
+	virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+	char *report = NULL;
+	char *reply = NULL;
+	char *reply_token, *reply_saveptr;
+	bool ok;
+
+	DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+	ok = virusfilter_io_writevl(io_h, "SCAN\t", 5, cwd_fname,
+				    (int)strlen(cwd_fname), "/", 1, fname,
+				    (int)strlen(fname), NULL);
+	if (!ok) {
+		DBG_ERR("fsavd: SCAN: Write error: %s\n", strerror(errno));
+		result = VIRUSFILTER_RESULT_ERROR;
+		report = talloc_asprintf(talloc_tos(),
+					 "Scanner I/O error: %s\n",
+					 strerror(errno));
+		goto virusfilter_fsav_scan_return;
+	}
+
+	TALLOC_FREE(reply);
+
+	for (;;) {
+		if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+			DBG_ERR("fsavd: SCANFILE: Read error: %s\n",
+				strerror(errno));
+			result = VIRUSFILTER_RESULT_ERROR;
+			report = talloc_asprintf(talloc_tos(),
+						 "Scanner I/O error: %s\n",
+						 strerror(errno));
+			break;
+		}
+
+		reply_token = strtok_r(reply, "\t", &reply_saveptr);
+
+		if (strcmp(reply_token, "OK") == 0) {
+			break;
+		} else if (strcmp(reply_token, "CLEAN") == 0) {
+
+			/* CLEAN\t<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 != NULL) {
+				  report = talloc_strdup(talloc_tos(),
+							 reply_token);
+			} else {
+				  report = talloc_asprintf(talloc_tos(),
+							"UNKNOWN INFECTION");
+			}
+		} else if (strcmp(reply_token, "OPEN_ARCHIVE") == 0) {
+
+			/* Ignore */
+		} else if (strcmp(reply_token, "CLOSE_ARCHIVE") == 0) {
+
+			/* Ignore */
+		} else if ((strcmp(reply_token, "SUSPECTED") == 0 ||
+			   strcmp(reply_token, "ARCHIVE_SUSPECTED") == 0 ||
+			   strcmp(reply_token, "MIME_SUSPECTED") == 0) &&
+			   config->block_suspected_file)
+		{
+			result = VIRUSFILTER_RESULT_SUSPECTED;
+			reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+			reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+			if (reply_token != NULL) {
+				  report = talloc_strdup(talloc_tos(),
+							 reply_token);
+			} else {
+				  report = talloc_asprintf(talloc_tos(),
+						"UNKNOWN REASON SUSPECTED");
+			}
+		} else if (strcmp(reply_token, "SCAN_FAILURE") == 0) {
+
+			/* SCAN_FAILURE\t<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;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_fsav ={
+	.connect = virusfilter_fsav_connect,
+	.disconnect = NULL,
+	.scan_init = virusfilter_fsav_scan_init,
+	.scan = virusfilter_fsav_scan,
+	.scan_end = virusfilter_fsav_scan_end,
+};
+
+int virusfilter_fsav_init(struct virusfilter_config *config)
+{
+	struct virusfilter_backend *backend = NULL;
+
+	if (config->socket_path == NULL) {
+		config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+	}
+
+	backend = talloc_zero(config, struct virusfilter_backend);
+	if (backend == NULL) {
+		return -1;
+	}
+
+	backend->fns = &virusfilter_backend_fsav;
+	backend->name = "fsav";
+
+	config->backend = backend;
+	return 0;
+}
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index 14fddb3b30e..f63c00a9955 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -515,6 +515,7 @@ bld.SAMBA3_MODULE('vfs_virusfilter',
                  source='''
                  vfs_virusfilter.c
                  vfs_virusfilter_sophos.c
+                 vfs_virusfilter_fsav.c
                  ''',
                  deps='samba-util VFS_VIRUSFILTER_UTILS',
                  init_function='',
-- 
2.13.6


From e6378f34f6a799a3a8fed92c7f7b1b4974b53c25 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 6/6] 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>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 docs-xml/manpages/vfs_virusfilter.8.xml  |   4 +
 source3/modules/vfs_virusfilter.c        |   3 +
 source3/modules/vfs_virusfilter_clamav.c | 195 +++++++++++++++++++++++++++++++
 source3/modules/vfs_virusfilter_common.h |   1 +
 source3/modules/wscript_build            |   1 +
 5 files changed, 204 insertions(+)
 create mode 100644 source3/modules/vfs_virusfilter_clamav.c

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index 2e70ab0b553..ee49df11575 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -46,6 +46,8 @@
 		  scanner</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/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index 8b4141592fb..aad6628f68d 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -448,6 +448,9 @@ static int virusfilter_vfs_connect(
 	case VIRUSFILTER_SCANNER_FSAV:
 		ret = virusfilter_fsav_init(config);
 		break;
+	case VIRUSFILTER_SCANNER_CLAMAV:
+		ret = virusfilter_clamav_init(config);
+		break;
 	default:
 		DBG_ERR("Unhandled scanner %d\n", backend);
 		return -1;
diff --git a/source3/modules/vfs_virusfilter_clamav.c b/source3/modules/vfs_virusfilter_clamav.c
new file mode 100644
index 00000000000..d0e1fc081b7
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_clamav.c
@@ -0,0 +1,195 @@
+/*
+   Samba-VirusFilter VFS modules
+   ClamAV clamd support
+   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Default values for standard "extra" configuration variables */
+
+#ifdef CLAMAV_DEFAULT_SOCKET_PATH
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH	CLAMAV_DEFAULT_SOCKET_PATH
+#else
+#  define VIRUSFILTER_DEFAULT_SOCKET_PATH	"/var/run/clamav/clamd.ctl"
+#endif
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+static int virusfilter_clamav_connect(struct vfs_handle_struct *handle,
+				      struct virusfilter_config *config,
+				      const char *svc,
+				      const char *user)
+{
+
+	/* To use clamd "zXXXX" commands */
+	virusfilter_io_set_writel_eol(config->io_h, "\0", 1);
+	virusfilter_io_set_readl_eol(config->io_h, "\0", 1);
+
+	return 0;
+}
+
+static virusfilter_result virusfilter_clamav_scan_init(
+	struct virusfilter_config *config)
+{
+	struct virusfilter_io_handle *io_h = config->io_h;
+	bool ok;
+
+	DBG_INFO("clamd: Connecting to socket: %s\n",
+		 config->socket_path);
+
+	become_root();
+	ok = virusfilter_io_connect_path(io_h, config->socket_path);
+	unbecome_root();
+
+	if (!ok) {
+		DBG_ERR("clamd: Connecting to socket failed: %s: %s\n",
+			config->socket_path, strerror(errno));
+		return VIRUSFILTER_RESULT_ERROR;
+	}
+
+	DBG_INFO("clamd: Connected\n");
+
+	return VIRUSFILTER_RESULT_OK;
+}
+
+static void virusfilter_clamav_scan_end(
+	struct virusfilter_config *config)
+{
+	struct virusfilter_io_handle *io_h = config->io_h;
+
+	DBG_INFO("clamd: Disconnecting\n");
+
+	virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_clamav_scan(
+	struct vfs_handle_struct *handle,
+	struct virusfilter_config *config,
+	const struct files_struct *fsp,
+	char **reportp)
+{
+	char *cwd_fname = fsp->conn->cwd_fname->base_name;
+	const char *fname = fsp->fsp_name->base_name;
+	size_t filepath_len = strlen(cwd_fname) + 1 /* slash */ + strlen(fname);
+	struct virusfilter_io_handle *io_h = config->io_h;
+	virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+	char *report = NULL;
+	char *reply = NULL;
+	char *reply_msg = NULL;
+	char *reply_token;
+	bool ok;
+
+	DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+	ok = virusfilter_io_writefl_readl(io_h, &reply, "zSCAN %s/%s",
+					  cwd_fname, fname);
+	if (!ok) {
+		DBG_ERR("clamd: zSCAN: I/O error: %s\n", strerror(errno));
+		result = VIRUSFILTER_RESULT_ERROR;
+		report = talloc_asprintf(talloc_tos(),
+					 "Scanner I/O error: %s\n",
+					 strerror(errno));
+		goto virusfilter_clamav_scan_return;
+	}
+
+	if (reply[filepath_len] != ':' ||
+	    reply[filepath_len+1] != ' ')
+	{
+		DBG_ERR("clamd: zSCAN: Invalid reply: %s\n",
+			reply);
+		result = VIRUSFILTER_RESULT_ERROR;
+		report = talloc_asprintf(talloc_tos(),
+					 "Scanner communication error");
+		goto virusfilter_clamav_scan_return;
+	}
+	reply_msg = reply + filepath_len + 2;
+
+	reply_token = strrchr(reply, ' ');
+
+	if (reply_token == NULL) {
+		DBG_ERR("clamd: zSCAN: Invalid reply: %s\n",
+			reply);
+		result = VIRUSFILTER_RESULT_ERROR;
+		report = talloc_asprintf(talloc_tos(),
+					 "Scanner communication error");
+		goto virusfilter_clamav_scan_return;
+	}
+	*reply_token = '\0';
+	reply_token++;
+
+	if (strcmp(reply_token, "OK") == 0) {
+
+		/* <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;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_clamav = {
+	.connect = virusfilter_clamav_connect,
+	.disconnect = NULL,
+	.scan_init = virusfilter_clamav_scan_init,
+	.scan = virusfilter_clamav_scan,
+	.scan_end = virusfilter_clamav_scan_end,
+};
+
+int virusfilter_clamav_init(struct virusfilter_config *config)
+{
+	struct virusfilter_backend *backend = NULL;
+
+	if (config->socket_path == NULL) {
+		config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+	}
+
+	backend = talloc_zero(config, struct virusfilter_backend);
+	if (backend == NULL) {
+		return -1;
+	}
+
+	backend->fns = &virusfilter_backend_clamav;
+	backend->name = "clamav";
+
+	config->backend = backend;
+	return 0;
+}
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
index a28ce2978fa..f71b0b949a7 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -148,5 +148,6 @@ struct virusfilter_backend {
 
 int virusfilter_sophos_init(struct virusfilter_config *config);
 int virusfilter_fsav_init(struct virusfilter_config *config);
+int virusfilter_clamav_init(struct virusfilter_config *config);
 
 #endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index f63c00a9955..5c529890470 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -516,6 +516,7 @@ bld.SAMBA3_MODULE('vfs_virusfilter',
                  vfs_virusfilter.c
                  vfs_virusfilter_sophos.c
                  vfs_virusfilter_fsav.c
+                 vfs_virusfilter_clamav.c
                  ''',
                  deps='samba-util VFS_VIRUSFILTER_UTILS',
                  init_function='',
-- 
2.13.6



More information about the samba-technical mailing list