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