Samba-VirusFilter: common headers and sources.
authorTrever L. Adams <trever.adams@gmail.com>
Tue, 18 Oct 2016 19:34:53 +0000 (13:34 -0600)
committerRalph Boehme <slow@samba.org>
Wed, 24 Jan 2018 09:29:46 +0000 (10:29 +0100)
Samba-VirusFilter Contributors:

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

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

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

With many thanks to the Samba Team.

Signed-off-by: Trever L. Adams <trever.adams@gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas@osstech.co.jp>
Reviewed-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
docs-xml/manpages/vfs_virusfilter.8.xml [new file with mode: 0644]
docs-xml/wscript_build
examples/scripts/vfs/virusfilter/virusfilter-notify.ksh [new file with mode: 0644]
source3/modules/vfs_virusfilter.c [new file with mode: 0644]
source3/modules/vfs_virusfilter_common.h [new file with mode: 0644]
source3/modules/vfs_virusfilter_utils.c [new file with mode: 0644]
source3/modules/vfs_virusfilter_utils.h [new file with mode: 0644]
source3/modules/wscript_build
source3/wscript

diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
new file mode 100644 (file)
index 0000000..eb6112e
--- /dev/null
@@ -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@example.com --cc "%U@example.com" --from samba@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@example.com --from samba@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>
index f586208b4719f0b594a8512a06b5ba6b373e5d98..954c62a29bc33f18605fa9a4534c3de43160252e 100644 (file)
@@ -90,6 +90,7 @@ manpages='''
          manpages/vfs_time_audit.8
          manpages/vfs_tsmsm.8
          manpages/vfs_unityed_media.8
+         manpages/vfs_virusfilter.8
          manpages/vfs_worm.8
          manpages/vfs_xattr_tdb.8
          manpages/vfstest.1
diff --git a/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh b/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh
new file mode 100644 (file)
index 0000000..a07b914
--- /dev/null
@@ -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:-@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 (file)
index 0000000..a23d1f7
--- /dev/null
@@ -0,0 +1,1508 @@
+/*
+ * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+ * Copyright (C) 2016-2017 Trever L. Adams
+ * Copyright (C) 2017 Ralph Boehme <slow@samba.org>
+ * Copyright (C) 2017 Jeremy Allison <jra@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/*
+ * Default configuration values
+ * ======================================================================
+ */
+
+#define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX          "virusfilter."
+#define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX          ".infected"
+#define VIRUSFILTER_DEFAULT_RENAME_PREFIX              "virusfilter."
+#define VIRUSFILTER_DEFAULT_RENAME_SUFFIX              ".infected"
+
+/* ====================================================================== */
+
+enum virusfilter_scanner_enum {
+       VIRUSFILTER_SCANNER_CLAMAV,
+       VIRUSFILTER_SCANNER_FSAV,
+       VIRUSFILTER_SCANNER_SOPHOS
+};
+
+static const struct enum_list scanner_list[] = {
+       { VIRUSFILTER_SCANNER_CLAMAV,   "clamav" },
+       { VIRUSFILTER_SCANNER_FSAV,     "fsav" },
+       { VIRUSFILTER_SCANNER_SOPHOS,   "sophos" },
+       { -1,                           NULL }
+};
+
+static const struct enum_list virusfilter_actions[] = {
+       { VIRUSFILTER_ACTION_QUARANTINE,        "quarantine" },
+       { VIRUSFILTER_ACTION_RENAME,            "rename" },
+       { VIRUSFILTER_ACTION_DELETE,            "delete" },
+
+       /* alias for "delete" */
+       { VIRUSFILTER_ACTION_DELETE,            "remove" },
+
+       /* alias for "delete" */
+       { VIRUSFILTER_ACTION_DELETE,            "unlink" },
+       { VIRUSFILTER_ACTION_DO_NOTHING,        "nothing" },
+       { -1,                                   NULL}
+};
+
+static int virusfilter_config_destructor(struct virusfilter_config *config)
+{
+       TALLOC_FREE(config->backend);
+       return 0;
+}
+
+/*
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_directory_exist(
+       struct vfs_handle_struct *handle,
+       const char *dname)
+{
+       int ret = -1;
+       struct smb_filename smb_fname = {
+               .base_name = discard_const_p(char, dname)
+       };
+
+       ret = SMB_VFS_STAT(handle->conn, &smb_fname);
+       if (ret == 0) {
+               return S_ISDIR(smb_fname.st.st_ex_mode);
+       }
+
+       return false;
+}
+
+/**
+ * Create directory tree
+ * @param conn connection
+ * @param dname Directory tree to be created
+ * @return Returns true for success
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_create_dir(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       const char *dname)
+{
+       size_t len = 0;
+       size_t cat_len = 0;
+       char *new_dir = NULL;
+       char *tmp_str = NULL;
+       char *token = NULL;
+       char *tok_str = NULL;
+       bool status = false;
+       bool ok = false;
+       int ret = -1;
+       char *saveptr = NULL;
+
+       tmp_str = talloc_strdup(talloc_tos(), dname);
+       if (tmp_str == NULL) {
+               DBG_ERR("virusfilter-vfs: out of memory!\n");
+               errno = ENOMEM;
+               goto done;
+       }
+       tok_str = tmp_str;
+
+       len = strlen(dname)+1;
+       new_dir = (char *)talloc_size(talloc_tos(), len + 1);
+       if (new_dir == NULL) {
+               DBG_ERR("virusfilter-vfs: out of memory!\n");
+               errno = ENOMEM;
+               goto done;
+       }
+       *new_dir = '\0';
+       if (dname[0] == '/') {
+               /* Absolute path. */
+               cat_len = strlcat(new_dir, "/", len + 1);
+               if (cat_len >= len+1) {
+                       goto done;
+               }
+       }
+
+       /* Create directory tree if neccessary */
+       for (token = strtok_r(tok_str, "/", &saveptr);
+            token != NULL;
+            token = strtok_r(NULL, "/", &saveptr))
+       {
+               cat_len = strlcat(new_dir, token, len + 1);
+               if (cat_len >= len+1) {
+                       goto done;
+               }
+               ok = quarantine_directory_exist(handle, new_dir);
+               if (ok == true) {
+                       DBG_DEBUG("quarantine: dir %s already exists\n",
+                                 new_dir);
+               } else {
+                       struct smb_filename *smb_fname = NULL;
+
+                       DBG_INFO("quarantine: creating new dir %s\n", new_dir);
+
+                       smb_fname = synthetic_smb_fname(talloc_tos(), new_dir,
+                                                       NULL, NULL, 0);
+                       if (smb_fname == NULL) {
+                               goto done;
+                       }
+
+                       ret = SMB_VFS_NEXT_MKDIR(handle,
+                                       smb_fname,
+                                       config->quarantine_dir_mode);
+                       if (ret != 0) {
+                               TALLOC_FREE(smb_fname);
+
+                               DBG_WARNING("quarantine: mkdir failed for %s "
+                                           "with error: %s\n", new_dir,
+                                           strerror(errno));
+                               status = false;
+                               goto done;
+                       }
+                       TALLOC_FREE(smb_fname);
+               }
+               cat_len = strlcat(new_dir, "/", len + 1);
+               if (cat_len >= len + 1) {
+                       goto done;
+               }
+       }
+
+       status = true;
+done:
+       TALLOC_FREE(tmp_str);
+       TALLOC_FREE(new_dir);
+       return status;
+}
+
+static int virusfilter_vfs_connect(
+       struct vfs_handle_struct *handle,
+       const char *svc,
+       const char *user)
+{
+       int snum = SNUM(handle->conn);
+       struct virusfilter_config *config = NULL;
+       const char *exclude_files = NULL;
+       const char *temp_quarantine_dir_mode = NULL;
+       char *sret = NULL;
+       char *tmp = NULL;
+       enum virusfilter_scanner_enum backend;
+       int connect_timeout = 0;
+       int io_timeout = 0;
+       int ret = -1;
+
+       config = talloc_zero(handle, struct virusfilter_config);
+       if (config == NULL) {
+               DBG_ERR("talloc_zero failed\n");
+               return -1;
+       }
+       talloc_set_destructor(config, virusfilter_config_destructor);
+
+       SMB_VFS_HANDLE_SET_DATA(handle, config, NULL,
+                               struct virusfilter_config, return -1);
+
+       config->scan_request_limit = lp_parm_int(
+               snum, "virusfilter", "scan request limit", 0);
+
+       config->scan_on_open = lp_parm_bool(
+               snum, "virusfilter", "scan on open", true);
+
+       config->scan_on_close = lp_parm_bool(
+               snum, "virusfilter", "scan on close", false);
+
+       config->max_nested_scan_archive = lp_parm_int(
+               snum, "virusfilter", "max nested scan archive", 1);
+
+       config->scan_archive = lp_parm_bool(
+               snum, "virusfilter", "scan archive", false);
+
+       config->scan_mime = lp_parm_bool(
+               snum, "virusfilter", "scan mime", false);
+
+       config->max_file_size = (ssize_t)lp_parm_ulong(
+               snum, "virusfilter", "max file size", 100000000L);
+
+       config->min_file_size = (ssize_t)lp_parm_ulong(
+               snum, "virusfilter", "min file size", 10);
+
+       exclude_files = lp_parm_const_string(
+               snum, "virusfilter", "exclude files", NULL);
+       if (exclude_files != NULL) {
+               set_namearray(&config->exclude_files, exclude_files);
+       }
+
+       config->cache_entry_limit = lp_parm_int(
+               snum, "virusfilter", "cache entry limit", 100);
+
+       config->cache_time_limit = lp_parm_int(
+               snum, "virusfilter", "cache time limit", 10);
+
+       config->infected_file_action = lp_parm_enum(
+               snum, "virusfilter", "infected file action",
+               virusfilter_actions, VIRUSFILTER_ACTION_DO_NOTHING);
+
+       config->infected_file_command = lp_parm_const_string(
+               snum, "virusfilter", "infected file command", NULL);
+
+       config->scan_error_command = lp_parm_const_string(
+               snum, "virusfilter", "scan error command", NULL);
+
+       config->block_access_on_error = lp_parm_bool(
+               snum, "virusfilter", "block access on error", false);
+
+       tmp = talloc_asprintf(config, "%s/.quarantine",
+               handle->conn->connectpath);
+
+       config->quarantine_dir = lp_parm_const_string(
+               snum, "virusfilter", "quarantine directory",
+               tmp ? tmp : "/tmp/.quarantine");
+
+       if (tmp != config->quarantine_dir) {
+               TALLOC_FREE(tmp);
+       }
+
+       temp_quarantine_dir_mode = lp_parm_const_string(
+               snum, "virusfilter", "quarantine directory mode", "0755");
+       if (temp_quarantine_dir_mode != NULL) {
+               sscanf(temp_quarantine_dir_mode, "%o",
+                      &config->quarantine_dir_mode);
+       }
+
+       config->quarantine_prefix = lp_parm_const_string(
+               snum, "virusfilter", "quarantine prefix",
+               VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+
+       config->quarantine_suffix = lp_parm_const_string(
+               snum, "virusfilter", "quarantine suffix",
+               VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+
+       /*
+        * Make sure prefixes and suffixes do not contain directory
+        * delimiters
+        */
+       sret = strstr(config->quarantine_prefix, "/");
+       if (sret != NULL) {
+               DBG_ERR("quarantine prefix must not contain directory "
+                       "delimiter(s) such as '/' (%s replaced with %s)\n",
+                       config->quarantine_prefix,
+                       VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+               config->quarantine_prefix =
+                       VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX;
+       }
+       sret = strstr(config->quarantine_suffix, "/");
+       if (sret != NULL) {
+               DBG_ERR("quarantine suffix must not contain directory "
+                       "delimiter(s) such as '/' (%s replaced with %s)\n",
+                       config->quarantine_suffix,
+                       VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+               config->quarantine_suffix =
+                       VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX;
+       }
+
+       config->quarantine_keep_tree = lp_parm_bool(
+               snum, "virusfilter", "quarantine keep tree", true);
+
+       config->quarantine_keep_name = lp_parm_bool(
+               snum, "virusfilter", "quarantine keep name", true);
+
+       config->rename_prefix = lp_parm_const_string(
+               snum, "virusfilter", "rename prefix",
+               VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+
+       config->rename_suffix = lp_parm_const_string(
+               snum, "virusfilter", "rename suffix",
+               VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+
+       /*
+        * Make sure prefixes and suffixes do not contain directory
+        * delimiters
+        */
+       sret = strstr(config->rename_prefix, "/");
+       if (sret != NULL) {
+               DBG_ERR("rename prefix must not contain directory "
+                       "delimiter(s) such as '/' (%s replaced with %s)\n",
+                       config->rename_prefix,
+                       VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+               config->rename_prefix =
+                       VIRUSFILTER_DEFAULT_RENAME_PREFIX;
+       }
+       sret = strstr(config->rename_suffix, "/");
+       if (sret != NULL) {
+               DBG_ERR("rename suffix must not contain directory "
+                       "delimiter(s) such as '/' (%s replaced with %s)\n",
+                       config->rename_suffix,
+                       VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+               config->rename_suffix =
+                       VIRUSFILTER_DEFAULT_RENAME_SUFFIX;
+       }
+
+       config->infected_open_errno = lp_parm_int(
+               snum, "virusfilter", "infected file errno on open", EACCES);
+
+       config->infected_close_errno = lp_parm_int(
+               snum, "virusfilter", "infected file errno on close", 0);
+
+       config->scan_error_open_errno = lp_parm_int(
+               snum, "virusfilter", "scan error errno on open", EACCES);
+
+       config->scan_error_close_errno = lp_parm_int(
+               snum, "virusfilter", "scan error errno on close", 0);
+
+       config->socket_path = lp_parm_const_string(
+               snum, "virusfilter", "socket path", NULL);
+
+       /* canonicalize socket_path */
+       if (config->socket_path != NULL && config->socket_path[0] != '/') {
+               DBG_ERR("socket path must be an absolute path. "
+                       "Using backend default\n");
+               config->socket_path = NULL;
+        }
+       if (config->socket_path != NULL) {
+               canonicalize_absolute_path(handle,
+                                          config->socket_path);
+       }
+
+       connect_timeout = lp_parm_int(snum, "virusfilter",
+                                     "connect timeout", 30000);
+
+       io_timeout = lp_parm_int(snum, "virusfilter", "io timeout", 60000);
+
+       config->io_h = virusfilter_io_new(config, connect_timeout, io_timeout);
+       if (config->io_h == NULL) {
+               DBG_ERR("virusfilter_io_new failed");
+               return -1;
+       }
+
+       if (config->cache_entry_limit > 0) {
+               config->cache = virusfilter_cache_new(handle,
+                                       config->cache_entry_limit,
+                                       config->cache_time_limit);
+               if (config->cache == NULL) {
+                       DBG_ERR("Initializing cache failed: Cache disabled\n");
+                       return -1;
+               }
+       }
+
+       /*
+        * Check quarantine directory now to save processing
+        * and becoming root over and over.
+        */
+       if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+               bool ok = true;
+               bool dir_exists;
+
+               /*
+                * Do SMB_VFS_NEXT_MKDIR(config->quarantine_dir)
+                * hierarchy
+                */
+               become_root();
+               dir_exists = quarantine_directory_exist(handle,
+                                               config->quarantine_dir);
+               if (!dir_exists) {
+                       DBG_DEBUG("Creating quarantine directory: %s\n",
+                                 config->quarantine_dir);
+                       ok = quarantine_create_dir(handle, config,
+                                             config->quarantine_dir);
+               }
+               unbecome_root();
+               if (!ok) {
+                       DBG_ERR("Creating quarantine directory %s "
+                               "failed with %s\n",
+                               config->quarantine_dir,
+                               strerror(errno));
+                       return -1;
+               }
+       }
+
+       /*
+        * Now that the frontend options are initialized, load the configured
+        * backend.
+        */
+
+       backend = (enum virusfilter_scanner_enum)lp_parm_enum(snum,
+                               "virusfilter",
+                               "scanner",
+                               scanner_list,
+                              -1);
+       if (backend == (enum virusfilter_scanner_enum)-1) {
+               DBG_ERR("No AV-Scanner configured, "
+                       "please set \"virusfilter:scanner\"\n");
+               return -1;
+       }
+
+       /* This goes away as soon as the next commit adds an actual backend... */
+       if (config->backend == NULL) {
+               DBG_INFO("Not implemented\n");
+               return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+       }
+
+       if (config->backend->fns->connect != NULL) {
+               ret = config->backend->fns->connect(handle, config, svc, user);
+               if (ret == -1) {
+                       return -1;
+               }
+       }
+
+       return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+}
+
+static void virusfilter_vfs_disconnect(struct vfs_handle_struct *handle)
+{
+       struct virusfilter_config *config = NULL;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct virusfilter_config, return);
+
+       if (config->backend->fns->disconnect != NULL) {
+               config->backend->fns->disconnect(handle);
+       }
+
+       free_namearray(config->exclude_files);
+       virusfilter_io_disconnect(config->io_h);
+
+       SMB_VFS_NEXT_DISCONNECT(handle);
+}
+
+static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx,
+                                     struct virusfilter_config *config,
+                                     char **env_list)
+{
+       int ret;
+
+       ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
+                                 VIRUSFILTER_VERSION);
+       if (ret == -1) {
+               return -1;
+       }
+       ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
+                                 config->backend->name);
+       if (ret == -1) {
+               return -1;
+       }
+
+       if (config->backend->version != 0) {
+               char *version = NULL;
+
+               version = talloc_asprintf(talloc_tos(), "%u",
+                                         config->backend->version);
+               if (version == NULL) {
+                       return -1;
+               }
+               ret = virusfilter_env_set(mem_ctx, env_list,
+                                         "VIRUSFILTER_MODULE_VERSION",
+                                         version);
+               TALLOC_FREE(version);
+               if (ret == -1) {
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+static char *quarantine_check_tree(TALLOC_CTX *mem_ctx,
+                                  struct vfs_handle_struct *handle,
+                                  struct virusfilter_config *config,
+                                  const struct smb_filename *smb_fname,
+                                  char *q_dir_in,
+                                  char *cwd_fname)
+{
+       char *temp_path = NULL;
+       char *q_dir_out = NULL;
+       bool ok;
+
+       temp_path = talloc_asprintf(talloc_tos(), "%s/%s", q_dir_in, cwd_fname);
+       if (temp_path == NULL) {
+               DBG_ERR("talloc_asprintf failed\n");
+               goto out;
+       }
+
+       become_root();
+       ok = quarantine_directory_exist(handle, temp_path);
+       unbecome_root();
+       if (ok) {
+               DBG_DEBUG("quarantine: directory [%s] exists\n", temp_path);
+               q_dir_out = talloc_move(mem_ctx, &temp_path);
+               goto out;
+       }
+
+       DBG_DEBUG("quarantine: Creating directory %s\n", temp_path);
+
+       become_root();
+       ok = quarantine_create_dir(handle, config, temp_path);
+       unbecome_root();
+       if (!ok) {
+               DBG_NOTICE("Could not create quarantine directory [%s], "
+                          "ignoring for [%s]\n",
+                          temp_path, smb_fname_str_dbg(smb_fname));
+               goto out;
+       }
+
+       q_dir_out = talloc_move(mem_ctx, &temp_path);
+
+out:
+       TALLOC_FREE(temp_path);
+       return q_dir_out;
+}
+
+static virusfilter_action infected_file_action_quarantine(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       TALLOC_CTX *mem_ctx,
+       const struct files_struct *fsp,
+       const char **filepath_newp)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       connection_struct *conn = handle->conn;
+       char *cwd_fname = fsp->conn->cwd_fname->base_name;
+       char *fname = fsp->fsp_name->base_name;
+       const struct smb_filename *smb_fname = fsp->fsp_name;
+       struct smb_filename *q_smb_fname = NULL;
+       char *q_dir = NULL;
+       char *q_prefix = NULL;
+       char *q_suffix = NULL;
+       char *q_filepath = NULL;
+       char *dir_name = NULL;
+       const char *base_name = NULL;
+       char *rand_filename_component = NULL;
+       virusfilter_action action = VIRUSFILTER_ACTION_QUARANTINE;
+       bool ok = false;
+       int ret = -1;
+       int saved_errno = 0;
+
+       q_dir = virusfilter_string_sub(frame, conn,
+                                      config->quarantine_dir);
+       q_prefix = virusfilter_string_sub(frame, conn,
+                                         config->quarantine_prefix);
+       q_suffix = virusfilter_string_sub(frame, conn,
+                                         config->quarantine_suffix);
+       if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
+               DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+                       "memory\n", cwd_fname, fname);
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       if (config->quarantine_keep_name || config->quarantine_keep_tree) {
+               ok = parent_dirname(frame, smb_fname->base_name,
+                                   &dir_name, &base_name);
+               if (!ok) {
+                       DBG_ERR("parent_dirname failed\n");
+                       action = VIRUSFILTER_ACTION_DO_NOTHING;
+                       goto out;
+               }
+
+               if (config->quarantine_keep_tree) {
+                       char *tree = NULL;
+
+                       tree = quarantine_check_tree(frame, handle, config,
+                                                    smb_fname, q_dir,
+                                                    cwd_fname);
+                       if (tree == NULL) {
+                               /*
+                                * If we can't create the tree, just move it
+                                * into the toplevel quarantine dir.
+                                */
+                               tree = q_dir;
+                       }
+                       q_dir = tree;
+               }
+       }
+
+       /* Get a 16 byte + \0 random filename component. */
+       rand_filename_component = generate_random_str(frame, 16);
+       if (rand_filename_component == NULL) {
+               DBG_ERR("generate_random_str failed\n");
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       if (config->quarantine_keep_name) {
+               q_filepath = talloc_asprintf(frame, "%s/%s%s%s-%s",
+                                            q_dir, q_prefix,
+                                            base_name, q_suffix,
+                                            rand_filename_component);
+       } else {
+               q_filepath = talloc_asprintf(frame, "%s/%s%s",
+                                            q_dir, q_prefix,
+                                            rand_filename_component);
+       }
+       if (q_filepath == NULL) {
+               DBG_ERR("talloc_asprintf failed\n");
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       q_smb_fname = synthetic_smb_fname(frame, q_filepath,
+                                         smb_fname->stream_name,
+                                         NULL, smb_fname->flags);
+       if (q_smb_fname == NULL) {
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       become_root();
+       ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
+       if (ret == -1) {
+               saved_errno = errno;
+       }
+       unbecome_root();
+       if (ret == -1) {
+               DBG_ERR("Quarantine [%s/%s] rename to %s failed: %s\n",
+                       cwd_fname, fname, q_filepath, strerror(saved_errno));
+               errno = saved_errno;
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       *filepath_newp = talloc_move(mem_ctx, &q_filepath);
+
+out:
+       TALLOC_FREE(frame);
+       return action;
+}
+
+static virusfilter_action infected_file_action_rename(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       TALLOC_CTX *mem_ctx,
+       const struct files_struct *fsp,
+       const char **filepath_newp)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       connection_struct *conn = handle->conn;
+       char *cwd_fname = fsp->conn->cwd_fname->base_name;
+       char *fname = fsp->fsp_name->base_name;
+       const struct smb_filename *smb_fname = fsp->fsp_name;
+       struct smb_filename *q_smb_fname = NULL;
+       char *q_dir = NULL;
+       char *q_prefix = NULL;
+       char *q_suffix = NULL;
+       char *q_filepath = NULL;
+       const char *base_name = NULL;
+       virusfilter_action action = VIRUSFILTER_ACTION_RENAME;
+       bool ok = false;
+       int ret = -1;
+       int saved_errno = 0;
+
+       q_prefix = virusfilter_string_sub(frame, conn,
+                                         config->rename_prefix);
+       q_suffix = virusfilter_string_sub(frame, conn,
+                                         config->rename_suffix);
+       if (q_prefix == NULL || q_suffix == NULL) {
+               DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+                       "memory\n", cwd_fname, fname);
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       ok = parent_dirname(frame, fname, &q_dir, &base_name);
+       if (!ok) {
+               DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+                       "memory\n", cwd_fname, fname);
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       if (q_dir == NULL) {
+               DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+                       "memory\n", cwd_fname, fname);
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       q_filepath = talloc_asprintf(frame, "%s/%s%s%s", q_dir,
+                                    q_prefix, base_name, q_suffix);
+
+       q_smb_fname = synthetic_smb_fname(frame, q_filepath,
+                                         smb_fname->stream_name, NULL,
+                                         smb_fname->flags);
+       if (q_smb_fname == NULL) {
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       become_root();
+       ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
+       if (ret == -1) {
+               saved_errno = errno;
+       }
+       unbecome_root();
+
+       if (ret == -1) {
+               DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
+                       cwd_fname, fname, strerror(saved_errno));
+               errno = saved_errno;
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               goto out;
+       }
+
+       *filepath_newp = talloc_move(mem_ctx, &q_filepath);
+
+out:
+       TALLOC_FREE(frame);
+       return action;
+}
+
+static virusfilter_action infected_file_action_delete(
+       struct vfs_handle_struct *handle,
+       const struct files_struct *fsp)
+{
+       int ret;
+       int saved_errno = 0;
+
+       become_root();
+       ret = SMB_VFS_NEXT_UNLINK(handle, fsp->fsp_name);
+       if (ret == -1) {
+               saved_errno = errno;
+       }
+       unbecome_root();
+       if (ret == -1) {
+               DBG_ERR("Delete [%s/%s] failed: %s\n",
+                       fsp->conn->cwd_fname->base_name,
+                       fsp->fsp_name->base_name,
+                       strerror(saved_errno));
+               errno = saved_errno;
+               return VIRUSFILTER_ACTION_DO_NOTHING;
+       }
+
+       return VIRUSFILTER_ACTION_DELETE;
+}
+
+static virusfilter_action virusfilter_do_infected_file_action(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       TALLOC_CTX *mem_ctx,
+       const struct files_struct *fsp,
+       const char **filepath_newp)
+{
+       virusfilter_action action;
+
+       *filepath_newp = NULL;
+
+       switch (config->infected_file_action) {
+       case VIRUSFILTER_ACTION_RENAME:
+               action = infected_file_action_rename(handle, config, mem_ctx,
+                                                    fsp, filepath_newp);
+               break;
+
+       case VIRUSFILTER_ACTION_QUARANTINE:
+               action = infected_file_action_quarantine(handle, config, mem_ctx,
+                                                        fsp, filepath_newp);
+               break;
+
+       case VIRUSFILTER_ACTION_DELETE:
+               action = infected_file_action_delete(handle, fsp);
+               break;
+
+       case VIRUSFILTER_ACTION_DO_NOTHING:
+       default:
+               action = VIRUSFILTER_ACTION_DO_NOTHING;
+               break;
+       }
+
+       return action;
+}
+
+static virusfilter_action virusfilter_treat_infected_file(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       const struct files_struct *fsp,
+       const char *report,
+       bool is_cache)
+{
+       connection_struct *conn = handle->conn;
+       char *cwd_fname = fsp->conn->cwd_fname->base_name;
+       char *fname = fsp->fsp_name->base_name;
+       TALLOC_CTX *mem_ctx = talloc_tos();
+       int i;
+       virusfilter_action action;
+       const char *action_name = "UNKNOWN";
+       const char *filepath_q = NULL;
+       char *env_list = NULL;
+       char *command = NULL;
+       int command_result;
+       int ret;
+
+       action = virusfilter_do_infected_file_action(handle, config, mem_ctx,
+                                                    fsp, &filepath_q);
+       for (i=0; virusfilter_actions[i].name; i++) {
+               if (virusfilter_actions[i].value == action) {
+                       action_name = virusfilter_actions[i].name;
+                       break;
+               }
+       }
+       DBG_WARNING("Infected file action: %s/%s: %s\n", cwd_fname,
+                   fname, action_name);
+
+       if (!config->infected_file_command) {
+               return action;
+       }
+
+       ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+       if (ret == -1) {
+               goto done;
+       }
+       ret = virusfilter_env_set(mem_ctx, &env_list,
+                                 "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
+                                 fname);
+       if (ret == -1) {
+               goto done;
+       }
+       if (report != NULL) {
+               ret = virusfilter_env_set(mem_ctx, &env_list,
+                                         "VIRUSFILTER_INFECTED_FILE_REPORT",
+                                         report);
+               if (ret == -1) {
+                       goto done;
+               }
+       }
+       ret = virusfilter_env_set(mem_ctx, &env_list,
+                                 "VIRUSFILTER_INFECTED_FILE_ACTION",
+                                 action_name);
+       if (ret == -1) {
+               goto done;
+       }
+       if (filepath_q != NULL) {
+               ret = virusfilter_env_set(mem_ctx, &env_list,
+                                         "VIRUSFILTER_QUARANTINED_FILE_PATH",
+                                         filepath_q);
+               if (ret == -1) {
+                       goto done;
+               }
+       }
+       if (is_cache) {
+               ret = virusfilter_env_set(mem_ctx, &env_list,
+                                         "VIRUSFILTER_RESULT_IS_CACHE", "yes");
+               if (ret == -1) {
+                       goto done;
+               }
+       }
+
+       command = virusfilter_string_sub(mem_ctx, conn,
+                                        config->infected_file_command);
+       if (command == NULL) {
+               DBG_ERR("virusfilter_string_sub failed\n");
+               goto done;
+       }
+
+       DBG_NOTICE("Infected file command line: %s/%s: %s\n", cwd_fname,
+                  fname, command);
+
+       command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+                                              conn, true);
+       if (command_result != 0) {
+               DBG_ERR("Infected file command failed: %d\n", command_result);
+       }
+
+       DBG_DEBUG("Infected file command finished: %d\n", command_result);
+
+done:
+       TALLOC_FREE(env_list);
+       TALLOC_FREE(command);
+
+       return action;
+}
+
+static void virusfilter_treat_scan_error(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       const struct files_struct *fsp,
+       const char *report,
+       bool is_cache)
+{
+       connection_struct *conn = handle->conn;
+       const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+       const char *fname = fsp->fsp_name->base_name;
+       TALLOC_CTX *mem_ctx = talloc_tos();
+       char *env_list = NULL;
+       char *command = NULL;
+       int command_result;
+       int ret;
+
+       if (!config->scan_error_command) {
+               return;
+       }
+       ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+       if (ret == -1) {
+               goto done;
+       }
+       ret = virusfilter_env_set(mem_ctx, &env_list,
+                                 "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
+                                 fname);
+       if (ret == -1) {
+               goto done;
+       }
+       if (report != NULL) {
+               ret = virusfilter_env_set(mem_ctx, &env_list,
+                                         "VIRUSFILTER_SCAN_ERROR_REPORT",
+                                         report);
+               if (ret == -1) {
+                       goto done;
+               }
+       }
+       if (is_cache) {
+               ret = virusfilter_env_set(mem_ctx, &env_list,
+                                         "VIRUSFILTER_RESULT_IS_CACHE", "1");
+               if (ret == -1) {
+                       goto done;
+               }
+       }
+
+       command = virusfilter_string_sub(mem_ctx, conn,
+                                        config->scan_error_command);
+       if (command == NULL) {
+               DBG_ERR("virusfilter_string_sub failed\n");
+               goto done;
+       }
+
+       DBG_NOTICE("Scan error command line: %s/%s: %s\n", cwd_fname,
+                  fname, command);
+
+       command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+                                              conn, true);
+       if (command_result != 0) {
+               DBG_ERR("Scan error command failed: %d\n", command_result);
+       }
+
+done:
+       TALLOC_FREE(env_list);
+       TALLOC_FREE(command);
+}
+
+static virusfilter_result virusfilter_scan(
+       struct vfs_handle_struct *handle,
+       struct virusfilter_config *config,
+       const struct files_struct *fsp)
+{
+       virusfilter_result scan_result;
+       char *scan_report = NULL;
+       const char *fname = fsp->fsp_name->base_name;
+       const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+       struct virusfilter_cache_entry *scan_cache_e = NULL;
+       bool is_cache = false;
+       virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING;
+       bool add_scan_cache = true;
+       bool ok = false;
+
+       if (config->cache) {
+               DBG_DEBUG("Searching cache entry: fname: %s\n", fname);
+               scan_cache_e = virusfilter_cache_get(config->cache,
+                                                    cwd_fname, fname);
+               if (scan_cache_e != NULL) {
+                       DBG_DEBUG("Cache entry found: cached result: %d\n",
+                             scan_cache_e->result);
+                       is_cache = true;
+                       scan_result = scan_cache_e->result;
+                       scan_report = scan_cache_e->report;
+                       goto virusfilter_scan_result_eval;
+               }
+               DBG_DEBUG("Cache entry not found\n");
+       }
+
+       if (config->backend->fns->scan_init != NULL) {
+               scan_result = config->backend->fns->scan_init(config);
+               if (scan_result != VIRUSFILTER_RESULT_OK) {
+                       scan_result = VIRUSFILTER_RESULT_ERROR;
+                       scan_report = talloc_asprintf(
+                               talloc_tos(),
+                               "Initializing scanner failed");
+                       goto virusfilter_scan_result_eval;
+               }
+       }
+
+       scan_result = config->backend->fns->scan(handle, config, fsp,
+                                                &scan_report);
+
+       if (config->backend->fns->scan_end != NULL) {
+               bool scan_end = true;
+
+               if (config->scan_request_limit > 0) {
+                       scan_end = false;
+                       config->scan_request_count++;
+                       if (config->scan_request_count >=
+                           config->scan_request_limit)
+                       {
+                               scan_end = true;
+                               config->scan_request_count = 0;
+                       }
+               }
+               if (scan_end) {
+                       config->backend->fns->scan_end(config);
+               }
+       }
+
+virusfilter_scan_result_eval:
+
+       switch (scan_result) {
+       case VIRUSFILTER_RESULT_CLEAN:
+               DBG_INFO("Scan result: Clean: %s/%s\n", cwd_fname, fname);
+               break;
+
+       case VIRUSFILTER_RESULT_INFECTED:
+               DBG_ERR("Scan result: Infected: %s/%s: %s\n",
+                       cwd_fname, fname, scan_report ? scan_report :
+                       "infected (memory error on report)");
+               file_action = virusfilter_treat_infected_file(handle,
+                                       config, fsp, scan_report, is_cache);
+               if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+                       add_scan_cache = false;
+               }
+               break;
+
+       case VIRUSFILTER_RESULT_SUSPECTED:
+               if (!config->block_suspected_file) {
+                       break;
+               }
+               DBG_ERR("Scan result: Suspected: %s/%s: %s\n",
+                       cwd_fname, fname, scan_report ? scan_report :
+                       "suspected infection (memory error on report)");
+               file_action = virusfilter_treat_infected_file(handle,
+                                       config, fsp, scan_report, is_cache);
+               if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+                       add_scan_cache = false;
+               }
+               break;
+
+       case VIRUSFILTER_RESULT_ERROR:
+               DBG_ERR("Scan result: Error: %s/%s: %s\n",
+                       cwd_fname, fname, scan_report ? scan_report :
+                       "error (memory error on report)");
+               virusfilter_treat_scan_error(handle, config, fsp,
+                                            scan_report, is_cache);
+               add_scan_cache = false;
+               break;
+
+       default:
+               DBG_ERR("Scan result: Unknown result code %d: %s/%s: %s\n",
+                       scan_result, cwd_fname, fname, scan_report ?
+                       scan_report : "Unknown (memory error on report)");
+               virusfilter_treat_scan_error(handle, config, fsp,
+                                            scan_report, is_cache);
+               add_scan_cache = false;
+               break;
+       }
+
+       if (config->cache) {
+               if (!is_cache && add_scan_cache) {
+                       DBG_DEBUG("Adding new cache entry: %s, %d\n", fname,
+                                 scan_result);
+                       ok = virusfilter_cache_entry_add(
+                                       config->cache, cwd_fname, fname,
+                                       scan_result, scan_report);
+                       if (!ok) {
+                               DBG_ERR("Cannot create cache entry: "
+                                       "virusfilter_cache_entry_new failed");
+                               goto virusfilter_scan_return;
+                       }
+               } else if (is_cache) {
+                       virusfilter_cache_entry_free(scan_cache_e);
+               }
+       }
+
+virusfilter_scan_return:
+       return scan_result;
+}
+
+static int virusfilter_vfs_open(
+       struct vfs_handle_struct *handle,
+       struct smb_filename *smb_fname,
+       files_struct *fsp,
+       int flags,
+       mode_t mode)
+{
+       TALLOC_CTX *mem_ctx = talloc_tos();
+       struct virusfilter_config *config;
+       const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+       virusfilter_result scan_result;
+       const char *fname = fsp->fsp_name->base_name;
+       char *dir_name = NULL;
+       const char *base_name = NULL;
+       int scan_errno = 0;
+       size_t test_prefix;
+       size_t test_suffix;
+       int rename_trap_count = 0;
+       int ret;
+       bool ok1, ok2;
+       char *sret = NULL;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct virusfilter_config, return -1);
+
+       test_prefix = strlen(config->rename_prefix);
+       test_suffix = strlen(config->rename_suffix);
+       if (test_prefix > 0) {
+               rename_trap_count++;
+       }
+       if (test_suffix > 0) {
+               rename_trap_count++;
+       }
+
+       ok1 = is_ntfs_stream_smb_fname(smb_fname);
+       ok2 = is_ntfs_default_stream_smb_fname(smb_fname);
+       if (ok1 && !ok2) {
+               DBG_INFO("Not scanned: only file backed streams can be scanned:"
+                        " %s/%s\n", cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       if (!config->scan_on_open) {
+               DBG_INFO("Not scanned: scan on open is disabled: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       if (flags & O_TRUNC) {
+               DBG_INFO("Not scanned: Open flags have O_TRUNC: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+       if (ret != 0) {
+
+               /*
+                * Do not return immediately if !(flags & O_CREAT) &&
+                * errno != ENOENT.
+                * Do not do this here or anywhere else. The module is
+                * stackable and there may be modules below, such as audit
+                * modules, which should be handled.
+                */
+               goto virusfilter_vfs_open_next;
+       }
+       ret = S_ISREG(smb_fname->st.st_ex_mode);
+       if (ret == 0) {
+               DBG_INFO("Not scanned: Directory or special file: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+       if (config->max_file_size > 0 &&
+           smb_fname->st.st_ex_size > config->max_file_size)
+       {
+               DBG_INFO("Not scanned: file size > max file size: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+       if (config->min_file_size > 0 &&
+           smb_fname->st.st_ex_size < config->min_file_size)
+       {
+               DBG_INFO("Not scanned: file size < min file size: %s/%s\n",
+                     cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       ok1 = is_in_path(fname, config->exclude_files, false);
+       if (config->exclude_files && ok1)
+       {
+               DBG_INFO("Not scanned: exclude files: %s/%s\n",
+                        cwd_fname, fname);
+               goto virusfilter_vfs_open_next;
+       }
+
+       if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+               sret = strstr_m(fname, config->quarantine_dir);
+               if (sret != NULL) {
+                       scan_errno = config->infected_open_errno;
+                       goto virusfilter_vfs_open_fail;
+               }
+       }
+
+       if (test_prefix > 0 || test_suffix > 0) {
+               ok1 = parent_dirname(mem_ctx, fname, &dir_name, &base_name);
+               if (ok1)
+               {
+                       if (test_prefix > 0) {
+                               ret = strncmp(base_name,
+                                   config->rename_prefix, test_prefix);
+                               if (ret != 0) {
+                                       test_prefix = 0;
+                               }
+                       }
+                       if (test_suffix > 0) {
+                               ret = strcmp(base_name + (strlen(base_name)
+                                                - test_suffix),
+                                                config->rename_suffix);
+                               if (ret != 0) {
+                                       test_suffix = 0;
+                               }
+                       }
+
+                       TALLOC_FREE(dir_name);
+
+                       if ((rename_trap_count == 2 && test_prefix &&
+                           test_suffix) || (rename_trap_count == 1 &&
+                           (test_prefix || test_suffix)))
+                       {
+                               scan_errno =
+                                       config->infected_open_errno;
+                               goto virusfilter_vfs_open_fail;
+                       }
+               }
+       }
+
+       scan_result = virusfilter_scan(handle, config, fsp);
+
+       switch (scan_result) {
+       case VIRUSFILTER_RESULT_CLEAN:
+               break;
+       case VIRUSFILTER_RESULT_INFECTED:
+               scan_errno = config->infected_open_errno;
+               goto virusfilter_vfs_open_fail;
+       case VIRUSFILTER_RESULT_ERROR:
+               if (config->block_access_on_error) {
+                       DBG_INFO("Block access\n");
+                       scan_errno = config->scan_error_open_errno;
+                       goto virusfilter_vfs_open_fail;
+               }
+               break;
+       default:
+               scan_errno = config->scan_error_open_errno;
+               goto virusfilter_vfs_open_fail;
+       }
+
+virusfilter_vfs_open_next:
+       return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
+
+virusfilter_vfs_open_fail:
+       errno = (scan_errno != 0) ? scan_errno : EACCES;
+       return -1;
+}
+
+static int virusfilter_vfs_close(
+       struct vfs_handle_struct *handle,
+       files_struct *fsp)
+{
+       /*
+         * The name of this variable is for consistency. If API changes to
+         * match _open change to cwd_fname as in virusfilter_vfs_open.
+         */
+       const char *cwd_fname = handle->conn->connectpath;
+
+       struct virusfilter_config *config = NULL;
+       char *fname = fsp->fsp_name->base_name = NULL;
+       int close_result = -1;
+       int close_errno = 0;
+       virusfilter_result scan_result;
+       int scan_errno = 0;
+       bool ok1, ok2;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct virusfilter_config, return -1);
+
+       /*
+        * Must close after scan? It appears not as the scanners are not
+        * internal and other modules such as greyhole seem to do
+        * SMB_VFS_NEXT_* functions before processing.
+        */
+       close_result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+       if (close_result == -1) {
+               close_errno = errno;
+       }
+
+       /*
+        * Return immediately if close_result == -1, and close_errno == EBADF.
+        * If close failed, file likely doesn't exist, do not try to scan.
+        */
+       if (close_result == -1 && close_errno == EBADF) {
+               if (fsp->modified) {
+                       DBG_DEBUG("Removing cache entry (if existent): "
+                                 "fname: %s\n", fname);
+                       virusfilter_cache_remove(config->cache,
+                                                cwd_fname, fname);
+               }
+               goto virusfilter_vfs_close_fail;
+       }
+
+       if (fsp->is_directory) {
+               DBG_INFO("Not scanned: Directory: %s/%s\n", cwd_fname,
+                        fname);
+               return close_result;
+       }
+
+       ok1 = is_ntfs_stream_smb_fname(fsp->fsp_name);
+       ok2 = is_ntfs_default_stream_smb_fname(fsp->fsp_name);
+       if (ok1 && !ok2) {
+               if (config->scan_on_open && fsp->modified) {
+                       if (config->cache) {
+                               DBG_DEBUG("Removing cache entry (if existent)"
+                                         ": fname: %s\n", fname);
+                               virusfilter_cache_remove(
+                                               config->cache,
+                                               cwd_fname, fname);
+                       }
+               }
+               DBG_INFO("Not scanned: only file backed streams can be scanned:"
+                        " %s/%s\n", cwd_fname, fname);
+               return close_result;
+       }
+
+       if (!config->scan_on_close) {
+               if (config->scan_on_open && fsp->modified) {
+                       if (config->cache) {
+                               DBG_DEBUG("Removing cache entry (if existent)"
+                                         ": fname: %s\n", fname);
+                               virusfilter_cache_remove(
+                                               config->cache,
+                                               cwd_fname, fname);
+                       }
+               }
+               DBG_INFO("Not scanned: scan on close is disabled: %s/%s\n",
+                        cwd_fname, fname);
+               return close_result;
+       }
+
+       if (!fsp->modified) {
+               DBG_NOTICE("Not scanned: File not modified: %s/%s\n",
+                          cwd_fname, fname);
+
+               return close_result;
+       }
+
+       if (config->exclude_files && is_in_path(fname,
+           config->exclude_files, false))
+       {
+               DBG_INFO("Not scanned: exclude files: %s/%s\n",
+                        cwd_fname, fname);
+               return close_result;
+       }
+
+       scan_result = virusfilter_scan(handle, config, fsp);
+
+       switch (scan_result) {
+       case VIRUSFILTER_RESULT_CLEAN:
+               break;
+       case VIRUSFILTER_RESULT_INFECTED:
+               scan_errno = config->infected_close_errno;
+               goto virusfilter_vfs_close_fail;
+       case VIRUSFILTER_RESULT_ERROR:
+               if (config->block_access_on_error) {
+                       DBG_INFO("Block access\n");
+                       scan_errno = config->scan_error_close_errno;
+                       goto virusfilter_vfs_close_fail;
+               }
+               break;
+       default:
+               scan_errno = config->scan_error_close_errno;
+               goto virusfilter_vfs_close_fail;
+       }
+
+       if (close_errno != 0) {
+               errno = close_errno;
+       }
+
+       return close_result;
+
+virusfilter_vfs_close_fail:
+
+       errno = (scan_errno != 0) ? scan_errno : close_errno;
+
+       return close_result;
+}
+
+static int virusfilter_vfs_unlink(
+       struct vfs_handle_struct *handle,
+       const struct smb_filename *smb_fname)
+{
+       int ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+       struct virusfilter_config *config = NULL;
+       char *fname = NULL;
+       char *cwd_fname = handle->conn->cwd_fname->base_name;
+
+       if (ret != 0 && errno != ENOENT) {
+               return ret;
+       }
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct virusfilter_config, return -1);
+
+       if (config->cache == NULL) {
+               return 0;
+       }
+
+       fname = smb_fname->base_name;
+
+       DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname);
+       virusfilter_cache_remove(config->cache, cwd_fname, fname);
+
+       return 0;
+}
+
+static int virusfilter_vfs_rename(
+       struct vfs_handle_struct *handle,
+       const struct smb_filename *smb_fname_src,
+       const struct smb_filename *smb_fname_dst)
+{
+       int ret = SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
+       struct virusfilter_config *config = NULL;
+       char *fname = NULL;
+       char *dst_fname = NULL;
+       char *cwd_fname = handle->conn->cwd_fname->base_name;
+
+       if (ret != 0) {
+               return ret;
+       }
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct virusfilter_config, return -1);
+
+       if (config->cache == NULL) {
+               return 0;
+       }
+
+       fname = smb_fname_src->base_name;
+       dst_fname = smb_fname_dst->base_name;
+
+       DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n",
+                 fname, dst_fname);
+       virusfilter_cache_entry_rename(config->cache,
+                                      cwd_fname, fname,
+                                      dst_fname);
+
+       return 0;
+}
+
+/* VFS operations */
+static struct vfs_fn_pointers vfs_virusfilter_fns = {
+       .connect_fn     = virusfilter_vfs_connect,
+       .disconnect_fn  = virusfilter_vfs_disconnect,
+       .open_fn        = virusfilter_vfs_open,
+       .close_fn       = virusfilter_vfs_close,
+       .unlink_fn      = virusfilter_vfs_unlink,
+       .rename_fn      = virusfilter_vfs_rename,
+};
+
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *);
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *ctx)
+{
+       NTSTATUS status;
+
+       status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+                                 "virusfilter",
+                                 &vfs_virusfilter_fns);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       virusfilter_debug_class = debug_add_class("virusfilter");
+       if (virusfilter_debug_class == -1) {
+               virusfilter_debug_class = DBGC_VFS;
+               DBG_ERR("Couldn't register custom debugging class!\n");
+       } else {
+               DBG_DEBUG("Debug class number: %d\n", virusfilter_debug_class);
+       }
+
+       DBG_INFO("registered\n");
+
+       return status;
+}
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
new file mode 100644 (file)
index 0000000..468883f
--- /dev/null
@@ -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 (file)
index 0000000..628e0ae
--- /dev/null
@@ -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 (file)
index 0000000..69754aa
--- /dev/null
@@ -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 */
index 079302cd584c8526a963741314c087b34e7a98e4..f417947737601f3be652e9529f3d44f7218fdfbb 100644 (file)
@@ -17,6 +17,11 @@ bld.SAMBA3_LIBRARY('non_posix_acls',
                    deps='samba-util vfs',
                    private_library=True)
 
+bld.SAMBA3_SUBSYSTEM('VFS_VIRUSFILTER_UTILS',
+                   source='vfs_virusfilter_utils.c',
+                   deps='strv',
+                   enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter')))
+
 bld.SAMBA3_SUBSYSTEM('VFS_AIXACL_UTIL',
                     source='vfs_aixacl_util.c',
                     enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl2')))
@@ -505,6 +510,14 @@ bld.SAMBA3_MODULE('vfs_snapper',
                  internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_snapper'),
                  enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_snapper'))
 
+bld.SAMBA3_MODULE('vfs_virusfilter',
+                 subsystem='vfs',
+                 source='vfs_virusfilter.c',
+                 deps='samba-util VFS_VIRUSFILTER_UTILS',
+                 init_function='',
+                 internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'),
+                 enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter'))
+
 bld.SAMBA3_MODULE('vfs_vxfs',
                  subsystem='vfs',
                  source='lib_vxfs.c vfs_vxfs.c',
index 8751833b221495b047c935c017fb33b8551ec25e..6d2d94bae872c7bbccbc70505885697ae6c76c66 100644 (file)
@@ -1669,7 +1669,7 @@ main() {
                                       vfs_preopen vfs_catia
                                       vfs_media_harmony vfs_unityed_media vfs_fruit vfs_shell_snap
                                       vfs_commit vfs_worm vfs_crossrename vfs_linux_xfs_sgid
-                                      vfs_time_audit vfs_offline
+                                      vfs_time_audit vfs_offline vfs_virusfilter
                                   '''))
     default_shared_modules.extend(TO_LIST('auth_script idmap_tdb2 idmap_script'))
     # these have broken dependencies