[PATCH] Samba VirusFilter (version 12)

Jeremy Allison jra at samba.org
Tue Jan 23 23:32:50 UTC 2018


On Tue, Jan 23, 2018 at 03:11:28PM -0700, Trever L. Adams wrote:
> On 01/23/2018 12:01 PM, Ralph Böhme wrote:
> > 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
> >
> Hello Ralph,
> 
> Things look really good. I have made a few changes that are stylistic
> (coding standard in one, the other, I prefer default actions be part of
> the variable declaration, so I set action at the top of the functions.
> There was one other small change, I believe. I already squashed it so...
> 
> All three actions have been well tested. (Except failure paths. I wasn't
> sure how to force a failure of rename/delete.)
> 
> Hopefully this is ready now. Thank you all!!

LGTM. Ralph, are you OK ?

(nice cleanup of virusfilter_do_infected_file_action() btw).

Jeremy.

> From b5ebe7d6abc583f092b0e82d5573deea79c9001f 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/5] 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.14.3
> 
> 
> From a65aff19837430a294aa3745178c91663f5bc977 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/5] 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                  | 1508 ++++++++++++++++++++
>  source3/modules/vfs_virusfilter_common.h           |  149 ++
>  source3/modules/vfs_virusfilter_utils.c            | 1025 +++++++++++++
>  source3/modules/vfs_virusfilter_utils.h            |  177 +++
>  source3/modules/wscript_build                      |   13 +
>  source3/wscript                                    |    2 +-
>  9 files changed, 3494 insertions(+), 1 deletion(-)
>  create mode 100644 docs-xml/manpages/vfs_virusfilter.8.xml
>  create mode 100644 examples/scripts/vfs/virusfilter/virusfilter-notify.ksh
>  create mode 100644 source3/modules/vfs_virusfilter.c
>  create mode 100644 source3/modules/vfs_virusfilter_common.h
>  create mode 100644 source3/modules/vfs_virusfilter_utils.c
>  create mode 100644 source3/modules/vfs_virusfilter_utils.h
> 
> diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
> new file mode 100644
> index 00000000000..eb6112e3827
> --- /dev/null
> +++ b/docs-xml/manpages/vfs_virusfilter.8.xml
> @@ -0,0 +1,336 @@
> +<?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 3ff1ade6976..f30786c3a8b 100644
> --- a/docs-xml/wscript_build
> +++ b/docs-xml/wscript_build
> @@ -88,6 +88,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..a23d1f7c641
> --- /dev/null
> +++ b/source3/modules/vfs_virusfilter.c
> @@ -0,0 +1,1508 @@
> +/*
> + * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
> + * Copyright (C) 2016-2017 Trever L. Adams
> + * Copyright (C) 2017 Ralph Boehme <slow 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 char *quarantine_check_tree(TALLOC_CTX *mem_ctx,
> +				   struct vfs_handle_struct *handle,
> +				   struct virusfilter_config *config,
> +				   const struct smb_filename *smb_fname,
> +				   char *q_dir_in,
> +				   char *cwd_fname)
> +{
> +	char *temp_path = NULL;
> +	char *q_dir_out = NULL;
> +	bool ok;
> +
> +	temp_path = talloc_asprintf(talloc_tos(), "%s/%s", q_dir_in, cwd_fname);
> +	if (temp_path == NULL) {
> +		DBG_ERR("talloc_asprintf failed\n");
> +		goto out;
> +	}
> +
> +	become_root();
> +	ok = quarantine_directory_exist(handle,	temp_path);
> +	unbecome_root();
> +	if (ok) {
> +		DBG_DEBUG("quarantine: directory [%s] exists\n", temp_path);
> +		q_dir_out = talloc_move(mem_ctx, &temp_path);
> +		goto out;
> +	}
> +
> +	DBG_DEBUG("quarantine: Creating directory %s\n", temp_path);
> +
> +	become_root();
> +	ok = quarantine_create_dir(handle, config, temp_path);
> +	unbecome_root();
> +	if (!ok) {
> +		DBG_NOTICE("Could not create quarantine directory [%s], "
> +			   "ignoring for [%s]\n",
> +			   temp_path, smb_fname_str_dbg(smb_fname));
> +		goto out;
> +	}
> +
> +	q_dir_out = talloc_move(mem_ctx, &temp_path);
> +
> +out:
> +	TALLOC_FREE(temp_path);
> +	return q_dir_out;
> +}
> +
> +static virusfilter_action infected_file_action_quarantine(
> +	struct vfs_handle_struct *handle,
> +	struct virusfilter_config *config,
> +	TALLOC_CTX *mem_ctx,
> +	const struct files_struct *fsp,
> +	const char **filepath_newp)
> +{
> +	TALLOC_CTX *frame = talloc_stackframe();
> +	connection_struct *conn = handle->conn;
> +	char *cwd_fname = fsp->conn->cwd_fname->base_name;
> +	char *fname = fsp->fsp_name->base_name;
> +	const struct smb_filename *smb_fname = fsp->fsp_name;
> +	struct smb_filename *q_smb_fname = NULL;
> +	char *q_dir = NULL;
> +	char *q_prefix = NULL;
> +	char *q_suffix = NULL;
> +	char *q_filepath = NULL;
> +	char *dir_name = NULL;
> +	const char *base_name = NULL;
> +	char *rand_filename_component = NULL;
> +	virusfilter_action action = VIRUSFILTER_ACTION_QUARANTINE;
> +	bool ok = false;
> +	int ret = -1;
> +	int saved_errno = 0;
> +
> +	q_dir = virusfilter_string_sub(frame, conn,
> +				       config->quarantine_dir);
> +	q_prefix = virusfilter_string_sub(frame, conn,
> +					  config->quarantine_prefix);
> +	q_suffix = virusfilter_string_sub(frame, conn,
> +					  config->quarantine_suffix);
> +	if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
> +		DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
> +			"memory\n", cwd_fname, fname);
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	if (config->quarantine_keep_name || config->quarantine_keep_tree) {
> +		ok = parent_dirname(frame, smb_fname->base_name,
> +				    &dir_name, &base_name);
> +		if (!ok) {
> +			DBG_ERR("parent_dirname failed\n");
> +			action = VIRUSFILTER_ACTION_DO_NOTHING;
> +			goto out;
> +		}
> +
> +		if (config->quarantine_keep_tree) {
> +			char *tree = NULL;
> +
> +			tree = quarantine_check_tree(frame, handle, config,
> +						     smb_fname, q_dir,
> +						     cwd_fname);
> +			if (tree == NULL) {
> +				/*
> +				 * If we can't create the tree, just move it
> +				 * into the toplevel quarantine dir.
> +				 */
> +				tree = q_dir;
> +			}
> +			q_dir = tree;
> +		}
> +	}
> +
> +	/* Get a 16 byte + \0 random filename component. */
> +	rand_filename_component = generate_random_str(frame, 16);
> +	if (rand_filename_component == NULL) {
> +		DBG_ERR("generate_random_str failed\n");
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	if (config->quarantine_keep_name) {
> +		q_filepath = talloc_asprintf(frame, "%s/%s%s%s-%s",
> +					     q_dir, q_prefix,
> +					     base_name, q_suffix,
> +					     rand_filename_component);
> +	} else {
> +		q_filepath = talloc_asprintf(frame, "%s/%s%s",
> +					     q_dir, q_prefix,
> +					     rand_filename_component);
> +	}
> +	if (q_filepath == NULL) {
> +		DBG_ERR("talloc_asprintf failed\n");
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	q_smb_fname = synthetic_smb_fname(frame, q_filepath,
> +					  smb_fname->stream_name,
> +					  NULL, smb_fname->flags);
> +	if (q_smb_fname == NULL) {
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	become_root();
> +	ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
> +	if (ret == -1) {
> +		saved_errno = errno;
> +	}
> +	unbecome_root();
> +	if (ret == -1) {
> +		DBG_ERR("Quarantine [%s/%s] rename to %s failed: %s\n",
> +			cwd_fname, fname, q_filepath, strerror(saved_errno));
> +		errno = saved_errno;
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	*filepath_newp = talloc_move(mem_ctx, &q_filepath);
> +
> +out:
> +	TALLOC_FREE(frame);
> +	return action;
> +}
> +
> +static virusfilter_action infected_file_action_rename(
> +	struct vfs_handle_struct *handle,
> +	struct virusfilter_config *config,
> +	TALLOC_CTX *mem_ctx,
> +	const struct files_struct *fsp,
> +	const char **filepath_newp)
> +{
> +	TALLOC_CTX *frame = talloc_stackframe();
> +	connection_struct *conn = handle->conn;
> +	char *cwd_fname = fsp->conn->cwd_fname->base_name;
> +	char *fname = fsp->fsp_name->base_name;
> +	const struct smb_filename *smb_fname = fsp->fsp_name;
> +	struct smb_filename *q_smb_fname = NULL;
> +	char *q_dir = NULL;
> +	char *q_prefix = NULL;
> +	char *q_suffix = NULL;
> +	char *q_filepath = NULL;
> +	const char *base_name = NULL;
> +	virusfilter_action action = VIRUSFILTER_ACTION_RENAME;
> +	bool ok = false;
> +	int ret = -1;
> +	int saved_errno = 0;
> +
> +	q_prefix = virusfilter_string_sub(frame, conn,
> +					  config->rename_prefix);
> +	q_suffix = virusfilter_string_sub(frame, conn,
> +					  config->rename_suffix);
> +	if (q_prefix == NULL || q_suffix == NULL) {
> +		DBG_ERR("Rename failed: %s/%s: Cannot allocate "
> +			"memory\n", cwd_fname, fname);
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	ok = parent_dirname(frame, fname, &q_dir, &base_name);
> +	if (!ok) {
> +		DBG_ERR("Rename failed: %s/%s: Cannot allocate "
> +			"memory\n", cwd_fname, fname);
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	if (q_dir == NULL) {
> +		DBG_ERR("Rename failed: %s/%s: Cannot allocate "
> +			"memory\n", cwd_fname, fname);
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	q_filepath = talloc_asprintf(frame, "%s/%s%s%s", q_dir,
> +				     q_prefix, base_name, q_suffix);
> +
> +	q_smb_fname = synthetic_smb_fname(frame, q_filepath,
> +					  smb_fname->stream_name, NULL,
> +					  smb_fname->flags);
> +	if (q_smb_fname == NULL) {
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	become_root();
> +	ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
> +	if (ret == -1) {
> +		saved_errno = errno;
> +	}
> +	unbecome_root();
> +
> +	if (ret == -1) {
> +		DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
> +			cwd_fname, fname, strerror(saved_errno));
> +		errno = saved_errno;
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		goto out;
> +	}
> +
> +	*filepath_newp = talloc_move(mem_ctx, &q_filepath);
> +
> +out:
> +	TALLOC_FREE(frame);
> +	return action;
> +}
> +
> +static virusfilter_action infected_file_action_delete(
> +	struct vfs_handle_struct *handle,
> +	const struct files_struct *fsp)
> +{
> +	int ret;
> +	int saved_errno = 0;
> +
> +	become_root();
> +	ret = SMB_VFS_NEXT_UNLINK(handle, fsp->fsp_name);
> +	if (ret == -1) {
> +		saved_errno = errno;
> +	}
> +	unbecome_root();
> +	if (ret == -1) {
> +		DBG_ERR("Delete [%s/%s] failed: %s\n",
> +			fsp->conn->cwd_fname->base_name,
> +			fsp->fsp_name->base_name,
> +			strerror(saved_errno));
> +		errno = saved_errno;
> +		return VIRUSFILTER_ACTION_DO_NOTHING;
> +	}
> +
> +	return VIRUSFILTER_ACTION_DELETE;
> +}
> +
> +static virusfilter_action virusfilter_do_infected_file_action(
> +	struct vfs_handle_struct *handle,
> +	struct virusfilter_config *config,
> +	TALLOC_CTX *mem_ctx,
> +	const struct files_struct *fsp,
> +	const char **filepath_newp)
> +{
> +	virusfilter_action action;
> +
> +	*filepath_newp = NULL;
> +
> +	switch (config->infected_file_action) {
> +	case VIRUSFILTER_ACTION_RENAME:
> +		action = infected_file_action_rename(handle, config, mem_ctx,
> +						     fsp, filepath_newp);
> +		break;
> +
> +	case VIRUSFILTER_ACTION_QUARANTINE:
> +		action = infected_file_action_quarantine(handle, config, mem_ctx,
> +							 fsp, filepath_newp);
> +		break;
> +
> +	case VIRUSFILTER_ACTION_DELETE:
> +		action = infected_file_action_delete(handle, fsp);
> +		break;
> +
> +	case VIRUSFILTER_ACTION_DO_NOTHING:
> +	default:
> +		action = VIRUSFILTER_ACTION_DO_NOTHING;
> +		break;
> +	}
> +
> +	return action;
> +}
> +
> +static virusfilter_action virusfilter_treat_infected_file(
> +	struct vfs_handle_struct *handle,
> +	struct virusfilter_config *config,
> +	const struct files_struct *fsp,
> +	const char *report,
> +	bool is_cache)
> +{
> +	connection_struct *conn = handle->conn;
> +	char *cwd_fname = fsp->conn->cwd_fname->base_name;
> +	char *fname = fsp->fsp_name->base_name;
> +	TALLOC_CTX *mem_ctx = talloc_tos();
> +	int i;
> +	virusfilter_action action;
> +	const char *action_name = "UNKNOWN";
> +	const char *filepath_q = NULL;
> +	char *env_list = NULL;
> +	char *command = NULL;
> +	int command_result;
> +	int ret;
> +
> +	action = virusfilter_do_infected_file_action(handle, config, mem_ctx,
> +						     fsp, &filepath_q);
> +	for (i=0; virusfilter_actions[i].name; i++) {
> +		if (virusfilter_actions[i].value == action) {
> +			action_name = virusfilter_actions[i].name;
> +			break;
> +		}
> +	}
> +	DBG_WARNING("Infected file action: %s/%s: %s\n", cwd_fname,
> +		    fname, action_name);
> +
> +	if (!config->infected_file_command) {
> +		return action;
> +	}
> +
> +	ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
> +	if (ret == -1) {
> +		goto done;
> +	}
> +	ret = virusfilter_env_set(mem_ctx, &env_list,
> +				  "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
> +				  fname);
> +	if (ret == -1) {
> +		goto done;
> +	}
> +	if (report != NULL) {
> +		ret = virusfilter_env_set(mem_ctx, &env_list,
> +					  "VIRUSFILTER_INFECTED_FILE_REPORT",
> +					  report);
> +		if (ret == -1) {
> +			goto done;
> +		}
> +	}
> +	ret = virusfilter_env_set(mem_ctx, &env_list,
> +				  "VIRUSFILTER_INFECTED_FILE_ACTION",
> +				  action_name);
> +	if (ret == -1) {
> +		goto done;
> +	}
> +	if (filepath_q != NULL) {
> +		ret = virusfilter_env_set(mem_ctx, &env_list,
> +					  "VIRUSFILTER_QUARANTINED_FILE_PATH",
> +					  filepath_q);
> +		if (ret == -1) {
> +			goto done;
> +		}
> +	}
> +	if (is_cache) {
> +		ret = virusfilter_env_set(mem_ctx, &env_list,
> +					  "VIRUSFILTER_RESULT_IS_CACHE", "yes");
> +		if (ret == -1) {
> +			goto done;
> +		}
> +	}
> +
> +	command = virusfilter_string_sub(mem_ctx, conn,
> +					 config->infected_file_command);
> +	if (command == NULL) {
> +		DBG_ERR("virusfilter_string_sub failed\n");
> +		goto done;
> +	}
> +
> +	DBG_NOTICE("Infected file command line: %s/%s: %s\n", cwd_fname,
> +		   fname, command);
> +
> +	command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
> +					       conn, true);
> +	if (command_result != 0) {
> +		DBG_ERR("Infected file command failed: %d\n", command_result);
> +	}
> +
> +	DBG_DEBUG("Infected file command finished: %d\n", command_result);
> +
> +done:
> +	TALLOC_FREE(env_list);
> +	TALLOC_FREE(command);
> +
> +	return action;
> +}
> +
> +static void virusfilter_treat_scan_error(
> +	struct vfs_handle_struct *handle,
> +	struct virusfilter_config *config,
> +	const struct files_struct *fsp,
> +	const char *report,
> +	bool is_cache)
> +{
> +	connection_struct *conn = handle->conn;
> +	const char *cwd_fname = fsp->conn->cwd_fname->base_name;
> +	const char *fname = fsp->fsp_name->base_name;
> +	TALLOC_CTX *mem_ctx = talloc_tos();
> +	char *env_list = NULL;
> +	char *command = NULL;
> +	int command_result;
> +	int ret;
> +
> +	if (!config->scan_error_command) {
> +		return;
> +	}
> +	ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
> +	if (ret == -1) {
> +		goto done;
> +	}
> +	ret = virusfilter_env_set(mem_ctx, &env_list,
> +				  "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
> +				  fname);
> +	if (ret == -1) {
> +		goto done;
> +	}
> +	if (report != NULL) {
> +		ret = virusfilter_env_set(mem_ctx, &env_list,
> +					  "VIRUSFILTER_SCAN_ERROR_REPORT",
> +					  report);
> +		if (ret == -1) {
> +			goto done;
> +		}
> +	}
> +	if (is_cache) {
> +		ret = virusfilter_env_set(mem_ctx, &env_list,
> +					  "VIRUSFILTER_RESULT_IS_CACHE", "1");
> +		if (ret == -1) {
> +			goto done;
> +		}
> +	}
> +
> +	command = virusfilter_string_sub(mem_ctx, conn,
> +					 config->scan_error_command);
> +	if (command == NULL) {
> +		DBG_ERR("virusfilter_string_sub failed\n");
> +		goto done;
> +	}
> +
> +	DBG_NOTICE("Scan error command line: %s/%s: %s\n", cwd_fname,
> +		   fname, command);
> +
> +	command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
> +					       conn, true);
> +	if (command_result != 0) {
> +		DBG_ERR("Scan error command failed: %d\n", command_result);
> +	}
> +
> +done:
> +	TALLOC_FREE(env_list);
> +	TALLOC_FREE(command);
> +}
> +
> +static virusfilter_result virusfilter_scan(
> +	struct vfs_handle_struct *handle,
> +	struct virusfilter_config *config,
> +	const struct files_struct *fsp)
> +{
> +	virusfilter_result scan_result;
> +	char *scan_report = NULL;
> +	const char *fname = fsp->fsp_name->base_name;
> +	const char *cwd_fname = fsp->conn->cwd_fname->base_name;
> +	struct virusfilter_cache_entry *scan_cache_e = NULL;
> +	bool is_cache = false;
> +	virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING;
> +	bool add_scan_cache = true;
> +	bool ok = false;
> +
> +	if (config->cache) {
> +		DBG_DEBUG("Searching cache entry: fname: %s\n", fname);
> +		scan_cache_e = virusfilter_cache_get(config->cache,
> +						     cwd_fname, fname);
> +		if (scan_cache_e != NULL) {
> +			DBG_DEBUG("Cache entry found: cached result: %d\n",
> +			      scan_cache_e->result);
> +			is_cache = true;
> +			scan_result = scan_cache_e->result;
> +			scan_report = scan_cache_e->report;
> +			goto virusfilter_scan_result_eval;
> +		}
> +		DBG_DEBUG("Cache entry not found\n");
> +	}
> +
> +	if (config->backend->fns->scan_init != NULL) {
> +		scan_result = config->backend->fns->scan_init(config);
> +		if (scan_result != VIRUSFILTER_RESULT_OK) {
> +			scan_result = VIRUSFILTER_RESULT_ERROR;
> +			scan_report = talloc_asprintf(
> +				talloc_tos(),
> +				"Initializing scanner failed");
> +			goto virusfilter_scan_result_eval;
> +		}
> +	}
> +
> +	scan_result = config->backend->fns->scan(handle, config, fsp,
> +						 &scan_report);
> +
> +	if (config->backend->fns->scan_end != NULL) {
> +		bool scan_end = true;
> +
> +		if (config->scan_request_limit > 0) {
> +			scan_end = false;
> +			config->scan_request_count++;
> +			if (config->scan_request_count >=
> +			    config->scan_request_limit)
> +			{
> +				scan_end = true;
> +				config->scan_request_count = 0;
> +			}
> +		}
> +		if (scan_end) {
> +			config->backend->fns->scan_end(config);
> +		}
> +	}
> +
> +virusfilter_scan_result_eval:
> +
> +	switch (scan_result) {
> +	case VIRUSFILTER_RESULT_CLEAN:
> +		DBG_INFO("Scan result: Clean: %s/%s\n", cwd_fname, fname);
> +		break;
> +
> +	case VIRUSFILTER_RESULT_INFECTED:
> +		DBG_ERR("Scan result: Infected: %s/%s: %s\n",
> +			cwd_fname, fname, scan_report ? scan_report :
> +			"infected (memory error on report)");
> +		file_action = virusfilter_treat_infected_file(handle,
> +					config, fsp, scan_report, is_cache);
> +		if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
> +			add_scan_cache = false;
> +		}
> +		break;
> +
> +	case VIRUSFILTER_RESULT_SUSPECTED:
> +		if (!config->block_suspected_file) {
> +			break;
> +		}
> +		DBG_ERR("Scan result: Suspected: %s/%s: %s\n",
> +			cwd_fname, fname, scan_report ? scan_report :
> +			"suspected infection (memory error on report)");
> +		file_action = virusfilter_treat_infected_file(handle,
> +					config, fsp, scan_report, is_cache);
> +		if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
> +			add_scan_cache = false;
> +		}
> +		break;
> +
> +	case VIRUSFILTER_RESULT_ERROR:
> +		DBG_ERR("Scan result: Error: %s/%s: %s\n",
> +			cwd_fname, fname, scan_report ? scan_report :
> +			"error (memory error on report)");
> +		virusfilter_treat_scan_error(handle, config, fsp,
> +					     scan_report, is_cache);
> +		add_scan_cache = false;
> +		break;
> +
> +	default:
> +		DBG_ERR("Scan result: Unknown result code %d: %s/%s: %s\n",
> +			scan_result, cwd_fname, fname, scan_report ?
> +			scan_report : "Unknown (memory error on report)");
> +		virusfilter_treat_scan_error(handle, config, fsp,
> +					     scan_report, is_cache);
> +		add_scan_cache = false;
> +		break;
> +	}
> +
> +	if (config->cache) {
> +		if (!is_cache && add_scan_cache) {
> +			DBG_DEBUG("Adding new cache entry: %s, %d\n", fname,
> +				  scan_result);
> +			ok = virusfilter_cache_entry_add(
> +					config->cache, cwd_fname, fname,
> +					scan_result, scan_report);
> +			if (!ok) {
> +				DBG_ERR("Cannot create cache entry: "
> +					"virusfilter_cache_entry_new failed");
> +				goto virusfilter_scan_return;
> +			}
> +		} else if (is_cache) {
> +			virusfilter_cache_entry_free(scan_cache_e);
> +		}
> +	}
> +
> +virusfilter_scan_return:
> +	return scan_result;
> +}
> +
> +static int virusfilter_vfs_open(
> +	struct vfs_handle_struct *handle,
> +	struct smb_filename *smb_fname,
> +	files_struct *fsp,
> +	int flags,
> +	mode_t mode)
> +{
> +	TALLOC_CTX *mem_ctx = talloc_tos();
> +	struct virusfilter_config *config;
> +	const char *cwd_fname = fsp->conn->cwd_fname->base_name;
> +	virusfilter_result scan_result;
> +	const char *fname = fsp->fsp_name->base_name;
> +	char *dir_name = NULL;
> +	const char *base_name = NULL;
> +	int scan_errno = 0;
> +	size_t test_prefix;
> +	size_t test_suffix;
> +	int rename_trap_count = 0;
> +	int ret;
> +	bool ok1, ok2;
> +	char *sret = NULL;
> +
> +	SMB_VFS_HANDLE_GET_DATA(handle, config,
> +				struct virusfilter_config, return -1);
> +
> +	test_prefix = strlen(config->rename_prefix);
> +	test_suffix = strlen(config->rename_suffix);
> +	if (test_prefix > 0) {
> +		rename_trap_count++;
> +	}
> +	if (test_suffix > 0) {
> +		rename_trap_count++;
> +	}
> +
> +	ok1 = is_ntfs_stream_smb_fname(smb_fname);
> +	ok2 = is_ntfs_default_stream_smb_fname(smb_fname);
> +	if (ok1 && !ok2) {
> +		DBG_INFO("Not scanned: only file backed streams can be scanned:"
> +			 " %s/%s\n", cwd_fname, fname);
> +		goto virusfilter_vfs_open_next;
> +	}
> +
> +	if (!config->scan_on_open) {
> +		DBG_INFO("Not scanned: scan on open is disabled: %s/%s\n",
> +			 cwd_fname, fname);
> +		goto virusfilter_vfs_open_next;
> +	}
> +
> +	if (flags & O_TRUNC) {
> +		DBG_INFO("Not scanned: Open flags have O_TRUNC: %s/%s\n",
> +			 cwd_fname, fname);
> +		goto virusfilter_vfs_open_next;
> +	}
> +
> +	ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
> +	if (ret != 0) {
> +
> +		/*
> +		 * Do not return immediately if !(flags & O_CREAT) &&
> +		 * errno != ENOENT.
> +		 * Do not do this here or anywhere else. The module is
> +		 * stackable and there may be modules below, such as audit
> +		 * modules, which should be handled.
> +		 */
> +		goto virusfilter_vfs_open_next;
> +	}
> +	ret = S_ISREG(smb_fname->st.st_ex_mode);
> +	if (ret == 0) {
> +		DBG_INFO("Not scanned: Directory or special file: %s/%s\n",
> +			 cwd_fname, fname);
> +		goto virusfilter_vfs_open_next;
> +	}
> +	if (config->max_file_size > 0 &&
> +	    smb_fname->st.st_ex_size > config->max_file_size)
> +	{
> +		DBG_INFO("Not scanned: file size > max file size: %s/%s\n",
> +			 cwd_fname, fname);
> +		goto virusfilter_vfs_open_next;
> +	}
> +	if (config->min_file_size > 0 &&
> +	    smb_fname->st.st_ex_size < config->min_file_size)
> +	{
> +		DBG_INFO("Not scanned: file size < min file size: %s/%s\n",
> +		      cwd_fname, fname);
> +		goto virusfilter_vfs_open_next;
> +	}
> +
> +	ok1 = is_in_path(fname, config->exclude_files, false);
> +	if (config->exclude_files && ok1)
> +	{
> +		DBG_INFO("Not scanned: exclude files: %s/%s\n",
> +			 cwd_fname, fname);
> +		goto virusfilter_vfs_open_next;
> +	}
> +
> +	if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
> +		sret = strstr_m(fname, config->quarantine_dir);
> +		if (sret != NULL) {
> +			scan_errno = config->infected_open_errno;
> +			goto virusfilter_vfs_open_fail;
> +		}
> +	}
> +
> +	if (test_prefix > 0 || test_suffix > 0) {
> +		ok1 = parent_dirname(mem_ctx, fname, &dir_name, &base_name);
> +		if (ok1)
> +		{
> +			if (test_prefix > 0) {
> +				ret = strncmp(base_name,
> +				    config->rename_prefix, test_prefix);
> +				if (ret != 0) {
> +					test_prefix = 0;
> +				}
> +			}
> +			if (test_suffix > 0) {
> +				ret = strcmp(base_name + (strlen(base_name)
> +						 - test_suffix),
> +						 config->rename_suffix);
> +				if (ret != 0) {
> +					test_suffix = 0;
> +				}
> +			}
> +
> +			TALLOC_FREE(dir_name);
> +
> +			if ((rename_trap_count == 2 && test_prefix &&
> +			    test_suffix) || (rename_trap_count == 1 &&
> +			    (test_prefix || test_suffix)))
> +			{
> +				scan_errno =
> +					config->infected_open_errno;
> +				goto virusfilter_vfs_open_fail;
> +			}
> +		}
> +	}
> +
> +	scan_result = virusfilter_scan(handle, config, fsp);
> +
> +	switch (scan_result) {
> +	case VIRUSFILTER_RESULT_CLEAN:
> +		break;
> +	case VIRUSFILTER_RESULT_INFECTED:
> +		scan_errno = config->infected_open_errno;
> +		goto virusfilter_vfs_open_fail;
> +	case VIRUSFILTER_RESULT_ERROR:
> +		if (config->block_access_on_error) {
> +			DBG_INFO("Block access\n");
> +			scan_errno = config->scan_error_open_errno;
> +			goto virusfilter_vfs_open_fail;
> +		}
> +		break;
> +	default:
> +		scan_errno = config->scan_error_open_errno;
> +		goto virusfilter_vfs_open_fail;
> +	}
> +
> +virusfilter_vfs_open_next:
> +	return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
> +
> +virusfilter_vfs_open_fail:
> +	errno = (scan_errno != 0) ? scan_errno : EACCES;
> +	return -1;
> +}
> +
> +static int virusfilter_vfs_close(
> +	struct vfs_handle_struct *handle,
> +	files_struct *fsp)
> +{
> +	/*
> +         * The name of this variable is for consistency. If API changes to
> +         * match _open change to cwd_fname as in virusfilter_vfs_open.
> +         */
> +	const char *cwd_fname = handle->conn->connectpath;
> +
> +	struct virusfilter_config *config = NULL;
> +	char *fname = fsp->fsp_name->base_name = NULL;
> +	int close_result = -1;
> +	int close_errno = 0;
> +	virusfilter_result scan_result;
> +	int scan_errno = 0;
> +	bool ok1, ok2;
> +
> +	SMB_VFS_HANDLE_GET_DATA(handle, config,
> +				struct virusfilter_config, return -1);
> +
> +	/*
> +	 * Must close after scan? It appears not as the scanners are not
> +	 * internal and other modules such as greyhole seem to do
> +	 * SMB_VFS_NEXT_* functions before processing.
> +	 */
> +	close_result = SMB_VFS_NEXT_CLOSE(handle, fsp);
> +	if (close_result == -1) {
> +		close_errno = errno;
> +	}
> +
> +	/*
> +	 * Return immediately if close_result == -1, and close_errno == EBADF.
> +	 * If close failed, file likely doesn't exist, do not try to scan.
> +	 */
> +	if (close_result == -1 && close_errno == EBADF) {
> +		if (fsp->modified) {
> +			DBG_DEBUG("Removing cache entry (if existent): "
> +				  "fname: %s\n", fname);
> +			virusfilter_cache_remove(config->cache,
> +						 cwd_fname, fname);
> +		}
> +		goto virusfilter_vfs_close_fail;
> +	}
> +
> +	if (fsp->is_directory) {
> +		DBG_INFO("Not scanned: Directory: %s/%s\n", cwd_fname,
> +			 fname);
> +		return close_result;
> +	}
> +
> +	ok1 = is_ntfs_stream_smb_fname(fsp->fsp_name);
> +	ok2 = is_ntfs_default_stream_smb_fname(fsp->fsp_name);
> +	if (ok1 && !ok2) {
> +		if (config->scan_on_open && fsp->modified) {
> +			if (config->cache) {
> +				DBG_DEBUG("Removing cache entry (if existent)"
> +					  ": fname: %s\n", fname);
> +				virusfilter_cache_remove(
> +						config->cache,
> +						cwd_fname, fname);
> +			}
> +		}
> +		DBG_INFO("Not scanned: only file backed streams can be scanned:"
> +			 " %s/%s\n", cwd_fname, fname);
> +		return close_result;
> +	}
> +
> +	if (!config->scan_on_close) {
> +		if (config->scan_on_open && fsp->modified) {
> +			if (config->cache) {
> +				DBG_DEBUG("Removing cache entry (if existent)"
> +					  ": fname: %s\n", fname);
> +				virusfilter_cache_remove(
> +						config->cache,
> +						cwd_fname, fname);
> +			}
> +		}
> +		DBG_INFO("Not scanned: scan on close is disabled: %s/%s\n",
> +			 cwd_fname, fname);
> +		return close_result;
> +	}
> +
> +	if (!fsp->modified) {
> +		DBG_NOTICE("Not scanned: File not modified: %s/%s\n",
> +			   cwd_fname, fname);
> +
> +		return close_result;
> +	}
> +
> +	if (config->exclude_files && is_in_path(fname,
> +	    config->exclude_files, false))
> +	{
> +		DBG_INFO("Not scanned: exclude files: %s/%s\n",
> +			 cwd_fname, fname);
> +		return close_result;
> +	}
> +
> +	scan_result = virusfilter_scan(handle, config, fsp);
> +
> +	switch (scan_result) {
> +	case VIRUSFILTER_RESULT_CLEAN:
> +		break;
> +	case VIRUSFILTER_RESULT_INFECTED:
> +		scan_errno = config->infected_close_errno;
> +		goto virusfilter_vfs_close_fail;
> +	case VIRUSFILTER_RESULT_ERROR:
> +		if (config->block_access_on_error) {
> +			DBG_INFO("Block access\n");
> +			scan_errno = config->scan_error_close_errno;
> +			goto virusfilter_vfs_close_fail;
> +		}
> +		break;
> +	default:
> +		scan_errno = config->scan_error_close_errno;
> +		goto virusfilter_vfs_close_fail;
> +	}
> +
> +	if (close_errno != 0) {
> +		errno = close_errno;
> +	}
> +
> +	return close_result;
> +
> +virusfilter_vfs_close_fail:
> +
> +	errno = (scan_errno != 0) ? scan_errno : close_errno;
> +
> +	return close_result;
> +}
> +
> +static int virusfilter_vfs_unlink(
> +	struct vfs_handle_struct *handle,
> +	const struct smb_filename *smb_fname)
> +{
> +	int ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
> +	struct virusfilter_config *config = NULL;
> +	char *fname = NULL;
> +	char *cwd_fname = handle->conn->cwd_fname->base_name;
> +
> +	if (ret != 0 && errno != ENOENT) {
> +		return ret;
> +	}
> +
> +	SMB_VFS_HANDLE_GET_DATA(handle, config,
> +				struct virusfilter_config, return -1);
> +
> +	if (config->cache == NULL) {
> +		return 0;
> +	}
> +
> +	fname = smb_fname->base_name;
> +
> +	DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname);
> +	virusfilter_cache_remove(config->cache, cwd_fname, fname);
> +
> +	return 0;
> +}
> +
> +static int virusfilter_vfs_rename(
> +	struct vfs_handle_struct *handle,
> +	const struct smb_filename *smb_fname_src,
> +	const struct smb_filename *smb_fname_dst)
> +{
> +	int ret = SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
> +	struct virusfilter_config *config = NULL;
> +	char *fname = NULL;
> +	char *dst_fname = NULL;
> +	char *cwd_fname = handle->conn->cwd_fname->base_name;
> +
> +	if (ret != 0) {
> +		return ret;
> +	}
> +
> +	SMB_VFS_HANDLE_GET_DATA(handle, config,
> +				struct virusfilter_config, return -1);
> +
> +	if (config->cache == NULL) {
> +		return 0;
> +	}
> +
> +	fname = smb_fname_src->base_name;
> +	dst_fname = smb_fname_dst->base_name;
> +
> +	DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n",
> +		  fname, dst_fname);
> +	virusfilter_cache_entry_rename(config->cache,
> +				       cwd_fname, fname,
> +				       dst_fname);
> +
> +	return 0;
> +}
> +
> +/* VFS operations */
> +static struct vfs_fn_pointers vfs_virusfilter_fns = {
> +	.connect_fn	= virusfilter_vfs_connect,
> +	.disconnect_fn	= virusfilter_vfs_disconnect,
> +	.open_fn	= virusfilter_vfs_open,
> +	.close_fn	= virusfilter_vfs_close,
> +	.unlink_fn	= virusfilter_vfs_unlink,
> +	.rename_fn	= virusfilter_vfs_rename,
> +};
> +
> +NTSTATUS vfs_virusfilter_init(TALLOC_CTX *);
> +NTSTATUS vfs_virusfilter_init(TALLOC_CTX *ctx)
> +{
> +	NTSTATUS status;
> +
> +	status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
> +				  "virusfilter",
> +				  &vfs_virusfilter_fns);
> +	if (!NT_STATUS_IS_OK(status)) {
> +		return status;
> +	}
> +
> +	virusfilter_debug_class = debug_add_class("virusfilter");
> +	if (virusfilter_debug_class == -1) {
> +		virusfilter_debug_class = DBGC_VFS;
> +		DBG_ERR("Couldn't register custom debugging class!\n");
> +	} else {
> +		DBG_DEBUG("Debug class number: %d\n", virusfilter_debug_class);
> +	}
> +
> +	DBG_INFO("registered\n");
> +
> +	return status;
> +}
> diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
> new file mode 100644
> index 00000000000..468883fdaf8
> --- /dev/null
> +++ b/source3/modules/vfs_virusfilter_common.h
> @@ -0,0 +1,149 @@
> +/*
> +   Samba-VirusFilter VFS modules
> +   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +*/
> +
> +#ifndef _VIRUSFILTER_COMMON_H
> +#define _VIRUSFILTER_COMMON_H
> +
> +#include <stdint.h>
> +#include <time.h>
> +
> +/* Samba common include file */
> +#include "includes.h"
> +
> +#include "smbd/smbd.h"
> +#include "smbd/globals.h"
> +#include "system/filesys.h"
> +#include "transfer_file.h"
> +#include "auth.h"
> +#include "passdb.h"
> +#include "../librpc/gen_ndr/ndr_netlogon.h"
> +#include "../lib/tsocket/tsocket.h"
> +
> +/* Samba debug class for VIRUSFILTER */
> +#undef DBGC_CLASS
> +#define DBGC_CLASS virusfilter_debug_class
> +extern int virusfilter_debug_class;
> +
> +/* Samba's global variable */
> +extern userdom_struct current_user_info;
> +
> +#define VIRUSFILTER_VERSION "0.1.5"
> +
> +/* ====================================================================== */
> +
> +typedef enum {
> +	VIRUSFILTER_ACTION_DO_NOTHING,
> +	VIRUSFILTER_ACTION_QUARANTINE,
> +	VIRUSFILTER_ACTION_RENAME,
> +	VIRUSFILTER_ACTION_DELETE,
> +} virusfilter_action;
> +
> +typedef enum {
> +	VIRUSFILTER_RESULT_OK,
> +	VIRUSFILTER_RESULT_CLEAN,
> +	VIRUSFILTER_RESULT_ERROR,
> +	VIRUSFILTER_RESULT_INFECTED,
> +	VIRUSFILTER_RESULT_SUSPECTED,
> +	/* FIXME: VIRUSFILTER_RESULT_RISKWARE, */
> +} virusfilter_result;
> +
> +struct virusfilter_config {
> +	int				scan_request_count;
> +	int				scan_request_limit;
> +
> +	/* Scan on file operations */
> +	bool				scan_on_open;
> +	bool				scan_on_close;
> +
> +	/* Special scan options */
> +	bool				scan_archive;
> +	int				max_nested_scan_archive;
> +	bool				scan_mime;
> +	bool				block_suspected_file;
> +
> +	/* Size limit */
> +	size_t				max_file_size;
> +	size_t				min_file_size;
> +
> +	/* Exclude files */
> +	name_compare_entry		*exclude_files;
> +
> +	/* Scan result cache */
> +	struct virusfilter_cache	*cache;
> +	int				cache_entry_limit;
> +	int				cache_time_limit;
> +
> +	/* Infected file options */
> +	virusfilter_action		infected_file_action;
> +	const char *			infected_file_command;
> +	int				infected_open_errno;
> +	int				infected_close_errno;
> +
> +	/* Scan error options */
> +	const char *			scan_error_command;
> +	int				scan_error_open_errno;
> +	int				scan_error_close_errno;
> +	bool				block_access_on_error;
> +
> +	/* Quarantine infected files */
> +	const char *			quarantine_dir;
> +	const char *			quarantine_prefix;
> +	const char *			quarantine_suffix;
> +	bool				quarantine_keep_tree;
> +	bool				quarantine_keep_name;
> +	mode_t				quarantine_dir_mode;
> +
> +	/* Rename infected files */
> +	const char *			rename_prefix;
> +	const char *			rename_suffix;
> +
> +	/* Network options */
> +	const char *			socket_path;
> +	struct virusfilter_io_handle	*io_h;
> +
> +	/* The backend AV engine */
> +	struct virusfilter_backend	*backend;
> +};
> +
> +struct virusfilter_backend_fns {
> +	int (*connect)(
> +		struct vfs_handle_struct *handle,
> +		struct virusfilter_config *config,
> +		const char *svc,
> +		const char *user);
> +	void (*disconnect)(
> +		struct vfs_handle_struct *handle);
> +	virusfilter_result (*scan_init)(
> +		struct virusfilter_config *config);
> +	virusfilter_result (*scan)(
> +		struct vfs_handle_struct *handle,
> +		struct virusfilter_config *config,
> +		const struct files_struct *fsp,
> +		char **reportp);
> +	void (*scan_end)(
> +		struct virusfilter_config *config);
> +};
> +
> +struct virusfilter_backend {
> +	unsigned version;
> +	const char *name;
> +	const struct virusfilter_backend_fns *fns;
> +	void *backend_private;
> +};
> +
> +#endif /* _VIRUSFILTER_COMMON_H */
> diff --git a/source3/modules/vfs_virusfilter_utils.c b/source3/modules/vfs_virusfilter_utils.c
> new file mode 100644
> index 00000000000..628e0aef99a
> --- /dev/null
> +++ b/source3/modules/vfs_virusfilter_utils.c
> @@ -0,0 +1,1025 @@
> +/*
> +   Samba-VirusFilter VFS modules
> +   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
> +   Copyright (C) 2016-2017 Trever L. Adams
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +*/
> +
> +#include "modules/vfs_virusfilter_common.h"
> +#include "modules/vfs_virusfilter_utils.h"
> +
> +struct iovec;
> +
> +#include "lib/util/iov_buf.h"
> +#include <tevent.h>
> +#include "lib/tsocket/tsocket.h"
> +
> +int virusfilter_debug_class = DBGC_VFS;
> +
> +/* ====================================================================== */
> +
> +char *virusfilter_string_sub(
> +	TALLOC_CTX *mem_ctx,
> +	connection_struct *conn,
> +	const char *str)
> +{
> +	return talloc_sub_advanced(mem_ctx,
> +		lp_servicename(mem_ctx, SNUM(conn)),
> +		conn->session_info->unix_info->unix_name,
> +		conn->connectpath,
> +		conn->session_info->unix_token->gid,
> +		conn->session_info->unix_info->sanitized_username,
> +		conn->session_info->info->domain_name,
> +		str);
> +}
> +
> +int virusfilter_vfs_next_move(
> +	struct vfs_handle_struct *vfs_h,
> +	const struct smb_filename *smb_fname_src,
> +	const struct smb_filename *smb_fname_dst)
> +{
> +	int result;
> +
> +	result = SMB_VFS_NEXT_RENAME(vfs_h, smb_fname_src, smb_fname_dst);
> +	if (result == 0 || errno != EXDEV) {
> +		return result;
> +	}
> +
> +	/*
> +	 * For now, do not handle EXDEV as poking around violates
> +	 * stackability. Return -1, simply refuse access.
> +	 */
> +	return -1;
> +}
> +
> +/* Line-based socket I/O
> + * ======================================================================
> + */
> +
> +struct virusfilter_io_handle *virusfilter_io_new(
> +	TALLOC_CTX *mem_ctx,
> +	int connect_timeout,
> +	int io_timeout)
> +{
> +	struct virusfilter_io_handle *io_h = talloc_zero(mem_ctx,
> +						struct virusfilter_io_handle);
> +
> +	if (io_h == NULL) {
> +		return NULL;
> +	}
> +
> +	io_h->stream = NULL;
> +	io_h->r_len = 0;
> +
> +	virusfilter_io_set_connect_timeout(io_h, connect_timeout);
> +	virusfilter_io_set_io_timeout(io_h, io_timeout);
> +	virusfilter_io_set_writel_eol(io_h, "\x0A", 1);
> +	virusfilter_io_set_readl_eol(io_h, "\x0A", 1);
> +
> +	return io_h;
> +}
> +
> +int virusfilter_io_set_connect_timeout(
> +	struct virusfilter_io_handle *io_h,
> +	int timeout)
> +{
> +	int timeout_old = io_h->connect_timeout;
> +
> +	/* timeout <= 0 means infinite */
> +	io_h->connect_timeout = (timeout > 0) ? timeout : -1;
> +
> +	return timeout_old;
> +}
> +
> +int virusfilter_io_set_io_timeout(
> +	struct virusfilter_io_handle *io_h,
> +	int timeout)
> +{
> +	int timeout_old = io_h->io_timeout;
> +
> +	/* timeout <= 0 means infinite */
> +	io_h->io_timeout = (timeout > 0) ? timeout : -1;
> +
> +	return timeout_old;
> +}
> +
> +void virusfilter_io_set_writel_eol(
> +	struct virusfilter_io_handle *io_h,
> +	const char *eol,
> +	int eol_size)
> +{
> +	if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) {
> +		return;
> +	}
> +
> +	memcpy(io_h->w_eol, eol, eol_size);
> +	io_h->w_eol_size = eol_size;
> +}
> +
> +void virusfilter_io_set_readl_eol(
> +	struct virusfilter_io_handle *io_h,
> +	const char *eol,
> +	int eol_size)
> +{
> +	if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) {
> +		return;
> +	}
> +
> +	memcpy(io_h->r_eol, eol, eol_size);
> +	io_h->r_eol_size = eol_size;
> +}
> +
> +bool virusfilter_io_connect_path(
> +	struct virusfilter_io_handle *io_h,
> +	const char *path)
> +{
> +	struct sockaddr_un addr;
> +	NTSTATUS status;
> +	int socket, bes_result, flags, ret;
> +
> +	ZERO_STRUCT(addr);
> +	addr.sun_family = AF_UNIX;
> +	strncpy(addr.sun_path, path, sizeof(addr.sun_path));
> +
> +	status = open_socket_out((struct sockaddr_storage *)&addr, 0,
> +				 io_h->connect_timeout,
> +				 &socket);
> +	if (!NT_STATUS_IS_OK(status)) {
> +		io_h->stream = NULL;
> +		return false;
> +	}
> +
> +	/* We must not block */
> +	flags = fcntl(socket, F_GETFL);
> +	if (flags <= 0) {
> +		/* Handle error by ignoring */;
> +		flags = 0;
> +		DBG_WARNING("Could not get flags on socket (%s).\n",
> +			    strerror(errno));
> +	}
> +	flags |= SOCK_NONBLOCK;
> +	ret = fcntl(socket, F_SETFL, flags);
> +	if (ret == -1) {
> +		/* Handle error by ignoring for now */
> +		DBG_WARNING("Could not set flags on socket: %s.\n",
> +			    strerror(errno));
> +	}
> +
> +	bes_result = tstream_bsd_existing_socket(io_h, socket, &io_h->stream);
> +	if (bes_result < 0) {
> +		DBG_ERR("Could not convert socket to tstream: %s.\n",
> +			strerror(errno));
> +		io_h->stream = NULL;
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +static void disconnect_done(struct tevent_req *req)
> +{
> +	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
> +	int ret;
> +	int err_ret;
> +
> +	ret = tstream_disconnect_recv(req, &err_ret);
> +	TALLOC_FREE(req);
> +	if (ret == -1) {
> +		*perr = err_ret;
> +	}
> +}
> +
> +bool virusfilter_io_disconnect(
> +	struct virusfilter_io_handle *io_h)
> +{
> +	struct tevent_req *req;
> +	struct tevent_context *ev;
> +	uint64_t *perror = NULL;
> +	bool ok = true;
> +	TALLOC_CTX *frame = talloc_stackframe();
> +
> +	if (io_h->stream == NULL) {
> +		io_h->r_len = 0;
> +		TALLOC_FREE(frame);
> +		return VIRUSFILTER_RESULT_OK;
> +	}
> +
> +	ev = tevent_context_init(frame);
> +	if (ev == NULL) {
> +		DBG_ERR("Failed to setup event context.\n");
> +		ok = false;
> +		goto fail;
> +	}
> +
> +	/* Error return - must be talloc'ed. */
> +	perror = talloc_zero(frame, uint64_t);
> +	if (perror == NULL) {
> +		goto fail;
> +	}
> +
> +	req = tstream_disconnect_send(io_h, ev, io_h->stream);
> +
> +	/* Callback when disconnect is done. */
> +	tevent_req_set_callback(req, disconnect_done, perror);
> +
> +	/* Set timeout. */
> +	ok = tevent_req_set_endtime(req, ev, timeval_current_ofs_msec(
> +				    io_h->connect_timeout));
> +	if (!ok) {
> +		DBG_ERR("Can't set endtime\n");
> +		goto fail;
> +	}
> +
> +	/* Loop waiting for req to finish. */
> +	ok = tevent_req_poll(req, ev);
> +	if (!ok) {
> +		DBG_ERR("tevent_req_poll failed\n");
> +		goto fail;
> +	}
> +
> +	/* Emit debug error if failed. */
> +	if (*perror != 0) {
> +		DBG_DEBUG("Error %s\n", strerror((int)*perror));
> +		goto fail;
> +	}
> +
> +	/* Here we know we disconnected. */
> +
> +	io_h->stream = NULL;
> +	io_h->r_len = 0;
> +
> +	fail:
> +		TALLOC_FREE(frame);
> +		return ok;
> +}
> +
> +static void writev_done(struct tevent_req *req)
> +{
> +	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
> +	int ret;
> +	int err_ret;
> +
> +	ret = tstream_writev_recv(req, &err_ret);
> +	TALLOC_FREE(req);
> +	if (ret == -1) {
> +		*perr = err_ret;
> +	}
> +}
> +
> +/****************************************************************************
> + Write all data from an iov array, with msec timeout (per write)
> + NB. This can be called with a non-socket fd, don't add dependencies
> + on socket calls.
> +****************************************************************************/
> +
> +bool write_data_iov_timeout(
> +	struct tstream_context *stream,
> +	const struct iovec *iov,
> +	size_t iovcnt,
> +	int ms_timeout)
> +{
> +	struct tevent_context *ev = NULL;
> +	struct tevent_req *req = NULL;
> +	uint64_t *perror = NULL;
> +	bool ok = false;
> +	TALLOC_CTX *frame = talloc_stackframe();
> +
> +	ev = tevent_context_init(frame);
> +	if (ev == NULL) {
> +		DBG_ERR("Failed to setup event context.\n");
> +		goto fail;
> +	}
> +
> +	/* Error return - must be talloc'ed. */
> +	perror = talloc_zero(frame, uint64_t);
> +	if (perror == NULL) {
> +		goto fail;
> +	}
> +
> +	/* Send the data. */
> +	req = tstream_writev_send(frame, ev, stream, iov, iovcnt);
> +	if (req == NULL) {
> +		DBG_ERR("Out of memory.\n");
> +		goto fail;
> +	}
> +
> +	/* Callback when *all* data sent. */
> +	tevent_req_set_callback(req, writev_done, perror);
> +
> +	/* Set timeout. */
> +	ok = tevent_req_set_endtime(req, ev,
> +				    timeval_current_ofs_msec(ms_timeout));
> +	if (!ok) {
> +		DBG_ERR("Can't set endtime\n");
> +		goto fail;
> +	}
> +
> +	/* Loop waiting for req to finish. */
> +	ok = tevent_req_poll(req, ev);
> +	if (!ok) {
> +		DBG_ERR("tevent_req_poll failed\n");
> +		goto fail;
> +	}
> +
> +	/* Done with req - freed by the callback. */
> +	req = NULL;
> +
> +	/* Emit debug error if failed. */
> +	if (*perror != 0) {
> +		DBG_DEBUG("Error %s\n", strerror((int)*perror));
> +		goto fail;
> +	}
> +
> +	/* Here we know we correctly wrote all data. */
> +	TALLOC_FREE(frame);
> +	return true;
> +
> +  fail:
> +	TALLOC_FREE(frame);
> +	return false;
> +}
> +
> +bool virusfilter_io_write(
> +	struct virusfilter_io_handle *io_h,
> +	const char *data,
> +	size_t data_size)
> +{
> +	struct iovec iov;
> +
> +	if (data_size == 0) {
> +		return VIRUSFILTER_RESULT_OK;
> +	}
> +
> +	iov.iov_base = discard_const_p(void, data);
> +	iov.iov_len = data_size;
> +
> +	return write_data_iov_timeout(io_h->stream, &iov, 1, io_h->io_timeout);
> +}
> +
> +bool virusfilter_io_writel(
> +	struct virusfilter_io_handle *io_h,
> +	const char *data,
> +	size_t data_size)
> +{
> +	bool ok;
> +
> +	ok = virusfilter_io_write(io_h, data, data_size);
> +	if (!ok) {
> +		return ok;
> +	}
> +
> +	return virusfilter_io_write(io_h, io_h->w_eol, io_h->w_eol_size);
> +}
> +
> +bool virusfilter_io_writefl(
> +	struct virusfilter_io_handle *io_h,
> +	const char *data_fmt, ...)
> +{
> +	va_list ap;
> +	char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
> +	size_t data_size;
> +
> +	va_start(ap, data_fmt);
> +	data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
> +	va_end(ap);
> +
> +	if (unlikely (data_size < 0)) {
> +		DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
> +		return false;
> +	}
> +
> +	memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size);
> +	data_size += io_h->w_eol_size;
> +
> +	return virusfilter_io_write(io_h, data, data_size);
> +}
> +
> +bool virusfilter_io_vwritefl(
> +	struct virusfilter_io_handle *io_h,
> +	const char *data_fmt, va_list ap)
> +{
> +	char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
> +	size_t data_size;
> +
> +	data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
> +
> +	if (unlikely (data_size < 0)) {
> +		DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
> +		return false;
> +	}
> +
> +	memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size);
> +	data_size += io_h->w_eol_size;
> +
> +	return virusfilter_io_write(io_h, data, data_size);
> +}
> +
> +bool virusfilter_io_writev(
> +	struct virusfilter_io_handle *io_h, ...)
> +{
> +	va_list ap;
> +	struct iovec iov[VIRUSFILTER_IO_IOV_MAX], *iov_p;
> +	int iov_n;
> +
> +	va_start(ap, io_h);
> +	for (iov_p = iov, iov_n = 0;
> +	     iov_n < VIRUSFILTER_IO_IOV_MAX;
> +	     iov_p++, iov_n++)
> +	{
> +		iov_p->iov_base = va_arg(ap, void *);
> +		if (iov_p->iov_base == NULL) {
> +			break;
> +		}
> +		iov_p->iov_len = va_arg(ap, int);
> +	}
> +	va_end(ap);
> +
> +	return write_data_iov_timeout(io_h->stream, iov, iov_n,
> +		io_h->io_timeout);
> +}
> +
> +bool virusfilter_io_writevl(
> +	struct virusfilter_io_handle *io_h, ...)
> +{
> +	va_list ap;
> +	struct iovec iov[VIRUSFILTER_IO_IOV_MAX + 1], *iov_p;
> +	int iov_n;
> +
> +	va_start(ap, io_h);
> +	for (iov_p = iov, iov_n = 0; iov_n < VIRUSFILTER_IO_IOV_MAX;
> +	     iov_p++, iov_n++)
> +	{
> +		iov_p->iov_base = va_arg(ap, void *);
> +		if (iov_p->iov_base == NULL) {
> +			break;
> +		}
> +		iov_p->iov_len = va_arg(ap, int);
> +	}
> +	va_end(ap);
> +
> +	iov_p->iov_base = io_h->r_eol;
> +	iov_p->iov_len = io_h->r_eol_size;
> +	iov_n++;
> +
> +	return write_data_iov_timeout(io_h->stream, iov, iov_n,
> +		io_h->io_timeout);
> +}
> +
> +static bool return_existing_line(TALLOC_CTX *ctx,
> +				struct virusfilter_io_handle *io_h,
> +				char **read_line)
> +{
> +	size_t read_line_len = 0;
> +	char *end_p = NULL;
> +	char *eol = NULL;
> +
> +	eol = memmem(io_h->r_buffer, io_h->r_len,
> +			io_h->r_eol, io_h->r_eol_size);
> +	if (eol == NULL) {
> +		return false;
> +	}
> +	end_p = eol + io_h->r_eol_size;
> +
> +	*eol = '\0';
> +	read_line_len = strlen(io_h->r_buffer) + 1;
> +	*read_line = talloc_memdup(ctx,
> +				io_h->r_buffer,
> +				read_line_len);
> +	if (*read_line == NULL) {
> +		return false;
> +	}
> +
> +	/*
> +	 * Copy the remaining buffer over the line
> +	 * we returned.
> +	 */
> +	memmove(io_h->r_buffer,
> +		end_p,
> +		io_h->r_len - (end_p - io_h->r_buffer));
> +
> +	/* And reduce the size left in the buffer. */
> +	io_h->r_len -= (end_p - io_h->r_buffer);
> +	return true;
> +}
> +
> +static void readv_done(struct tevent_req *req)
> +{
> +	uint64_t *perr = tevent_req_callback_data(req, uint64_t);
> +	int ret;
> +	int err_ret;
> +
> +	ret = tstream_readv_recv(req, &err_ret);
> +	TALLOC_FREE(req);
> +	if (ret == -1) {
> +		*perr = err_ret;
> +	}
> +}
> +
> +bool virusfilter_io_readl(TALLOC_CTX *ctx,
> +			struct virusfilter_io_handle *io_h,
> +			char **read_line)
> +{
> +	struct tevent_context *ev = NULL;
> +	bool ok = false;
> +	uint64_t *perror = NULL;
> +	TALLOC_CTX *frame = talloc_stackframe();
> +
> +	/* Search for an existing complete line. */
> +	ok = return_existing_line(ctx, io_h, read_line);
> +	if (ok) {
> +		goto finish;
> +	}
> +
> +	/*
> +	 * No complete line in the buffer. We must read more
> +	 * from the server.
> +	 */
> +	ev = tevent_context_init(frame);
> +	if (ev == NULL) {
> +		DBG_ERR("Failed to setup event context.\n");
> +		goto finish;
> +	}
> +
> +	/* Error return - must be talloc'ed. */
> +	perror = talloc_zero(frame, uint64_t);
> +	if (perror == NULL) {
> +		goto finish;
> +	}
> +
> +	for (;;) {
> +		ssize_t pending = 0;
> +		size_t read_size = 0;
> +		struct iovec iov;
> +		struct tevent_req *req = NULL;
> +
> +		/*
> +		 * How much can we read ?
> +		 */
> +		pending = tstream_pending_bytes(io_h->stream);
> +		if (pending < 0) {
> +			DBG_ERR("tstream_pending_bytes failed (%s).\n",
> +				strerror(errno));
> +			goto finish;
> +		}
> +
> +		read_size = pending;
> +		/* Must read at least one byte. */
> +		read_size = MIN(read_size, 1);
> +
> +		/* And max remaining buffer space. */
> +		read_size = MAX(read_size,
> +				(sizeof(io_h->r_buffer) - io_h->r_len));
> +
> +		if (read_size == 0) {
> +			/* Buffer is full with no EOL. Error out. */
> +			DBG_ERR("Line buffer full.\n");
> +			goto finish;
> +		}
> +
> +		iov.iov_base = io_h->r_buffer + io_h->r_len;
> +		iov.iov_len = read_size;
> +
> +		/* Read the data. */
> +		req = tstream_readv_send(frame,
> +					ev,
> +					io_h->stream,
> +					&iov,
> +					1);
> +		if (req == NULL) {
> +			DBG_ERR("out of memory.\n");
> +			goto finish;
> +		}
> +
> +		/* Callback when *all* data read. */
> +		tevent_req_set_callback(req, readv_done, perror);
> +
> +		/* Set timeout. */
> +		ok = tevent_req_set_endtime(req, ev,
> +				timeval_current_ofs_msec(io_h->io_timeout));
> +		if (!ok) {
> +			DBG_ERR("can't set endtime\n");
> +			goto finish;
> +		}
> +
> +		/* Loop waiting for req to finish. */
> +		ok = tevent_req_poll(req, ev);
> +		if (!ok) {
> +			DBG_ERR("tevent_req_poll failed\n");
> +			goto finish;
> +		}
> +
> +		/* Done with req - freed by the callback. */
> +		req = NULL;
> +
> +		/*
> +		 * Emit debug error if failed.
> +		 * EPIPE may be success so, don't exit.
> +		 */
> +		if (*perror != 0 && *perror != EPIPE) {
> +			DBG_DEBUG("Error %s\n", strerror((int)*perror));
> +			errno = (int)*perror;
> +			goto finish;
> +		}
> +
> +		/*
> +		 * We read read_size bytes. Extend the useable
> +		 * buffer length.
> +		 */
> +		io_h->r_len += read_size;
> +
> +		/* Paranoia... */
> +		SMB_ASSERT(io_h->r_len <= sizeof(io_h->r_buffer));
> +
> +		/* Exit if we have a line to return. */
> +		ok = return_existing_line(ctx, io_h, read_line);
> +		if (ok) {
> +			goto finish;
> +		}
> +		/* No eol - keep reading. */
> +	}
> +
> +  finish:
> +
> +	TALLOC_FREE(frame);
> +	return ok;
> +}
> +
> +bool virusfilter_io_writefl_readl(
> +	struct virusfilter_io_handle *io_h,
> +	char **read_line,
> +	const char *fmt, ...)
> +{
> +	bool ok;
> +
> +	if (fmt) {
> +		va_list ap;
> +
> +		va_start(ap, fmt);
> +		ok = virusfilter_io_vwritefl(io_h, fmt, ap);
> +		va_end(ap);
> +
> +		if (!ok) {
> +			return ok;
> +		}
> +	}
> +
> +	ok = virusfilter_io_readl(talloc_tos(), io_h, read_line);
> +	if (!ok) {
> +		DBG_ERR("virusfilter_io_readl not OK: %d\n", ok);
> +		return false;
> +	}
> +	if (io_h->r_len == 0) { /* EOF */
> +		DBG_ERR("virusfilter_io_readl EOF\n");
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +struct virusfilter_cache *virusfilter_cache_new(
> +	TALLOC_CTX *ctx,
> +	int entry_limit,
> +	time_t time_limit)
> +{
> +	struct virusfilter_cache *cache;
> +
> +	if (time_limit == 0) {
> +		return NULL;
> +	}
> +
> +	cache = talloc_zero(ctx, struct virusfilter_cache);
> +	if (cache == NULL) {
> +		DBG_ERR("talloc_zero failed.\n");
> +		return NULL;
> +	}
> +
> +	cache->cache = memcache_init(cache->ctx, entry_limit *
> +				       (sizeof(struct virusfilter_cache_entry)
> +				       + VIRUSFILTER_CACHE_BUFFER_SIZE));
> +	if (cache->cache == NULL) {
> +		DBG_ERR("memcache_init failed.\n");
> +		return NULL;
> +	}
> +	cache->ctx = ctx;
> +	cache->time_limit = time_limit;
> +
> +	return cache;
> +}
> +
> +bool virusfilter_cache_entry_add(
> +	struct virusfilter_cache *cache,
> +	const char *directory,
> +	const char *fname,
> +	virusfilter_result result,
> +	char *report)
> +{
> +	int blob_size = sizeof(struct virusfilter_cache_entry);
> +	struct virusfilter_cache_entry *cache_e =
> +					talloc_zero_size(NULL, blob_size);
> +	int fname_len = 0;
> +
> +	if (fname == NULL || directory == NULL) {
> +		TALLOC_FREE(report);
> +		return false;
> +	}
> +
> +	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
> +
> +	if (fname == NULL) {
> +		TALLOC_FREE(report);
> +		return false;
> +	}
> +
> +	fname_len = strlen(fname);
> +
> +	if (cache_e == NULL|| cache->time_limit == 0) {
> +		TALLOC_FREE(report);
> +		return false;
> +	}
> +
> +	cache_e->result = result;
> +	if (report != NULL) {
> +		cache_e->report = talloc_steal(cache_e, report);
> +	}
> +	if (cache->time_limit > 0) {
> +		cache_e->time = time(NULL);
> +	}
> +
> +	memcache_add_talloc(cache->cache,
> +			    VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
> +			    data_blob_const(fname, fname_len), &cache_e);
> +
> +	return true;
> +}
> +
> +bool virusfilter_cache_entry_rename(
> +	struct virusfilter_cache *cache,
> +	const char *directory,
> +	char *old_fname,
> +	char *new_fname)
> +{
> +	int old_fname_len = 0;
> +	int new_fname_len = 0;
> +	struct virusfilter_cache_entry *new_data = NULL;
> +	struct virusfilter_cache_entry *old_data = NULL;
> +
> +	if (old_fname == NULL || new_fname == NULL || directory == NULL) {
> +		return false;
> +	}
> +
> +	old_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, old_fname);
> +	new_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, new_fname);
> +
> +	if (old_fname == NULL || new_fname == NULL) {
> +		TALLOC_FREE(old_fname);
> +		TALLOC_FREE(new_fname);
> +		return false;
> +	}
> +
> +	old_fname_len = strlen(old_fname);
> +	new_fname_len = strlen(new_fname);
> +
> +	old_data = memcache_lookup_talloc(
> +				cache->cache,
> +				VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
> +				data_blob_const(old_fname, old_fname_len));
> +
> +	if (old_data == NULL) {
> +		return false;
> +	}
> +
> +	new_data = talloc_memdup(cache->ctx, old_data,
> +				 sizeof(struct virusfilter_cache_entry));
> +	if (new_data == NULL) {
> +		return false;
> +	}
> +	new_data->report = talloc_strdup(new_data, old_data->report);
> +
> +	memcache_add_talloc(cache->cache,
> +			VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
> +			data_blob_const(new_fname, new_fname_len), &new_data);
> +
> +	memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
> +			data_blob_const(old_fname, old_fname_len));
> +
> +	return true;
> +}
> +
> +void virusfilter_cache_purge(struct virusfilter_cache *cache)
> +{
> +	memcache_flush(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
> +}
> +
> +struct virusfilter_cache_entry *virusfilter_cache_get(
> +	struct virusfilter_cache *cache,
> +	const char *directory,
> +	const char *fname)
> +{
> +	int fname_len = 0;
> +	struct virusfilter_cache_entry *cache_e = NULL;
> +	struct virusfilter_cache_entry *data = NULL;
> +
> +	if (fname == NULL || directory == NULL) {
> +		return 0;
> +	}
> +
> +	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
> +
> +	if (fname == NULL) {
> +		return 0;
> +	}
> +
> +	fname_len = strlen(fname);
> +
> +	data = memcache_lookup_talloc(cache->cache,
> +				      VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
> +				      data_blob_const(fname, fname_len));
> +
> +	if (data == NULL) {
> +		return cache_e;
> +	}
> +
> +	if (cache->time_limit > 0) {
> +		if (time(NULL) - data->time  > cache->time_limit) {
> +			DBG_DEBUG("Cache entry is too old: %s\n",
> +				  fname);
> +			virusfilter_cache_remove(cache, directory, fname);
> +			return cache_e;
> +		}
> +	}
> +	cache_e = talloc_memdup(cache->ctx, data,
> +			       sizeof(struct virusfilter_cache_entry));
> +	if (cache_e == NULL) {
> +		return NULL;
> +	}
> +	if (data->report != NULL) {
> +		cache_e->report = talloc_strdup(cache_e, data->report);
> +	} else {
> +		cache_e->report = NULL;
> +	}
> +
> +	return cache_e;
> +}
> +
> +void virusfilter_cache_remove(struct virusfilter_cache *cache,
> +	const char *directory,
> +	const char *fname)
> +{
> +	DBG_DEBUG("Purging cache entry: %s/%s\n", directory, fname);
> +
> +	if (fname == NULL || directory == NULL) {
> +		return;
> +	}
> +
> +	fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
> +
> +	if (fname == NULL) {
> +		return;
> +	}
> +
> +	memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
> +			data_blob_const(fname, strlen(fname)));
> +}
> +
> +void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e)
> +{
> +	if (cache_e != NULL) {
> +		TALLOC_FREE(cache_e->report);
> +		cache_e->report = NULL;
> +	}
> +	TALLOC_FREE(cache_e);
> +}
> +
> +/* Shell scripting
> + * ======================================================================
> + */
> +
> +int virusfilter_env_set(
> +	TALLOC_CTX *mem_ctx,
> +	char **env_list,
> +	const char *name,
> +	const char *value)
> +{
> +	char *env_new;
> +	int ret;
> +
> +	env_new = talloc_asprintf(mem_ctx, "%s=%s", name, value);
> +	if (env_new == NULL) {
> +		DBG_ERR("talloc_asprintf failed\n");
> +		return -1;
> +	}
> +
> +	ret = strv_add(mem_ctx, env_list, env_new);
> +
> +	TALLOC_FREE(env_new);
> +
> +	return ret;
> +}
> +
> +/* virusfilter_env version Samba's *_sub_advanced() in substitute.c */
> +int virusfilter_shell_set_conn_env(
> +	TALLOC_CTX *mem_ctx,
> +	char **env_list,
> +	connection_struct *conn)
> +{
> +	int snum = SNUM(conn);
> +	char *server_addr_p;
> +	char *client_addr_p;
> +	const char *local_machine_name = get_local_machine_name();
> +	fstring pidstr;
> +	int ret;
> +
> +	if (local_machine_name == NULL || *local_machine_name == '\0') {
> +		local_machine_name = lp_netbios_name();
> +	}
> +
> +	server_addr_p = tsocket_address_inet_addr_string(
> +				conn->sconn->local_address, talloc_tos());
> +
> +	if (server_addr_p != NULL) {
> +		ret = strncmp("::ffff:", server_addr_p, 7);
> +		if (ret == 0) {
> +			server_addr_p += 7;
> +		}
> +		virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_IP",
> +				    server_addr_p);
> +	}
> +	TALLOC_FREE(server_addr_p);
> +
> +	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_NAME",
> +			    myhostname());
> +	virusfilter_env_set(mem_ctx, env_list,
> +			    "VIRUSFILTER_SERVER_NETBIOS_NAME",
> +			    local_machine_name);
> +	slprintf(pidstr,sizeof(pidstr)-1, "%ld", (long)getpid());
> +	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_PID",
> +			    pidstr);
> +
> +	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_NAME",
> +			    lp_const_servicename(snum));
> +	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_PATH",
> +			    conn->cwd_fname->base_name);
> +
> +	client_addr_p = tsocket_address_inet_addr_string(
> +				conn->sconn->remote_address, talloc_tos());
> +
> +	if (client_addr_p != NULL) {
> +		ret = strncmp("::ffff:", client_addr_p, 7);
> +		if (ret == 0) {
> +			client_addr_p += 7;
> +		}
> +		virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_IP",
> +				    client_addr_p);
> +	}
> +	TALLOC_FREE(client_addr_p);
> +
> +	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_NAME",
> +			    conn->sconn->remote_hostname);
> +	virusfilter_env_set(mem_ctx, env_list,
> +			    "VIRUSFILTER_CLIENT_NETBIOS_NAME",
> +			    get_remote_machine_name());
> +
> +	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_NAME",
> +			    get_current_username());
> +	virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_DOMAIN",
> +			    current_user_info.domain);
> +
> +	return 0;
> +}
> +
> +/* Wrapper to Samba's smbrun() in smbrun.c */
> +int virusfilter_shell_run(
> +	TALLOC_CTX *mem_ctx,
> +	const char *cmd,
> +	char **env_list,
> +	connection_struct *conn,
> +	bool sanitize)
> +{
> +	int ret;
> +
> +	if (conn != NULL) {
> +		ret = virusfilter_shell_set_conn_env(mem_ctx, env_list, conn);
> +		if (ret == -1) {
> +			return -1;
> +		}
> +	}
> +
> +	if (sanitize) {
> +		return smbrun(cmd, NULL, strv_to_env(talloc_tos(), *env_list));
> +	} else {
> +		return smbrun_no_sanitize(cmd, NULL, strv_to_env(talloc_tos(),
> +					  *env_list));
> +	}
> +}
> diff --git a/source3/modules/vfs_virusfilter_utils.h b/source3/modules/vfs_virusfilter_utils.h
> new file mode 100644
> index 00000000000..69754aa6546
> --- /dev/null
> +++ b/source3/modules/vfs_virusfilter_utils.h
> @@ -0,0 +1,177 @@
> +/*
> +   Samba-VirusFilter VFS modules
> +   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +*/
> +
> +#ifndef _VIRUSFILTER_UTILS_H
> +#define _VIRUSFILTER_UTILS_H
> +
> +#include "modules/vfs_virusfilter_common.h"
> +#include "../lib/util/memcache.h"
> +#include "../lib/util/strv.h"
> +
> +/*#define str_eq(s1, s2)		\
> +	((strcmp((s1), (s2)) == 0) ? true : false)
> +#define strn_eq(s1, s2, n)	\
> +	((strncmp((s1), (s2), (n)) == 0) ? true : false) */
> +
> +/* "* 3" is for %-encoding */
> +#define VIRUSFILTER_IO_URL_MAX		(PATH_MAX * 3)
> +#define VIRUSFILTER_IO_BUFFER_SIZE	(VIRUSFILTER_IO_URL_MAX + 128)
> +#define VIRUSFILTER_IO_EOL_SIZE		1
> +#define VIRUSFILTER_IO_IOV_MAX		16
> +#define VIRUSFILTER_CACHE_BUFFER_SIZE	(PATH_MAX + 128)
> +
> +struct virusfilter_io_handle {
> +	struct tstream_context *stream;
> +	int		connect_timeout;	/* msec */
> +	int		io_timeout;		/* msec */
> +
> +	/* end-of-line character(s) */
> +	char		w_eol[VIRUSFILTER_IO_EOL_SIZE];
> +	int		w_eol_size;
> +
> +	/* end-of-line character(s) */
> +	char		r_eol[VIRUSFILTER_IO_EOL_SIZE];
> +	int		r_eol_size;
> +
> +	/* buffer */
> +	char		r_buffer[VIRUSFILTER_IO_BUFFER_SIZE];
> +	size_t		r_len;
> +};
> +
> +struct virusfilter_cache_entry {
> +	time_t time;
> +	virusfilter_result result;
> +	char *report;
> +};
> +
> +struct virusfilter_cache {
> +	struct memcache *cache;
> +	TALLOC_CTX *ctx;
> +	time_t time_limit;
> +};
> +
> +/* ====================================================================== */
> +
> +char *virusfilter_string_sub(
> +	TALLOC_CTX *mem_ctx,
> +	connection_struct *conn,
> +	const char *str);
> +int virusfilter_vfs_next_move(
> +	vfs_handle_struct *handle,
> +	const struct smb_filename *smb_fname_src,
> +	const struct smb_filename *smb_fname_dst);
> +
> +/* Line-based socket I/O */
> +struct virusfilter_io_handle *virusfilter_io_new(
> +	TALLOC_CTX *mem_ctx,
> +	int connect_timeout,
> +	int timeout);
> +int virusfilter_io_set_connect_timeout(
> +	struct virusfilter_io_handle *io_h,
> +	int timeout);
> +int virusfilter_io_set_io_timeout(
> +	struct virusfilter_io_handle *io_h, int timeout);
> +void virusfilter_io_set_writel_eol(
> +	struct virusfilter_io_handle *io_h,
> +	const char *eol,
> +	int eol_size);
> +void virusfilter_io_set_readl_eol(
> +	struct virusfilter_io_handle *io_h,
> +	const char *eol,
> +	int eol_size);
> +bool virusfilter_io_connect_path(
> +	struct virusfilter_io_handle *io_h,
> +	const char *path);
> +bool virusfilter_io_disconnect(
> +	struct virusfilter_io_handle *io_h);
> +bool write_data_iov_timeout(
> +	struct tstream_context *stream,
> +	const struct iovec *iov,
> +	size_t iovcnt,
> +	int ms_timeout);
> +bool virusfilter_io_write(
> +	struct virusfilter_io_handle *io_h,
> +	const char *data,
> +	size_t data_size);
> +bool virusfilter_io_writel(
> +	struct virusfilter_io_handle *io_h,
> +	const char *data,
> +	size_t data_size);
> +bool virusfilter_io_writefl(
> +	struct virusfilter_io_handle *io_h,
> +	const char *data_fmt, ...);
> +bool virusfilter_io_vwritefl(
> +	struct virusfilter_io_handle *io_h,
> +	const char *data_fmt, va_list ap);
> +bool virusfilter_io_writev(
> +	struct virusfilter_io_handle *io_h, ...);
> +bool virusfilter_io_writevl(
> +	struct virusfilter_io_handle *io_h, ...);
> +bool virusfilter_io_readl(TALLOC_CTX *ctx,
> +			struct virusfilter_io_handle *io_h,
> +			char **read_line);
> +bool virusfilter_io_writefl_readl(
> +	struct virusfilter_io_handle *io_h,
> +	char **read_line,
> +	const char *fmt, ...);
> +
> +/* Scan result cache */
> +struct virusfilter_cache *virusfilter_cache_new(
> +	TALLOC_CTX *ctx,
> +	int entry_limit,
> +	time_t time_limit);
> +bool virusfilter_cache_entry_add(
> +	struct virusfilter_cache *cache,
> +	const char *directory,
> +	const char *fname,
> +	virusfilter_result result,
> +	char *report);
> +bool virusfilter_cache_entry_rename(
> +	struct virusfilter_cache *cache,
> +	const char *directory,
> +	char *old_fname,
> +	char *new_fname);
> +void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e);
> +struct virusfilter_cache_entry *virusfilter_cache_get(
> +	struct virusfilter_cache *cache,
> +	const char *directory,
> +	const char *fname);
> +void virusfilter_cache_remove(
> +	struct virusfilter_cache *cache,
> +	const char *directory,
> +	const char *fname);
> +void virusfilter_cache_purge(struct virusfilter_cache *cache);
> +
> +/* Shell scripting */
> +int virusfilter_env_set(
> +	TALLOC_CTX *mem_ctx,
> +	char **env_list,
> +	const char *name,
> +	const char *value);
> +int virusfilter_shell_set_conn_env(
> +	TALLOC_CTX *mem_ctx,
> +	char **env_list,
> +	connection_struct *conn);
> +int virusfilter_shell_run(
> +	TALLOC_CTX *mem_ctx,
> +	const char *cmd,
> +	char **env_list,
> +	connection_struct *conn,
> +	bool sanitize);
> +
> +#endif /* _VIRUSFILTER_UTILS_H */
> diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
> index 24eeee6aad6..b423eb0083a 100644
> --- a/source3/modules/wscript_build
> +++ b/source3/modules/wscript_build
> @@ -14,6 +14,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')))
> @@ -489,6 +494,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 f3b6d330121..42dadc86150 100644
> --- a/source3/wscript
> +++ b/source3/wscript
> @@ -1687,7 +1687,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.14.3
> 
> 
> From c9068c08c2eae53f0c263338854f2b1bdbe0a6f5 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 3/5] 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 a23d1f7c641..8947e35b14b 100644
> --- a/source3/modules/vfs_virusfilter.c
> +++ b/source3/modules/vfs_virusfilter.c
> @@ -441,10 +441,17 @@ static int virusfilter_vfs_connect(
>  		return -1;
>  	}
>  
> -	/* This goes away as soon as the next commit adds an actual backend... */
> -	if (config->backend == NULL) {
> -		DBG_INFO("Not implemented\n");
> -		return SMB_VFS_NEXT_CONNECT(handle, svc, user);
> +	switch (backend) {
> +	case VIRUSFILTER_SCANNER_SOPHOS:
> +		ret = virusfilter_sophos_init(config);
> +		break;
> +	default:
> +		DBG_ERR("Unhandled scanner %d\n", backend);
> +		return -1;
> +	}
> +	if (ret != 0) {
> +		DBG_ERR("Scanner backend init failed\n");
> +		return -1;
>  	}
>  
>  	if (config->backend->fns->connect != NULL) {
> diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
> index 468883fdaf8..69519c9daa4 100644
> --- a/source3/modules/vfs_virusfilter_common.h
> +++ b/source3/modules/vfs_virusfilter_common.h
> @@ -146,4 +146,6 @@ struct virusfilter_backend {
>  	void *backend_private;
>  };
>  
> +int virusfilter_sophos_init(struct virusfilter_config *config);
> +
>  #endif /* _VIRUSFILTER_COMMON_H */
> diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c
> new file mode 100644
> index 00000000000..72051cd64a2
> --- /dev/null
> +++ b/source3/modules/vfs_virusfilter_sophos.c
> @@ -0,0 +1,391 @@
> +/*
> +   Samba-VirusFilter VFS modules
> +   Sophos Anti-Virus savdid (SSSP/1.0) support
> +   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <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 b423eb0083a..cfab82f26b2 100644
> --- a/source3/modules/wscript_build
> +++ b/source3/modules/wscript_build
> @@ -496,7 +496,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.14.3
> 
> 
> From e80b0b64c50b0683d940c3db04e868c9f7747747 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 4/5] 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 8947e35b14b..338b4fc899c 100644
> --- a/source3/modules/vfs_virusfilter.c
> +++ b/source3/modules/vfs_virusfilter.c
> @@ -445,6 +445,9 @@ static int virusfilter_vfs_connect(
>  	case VIRUSFILTER_SCANNER_SOPHOS:
>  		ret = virusfilter_sophos_init(config);
>  		break;
> +	case VIRUSFILTER_SCANNER_FSAV:
> +		ret = virusfilter_fsav_init(config);
> +		break;
>  	default:
>  		DBG_ERR("Unhandled scanner %d\n", backend);
>  		return -1;
> diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
> index 69519c9daa4..a28ce2978fa 100644
> --- a/source3/modules/vfs_virusfilter_common.h
> +++ b/source3/modules/vfs_virusfilter_common.h
> @@ -147,5 +147,6 @@ struct virusfilter_backend {
>  };
>  
>  int virusfilter_sophos_init(struct virusfilter_config *config);
> +int virusfilter_fsav_init(struct virusfilter_config *config);
>  
>  #endif /* _VIRUSFILTER_COMMON_H */
> diff --git a/source3/modules/vfs_virusfilter_fsav.c b/source3/modules/vfs_virusfilter_fsav.c
> new file mode 100644
> index 00000000000..2b874d7db43
> --- /dev/null
> +++ b/source3/modules/vfs_virusfilter_fsav.c
> @@ -0,0 +1,451 @@
> +/*
> +   Samba-VirusFilter VFS modules
> +   F-Secure Anti-Virus fsavd support
> +   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <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 cfab82f26b2..401bea9c5b1 100644
> --- a/source3/modules/wscript_build
> +++ b/source3/modules/wscript_build
> @@ -499,6 +499,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.14.3
> 
> 
> From 136de6d4db19049ef62f9b9c810e94f26aa20c82 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 5/5] 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 338b4fc899c..9b29923110d 100644
> --- a/source3/modules/vfs_virusfilter.c
> +++ b/source3/modules/vfs_virusfilter.c
> @@ -448,6 +448,9 @@ static int virusfilter_vfs_connect(
>  	case VIRUSFILTER_SCANNER_FSAV:
>  		ret = virusfilter_fsav_init(config);
>  		break;
> +	case VIRUSFILTER_SCANNER_CLAMAV:
> +		ret = virusfilter_clamav_init(config);
> +		break;
>  	default:
>  		DBG_ERR("Unhandled scanner %d\n", backend);
>  		return -1;
> diff --git a/source3/modules/vfs_virusfilter_clamav.c b/source3/modules/vfs_virusfilter_clamav.c
> new file mode 100644
> index 00000000000..d0e1fc081b7
> --- /dev/null
> +++ b/source3/modules/vfs_virusfilter_clamav.c
> @@ -0,0 +1,195 @@
> +/*
> +   Samba-VirusFilter VFS modules
> +   ClamAV clamd support
> +   Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <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 401bea9c5b1..eeb9852e77f 100644
> --- a/source3/modules/wscript_build
> +++ b/source3/modules/wscript_build
> @@ -500,6 +500,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.14.3
> 







More information about the samba-technical mailing list