[PATCH] Samba VirusFilter (version 12)
Trever L. Adams
trever at middleearth.sapphiresunday.org
Tue Jan 23 13:40:35 UTC 2018
On 01/22/2018 03:34 PM, Jeremy Allison wrote:
> On Fri, Jan 12, 2018 at 06:45:42PM -0700, Trever L. Adams wrote:
>> Opinion: vfs_streams_depot should be one of those that transforms the
>> name since it changes the file name the system is accessing. This would
>> fix the problem I mentioned earlier. It seems I had a patch for this,
>> but cannot find it. By chance do you have a copy of such a thing as part
>> of working with me? It seems like it modified the open function and
>> added a flag and changed is_ntfs_stream_smb_fname to check for that
>> flag. Or should it have something like catia_realpath(...) and
>> virusfilter do a realpath call? (I am sorry, still new to the VFS
>> interface and trying to figure everything out.
>>
>> As for the distributed file system, if it is mounted as an actual fs by
>> the kernel, it can be scanned. Do these modules do this? Or are they
>> hidden from the kernel view some how?
>>
>> Ok, a possible solution:
>>
>> Rename action currently just blindly renames. It does no checks. This
>> was by design. It may be incorrect, I wrote the code and stand open for
>> criticism of it. I figured since the user can see the files and may
>> notice the rename, things aren't all that bad.
>>
>> Quarantine is not viewable by the user (not normally anyway). There
>> should be some way of preserving multiple copies. For about two weeks
>> now, I have been wondering if they should have a time stamp (i.e.
>> 2018-01-12-18:29:57.1234) appended to the name instead of the mkstemp
>> format. The problem is how do we check to see if the underlying name
>> exists? Is such a step necessary? What resolution provides a reasonable
>> enough granularity (i.e. seconds, milliseconds, etc. I figure at least
>> seconds)?
> OK, here is a new version that has been modified to fill README.Coding
> standards and gets around the stackable problem by quarantining detected
> failures to a filename with a 16-byte randomly generated string appended
> to it (generated by generate_random_str()). As these are problem files
> that the user doesn't have access to anyway, I think this is an acceptable
> solution.
>
> Trever can you test this patchset to make sure it's all OK, and once
> that's done, Ralph if you could review it, then I think (fingers crossed :-)
> we're finally good to merge into master !
>
> Cheers,
>
> Jeremy.
Thank you for these wonderful clean ups. I agree with the change on the
random string. I did find two problems (shown as deltas between your
patch set and mine). The first is a coding style and a failure:
diff --git a/source3/modules/vfs_virusfilter.c
b/source3/modules/vfs_virusfilter.c
new file mode 100644
-index 00000000000..eb8ac26665f
+index 00000000000..8703f2d303b
--- /dev/null
+++ b/source3/modules/vfs_virusfilter.c
@@ -0,0 +1,1454 @@
@@ -1132,7 +1132,7 @@
+ * and becoming root over and over.
+ */
+ if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
-+ bool ok = false;
++ bool ok = true;
+ bool dir_exists;
+
+ /*
@@ -1149,7 +1149,7 @@
+ config->quarantine_dir);
+ }
+ unbecome_root();
-+ if (ok == false) {
++ if (!ok) {
+ DBG_ERR("Creating quarantine directory %s "
+ "failed with %s\n",
+ config->quarantine_dir,
If it is default to false, the module will never load if the directory
already exists. This is near the end of virusfilter_vfs_connect.
The second is a coding style that Jim caught that I had fixed locally
that didn't make it into the last version. My old version made use of
ok. Based on changes you made, I figured it was okay to simply return
the returned value.
diff --git a/source3/modules/vfs_virusfilter_utils.c
b/source3/modules/vfs_virusfilter_utils.c
new file mode 100644
-index 00000000000..2b98431f676
+index 00000000000..628e0aef99a
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.c
-@@ -0,0 +1,1031 @@
+@@ -0,0 +1,1025 @@
+/*
+ Samba-VirusFilter VFS modules
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
@@ -2713,13 +2713,7 @@
+ iov.iov_base = discard_const_p(void, data);
+ iov.iov_len = data_size;
+
-+ switch (write_data_iov_timeout(io_h->stream, &iov, 1,
-+ io_h->io_timeout)) {
-+ case false:
-+ return false;
-+ default:
-+ return true;
-+ }
++ return write_data_iov_timeout(io_h->stream, &iov, 1,
io_h->io_timeout);
+}
+
+bool virusfilter_io_writel(
If these are the right solution (and they do work here), then I
recommend my attached patch set labeled as version 14.
Thank you all so much for making this module what it is and getting it
merged in.
Trever
-------------- next part --------------
From e6d3b8bbf34fb4f2d3be8043ede7f5cb95fa25ac Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:37:19 -0600
Subject: [PATCH 1/5] Samba-VirusFilter: memcache changes.
Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
lib/util/memcache.c | 1 +
lib/util/memcache.h | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/util/memcache.c b/lib/util/memcache.c
index acd663cd4e7..819ba44b443 100644
--- a/lib/util/memcache.c
+++ b/lib/util/memcache.c
@@ -55,6 +55,7 @@ static bool memcache_is_talloc(enum memcache_number n)
case SINGLETON_CACHE_TALLOC:
case SHARE_MODE_LOCK_CACHE:
case GETWD_CACHE:
+ case VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC:
result = true;
break;
default:
diff --git a/lib/util/memcache.h b/lib/util/memcache.h
index b87746bc928..670cbd6821f 100644
--- a/lib/util/memcache.h
+++ b/lib/util/memcache.h
@@ -44,7 +44,8 @@ enum memcache_number {
SINGLETON_CACHE_TALLOC, /* talloc */
SINGLETON_CACHE,
SMB1_SEARCH_OFFSET_MAP,
- SHARE_MODE_LOCK_CACHE /* talloc */
+ SHARE_MODE_LOCK_CACHE, /* talloc */
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC /* talloc */
};
/*
--
2.14.3
From 539c314dfad3ff8fed2ffe8299bbf5f5935497e4 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:34:53 -0600
Subject: [PATCH 2/5] Samba-VirusFilter: common headers and sources.
Samba-VirusFilter Contributors:
SATOH Fumiyasu @ OSS Technology Corp., Japan
Module creator/maintainer
Luke Dixon luke.dixon at zynstra.com
Samba 4 support
Trever L. Adams
Documentation
Code contributions
Samba-master merge work
With many thanks to the Samba Team.
Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas at osstech.co.jp>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
docs-xml/manpages/vfs_virusfilter.8.xml | 336 +++++
docs-xml/wscript_build | 1 +
.../scripts/vfs/virusfilter/virusfilter-notify.ksh | 284 ++++
source3/modules/vfs_virusfilter.c | 1454 ++++++++++++++++++++
source3/modules/vfs_virusfilter_common.h | 149 ++
source3/modules/vfs_virusfilter_utils.c | 1025 ++++++++++++++
source3/modules/vfs_virusfilter_utils.h | 177 +++
source3/modules/wscript_build | 13 +
source3/wscript | 2 +-
9 files changed, 3440 insertions(+), 1 deletion(-)
create mode 100644 docs-xml/manpages/vfs_virusfilter.8.xml
create mode 100644 examples/scripts/vfs/virusfilter/virusfilter-notify.ksh
create mode 100644 source3/modules/vfs_virusfilter.c
create mode 100644 source3/modules/vfs_virusfilter_common.h
create mode 100644 source3/modules/vfs_virusfilter_utils.c
create mode 100644 source3/modules/vfs_virusfilter_utils.h
diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
new file mode 100644
index 00000000000..eb6112e3827
--- /dev/null
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//Samba-Team//DTD DocBook V4.2-Based Variant V1.0//EN" "http://www.samba.org/samba/DTD/samba-doc">
+<refentry id="vfs_virusfilter.8">
+
+<refmeta>
+ <refentrytitle>vfs_virusfilter</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="source">Samba</refmiscinfo>
+ <refmiscinfo class="manual">System Administration tools</refmiscinfo>
+ <refmiscinfo class="version">4.8</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+ <refname>vfs_virusfilter</refname>
+ <refpurpose>On access virus scanner</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+ <cmdsynopsis>
+ <command>vfs objects = virusfilter</command>
+ </cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>This is a set of various Samba VFS modules to scan and filter
+ virus files on Samba file services with an anti-virus scanner.</para>
+
+ <para>This module is stackable.</para>
+
+</refsect1>
+
+<refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>virusfilter:scanner</term>
+ <listitem>
+ <para>The antivirus scan-engine.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:socket path = PATH</term>
+ <listitem>
+ <para>Path of local socket for the virus scanner.
+ </para>
+ <para>If this option is not set, the default path depends on the
+ configured AV scanning engine.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:connect timeout = 30000</term>
+ <listitem>
+ <para>Controls how long to wait on connecting to the virus
+ scanning process before timing out. Value is in milliseconds.
+ </para>
+ <para>If this option is not set, the default is 30000.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:io timeout = 60000</term>
+ <listitem>
+ <para>Controls how long to wait on communications with the virus
+ scanning process before timing out. Value is in milliseconds.
+ </para>
+ <para>If this option is not set, the default is 60000.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:scan on open = yes</term>
+ <listitem>
+ <para>This option controls whether files are scanned on open.
+ </para>
+ <para>If this option is not set, the default is yes.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:scan on close = no</term>
+ <listitem>
+ <para>This option controls whether files are scanned on close.
+ </para>
+ <para>If this option is not set, the default is no.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:max file size = 100000000</term>
+ <listitem>
+ <para>This is the largest sized file, in bytes, which will be scanned.
+ </para>
+ <para>If this option is not set, the default is 100MB.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:min file size = 10</term>
+ <listitem>
+ <para>This is the smallest sized file, in bytes, which will be scanned.
+ </para>
+ <para>If this option is not set, the default is 10.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:infected file action = nothing</term>
+ <listitem>
+ <para>What to do with an infected file. The options are
+ nothing, quarantine, rename, delete.</para>
+ <para>If this option is not set, the default is nothing.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:infected file errno on open = EACCES</term>
+ <listitem>
+ <para>What errno to return on open if the file is infected.
+ </para>
+ <para>If this option is not set, the default is EACCES.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:infected file errno on close = 0</term>
+ <listitem>
+ <para>What errno to return on close if the file is infected.
+ </para>
+ <para>If this option is not set, the default is 0.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:quarantine directory = PATH</term>
+ <listitem>
+ <para>Where to move infected files. This path must be an
+ absolute path.</para>
+ <para>If this option is not set, the default is ".quarantine"
+ relative to the share path. </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:quarantine prefix = virusfilter.</term>
+ <listitem>
+ <para>Prefix for quarantined files.</para>
+ <para>If this option is not set, the default is "virusfilter.".</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:quarantine suffix = .infected</term>
+ <listitem>
+ <para>Suffix for quarantined files.
+ This option is only used if keep name is true. Otherwise it is ignored.</para>
+ <para>If this option is not set, the default is ".infected".</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:rename prefix = virusfilter.</term>
+ <listitem>
+ <para>Prefix for infected files.</para>
+ <para>If this option is not set, the default is "virusfilter.".</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:rename suffix = .infected</term>
+ <listitem>
+ <para>Suffix for infected files.</para>
+ <para>If this option is not set, the default is ".infected".</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:quarantine keep tree = yes</term>
+ <listitem>
+ <para>If keep tree is set, the directory structure relative
+ to the share is maintained in the quarantine directory.
+ </para>
+ <para>If this option is not set, the default is yes.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:quarantine keep name = yes</term>
+ <listitem>
+ <para>Should the file name be left unmodified other than adding a suffix
+ and/or prefix and a random suffix name as defined in virusfilter:rename prefix
+ and virusfilter:rename suffix.</para>
+ <para>If this option is not set, the default is yes.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:infected file command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --cc "%U at example.com" --from samba at example.com --subject-prefix "Samba: Infected File: "</term>
+ <listitem>
+ <para>External command to run on an infected file is found.</para>
+ <para>If this option is not set, the default is none.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:scan archive = true</term>
+ <listitem>
+ <para>This defines whether or not to scan archives.</para>
+ <para>Sophos supports this and defaults to false.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:max nested scan archive = 1</term>
+ <listitem>
+ <para>This defines the maximum depth to search nested archives.</para>
+ <para>The Sophos module supports this and defaults to 1.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:scan error command = @SAMBA_DATADIR@/bin/virusfilter-notify --mail-to virusmaster at example.com --from samba at example.com --subject-prefix "Samba: Scan Error: "</term>
+ <listitem>
+ <para>External command to run on scan error.</para>
+ <para>If this option is not set, the default is none.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:exclude files = empty</term>
+ <listitem>
+ <para>Files to exclude from scanning.</para>
+ <para>If this option is not set, the default is empty.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:block access on error = false</term>
+ <listitem>
+ <para>Controls whether or not access should be blocked on
+ a scanning error.</para>
+ <para>If this option is not set, the default is false.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:scan error errno on open = EACCES</term>
+ <listitem>
+ <para>What errno to return on open if there is an error in
+ scanning the file and block access on error is true.
+ </para>
+ <para>If this option is not set, the default is EACCES.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:scan error errno on close = 0</term>
+ <listitem>
+ <para>What errno to return on close if there is an error in
+ scanning the file and block access on error is true.
+ </para>
+ <para>If this option is not set, the default is 0.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:cache entry limit = 100</term>
+ <listitem>
+ <para>The maximum number of entries in the scanning results
+ cache. Due to how Samba's memcache works, this is approximate.</para>
+ <para>If this option is not set, the default is 100.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:cache time limit = 10</term>
+ <listitem>
+ <para>The maximum number of seconds that a scanning result
+ will stay in the results cache. -1 disables the limit.
+ 0 disables caching.</para>
+ <para>If this option is not set, the default is 10.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:quarantine directory mode = 0755</term>
+ <listitem>
+ <para>This is the octet mode for the quarantine directory and
+ its sub-directories as they are created.</para>
+ <para>If this option is not set, the default is 0755 or
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH |
+ S_IXOTH.</para>
+ <para>Permissions must be such that all users can read and
+ search. I.E. don't mess with this unless you really know what
+ you are doing.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+</refsect1>
+
+<refsect1>
+ <title>NOTES</title>
+
+ <para>This module can scan other than default streams, if the
+ alternative datastreams are each backed as separate files, such as with
+ the vfs module streams_depot.</para>
+
+ <para>For proper operation the streams support module must be before
+ the virusfilter module in your vfs objects list (i.e. streams_depot
+ must be called before virusfilter module).</para>
+
+ <para>This module is intended for security in depth by providing
+ virus scanning capability on the server. It is not intended to be used
+ in lieu of proper client based security. Other modules for security may
+ exist and may be desirable for security in depth on the server.</para>
+</refsect1>
+
+<refsect1>
+ <title>AUTHOR</title>
+
+ <para>The original Samba software and related utilities
+ were created by Andrew Tridgell. Samba is now developed
+ by the Samba Team as an Open Source project similar
+ to the way the Linux kernel is developed.</para>
+
+</refsect1>
+
+</refentry>
diff --git a/docs-xml/wscript_build b/docs-xml/wscript_build
index 3ff1ade6976..f30786c3a8b 100644
--- a/docs-xml/wscript_build
+++ b/docs-xml/wscript_build
@@ -88,6 +88,7 @@ manpages='''
manpages/vfs_time_audit.8
manpages/vfs_tsmsm.8
manpages/vfs_unityed_media.8
+ manpages/vfs_virusfilter.8
manpages/vfs_worm.8
manpages/vfs_xattr_tdb.8
manpages/vfstest.1
diff --git a/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh b/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh
new file mode 100644
index 00000000000..a07b9148e83
--- /dev/null
+++ b/examples/scripts/vfs/virusfilter/virusfilter-notify.ksh
@@ -0,0 +1,284 @@
+#!/bin/ksh
+##
+## Samba-VirusFilter VFS modules
+## Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+##
+
+set -u
+
+pdie() { echo "$0: ERROR: ${1-}" 1>&2; exit "${2-1}"; }
+
+## ======================================================================
+
+sendmail="${VIRUSFILTER_NOTIFY_SENDMAIL_COMMAND:-/usr/sbin/sendmail}"
+sendmail_opts="${VIRUSFILTER_NOTIFY_SENDMAIL_OPTIONS:-}"
+
+smbclient="${VIRUSFILTER_NOTIFY_SMBCLIENT_COMMAND:- at SAMBA_BINDIR@/smbclient}"
+smbclient_opts="${VIRUSFILTER_NOTIFY_SMBCLIENT_OPTIONS:-}"
+
+## ======================================================================
+
+if [ -n "${VIRUSFILTER_RESULT_IS_CACHE-}" ]; then
+ ## Result is cache. Ignore!
+ exit 0
+fi
+
+if [ ! -t 1 ] && [ -z "${VIRUSFILTER_NOTIFY_BG-}" ]; then
+ export VIRUSFILTER_NOTIFY_BG=1
+ "$0" ${1+"$@"} </dev/null >/dev/null &
+ exit 0
+fi
+
+## ----------------------------------------------------------------------
+
+if [ -n "${VIRUSFILTER_INFECTED_FILE_ACTION-}" ]; then
+ report="$VIRUSFILTER_INFECTED_FILE_REPORT"
+else
+ report="$VIRUSFILTER_SCAN_ERROR_REPORT"
+fi
+
+if [ X"$VIRUSFILTER_SERVER_NAME" != X"$VIRUSFILTER_SERVER_IP" ]; then
+ server_name="$VIRUSFILTER_SERVER_NAME"
+else
+ server_name="$VIRUSFILTER_SERVER_NETBIOS_NAME"
+fi
+
+if [ X"$VIRUSFILTER_CLIENT_NAME" != X"$VIRUSFILTER_CLIENT_IP" ]; then
+ client_name="$VIRUSFILTER_CLIENT_NAME"
+else
+ client_name="$VIRUSFILTER_CLIENT_NETBIOS_NAME"
+fi
+
+mail_to=""
+winpopup_to=""
+subject_prefix=""
+sender=""
+from=""
+cc=""
+bcc=""
+content_type="text/plain"
+content_encoding="UTF-8"
+
+cmd_usage="Usage: $0 [OPTIONS]
+
+Options:
+ --mail-to ADDRESS
+ Send a notice message to this e-mail address(es)
+ --winpopup-to NAME
+ Send a \"WinPopup\" message to this NetBIOS name
+ --sender ADDRESS
+ Envelope sender address for mail
+ --from ADDRESS
+ From: e-mail address for mail
+ --cc ADDRESS
+ Cc: e-mail address(es) for mail
+ --bcc ADDRESS
+ Bcc: e-mail address(es) for mail
+ --subject-prefix PREFIX
+ Subject: prefix string for mail
+ --content-type TYPE
+ --content-encoding ENCODING
+ Content-Type: TYPE; charset=\"ENCODING\" for mail [$content_type; charset=\"$content_encoding\"]
+ --header-file FILE
+ Prepend the content of FILE to the message
+ --footer-file FILE
+ Append the content of FILE to the message
+"
+
+## ----------------------------------------------------------------------
+
+getopts_want_arg()
+{
+ if [ "$#" -lt 2 ]; then
+ pdie "Option requires an argument: $1"
+ fi
+ if [ "$#" -ge 3 ]; then
+ if expr x"$2" : x"$3\$" >/dev/null; then
+ : OK
+ else
+ pdie "Invalid value for option: $1 $2"
+ fi
+ fi
+}
+
+while [ "$#" -gt 0 ]; do
+ OPT="$1"; shift
+ case "$OPT" in
+ --help)
+ echo "$cmd_usage"
+ exit 0
+ ;;
+ --mail-to)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ mail_to="${mail_to:+$mail_to, }$1"; shift
+ ;;
+ --winpopup-to)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ winpopup_to="$1"; shift
+ ;;
+ --sender)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ sender="$1"; shift
+ ;;
+ --from)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ from="$1"; shift
+ ;;
+ --cc)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ cc="${cc:+$cc, }$1"; shift
+ ;;
+ --bcc)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ bcc="${bcc:+$bcc, }$1"; shift
+ ;;
+ --subject-prefix)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ subject_prefix="$1"; shift
+ ;;
+ --content-type)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ content_type="$1"; shift
+ ;;
+ --content-encoding)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ content_encoding="$1"; shift
+ ;;
+ --header-file)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ header_file="$1"; shift
+ ;;
+ --footer-file)
+ getopts_want_arg "$OPT" ${1+"$1"}
+ footer_file="$1"; shift
+ ;;
+ --)
+ break
+ ;;
+ -*)
+ pdie "Invalid option: $OPT"
+ ;;
+ *)
+ set -- "$OPT" ${1+"$@"}
+ break
+ ;;
+ esac
+done
+
+[ -z "$sender" ] && sender="$from"
+subject="$subject_prefix$report"
+
+## ======================================================================
+
+msg_header="\
+Subject: $subject
+Content-Type: $content_type; charset=$content_encoding
+X-VIRUSFILTER-Version: $VIRUSFILTER_VERSION
+X-VIRUSFILTER-Module-Name: $VIRUSFILTER_MODULE_NAME
+"
+
+if [ -n "${VIRUSFILTER_MODULE_VERSION-}" ]; then
+ msg_header="${msg_header}\
+X-VIRUSFILTER-Module-Version: $VIRUSFILTER_MODULE_VERSION
+"
+fi
+
+if [ -n "${from-}" ]; then
+ msg_header="${msg_header}\
+From: $from
+"
+fi
+
+if [ -n "${mail_to-}" ]; then
+ msg_header="${msg_header}\
+To: $mail_to
+"
+fi
+
+if [ -n "${cc-}" ]; then
+ msg_header="${msg_header}\
+Cc: $cc
+"
+fi
+
+if [ -n "${bcc-}" ]; then
+ msg_header="${msg_header}\
+Bcc: $bcc
+"
+fi
+
+## ----------------------------------------------------------------------
+
+msg_body=""
+
+if [ -n "${header_file-}" ] && [ -f "$header_file" ]; then
+ msg_body="${msg_body}\
+`cat "$header_file"`
+"
+fi
+
+msg_body="${msg_body}\
+Server: $server_name ($VIRUSFILTER_SERVER_IP)
+Server PID: $VIRUSFILTER_SERVER_PID
+Service name: $VIRUSFILTER_SERVICE_NAME
+Service path: $VIRUSFILTER_SERVICE_PATH
+Client: $client_name ($VIRUSFILTER_CLIENT_IP)
+User: $VIRUSFILTER_USER_DOMAIN\\$VIRUSFILTER_USER_NAME
+"
+
+if [ -n "${VIRUSFILTER_INFECTED_FILE_ACTION-}" ]; then
+ msg_body="${msg_body}\
+Infected file report: $VIRUSFILTER_INFECTED_FILE_REPORT
+"
+ msg_body="${msg_body}\
+Infected file path: $VIRUSFILTER_SERVICE_PATH/$VIRUSFILTER_INFECTED_SERVICE_FILE_PATH
+"
+ msg_body="${msg_body}\
+Infected file action: $VIRUSFILTER_INFECTED_FILE_ACTION
+"
+else
+ msg_body="${msg_body}\
+Scan error report: $VIRUSFILTER_SCAN_ERROR_REPORT
+Scan error file path: $VIRUSFILTER_SERVICE_PATH/$VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH
+"
+fi
+
+if [ -n "${VIRUSFILTER_QUARANTINED_FILE_PATH-}" ]; then
+ msg_body="${msg_body}\
+Quarantined/Renamed file path: ${VIRUSFILTER_QUARANTINED_FILE_PATH-}
+"
+fi
+
+if [ -n "${footer_file-}" ] && [ -f "$footer_file" ]; then
+ msg_body="${msg_body}\
+`cat "$footer_file"`
+"
+fi
+
+## ======================================================================
+
+if [ -n "$mail_to" ]; then
+ (echo "$msg_header"; echo "$msg_body") \
+ |"$sendmail" -t -i ${sender:+-f "$sender"} $sendmail_opts
+fi
+
+if [ -n "$winpopup_to" ]; then
+ echo "$msg_body" \
+ |"$smbclient" -M "$winpopup_to" -U% $smbclient_opts \
+ >/dev/null
+fi
+
+exit 0
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
new file mode 100644
index 00000000000..8703f2d303b
--- /dev/null
+++ b/source3/modules/vfs_virusfilter.c
@@ -0,0 +1,1454 @@
+/*
+ * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+ * Copyright (C) 2016-2017 Trever L. Adams
+ * Copyright (C) 2017 Ralph Boehme <slow at samba.org>
+ * Copyright (C) 2017 Jeremy Allison <jra at samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/*
+ * Default configuration values
+ * ======================================================================
+ */
+
+#define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX "virusfilter."
+#define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX ".infected"
+#define VIRUSFILTER_DEFAULT_RENAME_PREFIX "virusfilter."
+#define VIRUSFILTER_DEFAULT_RENAME_SUFFIX ".infected"
+
+/* ====================================================================== */
+
+enum virusfilter_scanner_enum {
+ VIRUSFILTER_SCANNER_CLAMAV,
+ VIRUSFILTER_SCANNER_FSAV,
+ VIRUSFILTER_SCANNER_SOPHOS
+};
+
+static const struct enum_list scanner_list[] = {
+ { VIRUSFILTER_SCANNER_CLAMAV, "clamav" },
+ { VIRUSFILTER_SCANNER_FSAV, "fsav" },
+ { VIRUSFILTER_SCANNER_SOPHOS, "sophos" },
+ { -1, NULL }
+};
+
+static const struct enum_list virusfilter_actions[] = {
+ { VIRUSFILTER_ACTION_QUARANTINE, "quarantine" },
+ { VIRUSFILTER_ACTION_RENAME, "rename" },
+ { VIRUSFILTER_ACTION_DELETE, "delete" },
+
+ /* alias for "delete" */
+ { VIRUSFILTER_ACTION_DELETE, "remove" },
+
+ /* alias for "delete" */
+ { VIRUSFILTER_ACTION_DELETE, "unlink" },
+ { VIRUSFILTER_ACTION_DO_NOTHING, "nothing" },
+ { -1, NULL}
+};
+
+static int virusfilter_config_destructor(struct virusfilter_config *config)
+{
+ TALLOC_FREE(config->backend);
+ return 0;
+}
+
+/*
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_directory_exist(
+ struct vfs_handle_struct *handle,
+ const char *dname)
+{
+ int ret = -1;
+ struct smb_filename smb_fname = {
+ .base_name = discard_const_p(char, dname)
+ };
+
+ ret = SMB_VFS_STAT(handle->conn, &smb_fname);
+ if (ret == 0) {
+ return S_ISDIR(smb_fname.st.st_ex_mode);
+ }
+
+ return false;
+}
+
+/**
+ * Create directory tree
+ * @param conn connection
+ * @param dname Directory tree to be created
+ * @return Returns true for success
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_create_dir(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *dname)
+{
+ size_t len = 0;
+ size_t cat_len = 0;
+ char *new_dir = NULL;
+ char *tmp_str = NULL;
+ char *token = NULL;
+ char *tok_str = NULL;
+ bool status = false;
+ bool ok = false;
+ int ret = -1;
+ char *saveptr = NULL;
+
+ tmp_str = talloc_strdup(talloc_tos(), dname);
+ if (tmp_str == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ errno = ENOMEM;
+ goto done;
+ }
+ tok_str = tmp_str;
+
+ len = strlen(dname)+1;
+ new_dir = (char *)talloc_size(talloc_tos(), len + 1);
+ if (new_dir == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ errno = ENOMEM;
+ goto done;
+ }
+ *new_dir = '\0';
+ if (dname[0] == '/') {
+ /* Absolute path. */
+ cat_len = strlcat(new_dir, "/", len + 1);
+ if (cat_len >= len+1) {
+ goto done;
+ }
+ }
+
+ /* Create directory tree if neccessary */
+ for (token = strtok_r(tok_str, "/", &saveptr);
+ token != NULL;
+ token = strtok_r(NULL, "/", &saveptr))
+ {
+ cat_len = strlcat(new_dir, token, len + 1);
+ if (cat_len >= len+1) {
+ goto done;
+ }
+ ok = quarantine_directory_exist(handle, new_dir);
+ if (ok == true) {
+ DBG_DEBUG("quarantine: dir %s already exists\n",
+ new_dir);
+ } else {
+ struct smb_filename *smb_fname = NULL;
+
+ DBG_INFO("quarantine: creating new dir %s\n", new_dir);
+
+ smb_fname = synthetic_smb_fname(talloc_tos(), new_dir,
+ NULL, NULL, 0);
+ if (smb_fname == NULL) {
+ goto done;
+ }
+
+ ret = SMB_VFS_NEXT_MKDIR(handle,
+ smb_fname,
+ config->quarantine_dir_mode);
+ if (ret != 0) {
+ TALLOC_FREE(smb_fname);
+
+ DBG_WARNING("quarantine: mkdir failed for %s "
+ "with error: %s\n", new_dir,
+ strerror(errno));
+ status = false;
+ goto done;
+ }
+ TALLOC_FREE(smb_fname);
+ }
+ cat_len = strlcat(new_dir, "/", len + 1);
+ if (cat_len >= len + 1) {
+ goto done;
+ }
+ }
+
+ status = true;
+done:
+ TALLOC_FREE(tmp_str);
+ TALLOC_FREE(new_dir);
+ return status;
+}
+
+static int virusfilter_vfs_connect(
+ struct vfs_handle_struct *handle,
+ const char *svc,
+ const char *user)
+{
+ int snum = SNUM(handle->conn);
+ struct virusfilter_config *config = NULL;
+ const char *exclude_files = NULL;
+ const char *temp_quarantine_dir_mode = NULL;
+ char *sret = NULL;
+ char *tmp = NULL;
+ enum virusfilter_scanner_enum backend;
+ int connect_timeout = 0;
+ int io_timeout = 0;
+ int ret = -1;
+
+ config = talloc_zero(handle, struct virusfilter_config);
+ if (config == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ return -1;
+ }
+ talloc_set_destructor(config, virusfilter_config_destructor);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config, NULL,
+ struct virusfilter_config, return -1);
+
+ config->scan_request_limit = lp_parm_int(
+ snum, "virusfilter", "scan request limit", 0);
+
+ config->scan_on_open = lp_parm_bool(
+ snum, "virusfilter", "scan on open", true);
+
+ config->scan_on_close = lp_parm_bool(
+ snum, "virusfilter", "scan on close", false);
+
+ config->max_nested_scan_archive = lp_parm_int(
+ snum, "virusfilter", "max nested scan archive", 1);
+
+ config->scan_archive = lp_parm_bool(
+ snum, "virusfilter", "scan archive", false);
+
+ config->scan_mime = lp_parm_bool(
+ snum, "virusfilter", "scan mime", false);
+
+ config->max_file_size = (ssize_t)lp_parm_ulong(
+ snum, "virusfilter", "max file size", 100000000L);
+
+ config->min_file_size = (ssize_t)lp_parm_ulong(
+ snum, "virusfilter", "min file size", 10);
+
+ exclude_files = lp_parm_const_string(
+ snum, "virusfilter", "exclude files", NULL);
+ if (exclude_files != NULL) {
+ set_namearray(&config->exclude_files, exclude_files);
+ }
+
+ config->cache_entry_limit = lp_parm_int(
+ snum, "virusfilter", "cache entry limit", 100);
+
+ config->cache_time_limit = lp_parm_int(
+ snum, "virusfilter", "cache time limit", 10);
+
+ config->infected_file_action = lp_parm_enum(
+ snum, "virusfilter", "infected file action",
+ virusfilter_actions, VIRUSFILTER_ACTION_DO_NOTHING);
+
+ config->infected_file_command = lp_parm_const_string(
+ snum, "virusfilter", "infected file command", NULL);
+
+ config->scan_error_command = lp_parm_const_string(
+ snum, "virusfilter", "scan error command", NULL);
+
+ config->block_access_on_error = lp_parm_bool(
+ snum, "virusfilter", "block access on error", false);
+
+ tmp = talloc_asprintf(config, "%s/.quarantine",
+ handle->conn->connectpath);
+
+ config->quarantine_dir = lp_parm_const_string(
+ snum, "virusfilter", "quarantine directory",
+ tmp ? tmp : "/tmp/.quarantine");
+
+ if (tmp != config->quarantine_dir) {
+ TALLOC_FREE(tmp);
+ }
+
+ temp_quarantine_dir_mode = lp_parm_const_string(
+ snum, "virusfilter", "quarantine directory mode", "0755");
+ if (temp_quarantine_dir_mode != NULL) {
+ sscanf(temp_quarantine_dir_mode, "%o",
+ &config->quarantine_dir_mode);
+ }
+
+ config->quarantine_prefix = lp_parm_const_string(
+ snum, "virusfilter", "quarantine prefix",
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+
+ config->quarantine_suffix = lp_parm_const_string(
+ snum, "virusfilter", "quarantine suffix",
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+
+ /*
+ * Make sure prefixes and suffixes do not contain directory
+ * delimiters
+ */
+ sret = strstr(config->quarantine_prefix, "/");
+ if (sret != NULL) {
+ DBG_ERR("quarantine prefix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->quarantine_prefix,
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+ config->quarantine_prefix =
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX;
+ }
+ sret = strstr(config->quarantine_suffix, "/");
+ if (sret != NULL) {
+ DBG_ERR("quarantine suffix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->quarantine_suffix,
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+ config->quarantine_suffix =
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX;
+ }
+
+ config->quarantine_keep_tree = lp_parm_bool(
+ snum, "virusfilter", "quarantine keep tree", true);
+
+ config->quarantine_keep_name = lp_parm_bool(
+ snum, "virusfilter", "quarantine keep name", true);
+
+ config->rename_prefix = lp_parm_const_string(
+ snum, "virusfilter", "rename prefix",
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+
+ config->rename_suffix = lp_parm_const_string(
+ snum, "virusfilter", "rename suffix",
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+
+ /*
+ * Make sure prefixes and suffixes do not contain directory
+ * delimiters
+ */
+ sret = strstr(config->rename_prefix, "/");
+ if (sret != NULL) {
+ DBG_ERR("rename prefix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->rename_prefix,
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+ config->rename_prefix =
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX;
+ }
+ sret = strstr(config->rename_suffix, "/");
+ if (sret != NULL) {
+ DBG_ERR("rename suffix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->rename_suffix,
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+ config->rename_suffix =
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX;
+ }
+
+ config->infected_open_errno = lp_parm_int(
+ snum, "virusfilter", "infected file errno on open", EACCES);
+
+ config->infected_close_errno = lp_parm_int(
+ snum, "virusfilter", "infected file errno on close", 0);
+
+ config->scan_error_open_errno = lp_parm_int(
+ snum, "virusfilter", "scan error errno on open", EACCES);
+
+ config->scan_error_close_errno = lp_parm_int(
+ snum, "virusfilter", "scan error errno on close", 0);
+
+ config->socket_path = lp_parm_const_string(
+ snum, "virusfilter", "socket path", NULL);
+
+ /* canonicalize socket_path */
+ if (config->socket_path != NULL && config->socket_path[0] != '/') {
+ DBG_ERR("socket path must be an absolute path. "
+ "Using backend default\n");
+ config->socket_path = NULL;
+ }
+ if (config->socket_path != NULL) {
+ canonicalize_absolute_path(handle,
+ config->socket_path);
+ }
+
+ connect_timeout = lp_parm_int(snum, "virusfilter",
+ "connect timeout", 30000);
+
+ io_timeout = lp_parm_int(snum, "virusfilter", "io timeout", 60000);
+
+ config->io_h = virusfilter_io_new(config, connect_timeout, io_timeout);
+ if (config->io_h == NULL) {
+ DBG_ERR("virusfilter_io_new failed");
+ return -1;
+ }
+
+ if (config->cache_entry_limit > 0) {
+ config->cache = virusfilter_cache_new(handle,
+ config->cache_entry_limit,
+ config->cache_time_limit);
+ if (config->cache == NULL) {
+ DBG_ERR("Initializing cache failed: Cache disabled\n");
+ return -1;
+ }
+ }
+
+ /*
+ * Check quarantine directory now to save processing
+ * and becoming root over and over.
+ */
+ if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+ bool ok = true;
+ bool dir_exists;
+
+ /*
+ * Do SMB_VFS_NEXT_MKDIR(config->quarantine_dir)
+ * hierarchy
+ */
+ become_root();
+ dir_exists = quarantine_directory_exist(handle,
+ config->quarantine_dir);
+ if (!dir_exists) {
+ DBG_DEBUG("Creating quarantine directory: %s\n",
+ config->quarantine_dir);
+ ok = quarantine_create_dir(handle, config,
+ config->quarantine_dir);
+ }
+ unbecome_root();
+ if (!ok) {
+ DBG_ERR("Creating quarantine directory %s "
+ "failed with %s\n",
+ config->quarantine_dir,
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ /*
+ * Now that the frontend options are initialized, load the configured
+ * backend.
+ */
+
+ backend = (enum virusfilter_scanner_enum)lp_parm_enum(snum,
+ "virusfilter",
+ "scanner",
+ scanner_list,
+ -1);
+ if (backend == (enum virusfilter_scanner_enum)-1) {
+ DBG_ERR("No AV-Scanner configured, "
+ "please set \"virusfilter:scanner\"\n");
+ return -1;
+ }
+
+ /* This goes away as soon as the next commit adds an actual backend... */
+ if (config->backend == NULL) {
+ DBG_INFO("Not implemented\n");
+ return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+ }
+
+ if (config->backend->fns->connect != NULL) {
+ ret = config->backend->fns->connect(handle, config, svc, user);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+}
+
+static void virusfilter_vfs_disconnect(struct vfs_handle_struct *handle)
+{
+ struct virusfilter_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return);
+
+ if (config->backend->fns->disconnect != NULL) {
+ config->backend->fns->disconnect(handle);
+ }
+
+ free_namearray(config->exclude_files);
+ virusfilter_io_disconnect(config->io_h);
+
+ SMB_VFS_NEXT_DISCONNECT(handle);
+}
+
+static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx,
+ struct virusfilter_config *config,
+ char **env_list)
+{
+ int ret;
+
+ ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
+ VIRUSFILTER_VERSION);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
+ config->backend->name);
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (config->backend->version != 0) {
+ char *version = NULL;
+
+ version = talloc_asprintf(talloc_tos(), "%u",
+ config->backend->version);
+ if (version == NULL) {
+ return -1;
+ }
+ ret = virusfilter_env_set(mem_ctx, env_list,
+ "VIRUSFILTER_MODULE_VERSION",
+ version);
+ TALLOC_FREE(version);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static virusfilter_action virusfilter_do_infected_file_action(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ const char **filepath_newp)
+{
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ connection_struct *conn = handle->conn;
+ char *cwd_fname = fsp->conn->cwd_fname->base_name;
+ char *fname = fsp->fsp_name->base_name;
+ const struct smb_filename *smb_fname = fsp->fsp_name;
+ struct smb_filename *q_smb_fname = NULL;
+ char *q_dir = NULL;
+ char *q_prefix = NULL;
+ char *q_suffix = NULL;
+ char *q_filepath = NULL;
+ char *dir_name = NULL;
+ char *temp_path = NULL;
+ const char *base_name = NULL;
+ char *rand_filename_component = NULL;
+ bool ok = false;
+ int ret = -1;
+ int saved_errno = 0;
+
+ *filepath_newp = NULL;
+
+ switch (config->infected_file_action) {
+ case VIRUSFILTER_ACTION_RENAME:
+ q_prefix = virusfilter_string_sub(mem_ctx, conn,
+ config->rename_prefix);
+ q_suffix = virusfilter_string_sub(mem_ctx, conn,
+ config->rename_suffix);
+ if (q_prefix == NULL || q_suffix == NULL) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ ok = parent_dirname(mem_ctx, fname, &q_dir, &base_name);
+ if (!ok) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (q_dir == NULL) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ q_filepath = talloc_asprintf(talloc_tos(), "%s/%s%s%s", q_dir,
+ q_prefix, base_name, q_suffix);
+
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+
+ q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+ smb_fname->stream_name, NULL,
+ smb_fname->flags);
+ if (q_smb_fname == NULL) {
+ TALLOC_FREE(q_filepath);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ become_root();
+ ret = virusfilter_vfs_next_move(handle, smb_fname,
+ q_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+
+ if (ret == -1) {
+ DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
+ cwd_fname, fname,
+ strerror(saved_errno));
+ errno = saved_errno;
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ *filepath_newp = q_filepath;
+
+ return VIRUSFILTER_ACTION_RENAME;
+
+ case VIRUSFILTER_ACTION_QUARANTINE:
+ q_dir = virusfilter_string_sub(mem_ctx, conn,
+ config->quarantine_dir);
+ q_prefix = virusfilter_string_sub(mem_ctx, conn,
+ config->quarantine_prefix);
+ q_suffix = virusfilter_string_sub(mem_ctx, conn,
+ config->quarantine_suffix);
+ if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
+ DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (config->quarantine_keep_name ||
+ config->quarantine_keep_tree)
+ {
+ ok = parent_dirname(mem_ctx, smb_fname->base_name,
+ &dir_name, &base_name);
+ if (!ok) {
+ DBG_ERR("Quarantine failed: %s/%s: Cannot "
+ "allocate memory\n", cwd_fname, fname);
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ TALLOC_FREE(rand_filename_component);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ if (config->quarantine_keep_tree) {
+ temp_path = talloc_asprintf(mem_ctx,
+ "%s/%s", q_dir, cwd_fname);
+ if (temp_path == NULL) {
+ DBG_ERR("Quarantine failed: %s/%s: "
+ "Cannot allocate memory\n",
+ cwd_fname, fname);
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ TALLOC_FREE(rand_filename_component);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ become_root();
+ ok = quarantine_directory_exist(handle,
+ temp_path);
+ if (ok) {
+ DBG_DEBUG("quarantine: Directory "
+ "already exists\n");
+ TALLOC_FREE(q_dir);
+ q_dir = temp_path;
+ } else {
+ DBG_DEBUG("quarantine: Creating "
+ "directory %s\n", temp_path);
+ ok = quarantine_create_dir(handle,
+ config, temp_path);
+ if (!ok) {
+ DBG_NOTICE("quarantine: Could "
+ "not create directory "
+ "ignoring for %s...\n",
+ smb_fname_str_dbg(
+ smb_fname));
+ TALLOC_FREE(temp_path);
+ } else {
+ TALLOC_FREE(q_dir);
+ q_dir = temp_path;
+ }
+ }
+ unbecome_root();
+ }
+ }
+
+ /* Get a 16 byte + \0 random filename component. */
+ rand_filename_component = generate_random_str(talloc_tos(), 16);
+ if (rand_filename_component == NULL) {
+ DBG_ERR("Quarantine failed: %s/%s: Cannot "
+ "allocate memory\n", cwd_fname, fname);
+ TALLOC_FREE(dir_name);
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+ if (config->quarantine_keep_name) {
+ q_filepath = talloc_asprintf(talloc_tos(),
+ "%s/%s%s%s-%s",
+ q_dir, q_prefix, base_name, q_suffix,
+ rand_filename_component);
+ } else {
+ q_filepath = talloc_asprintf(talloc_tos(),
+ "%s/%s%s", q_dir, q_prefix,
+ rand_filename_component);
+ }
+
+ TALLOC_FREE(dir_name);
+ TALLOC_FREE(q_dir);
+ TALLOC_FREE(q_prefix);
+ TALLOC_FREE(q_suffix);
+ TALLOC_FREE(rand_filename_component);
+
+ if (q_filepath == NULL) {
+ DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ q_smb_fname = synthetic_smb_fname(mem_ctx, q_filepath,
+ smb_fname->stream_name, NULL, smb_fname->flags);
+ if (q_smb_fname == NULL) {
+ TALLOC_FREE(q_filepath);
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ become_root();
+ ret = virusfilter_vfs_next_move(handle, smb_fname,
+ q_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+ if (ret == -1) {
+ DBG_ERR("Quarantine failed: %s/%s: Rename to %s "
+ "failed: %s\n",
+ cwd_fname, fname,
+ q_filepath,
+ strerror(saved_errno));
+ errno = saved_errno;
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ *filepath_newp = q_filepath;
+
+ return VIRUSFILTER_ACTION_QUARANTINE;
+
+ case VIRUSFILTER_ACTION_DELETE:
+ become_root();
+ ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+ if (ret == -1) {
+ DBG_ERR("Delete failed: %s/%s: Unlink failed: %s\n",
+ cwd_fname, fname,
+ strerror(saved_errno));
+ errno = saved_errno;
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+ return VIRUSFILTER_ACTION_DELETE;
+
+ case VIRUSFILTER_ACTION_DO_NOTHING:
+ default:
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+}
+
+static virusfilter_action virusfilter_treat_infected_file(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ const char *report,
+ bool is_cache)
+{
+ connection_struct *conn = handle->conn;
+ char *cwd_fname = fsp->conn->cwd_fname->base_name;
+ char *fname = fsp->fsp_name->base_name;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ int i;
+ virusfilter_action action;
+ const char *action_name = "UNKNOWN";
+ const char *filepath_q = NULL;
+ char *env_list = NULL;
+ char *command = NULL;
+ int command_result;
+ int ret;
+
+ action = virusfilter_do_infected_file_action(handle, config, fsp,
+ &filepath_q);
+ for (i=0; virusfilter_actions[i].name; i++) {
+ if (virusfilter_actions[i].value == action) {
+ action_name = virusfilter_actions[i].name;
+ break;
+ }
+ }
+ DBG_WARNING("Infected file action: %s/%s: %s\n", cwd_fname,
+ fname, action_name);
+
+ if (!config->infected_file_command) {
+ return action;
+ }
+
+ ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+ if (ret == -1) {
+ goto done;
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
+ fname);
+ if (ret == -1) {
+ goto done;
+ }
+ if (report != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_FILE_REPORT",
+ report);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_FILE_ACTION",
+ action_name);
+ if (ret == -1) {
+ goto done;
+ }
+ if (filepath_q != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_QUARANTINED_FILE_PATH",
+ filepath_q);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ if (is_cache) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_RESULT_IS_CACHE", "yes");
+ if (ret == -1) {
+ goto done;
+ }
+ }
+
+ command = virusfilter_string_sub(mem_ctx, conn,
+ config->infected_file_command);
+ if (command == NULL) {
+ DBG_ERR("virusfilter_string_sub failed\n");
+ goto done;
+ }
+
+ DBG_NOTICE("Infected file command line: %s/%s: %s\n", cwd_fname,
+ fname, command);
+
+ command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+ conn, true);
+ if (command_result != 0) {
+ DBG_ERR("Infected file command failed: %d\n", command_result);
+ }
+
+ DBG_DEBUG("Infected file command finished: %d\n", command_result);
+
+done:
+ TALLOC_FREE(env_list);
+ TALLOC_FREE(command);
+
+ return action;
+}
+
+static void virusfilter_treat_scan_error(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ const char *report,
+ bool is_cache)
+{
+ connection_struct *conn = handle->conn;
+ const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ char *env_list = NULL;
+ char *command = NULL;
+ int command_result;
+ int ret;
+
+ if (!config->scan_error_command) {
+ return;
+ }
+ ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+ if (ret == -1) {
+ goto done;
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
+ fname);
+ if (ret == -1) {
+ goto done;
+ }
+ if (report != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_SCAN_ERROR_REPORT",
+ report);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ if (is_cache) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_RESULT_IS_CACHE", "1");
+ if (ret == -1) {
+ goto done;
+ }
+ }
+
+ command = virusfilter_string_sub(mem_ctx, conn,
+ config->scan_error_command);
+ if (command == NULL) {
+ DBG_ERR("virusfilter_string_sub failed\n");
+ goto done;
+ }
+
+ DBG_NOTICE("Scan error command line: %s/%s: %s\n", cwd_fname,
+ fname, command);
+
+ command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+ conn, true);
+ if (command_result != 0) {
+ DBG_ERR("Scan error command failed: %d\n", command_result);
+ }
+
+done:
+ TALLOC_FREE(env_list);
+ TALLOC_FREE(command);
+}
+
+static virusfilter_result virusfilter_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp)
+{
+ virusfilter_result scan_result;
+ char *scan_report = NULL;
+ const char *fname = fsp->fsp_name->base_name;
+ const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+ struct virusfilter_cache_entry *scan_cache_e = NULL;
+ bool is_cache = false;
+ virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING;
+ bool add_scan_cache = true;
+ bool ok = false;
+
+ if (config->cache) {
+ DBG_DEBUG("Searching cache entry: fname: %s\n", fname);
+ scan_cache_e = virusfilter_cache_get(config->cache,
+ cwd_fname, fname);
+ if (scan_cache_e != NULL) {
+ DBG_DEBUG("Cache entry found: cached result: %d\n",
+ scan_cache_e->result);
+ is_cache = true;
+ scan_result = scan_cache_e->result;
+ scan_report = scan_cache_e->report;
+ goto virusfilter_scan_result_eval;
+ }
+ DBG_DEBUG("Cache entry not found\n");
+ }
+
+ if (config->backend->fns->scan_init != NULL) {
+ scan_result = config->backend->fns->scan_init(config);
+ if (scan_result != VIRUSFILTER_RESULT_OK) {
+ scan_result = VIRUSFILTER_RESULT_ERROR;
+ scan_report = talloc_asprintf(
+ talloc_tos(),
+ "Initializing scanner failed");
+ goto virusfilter_scan_result_eval;
+ }
+ }
+
+ scan_result = config->backend->fns->scan(handle, config, fsp,
+ &scan_report);
+
+ if (config->backend->fns->scan_end != NULL) {
+ bool scan_end = true;
+
+ if (config->scan_request_limit > 0) {
+ scan_end = false;
+ config->scan_request_count++;
+ if (config->scan_request_count >=
+ config->scan_request_limit)
+ {
+ scan_end = true;
+ config->scan_request_count = 0;
+ }
+ }
+ if (scan_end) {
+ config->backend->fns->scan_end(config);
+ }
+ }
+
+virusfilter_scan_result_eval:
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ DBG_INFO("Scan result: Clean: %s/%s\n", cwd_fname, fname);
+ break;
+
+ case VIRUSFILTER_RESULT_INFECTED:
+ DBG_ERR("Scan result: Infected: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "infected (memory error on report)");
+ file_action = virusfilter_treat_infected_file(handle,
+ config, fsp, scan_report, is_cache);
+ if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+ add_scan_cache = false;
+ }
+ break;
+
+ case VIRUSFILTER_RESULT_SUSPECTED:
+ if (!config->block_suspected_file) {
+ break;
+ }
+ DBG_ERR("Scan result: Suspected: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "suspected infection (memory error on report)");
+ file_action = virusfilter_treat_infected_file(handle,
+ config, fsp, scan_report, is_cache);
+ if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+ add_scan_cache = false;
+ }
+ break;
+
+ case VIRUSFILTER_RESULT_ERROR:
+ DBG_ERR("Scan result: Error: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "error (memory error on report)");
+ virusfilter_treat_scan_error(handle, config, fsp,
+ scan_report, is_cache);
+ add_scan_cache = false;
+ break;
+
+ default:
+ DBG_ERR("Scan result: Unknown result code %d: %s/%s: %s\n",
+ scan_result, cwd_fname, fname, scan_report ?
+ scan_report : "Unknown (memory error on report)");
+ virusfilter_treat_scan_error(handle, config, fsp,
+ scan_report, is_cache);
+ add_scan_cache = false;
+ break;
+ }
+
+ if (config->cache) {
+ if (!is_cache && add_scan_cache) {
+ DBG_DEBUG("Adding new cache entry: %s, %d\n", fname,
+ scan_result);
+ ok = virusfilter_cache_entry_add(
+ config->cache, cwd_fname, fname,
+ scan_result, scan_report);
+ if (!ok) {
+ DBG_ERR("Cannot create cache entry: "
+ "virusfilter_cache_entry_new failed");
+ goto virusfilter_scan_return;
+ }
+ } else if (is_cache) {
+ virusfilter_cache_entry_free(scan_cache_e);
+ }
+ }
+
+virusfilter_scan_return:
+ return scan_result;
+}
+
+static int virusfilter_vfs_open(
+ struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct virusfilter_config *config;
+ const char *cwd_fname = fsp->conn->cwd_fname->base_name;
+ virusfilter_result scan_result;
+ const char *fname = fsp->fsp_name->base_name;
+ char *dir_name = NULL;
+ const char *base_name = NULL;
+ int scan_errno = 0;
+ size_t test_prefix;
+ size_t test_suffix;
+ int rename_trap_count = 0;
+ int ret;
+ bool ok1, ok2;
+ char *sret = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ test_prefix = strlen(config->rename_prefix);
+ test_suffix = strlen(config->rename_suffix);
+ if (test_prefix > 0) {
+ rename_trap_count++;
+ }
+ if (test_suffix > 0) {
+ rename_trap_count++;
+ }
+
+ ok1 = is_ntfs_stream_smb_fname(smb_fname);
+ ok2 = is_ntfs_default_stream_smb_fname(smb_fname);
+ if (ok1 && !ok2) {
+ DBG_INFO("Not scanned: only file backed streams can be scanned:"
+ " %s/%s\n", cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (!config->scan_on_open) {
+ DBG_INFO("Not scanned: scan on open is disabled: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (flags & O_TRUNC) {
+ DBG_INFO("Not scanned: Open flags have O_TRUNC: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ if (ret != 0) {
+
+ /*
+ * Do not return immediately if !(flags & O_CREAT) &&
+ * errno != ENOENT.
+ * Do not do this here or anywhere else. The module is
+ * stackable and there may be modules below, such as audit
+ * modules, which should be handled.
+ */
+ goto virusfilter_vfs_open_next;
+ }
+ ret = S_ISREG(smb_fname->st.st_ex_mode);
+ if (ret == 0) {
+ DBG_INFO("Not scanned: Directory or special file: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+ if (config->max_file_size > 0 &&
+ smb_fname->st.st_ex_size > config->max_file_size)
+ {
+ DBG_INFO("Not scanned: file size > max file size: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+ if (config->min_file_size > 0 &&
+ smb_fname->st.st_ex_size < config->min_file_size)
+ {
+ DBG_INFO("Not scanned: file size < min file size: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ ok1 = is_in_path(fname, config->exclude_files, false);
+ if (config->exclude_files && ok1)
+ {
+ DBG_INFO("Not scanned: exclude files: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+ sret = strstr_m(fname, config->quarantine_dir);
+ if (sret != NULL) {
+ scan_errno = config->infected_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+ }
+
+ if (test_prefix > 0 || test_suffix > 0) {
+ ok1 = parent_dirname(mem_ctx, fname, &dir_name, &base_name);
+ if (ok1)
+ {
+ if (test_prefix > 0) {
+ ret = strncmp(base_name,
+ config->rename_prefix, test_prefix);
+ if (ret != 0) {
+ test_prefix = 0;
+ }
+ }
+ if (test_suffix > 0) {
+ ret = strcmp(base_name + (strlen(base_name)
+ - test_suffix),
+ config->rename_suffix);
+ if (ret != 0) {
+ test_suffix = 0;
+ }
+ }
+
+ TALLOC_FREE(dir_name);
+
+ if ((rename_trap_count == 2 && test_prefix &&
+ test_suffix) || (rename_trap_count == 1 &&
+ (test_prefix || test_suffix)))
+ {
+ scan_errno =
+ config->infected_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+ }
+ }
+
+ scan_result = virusfilter_scan(handle, config, fsp);
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ break;
+ case VIRUSFILTER_RESULT_INFECTED:
+ scan_errno = config->infected_open_errno;
+ goto virusfilter_vfs_open_fail;
+ case VIRUSFILTER_RESULT_ERROR:
+ if (config->block_access_on_error) {
+ DBG_INFO("Block access\n");
+ scan_errno = config->scan_error_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+ break;
+ default:
+ scan_errno = config->scan_error_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+
+virusfilter_vfs_open_next:
+ return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
+
+virusfilter_vfs_open_fail:
+ errno = (scan_errno != 0) ? scan_errno : EACCES;
+ return -1;
+}
+
+static int virusfilter_vfs_close(
+ struct vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ /*
+ * The name of this variable is for consistency. If API changes to
+ * match _open change to cwd_fname as in virusfilter_vfs_open.
+ */
+ const char *cwd_fname = handle->conn->connectpath;
+
+ struct virusfilter_config *config = NULL;
+ char *fname = fsp->fsp_name->base_name = NULL;
+ int close_result = -1;
+ int close_errno = 0;
+ virusfilter_result scan_result;
+ int scan_errno = 0;
+ bool ok1, ok2;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ /*
+ * Must close after scan? It appears not as the scanners are not
+ * internal and other modules such as greyhole seem to do
+ * SMB_VFS_NEXT_* functions before processing.
+ */
+ close_result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+ if (close_result == -1) {
+ close_errno = errno;
+ }
+
+ /*
+ * Return immediately if close_result == -1, and close_errno == EBADF.
+ * If close failed, file likely doesn't exist, do not try to scan.
+ */
+ if (close_result == -1 && close_errno == EBADF) {
+ if (fsp->modified) {
+ DBG_DEBUG("Removing cache entry (if existent): "
+ "fname: %s\n", fname);
+ virusfilter_cache_remove(config->cache,
+ cwd_fname, fname);
+ }
+ goto virusfilter_vfs_close_fail;
+ }
+
+ if (fsp->is_directory) {
+ DBG_INFO("Not scanned: Directory: %s/%s\n", cwd_fname,
+ fname);
+ return close_result;
+ }
+
+ ok1 = is_ntfs_stream_smb_fname(fsp->fsp_name);
+ ok2 = is_ntfs_default_stream_smb_fname(fsp->fsp_name);
+ if (ok1 && !ok2) {
+ if (config->scan_on_open && fsp->modified) {
+ if (config->cache) {
+ DBG_DEBUG("Removing cache entry (if existent)"
+ ": fname: %s\n", fname);
+ virusfilter_cache_remove(
+ config->cache,
+ cwd_fname, fname);
+ }
+ }
+ DBG_INFO("Not scanned: only file backed streams can be scanned:"
+ " %s/%s\n", cwd_fname, fname);
+ return close_result;
+ }
+
+ if (!config->scan_on_close) {
+ if (config->scan_on_open && fsp->modified) {
+ if (config->cache) {
+ DBG_DEBUG("Removing cache entry (if existent)"
+ ": fname: %s\n", fname);
+ virusfilter_cache_remove(
+ config->cache,
+ cwd_fname, fname);
+ }
+ }
+ DBG_INFO("Not scanned: scan on close is disabled: %s/%s\n",
+ cwd_fname, fname);
+ return close_result;
+ }
+
+ if (!fsp->modified) {
+ DBG_NOTICE("Not scanned: File not modified: %s/%s\n",
+ cwd_fname, fname);
+
+ return close_result;
+ }
+
+ if (config->exclude_files && is_in_path(fname,
+ config->exclude_files, false))
+ {
+ DBG_INFO("Not scanned: exclude files: %s/%s\n",
+ cwd_fname, fname);
+ return close_result;
+ }
+
+ scan_result = virusfilter_scan(handle, config, fsp);
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ break;
+ case VIRUSFILTER_RESULT_INFECTED:
+ scan_errno = config->infected_close_errno;
+ goto virusfilter_vfs_close_fail;
+ case VIRUSFILTER_RESULT_ERROR:
+ if (config->block_access_on_error) {
+ DBG_INFO("Block access\n");
+ scan_errno = config->scan_error_close_errno;
+ goto virusfilter_vfs_close_fail;
+ }
+ break;
+ default:
+ scan_errno = config->scan_error_close_errno;
+ goto virusfilter_vfs_close_fail;
+ }
+
+ if (close_errno != 0) {
+ errno = close_errno;
+ }
+
+ return close_result;
+
+virusfilter_vfs_close_fail:
+
+ errno = (scan_errno != 0) ? scan_errno : close_errno;
+
+ return close_result;
+}
+
+static int virusfilter_vfs_unlink(
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int ret = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+ struct virusfilter_config *config = NULL;
+ char *fname = NULL;
+ char *cwd_fname = handle->conn->cwd_fname->base_name;
+
+ if (ret != 0 && errno != ENOENT) {
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ if (config->cache == NULL) {
+ return 0;
+ }
+
+ fname = smb_fname->base_name;
+
+ DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname);
+ virusfilter_cache_remove(config->cache, cwd_fname, fname);
+
+ return 0;
+}
+
+static int virusfilter_vfs_rename(
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst)
+{
+ int ret = SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
+ struct virusfilter_config *config = NULL;
+ char *fname = NULL;
+ char *dst_fname = NULL;
+ char *cwd_fname = handle->conn->cwd_fname->base_name;
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ if (config->cache == NULL) {
+ return 0;
+ }
+
+ fname = smb_fname_src->base_name;
+ dst_fname = smb_fname_dst->base_name;
+
+ DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n",
+ fname, dst_fname);
+ virusfilter_cache_entry_rename(config->cache,
+ cwd_fname, fname,
+ dst_fname);
+
+ return 0;
+}
+
+/* VFS operations */
+static struct vfs_fn_pointers vfs_virusfilter_fns = {
+ .connect_fn = virusfilter_vfs_connect,
+ .disconnect_fn = virusfilter_vfs_disconnect,
+ .open_fn = virusfilter_vfs_open,
+ .close_fn = virusfilter_vfs_close,
+ .unlink_fn = virusfilter_vfs_unlink,
+ .rename_fn = virusfilter_vfs_rename,
+};
+
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *);
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS status;
+
+ status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "virusfilter",
+ &vfs_virusfilter_fns);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ virusfilter_debug_class = debug_add_class("virusfilter");
+ if (virusfilter_debug_class == -1) {
+ virusfilter_debug_class = DBGC_VFS;
+ DBG_ERR("Couldn't register custom debugging class!\n");
+ } else {
+ DBG_DEBUG("Debug class number: %d\n", virusfilter_debug_class);
+ }
+
+ DBG_INFO("registered\n");
+
+ return status;
+}
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
new file mode 100644
index 00000000000..468883fdaf8
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -0,0 +1,149 @@
+/*
+ Samba-VirusFilter VFS modules
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_COMMON_H
+#define _VIRUSFILTER_COMMON_H
+
+#include <stdint.h>
+#include <time.h>
+
+/* Samba common include file */
+#include "includes.h"
+
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "system/filesys.h"
+#include "transfer_file.h"
+#include "auth.h"
+#include "passdb.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../lib/tsocket/tsocket.h"
+
+/* Samba debug class for VIRUSFILTER */
+#undef DBGC_CLASS
+#define DBGC_CLASS virusfilter_debug_class
+extern int virusfilter_debug_class;
+
+/* Samba's global variable */
+extern userdom_struct current_user_info;
+
+#define VIRUSFILTER_VERSION "0.1.5"
+
+/* ====================================================================== */
+
+typedef enum {
+ VIRUSFILTER_ACTION_DO_NOTHING,
+ VIRUSFILTER_ACTION_QUARANTINE,
+ VIRUSFILTER_ACTION_RENAME,
+ VIRUSFILTER_ACTION_DELETE,
+} virusfilter_action;
+
+typedef enum {
+ VIRUSFILTER_RESULT_OK,
+ VIRUSFILTER_RESULT_CLEAN,
+ VIRUSFILTER_RESULT_ERROR,
+ VIRUSFILTER_RESULT_INFECTED,
+ VIRUSFILTER_RESULT_SUSPECTED,
+ /* FIXME: VIRUSFILTER_RESULT_RISKWARE, */
+} virusfilter_result;
+
+struct virusfilter_config {
+ int scan_request_count;
+ int scan_request_limit;
+
+ /* Scan on file operations */
+ bool scan_on_open;
+ bool scan_on_close;
+
+ /* Special scan options */
+ bool scan_archive;
+ int max_nested_scan_archive;
+ bool scan_mime;
+ bool block_suspected_file;
+
+ /* Size limit */
+ size_t max_file_size;
+ size_t min_file_size;
+
+ /* Exclude files */
+ name_compare_entry *exclude_files;
+
+ /* Scan result cache */
+ struct virusfilter_cache *cache;
+ int cache_entry_limit;
+ int cache_time_limit;
+
+ /* Infected file options */
+ virusfilter_action infected_file_action;
+ const char * infected_file_command;
+ int infected_open_errno;
+ int infected_close_errno;
+
+ /* Scan error options */
+ const char * scan_error_command;
+ int scan_error_open_errno;
+ int scan_error_close_errno;
+ bool block_access_on_error;
+
+ /* Quarantine infected files */
+ const char * quarantine_dir;
+ const char * quarantine_prefix;
+ const char * quarantine_suffix;
+ bool quarantine_keep_tree;
+ bool quarantine_keep_name;
+ mode_t quarantine_dir_mode;
+
+ /* Rename infected files */
+ const char * rename_prefix;
+ const char * rename_suffix;
+
+ /* Network options */
+ const char * socket_path;
+ struct virusfilter_io_handle *io_h;
+
+ /* The backend AV engine */
+ struct virusfilter_backend *backend;
+};
+
+struct virusfilter_backend_fns {
+ int (*connect)(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *svc,
+ const char *user);
+ void (*disconnect)(
+ struct vfs_handle_struct *handle);
+ virusfilter_result (*scan_init)(
+ struct virusfilter_config *config);
+ virusfilter_result (*scan)(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp);
+ void (*scan_end)(
+ struct virusfilter_config *config);
+};
+
+struct virusfilter_backend {
+ unsigned version;
+ const char *name;
+ const struct virusfilter_backend_fns *fns;
+ void *backend_private;
+};
+
+#endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_utils.c b/source3/modules/vfs_virusfilter_utils.c
new file mode 100644
index 00000000000..628e0aef99a
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.c
@@ -0,0 +1,1025 @@
+/*
+ Samba-VirusFilter VFS modules
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+ Copyright (C) 2016-2017 Trever L. Adams
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+struct iovec;
+
+#include "lib/util/iov_buf.h"
+#include <tevent.h>
+#include "lib/tsocket/tsocket.h"
+
+int virusfilter_debug_class = DBGC_VFS;
+
+/* ====================================================================== */
+
+char *virusfilter_string_sub(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *str)
+{
+ return talloc_sub_advanced(mem_ctx,
+ lp_servicename(mem_ctx, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ str);
+}
+
+int virusfilter_vfs_next_move(
+ struct vfs_handle_struct *vfs_h,
+ const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_RENAME(vfs_h, smb_fname_src, smb_fname_dst);
+ if (result == 0 || errno != EXDEV) {
+ return result;
+ }
+
+ /*
+ * For now, do not handle EXDEV as poking around violates
+ * stackability. Return -1, simply refuse access.
+ */
+ return -1;
+}
+
+/* Line-based socket I/O
+ * ======================================================================
+ */
+
+struct virusfilter_io_handle *virusfilter_io_new(
+ TALLOC_CTX *mem_ctx,
+ int connect_timeout,
+ int io_timeout)
+{
+ struct virusfilter_io_handle *io_h = talloc_zero(mem_ctx,
+ struct virusfilter_io_handle);
+
+ if (io_h == NULL) {
+ return NULL;
+ }
+
+ io_h->stream = NULL;
+ io_h->r_len = 0;
+
+ virusfilter_io_set_connect_timeout(io_h, connect_timeout);
+ virusfilter_io_set_io_timeout(io_h, io_timeout);
+ virusfilter_io_set_writel_eol(io_h, "\x0A", 1);
+ virusfilter_io_set_readl_eol(io_h, "\x0A", 1);
+
+ return io_h;
+}
+
+int virusfilter_io_set_connect_timeout(
+ struct virusfilter_io_handle *io_h,
+ int timeout)
+{
+ int timeout_old = io_h->connect_timeout;
+
+ /* timeout <= 0 means infinite */
+ io_h->connect_timeout = (timeout > 0) ? timeout : -1;
+
+ return timeout_old;
+}
+
+int virusfilter_io_set_io_timeout(
+ struct virusfilter_io_handle *io_h,
+ int timeout)
+{
+ int timeout_old = io_h->io_timeout;
+
+ /* timeout <= 0 means infinite */
+ io_h->io_timeout = (timeout > 0) ? timeout : -1;
+
+ return timeout_old;
+}
+
+void virusfilter_io_set_writel_eol(
+ struct virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size)
+{
+ if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) {
+ return;
+ }
+
+ memcpy(io_h->w_eol, eol, eol_size);
+ io_h->w_eol_size = eol_size;
+}
+
+void virusfilter_io_set_readl_eol(
+ struct virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size)
+{
+ if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) {
+ return;
+ }
+
+ memcpy(io_h->r_eol, eol, eol_size);
+ io_h->r_eol_size = eol_size;
+}
+
+bool virusfilter_io_connect_path(
+ struct virusfilter_io_handle *io_h,
+ const char *path)
+{
+ struct sockaddr_un addr;
+ NTSTATUS status;
+ int socket, bes_result, flags, ret;
+
+ ZERO_STRUCT(addr);
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path));
+
+ status = open_socket_out((struct sockaddr_storage *)&addr, 0,
+ io_h->connect_timeout,
+ &socket);
+ if (!NT_STATUS_IS_OK(status)) {
+ io_h->stream = NULL;
+ return false;
+ }
+
+ /* We must not block */
+ flags = fcntl(socket, F_GETFL);
+ if (flags <= 0) {
+ /* Handle error by ignoring */;
+ flags = 0;
+ DBG_WARNING("Could not get flags on socket (%s).\n",
+ strerror(errno));
+ }
+ flags |= SOCK_NONBLOCK;
+ ret = fcntl(socket, F_SETFL, flags);
+ if (ret == -1) {
+ /* Handle error by ignoring for now */
+ DBG_WARNING("Could not set flags on socket: %s.\n",
+ strerror(errno));
+ }
+
+ bes_result = tstream_bsd_existing_socket(io_h, socket, &io_h->stream);
+ if (bes_result < 0) {
+ DBG_ERR("Could not convert socket to tstream: %s.\n",
+ strerror(errno));
+ io_h->stream = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+static void disconnect_done(struct tevent_req *req)
+{
+ uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+ int ret;
+ int err_ret;
+
+ ret = tstream_disconnect_recv(req, &err_ret);
+ TALLOC_FREE(req);
+ if (ret == -1) {
+ *perr = err_ret;
+ }
+}
+
+bool virusfilter_io_disconnect(
+ struct virusfilter_io_handle *io_h)
+{
+ struct tevent_req *req;
+ struct tevent_context *ev;
+ uint64_t *perror = NULL;
+ bool ok = true;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (io_h->stream == NULL) {
+ io_h->r_len = 0;
+ TALLOC_FREE(frame);
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ ev = tevent_context_init(frame);
+ if (ev == NULL) {
+ DBG_ERR("Failed to setup event context.\n");
+ ok = false;
+ goto fail;
+ }
+
+ /* Error return - must be talloc'ed. */
+ perror = talloc_zero(frame, uint64_t);
+ if (perror == NULL) {
+ goto fail;
+ }
+
+ req = tstream_disconnect_send(io_h, ev, io_h->stream);
+
+ /* Callback when disconnect is done. */
+ tevent_req_set_callback(req, disconnect_done, perror);
+
+ /* Set timeout. */
+ ok = tevent_req_set_endtime(req, ev, timeval_current_ofs_msec(
+ io_h->connect_timeout));
+ if (!ok) {
+ DBG_ERR("Can't set endtime\n");
+ goto fail;
+ }
+
+ /* Loop waiting for req to finish. */
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_ERR("tevent_req_poll failed\n");
+ goto fail;
+ }
+
+ /* Emit debug error if failed. */
+ if (*perror != 0) {
+ DBG_DEBUG("Error %s\n", strerror((int)*perror));
+ goto fail;
+ }
+
+ /* Here we know we disconnected. */
+
+ io_h->stream = NULL;
+ io_h->r_len = 0;
+
+ fail:
+ TALLOC_FREE(frame);
+ return ok;
+}
+
+static void writev_done(struct tevent_req *req)
+{
+ uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+ int ret;
+ int err_ret;
+
+ ret = tstream_writev_recv(req, &err_ret);
+ TALLOC_FREE(req);
+ if (ret == -1) {
+ *perr = err_ret;
+ }
+}
+
+/****************************************************************************
+ Write all data from an iov array, with msec timeout (per write)
+ NB. This can be called with a non-socket fd, don't add dependencies
+ on socket calls.
+****************************************************************************/
+
+bool write_data_iov_timeout(
+ struct tstream_context *stream,
+ const struct iovec *iov,
+ size_t iovcnt,
+ int ms_timeout)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ uint64_t *perror = NULL;
+ bool ok = false;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ev = tevent_context_init(frame);
+ if (ev == NULL) {
+ DBG_ERR("Failed to setup event context.\n");
+ goto fail;
+ }
+
+ /* Error return - must be talloc'ed. */
+ perror = talloc_zero(frame, uint64_t);
+ if (perror == NULL) {
+ goto fail;
+ }
+
+ /* Send the data. */
+ req = tstream_writev_send(frame, ev, stream, iov, iovcnt);
+ if (req == NULL) {
+ DBG_ERR("Out of memory.\n");
+ goto fail;
+ }
+
+ /* Callback when *all* data sent. */
+ tevent_req_set_callback(req, writev_done, perror);
+
+ /* Set timeout. */
+ ok = tevent_req_set_endtime(req, ev,
+ timeval_current_ofs_msec(ms_timeout));
+ if (!ok) {
+ DBG_ERR("Can't set endtime\n");
+ goto fail;
+ }
+
+ /* Loop waiting for req to finish. */
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_ERR("tevent_req_poll failed\n");
+ goto fail;
+ }
+
+ /* Done with req - freed by the callback. */
+ req = NULL;
+
+ /* Emit debug error if failed. */
+ if (*perror != 0) {
+ DBG_DEBUG("Error %s\n", strerror((int)*perror));
+ goto fail;
+ }
+
+ /* Here we know we correctly wrote all data. */
+ TALLOC_FREE(frame);
+ return true;
+
+ fail:
+ TALLOC_FREE(frame);
+ return false;
+}
+
+bool virusfilter_io_write(
+ struct virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size)
+{
+ struct iovec iov;
+
+ if (data_size == 0) {
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ iov.iov_base = discard_const_p(void, data);
+ iov.iov_len = data_size;
+
+ return write_data_iov_timeout(io_h->stream, &iov, 1, io_h->io_timeout);
+}
+
+bool virusfilter_io_writel(
+ struct virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size)
+{
+ bool ok;
+
+ ok = virusfilter_io_write(io_h, data, data_size);
+ if (!ok) {
+ return ok;
+ }
+
+ return virusfilter_io_write(io_h, io_h->w_eol, io_h->w_eol_size);
+}
+
+bool virusfilter_io_writefl(
+ struct virusfilter_io_handle *io_h,
+ const char *data_fmt, ...)
+{
+ va_list ap;
+ char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
+ size_t data_size;
+
+ va_start(ap, data_fmt);
+ data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
+ va_end(ap);
+
+ if (unlikely (data_size < 0)) {
+ DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size);
+ data_size += io_h->w_eol_size;
+
+ return virusfilter_io_write(io_h, data, data_size);
+}
+
+bool virusfilter_io_vwritefl(
+ struct virusfilter_io_handle *io_h,
+ const char *data_fmt, va_list ap)
+{
+ char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
+ size_t data_size;
+
+ data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
+
+ if (unlikely (data_size < 0)) {
+ DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size);
+ data_size += io_h->w_eol_size;
+
+ return virusfilter_io_write(io_h, data, data_size);
+}
+
+bool virusfilter_io_writev(
+ struct virusfilter_io_handle *io_h, ...)
+{
+ va_list ap;
+ struct iovec iov[VIRUSFILTER_IO_IOV_MAX], *iov_p;
+ int iov_n;
+
+ va_start(ap, io_h);
+ for (iov_p = iov, iov_n = 0;
+ iov_n < VIRUSFILTER_IO_IOV_MAX;
+ iov_p++, iov_n++)
+ {
+ iov_p->iov_base = va_arg(ap, void *);
+ if (iov_p->iov_base == NULL) {
+ break;
+ }
+ iov_p->iov_len = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ return write_data_iov_timeout(io_h->stream, iov, iov_n,
+ io_h->io_timeout);
+}
+
+bool virusfilter_io_writevl(
+ struct virusfilter_io_handle *io_h, ...)
+{
+ va_list ap;
+ struct iovec iov[VIRUSFILTER_IO_IOV_MAX + 1], *iov_p;
+ int iov_n;
+
+ va_start(ap, io_h);
+ for (iov_p = iov, iov_n = 0; iov_n < VIRUSFILTER_IO_IOV_MAX;
+ iov_p++, iov_n++)
+ {
+ iov_p->iov_base = va_arg(ap, void *);
+ if (iov_p->iov_base == NULL) {
+ break;
+ }
+ iov_p->iov_len = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ iov_p->iov_base = io_h->r_eol;
+ iov_p->iov_len = io_h->r_eol_size;
+ iov_n++;
+
+ return write_data_iov_timeout(io_h->stream, iov, iov_n,
+ io_h->io_timeout);
+}
+
+static bool return_existing_line(TALLOC_CTX *ctx,
+ struct virusfilter_io_handle *io_h,
+ char **read_line)
+{
+ size_t read_line_len = 0;
+ char *end_p = NULL;
+ char *eol = NULL;
+
+ eol = memmem(io_h->r_buffer, io_h->r_len,
+ io_h->r_eol, io_h->r_eol_size);
+ if (eol == NULL) {
+ return false;
+ }
+ end_p = eol + io_h->r_eol_size;
+
+ *eol = '\0';
+ read_line_len = strlen(io_h->r_buffer) + 1;
+ *read_line = talloc_memdup(ctx,
+ io_h->r_buffer,
+ read_line_len);
+ if (*read_line == NULL) {
+ return false;
+ }
+
+ /*
+ * Copy the remaining buffer over the line
+ * we returned.
+ */
+ memmove(io_h->r_buffer,
+ end_p,
+ io_h->r_len - (end_p - io_h->r_buffer));
+
+ /* And reduce the size left in the buffer. */
+ io_h->r_len -= (end_p - io_h->r_buffer);
+ return true;
+}
+
+static void readv_done(struct tevent_req *req)
+{
+ uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+ int ret;
+ int err_ret;
+
+ ret = tstream_readv_recv(req, &err_ret);
+ TALLOC_FREE(req);
+ if (ret == -1) {
+ *perr = err_ret;
+ }
+}
+
+bool virusfilter_io_readl(TALLOC_CTX *ctx,
+ struct virusfilter_io_handle *io_h,
+ char **read_line)
+{
+ struct tevent_context *ev = NULL;
+ bool ok = false;
+ uint64_t *perror = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Search for an existing complete line. */
+ ok = return_existing_line(ctx, io_h, read_line);
+ if (ok) {
+ goto finish;
+ }
+
+ /*
+ * No complete line in the buffer. We must read more
+ * from the server.
+ */
+ ev = tevent_context_init(frame);
+ if (ev == NULL) {
+ DBG_ERR("Failed to setup event context.\n");
+ goto finish;
+ }
+
+ /* Error return - must be talloc'ed. */
+ perror = talloc_zero(frame, uint64_t);
+ if (perror == NULL) {
+ goto finish;
+ }
+
+ for (;;) {
+ ssize_t pending = 0;
+ size_t read_size = 0;
+ struct iovec iov;
+ struct tevent_req *req = NULL;
+
+ /*
+ * How much can we read ?
+ */
+ pending = tstream_pending_bytes(io_h->stream);
+ if (pending < 0) {
+ DBG_ERR("tstream_pending_bytes failed (%s).\n",
+ strerror(errno));
+ goto finish;
+ }
+
+ read_size = pending;
+ /* Must read at least one byte. */
+ read_size = MIN(read_size, 1);
+
+ /* And max remaining buffer space. */
+ read_size = MAX(read_size,
+ (sizeof(io_h->r_buffer) - io_h->r_len));
+
+ if (read_size == 0) {
+ /* Buffer is full with no EOL. Error out. */
+ DBG_ERR("Line buffer full.\n");
+ goto finish;
+ }
+
+ iov.iov_base = io_h->r_buffer + io_h->r_len;
+ iov.iov_len = read_size;
+
+ /* Read the data. */
+ req = tstream_readv_send(frame,
+ ev,
+ io_h->stream,
+ &iov,
+ 1);
+ if (req == NULL) {
+ DBG_ERR("out of memory.\n");
+ goto finish;
+ }
+
+ /* Callback when *all* data read. */
+ tevent_req_set_callback(req, readv_done, perror);
+
+ /* Set timeout. */
+ ok = tevent_req_set_endtime(req, ev,
+ timeval_current_ofs_msec(io_h->io_timeout));
+ if (!ok) {
+ DBG_ERR("can't set endtime\n");
+ goto finish;
+ }
+
+ /* Loop waiting for req to finish. */
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_ERR("tevent_req_poll failed\n");
+ goto finish;
+ }
+
+ /* Done with req - freed by the callback. */
+ req = NULL;
+
+ /*
+ * Emit debug error if failed.
+ * EPIPE may be success so, don't exit.
+ */
+ if (*perror != 0 && *perror != EPIPE) {
+ DBG_DEBUG("Error %s\n", strerror((int)*perror));
+ errno = (int)*perror;
+ goto finish;
+ }
+
+ /*
+ * We read read_size bytes. Extend the useable
+ * buffer length.
+ */
+ io_h->r_len += read_size;
+
+ /* Paranoia... */
+ SMB_ASSERT(io_h->r_len <= sizeof(io_h->r_buffer));
+
+ /* Exit if we have a line to return. */
+ ok = return_existing_line(ctx, io_h, read_line);
+ if (ok) {
+ goto finish;
+ }
+ /* No eol - keep reading. */
+ }
+
+ finish:
+
+ TALLOC_FREE(frame);
+ return ok;
+}
+
+bool virusfilter_io_writefl_readl(
+ struct virusfilter_io_handle *io_h,
+ char **read_line,
+ const char *fmt, ...)
+{
+ bool ok;
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ ok = virusfilter_io_vwritefl(io_h, fmt, ap);
+ va_end(ap);
+
+ if (!ok) {
+ return ok;
+ }
+ }
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, read_line);
+ if (!ok) {
+ DBG_ERR("virusfilter_io_readl not OK: %d\n", ok);
+ return false;
+ }
+ if (io_h->r_len == 0) { /* EOF */
+ DBG_ERR("virusfilter_io_readl EOF\n");
+ return false;
+ }
+
+ return true;
+}
+
+struct virusfilter_cache *virusfilter_cache_new(
+ TALLOC_CTX *ctx,
+ int entry_limit,
+ time_t time_limit)
+{
+ struct virusfilter_cache *cache;
+
+ if (time_limit == 0) {
+ return NULL;
+ }
+
+ cache = talloc_zero(ctx, struct virusfilter_cache);
+ if (cache == NULL) {
+ DBG_ERR("talloc_zero failed.\n");
+ return NULL;
+ }
+
+ cache->cache = memcache_init(cache->ctx, entry_limit *
+ (sizeof(struct virusfilter_cache_entry)
+ + VIRUSFILTER_CACHE_BUFFER_SIZE));
+ if (cache->cache == NULL) {
+ DBG_ERR("memcache_init failed.\n");
+ return NULL;
+ }
+ cache->ctx = ctx;
+ cache->time_limit = time_limit;
+
+ return cache;
+}
+
+bool virusfilter_cache_entry_add(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname,
+ virusfilter_result result,
+ char *report)
+{
+ int blob_size = sizeof(struct virusfilter_cache_entry);
+ struct virusfilter_cache_entry *cache_e =
+ talloc_zero_size(NULL, blob_size);
+ int fname_len = 0;
+
+ if (fname == NULL || directory == NULL) {
+ TALLOC_FREE(report);
+ return false;
+ }
+
+ fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+ if (fname == NULL) {
+ TALLOC_FREE(report);
+ return false;
+ }
+
+ fname_len = strlen(fname);
+
+ if (cache_e == NULL|| cache->time_limit == 0) {
+ TALLOC_FREE(report);
+ return false;
+ }
+
+ cache_e->result = result;
+ if (report != NULL) {
+ cache_e->report = talloc_steal(cache_e, report);
+ }
+ if (cache->time_limit > 0) {
+ cache_e->time = time(NULL);
+ }
+
+ memcache_add_talloc(cache->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, fname_len), &cache_e);
+
+ return true;
+}
+
+bool virusfilter_cache_entry_rename(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ char *old_fname,
+ char *new_fname)
+{
+ int old_fname_len = 0;
+ int new_fname_len = 0;
+ struct virusfilter_cache_entry *new_data = NULL;
+ struct virusfilter_cache_entry *old_data = NULL;
+
+ if (old_fname == NULL || new_fname == NULL || directory == NULL) {
+ return false;
+ }
+
+ old_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, old_fname);
+ new_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, new_fname);
+
+ if (old_fname == NULL || new_fname == NULL) {
+ TALLOC_FREE(old_fname);
+ TALLOC_FREE(new_fname);
+ return false;
+ }
+
+ old_fname_len = strlen(old_fname);
+ new_fname_len = strlen(new_fname);
+
+ old_data = memcache_lookup_talloc(
+ cache->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(old_fname, old_fname_len));
+
+ if (old_data == NULL) {
+ return false;
+ }
+
+ new_data = talloc_memdup(cache->ctx, old_data,
+ sizeof(struct virusfilter_cache_entry));
+ if (new_data == NULL) {
+ return false;
+ }
+ new_data->report = talloc_strdup(new_data, old_data->report);
+
+ memcache_add_talloc(cache->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(new_fname, new_fname_len), &new_data);
+
+ memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(old_fname, old_fname_len));
+
+ return true;
+}
+
+void virusfilter_cache_purge(struct virusfilter_cache *cache)
+{
+ memcache_flush(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
+}
+
+struct virusfilter_cache_entry *virusfilter_cache_get(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname)
+{
+ int fname_len = 0;
+ struct virusfilter_cache_entry *cache_e = NULL;
+ struct virusfilter_cache_entry *data = NULL;
+
+ if (fname == NULL || directory == NULL) {
+ return 0;
+ }
+
+ fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+ if (fname == NULL) {
+ return 0;
+ }
+
+ fname_len = strlen(fname);
+
+ data = memcache_lookup_talloc(cache->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, fname_len));
+
+ if (data == NULL) {
+ return cache_e;
+ }
+
+ if (cache->time_limit > 0) {
+ if (time(NULL) - data->time > cache->time_limit) {
+ DBG_DEBUG("Cache entry is too old: %s\n",
+ fname);
+ virusfilter_cache_remove(cache, directory, fname);
+ return cache_e;
+ }
+ }
+ cache_e = talloc_memdup(cache->ctx, data,
+ sizeof(struct virusfilter_cache_entry));
+ if (cache_e == NULL) {
+ return NULL;
+ }
+ if (data->report != NULL) {
+ cache_e->report = talloc_strdup(cache_e, data->report);
+ } else {
+ cache_e->report = NULL;
+ }
+
+ return cache_e;
+}
+
+void virusfilter_cache_remove(struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname)
+{
+ DBG_DEBUG("Purging cache entry: %s/%s\n", directory, fname);
+
+ if (fname == NULL || directory == NULL) {
+ return;
+ }
+
+ fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+ if (fname == NULL) {
+ return;
+ }
+
+ memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, strlen(fname)));
+}
+
+void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e)
+{
+ if (cache_e != NULL) {
+ TALLOC_FREE(cache_e->report);
+ cache_e->report = NULL;
+ }
+ TALLOC_FREE(cache_e);
+}
+
+/* Shell scripting
+ * ======================================================================
+ */
+
+int virusfilter_env_set(
+ TALLOC_CTX *mem_ctx,
+ char **env_list,
+ const char *name,
+ const char *value)
+{
+ char *env_new;
+ int ret;
+
+ env_new = talloc_asprintf(mem_ctx, "%s=%s", name, value);
+ if (env_new == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ return -1;
+ }
+
+ ret = strv_add(mem_ctx, env_list, env_new);
+
+ TALLOC_FREE(env_new);
+
+ return ret;
+}
+
+/* virusfilter_env version Samba's *_sub_advanced() in substitute.c */
+int virusfilter_shell_set_conn_env(
+ TALLOC_CTX *mem_ctx,
+ char **env_list,
+ connection_struct *conn)
+{
+ int snum = SNUM(conn);
+ char *server_addr_p;
+ char *client_addr_p;
+ const char *local_machine_name = get_local_machine_name();
+ fstring pidstr;
+ int ret;
+
+ if (local_machine_name == NULL || *local_machine_name == '\0') {
+ local_machine_name = lp_netbios_name();
+ }
+
+ server_addr_p = tsocket_address_inet_addr_string(
+ conn->sconn->local_address, talloc_tos());
+
+ if (server_addr_p != NULL) {
+ ret = strncmp("::ffff:", server_addr_p, 7);
+ if (ret == 0) {
+ server_addr_p += 7;
+ }
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_IP",
+ server_addr_p);
+ }
+ TALLOC_FREE(server_addr_p);
+
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_NAME",
+ myhostname());
+ virusfilter_env_set(mem_ctx, env_list,
+ "VIRUSFILTER_SERVER_NETBIOS_NAME",
+ local_machine_name);
+ slprintf(pidstr,sizeof(pidstr)-1, "%ld", (long)getpid());
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_PID",
+ pidstr);
+
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_NAME",
+ lp_const_servicename(snum));
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_PATH",
+ conn->cwd_fname->base_name);
+
+ client_addr_p = tsocket_address_inet_addr_string(
+ conn->sconn->remote_address, talloc_tos());
+
+ if (client_addr_p != NULL) {
+ ret = strncmp("::ffff:", client_addr_p, 7);
+ if (ret == 0) {
+ client_addr_p += 7;
+ }
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_IP",
+ client_addr_p);
+ }
+ TALLOC_FREE(client_addr_p);
+
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_NAME",
+ conn->sconn->remote_hostname);
+ virusfilter_env_set(mem_ctx, env_list,
+ "VIRUSFILTER_CLIENT_NETBIOS_NAME",
+ get_remote_machine_name());
+
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_NAME",
+ get_current_username());
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_DOMAIN",
+ current_user_info.domain);
+
+ return 0;
+}
+
+/* Wrapper to Samba's smbrun() in smbrun.c */
+int virusfilter_shell_run(
+ TALLOC_CTX *mem_ctx,
+ const char *cmd,
+ char **env_list,
+ connection_struct *conn,
+ bool sanitize)
+{
+ int ret;
+
+ if (conn != NULL) {
+ ret = virusfilter_shell_set_conn_env(mem_ctx, env_list, conn);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ if (sanitize) {
+ return smbrun(cmd, NULL, strv_to_env(talloc_tos(), *env_list));
+ } else {
+ return smbrun_no_sanitize(cmd, NULL, strv_to_env(talloc_tos(),
+ *env_list));
+ }
+}
diff --git a/source3/modules/vfs_virusfilter_utils.h b/source3/modules/vfs_virusfilter_utils.h
new file mode 100644
index 00000000000..69754aa6546
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.h
@@ -0,0 +1,177 @@
+/*
+ Samba-VirusFilter VFS modules
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_UTILS_H
+#define _VIRUSFILTER_UTILS_H
+
+#include "modules/vfs_virusfilter_common.h"
+#include "../lib/util/memcache.h"
+#include "../lib/util/strv.h"
+
+/*#define str_eq(s1, s2) \
+ ((strcmp((s1), (s2)) == 0) ? true : false)
+#define strn_eq(s1, s2, n) \
+ ((strncmp((s1), (s2), (n)) == 0) ? true : false) */
+
+/* "* 3" is for %-encoding */
+#define VIRUSFILTER_IO_URL_MAX (PATH_MAX * 3)
+#define VIRUSFILTER_IO_BUFFER_SIZE (VIRUSFILTER_IO_URL_MAX + 128)
+#define VIRUSFILTER_IO_EOL_SIZE 1
+#define VIRUSFILTER_IO_IOV_MAX 16
+#define VIRUSFILTER_CACHE_BUFFER_SIZE (PATH_MAX + 128)
+
+struct virusfilter_io_handle {
+ struct tstream_context *stream;
+ int connect_timeout; /* msec */
+ int io_timeout; /* msec */
+
+ /* end-of-line character(s) */
+ char w_eol[VIRUSFILTER_IO_EOL_SIZE];
+ int w_eol_size;
+
+ /* end-of-line character(s) */
+ char r_eol[VIRUSFILTER_IO_EOL_SIZE];
+ int r_eol_size;
+
+ /* buffer */
+ char r_buffer[VIRUSFILTER_IO_BUFFER_SIZE];
+ size_t r_len;
+};
+
+struct virusfilter_cache_entry {
+ time_t time;
+ virusfilter_result result;
+ char *report;
+};
+
+struct virusfilter_cache {
+ struct memcache *cache;
+ TALLOC_CTX *ctx;
+ time_t time_limit;
+};
+
+/* ====================================================================== */
+
+char *virusfilter_string_sub(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *str);
+int virusfilter_vfs_next_move(
+ vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst);
+
+/* Line-based socket I/O */
+struct virusfilter_io_handle *virusfilter_io_new(
+ TALLOC_CTX *mem_ctx,
+ int connect_timeout,
+ int timeout);
+int virusfilter_io_set_connect_timeout(
+ struct virusfilter_io_handle *io_h,
+ int timeout);
+int virusfilter_io_set_io_timeout(
+ struct virusfilter_io_handle *io_h, int timeout);
+void virusfilter_io_set_writel_eol(
+ struct virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size);
+void virusfilter_io_set_readl_eol(
+ struct virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size);
+bool virusfilter_io_connect_path(
+ struct virusfilter_io_handle *io_h,
+ const char *path);
+bool virusfilter_io_disconnect(
+ struct virusfilter_io_handle *io_h);
+bool write_data_iov_timeout(
+ struct tstream_context *stream,
+ const struct iovec *iov,
+ size_t iovcnt,
+ int ms_timeout);
+bool virusfilter_io_write(
+ struct virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size);
+bool virusfilter_io_writel(
+ struct virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size);
+bool virusfilter_io_writefl(
+ struct virusfilter_io_handle *io_h,
+ const char *data_fmt, ...);
+bool virusfilter_io_vwritefl(
+ struct virusfilter_io_handle *io_h,
+ const char *data_fmt, va_list ap);
+bool virusfilter_io_writev(
+ struct virusfilter_io_handle *io_h, ...);
+bool virusfilter_io_writevl(
+ struct virusfilter_io_handle *io_h, ...);
+bool virusfilter_io_readl(TALLOC_CTX *ctx,
+ struct virusfilter_io_handle *io_h,
+ char **read_line);
+bool virusfilter_io_writefl_readl(
+ struct virusfilter_io_handle *io_h,
+ char **read_line,
+ const char *fmt, ...);
+
+/* Scan result cache */
+struct virusfilter_cache *virusfilter_cache_new(
+ TALLOC_CTX *ctx,
+ int entry_limit,
+ time_t time_limit);
+bool virusfilter_cache_entry_add(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname,
+ virusfilter_result result,
+ char *report);
+bool virusfilter_cache_entry_rename(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ char *old_fname,
+ char *new_fname);
+void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e);
+struct virusfilter_cache_entry *virusfilter_cache_get(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname);
+void virusfilter_cache_remove(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname);
+void virusfilter_cache_purge(struct virusfilter_cache *cache);
+
+/* Shell scripting */
+int virusfilter_env_set(
+ TALLOC_CTX *mem_ctx,
+ char **env_list,
+ const char *name,
+ const char *value);
+int virusfilter_shell_set_conn_env(
+ TALLOC_CTX *mem_ctx,
+ char **env_list,
+ connection_struct *conn);
+int virusfilter_shell_run(
+ TALLOC_CTX *mem_ctx,
+ const char *cmd,
+ char **env_list,
+ connection_struct *conn,
+ bool sanitize);
+
+#endif /* _VIRUSFILTER_UTILS_H */
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index 24eeee6aad6..b423eb0083a 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -14,6 +14,11 @@ bld.SAMBA3_LIBRARY('non_posix_acls',
deps='samba-util vfs',
private_library=True)
+bld.SAMBA3_SUBSYSTEM('VFS_VIRUSFILTER_UTILS',
+ source='vfs_virusfilter_utils.c',
+ deps='strv',
+ enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter')))
+
bld.SAMBA3_SUBSYSTEM('VFS_AIXACL_UTIL',
source='vfs_aixacl_util.c',
enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl2')))
@@ -489,6 +494,14 @@ bld.SAMBA3_MODULE('vfs_snapper',
internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_snapper'),
enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_snapper'))
+bld.SAMBA3_MODULE('vfs_virusfilter',
+ subsystem='vfs',
+ source='vfs_virusfilter.c',
+ deps='samba-util VFS_VIRUSFILTER_UTILS',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter'))
+
bld.SAMBA3_MODULE('vfs_vxfs',
subsystem='vfs',
source='lib_vxfs.c vfs_vxfs.c',
diff --git a/source3/wscript b/source3/wscript
index f3b6d330121..42dadc86150 100644
--- a/source3/wscript
+++ b/source3/wscript
@@ -1687,7 +1687,7 @@ main() {
vfs_preopen vfs_catia
vfs_media_harmony vfs_unityed_media vfs_fruit vfs_shell_snap
vfs_commit vfs_worm vfs_crossrename vfs_linux_xfs_sgid
- vfs_time_audit vfs_offline
+ vfs_time_audit vfs_offline vfs_virusfilter
'''))
default_shared_modules.extend(TO_LIST('auth_script idmap_tdb2 idmap_script'))
# these have broken dependencies
--
2.14.3
From 09ff4462d9b1ec461a5f9a89f83b5c07c6d32133 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:38:14 -0600
Subject: [PATCH 3/5] Samba-VirusFilter: Sophos VFS backend.
Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas at osstech.co.jp>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
docs-xml/manpages/vfs_virusfilter.8.xml | 6 +
source3/modules/vfs_virusfilter.c | 15 +-
source3/modules/vfs_virusfilter_common.h | 2 +
source3/modules/vfs_virusfilter_sophos.c | 391 +++++++++++++++++++++++++++++++
source3/modules/wscript_build | 5 +-
5 files changed, 414 insertions(+), 5 deletions(-)
create mode 100644 source3/modules/vfs_virusfilter_sophos.c
diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index eb6112e3827..c4bc8920043 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -41,6 +41,10 @@
<term>virusfilter:scanner</term>
<listitem>
<para>The antivirus scan-engine.</para>
+ <itemizedlist>
+ <listitem><para><emphasis>sophos</emphasis>, the Sophos AV
+ scanner</para></listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
@@ -52,6 +56,8 @@
<para>If this option is not set, the default path depends on the
configured AV scanning engine.
</para>
+ <para>For the <emphasis>sophos</emphasis>backend the default is
+ <emphasis>/var/run/savdi/sssp.sock</emphasis>.</para>
</listitem>
</varlistentry>
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index 8703f2d303b..4b13c48a5f4 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -441,10 +441,17 @@ static int virusfilter_vfs_connect(
return -1;
}
- /* This goes away as soon as the next commit adds an actual backend... */
- if (config->backend == NULL) {
- DBG_INFO("Not implemented\n");
- return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+ switch (backend) {
+ case VIRUSFILTER_SCANNER_SOPHOS:
+ ret = virusfilter_sophos_init(config);
+ break;
+ default:
+ DBG_ERR("Unhandled scanner %d\n", backend);
+ return -1;
+ }
+ if (ret != 0) {
+ DBG_ERR("Scanner backend init failed\n");
+ return -1;
}
if (config->backend->fns->connect != NULL) {
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
index 468883fdaf8..69519c9daa4 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -146,4 +146,6 @@ struct virusfilter_backend {
void *backend_private;
};
+int virusfilter_sophos_init(struct virusfilter_config *config);
+
#endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c
new file mode 100644
index 00000000000..72051cd64a2
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_sophos.c
@@ -0,0 +1,391 @@
+/*
+ Samba-VirusFilter VFS modules
+ Sophos Anti-Virus savdid (SSSP/1.0) support
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/* Default values for standard "extra" configuration variables */
+#ifdef SOPHOS_DEFAULT_SOCKET_PATH
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH SOPHOS_DEFAULT_SOCKET_PATH
+#else
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/var/run/savdi/sssp.sock"
+#endif
+
+static void virusfilter_sophos_scan_end(struct virusfilter_config *config);
+
+/* Python's urllib.quote(string[, safe]) clone */
+static int virusfilter_url_quote(const char *src, char *dst, int dst_size)
+{
+ char *dst_c = dst;
+ static char hex[] = "0123456789ABCDEF";
+
+ for (; *src != '\0'; src++) {
+ if ((*src < '0' && *src != '-' && *src != '.' && *src != '/') ||
+ (*src > '9' && *src < 'A') ||
+ (*src > 'Z' && *src < 'a' && *src != '_') ||
+ (*src > 'z'))
+ {
+ if (dst_size < 4) {
+ return -1;
+ }
+ *dst_c++ = '%';
+ *dst_c++ = hex[(*src >> 4) & 0x0F];
+ *dst_c++ = hex[*src & 0x0F];
+ dst_size -= 3;
+ } else {
+ if (dst_size < 2) {
+ return -1;
+ }
+ *dst_c++ = *src;
+ dst_size--;
+ }
+ }
+
+ *dst_c = '\0';
+
+ return (dst_c - dst);
+}
+
+static int virusfilter_sophos_connect(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *svc,
+ const char *user)
+{
+ virusfilter_io_set_readl_eol(config->io_h, "\x0D\x0A", 2);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_sophos_scan_ping(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+ char *reply = NULL;
+ bool ok;
+ int ret;
+
+ /* SSSP/1.0 has no "PING" command */
+ ok = virusfilter_io_writel(io_h, "SSSP/1.0 OPTIONS\n", 17);
+ if (!ok) {
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ for (;;) {
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+ ret = strcmp(reply, "");
+ if (ret == 0) {
+ break;
+ }
+ TALLOC_FREE(reply);
+ }
+
+ TALLOC_FREE(reply);
+ return VIRUSFILTER_RESULT_OK;
+}
+
+static virusfilter_result virusfilter_sophos_scan_init(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+ char *reply = NULL;
+ int ret;
+ bool ok;
+
+ if (io_h->stream != NULL) {
+ DBG_DEBUG("SSSP: Checking if connection is alive\n");
+
+ ret = virusfilter_sophos_scan_ping(config);
+ if (ret == VIRUSFILTER_RESULT_OK)
+ {
+ DBG_DEBUG("SSSP: Re-using existent connection\n");
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ DBG_INFO("SSSP: Closing dead connection\n");
+ virusfilter_sophos_scan_end(config);
+ }
+
+
+ DBG_INFO("SSSP: Connecting to socket: %s\n",
+ config->socket_path);
+
+ become_root();
+ ok = virusfilter_io_connect_path(io_h, config->socket_path);
+ unbecome_root();
+
+ if (!ok) {
+ DBG_ERR("SSSP: Connecting to socket failed: %s: %s\n",
+ config->socket_path, strerror(errno));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: Reading greeting message failed: %s\n",
+ strerror(errno));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ ret = strncmp(reply, "OK SSSP/1.0", 11);
+ if (ret != 0) {
+ DBG_ERR("SSSP: Invalid greeting message: %s\n",
+ reply);
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ DBG_DEBUG("SSSP: Connected\n");
+
+ DBG_INFO("SSSP: Configuring\n");
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "SSSP/1.0 OPTIONS\noutput:brief\nsavigrp:GrpArchiveUnpack %d\n",
+ config->scan_archive ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("SSSP: OPTIONS: I/O error: %s\n", strerror(errno));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ ret = strncmp(reply, "ACC ", 4);
+ if (ret != 0) {
+ DBG_ERR("SSSP: OPTIONS: Not accepted: %s\n", reply);
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ ret = strncmp(reply, "DONE OK ", 8);
+ if (ret != 0) {
+ DBG_ERR("SSSP: OPTIONS failed: %s\n", reply);
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ ret = strcmp(reply, "");
+ if (ret != 0) {
+ DBG_ERR("SSSP: OPTIONS: Invalid reply: %s\n", reply);
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ DBG_DEBUG("SSSP: Configured\n");
+
+ return VIRUSFILTER_RESULT_OK;
+
+virusfilter_sophos_scan_init_failed:
+
+ TALLOC_FREE(reply);
+
+ virusfilter_sophos_scan_end(config);
+
+ return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_sophos_scan_end(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+
+ DBG_INFO("SSSP: Disconnecting\n");
+
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_sophos_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp)
+{
+ char *cwd_fname = fsp->conn->cwd_fname->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ char fileurl[VIRUSFILTER_IO_URL_MAX+1];
+ int fileurl_len, fileurl_len2;
+ struct virusfilter_io_handle *io_h = config->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_ERROR;
+ char *report = NULL;
+ char *reply = NULL;
+ char *reply_token, *reply_saveptr;
+ int ret;
+ bool ok;
+
+ DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+ fileurl_len = virusfilter_url_quote(cwd_fname, fileurl,
+ VIRUSFILTER_IO_URL_MAX);
+ if (fileurl_len < 0) {
+ DBG_ERR("virusfilter_url_quote failed: File path too long: "
+ "%s/%s\n", cwd_fname, fname);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(), "File path too long");
+ goto virusfilter_sophos_scan_return;
+ }
+ fileurl[fileurl_len] = '/';
+ fileurl_len++;
+
+ fileurl_len += fileurl_len2 = virusfilter_url_quote(fname,
+ fileurl + fileurl_len, VIRUSFILTER_IO_URL_MAX - fileurl_len);
+ if (fileurl_len2 < 0) {
+ DBG_ERR("virusfilter_url_quote failed: File path too long: "
+ "%s/%s\n", cwd_fname, fname);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(), "File path too long");
+ goto virusfilter_sophos_scan_return;
+ }
+ fileurl_len += fileurl_len2;
+
+ ok = virusfilter_io_writevl(io_h, "SSSP/1.0 SCANFILE ", 18, fileurl,
+ fileurl_len, NULL);
+ if (!ok) {
+ DBG_ERR("SSSP: SCANFILE: Write error: %s\n",
+ strerror(errno));
+ goto virusfilter_sophos_scan_io_error;
+ }
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: SCANFILE: Read error: %s\n", strerror(errno));
+ goto virusfilter_sophos_scan_io_error;
+ }
+ ret = strncmp(reply, "ACC ", 4);
+ if (ret != 0) {
+ DBG_ERR("SSSP: SCANFILE: Not accepted: %s\n",
+ reply);
+ result = VIRUSFILTER_RESULT_ERROR;
+ goto virusfilter_sophos_scan_return;
+ }
+
+ TALLOC_FREE(reply);
+
+ result = VIRUSFILTER_RESULT_CLEAN;
+ for (;;) {
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: SCANFILE: Read error: %s\n",
+ strerror(errno));
+ goto virusfilter_sophos_scan_io_error;
+ }
+
+ ret = strcmp(reply, "");
+ if (ret == 0) {
+ break;
+ }
+
+ reply_token = strtok_r(reply, " ", &reply_saveptr);
+
+ if (strcmp(reply_token, "VIRUS") == 0) {
+ result = VIRUSFILTER_RESULT_INFECTED;
+ reply_token = strtok_r(NULL, " ", &reply_saveptr);
+ if (reply_token != NULL) {
+ report = talloc_strdup(talloc_tos(),
+ reply_token);
+ } else {
+ report = talloc_asprintf(talloc_tos(),
+ "UNKNOWN INFECTION");
+ }
+ } else if (strcmp(reply_token, "OK") == 0) {
+
+ /* Ignore */
+ } else if (strcmp(reply_token, "DONE") == 0) {
+ reply_token = strtok_r(NULL, "", &reply_saveptr);
+ if (reply_token != NULL &&
+
+ /* Succeed */
+ strncmp(reply_token, "OK 0000 ", 8) != 0 &&
+
+ /* Infected */
+ strncmp(reply_token, "OK 0203 ", 8) != 0)
+ {
+ DBG_ERR("SSSP: SCANFILE: Error: %s\n",
+ reply_token);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner error: %s\n",
+ reply_token);
+ }
+ } else {
+ DBG_ERR("SSSP: SCANFILE: Invalid reply: %s\n",
+ reply_token);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(), "Scanner "
+ "communication error");
+ }
+
+ TALLOC_FREE(reply);
+ }
+
+virusfilter_sophos_scan_return:
+ TALLOC_FREE(reply);
+
+ if (report == NULL) {
+ *reportp = talloc_asprintf(talloc_tos(),
+ "Scanner report memory error");
+ } else {
+ *reportp = report;
+ }
+
+ return result;
+
+virusfilter_sophos_scan_io_error:
+ *reportp = talloc_asprintf(talloc_tos(),
+ "Scanner I/O error: %s\n", strerror(errno));
+
+ return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_sophos ={
+ .connect = virusfilter_sophos_connect,
+ .disconnect = NULL,
+ .scan_init = virusfilter_sophos_scan_init,
+ .scan = virusfilter_sophos_scan,
+ .scan_end = virusfilter_sophos_scan_end,
+};
+
+int virusfilter_sophos_init(struct virusfilter_config *config)
+{
+ struct virusfilter_backend *backend = NULL;
+
+ if (config->socket_path == NULL) {
+ config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+ }
+
+ backend = talloc_zero(config, struct virusfilter_backend);
+ if (backend == NULL) {
+ return -1;
+ }
+
+ backend->fns = &virusfilter_backend_sophos;
+ backend->name = "sophos";
+
+ config->backend = backend;
+ return 0;
+}
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index b423eb0083a..cfab82f26b2 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -496,7 +496,10 @@ bld.SAMBA3_MODULE('vfs_snapper',
bld.SAMBA3_MODULE('vfs_virusfilter',
subsystem='vfs',
- source='vfs_virusfilter.c',
+ source='''
+ vfs_virusfilter.c
+ vfs_virusfilter_sophos.c
+ ''',
deps='samba-util VFS_VIRUSFILTER_UTILS',
init_function='',
internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'),
--
2.14.3
From fdbcbaf7e3e5e26a503b569291728566682d1eb8 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:39:20 -0600
Subject: [PATCH 4/5] Samba-VirusFilter: F-Secure AntiVirus (fsav) VFS and man
page.
Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas at osstech.co.jp>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
docs-xml/manpages/vfs_virusfilter.8.xml | 27 +-
source3/modules/vfs_virusfilter.c | 3 +
source3/modules/vfs_virusfilter_common.h | 1 +
source3/modules/vfs_virusfilter_fsav.c | 451 +++++++++++++++++++++++++++++++
source3/modules/wscript_build | 1 +
5 files changed, 481 insertions(+), 2 deletions(-)
create mode 100644 source3/modules/vfs_virusfilter_fsav.c
diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index c4bc8920043..2e70ab0b553 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -44,6 +44,8 @@
<itemizedlist>
<listitem><para><emphasis>sophos</emphasis>, the Sophos AV
scanner</para></listitem>
+ <listitem><para><emphasis>fsav</emphasis>, the F-Secure AV
+ scanner</para></listitem>
</itemizedlist>
</listitem>
</varlistentry>
@@ -58,6 +60,8 @@
</para>
<para>For the <emphasis>sophos</emphasis>backend the default is
<emphasis>/var/run/savdi/sssp.sock</emphasis>.</para>
+ <para>For the <emphasis>fsav</emphasis> backend the default is
+ <emphasis>/tmp/.fsav-0</emphasis>.</para>
</listitem>
</varlistentry>
@@ -219,7 +223,7 @@
<term>virusfilter:scan archive = true</term>
<listitem>
<para>This defines whether or not to scan archives.</para>
- <para>Sophos supports this and defaults to false.</para>
+ <para>Sophos and F-Secure support this and it defaults to false.</para>
</listitem>
</varlistentry>
@@ -227,7 +231,16 @@
<term>virusfilter:max nested scan archive = 1</term>
<listitem>
<para>This defines the maximum depth to search nested archives.</para>
- <para>The Sophos module supports this and defaults to 1.</para>
+ <para>The Sophos and F-Secure support this and it defaults to 1.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>virusfilter:scan mime = true</term>
+ <listitem>
+ <para>This defines whether or not to scan mime files.</para>
+ <para>Only the <emphasis>fsav</emphasis>scanner supports this
+ option and defaults to false.</para>
</listitem>
</varlistentry>
@@ -309,6 +322,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>virusfilter:block suspected file = false</term>
+ <listitem>
+ <para>With this option on, suspected malware will be blocked as
+ well. Only the <emphasis>fsav</emphasis>scanner supports this
+ option.</para>
+ <para>If this option is not set, the default is false.</para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index 4b13c48a5f4..ceb78e45a05 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -445,6 +445,9 @@ static int virusfilter_vfs_connect(
case VIRUSFILTER_SCANNER_SOPHOS:
ret = virusfilter_sophos_init(config);
break;
+ case VIRUSFILTER_SCANNER_FSAV:
+ ret = virusfilter_fsav_init(config);
+ break;
default:
DBG_ERR("Unhandled scanner %d\n", backend);
return -1;
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
index 69519c9daa4..a28ce2978fa 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -147,5 +147,6 @@ struct virusfilter_backend {
};
int virusfilter_sophos_init(struct virusfilter_config *config);
+int virusfilter_fsav_init(struct virusfilter_config *config);
#endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_fsav.c b/source3/modules/vfs_virusfilter_fsav.c
new file mode 100644
index 00000000000..2b874d7db43
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_fsav.c
@@ -0,0 +1,451 @@
+/*
+ Samba-VirusFilter VFS modules
+ F-Secure Anti-Virus fsavd support
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+#ifdef FSAV_DEFAULT_SOCKET_PATH
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH FSAV_DEFAULT_SOCKET_PATH
+#else
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/tmp/.fsav-0"
+#endif
+
+/* Default values for module-specific configuration variables */
+/* 5 = F-Secure Linux 7 or later? */
+
+#define VIRUSFILTER_DEFAULT_FSAV_PROTOCOL 5
+#define VIRUSFILTER_DEFAULT_SCAN_RISKWARE false
+#define VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST true
+#define VIRUSFILTER_DEFAULT_FILTER_FILENAME false
+
+struct virusfilter_fsav_config {
+ /* Backpointer */
+ struct virusfilter_config *config;
+
+ int fsav_protocol;
+ bool scan_riskware;
+ bool stop_scan_on_first;
+ bool filter_filename;
+};
+
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config);
+
+static int virusfilter_fsav_destruct_config(
+ struct virusfilter_fsav_config *fsav_config)
+{
+ virusfilter_fsav_scan_end(fsav_config->config);
+ return 0;
+}
+
+static int virusfilter_fsav_connect(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *svc,
+ const char *user)
+{
+ int snum = SNUM(handle->conn);
+ struct virusfilter_fsav_config *fsav_config = NULL;
+
+ fsav_config = talloc_zero(config->backend,
+ struct virusfilter_fsav_config);
+ if (fsav_config == NULL) {
+ return -1;
+ }
+
+ fsav_config->config = config;
+
+ fsav_config->fsav_protocol = lp_parm_int(
+ snum, "virusfilter", "fsav protocol",
+ VIRUSFILTER_DEFAULT_FSAV_PROTOCOL);
+
+ fsav_config->scan_riskware = lp_parm_bool(
+ snum, "virusfilter", "scan riskware",
+ VIRUSFILTER_DEFAULT_SCAN_RISKWARE);
+
+ fsav_config->stop_scan_on_first = lp_parm_bool(
+ snum, "virusfilter", "stop scan on first",
+ VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST);
+
+ fsav_config->filter_filename = lp_parm_bool(
+ snum, "virusfilter", "filter filename",
+ VIRUSFILTER_DEFAULT_FILTER_FILENAME);
+
+ talloc_set_destructor(fsav_config, virusfilter_fsav_destruct_config);
+
+ config->backend->backend_private = fsav_config;
+
+ config->block_suspected_file = lp_parm_bool(
+ snum, "virusfilter", "block suspected file", false);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_fsav_scan_init(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_fsav_config *fsav_config = NULL;
+ struct virusfilter_io_handle *io_h = config->io_h;
+ char *reply = NULL;
+ bool ok;
+ int ret;
+
+ fsav_config = talloc_get_type_abort(config->backend->backend_private,
+ struct virusfilter_fsav_config);
+
+ if (io_h->stream != NULL) {
+ DBG_DEBUG("fsavd: Checking if connection is alive\n");
+
+ /* FIXME: I don't know the correct PING command format... */
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "PING");
+ if (ok) {
+ ret = strncmp(reply, "ERROR\t", 6);
+ if (ret == 0) {
+ DBG_DEBUG("fsavd: Re-using existent "
+ "connection\n");
+ goto virusfilter_fsav_init_succeed;
+ }
+ }
+
+ DBG_DEBUG("fsavd: Closing dead connection\n");
+ virusfilter_fsav_scan_end(config);
+ }
+
+ DBG_INFO("fsavd: Connecting to socket: %s\n",
+ config->socket_path);
+
+ become_root();
+ ok = virusfilter_io_connect_path(io_h, config->socket_path);
+ unbecome_root();
+
+ if (!ok) {
+ DBG_ERR("fsavd: Connecting to socket failed: %s: %s\n",
+ config->socket_path, strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("fsavd: Reading greeting message failed: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "DBVERSION\t", 10);
+ if (ret != 0) {
+ DBG_ERR("fsavd: Invalid greeting message: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ DBG_DEBUG("fsavd: Connected\n");
+
+ DBG_INFO("fsavd: Configuring\n");
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "PROTOCOL\t%d",
+ fsav_config->fsav_protocol);
+ if (!ok) {
+ DBG_ERR("fsavd: PROTOCOL: I/O error: %s\n", strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: PROTOCOL: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "CONFIGURE\tSTOPONFIRST\t%d",
+ fsav_config->stop_scan_on_first ?
+ 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE STOPONFIRST: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE STOPONFIRST: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tFILTER\t%d",
+ fsav_config->filter_filename ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE FILTER: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE FILTER: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "CONFIGURE\tARCHIVE\t%d",
+ config->scan_archive ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE ARCHIVE: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "CONFIGURE\tMAXARCH\t%d",
+ config->max_nested_scan_archive);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE MAXARCH: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE MAXARCH: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "CONFIGURE\tMIME\t%d",
+ config->scan_mime ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE MIME: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE MIME: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tRISKWARE\t%d",
+ fsav_config->scan_riskware ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE RISKWARE: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE RISKWARE: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ DBG_DEBUG("fsavd: Configured\n");
+
+virusfilter_fsav_init_succeed:
+ TALLOC_FREE(reply);
+ return VIRUSFILTER_RESULT_OK;
+
+virusfilter_fsav_init_failed:
+ TALLOC_FREE(reply);
+ virusfilter_fsav_scan_end(config);
+
+ return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+
+ DBG_INFO("fsavd: Disconnecting\n");
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_fsav_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp)
+{
+ char *cwd_fname = fsp->conn->cwd_fname->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ struct virusfilter_io_handle *io_h = config->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+ char *report = NULL;
+ char *reply = NULL;
+ char *reply_token, *reply_saveptr;
+ bool ok;
+
+ DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+ ok = virusfilter_io_writevl(io_h, "SCAN\t", 5, cwd_fname,
+ (int)strlen(cwd_fname), "/", 1, fname,
+ (int)strlen(fname), NULL);
+ if (!ok) {
+ DBG_ERR("fsavd: SCAN: Write error: %s\n", strerror(errno));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_scan_return;
+ }
+
+ TALLOC_FREE(reply);
+
+ for (;;) {
+ if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+ DBG_ERR("fsavd: SCANFILE: Read error: %s\n",
+ strerror(errno));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner I/O error: %s\n",
+ strerror(errno));
+ break;
+ }
+
+ reply_token = strtok_r(reply, "\t", &reply_saveptr);
+
+ if (strcmp(reply_token, "OK") == 0) {
+ break;
+ } else if (strcmp(reply_token, "CLEAN") == 0) {
+
+ /* CLEAN\t<FILEPATH> */
+ result = VIRUSFILTER_RESULT_CLEAN;
+ report = talloc_asprintf(talloc_tos(), "Clean");
+ } else if (strcmp(reply_token, "INFECTED") == 0 ||
+ strcmp(reply_token, "ARCHIVE_INFECTED") == 0 ||
+ strcmp(reply_token, "MIME_INFECTED") == 0 ||
+ strcmp(reply_token, "RISKWARE") == 0 ||
+ strcmp(reply_token, "ARCHIVE_RISKWARE") == 0 ||
+ strcmp(reply_token, "MIME_RISKWARE") == 0)
+ {
+
+ /* INFECTED\t<FILEPATH>\t<REPORT>\t<ENGINE> */
+ result = VIRUSFILTER_RESULT_INFECTED;
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ if (reply_token != NULL) {
+ report = talloc_strdup(talloc_tos(),
+ reply_token);
+ } else {
+ report = talloc_asprintf(talloc_tos(),
+ "UNKNOWN INFECTION");
+ }
+ } else if (strcmp(reply_token, "OPEN_ARCHIVE") == 0) {
+
+ /* Ignore */
+ } else if (strcmp(reply_token, "CLOSE_ARCHIVE") == 0) {
+
+ /* Ignore */
+ } else if ((strcmp(reply_token, "SUSPECTED") == 0 ||
+ strcmp(reply_token, "ARCHIVE_SUSPECTED") == 0 ||
+ strcmp(reply_token, "MIME_SUSPECTED") == 0) &&
+ config->block_suspected_file)
+ {
+ result = VIRUSFILTER_RESULT_SUSPECTED;
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ if (reply_token != NULL) {
+ report = talloc_strdup(talloc_tos(),
+ reply_token);
+ } else {
+ report = talloc_asprintf(talloc_tos(),
+ "UNKNOWN REASON SUSPECTED");
+ }
+ } else if (strcmp(reply_token, "SCAN_FAILURE") == 0) {
+
+ /* SCAN_FAILURE\t<FILEPATH>\t0x<CODE>\t<REPORT> [<ENGINE>] */
+ result = VIRUSFILTER_RESULT_ERROR;
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ DBG_ERR("fsavd: SCANFILE: Scaner error: %s\n",
+ reply_token ? reply_token : "UNKNOWN ERROR");
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner error: %s",
+ reply_token ? reply_token :
+ "UNKNOWN ERROR");
+ } else {
+ result = VIRUSFILTER_RESULT_ERROR;
+ DBG_ERR("fsavd: SCANFILE: Invalid reply: %s\t",
+ reply_token);
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner communication error");
+ }
+
+ TALLOC_FREE(reply);
+ }
+
+virusfilter_fsav_scan_return:
+ TALLOC_FREE(reply);
+
+ if (report == NULL) {
+ *reportp = talloc_asprintf(talloc_tos(), "Scanner report memory "
+ "error");
+ } else {
+ *reportp = report;
+ }
+
+ return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_fsav ={
+ .connect = virusfilter_fsav_connect,
+ .disconnect = NULL,
+ .scan_init = virusfilter_fsav_scan_init,
+ .scan = virusfilter_fsav_scan,
+ .scan_end = virusfilter_fsav_scan_end,
+};
+
+int virusfilter_fsav_init(struct virusfilter_config *config)
+{
+ struct virusfilter_backend *backend = NULL;
+
+ if (config->socket_path == NULL) {
+ config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+ }
+
+ backend = talloc_zero(config, struct virusfilter_backend);
+ if (backend == NULL) {
+ return -1;
+ }
+
+ backend->fns = &virusfilter_backend_fsav;
+ backend->name = "fsav";
+
+ config->backend = backend;
+ return 0;
+}
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index cfab82f26b2..401bea9c5b1 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -499,6 +499,7 @@ bld.SAMBA3_MODULE('vfs_virusfilter',
source='''
vfs_virusfilter.c
vfs_virusfilter_sophos.c
+ vfs_virusfilter_fsav.c
''',
deps='samba-util VFS_VIRUSFILTER_UTILS',
init_function='',
--
2.14.3
From 664aa85e09e7a1566f1ebdd1d29de01ae1d13e90 Mon Sep 17 00:00:00 2001
From: "Trever L. Adams" <trever.adams at gmail.com>
Date: Tue, 18 Oct 2016 13:40:01 -0600
Subject: [PATCH 5/5] Samba-VirusFilter: clamav VFS and man page.
Signed-off-by: Trever L. Adams <trever.adams at gmail.com>
Signed-off-by: SATOH Fumiyasu <fumiyas at osstech.co.jp>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
docs-xml/manpages/vfs_virusfilter.8.xml | 4 +
source3/modules/vfs_virusfilter.c | 3 +
source3/modules/vfs_virusfilter_clamav.c | 195 +++++++++++++++++++++++++++++++
source3/modules/vfs_virusfilter_common.h | 1 +
source3/modules/wscript_build | 1 +
5 files changed, 204 insertions(+)
create mode 100644 source3/modules/vfs_virusfilter_clamav.c
diff --git a/docs-xml/manpages/vfs_virusfilter.8.xml b/docs-xml/manpages/vfs_virusfilter.8.xml
index 2e70ab0b553..ee49df11575 100644
--- a/docs-xml/manpages/vfs_virusfilter.8.xml
+++ b/docs-xml/manpages/vfs_virusfilter.8.xml
@@ -46,6 +46,8 @@
scanner</para></listitem>
<listitem><para><emphasis>fsav</emphasis>, the F-Secure AV
scanner</para></listitem>
+ <listitem><para><emphasis>clamav</emphasis>, the ClamAV
+ scanner</para></listitem>
</itemizedlist>
</listitem>
</varlistentry>
@@ -62,6 +64,8 @@
<emphasis>/var/run/savdi/sssp.sock</emphasis>.</para>
<para>For the <emphasis>fsav</emphasis> backend the default is
<emphasis>/tmp/.fsav-0</emphasis>.</para>
+ <para>For the <emphasis>fsav</emphasis> backend the default is
+ <emphasis>/var/run/clamav/clamd.ctl</emphasis>.</para>
</listitem>
</varlistentry>
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
index ceb78e45a05..20a208f359e 100644
--- a/source3/modules/vfs_virusfilter.c
+++ b/source3/modules/vfs_virusfilter.c
@@ -448,6 +448,9 @@ static int virusfilter_vfs_connect(
case VIRUSFILTER_SCANNER_FSAV:
ret = virusfilter_fsav_init(config);
break;
+ case VIRUSFILTER_SCANNER_CLAMAV:
+ ret = virusfilter_clamav_init(config);
+ break;
default:
DBG_ERR("Unhandled scanner %d\n", backend);
return -1;
diff --git a/source3/modules/vfs_virusfilter_clamav.c b/source3/modules/vfs_virusfilter_clamav.c
new file mode 100644
index 00000000000..d0e1fc081b7
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_clamav.c
@@ -0,0 +1,195 @@
+/*
+ Samba-VirusFilter VFS modules
+ ClamAV clamd support
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Default values for standard "extra" configuration variables */
+
+#ifdef CLAMAV_DEFAULT_SOCKET_PATH
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH CLAMAV_DEFAULT_SOCKET_PATH
+#else
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/var/run/clamav/clamd.ctl"
+#endif
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+static int virusfilter_clamav_connect(struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *svc,
+ const char *user)
+{
+
+ /* To use clamd "zXXXX" commands */
+ virusfilter_io_set_writel_eol(config->io_h, "\0", 1);
+ virusfilter_io_set_readl_eol(config->io_h, "\0", 1);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_clamav_scan_init(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+ bool ok;
+
+ DBG_INFO("clamd: Connecting to socket: %s\n",
+ config->socket_path);
+
+ become_root();
+ ok = virusfilter_io_connect_path(io_h, config->socket_path);
+ unbecome_root();
+
+ if (!ok) {
+ DBG_ERR("clamd: Connecting to socket failed: %s: %s\n",
+ config->socket_path, strerror(errno));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ DBG_INFO("clamd: Connected\n");
+
+ return VIRUSFILTER_RESULT_OK;
+}
+
+static void virusfilter_clamav_scan_end(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+
+ DBG_INFO("clamd: Disconnecting\n");
+
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_clamav_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp)
+{
+ char *cwd_fname = fsp->conn->cwd_fname->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ size_t filepath_len = strlen(cwd_fname) + 1 /* slash */ + strlen(fname);
+ struct virusfilter_io_handle *io_h = config->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+ char *report = NULL;
+ char *reply = NULL;
+ char *reply_msg = NULL;
+ char *reply_token;
+ bool ok;
+
+ DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "zSCAN %s/%s",
+ cwd_fname, fname);
+ if (!ok) {
+ DBG_ERR("clamd: zSCAN: I/O error: %s\n", strerror(errno));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_clamav_scan_return;
+ }
+
+ if (reply[filepath_len] != ':' ||
+ reply[filepath_len+1] != ' ')
+ {
+ DBG_ERR("clamd: zSCAN: Invalid reply: %s\n",
+ reply);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner communication error");
+ goto virusfilter_clamav_scan_return;
+ }
+ reply_msg = reply + filepath_len + 2;
+
+ reply_token = strrchr(reply, ' ');
+
+ if (reply_token == NULL) {
+ DBG_ERR("clamd: zSCAN: Invalid reply: %s\n",
+ reply);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner communication error");
+ goto virusfilter_clamav_scan_return;
+ }
+ *reply_token = '\0';
+ reply_token++;
+
+ if (strcmp(reply_token, "OK") == 0) {
+
+ /* <FILEPATH>: OK */
+ result = VIRUSFILTER_RESULT_CLEAN;
+ report = talloc_asprintf(talloc_tos(), "Clean");
+ } else if (strcmp(reply_token, "FOUND") == 0) {
+
+ /* <FILEPATH>: <REPORT> FOUND */
+ result = VIRUSFILTER_RESULT_INFECTED;
+ report = talloc_strdup(talloc_tos(), reply_msg);
+ } else if (strcmp(reply_token, "ERROR") == 0) {
+
+ /* <FILEPATH>: <REPORT> ERROR */
+ DBG_ERR("clamd: zSCAN: Error: %s\n", reply_msg);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner error: %s\t", reply_msg);
+ } else {
+ DBG_ERR("clamd: zSCAN: Invalid reply: %s\n", reply_token);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner communication error");
+ }
+
+virusfilter_clamav_scan_return:
+ TALLOC_FREE(reply);
+ if (report == NULL) {
+ *reportp = talloc_asprintf(talloc_tos(),
+ "Scanner report memory error");
+ } else {
+ *reportp = report;
+ }
+
+ return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_clamav = {
+ .connect = virusfilter_clamav_connect,
+ .disconnect = NULL,
+ .scan_init = virusfilter_clamav_scan_init,
+ .scan = virusfilter_clamav_scan,
+ .scan_end = virusfilter_clamav_scan_end,
+};
+
+int virusfilter_clamav_init(struct virusfilter_config *config)
+{
+ struct virusfilter_backend *backend = NULL;
+
+ if (config->socket_path == NULL) {
+ config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+ }
+
+ backend = talloc_zero(config, struct virusfilter_backend);
+ if (backend == NULL) {
+ return -1;
+ }
+
+ backend->fns = &virusfilter_backend_clamav;
+ backend->name = "clamav";
+
+ config->backend = backend;
+ return 0;
+}
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
index a28ce2978fa..f71b0b949a7 100644
--- a/source3/modules/vfs_virusfilter_common.h
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -148,5 +148,6 @@ struct virusfilter_backend {
int virusfilter_sophos_init(struct virusfilter_config *config);
int virusfilter_fsav_init(struct virusfilter_config *config);
+int virusfilter_clamav_init(struct virusfilter_config *config);
#endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index 401bea9c5b1..eeb9852e77f 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -500,6 +500,7 @@ bld.SAMBA3_MODULE('vfs_virusfilter',
vfs_virusfilter.c
vfs_virusfilter_sophos.c
vfs_virusfilter_fsav.c
+ vfs_virusfilter_clamav.c
''',
deps='samba-util VFS_VIRUSFILTER_UTILS',
init_function='',
--
2.14.3
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 886 bytes
Desc: OpenPGP digital signature
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20180123/15c224a2/signature.sig>
More information about the samba-technical
mailing list